diff --git a/.gitmodules b/.gitmodules index 9dbc50ef61..2d11fdfa9e 100644 --- a/.gitmodules +++ b/.gitmodules @@ -2,5 +2,5 @@ path = nukebuild/Numerge url = https://github.com/kekekeks/Numerge.git [submodule "src/Markup/Avalonia.Markup.Xaml/XamlIl/xamlil.github"] - path = src/Markup/Avalonia.Markup.Xaml/XamlIl/xamlil.github + path = src/Markup/Avalonia.Markup.Xaml.Loader/xamlil.github url = https://github.com/kekekeks/XamlX.git diff --git a/Avalonia.sln b/Avalonia.sln index 4ab647a25e..ffd7b4d4f4 100644 --- a/Avalonia.sln +++ b/Avalonia.sln @@ -123,10 +123,13 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ControlCatalog.NetCore", "s EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Props", "Props", "{F3AC8BC1-27F5-4255-9AFC-04ABFD11683A}" ProjectSection(SolutionItems) = preProject + build\AndroidWorkarounds.props = build\AndroidWorkarounds.props build\Base.props = build\Base.props build\Binding.props = build\Binding.props - build\BuildTargets.targets = build\BuildTargets.targets + build\CoreLibraries.props = build\CoreLibraries.props + build\EmbedXaml.props = build\EmbedXaml.props build\HarfBuzzSharp.props = build\HarfBuzzSharp.props + build\iOSWorkarounds.props = build\iOSWorkarounds.props build\JetBrains.Annotations.props = build\JetBrains.Annotations.props build\JetBrains.dotMemoryUnit.props = build\JetBrains.dotMemoryUnit.props build\Magick.NET-Q16-AnyCPU.props = build\Magick.NET-Q16-AnyCPU.props @@ -136,17 +139,25 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Props", "Props", "{F3AC8BC1 build\NetCore.props = build\NetCore.props build\NetFX.props = build\NetFX.props build\ReactiveUI.props = build\ReactiveUI.props + build\ReferenceCoreLibraries.props = build\ReferenceCoreLibraries.props build\Rx.props = build\Rx.props build\SampleApp.props = build\SampleApp.props + build\SharedVersion.props = build\SharedVersion.props build\SharpDX.props = build\SharpDX.props build\SkiaSharp.props = build\SkiaSharp.props + build\SourceLink.props = build\SourceLink.props + build\System.Drawing.Common.props = build\System.Drawing.Common.props build\System.Memory.props = build\System.Memory.props + build\UnitTests.NetFX.props = build\UnitTests.NetFX.props build\XUnit.props = build\XUnit.props + build\ApiDiff.props = build\ApiDiff.props EndProjectSection EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Targets", "Targets", "{4D6FAF79-58B4-482F-9122-0668C346364C}" ProjectSection(SolutionItems) = preProject build\UnitTests.NetCore.targets = build\UnitTests.NetCore.targets + build\BuildTargets.targets = build\BuildTargets.targets + build\LegacyProject.targets = build\LegacyProject.targets EndProjectSection EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Linux", "Linux", "{86C53C40-57AA-45B8-AD42-FAE0EFDF0F2B}" @@ -211,6 +222,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Avalonia.Headless", "src\Av EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Avalonia.Headless.Vnc", "src\Avalonia.Headless.Vnc\Avalonia.Headless.Vnc.csproj", "{B859AE7C-F34F-4A9E-88AE-E0E7229FDE1E}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Avalonia.Markup.Xaml.Loader", "src\Markup\Avalonia.Markup.Xaml.Loader\Avalonia.Markup.Xaml.Loader.csproj", "{909A8CBD-7D0E-42FD-B841-022AD8925820}" +EndProject Global GlobalSection(SharedMSBuildProjectFiles) = preSolution src\Shared\RenderHelpers\RenderHelpers.projitems*{3c4c0cb4-0c0f-4450-a37b-148c84ff905f}*SharedItemsImports = 13 @@ -1998,6 +2011,30 @@ Global {C42D2FC1-A531-4ED4-84B9-89AEC7C962FC}.Release|iPhone.Build.0 = Release|Any CPU {C42D2FC1-A531-4ED4-84B9-89AEC7C962FC}.Release|iPhoneSimulator.ActiveCfg = Release|Any CPU {C42D2FC1-A531-4ED4-84B9-89AEC7C962FC}.Release|iPhoneSimulator.Build.0 = Release|Any CPU + {909A8CBD-7D0E-42FD-B841-022AD8925820}.Ad-Hoc|Any CPU.ActiveCfg = Debug|Any CPU + {909A8CBD-7D0E-42FD-B841-022AD8925820}.Ad-Hoc|Any CPU.Build.0 = Debug|Any CPU + {909A8CBD-7D0E-42FD-B841-022AD8925820}.Ad-Hoc|iPhone.ActiveCfg = Debug|Any CPU + {909A8CBD-7D0E-42FD-B841-022AD8925820}.Ad-Hoc|iPhone.Build.0 = Debug|Any CPU + {909A8CBD-7D0E-42FD-B841-022AD8925820}.Ad-Hoc|iPhoneSimulator.ActiveCfg = Debug|Any CPU + {909A8CBD-7D0E-42FD-B841-022AD8925820}.Ad-Hoc|iPhoneSimulator.Build.0 = Debug|Any CPU + {909A8CBD-7D0E-42FD-B841-022AD8925820}.AppStore|Any CPU.ActiveCfg = Debug|Any CPU + {909A8CBD-7D0E-42FD-B841-022AD8925820}.AppStore|Any CPU.Build.0 = Debug|Any CPU + {909A8CBD-7D0E-42FD-B841-022AD8925820}.AppStore|iPhone.ActiveCfg = Debug|Any CPU + {909A8CBD-7D0E-42FD-B841-022AD8925820}.AppStore|iPhone.Build.0 = Debug|Any CPU + {909A8CBD-7D0E-42FD-B841-022AD8925820}.AppStore|iPhoneSimulator.ActiveCfg = Debug|Any CPU + {909A8CBD-7D0E-42FD-B841-022AD8925820}.AppStore|iPhoneSimulator.Build.0 = Debug|Any CPU + {909A8CBD-7D0E-42FD-B841-022AD8925820}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {909A8CBD-7D0E-42FD-B841-022AD8925820}.Debug|Any CPU.Build.0 = Debug|Any CPU + {909A8CBD-7D0E-42FD-B841-022AD8925820}.Debug|iPhone.ActiveCfg = Debug|Any CPU + {909A8CBD-7D0E-42FD-B841-022AD8925820}.Debug|iPhone.Build.0 = Debug|Any CPU + {909A8CBD-7D0E-42FD-B841-022AD8925820}.Debug|iPhoneSimulator.ActiveCfg = Debug|Any CPU + {909A8CBD-7D0E-42FD-B841-022AD8925820}.Debug|iPhoneSimulator.Build.0 = Debug|Any CPU + {909A8CBD-7D0E-42FD-B841-022AD8925820}.Release|Any CPU.ActiveCfg = Release|Any CPU + {909A8CBD-7D0E-42FD-B841-022AD8925820}.Release|Any CPU.Build.0 = Release|Any CPU + {909A8CBD-7D0E-42FD-B841-022AD8925820}.Release|iPhone.ActiveCfg = Release|Any CPU + {909A8CBD-7D0E-42FD-B841-022AD8925820}.Release|iPhone.Build.0 = Release|Any CPU + {909A8CBD-7D0E-42FD-B841-022AD8925820}.Release|iPhoneSimulator.ActiveCfg = Release|Any CPU + {909A8CBD-7D0E-42FD-B841-022AD8925820}.Release|iPhoneSimulator.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -2056,6 +2093,7 @@ Global {AF915D5C-AB00-4EA0-B5E6-001F4AE84E68} = {C5A00AC3-B34C-4564-9BDD-2DA473EF4D8B} {351337F5-D66F-461B-A957-4EF60BDB4BA6} = {C5A00AC3-B34C-4564-9BDD-2DA473EF4D8B} {3C84E04B-36CF-4D0D-B965-C26DD649D1F3} = {A0CC0258-D18C-4AB3-854F-7101680FC3F9} + {909A8CBD-7D0E-42FD-B841-022AD8925820} = {8B6A8209-894F-4BA1-B880-965FD453982C} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {87366D66-1391-4D90-8999-95A620AD786A} diff --git a/Avalonia.v3.ncrunchsolution b/Avalonia.v3.ncrunchsolution index a2208a9a91..bef7e45524 100644 --- a/Avalonia.v3.ncrunchsolution +++ b/Avalonia.v3.ncrunchsolution @@ -3,6 +3,7 @@ tests\TestFiles\**.* src\Avalonia.Build.Tasks\bin\Debug\netstandard2.0\Avalonia.Build.Tasks.dll + src\Avalonia.Build.Tasks\bin\Debug\netstandard2.0\Mono.Cecil.dll True .ncrunch diff --git a/Directory.Build.props b/Directory.Build.props index 1f26df9bbc..b41f8c488e 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -1,5 +1,6 @@ $(MSBuildThisFileDirectory)build-intermediate/nuget + $(MSBuildThisFileDirectory)\src\tools\Avalonia.Designer.HostApp\bin\$(Configuration)\netcoreapp2.0\Avalonia.Designer.HostApp.dll diff --git a/NuGet.Config b/NuGet.Config index 2a1e0af74d..3abd236d42 100644 --- a/NuGet.Config +++ b/NuGet.Config @@ -1,7 +1,9 @@  + + diff --git a/build/ApiDiff.props b/build/ApiDiff.props new file mode 100644 index 0000000000..1573dc2dd6 --- /dev/null +++ b/build/ApiDiff.props @@ -0,0 +1,12 @@ + + + 0.10.0-preview2 + $(PackageId) + Avalonia + + + + + + + diff --git a/build/SkiaSharp.props b/build/SkiaSharp.props index a8d9332c57..bbef48050e 100644 --- a/build/SkiaSharp.props +++ b/build/SkiaSharp.props @@ -1,6 +1,6 @@  - + diff --git a/dirs.proj b/dirs.proj index 4b3b1183f0..26c8f54b23 100644 --- a/dirs.proj +++ b/dirs.proj @@ -6,7 +6,7 @@ - + diff --git a/native/Avalonia.Native/src/OSX/Screens.mm b/native/Avalonia.Native/src/OSX/Screens.mm index 278daf9a18..455cfa2e41 100644 --- a/native/Avalonia.Native/src/OSX/Screens.mm +++ b/native/Avalonia.Native/src/OSX/Screens.mm @@ -4,6 +4,14 @@ class Screens : public ComSingleObject { public: FORWARD_IUNKNOWN() + + private: + CGFloat PrimaryDisplayHeight() + { + return NSMaxY([[[NSScreen screens] firstObject] frame]); + } + +public: virtual HRESULT GetScreenCount (int* ret) override { @autoreleasepool @@ -25,15 +33,15 @@ class Screens : public ComSingleObject auto screen = [[NSScreen screens] objectAtIndex:index]; - ret->Bounds.X = [screen frame].origin.x; - ret->Bounds.Y = [screen frame].origin.y; ret->Bounds.Height = [screen frame].size.height; ret->Bounds.Width = [screen frame].size.width; + ret->Bounds.X = [screen frame].origin.x; + ret->Bounds.Y = PrimaryDisplayHeight() - [screen frame].origin.y - ret->Bounds.Height; - ret->WorkingArea.X = [screen visibleFrame].origin.x; - ret->WorkingArea.Y = [screen visibleFrame].origin.y; ret->WorkingArea.Height = [screen visibleFrame].size.height; ret->WorkingArea.Width = [screen visibleFrame].size.width; + ret->WorkingArea.X = [screen visibleFrame].origin.x; + ret->WorkingArea.Y = ret->Bounds.Height - [screen visibleFrame].origin.y - ret->WorkingArea.Height; ret->PixelDensity = [screen backingScaleFactor]; diff --git a/native/Avalonia.Native/src/OSX/window.mm b/native/Avalonia.Native/src/OSX/window.mm index 872269bb26..2d0ffbe4f0 100644 --- a/native/Avalonia.Native/src/OSX/window.mm +++ b/native/Avalonia.Native/src/OSX/window.mm @@ -1291,10 +1291,15 @@ NSArray* AllLoopModes = [NSArray arrayWithObjects: NSDefaultRunLoopMode, NSEvent _parent->UpdateCursor(); auto fsize = [self convertSizeToBacking: [self frame].size]; - _lastPixelSize.Width = (int)fsize.width; - _lastPixelSize.Height = (int)fsize.height; - [self updateRenderTarget]; - _parent->BaseEvents->Resized(AvnSize{newSize.width, newSize.height}); + + if(_lastPixelSize.Width != (int)fsize.width || _lastPixelSize.Height != (int)fsize.height) + { + _lastPixelSize.Width = (int)fsize.width; + _lastPixelSize.Height = (int)fsize.height; + [self updateRenderTarget]; + + _parent->BaseEvents->Resized(AvnSize{newSize.width, newSize.height}); + } } - (void)updateLayer diff --git a/nukebuild/Build.cs b/nukebuild/Build.cs index fe877dc49c..fbfbf47e1b 100644 --- a/nukebuild/Build.cs +++ b/nukebuild/Build.cs @@ -128,7 +128,9 @@ partial class Build : NukeBuild { var webappDir = RootDirectory / "src" / "Avalonia.DesignerSupport" / "Remote" / "HtmlTransport" / "webapp"; - NpmTasks.NpmInstall(c => c.SetWorkingDirectory(webappDir)); + NpmTasks.NpmInstall(c => c + .SetWorkingDirectory(webappDir) + .SetArgumentConfigurator(a => a.Add("--silent"))); NpmTasks.NpmRun(c => c .SetWorkingDirectory(webappDir) .SetCommand("dist")); diff --git a/readme.md b/readme.md index 19a9a8420d..c1bf5bbfde 100644 --- a/readme.md +++ b/readme.md @@ -1,4 +1,4 @@ -[![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) [![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) +[![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) [![MyGet](https://img.shields.io/myget/avalonia-ci/vpre/Avalonia.svg?label=myget)](https://www.myget.org/gallery/avalonia-ci) ![Size](https://img.shields.io/github/repo-size/avaloniaui/avalonia.svg) diff --git a/samples/ControlCatalog/App.xaml.cs b/samples/ControlCatalog/App.xaml.cs index 0baa6589c6..3f1ec289d1 100644 --- a/samples/ControlCatalog/App.xaml.cs +++ b/samples/ControlCatalog/App.xaml.cs @@ -29,11 +29,19 @@ namespace ControlCatalog { new StyleInclude(new Uri("resm:Styles?assembly=ControlCatalog")) { - Source = new Uri("resm:Avalonia.Themes.Default.Accents.BaseLight.xaml?assembly=Avalonia.Themes.Default") + Source = new Uri("avares://Avalonia.Themes.Fluent/Accents/Base.xaml") }, new StyleInclude(new Uri("resm:Styles?assembly=ControlCatalog")) { - Source = new Uri("resm:Avalonia.Themes.Default.DefaultTheme.xaml?assembly=Avalonia.Themes.Default") + Source = new Uri("avares://Avalonia.Themes.Fluent/Accents/BaseLight.xaml") + }, + new StyleInclude(new Uri("resm:Styles?assembly=ControlCatalog")) + { + Source = new Uri("avares://Avalonia.Themes.Default/Accents/BaseLight.xaml") + }, + new StyleInclude(new Uri("resm:Styles?assembly=ControlCatalog")) + { + Source = new Uri("avares://Avalonia.Themes.Default/DefaultTheme.xaml") }, }; @@ -41,11 +49,19 @@ namespace ControlCatalog { new StyleInclude(new Uri("resm:Styles?assembly=ControlCatalog")) { - Source = new Uri("resm:Avalonia.Themes.Default.Accents.BaseDark.xaml?assembly=Avalonia.Themes.Default") + Source = new Uri("avares://Avalonia.Themes.Fluent/Accents/Base.xaml") + }, + new StyleInclude(new Uri("resm:Styles?assembly=ControlCatalog")) + { + Source = new Uri("avares://Avalonia.Themes.Fluent/Accents/BaseDark.xaml") + }, + new StyleInclude(new Uri("resm:Styles?assembly=ControlCatalog")) + { + Source = new Uri("avares://Avalonia.Themes.Default/Accents/BaseDark.xaml") }, new StyleInclude(new Uri("resm:Styles?assembly=ControlCatalog")) { - Source = new Uri("resm:Avalonia.Themes.Default.DefaultTheme.xaml?assembly=Avalonia.Themes.Default") + Source = new Uri("avares://Avalonia.Themes.Default/DefaultTheme.xaml") }, }; diff --git a/samples/ControlCatalog/MainView.xaml b/samples/ControlCatalog/MainView.xaml index fa4fd7dd07..af95e3c356 100644 --- a/samples/ControlCatalog/MainView.xaml +++ b/samples/ControlCatalog/MainView.xaml @@ -2,7 +2,6 @@ xmlns:pages="clr-namespace:ControlCatalog.Pages" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" x:Class="ControlCatalog.MainView" - Background="Black" Foreground="{DynamicResource ThemeForegroundBrush}" FontSize="{DynamicResource FontSizeNormal}"> diff --git a/samples/ControlCatalog/Pages/ItemsRepeaterPage.xaml b/samples/ControlCatalog/Pages/ItemsRepeaterPage.xaml index 08bb1584a9..304782dbf9 100644 --- a/samples/ControlCatalog/Pages/ItemsRepeaterPage.xaml +++ b/samples/ControlCatalog/Pages/ItemsRepeaterPage.xaml @@ -1,6 +1,30 @@ + + + + + + + + + + + + + + + + ItemsRepeater @@ -12,8 +36,6 @@ Stack - Horizontal UniformGrid - Vertical UniformGrid - Horizontal - WrapLayout - Horizontal - WrapLayout - Veritcal @@ -25,20 +47,8 @@ - - - - - - - - - - - - + diff --git a/samples/ControlCatalog/Pages/ItemsRepeaterPage.xaml.cs b/samples/ControlCatalog/Pages/ItemsRepeaterPage.xaml.cs index a4003cadc9..9e898c4536 100644 --- a/samples/ControlCatalog/Pages/ItemsRepeaterPage.xaml.cs +++ b/samples/ControlCatalog/Pages/ItemsRepeaterPage.xaml.cs @@ -38,6 +38,12 @@ namespace ControlCatalog.Pages AvaloniaXamlLoader.Load(this); } + public void OnSelectTemplateKey(object sender, SelectTemplateEventArgs e) + { + var item = (ItemsRepeaterPageViewModel.Item)e.DataContext; + e.TemplateKey = (item.Index % 2 == 0) ? "even" : "odd"; + } + private void LayoutChanged(object sender, SelectionChangedEventArgs e) { if (_repeater == null) diff --git a/samples/ControlCatalog/Pages/ListBoxPage.xaml b/samples/ControlCatalog/Pages/ListBoxPage.xaml index 47b4ce7151..f4d81418ac 100644 --- a/samples/ControlCatalog/Pages/ListBoxPage.xaml +++ b/samples/ControlCatalog/Pages/ListBoxPage.xaml @@ -10,7 +10,13 @@ HorizontalAlignment="Center" Spacing="16"> - + diff --git a/samples/ControlCatalog/Pages/MenuPage.xaml b/samples/ControlCatalog/Pages/MenuPage.xaml index de9ea34e80..e9d2301e89 100644 --- a/samples/ControlCatalog/Pages/MenuPage.xaml +++ b/samples/ControlCatalog/Pages/MenuPage.xaml @@ -20,7 +20,9 @@ - + + + @@ -52,6 +54,33 @@ + + + + Mixed + + + + + + + + + + + + + + + + + + + + + diff --git a/samples/ControlCatalog/ViewModels/ItemsRepeaterPageViewModel.cs b/samples/ControlCatalog/ViewModels/ItemsRepeaterPageViewModel.cs index 73aaeff994..f893a6e28e 100644 --- a/samples/ControlCatalog/ViewModels/ItemsRepeaterPageViewModel.cs +++ b/samples/ControlCatalog/ViewModels/ItemsRepeaterPageViewModel.cs @@ -62,13 +62,9 @@ namespace ControlCatalog.ViewModels public class Item : ReactiveObject { private double _height = double.NaN; - private int _index; - - public Item(int index) - { - _index = index; - } + public Item(int index) => Index = index; + public int Index { get; } public string Text { get; set; } public double Height @@ -76,8 +72,6 @@ namespace ControlCatalog.ViewModels get => _height; set => this.RaiseAndSetIfChanged(ref _height, value); } - - public IBrush Background => ((_index % 2) == 0) ? Brushes.Yellow : Brushes.Wheat; } } } diff --git a/samples/ControlCatalog/ViewModels/MenuPageViewModel.cs b/samples/ControlCatalog/ViewModels/MenuPageViewModel.cs index dc9c4a8f49..9e7ae8b716 100644 --- a/samples/ControlCatalog/ViewModels/MenuPageViewModel.cs +++ b/samples/ControlCatalog/ViewModels/MenuPageViewModel.cs @@ -17,6 +17,23 @@ namespace ControlCatalog.ViewModels SaveCommand = ReactiveCommand.Create(Save, Observable.Return(false)); OpenRecentCommand = ReactiveCommand.Create(OpenRecent); + var recentItems = new[] + { + new MenuItemViewModel + { + Header = "File1.txt", + Command = OpenRecentCommand, + CommandParameter = @"c:\foo\File1.txt" + }, + new MenuItemViewModel + { + Header = "File2.txt", + Command = OpenRecentCommand, + CommandParameter = @"c:\foo\File2.txt" + }, + }; + + RecentItems = recentItems; MenuItems = new[] { new MenuItemViewModel @@ -24,27 +41,13 @@ namespace ControlCatalog.ViewModels Header = "_File", Items = new[] { - new MenuItemViewModel { Header = "_Open...", Command = OpenCommand }, + new MenuItemViewModel { Header = "O_pen...", Command = OpenCommand }, new MenuItemViewModel { Header = "Save", Command = SaveCommand }, new MenuItemViewModel { Header = "-" }, new MenuItemViewModel { Header = "Recent", - Items = new[] - { - new MenuItemViewModel - { - Header = "File1.txt", - Command = OpenRecentCommand, - CommandParameter = @"c:\foo\File1.txt" - }, - new MenuItemViewModel - { - Header = "File2.txt", - Command = OpenRecentCommand, - CommandParameter = @"c:\foo\File2.txt" - }, - } + Items = recentItems }, } }, @@ -61,6 +64,7 @@ namespace ControlCatalog.ViewModels } public IReadOnlyList MenuItems { get; set; } + public IReadOnlyList RecentItems { get; set; } public ReactiveCommand OpenCommand { get; } public ReactiveCommand SaveCommand { get; } public ReactiveCommand OpenRecentCommand { get; } diff --git a/samples/RenderDemo/App.xaml b/samples/RenderDemo/App.xaml index ccc3f54cc0..61e4d2385b 100644 --- a/samples/RenderDemo/App.xaml +++ b/samples/RenderDemo/App.xaml @@ -3,8 +3,7 @@ xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" x:Class="RenderDemo.App"> - - + - \ No newline at end of file + diff --git a/samples/RenderDemo/SideBar.xaml b/samples/RenderDemo/SideBar.xaml index 07fdb91a16..fd23067f61 100644 --- a/samples/RenderDemo/SideBar.xaml +++ b/samples/RenderDemo/SideBar.xaml @@ -1,65 +1,68 @@ - + - - - - - + + + + + + diff --git a/src/Android/Avalonia.Android/Platform/SkiaPlatform/TopLevelImpl.cs b/src/Android/Avalonia.Android/Platform/SkiaPlatform/TopLevelImpl.cs index 69fd2a9f13..71dce93ce7 100644 --- a/src/Android/Avalonia.Android/Platform/SkiaPlatform/TopLevelImpl.cs +++ b/src/Android/Avalonia.Android/Platform/SkiaPlatform/TopLevelImpl.cs @@ -126,7 +126,7 @@ namespace Avalonia.Android.Platform.SkiaPlatform _view.Visibility = ViewStates.Visible; } - public double Scaling => 1; + public double RenderScaling => 1; void Draw() { diff --git a/src/Avalonia.Animation/Avalonia.Animation.csproj b/src/Avalonia.Animation/Avalonia.Animation.csproj index 73c619942e..ee91acf45e 100644 --- a/src/Avalonia.Animation/Avalonia.Animation.csproj +++ b/src/Avalonia.Animation/Avalonia.Animation.csproj @@ -1,9 +1,10 @@  - netstandard2.0 + netstandard2.0 + diff --git a/src/Avalonia.Base/Avalonia.Base.csproj b/src/Avalonia.Base/Avalonia.Base.csproj index cbcf7a7386..9bfd387ca9 100644 --- a/src/Avalonia.Base/Avalonia.Base.csproj +++ b/src/Avalonia.Base/Avalonia.Base.csproj @@ -3,7 +3,7 @@ netstandard2.0 Avalonia.Base Avalonia - True + True @@ -13,4 +13,5 @@ + diff --git a/src/Avalonia.Base/Threading/AvaloniaScheduler.cs b/src/Avalonia.Base/Threading/AvaloniaScheduler.cs index 0bfc713ba0..397826df53 100644 --- a/src/Avalonia.Base/Threading/AvaloniaScheduler.cs +++ b/src/Avalonia.Base/Threading/AvaloniaScheduler.cs @@ -9,6 +9,16 @@ namespace Avalonia.Threading /// public class AvaloniaScheduler : LocalScheduler { + /// + /// Users can schedule actions on the dispatcher thread while being on the correct thread already. + /// We are optimizing this case by invoking user callback immediately which can lead to stack overflows in certain cases. + /// To prevent this we are limiting amount of reentrant calls to before we will + /// schedule on a dispatcher anyway. + /// + private const int MaxReentrantSchedules = 32; + + private int _reentrancyGuard; + /// /// The instance of the . /// @@ -24,31 +34,58 @@ namespace Avalonia.Threading /// public override IDisposable Schedule(TState state, TimeSpan dueTime, Func action) { - var composite = new CompositeDisposable(2); + IDisposable PostOnDispatcher() + { + var composite = new CompositeDisposable(2); + + var cancellation = new CancellationDisposable(); + + Dispatcher.UIThread.Post(() => + { + if (!cancellation.Token.IsCancellationRequested) + { + composite.Add(action(this, state)); + } + }, DispatcherPriority.DataBind); + + composite.Add(cancellation); + + return composite; + } + if (dueTime == TimeSpan.Zero) { if (!Dispatcher.UIThread.CheckAccess()) { - var cancellation = new CancellationDisposable(); - Dispatcher.UIThread.Post(() => - { - if (!cancellation.Token.IsCancellationRequested) - { - composite.Add(action(this, state)); - } - }, DispatcherPriority.DataBind); - composite.Add(cancellation); + return PostOnDispatcher(); } else { - return action(this, state); + if (_reentrancyGuard >= MaxReentrantSchedules) + { + return PostOnDispatcher(); + } + + try + { + _reentrancyGuard++; + + return action(this, state); + } + finally + { + _reentrancyGuard--; + } } } else { + var composite = new CompositeDisposable(2); + composite.Add(DispatcherTimer.RunOnce(() => composite.Add(action(this, state)), dueTime)); + + return composite; } - return composite; } } } diff --git a/src/Avalonia.Base/Threading/AvaloniaSynchronizationContext.cs b/src/Avalonia.Base/Threading/AvaloniaSynchronizationContext.cs index 38a23f918f..40cf81358f 100644 --- a/src/Avalonia.Base/Threading/AvaloniaSynchronizationContext.cs +++ b/src/Avalonia.Base/Threading/AvaloniaSynchronizationContext.cs @@ -1,3 +1,5 @@ +using System; +using System.Runtime.ConstrainedExecution; using System.Threading; namespace Avalonia.Threading @@ -7,6 +9,20 @@ namespace Avalonia.Threading /// public class AvaloniaSynchronizationContext : SynchronizationContext { + public interface INonPumpingPlatformWaitProvider + { + int Wait(IntPtr[] waitHandles, bool waitAll, int millisecondsTimeout); + } + + private readonly INonPumpingPlatformWaitProvider _waitProvider; + + public AvaloniaSynchronizationContext(INonPumpingPlatformWaitProvider waitProvider) + { + _waitProvider = waitProvider; + if (_waitProvider != null) + SetWaitNotificationRequired(); + } + /// /// Controls if SynchronizationContext should be installed in InstallIfNeeded. Used by Designer. /// @@ -22,7 +38,8 @@ namespace Avalonia.Threading return; } - SetSynchronizationContext(new AvaloniaSynchronizationContext()); + SetSynchronizationContext(new AvaloniaSynchronizationContext(AvaloniaLocator.Current + .GetService())); } /// @@ -39,5 +56,13 @@ namespace Avalonia.Threading else Dispatcher.UIThread.InvokeAsync(() => d(state), DispatcherPriority.Send).Wait(); } + + [PrePrepareMethod] + public override int Wait(IntPtr[] waitHandles, bool waitAll, int millisecondsTimeout) + { + if (_waitProvider != null) + return _waitProvider.Wait(waitHandles, waitAll, millisecondsTimeout); + return base.Wait(waitHandles, waitAll, millisecondsTimeout); + } } } diff --git a/src/Avalonia.Build.Tasks/Avalonia.Build.Tasks.csproj b/src/Avalonia.Build.Tasks/Avalonia.Build.Tasks.csproj index 7117fd51a2..490364c0d8 100644 --- a/src/Avalonia.Build.Tasks/Avalonia.Build.Tasks.csproj +++ b/src/Avalonia.Build.Tasks/Avalonia.Build.Tasks.csproj @@ -17,14 +17,14 @@ Shared/AvaloniaResourceXamlInfo.cs - + XamlIlExtensions/%(RecursiveDir)%(FileName)%(Extension) - + XamlIl/%(RecursiveDir)%(FileName)%(Extension) - + XamlIl.Cecil/%(RecursiveDir)%(FileName)%(Extension) @@ -57,8 +57,8 @@ Markup/%(RecursiveDir)%(FileName)%(Extension) - - + + diff --git a/src/Avalonia.Build.Tasks/XamlCompilerTaskExecutor.cs b/src/Avalonia.Build.Tasks/XamlCompilerTaskExecutor.cs index c1cc0e7bf0..c9c9c562bd 100644 --- a/src/Avalonia.Build.Tasks/XamlCompilerTaskExecutor.cs +++ b/src/Avalonia.Build.Tasks/XamlCompilerTaskExecutor.cs @@ -128,7 +128,7 @@ namespace Avalonia.Build.Tasks asm.MainModule.Types.Add(typeDef); var builder = typeSystem.CreateTypeBuilder(typeDef); - foreach (var res in group.Resources.Where(CheckXamlName)) + foreach (var res in group.Resources.Where(CheckXamlName).OrderBy(x=>x.FilePath.ToLowerInvariant())) { try { diff --git a/src/Avalonia.Controls.DataGrid/Avalonia.Controls.DataGrid.csproj b/src/Avalonia.Controls.DataGrid/Avalonia.Controls.DataGrid.csproj index 27853a1540..6975642db2 100644 --- a/src/Avalonia.Controls.DataGrid/Avalonia.Controls.DataGrid.csproj +++ b/src/Avalonia.Controls.DataGrid/Avalonia.Controls.DataGrid.csproj @@ -22,4 +22,5 @@ + diff --git a/src/Avalonia.Controls.DataGrid/DataGrid.cs b/src/Avalonia.Controls.DataGrid/DataGrid.cs index fe9656a00b..7e921944ea 100644 --- a/src/Avalonia.Controls.DataGrid/DataGrid.cs +++ b/src/Avalonia.Controls.DataGrid/DataGrid.cs @@ -3922,6 +3922,12 @@ namespace Avalonia.Controls dataGridCell: editingCell); 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); + } } // We're done, so raise the CellEditEnded event diff --git a/src/Avalonia.Controls/Avalonia.Controls.csproj b/src/Avalonia.Controls/Avalonia.Controls.csproj index 32331d29ab..480dcfcb85 100644 --- a/src/Avalonia.Controls/Avalonia.Controls.csproj +++ b/src/Avalonia.Controls/Avalonia.Controls.csproj @@ -1,6 +1,6 @@  - netstandard2.0 + netstandard2.0 @@ -14,4 +14,5 @@ + diff --git a/src/Avalonia.Controls/Chrome/CaptionButtons.cs b/src/Avalonia.Controls/Chrome/CaptionButtons.cs index 75d6c366b8..a86cbc271b 100644 --- a/src/Avalonia.Controls/Chrome/CaptionButtons.cs +++ b/src/Avalonia.Controls/Chrome/CaptionButtons.cs @@ -38,12 +38,10 @@ namespace Avalonia.Controls.Chrome { if (_disposables != null) { - var layer = ChromeOverlayLayer.GetOverlayLayer(_hostWindow); - - layer?.Children.Remove(this); - _disposables.Dispose(); _disposables = null; + + _hostWindow = null; } } diff --git a/src/Avalonia.Controls/Chrome/TitleBar.cs b/src/Avalonia.Controls/Chrome/TitleBar.cs index 78b49d2a03..c0c8076dd8 100644 --- a/src/Avalonia.Controls/Chrome/TitleBar.cs +++ b/src/Avalonia.Controls/Chrome/TitleBar.cs @@ -12,106 +12,86 @@ namespace Avalonia.Controls.Chrome public class TitleBar : TemplatedControl { private CompositeDisposable? _disposables; - private readonly Window? _hostWindow; private CaptionButtons? _captionButtons; - public TitleBar(Window hostWindow) + private void UpdateSize(Window window) { - _hostWindow = hostWindow; - } - - public TitleBar() - { - - } - - public void Attach() - { - if (_disposables == null) - { - var layer = ChromeOverlayLayer.GetOverlayLayer(_hostWindow); - - layer?.Children.Add(this); - - if (_hostWindow != null) - { - _disposables = new CompositeDisposable - { - _hostWindow.GetObservable(Window.WindowDecorationMarginProperty) - .Subscribe(x => UpdateSize()), - - _hostWindow.GetObservable(Window.ExtendClientAreaTitleBarHeightHintProperty) - .Subscribe(x => UpdateSize()), - - _hostWindow.GetObservable(Window.OffScreenMarginProperty) - .Subscribe(x => UpdateSize()), - - _hostWindow.GetObservable(Window.WindowStateProperty) - .Subscribe(x => - { - PseudoClasses.Set(":minimized", x == WindowState.Minimized); - PseudoClasses.Set(":normal", x == WindowState.Normal); - PseudoClasses.Set(":maximized", x == WindowState.Maximized); - PseudoClasses.Set(":fullscreen", x == WindowState.FullScreen); - }) - }; - - _captionButtons?.Attach(_hostWindow); - } - - UpdateSize(); - } - } - - private void UpdateSize() - { - if (_hostWindow != null) + if (window != null) { Margin = new Thickness( - _hostWindow.OffScreenMargin.Left, - _hostWindow.OffScreenMargin.Top, - _hostWindow.OffScreenMargin.Right, - _hostWindow.OffScreenMargin.Bottom); + window.OffScreenMargin.Left, + window.OffScreenMargin.Top, + window.OffScreenMargin.Right, + window.OffScreenMargin.Bottom); - if (_hostWindow.WindowState != WindowState.FullScreen) + if (window.WindowState != WindowState.FullScreen) { - Height = _hostWindow.WindowDecorationMargin.Top; + Height = window.WindowDecorationMargin.Top; if (_captionButtons != null) { _captionButtons.Height = Height; } } + + IsVisible = window.PlatformImpl.NeedsManagedDecorations; } } - public void Detach() + protected override void OnApplyTemplate(TemplateAppliedEventArgs e) { - if (_disposables != null) - { - var layer = ChromeOverlayLayer.GetOverlayLayer(_hostWindow); - - layer?.Children.Remove(this); + base.OnApplyTemplate(e); - _disposables.Dispose(); - _disposables = null; + _captionButtons?.Detach(); + + _captionButtons = e.NameScope.Get("PART_CaptionButtons"); - _captionButtons?.Detach(); + if (VisualRoot is Window window) + { + _captionButtons?.Attach(window); + + UpdateSize(window); } } - protected override void OnApplyTemplate(TemplateAppliedEventArgs e) + protected override void OnAttachedToVisualTree(VisualTreeAttachmentEventArgs e) { - base.OnApplyTemplate(e); - - _captionButtons = e.NameScope.Get("PART_CaptionButtons"); + base.OnAttachedToVisualTree(e); - if (_hostWindow != null) + if (VisualRoot is Window window) { - _captionButtons.Attach(_hostWindow); + _disposables = new CompositeDisposable + { + window.GetObservable(Window.WindowDecorationMarginProperty) + .Subscribe(x => UpdateSize(window)), + window.GetObservable(Window.ExtendClientAreaTitleBarHeightHintProperty) + .Subscribe(x => UpdateSize(window)), + window.GetObservable(Window.OffScreenMarginProperty) + .Subscribe(x => UpdateSize(window)), + window.GetObservable(Window.ExtendClientAreaChromeHintsProperty) + .Subscribe(x => UpdateSize(window)), + window.GetObservable(Window.WindowStateProperty) + .Subscribe(x => + { + PseudoClasses.Set(":minimized", x == WindowState.Minimized); + PseudoClasses.Set(":normal", x == WindowState.Normal); + PseudoClasses.Set(":maximized", x == WindowState.Maximized); + PseudoClasses.Set(":fullscreen", x == WindowState.FullScreen); + }), + window.GetObservable(Window.IsExtendedIntoWindowDecorationsProperty) + .Subscribe(x => UpdateSize(window)) + }; } + } + + protected override void OnDetachedFromVisualTree(VisualTreeAttachmentEventArgs e) + { + base.OnDetachedFromVisualTree(e); - UpdateSize(); + _disposables?.Dispose(); + + _captionButtons?.Detach(); + _captionButtons = null; } } } diff --git a/src/Avalonia.Controls/DateTimePickers/DatePicker.cs b/src/Avalonia.Controls/DateTimePickers/DatePicker.cs index 5d3311e8c6..a41c159980 100644 --- a/src/Avalonia.Controls/DateTimePickers/DatePicker.cs +++ b/src/Avalonia.Controls/DateTimePickers/DatePicker.cs @@ -88,7 +88,7 @@ namespace Avalonia.Controls AvaloniaProperty.RegisterDirect(nameof(SelectedDate), x => x.SelectedDate, (x, v) => x.SelectedDate = v); - //Template Items + // Template Items private Button _flyoutButton; private TextBlock _dayText; private TextBlock _monthText; @@ -359,10 +359,14 @@ namespace Avalonia.Controls } } - Grid.SetColumn(_spacer1, 1); - Grid.SetColumn(_spacer2, 3); - _spacer1.IsVisible = columnIndex > 1; - _spacer2.IsVisible = columnIndex > 2; + var isSpacer1Visible = columnIndex > 1; + var isSpacer2Visible = columnIndex > 2; + // ternary conditional operator is used to make sure grid cells will be validated + Grid.SetColumn(_spacer1, isSpacer1Visible ? 1 : 0); + Grid.SetColumn(_spacer2, isSpacer2Visible ? 3 : 0); + + _spacer1.IsVisible = isSpacer1Visible; + _spacer2.IsVisible = isSpacer2Visible; } private void SetSelectedDateText() @@ -398,7 +402,7 @@ namespace Avalonia.Controls var deltaY = _presenter.GetOffsetForPopup(); - //The extra 5 px I think is related to default popup placement behavior + // The extra 5 px I think is related to default popup placement behavior _popup.Host.ConfigurePosition(_popup.PlacementTarget, PlacementMode.AnchorAndGravity, new Point(0, deltaY + 5), Primitives.PopupPositioning.PopupAnchor.Bottom, Primitives.PopupPositioning.PopupGravity.Bottom, Primitives.PopupPositioning.PopupPositionerConstraintAdjustment.SlideY); diff --git a/src/Avalonia.Controls/DateTimePickers/DatePickerPresenter.cs b/src/Avalonia.Controls/DateTimePickers/DatePickerPresenter.cs index 8b86e46e88..31527ccb16 100644 --- a/src/Avalonia.Controls/DateTimePickers/DatePickerPresenter.cs +++ b/src/Avalonia.Controls/DateTimePickers/DatePickerPresenter.cs @@ -77,7 +77,7 @@ namespace Avalonia.Controls DatePicker.YearVisibleProperty.AddOwner(x => x.YearVisible, (x, v) => x.YearVisible = v); - //Template Items + // Template Items private Grid _pickerContainer; private Button _acceptButton; private Button _dismissButton; @@ -107,7 +107,7 @@ namespace Avalonia.Controls private bool _yearVisible = true; private DateTimeOffset _syncDate; - private GregorianCalendar _calendar; + private readonly GregorianCalendar _calendar; private bool _suppressUpdateSelection; public DatePickerPresenter() @@ -234,7 +234,7 @@ namespace Avalonia.Controls protected override void OnApplyTemplate(TemplateAppliedEventArgs e) { base.OnApplyTemplate(e); - //These are requirements, so throw if not found + // These are requirements, so throw if not found _pickerContainer = e.NameScope.Get("PickerContainer"); _monthHost = e.NameScope.Get("MonthHost"); _dayHost = e.NameScope.Get("DayHost"); @@ -326,7 +326,7 @@ namespace Avalonia.Controls /// private void InitPicker() { - //OnApplyTemplate must've been called before we can init here... + // OnApplyTemplate must've been called before we can init here... if (_pickerContainer == null) return; @@ -344,12 +344,11 @@ namespace Avalonia.Controls SetGrid(); - //Date should've been set when we reach this point + // Date should've been set when we reach this point var dt = Date; if (DayVisible) { - GregorianCalendar gc = new GregorianCalendar(); - var maxDays = gc.GetDaysInMonth(dt.Year, dt.Month); + var maxDays = _calendar.GetDaysInMonth(dt.Year, dt.Month); _daySelector.MaximumValue = maxDays; _daySelector.MinimumValue = 1; _daySelector.SelectedValue = dt.Day; @@ -407,10 +406,14 @@ namespace Avalonia.Controls } } - Grid.SetColumn(_spacer1, 1); - Grid.SetColumn(_spacer2, 3); - _spacer1.IsVisible = columnIndex > 1; - _spacer2.IsVisible = columnIndex > 2; + var isSpacer1Visible = columnIndex > 1; + var isSpacer2Visible = columnIndex > 2; + // ternary conditional operator is used to make sure grid cells will be validated + Grid.SetColumn(_spacer1, isSpacer1Visible ? 1 : 0); + Grid.SetColumn(_spacer2, isSpacer2Visible ? 3 : 0); + + _spacer1.IsVisible = isSpacer1Visible; + _spacer2.IsVisible = isSpacer2Visible; } private void SetInitialFocus() @@ -433,12 +436,12 @@ namespace Avalonia.Controls } } - private void OnDismissButtonClicked(object sender, Avalonia.Interactivity.RoutedEventArgs e) + private void OnDismissButtonClicked(object sender, RoutedEventArgs e) { OnDismiss(); } - private void OnAcceptButtonClicked(object sender, Avalonia.Interactivity.RoutedEventArgs e) + private void OnAcceptButtonClicked(object sender, RoutedEventArgs e) { Date = _syncDate; OnConfirmed(); @@ -471,7 +474,7 @@ namespace Avalonia.Controls _syncDate = newDate; - //We don't need to update the days if not displaying day, not february + // We don't need to update the days if not displaying day, not february if (!DayVisible || _syncDate.Month != 2) return; diff --git a/src/Avalonia.Controls/DateTimePickers/TimePicker.cs b/src/Avalonia.Controls/DateTimePickers/TimePicker.cs index 3fa426b1d3..e54da1fb3a 100644 --- a/src/Avalonia.Controls/DateTimePickers/TimePicker.cs +++ b/src/Avalonia.Controls/DateTimePickers/TimePicker.cs @@ -15,7 +15,7 @@ namespace Avalonia.Controls /// Defines the property /// public static readonly DirectProperty MinuteIncrementProperty = - AvaloniaProperty.RegisterDirect(nameof(MinuteIncrement), + AvaloniaProperty.RegisterDirect(nameof(MinuteIncrement), x => x.MinuteIncrement, (x, v) => x.MinuteIncrement = v); /// @@ -34,17 +34,17 @@ namespace Avalonia.Controls /// Defines the property /// public static readonly DirectProperty ClockIdentifierProperty = - AvaloniaProperty.RegisterDirect(nameof(ClockIdentifier), + AvaloniaProperty.RegisterDirect(nameof(ClockIdentifier), x => x.ClockIdentifier, (x, v) => x.ClockIdentifier = v); /// /// Defines the property /// public static readonly DirectProperty SelectedTimeProperty = - AvaloniaProperty.RegisterDirect(nameof(SelectedTime), + AvaloniaProperty.RegisterDirect(nameof(SelectedTime), x => x.SelectedTime, (x, v) => x.SelectedTime = v); - //Template Items + // Template Items private TimePickerPresenter _presenter; private Button _flyoutButton; private Border _firstPickerHost; @@ -52,7 +52,7 @@ namespace Avalonia.Controls private Border _thirdPickerHost; private TextBlock _hourText; private TextBlock _minuteText; - public TextBlock _periodText; + private TextBlock _periodText; private Rectangle _firstSplitter; private Rectangle _secondSplitter; private Grid _contentGrid; @@ -145,7 +145,7 @@ namespace Avalonia.Controls if (_flyoutButton != null) _flyoutButton.Click -= OnFlyoutButtonClicked; - if(_presenter != null) + if (_presenter != null) { _presenter.Confirmed -= OnConfirmed; _presenter.Dismissed -= OnDismissPicker; @@ -170,7 +170,6 @@ namespace Avalonia.Controls _popup = e.NameScope.Find("Popup"); _presenter = e.NameScope.Find("PickerPresenter"); - if (_flyoutButton != null) _flyoutButton.Click += OnFlyoutButtonClicked; @@ -185,7 +184,6 @@ namespace Avalonia.Controls _presenter[!TimePickerPresenter.MinuteIncrementProperty] = this[!MinuteIncrementProperty]; _presenter[!TimePickerPresenter.ClockIdentifierProperty] = this[!ClockIdentifierProperty]; } - } private void SetGrid() @@ -195,30 +193,19 @@ namespace Avalonia.Controls bool use24HourClock = ClockIdentifier == "24HourClock"; - if (!use24HourClock) - { - _contentGrid.ColumnDefinitions = new ColumnDefinitions("*,Auto,*,Auto,*"); - _thirdPickerHost.IsVisible = true; - _secondSplitter.IsVisible = true; + var columnsD = use24HourClock ? "*, Auto, *" : "*, Auto, *, Auto, *"; + _contentGrid.ColumnDefinitions = new ColumnDefinitions(columnsD); - Grid.SetColumn(_firstPickerHost, 0); - Grid.SetColumn(_secondPickerHost, 2); - Grid.SetColumn(_thirdPickerHost, 4); + _thirdPickerHost.IsVisible = !use24HourClock; + _secondSplitter.IsVisible = !use24HourClock; - Grid.SetColumn(_firstSplitter, 1); - Grid.SetColumn(_secondSplitter, 3); - } - else - { - _contentGrid.ColumnDefinitions = new ColumnDefinitions("*,Auto,*"); - _thirdPickerHost.IsVisible = false; - _secondSplitter.IsVisible = false; + Grid.SetColumn(_firstPickerHost, 0); + Grid.SetColumn(_secondPickerHost, 2); - Grid.SetColumn(_firstPickerHost, 0); - Grid.SetColumn(_secondPickerHost, 2); + Grid.SetColumn(_thirdPickerHost, use24HourClock ? 0 : 4); - Grid.SetColumn(_firstSplitter, 1); - } + Grid.SetColumn(_firstSplitter, 1); + Grid.SetColumn(_secondSplitter, use24HourClock ? 0 : 3); } private void SetSelectedTimeText() @@ -237,14 +224,13 @@ namespace Avalonia.Controls hr = hr > 12 ? hr - 12 : hr == 0 ? 12 : hr; newTime = new TimeSpan(hr, newTime.Minutes, 0); } - _hourText.Text = newTime.ToString("%h"); + _hourText.Text = newTime.ToString("%h"); _minuteText.Text = newTime.ToString("mm"); PseudoClasses.Set(":hasnotime", false); _periodText.Text = time.Value.Hours >= 12 ? CultureInfo.CurrentCulture.DateTimeFormat.PMDesignator : CultureInfo.CurrentCulture.DateTimeFormat.AMDesignator; - } else { @@ -262,7 +248,7 @@ namespace Avalonia.Controls SelectedTimeChanged?.Invoke(this, new TimePickerSelectedValueChangedEventArgs(oldTime, newTime)); } - private void OnFlyoutButtonClicked(object sender, Avalonia.Interactivity.RoutedEventArgs e) + private void OnFlyoutButtonClicked(object sender, Interactivity.RoutedEventArgs e) { _presenter.Time = SelectedTime ?? DateTime.Now.TimeOfDay; @@ -270,7 +256,7 @@ namespace Avalonia.Controls var deltaY = _presenter.GetOffsetForPopup(); - //The extra 5 px I think is related to default popup placement behavior + // The extra 5 px I think is related to default popup placement behavior _popup.Host.ConfigurePosition(_popup.PlacementTarget, PlacementMode.AnchorAndGravity, new Point(0, deltaY + 5), Primitives.PopupPositioning.PopupAnchor.Bottom, Primitives.PopupPositioning.PopupGravity.Bottom, Primitives.PopupPositioning.PopupPositionerConstraintAdjustment.SlideY); @@ -287,6 +273,5 @@ namespace Avalonia.Controls _popup.Close(); SelectedTime = _presenter.Time; } - } } diff --git a/src/Avalonia.Controls/DateTimePickers/TimePickerPresenter.cs b/src/Avalonia.Controls/DateTimePickers/TimePickerPresenter.cs index 5ab5b92e16..920eeeb406 100644 --- a/src/Avalonia.Controls/DateTimePickers/TimePickerPresenter.cs +++ b/src/Avalonia.Controls/DateTimePickers/TimePickerPresenter.cs @@ -39,7 +39,7 @@ namespace Avalonia.Controls KeyboardNavigation.SetTabNavigation(this, KeyboardNavigationMode.Cycle); } - //TemplateItems + // TemplateItems private Grid _pickerContainer; private Button _acceptButton; private Button _dismissButton; @@ -55,8 +55,8 @@ namespace Avalonia.Controls private Button _minuteDownButton; private Button _periodDownButton; - //Backing Fields - private TimeSpan _Time; + // Backing Fields + private TimeSpan _time; private int _minuteIncrement = 1; private string _clockIdentifier = "12HourClock"; @@ -83,7 +83,7 @@ namespace Avalonia.Controls get => _clockIdentifier; set { - if (string.IsNullOrEmpty(value) || value == "" || !(value == "12HourClock" || value == "24HourClock")) + if (string.IsNullOrEmpty(value) || !(value == "12HourClock" || value == "24HourClock")) throw new ArgumentException("Invalid ClockIdentifier"); SetAndRaise(ClockIdentifierProperty, ref _clockIdentifier, value); InitPicker(); @@ -95,10 +95,10 @@ namespace Avalonia.Controls /// public TimeSpan Time { - get => _Time; + get => _time; set { - SetAndRaise(TimeProperty, ref _Time, value); + SetAndRaise(TimeProperty, ref _time, value); InitPicker(); } } @@ -213,26 +213,24 @@ namespace Avalonia.Controls private void SetGrid() { - if (ClockIdentifier == "12HourClock") - { - _pickerContainer.ColumnDefinitions = new ColumnDefinitions("*,Auto,*,Auto,*"); - _spacer2.IsVisible = true; - _periodHost.IsVisible = true; - } - else - { - _pickerContainer.ColumnDefinitions = new ColumnDefinitions("*,Auto,*"); - _spacer2.IsVisible = false; - _periodHost.IsVisible = false; - } + bool use24HourClock = ClockIdentifier == "24HourClock"; + + var columnsD = use24HourClock ? "*, Auto, *" : "*, Auto, *, Auto, *"; + _pickerContainer.ColumnDefinitions = new ColumnDefinitions(columnsD); + + _spacer2.IsVisible = !use24HourClock; + _periodHost.IsVisible = !use24HourClock; + + Grid.SetColumn(_spacer2, use24HourClock ? 0 : 3); + Grid.SetColumn(_periodHost, use24HourClock ? 0 : 4); } - private void OnDismissButtonClicked(object sender, Avalonia.Interactivity.RoutedEventArgs e) + private void OnDismissButtonClicked(object sender, RoutedEventArgs e) { OnDismiss(); } - private void OnAcceptButtonClicked(object sender, Avalonia.Interactivity.RoutedEventArgs e) + private void OnAcceptButtonClicked(object sender, RoutedEventArgs e) { OnConfirmed(); } diff --git a/src/Avalonia.Controls/Embedding/Offscreen/OffscreenTopLevelImpl.cs b/src/Avalonia.Controls/Embedding/Offscreen/OffscreenTopLevelImpl.cs index f8bd2878d9..522103c7bd 100644 --- a/src/Avalonia.Controls/Embedding/Offscreen/OffscreenTopLevelImpl.cs +++ b/src/Avalonia.Controls/Embedding/Offscreen/OffscreenTopLevelImpl.cs @@ -35,7 +35,7 @@ namespace Avalonia.Controls.Embedding.Offscreen } } - public double Scaling + public double RenderScaling { get { return _scaling; } set diff --git a/src/Avalonia.Controls/Generators/TreeItemContainerGenerator.cs b/src/Avalonia.Controls/Generators/TreeItemContainerGenerator.cs index cd1ce3deae..9e65ef5f81 100644 --- a/src/Avalonia.Controls/Generators/TreeItemContainerGenerator.cs +++ b/src/Avalonia.Controls/Generators/TreeItemContainerGenerator.cs @@ -142,7 +142,6 @@ namespace Avalonia.Controls.Generators private readonly IDataTemplate _inner; public WrapperTreeDataTemplate(IDataTemplate inner) => _inner = inner; public IControl Build(object param) => _inner.Build(param); - public bool SupportsRecycling => _inner.SupportsRecycling; public bool Match(object data) => _inner.Match(data); public InstancedBinding ItemsSelector(object item) => null; } diff --git a/src/Avalonia.Controls/MenuItem.cs b/src/Avalonia.Controls/MenuItem.cs index aab099911b..e7570a87c1 100644 --- a/src/Avalonia.Controls/MenuItem.cs +++ b/src/Avalonia.Controls/MenuItem.cs @@ -98,8 +98,7 @@ namespace Avalonia.Controls private ICommand? _command; private bool _commandCanExecute = true; - private Popup? _popup; - private IDisposable? _gridHack; + private Popup _popup; /// /// Initializes static members of the class. @@ -325,9 +324,6 @@ namespace Avalonia.Controls { Command.CanExecuteChanged -= CanExecuteChanged; } - - _gridHack?.Dispose(); - _gridHack = null; } protected override void OnAttachedToVisualTree(VisualTreeAttachmentEventArgs e) @@ -347,9 +343,7 @@ namespace Avalonia.Controls // the WPF codebase: // // https://github.com/dotnet/wpf/blob/89537909bdf36bc918e88b37751add46a8980bb0/src/Microsoft.DotNet.Wpf/src/PresentationFramework/System/Windows/Controls/MenuItem.cs#L2126-L2141 - _gridHack = Bind( - DefinitionBase.PrivateSharedSizeScopeProperty, - parent.GetBindingObservable(DefinitionBase.PrivateSharedSizeScopeProperty)); + SetValue(DefinitionBase.PrivateSharedSizeScopeProperty, parent.GetValue(DefinitionBase.PrivateSharedSizeScopeProperty)); } } diff --git a/src/Avalonia.Controls/Platform/ITopLevelImpl.cs b/src/Avalonia.Controls/Platform/ITopLevelImpl.cs index 0d77cbf802..7514f214aa 100644 --- a/src/Avalonia.Controls/Platform/ITopLevelImpl.cs +++ b/src/Avalonia.Controls/Platform/ITopLevelImpl.cs @@ -23,10 +23,10 @@ namespace Avalonia.Platform Size ClientSize { get; } /// - /// Gets the scaling factor for the toplevel. + /// Gets the scaling factor for the toplevel. This is used for rendering. /// - double Scaling { get; } - + double RenderScaling { get; } + /// /// The list of native platform's surfaces that can be consumed by rendering subsystems. /// diff --git a/src/Avalonia.Controls/Platform/IWindowBaseImpl.cs b/src/Avalonia.Controls/Platform/IWindowBaseImpl.cs index b190c4f2e7..ecaf87d1ed 100644 --- a/src/Avalonia.Controls/Platform/IWindowBaseImpl.cs +++ b/src/Avalonia.Controls/Platform/IWindowBaseImpl.cs @@ -13,6 +13,11 @@ namespace Avalonia.Platform /// Hides the window. /// void Hide(); + + /// + /// Gets the scaling factor for Window positioning and sizing. + /// + double DesktopScaling { get; } /// /// Gets the position of the window in device pixels. diff --git a/src/Avalonia.Controls/Presenters/CarouselPresenter.cs b/src/Avalonia.Controls/Presenters/CarouselPresenter.cs index 70a7583daf..7888249bdd 100644 --- a/src/Avalonia.Controls/Presenters/CarouselPresenter.cs +++ b/src/Avalonia.Controls/Presenters/CarouselPresenter.cs @@ -155,6 +155,11 @@ namespace Avalonia.Controls.Presenters } } + protected override void PanelCreated(IPanel panel) + { + ItemsChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset)); + } + /// /// Moves to the selected page, animating if a is set. /// diff --git a/src/Avalonia.Controls/Presenters/ContentPresenter.cs b/src/Avalonia.Controls/Presenters/ContentPresenter.cs index c4571505ba..8837901816 100644 --- a/src/Avalonia.Controls/Presenters/ContentPresenter.cs +++ b/src/Avalonia.Controls/Presenters/ContentPresenter.cs @@ -86,7 +86,7 @@ namespace Avalonia.Controls.Presenters private IControl _child; private bool _createdChild; - private IDataTemplate _dataTemplate; + private IRecyclingDataTemplate _recyclingDataTemplate; private readonly BorderRenderHelper _borderRenderer = new BorderRenderHelper(); /// @@ -281,7 +281,7 @@ namespace Avalonia.Controls.Presenters protected override void OnAttachedToLogicalTree(LogicalTreeAttachmentEventArgs e) { base.OnAttachedToLogicalTree(e); - _dataTemplate = null; + _recyclingDataTemplate = null; _createdChild = false; InvalidateMeasure(); } @@ -307,22 +307,21 @@ namespace Avalonia.Controls.Presenters { var dataTemplate = this.FindDataTemplate(content, ContentTemplate) ?? FuncDataTemplate.Default; - // We have content and it isn't a control, so if the new data template is the same - // as the old data template, try to recycle the existing child control to display - // the new data. - if (dataTemplate == _dataTemplate && dataTemplate.SupportsRecycling) + if (dataTemplate is IRecyclingDataTemplate rdt) { - newChild = oldChild; + var toRecycle = rdt == _recyclingDataTemplate ? oldChild : null; + newChild = rdt.Build(content, toRecycle); + _recyclingDataTemplate = rdt; } else { - _dataTemplate = dataTemplate; - newChild = _dataTemplate.Build(content); + newChild = dataTemplate.Build(content); + _recyclingDataTemplate = null; } } else { - _dataTemplate = null; + _recyclingDataTemplate = null; } return newChild; @@ -422,7 +421,7 @@ namespace Avalonia.Controls.Presenters LogicalChildren.Remove(Child); ((ISetInheritanceParent)Child).SetParent(Child.Parent); Child = null; - _dataTemplate = null; + _recyclingDataTemplate = null; } InvalidateMeasure(); diff --git a/src/Avalonia.Controls/Presenters/ItemsPresenterBase.cs b/src/Avalonia.Controls/Presenters/ItemsPresenterBase.cs index 23846bcd2e..52f173fc71 100644 --- a/src/Avalonia.Controls/Presenters/ItemsPresenterBase.cs +++ b/src/Avalonia.Controls/Presenters/ItemsPresenterBase.cs @@ -229,8 +229,6 @@ namespace Avalonia.Controls.Presenters } PanelCreated(Panel); - - ItemsChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset)); } /// diff --git a/src/Avalonia.Controls/Primitives/ChromeOverlayLayer.cs b/src/Avalonia.Controls/Primitives/ChromeOverlayLayer.cs index ba0fdfd535..7171ecc302 100644 --- a/src/Avalonia.Controls/Primitives/ChromeOverlayLayer.cs +++ b/src/Avalonia.Controls/Primitives/ChromeOverlayLayer.cs @@ -8,7 +8,7 @@ namespace Avalonia.Controls.Primitives { public class ChromeOverlayLayer : Panel, ICustomSimpleHitTest { - public static ChromeOverlayLayer? GetOverlayLayer(IVisual visual) + public static Panel? GetOverlayLayer(IVisual visual) { foreach (var v in visual.GetVisualAncestors()) if (v is VisualLayerManager vlm) @@ -24,6 +24,11 @@ namespace Avalonia.Controls.Primitives return null; } + public void Add(Control c) + { + base.Children.Add(c); + } + public bool HitTest(Point point) => Children.HitTestCustom(point); } } diff --git a/src/Avalonia.Controls/Primitives/PopupPositioning/ManagedPopupPositionerPopupImplHelper.cs b/src/Avalonia.Controls/Primitives/PopupPositioning/ManagedPopupPositionerPopupImplHelper.cs index b0e3d1ab08..91ed5d975d 100644 --- a/src/Avalonia.Controls/Primitives/PopupPositioning/ManagedPopupPositionerPopupImplHelper.cs +++ b/src/Avalonia.Controls/Primitives/PopupPositioning/ManagedPopupPositionerPopupImplHelper.cs @@ -40,9 +40,9 @@ namespace Avalonia.Controls.Primitives.PopupPositioning public void MoveAndResize(Point devicePoint, Size virtualSize) { - _moveResize(new PixelPoint((int)devicePoint.X, (int)devicePoint.Y), virtualSize, _parent.Scaling); + _moveResize(new PixelPoint((int)devicePoint.X, (int)devicePoint.Y), virtualSize, _parent.RenderScaling); } - public virtual double Scaling => _parent.Scaling; + public virtual double Scaling => _parent.DesktopScaling; } } diff --git a/src/Avalonia.Controls/Primitives/SelectingItemsControl.cs b/src/Avalonia.Controls/Primitives/SelectingItemsControl.cs index c915dc70b6..59b7777b1b 100644 --- a/src/Avalonia.Controls/Primitives/SelectingItemsControl.cs +++ b/src/Avalonia.Controls/Primitives/SelectingItemsControl.cs @@ -692,14 +692,24 @@ namespace Avalonia.Controls.Primitives } } - foreach (var i in e.SelectedIndices) + if (e.SelectedIndices.Count > 0 || e.DeselectedIndices.Count > 0) { - Mark(i.GetAt(0), true); - } + foreach (var i in e.SelectedIndices) + { + Mark(i.GetAt(0), true); + } - foreach (var i in e.DeselectedIndices) + foreach (var i in e.DeselectedIndices) + { + Mark(i.GetAt(0), false); + } + } + else if (e.DeselectedItems.Count > 0) { - Mark(i.GetAt(0), false); + // (De)selected indices being empty means that a selected item was removed from + // the Items (it can't tell us the index of the item because the index is no longer + // valid). In this case, we just update the selection state of all containers. + UpdateContainerSelection(); } var newSelectedIndex = SelectedIndex; diff --git a/src/Avalonia.Controls/Primitives/VisualLayerManager.cs b/src/Avalonia.Controls/Primitives/VisualLayerManager.cs index a4e230e2f4..d8d3450c6f 100644 --- a/src/Avalonia.Controls/Primitives/VisualLayerManager.cs +++ b/src/Avalonia.Controls/Primitives/VisualLayerManager.cs @@ -13,10 +13,12 @@ namespace Avalonia.Controls.Primitives private ILogicalRoot _logicalRoot; private readonly List _layers = new List(); - + + public static readonly StyledProperty ChromeOverlayLayerProperty = + AvaloniaProperty.Register(nameof(ChromeOverlayLayer)); public bool IsPopup { get; set; } - + public AdornerLayer AdornerLayer { get @@ -32,10 +34,19 @@ namespace Avalonia.Controls.Primitives { get { - var rv = FindLayer(); - if (rv == null) - AddLayer(rv = new ChromeOverlayLayer(), ChromeZIndex); - return rv; + var current = GetValue(ChromeOverlayLayerProperty); + + if (current is null) + { + var chromeOverlayLayer = new ChromeOverlayLayer(); + AddLayer(chromeOverlayLayer, ChromeZIndex); + + SetValue(ChromeOverlayLayerProperty, chromeOverlayLayer); + + current = chromeOverlayLayer; + } + + return current; } } @@ -46,7 +57,7 @@ namespace Avalonia.Controls.Primitives if (IsPopup) return null; var rv = FindLayer(); - if(rv == null) + if (rv == null) AddLayer(rv = new OverlayLayer(), OverlayZIndex); return rv; } @@ -88,7 +99,8 @@ namespace Avalonia.Controls.Primitives layer.ZIndex = zindex; VisualChildren.Add(layer); if (((ILogical)this).IsAttachedToLogicalTree) - ((ILogical)layer).NotifyAttachedToLogicalTree(new LogicalTreeAttachmentEventArgs(_logicalRoot, layer, this)); + ((ILogical)layer).NotifyAttachedToLogicalTree( + new LogicalTreeAttachmentEventArgs(_logicalRoot, layer, this)); InvalidateArrange(); } diff --git a/src/Avalonia.Controls/ProgressBar.cs b/src/Avalonia.Controls/ProgressBar.cs index e904957429..a92f24a050 100644 --- a/src/Avalonia.Controls/ProgressBar.cs +++ b/src/Avalonia.Controls/ProgressBar.cs @@ -215,7 +215,7 @@ namespace Avalonia.Controls TemplateProperties.Container2AnimationEndPosition = barIndicatorWidth2 * 1.66; // Position at 166% // Remove these properties when we switch to fluent as default and removed the old one. - IndeterminateStartingOffset = -(dim / 5d); + IndeterminateStartingOffset = -dim; IndeterminateEndingOffset = dim; var padding = Padding; diff --git a/src/Avalonia.Controls/RelativePanel.AttachedProperties.cs b/src/Avalonia.Controls/RelativePanel.AttachedProperties.cs index f93de5ca15..f64c26682b 100644 --- a/src/Avalonia.Controls/RelativePanel.AttachedProperties.cs +++ b/src/Avalonia.Controls/RelativePanel.AttachedProperties.cs @@ -33,7 +33,7 @@ namespace Avalonia.Controls AlignVerticalCenterWithProperty.Changed.AddClassHandler(OnAlignPropertiesChanged); BelowProperty.Changed.AddClassHandler(OnAlignPropertiesChanged); LeftOfProperty.Changed.AddClassHandler(OnAlignPropertiesChanged); - LeftOfProperty.Changed.AddClassHandler(OnAlignPropertiesChanged); + RightOfProperty.Changed.AddClassHandler(OnAlignPropertiesChanged); } /// diff --git a/src/Avalonia.Controls/RelativePanel.cs b/src/Avalonia.Controls/RelativePanel.cs index 033a5559f5..27f13a3f57 100644 --- a/src/Avalonia.Controls/RelativePanel.cs +++ b/src/Avalonia.Controls/RelativePanel.cs @@ -1,4 +1,4 @@ -/// Ported from https://github.com/HandyOrg/HandyControl/blob/master/src/Shared/HandyControl_Shared/Controls/Panel/RelativePanel.cs +// Ported from https://github.com/HandyOrg/HandyControl/blob/master/src/Shared/HandyControl_Shared/Controls/Panel/RelativePanel.cs using System; using System.Collections.Generic; using System.Linq; @@ -14,25 +14,28 @@ namespace Avalonia.Controls public RelativePanel() => _childGraph = new Graph(); - protected override Size MeasureOverride(Size availableSize) + private Layoutable? GetDependencyElement(AvaloniaProperty property, AvaloniaObject child) { - foreach (var child in Children) + var dependency = child.GetValue(property); + + if (dependency is Layoutable layoutable) { - child?.Measure(availableSize); + if (Children.Contains((ILayoutable)layoutable)) + return layoutable; + + throw new ArgumentException($"RelativePanel error: Element does not exist in the current context: {property.Name}"); } - return availableSize; + return null; } - protected override Size ArrangeOverride(Size arrangeSize) + protected override Size MeasureOverride(Size availableSize) { - _childGraph.Reset(arrangeSize); - - foreach (var child in Children.OfType()) + _childGraph.Clear(); + foreach (Layoutable child in Children) { if (child == null) continue; - var node = _childGraph.AddNode(child); node.AlignLeftWithNode = _childGraph.AddLink(node, GetDependencyElement(AlignLeftWithProperty, child)); @@ -47,63 +50,44 @@ namespace Avalonia.Controls node.AlignHorizontalCenterWith = _childGraph.AddLink(node, GetDependencyElement(AlignHorizontalCenterWithProperty, child)); node.AlignVerticalCenterWith = _childGraph.AddLink(node, GetDependencyElement(AlignVerticalCenterWithProperty, child)); - } - if (_childGraph.CheckCyclic()) - { - throw new Exception("RelativePanel error: Circular dependency detected. Layout could not complete."); } + _childGraph.Measure(availableSize); - var size = new Size(); - - foreach (var child in Children) - { - if (child.Bounds.Bottom > size.Height) - { - size = size.WithHeight(child.Bounds.Bottom); - } + _childGraph.Reset(false); + var boundingSize = _childGraph.GetBoundingSize(Width.IsNaN(), Height.IsNaN()); + _childGraph.Reset(); + _childGraph.Measure(boundingSize); + return boundingSize; + } - if (child.Bounds.Right > size.Width) - { - size = size.WithWidth(child.Bounds.Right); - } - } + protected override Size ArrangeOverride(Size arrangeSize) + { + _childGraph.GetNodes().Do(node => node.Arrange(arrangeSize)); + return arrangeSize; + } - if (VerticalAlignment == VerticalAlignment.Stretch) - { - size = size.WithHeight(arrangeSize.Height); - } + private class GraphNode + { + public bool Measured { get; set; } - if (HorizontalAlignment == HorizontalAlignment.Stretch) - { - size = size.WithWidth(arrangeSize.Width); - } + public Layoutable Element { get; } - return size; - } + private bool HorizontalOffsetFlag { get; set; } - private Layoutable? GetDependencyElement(AvaloniaProperty property, AvaloniaObject child) - { - var dependency = child.GetValue(property); + private bool VerticalOffsetFlag { get; set; } - if (dependency is Layoutable layoutable) - { - if (Children.Contains((ILayoutable)layoutable)) - return layoutable; + private Size BoundingSize { get; set; } - throw new ArgumentException($"RelativePanel error: Element does not exist in the current context: {property.Name}"); - } + public Size OriginDesiredSize { get; set; } - return null; - } + public double Left { get; set; } = double.NaN; - private class GraphNode - { - public Point Position { get; set; } + public double Top { get; set; } = double.NaN; - public bool Arranged { get; set; } + public double Right { get; set; } = double.NaN; - public Layoutable Element { get; } + public double Bottom { get; set; } = double.NaN; public HashSet OutgoingNodes { get; } @@ -132,19 +116,106 @@ namespace Avalonia.Controls OutgoingNodes = new HashSet(); Element = element; } + + public void Arrange(Size arrangeSize) => Element.Arrange(new Rect(Left, Top, Math.Max(arrangeSize.Width - Left - Right, 0), Math.Max(arrangeSize.Height - Top - Bottom, 0))); + + public void Reset(bool clearPos) + { + if (clearPos) + { + Left = double.NaN; + Top = double.NaN; + Right = double.NaN; + Bottom = double.NaN; + } + + Measured = false; + } + + public Size GetBoundingSize() + { + if (Left < 0 || Top < 0) return default; + if (Measured) + return BoundingSize; + + if (!OutgoingNodes.Any()) + { + BoundingSize = Element.DesiredSize; + Measured = true; + } + else + { + BoundingSize = GetBoundingSize(this, Element.DesiredSize, OutgoingNodes); + Measured = true; + } + + return BoundingSize; + } + + private static Size GetBoundingSize(GraphNode prevNode, Size prevSize, IEnumerable nodes) + { + foreach (var node in nodes) + { + if (node.Measured || !node.OutgoingNodes.Any()) + { + if (prevNode.LeftOfNode != null && prevNode.LeftOfNode == node || + prevNode.RightOfNode != null && prevNode.RightOfNode == node) + { + prevSize = prevSize.WithWidth(prevSize.Width + node.BoundingSize.Width); + if (GetAlignHorizontalCenterWithPanel(node.Element) || node.HorizontalOffsetFlag) + { + prevSize = prevSize.WithWidth(prevSize.Width + prevNode.OriginDesiredSize.Width); + prevNode.HorizontalOffsetFlag = true; + } + if (node.VerticalOffsetFlag) + { + prevNode.VerticalOffsetFlag = true; + } + } + + if (prevNode.AboveNode != null && prevNode.AboveNode == node || + prevNode.BelowNode != null && prevNode.BelowNode == node) + { + prevSize = prevSize.WithHeight(prevSize.Height + node.BoundingSize.Height); + if (GetAlignVerticalCenterWithPanel(node.Element) || node.VerticalOffsetFlag) + { + prevSize = prevSize.WithHeight(prevSize.Height + node.OriginDesiredSize.Height); + prevNode.VerticalOffsetFlag = true; + } + if (node.HorizontalOffsetFlag) + { + prevNode.HorizontalOffsetFlag = true; + } + } + } + else + { + return GetBoundingSize(node, prevSize, node.OutgoingNodes); + } + } + + return prevSize; + } } private class Graph { private readonly Dictionary _nodeDic; - private Size _arrangeSize; + private Size AvailableSize { get; set; } + + public Graph() => _nodeDic = new Dictionary(); + + public IEnumerable GetNodes() => _nodeDic.Values; - public Graph() + public void Clear() { - _nodeDic = new Dictionary(); + AvailableSize = new Size(); + _nodeDic.Clear(); } + public void Reset(bool clearPos = true) => _nodeDic.Values.Do(node => node.Reset(clearPos)); + public GraphNode? AddLink(GraphNode from, Layoutable? to) { if (to == null) @@ -177,177 +248,289 @@ namespace Avalonia.Controls return _nodeDic[value]; } - public void Reset(Size arrangeSize) + public void Measure(Size availableSize) { - _arrangeSize = arrangeSize; - _nodeDic.Clear(); + AvailableSize = availableSize; + Measure(_nodeDic.Values, null); } - public bool CheckCyclic() => CheckCyclic(_nodeDic.Values, null); - - private bool CheckCyclic(IEnumerable nodes, HashSet? set) + private void Measure(IEnumerable nodes, HashSet? set) { - set ??= new HashSet(); + set ??= new HashSet(); foreach (var node in nodes) { - if (!node.Arranged && node.OutgoingNodes.Count == 0) + if (!node.Measured && !node.OutgoingNodes.Any()) { - ArrangeChild(node, true); + MeasureChild(node); continue; } - - if (node.OutgoingNodes.All(item => item.Arranged)) + + if (node.OutgoingNodes.All(item => item.Measured)) { - ArrangeChild(node); + MeasureChild(node); continue; } - + if (!set.Add(node.Element)) - return true; + throw new Exception("RelativePanel error: Circular dependency detected. Layout could not complete."); + + Measure(node.OutgoingNodes, set); - return CheckCyclic(node.OutgoingNodes, set); + if (!node.Measured) + { + MeasureChild(node); + } } - - return false; } - private void ArrangeChild(GraphNode node, bool ignoneSibling = false) + private void MeasureChild(GraphNode node) { var child = node.Element; - var childSize = child.DesiredSize; - var childPos = new Point(); + child.Measure(new Size(double.PositiveInfinity, double.PositiveInfinity)); + node.OriginDesiredSize = child.DesiredSize; - if (GetAlignHorizontalCenterWithPanel(child)) + var alignLeftWithPanel = GetAlignLeftWithPanel(child); + var alignTopWithPanel = GetAlignTopWithPanel(child); + var alignRightWithPanel = GetAlignRightWithPanel(child); + var alignBottomWithPanel = GetAlignBottomWithPanel(child); + + if (alignLeftWithPanel) + node.Left = 0; + if (alignTopWithPanel) + node.Top = 0; + if (alignRightWithPanel) + node.Right = 0; + if (alignBottomWithPanel) + node.Bottom = 0; + + if (node.AlignLeftWithNode != null) { - childPos = childPos.WithX((_arrangeSize.Width - childSize.Width) / 2); + node.Left = node.Left.IsNaN() ? node.AlignLeftWithNode.Left : node.AlignLeftWithNode.Left * 0.5; } - if (GetAlignVerticalCenterWithPanel(child)) + if (node.AlignTopWithNode != null) { - childPos = childPos.WithY((_arrangeSize.Height - childSize.Height) / 2); + node.Top = node.Top.IsNaN() ? node.AlignTopWithNode.Top : node.AlignTopWithNode.Top * 0.5; } - var alignLeftWithPanel = GetAlignLeftWithPanel(child); - var alignTopWithPanel = GetAlignTopWithPanel(child); - var alignRightWithPanel = GetAlignRightWithPanel(child); - var alignBottomWithPanel = GetAlignBottomWithPanel(child); + if (node.AlignRightWithNode != null) + { + node.Right = node.Right.IsNaN() + ? node.AlignRightWithNode.Right + : node.AlignRightWithNode.Right * 0.5; + } - if (!ignoneSibling) + if (node.AlignBottomWithNode != null) { - if (node.LeftOfNode != null) - { - childPos = childPos.WithX(node.LeftOfNode.Position.X - childSize.Width); - } + node.Bottom = node.Bottom.IsNaN() + ? node.AlignBottomWithNode.Bottom + : node.AlignBottomWithNode.Bottom * 0.5; + } - if (node.AboveNode != null) - { - childPos = childPos.WithY(node.AboveNode.Position.Y - childSize.Height); - } + var availableHeight = AvailableSize.Height - node.Top - node.Bottom; + if (availableHeight.IsNaN()) + { + availableHeight = AvailableSize.Height; - if (node.RightOfNode != null) + if (!node.Top.IsNaN() && node.Bottom.IsNaN()) { - childPos = childPos.WithX(node.RightOfNode.Position.X + node.RightOfNode.Element.DesiredSize.Width); + availableHeight -= node.Top; } - - if (node.BelowNode != null) + else if (node.Top.IsNaN() && !node.Bottom.IsNaN()) { - childPos = childPos.WithY(node.BelowNode.Position.Y + node.BelowNode.Element.DesiredSize.Height); + availableHeight -= node.Bottom; } + } + + var availableWidth = AvailableSize.Width - node.Left - node.Right; + if (availableWidth.IsNaN()) + { + availableWidth = AvailableSize.Width; - if (node.AlignHorizontalCenterWith != null) + if (!node.Left.IsNaN() && node.Right.IsNaN()) { - childPos = childPos.WithX(node.AlignHorizontalCenterWith.Position.X + - (node.AlignHorizontalCenterWith.Element.DesiredSize.Width - childSize.Width) / 2); + availableWidth -= node.Left; } - - if (node.AlignVerticalCenterWith != null) + else if (node.Left.IsNaN() && !node.Right.IsNaN()) { - childPos = childPos.WithY(node.AlignVerticalCenterWith.Position.Y + - (node.AlignVerticalCenterWith.Element.DesiredSize.Height - childSize.Height) / 2); + availableWidth -= node.Right; } + } - if (node.AlignLeftWithNode != null) + child.Measure(new Size(Math.Max(availableWidth, 0), Math.Max(availableHeight, 0))); + var childSize = child.DesiredSize; + + if (node.LeftOfNode != null && node.Left.IsNaN()) + { + node.Left = node.LeftOfNode.Left - childSize.Width; + } + + if (node.AboveNode != null && node.Top.IsNaN()) + { + node.Top = node.AboveNode.Top - childSize.Height; + } + + if (node.RightOfNode != null) + { + if (node.Right.IsNaN()) { - childPos = childPos.WithX(node.AlignLeftWithNode.Position.X); + node.Right = node.RightOfNode.Right - childSize.Width; } - if (node.AlignTopWithNode != null) + if (node.Left.IsNaN()) { - childPos = childPos.WithY(node.AlignTopWithNode.Position.Y); + node.Left = AvailableSize.Width - node.RightOfNode.Right; } + } - if (node.AlignRightWithNode != null) + if (node.BelowNode != null) + { + if (node.Bottom.IsNaN()) { - childPos = childPos.WithX(node.AlignRightWithNode.Element.DesiredSize.Width + node.AlignRightWithNode.Position.X - childSize.Width); + node.Bottom = node.BelowNode.Bottom - childSize.Height; } - if (node.AlignBottomWithNode != null) + if (node.Top.IsNaN()) { - childPos = childPos.WithY(node.AlignBottomWithNode.Element.DesiredSize.Height + node.AlignBottomWithNode.Position.Y - childSize.Height); + node.Top = AvailableSize.Height - node.BelowNode.Bottom; } } - if (alignLeftWithPanel) + if (node.AlignHorizontalCenterWith != null) { - if (node.AlignRightWithNode != null) - { - childPos = childPos.WithX((node.AlignRightWithNode.Element.DesiredSize.Width + node.AlignRightWithNode.Position.X - childSize.Width) / 2); - } + var halfWidthLeft = (AvailableSize.Width + node.AlignHorizontalCenterWith.Left - node.AlignHorizontalCenterWith.Right - childSize.Width) * 0.5; + var halfWidthRight = (AvailableSize.Width - node.AlignHorizontalCenterWith.Left + node.AlignHorizontalCenterWith.Right - childSize.Width) * 0.5; + + if (node.Left.IsNaN()) + node.Left = halfWidthLeft; else - { - childPos = childPos.WithX(0); - } + node.Left = (node.Left + halfWidthLeft) * 0.5; + + if (node.Right.IsNaN()) + node.Right = halfWidthRight; + else + node.Right = (node.Right + halfWidthRight) * 0.5; } - if (alignTopWithPanel) + if (node.AlignVerticalCenterWith != null) { - if (node.AlignBottomWithNode != null) - { - childPos = childPos.WithY((node.AlignBottomWithNode.Element.DesiredSize.Height + node.AlignBottomWithNode.Position.Y - childSize.Height) / 2); - } + var halfHeightTop = (AvailableSize.Height + node.AlignVerticalCenterWith.Top - node.AlignVerticalCenterWith.Bottom - childSize.Height) * 0.5; + var halfHeightBottom = (AvailableSize.Height - node.AlignVerticalCenterWith.Top + node.AlignVerticalCenterWith.Bottom - childSize.Height) * 0.5; + + if (node.Top.IsNaN()) + node.Top = halfHeightTop; else - { - childPos = childPos.WithY(0); - } + node.Top = (node.Top + halfHeightTop) * 0.5; + + if (node.Bottom.IsNaN()) + node.Bottom = halfHeightBottom; + else + node.Bottom = (node.Bottom + halfHeightBottom) * 0.5; } - if (alignRightWithPanel) + if (GetAlignHorizontalCenterWithPanel(child)) { - if (alignLeftWithPanel) - { - childPos = childPos.WithX((_arrangeSize.Width - childSize.Width) / 2); - } - else if (node.AlignLeftWithNode == null) - { - childPos = childPos.WithX(_arrangeSize.Width - childSize.Width); - } + var halfSubWidth = (AvailableSize.Width - childSize.Width) * 0.5; + + if (node.Left.IsNaN()) + node.Left = halfSubWidth; else - { - childPos = childPos.WithX((_arrangeSize.Width + node.AlignLeftWithNode.Position.X - childSize.Width) / 2); - } + node.Left = (node.Left + halfSubWidth) * 0.5; + + if (node.Right.IsNaN()) + node.Right = halfSubWidth; + else + node.Right = (node.Right + halfSubWidth) * 0.5; } - if (alignBottomWithPanel) + if (GetAlignVerticalCenterWithPanel(child)) { - if (alignTopWithPanel) - { - childPos = childPos.WithY((_arrangeSize.Height - childSize.Height) / 2); - } - else if (node.AlignTopWithNode == null) + var halfSubHeight = (AvailableSize.Height - childSize.Height) * 0.5; + + if (node.Top.IsNaN()) + node.Top = halfSubHeight; + else + node.Top = (node.Top + halfSubHeight) * 0.5; + + if (node.Bottom.IsNaN()) + node.Bottom = halfSubHeight; + else + node.Bottom = (node.Bottom + halfSubHeight) * 0.5; + } + + if (node.Left.IsNaN()) + { + if (!node.Right.IsNaN()) + node.Left = AvailableSize.Width - node.Right - childSize.Width; + else { - childPos = childPos.WithY(_arrangeSize.Height - childSize.Height); + node.Left = 0; + node.Right = AvailableSize.Width - childSize.Width; } + } + else if (!node.Left.IsNaN() && node.Right.IsNaN()) + { + node.Right = AvailableSize.Width - node.Left - childSize.Width; + } + + if (node.Top.IsNaN()) + { + if (!node.Bottom.IsNaN()) + node.Top = AvailableSize.Height - node.Bottom - childSize.Height; else { - childPos = childPos.WithY((_arrangeSize.Height + node.AlignTopWithNode.Position.Y - childSize.Height) / 2); + node.Top = 0; + node.Bottom = AvailableSize.Height - childSize.Height; } } + else if (!node.Top.IsNaN() && node.Bottom.IsNaN()) + { + node.Bottom = AvailableSize.Height - node.Top - childSize.Height; + } + + node.Measured = true; + } + + public Size GetBoundingSize(bool calcWidth, bool calcHeight) + { + var boundingSize = new Size(); + + foreach (var node in _nodeDic.Values) + { + var size = node.GetBoundingSize(); + boundingSize = boundingSize.WithWidth(Math.Max(boundingSize.Width, size.Width)); + boundingSize = boundingSize.WithHeight(Math.Max(boundingSize.Height, size.Height)); + } + + boundingSize = boundingSize.WithWidth(calcWidth ? boundingSize.Width : AvailableSize.Width); + boundingSize = boundingSize.WithHeight(calcHeight ? boundingSize.Height : AvailableSize.Height); + return boundingSize; + } + } + } + + internal static partial class Extensions + { + /// + /// Returns a value that indicates whether the specified value is not a number (). + /// + /// A double-precision floating-point number. + /// true if evaluates to ; otherwise, false. + public static bool IsNaN(this double d) + { + return double.IsNaN(d); + } - child.Arrange(new Rect(childPos.X, childPos.Y, childSize.Width, childSize.Height)); - node.Position = childPos; - node.Arranged = true; + public static IEnumerable Do(this IEnumerable source, Action predicate) + { + var enumerable = source as IList ?? source.ToList(); + foreach (var item in enumerable) + { + predicate.Invoke(item); } + + return enumerable; } } } diff --git a/src/Avalonia.Controls/Remote/Server/RemoteServerTopLevelImpl.cs b/src/Avalonia.Controls/Remote/Server/RemoteServerTopLevelImpl.cs index f5c673d5f9..931c27c575 100644 --- a/src/Avalonia.Controls/Remote/Server/RemoteServerTopLevelImpl.cs +++ b/src/Avalonia.Controls/Remote/Server/RemoteServerTopLevelImpl.cs @@ -192,7 +192,7 @@ namespace Avalonia.Controls.Remote.Server GetAvaloniaInputModifiers(pressed.Modifiers))); }, DispatcherPriority.Input); } - if (obj is PointerPressedEventMessage released) + if (obj is PointerReleasedEventMessage released) { Dispatcher.UIThread.Post(() => { diff --git a/src/Avalonia.Controls/Repeater/ElementFactory.cs b/src/Avalonia.Controls/Repeater/ElementFactory.cs new file mode 100644 index 0000000000..644e077221 --- /dev/null +++ b/src/Avalonia.Controls/Repeater/ElementFactory.cs @@ -0,0 +1,27 @@ +using Avalonia.Controls.Templates; + +namespace Avalonia.Controls +{ + public abstract class ElementFactory : IElementFactory + { + public IControl Build(object data) + { + return GetElementCore(new ElementFactoryGetArgs { Data = data }); + } + + public IControl GetElement(ElementFactoryGetArgs args) + { + return GetElementCore(args); + } + + public bool Match(object data) => true; + + public void RecycleElement(ElementFactoryRecycleArgs args) + { + RecycleElementCore(args); + } + + protected abstract IControl GetElementCore(ElementFactoryGetArgs args); + protected abstract void RecycleElementCore(ElementFactoryRecycleArgs args); + } +} diff --git a/src/Avalonia.Controls/Repeater/IElementFactory.cs b/src/Avalonia.Controls/Repeater/IElementFactory.cs new file mode 100644 index 0000000000..6a899a6f26 --- /dev/null +++ b/src/Avalonia.Controls/Repeater/IElementFactory.cs @@ -0,0 +1,66 @@ +using Avalonia.Controls.Templates; + +namespace Avalonia.Controls +{ + /// + /// Represents the optional arguments to use when calling an implementation of the + /// 's method. + /// + public class ElementFactoryGetArgs + { + /// + /// Gets or sets the data item for which an appropriate element tree should be realized + /// when calling . + /// + public object Data { get; set; } + + /// + /// Gets or sets the that is expected to be the parent of the + /// realized element from . + /// + public IControl Parent { get; set; } + + /// + /// Gets or sets the index of the item that should be realized. + /// + public int Index { get; set; } + } + + /// + /// Represents the optional arguments to use when calling an implementation of the + /// 's method. + /// + public class ElementFactoryRecycleArgs + { + /// + /// Gets or sets the to recycle when calling + /// . + /// + public IControl Element { get; set; } + + /// + /// Gets or sets the that is expected to be the parent of the + /// realized element from . + /// + public IControl Parent { get; set; } + } + + /// + /// A data template that supports creating and recyling elements for an . + /// + public interface IElementFactory : IDataTemplate + { + /// + /// Gets an . + /// + /// The element args. + public IControl GetElement(ElementFactoryGetArgs args); + + /// + /// Recycles an that was previously retrieved using + /// . + /// + /// The recycle args. + public void RecycleElement(ElementFactoryRecycleArgs args); + } +} diff --git a/src/Avalonia.Controls/Repeater/ItemTemplateWrapper.cs b/src/Avalonia.Controls/Repeater/ItemTemplateWrapper.cs index 04d859c742..dd97cde218 100644 --- a/src/Avalonia.Controls/Repeater/ItemTemplateWrapper.cs +++ b/src/Avalonia.Controls/Repeater/ItemTemplateWrapper.cs @@ -7,13 +7,26 @@ using Avalonia.Controls.Templates; namespace Avalonia.Controls { - internal class ItemTemplateWrapper + internal class ItemTemplateWrapper : IElementFactory { private readonly IDataTemplate _dataTemplate; public ItemTemplateWrapper(IDataTemplate dataTemplate) => _dataTemplate = dataTemplate; - public IControl GetElement(IControl parent, object data) + public IControl Build(object param) => GetElement(null, param); + public bool Match(object data) => _dataTemplate.Match(data); + + public IControl GetElement(ElementFactoryGetArgs args) + { + return GetElement(args.Parent, args.Data); + } + + public void RecycleElement(ElementFactoryRecycleArgs args) + { + RecycleElement(args.Parent, args.Element); + } + + private IControl GetElement(IControl parent, object data) { var selectedTemplate = _dataTemplate; var recyclePool = RecyclePool.GetPoolInstance(selectedTemplate); @@ -37,7 +50,7 @@ namespace Avalonia.Controls return element; } - public void RecycleElement(IControl parent, IControl element) + private void RecycleElement(IControl parent, IControl element) { var selectedTemplate = _dataTemplate; var recyclePool = RecyclePool.GetPoolInstance(selectedTemplate); diff --git a/src/Avalonia.Controls/Repeater/ItemsRepeater.cs b/src/Avalonia.Controls/Repeater/ItemsRepeater.cs index 87f4760156..8bc356bdec 100644 --- a/src/Avalonia.Controls/Repeater/ItemsRepeater.cs +++ b/src/Avalonia.Controls/Repeater/ItemsRepeater.cs @@ -141,7 +141,7 @@ namespace Avalonia.Controls /// public ItemsSourceView ItemsSourceView { get; private set; } - internal ItemTemplateWrapper ItemTemplateShim { get; set; } + internal IElementFactory ItemTemplateShim { get; set; } internal Point LayoutOrigin { get; set; } internal object LayoutState { get; set; } internal IControl MadeAnchor => _viewportManager.MadeAnchor; @@ -664,7 +664,7 @@ namespace Avalonia.Controls } } - ItemTemplateShim = new ItemTemplateWrapper(newValue); + ItemTemplateShim = newValue as IElementFactory ?? new ItemTemplateWrapper(newValue); InvalidateMeasure(); } diff --git a/src/Avalonia.Controls/Repeater/RecyclePool.cs b/src/Avalonia.Controls/Repeater/RecyclePool.cs index 4e5950bdc5..28f299043c 100644 --- a/src/Avalonia.Controls/Repeater/RecyclePool.cs +++ b/src/Avalonia.Controls/Repeater/RecyclePool.cs @@ -11,10 +11,13 @@ using Avalonia.Controls.Templates; namespace Avalonia.Controls { - internal class RecyclePool + public class RecyclePool { - public static readonly AttachedProperty OriginTemplateProperty = - AvaloniaProperty.RegisterAttached("OriginTemplate", typeof(RecyclePool)); + internal static readonly AttachedProperty OriginTemplateProperty = + AvaloniaProperty.RegisterAttached("OriginTemplate"); + + internal static readonly AttachedProperty ReuseKeyProperty = + AvaloniaProperty.RegisterAttached("ReuseKey", string.Empty); private static ConditionalWeakTable s_pools = new ConditionalWeakTable(); private readonly Dictionary> _elements = new Dictionary>(); @@ -77,6 +80,9 @@ namespace Avalonia.Controls return null; } + internal string GetReuseKey(IControl element) => element.GetValue(ReuseKeyProperty); + internal void SetReuseKey(IControl element, string value) => element.SetValue(ReuseKeyProperty, value); + private IPanel EnsureOwnerIsPanelOrNull(IControl owner) { if (owner is IPanel panel) diff --git a/src/Avalonia.Controls/Repeater/RecyclingElementFactory.cs b/src/Avalonia.Controls/Repeater/RecyclingElementFactory.cs new file mode 100644 index 0000000000..9503239e34 --- /dev/null +++ b/src/Avalonia.Controls/Repeater/RecyclingElementFactory.cs @@ -0,0 +1,119 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using Avalonia.Controls.Templates; + +#nullable enable + +namespace Avalonia.Controls +{ + public class SelectTemplateEventArgs : EventArgs + { + public string? TemplateKey { get; set; } + public object? DataContext { get; internal set; } + public IControl? Owner { get; internal set; } + } + + public class RecyclingElementFactory : ElementFactory + { + private RecyclePool? _recyclePool; + private IDictionary? _templates; + private SelectTemplateEventArgs? _args; + + public RecyclingElementFactory() + { + Templates = new Dictionary(); + } + + public RecyclePool RecyclePool + { + get => _recyclePool ??= new RecyclePool(); + set => _recyclePool = value ?? throw new ArgumentNullException(nameof(value)); + } + + public IDictionary Templates + { + get => _templates ??= new Dictionary(); + set => _templates = value ?? throw new ArgumentNullException(nameof(value)); + } + + public event EventHandler? SelectTemplateKey; + + protected override IControl GetElementCore(ElementFactoryGetArgs args) + { + if (_templates == null || _templates.Count == 0) + { + throw new InvalidOperationException("Templates cannot be empty."); + } + + var templateKey = Templates.Count == 1 ? + Templates.First().Key : + OnSelectTemplateKeyCore(args.Data, args.Parent); + + if (string.IsNullOrEmpty(templateKey)) + { + // Note: We could allow null/whitespace, which would work as long as + // the recycle pool is not shared. in order to make this work in all cases + // currently we validate that a valid template key is provided. + throw new InvalidOperationException("Template key cannot be null or empty."); + } + + // Get an element from the Recycle Pool or create one + var element = RecyclePool.TryGetElement(templateKey, args.Parent); + + if (element is null) + { + // No need to call HasKey if there is only one template. + if (Templates.Count > 1 && !Templates.ContainsKey(templateKey)) + { + var message = $"No templates of key '{templateKey}' were found in the templates collection."; + throw new InvalidOperationException(message); + } + + var dataTemplate = Templates[templateKey]; + element = dataTemplate.Build(args.Data); + + // Associate ReuseKey with element + RecyclePool.SetReuseKey(element, templateKey); + } + + return element; + } + + protected override void RecycleElementCore(ElementFactoryRecycleArgs args) + { + var element = args.Element; + var key = RecyclePool.GetReuseKey(element); + RecyclePool.PutElement(element, key, args.Parent); + } + + protected virtual string OnSelectTemplateKeyCore(object dataContext, IControl owner) + { + if (SelectTemplateKey is object) + { + _args ??= new SelectTemplateEventArgs(); + _args.TemplateKey = null; + _args.DataContext = dataContext; + _args.Owner = owner; + + try + { + SelectTemplateKey(this, _args); + } + finally + { + _args.DataContext = null; + _args.Owner = null; + } + } + + if (string.IsNullOrEmpty(_args?.TemplateKey)) + { + throw new InvalidOperationException( + "Please provide a valid template identifier in the handler for the SelectTemplateKey event."); + } + + return _args!.TemplateKey!; + } + } +} diff --git a/src/Avalonia.Controls/Repeater/ViewManager.cs b/src/Avalonia.Controls/Repeater/ViewManager.cs index eff51804b9..416b1e2824 100644 --- a/src/Avalonia.Controls/Repeater/ViewManager.cs +++ b/src/Avalonia.Controls/Repeater/ViewManager.cs @@ -6,11 +6,9 @@ using System; using System.Collections.Generic; using System.Collections.Specialized; -using System.Linq; using Avalonia.Controls.Templates; using Avalonia.Input; using Avalonia.Interactivity; -using Avalonia.Layout; using Avalonia.Logging; using Avalonia.VisualTree; @@ -26,6 +24,8 @@ namespace Avalonia.Controls private readonly UniqueIdElementPool _resetPool; private IControl _lastFocusedElement; private bool _isDataSourceStableResetPending; + private ElementFactoryGetArgs _elementFactoryGetArgs; + private ElementFactoryRecycleArgs _elementFactoryRecycleArgs; private int _firstRealizedElementIndexHeldByLayout = FirstRealizedElementIndexDefault; private int _lastRealizedElementIndexHeldByLayout = LastRealizedElementIndexDefault; private bool _eventsSubscribed; @@ -134,7 +134,14 @@ namespace Avalonia.Controls if (_owner.ItemTemplateShim != null) { - _owner.ItemTemplateShim.RecycleElement(_owner, element); + var context = _elementFactoryRecycleArgs ??= new ElementFactoryRecycleArgs(); + context.Element = element; + context.Parent = _owner; + + _owner.ItemTemplateShim.RecycleElement(context); + + context.Element = null; + context.Parent = null; } else { @@ -579,7 +586,7 @@ namespace Avalonia.Controls var data = _owner.ItemsSourceView.GetAt(index); var providedElementFactory = _owner.ItemTemplateShim; - ItemTemplateWrapper GetElementFactory() + IElementFactory GetElementFactory() { if (providedElementFactory == null) { @@ -602,7 +609,20 @@ namespace Avalonia.Controls } var elementFactory = GetElementFactory(); - return elementFactory.GetElement(_owner, data); + var args = _elementFactoryGetArgs ??= new ElementFactoryGetArgs(); + + try + { + args.Data = data; + args.Parent = _owner; + args.Index = index; + return elementFactory.GetElement(args); + } + finally + { + args.Data = null; + args.Parent = null; + } } var element = GetElement(); @@ -732,6 +752,7 @@ namespace Avalonia.Controls { _owner.GotFocus += OnFocusChanged; _owner.LostFocus += OnFocusChanged; + _eventsSubscribed = true; } } diff --git a/src/Avalonia.Controls/Templates/FuncDataTemplate.cs b/src/Avalonia.Controls/Templates/FuncDataTemplate.cs index d454a29021..1afd86a11e 100644 --- a/src/Avalonia.Controls/Templates/FuncDataTemplate.cs +++ b/src/Avalonia.Controls/Templates/FuncDataTemplate.cs @@ -6,7 +6,7 @@ namespace Avalonia.Controls.Templates /// /// Builds a control for a piece of data. /// - public class FuncDataTemplate : FuncTemplate, IDataTemplate + public class FuncDataTemplate : FuncTemplate, IRecyclingDataTemplate { /// /// The default data template used in the case where no matching data template is found. @@ -30,10 +30,8 @@ namespace Avalonia.Controls.Templates }, true); - /// - /// The implementation of the method. - /// private readonly Func _match; + private readonly bool _supportsRecycling; /// /// Initializes a new instance of the class. @@ -70,12 +68,9 @@ namespace Avalonia.Controls.Templates Contract.Requires(match != null); _match = match; - SupportsRecycling = supportsRecycling; + _supportsRecycling = supportsRecycling; } - /// - public bool SupportsRecycling { get; } - /// /// Checks to see if this data template matches the specified data. /// @@ -88,6 +83,24 @@ namespace Avalonia.Controls.Templates return _match(data); } + /// + /// Creates or recycles a control to display the specified data. + /// + /// The data to display. + /// An optional control to recycle. + /// + /// The control if supplied and applicable to + /// , otherwise a new control. + /// + /// + /// The caller should ensure that any control passed to + /// originated from the same data template. + /// + public IControl Build(object data, IControl existing) + { + return _supportsRecycling && existing is object ? existing : Build(data); + } + /// /// Determines of an object is of the specified type. /// diff --git a/src/Avalonia.Controls/Templates/FuncTemplate`2.cs b/src/Avalonia.Controls/Templates/FuncTemplate`2.cs index d08616b968..cd0e3ad603 100644 --- a/src/Avalonia.Controls/Templates/FuncTemplate`2.cs +++ b/src/Avalonia.Controls/Templates/FuncTemplate`2.cs @@ -1,5 +1,7 @@ using System; +#nullable enable + namespace Avalonia.Controls.Templates { /// @@ -18,9 +20,7 @@ namespace Avalonia.Controls.Templates /// The function used to create the control. public FuncTemplate(Func func) { - Contract.Requires(func != null); - - _func = func; + _func = func ?? throw new ArgumentNullException(nameof(func)); } /// diff --git a/src/Avalonia.Controls/Templates/IDataTemplate.cs b/src/Avalonia.Controls/Templates/IDataTemplate.cs index cfde029eb8..0368748a0b 100644 --- a/src/Avalonia.Controls/Templates/IDataTemplate.cs +++ b/src/Avalonia.Controls/Templates/IDataTemplate.cs @@ -1,3 +1,7 @@ +using System; + +#nullable enable + namespace Avalonia.Controls.Templates { /// @@ -5,12 +9,6 @@ namespace Avalonia.Controls.Templates /// public interface IDataTemplate : ITemplate { - /// - /// Gets a value indicating whether the data template supports recycling of the generated - /// control. - /// - bool SupportsRecycling { get; } - /// /// Checks to see if this data template matches the specified data. /// @@ -20,4 +18,4 @@ namespace Avalonia.Controls.Templates /// bool Match(object data); } -} \ No newline at end of file +} diff --git a/src/Avalonia.Controls/Templates/IRecyclingDataTemplate.cs b/src/Avalonia.Controls/Templates/IRecyclingDataTemplate.cs new file mode 100644 index 0000000000..25956a9c9a --- /dev/null +++ b/src/Avalonia.Controls/Templates/IRecyclingDataTemplate.cs @@ -0,0 +1,25 @@ +#nullable enable + +namespace Avalonia.Controls.Templates +{ + /// + /// An that supports recycling existing elements. + /// + public interface IRecyclingDataTemplate : IDataTemplate + { + /// + /// Creates or recycles a control to display the specified data. + /// + /// The data to display. + /// An optional control to recycle. + /// + /// The control if supplied and applicable to + /// , otherwise a new control. + /// + /// + /// The caller should ensure that any control passed to + /// originated from the same data template. + /// + IControl Build(object data, IControl? existing); + } +} diff --git a/src/Avalonia.Controls/TextBox.cs b/src/Avalonia.Controls/TextBox.cs index 394699ce64..87a674fabd 100644 --- a/src/Avalonia.Controls/TextBox.cs +++ b/src/Avalonia.Controls/TextBox.cs @@ -134,7 +134,7 @@ namespace Avalonia.Controls { if (acceptsReturn) { - return wrapping == TextWrapping.NoWrap ? + return wrapping != TextWrapping.Wrap ? ScrollBarVisibility.Auto : ScrollBarVisibility.Disabled; } diff --git a/src/Avalonia.Controls/TickBar.cs b/src/Avalonia.Controls/TickBar.cs index 22145d8742..6ea5277a55 100644 --- a/src/Avalonia.Controls/TickBar.cs +++ b/src/Avalonia.Controls/TickBar.cs @@ -39,11 +39,15 @@ namespace Avalonia.Controls { static TickBar() { - AffectsRender(ReservedSpaceProperty, + AffectsRender(FillProperty, + IsDirectionReversedProperty, + ReservedSpaceProperty, MaximumProperty, MinimumProperty, OrientationProperty, - TickFrequencyProperty); + PlacementProperty, + TickFrequencyProperty, + TicksProperty); } public TickBar() : base() @@ -137,7 +141,7 @@ namespace Avalonia.Controls /// /// The Ticks property contains collection of value of type Double which /// are the logical positions use to draw the ticks. - /// The property value is a . + /// The property value is a . /// public AvaloniaList Ticks { @@ -169,7 +173,6 @@ namespace Avalonia.Controls public static readonly StyledProperty PlacementProperty = AvaloniaProperty.Register(nameof(Placement), 0d); - /// /// Placement property specified how the Tick will be placed. /// This property affects the way ticks are drawn. @@ -189,7 +192,7 @@ namespace Avalonia.Controls /// /// TickBar will use ReservedSpaceProperty for left and right spacing (for horizontal orientation) or - /// tob and bottom spacing (for vertical orienation). + /// top and bottom spacing (for vertical orienation). /// The space on both sides of TickBar is half of specified ReservedSpace. /// This property has type of . /// @@ -201,7 +204,7 @@ namespace Avalonia.Controls /// /// Draw ticks. - /// Ticks can be draw in 8 diffrent ways depends on Placment property and IsDirectionReversed property. + /// Ticks can be draw in 8 different ways depends on Placement property and IsDirectionReversed property. /// /// This function also draw selection-tick(s) if IsSelectionRangeEnabled is 'true' and /// SelectionStart and SelectionEnd are valid. @@ -211,9 +214,7 @@ namespace Avalonia.Controls /// /// The secondary ticks (all other ticks, including selection-tics) height will be 75% of TickBar's render size. /// - /// Brush that use to fill ticks is specified by Shape.Fill property. - /// - /// Pen that use to draw ticks is specified by Shape.Pen property. + /// Brush that use to fill ticks is specified by Fill property. /// public override void Render(DrawingContext dc) { @@ -222,7 +223,6 @@ namespace Avalonia.Controls var tickLen = 0.0d; // Height for Primary Tick (for Mininum and Maximum value) var tickLen2 = 0.0d; // Height for Secondary Tick var logicalToPhysical = 1.0; - var progression = 1.0d; var startPoint = new Point(); var endPoint = new Point(); var rSpace = Orientation == Orientation.Horizontal ? ReservedSpace.Width : ReservedSpace.Height; @@ -242,7 +242,6 @@ namespace Avalonia.Controls startPoint = new Point(halfReservedSpace, size.Height); endPoint = new Point(halfReservedSpace + size.Width, size.Height); logicalToPhysical = size.Width / range; - progression = 1; break; case TickBarPlacement.Bottom: @@ -255,7 +254,6 @@ namespace Avalonia.Controls startPoint = new Point(halfReservedSpace, 0d); endPoint = new Point(halfReservedSpace + size.Width, 0d); logicalToPhysical = size.Width / range; - progression = 1; break; case TickBarPlacement.Left: @@ -269,7 +267,6 @@ namespace Avalonia.Controls startPoint = new Point(size.Width, size.Height + halfReservedSpace); endPoint = new Point(size.Width, halfReservedSpace); logicalToPhysical = size.Height / range * -1; - progression = -1; break; case TickBarPlacement.Right: @@ -282,7 +279,6 @@ namespace Avalonia.Controls startPoint = new Point(0d, size.Height + halfReservedSpace); endPoint = new Point(0d, halfReservedSpace); logicalToPhysical = size.Height / range * -1; - progression = -1; break; }; @@ -291,7 +287,6 @@ namespace Avalonia.Controls // Invert direciton of the ticks if (IsDirectionReversed) { - progression *= -progression; logicalToPhysical *= -1; // swap startPoint & endPoint diff --git a/src/Avalonia.Controls/ToolTip.cs b/src/Avalonia.Controls/ToolTip.cs index b458b15c64..cf0652247f 100644 --- a/src/Avalonia.Controls/ToolTip.cs +++ b/src/Avalonia.Controls/ToolTip.cs @@ -66,6 +66,7 @@ namespace Avalonia.Controls static ToolTip() { TipProperty.Changed.Subscribe(ToolTipService.Instance.TipChanged); + IsOpenProperty.Changed.Subscribe(ToolTipService.Instance.TipOpenChanged); IsOpenProperty.Changed.Subscribe(IsOpenChanged); } diff --git a/src/Avalonia.Controls/ToolTipService.cs b/src/Avalonia.Controls/ToolTipService.cs index d90729e8a5..e2a0f9e50c 100644 --- a/src/Avalonia.Controls/ToolTipService.cs +++ b/src/Avalonia.Controls/ToolTipService.cs @@ -37,6 +37,27 @@ namespace Avalonia.Controls } } + internal void TipOpenChanged(AvaloniaPropertyChangedEventArgs e) + { + var control = (Control)e.Sender; + + if (e.OldValue is false && e.NewValue is true) + { + control.DetachedFromVisualTree += ControlDetaching; + } + else if(e.OldValue is true && e.NewValue is false) + { + control.DetachedFromVisualTree -= ControlDetaching; + } + } + + private void ControlDetaching(object sender, VisualTreeAttachmentEventArgs e) + { + var control = (Control)sender; + control.DetachedFromVisualTree -= ControlDetaching; + Close(control); + } + /// /// Called when the pointer enters a control with an attached tooltip. /// diff --git a/src/Avalonia.Controls/TopLevel.cs b/src/Avalonia.Controls/TopLevel.cs index 611f0c9290..3d24f60463 100644 --- a/src/Avalonia.Controls/TopLevel.cs +++ b/src/Avalonia.Controls/TopLevel.cs @@ -280,10 +280,10 @@ namespace Avalonia.Controls } /// - double ILayoutRoot.LayoutScaling => PlatformImpl?.Scaling ?? 1; + double ILayoutRoot.LayoutScaling => PlatformImpl?.RenderScaling ?? 1; /// - double IRenderRoot.RenderScaling => PlatformImpl?.Scaling ?? 1; + double IRenderRoot.RenderScaling => PlatformImpl?.RenderScaling ?? 1; IStyleHost IStyleHost.StylingParent => _globalStyles; diff --git a/src/Avalonia.Controls/Window.cs b/src/Avalonia.Controls/Window.cs index 18d8c89f49..d8e7f3a387 100644 --- a/src/Avalonia.Controls/Window.cs +++ b/src/Avalonia.Controls/Window.cs @@ -6,6 +6,7 @@ using System.Reactive.Linq; using System.Threading.Tasks; using Avalonia.Controls.Chrome; using Avalonia.Controls.Platform; +using Avalonia.Controls.Primitives; using Avalonia.Data; using Avalonia.Input; using Avalonia.Interactivity; @@ -70,8 +71,7 @@ namespace Avalonia.Controls /// public class Window : WindowBase, IStyleable, IFocusScope, ILayoutRoot { - private readonly List<(Window child, bool isDialog)> _children = new List<(Window, bool)>(); - private TitleBar _managedTitleBar; + private readonly List<(Window child, bool isDialog)> _children = new List<(Window, bool)>(); private bool _isExtendedIntoWindowDecorations; private Thickness _windowDecorationMargin; private Thickness _offScreenMargin; @@ -552,20 +552,6 @@ namespace Avalonia.Controls IsExtendedIntoWindowDecorations = isExtended; WindowDecorationMargin = PlatformImpl.ExtendedMargins; OffScreenMargin = PlatformImpl.OffScreenMargin; - - if (PlatformImpl.NeedsManagedDecorations) - { - if (_managedTitleBar == null) - { - _managedTitleBar = new TitleBar(this); - _managedTitleBar.Attach(); - } - } - else - { - _managedTitleBar?.Detach(); - _managedTitleBar = null; - } } /// @@ -818,7 +804,7 @@ namespace Avalonia.Controls private void SetWindowStartupLocation(IWindowBaseImpl owner = null) { - var scaling = owner?.Scaling ?? PlatformImpl?.Scaling ?? 1; + var scaling = owner?.DesktopScaling ?? PlatformImpl?.DesktopScaling ?? 1; // TODO: We really need non-client size here. var rect = new PixelRect( diff --git a/src/Avalonia.DesignerSupport/Avalonia.DesignerSupport.csproj b/src/Avalonia.DesignerSupport/Avalonia.DesignerSupport.csproj index 76ea8a3445..35bd0f97e1 100644 --- a/src/Avalonia.DesignerSupport/Avalonia.DesignerSupport.csproj +++ b/src/Avalonia.DesignerSupport/Avalonia.DesignerSupport.csproj @@ -9,6 +9,7 @@ 0.7.0 CS1591 true + Avalonia @@ -26,4 +27,5 @@ + diff --git a/src/Avalonia.DesignerSupport/DesignWindowLoader.cs b/src/Avalonia.DesignerSupport/DesignWindowLoader.cs index f3bb0edce5..59862da230 100644 --- a/src/Avalonia.DesignerSupport/DesignWindowLoader.cs +++ b/src/Avalonia.DesignerSupport/DesignWindowLoader.cs @@ -18,10 +18,11 @@ namespace Avalonia.DesignerSupport Control control; using (PlatformManager.DesignerMode()) { - var loader = new AvaloniaXamlLoader() {IsDesignMode = true}; + var loader = AvaloniaLocator.Current.GetService(); var stream = new MemoryStream(Encoding.UTF8.GetBytes(xaml)); - + if (loader == null) + throw new XamlLoadException("Runtime XAML loader is not registered"); Uri baseUri = null; if (assemblyPath != null) @@ -34,7 +35,7 @@ namespace Avalonia.DesignerSupport } var localAsm = assemblyPath != null ? Assembly.LoadFile(Path.GetFullPath(assemblyPath)) : null; - var loaded = loader.Load(stream, localAsm, null, baseUri); + var loaded = loader.Load(stream, localAsm, null, baseUri, true); var style = loaded as IStyle; if (style != null) { diff --git a/src/Avalonia.DesignerSupport/Remote/FileWatcherTransport.cs b/src/Avalonia.DesignerSupport/Remote/FileWatcherTransport.cs index 0cb71dd217..0448a5c05d 100644 --- a/src/Avalonia.DesignerSupport/Remote/FileWatcherTransport.cs +++ b/src/Avalonia.DesignerSupport/Remote/FileWatcherTransport.cs @@ -9,12 +9,14 @@ namespace Avalonia.DesignerSupport.Remote { class FileWatcherTransport : IAvaloniaRemoteTransportConnection, ITransportWithEnforcedMethod { + private readonly string _appPath; private string _path; private string _lastContents; private bool _disposed; - public FileWatcherTransport(Uri file) + public FileWatcherTransport(Uri file, string appPath) { + _appPath = appPath; _path = file.LocalPath; } @@ -73,7 +75,11 @@ namespace Avalonia.DesignerSupport.Remote { Console.WriteLine("Triggering XAML update"); _lastContents = data; - _onMessage?.Invoke(this, new UpdateXamlMessage { Xaml = data }); + _onMessage?.Invoke(this, new UpdateXamlMessage + { + Xaml = data, + AssemblyPath = _appPath + }); } await Task.Delay(100); diff --git a/src/Avalonia.DesignerSupport/Remote/HtmlTransport/webapp/package-lock.json b/src/Avalonia.DesignerSupport/Remote/HtmlTransport/webapp/package-lock.json index 87536c670f..028027a974 100644 --- a/src/Avalonia.DesignerSupport/Remote/HtmlTransport/webapp/package-lock.json +++ b/src/Avalonia.DesignerSupport/Remote/HtmlTransport/webapp/package-lock.json @@ -3564,12 +3564,14 @@ "balanced-match": { "version": "1.0.0", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "brace-expansion": { "version": "1.1.11", "bundled": true, "dev": true, + "optional": true, "requires": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" @@ -3584,17 +3586,20 @@ "code-point-at": { "version": "1.1.0", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "concat-map": { "version": "0.0.1", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "console-control-strings": { "version": "1.1.0", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "core-util-is": { "version": "1.0.2", @@ -3711,7 +3716,8 @@ "inherits": { "version": "2.0.3", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "ini": { "version": "1.3.5", @@ -3723,6 +3729,7 @@ "version": "1.0.0", "bundled": true, "dev": true, + "optional": true, "requires": { "number-is-nan": "^1.0.0" } @@ -3737,6 +3744,7 @@ "version": "3.0.4", "bundled": true, "dev": true, + "optional": true, "requires": { "brace-expansion": "^1.1.7" } @@ -3744,12 +3752,14 @@ "minimist": { "version": "0.0.8", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "minipass": { "version": "2.3.5", "bundled": true, "dev": true, + "optional": true, "requires": { "safe-buffer": "^5.1.2", "yallist": "^3.0.0" @@ -3768,6 +3778,7 @@ "version": "0.5.1", "bundled": true, "dev": true, + "optional": true, "requires": { "minimist": "0.0.8" } @@ -3848,7 +3859,8 @@ "number-is-nan": { "version": "1.0.1", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "object-assign": { "version": "4.1.1", @@ -3860,6 +3872,7 @@ "version": "1.4.0", "bundled": true, "dev": true, + "optional": true, "requires": { "wrappy": "1" } @@ -3981,6 +3994,7 @@ "version": "1.0.2", "bundled": true, "dev": true, + "optional": true, "requires": { "code-point-at": "^1.0.0", "is-fullwidth-code-point": "^1.0.0", diff --git a/src/Avalonia.DesignerSupport/Remote/HtmlTransport/webapp/webpack.config.js b/src/Avalonia.DesignerSupport/Remote/HtmlTransport/webapp/webpack.config.js index 3057f269a5..3750351165 100644 --- a/src/Avalonia.DesignerSupport/Remote/HtmlTransport/webapp/webpack.config.js +++ b/src/Avalonia.DesignerSupport/Remote/HtmlTransport/webapp/webpack.config.js @@ -93,7 +93,7 @@ const config = { plugins: [ new Printer(), - new CleanWebpackPlugin([path.resolve(__dirname, 'build')]), + new CleanWebpackPlugin([path.resolve(__dirname, 'build')], { verbose: false }), new MiniCssExtractPlugin({ filename: "[name].[chunkhash]h" + ".css", diff --git a/src/Avalonia.DesignerSupport/Remote/PreviewerWindowImpl.cs b/src/Avalonia.DesignerSupport/Remote/PreviewerWindowImpl.cs index dce24df9d9..25c26be91e 100644 --- a/src/Avalonia.DesignerSupport/Remote/PreviewerWindowImpl.cs +++ b/src/Avalonia.DesignerSupport/Remote/PreviewerWindowImpl.cs @@ -36,6 +36,7 @@ namespace Avalonia.DesignerSupport.Remote { } + public double DesktopScaling => 1.0; public PixelPoint Position { get; set; } public Action PositionChanged { get; set; } public Action Deactivated { get; set; } diff --git a/src/Avalonia.DesignerSupport/Remote/RemoteDesignerEntryPoint.cs b/src/Avalonia.DesignerSupport/Remote/RemoteDesignerEntryPoint.cs index e61fe82c41..764fc7b332 100644 --- a/src/Avalonia.DesignerSupport/Remote/RemoteDesignerEntryPoint.cs +++ b/src/Avalonia.DesignerSupport/Remote/RemoteDesignerEntryPoint.cs @@ -112,8 +112,9 @@ namespace Avalonia.DesignerSupport.Remote return rv; } - static IAvaloniaRemoteTransportConnection CreateTransport(Uri transport) + static IAvaloniaRemoteTransportConnection CreateTransport(CommandLineArgs args) { + var transport = args.Transport; if (transport.Scheme == "tcp-bson") { return new BsonTcpTransport().Connect(IPAddress.Parse(transport.Host), transport.Port).Result; @@ -121,7 +122,7 @@ namespace Avalonia.DesignerSupport.Remote if (transport.Scheme == "file") { - return new FileWatcherTransport(transport); + return new FileWatcherTransport(transport, args.AppPath); } PrintUsage(); return null; @@ -160,7 +161,7 @@ namespace Avalonia.DesignerSupport.Remote public static void Main(string[] cmdline) { var args = ParseCommandLineArgs(cmdline); - var transport = CreateTransport(args.Transport); + var transport = CreateTransport(args); if (transport is ITransportWithEnforcedMethod enforcedMethod) args.Method = enforcedMethod.PreviewerMethod; var asm = Assembly.LoadFile(System.IO.Path.GetFullPath(args.AppPath)); diff --git a/src/Avalonia.DesignerSupport/Remote/Stubs.cs b/src/Avalonia.DesignerSupport/Remote/Stubs.cs index 168cdbc03f..f377b1bcd1 100644 --- a/src/Avalonia.DesignerSupport/Remote/Stubs.cs +++ b/src/Avalonia.DesignerSupport/Remote/Stubs.cs @@ -21,7 +21,8 @@ namespace Avalonia.DesignerSupport.Remote public IPlatformHandle Handle { get; } public Size MaxAutoSizeHint { get; } public Size ClientSize { get; } - public double Scaling { get; } = 1.0; + public double RenderScaling { get; } = 1.0; + public double DesktopScaling => 1.0; public IEnumerable Surfaces { get; } public Action Input { get; set; } public Action Paint { get; set; } diff --git a/src/Avalonia.Desktop/Avalonia.Desktop.csproj b/src/Avalonia.Desktop/Avalonia.Desktop.csproj index 72b7b5c1e7..a9589b7f2b 100644 --- a/src/Avalonia.Desktop/Avalonia.Desktop.csproj +++ b/src/Avalonia.Desktop/Avalonia.Desktop.csproj @@ -11,4 +11,6 @@ + + diff --git a/src/Avalonia.DesktopRuntime/Avalonia.DesktopRuntime.csproj b/src/Avalonia.DesktopRuntime/Avalonia.DesktopRuntime.csproj index c0e46416c1..805aae2d1a 100644 --- a/src/Avalonia.DesktopRuntime/Avalonia.DesktopRuntime.csproj +++ b/src/Avalonia.DesktopRuntime/Avalonia.DesktopRuntime.csproj @@ -17,4 +17,5 @@ + diff --git a/src/Avalonia.Diagnostics/Avalonia.Diagnostics.csproj b/src/Avalonia.Diagnostics/Avalonia.Diagnostics.csproj index 5f49a46839..db8684747d 100644 --- a/src/Avalonia.Diagnostics/Avalonia.Diagnostics.csproj +++ b/src/Avalonia.Diagnostics/Avalonia.Diagnostics.csproj @@ -2,6 +2,7 @@ netstandard2.0 Avalonia + Avalonia.Diagnostics @@ -28,4 +29,5 @@ + diff --git a/src/Avalonia.Diagnostics/Diagnostics/ViewLocator.cs b/src/Avalonia.Diagnostics/Diagnostics/ViewLocator.cs index c06fbec801..be3564e781 100644 --- a/src/Avalonia.Diagnostics/Diagnostics/ViewLocator.cs +++ b/src/Avalonia.Diagnostics/Diagnostics/ViewLocator.cs @@ -7,8 +7,6 @@ namespace Avalonia.Diagnostics { internal class ViewLocator : IDataTemplate { - public bool SupportsRecycling => false; - public IControl Build(object data) { var name = data.GetType().FullName.Replace("ViewModel", "View"); diff --git a/src/Avalonia.Dialogs/Avalonia.Dialogs.csproj b/src/Avalonia.Dialogs/Avalonia.Dialogs.csproj index f54ddf32be..dd476e2559 100644 --- a/src/Avalonia.Dialogs/Avalonia.Dialogs.csproj +++ b/src/Avalonia.Dialogs/Avalonia.Dialogs.csproj @@ -15,4 +15,6 @@ + + diff --git a/src/Avalonia.FreeDesktop/Avalonia.FreeDesktop.csproj b/src/Avalonia.FreeDesktop/Avalonia.FreeDesktop.csproj index d9fd3b78a2..260d7350d2 100644 --- a/src/Avalonia.FreeDesktop/Avalonia.FreeDesktop.csproj +++ b/src/Avalonia.FreeDesktop/Avalonia.FreeDesktop.csproj @@ -1,7 +1,7 @@  - netstandard2.0 + netstandard2.0 @@ -10,6 +10,5 @@ - - + diff --git a/src/Avalonia.FreeDesktop/DBusHelper.cs b/src/Avalonia.FreeDesktop/DBusHelper.cs index b445f86613..91c4c28995 100644 --- a/src/Avalonia.FreeDesktop/DBusHelper.cs +++ b/src/Avalonia.FreeDesktop/DBusHelper.cs @@ -43,7 +43,7 @@ namespace Avalonia.FreeDesktop public void Initialized() { lock (_lock) - _ctx = new AvaloniaSynchronizationContext(); + _ctx = new AvaloniaSynchronizationContext(null); } } public static Connection Connection { get; private set; } diff --git a/src/Avalonia.Headless/Avalonia.Headless.csproj b/src/Avalonia.Headless/Avalonia.Headless.csproj index b871316a2c..ac591b68a8 100644 --- a/src/Avalonia.Headless/Avalonia.Headless.csproj +++ b/src/Avalonia.Headless/Avalonia.Headless.csproj @@ -1,8 +1,9 @@  netstandard2.0 + - + diff --git a/src/Avalonia.Headless/HeadlessWindowImpl.cs b/src/Avalonia.Headless/HeadlessWindowImpl.cs index 5bd46b6714..8f4fa5e304 100644 --- a/src/Avalonia.Headless/HeadlessWindowImpl.cs +++ b/src/Avalonia.Headless/HeadlessWindowImpl.cs @@ -41,7 +41,8 @@ namespace Avalonia.Headless } public Size ClientSize { get; set; } - public double Scaling { get; } = 1; + public double RenderScaling { get; } = 1; + public double DesktopScaling => RenderScaling; public IEnumerable Surfaces { get; } public Action Input { get; set; } public Action Paint { get; set; } @@ -62,9 +63,9 @@ namespace Avalonia.Headless public IInputRoot InputRoot { get; set; } - public Point PointToClient(PixelPoint point) => point.ToPoint(Scaling); + public Point PointToClient(PixelPoint point) => point.ToPoint(RenderScaling); - public PixelPoint PointToScreen(Point point) => PixelPoint.FromPoint(point, Scaling); + public PixelPoint PointToScreen(Point point) => PixelPoint.FromPoint(point, RenderScaling); public void SetCursor(IPlatformHandle cursor) { @@ -201,7 +202,7 @@ namespace Avalonia.Headless public ILockedFramebuffer Lock() { - var bmp = new WriteableBitmap(PixelSize.FromSize(ClientSize, Scaling), new Vector(96, 96) * Scaling); + var bmp = new WriteableBitmap(PixelSize.FromSize(ClientSize, RenderScaling), new Vector(96, 96) * RenderScaling); var fb = bmp.Lock(); return new FramebufferProxy(fb, () => { diff --git a/src/Avalonia.Input/Avalonia.Input.csproj b/src/Avalonia.Input/Avalonia.Input.csproj index ea560ce2ea..2204778afe 100644 --- a/src/Avalonia.Input/Avalonia.Input.csproj +++ b/src/Avalonia.Input/Avalonia.Input.csproj @@ -10,4 +10,5 @@ + diff --git a/src/Avalonia.Interactivity/Avalonia.Interactivity.csproj b/src/Avalonia.Interactivity/Avalonia.Interactivity.csproj index 730ca2bd6e..2da23410d6 100644 --- a/src/Avalonia.Interactivity/Avalonia.Interactivity.csproj +++ b/src/Avalonia.Interactivity/Avalonia.Interactivity.csproj @@ -11,4 +11,5 @@ + \ No newline at end of file diff --git a/src/Avalonia.Layout/Avalonia.Layout.csproj b/src/Avalonia.Layout/Avalonia.Layout.csproj index 410a0791b1..076ec56cd6 100644 --- a/src/Avalonia.Layout/Avalonia.Layout.csproj +++ b/src/Avalonia.Layout/Avalonia.Layout.csproj @@ -8,4 +8,5 @@ + diff --git a/src/Avalonia.Native/AvaloniaNativeMenuExporter.cs b/src/Avalonia.Native/AvaloniaNativeMenuExporter.cs index 0f2551ffeb..6d1b95b997 100644 --- a/src/Avalonia.Native/AvaloniaNativeMenuExporter.cs +++ b/src/Avalonia.Native/AvaloniaNativeMenuExporter.cs @@ -11,7 +11,7 @@ namespace Avalonia.Native class AvaloniaNativeMenuExporter : ITopLevelNativeMenuExporter { private IAvaloniaNativeFactory _factory; - private bool _resetQueued; + private bool _resetQueued = true; private bool _exported = false; private IAvnWindow _nativeWindow; private NativeMenu _menu; @@ -39,8 +39,7 @@ namespace Avalonia.Native public void SetNativeMenu(NativeMenu menu) { _menu = menu == null ? new NativeMenu() : menu; - - DoLayoutReset(); + DoLayoutReset(true); } internal void UpdateIfNeeded() @@ -74,31 +73,34 @@ namespace Avalonia.Native return result; } - void DoLayoutReset() + private void DoLayoutReset(bool forceUpdate = false) { - _resetQueued = false; - - if (_nativeWindow is null) + if (_resetQueued || forceUpdate) { - var appMenu = NativeMenu.GetMenu(Application.Current); + _resetQueued = false; - if (appMenu == null) + if (_nativeWindow is null) { - appMenu = CreateDefaultAppMenu(); - NativeMenu.SetMenu(Application.Current, appMenu); - } + var appMenu = NativeMenu.GetMenu(Application.Current); - SetMenu(appMenu); - } - else - { - if (_menu != null) + if (appMenu == null) + { + appMenu = CreateDefaultAppMenu(); + NativeMenu.SetMenu(Application.Current, appMenu); + } + + SetMenu(appMenu); + } + else { - SetMenu(_nativeWindow, _menu); + if (_menu != null) + { + SetMenu(_nativeWindow, _menu); + } } - } - _exported = true; + _exported = true; + } } internal void QueueReset() @@ -106,7 +108,7 @@ namespace Avalonia.Native if (_resetQueued) return; _resetQueued = true; - Dispatcher.UIThread.Post(DoLayoutReset, DispatcherPriority.Background); + Dispatcher.UIThread.Post(() => DoLayoutReset(), DispatcherPriority.Background); } private void SetMenu(NativeMenu menu) diff --git a/src/Avalonia.Native/OsxManagedPopupPositionerPopupImplHelper.cs b/src/Avalonia.Native/OsxManagedPopupPositionerPopupImplHelper.cs deleted file mode 100644 index 8aa9b1a122..0000000000 --- a/src/Avalonia.Native/OsxManagedPopupPositionerPopupImplHelper.cs +++ /dev/null @@ -1,15 +0,0 @@ -using Avalonia.Controls.Primitives.PopupPositioning; -using Avalonia.Platform; - -namespace Avalonia.Native -{ - class OsxManagedPopupPositionerPopupImplHelper : ManagedPopupPositionerPopupImplHelper - { - public OsxManagedPopupPositionerPopupImplHelper(IWindowBaseImpl parent, MoveResizeDelegate moveResize) : base(parent, moveResize) - { - - } - - public override double Scaling => 1; - } -} diff --git a/src/Avalonia.Native/OsxUnicodeKeys.cs b/src/Avalonia.Native/OsxUnicodeKeys.cs index b4056c8cd8..fd9acfa714 100644 --- a/src/Avalonia.Native/OsxUnicodeKeys.cs +++ b/src/Avalonia.Native/OsxUnicodeKeys.cs @@ -140,6 +140,11 @@ namespace Avalonia.Native.Interop } else { + if (key >= Key.D0 && key <= Key.D9) + { + return key.ToString().Replace("D", ""); + } + return key.ToString().ToLower(); } } diff --git a/src/Avalonia.Native/PopupImpl.cs b/src/Avalonia.Native/PopupImpl.cs index b0da5fdc43..2d246e08d2 100644 --- a/src/Avalonia.Native/PopupImpl.cs +++ b/src/Avalonia.Native/PopupImpl.cs @@ -26,7 +26,7 @@ namespace Avalonia.Native var context = _opts.UseGpu ? glFeature?.DeferredContext : null; Init(factory.CreatePopup(e, context?.Context), factory.CreateScreens(), context); } - PopupPositioner = new ManagedPopupPositioner(new OsxManagedPopupPositionerPopupImplHelper(parent, MoveResize)); + PopupPositioner = new ManagedPopupPositioner(new ManagedPopupPositionerPopupImplHelper(parent, MoveResize)); } private void MoveResize(PixelPoint position, Size size, double scaling) diff --git a/src/Avalonia.Native/WindowImplBase.cs b/src/Avalonia.Native/WindowImplBase.cs index f916e95d7c..4b13666edd 100644 --- a/src/Avalonia.Native/WindowImplBase.cs +++ b/src/Avalonia.Native/WindowImplBase.cs @@ -81,7 +81,7 @@ namespace Avalonia.Native _glSurface = new GlPlatformSurface(window, _glContext); Screen = new ScreenImpl(screens); _savedLogicalSize = ClientSize; - _savedScaling = Scaling; + _savedScaling = RenderScaling; _nativeControlHost = new NativeControlHostImpl(_native.CreateNativeControlHost()); var monitor = Screen.AllScreens.OrderBy(x => x.PixelDensity) @@ -300,7 +300,15 @@ namespace Avalonia.Native public IRenderer CreateRenderer(IRenderRoot root) { if (_deferredRendering) - return new DeferredRenderer(root, AvaloniaLocator.Current.GetService()); + { + var loop = AvaloniaLocator.Current.GetService(); + var customRendererFactory = AvaloniaLocator.Current.GetService(); + + if (customRendererFactory != null) + return customRendererFactory.Create(root, loop); + return new DeferredRenderer(root, loop); + } + return new ImmediateRenderer(root); } @@ -369,7 +377,9 @@ namespace Avalonia.Native _native.SetTopMost(value); } - public double Scaling => _native?.GetScaling() ?? 1; + public double RenderScaling => _native?.GetScaling() ?? 1; + + public double DesktopScaling => 1; public Action Deactivated { get; set; } public Action Activated { get; set; } @@ -432,7 +442,7 @@ namespace Avalonia.Native TransparencyLevel = transparencyLevel; - _native.SetBlurEnabled(TransparencyLevel >= WindowTransparencyLevel.Blur); + _native?.SetBlurEnabled(TransparencyLevel >= WindowTransparencyLevel.Blur); TransparencyLevelChanged?.Invoke(TransparencyLevel); } } diff --git a/src/Avalonia.OpenGL/Avalonia.OpenGL.csproj b/src/Avalonia.OpenGL/Avalonia.OpenGL.csproj index 72132eed93..d761e60c07 100644 --- a/src/Avalonia.OpenGL/Avalonia.OpenGL.csproj +++ b/src/Avalonia.OpenGL/Avalonia.OpenGL.csproj @@ -9,6 +9,5 @@ - - + diff --git a/src/Avalonia.ReactiveUI/Avalonia.ReactiveUI.csproj b/src/Avalonia.ReactiveUI/Avalonia.ReactiveUI.csproj index 64145b9c3c..d01a495108 100644 --- a/src/Avalonia.ReactiveUI/Avalonia.ReactiveUI.csproj +++ b/src/Avalonia.ReactiveUI/Avalonia.ReactiveUI.csproj @@ -8,4 +8,5 @@ + diff --git a/src/Avalonia.Remote.Protocol/Avalonia.Remote.Protocol.csproj b/src/Avalonia.Remote.Protocol/Avalonia.Remote.Protocol.csproj index 6684772bfe..6c241bbc75 100644 --- a/src/Avalonia.Remote.Protocol/Avalonia.Remote.Protocol.csproj +++ b/src/Avalonia.Remote.Protocol/Avalonia.Remote.Protocol.csproj @@ -4,8 +4,10 @@ AVALONIA_REMOTE_PROTOCOL;$(DefineConstants) true Key.snk + Avalonia.Remote.Protocol + \ No newline at end of file diff --git a/src/Avalonia.Styling/Avalonia.Styling.csproj b/src/Avalonia.Styling/Avalonia.Styling.csproj index b4f6c2c942..d9f0cef3da 100644 --- a/src/Avalonia.Styling/Avalonia.Styling.csproj +++ b/src/Avalonia.Styling/Avalonia.Styling.csproj @@ -8,4 +8,5 @@ + diff --git a/src/Avalonia.Themes.Default/Avalonia.Themes.Default.csproj b/src/Avalonia.Themes.Default/Avalonia.Themes.Default.csproj index c44cc358e8..153e21761e 100644 --- a/src/Avalonia.Themes.Default/Avalonia.Themes.Default.csproj +++ b/src/Avalonia.Themes.Default/Avalonia.Themes.Default.csproj @@ -19,4 +19,5 @@ + diff --git a/src/Avalonia.Themes.Default/ComboBox.xaml b/src/Avalonia.Themes.Default/ComboBox.xaml index 411253941a..cced76e850 100644 --- a/src/Avalonia.Themes.Default/ComboBox.xaml +++ b/src/Avalonia.Themes.Default/ComboBox.xaml @@ -1,4 +1,5 @@ - + @@ -24,6 +25,7 @@ + + + diff --git a/src/Avalonia.Themes.Default/ProgressBar.xaml b/src/Avalonia.Themes.Default/ProgressBar.xaml index 43a0752bc8..d9c407245b 100644 --- a/src/Avalonia.Themes.Default/ProgressBar.xaml +++ b/src/Avalonia.Themes.Default/ProgressBar.xaml @@ -13,19 +13,14 @@ - - + + + + + - - + + @@ -54,36 +49,31 @@ - - diff --git a/src/Avalonia.Themes.Default/TextBox.xaml b/src/Avalonia.Themes.Default/TextBox.xaml index 4fb3653e89..6a746dda30 100644 --- a/src/Avalonia.Themes.Default/TextBox.xaml +++ b/src/Avalonia.Themes.Default/TextBox.xaml @@ -40,6 +40,8 @@ - - - - - - - @@ -144,6 +148,8 @@ @@ -181,10 +187,15 @@ - + + + @@ -210,14 +221,15 @@ - - - diff --git a/src/Avalonia.Themes.Fluent/NumericUpDown.xaml b/src/Avalonia.Themes.Fluent/NumericUpDown.xaml index d1610b343c..cc5e2234f8 100644 --- a/src/Avalonia.Themes.Fluent/NumericUpDown.xaml +++ b/src/Avalonia.Themes.Fluent/NumericUpDown.xaml @@ -43,7 +43,7 @@ - - - - diff --git a/src/Avalonia.Themes.Fluent/TitleBar.xaml b/src/Avalonia.Themes.Fluent/TitleBar.xaml index 45798d3fa1..4dba5b4ba4 100644 --- a/src/Avalonia.Themes.Fluent/TitleBar.xaml +++ b/src/Avalonia.Themes.Fluent/TitleBar.xaml @@ -15,7 +15,7 @@ - + diff --git a/src/Avalonia.Themes.Fluent/ToggleButton.xaml b/src/Avalonia.Themes.Fluent/ToggleButton.xaml index f2f07d3e2a..49e2280a6d 100644 --- a/src/Avalonia.Themes.Fluent/ToggleButton.xaml +++ b/src/Avalonia.Themes.Fluent/ToggleButton.xaml @@ -39,67 +39,67 @@ - - - - - - - - - - - "; - var loader = new AvaloniaXamlLoader(); - var style = (Style)loader.Load(xaml); + var style = (Style)AvaloniaRuntimeXamlLoader.Load(xaml); var setter = (Setter)(style.Setters.First()); Assert.IsType(setter.Value); diff --git a/tests/Avalonia.Markup.Xaml.UnitTests/Xaml/BasicTests.cs b/tests/Avalonia.Markup.Xaml.UnitTests/Xaml/BasicTests.cs index b28fb28020..805d9e1b49 100644 --- a/tests/Avalonia.Markup.Xaml.UnitTests/Xaml/BasicTests.cs +++ b/tests/Avalonia.Markup.Xaml.UnitTests/Xaml/BasicTests.cs @@ -26,7 +26,7 @@ namespace Avalonia.Markup.Xaml.UnitTests.Xaml { var xaml = @""; - var target = AvaloniaXamlLoader.Parse(xaml); + var target = AvaloniaRuntimeXamlLoader.Parse(xaml); Assert.NotNull(target); Assert.Equal("Foo", target.Content); @@ -37,7 +37,7 @@ namespace Avalonia.Markup.Xaml.UnitTests.Xaml { var xaml = @"Foo"; - var target = AvaloniaXamlLoader.Parse(xaml); + var target = AvaloniaRuntimeXamlLoader.Parse(xaml); Assert.NotNull(target); Assert.Equal("Foo", target.Content); @@ -49,7 +49,7 @@ namespace Avalonia.Markup.Xaml.UnitTests.Xaml var xaml = @""; - var target = AvaloniaXamlLoader.Parse(xaml); + var target = AvaloniaRuntimeXamlLoader.Parse(xaml); Assert.NotNull(target); Assert.Equal(21.0, TextBlock.GetFontSize(target)); @@ -65,7 +65,7 @@ namespace Avalonia.Markup.Xaml.UnitTests.Xaml "; - var target = AvaloniaXamlLoader.Parse(xaml); + var target = AvaloniaRuntimeXamlLoader.Parse(xaml); Assert.Equal(2, Grid.GetColumn((TestControl)target.Content)); } @@ -78,7 +78,7 @@ namespace Avalonia.Markup.Xaml.UnitTests.Xaml xmlns:test='clr-namespace:Avalonia.Markup.Xaml.UnitTests.Xaml;assembly=Avalonia.Markup.Xaml.UnitTests' test:BasicTestsAttachedPropertyHolder.Foo='Bar'/>"; - var target = AvaloniaXamlLoader.Parse(xaml); + var target = AvaloniaRuntimeXamlLoader.Parse(xaml); Assert.NotNull(target); Assert.Equal("Bar", BasicTestsAttachedPropertyHolder.GetFoo(target)); @@ -92,7 +92,7 @@ namespace Avalonia.Markup.Xaml.UnitTests.Xaml var xaml = @""; - var target = AvaloniaXamlLoader.Parse(xaml); + var target = AvaloniaRuntimeXamlLoader.Parse(xaml); target.DataContext = 21.0; @@ -108,7 +108,7 @@ namespace Avalonia.Markup.Xaml.UnitTests.Xaml Foo "; - var target = AvaloniaXamlLoader.Parse(xaml); + var target = AvaloniaRuntimeXamlLoader.Parse(xaml); Assert.Empty(target.Children); @@ -121,7 +121,7 @@ namespace Avalonia.Markup.Xaml.UnitTests.Xaml var xaml = @""; - XamlTestHelpers.AssertThrowsXamlException(() => AvaloniaXamlLoader.Parse(xaml)); + XamlTestHelpers.AssertThrowsXamlException(() => AvaloniaRuntimeXamlLoader.Parse(xaml)); } [Fact] @@ -136,7 +136,7 @@ namespace Avalonia.Markup.Xaml.UnitTests.Xaml "; - var contentControl = AvaloniaXamlLoader.Parse(xaml); + var contentControl = AvaloniaRuntimeXamlLoader.Parse(xaml); var target = contentControl.ContentTemplate; Assert.NotNull(target); @@ -154,7 +154,7 @@ namespace Avalonia.Markup.Xaml.UnitTests.Xaml "; - var control = AvaloniaXamlLoader.Parse(xaml); + var control = AvaloniaRuntimeXamlLoader.Parse(xaml); var button = control.FindControl "; - var control = AvaloniaXamlLoader.Parse(xaml); + var control = AvaloniaRuntimeXamlLoader.Parse(xaml); var button = control.FindControl