diff --git a/.editorconfig b/.editorconfig index d07618df6c..62a533e468 100644 --- a/.editorconfig +++ b/.editorconfig @@ -177,7 +177,9 @@ dotnet_diagnostic.CA1828.severity = warning dotnet_diagnostic.CA1829.severity = warning #CA1847: Use string.Contains(char) instead of string.Contains(string) with single characters dotnet_diagnostic.CA1847.severity = warning -#CACA2211:Non-constant fields should not be visible +#CA1854: Prefer the IDictionary.TryGetValue(TKey, out TValue) method +dotnet_diagnostic.CA1854.severity = warning +#CA2211:Non-constant fields should not be visible dotnet_diagnostic.CA2211.severity = error # Wrapping preferences diff --git a/Avalonia.sln b/Avalonia.sln index 5d57b4e90d..2175de6050 100644 --- a/Avalonia.sln +++ b/Avalonia.sln @@ -262,13 +262,14 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SafeAreaDemo.iOS", "samples EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Headless", "Headless", "{FF237916-7150-496B-89ED-6CA3292896E7}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Avalonia.Headless.XUnit", "src\Headless\Avalonia.Headless.XUnit\Avalonia.Headless.XUnit.csproj", "{F47F8316-4D4B-4026-8EF3-16B2CFDA8119}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Avalonia.Headless.XUnit", "src\Headless\Avalonia.Headless.XUnit\Avalonia.Headless.XUnit.csproj", "{F47F8316-4D4B-4026-8EF3-16B2CFDA8119}" EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Avalonia.Headless.NUnit", "src\Headless\Avalonia.Headless.NUnit\Avalonia.Headless.NUnit.csproj", "{ED976634-B118-43F8-8B26-0279C7A7044F}" EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Avalonia.Headless.XUnit.UnitTests", "tests\Avalonia.Headless.XUnit.UnitTests\Avalonia.Headless.XUnit.UnitTests.csproj", "{EBA7613E-C36C-4E0C-AB45-71B143F86219}" EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Avalonia.Headless.NUnit.UnitTests", "tests\Avalonia.Headless.NUnit.UnitTests\Avalonia.Headless.NUnit.UnitTests.csproj", "{47025FBC-2130-42EE-98C9-D3989B3B9446}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Avalonia.Headless.UnitTests", "tests\Avalonia.Headless.UnitTests\Avalonia.Headless.UnitTests.csproj", "{3B2405E8-9E7A-46D1-8E2D-EF9ED124C9F2}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution @@ -649,6 +650,14 @@ Global {47025FBC-2130-42EE-98C9-D3989B3B9446}.Debug|Any CPU.Build.0 = Debug|Any CPU {47025FBC-2130-42EE-98C9-D3989B3B9446}.Release|Any CPU.ActiveCfg = Release|Any CPU {47025FBC-2130-42EE-98C9-D3989B3B9446}.Release|Any CPU.Build.0 = Release|Any CPU + {F47F8316-4D4B-4026-8EF3-16B2CFDA8119}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {F47F8316-4D4B-4026-8EF3-16B2CFDA8119}.Debug|Any CPU.Build.0 = Debug|Any CPU + {F47F8316-4D4B-4026-8EF3-16B2CFDA8119}.Release|Any CPU.ActiveCfg = Release|Any CPU + {F47F8316-4D4B-4026-8EF3-16B2CFDA8119}.Release|Any CPU.Build.0 = Release|Any CPU + {3B2405E8-9E7A-46D1-8E2D-EF9ED124C9F2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {3B2405E8-9E7A-46D1-8E2D-EF9ED124C9F2}.Debug|Any CPU.Build.0 = Debug|Any CPU + {3B2405E8-9E7A-46D1-8E2D-EF9ED124C9F2}.Release|Any CPU.ActiveCfg = Release|Any CPU + {3B2405E8-9E7A-46D1-8E2D-EF9ED124C9F2}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -693,6 +702,8 @@ Global {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} + {8C89950F-F5D9-47FC-8066-CBC1EC3DF8FC} = {FF237916-7150-496B-89ED-6CA3292896E7} + {B859AE7C-F34F-4A9E-88AE-E0E7229FDE1E} = {FF237916-7150-496B-89ED-6CA3292896E7} {909A8CBD-7D0E-42FD-B841-022AD8925820} = {8B6A8209-894F-4BA1-B880-965FD453982C} {11BE52AF-E2DD-4CF0-B19A-05285ACAF571} = {9B9E3891-2366-4253-A952-D08BCEB71098} {BC594FD5-4AF2-409E-A1E6-04123F54D7C5} = {9B9E3891-2366-4253-A952-D08BCEB71098} @@ -729,6 +740,8 @@ Global {ED976634-B118-43F8-8B26-0279C7A7044F} = {FF237916-7150-496B-89ED-6CA3292896E7} {EBA7613E-C36C-4E0C-AB45-71B143F86219} = {C5A00AC3-B34C-4564-9BDD-2DA473EF4D8B} {47025FBC-2130-42EE-98C9-D3989B3B9446} = {C5A00AC3-B34C-4564-9BDD-2DA473EF4D8B} + {F47F8316-4D4B-4026-8EF3-16B2CFDA8119} = {FF237916-7150-496B-89ED-6CA3292896E7} + {3B2405E8-9E7A-46D1-8E2D-EF9ED124C9F2} = {C5A00AC3-B34C-4564-9BDD-2DA473EF4D8B} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {87366D66-1391-4D90-8999-95A620AD786A} diff --git a/build/ExternalConsumers.props b/build/ExternalConsumers.props new file mode 100644 index 0000000000..d79e951330 --- /dev/null +++ b/build/ExternalConsumers.props @@ -0,0 +1,32 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/dirs.proj b/dirs.proj index f1eaae8a4a..d29aa61fcb 100644 --- a/dirs.proj +++ b/dirs.proj @@ -9,10 +9,11 @@ - + - + + diff --git a/readme.md b/readme.md index c8135080fe..6dd556bd0d 100644 --- a/readme.md +++ b/readme.md @@ -1,26 +1,43 @@ -[![GH_Banner](https://user-images.githubusercontent.com/552074/218457976-92e76834-9e22-4e35-acfa-aa50281bc0f9.png)](https://avaloniaui.net/xpf) +![Star our repo to show support](https://user-images.githubusercontent.com/552074/235945895-1b896994-a0b6-4e7c-a522-c5688c4ec1b9.png) +![Header](https://user-images.githubusercontent.com/552074/235865745-2a8e7274-4f66-4f77-8f05-feeb76e7d478.png) [![Telegram](https://raw.githubusercontent.com/Patrolavia/telegram-badge/master/chat.svg)](https://t.me/Avalonia) [![Gitter](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/AvaloniaUI/Avalonia?utm_campaign=pr-badge&utm_content=badge&utm_medium=badge&utm_source=badge) [![Discord](https://img.shields.io/badge/discord-join%20chat-46BC99)]( https://aka.ms/dotnet-discord) [![Build Status](https://dev.azure.com/AvaloniaUI/AvaloniaUI/_apis/build/status/AvaloniaUI.Avalonia)](https://dev.azure.com/AvaloniaUI/AvaloniaUI/_build/latest?definitionId=4) [![Backers on Open Collective](https://opencollective.com/Avalonia/backers/badge.svg)](#backers) [![Sponsors on Open Collective](https://opencollective.com/Avalonia/sponsors/badge.svg)](#sponsors) ![License](https://img.shields.io/github/license/avaloniaui/avalonia.svg)
[![NuGet](https://img.shields.io/nuget/v/Avalonia.svg)](https://www.nuget.org/packages/Avalonia) [![downloads](https://img.shields.io/nuget/dt/avalonia)](https://www.nuget.org/packages/Avalonia) ![Size](https://img.shields.io/github/repo-size/avaloniaui/avalonia.svg) -# ⚠️ **v11 Update - Pausing community contributions** - -for more information see [this](https://github.com/AvaloniaUI/Avalonia/discussions/10599) discussion. +⚠️ **v11 Update - [Pausing community contributions](https://github.com/AvaloniaUI/Avalonia/discussions/10599)** ## 📖 About -Avalonia is a cross-platform UI framework for dotnet, providing a flexible styling system and supporting a wide range of Operating Systems such as Windows, Linux, macOS. Avalonia is mature and production ready. We also have in beta release support for iOS, Android and in early stages support for browser via WASM. +[Avalonia](https://avaloniaui.net) is a cross-platform UI framework for dotnet, providing a flexible styling system and supporting a wide range of platforms such as Windows, macOS, Linux, iOS, Android and WebAssembly. Avalonia is mature and production ready and is used by companies, including [Schneider Electric](https://avaloniaui.net/showcase#se), [Unity](https://avaloniaui.net/showcase#unity), [JetBrains](https://avaloniaui.net/showcase#rider) and [Github](https://avaloniaui.net/showcase#github). + +Considered by many to be the spiritual successor to WPF, Avalonia UI provides a familiar, modern development experience for XAML developers creating cross-platform applications. While Avalonia UI is [similar to WPF](https://docs.avaloniaui.net/misc/wpf), it isn't a 1:1 copy, and you'll find plenty of improvements. -![image](https://user-images.githubusercontent.com/4672627/152126443-932966cf-57e7-4e77-9be6-62463a66b9f8.png) +For those seeking a cross-platform WPF, we have created [Avalonia XPF](https://avaloniaui.net/xpf), enabling WPF applications to run on macOS and Linux with little to no code changes. Avalonia XPF is a commercial product and is licensed per-app, per-platform. -To see the status of some of our features, please see our [Roadmap](https://github.com/AvaloniaUI/Avalonia/issues/2239). You can also see what [breaking changes](https://github.com/AvaloniaUI/Avalonia/issues/3538) we have planned and what our [past breaking changes](https://github.com/AvaloniaUI/Avalonia/wiki/Breaking-Changes) have been. [Awesome Avalonia](https://github.com/AvaloniaCommunity/awesome-avalonia) is community-curated list of awesome Avalonia UI tools, libraries, projects and resources. Go and see what people are building with Avalonia! +#### Roadmap +To see the status of some of our features, please see our [Roadmap](https://github.com/AvaloniaUI/Avalonia/issues/2239). + +#### Breaking Changes +You can also see what [breaking changes](https://github.com/AvaloniaUI/Avalonia/issues/3538) we have planned and what our [past breaking changes](https://github.com/AvaloniaUI/Avalonia/wiki/Breaking-Changes) have been. + +#### Awesome Avalonia +[Awesome Avalonia](https://github.com/AvaloniaCommunity/awesome-avalonia) is community-curated list of awesome Avalonia UI tools, libraries, projects and resources. Go and see what people are building with Avalonia! ## 🚀 Getting Started +See our [Get Started](https://avaloniaui.net/GettingStarted) guide to begin developing apps with Avalonia UI. + +### Visual Studio The Avalonia [Visual Studio Extension](https://marketplace.visualstudio.com/items?itemName=AvaloniaTeam.AvaloniaforVisualStudio) contains project and control templates that will help you get started, or you can use the .NET Core CLI. For a starter guide see our [documentation](https://docs.avaloniaui.net/docs/getting-started). +### JetBrains Rider +[JetBrains Rider](https://www.jetbrains.com/rider/whatsnew/?mkt_tok=eyJpIjoiTURBNU1HSmhNV0kwTUdFMiIsInQiOiJtNnU2VEc1TlNLa1ZRVkROYmdZYVpYREJsaU1qdUhmS3dxSzRHczdYWHl0RVlTNDMwSFwvNUs3VENTNVM0bVcyNFdaRmVYZzVWTTF1N3VrQWNGTkJreEhlam1hMlB4UVVWcHBGM1dNOUxoXC95YnRQdGgyUXl1YmZCM3h3d3BVWWdBIn0%3D#avalonia-support) now has official support for Avalonia. + +Code completion, inspections and refactorings are supported out of the box, for XAML previewer add `https://plugins.jetbrains.com/plugins/dev/14839` to plugin repositories and install [AvaloniaRider](https://github.com/ForNeVeR/AvaloniaRider) plugin. + +### Avalonia Packages Avalonia is delivered via NuGet package manager. You can find the packages here: https://www.nuget.org/packages/Avalonia/ Use these commands in the Package Manager console to install Avalonia manually: @@ -30,31 +47,26 @@ Install-Package Avalonia.Desktop ``` ## Showcase +[![Showcase_Banner](https://user-images.githubusercontent.com/552074/235946124-bf6fda52-0c9f-4730-868b-0de957e5b97b.png)](https://avaloniaui.net/showcase) -Examples of UIs built with Avalonia - -([Lunacy](https://icons8.com/lunacy)) - -![image](https://user-images.githubusercontent.com/4672627/152325740-261c27a3-e6f0-4662-bff7-4796d4940e04.png) -([PlasticSCM](https://www.plasticscm.com/)) -![image](https://user-images.githubusercontent.com/4672627/152326453-14944c4d-33da-4d50-a268-b87f80927adb.png) -([WasabiWallet](https://www.wasabiwallet.io/)) - -## JetBrains Rider - -[JetBrains Rider](https://www.jetbrains.com/rider/whatsnew/?mkt_tok=eyJpIjoiTURBNU1HSmhNV0kwTUdFMiIsInQiOiJtNnU2VEc1TlNLa1ZRVkROYmdZYVpYREJsaU1qdUhmS3dxSzRHczdYWHl0RVlTNDMwSFwvNUs3VENTNVM0bVcyNFdaRmVYZzVWTTF1N3VrQWNGTkJreEhlam1hMlB4UVVWcHBGM1dNOUxoXC95YnRQdGgyUXl1YmZCM3h3d3BVWWdBIn0%3D#avalonia-support) now has official support for Avalonia. - -Code completion, inspections and refactorings are supported out of the box, for XAML previewer add `https://plugins.jetbrains.com/plugins/dev/14839` to plugin repositories and install [AvaloniaRider](https://github.com/ForNeVeR/AvaloniaRider) plugin. +See what others have built with Avalonia UI on our [Showcase](https://avaloniaui.net/Showcase). We welcome submissions! ## Bleeding Edge Builds We also have a [nightly build](https://github.com/AvaloniaUI/Avalonia/wiki/Using-nightly-build-feed) which tracks the current state of master. Although these packages are less stable than the release on NuGet.org, you'll get all the latest features and bugfixes right away and many of our users actually prefer this feed! -## Documentation +## Learning -Documentation can be found at https://docs.avaloniaui.net. We also have a [tutorial](https://docs.avaloniaui.net/docs/getting-started/programming-with-avalonia) over there for newcomers. +### Documentation +Documentation can be found at https://docs.avaloniaui.net. + +### Tutorials +We also have a [tutorial](https://docs.avaloniaui.net/docs/getting-started/programming-with-avalonia) over there for newcomers. + +### Samples +We have a [range of samples](https://github.com/AvaloniaUI/Avalonia.Samples) to help you get started. ## Building and Using @@ -116,3 +128,8 @@ We have a range of [support plans available](https://avaloniaui.net/support) for ## .NET Foundation This project is supported by the [.NET Foundation](https://dotnetfoundation.org). + +## Avalonia XPF +Unleash the full potential of your existing WPF apps with our cross-platform UI framework, enabling WPF apps to run on macOS and Linux without requiring expensive and risky rewrites. + +[![GH_Banner](https://user-images.githubusercontent.com/552074/218457976-92e76834-9e22-4e35-acfa-aa50281bc0f9.png)](https://avaloniaui.net/xpf) diff --git a/samples/ControlCatalog/Pages/ComboBoxPage.xaml b/samples/ControlCatalog/Pages/ComboBoxPage.xaml index 748a46c447..f3f6cfe0af 100644 --- a/samples/ControlCatalog/Pages/ComboBoxPage.xaml +++ b/samples/ControlCatalog/Pages/ComboBoxPage.xaml @@ -98,6 +98,21 @@ Inline Item 3 Inline Item 4 + + + + + + + + + + + + + + + WrapSelection diff --git a/samples/ControlCatalog/ViewModels/ComboBoxPageViewModel.cs b/samples/ControlCatalog/ViewModels/ComboBoxPageViewModel.cs index d3e4ea7c31..6ab7bb02e3 100644 --- a/samples/ControlCatalog/ViewModels/ComboBoxPageViewModel.cs +++ b/samples/ControlCatalog/ViewModels/ComboBoxPageViewModel.cs @@ -16,5 +16,20 @@ namespace ControlCatalog.ViewModels get => _wrapSelection; set => this.RaiseAndSetIfChanged(ref _wrapSelection, value); } + + public ObservableCollection Values { get; set; } = new ObservableCollection + { + new IdAndName(){ Id = "Id 1", Name = "Name 1" }, + new IdAndName(){ Id = "Id 2", Name = "Name 2" }, + new IdAndName(){ Id = "Id 3", Name = "Name 3" }, + new IdAndName(){ Id = "Id 4", Name = "Name 4" }, + new IdAndName(){ Id = "Id 5", Name = "Name 5" }, + }; + } + + public class IdAndName + { + public string Id { get; set; } + public string Name { get; set; } } } diff --git a/samples/Sandbox/MainWindow.axaml.cs b/samples/Sandbox/MainWindow.axaml.cs index b8e9f0ff42..e3dda25b29 100644 --- a/samples/Sandbox/MainWindow.axaml.cs +++ b/samples/Sandbox/MainWindow.axaml.cs @@ -1,6 +1,7 @@ using Avalonia; using Avalonia.Controls; using Avalonia.Controls.Presenters; +using Avalonia.Input.TextInput; using Avalonia.Markup.Xaml; using Avalonia.Win32.WinRT.Composition; diff --git a/src/Avalonia.Base/Avalonia.Base.csproj b/src/Avalonia.Base/Avalonia.Base.csproj index 639c27bf03..eafff3b780 100644 --- a/src/Avalonia.Base/Avalonia.Base.csproj +++ b/src/Avalonia.Base/Avalonia.Base.csproj @@ -22,6 +22,7 @@ + diff --git a/src/Avalonia.Base/Data/BindingPriority.cs b/src/Avalonia.Base/Data/BindingPriority.cs index 5fd5aae43b..cb7f559e0a 100644 --- a/src/Avalonia.Base/Data/BindingPriority.cs +++ b/src/Avalonia.Base/Data/BindingPriority.cs @@ -1,4 +1,5 @@ using System; +using System.ComponentModel; namespace Avalonia.Data { @@ -47,7 +48,7 @@ namespace Avalonia.Data /// Unset = int.MaxValue, - [Obsolete("Use Template priority")] + [Obsolete("Use Template priority"), EditorBrowsable(EditorBrowsableState.Never)] TemplatedParent = Template, } } diff --git a/src/Avalonia.Base/Data/Core/Plugins/DataAnnotationsValidationPlugin.cs b/src/Avalonia.Base/Data/Core/Plugins/DataAnnotationsValidationPlugin.cs index ba5f59ea23..bc300386b9 100644 --- a/src/Avalonia.Base/Data/Core/Plugins/DataAnnotationsValidationPlugin.cs +++ b/src/Avalonia.Base/Data/Core/Plugins/DataAnnotationsValidationPlugin.cs @@ -10,7 +10,7 @@ namespace Avalonia.Data.Core.Plugins /// /// Validates properties on that have s. /// - internal class DataAnnotationsValidationPlugin : IDataValidationPlugin + public class DataAnnotationsValidationPlugin : IDataValidationPlugin { /// [RequiresUnreferencedCode(TrimmingMessages.DataValidationPluginRequiresUnreferencedCodeMessage)] diff --git a/src/Avalonia.Base/Data/Core/Plugins/ExceptionValidationPlugin.cs b/src/Avalonia.Base/Data/Core/Plugins/ExceptionValidationPlugin.cs index e60a341309..2bb8da2c74 100644 --- a/src/Avalonia.Base/Data/Core/Plugins/ExceptionValidationPlugin.cs +++ b/src/Avalonia.Base/Data/Core/Plugins/ExceptionValidationPlugin.cs @@ -7,7 +7,7 @@ namespace Avalonia.Data.Core.Plugins /// /// Validates properties that report errors by throwing exceptions. /// - internal class ExceptionValidationPlugin : IDataValidationPlugin + public class ExceptionValidationPlugin : IDataValidationPlugin { /// [RequiresUnreferencedCode(TrimmingMessages.DataValidationPluginRequiresUnreferencedCodeMessage)] diff --git a/src/Avalonia.Base/Data/Core/Plugins/IndeiValidationPlugin.cs b/src/Avalonia.Base/Data/Core/Plugins/IndeiValidationPlugin.cs index 3384a99333..87a2f67ee8 100644 --- a/src/Avalonia.Base/Data/Core/Plugins/IndeiValidationPlugin.cs +++ b/src/Avalonia.Base/Data/Core/Plugins/IndeiValidationPlugin.cs @@ -10,7 +10,7 @@ namespace Avalonia.Data.Core.Plugins /// /// Validates properties on objects that implement . /// - internal class IndeiValidationPlugin : IDataValidationPlugin + public class IndeiValidationPlugin : IDataValidationPlugin { private static readonly WeakEvent ErrorsChangedWeakEvent = WeakEvent.Register( diff --git a/src/Avalonia.Base/Data/InstancedBinding.cs b/src/Avalonia.Base/Data/InstancedBinding.cs index c09c31632e..f93813c0b2 100644 --- a/src/Avalonia.Base/Data/InstancedBinding.cs +++ b/src/Avalonia.Base/Data/InstancedBinding.cs @@ -1,4 +1,5 @@ using System; +using System.ComponentModel; using Avalonia.Reactive; using ObservableEx = Avalonia.Reactive.Observable; @@ -49,7 +50,7 @@ namespace Avalonia.Data /// public IObservable Source { get; } - [Obsolete("Use Source property")] + [Obsolete("Use Source property"), EditorBrowsable(EditorBrowsableState.Never)] public IObservable Observable => Source; /// diff --git a/src/Avalonia.Base/Input/DataFormats.cs b/src/Avalonia.Base/Input/DataFormats.cs index 35d50e669a..f593ed205f 100644 --- a/src/Avalonia.Base/Input/DataFormats.cs +++ b/src/Avalonia.Base/Input/DataFormats.cs @@ -1,4 +1,5 @@ using System; +using System.ComponentModel; namespace Avalonia.Input { @@ -17,7 +18,7 @@ namespace Avalonia.Input /// /// Dataformat for one or more filenames /// - [Obsolete("Use DataFormats.Files, this format is supported only on desktop platforms.")] + [Obsolete("Use DataFormats.Files, this format is supported only on desktop platforms."), EditorBrowsable(EditorBrowsableState.Never)] public static readonly string FileNames = nameof(FileNames); } } diff --git a/src/Avalonia.Base/Input/DataObjectExtensions.cs b/src/Avalonia.Base/Input/DataObjectExtensions.cs index 6af531b0d8..d2e525cd68 100644 --- a/src/Avalonia.Base/Input/DataObjectExtensions.cs +++ b/src/Avalonia.Base/Input/DataObjectExtensions.cs @@ -1,4 +1,5 @@ using System.Collections.Generic; +using System.ComponentModel; using System.Linq; using Avalonia.Platform.Storage; @@ -25,7 +26,7 @@ namespace Avalonia.Input /// /// Collection of file names. If format isn't available, returns null. /// - [System.Obsolete("Use GetFiles, this method is supported only on desktop platforms.")] + [System.Obsolete("Use GetFiles, this method is supported only on desktop platforms."), EditorBrowsable(EditorBrowsableState.Never)] public static IEnumerable? GetFileNames(this IDataObject dataObject) { return (dataObject.Get(DataFormats.FileNames) as IEnumerable) diff --git a/src/Avalonia.Base/Input/FocusManager.cs b/src/Avalonia.Base/Input/FocusManager.cs index 2bf666af44..c8de7267ca 100644 --- a/src/Avalonia.Base/Input/FocusManager.cs +++ b/src/Avalonia.Base/Input/FocusManager.cs @@ -122,6 +122,11 @@ namespace Avalonia.Input { scope = scope ?? throw new ArgumentNullException(nameof(scope)); + if (element is not null && !CanFocus(element)) + { + return; + } + if (_focusScopes.TryGetValue(scope, out var existingElement)) { if (element != existingElement) @@ -242,6 +247,6 @@ namespace Avalonia.Input } } - private static bool IsVisible(IInputElement e) => (e as Visual)?.IsVisible ?? true; + private static bool IsVisible(IInputElement e) => (e as Visual)?.IsEffectivelyVisible ?? true; } } diff --git a/src/Avalonia.Base/Input/InputElement.cs b/src/Avalonia.Base/Input/InputElement.cs index 962c7aa334..33ddbaedf9 100644 --- a/src/Avalonia.Base/Input/InputElement.cs +++ b/src/Avalonia.Base/Input/InputElement.cs @@ -647,6 +647,10 @@ namespace Avalonia.Input { PseudoClasses.Set(":focus-within", change.GetNewValue()); } + else if (change.Property == IsVisibleProperty && !change.GetNewValue() && IsFocused) + { + FocusManager.Instance?.Focus(null); + } } /// diff --git a/src/Avalonia.Base/Input/TextInput/InputMethodManager.cs b/src/Avalonia.Base/Input/TextInput/InputMethodManager.cs index 9b5668bf98..1c61334888 100644 --- a/src/Avalonia.Base/Input/TextInput/InputMethodManager.cs +++ b/src/Avalonia.Base/Input/TextInput/InputMethodManager.cs @@ -48,9 +48,9 @@ namespace Avalonia.Input.TextInput } _transformTracker.SetVisual(_client?.TextViewVisual); - UpdateCursorRect(); _im?.SetClient(_client); + UpdateCursorRect(); } else { diff --git a/src/Avalonia.Base/Layout/LayoutManager.cs b/src/Avalonia.Base/Layout/LayoutManager.cs index f47738f2e4..7873f83edb 100644 --- a/src/Avalonia.Base/Layout/LayoutManager.cs +++ b/src/Avalonia.Base/Layout/LayoutManager.cs @@ -21,6 +21,7 @@ namespace Avalonia.Layout private readonly Layoutable _owner; private readonly LayoutQueue _toMeasure = new LayoutQueue(v => !v.IsMeasureValid); private readonly LayoutQueue _toArrange = new LayoutQueue(v => !v.IsArrangeValid); + private readonly List _toArrangeAfterMeasure = new(); private readonly Action _executeLayoutPass; private List? _effectiveViewportChangedListeners; private bool _disposed; @@ -266,9 +267,14 @@ namespace Avalonia.Layout if (!control.IsArrangeValid) { - Arrange(control); + if (Arrange(control) == ArrangeResult.AncestorMeasureInvalid) + _toArrangeAfterMeasure.Add(control); } } + + foreach (var i in _toArrangeAfterMeasure) + InvalidateArrange(i); + _toArrangeAfterMeasure.Clear(); } private bool Measure(Layoutable control) @@ -304,19 +310,19 @@ namespace Avalonia.Layout return true; } - private bool Arrange(Layoutable control) + private ArrangeResult Arrange(Layoutable control) { if (!control.IsVisible || !control.IsAttachedToVisualTree) - return false; + return ArrangeResult.NotVisible; if (control.VisualParent is Layoutable parent) { - if (!Arrange(parent)) - return false; + if (Arrange(parent) is var parentResult && parentResult != ArrangeResult.Arranged) + return parentResult; } if (!control.IsMeasureValid) - return false; + return ArrangeResult.AncestorMeasureInvalid; if (!control.IsArrangeValid) { @@ -332,7 +338,7 @@ namespace Avalonia.Layout } } - return true; + return ArrangeResult.Arranged; } private void QueueLayoutPass() @@ -435,5 +441,12 @@ namespace Avalonia.Layout public Layoutable Listener { get; } public Rect Viewport { get; set; } } + + private enum ArrangeResult + { + Arranged, + NotVisible, + AncestorMeasureInvalid, + } } } diff --git a/src/Avalonia.Base/Media/Color.cs b/src/Avalonia.Base/Media/Color.cs index 56a3b0d7a5..50c2faacc0 100644 --- a/src/Avalonia.Base/Media/Color.cs +++ b/src/Avalonia.Base/Media/Color.cs @@ -6,6 +6,7 @@ // Licensed to The Avalonia Project under MIT License, courtesy of The .NET Foundation. using System; +using System.ComponentModel; using System.Globalization; #if !BUILDTASK using Avalonia.Animation.Animators; @@ -465,7 +466,7 @@ namespace Avalonia.Media } /// - [Obsolete("Use Color.ToUInt32() instead.")] + [Obsolete("Use Color.ToUInt32() instead."), EditorBrowsable(EditorBrowsableState.Never)] public uint ToUint32() { return ToUInt32(); diff --git a/src/Avalonia.Base/Media/DrawingContext.cs b/src/Avalonia.Base/Media/DrawingContext.cs index 18d6968168..02294368c5 100644 --- a/src/Avalonia.Base/Media/DrawingContext.cs +++ b/src/Avalonia.Base/Media/DrawingContext.cs @@ -1,10 +1,10 @@ using System; using System.Collections.Generic; +using System.ComponentModel; using Avalonia.Platform; using Avalonia.Rendering.SceneGraph; using Avalonia.Threading; using Avalonia.Utilities; -using Avalonia.Media.Imaging; namespace Avalonia.Media { @@ -53,12 +53,10 @@ namespace Avalonia.Media /// The image. /// The rect in the image to draw. /// The rect in the output to draw to. - /// The bitmap interpolation mode. - public virtual void DrawImage(IImage source, Rect sourceRect, Rect destRect, - BitmapInterpolationMode bitmapInterpolationMode = default) + public virtual void DrawImage(IImage source, Rect sourceRect, Rect destRect) { _ = source ?? throw new ArgumentNullException(nameof(source)); - source.Draw(this, sourceRect, destRect, bitmapInterpolationMode); + source.Draw(this, sourceRect, destRect); } /// @@ -68,8 +66,7 @@ namespace Avalonia.Media /// The opacity to draw with. /// The rect in the image to draw. /// The rect in the output to draw to. - /// The bitmap interpolation mode. - internal abstract void DrawBitmap(IRef source, double opacity, Rect sourceRect, Rect destRect, BitmapInterpolationMode bitmapInterpolationMode = BitmapInterpolationMode.Default); + internal abstract void DrawBitmap(IRef source, double opacity, Rect sourceRect, Rect destRect); /// /// Draws a line. @@ -286,8 +283,7 @@ namespace Avalonia.Media Opacity, Clip, GeometryClip, - OpacityMask, - BitmapBlendMode + OpacityMask } public RestoreState(DrawingContext context, PushedStateType type) @@ -312,8 +308,6 @@ namespace Avalonia.Media _context.PopGeometryClipCore(); else if (_type == PushedStateType.OpacityMask) _context.PopOpacityMaskCore(); - else if (_type == PushedStateType.BitmapBlendMode) - _context.PopBitmapBlendModeCore(); } } @@ -394,16 +388,6 @@ namespace Avalonia.Media } protected abstract void PushOpacityMaskCore(IBrush mask, Rect bounds); - public PushedState PushBitmapBlendMode(BitmapBlendingMode blendingMode) - { - PushBitmapBlendMode(blendingMode); - _states ??= StateStackPool.Get(); - _states.Push(new RestoreState(this, RestoreState.PushedStateType.BitmapBlendMode)); - return new PushedState(this); - } - - protected abstract void PushBitmapBlendModeCore(BitmapBlendingMode blendingMode); - /// /// Pushes a matrix transformation. /// @@ -417,11 +401,11 @@ namespace Avalonia.Media return new PushedState(this); } - [Obsolete("Use PushTransform")] + [Obsolete("Use PushTransform"), EditorBrowsable(EditorBrowsableState.Never)] public PushedState PushPreTransform(Matrix matrix) => PushTransform(matrix); - [Obsolete("Use PushTransform")] + [Obsolete("Use PushTransform"), EditorBrowsable(EditorBrowsableState.Never)] public PushedState PushPostTransform(Matrix matrix) => PushTransform(matrix); - [Obsolete("Use PushTransform")] + [Obsolete("Use PushTransform"), EditorBrowsable(EditorBrowsableState.Never)] public PushedState PushTransformContainer() => PushTransform(Matrix.Identity); @@ -431,7 +415,6 @@ namespace Avalonia.Media protected abstract void PopGeometryClipCore(); protected abstract void PopOpacityCore(); protected abstract void PopOpacityMaskCore(); - protected abstract void PopBitmapBlendModeCore(); protected abstract void PopTransformCore(); private static bool PenIsVisible(IPen? pen) diff --git a/src/Avalonia.Base/Media/DrawingGroup.cs b/src/Avalonia.Base/Media/DrawingGroup.cs index c96d2aad57..5a5bd50c7c 100644 --- a/src/Avalonia.Base/Media/DrawingGroup.cs +++ b/src/Avalonia.Base/Media/DrawingGroup.cs @@ -196,13 +196,7 @@ namespace Avalonia.Media throw new NotImplementedException(); } - protected override void PushBitmapBlendModeCore(BitmapBlendingMode blendingMode) - { - throw new NotImplementedException(); - } - - internal override void DrawBitmap(IRef source, double opacity, Rect sourceRect, Rect destRect, - BitmapInterpolationMode bitmapInterpolationMode = BitmapInterpolationMode.Default) + internal override void DrawBitmap(IRef source, double opacity, Rect sourceRect, Rect destRect) { throw new NotImplementedException(); } @@ -321,8 +315,6 @@ namespace Avalonia.Media protected override void PopOpacityMaskCore() => Pop(); - protected override void PopBitmapBlendModeCore() => Pop(); - protected override void PopTransformCore() => Pop(); /// diff --git a/src/Avalonia.Base/Media/DrawingImage.cs b/src/Avalonia.Base/Media/DrawingImage.cs index 52fbd87db7..c83e8eb6ee 100644 --- a/src/Avalonia.Base/Media/DrawingImage.cs +++ b/src/Avalonia.Base/Media/DrawingImage.cs @@ -1,6 +1,5 @@ using System; using Avalonia.Metadata; -using Avalonia.Media.Imaging; namespace Avalonia.Media { @@ -43,8 +42,7 @@ namespace Avalonia.Media void IImage.Draw( DrawingContext context, Rect sourceRect, - Rect destRect, - BitmapInterpolationMode bitmapInterpolationMode) + Rect destRect) { var drawing = Drawing; diff --git a/src/Avalonia.Base/Media/EdgeMode.cs b/src/Avalonia.Base/Media/EdgeMode.cs new file mode 100644 index 0000000000..f50a2f7164 --- /dev/null +++ b/src/Avalonia.Base/Media/EdgeMode.cs @@ -0,0 +1,10 @@ +namespace Avalonia.Media +{ + public enum EdgeMode : byte + { + Unspecified, + + Antialias, + Aliased + } +} diff --git a/src/Avalonia.Base/Media/GlyphRun.cs b/src/Avalonia.Base/Media/GlyphRun.cs index d795cca894..20e0f96ff7 100644 --- a/src/Avalonia.Base/Media/GlyphRun.cs +++ b/src/Avalonia.Base/Media/GlyphRun.cs @@ -153,7 +153,7 @@ namespace Avalonia.Media /// /// Gets the conservative bounding box of the . /// - public Rect Bounds => PlatformImpl.Item.Bounds; + public Rect Bounds => new Rect(new Size(Metrics.WidthIncludingTrailingWhitespace, Metrics.Height)); /// /// @@ -166,7 +166,7 @@ namespace Avalonia.Media /// public Point BaselineOrigin { - get => PlatformImpl.Item.BaselineOrigin; + get => _baselineOrigin ?? new Point(0, Metrics.Baseline); set => Set(ref _baselineOrigin, value); } @@ -676,13 +676,17 @@ namespace Avalonia.Media } } - return new GlyphRunMetrics( - width, - trailingWhitespaceLength, - newLineLength, - firstCluster, - lastCluster - ); + return new GlyphRunMetrics + { + Baseline = -GlyphTypeface.Metrics.Ascent * Scale, + Width = width, + WidthIncludingTrailingWhitespace = widthIncludingTrailingWhitespace, + Height = height, + NewLineLength = newLineLength, + TrailingWhitespaceLength = trailingWhitespaceLength, + FirstCluster = firstCluster, + LastCluster = lastCluster + }; } private int GetTrailingWhitespaceLength(bool isReversed, out int newLineLength, out int glyphCount) @@ -820,10 +824,11 @@ namespace Avalonia.Media private IRef CreateGlyphRunImpl() { var platformImpl = s_renderInterface.CreateGlyphRun( - GlyphTypeface, - FontRenderingEmSize, - GlyphInfos, - _baselineOrigin ?? new Point(0, -GlyphTypeface.Metrics.Ascent * Scale)); + GlyphTypeface, + FontRenderingEmSize, + GlyphInfos, + BaselineOrigin, + Bounds); _platformImpl = RefCountable.Create(platformImpl); @@ -835,5 +840,16 @@ namespace Avalonia.Media _platformImpl?.Dispose(); _platformImpl = null; } + + /// + /// Gets the intersections of specified upper and lower limit. + /// + /// Upper limit. + /// Lower limit. + /// + public IReadOnlyList GetIntersections(float lowerLimit, float upperLimit) + { + return PlatformImpl.Item.GetIntersections(lowerLimit, upperLimit); + } } } diff --git a/src/Avalonia.Base/Media/GlyphRunMetrics.cs b/src/Avalonia.Base/Media/GlyphRunMetrics.cs index 09b183d044..9ca1d5ea12 100644 --- a/src/Avalonia.Base/Media/GlyphRunMetrics.cs +++ b/src/Avalonia.Base/Media/GlyphRunMetrics.cs @@ -2,23 +2,20 @@ { public readonly record struct GlyphRunMetrics { - public GlyphRunMetrics(double width, int trailingWhitespaceLength, int newLineLength, int firstCluster, int lastCluster) - { - Width = width; - TrailingWhitespaceLength = trailingWhitespaceLength; - NewLineLength = newLineLength; - FirstCluster = firstCluster; - LastCluster = lastCluster; - } + public double Baseline { get; init; } - public double Width { get; } + public double Width { get; init; } - public int TrailingWhitespaceLength { get; } + public double WidthIncludingTrailingWhitespace { get; init; } - public int NewLineLength { get; } + public double Height { get; init; } - public int FirstCluster { get; } + public int TrailingWhitespaceLength { get; init; } - public int LastCluster { get; } + public int NewLineLength { get; init; } + + public int FirstCluster { get; init; } + + public int LastCluster { get; init; } } } diff --git a/src/Avalonia.Base/Media/IImage.cs b/src/Avalonia.Base/Media/IImage.cs index cbe25b7b58..4e0b952b88 100644 --- a/src/Avalonia.Base/Media/IImage.cs +++ b/src/Avalonia.Base/Media/IImage.cs @@ -18,11 +18,9 @@ namespace Avalonia.Media /// The drawing context. /// The rect in the image to draw. /// The rect in the output to draw to. - /// The bitmap interpolation mode. void Draw( DrawingContext context, Rect sourceRect, - Rect destRect, - BitmapInterpolationMode bitmapInterpolationMode); + Rect destRect); } } diff --git a/src/Avalonia.Base/Media/ITileBrush.cs b/src/Avalonia.Base/Media/ITileBrush.cs index cb5a591003..586f6053a1 100644 --- a/src/Avalonia.Base/Media/ITileBrush.cs +++ b/src/Avalonia.Base/Media/ITileBrush.cs @@ -39,13 +39,5 @@ namespace Avalonia.Media /// Gets the brush's tile mode. /// TileMode TileMode { get; } - - /// - /// Gets the bitmap interpolation mode. - /// - /// - /// The bitmap interpolation mode. - /// - BitmapInterpolationMode BitmapInterpolationMode { get; } } } diff --git a/src/Avalonia.Base/Media/Imaging/Bitmap.cs b/src/Avalonia.Base/Media/Imaging/Bitmap.cs index c4720d772e..07bb3db100 100644 --- a/src/Avalonia.Base/Media/Imaging/Bitmap.cs +++ b/src/Avalonia.Base/Media/Imaging/Bitmap.cs @@ -224,15 +224,13 @@ namespace Avalonia.Media.Imaging void IImage.Draw( DrawingContext context, Rect sourceRect, - Rect destRect, - BitmapInterpolationMode bitmapInterpolationMode) + Rect destRect) { context.DrawBitmap( PlatformImpl, 1, sourceRect, - destRect, - bitmapInterpolationMode); + destRect); } private static IPlatformRenderInterface GetFactory() diff --git a/src/Avalonia.Base/Media/Imaging/BitmapBlendingMode.cs b/src/Avalonia.Base/Media/Imaging/BitmapBlendingMode.cs index eb39020939..73a3f7b269 100644 --- a/src/Avalonia.Base/Media/Imaging/BitmapBlendingMode.cs +++ b/src/Avalonia.Base/Media/Imaging/BitmapBlendingMode.cs @@ -3,8 +3,10 @@ namespace Avalonia.Media.Imaging /// /// Controls the way the bitmaps are drawn together. /// - public enum BitmapBlendingMode + public enum BitmapBlendingMode : byte { + Unspecified, + /// /// Source is placed over the destination. /// @@ -52,6 +54,6 @@ namespace Avalonia.Media.Imaging /// /// Display the sum of the source image and destination image. /// - Plus, + Plus } } diff --git a/src/Avalonia.Base/Media/Imaging/BitmapInterpolationMode.cs b/src/Avalonia.Base/Media/Imaging/BitmapInterpolationMode.cs index 7cdb5d8b9f..eaa64892a4 100644 --- a/src/Avalonia.Base/Media/Imaging/BitmapInterpolationMode.cs +++ b/src/Avalonia.Base/Media/Imaging/BitmapInterpolationMode.cs @@ -3,12 +3,14 @@ /// /// Controls the performance and quality of bitmap scaling. /// - public enum BitmapInterpolationMode + public enum BitmapInterpolationMode : byte { + Unspecified, + /// - /// Uses the default behavior of the underling render backend. + /// Disable interpolation. /// - Default, + None, /// /// The best performance but worst image quality. @@ -18,7 +20,7 @@ /// /// Good performance and decent image quality. /// - MediumQuality, + MediumQuality, /// /// Highest quality but worst performance. diff --git a/src/Avalonia.Base/Media/Imaging/CroppedBitmap.cs b/src/Avalonia.Base/Media/Imaging/CroppedBitmap.cs index 8cdf5b592a..93556679e9 100644 --- a/src/Avalonia.Base/Media/Imaging/CroppedBitmap.cs +++ b/src/Avalonia.Base/Media/Imaging/CroppedBitmap.cs @@ -83,12 +83,12 @@ namespace Avalonia.Media.Imaging } } - public void Draw(DrawingContext context, Rect sourceRect, Rect destRect, BitmapInterpolationMode bitmapInterpolationMode) + public void Draw(DrawingContext context, Rect sourceRect, Rect destRect) { if (Source is not IBitmap bmp) return; var topLeft = SourceRect.TopLeft.ToPointWithDpi(bmp.Dpi); - Source.Draw(context, sourceRect.Translate(new Vector(topLeft.X, topLeft.Y)), destRect, bitmapInterpolationMode); + Source.Draw(context, sourceRect.Translate(new Vector(topLeft.X, topLeft.Y)), destRect); } } } diff --git a/src/Avalonia.Base/Media/Imaging/RenderTargetBitmap.cs b/src/Avalonia.Base/Media/Imaging/RenderTargetBitmap.cs index e77dd9d1ab..4921e9b756 100644 --- a/src/Avalonia.Base/Media/Imaging/RenderTargetBitmap.cs +++ b/src/Avalonia.Base/Media/Imaging/RenderTargetBitmap.cs @@ -9,7 +9,7 @@ namespace Avalonia.Media.Imaging /// /// A bitmap that holds the rendering of a . /// - public class RenderTargetBitmap : Bitmap, IDisposable + public class RenderTargetBitmap : Bitmap { /// /// Initializes a new instance of the class. @@ -68,5 +68,11 @@ namespace Avalonia.Media.Imaging platform.Clear(Colors.Transparent); return new PlatformDrawingContext(platform); } + + public override void Dispose() + { + PlatformImpl.Dispose(); + base.Dispose(); + } } } diff --git a/src/Avalonia.Base/Media/ImmediateDrawingContext.cs b/src/Avalonia.Base/Media/ImmediateDrawingContext.cs index 17c4560523..fdf10596bb 100644 --- a/src/Avalonia.Base/Media/ImmediateDrawingContext.cs +++ b/src/Avalonia.Base/Media/ImmediateDrawingContext.cs @@ -79,11 +79,10 @@ namespace Avalonia.Media /// The bitmap. /// The rect in the image to draw. /// The rect in the output to draw to. - /// The bitmap interpolation mode. - public void DrawBitmap(IBitmap source, Rect sourceRect, Rect destRect, BitmapInterpolationMode bitmapInterpolationMode = default) + public void DrawBitmap(IBitmap source, Rect sourceRect, Rect destRect) { _ = source ?? throw new ArgumentNullException(nameof(source)); - PlatformImpl.DrawBitmap(source.PlatformImpl, 1, sourceRect, destRect, bitmapInterpolationMode); + PlatformImpl.DrawBitmap(source.PlatformImpl, 1, sourceRect, destRect); } /// diff --git a/src/Avalonia.Base/Media/Immutable/ImmutableImageBrush.cs b/src/Avalonia.Base/Media/Immutable/ImmutableImageBrush.cs index 668a907fdf..175038ba75 100644 --- a/src/Avalonia.Base/Media/Immutable/ImmutableImageBrush.cs +++ b/src/Avalonia.Base/Media/Immutable/ImmutableImageBrush.cs @@ -22,7 +22,6 @@ namespace Avalonia.Media.Immutable /// How the source rectangle will be stretched to fill the destination rect. /// /// The tile mode. - /// The bitmap interpolation mode. public ImmutableImageBrush( IBitmap? source, AlignmentX alignmentX = AlignmentX.Center, @@ -33,8 +32,7 @@ namespace Avalonia.Media.Immutable RelativePoint transformOrigin = default, RelativeRect? sourceRect = null, Stretch stretch = Stretch.Uniform, - TileMode tileMode = TileMode.None, - BitmapInterpolationMode bitmapInterpolationMode = BitmapInterpolationMode.Default) + TileMode tileMode = TileMode.None) : base( alignmentX, alignmentY, @@ -44,8 +42,7 @@ namespace Avalonia.Media.Immutable transformOrigin, sourceRect ?? RelativeRect.Fill, stretch, - tileMode, - bitmapInterpolationMode) + tileMode) { Source = source; } diff --git a/src/Avalonia.Base/Media/Immutable/ImmutableTileBrush.cs b/src/Avalonia.Base/Media/Immutable/ImmutableTileBrush.cs index 1ee52365e0..7e139af516 100644 --- a/src/Avalonia.Base/Media/Immutable/ImmutableTileBrush.cs +++ b/src/Avalonia.Base/Media/Immutable/ImmutableTileBrush.cs @@ -21,7 +21,6 @@ namespace Avalonia.Media.Immutable /// How the source rectangle will be stretched to fill the destination rect. /// /// The tile mode. - /// The bitmap interpolation mode. protected ImmutableTileBrush( AlignmentX alignmentX, AlignmentY alignmentY, @@ -31,8 +30,7 @@ namespace Avalonia.Media.Immutable RelativePoint transformOrigin, RelativeRect sourceRect, Stretch stretch, - TileMode tileMode, - BitmapInterpolationMode bitmapInterpolationMode) + TileMode tileMode) { AlignmentX = alignmentX; AlignmentY = alignmentY; @@ -43,7 +41,6 @@ namespace Avalonia.Media.Immutable SourceRect = sourceRect; Stretch = stretch; TileMode = tileMode; - BitmapInterpolationMode = bitmapInterpolationMode; } /// @@ -60,8 +57,7 @@ namespace Avalonia.Media.Immutable source.TransformOrigin, source.SourceRect, source.Stretch, - source.TileMode, - source.BitmapInterpolationMode) + source.TileMode) { } @@ -95,8 +91,5 @@ namespace Avalonia.Media.Immutable /// public TileMode TileMode { get; } - - /// - public BitmapInterpolationMode BitmapInterpolationMode { get; } } } diff --git a/src/Avalonia.Base/Media/PlatformDrawingContext.cs b/src/Avalonia.Base/Media/PlatformDrawingContext.cs index 4b683c6acb..09c0cd26ac 100644 --- a/src/Avalonia.Base/Media/PlatformDrawingContext.cs +++ b/src/Avalonia.Base/Media/PlatformDrawingContext.cs @@ -26,6 +26,12 @@ internal sealed class PlatformDrawingContext : DrawingContext, IDrawingContextWi _ownsImpl = ownsImpl; } + public RenderOptions RenderOptions + { + get => _impl.RenderOptions; + set => _impl.RenderOptions = value; + } + protected override void DrawLineCore(IPen pen, Point p1, Point p2) => _impl.DrawLine(pen, p1, p2); @@ -38,9 +44,8 @@ internal sealed class PlatformDrawingContext : DrawingContext, IDrawingContextWi protected override void DrawEllipseCore(IBrush? brush, IPen? pen, Rect rect) => _impl.DrawEllipse(brush, pen, rect); - internal override void DrawBitmap(IRef source, double opacity, Rect sourceRect, Rect destRect, - BitmapInterpolationMode bitmapInterpolationMode = BitmapInterpolationMode.Default) => - _impl.DrawBitmap(source, opacity, sourceRect, destRect, bitmapInterpolationMode); + internal override void DrawBitmap(IRef source, double opacity, Rect sourceRect, Rect destRect) => + _impl.DrawBitmap(source, opacity, sourceRect, destRect); public override void Custom(ICustomDrawOperation custom) { @@ -77,9 +82,6 @@ internal sealed class PlatformDrawingContext : DrawingContext, IDrawingContextWi protected override void PushOpacityMaskCore(IBrush mask, Rect bounds) => _impl.PushOpacityMask(mask, bounds); - protected override void PushBitmapBlendModeCore(BitmapBlendingMode blendingMode) => - _impl.PushBitmapBlendMode(blendingMode); - protected override void PushTransformCore(Matrix matrix) { _transforms ??= TransformStackPool.Get(); @@ -96,8 +98,6 @@ internal sealed class PlatformDrawingContext : DrawingContext, IDrawingContextWi protected override void PopOpacityMaskCore() => _impl.PopOpacityMask(); - protected override void PopBitmapBlendModeCore() => _impl.PopBitmapBlendMode(); - protected override void PopTransformCore() => _impl.Transform = (_transforms ?? throw new ObjectDisposedException(nameof(PlatformDrawingContext))).Pop(); diff --git a/src/Avalonia.Base/Media/RenderOptions.cs b/src/Avalonia.Base/Media/RenderOptions.cs index 5863d0ac58..639498543b 100644 --- a/src/Avalonia.Base/Media/RenderOptions.cs +++ b/src/Avalonia.Base/Media/RenderOptions.cs @@ -1,36 +1,131 @@ using Avalonia.Media.Imaging; namespace Avalonia.Media -{ - public class RenderOptions +{ + public readonly record struct RenderOptions { + public BitmapInterpolationMode BitmapInterpolationMode { get; init; } + public EdgeMode EdgeMode { get; init; } + public TextRenderingMode TextRenderingMode { get; init; } + public BitmapBlendingMode BitmapBlendingMode { get; init; } + + /// + /// Gets the value of the BitmapInterpolationMode attached property for a visual. + /// + /// The control. + /// The control's left coordinate. + public static BitmapInterpolationMode GetBitmapInterpolationMode(Visual visual) + { + return visual.RenderOptions.BitmapInterpolationMode; + } + /// - /// Defines the property. + /// Sets the value of the BitmapInterpolationMode attached property for a visual. /// - public static readonly StyledProperty BitmapInterpolationModeProperty = - AvaloniaProperty.RegisterAttached( - "BitmapInterpolationMode", - BitmapInterpolationMode.MediumQuality, - inherits: true); + /// The control. + /// The left value. + public static void SetBitmapInterpolationMode(Visual visual, BitmapInterpolationMode value) + { + visual.RenderOptions = visual.RenderOptions with { BitmapInterpolationMode = value }; + } /// - /// Gets the value of the BitmapInterpolationMode attached property for a control. + /// Gets the value of the BitmapBlendingMode attached property for a visual. /// - /// The control. + /// The control. /// The control's left coordinate. - public static BitmapInterpolationMode GetBitmapInterpolationMode(AvaloniaObject element) + public static BitmapBlendingMode GetBitmapBlendingMode(Visual visual) { - return element.GetValue(BitmapInterpolationModeProperty); + return visual.RenderOptions.BitmapBlendingMode; } /// - /// Sets the value of the BitmapInterpolationMode attached property for a control. + /// Sets the value of the BitmapBlendingMode attached property for a visual. /// - /// The control. + /// The control. /// The left value. - public static void SetBitmapInterpolationMode(AvaloniaObject element, BitmapInterpolationMode value) + public static void SetBitmapBlendingMode(Visual visual, BitmapBlendingMode value) { - element.SetValue(BitmapInterpolationModeProperty, value); + visual.RenderOptions = visual.RenderOptions with { BitmapBlendingMode = value }; + } + + /// + /// Gets the value of the EdgeMode attached property for a visual. + /// + /// The control. + /// The control's left coordinate. + public static EdgeMode GetEdgeMode(Visual visual) + { + return visual.RenderOptions.EdgeMode; + } + + /// + /// Sets the value of the EdgeMode attached property for a visual. + /// + /// The control. + /// The left value. + public static void SetEdgeMode(Visual visual, EdgeMode value) + { + visual.RenderOptions = visual.RenderOptions with { EdgeMode = value }; + } + + /// + /// Gets the value of the TextRenderingMode attached property for a visual. + /// + /// The control. + /// The control's left coordinate. + public static TextRenderingMode GetTextRenderingMode(Visual visual) + { + return visual.RenderOptions.TextRenderingMode; + } + + /// + /// Sets the value of the TextRenderingMode attached property for a visual. + /// + /// The control. + /// The left value. + public static void SetTextRenderingMode(Visual visual, TextRenderingMode value) + { + visual.RenderOptions = visual.RenderOptions with { TextRenderingMode = value }; + } + + public RenderOptions MergeWith(RenderOptions other) + { + var bitmapInterpolationMode = BitmapInterpolationMode; + + if (bitmapInterpolationMode == BitmapInterpolationMode.Unspecified) + { + bitmapInterpolationMode = other.BitmapInterpolationMode; + } + + var edgeMode = EdgeMode; + + if (edgeMode == EdgeMode.Unspecified) + { + edgeMode = other.EdgeMode; + } + + var textRenderingMode = TextRenderingMode; + + if (textRenderingMode == TextRenderingMode.Unspecified) + { + textRenderingMode = other.TextRenderingMode; + } + + var bitmapBlendingMode = BitmapBlendingMode; + + if (bitmapBlendingMode == BitmapBlendingMode.Unspecified) + { + bitmapBlendingMode = other.BitmapBlendingMode; + } + + return new RenderOptions + { + BitmapInterpolationMode = bitmapInterpolationMode, + EdgeMode = edgeMode, + TextRenderingMode = textRenderingMode, + BitmapBlendingMode = bitmapBlendingMode + }; } } } diff --git a/src/Avalonia.Base/Media/TextDecoration.cs b/src/Avalonia.Base/Media/TextDecoration.cs index e89a7d8826..8661959aa6 100644 --- a/src/Avalonia.Base/Media/TextDecoration.cs +++ b/src/Avalonia.Base/Media/TextDecoration.cs @@ -1,10 +1,6 @@ -using System; -using System.Collections.Generic; +using System.Collections.Generic; using Avalonia.Collections; -using Avalonia.Collections.Pooled; using Avalonia.Media.TextFormatting; -using Avalonia.Platform; -using Avalonia.Utilities; namespace Avalonia.Media { @@ -218,7 +214,7 @@ namespace Avalonia.Media { var offsetY = glyphRun.BaselineOrigin.Y - origin.Y; - var intersections = glyphRun.PlatformImpl.Item.GetIntersections((float)(thickness * 0.5d - offsetY), (float)(thickness * 1.5d - offsetY)); + var intersections = glyphRun.GetIntersections((float)(thickness * 0.5d - offsetY), (float)(thickness * 1.5d - offsetY)); if (intersections.Count > 0) { diff --git a/src/Avalonia.Base/Media/TextFormatting/TextCharacters.cs b/src/Avalonia.Base/Media/TextFormatting/TextCharacters.cs index 253c7075fa..7d4fac337d 100644 --- a/src/Avalonia.Base/Media/TextFormatting/TextCharacters.cs +++ b/src/Avalonia.Base/Media/TextFormatting/TextCharacters.cs @@ -185,7 +185,9 @@ namespace Avalonia.Media.TextFormatting } //Stop at the first missing glyph - if (!currentCodepoint.IsBreakChar && !glyphTypeface.TryGetGlyph(currentCodepoint, out _)) + if (!currentCodepoint.IsBreakChar && + currentCodepoint.GeneralCategory != GeneralCategory.Control && + !glyphTypeface.TryGetGlyph(currentCodepoint, out _)) { break; } diff --git a/src/Avalonia.Base/Media/TextFormatting/TextLineImpl.cs b/src/Avalonia.Base/Media/TextFormatting/TextLineImpl.cs index 3264d5e88a..1234067844 100644 --- a/src/Avalonia.Base/Media/TextFormatting/TextLineImpl.cs +++ b/src/Avalonia.Base/Media/TextFormatting/TextLineImpl.cs @@ -83,7 +83,7 @@ namespace Avalonia.Media.TextFormatting /// public override void Draw(DrawingContext drawingContext, Point lineOrigin) { - var (currentX, currentY) = lineOrigin; + var (currentX, currentY) = lineOrigin + new Point(Start, 0); foreach (var textRun in _textRuns) { @@ -698,7 +698,7 @@ namespace Avalonia.Media.TextFormatting i = lastRunIndex; //Possible overlap at runs of different direction - if (directionalWidth == 0) + if (directionalWidth == 0 && i < _textRuns.Length - 1) { //In case a run only contains a linebreak we don't want to skip it. if (currentRun is ShapedTextRun shaped) @@ -844,7 +844,7 @@ namespace Avalonia.Media.TextFormatting i = firstRunIndex; //Possible overlap at runs of different direction - if (directionalWidth == 0) + if (directionalWidth == 0 && i > 0) { //In case a run only contains a linebreak we don't want to skip it. if (currentRun is ShapedTextRun shaped) @@ -860,8 +860,8 @@ namespace Avalonia.Media.TextFormatting } } - TextBounds? textBounds = null; int coveredLength; + TextBounds? textBounds; switch (currentDirection) { @@ -942,6 +942,13 @@ namespace Avalonia.Media.TextFormatting new TextRunBounds( new Rect(startX, 0, drawableTextRun.Size.Width, Height), currentPosition, currentRun.Length, currentRun)); } + else + { + //Add potential TextEndOfParagraph + textRunBounds.Add( + new TextRunBounds( + new Rect(endX, 0, 0, Height), currentPosition, currentRun.Length, currentRun)); + } currentPosition += currentRun.Length; @@ -1007,6 +1014,13 @@ namespace Avalonia.Media.TextFormatting endX += drawableTextRun.Size.Width; } + else + { + //Add potential TextEndOfParagraph + textRunBounds.Add( + new TextRunBounds( + new Rect(endX, 0, 0, Height), currentPosition, currentRun.Length, currentRun)); + } currentPosition += currentRun.Length; @@ -1409,8 +1423,6 @@ namespace Avalonia.Media.TextFormatting var fontMetrics = _paragraphProperties.DefaultTextRunProperties.CachedGlyphTypeface.Metrics; var fontRenderingEmSize = _paragraphProperties.DefaultTextRunProperties.FontRenderingEmSize; var scale = fontRenderingEmSize / fontMetrics.DesignEmHeight; - - var width = 0d; var widthIncludingWhitespace = 0d; var trailingWhitespaceLength = 0; var newLineLength = 0; @@ -1422,13 +1434,6 @@ namespace Avalonia.Media.TextFormatting var lineHeight = _paragraphProperties.LineHeight; - var lastRunIndex = _textRuns.Length - 1; - - if (lastRunIndex > 0 && _textRuns[lastRunIndex] is TextEndOfLine) - { - lastRunIndex--; - } - for (var index = 0; index < _textRuns.Length; index++) { switch (_textRuns[index]) @@ -1486,7 +1491,7 @@ namespace Avalonia.Media.TextFormatting } } - width = widthIncludingWhitespace; + var width = widthIncludingWhitespace; for (var i = _textRuns.Length - 1; i >= 0; i--) { diff --git a/src/Avalonia.Base/Media/TextRenderingMode.cs b/src/Avalonia.Base/Media/TextRenderingMode.cs new file mode 100644 index 0000000000..927d2bce73 --- /dev/null +++ b/src/Avalonia.Base/Media/TextRenderingMode.cs @@ -0,0 +1,11 @@ +namespace Avalonia.Media +{ + public enum TextRenderingMode : byte + { + Unspecified, + + SubpixelAntialias, + Antialias, + Alias + } +} diff --git a/src/Avalonia.Base/Media/TileBrush.cs b/src/Avalonia.Base/Media/TileBrush.cs index ab1ee2d604..d7b818a174 100644 --- a/src/Avalonia.Base/Media/TileBrush.cs +++ b/src/Avalonia.Base/Media/TileBrush.cs @@ -83,7 +83,6 @@ namespace Avalonia.Media SourceRectProperty, StretchProperty, TileModeProperty); - RenderOptions.BitmapInterpolationModeProperty.OverrideDefaultValue(BitmapInterpolationMode.Default); } /// @@ -140,17 +139,5 @@ namespace Avalonia.Media get { return (TileMode)GetValue(TileModeProperty); } set { SetValue(TileModeProperty, value); } } - - /// - /// Gets or sets the bitmap interpolation mode. - /// - /// - /// The bitmap interpolation mode. - /// - public BitmapInterpolationMode BitmapInterpolationMode - { - get { return RenderOptions.GetBitmapInterpolationMode(this); } - set { RenderOptions.SetBitmapInterpolationMode(this, value); } - } } } diff --git a/src/Avalonia.Base/Platform/IDrawingContextImpl.cs b/src/Avalonia.Base/Platform/IDrawingContextImpl.cs index ef53024508..d86519656c 100644 --- a/src/Avalonia.Base/Platform/IDrawingContextImpl.cs +++ b/src/Avalonia.Base/Platform/IDrawingContextImpl.cs @@ -1,8 +1,8 @@ using System; using Avalonia.Media; using Avalonia.Utilities; -using Avalonia.Media.Imaging; using Avalonia.Metadata; +using Avalonia.Media.Imaging; namespace Avalonia.Platform { @@ -12,6 +12,11 @@ namespace Avalonia.Platform [Unstable] public interface IDrawingContextImpl : IDisposable { + /// + /// Gets or sets the current render options used to control the rendering behavior of drawing operations. + /// + RenderOptions RenderOptions { get; set; } + /// /// Gets or sets the current transform of the drawing context. /// @@ -30,8 +35,7 @@ namespace Avalonia.Platform /// The opacity to draw with. /// The rect in the image to draw. /// The rect in the output to draw to. - /// The bitmap interpolation mode. - void DrawBitmap(IRef source, double opacity, Rect sourceRect, Rect destRect, BitmapInterpolationMode bitmapInterpolationMode = BitmapInterpolationMode.Default); + void DrawBitmap(IRef source, double opacity, Rect sourceRect, Rect destRect); /// /// Draws a bitmap image. @@ -157,15 +161,6 @@ namespace Avalonia.Platform void PopGeometryClip(); /// - /// Pushes a bitmap blending value. - /// - /// The bitmap blending mode. - void PushBitmapBlendMode(BitmapBlendingMode blendingMode); - - /// - /// Pops the latest pushed bitmap blending value. - /// - void PopBitmapBlendMode(); /// /// Attempts to get an optional feature from the drawing context implementation diff --git a/src/Avalonia.Base/Platform/IGlyphRunBuffer.cs b/src/Avalonia.Base/Platform/IGlyphRunBuffer.cs deleted file mode 100644 index c1fc7a5967..0000000000 --- a/src/Avalonia.Base/Platform/IGlyphRunBuffer.cs +++ /dev/null @@ -1,22 +0,0 @@ -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/IGlyphRunImpl.cs b/src/Avalonia.Base/Platform/IGlyphRunImpl.cs index fccea27c43..2342f32307 100644 --- a/src/Avalonia.Base/Platform/IGlyphRunImpl.cs +++ b/src/Avalonia.Base/Platform/IGlyphRunImpl.cs @@ -1,25 +1,36 @@ using System; using System.Collections.Generic; +using Avalonia.Media; using Avalonia.Metadata; namespace Avalonia.Platform { /// - /// Actual implementation of a glyph run that stores platform dependent resources. + /// An immutable platform representation of a . /// [Unstable] - public interface IGlyphRunImpl : IDisposable + public interface IGlyphRunImpl : IDisposable { /// - /// Gets the conservative bounding box of the glyph run./>. + /// Gets the for the . /// - Rect Bounds { get; } + IGlyphTypeface GlyphTypeface { get; } + + /// + /// Gets the em size used for rendering the . + /// + double FontRenderingEmSize { get; } /// /// Gets the baseline origin of the glyph run./>. /// Point BaselineOrigin { get; } + /// + /// Gets the conservative bounding box of the glyph run./>. + /// + Rect Bounds { get; } + /// /// Gets the intersections of specified upper and lower limit. /// diff --git a/src/Avalonia.Base/Platform/IPlatformRenderInterface.cs b/src/Avalonia.Base/Platform/IPlatformRenderInterface.cs index 6f62c3be1d..b0d17f9c85 100644 --- a/src/Avalonia.Base/Platform/IPlatformRenderInterface.cs +++ b/src/Avalonia.Base/Platform/IPlatformRenderInterface.cs @@ -169,8 +169,9 @@ namespace Avalonia.Platform /// The font rendering em size. /// The list of glyphs. /// The baseline origin of the run. Can be null. + /// the conservative bounding box of the run /// An . - IGlyphRunImpl CreateGlyphRun(IGlyphTypeface glyphTypeface, double fontRenderingEmSize, IReadOnlyList glyphInfos, Point baselineOrigin); + IGlyphRunImpl CreateGlyphRun(IGlyphTypeface glyphTypeface, double fontRenderingEmSize, IReadOnlyList glyphInfos, Point baselineOrigin, Rect bounds); /// /// Creates a backend-specific object using a low-level API graphics context diff --git a/src/Avalonia.Base/Platform/StandardAssetLoader.cs b/src/Avalonia.Base/Platform/StandardAssetLoader.cs index 118e57c7af..1d9363c70d 100644 --- a/src/Avalonia.Base/Platform/StandardAssetLoader.cs +++ b/src/Avalonia.Base/Platform/StandardAssetLoader.cs @@ -4,6 +4,7 @@ using System.Diagnostics.CodeAnalysis; using System.IO; using System.Linq; using System.Reflection; +using Avalonia.Metadata; using Avalonia.Platform.Internal; using Avalonia.Utilities; @@ -12,12 +13,13 @@ namespace Avalonia.Platform; /// /// Loads assets compiled into the application binary. /// -internal class StandardAssetLoader : IAssetLoader +[Unstable("StandardAssetLoader is considered unstable. Please use AssetLoader static class instead.")] +public class StandardAssetLoader : IAssetLoader { private readonly IAssemblyDescriptorResolver _assemblyDescriptorResolver; private AssemblyDescriptor? _defaultResmAssembly; - public StandardAssetLoader(IAssemblyDescriptorResolver resolver, Assembly? assembly = null) + internal StandardAssetLoader(IAssemblyDescriptorResolver resolver, Assembly? assembly = null) { if (assembly == null) assembly = Assembly.GetEntryAssembly(); @@ -153,6 +155,8 @@ internal class StandardAssetLoader : IAssetLoader return Enumerable.Empty(); } + public static void RegisterResUriParsers() => AssetLoader.RegisterResUriParsers(); + private bool TryGetAsset(Uri uri, Uri? baseUri, [NotNullWhen(true)] out IAssetDescriptor? assetDescriptor) { assetDescriptor = null; diff --git a/src/Avalonia.Base/Platform/Storage/FileIO/StorageProviderHelpers.cs b/src/Avalonia.Base/Platform/Storage/FileIO/StorageProviderHelpers.cs index 608f924808..55aac6f3fa 100644 --- a/src/Avalonia.Base/Platform/Storage/FileIO/StorageProviderHelpers.cs +++ b/src/Avalonia.Base/Platform/Storage/FileIO/StorageProviderHelpers.cs @@ -50,7 +50,8 @@ internal static class StorageProviderHelpers } } - public static string NameWithExtension(string path, string? defaultExtension, FilePickerFileType? filter) + [return: NotNullIfNotNull(nameof(path))] + public static string? NameWithExtension(string? path, string? defaultExtension, FilePickerFileType? filter) { var name = Path.GetFileName(path); if (name != null && !Path.HasExtension(name)) diff --git a/src/Avalonia.Base/Rendering/Composition/CompositingRenderer.cs b/src/Avalonia.Base/Rendering/Composition/CompositingRenderer.cs index 814ecdba29..1bf52729a0 100644 --- a/src/Avalonia.Base/Rendering/Composition/CompositingRenderer.cs +++ b/src/Avalonia.Base/Rendering/Composition/CompositingRenderer.cs @@ -260,6 +260,8 @@ public class CompositingRenderer : IRendererWithCompositor if (!comp.Effect.EffectEquals(visual.Effect)) comp.Effect = visual.Effect?.ToImmutable(); + comp.RenderOptions = visual.RenderOptions; + var renderTransform = Matrix.Identity; if (visual.HasMirrorTransform) @@ -272,8 +274,6 @@ public class CompositingRenderer : IRendererWithCompositor renderTransform *= (-offset) * visual.RenderTransform.Value * (offset); } - - comp.TransformMatrix = MatrixUtils.ToMatrix4x4(renderTransform); _recorder.BeginUpdate(comp.DrawList); diff --git a/src/Avalonia.Base/Rendering/Composition/Drawing/CompositionDrawingContext.cs b/src/Avalonia.Base/Rendering/Composition/Drawing/CompositionDrawingContext.cs index f81cc5a1a0..ec419e6313 100644 --- a/src/Avalonia.Base/Rendering/Composition/Drawing/CompositionDrawingContext.cs +++ b/src/Avalonia.Base/Rendering/Composition/Drawing/CompositionDrawingContext.cs @@ -78,15 +78,14 @@ internal sealed class CompositionDrawingContext : DrawingContext, IDrawingContex } } - internal override void DrawBitmap(IRef source, double opacity, Rect sourceRect, Rect destRect, - BitmapInterpolationMode bitmapInterpolationMode = BitmapInterpolationMode.Default) + internal override void DrawBitmap(IRef source, double opacity, Rect sourceRect, Rect destRect) { var next = NextDrawAs(); if (next == null || - !next.Item.Equals(Transform, source, opacity, sourceRect, destRect, bitmapInterpolationMode)) + !next.Item.Equals(Transform, source, opacity, sourceRect, destRect)) { - Add(new ImageNode(Transform, source, opacity, sourceRect, destRect, bitmapInterpolationMode)); + Add(new ImageNode(Transform, source, opacity, sourceRect, destRect)); } else { @@ -227,20 +226,6 @@ internal sealed class CompositionDrawingContext : DrawingContext, IDrawingContex } } - protected override void PopBitmapBlendModeCore() - { - var next = NextDrawAs(); - - if (next == null || !next.Item.Equals(null)) - { - Add(new BitmapBlendModeNode()); - } - else - { - ++_drawOperationIndex; - } - } - protected override void PopOpacityCore() { var next = NextDrawAs(); @@ -354,21 +339,6 @@ internal sealed class CompositionDrawingContext : DrawingContext, IDrawingContex _needsToPopOpacityMask.Push(needsToPop); } - /// - protected override void PushBitmapBlendModeCore(BitmapBlendingMode blendingMode) - { - var next = NextDrawAs(); - - if (next == null || !next.Item.Equals(blendingMode)) - { - Add(new BitmapBlendModeNode(blendingMode)); - } - else - { - ++_drawOperationIndex; - } - } - private void Add(T node) where T : class, IDrawOperation { if (_drawOperationIndex < _builder.Count) diff --git a/src/Avalonia.Base/Rendering/Composition/Server/DrawingContextProxy.cs b/src/Avalonia.Base/Rendering/Composition/Server/DrawingContextProxy.cs index 901bdaae0d..5a4890e568 100644 --- a/src/Avalonia.Base/Rendering/Composition/Server/DrawingContextProxy.cs +++ b/src/Avalonia.Base/Rendering/Composition/Server/DrawingContextProxy.cs @@ -42,15 +42,20 @@ internal class CompositorDrawingContextProxy : IDrawingContextImpl, set => _impl.Transform = (_transform = value) * PostTransform; } + public RenderOptions RenderOptions + { + get => _impl.RenderOptions; + set => _impl.RenderOptions = value; + } + public void Clear(Color color) { _impl.Clear(color); } - public void DrawBitmap(IRef source, double opacity, Rect sourceRect, Rect destRect, - BitmapInterpolationMode bitmapInterpolationMode = BitmapInterpolationMode.Default) + public void DrawBitmap(IRef source, double opacity, Rect sourceRect, Rect destRect) { - _impl.DrawBitmap(source, opacity, sourceRect, destRect, bitmapInterpolationMode); + _impl.DrawBitmap(source, opacity, sourceRect, destRect); } public void DrawBitmap(IRef source, IBrush opacityMask, Rect opacityMaskRect, Rect destRect) @@ -133,16 +138,6 @@ internal class CompositorDrawingContextProxy : IDrawingContextImpl, _impl.PopGeometryClip(); } - public void PushBitmapBlendMode(BitmapBlendingMode blendingMode) - { - _impl.PushBitmapBlendMode(blendingMode); - } - - public void PopBitmapBlendMode() - { - _impl.PopBitmapBlendMode(); - } - public object? GetFeature(Type t) => _impl.GetFeature(t); diff --git a/src/Avalonia.Base/Rendering/Composition/Server/ServerCompositionTarget.cs b/src/Avalonia.Base/Rendering/Composition/Server/ServerCompositionTarget.cs index 3e88b9e77b..45275bdfe1 100644 --- a/src/Avalonia.Base/Rendering/Composition/Server/ServerCompositionTarget.cs +++ b/src/Avalonia.Base/Rendering/Composition/Server/ServerCompositionTarget.cs @@ -181,7 +181,7 @@ namespace Avalonia.Rendering.Composition.Server else targetContext.DrawBitmap(RefCountable.CreateUnownedNotClonable(_layer), 1, new Rect(_layerSize), - new Rect(Size), BitmapInterpolationMode.LowQuality); + new Rect(Size)); if (DebugOverlays != RendererDebugOverlays.None) { diff --git a/src/Avalonia.Base/Rendering/Composition/Server/ServerCompositionVisual.cs b/src/Avalonia.Base/Rendering/Composition/Server/ServerCompositionVisual.cs index 6e7ef85183..853b90be5e 100644 --- a/src/Avalonia.Base/Rendering/Composition/Server/ServerCompositionVisual.cs +++ b/src/Avalonia.Base/Rendering/Composition/Server/ServerCompositionVisual.cs @@ -66,6 +66,8 @@ namespace Avalonia.Rendering.Composition.Server if (OpacityMaskBrush != null) canvas.PushOpacityMask(OpacityMaskBrush, boundsRect); + canvas.RenderOptions = RenderOptions; + RenderCore(canvas, currentTransformedClip); // Hack to force invalidation of SKMatrix @@ -122,6 +124,11 @@ namespace Avalonia.Rendering.Composition.Server var wasVisible = IsVisibleInFrame; + if(Parent != null) + { + RenderOptions = RenderOptions.MergeWith(Parent.RenderOptions); + } + // Calculate new parent-relative transform if (_combinedTransformDirty) { diff --git a/src/Avalonia.Base/Rendering/ImmediateRenderer.cs b/src/Avalonia.Base/Rendering/ImmediateRenderer.cs index 9b7d358b1d..3462b1008a 100644 --- a/src/Avalonia.Base/Rendering/ImmediateRenderer.cs +++ b/src/Avalonia.Base/Rendering/ImmediateRenderer.cs @@ -44,79 +44,99 @@ namespace Avalonia.Rendering public static void Render(DrawingContext context, Visual visual, Rect clipRect) { - var opacity = visual.Opacity; - var clipToBounds = visual.ClipToBounds; - var bounds = new Rect(visual.Bounds.Size); + var currentRenderOptions = default(RenderOptions); + var platformContext = context as PlatformDrawingContext; - if (visual.IsVisible && opacity > 0) + try { - var m = Matrix.CreateTranslation(visual.Bounds.Position); - - var renderTransform = Matrix.Identity; - - // this should be calculated BEFORE renderTransform - if (visual.HasMirrorTransform) + if (platformContext != null) { - var mirrorMatrix = new Matrix(-1.0, 0.0, 0.0, 1.0, visual.Bounds.Width, 0); - renderTransform *= mirrorMatrix; - } + currentRenderOptions = platformContext.RenderOptions; - if (visual.RenderTransform != null) - { - var origin = visual.RenderTransformOrigin.ToPixels(new Size(visual.Bounds.Width, visual.Bounds.Height)); - var offset = Matrix.CreateTranslation(origin); - var finalTransform = (-offset) * visual.RenderTransform.Value * (offset); - renderTransform *= finalTransform; + platformContext.RenderOptions = visual.RenderOptions.MergeWith(platformContext.RenderOptions); } - m = renderTransform * m; + var opacity = visual.Opacity; + var clipToBounds = visual.ClipToBounds; + var bounds = new Rect(visual.Bounds.Size); - if (clipToBounds) + if (visual.IsVisible && opacity > 0) { + var m = Matrix.CreateTranslation(visual.Bounds.Position); + + var renderTransform = Matrix.Identity; + + // this should be calculated BEFORE renderTransform + if (visual.HasMirrorTransform) + { + var mirrorMatrix = new Matrix(-1.0, 0.0, 0.0, 1.0, visual.Bounds.Width, 0); + renderTransform *= mirrorMatrix; + } + if (visual.RenderTransform != null) { - clipRect = new Rect(visual.Bounds.Size); + var origin = visual.RenderTransformOrigin.ToPixels(new Size(visual.Bounds.Width, visual.Bounds.Height)); + var offset = Matrix.CreateTranslation(origin); + var finalTransform = (-offset) * visual.RenderTransform.Value * (offset); + renderTransform *= finalTransform; } - else + + m = renderTransform * m; + + if (clipToBounds) { - clipRect = clipRect.Intersect(new Rect(visual.Bounds.Size)); + if (visual.RenderTransform != null) + { + clipRect = new Rect(visual.Bounds.Size); + } + else + { + clipRect = clipRect.Intersect(new Rect(visual.Bounds.Size)); + } } - } - using (context.PushTransform(m)) - using (context.PushOpacity(opacity, bounds)) - using (clipToBounds + using (context.PushTransform(m)) + using (context.PushOpacity(opacity, bounds)) + using (clipToBounds #pragma warning disable CS0618 // Type or member is obsolete - ? visual is IVisualWithRoundRectClip roundClipVisual - ? context.PushClip(new RoundedRect(bounds, roundClipVisual.ClipToBoundsRadius)) - : context.PushClip(bounds) - : default) + ? visual is IVisualWithRoundRectClip roundClipVisual + ? context.PushClip(new RoundedRect(bounds, roundClipVisual.ClipToBoundsRadius)) + : context.PushClip(bounds) + : default) #pragma warning restore CS0618 // Type or member is obsolete - using (visual.Clip != null ? context.PushGeometryClip(visual.Clip) : default) - using (visual.OpacityMask != null ? context.PushOpacityMask(visual.OpacityMask, bounds) : default) - using (context.PushTransform(Matrix.Identity)) - { - visual.Render(context); - - var childrenEnumerable = visual.HasNonUniformZIndexChildren - ? visual.VisualChildren.OrderBy(x => x, ZIndexComparer.Instance) - : (IEnumerable)visual.VisualChildren; - - foreach (var child in childrenEnumerable) + using (visual.Clip != null ? context.PushGeometryClip(visual.Clip) : default) + using (visual.OpacityMask != null ? context.PushOpacityMask(visual.OpacityMask, bounds) : default) + using (context.PushTransform(Matrix.Identity)) { - var childBounds = GetTransformedBounds(child); + visual.Render(context); + + var childrenEnumerable = visual.HasNonUniformZIndexChildren + ? visual.VisualChildren.OrderBy(x => x, ZIndexComparer.Instance) + : (IEnumerable)visual.VisualChildren; - if (!child.ClipToBounds || clipRect.Intersects(childBounds)) + foreach (var child in childrenEnumerable) { - var childClipRect = child.RenderTransform == null - ? clipRect.Translate(-childBounds.Position) - : clipRect; - Render(context, child, childClipRect); - } + var childBounds = GetTransformedBounds(child); + + if (!child.ClipToBounds || clipRect.Intersects(childBounds)) + { + var childClipRect = child.RenderTransform == null + ? clipRect.Translate(-childBounds.Position) + : clipRect; + Render(context, child, childClipRect); + } + } } } } + finally + { + if (platformContext != null) + { + platformContext.RenderOptions = currentRenderOptions; + } + } } } } diff --git a/src/Avalonia.Base/Rendering/SceneGraph/BitmapBlendModeNode.cs b/src/Avalonia.Base/Rendering/SceneGraph/BitmapBlendModeNode.cs deleted file mode 100644 index b1190a159b..0000000000 --- a/src/Avalonia.Base/Rendering/SceneGraph/BitmapBlendModeNode.cs +++ /dev/null @@ -1,68 +0,0 @@ -using Avalonia.Platform; -using Avalonia.Media.Imaging; - -namespace Avalonia.Rendering.SceneGraph -{ - /// - /// A node in the scene graph which represents an bitmap blending mode push or pop. - /// - internal class BitmapBlendModeNode : IDrawOperation - { - /// - /// Initializes a new instance of the class that represents an - /// push. - /// - /// The to push. - public BitmapBlendModeNode(BitmapBlendingMode bitmapBlend) - { - BlendingMode = bitmapBlend; - } - - /// - /// Initializes a new instance of the class that represents an - /// pop. - /// - public BitmapBlendModeNode() - { - } - - /// - public Rect Bounds => default; - - /// - /// Gets the BitmapBlend to be pushed or null if the operation represents a pop. - /// - public BitmapBlendingMode? BlendingMode { get; } - - /// - public bool HitTest(Point p) => false; - - /// - /// Determines if this draw operation equals another. - /// - /// the how to compare - /// True if the draw operations are the same, otherwise false. - /// - /// The properties of the other draw operation are passed in as arguments to prevent - /// allocation of a not-yet-constructed draw operation object. - /// - public bool Equals(BitmapBlendingMode? blendingMode) => BlendingMode == blendingMode; - - /// - public void Render(IDrawingContextImpl context) - { - if (BlendingMode.HasValue) - { - context.PushBitmapBlendMode(BlendingMode.Value); - } - else - { - context.PopBitmapBlendMode(); - } - } - - public void Dispose() - { - } - } -} diff --git a/src/Avalonia.Base/Rendering/SceneGraph/GlyphRunNode.cs b/src/Avalonia.Base/Rendering/SceneGraph/GlyphRunNode.cs index 1c4e63b34a..764c5c65f9 100644 --- a/src/Avalonia.Base/Rendering/SceneGraph/GlyphRunNode.cs +++ b/src/Avalonia.Base/Rendering/SceneGraph/GlyphRunNode.cs @@ -1,5 +1,4 @@ -using System; -using Avalonia.Media; +using Avalonia.Media; using Avalonia.Platform; using Avalonia.Utilities; @@ -18,7 +17,7 @@ namespace Avalonia.Rendering.SceneGraph /// The glyph run to draw. public GlyphRunNode( Matrix transform, - IImmutableBrush foreground, + IImmutableBrush? foreground, IRef glyphRun) : base(glyphRun.Item.Bounds, transform, foreground) { diff --git a/src/Avalonia.Base/Rendering/SceneGraph/ImageNode.cs b/src/Avalonia.Base/Rendering/SceneGraph/ImageNode.cs index ac946cc8b2..caf0eee175 100644 --- a/src/Avalonia.Base/Rendering/SceneGraph/ImageNode.cs +++ b/src/Avalonia.Base/Rendering/SceneGraph/ImageNode.cs @@ -1,6 +1,5 @@ using Avalonia.Platform; using Avalonia.Utilities; -using Avalonia.Media.Imaging; namespace Avalonia.Rendering.SceneGraph { @@ -17,15 +16,13 @@ namespace Avalonia.Rendering.SceneGraph /// The draw opacity. /// The source rect. /// The destination rect. - /// The bitmap interpolation mode. - public ImageNode(Matrix transform, IRef source, double opacity, Rect sourceRect, Rect destRect, BitmapInterpolationMode bitmapInterpolationMode) + public ImageNode(Matrix transform, IRef source, double opacity, Rect sourceRect, Rect destRect) : base(destRect, transform) { Source = source.Clone(); Opacity = opacity; SourceRect = sourceRect; DestRect = destRect; - BitmapInterpolationMode = bitmapInterpolationMode; SourceVersion = Source.Item.Version; } @@ -53,14 +50,6 @@ namespace Avalonia.Rendering.SceneGraph /// Gets the destination rect. /// public Rect DestRect { get; } - - /// - /// Gets the bitmap interpolation mode. - /// - /// - /// The scaling mode. - /// - public BitmapInterpolationMode BitmapInterpolationMode { get; } /// /// Determines if this draw operation equals another. @@ -70,27 +59,25 @@ namespace Avalonia.Rendering.SceneGraph /// The opacity of the other draw operation. /// The source rect of the other draw operation. /// The dest rect of the other draw operation. - /// The bitmap interpolation mode. /// True if the draw operations are the same, otherwise false. /// /// The properties of the other draw operation are passed in as arguments to prevent /// allocation of a not-yet-constructed draw operation object. /// - public bool Equals(Matrix transform, IRef source, double opacity, Rect sourceRect, Rect destRect, BitmapInterpolationMode bitmapInterpolationMode) + public bool Equals(Matrix transform, IRef source, double opacity, Rect sourceRect, Rect destRect) { return transform == Transform && Equals(source.Item, Source.Item) && source.Item.Version == SourceVersion && opacity == Opacity && sourceRect == SourceRect && - destRect == DestRect && - bitmapInterpolationMode == BitmapInterpolationMode; + destRect == DestRect; } /// public override void Render(IDrawingContextImpl context) { - context.DrawBitmap(Source, Opacity, SourceRect, DestRect, BitmapInterpolationMode); + context.DrawBitmap(Source, Opacity, SourceRect, DestRect); } /// diff --git a/src/Avalonia.Base/Threading/Dispatcher.Invoke.cs b/src/Avalonia.Base/Threading/Dispatcher.Invoke.cs index 699186868a..bb1663eac0 100644 --- a/src/Avalonia.Base/Threading/Dispatcher.Invoke.cs +++ b/src/Avalonia.Base/Threading/Dispatcher.Invoke.cs @@ -248,11 +248,11 @@ public partial class Dispatcher /// An operation representing the queued delegate to be invoked. /// /// - /// Note that the default priority is DispatcherPriority.Normal. + /// Note that the default priority is DispatcherPriority.Default. /// public DispatcherOperation InvokeAsync(Action callback) { - return InvokeAsync(callback, DispatcherPriority.Normal, CancellationToken.None); + return InvokeAsync(callback, default, CancellationToken.None); } /// @@ -326,11 +326,11 @@ public partial class Dispatcher /// An operation representing the queued delegate to be invoked. /// /// - /// Note that the default priority is DispatcherPriority.Normal. + /// Note that the default priority is DispatcherPriority.Default. /// public DispatcherOperation InvokeAsync(Func callback) { - return InvokeAsync(callback, DispatcherPriority.Normal, CancellationToken.None); + return InvokeAsync(callback, DispatcherPriority.Default, CancellationToken.None); } /// @@ -541,6 +541,18 @@ public partial class Dispatcher InvokeAsyncImpl(new DispatcherOperation(this, priority, action, true), CancellationToken.None); } + /// + /// Executes the specified Func<Task> asynchronously on the + /// thread that the Dispatcher was created on + /// + /// + /// A Func<Task> delegate to invoke through the dispatcher. + /// + /// + /// An task that completes after the task returned from callback finishes. + /// + public Task InvokeAsync(Func callback) => InvokeAsync(callback, DispatcherPriority.Default); + /// /// Executes the specified Func<Task> asynchronously on the /// thread that the Dispatcher was created on @@ -556,11 +568,29 @@ public partial class Dispatcher /// /// An task that completes after the task returned from callback finishes /// - public Task InvokeAsync(Func callback, DispatcherPriority priority = default) + public Task InvokeAsync(Func callback, DispatcherPriority priority) { _ = callback ?? throw new ArgumentNullException(nameof(callback)); return InvokeAsync(callback, priority).GetTask().Unwrap(); } + + /// + /// Executes the specified Func<Task<TResult>> asynchronously on the + /// thread that the Dispatcher was created on + /// + /// + /// A Func<Task<TResult>> delegate to invoke through the dispatcher. + /// + /// + /// The priority that determines in what order the specified + /// callback is invoked relative to the other pending operations + /// in the Dispatcher. + /// + /// + /// An task that completes after the task returned from callback finishes + /// + public Task InvokeAsync(Func> action) => + InvokeAsync(action, DispatcherPriority.Default); /// /// Executes the specified Func<Task<TResult>> asynchronously on the @@ -577,7 +607,7 @@ public partial class Dispatcher /// /// An task that completes after the task returned from callback finishes /// - public Task InvokeAsync(Func> action, DispatcherPriority priority = default) + public Task InvokeAsync(Func> action, DispatcherPriority priority) { _ = action ?? throw new ArgumentNullException(nameof(action)); return InvokeAsync>(action, priority).GetTask().Unwrap(); diff --git a/src/Avalonia.Base/Threading/DispatcherFrame.cs b/src/Avalonia.Base/Threading/DispatcherFrame.cs index 1f8974dfa3..e826432475 100644 --- a/src/Avalonia.Base/Threading/DispatcherFrame.cs +++ b/src/Avalonia.Base/Threading/DispatcherFrame.cs @@ -91,31 +91,44 @@ public class DispatcherFrame internal void Run(IControlledDispatcherImpl impl) { - // Since the actual platform run loop is controlled by a Cancellation token, we are restarting - // it if frame still needs to run - while (Continue) - RunCore(impl); - } - - private void RunCore(IControlledDispatcherImpl impl) - { - if (_isRunning) - throw new InvalidOperationException("This frame is already running"); - _isRunning = true; - try - { - _cancellationTokenSource = new CancellationTokenSource(); - // Wake up the dispatcher in case it has pending jobs - Dispatcher.RequestProcessing(); - impl.RunLoop(_cancellationTokenSource.Token); - } - finally + Dispatcher.VerifyAccess(); + + // Since the actual platform run loop is controlled by a Cancellation token, we have an + // outer loop that restarts the platform one in case Continue was set to true after being set to false + while (true) { - _isRunning = false; - _cancellationTokenSource?.Cancel(); - _cancellationTokenSource = null; + // Take the instance lock since `Continue` is changed from one too + lock (Dispatcher.InstanceLock) + { + if (!Continue) + return; + + if (_isRunning) + throw new InvalidOperationException("This frame is already running"); + + _cancellationTokenSource = new CancellationTokenSource(); + _isRunning = true; + } + + try + { + // Wake up the dispatcher in case it has pending jobs + Dispatcher.RequestProcessing(); + impl.RunLoop(_cancellationTokenSource.Token); + } + finally + { + lock (Dispatcher.InstanceLock) + { + _isRunning = false; + _cancellationTokenSource?.Cancel(); + _cancellationTokenSource?.Dispose(); + _cancellationTokenSource = null; + } + } } } + internal void MaybeExitOnDispatcherRequest() { diff --git a/src/Avalonia.Base/Threading/DispatcherOperation.cs b/src/Avalonia.Base/Threading/DispatcherOperation.cs index 809c41ff02..8bd6d3bc01 100644 --- a/src/Avalonia.Base/Threading/DispatcherOperation.cs +++ b/src/Avalonia.Base/Threading/DispatcherOperation.cs @@ -331,6 +331,8 @@ public class DispatcherOperation : DispatcherOperation private TaskCompletionSource TaskCompletionSource => (TaskCompletionSource)TaskSource!; + public new TaskAwaiter GetAwaiter() => GetTask().GetAwaiter(); + public new Task GetTask() => TaskCompletionSource!.Task; protected override Task GetTaskCore() => GetTask(); diff --git a/src/Avalonia.Base/Threading/DispatcherPriority.cs b/src/Avalonia.Base/Threading/DispatcherPriority.cs index 3017b45dc7..a43dd8e4a2 100644 --- a/src/Avalonia.Base/Threading/DispatcherPriority.cs +++ b/src/Avalonia.Base/Threading/DispatcherPriority.cs @@ -1,4 +1,5 @@ using System; +using System.ComponentModel; namespace Avalonia.Threading { @@ -100,7 +101,7 @@ namespace Avalonia.Threading /// /// The job will be processed with the same priority as data binding. /// - [Obsolete("WPF compatibility")] public static readonly DispatcherPriority DataBind = new(Layout); + [Obsolete("WPF compatibility"), EditorBrowsable(EditorBrowsableState.Never)] public static readonly DispatcherPriority DataBind = new(Layout); /// /// The job will be processed with normal priority. diff --git a/src/Avalonia.Base/Visual.cs b/src/Avalonia.Base/Visual.cs index 8717b5340a..30c89d186f 100644 --- a/src/Avalonia.Base/Visual.cs +++ b/src/Avalonia.Base/Visual.cs @@ -318,7 +318,9 @@ namespace Avalonia internal CompositionDrawListVisual? CompositionVisual { get; private set; } internal CompositionVisual? ChildCompositionVisual { get; set; } - + + internal RenderOptions RenderOptions { get; set; } + public bool HasNonUniformZIndexChildren { get; private set; } /// diff --git a/src/Avalonia.Base/VisualTree/IVisualWithRoundRectClip.cs b/src/Avalonia.Base/VisualTree/IVisualWithRoundRectClip.cs index 9ace215d03..0079515a63 100644 --- a/src/Avalonia.Base/VisualTree/IVisualWithRoundRectClip.cs +++ b/src/Avalonia.Base/VisualTree/IVisualWithRoundRectClip.cs @@ -1,14 +1,15 @@ using System; +using System.ComponentModel; namespace Avalonia.VisualTree { - [Obsolete("Internal API, will be removed in future versions, you've been warned")] + [Obsolete("Internal API, will be removed in future versions, you've been warned"), EditorBrowsable(EditorBrowsableState.Never)] public interface IVisualWithRoundRectClip { /// /// Gets a value indicating the corner radius of control's clip bounds /// - [Obsolete("Internal API, will be removed in future versions, you've been warned")] + [Obsolete("Internal API, will be removed in future versions, you've been warned"), EditorBrowsable(EditorBrowsableState.Never)] CornerRadius ClipToBoundsRadius { get; } } diff --git a/src/Avalonia.Base/composition-schema.xml b/src/Avalonia.Base/composition-schema.xml index 91d718dfd8..a24c249eed 100644 --- a/src/Avalonia.Base/composition-schema.xml +++ b/src/Avalonia.Base/composition-schema.xml @@ -30,6 +30,7 @@ + diff --git a/src/Avalonia.Controls.DataGrid/DataGrid.cs b/src/Avalonia.Controls.DataGrid/DataGrid.cs index e56e32a5ff..a55a47fa53 100644 --- a/src/Avalonia.Controls.DataGrid/DataGrid.cs +++ b/src/Avalonia.Controls.DataGrid/DataGrid.cs @@ -729,6 +729,8 @@ namespace Avalonia.Controls RowDetailsTemplateProperty.Changed.AddClassHandler((x, e) => x.OnRowDetailsTemplateChanged(e)); RowDetailsVisibilityModeProperty.Changed.AddClassHandler((x, e) => x.OnRowDetailsVisibilityModeChanged(e)); AutoGenerateColumnsProperty.Changed.AddClassHandler((x, e) => x.OnAutoGenerateColumnsChanged(e)); + + FocusableProperty.OverrideDefaultValue(true); } /// @@ -2478,7 +2480,7 @@ namespace Avalonia.Controls if (_hScrollBar != null) { - //_hScrollBar.IsTabStop = false; + _hScrollBar.IsTabStop = false; _hScrollBar.Maximum = 0.0; _hScrollBar.Orientation = Orientation.Horizontal; _hScrollBar.IsVisible = false; @@ -2494,7 +2496,7 @@ namespace Avalonia.Controls if (_vScrollBar != null) { - //_vScrollBar.IsTabStop = false; + _vScrollBar.IsTabStop = false; _vScrollBar.Maximum = 0.0; _vScrollBar.Orientation = Orientation.Vertical; _vScrollBar.IsVisible = false; @@ -3734,7 +3736,7 @@ namespace Avalonia.Controls if (sender is Control editingElement) { editingElement.LostFocus -= EditingElement_LostFocus; - if (EditingRow != null && EditingColumnIndex != -1) + if (EditingRow != null && _editingColumnIndex != -1) { FocusEditingCell(true); } @@ -4039,18 +4041,22 @@ namespace Avalonia.Controls return true; } - Debug.Assert(EditingRow != null); + var editingRow = EditingRow; + if (editingRow is null) + { + return true; + } + Debug.Assert(_editingColumnIndex >= 0); Debug.Assert(_editingColumnIndex < ColumnsItemsInternal.Count); Debug.Assert(_editingColumnIndex == CurrentColumnIndex); - Debug.Assert(EditingRow != null && EditingRow.Slot == CurrentSlot); // Cache these to see if they change later int currentSlot = CurrentSlot; int currentColumnIndex = CurrentColumnIndex; // We're ready to start ending, so raise the event - DataGridCell editingCell = EditingRow.Cells[_editingColumnIndex]; + DataGridCell editingCell = editingRow.Cells[_editingColumnIndex]; var editingElement = editingCell.Content as Control; if (editingElement == null) { @@ -4058,7 +4064,7 @@ namespace Avalonia.Controls } if (raiseEvents) { - DataGridCellEditEndingEventArgs e = new DataGridCellEditEndingEventArgs(CurrentColumn, EditingRow, editingElement, editAction); + DataGridCellEditEndingEventArgs e = new DataGridCellEditEndingEventArgs(CurrentColumn, editingRow, editingElement, editAction); OnCellEditEnding(e); if (e.Cancel) { @@ -4112,7 +4118,7 @@ namespace Avalonia.Controls } else { - if (EditingRow != null) + if (editingRow != null) { if (editingCell.IsValid) { @@ -4120,10 +4126,10 @@ namespace Avalonia.Controls editingCell.UpdatePseudoClasses(); } - if (EditingRow.IsValid) + if (editingRow.IsValid) { - EditingRow.IsValid = false; - EditingRow.UpdatePseudoClasses(); + editingRow.IsValid = false; + editingRow.UpdatePseudoClasses(); } } @@ -4169,22 +4175,22 @@ namespace Avalonia.Controls PopulateCellContent( isCellEdited: !exitEditingMode, dataGridColumn: CurrentColumn, - dataGridRow: EditingRow, + dataGridRow: editingRow, dataGridCell: editingCell); - EditingRow.InvalidateDesiredHeight(); + editingRow.InvalidateDesiredHeight(); var column = editingCell.OwningColumn; if (column.Width.IsSizeToCells || column.Width.IsAuto) {// Invalidate desired width and force recalculation column.SetWidthDesiredValue(0); - EditingRow.OwningGrid.AutoSizeColumn(column, editingCell.DesiredSize.Width); + editingRow.OwningGrid.AutoSizeColumn(column, editingCell.DesiredSize.Width); } } // We're done, so raise the CellEditEnded event if (raiseEvents) { - OnCellEditEnded(new DataGridCellEditEndedEventArgs(CurrentColumn, EditingRow, editAction)); + OnCellEditEnded(new DataGridCellEditEndedEventArgs(CurrentColumn, editingRow, editAction)); } // There's a chance that somebody reopened this cell for edit within the CellEditEnded handler, @@ -4427,8 +4433,7 @@ namespace Avalonia.Controls dataGridCell.Focus(); success = dataGridCell.ContainsFocusedElement(); } - //TODO Check - //success = dataGridCell.ContainsFocusedElement() ? true : dataGridCell.Focus(); + _focusEditingControl = !success; } return success; diff --git a/src/Avalonia.Controls.DataGrid/DataGridCell.cs b/src/Avalonia.Controls.DataGrid/DataGridCell.cs index dd802678d4..599bea056b 100644 --- a/src/Avalonia.Controls.DataGrid/DataGridCell.cs +++ b/src/Avalonia.Controls.DataGrid/DataGridCell.cs @@ -33,6 +33,8 @@ namespace Avalonia.Controls { PointerPressedEvent.AddClassHandler( (x,e) => x.DataGridCell_PointerPressed(e), handledEventsToo: true); + FocusableProperty.OverrideDefaultValue(true); + IsTabStopProperty.OverrideDefaultValue(false); } public DataGridCell() { } @@ -169,8 +171,7 @@ namespace Avalonia.Controls OwningGrid.OnCellPointerPressed(new DataGridCellPointerPressedEventArgs(this, OwningRow, OwningColumn, e)); if (e.GetCurrentPoint(this).Properties.IsLeftButtonPressed) { - if (!e.Handled) - //if (!e.Handled && OwningGrid.IsTabStop) + if (!e.Handled && OwningGrid.IsTabStop) { OwningGrid.Focus(); } @@ -190,8 +191,7 @@ namespace Avalonia.Controls } else if (e.GetCurrentPoint(this).Properties.IsRightButtonPressed) { - if (!e.Handled) - //if (!e.Handled && OwningGrid.IsTabStop) + if (!e.Handled && OwningGrid.IsTabStop) { OwningGrid.Focus(); } diff --git a/src/Avalonia.Controls.DataGrid/DataGridColumnHeader.cs b/src/Avalonia.Controls.DataGrid/DataGridColumnHeader.cs index 5250f80f77..ef1e84c745 100644 --- a/src/Avalonia.Controls.DataGrid/DataGridColumnHeader.cs +++ b/src/Avalonia.Controls.DataGrid/DataGridColumnHeader.cs @@ -72,6 +72,7 @@ namespace Avalonia.Controls { AreSeparatorsVisibleProperty.Changed.AddClassHandler((x, e) => x.OnAreSeparatorsVisibleChanged(e)); PressedMixin.Attach(); + IsTabStopProperty.OverrideDefaultValue(false); } /// diff --git a/src/Avalonia.Controls.DataGrid/DataGridColumns.cs b/src/Avalonia.Controls.DataGrid/DataGridColumns.cs index 703bc0d9c3..4056b78bfe 100644 --- a/src/Avalonia.Controls.DataGrid/DataGridColumns.cs +++ b/src/Avalonia.Controls.DataGrid/DataGridColumns.cs @@ -12,6 +12,7 @@ using System.Collections.Generic; using System.ComponentModel.DataAnnotations; using System.Diagnostics; using System.Reflection; +using Avalonia.Layout; namespace Avalonia.Controls { @@ -489,7 +490,7 @@ namespace Avalonia.Controls { DataGridFillerColumn fillerColumn = ColumnsInternal.FillerColumn; double totalColumnsWidth = ColumnsInternal.VisibleEdgedColumnsWidth; - if (finalWidth > totalColumnsWidth) + if (finalWidth - totalColumnsWidth > LayoutHelper.LayoutEpsilon) { fillerColumn.FillerWidth = finalWidth - totalColumnsWidth; } @@ -971,6 +972,12 @@ namespace Avalonia.Controls { cx += _negHorizontalOffset; _horizontalOffset -= _negHorizontalOffset; + if (_horizontalOffset < LayoutHelper.LayoutEpsilon) + { + // Snap to zero to avoid trying to partially scroll in first scrolled off column below + _horizontalOffset = 0; + } + _negHorizontalOffset = 0; } else @@ -979,6 +986,11 @@ namespace Avalonia.Controls _negHorizontalOffset -= displayWidth - cx; cx = displayWidth; } + + // Make sure the HorizontalAdjustment is not greater than the new HorizontalOffset + // since it would cause an assertion failure in DataGridCellsPresenter.ShouldDisplayCell + // called by DataGridCellsPresenter.MeasureOverride. + HorizontalAdjustment = Math.Min(HorizontalAdjustment, _horizontalOffset); } // second try to scroll entire columns if (cx < displayWidth && _horizontalOffset > 0) diff --git a/src/Avalonia.Controls.DataGrid/DataGridRow.cs b/src/Avalonia.Controls.DataGrid/DataGridRow.cs index ea9b2fe972..dfda7d6e4f 100644 --- a/src/Avalonia.Controls.DataGrid/DataGridRow.cs +++ b/src/Avalonia.Controls.DataGrid/DataGridRow.cs @@ -128,6 +128,7 @@ namespace Avalonia.Controls DetailsTemplateProperty.Changed.AddClassHandler((x, e) => x.OnDetailsTemplateChanged(e)); AreDetailsVisibleProperty.Changed.AddClassHandler((x, e) => x.OnAreDetailsVisibleChanged(e)); PointerPressedEvent.AddClassHandler((x, e) => x.DataGridRow_PointerPressed(e), handledEventsToo: true); + IsTabStopProperty.OverrideDefaultValue(false); } /// diff --git a/src/Avalonia.Controls.DataGrid/DataGridRowGroupHeader.cs b/src/Avalonia.Controls.DataGrid/DataGridRowGroupHeader.cs index 10efded58a..e51c2526b1 100644 --- a/src/Avalonia.Controls.DataGrid/DataGridRowGroupHeader.cs +++ b/src/Avalonia.Controls.DataGrid/DataGridRowGroupHeader.cs @@ -106,6 +106,7 @@ namespace Avalonia.Controls { SublevelIndentProperty.Changed.AddClassHandler((x,e) => x.OnSublevelIndentChanged(e)); PressedMixin.Attach(); + IsTabStopProperty.OverrideDefaultValue(false); } /// @@ -301,8 +302,7 @@ namespace Avalonia.Controls } else { - //if (!e.Handled && OwningGrid.IsTabStop) - if (!e.Handled) + if (!e.Handled && OwningGrid.IsTabStop) { OwningGrid.Focus(); } diff --git a/src/Avalonia.Controls.DataGrid/DataGridRows.cs b/src/Avalonia.Controls.DataGrid/DataGridRows.cs index 00e035270c..44079d24d0 100644 --- a/src/Avalonia.Controls.DataGrid/DataGridRows.cs +++ b/src/Avalonia.Controls.DataGrid/DataGridRows.cs @@ -1589,6 +1589,23 @@ namespace Avalonia.Controls CorrectSlotsAfterDeletion(slot, isRow); OnRemovedElement(slot, item); + + // Synchronize CurrentCellCoordinates, CurrentColumn, CurrentColumnIndex, CurrentItem + // and CurrentSlot with the currently edited cell, since OnRemovingElement called + // SetCurrentCellCore(-1, -1) to temporarily reset the current cell. + if (_temporarilyResetCurrentCell && + _editingColumnIndex != -1 && + _previousCurrentItem != null && + EditingRow != null && + EditingRow.Slot != -1) + { + ProcessSelectionAndCurrency( + columnIndex: _editingColumnIndex, + item: _previousCurrentItem, + backupSlot: this.EditingRow.Slot, + action: DataGridSelectionAction.None, + scrollIntoView: false); + } } private void RemoveNonDisplayedRows(int newFirstDisplayedSlot, int newLastDisplayedSlot) diff --git a/src/Avalonia.Controls.DataGrid/Themes/Fluent.xaml b/src/Avalonia.Controls.DataGrid/Themes/Fluent.xaml index e4642c1453..082eac60be 100644 --- a/src/Avalonia.Controls.DataGrid/Themes/Fluent.xaml +++ b/src/Avalonia.Controls.DataGrid/Themes/Fluent.xaml @@ -82,7 +82,6 @@ - - @@ -268,7 +266,6 @@ - @@ -310,7 +307,6 @@ - @@ -408,7 +404,6 @@ - @@ -433,7 +428,7 @@ BorderThickness="{TemplateBinding BorderThickness}" Background="{TemplateBinding Background}" CornerRadius="{TemplateBinding CornerRadius}" - Focusable="False" + IsTabStop="False" Foreground="{TemplateBinding Foreground}" /> +