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? ItemFilter + { + get => _itemFilter; + set => SetAndRaise(ItemFilterProperty, ref _itemFilter, value); + } + + /// + /// Gets or sets the custom method that uses the user-entered text to + /// filter items specified by the + /// property in a text-based way for display in the drop-down. + /// + /// The custom method that uses the user-entered text to filter + /// items specified by the + /// property in a text-based way for display in the drop-down. + /// + /// The search mode is automatically set to Custom if you set the + /// TextFilter property. + /// + public AutoCompleteFilterPredicate? TextFilter + { + get => _textFilter; + set => SetAndRaise(TextFilterProperty, ref _textFilter, value); + } + + /// + /// Gets or sets the custom method that combines the user-entered + /// text and one of the items specified by the . + /// + /// + /// The custom method that combines the user-entered + /// text and one of the items specified by the . + /// + public AutoCompleteSelector? ItemSelector + { + get => _itemSelector; + set => SetAndRaise(ItemSelectorProperty, ref _itemSelector, value); + } + + /// + /// Gets or sets the custom method that combines the user-entered + /// text and one of the items specified by the + /// in a text-based way. + /// + /// + /// The custom method that combines the user-entered + /// text and one of the items specified by the + /// in a text-based way. + /// + public AutoCompleteSelector? TextSelector + { + get => _textSelector; + set => SetAndRaise(TextSelectorProperty, ref _textSelector, value); + } + + public Func>>? AsyncPopulator + { + get => _asyncPopulator; + set => SetAndRaise(AsyncPopulatorProperty, ref _asyncPopulator, value); + } + + /// + /// Gets or sets a collection that is used to generate the items for the + /// drop-down portion of the control. + /// + /// The collection that is used to generate the items of the + /// drop-down portion of the control. + public IEnumerable? Items + { + get => _itemsEnumerable; + set => SetAndRaise(ItemsProperty, ref _itemsEnumerable, value); + } + } +} diff --git a/src/Avalonia.Controls/AutoCompleteBox.cs b/src/Avalonia.Controls/AutoCompleteBox/AutoCompleteBox.cs similarity index 71% rename from src/Avalonia.Controls/AutoCompleteBox.cs rename to src/Avalonia.Controls/AutoCompleteBox/AutoCompleteBox.cs index c675139831..a027b8b650 100644 --- a/src/Avalonia.Controls/AutoCompleteBox.cs +++ b/src/Avalonia.Controls/AutoCompleteBox/AutoCompleteBox.cs @@ -1,6 +1,6 @@ // (c) Copyright Microsoft Corporation. // This source is subject to the Microsoft Public License (Ms-PL). -// Please see http://go.microsoft.com/fwlink/?LinkID=131993 for details. +// Please see https://go.microsoft.com/fwlink/?LinkID=131993 for details. // All other rights reserved. using System; @@ -26,65 +26,6 @@ using Avalonia.VisualTree; namespace Avalonia.Controls { - /// - /// Provides data for the - /// - /// event. - /// - public class PopulatedEventArgs : EventArgs - { - /// - /// Gets the list of possible matches added to the drop-down portion of - /// the - /// control. - /// - /// The list of possible matches added to the - /// . - public IEnumerable Data { get; private set; } - - /// - /// Initializes a new instance of the - /// . - /// - /// The list of possible matches added to the - /// drop-down portion of the - /// control. - public PopulatedEventArgs(IEnumerable data) - { - Data = data; - } - } - - /// - /// Provides data for the - /// - /// event. - /// - public class PopulatingEventArgs : CancelEventArgs - { - /// - /// Gets the text that is used to determine which items to display in - /// the - /// control. - /// - /// The text that is used to determine which items to display in - /// the . - public string? Parameter { get; private set; } - - /// - /// Initializes a new instance of the - /// . - /// - /// The value of the - /// - /// property, which is used to filter items for the - /// control. - public PopulatingEventArgs(string? parameter) - { - Parameter = parameter; - } - } - /// /// Represents the filter used by the /// control to @@ -100,132 +41,6 @@ namespace Avalonia.Controls /// be either a string or an object. public delegate bool AutoCompleteFilterPredicate(string? search, T item); - /// - /// Specifies how text in the text box portion of the - /// control is used - /// to filter items specified by the - /// - /// property for display in the drop-down. - /// - public enum AutoCompleteFilterMode - { - /// - /// Specifies that no filter is used. All items are returned. - /// - None = 0, - - /// - /// Specifies a culture-sensitive, case-insensitive filter where the - /// returned items start with the specified text. The filter uses the - /// - /// method, specifying - /// as - /// the string comparison criteria. - /// - StartsWith = 1, - - /// - /// Specifies a culture-sensitive, case-sensitive filter where the - /// returned items start with the specified text. The filter uses the - /// - /// method, specifying - /// as the string - /// comparison criteria. - /// - StartsWithCaseSensitive = 2, - - /// - /// Specifies an ordinal, case-insensitive filter where the returned - /// items start with the specified text. The filter uses the - /// - /// method, specifying - /// as the - /// string comparison criteria. - /// - StartsWithOrdinal = 3, - - /// - /// Specifies an ordinal, case-sensitive filter where the returned items - /// start with the specified text. The filter uses the - /// - /// method, specifying as - /// the string comparison criteria. - /// - StartsWithOrdinalCaseSensitive = 4, - - /// - /// Specifies a culture-sensitive, case-insensitive filter where the - /// returned items contain the specified text. - /// - Contains = 5, - - /// - /// Specifies a culture-sensitive, case-sensitive filter where the - /// returned items contain the specified text. - /// - ContainsCaseSensitive = 6, - - /// - /// Specifies an ordinal, case-insensitive filter where the returned - /// items contain the specified text. - /// - ContainsOrdinal = 7, - - /// - /// Specifies an ordinal, case-sensitive filter where the returned items - /// contain the specified text. - /// - ContainsOrdinalCaseSensitive = 8, - - /// - /// Specifies a culture-sensitive, case-insensitive filter where the - /// returned items equal the specified text. The filter uses the - /// - /// method, specifying - /// as - /// the search comparison criteria. - /// - Equals = 9, - - /// - /// Specifies a culture-sensitive, case-sensitive filter where the - /// returned items equal the specified text. The filter uses the - /// - /// method, specifying - /// as the string - /// comparison criteria. - /// - EqualsCaseSensitive = 10, - - /// - /// Specifies an ordinal, case-insensitive filter where the returned - /// items equal the specified text. The filter uses the - /// - /// method, specifying - /// as the - /// string comparison criteria. - /// - EqualsOrdinal = 11, - - /// - /// Specifies an ordinal, case-sensitive filter where the returned items - /// equal the specified text. The filter uses the - /// - /// method, specifying as - /// the string comparison criteria. - /// - EqualsOrdinalCaseSensitive = 12, - - /// - /// Specifies that a custom filter is used. This mode is used when the - /// - /// or - /// - /// properties are set. - /// - Custom = 13, - } - /// /// Represents the selector used by the /// control to @@ -257,7 +72,7 @@ namespace Avalonia.Controls [TemplatePart(ElementSelectionAdapter, typeof(ISelectionAdapter))] [TemplatePart(ElementTextBox, typeof(TextBox))] [PseudoClasses(":dropdownopen")] - public class AutoCompleteBox : TemplatedControl + public partial class AutoCompleteBox : TemplatedControl { /// /// Specifies the name of the selection adapter TemplatePart. @@ -394,221 +209,22 @@ namespace Avalonia.Controls private readonly EventHandler _populateDropDownHandler; - public static readonly RoutedEvent SelectionChangedEvent = - RoutedEvent.Register(nameof(SelectionChanged), RoutingStrategies.Bubble, typeof(AutoCompleteBox)); - - public static readonly StyledProperty WatermarkProperty = - TextBox.WatermarkProperty.AddOwner(); - - /// - /// Identifies the - /// - /// dependency property. - /// - /// The identifier for the - /// - /// dependency property. - public static readonly StyledProperty MinimumPrefixLengthProperty = - AvaloniaProperty.Register( - nameof(MinimumPrefixLength), 1, - validate: IsValidMinimumPrefixLength); - - /// - /// Identifies the - /// - /// dependency property. - /// - /// The identifier for the - /// - /// dependency property. - public static readonly StyledProperty MinimumPopulateDelayProperty = - AvaloniaProperty.Register( - nameof(MinimumPopulateDelay), - TimeSpan.Zero, - validate: IsValidMinimumPopulateDelay); - /// - /// Identifies the - /// - /// dependency property. + /// /// - /// The identifier for the - /// - /// dependency property. - public static readonly StyledProperty MaxDropDownHeightProperty = - AvaloniaProperty.Register( - nameof(MaxDropDownHeight), - double.PositiveInfinity, - validate: IsValidMaxDropDownHeight); - - /// - /// Identifies the - /// - /// dependency property. - /// - /// The identifier for the - /// - /// dependency property. - public static readonly StyledProperty IsTextCompletionEnabledProperty = - AvaloniaProperty.Register(nameof(IsTextCompletionEnabled)); + public static readonly RoutedEvent SelectionChangedEvent = + RoutedEvent.Register( + nameof(SelectionChanged), + RoutingStrategies.Bubble, + typeof(AutoCompleteBox)); /// - /// Identifies the - /// - /// dependency property. + /// Defines the event. /// - /// The identifier for the - /// - /// dependency property. - public static readonly StyledProperty ItemTemplateProperty = - AvaloniaProperty.Register(nameof(ItemTemplate)); - - /// - /// Identifies the - /// - /// dependency property. - /// - /// The identifier for the - /// - /// dependency property. - public static readonly DirectProperty IsDropDownOpenProperty = - AvaloniaProperty.RegisterDirect( - nameof(IsDropDownOpen), - o => o.IsDropDownOpen, - (o, v) => o.IsDropDownOpen = v); - - /// - /// Identifies the - /// - /// dependency property. - /// - /// The identifier the - /// - /// dependency 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 - /// - /// dependency property. - /// - /// The identifier for the - /// - /// dependency property. - public static readonly DirectProperty TextProperty = - TextBlock.TextProperty.AddOwnerWithDataValidation( - o => o.Text, - (o, v) => o.Text = v, - defaultBindingMode: BindingMode.TwoWay, - enableDataValidation: true); - - /// - /// Identifies the - /// - /// dependency property. - /// - /// The identifier for the - /// - /// dependency property. - public static readonly DirectProperty SearchTextProperty = - AvaloniaProperty.RegisterDirect( - nameof(SearchText), - o => o.SearchText, - unsetValue: string.Empty); - - /// - /// Gets the identifier for the - /// - /// dependency property. - /// - public static readonly StyledProperty FilterModeProperty = - AvaloniaProperty.Register( - nameof(FilterMode), - defaultValue: AutoCompleteFilterMode.StartsWith, - validate: IsValidFilterMode); - - /// - /// Identifies the - /// - /// dependency property. - /// - /// The identifier for the - /// - /// dependency property. - public static readonly DirectProperty?> ItemFilterProperty = - AvaloniaProperty.RegisterDirect?>( - nameof(ItemFilter), - o => o.ItemFilter, - (o, v) => o.ItemFilter = v); - - /// - /// Identifies the - /// - /// dependency property. - /// - /// The identifier for the - /// - /// dependency 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 - /// - /// dependency property. - /// - /// The identifier for the - /// - /// dependency property. - public static readonly DirectProperty?> ItemSelectorProperty = - AvaloniaProperty.RegisterDirect?>( - nameof(ItemSelector), - o => o.ItemSelector, - (o, v) => o.ItemSelector = v); - - /// - /// Identifies the - /// - /// dependency property. - /// - /// The identifier for the - /// - /// dependency property. - public static readonly DirectProperty?> TextSelectorProperty = - AvaloniaProperty.RegisterDirect?>( - nameof(TextSelector), - o => o.TextSelector, - (o, v) => o.TextSelector = v); - - /// - /// Identifies the - /// - /// dependency property. - /// - /// The identifier for the - /// - /// dependency 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); + public static readonly RoutedEvent TextChangedEvent = + RoutedEvent.Register( + nameof(TextChanged), + RoutingStrategies.Bubble); private static bool IsValidMinimumPrefixLength(int value) => value >= -1; @@ -871,315 +487,6 @@ namespace Avalonia.Controls ClearView(); } - /// - /// Gets or sets the minimum number of characters required to be entered - /// in the text box before the - /// displays - /// possible matches. - /// 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 { return 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 { return 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 { return 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 { return 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 { return 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 { return _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 { return _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 { return _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 { return _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 { return _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 { return GetValue(FilterModeProperty); } - set { SetValue(FilterModeProperty, value); } - } - - public string? Watermark - { - get { return 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? ItemFilter - { - get { return _itemFilter; } - set { SetAndRaise(ItemFilterProperty, ref _itemFilter, value); } - } - - /// - /// Gets or sets the custom method that uses the user-entered text to - /// filter items specified by the - /// - /// property in a text-based way for display in the drop-down. - /// - /// The custom method that uses the user-entered text to filter - /// items specified by the - /// - /// property in a text-based way for display in the drop-down. - /// - /// The search mode is automatically set to Custom if you set the - /// TextFilter property. - /// - public AutoCompleteFilterPredicate? TextFilter - { - get { return _textFilter; } - set { SetAndRaise(TextFilterProperty, ref _textFilter, value); } - } - - /// - /// Gets or sets the custom method that combines the user-entered - /// text and one of the items specified by the - /// . - /// - /// - /// The custom method that combines the user-entered - /// text and one of the items specified by the - /// . - /// - public AutoCompleteSelector? ItemSelector - { - get { return _itemSelector; } - set { SetAndRaise(ItemSelectorProperty, ref _itemSelector, value); } - } - - /// - /// Gets or sets the custom method that combines the user-entered - /// text and one of the items specified by the - /// - /// in a text-based way. - /// - /// - /// The custom method that combines the user-entered - /// text and one of the items specified by the - /// - /// in a text-based way. - /// - public AutoCompleteSelector? TextSelector - { - get { return _textSelector; } - set { SetAndRaise(TextSelectorProperty, ref _textSelector, value); } - } - - public Func>>? AsyncPopulator - { - get { return _asyncPopulator; } - set { SetAndRaise(AsyncPopulatorProperty, ref _asyncPopulator, value); } - } - - /// - /// Gets or sets a collection that is used to generate the items for the - /// drop-down portion of the - /// control. - /// - /// The collection that is used to generate the items of the - /// drop-down portion of the - /// control. - public IEnumerable? Items - { - get { return _itemsEnumerable; } - set { SetAndRaise(ItemsProperty, ref _itemsEnumerable, value); } - } - /// /// Gets or sets the drop down popup control. /// @@ -1190,7 +497,7 @@ namespace Avalonia.Controls /// private TextBox? TextBox { - get { return _textBox; } + get => _textBox; set { _textBoxSubscriptions?.Dispose(); @@ -1254,7 +561,7 @@ namespace Avalonia.Controls /// protected ISelectionAdapter? SelectionAdapter { - get { return _adapter; } + get => _adapter; set { if (_adapter != null) @@ -1529,10 +836,14 @@ namespace Avalonia.Controls } /// - /// Occurs when the text in the text box portion of the - /// changes. + /// Occurs asynchronously when the text in the portion of the + /// changes. /// - public event EventHandler? TextChanged; + public event EventHandler? TextChanged + { + add => AddHandler(TextChangedEvent, value); + remove => RemoveHandler(TextChangedEvent, value); + } /// /// Occurs when the @@ -1690,15 +1001,12 @@ namespace Avalonia.Controls } /// - /// Raises the - /// - /// event. + /// Raises the event. /// - /// A - /// that contains the event data. - protected virtual void OnTextChanged(RoutedEventArgs e) + /// A that contains the event data. + protected virtual void OnTextChanged(TextChangedEventArgs e) { - TextChanged?.Invoke(this, e); + RaiseEvent(e); } /// @@ -1985,7 +1293,7 @@ namespace Avalonia.Controls if (callTextChanged) { - OnTextChanged(new RoutedEventArgs()); + OnTextChanged(new TextChangedEventArgs(TextChangedEvent)); } } @@ -2740,8 +2048,6 @@ namespace Avalonia.Controls /// private IBinding? _binding; - #region public T Value - /// /// Identifies the Value dependency property. /// @@ -2753,18 +2059,16 @@ namespace Avalonia.Controls /// public T Value { - get { return GetValue(ValueProperty); } - set { SetValue(ValueProperty, value); } + get => GetValue(ValueProperty); + set => SetValue(ValueProperty, value); } - #endregion public string Value - /// /// Gets or sets the value binding. /// public IBinding? ValueBinding { - get { return _binding; } + get => _binding; set { _binding = value; diff --git a/src/Avalonia.Controls/AutoCompleteBox/AutoCompleteFilterMode.cs b/src/Avalonia.Controls/AutoCompleteBox/AutoCompleteFilterMode.cs new file mode 100644 index 0000000000..c17f5a19ab --- /dev/null +++ b/src/Avalonia.Controls/AutoCompleteBox/AutoCompleteFilterMode.cs @@ -0,0 +1,131 @@ +// (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; + +namespace Avalonia.Controls +{ + /// + /// Specifies how text in the text box portion of the + /// control is used to filter items specified by the + /// property for display in the drop-down. + /// + public enum AutoCompleteFilterMode + { + /// + /// Specifies that no filter is used. All items are returned. + /// + None = 0, + + /// + /// Specifies a culture-sensitive, case-insensitive filter where the + /// returned items start with the specified text. The filter uses the + /// + /// method, specifying + /// as + /// the string comparison criteria. + /// + StartsWith = 1, + + /// + /// Specifies a culture-sensitive, case-sensitive filter where the + /// returned items start with the specified text. The filter uses the + /// + /// method, specifying + /// as the string + /// comparison criteria. + /// + StartsWithCaseSensitive = 2, + + /// + /// Specifies an ordinal, case-insensitive filter where the returned + /// items start with the specified text. The filter uses the + /// + /// method, specifying + /// as the + /// string comparison criteria. + /// + StartsWithOrdinal = 3, + + /// + /// Specifies an ordinal, case-sensitive filter where the returned items + /// start with the specified text. The filter uses the + /// + /// method, specifying as + /// the string comparison criteria. + /// + StartsWithOrdinalCaseSensitive = 4, + + /// + /// Specifies a culture-sensitive, case-insensitive filter where the + /// returned items contain the specified text. + /// + Contains = 5, + + /// + /// Specifies a culture-sensitive, case-sensitive filter where the + /// returned items contain the specified text. + /// + ContainsCaseSensitive = 6, + + /// + /// Specifies an ordinal, case-insensitive filter where the returned + /// items contain the specified text. + /// + ContainsOrdinal = 7, + + /// + /// Specifies an ordinal, case-sensitive filter where the returned items + /// contain the specified text. + /// + ContainsOrdinalCaseSensitive = 8, + + /// + /// Specifies a culture-sensitive, case-insensitive filter where the + /// returned items equal the specified text. The filter uses the + /// + /// method, specifying + /// as + /// the search comparison criteria. + /// + Equals = 9, + + /// + /// Specifies a culture-sensitive, case-sensitive filter where the + /// returned items equal the specified text. The filter uses the + /// + /// method, specifying + /// as the string + /// comparison criteria. + /// + EqualsCaseSensitive = 10, + + /// + /// Specifies an ordinal, case-insensitive filter where the returned + /// items equal the specified text. The filter uses the + /// + /// method, specifying + /// as the + /// string comparison criteria. + /// + EqualsOrdinal = 11, + + /// + /// Specifies an ordinal, case-sensitive filter where the returned items + /// equal the specified text. The filter uses the + /// + /// method, specifying as + /// the string comparison criteria. + /// + EqualsOrdinalCaseSensitive = 12, + + /// + /// Specifies that a custom filter is used. This mode is used when the + /// or + /// properties are set. + /// + Custom = 13, + } +} diff --git a/src/Avalonia.Controls/AutoCompleteBox/PopulatedEventArgs.cs b/src/Avalonia.Controls/AutoCompleteBox/PopulatedEventArgs.cs new file mode 100644 index 0000000000..22bc1d3cab --- /dev/null +++ b/src/Avalonia.Controls/AutoCompleteBox/PopulatedEventArgs.cs @@ -0,0 +1,39 @@ +// (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; + +namespace Avalonia.Controls +{ + /// + /// Provides data for the + /// + /// event. + /// + public class PopulatedEventArgs : EventArgs + { + /// + /// Gets the list of possible matches added to the drop-down portion of + /// the + /// control. + /// + /// The list of possible matches added to the + /// . + public IEnumerable Data { get; private set; } + + /// + /// Initializes a new instance of the + /// . + /// + /// The list of possible matches added to the + /// drop-down portion of the + /// control. + public PopulatedEventArgs(IEnumerable data) + { + Data = data; + } + } +} diff --git a/src/Avalonia.Controls/AutoCompleteBox/PopulatingEventArgs.cs b/src/Avalonia.Controls/AutoCompleteBox/PopulatingEventArgs.cs new file mode 100644 index 0000000000..c4941ad6fe --- /dev/null +++ b/src/Avalonia.Controls/AutoCompleteBox/PopulatingEventArgs.cs @@ -0,0 +1,39 @@ +// (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.ComponentModel; + +namespace Avalonia.Controls +{ + /// + /// Provides data for the + /// + /// event. + /// + public class PopulatingEventArgs : CancelEventArgs + { + /// + /// Gets the text that is used to determine which items to display in + /// the + /// control. + /// + /// The text that is used to determine which items to display in + /// the . + public string? Parameter { get; private set; } + + /// + /// Initializes a new instance of the + /// . + /// + /// The value of the + /// + /// property, which is used to filter items for the + /// control. + public PopulatingEventArgs(string? parameter) + { + Parameter = parameter; + } + } +} diff --git a/src/Avalonia.Controls/Calendar/Calendar.cs b/src/Avalonia.Controls/Calendar/Calendar.cs index 0de068a416..9c88bae5f6 100644 --- a/src/Avalonia.Controls/Calendar/Calendar.cs +++ b/src/Avalonia.Controls/Calendar/Calendar.cs @@ -1,6 +1,6 @@ // (c) Copyright Microsoft Corporation. // This source is subject to the Microsoft Public License (Ms-PL). -// Please see http://go.microsoft.com/fwlink/?LinkID=131993 for details. +// Please see https://go.microsoft.com/fwlink/?LinkID=131993 for details. // All other rights reserved. using System; diff --git a/src/Avalonia.Controls/Calendar/CalendarBlackoutDatesCollection.cs b/src/Avalonia.Controls/Calendar/CalendarBlackoutDatesCollection.cs index 5d883f2d14..a92feec509 100644 --- a/src/Avalonia.Controls/Calendar/CalendarBlackoutDatesCollection.cs +++ b/src/Avalonia.Controls/Calendar/CalendarBlackoutDatesCollection.cs @@ -1,6 +1,6 @@ // (c) Copyright Microsoft Corporation. // This source is subject to the Microsoft Public License (Ms-PL). -// Please see http://go.microsoft.com/fwlink/?LinkID=131993 for details. +// Please see https://go.microsoft.com/fwlink/?LinkID=131993 for details. // All other rights reserved. using System; diff --git a/src/Avalonia.Controls/Calendar/CalendarButton.cs b/src/Avalonia.Controls/Calendar/CalendarButton.cs index 0a8e4dfae8..d8672cbf18 100644 --- a/src/Avalonia.Controls/Calendar/CalendarButton.cs +++ b/src/Avalonia.Controls/Calendar/CalendarButton.cs @@ -1,6 +1,6 @@ // (c) Copyright Microsoft Corporation. // This source is subject to the Microsoft Public License (Ms-PL). -// Please see http://go.microsoft.com/fwlink/?LinkID=131993 for details. +// Please see https://go.microsoft.com/fwlink/?LinkID=131993 for details. // All other rights reserved. using Avalonia.Controls.Metadata; diff --git a/src/Avalonia.Controls/Calendar/CalendarDateRange.cs b/src/Avalonia.Controls/Calendar/CalendarDateRange.cs index 88bc5ed7bd..793ef7a2ee 100644 --- a/src/Avalonia.Controls/Calendar/CalendarDateRange.cs +++ b/src/Avalonia.Controls/Calendar/CalendarDateRange.cs @@ -1,6 +1,6 @@ // (c) Copyright Microsoft Corporation. // This source is subject to the Microsoft Public License (Ms-PL). -// Please see http://go.microsoft.com/fwlink/?LinkID=131993 for details. +// Please see https://go.microsoft.com/fwlink/?LinkID=131993 for details. // All other rights reserved. using System; diff --git a/src/Avalonia.Controls/Calendar/CalendarDayButton.cs b/src/Avalonia.Controls/Calendar/CalendarDayButton.cs index 2ba4e36260..3d0befdba7 100644 --- a/src/Avalonia.Controls/Calendar/CalendarDayButton.cs +++ b/src/Avalonia.Controls/Calendar/CalendarDayButton.cs @@ -1,6 +1,6 @@ // (c) Copyright Microsoft Corporation. // This source is subject to the Microsoft Public License (Ms-PL). -// Please see http://go.microsoft.com/fwlink/?LinkID=131993 for details. +// Please see https://go.microsoft.com/fwlink/?LinkID=131993 for details. // All other rights reserved. using System; diff --git a/src/Avalonia.Controls/Calendar/CalendarExtensions.cs b/src/Avalonia.Controls/Calendar/CalendarExtensions.cs index 00b5ce10bc..cb3ee06a9e 100644 --- a/src/Avalonia.Controls/Calendar/CalendarExtensions.cs +++ b/src/Avalonia.Controls/Calendar/CalendarExtensions.cs @@ -1,6 +1,6 @@ // (c) Copyright Microsoft Corporation. // This source is subject to the Microsoft Public License (Ms-PL). -// Please see http://go.microsoft.com/fwlink/?LinkID=131993 for details. +// Please see https://go.microsoft.com/fwlink/?LinkID=131993 for details. // All other rights reserved. using Avalonia.Input; diff --git a/src/Avalonia.Controls/Calendar/CalendarItem.cs b/src/Avalonia.Controls/Calendar/CalendarItem.cs index e2eabb5f28..eec3bdc9f2 100644 --- a/src/Avalonia.Controls/Calendar/CalendarItem.cs +++ b/src/Avalonia.Controls/Calendar/CalendarItem.cs @@ -1,6 +1,6 @@ // (c) Copyright Microsoft Corporation. // This source is subject to the Microsoft Public License (Ms-PL). -// Please see http://go.microsoft.com/fwlink/?LinkID=131993 for details. +// Please see https://go.microsoft.com/fwlink/?LinkID=131993 for details. // All other rights reserved. using System; diff --git a/src/Avalonia.Controls/Calendar/DateTimeHelper.cs b/src/Avalonia.Controls/Calendar/DateTimeHelper.cs index 7a5c74a51b..bfff03a926 100644 --- a/src/Avalonia.Controls/Calendar/DateTimeHelper.cs +++ b/src/Avalonia.Controls/Calendar/DateTimeHelper.cs @@ -1,6 +1,6 @@ // (c) Copyright Microsoft Corporation. // This source is subject to the Microsoft Public License (Ms-PL). -// Please see http://go.microsoft.com/fwlink/?LinkID=131993 for details. +// Please see https://go.microsoft.com/fwlink/?LinkID=131993 for details. // All other rights reserved. using System; diff --git a/src/Avalonia.Controls/Calendar/SelectedDatesCollection.cs b/src/Avalonia.Controls/Calendar/SelectedDatesCollection.cs index f4bc2528ba..211b5edb0d 100644 --- a/src/Avalonia.Controls/Calendar/SelectedDatesCollection.cs +++ b/src/Avalonia.Controls/Calendar/SelectedDatesCollection.cs @@ -1,6 +1,6 @@ // (c) Copyright Microsoft Corporation. // This source is subject to the Microsoft Public License (Ms-PL). -// Please see http://go.microsoft.com/fwlink/?LinkID=131993 for details. +// Please see https://go.microsoft.com/fwlink/?LinkID=131993 for details. // All other rights reserved. using Avalonia.Threading; diff --git a/src/Avalonia.Controls/CalendarDatePicker/CalendarDatePicker.cs b/src/Avalonia.Controls/CalendarDatePicker/CalendarDatePicker.cs index 3d592e9ab5..ec1273ca98 100644 --- a/src/Avalonia.Controls/CalendarDatePicker/CalendarDatePicker.cs +++ b/src/Avalonia.Controls/CalendarDatePicker/CalendarDatePicker.cs @@ -1,6 +1,6 @@ // (c) Copyright Microsoft Corporation. // This source is subject to the Microsoft Public License (Ms-PL). -// Please see http://go.microsoft.com/fwlink/?LinkID=131993 for details. +// Please see https://go.microsoft.com/fwlink/?LinkID=131993 for details. // All other rights reserved. using System; diff --git a/src/Avalonia.Controls/CalendarDatePicker/CalendarDatePickerDateValidationErrorEventArgs.cs b/src/Avalonia.Controls/CalendarDatePicker/CalendarDatePickerDateValidationErrorEventArgs.cs index 647910cb6b..b58b549030 100644 --- a/src/Avalonia.Controls/CalendarDatePicker/CalendarDatePickerDateValidationErrorEventArgs.cs +++ b/src/Avalonia.Controls/CalendarDatePicker/CalendarDatePickerDateValidationErrorEventArgs.cs @@ -1,6 +1,6 @@ // (c) Copyright Microsoft Corporation. // This source is subject to the Microsoft Public License (Ms-PL). -// Please see http://go.microsoft.com/fwlink/?LinkID=131993 for details. +// Please see https://go.microsoft.com/fwlink/?LinkID=131993 for details. // All other rights reserved. using System; diff --git a/src/Avalonia.Controls/CalendarDatePicker/CalendarDatePickerFormat.cs b/src/Avalonia.Controls/CalendarDatePicker/CalendarDatePickerFormat.cs index 4d96859d75..ffd1f6f594 100644 --- a/src/Avalonia.Controls/CalendarDatePicker/CalendarDatePickerFormat.cs +++ b/src/Avalonia.Controls/CalendarDatePicker/CalendarDatePickerFormat.cs @@ -1,6 +1,6 @@ // (c) Copyright Microsoft Corporation. // This source is subject to the Microsoft Public License (Ms-PL). -// Please see http://go.microsoft.com/fwlink/?LinkID=131993 for details. +// Please see https://go.microsoft.com/fwlink/?LinkID=131993 for details. // All other rights reserved. namespace Avalonia.Controls diff --git a/src/Avalonia.Controls/DateTimePickers/DatePicker.cs b/src/Avalonia.Controls/DateTimePickers/DatePicker.cs index fb27feb521..bb05cd1b1f 100644 --- a/src/Avalonia.Controls/DateTimePickers/DatePicker.cs +++ b/src/Avalonia.Controls/DateTimePickers/DatePicker.cs @@ -40,18 +40,6 @@ namespace Avalonia.Controls AvaloniaProperty.RegisterDirect(nameof(DayVisible), x => x.DayVisible, (x, v) => x.DayVisible = v); - /// - /// Defines the Property - /// - public static readonly StyledProperty HeaderProperty = - AvaloniaProperty.Register(nameof(Header)); - - /// - /// Defines the Property - /// - public static readonly StyledProperty HeaderTemplateProperty = - AvaloniaProperty.Register(nameof(HeaderTemplate)); - /// /// Defines the Property /// @@ -152,24 +140,6 @@ namespace Avalonia.Controls } } - /// - /// Gets or sets the DatePicker header - /// - public object Header - { - get => GetValue(HeaderProperty); - set => SetValue(HeaderProperty, value); - } - - /// - /// Gets or sets the header template - /// - public IDataTemplate HeaderTemplate - { - get => GetValue(HeaderTemplateProperty); - set => SetValue(HeaderTemplateProperty, value); - } - /// /// Gets or sets the maximum year for the picker /// diff --git a/src/Avalonia.Controls/DateTimePickers/TimePicker.cs b/src/Avalonia.Controls/DateTimePickers/TimePicker.cs index a709e4fb49..228b9ae205 100644 --- a/src/Avalonia.Controls/DateTimePickers/TimePicker.cs +++ b/src/Avalonia.Controls/DateTimePickers/TimePicker.cs @@ -34,18 +34,6 @@ namespace Avalonia.Controls AvaloniaProperty.RegisterDirect(nameof(MinuteIncrement), x => x.MinuteIncrement, (x, v) => x.MinuteIncrement = v); - /// - /// Defines the property - /// - public static readonly StyledProperty HeaderProperty = - AvaloniaProperty.Register(nameof(Header)); - - /// - /// Defines the property - /// - public static readonly StyledProperty HeaderTemplateProperty = - AvaloniaProperty.Register(nameof(HeaderTemplate)); - /// /// Defines the property /// @@ -103,24 +91,6 @@ namespace Avalonia.Controls } } - /// - /// Gets or sets the header - /// - public object Header - { - get => GetValue(HeaderProperty); - set => SetValue(HeaderProperty, value); - } - - /// - /// Gets or sets the header template - /// - public IDataTemplate HeaderTemplate - { - get => GetValue(HeaderTemplateProperty); - set => SetValue(HeaderTemplateProperty, value); - } - /// /// Gets or sets the clock identifier, either 12HourClock or 24HourClock /// diff --git a/src/Avalonia.Controls/Primitives/PopupPositioning/IPopupPositioner.cs b/src/Avalonia.Controls/Primitives/PopupPositioning/IPopupPositioner.cs index d8de813d47..615eb69fe3 100644 --- a/src/Avalonia.Controls/Primitives/PopupPositioning/IPopupPositioner.cs +++ b/src/Avalonia.Controls/Primitives/PopupPositioning/IPopupPositioner.cs @@ -35,7 +35,7 @@ DEALINGS IN THE SOFTWARE. The above is the version of the MIT "Expat" License used by X.org: - http://cgit.freedesktop.org/xorg/xserver/tree/COPYING + https://cgit.freedesktop.org/xorg/xserver/tree/COPYING Adjustments for Avalonia needs: diff --git a/src/Avalonia.Controls/Primitives/RangeBase.cs b/src/Avalonia.Controls/Primitives/RangeBase.cs index acb8e0f006..38d848d69b 100644 --- a/src/Avalonia.Controls/Primitives/RangeBase.cs +++ b/src/Avalonia.Controls/Primitives/RangeBase.cs @@ -175,7 +175,7 @@ namespace Avalonia.Controls.Primitives /// The value. private static bool ValidateDouble(double value) { - return !double.IsInfinity(value) || !double.IsNaN(value); + return !double.IsInfinity(value) && !double.IsNaN(value); } /// diff --git a/src/Avalonia.Controls/TextBox.cs b/src/Avalonia.Controls/TextBox.cs index 964a153c8b..da4e90fb66 100644 --- a/src/Avalonia.Controls/TextBox.cs +++ b/src/Avalonia.Controls/TextBox.cs @@ -18,6 +18,7 @@ using Avalonia.Media.TextFormatting; using Avalonia.Media.TextFormatting.Unicode; using Avalonia.Automation.Peers; using System.Diagnostics; +using Avalonia.Threading; namespace Avalonia.Controls { @@ -159,18 +160,41 @@ namespace Avalonia.Controls (o, v) => o.UndoLimit = v, unsetValue: -1); + /// + /// Defines the event. + /// public static readonly RoutedEvent CopyingToClipboardEvent = RoutedEvent.Register( nameof(CopyingToClipboard), RoutingStrategies.Bubble); + /// + /// Defines the event. + /// public static readonly RoutedEvent CuttingToClipboardEvent = RoutedEvent.Register( nameof(CuttingToClipboard), RoutingStrategies.Bubble); + /// + /// Defines the event. + /// public static readonly RoutedEvent PastingFromClipboardEvent = RoutedEvent.Register( nameof(PastingFromClipboard), RoutingStrategies.Bubble); + /// + /// Defines the event. + /// + public static readonly RoutedEvent TextChangedEvent = + RoutedEvent.Register( + nameof(TextChanged), RoutingStrategies.Bubble); + + /// + /// Defines the event. + /// + public static readonly RoutedEvent TextChangingEvent = + RoutedEvent.Register( + nameof(TextChanging), RoutingStrategies.Bubble); + readonly struct UndoRedoState : IEquatable { public string? Text { get; } @@ -359,8 +383,8 @@ namespace Avalonia.Controls /// public double LineHeight { - get { return GetValue(LineHeightProperty); } - set { SetValue(LineHeightProperty, value); } + get => GetValue(LineHeightProperty); + set => SetValue(LineHeightProperty, value); } [Content] @@ -376,11 +400,19 @@ namespace Avalonia.Controls CaretIndex = CoerceCaretIndex(caretIndex, value); SelectionStart = CoerceCaretIndex(selectionStart, value); SelectionEnd = CoerceCaretIndex(selectionEnd, value); - if (SetAndRaise(TextProperty, ref _text, value) && IsUndoEnabled && !_isUndoingRedoing) + + var textChanged = SetAndRaise(TextProperty, ref _text, value); + + if (textChanged && IsUndoEnabled && !_isUndoingRedoing) { _undoRedoHelper.Clear(); SnapshotUndoRedo(); // so we always have an initial state } + + if (textChanged) + { + RaiseTextChangeEvents(); + } } } @@ -564,6 +596,27 @@ namespace Avalonia.Controls remove => RemoveHandler(PastingFromClipboardEvent, value); } + /// + /// Occurs asynchronously after text changes and the new text is rendered. + /// + public event EventHandler? TextChanged + { + add => AddHandler(TextChangedEvent, value); + remove => RemoveHandler(TextChangedEvent, value); + } + + /// + /// Occurs synchronously when text starts to change but before it is rendered. + /// + /// + /// This event occurs just after the property value has been updated. + /// + public event EventHandler? TextChanging + { + add => AddHandler(TextChangingEvent, value); + remove => RemoveHandler(TextChangingEvent, value); + } + protected override void OnApplyTemplate(TemplateAppliedEventArgs e) { _presenter = e.NameScope.Get("PART_TextPresenter"); @@ -1252,7 +1305,7 @@ namespace Avalonia.Controls if (text != null && _wordSelectionStart >= 0) { - var distance = caretIndex - _wordSelectionStart; + var distance = caretIndex - _wordSelectionStart; if (distance <= 0) { @@ -1539,11 +1592,39 @@ namespace Avalonia.Controls return text.Substring(start, end - start); } + /// + /// Raises both the and events. + /// + /// + /// This must be called after the property is set. + /// + private void RaiseTextChangeEvents() + { + // Note the following sequence of these events (following WinUI) + // 1. TextChanging occurs synchronously when text starts to change but before it is rendered. + // This occurs after the Text property is set. + // 2. TextChanged occurs asynchronously after text changes and the new text is rendered. + + var textChangingEventArgs = new TextChangingEventArgs(TextChangingEvent); + RaiseEvent(textChangingEventArgs); + + Dispatcher.UIThread.Post(() => + { + var textChangedEventArgs = new TextChangedEventArgs(TextChangedEvent); + RaiseEvent(textChangedEventArgs); + }, DispatcherPriority.Normal); + } + private void SetTextInternal(string value, bool raiseTextChanged = true) { if (raiseTextChanged) { - SetAndRaise(TextProperty, ref _text, value); + bool textChanged = SetAndRaise(TextProperty, ref _text, value); + + if (textChanged) + { + RaiseTextChangeEvents(); + } } else { diff --git a/src/Avalonia.Controls/TextBoxTextInputMethodClient.cs b/src/Avalonia.Controls/TextBoxTextInputMethodClient.cs index d39d964277..5d5ffcc381 100644 --- a/src/Avalonia.Controls/TextBoxTextInputMethodClient.cs +++ b/src/Avalonia.Controls/TextBoxTextInputMethodClient.cs @@ -64,7 +64,7 @@ namespace Avalonia.Controls return new TextInputMethodSurroundingText { - Text = lineText ?? "", + Text = lineText ?? "", AnchorOffset = anchorOffset, CursorOffset = cursorOffset }; diff --git a/src/Avalonia.Controls/TextChangedEventArgs.cs b/src/Avalonia.Controls/TextChangedEventArgs.cs new file mode 100644 index 0000000000..77c609f19b --- /dev/null +++ b/src/Avalonia.Controls/TextChangedEventArgs.cs @@ -0,0 +1,20 @@ +using Avalonia.Interactivity; + +namespace Avalonia.Controls +{ + /// + /// Provides data specific to a TextChanged event. + /// + public class TextChangedEventArgs : RoutedEventArgs + { + public TextChangedEventArgs(RoutedEvent? routedEvent) + : base (routedEvent) + { + } + + public TextChangedEventArgs(RoutedEvent? routedEvent, IInteractive? source) + : base(routedEvent, source) + { + } + } +} diff --git a/src/Avalonia.Controls/TextChangingEventArgs.cs b/src/Avalonia.Controls/TextChangingEventArgs.cs new file mode 100644 index 0000000000..4dedbc927b --- /dev/null +++ b/src/Avalonia.Controls/TextChangingEventArgs.cs @@ -0,0 +1,20 @@ +using Avalonia.Interactivity; + +namespace Avalonia.Controls +{ + /// + /// Provides data specific to a TextChanging event. + /// + public class TextChangingEventArgs : RoutedEventArgs + { + public TextChangingEventArgs(RoutedEvent? routedEvent) + : base (routedEvent) + { + } + + public TextChangingEventArgs(RoutedEvent? routedEvent, IInteractive? source) + : base(routedEvent, source) + { + } + } +} diff --git a/src/Avalonia.Controls/Utils/ISelectionAdapter.cs b/src/Avalonia.Controls/Utils/ISelectionAdapter.cs index c5fb12197f..3ede518ffa 100644 --- a/src/Avalonia.Controls/Utils/ISelectionAdapter.cs +++ b/src/Avalonia.Controls/Utils/ISelectionAdapter.cs @@ -1,6 +1,6 @@ // (c) Copyright Microsoft Corporation. // This source is subject to the Microsoft Public License (Ms-PL). -// Please see http://go.microsoft.com/fwlink/?LinkID=131993 for details. +// Please see https://go.microsoft.com/fwlink/?LinkID=131993 for details. // All other rights reserved. using System; diff --git a/src/Avalonia.Controls/Utils/SelectingItemsControlSelectionAdapter.cs b/src/Avalonia.Controls/Utils/SelectingItemsControlSelectionAdapter.cs index 0288f99dce..3c1b1262ae 100644 --- a/src/Avalonia.Controls/Utils/SelectingItemsControlSelectionAdapter.cs +++ b/src/Avalonia.Controls/Utils/SelectingItemsControlSelectionAdapter.cs @@ -1,6 +1,6 @@ // (c) Copyright Microsoft Corporation. // This source is subject to the Microsoft Public License (Ms-PL). -// Please see http://go.microsoft.com/fwlink/?LinkID=131993 for details. +// Please see https://go.microsoft.com/fwlink/?LinkID=131993 for details. // All other rights reserved. using System; diff --git a/src/Avalonia.DesignerSupport/Remote/HtmlTransport/SimpleWebSocketHttpServer.cs b/src/Avalonia.DesignerSupport/Remote/HtmlTransport/SimpleWebSocketHttpServer.cs index 6af21bbcf5..9a872df960 100644 --- a/src/Avalonia.DesignerSupport/Remote/HtmlTransport/SimpleWebSocketHttpServer.cs +++ b/src/Avalonia.DesignerSupport/Remote/HtmlTransport/SimpleWebSocketHttpServer.cs @@ -140,7 +140,7 @@ namespace Avalonia.DesignerSupport.Remote.HtmlTransport IsWebsocketRequest = true; if (headers.TryGetValue("Sec-WebSocket-Protocol", out h)) WebSocketProtocols = h.Split(',').Select(x => x.Trim()).ToArray(); - else WebSocketProtocols = new string[0]; + else WebSocketProtocols = Array.Empty(); } } diff --git a/src/Avalonia.DesignerSupport/Remote/Stubs.cs b/src/Avalonia.DesignerSupport/Remote/Stubs.cs index e6677194b0..7d4a6518ef 100644 --- a/src/Avalonia.DesignerSupport/Remote/Stubs.cs +++ b/src/Avalonia.DesignerSupport/Remote/Stubs.cs @@ -191,7 +191,7 @@ namespace Avalonia.DesignerSupport.Remote public Task ClearAsync() => Task.CompletedTask; public Task SetDataObjectAsync(IDataObject data) => Task.CompletedTask; - public Task GetFormatsAsync() => Task.FromResult(new string[0]); + public Task GetFormatsAsync() => Task.FromResult(Array.Empty()); public Task GetDataAsync(string format) => Task.FromResult((object)null); } diff --git a/src/Avalonia.FreeDesktop/DBusMenuExporter.cs b/src/Avalonia.FreeDesktop/DBusMenuExporter.cs index c0511420a6..657e324010 100644 --- a/src/Avalonia.FreeDesktop/DBusMenuExporter.cs +++ b/src/Avalonia.FreeDesktop/DBusMenuExporter.cs @@ -340,7 +340,7 @@ namespace Avalonia.FreeDesktop { var id = item == null ? 0 : GetId(item); var props = GetProperties((item, menu), propertyNames); - var children = (depth == 0 || menu == null) ? new object[0] : new object[menu.Items.Count]; + var children = (depth == 0 || menu == null) ? Array.Empty() : new object[menu.Items.Count]; if(menu != null) for (var c = 0; c < children.Length; c++) { @@ -397,7 +397,7 @@ namespace Avalonia.FreeDesktop { foreach (var e in Events) HandleEvent(e.id, e.eventId, e.data, e.timestamp); - return Task.FromResult(new int[0]); + return Task.FromResult(Array.Empty()); } public async Task AboutToShowAsync(int Id) @@ -407,7 +407,7 @@ namespace Avalonia.FreeDesktop public async Task<(int[] updatesNeeded, int[] idErrors)> AboutToShowGroupAsync(int[] Ids) { - return (new int[0], new int[0]); + return (Array.Empty(), Array.Empty()); } #region Events diff --git a/src/Avalonia.Headless/HeadlessPlatformRenderInterface.cs b/src/Avalonia.Headless/HeadlessPlatformRenderInterface.cs index cb23c6c336..fcd7f1e31f 100644 --- a/src/Avalonia.Headless/HeadlessPlatformRenderInterface.cs +++ b/src/Avalonia.Headless/HeadlessPlatformRenderInterface.cs @@ -110,14 +110,24 @@ namespace Avalonia.Headless return new HeadlessBitmapStub(destinationSize, new Vector(96, 96)); } - public IGlyphRunImpl CreateGlyphRun(GlyphRun glyphRun) + public IGeometryImpl BuildGlyphRunGeometry(GlyphRun glyphRun) { - return new HeadlessGlyphRunStub(); + return new HeadlessGeometryStub(new Rect(glyphRun.Size)); } - public IGeometryImpl BuildGlyphRunGeometry(GlyphRun glyphRun) + public IGlyphRunBuffer AllocateGlyphRun(IGlyphTypeface glyphTypeface, float fontRenderingEmSize, int length) { - return new HeadlessGeometryStub(new Rect(glyphRun.Size)); + return new HeadlessGlyphRunBufferStub(); + } + + public IHorizontalGlyphRunBuffer AllocateHorizontalGlyphRun(IGlyphTypeface glyphTypeface, float fontRenderingEmSize, int length) + { + return new HeadlessHorizontalGlyphRunBufferStub(); + } + + public IPositionedGlyphRunBuffer AllocatePositionedGlyphRun(IGlyphTypeface glyphTypeface, float fontRenderingEmSize, int length) + { + return new HeadlessPositionedGlyphRunBufferStub(); } class HeadlessGeometryStub : IGeometryImpl @@ -203,6 +213,26 @@ namespace Avalonia.Headless public Matrix Transform { get; } } + class HeadlessGlyphRunBufferStub : IGlyphRunBuffer + { + public Span GlyphIndices => Span.Empty; + + public IGlyphRunImpl Build() + { + return new HeadlessGlyphRunStub(); + } + } + + class HeadlessHorizontalGlyphRunBufferStub : HeadlessGlyphRunBufferStub, IHorizontalGlyphRunBuffer + { + public Span GlyphPositions => Span.Empty; + } + + class HeadlessPositionedGlyphRunBufferStub : HeadlessGlyphRunBufferStub, IPositionedGlyphRunBuffer + { + public Span GlyphPositions => Span.Empty; + } + class HeadlessGlyphRunStub : IGlyphRunImpl { public void Dispose() diff --git a/src/Avalonia.Headless/HeadlessPlatformStubs.cs b/src/Avalonia.Headless/HeadlessPlatformStubs.cs index ed51529a96..c8ac947c16 100644 --- a/src/Avalonia.Headless/HeadlessPlatformStubs.cs +++ b/src/Avalonia.Headless/HeadlessPlatformStubs.cs @@ -75,8 +75,13 @@ namespace Avalonia.Headless public TimeSpan TouchDoubleClickTime => DoubleClickTime; } - class HeadlessGlyphTypefaceImpl : IGlyphTypefaceImpl + class HeadlessGlyphTypefaceImpl : IGlyphTypeface { + public FontMetrics Metrics => new FontMetrics + { + + }; + public short DesignEmHeight => 10; public int Ascent => 5; @@ -95,6 +100,8 @@ namespace Avalonia.Headless public bool IsFixedPitch => true; + public int GlyphCount => 1337; + public void Dispose() { } @@ -104,6 +111,13 @@ namespace Avalonia.Headless return 1; } + public bool TryGetGlyph(uint codepoint, out ushort glyph) + { + glyph = 1; + + return true; + } + public int GetGlyphAdvance(ushort glyph) { return 1; @@ -118,6 +132,12 @@ namespace Avalonia.Headless { return codepoints.ToArray().Select(x => (ushort)x).ToArray(); } + + public bool TryGetTable(uint tag, out byte[] table) + { + table = null; + return false; + } } class HeadlessTextShaperStub : ITextShaperImpl @@ -134,7 +154,7 @@ namespace Avalonia.Headless class HeadlessFontManagerStub : IFontManagerImpl { - public IGlyphTypefaceImpl CreateGlyphTypeface(Typeface typeface) + public IGlyphTypeface CreateGlyphTypeface(Typeface typeface) { return new HeadlessGlyphTypefaceImpl(); } diff --git a/src/Avalonia.Native/IconLoader.cs b/src/Avalonia.Native/IconLoader.cs index edb8b94e83..04779a43aa 100644 --- a/src/Avalonia.Native/IconLoader.cs +++ b/src/Avalonia.Native/IconLoader.cs @@ -6,7 +6,7 @@ namespace Avalonia.Native // OSX doesn't have a concept of *window* icon. // Icons in the title bar are only shown if there is // an opened file (on disk) associated with the current window - // see http://stackoverflow.com/a/7038671/2231814 + // see https://stackoverflow.com/a/7038671/2231814 class IconLoader : IPlatformIconLoader { class IconStub : IWindowIconImpl diff --git a/src/Avalonia.Themes.Fluent/Accents/FluentControlResourcesDark.xaml b/src/Avalonia.Themes.Fluent/Accents/FluentControlResourcesDark.xaml index 88b9f18eee..e480fb1670 100644 --- a/src/Avalonia.Themes.Fluent/Accents/FluentControlResourcesDark.xaml +++ b/src/Avalonia.Themes.Fluent/Accents/FluentControlResourcesDark.xaml @@ -358,7 +358,6 @@ 4 2 0 - Normal @@ -375,8 +374,6 @@ - - @@ -424,7 +421,6 @@ - @@ -455,8 +451,6 @@ - - @@ -595,7 +589,6 @@ - diff --git a/src/Avalonia.Themes.Fluent/Accents/FluentControlResourcesLight.xaml b/src/Avalonia.Themes.Fluent/Accents/FluentControlResourcesLight.xaml index b51a0bf259..4f421f64a7 100644 --- a/src/Avalonia.Themes.Fluent/Accents/FluentControlResourcesLight.xaml +++ b/src/Avalonia.Themes.Fluent/Accents/FluentControlResourcesLight.xaml @@ -354,7 +354,6 @@ 4 2 0 - Normal @@ -371,8 +370,6 @@ - - @@ -420,7 +417,6 @@ - @@ -450,8 +446,6 @@ - - @@ -589,7 +583,6 @@ - diff --git a/src/Avalonia.Themes.Fluent/Assets/Inter-Bold.ttf b/src/Avalonia.Themes.Fluent/Assets/Inter-Bold.ttf index 847ffd191b..a372c5fcca 100644 Binary files a/src/Avalonia.Themes.Fluent/Assets/Inter-Bold.ttf and b/src/Avalonia.Themes.Fluent/Assets/Inter-Bold.ttf differ diff --git a/src/Avalonia.Themes.Fluent/Assets/Inter-ExtraLight.ttf b/src/Avalonia.Themes.Fluent/Assets/Inter-ExtraLight.ttf index 33267cd799..13ef261fff 100644 Binary files a/src/Avalonia.Themes.Fluent/Assets/Inter-ExtraLight.ttf and b/src/Avalonia.Themes.Fluent/Assets/Inter-ExtraLight.ttf differ diff --git a/src/Avalonia.Themes.Fluent/Assets/Inter-Light.ttf b/src/Avalonia.Themes.Fluent/Assets/Inter-Light.ttf index c22eafe9fb..9f75ff2780 100644 Binary files a/src/Avalonia.Themes.Fluent/Assets/Inter-Light.ttf and b/src/Avalonia.Themes.Fluent/Assets/Inter-Light.ttf differ diff --git a/src/Avalonia.Themes.Fluent/Assets/Inter-Medium.ttf b/src/Avalonia.Themes.Fluent/Assets/Inter-Medium.ttf index f782894eea..2c5e25453c 100644 Binary files a/src/Avalonia.Themes.Fluent/Assets/Inter-Medium.ttf and b/src/Avalonia.Themes.Fluent/Assets/Inter-Medium.ttf differ diff --git a/src/Avalonia.Themes.Fluent/Assets/Inter-Regular.ttf b/src/Avalonia.Themes.Fluent/Assets/Inter-Regular.ttf index 3b7e686e54..c28fe4d744 100644 Binary files a/src/Avalonia.Themes.Fluent/Assets/Inter-Regular.ttf and b/src/Avalonia.Themes.Fluent/Assets/Inter-Regular.ttf differ diff --git a/src/Avalonia.Themes.Fluent/Assets/Inter-SemiBold.ttf b/src/Avalonia.Themes.Fluent/Assets/Inter-SemiBold.ttf index 556e972f48..d085eb4994 100644 Binary files a/src/Avalonia.Themes.Fluent/Assets/Inter-SemiBold.ttf and b/src/Avalonia.Themes.Fluent/Assets/Inter-SemiBold.ttf differ diff --git a/src/Avalonia.Themes.Fluent/Assets/Inter-Thin.ttf b/src/Avalonia.Themes.Fluent/Assets/Inter-Thin.ttf index e49058e33d..016a13482b 100644 Binary files a/src/Avalonia.Themes.Fluent/Assets/Inter-Thin.ttf and b/src/Avalonia.Themes.Fluent/Assets/Inter-Thin.ttf differ diff --git a/src/Avalonia.Themes.Fluent/Controls/Calendar.xaml b/src/Avalonia.Themes.Fluent/Controls/Calendar.xaml index 7042f51c71..9c66ea9b84 100644 --- a/src/Avalonia.Themes.Fluent/Controls/Calendar.xaml +++ b/src/Avalonia.Themes.Fluent/Controls/Calendar.xaml @@ -1,7 +1,7 @@  diff --git a/src/Avalonia.Themes.Fluent/Controls/CalendarButton.xaml b/src/Avalonia.Themes.Fluent/Controls/CalendarButton.xaml index d1aee7ee9a..06b6cf30c2 100644 --- a/src/Avalonia.Themes.Fluent/Controls/CalendarButton.xaml +++ b/src/Avalonia.Themes.Fluent/Controls/CalendarButton.xaml @@ -1,7 +1,7 @@  @@ -28,7 +28,6 @@ - 0,0,0,4 40 40 41 @@ -84,18 +83,8 @@ - - - +