diff --git a/.editorconfig b/.editorconfig
index 128b6f1712..9014c8938f 100644
--- a/.editorconfig
+++ b/.editorconfig
@@ -134,6 +134,16 @@ 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.
+
+# CA1802: Use literals where appropriate
+dotnet_diagnostic.CA1802.severity = warning
+# CA1825: Avoid zero-length array allocations
+dotnet_diagnostic.CA1825.severity = warning
+# CA1821: Remove empty finalizers
+dotnet_diagnostic.CA1821.severity = warning
+
# 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/NOTICE.md b/NOTICE.md
index 92fd725957..e97fc654c9 100644
--- a/NOTICE.md
+++ b/NOTICE.md
@@ -111,7 +111,7 @@ DEALINGS IN THE SOFTWARE.
# Metsys.Bson
-Copyright (c) 2010, Karl Seguin - http://www.openmymind.net/
+Copyright (c) 2010, Karl Seguin - https://www.openmymind.net/
All rights reserved.
Redistribution and use in source and binary forms, with or without
@@ -302,4 +302,4 @@ https://github.com/chromium/chromium
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
-// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
\ No newline at end of file
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
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/dirs.proj b/dirs.proj
index a2544ef951..f1eaae8a4a 100644
--- a/dirs.proj
+++ b/dirs.proj
@@ -9,21 +9,18 @@
-
-
-
-
-
-
-
+
+
+
+
-
+
diff --git a/samples/ControlCatalog.Android/MainActivity.cs b/samples/ControlCatalog.Android/MainActivity.cs
index 33ca511340..62c582610c 100644
--- a/samples/ControlCatalog.Android/MainActivity.cs
+++ b/samples/ControlCatalog.Android/MainActivity.cs
@@ -5,16 +5,8 @@ using Avalonia.Android;
namespace ControlCatalog.Android
{
- [Activity(Label = "ControlCatalog.Android", Theme = "@style/MyTheme.NoActionBar", Icon = "@drawable/icon", LaunchMode = LaunchMode.SingleInstance, ConfigurationChanges = ConfigChanges.Orientation | ConfigChanges.ScreenSize)]
- public class MainActivity : AvaloniaActivity
+ [Activity(Label = "ControlCatalog.Android", Theme = "@style/MyTheme.NoActionBar", Icon = "@drawable/icon", LaunchMode = LaunchMode.SingleTop, ConfigurationChanges = ConfigChanges.Orientation | ConfigChanges.ScreenSize)]
+ public class MainActivity : AvaloniaMainActivity
{
- protected override AppBuilder CustomizeAppBuilder(AppBuilder builder)
- {
- return base.CustomizeAppBuilder(builder)
- .AfterSetup(_ =>
- {
- Pages.EmbedSample.Implementation = new EmbedSampleAndroid();
- });
- }
}
}
diff --git a/samples/ControlCatalog.Android/SplashActivity.cs b/samples/ControlCatalog.Android/SplashActivity.cs
index dc292fd37b..908b5f082a 100644
--- a/samples/ControlCatalog.Android/SplashActivity.cs
+++ b/samples/ControlCatalog.Android/SplashActivity.cs
@@ -1,12 +1,23 @@
using Android.App;
using Android.Content;
+using Android.Content.PM;
using Android.OS;
+using Avalonia.Android;
namespace ControlCatalog.Android
{
[Activity(Theme = "@style/MyTheme.Splash", MainLauncher = true, NoHistory = true)]
- public class SplashActivity : Activity
+ public class SplashActivity : AvaloniaSplashActivity
{
+ protected override Avalonia.AppBuilder CustomizeAppBuilder(Avalonia.AppBuilder builder)
+ {
+ return base.CustomizeAppBuilder(builder)
+ .AfterSetup(_ =>
+ {
+ Pages.EmbedSample.Implementation = new EmbedSampleAndroid();
+ });
+ }
+
protected override void OnCreate(Bundle? savedInstanceState)
{
base.OnCreate(savedInstanceState);
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/samples/ControlCatalog/Pages/DateTimePickerPage.xaml b/samples/ControlCatalog/Pages/DateTimePickerPage.xaml
index 29cd939520..6217d39b21 100644
--- a/samples/ControlCatalog/Pages/DateTimePickerPage.xaml
+++ b/samples/ControlCatalog/Pages/DateTimePickerPage.xaml
@@ -13,17 +13,17 @@
Margin="16"
HorizontalAlignment="Stretch"
Spacing="16">
- A simple DatePicker with a header
+ A simple DatePicker
-
+
- <DatePicker Header="Pick a date" />
+ <DatePicker/>
@@ -33,7 +33,7 @@
-
+
@@ -79,24 +79,24 @@
-
+
- A TimePicker with a header and minute increments specified.
+ A TimePicker with minute increments specified.
-
+
- <TimePicker Header="Arrival time" MinuteIncrement="15" />
+ <TimePicker MinuteIncrement="15" />
@@ -107,13 +107,13 @@
-
+
- <TimePicker ClockIdentifier="12HourClock" Header="12 hour clock" />
+ <TimePicker ClockIdentifier="12HourClock" />
@@ -124,13 +124,13 @@
-
+
- <TimePicker ClockIdentifier="24HourClock" Header="24 hour clock" />
+ <TimePicker ClockIdentifier="24HourClock" />
diff --git a/samples/MobileSandbox.Android/MainActivity.cs b/samples/MobileSandbox.Android/MainActivity.cs
index ac9242dd52..d65f0dec92 100644
--- a/samples/MobileSandbox.Android/MainActivity.cs
+++ b/samples/MobileSandbox.Android/MainActivity.cs
@@ -5,8 +5,8 @@ using Avalonia.Android;
namespace MobileSandbox.Android
{
- [Activity(Label = "MobileSandbox.Android", Theme = "@style/MyTheme.NoActionBar", Icon = "@drawable/icon", LaunchMode = LaunchMode.SingleInstance, ConfigurationChanges = ConfigChanges.Orientation | ConfigChanges.ScreenSize)]
- public class MainActivity : AvaloniaActivity
+ [Activity(Label = "MobileSandbox.Android", Theme = "@style/MyTheme.NoActionBar", Icon = "@drawable/icon", LaunchMode = LaunchMode.SingleTop, ConfigurationChanges = ConfigChanges.Orientation | ConfigChanges.ScreenSize)]
+ public class MainActivity : AvaloniaMainActivity
{
}
}
diff --git a/samples/MobileSandbox.Android/SplashActivity.cs b/samples/MobileSandbox.Android/SplashActivity.cs
index c26371d6fe..ced092554d 100644
--- a/samples/MobileSandbox.Android/SplashActivity.cs
+++ b/samples/MobileSandbox.Android/SplashActivity.cs
@@ -1,11 +1,11 @@
using Android.App;
using Android.Content;
-using Android.OS;
+using Avalonia.Android;
namespace MobileSandbox.Android
{
[Activity(Theme = "@style/MyTheme.Splash", MainLauncher = true, NoHistory = true)]
- public class SplashActivity : Activity
+ public class SplashActivity : AvaloniaSplashActivity
{
protected override void OnResume()
{
diff --git a/samples/RenderDemo/Pages/GlyphRunPage.xaml.cs b/samples/RenderDemo/Pages/GlyphRunPage.xaml.cs
index 674ed8e61f..5c31e138e0 100644
--- a/samples/RenderDemo/Pages/GlyphRunPage.xaml.cs
+++ b/samples/RenderDemo/Pages/GlyphRunPage.xaml.cs
@@ -22,7 +22,7 @@ namespace RenderDemo.Pages
public class GlyphRunControl : Control
{
- private GlyphTypeface _glyphTypeface = Typeface.Default.GlyphTypeface;
+ private IGlyphTypeface _glyphTypeface = Typeface.Default.GlyphTypeface;
private readonly Random _rand = new Random();
private ushort[] _glyphIndices = new ushort[1];
private char[] _characters = new char[1];
@@ -81,7 +81,7 @@ namespace RenderDemo.Pages
public class GlyphRunGeometryControl : Control
{
- private GlyphTypeface _glyphTypeface = Typeface.Default.GlyphTypeface;
+ private IGlyphTypeface _glyphTypeface = Typeface.Default.GlyphTypeface;
private readonly Random _rand = new Random();
private ushort[] _glyphIndices = new ushort[1];
private char[] _characters = new char[1];
diff --git a/src/Android/Avalonia.Android/AvaloniaActivity.cs b/src/Android/Avalonia.Android/AvaloniaActivity.cs
deleted file mode 100644
index 4ee4bc1375..0000000000
--- a/src/Android/Avalonia.Android/AvaloniaActivity.cs
+++ /dev/null
@@ -1,105 +0,0 @@
-using Android.OS;
-using AndroidX.AppCompat.App;
-using Android.Content.Res;
-using AndroidX.Lifecycle;
-using Avalonia.Controls.ApplicationLifetimes;
-using Avalonia.Controls;
-using Android.Runtime;
-using Android.App;
-using Android.Content;
-using System;
-
-namespace Avalonia.Android
-{
- public abstract class AvaloniaActivity : AppCompatActivity
- {
- internal class SingleViewLifetime : ISingleViewApplicationLifetime
- {
- public AvaloniaView View { get; internal set; }
-
- public Control MainView
- {
- get => (Control)View.Content;
- set => View.Content = value;
- }
- }
-
- internal Action ActivityResult;
- internal AvaloniaView View;
- internal AvaloniaViewModel _viewModel;
-
- protected abstract AppBuilder CreateAppBuilder();
-
- protected override void OnCreate(Bundle savedInstanceState)
- {
- var builder = CreateAppBuilder();
-
-
- var lifetime = new SingleViewLifetime();
-
- builder.AfterSetup(x =>
- {
- _viewModel = new ViewModelProvider(this).Get(Java.Lang.Class.FromType(typeof(AvaloniaViewModel))) as AvaloniaViewModel;
-
- View = new AvaloniaView(this);
- if (_viewModel.Content != null)
- {
- View.Content = _viewModel.Content;
- }
-
- SetContentView(View);
- lifetime.View = View;
-
- View.Prepare();
- });
-
- builder.SetupWithLifetime(lifetime);
-
- base.OnCreate(savedInstanceState);
- }
- public object Content
- {
- get
- {
- return _viewModel.Content;
- }
- set
- {
- _viewModel.Content = value;
- if (View != null)
- View.Content = value;
- }
- }
-
- public override void OnConfigurationChanged(Configuration newConfig)
- {
- base.OnConfigurationChanged(newConfig);
- }
-
- protected override void OnDestroy()
- {
- View.Content = null;
-
- base.OnDestroy();
- }
-
- protected override void OnActivityResult(int requestCode, [GeneratedEnum] Result resultCode, Intent data)
- {
- base.OnActivityResult(requestCode, resultCode, data);
-
- ActivityResult?.Invoke(requestCode, resultCode, data);
- }
- }
-
- public abstract class AvaloniaActivity : AvaloniaActivity where TApp : Application, new()
- {
- protected virtual AppBuilder CustomizeAppBuilder(AppBuilder builder) => builder.UseAndroid();
-
- protected override AppBuilder CreateAppBuilder()
- {
- var builder = AppBuilder.Configure();
-
- return CustomizeAppBuilder(builder);
- }
- }
-}
diff --git a/src/Android/Avalonia.Android/AvaloniaMainActivity.cs b/src/Android/Avalonia.Android/AvaloniaMainActivity.cs
new file mode 100644
index 0000000000..de8d02f188
--- /dev/null
+++ b/src/Android/Avalonia.Android/AvaloniaMainActivity.cs
@@ -0,0 +1,70 @@
+using System;
+using Android.App;
+using Android.Content;
+using Android.Content.Res;
+using Android.OS;
+using Android.Runtime;
+using AndroidX.AppCompat.App;
+using AndroidX.Lifecycle;
+
+namespace Avalonia.Android
+{
+ public abstract class AvaloniaMainActivity : AppCompatActivity, IActivityResultHandler
+ {
+ internal static object ViewContent;
+
+ public Action ActivityResult { get; set; }
+ internal AvaloniaView View;
+
+ protected override void OnCreate(Bundle savedInstanceState)
+ {
+ View = new AvaloniaView(this);
+ if (ViewContent != null)
+ {
+ View.Content = ViewContent;
+ }
+
+ if (Avalonia.Application.Current.ApplicationLifetime is SingleViewLifetime lifetime)
+ {
+ lifetime.View = View;
+ }
+
+ base.OnCreate(savedInstanceState);
+
+ SetContentView(View);
+ }
+
+ public object Content
+ {
+ get
+ {
+ return ViewContent;
+ }
+ set
+ {
+ ViewContent = value;
+ if (View != null)
+ View.Content = value;
+ }
+ }
+
+ public override void OnConfigurationChanged(Configuration newConfig)
+ {
+ base.OnConfigurationChanged(newConfig);
+ }
+
+ protected override void OnDestroy()
+ {
+ View.Content = null;
+
+ base.OnDestroy();
+ }
+
+ protected override void OnActivityResult(int requestCode, [GeneratedEnum] Result resultCode, Intent data)
+ {
+ base.OnActivityResult(requestCode, resultCode, data);
+
+ ActivityResult?.Invoke(requestCode, resultCode, data);
+ }
+ }
+}
diff --git a/src/Android/Avalonia.Android/AvaloniaSplashActivity.cs b/src/Android/Avalonia.Android/AvaloniaSplashActivity.cs
new file mode 100644
index 0000000000..5b5ebd1bd9
--- /dev/null
+++ b/src/Android/Avalonia.Android/AvaloniaSplashActivity.cs
@@ -0,0 +1,34 @@
+using Android.OS;
+using AndroidX.AppCompat.App;
+using AndroidX.Lifecycle;
+
+namespace Avalonia.Android
+{
+ public abstract class AvaloniaSplashActivity : AppCompatActivity
+ {
+ protected abstract AppBuilder CreateAppBuilder();
+
+ protected override void OnCreate(Bundle? savedInstanceState)
+ {
+ base.OnCreate(savedInstanceState);
+
+ var builder = CreateAppBuilder();
+
+ var lifetime = new SingleViewLifetime();
+
+ builder.SetupWithLifetime(lifetime);
+ }
+ }
+
+ public abstract class AvaloniaSplashActivity : AvaloniaSplashActivity where TApp : Application, new()
+ {
+ protected virtual AppBuilder CustomizeAppBuilder(AppBuilder builder) => builder.UseAndroid();
+
+ protected override AppBuilder CreateAppBuilder()
+ {
+ var builder = AppBuilder.Configure();
+
+ return CustomizeAppBuilder(builder);
+ }
+ }
+}
diff --git a/src/Android/Avalonia.Android/AvaloniaView.cs b/src/Android/Avalonia.Android/AvaloniaView.cs
index 94e863210b..5267843bfc 100644
--- a/src/Android/Avalonia.Android/AvaloniaView.cs
+++ b/src/Android/Avalonia.Android/AvaloniaView.cs
@@ -21,10 +21,7 @@ namespace Avalonia.Android
{
_view = new ViewImpl(this);
AddView(_view.View);
- }
- internal void Prepare ()
- {
_root = new EmbeddableControlRoot(_view);
_root.Prepare();
}
diff --git a/src/Android/Avalonia.Android/AvaloniaViewModel.cs b/src/Android/Avalonia.Android/AvaloniaViewModel.cs
deleted file mode 100644
index 1b2c00987a..0000000000
--- a/src/Android/Avalonia.Android/AvaloniaViewModel.cs
+++ /dev/null
@@ -1,11 +0,0 @@
-using System;
-using System.Collections.Generic;
-using System.Text;
-
-namespace Avalonia.Android
-{
- internal class AvaloniaViewModel : AndroidX.Lifecycle.ViewModel
- {
- public object Content { get; set; }
- }
-}
diff --git a/src/Android/Avalonia.Android/IActivityResultHandler.cs b/src/Android/Avalonia.Android/IActivityResultHandler.cs
new file mode 100644
index 0000000000..14094ee185
--- /dev/null
+++ b/src/Android/Avalonia.Android/IActivityResultHandler.cs
@@ -0,0 +1,11 @@
+using System;
+using Android.App;
+using Android.Content;
+
+namespace Avalonia.Android
+{
+ public interface IActivityResultHandler
+ {
+ public Action ActivityResult { get; set; }
+ }
+}
diff --git a/src/Android/Avalonia.Android/OpenGL/GlPlatformSurface.cs b/src/Android/Avalonia.Android/OpenGL/GlPlatformSurface.cs
index a9710039f8..e85ed11028 100644
--- a/src/Android/Avalonia.Android/OpenGL/GlPlatformSurface.cs
+++ b/src/Android/Avalonia.Android/OpenGL/GlPlatformSurface.cs
@@ -1,4 +1,5 @@
-using Avalonia.OpenGL.Egl;
+using Avalonia.OpenGL;
+using Avalonia.OpenGL.Egl;
using Avalonia.OpenGL.Surfaces;
namespace Avalonia.Android.OpenGL
@@ -19,7 +20,8 @@ namespace Avalonia.Android.OpenGL
public static GlPlatformSurface TryCreate(IEglWindowGlPlatformSurfaceInfo info)
{
- if (EglPlatformOpenGlInterface.TryCreate() is EglPlatformOpenGlInterface egl)
+ var feature = AvaloniaLocator.Current.GetService();
+ if (feature is EglPlatformOpenGlInterface egl)
{
return new GlPlatformSurface(egl, info);
}
diff --git a/src/Android/Avalonia.Android/Platform/SkiaPlatform/TopLevelImpl.cs b/src/Android/Avalonia.Android/Platform/SkiaPlatform/TopLevelImpl.cs
index f8eaeba897..984eb775b5 100644
--- a/src/Android/Avalonia.Android/Platform/SkiaPlatform/TopLevelImpl.cs
+++ b/src/Android/Avalonia.Android/Platform/SkiaPlatform/TopLevelImpl.cs
@@ -1,22 +1,16 @@
using System;
using System.Collections.Generic;
-
+using Android.App;
using Android.Content;
using Android.Graphics;
-using Android.Media.TV;
-using Android.OS;
using Android.Runtime;
-using Android.Text;
using Android.Views;
using Android.Views.InputMethods;
-using Android.Widget;
using Avalonia.Android.OpenGL;
-using Avalonia.Android.Platform.Input;
using Avalonia.Android.Platform.Specific;
using Avalonia.Android.Platform.Specific.Helpers;
using Avalonia.Android.Platform.Storage;
using Avalonia.Controls;
-using Avalonia.Controls.Documents;
using Avalonia.Controls.Platform;
using Avalonia.Controls.Platform.Surfaces;
using Avalonia.Input;
@@ -29,7 +23,6 @@ using Avalonia.Platform.Storage;
using Avalonia.Rendering;
using Avalonia.Rendering.Composition;
using Java.Lang;
-using static System.Net.Mime.MediaTypeNames;
namespace Avalonia.Android.Platform.SkiaPlatform
{
@@ -59,7 +52,7 @@ namespace Avalonia.Android.Platform.SkiaPlatform
_view.Resources.DisplayMetrics.HeightPixels).ToSize(RenderScaling);
NativeControlHost = new AndroidNativeControlHostImpl(avaloniaView);
- StorageProvider = new AndroidStorageProvider((AvaloniaActivity)avaloniaView.Context);
+ StorageProvider = new AndroidStorageProvider((Activity)avaloniaView.Context);
}
public virtual Point GetAvaloniaPointFromEvent(MotionEvent e, int pointerIndex) =>
@@ -301,7 +294,7 @@ namespace Avalonia.Android.Platform.SkiaPlatform
IsComposing = true;
- _inputMethod.Client.SetPreeditText(ComposingText);
+ _inputMethod.Client?.SetPreeditText(ComposingText);
return base.SetComposingText(text, newCursorPosition);
}
diff --git a/src/Android/Avalonia.Android/Platform/Specific/Helpers/AndroidMotionEventsHelper.cs b/src/Android/Avalonia.Android/Platform/Specific/Helpers/AndroidMotionEventsHelper.cs
index ce385ebe34..6d0e6be0ad 100644
--- a/src/Android/Avalonia.Android/Platform/Specific/Helpers/AndroidMotionEventsHelper.cs
+++ b/src/Android/Avalonia.Android/Platform/Specific/Helpers/AndroidMotionEventsHelper.cs
@@ -15,7 +15,7 @@ namespace Avalonia.Android.Platform.Specific.Helpers
internal class AndroidMotionEventsHelper : IDisposable
{
private static readonly PooledList s_intermediatePointsPooledList = new(ClearMode.Never);
- private static readonly float s_radiansToDegree = (float)(180f * Math.PI);
+ private const float RadiansToDegree = (float)(180f * Math.PI);
private readonly TouchDevice _touchDevice;
private readonly MouseDevice _mouseDevice;
private readonly PenDevice _penDevice;
@@ -223,7 +223,7 @@ namespace Avalonia.Android.Platform.Specific.Helpers
{
Position = new Point(e.GetX(index), e.GetY(index)) / _view.RenderScaling,
Pressure = Math.Min(e.GetPressure(index), 1), // android pressure can depend on the device, can be mixed up with "GetSize", may be larger than 1.0f on some devices
- Twist = e.GetOrientation(index) * s_radiansToDegree
+ Twist = e.GetOrientation(index) * RadiansToDegree
};
}
@@ -233,7 +233,7 @@ namespace Avalonia.Android.Platform.Specific.Helpers
{
Position = new Point(e.GetHistoricalX(index, pos), e.GetHistoricalY(index, pos)) / _view.RenderScaling,
Pressure = Math.Min(e.GetHistoricalPressure(index, pos), 1),
- Twist = e.GetHistoricalOrientation(index, pos) * s_radiansToDegree
+ Twist = e.GetHistoricalOrientation(index, pos) * RadiansToDegree
};
}
diff --git a/src/Android/Avalonia.Android/Platform/Storage/AndroidStorageProvider.cs b/src/Android/Avalonia.Android/Platform/Storage/AndroidStorageProvider.cs
index 653f450ec8..62e43ff2ef 100644
--- a/src/Android/Avalonia.Android/Platform/Storage/AndroidStorageProvider.cs
+++ b/src/Android/Avalonia.Android/Platform/Storage/AndroidStorageProvider.cs
@@ -14,10 +14,10 @@ namespace Avalonia.Android.Platform.Storage;
internal class AndroidStorageProvider : IStorageProvider
{
- private readonly AvaloniaActivity _activity;
+ private readonly Activity _activity;
private int _lastRequestCode = 20000;
- public AndroidStorageProvider(AvaloniaActivity activity)
+ public AndroidStorageProvider(Activity activity)
{
_activity = activity;
}
@@ -119,7 +119,10 @@ internal class AndroidStorageProvider : IStorageProvider
var tcs = new TaskCompletionSource();
var currentRequestCode = _lastRequestCode++;
- _activity.ActivityResult += OnActivityResult;
+ if (_activity is IActivityResultHandler mainActivity)
+ {
+ mainActivity.ActivityResult += OnActivityResult;
+ }
_activity.StartActivityForResult(pickerIntent, currentRequestCode);
var result = await tcs.Task;
@@ -158,7 +161,11 @@ internal class AndroidStorageProvider : IStorageProvider
return;
}
- _activity.ActivityResult -= OnActivityResult;
+
+ if (_activity is IActivityResultHandler mainActivity)
+ {
+ mainActivity.ActivityResult -= OnActivityResult;
+ }
_ = tcs.TrySetResult(resultCode == Result.Ok ? data : null);
}
diff --git a/src/Android/Avalonia.Android/SingleViewLifetime.cs b/src/Android/Avalonia.Android/SingleViewLifetime.cs
new file mode 100644
index 0000000000..eef763a932
--- /dev/null
+++ b/src/Android/Avalonia.Android/SingleViewLifetime.cs
@@ -0,0 +1,26 @@
+using Avalonia.Controls;
+using Avalonia.Controls.ApplicationLifetimes;
+
+namespace Avalonia.Android
+{
+ internal class SingleViewLifetime : ISingleViewApplicationLifetime
+ {
+ private AvaloniaView _view;
+
+ public AvaloniaView View
+ {
+ get => _view; internal set
+ {
+ if (_view != null)
+ {
+ _view.Content = null;
+ _view.Dispose();
+ }
+ _view = value;
+ _view.Content = MainView;
+ }
+ }
+
+ public Control MainView { get; set; }
+ }
+}
diff --git a/src/Avalonia.Base/Avalonia.Base.csproj b/src/Avalonia.Base/Avalonia.Base.csproj
index 1cb29e4e37..402bc3a099 100644
--- a/src/Avalonia.Base/Avalonia.Base.csproj
+++ b/src/Avalonia.Base/Avalonia.Base.csproj
@@ -21,6 +21,9 @@
+
+
+
@@ -38,7 +41,7 @@
-
+
diff --git a/src/Avalonia.Base/Input/Cursor.cs b/src/Avalonia.Base/Input/Cursor.cs
index 98c4258a90..8e79206f93 100644
--- a/src/Avalonia.Base/Input/Cursor.cs
+++ b/src/Avalonia.Base/Input/Cursor.cs
@@ -33,7 +33,7 @@ namespace Avalonia.Input
DragLink,
None,
- // Not available in GTK directly, see http://www.pixelbeat.org/programming/x_cursors/
+ // Not available in GTK directly, see https://www.pixelbeat.org/programming/x_cursors/
// We might enable them later, preferably, by loading pixmax directly from theme with fallback image
// SizeNorthWestSouthEast,
// SizeNorthEastSouthWest,
diff --git a/src/Avalonia.Base/Media/FontManager.cs b/src/Avalonia.Base/Media/FontManager.cs
index 37091b82e3..d92d003c2a 100644
--- a/src/Avalonia.Base/Media/FontManager.cs
+++ b/src/Avalonia.Base/Media/FontManager.cs
@@ -13,8 +13,8 @@ namespace Avalonia.Media
///
public sealed class FontManager
{
- private readonly ConcurrentDictionary _glyphTypefaceCache =
- new ConcurrentDictionary();
+ private readonly ConcurrentDictionary _glyphTypefaceCache =
+ new ConcurrentDictionary();
private readonly FontFamily _defaultFontFamily;
private readonly IReadOnlyList? _fontFallbacks;
@@ -81,13 +81,13 @@ namespace Avalonia.Media
PlatformImpl.GetInstalledFontFamilyNames(checkForUpdates);
///
- /// Returns a new , or an existing one if a matching exists.
+ /// Returns a new , or an existing one if a matching exists.
///
/// The typeface.
///
- /// The .
+ /// The .
///
- public GlyphTypeface GetOrAddGlyphTypeface(Typeface typeface)
+ public IGlyphTypeface GetOrAddGlyphTypeface(Typeface typeface)
{
while (true)
{
@@ -96,7 +96,7 @@ namespace Avalonia.Media
return glyphTypeface;
}
- glyphTypeface = new GlyphTypeface(typeface);
+ glyphTypeface = PlatformImpl.CreateGlyphTypeface(typeface);
if (_glyphTypefaceCache.TryAdd(typeface, glyphTypeface))
{
diff --git a/src/Avalonia.Base/Media/FontMetrics.cs b/src/Avalonia.Base/Media/FontMetrics.cs
new file mode 100644
index 0000000000..1cd01675db
--- /dev/null
+++ b/src/Avalonia.Base/Media/FontMetrics.cs
@@ -0,0 +1,58 @@
+namespace Avalonia.Media
+{
+ ///
+ /// The font metrics is holding information about a font's ascent, descent, etc. in design em units.
+ ///
+ public readonly struct FontMetrics
+ {
+ ///
+ /// Gets the font design units per em.
+ ///
+ public short DesignEmHeight { get; init; }
+
+ ///
+ /// A value indicating whether all glyphs in the font have the same advancement.
+ ///
+ public bool IsFixedPitch { get; init; }
+
+ ///
+ /// Gets the recommended distance above the baseline in design em size.
+ ///
+ public int Ascent { get; init; }
+
+ ///
+ /// Gets the recommended distance under the baseline in design em size.
+ ///
+ public int Descent { get; init; }
+
+ ///
+ /// Gets the recommended additional space between two lines of text in design em size.
+ ///
+ public int LineGap { get; init; }
+
+ ///
+ /// Gets the recommended line spacing of a formed text line.
+ ///
+ public int LineSpacing => Descent - Ascent + LineGap;
+
+ ///
+ /// Gets a value that indicates the distance of the underline from the baseline in design em size.
+ ///
+ public int UnderlinePosition { get; init; }
+
+ ///
+ /// Gets a value that indicates the thickness of the underline in design em size.
+ ///
+ public int UnderlineThickness { get; init; }
+
+ ///
+ /// Gets a value that indicates the distance of the strikethrough from the baseline in design em size.
+ ///
+ public int StrikethroughPosition { get; init; }
+
+ ///
+ /// Gets a value that indicates the thickness of the underline in design em size.
+ ///
+ public int StrikethroughThickness { get; init; }
+ }
+}
diff --git a/src/Avalonia.Base/Media/GlyphRun.cs b/src/Avalonia.Base/Media/GlyphRun.cs
index 2289f98228..a1cb00e209 100644
--- a/src/Avalonia.Base/Media/GlyphRun.cs
+++ b/src/Avalonia.Base/Media/GlyphRun.cs
@@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
+using System.Drawing;
using Avalonia.Media.TextFormatting.Unicode;
using Avalonia.Platform;
using Avalonia.Utilities;
@@ -15,7 +16,7 @@ namespace Avalonia.Media
private static readonly IComparer s_descendingComparer = new ReverseComparer();
private IGlyphRunImpl? _glyphRunImpl;
- private GlyphTypeface _glyphTypeface;
+ private IGlyphTypeface _glyphTypeface;
private double _fontRenderingEmSize;
private int _biDiLevel;
private Point? _baselineOrigin;
@@ -42,7 +43,7 @@ namespace Avalonia.Media
/// The glyph clusters.
/// The bidi level.
public GlyphRun(
- GlyphTypeface glyphTypeface,
+ IGlyphTypeface glyphTypeface,
double fontRenderingEmSize,
ReadOnlySlice characters,
IReadOnlyList glyphIndices,
@@ -69,9 +70,9 @@ namespace Avalonia.Media
}
///
- /// Gets the for the .
+ /// Gets the for the .
///
- public GlyphTypeface GlyphTypeface => _glyphTypeface;
+ public IGlyphTypeface GlyphTypeface => _glyphTypeface;
///
/// Gets or sets the em size used for rendering the .
@@ -171,7 +172,7 @@ namespace Avalonia.Media
///
/// Gets the scale of the current
///
- internal double Scale => FontRenderingEmSize / GlyphTypeface.DesignEmHeight;
+ internal double Scale => FontRenderingEmSize / GlyphTypeface.Metrics.DesignEmHeight;
///
/// Returns true if the text direction is left-to-right. Otherwise, returns false.
@@ -612,7 +613,7 @@ namespace Avalonia.Media
/// The baseline origin.
private Point CalculateBaselineOrigin()
{
- return new Point(0, -GlyphTypeface.Ascent * Scale);
+ return new Point(0, -GlyphTypeface.Metrics.Ascent * Scale);
}
private GlyphRunMetrics CreateGlyphRunMetrics()
@@ -636,7 +637,7 @@ namespace Avalonia.Media
}
var isReversed = firstCluster > lastCluster;
- var height = (GlyphTypeface.Descent - GlyphTypeface.Ascent + GlyphTypeface.LineGap) * Scale;
+ var height = GlyphTypeface.Metrics.LineSpacing * Scale;
var widthIncludingTrailingWhitespace = 0d;
var trailingWhitespaceLength = GetTrailingWhitespaceLength(isReversed, out var newLineLength, out var glyphCount);
@@ -854,9 +855,87 @@ namespace Avalonia.Media
throw new InvalidOperationException();
}
+ _glyphRunImpl = CreateGlyphRunImpl();
+ }
+
+ private IGlyphRunImpl CreateGlyphRunImpl()
+ {
+ IGlyphRunImpl glyphRunImpl;
+
var platformRenderInterface = AvaloniaLocator.Current.GetRequiredService();
+ var count = GlyphIndices.Count;
+ var scale = (float)(FontRenderingEmSize / GlyphTypeface.Metrics.DesignEmHeight);
+
+ if (GlyphOffsets == null)
+ {
+ if (GlyphTypeface.Metrics.IsFixedPitch)
+ {
+ var buffer = platformRenderInterface.AllocateGlyphRun(GlyphTypeface, (float)FontRenderingEmSize, count);
+
+ var glyphs = buffer.GlyphIndices;
+
+ for (int i = 0; i < glyphs.Length; i++)
+ {
+ glyphs[i] = GlyphIndices[i];
+ }
+
+ glyphRunImpl = buffer.Build();
+ }
+ else
+ {
+ var buffer = platformRenderInterface.AllocateHorizontalGlyphRun(GlyphTypeface, (float)FontRenderingEmSize, count);
+ var glyphs = buffer.GlyphIndices;
+ var positions = buffer.GlyphPositions;
+ var width = 0d;
+
+ for (var i = 0; i < count; i++)
+ {
+ positions[i] = (float)width;
+
+ if (GlyphAdvances == null)
+ {
+ width += GlyphTypeface.GetGlyphAdvance(GlyphIndices[i]) * scale;
+ }
+ else
+ {
+ width += GlyphAdvances[i];
+ }
+
+ glyphs[i] = GlyphIndices[i];
+ }
+
+ glyphRunImpl = buffer.Build();
+ }
+ }
+ else
+ {
+ var buffer = platformRenderInterface.AllocatePositionedGlyphRun(GlyphTypeface, (float)FontRenderingEmSize, count);
+ var glyphs = buffer.GlyphIndices;
+ var glyphPositions = buffer.GlyphPositions;
+ var currentX = 0.0;
+
+ for (var i = 0; i < count; i++)
+ {
+ var glyphOffset = GlyphOffsets[i];
+
+ glyphPositions[i] = new PointF((float)(currentX + glyphOffset.X), (float)glyphOffset.Y);
+
+ if (GlyphAdvances == null)
+ {
+ currentX += GlyphTypeface.GetGlyphAdvance(GlyphIndices[i]) * scale;
+ }
+ else
+ {
+ currentX += GlyphAdvances[i];
+ }
+
+ glyphs[i] = GlyphIndices[i];
+ }
+
+ glyphRunImpl = buffer.Build();
+ }
- _glyphRunImpl = platformRenderInterface.CreateGlyphRun(this);
+ return glyphRunImpl;
}
void IDisposable.Dispose()
diff --git a/src/Avalonia.Base/Media/GlyphTypeface.cs b/src/Avalonia.Base/Media/GlyphTypeface.cs
deleted file mode 100644
index 45ef04e77f..0000000000
--- a/src/Avalonia.Base/Media/GlyphTypeface.cs
+++ /dev/null
@@ -1,125 +0,0 @@
-using System;
-using Avalonia.Platform;
-
-namespace Avalonia.Media
-{
- public sealed class GlyphTypeface : IDisposable
- {
- public GlyphTypeface(Typeface typeface)
- : this(FontManager.Current.PlatformImpl.CreateGlyphTypeface(typeface))
- {
- }
-
- public GlyphTypeface(IGlyphTypefaceImpl platformImpl)
- {
- PlatformImpl = platformImpl;
- }
-
- public IGlyphTypefaceImpl PlatformImpl { get; }
-
- ///
- /// Gets the font design units per em.
- ///
- public short DesignEmHeight => PlatformImpl.DesignEmHeight;
-
- ///
- /// Gets the recommended distance above the baseline in design em size.
- ///
- public int Ascent => PlatformImpl.Ascent;
-
- ///
- /// Gets the recommended distance under the baseline in design em size.
- ///
- public int Descent => PlatformImpl.Descent;
-
- ///
- /// Gets the recommended additional space between two lines of text in design em size.
- ///
- public int LineGap => PlatformImpl.LineGap;
-
- ///
- /// Gets the recommended line height.
- ///
- public int LineHeight => Descent - Ascent + LineGap;
-
- ///
- /// Gets a value that indicates the distance of the underline from the baseline in design em size.
- ///
- public int UnderlinePosition => PlatformImpl.UnderlinePosition;
-
- ///
- /// Gets a value that indicates the thickness of the underline in design em size.
- ///
- public int UnderlineThickness => PlatformImpl.UnderlineThickness;
-
- ///
- /// Gets a value that indicates the distance of the strikethrough from the baseline in design em size.
- ///
- public int StrikethroughPosition => PlatformImpl.StrikethroughPosition;
-
- ///
- /// Gets a value that indicates the thickness of the underline in design em size.
- ///
- public int StrikethroughThickness => PlatformImpl.StrikethroughThickness;
-
- ///
- /// A value indicating whether all glyphs in the font have the same advancement.
- ///
- public bool IsFixedPitch => PlatformImpl.IsFixedPitch;
-
- ///
- /// Returns an glyph index for the specified codepoint.
- ///
- ///
- /// Returns a replacement glyph if a glyph isn't found.
- ///
- /// The codepoint.
- ///
- /// A glyph index.
- ///
- public ushort GetGlyph(uint codepoint) => PlatformImpl.GetGlyph(codepoint);
-
- ///
- /// Tries to get an glyph index for specified codepoint.
- ///
- /// The codepoint.
- /// A glyph index.
- ///
- /// true if an glyph index was found, false otherwise.
- ///
- public bool TryGetGlyph(uint codepoint, out ushort glyph)
- {
- glyph = PlatformImpl.GetGlyph(codepoint);
-
- return glyph != 0;
- }
-
- ///
- /// Returns an array of glyph indices. Codepoints that are not represented by the font are returned as 0.
- ///
- /// The codepoints to map.
- ///
- public ushort[] GetGlyphs(ReadOnlySpan codepoints) => PlatformImpl.GetGlyphs(codepoints);
-
- ///
- /// Returns the glyph advance for the specified glyph.
- ///
- /// The glyph.
- ///
- /// The advance.
- ///
- public int GetGlyphAdvance(ushort glyph) => PlatformImpl.GetGlyphAdvance(glyph);
-
- ///
- /// Returns an array of glyph advances in design em size.
- ///
- /// The glyph indices.
- ///
- public int[] GetGlyphAdvances(ReadOnlySpan glyphs) => PlatformImpl.GetGlyphAdvances(glyphs);
-
- void IDisposable.Dispose()
- {
- PlatformImpl?.Dispose();
- }
- }
-}
diff --git a/src/Avalonia.Base/Platform/IGlyphTypefaceImpl.cs b/src/Avalonia.Base/Media/IGlyphTypeface.cs
similarity index 52%
rename from src/Avalonia.Base/Platform/IGlyphTypefaceImpl.cs
rename to src/Avalonia.Base/Media/IGlyphTypeface.cs
index 415f34fb29..de2a2309ee 100644
--- a/src/Avalonia.Base/Platform/IGlyphTypefaceImpl.cs
+++ b/src/Avalonia.Base/Media/IGlyphTypeface.cs
@@ -1,55 +1,23 @@
using System;
using Avalonia.Metadata;
-namespace Avalonia.Platform
+namespace Avalonia.Media
{
[Unstable]
- public interface IGlyphTypefaceImpl : IDisposable
+ public interface IGlyphTypeface : IDisposable
{
///
- /// Gets the font design units per em.
+ /// Gets the number of glyphs held by this glyph typeface.
///
- short DesignEmHeight { get; }
+ int GlyphCount { get; }
///
- /// Gets the recommended distance above the baseline in design em size.
+ /// Gets the font metrics.
///
- int Ascent { get; }
-
- ///
- /// Gets the recommended distance under the baseline in design em size.
- ///
- int Descent { get; }
-
- ///
- /// Gets the recommended additional space between two lines of text in design em size.
- ///
- int LineGap { get; }
-
- ///
- /// Gets a value that indicates the distance of the underline from the baseline in design em size.
- ///
- int UnderlinePosition { get; }
-
- ///
- /// Gets a value that indicates the thickness of the underline in design em size.
- ///
- int UnderlineThickness { get; }
-
- ///
- /// Gets a value that indicates the distance of the strikethrough from the baseline in design em size.
- ///
- int StrikethroughPosition { get; }
-
- ///
- /// Gets a value that indicates the thickness of the underline in design em size.
- ///
- int StrikethroughThickness { get; }
-
- ///
- /// A value indicating whether all glyphs in the font have the same advancement.
- ///
- bool IsFixedPitch { get; }
+ ///
+ /// The font metrics.
+ ///
+ FontMetrics Metrics { get; }
///
/// Returns an glyph index for the specified codepoint.
@@ -63,6 +31,16 @@ namespace Avalonia.Platform
///
ushort GetGlyph(uint codepoint);
+ ///
+ /// Tries to get an glyph index for specified codepoint.
+ ///
+ /// The codepoint.
+ /// A glyph index.
+ ///
+ /// true if an glyph index was found, false otherwise.
+ ///
+ bool TryGetGlyph(uint codepoint, out ushort glyph);
+
///
/// Returns an array of glyph indices. Codepoints that are not represented by the font are returned as 0.
///
@@ -89,5 +67,13 @@ namespace Avalonia.Platform
/// An array of glyph advances.
///
int[] GetGlyphAdvances(ReadOnlySpan glyphs);
+
+ ///
+ /// Returns the contents of the table data for the specified tag.
+ ///
+ /// The table tag to get the data for.
+ /// The contents of the table data for the specified tag.
+ /// Returns true if the content exists, otherwise false.
+ bool TryGetTable(uint tag, out byte[] table);
}
}
diff --git a/src/Avalonia.Base/Media/PreciseEllipticArcHelper.cs b/src/Avalonia.Base/Media/PreciseEllipticArcHelper.cs
index 5dd647e8ca..49dfe3c7b3 100644
--- a/src/Avalonia.Base/Media/PreciseEllipticArcHelper.cs
+++ b/src/Avalonia.Base/Media/PreciseEllipticArcHelper.cs
@@ -1081,7 +1081,7 @@ namespace Avalonia.Media
Point c = rest * (cs) + translation;
- // See "http://www.w3.org/TR/SVG/implnote.html#ArcConversionEndpointToCenter" to understand
+ // See "https://www.w3.org/TR/SVG/implnote.html#ArcConversionEndpointToCenter" to understand
// how the ellipse center is calculated
diff --git a/src/Avalonia.Base/Media/TextDecoration.cs b/src/Avalonia.Base/Media/TextDecoration.cs
index 4c9764af96..0a7328125a 100644
--- a/src/Avalonia.Base/Media/TextDecoration.cs
+++ b/src/Avalonia.Base/Media/TextDecoration.cs
@@ -155,9 +155,9 @@ namespace Avalonia.Media
///
/// The drawing context.
/// The decorated run.
- /// The font metrics of the decorated run.
+ /// The font metrics of the decorated run.
/// The default brush that is used to draw the decoration.
- internal void Draw(DrawingContext drawingContext, GlyphRun glyphRun, FontMetrics fontMetrics, IBrush defaultBrush)
+ internal void Draw(DrawingContext drawingContext, GlyphRun glyphRun, TextMetrics textMetrics, IBrush defaultBrush)
{
var baselineOrigin = glyphRun.BaselineOrigin;
var thickness = StrokeThickness;
@@ -168,16 +168,16 @@ namespace Avalonia.Media
switch (Location)
{
case TextDecorationLocation.Underline:
- thickness = fontMetrics.UnderlineThickness;
+ thickness = textMetrics.UnderlineThickness;
break;
case TextDecorationLocation.Strikethrough:
- thickness = fontMetrics.StrikethroughThickness;
+ thickness = textMetrics.StrikethroughThickness;
break;
}
break;
case TextDecorationUnit.FontRenderingEmSize:
- thickness = fontMetrics.FontRenderingEmSize * thickness;
+ thickness = textMetrics.FontRenderingEmSize * thickness;
break;
}
@@ -189,17 +189,17 @@ namespace Avalonia.Media
origin += glyphRun.BaselineOrigin;
break;
case TextDecorationLocation.Strikethrough:
- origin += new Point(baselineOrigin.X, baselineOrigin.Y + fontMetrics.StrikethroughPosition);
+ origin += new Point(baselineOrigin.X, baselineOrigin.Y + textMetrics.StrikethroughPosition);
break;
case TextDecorationLocation.Underline:
- origin += new Point(baselineOrigin.X, baselineOrigin.Y + fontMetrics.UnderlinePosition);
+ origin += new Point(baselineOrigin.X, baselineOrigin.Y + textMetrics.UnderlinePosition);
break;
}
switch (StrokeOffsetUnit)
{
case TextDecorationUnit.FontRenderingEmSize:
- origin += new Point(0, StrokeOffset * fontMetrics.FontRenderingEmSize);
+ origin += new Point(0, StrokeOffset * textMetrics.FontRenderingEmSize);
break;
case TextDecorationUnit.Pixel:
origin += new Point(0, StrokeOffset);
diff --git a/src/Avalonia.Base/Media/TextFormatting/ShapedBuffer.cs b/src/Avalonia.Base/Media/TextFormatting/ShapedBuffer.cs
index 47a6334e39..85924a3d32 100644
--- a/src/Avalonia.Base/Media/TextFormatting/ShapedBuffer.cs
+++ b/src/Avalonia.Base/Media/TextFormatting/ShapedBuffer.cs
@@ -8,13 +8,13 @@ namespace Avalonia.Media.TextFormatting
{
private static readonly IComparer s_clusterComparer = new CompareClusters();
- public ShapedBuffer(ReadOnlySlice text, int length, GlyphTypeface glyphTypeface, double fontRenderingEmSize, sbyte bidiLevel)
+ public ShapedBuffer(ReadOnlySlice text, int length, IGlyphTypeface glyphTypeface, double fontRenderingEmSize, sbyte bidiLevel)
: this(text, new GlyphInfo[length], glyphTypeface, fontRenderingEmSize, bidiLevel)
{
}
- internal ShapedBuffer(ReadOnlySlice text, ArraySlice glyphInfos, GlyphTypeface glyphTypeface, double fontRenderingEmSize, sbyte bidiLevel)
+ internal ShapedBuffer(ReadOnlySlice text, ArraySlice glyphInfos, IGlyphTypeface glyphTypeface, double fontRenderingEmSize, sbyte bidiLevel)
{
Text = text;
GlyphInfos = glyphInfos;
@@ -29,7 +29,7 @@ namespace Avalonia.Media.TextFormatting
public int Length => GlyphInfos.Length;
- public GlyphTypeface GlyphTypeface { get; }
+ public IGlyphTypeface GlyphTypeface { get; }
public double FontRenderingEmSize { get; }
diff --git a/src/Avalonia.Base/Media/TextFormatting/ShapedTextCharacters.cs b/src/Avalonia.Base/Media/TextFormatting/ShapedTextCharacters.cs
index 53287a264d..21101f462c 100644
--- a/src/Avalonia.Base/Media/TextFormatting/ShapedTextCharacters.cs
+++ b/src/Avalonia.Base/Media/TextFormatting/ShapedTextCharacters.cs
@@ -1,5 +1,4 @@
using System;
-using System.Diagnostics;
using Avalonia.Media.TextFormatting.Unicode;
using Avalonia.Utilities;
@@ -18,7 +17,7 @@ namespace Avalonia.Media.TextFormatting
Text = shapedBuffer.Text;
Properties = properties;
TextSourceLength = Text.Length;
- FontMetrics = new FontMetrics(properties.Typeface, properties.FontRenderingEmSize);
+ TextMetrics = new TextMetrics(properties.Typeface, properties.FontRenderingEmSize);
}
public bool IsReversed { get; private set; }
@@ -36,9 +35,9 @@ namespace Avalonia.Media.TextFormatting
///
public override int TextSourceLength { get; }
- public FontMetrics FontMetrics { get; }
+ public TextMetrics TextMetrics { get; }
- public override double Baseline => -FontMetrics.Ascent;
+ public override double Baseline => -TextMetrics.Ascent;
public override Size Size => GlyphRun.Size;
@@ -89,7 +88,7 @@ namespace Avalonia.Media.TextFormatting
foreach (var textDecoration in Properties.TextDecorations)
{
- textDecoration.Draw(drawingContext, GlyphRun, FontMetrics, Properties.ForegroundBrush);
+ textDecoration.Draw(drawingContext, GlyphRun, TextMetrics, Properties.ForegroundBrush);
}
}
}
diff --git a/src/Avalonia.Base/Media/TextFormatting/TextLineImpl.cs b/src/Avalonia.Base/Media/TextFormatting/TextLineImpl.cs
index aba8008fb9..96f88d1f44 100644
--- a/src/Avalonia.Base/Media/TextFormatting/TextLineImpl.cs
+++ b/src/Avalonia.Base/Media/TextFormatting/TextLineImpl.cs
@@ -1378,17 +1378,17 @@ namespace Avalonia.Media.TextFormatting
private TextLineMetrics CreateLineMetrics()
{
- var glyphTypeface = _paragraphProperties.DefaultTextRunProperties.Typeface.GlyphTypeface;
+ var fontMetrics = _paragraphProperties.DefaultTextRunProperties.Typeface.GlyphTypeface.Metrics;
var fontRenderingEmSize = _paragraphProperties.DefaultTextRunProperties.FontRenderingEmSize;
- var scale = fontRenderingEmSize / glyphTypeface.DesignEmHeight;
+ var scale = fontRenderingEmSize / fontMetrics.DesignEmHeight;
var width = 0d;
var widthIncludingWhitespace = 0d;
var trailingWhitespaceLength = 0;
var newLineLength = 0;
- var ascent = glyphTypeface.Ascent * scale;
- var descent = glyphTypeface.Descent * scale;
- var lineGap = glyphTypeface.LineGap * scale;
+ var ascent = fontMetrics.Ascent * scale;
+ var descent = fontMetrics.Descent * scale;
+ var lineGap = fontMetrics.LineGap * scale;
var height = descent - ascent + lineGap;
@@ -1400,26 +1400,26 @@ namespace Avalonia.Media.TextFormatting
{
case ShapedTextCharacters textRun:
{
- var fontMetrics =
- new FontMetrics(textRun.Properties.Typeface, textRun.Properties.FontRenderingEmSize);
+ var textMetrics =
+ new TextMetrics(textRun.Properties.Typeface, textRun.Properties.FontRenderingEmSize);
if (fontRenderingEmSize < textRun.Properties.FontRenderingEmSize)
{
fontRenderingEmSize = textRun.Properties.FontRenderingEmSize;
- if (ascent > fontMetrics.Ascent)
+ if (ascent > textMetrics.Ascent)
{
- ascent = fontMetrics.Ascent;
+ ascent = textMetrics.Ascent;
}
- if (descent < fontMetrics.Descent)
+ if (descent < textMetrics.Descent)
{
- descent = fontMetrics.Descent;
+ descent = textMetrics.Descent;
}
- if (lineGap < fontMetrics.LineGap)
+ if (lineGap < textMetrics.LineGap)
{
- lineGap = fontMetrics.LineGap;
+ lineGap = textMetrics.LineGap;
}
if (descent - ascent + lineGap > height)
diff --git a/src/Avalonia.Base/Media/TextFormatting/FontMetrics.cs b/src/Avalonia.Base/Media/TextFormatting/TextMetrics.cs
similarity index 69%
rename from src/Avalonia.Base/Media/TextFormatting/FontMetrics.cs
rename to src/Avalonia.Base/Media/TextFormatting/TextMetrics.cs
index e01bba00a4..0382e66b5a 100644
--- a/src/Avalonia.Base/Media/TextFormatting/FontMetrics.cs
+++ b/src/Avalonia.Base/Media/TextFormatting/TextMetrics.cs
@@ -1,33 +1,33 @@
namespace Avalonia.Media.TextFormatting
{
///
- /// A metric that holds information about font specific measurements.
+ /// A metric that holds information about text specific measurements.
///
- public readonly struct FontMetrics
+ public readonly struct TextMetrics
{
- public FontMetrics(Typeface typeface, double fontRenderingEmSize)
+ public TextMetrics(Typeface typeface, double fontRenderingEmSize)
{
- var glyphTypeface = typeface.GlyphTypeface;
+ var fontMetrics = typeface.GlyphTypeface.Metrics;
- var scale = fontRenderingEmSize / glyphTypeface.DesignEmHeight;
+ var scale = fontRenderingEmSize / fontMetrics.DesignEmHeight;
FontRenderingEmSize = fontRenderingEmSize;
- Ascent = glyphTypeface.Ascent * scale;
+ Ascent = fontMetrics.Ascent * scale;
- Descent = glyphTypeface.Descent * scale;
+ Descent = fontMetrics.Descent * scale;
- LineGap = glyphTypeface.LineGap * scale;
+ LineGap = fontMetrics.LineGap * scale;
LineHeight = Descent - Ascent + LineGap;
- UnderlineThickness = glyphTypeface.UnderlineThickness * scale;
+ UnderlineThickness = fontMetrics.UnderlineThickness * scale;
- UnderlinePosition = glyphTypeface.UnderlinePosition * scale;
+ UnderlinePosition = fontMetrics.UnderlinePosition * scale;
- StrikethroughThickness = glyphTypeface.StrikethroughThickness * scale;
+ StrikethroughThickness = fontMetrics.StrikethroughThickness * scale;
- StrikethroughPosition = glyphTypeface.StrikethroughPosition * scale;
+ StrikethroughPosition = fontMetrics.StrikethroughPosition * scale;
}
///
diff --git a/src/Avalonia.Base/Media/TextFormatting/TextShaperOptions.cs b/src/Avalonia.Base/Media/TextFormatting/TextShaperOptions.cs
index 4e75bb921e..0d00bed51e 100644
--- a/src/Avalonia.Base/Media/TextFormatting/TextShaperOptions.cs
+++ b/src/Avalonia.Base/Media/TextFormatting/TextShaperOptions.cs
@@ -8,7 +8,7 @@ namespace Avalonia.Media.TextFormatting
public readonly struct TextShaperOptions
{
public TextShaperOptions(
- GlyphTypeface typeface,
+ IGlyphTypeface typeface,
double fontRenderingEmSize = 12,
sbyte bidiLevel = 0,
CultureInfo? culture = null,
@@ -24,7 +24,7 @@ namespace Avalonia.Media.TextFormatting
///
/// Get the typeface.
///
- public GlyphTypeface Typeface { get; }
+ public IGlyphTypeface Typeface { get; }
///
/// Get the font rendering em size.
///
diff --git a/src/Avalonia.Base/Media/TextFormatting/Unicode/BinaryReaderExtensions.cs b/src/Avalonia.Base/Media/TextFormatting/Unicode/BinaryReaderExtensions.cs
index 412007c6e0..ccbceb6a7b 100644
--- a/src/Avalonia.Base/Media/TextFormatting/Unicode/BinaryReaderExtensions.cs
+++ b/src/Avalonia.Base/Media/TextFormatting/Unicode/BinaryReaderExtensions.cs
@@ -5,7 +5,7 @@
// not use this product except in compliance with the License. You may obtain
// a copy of the License at
//
-// http://www.apache.org/licenses/LICENSE-2.0
+// https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
diff --git a/src/Avalonia.Base/Media/TextFormatting/Unicode/Codepoint.cs b/src/Avalonia.Base/Media/TextFormatting/Unicode/Codepoint.cs
index ab17263806..de40839853 100644
--- a/src/Avalonia.Base/Media/TextFormatting/Unicode/Codepoint.cs
+++ b/src/Avalonia.Base/Media/TextFormatting/Unicode/Codepoint.cs
@@ -1,4 +1,5 @@
-using System.Runtime.CompilerServices;
+using System;
+using System.Runtime.CompilerServices;
using Avalonia.Utilities;
namespace Avalonia.Media.TextFormatting.Unicode
@@ -104,7 +105,7 @@ namespace Avalonia.Media.TextFormatting.Unicode
///
/// Gets the canonical representation of a given codepoint.
- ///
+ ///
///
/// The code point to be mapped.
/// The mapped canonical code point, or the passed .
@@ -165,7 +166,7 @@ namespace Avalonia.Media.TextFormatting.Unicode
/// The index to read at.
/// The count of character that were read.
///
- public static Codepoint ReadAt(ReadOnlySlice text, int index, out int count)
+ public static Codepoint ReadAt(ReadOnlySpan text, int index, out int count)
{
count = 1;
diff --git a/src/Avalonia.Base/Media/TextFormatting/Unicode/GraphemeBreak.cs b/src/Avalonia.Base/Media/TextFormatting/Unicode/GraphemeBreak.cs
index 6986b908a1..d0ceba62f4 100644
--- a/src/Avalonia.Base/Media/TextFormatting/Unicode/GraphemeBreak.cs
+++ b/src/Avalonia.Base/Media/TextFormatting/Unicode/GraphemeBreak.cs
@@ -2,6 +2,6 @@ namespace Avalonia.Media.TextFormatting.Unicode
{
internal static class GraphemeBreak
{
- public static byte[] Data => new byte[0];
+ public static byte[] Data => System.Array.Empty();
}
}
diff --git a/src/Avalonia.Base/Media/TextFormatting/Unicode/LineBreak.cs b/src/Avalonia.Base/Media/TextFormatting/Unicode/LineBreak.cs
index 34b14f008f..59c4df0a2e 100644
--- a/src/Avalonia.Base/Media/TextFormatting/Unicode/LineBreak.cs
+++ b/src/Avalonia.Base/Media/TextFormatting/Unicode/LineBreak.cs
@@ -5,7 +5,7 @@
// not use this product except in compliance with the License. You may obtain
// a copy of the License at
//
-// http://www.apache.org/licenses/LICENSE-2.0
+// https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
diff --git a/src/Avalonia.Base/Media/TextFormatting/Unicode/UnicodeTrie.cs b/src/Avalonia.Base/Media/TextFormatting/Unicode/UnicodeTrie.cs
index cf03ed7cd3..079f830ddc 100644
--- a/src/Avalonia.Base/Media/TextFormatting/Unicode/UnicodeTrie.cs
+++ b/src/Avalonia.Base/Media/TextFormatting/Unicode/UnicodeTrie.cs
@@ -5,7 +5,7 @@
// not use this product except in compliance with the License. You may obtain
// a copy of the License at
//
-// http://www.apache.org/licenses/LICENSE-2.0
+// https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
diff --git a/src/Avalonia.Base/Media/TextFormatting/Unicode/UnicodeTrieBuilder.Constants.cs b/src/Avalonia.Base/Media/TextFormatting/Unicode/UnicodeTrieBuilder.Constants.cs
index 29ee45acc2..de0304f4c9 100644
--- a/src/Avalonia.Base/Media/TextFormatting/Unicode/UnicodeTrieBuilder.Constants.cs
+++ b/src/Avalonia.Base/Media/TextFormatting/Unicode/UnicodeTrieBuilder.Constants.cs
@@ -5,7 +5,7 @@
// not use this product except in compliance with the License. You may obtain
// a copy of the License at
//
-// http://www.apache.org/licenses/LICENSE-2.0
+// https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
diff --git a/src/Avalonia.Base/Media/TextFormatting/Unicode/UnicodeTrieBuilder.cs b/src/Avalonia.Base/Media/TextFormatting/Unicode/UnicodeTrieBuilder.cs
index 87f96984c5..755d603539 100644
--- a/src/Avalonia.Base/Media/TextFormatting/Unicode/UnicodeTrieBuilder.cs
+++ b/src/Avalonia.Base/Media/TextFormatting/Unicode/UnicodeTrieBuilder.cs
@@ -5,7 +5,7 @@
// not use this product except in compliance with the License. You may obtain
// a copy of the License at
//
-// http://www.apache.org/licenses/LICENSE-2.0
+// https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
diff --git a/src/Avalonia.Base/Media/Typeface.cs b/src/Avalonia.Base/Media/Typeface.cs
index f0daa841d9..e6047bf96c 100644
--- a/src/Avalonia.Base/Media/Typeface.cs
+++ b/src/Avalonia.Base/Media/Typeface.cs
@@ -81,7 +81,7 @@ namespace Avalonia.Media
///
/// The glyph typeface.
///
- public GlyphTypeface GlyphTypeface => FontManager.Current.GetOrAddGlyphTypeface(this);
+ public IGlyphTypeface GlyphTypeface => FontManager.Current.GetOrAddGlyphTypeface(this);
public static bool operator !=(Typeface a, Typeface b)
{
diff --git a/src/Avalonia.Base/Platform/IFontManagerImpl.cs b/src/Avalonia.Base/Platform/IFontManagerImpl.cs
index 932249bd52..cd6e64abaf 100644
--- a/src/Avalonia.Base/Platform/IFontManagerImpl.cs
+++ b/src/Avalonia.Base/Platform/IFontManagerImpl.cs
@@ -43,6 +43,6 @@ namespace Avalonia.Platform
/// 0
/// The created glyph typeface. Can be Null if it was not possible to create a glyph typeface.
///
- IGlyphTypefaceImpl CreateGlyphTypeface(Typeface typeface);
+ IGlyphTypeface CreateGlyphTypeface(Typeface typeface);
}
}
diff --git a/src/Avalonia.Base/Platform/IGlyphRunBuffer.cs b/src/Avalonia.Base/Platform/IGlyphRunBuffer.cs
new file mode 100644
index 0000000000..c1fc7a5967
--- /dev/null
+++ b/src/Avalonia.Base/Platform/IGlyphRunBuffer.cs
@@ -0,0 +1,22 @@
+using System;
+using System.Drawing;
+
+namespace Avalonia.Platform
+{
+ public interface IGlyphRunBuffer
+ {
+ Span GlyphIndices { get; }
+
+ IGlyphRunImpl Build();
+ }
+
+ public interface IHorizontalGlyphRunBuffer : IGlyphRunBuffer
+ {
+ Span GlyphPositions { get; }
+ }
+
+ public interface IPositionedGlyphRunBuffer : IGlyphRunBuffer
+ {
+ Span GlyphPositions { get; }
+ }
+}
diff --git a/src/Avalonia.Base/Platform/IPlatformRenderInterface.cs b/src/Avalonia.Base/Platform/IPlatformRenderInterface.cs
index e39a4e23df..9d0d7974b4 100644
--- a/src/Avalonia.Base/Platform/IPlatformRenderInterface.cs
+++ b/src/Avalonia.Base/Platform/IPlatformRenderInterface.cs
@@ -171,11 +171,40 @@ namespace Avalonia.Platform
IBitmapImpl LoadBitmap(PixelFormat format, AlphaFormat alphaFormat, IntPtr data, PixelSize size, Vector dpi, int stride);
///
- /// Creates a platform implementation of a glyph run.
+ /// Allocates a platform glyph run buffer.
///
- /// The glyph run.
- ///
- IGlyphRunImpl CreateGlyphRun(GlyphRun glyphRun);
+ /// The glyph typeface.
+ /// The font rendering em size.
+ /// The length.
+ /// An .
+ ///
+ /// This buffer only holds glyph indices.
+ ///
+ IGlyphRunBuffer AllocateGlyphRun(IGlyphTypeface glyphTypeface, float fontRenderingEmSize, int length);
+
+ ///
+ /// Allocates a horizontal platform glyph run buffer.
+ ///
+ /// The glyph typeface.
+ /// The font rendering em size.
+ /// The length.
+ /// An .
+ ///
+ /// This buffer holds glyph indices and glyph advances.
+ ///
+ IHorizontalGlyphRunBuffer AllocateHorizontalGlyphRun(IGlyphTypeface glyphTypeface, float fontRenderingEmSize, int length);
+
+ ///
+ /// Allocates a positioned platform glyph run buffer.
+ ///
+ /// The glyph typeface.
+ /// The font rendering em size.
+ /// The length.
+ /// An .
+ ///
+ /// This buffer holds glyph indices, glyph advances and glyph positions.
+ ///
+ IPositionedGlyphRunBuffer AllocatePositionedGlyphRun(IGlyphTypeface glyphTypeface, float fontRenderingEmSize, int length);
///
/// Gets a value indicating whether the platform directly supports rectangles with rounded corners.
diff --git a/src/Avalonia.Base/Rendering/Composition/Server/FpsCounter.cs b/src/Avalonia.Base/Rendering/Composition/Server/FpsCounter.cs
index b0b3982ed5..06fb526736 100644
--- a/src/Avalonia.Base/Rendering/Composition/Server/FpsCounter.cs
+++ b/src/Avalonia.Base/Rendering/Composition/Server/FpsCounter.cs
@@ -25,7 +25,7 @@ internal class FpsCounter
// ASCII chars
private GlyphRun[] _runs = new GlyphRun[LastChar - FirstChar + 1];
- public FpsCounter(GlyphTypeface typeface)
+ public FpsCounter(IGlyphTypeface typeface)
{
for (var c = FirstChar; c <= LastChar; c++)
{
diff --git a/src/Avalonia.Base/Utilities/BinarySearchExtension.cs b/src/Avalonia.Base/Utilities/BinarySearchExtension.cs
index a4f6ae89c1..b7060d2e21 100644
--- a/src/Avalonia.Base/Utilities/BinarySearchExtension.cs
+++ b/src/Avalonia.Base/Utilities/BinarySearchExtension.cs
@@ -5,7 +5,7 @@
// not use this product except in compliance with the License. You may obtain
// a copy of the License at
//
-// http://www.apache.org/licenses/LICENSE-2.0
+// https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
diff --git a/src/Avalonia.Base/Utilities/MathUtilities.cs b/src/Avalonia.Base/Utilities/MathUtilities.cs
index 3c48c3469e..feb1097b5f 100644
--- a/src/Avalonia.Base/Utilities/MathUtilities.cs
+++ b/src/Avalonia.Base/Utilities/MathUtilities.cs
@@ -11,7 +11,7 @@ namespace Avalonia.Utilities
static class MathUtilities
{
// smallest such that 1.0+DoubleEpsilon != 1.0
- internal static readonly double DoubleEpsilon = 2.2204460492503131e-016;
+ internal const double DoubleEpsilon = 2.2204460492503131e-016;
private const float FloatEpsilon = 1.192092896e-07F;
diff --git a/src/Avalonia.Base/Utilities/ReadOnlySlice.cs b/src/Avalonia.Base/Utilities/ReadOnlySlice.cs
index ad545b2923..583a3139b9 100644
--- a/src/Avalonia.Base/Utilities/ReadOnlySlice.cs
+++ b/src/Avalonia.Base/Utilities/ReadOnlySlice.cs
@@ -214,6 +214,8 @@ namespace Avalonia.Utilities
return new ReadOnlySlice(memory);
}
+ public static implicit operator ReadOnlySpan(ReadOnlySlice slice) => slice.Span;
+
internal class ReadOnlySliceDebugView
{
private readonly ReadOnlySlice _readOnlySlice;
diff --git a/src/Avalonia.Build.Tasks/Avalonia.Build.Tasks.csproj b/src/Avalonia.Build.Tasks/Avalonia.Build.Tasks.csproj
index 22c410198f..74debed828 100644
--- a/src/Avalonia.Build.Tasks/Avalonia.Build.Tasks.csproj
+++ b/src/Avalonia.Build.Tasks/Avalonia.Build.Tasks.csproj
@@ -9,6 +9,11 @@
NU1605;CS8632
+
+
+ false
+
+
Shared/AvaloniaResourcesIndex.cs
diff --git a/src/Avalonia.Controls.ColorPicker/Helpers/ColorPickerHelpers.cs b/src/Avalonia.Controls.ColorPicker/Helpers/ColorPickerHelpers.cs
index c1904a3c30..381bc42aaa 100644
--- a/src/Avalonia.Controls.ColorPicker/Helpers/ColorPickerHelpers.cs
+++ b/src/Avalonia.Controls.ColorPicker/Helpers/ColorPickerHelpers.cs
@@ -49,7 +49,7 @@ namespace Avalonia.Controls.Primitives
{
if (width == 0 || height == 0)
{
- return new byte[0];
+ return Array.Empty();
}
var bitmap = await Task.Run(() =>
diff --git a/src/Avalonia.Controls.DataGrid/Collections/DataGridCollectionView.cs b/src/Avalonia.Controls.DataGrid/Collections/DataGridCollectionView.cs
index 906ec661ae..30517f4b00 100644
--- a/src/Avalonia.Controls.DataGrid/Collections/DataGridCollectionView.cs
+++ b/src/Avalonia.Controls.DataGrid/Collections/DataGridCollectionView.cs
@@ -3412,23 +3412,30 @@ namespace Avalonia.Collections
RefreshOrDefer();
return;
}
-
- object addedItem = args.NewItems?[0];
- object removedItem = args.OldItems?[0];
-
+
// fire notifications for removes
- if (args.Action == NotifyCollectionChangedAction.Remove ||
- args.Action == NotifyCollectionChangedAction.Replace)
+ if (args.OldItems != null &&
+ (args.Action == NotifyCollectionChangedAction.Remove ||
+ args.Action == NotifyCollectionChangedAction.Replace))
{
- ProcessRemoveEvent(removedItem, args.Action == NotifyCollectionChangedAction.Replace);
+ foreach (var removedItem in args.OldItems)
+ {
+ ProcessRemoveEvent(removedItem, args.Action == NotifyCollectionChangedAction.Replace);
+ }
}
// fire notifications for adds
- if ((args.Action == NotifyCollectionChangedAction.Add ||
- args.Action == NotifyCollectionChangedAction.Replace) &&
- (Filter == null || PassesFilter(addedItem)))
+ if (args.NewItems != null &&
+ (args.Action == NotifyCollectionChangedAction.Add ||
+ args.Action == NotifyCollectionChangedAction.Replace))
{
- ProcessAddEvent(addedItem, args.NewStartingIndex);
+ for (var i = 0; i < args.NewItems.Count; i++)
+ {
+ if (Filter == null || PassesFilter(args.NewItems[i]))
+ {
+ ProcessAddEvent(args.NewItems[i], args.NewStartingIndex + i);
+ }
+ }
}
if (args.Action != NotifyCollectionChangedAction.Replace)
{
diff --git a/src/Avalonia.Controls/AutoCompleteBox/AutoCompleteBox.Properties.cs b/src/Avalonia.Controls/AutoCompleteBox/AutoCompleteBox.Properties.cs
new file mode 100644
index 0000000000..ecbe01d8b7
--- /dev/null
+++ b/src/Avalonia.Controls/AutoCompleteBox/AutoCompleteBox.Properties.cs
@@ -0,0 +1,463 @@
+// (c) Copyright Microsoft Corporation.
+// This source is subject to the Microsoft Public License (Ms-PL).
+// Please see https://go.microsoft.com/fwlink/?LinkID=131993 for details.
+// All other rights reserved.
+
+using System;
+using System.Collections;
+using System.Collections.Generic;
+using System.Threading;
+using System.Threading.Tasks;
+using Avalonia.Controls.Templates;
+using Avalonia.Data;
+
+namespace Avalonia.Controls
+{
+ public partial class AutoCompleteBox
+ {
+ public static readonly StyledProperty WatermarkProperty =
+ TextBox.WatermarkProperty.AddOwner();
+
+ ///
+ /// Identifies the property.
+ ///
+ /// The identifier for the property.
+ public static readonly StyledProperty MinimumPrefixLengthProperty =
+ AvaloniaProperty.Register(
+ nameof(MinimumPrefixLength), 1,
+ validate: IsValidMinimumPrefixLength);
+
+ ///
+ /// Identifies the property.
+ ///
+ /// The identifier for the property.
+ public static readonly StyledProperty MinimumPopulateDelayProperty =
+ AvaloniaProperty.Register(
+ nameof(MinimumPopulateDelay),
+ TimeSpan.Zero,
+ validate: IsValidMinimumPopulateDelay);
+
+ ///
+ /// Identifies the property.
+ ///
+ /// The identifier for the property.
+ public static readonly StyledProperty MaxDropDownHeightProperty =
+ AvaloniaProperty.Register(
+ nameof(MaxDropDownHeight),
+ double.PositiveInfinity,
+ validate: IsValidMaxDropDownHeight);
+
+ ///
+ /// Identifies the property.
+ ///
+ /// The identifier for the property.
+ public static readonly StyledProperty IsTextCompletionEnabledProperty =
+ AvaloniaProperty.Register(nameof(IsTextCompletionEnabled));
+
+ ///
+ /// Identifies the property.
+ ///
+ /// The identifier for the property.
+ public static readonly StyledProperty ItemTemplateProperty =
+ AvaloniaProperty.Register(nameof(ItemTemplate));
+
+ ///
+ /// Identifies the property.
+ ///
+ /// The identifier for the property.
+ public static readonly DirectProperty IsDropDownOpenProperty =
+ AvaloniaProperty.RegisterDirect(
+ nameof(IsDropDownOpen),
+ o => o.IsDropDownOpen,
+ (o, v) => o.IsDropDownOpen = v);
+
+ ///
+ /// Identifies the property.
+ ///
+ /// The identifier the property.
+ public static readonly DirectProperty SelectedItemProperty =
+ AvaloniaProperty.RegisterDirect(
+ nameof(SelectedItem),
+ o => o.SelectedItem,
+ (o, v) => o.SelectedItem = v,
+ defaultBindingMode: BindingMode.TwoWay,
+ enableDataValidation: true);
+
+ ///
+ /// Identifies the property.
+ ///
+ /// The identifier for the property.
+ public static readonly DirectProperty TextProperty =
+ TextBlock.TextProperty.AddOwnerWithDataValidation(
+ o => o.Text,
+ (o, v) => o.Text = v,
+ defaultBindingMode: BindingMode.TwoWay,
+ enableDataValidation: true);
+
+ ///
+ /// Identifies the property.
+ ///
+ /// The identifier for the property.
+ public static readonly DirectProperty SearchTextProperty =
+ AvaloniaProperty.RegisterDirect(
+ nameof(SearchText),
+ o => o.SearchText,
+ unsetValue: string.Empty);
+
+ ///
+ /// Gets the identifier for the property.
+ ///
+ public static readonly StyledProperty FilterModeProperty =
+ AvaloniaProperty.Register(
+ nameof(FilterMode),
+ defaultValue: AutoCompleteFilterMode.StartsWith,
+ validate: IsValidFilterMode);
+
+ ///
+ /// Identifies the property.
+ ///
+ /// The identifier for the property.
+ public static readonly DirectProperty?> ItemFilterProperty =
+ AvaloniaProperty.RegisterDirect?>(
+ nameof(ItemFilter),
+ o => o.ItemFilter,
+ (o, v) => o.ItemFilter = v);
+
+ ///
+ /// Identifies the property.
+ ///
+ /// The identifier for the property.
+ public static readonly DirectProperty?> TextFilterProperty =
+ AvaloniaProperty.RegisterDirect?>(
+ nameof(TextFilter),
+ o => o.TextFilter,
+ (o, v) => o.TextFilter = v,
+ unsetValue: AutoCompleteSearch.GetFilter(AutoCompleteFilterMode.StartsWith));
+
+ ///
+ /// Identifies the property.
+ ///
+ /// The identifier for the property.
+ public static readonly DirectProperty?> ItemSelectorProperty =
+ AvaloniaProperty.RegisterDirect?>(
+ nameof(ItemSelector),
+ o => o.ItemSelector,
+ (o, v) => o.ItemSelector = v);
+
+ ///
+ /// Identifies the property.
+ ///
+ /// The identifier for the property.
+ public static readonly DirectProperty?> TextSelectorProperty =
+ AvaloniaProperty.RegisterDirect?>(
+ nameof(TextSelector),
+ o => o.TextSelector,
+ (o, v) => o.TextSelector = v);
+
+ ///
+ /// Identifies the property.
+ ///
+ /// The identifier for the property.
+ public static readonly DirectProperty ItemsProperty =
+ AvaloniaProperty.RegisterDirect(
+ nameof(Items),
+ o => o.Items,
+ (o, v) => o.Items = v);
+
+ public static readonly DirectProperty>>?> AsyncPopulatorProperty =
+ AvaloniaProperty.RegisterDirect>>?>(
+ nameof(AsyncPopulator),
+ o => o.AsyncPopulator,
+ (o, v) => o.AsyncPopulator = v);
+
+ ///
+ /// Gets or sets the minimum number of characters required to be entered
+ /// in the text box before the displays possible matches.
+ ///
+ ///
+ /// The minimum number of characters to be entered in the text box
+ /// before the
+ /// displays possible matches. The default is 1.
+ ///
+ ///
+ /// If you set MinimumPrefixLength to -1, the AutoCompleteBox will
+ /// not provide possible matches. There is no maximum value, but
+ /// setting MinimumPrefixLength to value that is too large will
+ /// prevent the AutoCompleteBox from providing possible matches as well.
+ ///
+ public int MinimumPrefixLength
+ {
+ get => GetValue(MinimumPrefixLengthProperty);
+ set => SetValue(MinimumPrefixLengthProperty, value);
+ }
+
+ ///
+ /// Gets or sets a value indicating whether the first possible match
+ /// found during the filtering process will be displayed automatically
+ /// in the text box.
+ ///
+ ///
+ /// True if the first possible match found will be displayed
+ /// automatically in the text box; otherwise, false. The default is
+ /// false.
+ ///
+ public bool IsTextCompletionEnabled
+ {
+ get => GetValue(IsTextCompletionEnabledProperty);
+ set => SetValue(IsTextCompletionEnabledProperty, value);
+ }
+
+ ///
+ /// Gets or sets the used
+ /// to display each item in the drop-down portion of the control.
+ ///
+ /// The used to
+ /// display each item in the drop-down. The default is null.
+ ///
+ /// You use the ItemTemplate property to specify the visualization
+ /// of the data objects in the drop-down portion of the AutoCompleteBox
+ /// control. If your AutoCompleteBox is bound to a collection and you
+ /// do not provide specific display instructions by using a
+ /// DataTemplate, the resulting UI of each item is a string
+ /// representation of each object in the underlying collection.
+ ///
+ public IDataTemplate ItemTemplate
+ {
+ get => GetValue(ItemTemplateProperty);
+ set => SetValue(ItemTemplateProperty, value);
+ }
+
+ ///
+ /// Gets or sets the minimum delay, after text is typed
+ /// in the text box before the
+ /// control
+ /// populates the list of possible matches in the drop-down.
+ ///
+ /// The minimum delay, after text is typed in
+ /// the text box, but before the
+ /// populates
+ /// the list of possible matches in the drop-down. The default is 0.
+ public TimeSpan MinimumPopulateDelay
+ {
+ get => GetValue(MinimumPopulateDelayProperty);
+ set => SetValue(MinimumPopulateDelayProperty, value);
+ }
+
+ ///
+ /// Gets or sets the maximum height of the drop-down portion of the
+ /// control.
+ ///
+ /// The maximum height of the drop-down portion of the
+ /// control.
+ /// The default is .
+ /// The specified value is less than 0.
+ public double MaxDropDownHeight
+ {
+ get => GetValue(MaxDropDownHeightProperty);
+ set => SetValue(MaxDropDownHeightProperty, value);
+ }
+
+ ///
+ /// Gets or sets a value indicating whether the drop-down portion of
+ /// the control is open.
+ ///
+ ///
+ /// True if the drop-down is open; otherwise, false. The default is
+ /// false.
+ ///
+ public bool IsDropDownOpen
+ {
+ get => _isDropDownOpen;
+ set => SetAndRaise(IsDropDownOpenProperty, ref _isDropDownOpen, value);
+ }
+
+ ///
+ /// Gets or sets the that
+ /// is used to get the values for display in the text portion of
+ /// the
+ /// control.
+ ///
+ /// The object used
+ /// when binding to a collection property.
+ [AssignBinding]
+ public IBinding? ValueMemberBinding
+ {
+ get => _valueBindingEvaluator?.ValueBinding;
+ set
+ {
+ if (ValueMemberBinding != value)
+ {
+ _valueBindingEvaluator = new BindingEvaluator(value);
+ OnValueMemberBindingChanged(value);
+ }
+ }
+ }
+
+ ///
+ /// Gets or sets the selected item in the drop-down.
+ ///
+ /// The selected item in the drop-down.
+ ///
+ /// If the IsTextCompletionEnabled property is true and text typed by
+ /// the user matches an item in the ItemsSource collection, which is
+ /// then displayed in the text box, the SelectedItem property will be
+ /// a null reference.
+ ///
+ public object? SelectedItem
+ {
+ get => _selectedItem;
+ set => SetAndRaise(SelectedItemProperty, ref _selectedItem, value);
+ }
+
+ ///
+ /// Gets or sets the text in the text box portion of the
+ /// control.
+ ///
+ /// The text in the text box portion of the
+ /// control.
+ public string? Text
+ {
+ get => _text;
+ set => SetAndRaise(TextProperty, ref _text, value);
+ }
+
+ ///
+ /// Gets the text that is used to filter items in the
+ /// item collection.
+ ///
+ /// The text that is used to filter items in the
+ /// item collection.
+ ///
+ /// The SearchText value is typically the same as the
+ /// Text property, but is set after the TextChanged event occurs
+ /// and before the Populating event.
+ ///
+ public string? SearchText
+ {
+ get => _searchText;
+ private set
+ {
+ try
+ {
+ _allowWrite = true;
+ SetAndRaise(SearchTextProperty, ref _searchText, value);
+ }
+ finally
+ {
+ _allowWrite = false;
+ }
+ }
+ }
+
+ ///
+ /// Gets or sets how the text in the text box is used to filter items
+ /// specified by the
+ /// property for display in the drop-down.
+ ///
+ /// One of the
+ /// values The default is .
+ /// The specified value is not a valid
+ /// .
+ ///
+ /// Use the FilterMode property to specify how possible matches are
+ /// filtered. For example, possible matches can be filtered in a
+ /// predefined or custom way. The search mode is automatically set to
+ /// Custom if you set the ItemFilter property.
+ ///
+ public AutoCompleteFilterMode FilterMode
+ {
+ get => GetValue(FilterModeProperty);
+ set => SetValue(FilterModeProperty, value);
+ }
+
+ public string? Watermark
+ {
+ get => GetValue(WatermarkProperty);
+ set => SetValue(WatermarkProperty, value);
+ }
+
+ ///
+ /// Gets or sets the custom method that uses user-entered text to filter
+ /// the items specified by the
+ /// property for display in the drop-down.
+ ///
+ /// The custom method that uses the user-entered text to filter
+ /// the items specified by the
+ /// property. The default is null.
+ ///
+ /// The filter mode is automatically set to Custom if you set the
+ /// ItemFilter property.
+ ///
+ public AutoCompleteFilterPredicate
- 0,0,0,4
40
40
41
@@ -84,18 +83,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; };
diff --git a/src/Windows/Avalonia.Direct2D1/Direct2D1Platform.cs b/src/Windows/Avalonia.Direct2D1/Direct2D1Platform.cs
index 81fa8c4bce..8103f89dad 100644
--- a/src/Windows/Avalonia.Direct2D1/Direct2D1Platform.cs
+++ b/src/Windows/Avalonia.Direct2D1/Direct2D1Platform.cs
@@ -12,6 +12,8 @@ using SharpDX.DirectWrite;
using GlyphRun = Avalonia.Media.GlyphRun;
using TextAlignment = Avalonia.Media.TextAlignment;
using SharpDX.Mathematics.Interop;
+using System.Runtime.InteropServices;
+using System.Drawing;
namespace Avalonia
{
@@ -160,7 +162,7 @@ namespace Avalonia.Direct2D1
public IGeometryImpl BuildGlyphRunGeometry(GlyphRun glyphRun)
{
- if (glyphRun.GlyphTypeface.PlatformImpl is not GlyphTypefaceImpl glyphTypeface)
+ if (glyphRun.GlyphTypeface is not GlyphTypefaceImpl glyphTypeface)
{
throw new InvalidOperationException("PlatformImpl can't be null.");
}
@@ -258,69 +260,66 @@ namespace Avalonia.Direct2D1
return new WicBitmapImpl(format, alphaFormat, data, size, dpi, stride);
}
- public IGlyphRunImpl CreateGlyphRun(GlyphRun glyphRun)
+ private class DWGlyphRunBuffer : IGlyphRunBuffer
{
- var glyphTypeface = (GlyphTypefaceImpl)glyphRun.GlyphTypeface.PlatformImpl;
+ protected readonly SharpDX.DirectWrite.GlyphRun _dwRun;
- var glyphCount = glyphRun.GlyphIndices.Count;
-
- var run = new SharpDX.DirectWrite.GlyphRun
+ public DWGlyphRunBuffer(IGlyphTypeface glyphTypeface, float fontRenderingEmSize, int length)
{
- FontFace = glyphTypeface.FontFace,
- FontSize = (float)glyphRun.FontRenderingEmSize
- };
-
- var indices = new short[glyphCount];
+ var glyphTypefaceImpl = (GlyphTypefaceImpl)glyphTypeface;
- for (var i = 0; i < glyphCount; i++)
- {
- indices[i] = (short)glyphRun.GlyphIndices[i];
+ _dwRun = new SharpDX.DirectWrite.GlyphRun
+ {
+ FontFace = glyphTypefaceImpl.FontFace,
+ FontSize = fontRenderingEmSize,
+ Indices = new short[length]
+ };
}
- run.Indices = indices;
-
- run.Advances = new float[glyphCount];
+ public Span GlyphIndices => MemoryMarshal.Cast(_dwRun.Indices.AsSpan());
- var scale = (float)(glyphRun.FontRenderingEmSize / glyphTypeface.DesignEmHeight);
-
- if (glyphRun.GlyphAdvances == null)
+ public IGlyphRunImpl Build()
{
- for (var i = 0; i < glyphCount; i++)
- {
- var advance = glyphTypeface.GetGlyphAdvance(glyphRun.GlyphIndices[i]) * scale;
-
- run.Advances[i] = advance;
- }
+ return new GlyphRunImpl(_dwRun);
}
- else
- {
- for (var i = 0; i < glyphCount; i++)
- {
- var advance = (float)glyphRun.GlyphAdvances[i];
+ }
- run.Advances[i] = advance;
- }
+ private class DWHorizontalGlyphRunBuffer : DWGlyphRunBuffer, IHorizontalGlyphRunBuffer
+ {
+ public DWHorizontalGlyphRunBuffer(IGlyphTypeface glyphTypeface, float fontRenderingEmSize, int length)
+ : base(glyphTypeface, fontRenderingEmSize, length)
+ {
+ _dwRun.Advances = new float[length];
}
- if (glyphRun.GlyphOffsets == null)
+ public Span GlyphPositions => _dwRun.Advances.AsSpan();
+ }
+
+ private class DWPositionedGlyphRunBuffer : DWGlyphRunBuffer, IPositionedGlyphRunBuffer
+ {
+ public DWPositionedGlyphRunBuffer(IGlyphTypeface glyphTypeface, float fontRenderingEmSize, int length)
+ : base(glyphTypeface, fontRenderingEmSize, length)
{
- return new GlyphRunImpl(run);
+ _dwRun.Advances = new float[length];
+ _dwRun.Offsets = new GlyphOffset[length];
}
- run.Offsets = new GlyphOffset[glyphCount];
+ public Span GlyphPositions => MemoryMarshal.Cast(_dwRun.Offsets.AsSpan());
+ }
- for (var i = 0; i < glyphCount; i++)
- {
- var (x, y) = glyphRun.GlyphOffsets[i];
+ public IGlyphRunBuffer AllocateGlyphRun(IGlyphTypeface glyphTypeface, float fontRenderingEmSize, int length)
+ {
+ return new DWGlyphRunBuffer(glyphTypeface, fontRenderingEmSize, length);
+ }
- run.Offsets[i] = new GlyphOffset
- {
- AdvanceOffset = (float)x,
- AscenderOffset = (float)y
- };
- }
+ public IHorizontalGlyphRunBuffer AllocateHorizontalGlyphRun(IGlyphTypeface glyphTypeface, float fontRenderingEmSize, int length)
+ {
+ return new DWHorizontalGlyphRunBuffer(glyphTypeface, fontRenderingEmSize, length);
+ }
- return new GlyphRunImpl(run);
+ public IPositionedGlyphRunBuffer AllocatePositionedGlyphRun(IGlyphTypeface glyphTypeface, float fontRenderingEmSize, int length)
+ {
+ return new DWPositionedGlyphRunBuffer(glyphTypeface, fontRenderingEmSize, length);
}
public bool SupportsIndividualRoundRects => false;
diff --git a/src/Windows/Avalonia.Direct2D1/Media/FontManagerImpl.cs b/src/Windows/Avalonia.Direct2D1/Media/FontManagerImpl.cs
index c996337520..b98ed3ffe6 100644
--- a/src/Windows/Avalonia.Direct2D1/Media/FontManagerImpl.cs
+++ b/src/Windows/Avalonia.Direct2D1/Media/FontManagerImpl.cs
@@ -62,7 +62,7 @@ namespace Avalonia.Direct2D1.Media
return false;
}
- public IGlyphTypefaceImpl CreateGlyphTypeface(Typeface typeface)
+ public IGlyphTypeface CreateGlyphTypeface(Typeface typeface)
{
return new GlyphTypefaceImpl(typeface);
}
diff --git a/src/Windows/Avalonia.Direct2D1/Media/GlyphTypefaceImpl.cs b/src/Windows/Avalonia.Direct2D1/Media/GlyphTypefaceImpl.cs
index 4154b44702..77d0e58d3d 100644
--- a/src/Windows/Avalonia.Direct2D1/Media/GlyphTypefaceImpl.cs
+++ b/src/Windows/Avalonia.Direct2D1/Media/GlyphTypefaceImpl.cs
@@ -1,14 +1,15 @@
using System;
+using System.Drawing.Drawing2D;
using Avalonia.Media;
using Avalonia.Metadata;
-using Avalonia.Platform;
using HarfBuzzSharp;
using SharpDX.DirectWrite;
+using FontMetrics = Avalonia.Media.FontMetrics;
namespace Avalonia.Direct2D1.Media
{
[Unstable]
- public class GlyphTypefaceImpl : IGlyphTypefaceImpl
+ public class GlyphTypefaceImpl : IGlyphTypeface
{
private bool _isDisposed;
@@ -26,40 +27,28 @@ namespace Avalonia.Direct2D1.Media
Font.GetScale(out var xScale, out _);
- DesignEmHeight = (short)xScale;
-
if (!Font.TryGetHorizontalFontExtents(out var fontExtents))
{
Font.TryGetVerticalFontExtents(out fontExtents);
}
- Ascent = -fontExtents.Ascender;
-
- Descent = -fontExtents.Descender;
-
- LineGap = fontExtents.LineGap;
-
- if (Font.OpenTypeMetrics.TryGetPosition(OpenTypeMetricsTag.UnderlineOffset, out var underlinePosition))
- {
- UnderlinePosition = underlinePosition;
- }
-
- if (Font.OpenTypeMetrics.TryGetPosition(OpenTypeMetricsTag.UnderlineSize, out var underlineThickness))
- {
- UnderlineThickness = underlineThickness;
- }
+ Font.OpenTypeMetrics.TryGetPosition(OpenTypeMetricsTag.UnderlineOffset, out var underlinePosition);
+ Font.OpenTypeMetrics.TryGetPosition(OpenTypeMetricsTag.UnderlineSize, out var underlineThickness);
+ Font.OpenTypeMetrics.TryGetPosition(OpenTypeMetricsTag.StrikeoutOffset, out var strikethroughPosition);
+ Font.OpenTypeMetrics.TryGetPosition(OpenTypeMetricsTag.StrikeoutSize, out var strikethroughThickness);
- if (Font.OpenTypeMetrics.TryGetPosition(OpenTypeMetricsTag.StrikeoutOffset, out var strikethroughPosition))
+ Metrics = new FontMetrics
{
- StrikethroughPosition = strikethroughPosition;
- }
-
- if (Font.OpenTypeMetrics.TryGetPosition(OpenTypeMetricsTag.StrikeoutSize, out var strikethroughThickness))
- {
- StrikethroughThickness = strikethroughThickness;
- }
-
- IsFixedPitch = FontFace.IsMonospacedFont;
+ DesignEmHeight = (short)xScale,
+ Ascent = -fontExtents.Ascender,
+ Descent = -fontExtents.Descender,
+ LineGap = fontExtents.LineGap,
+ UnderlinePosition = underlinePosition,
+ UnderlineThickness = underlineThickness,
+ StrikethroughPosition = strikethroughPosition,
+ StrikethroughThickness = strikethroughThickness,
+ IsFixedPitch = FontFace.IsMonospacedFont
+ };
}
private Blob GetTable(Face face, Tag tag)
@@ -89,35 +78,11 @@ namespace Avalonia.Direct2D1.Media
public HarfBuzzSharp.Font Font { get; }
- ///
- public short DesignEmHeight { get; }
-
- ///
- public int Ascent { get; }
-
- ///
- public int Descent { get; }
-
- ///
- public int LineGap { get; }
+ public FontMetrics Metrics { get; }
- //ToDo: Read font table for these values
- ///
- public int UnderlinePosition { get; }
+ public int GlyphCount { get; set; }
- ///
- public int UnderlineThickness { get; }
-
- ///
- public int StrikethroughPosition { get; }
-
- ///
- public int StrikethroughThickness { get; }
-
- ///
- public bool IsFixedPitch { get; }
-
- ///
+ ///
public ushort GetGlyph(uint codepoint)
{
if (Font.TryGetGlyph(codepoint, out var glyph))
@@ -128,7 +93,14 @@ namespace Avalonia.Direct2D1.Media
return 0;
}
- ///
+ public bool TryGetGlyph(uint codepoint, out ushort glyph)
+ {
+ glyph = GetGlyph(codepoint);
+
+ return glyph != 0;
+ }
+
+ ///
public ushort[] GetGlyphs(ReadOnlySpan codepoints)
{
var glyphs = new ushort[codepoints.Length];
@@ -144,13 +116,13 @@ namespace Avalonia.Direct2D1.Media
return glyphs;
}
- ///
+ ///
public int GetGlyphAdvance(ushort glyph)
{
return Font.GetHorizontalGlyphAdvance(glyph);
}
- ///
+ ///
public int[] GetGlyphAdvances(ReadOnlySpan glyphs)
{
var glyphIndices = new uint[glyphs.Length];
@@ -187,6 +159,21 @@ namespace Avalonia.Direct2D1.Media
Dispose(true);
GC.SuppressFinalize(this);
}
+
+ public bool TryGetTable(uint tag, out byte[] table)
+ {
+ table = null;
+ var blob = Face.ReferenceTable(tag);
+
+ if (blob.Length > 0)
+ {
+ table = blob.AsSpan().ToArray();
+
+ return true;
+ }
+
+ return false;
+ }
}
}
diff --git a/src/Windows/Avalonia.Direct2D1/Media/TextShaperImpl.cs b/src/Windows/Avalonia.Direct2D1/Media/TextShaperImpl.cs
index bda36e6c8c..064320f809 100644
--- a/src/Windows/Avalonia.Direct2D1/Media/TextShaperImpl.cs
+++ b/src/Windows/Avalonia.Direct2D1/Media/TextShaperImpl.cs
@@ -31,7 +31,7 @@ namespace Avalonia.Direct2D1.Media
buffer.Language = new Language(culture ?? CultureInfo.CurrentCulture);
- var font = ((GlyphTypefaceImpl)typeface.PlatformImpl).Font;
+ var font = ((GlyphTypefaceImpl)typeface).Font;
font.Shape(buffer);
diff --git a/src/Windows/Avalonia.Win32/Win32Platform.cs b/src/Windows/Avalonia.Win32/Win32Platform.cs
index 29b4baeeb0..3cdc3586dc 100644
--- a/src/Windows/Avalonia.Win32/Win32Platform.cs
+++ b/src/Windows/Avalonia.Win32/Win32Platform.cs
@@ -241,8 +241,8 @@ namespace Avalonia.Win32
});
}
- private static readonly int SignalW = unchecked((int) 0xdeadbeaf);
- private static readonly int SignalL = unchecked((int)0x12345678);
+ private const int SignalW = unchecked((int)0xdeadbeaf);
+ private const int SignalL = unchecked((int)0x12345678);
public void Signal(DispatcherPriority prio)
{
diff --git a/src/iOS/Avalonia.iOS/TextInputResponder.cs b/src/iOS/Avalonia.iOS/TextInputResponder.cs
index feb8ef1bb7..d5723d78f7 100644
--- a/src/iOS/Avalonia.iOS/TextInputResponder.cs
+++ b/src/iOS/Avalonia.iOS/TextInputResponder.cs
@@ -405,7 +405,7 @@ partial class AvaloniaView
// TODO: Query from the input client
Logger.TryGet(LogEventLevel.Debug, ImeLog)?
.Log(null, "IUITextInput:GetSelectionRect");
- return new UITextSelectionRect[0];
+ return Array.Empty();
}
[Export("textStylingAtPosition:inDirection:")]
diff --git a/src/tools/Avalonia.Designer.HostApp/Avalonia.Designer.HostApp.csproj b/src/tools/Avalonia.Designer.HostApp/Avalonia.Designer.HostApp.csproj
index dadef1cb9c..0c0fe5b921 100644
--- a/src/tools/Avalonia.Designer.HostApp/Avalonia.Designer.HostApp.csproj
+++ b/src/tools/Avalonia.Designer.HostApp/Avalonia.Designer.HostApp.csproj
@@ -3,6 +3,10 @@
Exe
net461;netcoreapp2.0
+
+
+ false
+
diff --git a/tests/Avalonia.Base.UnitTests/Media/GlyphRunTests.cs b/tests/Avalonia.Base.UnitTests/Media/GlyphRunTests.cs
index cd0586233f..df16c1b34f 100644
--- a/tests/Avalonia.Base.UnitTests/Media/GlyphRunTests.cs
+++ b/tests/Avalonia.Base.UnitTests/Media/GlyphRunTests.cs
@@ -185,7 +185,7 @@ namespace Avalonia.Base.UnitTests.Media
var characters = new ReadOnlySlice(Enumerable.Repeat('a', count).ToArray(), start, count);
- return new GlyphRun(new GlyphTypeface(new MockGlyphTypeface()), 10, characters, glyphIndices, glyphAdvances,
+ return new GlyphRun(new MockGlyphTypeface(), 10, characters, glyphIndices, glyphAdvances,
glyphClusters: glyphClusters, biDiLevel: bidiLevel);
}
}
diff --git a/tests/Avalonia.Base.UnitTests/VisualTree/MockRenderInterface.cs b/tests/Avalonia.Base.UnitTests/VisualTree/MockRenderInterface.cs
index 1f0b82b465..5f8eb45f71 100644
--- a/tests/Avalonia.Base.UnitTests/VisualTree/MockRenderInterface.cs
+++ b/tests/Avalonia.Base.UnitTests/VisualTree/MockRenderInterface.cs
@@ -126,6 +126,21 @@ namespace Avalonia.Base.UnitTests.VisualTree
throw new NotImplementedException();
}
+ public IGlyphRunBuffer AllocateGlyphRun(IGlyphTypeface glyphTypeface, float fontRenderingEmSize, int length)
+ {
+ throw new NotImplementedException();
+ }
+
+ public IHorizontalGlyphRunBuffer AllocateHorizontalGlyphRun(IGlyphTypeface glyphTypeface, float fontRenderingEmSize, int length)
+ {
+ throw new NotImplementedException();
+ }
+
+ public IPositionedGlyphRunBuffer AllocatePositionedGlyphRun(IGlyphTypeface glyphTypeface, float fontRenderingEmSize, int length)
+ {
+ throw new NotImplementedException();
+ }
+
class MockStreamGeometry : IStreamGeometryImpl
{
private MockStreamGeometryContext _impl = new MockStreamGeometryContext();
@@ -235,7 +250,7 @@ namespace Avalonia.Base.UnitTests.VisualTree
public bool FillContains(Point point)
{
- // Use the algorithm from http://www.blackpawn.com/texts/pointinpoly/default.html
+ // Use the algorithm from https://www.blackpawn.com/texts/pointinpoly/default.html
// to determine if the point is in the geometry (since it will always be convex in this situation)
for (int i = 0; i < points.Count; i++)
{
diff --git a/tests/Avalonia.Benchmarks/NullRenderingPlatform.cs b/tests/Avalonia.Benchmarks/NullRenderingPlatform.cs
index 0193f5d772..34f0dfef11 100644
--- a/tests/Avalonia.Benchmarks/NullRenderingPlatform.cs
+++ b/tests/Avalonia.Benchmarks/NullRenderingPlatform.cs
@@ -112,12 +112,22 @@ namespace Avalonia.Benchmarks
return new MockFontManagerImpl();
}
- public IGlyphRunImpl CreateGlyphRun(GlyphRun glyphRun)
+ public IGeometryImpl BuildGlyphRunGeometry(GlyphRun glyphRun)
{
- return new NullGlyphRun();
+ return new MockStreamGeometryImpl();
}
- public IGeometryImpl BuildGlyphRunGeometry(GlyphRun glyphRun)
+ public IGlyphRunBuffer AllocateGlyphRun(IGlyphTypeface glyphTypeface, float fontRenderingEmSize, int length)
+ {
+ throw new NotImplementedException();
+ }
+
+ public IHorizontalGlyphRunBuffer AllocateHorizontalGlyphRun(IGlyphTypeface glyphTypeface, float fontRenderingEmSize, int length)
+ {
+ throw new NotImplementedException();
+ }
+
+ public IPositionedGlyphRunBuffer AllocatePositionedGlyphRun(IGlyphTypeface glyphTypeface, float fontRenderingEmSize, int length)
{
throw new NotImplementedException();
}
diff --git a/tests/Avalonia.Skia.UnitTests/Media/CustomFontManagerImpl.cs b/tests/Avalonia.Skia.UnitTests/Media/CustomFontManagerImpl.cs
index 7d0cf05b0b..7998c95877 100644
--- a/tests/Avalonia.Skia.UnitTests/Media/CustomFontManagerImpl.cs
+++ b/tests/Avalonia.Skia.UnitTests/Media/CustomFontManagerImpl.cs
@@ -64,7 +64,7 @@ namespace Avalonia.Skia.UnitTests.Media
return true;
}
- public IGlyphTypefaceImpl CreateGlyphTypeface(Typeface typeface)
+ public IGlyphTypeface CreateGlyphTypeface(Typeface typeface)
{
SKTypeface skTypeface;
diff --git a/tests/Avalonia.Skia.UnitTests/Media/TextFormatting/TextFormatterTests.cs b/tests/Avalonia.Skia.UnitTests/Media/TextFormatting/TextFormatterTests.cs
index 960c409058..a315158e1b 100644
--- a/tests/Avalonia.Skia.UnitTests/Media/TextFormatting/TextFormatterTests.cs
+++ b/tests/Avalonia.Skia.UnitTests/Media/TextFormatting/TextFormatterTests.cs
@@ -237,7 +237,7 @@ namespace Avalonia.Skia.UnitTests.Media.TextFormatting
var glyph = typeface.GlyphTypeface.GetGlyph('a');
var advance = typeface.GlyphTypeface.GetGlyphAdvance(glyph) *
- (12.0 / typeface.GlyphTypeface.DesignEmHeight);
+ (12.0 / typeface.GlyphTypeface.Metrics.DesignEmHeight);
var paragraphWidth = advance * numberOfCharactersPerLine;
diff --git a/tests/Avalonia.Skia.UnitTests/Media/TextFormatting/TextLayoutTests.cs b/tests/Avalonia.Skia.UnitTests/Media/TextFormatting/TextLayoutTests.cs
index e21986d3a2..d6da2c77c4 100644
--- a/tests/Avalonia.Skia.UnitTests/Media/TextFormatting/TextLayoutTests.cs
+++ b/tests/Avalonia.Skia.UnitTests/Media/TextFormatting/TextLayoutTests.cs
@@ -578,9 +578,9 @@ namespace Avalonia.Skia.UnitTests.Media.TextFormatting
{
var glyphTypeface = Typeface.Default.GlyphTypeface;
- var emHeight = glyphTypeface.DesignEmHeight;
+ var emHeight = glyphTypeface.Metrics.DesignEmHeight;
- var lineHeight = (glyphTypeface.Descent - glyphTypeface.Ascent) * (12.0 / emHeight);
+ var lineHeight = glyphTypeface.Metrics.LineSpacing * (12.0 / emHeight);
var layout = new TextLayout(
text,
diff --git a/tests/Avalonia.Skia.UnitTests/Media/TextFormatting/TextLineTests.cs b/tests/Avalonia.Skia.UnitTests/Media/TextFormatting/TextLineTests.cs
index 251c850fc8..33c18c5064 100644
--- a/tests/Avalonia.Skia.UnitTests/Media/TextFormatting/TextLineTests.cs
+++ b/tests/Avalonia.Skia.UnitTests/Media/TextFormatting/TextLineTests.cs
@@ -12,7 +12,7 @@ namespace Avalonia.Skia.UnitTests.Media.TextFormatting
{
public class TextLineTests
{
- private static readonly string s_multiLineText = "012345678\r\r0123456789";
+ private const string s_multiLineText = "012345678\r\r0123456789";
[Fact]
public void Should_Get_First_CharacterHit()
diff --git a/tests/Avalonia.UnitTests/HarfBuzzFontManagerImpl.cs b/tests/Avalonia.UnitTests/HarfBuzzFontManagerImpl.cs
index 9f958bdcdd..2b1685f358 100644
--- a/tests/Avalonia.UnitTests/HarfBuzzFontManagerImpl.cs
+++ b/tests/Avalonia.UnitTests/HarfBuzzFontManagerImpl.cs
@@ -58,7 +58,7 @@ namespace Avalonia.UnitTests
return false;
}
- public IGlyphTypefaceImpl CreateGlyphTypeface(Typeface typeface)
+ public IGlyphTypeface CreateGlyphTypeface(Typeface typeface)
{
var fontFamily = typeface.FontFamily;
diff --git a/tests/Avalonia.UnitTests/HarfBuzzGlyphTypefaceImpl.cs b/tests/Avalonia.UnitTests/HarfBuzzGlyphTypefaceImpl.cs
index 32e0434cd4..3bcbc2efbd 100644
--- a/tests/Avalonia.UnitTests/HarfBuzzGlyphTypefaceImpl.cs
+++ b/tests/Avalonia.UnitTests/HarfBuzzGlyphTypefaceImpl.cs
@@ -1,11 +1,11 @@
using System;
using System.IO;
-using Avalonia.Platform;
+using Avalonia.Media;
using HarfBuzzSharp;
namespace Avalonia.UnitTests
{
- public class HarfBuzzGlyphTypefaceImpl : IGlyphTypefaceImpl
+ public class HarfBuzzGlyphTypefaceImpl : IGlyphTypeface
{
private bool _isDisposed;
private Blob _blob;
@@ -21,70 +21,49 @@ namespace Avalonia.UnitTests
Font.SetFunctionsOpenType();
Font.GetScale(out var scale, out _);
-
- DesignEmHeight = (short)scale;
-
- var metrics = Font.OpenTypeMetrics;
const double defaultFontRenderingEmSize = 12.0;
- Ascent = (int)(metrics.GetXVariation(OpenTypeMetricsTag.HorizontalAscender) / defaultFontRenderingEmSize * DesignEmHeight);
+ var metrics = Font.OpenTypeMetrics;
- Descent = (int)(metrics.GetXVariation(OpenTypeMetricsTag.HorizontalDescender) / defaultFontRenderingEmSize * DesignEmHeight);
+ Metrics = new FontMetrics
+ {
+ DesignEmHeight = (short)scale,
+ Ascent = (int)(metrics.GetXVariation(OpenTypeMetricsTag.HorizontalAscender) / defaultFontRenderingEmSize * scale),
+ Descent = (int)(metrics.GetXVariation(OpenTypeMetricsTag.HorizontalDescender) / defaultFontRenderingEmSize * scale),
+ LineGap = (int)(metrics.GetXVariation(OpenTypeMetricsTag.HorizontalLineGap) / defaultFontRenderingEmSize * scale),
- LineGap = (int)(metrics.GetXVariation(OpenTypeMetricsTag.HorizontalLineGap) / defaultFontRenderingEmSize * DesignEmHeight);
+ UnderlinePosition = (int)(metrics.GetXVariation(OpenTypeMetricsTag.UnderlineOffset) / defaultFontRenderingEmSize * scale),
- UnderlinePosition = (int)(metrics.GetXVariation(OpenTypeMetricsTag.UnderlineOffset) / defaultFontRenderingEmSize * DesignEmHeight);
+ UnderlineThickness = (int)(metrics.GetXVariation(OpenTypeMetricsTag.UnderlineSize) / defaultFontRenderingEmSize * scale),
- UnderlineThickness = (int)(metrics.GetXVariation(OpenTypeMetricsTag.UnderlineSize) / defaultFontRenderingEmSize * DesignEmHeight);
+ StrikethroughPosition = (int)(metrics.GetXVariation(OpenTypeMetricsTag.StrikeoutOffset) / defaultFontRenderingEmSize * scale),
- StrikethroughPosition = (int)(metrics.GetXVariation(OpenTypeMetricsTag.StrikeoutOffset) / defaultFontRenderingEmSize * DesignEmHeight);
+ StrikethroughThickness = (int)(metrics.GetXVariation(OpenTypeMetricsTag.StrikeoutSize) / defaultFontRenderingEmSize * scale),
- StrikethroughThickness = (int)(metrics.GetXVariation(OpenTypeMetricsTag.StrikeoutSize) / defaultFontRenderingEmSize * DesignEmHeight);
+ IsFixedPitch = GetGlyphAdvance(GetGlyph('a')) == GetGlyphAdvance(GetGlyph('b'))
+ };
- IsFixedPitch = GetGlyphAdvance(GetGlyph('a')) == GetGlyphAdvance(GetGlyph('b'));
+ GlyphCount = Face.GlyphCount;
IsFakeBold = isFakeBold;
IsFakeItalic = isFakeItalic;
}
+ public FontMetrics Metrics { get; }
+
public Face Face { get; }
public Font Font { get; }
- ///
- public short DesignEmHeight { get; }
-
- ///
- public int Ascent { get; }
-
- ///
- public int Descent { get; }
-
- ///
- public int LineGap { get; }
-
- ///
- public int UnderlinePosition { get; }
-
- ///
- public int UnderlineThickness { get; }
-
- ///
- public int StrikethroughPosition { get; }
-
- ///
- public int StrikethroughThickness { get; }
-
- ///
- public bool IsFixedPitch { get; }
+ public int GlyphCount { get; set; }
public bool IsFakeBold { get; }
public bool IsFakeItalic { get; }
- ///
+ ///
public ushort GetGlyph(uint codepoint)
{
if (Font.TryGetGlyph(codepoint, out var glyph))
@@ -95,7 +74,21 @@ namespace Avalonia.UnitTests
return 0;
}
- ///
+ public bool TryGetGlyph(uint codepoint,out ushort glyph)
+ {
+ glyph = 0;
+
+ if (Font.TryGetGlyph(codepoint, out var glyphId))
+ {
+ glyph = (ushort)glyphId;
+
+ return true;
+ }
+
+ return false;
+ }
+
+ ///
public ushort[] GetGlyphs(ReadOnlySpan codepoints)
{
var glyphs = new ushort[codepoints.Length];
@@ -111,13 +104,13 @@ namespace Avalonia.UnitTests
return glyphs;
}
- ///
+ ///
public int GetGlyphAdvance(ushort glyph)
{
return Font.GetHorizontalGlyphAdvance(glyph);
}
- ///
+ ///
public int[] GetGlyphAdvances(ReadOnlySpan glyphs)
{
var glyphIndices = new uint[glyphs.Length];
@@ -130,6 +123,21 @@ namespace Avalonia.UnitTests
return Font.GetHorizontalGlyphAdvances(glyphIndices);
}
+ public bool TryGetTable(uint tag, out byte[] table)
+ {
+ table = null;
+ var blob = Face.ReferenceTable(tag);
+
+ if (blob.Length > 0)
+ {
+ table = blob.AsSpan().ToArray();
+
+ return true;
+ }
+
+ return false;
+ }
+
private void Dispose(bool disposing)
{
if (_isDisposed)
diff --git a/tests/Avalonia.UnitTests/HarfBuzzTextShaperImpl.cs b/tests/Avalonia.UnitTests/HarfBuzzTextShaperImpl.cs
index 459ae3a549..7b7488bd5a 100644
--- a/tests/Avalonia.UnitTests/HarfBuzzTextShaperImpl.cs
+++ b/tests/Avalonia.UnitTests/HarfBuzzTextShaperImpl.cs
@@ -30,7 +30,7 @@ namespace Avalonia.UnitTests
buffer.Language = new Language(culture ?? CultureInfo.CurrentCulture);
- var font = ((HarfBuzzGlyphTypefaceImpl)typeface.PlatformImpl).Font;
+ var font = ((HarfBuzzGlyphTypefaceImpl)typeface).Font;
font.Shape(buffer);
diff --git a/tests/Avalonia.UnitTests/MockFontManagerImpl.cs b/tests/Avalonia.UnitTests/MockFontManagerImpl.cs
index e2678394df..e9b923a367 100644
--- a/tests/Avalonia.UnitTests/MockFontManagerImpl.cs
+++ b/tests/Avalonia.UnitTests/MockFontManagerImpl.cs
@@ -33,7 +33,7 @@ namespace Avalonia.UnitTests
return false;
}
- public IGlyphTypefaceImpl CreateGlyphTypeface(Typeface typeface)
+ public IGlyphTypeface CreateGlyphTypeface(Typeface typeface)
{
return new MockGlyphTypeface();
}
diff --git a/tests/Avalonia.UnitTests/MockGlyphTypeface.cs b/tests/Avalonia.UnitTests/MockGlyphTypeface.cs
index c9c59a6e32..a1c492a7f1 100644
--- a/tests/Avalonia.UnitTests/MockGlyphTypeface.cs
+++ b/tests/Avalonia.UnitTests/MockGlyphTypeface.cs
@@ -1,19 +1,19 @@
using System;
-using Avalonia.Platform;
+using Avalonia.Media;
namespace Avalonia.UnitTests
{
- public class MockGlyphTypeface : IGlyphTypefaceImpl
+ public class MockGlyphTypeface : IGlyphTypeface
{
- public short DesignEmHeight => 10;
- public int Ascent => 2;
- public int Descent => 10;
- public int LineGap { get; }
- public int UnderlinePosition { get; }
- public int UnderlineThickness { get; }
- public int StrikethroughPosition { get; }
- public int StrikethroughThickness { get; }
- public bool IsFixedPitch { get; }
+ public FontMetrics Metrics => new FontMetrics
+ {
+ DesignEmHeight = 10,
+ Ascent = 2,
+ Descent = 10,
+ IsFixedPitch = true
+ };
+
+ public int GlyphCount => 1337;
public ushort GetGlyph(uint codepoint)
{
@@ -30,6 +30,13 @@ namespace Avalonia.UnitTests
return 8;
}
+ public bool TryGetGlyph(uint codepoint, out ushort glyph)
+ {
+ glyph = 8;
+
+ return true;
+ }
+
public int[] GetGlyphAdvances(ReadOnlySpan glyphs)
{
var advances = new int[glyphs.Length];
@@ -43,5 +50,11 @@ namespace Avalonia.UnitTests
}
public void Dispose() { }
+
+ public bool TryGetTable(uint tag, out byte[] table)
+ {
+ table = null;
+ return false;
+ }
}
}
diff --git a/tests/Avalonia.UnitTests/MockPlatformRenderInterface.cs b/tests/Avalonia.UnitTests/MockPlatformRenderInterface.cs
index f6a127b573..586436ef7f 100644
--- a/tests/Avalonia.UnitTests/MockPlatformRenderInterface.cs
+++ b/tests/Avalonia.UnitTests/MockPlatformRenderInterface.cs
@@ -152,6 +152,21 @@ namespace Avalonia.UnitTests
return Mock.Of();
}
+ public IGlyphRunBuffer AllocateGlyphRun(IGlyphTypeface glyphTypeface, float fontRenderingEmSize, int length)
+ {
+ return Mock.Of();
+ }
+
+ public IHorizontalGlyphRunBuffer AllocateHorizontalGlyphRun(IGlyphTypeface glyphTypeface, float fontRenderingEmSize, int length)
+ {
+ return Mock.Of();
+ }
+
+ public IPositionedGlyphRunBuffer AllocatePositionedGlyphRun(IGlyphTypeface glyphTypeface, float fontRenderingEmSize, int length)
+ {
+ return Mock.Of();
+ }
+
public bool SupportsIndividualRoundRects { get; set; }
public AlphaFormat DefaultAlphaFormat => AlphaFormat.Premul;
diff --git a/tests/Avalonia.UnitTests/MockStreamGeometryImpl.cs b/tests/Avalonia.UnitTests/MockStreamGeometryImpl.cs
index 864e2efbaf..9d039a386e 100644
--- a/tests/Avalonia.UnitTests/MockStreamGeometryImpl.cs
+++ b/tests/Avalonia.UnitTests/MockStreamGeometryImpl.cs
@@ -148,7 +148,7 @@ namespace Avalonia.UnitTests
public bool FillContains(Point point)
{
- // Use the algorithm from http://www.blackpawn.com/texts/pointinpoly/default.html
+ // Use the algorithm from https://www.blackpawn.com/texts/pointinpoly/default.html
// to determine if the point is in the geometry (since it will always be convex in this situation)
for (int i = 0; i < points.Count; i++)
{