diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md new file mode 100644 index 0000000000..9a0da4aa9b --- /dev/null +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -0,0 +1,17 @@ +This template is not intended to be prescriptive, but to help us review pull requests it would be useful if you included as much of the following information as possible: + +- What does the pull request do? +- What is the current behavior? +- What is the updated/expected behavior with this PR? +- How was the solution implemented (if it's not obvious)? + +Checklist: + +- [ ] Added unit tests (if possible)? +- [ ] Added XML documentation to any related classes? +- [ ] Consider submitting a PR to https://github.com/AvaloniaUI/Avaloniaui.net with user documentation + +If the pull request fixes issue(s) list them like this: + +Fixes #123 +Fixes #456 \ No newline at end of file diff --git a/Avalonia.sln b/Avalonia.sln index 88914fe188..47ee4c1ad9 100644 --- a/Avalonia.sln +++ b/Avalonia.sln @@ -76,7 +76,7 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Avalonia.Markup", "src\Mark EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Avalonia.Markup.UnitTests", "tests\Avalonia.Markup.UnitTests\Avalonia.Markup.UnitTests.csproj", "{8EF392D5-1416-45AA-9956-7CBBC3229E8A}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BindingTest", "samples\BindingTest\BindingTest.csproj", "{08B3E6B9-1CD5-443C-9F61-6D49D1C5F162}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "BindingTest", "samples\BindingTest\BindingTest.csproj", "{08B3E6B9-1CD5-443C-9F61-6D49D1C5F162}" EndProject Project("{D954291E-2A0B-460D-934E-DC6B0785DB48}") = "RenderHelpers", "src\Shared\RenderHelpers\RenderHelpers.shproj", "{3C4C0CB4-0C0F-4450-A37B-148C84FF905F}" EndProject @@ -114,7 +114,7 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Avalonia.DesignerSupport.Te EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Avalonia.DesignerSupport.TestApp", "tests\Avalonia.DesignerSupport.TestApp\Avalonia.DesignerSupport.TestApp.csproj", "{F1381F98-4D24-409A-A6C5-1C5B1E08BB08}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "VirtualizationTest", "samples\VirtualizationTest\VirtualizationTest.csproj", "{FBCAF3D0-2808-4934-8E96-3F607594517B}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "VirtualizationTest", "samples\VirtualizationTest\VirtualizationTest.csproj", "{FBCAF3D0-2808-4934-8E96-3F607594517B}" EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Interop", "Interop", "{A0CC0258-D18C-4AB3-854F-7101680FC3F9}" EndProject @@ -122,7 +122,7 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WindowsInteropTest", "sampl EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Avalonia.DotNetFrameworkRuntime", "src\Avalonia.DotNetFrameworkRuntime\Avalonia.DotNetFrameworkRuntime.csproj", "{4A1ABB09-9047-4BD5-A4AD-A055E52C5EE0}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "RenderTest", "samples\RenderTest\RenderTest.csproj", "{F1FDC5B0-4654-416F-AE69-E3E9BBD87801}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "RenderTest", "samples\RenderTest\RenderTest.csproj", "{F1FDC5B0-4654-416F-AE69-E3E9BBD87801}" EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ControlCatalog.Android", "samples\ControlCatalog.Android\ControlCatalog.Android.csproj", "{29132311-1848-4FD6-AE0C-4FF841151BD3}" EndProject @@ -146,11 +146,12 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Props", "Props", "{F3AC8BC1 build\Microsoft.Reactive.Testing.props = build\Microsoft.Reactive.Testing.props build\Moq.props = build\Moq.props build\NetCore.props = build\NetCore.props + build\NetFX.props = build\NetFX.props build\ReactiveUI.props = build\ReactiveUI.props build\Rx.props = build\Rx.props + build\SampleApp.props = build\SampleApp.props build\Serilog.props = build\Serilog.props build\SharpDX.props = build\SharpDX.props - build\SkiaSharp.Desktop.props = build\SkiaSharp.Desktop.props build\SkiaSharp.props = build\SkiaSharp.props build\Splat.props = build\Splat.props build\Sprache.props = build\Sprache.props @@ -2590,6 +2591,7 @@ Global {E2999E4A-9086-401F-898C-AEB0AD38E676} = {9B9E3891-2366-4253-A952-D08BCEB71098} {050CC912-FF49-4A8B-B534-9544017446DD} = {4ED8B739-6F4E-4CD4-B993-545E6B5CE637} {F40FC0A2-1BC3-401C-BFC1-928EC4D4A9CE} = {9B9E3891-2366-4253-A952-D08BCEB71098} + {CBFD5788-567D-401B-9DFA-74E4224025A0} = {A59C4C0A-64DF-4621-B450-2BA00D6F61E2} {4ADA61C8-D191-428D-9066-EF4F0D86520F} = {4ED8B739-6F4E-4CD4-B993-545E6B5CE637} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution diff --git a/build/Base.props b/build/Base.props index 6689465338..78930156e7 100644 --- a/build/Base.props +++ b/build/Base.props @@ -2,4 +2,4 @@ - + \ No newline at end of file diff --git a/build/EmbedXaml.props b/build/EmbedXaml.props new file mode 100644 index 0000000000..219ffb2e42 --- /dev/null +++ b/build/EmbedXaml.props @@ -0,0 +1,11 @@ + + + + %(Filename) + + + Designer + + + \ No newline at end of file diff --git a/build/SampleApp.props b/build/SampleApp.props new file mode 100644 index 0000000000..3b538e4029 --- /dev/null +++ b/build/SampleApp.props @@ -0,0 +1,13 @@ + + + WinExe + + + + + + + + + diff --git a/build/SharedVersion.props b/build/SharedVersion.props new file mode 100644 index 0000000000..8b7b02de5d --- /dev/null +++ b/build/SharedVersion.props @@ -0,0 +1,13 @@ + + + Avalonia + 0.6.2 + Copyright 2016 © The AvaloniaUI Project + https://github.com/AvaloniaUI/Avalonia/blob/master/licence.md + https://github.com/AvaloniaUI/Avalonia/ + https://github.com/AvaloniaUI/Avalonia/ + true + CS1591 + + \ No newline at end of file diff --git a/samples/BindingTest/BindingTest.csproj b/samples/BindingTest/BindingTest.csproj index a17fe0eed1..30d7dbb2ec 100644 --- a/samples/BindingTest/BindingTest.csproj +++ b/samples/BindingTest/BindingTest.csproj @@ -1,155 +1,33 @@ - - - + - Debug - AnyCPU - {08B3E6B9-1CD5-443C-9F61-6D49D1C5F162} - WinExe - Properties - BindingTest - BindingTest - v4.7 - 512 - true - - PackageReference + Exe + netcoreapp2.0;net461 - - AnyCPU - true - full - false - bin\Debug\ - DEBUG;TRACE - prompt - 4 - - - AnyCPU - pdbonly - true - bin\Release\ - TRACE - prompt - 4 - - - - - - - - - - - - - - - - - Designer - - - App.xaml - - - MainWindow.xaml - - - - TestItemView.xaml - - - - - - - - - - - - Designer - - - - - {4A1ABB09-9047-4BD5-A4AD-A055E52C5EE0} - Avalonia.DotNetFrameworkRuntime - - - {3e53a01a-b331-47f3-b828-4a5717e77a24} - Avalonia.Markup.Xaml - - - {6417e941-21bc-467b-a771-0de389353ce6} - Avalonia.Markup - - - {d211e587-d8bc-45b9-95a4-f297c8fa5200} - Avalonia.Animation - - - {b09b78d8-9b26-48b0-9149-d64a2f120f3f} - Avalonia.Base - - - {d2221c82-4a25-4583-9b43-d791e3f6820c} - Avalonia.Controls - - - {799a7bb5-3c2c-48b6-85a7-406a12c420da} - Avalonia.DesignerSupport - - - {7062ae20-5dcc-4442-9645-8195bdece63e} - Avalonia.Diagnostics - - - {62024b2d-53eb-4638-b26b-85eeaa54866e} - Avalonia.Input - - - {6b0ed19d-a08b-461c-a9d9-a9ee40b0c06b} - Avalonia.Interactivity - - - {42472427-4774-4c81-8aff-9f27b8e31721} - Avalonia.Layout - - - {b61b66a3-b82d-4875-8001-89d3394fe0c9} - Avalonia.Logging.Serilog - - - {6417b24e-49c2-4985-8db2-3ab9d898ec91} - Avalonia.ReactiveUI - - - {eb582467-6abb-43a1-b052-e981ba910e3a} - Avalonia.Visuals - - - {f1baa01a-f176-4c6a-b39d-5b40bb1b148f} - Avalonia.Styling - - - {3e10a5fa-e8da-48b1-ad44-6a5b6cb7750f} - Avalonia.Themes.Default - - - {3e908f67-5543-4879-a1dc-08eace79b3cd} - Avalonia.Direct2D1 - - - {811a76cf-1cf6-440f-963b-bbe31bd72a82} - Avalonia.Win32 - + + + + + + + + + + + + + + + + + + + + + - + + diff --git a/samples/BindingTest/MainWindow.xaml b/samples/BindingTest/MainWindow.xaml index 3547e33181..6b80225686 100644 --- a/samples/BindingTest/MainWindow.xaml +++ b/samples/BindingTest/MainWindow.xaml @@ -1,11 +1,18 @@ + xmlns:x='http://schemas.microsoft.com/winfx/2006/xaml' + xmlns:vm="clr-namespace:BindingTest.ViewModels" + xmlns:local="clr-namespace:BindingTest" + Title="AvaloniaUI Bindings Test" + Width="800" + Height="600"> + + + @@ -40,6 +47,10 @@ + + diff --git a/samples/BindingTest/Properties/AssemblyInfo.cs b/samples/BindingTest/Properties/AssemblyInfo.cs deleted file mode 100644 index 79166d5f29..0000000000 --- a/samples/BindingTest/Properties/AssemblyInfo.cs +++ /dev/null @@ -1,36 +0,0 @@ -using System.Reflection; -using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; - -// General Information about an assembly is controlled through the following -// set of attributes. Change these attribute values to modify the information -// associated with an assembly. -[assembly: AssemblyTitle("BindingTest")] -[assembly: AssemblyDescription("")] -[assembly: AssemblyConfiguration("")] -[assembly: AssemblyCompany("")] -[assembly: AssemblyProduct("BindingTest")] -[assembly: AssemblyCopyright("Copyright © 2015")] -[assembly: AssemblyTrademark("")] -[assembly: AssemblyCulture("")] - -// Setting ComVisible to false makes the types in this assembly not visible -// to COM components. If you need to access a type in this assembly from -// COM, set the ComVisible attribute to true on that type. -[assembly: ComVisible(false)] - -// The following GUID is for the ID of the typelib if this project is exposed to COM -[assembly: Guid("08b3e6b9-1cd5-443c-9f61-6d49d1c5f162")] - -// Version information for an assembly consists of the following four values: -// -// Major Version -// Minor Version -// Build Number -// Revision -// -// You can specify all the values or you can default the Build and Revision Numbers -// by using the '*' as shown below: -// [assembly: AssemblyVersion("1.0.*")] -[assembly: AssemblyVersion("1.0.0.0")] -[assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/samples/ControlCatalog.Desktop/ControlCatalog.Desktop.csproj b/samples/ControlCatalog.Desktop/ControlCatalog.Desktop.csproj index 8a5959e361..24b168c854 100644 --- a/samples/ControlCatalog.Desktop/ControlCatalog.Desktop.csproj +++ b/samples/ControlCatalog.Desktop/ControlCatalog.Desktop.csproj @@ -1,138 +1,21 @@ - - - + + - Debug - AnyCPU - {2B888490-D14A-4BCA-AB4B-48676FA93C9B} - WinExe - Properties - ControlCatalog.Desktop - ControlCatalog.Desktop - v4.7 - 512 - true - - PackageReference + Exe + net461 - - AnyCPU - true - full - false - bin\Debug\ - DEBUG;TRACE - prompt - 4 - - - x86 - pdbonly - true - bin\Release\ - TRACE - prompt - 4 - - - - - - - - - - - - - - - - - - + - + + + + + - - {799a7bb5-3c2c-48b6-85a7-406a12c420da} - Avalonia.DesignerSupport - - - {4A1ABB09-9047-4BD5-A4AD-A055E52C5EE0} - Avalonia.DotNetFrameworkRuntime - - - {bb1f7bb5-6ad4-4776-94d9-c09d0a972658} - Avalonia.Gtk3 - - - {3E53A01A-B331-47F3-B828-4A5717E77A24} - Avalonia.Markup.Xaml - - - {6417E941-21BC-467B-A771-0DE389353CE6} - Avalonia.Markup - - - {d211e587-d8bc-45b9-95a4-f297c8fa5200} - Avalonia.Animation - - - {B09B78D8-9B26-48B0-9149-D64A2F120F3F} - Avalonia.Base - - - {D2221C82-4A25-4583-9B43-D791E3F6820C} - Avalonia.Controls - - - {62024b2d-53eb-4638-b26b-85eeaa54866e} - Avalonia.Input - - - {6b0ed19d-a08b-461c-a9d9-a9ee40b0c06b} - Avalonia.Interactivity - - - {42472427-4774-4c81-8aff-9f27b8e31721} - Avalonia.Layout - - - {B61B66A3-B82D-4875-8001-89D3394FE0C9} - Avalonia.Logging.Serilog - - - {eb582467-6abb-43a1-b052-e981ba910e3a} - Avalonia.Visuals - - - {F1BAA01A-F176-4C6A-B39D-5B40BB1B148F} - Avalonia.Styling - - - {3E10A5FA-E8DA-48B1-AD44-6A5B6CB7750F} - Avalonia.Themes.Default - - - {7d2d3083-71dd-4cc9-8907-39a0d86fb322} - Avalonia.Skia - - - {3E908F67-5543-4879-A1DC-08EACE79B3CD} - Avalonia.Direct2D1 - - - {811A76CF-1CF6-440F-963B-BBE31BD72A82} - Avalonia.Win32 - - - {d0a739b9-3c68-4ba6-a328-41606954b6bd} - ControlCatalog - + - + + - \ No newline at end of file diff --git a/samples/ControlCatalog.Desktop/Properties/AssemblyInfo.cs b/samples/ControlCatalog.Desktop/Properties/AssemblyInfo.cs deleted file mode 100644 index 78460f60db..0000000000 --- a/samples/ControlCatalog.Desktop/Properties/AssemblyInfo.cs +++ /dev/null @@ -1,36 +0,0 @@ -using System.Reflection; -using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; - -// General Information about an assembly is controlled through the following -// set of attributes. Change these attribute values to modify the information -// associated with an assembly. -[assembly: AssemblyTitle("ControlCatalog.Desktop")] -[assembly: AssemblyDescription("")] -[assembly: AssemblyConfiguration("")] -[assembly: AssemblyCompany("")] -[assembly: AssemblyProduct("ControlCatalog.Desktop")] -[assembly: AssemblyCopyright("Copyright © 2016")] -[assembly: AssemblyTrademark("")] -[assembly: AssemblyCulture("")] - -// Setting ComVisible to false makes the types in this assembly not visible -// to COM components. If you need to access a type in this assembly from -// COM, set the ComVisible attribute to true on that type. -[assembly: ComVisible(false)] - -// The following GUID is for the ID of the typelib if this project is exposed to COM -[assembly: Guid("2b888490-d14a-4bca-ab4b-48676fa93c9b")] - -// Version information for an assembly consists of the following four values: -// -// Major Version -// Minor Version -// Build Number -// Revision -// -// You can specify all the values or you can default the Build and Revision Numbers -// by using the '*' as shown below: -// [assembly: AssemblyVersion("1.0.*")] -[assembly: AssemblyVersion("1.0.0.0")] -[assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/samples/ControlCatalog/DecoratedWindow.xaml b/samples/ControlCatalog/DecoratedWindow.xaml index d3dbad679a..b2f6497caa 100644 --- a/samples/ControlCatalog/DecoratedWindow.xaml +++ b/samples/ControlCatalog/DecoratedWindow.xaml @@ -1,7 +1,7 @@  + Icon="resm:ControlCatalog.Assets.test_icon.ico" + xmlns:local="clr-namespace:ControlCatalog" HasSystemDecorations="False"> diff --git a/samples/ControlCatalog/MainView.xaml b/samples/ControlCatalog/MainView.xaml index 1107d34b3e..377871f658 100644 --- a/samples/ControlCatalog/MainView.xaml +++ b/samples/ControlCatalog/MainView.xaml @@ -1,5 +1,5 @@ diff --git a/samples/ControlCatalog/MainWindow.xaml b/samples/ControlCatalog/MainWindow.xaml index f39beced1a..7029273a84 100644 --- a/samples/ControlCatalog/MainWindow.xaml +++ b/samples/ControlCatalog/MainWindow.xaml @@ -1,6 +1,6 @@  + xmlns:local="clr-namespace:ControlCatalog"> \ No newline at end of file diff --git a/samples/Directory.Build.props b/samples/Directory.Build.props new file mode 100644 index 0000000000..7325bab2a3 --- /dev/null +++ b/samples/Directory.Build.props @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/samples/RenderTest/MainWindow.xaml b/samples/RenderTest/MainWindow.xaml index 9e9a600161..ce8fd3a43e 100644 --- a/samples/RenderTest/MainWindow.xaml +++ b/samples/RenderTest/MainWindow.xaml @@ -1,6 +1,8 @@ + Title="AvaloniaUI Rendering Test" + xmlns:pages="clr-namespace:RenderTest.Pages" + Width="800" + Height="600"> diff --git a/samples/RenderTest/Properties/AssemblyInfo.cs b/samples/RenderTest/Properties/AssemblyInfo.cs deleted file mode 100644 index f66c158075..0000000000 --- a/samples/RenderTest/Properties/AssemblyInfo.cs +++ /dev/null @@ -1,36 +0,0 @@ -using System.Reflection; -using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; - -// General Information about an assembly is controlled through the following -// set of attributes. Change these attribute values to modify the information -// associated with an assembly. -[assembly: AssemblyTitle("RenderTest")] -[assembly: AssemblyDescription("")] -[assembly: AssemblyConfiguration("")] -[assembly: AssemblyCompany("")] -[assembly: AssemblyProduct("RenderTest")] -[assembly: AssemblyCopyright("Copyright © 2016")] -[assembly: AssemblyTrademark("")] -[assembly: AssemblyCulture("")] - -// Setting ComVisible to false makes the types in this assembly not visible -// to COM components. If you need to access a type in this assembly from -// COM, set the ComVisible attribute to true on that type. -[assembly: ComVisible(false)] - -// The following GUID is for the ID of the typelib if this project is exposed to COM -[assembly: Guid("f1fdc5b0-4654-416f-ae69-e3e9bbd87801")] - -// Version information for an assembly consists of the following four values: -// -// Major Version -// Minor Version -// Build Number -// Revision -// -// You can specify all the values or you can default the Build and Revision Numbers -// by using the '*' as shown below: -// [assembly: AssemblyVersion("1.0.*")] -[assembly: AssemblyVersion("1.0.0.0")] -[assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/samples/RenderTest/RenderTest.csproj b/samples/RenderTest/RenderTest.csproj index b33d5d3c70..30d7dbb2ec 100644 --- a/samples/RenderTest/RenderTest.csproj +++ b/samples/RenderTest/RenderTest.csproj @@ -1,184 +1,33 @@ - - - + - Debug - AnyCPU - {F1FDC5B0-4654-416F-AE69-E3E9BBD87801} - WinExe - Properties - RenderTest - RenderTest - v4.7 - 512 - true - + Exe + netcoreapp2.0;net461 - - AnyCPU - true - full - false - bin\Debug\ - DEBUG;TRACE - prompt - 4 - - - AnyCPU - pdbonly - true - bin\Release\ - TRACE - prompt - 4 - - - - - - - - - - - - - - - - - - App.xaml - - - DrawingPage.xaml - - - ClippingPage.xaml - - - AnimationsPage.xaml - - - - - MainWindow.xaml - - - - - - - - - Designer - - - - {d211e587-d8bc-45b9-95a4-f297c8fa5200} - Avalonia.Animation - - - {b09b78d8-9b26-48b0-9149-d64a2f120f3f} - Avalonia.Base - - - {d2221c82-4a25-4583-9b43-d791e3f6820c} - Avalonia.Controls - - - {799a7bb5-3c2c-48b6-85a7-406a12c420da} - Avalonia.DesignerSupport - - - {7062ae20-5dcc-4442-9645-8195bdece63e} - Avalonia.Diagnostics - - - {4a1abb09-9047-4bd5-a4ad-a055e52c5ee0} - Avalonia.DotNetFrameworkRuntime - - - {62024b2d-53eb-4638-b26b-85eeaa54866e} - Avalonia.Input - - - {6b0ed19d-a08b-461c-a9d9-a9ee40b0c06b} - Avalonia.Interactivity - - - {42472427-4774-4c81-8aff-9f27b8e31721} - Avalonia.Layout - - - {b61b66a3-b82d-4875-8001-89d3394fe0c9} - Avalonia.Logging.Serilog - - - {6417b24e-49c2-4985-8db2-3ab9d898ec91} - Avalonia.ReactiveUI - - - {eb582467-6abb-43a1-b052-e981ba910e3a} - Avalonia.Visuals - - - {f1baa01a-f176-4c6a-b39d-5b40bb1b148f} - Avalonia.Styling - - - {3e10a5fa-e8da-48b1-ad44-6a5b6cb7750f} - Avalonia.Themes.Default - - - {3e53a01a-b331-47f3-b828-4a5717e77a24} - Avalonia.Markup.Xaml - - - {6417e941-21bc-467b-a771-0de389353ce6} - Avalonia.Markup - - - {7d2d3083-71dd-4cc9-8907-39a0d86fb322} - Avalonia.Skia - - - {3e908f67-5543-4879-a1dc-08eace79b3cd} - Avalonia.Direct2D1 - - - {811a76cf-1cf6-440f-963b-bbe31bd72a82} - Avalonia.Win32 - - - - - Designer - - - - - Designer - - - - - Designer - - - - - Designer - - - - - Designer - - - + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/samples/VirtualizationTest/MainWindow.xaml b/samples/VirtualizationTest/MainWindow.xaml index 52c2b33680..eb94253d27 100644 --- a/samples/VirtualizationTest/MainWindow.xaml +++ b/samples/VirtualizationTest/MainWindow.xaml @@ -1,5 +1,7 @@ + Title="AvaloniaUI Virtualization Test" + Width="800" + Height="600"> - - + - Debug - AnyCPU - {FBCAF3D0-2808-4934-8E96-3F607594517B} - WinExe - Properties - VirtualizationTest - VirtualizationTest - v4.7 - 512 - true - + Exe + netcoreapp2.0;net461 - - AnyCPU - true - full - false - bin\Debug\ - DEBUG;TRACE - prompt - 4 - - - AnyCPU - pdbonly - true - bin\Release\ - TRACE - prompt - 4 - - - - - - - - - - - - - - - - - App.xaml - - - MainWindow.xaml - - - - - - - - - - - - {d211e587-d8bc-45b9-95a4-f297c8fa5200} - Avalonia.Animation - - - {b09b78d8-9b26-48b0-9149-d64a2f120f3f} - Avalonia.Base - - - {d2221c82-4a25-4583-9b43-d791e3f6820c} - Avalonia.Controls - - - {799a7bb5-3c2c-48b6-85a7-406a12c420da} - Avalonia.DesignerSupport - - - {7062ae20-5dcc-4442-9645-8195bdece63e} - Avalonia.Diagnostics - - - {4A1ABB09-9047-4BD5-A4AD-A055E52C5EE0} - Avalonia.DotNetFrameworkRuntime - - - {62024b2d-53eb-4638-b26b-85eeaa54866e} - Avalonia.Input - - - {6b0ed19d-a08b-461c-a9d9-a9ee40b0c06b} - Avalonia.Interactivity - - - {42472427-4774-4c81-8aff-9f27b8e31721} - Avalonia.Layout - - - {B61B66A3-B82D-4875-8001-89D3394FE0C9} - Avalonia.Logging.Serilog - - - {6417b24e-49c2-4985-8db2-3ab9d898ec91} - Avalonia.ReactiveUI - - - {eb582467-6abb-43a1-b052-e981ba910e3a} - Avalonia.Visuals - - - {f1baa01a-f176-4c6a-b39d-5b40bb1b148f} - Avalonia.Styling - - - {3e10a5fa-e8da-48b1-ad44-6a5b6cb7750f} - Avalonia.Themes.Default - - - {3e53a01a-b331-47f3-b828-4a5717e77a24} - Avalonia.Markup.Xaml - - - {6417e941-21bc-467b-a771-0de389353ce6} - Avalonia.Markup - - - {3e908f67-5543-4879-a1dc-08eace79b3cd} - Avalonia.Direct2D1 - - - {811a76cf-1cf6-440f-963b-bbe31bd72a82} - Avalonia.Win32 - - - - - Designer - - - - Designer - + + + + + + + + + + + + + + + + + + + + + - + + diff --git a/src/Android/Avalonia.Android/Platform/SkiaPlatform/PopupImpl.cs b/src/Android/Avalonia.Android/Platform/SkiaPlatform/PopupImpl.cs index 0b683239fb..78f744cea0 100644 --- a/src/Android/Avalonia.Android/Platform/SkiaPlatform/PopupImpl.cs +++ b/src/Android/Avalonia.Android/Platform/SkiaPlatform/PopupImpl.cs @@ -36,7 +36,11 @@ namespace Avalonia.Android.Platform.SkiaPlatform _clientSize = value; UpdateParams(); } - + + public void SetMinMaxSize(Size minSize, Size maxSize) + { + } + public IScreenImpl Screen { get; } public Point Position diff --git a/src/Avalonia.Animation/Avalonia.Animation.csproj b/src/Avalonia.Animation/Avalonia.Animation.csproj index 2101c5669d..46e41dd32a 100644 --- a/src/Avalonia.Animation/Avalonia.Animation.csproj +++ b/src/Avalonia.Animation/Avalonia.Animation.csproj @@ -1,35 +1,7 @@  netstandard2.0 - false - - true - full - false - bin\Debug\ - DEBUG;TRACE - prompt - 4 - bin\Debug\Avalonia.Animation.xml - false - - - pdbonly - true - bin\Release\ - TRACE - prompt - 4 - bin\Release\Avalonia.Animation.xml - CS1591 - true - - - - Properties\SharedAssemblyInfo.cs - - diff --git a/src/Avalonia.Animation/Properties/AssemblyInfo.cs b/src/Avalonia.Animation/Properties/AssemblyInfo.cs deleted file mode 100644 index a41edcc7b1..0000000000 --- a/src/Avalonia.Animation/Properties/AssemblyInfo.cs +++ /dev/null @@ -1,6 +0,0 @@ -// Copyright (c) The Avalonia Project. All rights reserved. -// Licensed under the MIT license. See licence.md file in the project root for full license information. - -using System.Reflection; - -[assembly: AssemblyTitle("Avalonia.Animation")] diff --git a/src/Avalonia.Base/AttachedProperty.cs b/src/Avalonia.Base/AttachedProperty.cs index 9d4d40bfef..fdb04b6dfc 100644 --- a/src/Avalonia.Base/AttachedProperty.cs +++ b/src/Avalonia.Base/AttachedProperty.cs @@ -9,7 +9,7 @@ namespace Avalonia /// An attached avalonia property. /// /// The type of the property's value. - public class AttachedProperty : StyledPropertyBase + public class AttachedProperty : StyledProperty { /// /// Initializes a new instance of the class. @@ -35,11 +35,10 @@ namespace Avalonia /// /// The owner type. /// The property. - public StyledProperty AddOwner() where TOwner : IAvaloniaObject + public new AttachedProperty AddOwner() where TOwner : IAvaloniaObject { - var result = new StyledProperty(this, typeof(TOwner)); - AvaloniaPropertyRegistry.Instance.Register(typeof(TOwner), result); - return result; + AvaloniaPropertyRegistry.Instance.Register(typeof(TOwner), this); + return this; } } } diff --git a/src/Avalonia.Base/Avalonia.Base.csproj b/src/Avalonia.Base/Avalonia.Base.csproj index 54537841a9..35adcbeb92 100644 --- a/src/Avalonia.Base/Avalonia.Base.csproj +++ b/src/Avalonia.Base/Avalonia.Base.csproj @@ -1,36 +1,9 @@  netstandard2.0 - false + Avalonia.Base Avalonia - - true - full - false - bin\Debug\ - DEBUG;TRACE - prompt - 4 - bin\Debug\Avalonia.Base.xml - CS1591 - - - pdbonly - true - bin\Release\ - TRACE - prompt - 4 - bin\Release\Avalonia.Base.xml - CS1591 - true - - - - Properties\SharedAssemblyInfo.cs - - diff --git a/src/Avalonia.Base/AvaloniaObject.cs b/src/Avalonia.Base/AvaloniaObject.cs index a46d567d28..68b9871fd1 100644 --- a/src/Avalonia.Base/AvaloniaObject.cs +++ b/src/Avalonia.Base/AvaloniaObject.cs @@ -12,7 +12,6 @@ using Avalonia.Diagnostics; using Avalonia.Logging; using Avalonia.Threading; using Avalonia.Utilities; -using System.Reactive.Concurrency; namespace Avalonia { @@ -72,7 +71,8 @@ namespace Avalonia public AvaloniaObject() { VerifyAccess(); - foreach (var property in AvaloniaPropertyRegistry.Instance.GetRegistered(this)) + + void Notify(AvaloniaProperty property) { object value = property.IsDirect ? ((IDirectPropertyAccessor)property).GetValue(this) : @@ -87,6 +87,16 @@ namespace Avalonia property.NotifyInitialized(e); } + + foreach (var property in AvaloniaPropertyRegistry.Instance.GetRegistered(this)) + { + Notify(property); + } + + foreach (var property in AvaloniaPropertyRegistry.Instance.GetRegisteredAttached(this.GetType())) + { + Notify(property); + } } /// @@ -218,11 +228,6 @@ namespace Avalonia } else { - if (!AvaloniaPropertyRegistry.Instance.IsRegistered(this, property)) - { - ThrowNotRegistered(property); - } - return GetValueInternal(property); } } @@ -377,11 +382,6 @@ namespace Avalonia { PriorityValue v; - if (!AvaloniaPropertyRegistry.Instance.IsRegistered(this, property)) - { - ThrowNotRegistered(property); - } - if (!_values.TryGetValue(property, out v)) { v = CreatePriorityValue(property); @@ -804,11 +804,6 @@ namespace Avalonia var originalValue = value; - if (!AvaloniaPropertyRegistry.Instance.IsRegistered(this, property)) - { - ThrowNotRegistered(property); - } - if (!TypeUtilities.TryConvertImplicit(property.PropertyType, value, out value)) { throw new ArgumentException(string.Format( @@ -836,18 +831,32 @@ namespace Avalonia } /// - /// Given a returns a registered avalonia property that is - /// equal or throws if not found. + /// Given a direct property, returns a registered avalonia property that is equivalent or + /// throws if not found. /// /// The property. /// The registered property. - public AvaloniaProperty GetRegistered(AvaloniaProperty property) + private AvaloniaProperty GetRegistered(AvaloniaProperty property) { - var result = AvaloniaPropertyRegistry.Instance.FindRegistered(this, property); + var direct = property as IDirectPropertyAccessor; + + if (direct == null) + { + throw new AvaloniaInternalException( + "AvaloniaObject.GetRegistered should only be called for direct properties"); + } + + if (property.OwnerType.IsAssignableFrom(GetType())) + { + return property; + } + + var result = AvaloniaPropertyRegistry.Instance.GetRegistered(this) + .FirstOrDefault(x => x == property); if (result == null) { - ThrowNotRegistered(property); + throw new ArgumentException($"Property '{property.Name} not registered on '{this.GetType()}"); } return result; @@ -898,15 +907,5 @@ namespace Avalonia value, priority); } - - /// - /// Throws an exception indicating that the specified property is not registered on this - /// object. - /// - /// The property - private void ThrowNotRegistered(AvaloniaProperty p) - { - throw new ArgumentException($"Property '{p.Name} not registered on '{this.GetType()}"); - } } } diff --git a/src/Avalonia.Base/AvaloniaProperty.cs b/src/Avalonia.Base/AvaloniaProperty.cs index fb78e3b2a0..f7dabd3a43 100644 --- a/src/Avalonia.Base/AvaloniaProperty.cs +++ b/src/Avalonia.Base/AvaloniaProperty.cs @@ -311,7 +311,9 @@ namespace Avalonia defaultBindingMode: defaultBindingMode); var result = new AttachedProperty(name, typeof(TOwner), metadata, inherits); - AvaloniaPropertyRegistry.Instance.Register(typeof(THost), result); + var registry = AvaloniaPropertyRegistry.Instance; + registry.Register(typeof(TOwner), result); + registry.RegisterAttached(typeof(THost), result); return result; } @@ -344,7 +346,9 @@ namespace Avalonia defaultBindingMode: defaultBindingMode); var result = new AttachedProperty(name, ownerType, metadata, inherits); - AvaloniaPropertyRegistry.Instance.Register(typeof(THost), result); + var registry = AvaloniaPropertyRegistry.Instance; + registry.Register(ownerType, result); + registry.RegisterAttached(typeof(THost), result); return result; } diff --git a/src/Avalonia.Base/AvaloniaPropertyRegistry.cs b/src/Avalonia.Base/AvaloniaPropertyRegistry.cs index ec1643427b..c0a4ace6ed 100644 --- a/src/Avalonia.Base/AvaloniaPropertyRegistry.cs +++ b/src/Avalonia.Base/AvaloniaPropertyRegistry.cs @@ -4,7 +4,6 @@ using System; using System.Collections.Generic; using System.Linq; -using System.Reflection; using System.Runtime.CompilerServices; namespace Avalonia @@ -14,23 +13,14 @@ namespace Avalonia /// public class AvaloniaPropertyRegistry { - /// - /// The registered properties by type. - /// private readonly Dictionary> _registered = new Dictionary>(); - - /// - /// The registered properties by type cached values to increase performance. - /// - private readonly Dictionary> _registeredCache = - new Dictionary>(); - - /// - /// The registered attached properties by owner type. - /// private readonly Dictionary> _attached = new Dictionary>(); + private readonly Dictionary> _registeredCache = + new Dictionary>(); + private readonly Dictionary> _attachedCache = + new Dictionary>(); /// /// Gets the instance @@ -39,51 +29,68 @@ namespace Avalonia = new AvaloniaPropertyRegistry(); /// - /// Gets all attached s registered by an owner. + /// Gets all non-attached s registered on a type. /// - /// The owner type. + /// The type. /// A collection of definitions. - public IEnumerable GetAttached(Type ownerType) + public IEnumerable GetRegistered(Type type) { - Dictionary inner; + Contract.Requires(type != null); + + if (_registeredCache.TryGetValue(type, out var result)) + { + return result; + } - // Ensure the type's static ctor has been run. - RuntimeHelpers.RunClassConstructor(ownerType.TypeHandle); + var t = type; + result = new List(); - if (_attached.TryGetValue(ownerType, out inner)) + while (t != null) { - return inner.Values; + // Ensure the type's static ctor has been run. + RuntimeHelpers.RunClassConstructor(t.TypeHandle); + + if (_registered.TryGetValue(t, out var registered)) + { + result.AddRange(registered.Values); + } + + t = t.BaseType; } - return Enumerable.Empty(); + _registeredCache.Add(type, result); + return result; } /// - /// Gets all s registered on a type. + /// Gets all attached s registered on a type. /// /// The type. /// A collection of definitions. - public IEnumerable GetRegistered(Type type) + public IEnumerable GetRegisteredAttached(Type type) { Contract.Requires(type != null); - while (type != null) + if (_attachedCache.TryGetValue(type, out var result)) { - // Ensure the type's static ctor has been run. - RuntimeHelpers.RunClassConstructor(type.TypeHandle); + return result; + } - Dictionary inner; + var t = type; + result = new List(); - if (_registered.TryGetValue(type, out inner)) + while (t != null) + { + if (_attached.TryGetValue(t, out var attached)) { - foreach (var p in inner) - { - yield return p.Value; - } + result.AddRange(attached.Values); } - type = type.GetTypeInfo().BaseType; + t = t.BaseType; } + + _attachedCache.Add(type, result); + return result; } /// @@ -99,142 +106,92 @@ namespace Avalonia } /// - /// Finds a registered on a type. + /// Finds a registered non-attached property on a type by name. /// /// The type. - /// The property. - /// The registered property or null if not found. - /// - /// Calling AddOwner on a AvaloniaProperty creates a new AvaloniaProperty that is a - /// different object but is equal according to . - /// - public AvaloniaProperty FindRegistered(Type type, AvaloniaProperty property) + /// The property name. + /// + /// The registered property or null if no matching property found. + /// + /// + /// The property name contains a '.'. + /// + public AvaloniaProperty FindRegistered(Type type, string name) { - Type currentType = type; - Dictionary cache; - AvaloniaProperty result; + Contract.Requires(type != null); + Contract.Requires(name != null); - if (_registeredCache.TryGetValue(type, out cache)) + if (name.Contains('.')) { - if (cache.TryGetValue(property.Id, out result)) - { - return result; - } + throw new InvalidOperationException("Attached properties not supported."); } - while (currentType != null) - { - Dictionary inner; - - if (_registered.TryGetValue(currentType, out inner)) - { - if (inner.TryGetValue(property.Id, out result)) - { - if (cache == null) - { - _registeredCache[type] = cache = new Dictionary(); - } - - cache[property.Id] = result; - - return result; - } - } - - currentType = currentType.GetTypeInfo().BaseType; - } - - return null; + return GetRegistered(type).FirstOrDefault(x => x.Name == name); } /// - /// Finds registered on an object. + /// Finds a registered non-attached property on a type by name. /// /// The object. - /// The property. - /// The registered property or null if not found. - /// - /// Calling AddOwner on a AvaloniaProperty creates a new AvaloniaProperty that is a - /// different object but is equal according to . - /// - public AvaloniaProperty FindRegistered(object o, AvaloniaProperty property) + /// The property name. + /// + /// The registered property or null if no matching property found. + /// + /// + /// The property name contains a '.'. + /// + public AvaloniaProperty FindRegistered(AvaloniaObject o, string name) { - return FindRegistered(o.GetType(), property); + Contract.Requires(o != null); + Contract.Requires(name != null); + + return FindRegistered(o.GetType(), name); } /// - /// Finds a registered property on a type by name. + /// Finds a registered attached property on a type by name. /// /// The type. - /// - /// The property name. If an attached property it should be in the form - /// "OwnerType.PropertyName". - /// + /// The owner type. + /// The property name. /// /// The registered property or null if no matching property found. /// - public AvaloniaProperty FindRegistered(Type type, string name) + /// + /// The property name contains a '.'. + /// + public AvaloniaProperty FindRegisteredAttached(Type type, Type ownerType, string name) { Contract.Requires(type != null); + Contract.Requires(ownerType != null); Contract.Requires(name != null); - var parts = name.Split('.'); - var types = GetImplementedTypes(type).ToList(); - - if (parts.Length < 1 || parts.Length > 2) + if (name.Contains('.')) { - throw new ArgumentException("Invalid property name."); + throw new InvalidOperationException("Attached properties not supported."); } - string propertyName; - var results = GetRegistered(type); - - if (parts.Length == 1) - { - propertyName = parts[0]; - results = results.Where(x => !x.IsAttached || types.Contains(x.OwnerType.Name)); - } - else - { - if (!types.Contains(parts[0])) - { - results = results.Where(x => x.OwnerType.Name == parts[0]); - } - - propertyName = parts[1]; - } - - return results.FirstOrDefault(x => x.Name == propertyName); + return GetRegisteredAttached(type).FirstOrDefault(x => x.Name == name); } /// - /// Finds a registered property on an object by name. + /// Finds a registered non-attached property on a type by name. /// /// The object. - /// - /// The property name. If an attached property it should be in the form - /// "OwnerType.PropertyName". - /// + /// The owner type. + /// The property name. /// /// The registered property or null if no matching property found. /// - public AvaloniaProperty FindRegistered(AvaloniaObject o, string name) + /// + /// The property name contains a '.'. + /// + public AvaloniaProperty FindRegisteredAttached(AvaloniaObject o, Type ownerType, string name) { - return FindRegistered(o.GetType(), name); - } + Contract.Requires(o != null); + Contract.Requires(name != null); - /// - /// Returns a type and all its base types. - /// - /// The type. - /// The type and all its base types. - private IEnumerable GetImplementedTypes(Type type) - { - while (type != null) - { - yield return type.Name; - type = type.GetTypeInfo().BaseType; - } + return FindRegisteredAttached(o.GetType(), ownerType, name); } /// @@ -245,7 +202,11 @@ namespace Avalonia /// True if the property is registered, otherwise false. public bool IsRegistered(Type type, AvaloniaProperty property) { - return FindRegistered(type, property) != null; + Contract.Requires(type != null); + Contract.Requires(property != null); + + return Instance.GetRegistered(type).Any(x => x == property) || + Instance.GetRegisteredAttached(type).Any(x => x == property); } /// @@ -256,6 +217,9 @@ namespace Avalonia /// True if the property is registered, otherwise false. public bool IsRegistered(object o, AvaloniaProperty property) { + Contract.Requires(o != null); + Contract.Requires(property != null); + return IsRegistered(o.GetType(), property); } @@ -274,34 +238,53 @@ namespace Avalonia Contract.Requires(type != null); Contract.Requires(property != null); - Dictionary inner; - - if (!_registered.TryGetValue(type, out inner)) + if (!_registered.TryGetValue(type, out var inner)) { inner = new Dictionary(); + inner.Add(property.Id, property); _registered.Add(type, inner); } - - if (!inner.ContainsKey(property.Id)) + else if (!inner.ContainsKey(property.Id)) { inner.Add(property.Id, property); } + + _registeredCache.Clear(); + } - if (property.IsAttached) + /// + /// Registers an attached on a type. + /// + /// The type. + /// The property. + /// + /// You won't usually want to call this method directly, instead use the + /// + /// method. + /// + public void RegisterAttached(Type type, AvaloniaProperty property) + { + Contract.Requires(type != null); + Contract.Requires(property != null); + + if (!property.IsAttached) { - if (!_attached.TryGetValue(property.OwnerType, out inner)) - { - inner = new Dictionary(); - _attached.Add(property.OwnerType, inner); - } + throw new InvalidOperationException( + "Cannot register a non-attached property as attached."); + } - if (!inner.ContainsKey(property.Id)) - { - inner.Add(property.Id, property); - } + if (!_attached.TryGetValue(type, out var inner)) + { + inner = new Dictionary(); + inner.Add(property.Id, property); + _attached.Add(type, inner); + } + else + { + inner.Add(property.Id, property); } - _registeredCache.Clear(); + _attachedCache.Clear(); } } } \ No newline at end of file diff --git a/src/Avalonia.Base/DirectProperty.cs b/src/Avalonia.Base/DirectProperty.cs index 8352528285..1ce73c20ba 100644 --- a/src/Avalonia.Base/DirectProperty.cs +++ b/src/Avalonia.Base/DirectProperty.cs @@ -75,6 +75,9 @@ namespace Avalonia /// public Action Setter { get; } + /// + Type IDirectPropertyAccessor.Owner => typeof(TOwner); + /// /// Registers the direct property on another type. /// diff --git a/src/Avalonia.Base/IDirectPropertyAccessor.cs b/src/Avalonia.Base/IDirectPropertyAccessor.cs index 62aeef73c7..4f46652693 100644 --- a/src/Avalonia.Base/IDirectPropertyAccessor.cs +++ b/src/Avalonia.Base/IDirectPropertyAccessor.cs @@ -1,6 +1,8 @@ // Copyright (c) The Avalonia Project. All rights reserved. // Licensed under the MIT license. See licence.md file in the project root for full license information. +using System; + namespace Avalonia { /// @@ -14,6 +16,11 @@ namespace Avalonia /// bool IsReadOnly { get; } + /// + /// Gets the class that registered the property. + /// + Type Owner { get; } + /// /// Gets the value of the property on the instance. /// diff --git a/src/Avalonia.Base/Platform/IAssetLoader.cs b/src/Avalonia.Base/Platform/IAssetLoader.cs index eae8db5a14..ba30af60bf 100644 --- a/src/Avalonia.Base/Platform/IAssetLoader.cs +++ b/src/Avalonia.Base/Platform/IAssetLoader.cs @@ -43,5 +43,21 @@ namespace Avalonia.Platform /// The resource was not found. /// Stream Open(Uri uri, Uri baseUri = null); + + /// + /// Opens the resource with the requested URI and returns the resource string and the + /// assembly containing the resource. + /// + /// The URI. + /// + /// A base URI to use if is relative. + /// + /// + /// The stream containing the resource contents together with the assembly. + /// + /// + /// The resource was not found. + /// + Tuple OpenAndGetAssembly(Uri uri, Uri baseUri = null); } } diff --git a/src/Avalonia.Base/Properties/AssemblyInfo.cs b/src/Avalonia.Base/Properties/AssemblyInfo.cs index 2ee7378413..0a276aa2aa 100644 --- a/src/Avalonia.Base/Properties/AssemblyInfo.cs +++ b/src/Avalonia.Base/Properties/AssemblyInfo.cs @@ -4,7 +4,6 @@ using System.Reflection; using System.Runtime.CompilerServices; -[assembly: AssemblyTitle("Avalonia.Base")] [assembly: InternalsVisibleTo("Avalonia.Base.UnitTests")] [assembly: InternalsVisibleTo("Avalonia.UnitTests")] [assembly: InternalsVisibleTo("DynamicProxyGenAssembly2")] \ No newline at end of file diff --git a/src/Avalonia.Base/Utilities/StringTokenizer.cs b/src/Avalonia.Base/Utilities/StringTokenizer.cs index 2559e52932..a159bfb72e 100644 --- a/src/Avalonia.Base/Utilities/StringTokenizer.cs +++ b/src/Avalonia.Base/Utilities/StringTokenizer.cs @@ -52,9 +52,16 @@ namespace Avalonia.Utilities public bool TryReadInt32(out Int32 result, char? separator = null) { - var success = TryReadString(out var stringResult, separator); - result = success ? int.Parse(stringResult, _formatProvider) : 0; - return success; + if (TryReadString(out var stringResult, separator) && + int.TryParse(stringResult, NumberStyles.Integer, _formatProvider, out result)) + { + return true; + } + else + { + result = default(Int32); + return false; + } } public int ReadInt32(char? separator = null) @@ -69,9 +76,16 @@ namespace Avalonia.Utilities public bool TryReadDouble(out double result, char? separator = null) { - var success = TryReadString(out var stringResult, separator); - result = success ? double.Parse(stringResult, _formatProvider) : 0; - return success; + if (TryReadString(out var stringResult, separator) && + double.TryParse(stringResult, NumberStyles.Float, _formatProvider, out result)) + { + return true; + } + else + { + result = default(double); + return false; + } } public double ReadDouble(char? separator = null) diff --git a/src/Avalonia.Controls/AutoCompleteBox.cs b/src/Avalonia.Controls/AutoCompleteBox.cs index 8e801d606b..351cbf5520 100644 --- a/src/Avalonia.Controls/AutoCompleteBox.cs +++ b/src/Avalonia.Controls/AutoCompleteBox.cs @@ -1954,6 +1954,10 @@ namespace Avalonia.Controls // 1. Minimum prefix length // 2. If a delay timer is in use, use it bool populateReady = newText.Length >= MinimumPrefixLength && MinimumPrefixLength >= 0; + if(populateReady && MinimumPrefixLength == 0 && String.IsNullOrEmpty(newText) && String.IsNullOrEmpty(SearchText)) + { + populateReady = false; + } _userCalledPopulate = populateReady ? userInitiated : false; // Update the interface and values only as necessary diff --git a/src/Avalonia.Controls/Avalonia.Controls.csproj b/src/Avalonia.Controls/Avalonia.Controls.csproj index 997e15050f..ae777115f4 100644 --- a/src/Avalonia.Controls/Avalonia.Controls.csproj +++ b/src/Avalonia.Controls/Avalonia.Controls.csproj @@ -1,35 +1,9 @@  netstandard2.0 + latest false - - true - full - false - bin\Debug\ - DEBUG;TRACE - prompt - 4 - bin\Debug\Avalonia.Controls.xml - CS1591;CS0067 - - - pdbonly - true - bin\Release\ - TRACE - prompt - 4 - bin\Release\Avalonia.Controls.xml - CS1591 - true - - - - Properties\SharedAssemblyInfo.cs - - diff --git a/src/Avalonia.Controls/ColumnDefinitions.cs b/src/Avalonia.Controls/ColumnDefinitions.cs index 32fe5a6034..972655262a 100644 --- a/src/Avalonia.Controls/ColumnDefinitions.cs +++ b/src/Avalonia.Controls/ColumnDefinitions.cs @@ -27,7 +27,7 @@ namespace Avalonia.Controls public ColumnDefinitions(string s) : this() { - AddRange(GridLength.ParseLengths(s, CultureInfo.InvariantCulture).Select(x => new ColumnDefinition(x))); + AddRange(GridLength.ParseLengths(s).Select(x => new ColumnDefinition(x))); } } } \ No newline at end of file diff --git a/src/Avalonia.Controls/ContextMenu.cs b/src/Avalonia.Controls/ContextMenu.cs index fdb04f4ade..78dc994df7 100644 --- a/src/Avalonia.Controls/ContextMenu.cs +++ b/src/Avalonia.Controls/ContextMenu.cs @@ -19,7 +19,7 @@ namespace Avalonia.Controls { ContextMenuProperty.Changed.Subscribe(ContextMenuChanged); - MenuItem.ClickEvent.AddClassHandler(x => x.OnContextMenuClick, handledEventsToo: true); + MenuItem.ClickEvent.AddClassHandler(x => x.OnContextMenuClick, handledEventsToo: true); } /// @@ -75,13 +75,14 @@ namespace Avalonia.Controls { if (control != null) { - if(_popup == null) + if (_popup == null) { _popup = new Popup() { PlacementMode = PlacementMode.Pointer, PlacementTarget = control, - StaysOpen = false + StaysOpen = false, + ObeyScreenEdges = true }; _popup.Closed += PopupClosed; diff --git a/src/Avalonia.Controls/Grid.cs b/src/Avalonia.Controls/Grid.cs index 2a564b6a2c..54fcefeb3f 100644 --- a/src/Avalonia.Controls/Grid.cs +++ b/src/Avalonia.Controls/Grid.cs @@ -4,7 +4,10 @@ using System; using System.Collections.Generic; using System.Linq; +using System.Runtime.CompilerServices; using Avalonia.Collections; +using Avalonia.Controls.Utils; +using JetBrains.Annotations; namespace Avalonia.Controls { @@ -45,10 +48,6 @@ namespace Avalonia.Controls private RowDefinitions _rowDefinitions; - private Segment[,] _rowMatrix; - - private Segment[,] _colMatrix; - /// /// Gets or sets the columns definitions for the grid. /// @@ -183,6 +182,18 @@ namespace Avalonia.Controls element.SetValue(RowSpanProperty, value); } + /// + /// Gets the result of the last column measurement. + /// Use this result to reduce the arrange calculation. + /// + private GridLayout.MeasureResult _columnMeasureCache; + + /// + /// Gets the result of the last row measurement. + /// Use this result to reduce the arrange calculation. + /// + private GridLayout.MeasureResult _rowMeasureCache; + /// /// Measures the grid. /// @@ -190,293 +201,74 @@ namespace Avalonia.Controls /// The desired size of the control. protected override Size MeasureOverride(Size constraint) { - Size totalSize = constraint; - int colCount = ColumnDefinitions.Count; - int rowCount = RowDefinitions.Count; - double totalStarsX = 0; - double totalStarsY = 0; - bool emptyRows = rowCount == 0; - bool emptyCols = colCount == 0; - bool hasChildren = Children.Count > 0; - - if (emptyRows) - { - rowCount = 1; - } - - if (emptyCols) - { - colCount = 1; - } - - CreateMatrices(rowCount, colCount); + // Situation 1/2: + // If the grid doesn't have any column/row definitions, it behaves like a normal panel. + // GridLayout supports this situation but we handle this separately for performance. - if (emptyRows) - { - _rowMatrix[0, 0] = new Segment(0, 0, double.PositiveInfinity, GridUnitType.Star); - _rowMatrix[0, 0].Stars = 1.0; - totalStarsY += 1.0; - } - else + if (ColumnDefinitions.Count == 0 && RowDefinitions.Count == 0) { - for (int i = 0; i < rowCount; i++) + var maxWidth = 0.0; + var maxHeight = 0.0; + foreach (var child in Children.OfType()) { - RowDefinition rowdef = RowDefinitions[i]; - GridLength height = rowdef.Height; - - rowdef.ActualHeight = double.PositiveInfinity; - _rowMatrix[i, i] = new Segment(0, rowdef.MinHeight, rowdef.MaxHeight, height.GridUnitType); - - if (height.GridUnitType == GridUnitType.Pixel) - { - _rowMatrix[i, i].OfferedSize = Clamp(height.Value, _rowMatrix[i, i].Min, _rowMatrix[i, i].Max); - _rowMatrix[i, i].DesiredSize = _rowMatrix[i, i].OfferedSize; - rowdef.ActualHeight = _rowMatrix[i, i].OfferedSize; - } - else if (height.GridUnitType == GridUnitType.Star) - { - _rowMatrix[i, i].Stars = height.Value; - totalStarsY += height.Value; - } - else if (height.GridUnitType == GridUnitType.Auto) - { - _rowMatrix[i, i].OfferedSize = Clamp(0, _rowMatrix[i, i].Min, _rowMatrix[i, i].Max); - _rowMatrix[i, i].DesiredSize = _rowMatrix[i, i].OfferedSize; - } + child.Measure(constraint); + maxWidth = Math.Max(maxWidth, child.DesiredSize.Width); + maxHeight = Math.Max(maxHeight, child.DesiredSize.Height); } - } - if (emptyCols) - { - _colMatrix[0, 0] = new Segment(0, 0, double.PositiveInfinity, GridUnitType.Star); - _colMatrix[0, 0].Stars = 1.0; - totalStarsX += 1.0; - } - else - { - for (int i = 0; i < colCount; i++) - { - ColumnDefinition coldef = ColumnDefinitions[i]; - GridLength width = coldef.Width; - - coldef.ActualWidth = double.PositiveInfinity; - _colMatrix[i, i] = new Segment(0, coldef.MinWidth, coldef.MaxWidth, width.GridUnitType); - - if (width.GridUnitType == GridUnitType.Pixel) - { - _colMatrix[i, i].OfferedSize = Clamp(width.Value, _colMatrix[i, i].Min, _colMatrix[i, i].Max); - _colMatrix[i, i].DesiredSize = _colMatrix[i, i].OfferedSize; - coldef.ActualWidth = _colMatrix[i, i].OfferedSize; - } - else if (width.GridUnitType == GridUnitType.Star) - { - _colMatrix[i, i].Stars = width.Value; - totalStarsX += width.Value; - } - else if (width.GridUnitType == GridUnitType.Auto) - { - _colMatrix[i, i].OfferedSize = Clamp(0, _colMatrix[i, i].Min, _colMatrix[i, i].Max); - _colMatrix[i, i].DesiredSize = _colMatrix[i, i].OfferedSize; - } - } + maxWidth = Math.Min(maxWidth, constraint.Width); + maxHeight = Math.Min(maxHeight, constraint.Height); + return new Size(maxWidth, maxHeight); } - List sizes = new List(); - GridNode node; - GridNode separator = new GridNode(null, 0, 0, 0); - int separatorIndex; + // Situation 2/2: + // If the grid defines some columns or rows. + // Debug Tip: + // - GridLayout doesn't hold any state, so you can drag the debugger execution + // arrow back to any statements and re-run them without any side-effect. - sizes.Add(separator); + var measureCache = new Dictionary(); + var (safeColumns, safeRows) = GetSafeColumnRows(); + var columnLayout = new GridLayout(ColumnDefinitions); + var rowLayout = new GridLayout(RowDefinitions); + // Note: If a child stays in a * or Auto column/row, use constraint to measure it. + columnLayout.AppendMeasureConventions(safeColumns, child => MeasureOnce(child, constraint).Width); + rowLayout.AppendMeasureConventions(safeRows, child => MeasureOnce(child, constraint).Height); - // Pre-process the grid children so that we know what types of elements we have so - // we can apply our special measuring rules. - GridWalker gridWalker = new GridWalker(this, _rowMatrix, _colMatrix); + // Calculate measurement. + var columnResult = columnLayout.Measure(constraint.Width); + var rowResult = rowLayout.Measure(constraint.Height); - for (int i = 0; i < 6; i++) + // Use the results of the measurement to measure the rest of the children. + foreach (var child in Children.OfType()) { - // These bools tell us which grid element type we should be measuring. i.e. - // 'star/auto' means we should measure elements with a star row and auto col - bool autoAuto = i == 0; - bool starAuto = i == 1; - bool autoStar = i == 2; - bool starAutoAgain = i == 3; - bool nonStar = i == 4; - bool remainingStar = i == 5; - - if (hasChildren) - { - ExpandStarCols(totalSize); - ExpandStarRows(totalSize); - } + var (column, columnSpan) = safeColumns[child]; + var (row, rowSpan) = safeRows[child]; + var width = Enumerable.Range(column, columnSpan).Select(x => columnResult.LengthList[x]).Sum(); + var height = Enumerable.Range(row, rowSpan).Select(x => rowResult.LengthList[x]).Sum(); - foreach (Control child in Children) - { - int col, row; - int colspan, rowspan; - double childSizeX = 0; - double childSizeY = 0; - bool starCol = false; - bool starRow = false; - bool autoCol = false; - bool autoRow = false; - - col = Math.Min(GetColumn(child), colCount - 1); - row = Math.Min(GetRow(child), rowCount - 1); - colspan = Math.Min(GetColumnSpan(child), colCount - col); - rowspan = Math.Min(GetRowSpan(child), rowCount - row); - - for (int r = row; r < row + rowspan; r++) - { - starRow |= _rowMatrix[r, r].Type == GridUnitType.Star; - autoRow |= _rowMatrix[r, r].Type == GridUnitType.Auto; - } - - for (int c = col; c < col + colspan; c++) - { - starCol |= _colMatrix[c, c].Type == GridUnitType.Star; - autoCol |= _colMatrix[c, c].Type == GridUnitType.Auto; - } - - // This series of if statements checks whether or not we should measure - // the current element and also if we need to override the sizes - // passed to the Measure call. - - // If the element has Auto rows and Auto columns and does not span Star - // rows/cols it should only be measured in the auto_auto phase. - // There are similar rules governing auto/star and star/auto elements. - // NOTE: star/auto elements are measured twice. The first time with - // an override for height, the second time without it. - if (autoRow && autoCol && !starRow && !starCol) - { - if (!autoAuto) - { - continue; - } - - childSizeX = double.PositiveInfinity; - childSizeY = double.PositiveInfinity; - } - else if (starRow && autoCol && !starCol) - { - if (!(starAuto || starAutoAgain)) - { - continue; - } - - if (starAuto && gridWalker.HasAutoStar) - { - childSizeY = double.PositiveInfinity; - } - - childSizeX = double.PositiveInfinity; - } - else if (autoRow && starCol && !starRow) - { - if (!autoStar) - { - continue; - } - - childSizeY = double.PositiveInfinity; - } - else if ((autoRow || autoCol) && !(starRow || starCol)) - { - if (!nonStar) - { - continue; - } - - if (autoRow) - { - childSizeY = double.PositiveInfinity; - } - - if (autoCol) - { - childSizeX = double.PositiveInfinity; - } - } - else if (!(starRow || starCol)) - { - if (!nonStar) - { - continue; - } - } - else - { - if (!remainingStar) - { - continue; - } - } - - for (int r = row; r < row + rowspan; r++) - { - childSizeY += _rowMatrix[r, r].OfferedSize; - } - - for (int c = col; c < col + colspan; c++) - { - childSizeX += _colMatrix[c, c].OfferedSize; - } - - child.Measure(new Size(childSizeX, childSizeY)); - Size desired = child.DesiredSize; - - // Elements distribute their height based on two rules: - // 1) Elements with rowspan/colspan == 1 distribute their height first - // 2) Everything else distributes in a LIFO manner. - // As such, add all UIElements with rowspan/colspan == 1 after the separator in - // the list and everything else before it. Then to process, just keep popping - // elements off the end of the list. - if (!starAuto) - { - node = new GridNode(_rowMatrix, row + rowspan - 1, row, desired.Height); - separatorIndex = sizes.IndexOf(separator); - sizes.Insert(node.Row == node.Column ? separatorIndex + 1 : separatorIndex, node); - } - - node = new GridNode(_colMatrix, col + colspan - 1, col, desired.Width); - - separatorIndex = sizes.IndexOf(separator); - sizes.Insert(node.Row == node.Column ? separatorIndex + 1 : separatorIndex, node); - } + MeasureOnce(child, new Size(width, height)); + } - sizes.Remove(separator); + // Cache the measure result and return the desired size. + _columnMeasureCache = columnResult; + _rowMeasureCache = rowResult; + return new Size(columnResult.DesiredLength, rowResult.DesiredLength); - while (sizes.Count > 0) + // Measure each child only once. + // If a child has been measured, it will just return the desired size. + Size MeasureOnce(Control child, Size size) + { + if (measureCache.TryGetValue(child, out var desiredSize)) { - node = sizes.Last(); - node.Matrix[node.Row, node.Column].DesiredSize = Math.Max(node.Matrix[node.Row, node.Column].DesiredSize, node.Size); - AllocateDesiredSize(rowCount, colCount); - sizes.Remove(node); + return desiredSize; } - sizes.Add(separator); - } - - // Once we have measured and distributed all sizes, we have to store - // the results. Every time we want to expand the rows/cols, this will - // be used as the baseline. - SaveMeasureResults(); - - sizes.Remove(separator); - - double gridSizeX = 0; - double gridSizeY = 0; - - for (int c = 0; c < colCount; c++) - { - gridSizeX += _colMatrix[c, c].DesiredSize; + child.Measure(size); + desiredSize = child.DesiredSize; + measureCache[child] = desiredSize; + return desiredSize; } - - for (int r = 0; r < rowCount; r++) - { - gridSizeY += _rowMatrix[r, r].DesiredSize; - } - - return new Size(gridSizeX, gridSizeY); } /// @@ -486,456 +278,138 @@ namespace Avalonia.Controls /// The space taken. protected override Size ArrangeOverride(Size finalSize) { - int colCount = ColumnDefinitions.Count; - int rowCount = RowDefinitions.Count; - int colMatrixDim = _colMatrix.GetLength(0); - int rowMatrixDim = _rowMatrix.GetLength(0); - - RestoreMeasureResults(); + // Situation 1/2: + // If the grid doesn't have any column/row definitions, it behaves like a normal panel. + // GridLayout supports this situation but we handle this separately for performance. - double totalConsumedX = 0; - double totalConsumedY = 0; - - for (int c = 0; c < colMatrixDim; c++) + if (ColumnDefinitions.Count == 0 && RowDefinitions.Count == 0) { - _colMatrix[c, c].OfferedSize = _colMatrix[c, c].DesiredSize; - totalConsumedX += _colMatrix[c, c].OfferedSize; - } - - for (int r = 0; r < rowMatrixDim; r++) - { - _rowMatrix[r, r].OfferedSize = _rowMatrix[r, r].DesiredSize; - totalConsumedY += _rowMatrix[r, r].OfferedSize; - } - - if (totalConsumedX != finalSize.Width) - { - ExpandStarCols(finalSize); - } - - if (totalConsumedY != finalSize.Height) - { - ExpandStarRows(finalSize); - } - - for (int c = 0; c < colCount; c++) - { - ColumnDefinitions[c].ActualWidth = _colMatrix[c, c].OfferedSize; - } - - for (int r = 0; r < rowCount; r++) - { - RowDefinitions[r].ActualHeight = _rowMatrix[r, r].OfferedSize; - } - - foreach (Control child in Children) - { - int col = Math.Min(GetColumn(child), colMatrixDim - 1); - int row = Math.Min(GetRow(child), rowMatrixDim - 1); - int colspan = Math.Min(GetColumnSpan(child), colMatrixDim - col); - int rowspan = Math.Min(GetRowSpan(child), rowMatrixDim - row); - - double childFinalX = 0; - double childFinalY = 0; - double childFinalW = 0; - double childFinalH = 0; - - for (int c = 0; c < col; c++) - { - childFinalX += _colMatrix[c, c].OfferedSize; - } - - for (int c = col; c < col + colspan; c++) + foreach (var child in Children.OfType()) { - childFinalW += _colMatrix[c, c].OfferedSize; + child.Arrange(new Rect(finalSize)); } - for (int r = 0; r < row; r++) - { - childFinalY += _rowMatrix[r, r].OfferedSize; - } - - for (int r = row; r < row + rowspan; r++) - { - childFinalH += _rowMatrix[r, r].OfferedSize; - } - - child.Arrange(new Rect(childFinalX, childFinalY, childFinalW, childFinalH)); + return finalSize; } - return finalSize; - } - - private static double Clamp(double val, double min, double max) - { - if (val < min) - { - return min; - } - else if (val > max) - { - return max; - } - else - { - return val; - } - } + // Situation 2/2: + // If the grid defines some columns or rows. + // Debug Tip: + // - GridLayout doesn't hold any state, so you can drag the debugger execution + // arrow back to any statements and re-run them without any side-effect. - private static int ValidateColumn(AvaloniaObject o, int value) - { - if (value < 0) - { - throw new ArgumentException("Invalid Grid.Column value."); - } + var (safeColumns, safeRows) = GetSafeColumnRows(); + var columnLayout = new GridLayout(ColumnDefinitions); + var rowLayout = new GridLayout(RowDefinitions); - return value; - } + // Calculate for arrange result. + var columnResult = columnLayout.Arrange(finalSize.Width, _columnMeasureCache); + var rowResult = rowLayout.Arrange(finalSize.Height, _rowMeasureCache); - private static int ValidateRow(AvaloniaObject o, int value) - { - if (value < 0) + // Arrange the children. + foreach (var child in Children.OfType()) { - throw new ArgumentException("Invalid Grid.Row value."); - } + var (column, columnSpan) = safeColumns[child]; + var (row, rowSpan) = safeRows[child]; + var x = Enumerable.Range(0, column).Sum(c => columnResult.LengthList[c]); + var y = Enumerable.Range(0, row).Sum(r => rowResult.LengthList[r]); + var width = Enumerable.Range(column, columnSpan).Sum(c => columnResult.LengthList[c]); + var height = Enumerable.Range(row, rowSpan).Sum(r => rowResult.LengthList[r]); - return value; - } - - private void CreateMatrices(int rowCount, int colCount) - { - if (_rowMatrix == null || _colMatrix == null || - _rowMatrix.GetLength(0) != rowCount || - _colMatrix.GetLength(0) != colCount) - { - _rowMatrix = new Segment[rowCount, rowCount]; - _colMatrix = new Segment[colCount, colCount]; - } - else - { - Array.Clear(_rowMatrix, 0, _rowMatrix.Length); - Array.Clear(_colMatrix, 0, _colMatrix.Length); + child.Arrange(new Rect(x, y, width, height)); } - } - - private void ExpandStarCols(Size availableSize) - { - int matrixCount = _colMatrix.GetLength(0); - int columnsCount = ColumnDefinitions.Count; - double width = availableSize.Width; - for (int i = 0; i < matrixCount; i++) + // Assign the actual width. + for (var i = 0; i < ColumnDefinitions.Count; i++) { - if (_colMatrix[i, i].Type == GridUnitType.Star) - { - _colMatrix[i, i].OfferedSize = 0; - } - else - { - width = Math.Max(width - _colMatrix[i, i].OfferedSize, 0); - } + ColumnDefinitions[i].ActualWidth = columnResult.LengthList[i]; } - AssignSize(_colMatrix, 0, matrixCount - 1, ref width, GridUnitType.Star, false); - width = Math.Max(0, width); - - if (columnsCount > 0) + // Assign the actual height. + for (var i = 0; i < RowDefinitions.Count; i++) { - for (int i = 0; i < matrixCount; i++) - { - if (_colMatrix[i, i].Type == GridUnitType.Star) - { - ColumnDefinitions[i].ActualWidth = _colMatrix[i, i].OfferedSize; - } - } - } - } - - private void ExpandStarRows(Size availableSize) - { - int matrixCount = _rowMatrix.GetLength(0); - int rowCount = RowDefinitions.Count; - double height = availableSize.Height; - - // When expanding star rows, we need to zero out their height before - // calling AssignSize. AssignSize takes care of distributing the - // available size when there are Mins and Maxs applied. - for (int i = 0; i < matrixCount; i++) - { - if (_rowMatrix[i, i].Type == GridUnitType.Star) - { - _rowMatrix[i, i].OfferedSize = 0.0; - } - else - { - height = Math.Max(height - _rowMatrix[i, i].OfferedSize, 0); - } + RowDefinitions[i].ActualHeight = rowResult.LengthList[i]; } - AssignSize(_rowMatrix, 0, matrixCount - 1, ref height, GridUnitType.Star, false); - - if (rowCount > 0) - { - for (int i = 0; i < matrixCount; i++) - { - if (_rowMatrix[i, i].Type == GridUnitType.Star) - { - RowDefinitions[i].ActualHeight = _rowMatrix[i, i].OfferedSize; - } - } - } + // Return the render size. + return finalSize; } - private void AssignSize( - Segment[,] matrix, - int start, - int end, - ref double size, - GridUnitType type, - bool desiredSize) + /// + /// Get the safe column/columnspan and safe row/rowspan. + /// This method ensures that none of the children has a column/row outside the bounds of the definitions. + /// + [Pure] + private (Dictionary safeColumns, + Dictionary safeRows) GetSafeColumnRows() { - double count = 0; - bool assigned; - - // Count how many segments are of the correct type. If we're measuring Star rows/cols - // we need to count the number of stars instead. - for (int i = start; i <= end; i++) - { - double segmentSize = desiredSize ? matrix[i, i].DesiredSize : matrix[i, i].OfferedSize; - if (segmentSize < matrix[i, i].Max) - { - count += type == GridUnitType.Star ? matrix[i, i].Stars : 1; - } - } - - do - { - double contribution = size / count; - - assigned = false; - - for (int i = start; i <= end; i++) - { - double segmentSize = desiredSize ? matrix[i, i].DesiredSize : matrix[i, i].OfferedSize; - - if (!(matrix[i, i].Type == type && segmentSize < matrix[i, i].Max)) - { - continue; - } - - double newsize = segmentSize; - newsize += contribution * (type == GridUnitType.Star ? matrix[i, i].Stars : 1); - newsize = Math.Min(newsize, matrix[i, i].Max); - assigned |= newsize > segmentSize; - size -= newsize - segmentSize; - - if (desiredSize) - { - matrix[i, i].DesiredSize = newsize; - } - else - { - matrix[i, i].OfferedSize = newsize; - } - } - } - while (assigned); + var columnCount = ColumnDefinitions.Count; + var rowCount = RowDefinitions.Count; + columnCount = columnCount == 0 ? 1 : columnCount; + rowCount = rowCount == 0 ? 1 : rowCount; + var safeColumns = Children.OfType().ToDictionary(child => child, + child => GetSafeSpan(columnCount, GetColumn(child), GetColumnSpan(child))); + var safeRows = Children.OfType().ToDictionary(child => child, + child => GetSafeSpan(rowCount, GetRow(child), GetRowSpan(child))); + return (safeColumns, safeRows); } - private void AllocateDesiredSize(int rowCount, int colCount) + /// + /// Gets the safe row/column and rowspan/columnspan for a specified range. + /// The user may assign row/column properties outside the bounds of the row/column count, this method coerces them inside. + /// + /// The row or column count. + /// The row or column that the user assigned. + /// The rowspan or columnspan that the user assigned. + /// The safe row/column and rowspan/columnspan. + [Pure, MethodImpl(MethodImplOptions.AggressiveInlining)] + private static (int index, int span) GetSafeSpan(int length, int userIndex, int userSpan) { - // First allocate the heights of the RowDefinitions, then allocate - // the widths of the ColumnDefinitions. - for (int i = 0; i < 2; i++) - { - Segment[,] matrix = i == 0 ? _rowMatrix : _colMatrix; - int count = i == 0 ? rowCount : colCount; - - for (int row = count - 1; row >= 0; row--) - { - for (int col = row; col >= 0; col--) - { - bool spansStar = false; - for (int j = row; j >= col; j--) - { - spansStar |= matrix[j, j].Type == GridUnitType.Star; - } - - // This is the amount of pixels which must be available between the grid rows - // at index 'col' and 'row'. i.e. if 'row' == 0 and 'col' == 2, there must - // be at least 'matrix [row][col].size' pixels of height allocated between - // all the rows in the range col -> row. - double current = matrix[row, col].DesiredSize; - - // Count how many pixels have already been allocated between the grid rows - // in the range col -> row. The amount of pixels allocated to each grid row/column - // is found on the diagonal of the matrix. - double totalAllocated = 0; - - for (int k = row; k >= col; k--) - { - totalAllocated += matrix[k, k].DesiredSize; - } - - // If the size requirement has not been met, allocate the additional required - // size between 'pixel' rows, then 'star' rows, finally 'auto' rows, until all - // height has been assigned. - if (totalAllocated < current) - { - double additional = current - totalAllocated; - - if (spansStar) - { - AssignSize(matrix, col, row, ref additional, GridUnitType.Star, true); - } - else - { - AssignSize(matrix, col, row, ref additional, GridUnitType.Pixel, true); - AssignSize(matrix, col, row, ref additional, GridUnitType.Auto, true); - } - } - } - } - } - - int rowMatrixDim = _rowMatrix.GetLength(0); - int colMatrixDim = _colMatrix.GetLength(0); + var index = userIndex; + var span = userSpan; - for (int r = 0; r < rowMatrixDim; r++) + if (index < 0) { - _rowMatrix[r, r].OfferedSize = _rowMatrix[r, r].DesiredSize; + span = index + span; + index = 0; } - for (int c = 0; c < colMatrixDim; c++) + if (span <= 0) { - _colMatrix[c, c].OfferedSize = _colMatrix[c, c].DesiredSize; + span = 1; } - } - private void SaveMeasureResults() - { - int rowMatrixDim = _rowMatrix.GetLength(0); - int colMatrixDim = _colMatrix.GetLength(0); - - for (int i = 0; i < rowMatrixDim; i++) + if (userIndex >= length) { - for (int j = 0; j < rowMatrixDim; j++) - { - _rowMatrix[i, j].OriginalSize = _rowMatrix[i, j].OfferedSize; - } + index = length - 1; + span = 1; } - - for (int i = 0; i < colMatrixDim; i++) + else if (userIndex + userSpan > length) { - for (int j = 0; j < colMatrixDim; j++) - { - _colMatrix[i, j].OriginalSize = _colMatrix[i, j].OfferedSize; - } - } - } - - private void RestoreMeasureResults() - { - int rowMatrixDim = _rowMatrix.GetLength(0); - int colMatrixDim = _colMatrix.GetLength(0); - - for (int i = 0; i < rowMatrixDim; i++) - { - for (int j = 0; j < rowMatrixDim; j++) - { - _rowMatrix[i, j].OfferedSize = _rowMatrix[i, j].OriginalSize; - } + span = length - userIndex; } - for (int i = 0; i < colMatrixDim; i++) - { - for (int j = 0; j < colMatrixDim; j++) - { - _colMatrix[i, j].OfferedSize = _colMatrix[i, j].OriginalSize; - } - } + return (index, span); } - private struct Segment + private static int ValidateColumn(AvaloniaObject o, int value) { - public double OriginalSize; - public double Max; - public double Min; - public double DesiredSize; - public double OfferedSize; - public double Stars; - public GridUnitType Type; - - public Segment(double offeredSize, double min, double max, GridUnitType type) + if (value < 0) { - OriginalSize = 0; - Min = min; - Max = max; - DesiredSize = 0; - OfferedSize = offeredSize; - Stars = 0; - Type = type; + throw new ArgumentException("Invalid Grid.Column value."); } - } - private struct GridNode - { - public readonly int Row; - public readonly int Column; - public readonly double Size; - public readonly Segment[,] Matrix; - - public GridNode(Segment[,] matrix, int row, int col, double size) - { - Matrix = matrix; - Row = row; - Column = col; - Size = size; - } + return value; } - private class GridWalker + private static int ValidateRow(AvaloniaObject o, int value) { - public GridWalker(Grid grid, Segment[,] rowMatrix, Segment[,] colMatrix) + if (value < 0) { - int rowMatrixDim = rowMatrix.GetLength(0); - int colMatrixDim = colMatrix.GetLength(0); - - foreach (Control child in grid.Children) - { - bool starCol = false; - bool starRow = false; - bool autoCol = false; - bool autoRow = false; - - int col = Math.Min(GetColumn(child), colMatrixDim - 1); - int row = Math.Min(GetRow(child), rowMatrixDim - 1); - int colspan = Math.Min(GetColumnSpan(child), colMatrixDim - 1); - int rowspan = Math.Min(GetRowSpan(child), rowMatrixDim - 1); - - for (int r = row; r < row + rowspan; r++) - { - starRow |= rowMatrix[r, r].Type == GridUnitType.Star; - autoRow |= rowMatrix[r, r].Type == GridUnitType.Auto; - } - - for (int c = col; c < col + colspan; c++) - { - starCol |= colMatrix[c, c].Type == GridUnitType.Star; - autoCol |= colMatrix[c, c].Type == GridUnitType.Auto; - } - - HasAutoAuto |= autoRow && autoCol && !starRow && !starCol; - HasStarAuto |= starRow && autoCol; - HasAutoStar |= autoRow && starCol; - } + throw new ArgumentException("Invalid Grid.Row value."); } - public bool HasAutoAuto { get; } - - public bool HasStarAuto { get; } - - public bool HasAutoStar { get; } + return value; } } -} \ No newline at end of file +} diff --git a/src/Avalonia.Controls/GridLength.cs b/src/Avalonia.Controls/GridLength.cs index 789953a249..608879812c 100644 --- a/src/Avalonia.Controls/GridLength.cs +++ b/src/Avalonia.Controls/GridLength.cs @@ -180,9 +180,8 @@ namespace Avalonia.Controls /// Parses a string to return a . /// /// The string. - /// The current culture. /// The . - public static GridLength Parse(string s, CultureInfo culture) + public static GridLength Parse(string s) { s = s.ToUpperInvariant(); @@ -193,12 +192,12 @@ namespace Avalonia.Controls else if (s.EndsWith("*")) { var valueString = s.Substring(0, s.Length - 1).Trim(); - var value = valueString.Length > 0 ? double.Parse(valueString, culture) : 1; + var value = valueString.Length > 0 ? double.Parse(valueString, CultureInfo.InvariantCulture) : 1; return new GridLength(value, GridUnitType.Star); } else { - var value = double.Parse(s, culture); + var value = double.Parse(s, CultureInfo.InvariantCulture); return new GridLength(value, GridUnitType.Pixel); } } @@ -207,15 +206,14 @@ namespace Avalonia.Controls /// Parses a string to return a collection of s. /// /// The string. - /// The current culture. /// The . - public static IEnumerable ParseLengths(string s, CultureInfo culture) + public static IEnumerable ParseLengths(string s) { - using (var tokenizer = new StringTokenizer(s, culture)) + using (var tokenizer = new StringTokenizer(s, CultureInfo.InvariantCulture)) { while (tokenizer.TryReadString(out var item)) { - yield return Parse(item, culture); + yield return Parse(item); } } } diff --git a/src/Avalonia.Controls/Platform/IWindowBaseImpl.cs b/src/Avalonia.Controls/Platform/IWindowBaseImpl.cs index 0a01cf3df4..4f7ac82df7 100644 --- a/src/Avalonia.Controls/Platform/IWindowBaseImpl.cs +++ b/src/Avalonia.Controls/Platform/IWindowBaseImpl.cs @@ -55,7 +55,7 @@ namespace Avalonia.Platform /// Gets the platform window handle. /// IPlatformHandle Handle { get; } - + /// /// Gets the maximum size of a window on the system. /// @@ -65,7 +65,13 @@ namespace Avalonia.Platform /// Sets the client size of the toplevel. /// void Resize(Size clientSize); - + + /// + /// Minimum width of the window. + /// + /// + void SetMinMaxSize(Size minSize, Size maxSize); + /// /// Gets platform specific display information /// diff --git a/src/Avalonia.Controls/Platform/IWindowImpl.cs b/src/Avalonia.Controls/Platform/IWindowImpl.cs index 1f84574318..3f2c977718 100644 --- a/src/Avalonia.Controls/Platform/IWindowImpl.cs +++ b/src/Avalonia.Controls/Platform/IWindowImpl.cs @@ -45,6 +45,11 @@ namespace Avalonia.Platform /// void ShowTaskbarIcon(bool value); + /// + /// Enables or disables resizing of the window + /// + void CanResize(bool value); + /// /// Gets or sets a method called before the underlying implementation is destroyed. /// Return true to prevent the underlying implementation from closing. diff --git a/src/Avalonia.Controls/Primitives/Popup.cs b/src/Avalonia.Controls/Primitives/Popup.cs index b8817d28f3..656f3890cd 100644 --- a/src/Avalonia.Controls/Primitives/Popup.cs +++ b/src/Avalonia.Controls/Primitives/Popup.cs @@ -40,6 +40,12 @@ namespace Avalonia.Controls.Primitives public static readonly StyledProperty PlacementModeProperty = AvaloniaProperty.Register(nameof(PlacementMode), defaultValue: PlacementMode.Bottom); + /// + /// Defines the property. + /// + public static readonly StyledProperty ObeyScreenEdgesProperty = + AvaloniaProperty.Register(nameof(ObeyScreenEdges)); + /// /// Defines the property. /// @@ -136,6 +142,16 @@ namespace Avalonia.Controls.Primitives set { SetValue(PlacementModeProperty, value); } } + /// + /// Gets or sets a value indicating whether the popup positions itself within the nearest screen boundary + /// when its opened at a position where it would otherwise overlap the screen edge. + /// + public bool ObeyScreenEdges + { + get => GetValue(ObeyScreenEdgesProperty); + set => SetValue(ObeyScreenEdgesProperty, value); + } + /// /// Gets or sets the Horizontal offset of the popup in relation to the /// @@ -216,12 +232,12 @@ namespace Avalonia.Controls.Primitives var window = _topLevel as Window; if (window != null) { - window.Deactivated += WindowDeactivated; + window.Deactivated += WindowDeactivated; } else { var parentPopuproot = _topLevel as PopupRoot; - if(parentPopuproot != null && parentPopuproot.Parent!=null) + if (parentPopuproot != null && parentPopuproot.Parent != null) { ((Popup)(parentPopuproot.Parent)).Closed += ParentClosed; } @@ -234,13 +250,18 @@ namespace Avalonia.Controls.Primitives _popupRoot.Show(); + if (ObeyScreenEdges) + { + _popupRoot.SnapInsideScreenEdges(); + } + _ignoreIsOpenChanged = true; IsOpen = true; _ignoreIsOpenChanged = false; Opened?.Invoke(this, EventArgs.Empty); } - + /// /// Closes the popup. /// @@ -346,8 +367,10 @@ namespace Avalonia.Controls.Primitives /// The popup's position in screen coordinates. protected virtual Point GetPosition() { - return GetPosition(PlacementTarget ?? this.GetVisualParent(), PlacementMode, PopupRoot, + var result = GetPosition(PlacementTarget ?? this.GetVisualParent(), PlacementMode, PopupRoot, HorizontalOffset, VerticalOffset); + + return result; } internal static Point GetPosition(Control target, PlacementMode placement, PopupRoot popupRoot, double horizontalOffset, double verticalOffset) @@ -399,8 +422,8 @@ namespace Avalonia.Controls.Primitives { if (!StaysOpen) { - if(!IsChildOrThis((IVisual)e.Source)) - { + if (!IsChildOrThis((IVisual)e.Source)) + { Close(); e.Handled = true; } @@ -412,12 +435,12 @@ namespace Avalonia.Controls.Primitives IVisual root = child.GetVisualRoot(); while (root is PopupRoot) { - if (root == PopupRoot) return true; + if (root == PopupRoot) return true; root = ((PopupRoot)root).Parent.GetVisualRoot(); } return false; } - + private void WindowDeactivated(object sender, EventArgs e) { if (!StaysOpen) diff --git a/src/Avalonia.Controls/Primitives/PopupRoot.cs b/src/Avalonia.Controls/Primitives/PopupRoot.cs index 507a085fed..457a7bd4b4 100644 --- a/src/Avalonia.Controls/Primitives/PopupRoot.cs +++ b/src/Avalonia.Controls/Primitives/PopupRoot.cs @@ -2,10 +2,12 @@ // Licensed under the MIT license. See licence.md file in the project root for full license information. using System; +using System.Linq; using Avalonia.Controls.Platform; using Avalonia.Controls.Presenters; using Avalonia.Interactivity; using Avalonia.Layout; +using Avalonia.LogicalTree; using Avalonia.Media; using Avalonia.Platform; using Avalonia.Styling; @@ -75,6 +77,30 @@ namespace Avalonia.Controls.Primitives /// public void Dispose() => PlatformImpl?.Dispose(); + /// + /// Moves the Popups position so that it doesnt overlap screen edges. + /// This method can be called immediately after Show has been called. + /// + public void SnapInsideScreenEdges() + { + var window = this.GetSelfAndLogicalAncestors().OfType().First(); + + var screen = window.Screens.ScreenFromPoint(Position); + + var screenX = Position.X + Bounds.Width - screen.Bounds.X; + var screenY = Position.Y + Bounds.Height - screen.Bounds.Y; + + if (screenX > screen.Bounds.Width) + { + Position = Position.WithX(Position.X - (screenX - screen.Bounds.Width)); + } + + if (screenY > screen.Bounds.Height) + { + Position = Position.WithY(Position.Y - (screenY - screen.Bounds.Height)); + } + } + /// protected override void OnTemplateApplied(TemplateAppliedEventArgs e) { diff --git a/src/Avalonia.Controls/Primitives/TemplatedControl.cs b/src/Avalonia.Controls/Primitives/TemplatedControl.cs index 77735f3f12..1a805a3822 100644 --- a/src/Avalonia.Controls/Primitives/TemplatedControl.cs +++ b/src/Avalonia.Controls/Primitives/TemplatedControl.cs @@ -207,7 +207,7 @@ namespace Avalonia.Controls.Primitives /// The control. /// The property value. /// - public bool GetIsTemplateFocusTarget(Control control) + public static bool GetIsTemplateFocusTarget(Control control) { return control.GetValue(IsTemplateFocusTargetProperty); } @@ -223,7 +223,7 @@ namespace Avalonia.Controls.Primitives /// attached property is set to true on an element in the control template, then the focus /// adorner will be shown around that control instead. /// - public void SetIsTemplateFocusTarget(Control control, bool value) + public static void SetIsTemplateFocusTarget(Control control, bool value) { control.SetValue(IsTemplateFocusTargetProperty, value); } diff --git a/src/Avalonia.Controls/Properties/AssemblyInfo.cs b/src/Avalonia.Controls/Properties/AssemblyInfo.cs index ae8c88f7e8..3466119411 100644 --- a/src/Avalonia.Controls/Properties/AssemblyInfo.cs +++ b/src/Avalonia.Controls/Properties/AssemblyInfo.cs @@ -5,7 +5,6 @@ using System.Reflection; using System.Runtime.CompilerServices; using Avalonia.Metadata; -[assembly: AssemblyTitle("Avalonia.Controls")] [assembly: InternalsVisibleTo("Avalonia.Controls.UnitTests")] [assembly: InternalsVisibleTo("Avalonia.DesignerSupport")] diff --git a/src/Avalonia.Controls/RowDefinitions.cs b/src/Avalonia.Controls/RowDefinitions.cs index e677492580..2dfad7111a 100644 --- a/src/Avalonia.Controls/RowDefinitions.cs +++ b/src/Avalonia.Controls/RowDefinitions.cs @@ -27,7 +27,7 @@ namespace Avalonia.Controls public RowDefinitions(string s) : this() { - AddRange(GridLength.ParseLengths(s, CultureInfo.InvariantCulture).Select(x => new RowDefinition(x))); + AddRange(GridLength.ParseLengths(s).Select(x => new RowDefinition(x))); } } } \ No newline at end of file diff --git a/src/Avalonia.Controls/Utils/GridLayout.cs b/src/Avalonia.Controls/Utils/GridLayout.cs new file mode 100644 index 0000000000..10a94a8c82 --- /dev/null +++ b/src/Avalonia.Controls/Utils/GridLayout.cs @@ -0,0 +1,700 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Globalization; +using System.Linq; +using System.Runtime.CompilerServices; +using Avalonia.Layout; +using JetBrains.Annotations; + +namespace Avalonia.Controls.Utils +{ + /// + /// Contains algorithms that can help to measure and arrange a Grid. + /// + internal class GridLayout + { + /// + /// Initialize a new instance from the column definitions. + /// The instance doesn't care about whether the definitions are rows or columns. + /// It will not calculate the column or row differently. + /// + internal GridLayout([NotNull] ColumnDefinitions columns) + { + if (columns == null) throw new ArgumentNullException(nameof(columns)); + _conventions = columns.Count == 0 + ? new List { new LengthConvention() } + : columns.Select(x => new LengthConvention(x.Width, x.MinWidth, x.MaxWidth)).ToList(); + } + + /// + /// Initialize a new instance from the row definitions. + /// The instance doesn't care about whether the definitions are rows or columns. + /// It will not calculate the column or row differently. + /// + internal GridLayout([NotNull] RowDefinitions rows) + { + if (rows == null) throw new ArgumentNullException(nameof(rows)); + _conventions = rows.Count == 0 + ? new List { new LengthConvention() } + : rows.Select(x => new LengthConvention(x.Height, x.MinHeight, x.MaxHeight)).ToList(); + } + + /// + /// Gets the layout tolerance. If any length offset is less than this value, we will treat them the same. + /// + private const double LayoutTolerance = 1.0 / 256.0; + + /// + /// Gets all the length conventions that come from column/row definitions. + /// These conventions provide cell limitations, such as the expected pixel length, the min/max pixel length and the * count. + /// + [NotNull] + private readonly List _conventions; + + /// + /// Gets all the length conventions that come from the grid children. + /// + [NotNull] + private readonly List _additionalConventions = + new List(); + + /// + /// Appending these elements into the convention list helps lay them out according to their desired sizes. + /// + /// Some elements are not only in a single grid cell, they have one or more column/row spans, + /// and these elements may affect the grid layout especially the measuring procedure. + /// Append these elements into the convention list can help to layout them correctly through + /// their desired size. Only a small subset of children need to be measured before layout starts + /// and they will be called via the callback. + /// + /// The grid children type. + /// + /// Contains the safe column/row index and its span. + /// Notice that we will not verify whether the range is in the column/row count, + /// so you should get the safe column/row info first. + /// + /// + /// This callback will be called if the thinks that a child should be + /// measured first. Usually, these are the children that have the * or Auto length. + /// + internal void AppendMeasureConventions([NotNull] IDictionary source, + [NotNull] Func getDesiredLength) + { + if (source == null) throw new ArgumentNullException(nameof(source)); + if (getDesiredLength == null) throw new ArgumentNullException(nameof(getDesiredLength)); + + // M1/7. Find all the Auto and * length columns/rows. (M1/7 means the 1st procedure of measurement.) + // Only these columns/rows' layout can be affected by the child desired size. + // + // Find all columns/rows that have Auto or * length. We'll measure the children in advance. + // Only these kind of columns/rows will affect the Grid layout. + // Please note: + // - If the column / row has Auto length, the Grid.DesiredSize and the column width + // will be affected by the child's desired size. + // - If the column / row has* length, the Grid.DesiredSize will be affected by the + // child's desired size but the column width not. + + // +-----------------------------------------------------------+ + // | * | A | * | P | A | * | P | * | * | + // +-----------------------------------------------------------+ + // _conventions: | min | max | | | min | | min max | max | + // _additionalC: |<- desired ->| |< desired >| + // _additionalC: |< desired >| |<- desired ->| + + // 寻找所有行列范围中包含 Auto 和 * 的元素,使用全部可用尺寸提前测量。 + // 因为只有这部分元素的布局才会被 Grid 的子元素尺寸影响。 + // 请注意: + // - Auto 长度的行列必定会受到子元素布局影响,会影响到行列的布局长度和 Grid 本身的 DesiredSize; + // - 而对于 * 长度,只有 Grid.DesiredSize 会受到子元素布局影响,而行列长度不会受影响。 + + // Find all the Auto and * length columns/rows. + var found = new Dictionary(); + for (var i = 0; i < _conventions.Count; i++) + { + var index = i; + var convention = _conventions[index]; + if (convention.Length.IsAuto || convention.Length.IsStar) + { + foreach (var pair in source.Where(x => + x.Value.index <= index && index < x.Value.index + x.Value.span)) + { + found[pair.Key] = pair.Value; + } + } + } + + // Append these layout into the additional convention list. + foreach (var pair in found) + { + var t = pair.Key; + var (index, span) = pair.Value; + var desiredLength = getDesiredLength(t); + if (Math.Abs(desiredLength) > LayoutTolerance) + { + _additionalConventions.Add(new AdditionalLengthConvention(index, span, desiredLength)); + } + } + } + + /// + /// Run measure procedure according to the and gets the . + /// + /// + /// The container length. Usually, it is the constraint of the method. + /// + /// + /// The measured result that containing the desired size and all the column/row lengths. + /// + [NotNull, Pure] + internal MeasureResult Measure(double containerLength) + { + // Prepare all the variables that this method needs to use. + var conventions = _conventions.Select(x => x.Clone()).ToList(); + var starCount = conventions.Where(x => x.Length.IsStar).Sum(x => x.Length.Value); + var aggregatedLength = 0.0; + double starUnitLength; + + // M2/7. Aggregate all the pixel lengths. Then we can get the remaining length by `containerLength - aggregatedLength`. + // We mark the aggregated length as "fix" because we can completely determine their values. Same as below. + // + // +-----------------------------------------------------------+ + // | * | A | * | P | A | * | P | * | * | + // +-----------------------------------------------------------+ + // |#fix#| |#fix#| + // + // 将全部的固定像素长度的行列长度累加。这样,containerLength - aggregatedLength 便能得到剩余长度。 + // 我们会将所有能够确定下长度的行列标记为 fix。下同。 + // 请注意: + // - 我们并没有直接从 containerLength 一直减下去,而是使用 aggregatedLength 进行累加,是因为无穷大相减得到的是 NaN,不利于后续计算。 + + aggregatedLength += conventions.Where(x => x.Length.IsAbsolute).Sum(x => x.Length.Value); + + // M3/7. Fix all the * lengths that have reached the minimum. + // + // +-----------------------------------------------------------+ + // | * | A | * | P | A | * | P | * | * | + // +-----------------------------------------------------------+ + // | min | max | | | min | | min max | max | + // | fix | |#fix#| fix | + + var shouldTestStarMin = true; + while (shouldTestStarMin) + { + // Calculate the unit * length to estimate the length of each column/row that has * length. + // Under this estimated length, check if there is a minimum value that has a length less than its constraint. + // If there is such a *, then fix the size of this cell, and then loop it again until there is no * that can be constrained by the minimum value. + // + // 计算单位 * 的长度,以便预估出每一个 * 行列的长度。 + // 在此预估的长度下,从前往后寻找是否存在某个 * 长度已经小于其约束的最小值。 + // 如果发现存在这样的 *,那么将此单元格的尺寸固定下来(Fix),然后循环重来,直至再也没有能被最小值约束的 *。 + var @fixed = false; + starUnitLength = (containerLength - aggregatedLength) / starCount; + foreach (var convention in conventions.Where(x => x.Length.IsStar)) + { + var (star, min) = (convention.Length.Value, convention.MinLength); + var starLength = star * starUnitLength; + if (starLength < min) + { + convention.Fix(min); + starLength = min; + aggregatedLength += starLength; + starCount -= star; + @fixed = true; + break; + } + } + + shouldTestStarMin = @fixed; + } + + // M4/7. Determine the absolute pixel size of all columns/rows that have an Auto length. + // + // +-----------------------------------------------------------+ + // | * | A | * | P | A | * | P | * | * | + // +-----------------------------------------------------------+ + // | min | max | | | min | | min max | max | + // |#fix#| | fix |#fix#| fix | fix | + + var shouldTestAuto = true; + while (shouldTestAuto) + { + var @fixed = false; + starUnitLength = (containerLength - aggregatedLength) / starCount; + for (var i = 0; i < conventions.Count; i++) + { + var convention = conventions[i]; + if (!convention.Length.IsAuto) + { + continue; + } + + var more = ApplyAdditionalConventionsForAuto(conventions, i, starUnitLength); + convention.Fix(more); + aggregatedLength += more; + @fixed = true; + break; + } + + shouldTestAuto = @fixed; + } + + // M5/7. Expand the stars according to the additional conventions (usually the child desired length). + // We can't fix this kind of length, so we just mark them as desired (des). + // + // +-----------------------------------------------------------+ + // | * | A | * | P | A | * | P | * | * | + // +-----------------------------------------------------------+ + // | min | max | | | min | | min max | max | + // |#des#| fix |#des#| fix | fix | fix | fix | #des# |#des#| + + var desiredStarMin = AggregateAdditionalConventionsForStars(conventions); + aggregatedLength += desiredStarMin; + + // M6/7. Determine the desired length of the grid for current container length. Its value is stored in desiredLength. + // Assume if the container has infinite length, the grid desired length is stored in greedyDesiredLength. + // + // +-----------------------------------------------------------+ + // | * | A | * | P | A | * | P | * | * | + // +-----------------------------------------------------------+ + // | min | max | | | min | | min max | max | + // |#des#| fix |#des#| fix | fix | fix | fix | #des# |#des#| + // Note: This table will be stored as the intermediate result into the MeasureResult and it will be reused by Arrange procedure. + // + // desiredLength = Math.Max(0.0, des + fix + des + fix + fix + fix + fix + des + des) + // greedyDesiredLength = des + fix + des + fix + fix + fix + fix + des + des + + var desiredLength = containerLength - aggregatedLength >= 0.0 ? aggregatedLength : containerLength; + var greedyDesiredLength = aggregatedLength; + + // M7/7. Expand all the rest stars. These stars have no conventions or only have + // max value they can be expanded from zero to constraint. + // + // +-----------------------------------------------------------+ + // | * | A | * | P | A | * | P | * | * | + // +-----------------------------------------------------------+ + // | min | max | | | min | | min max | max | + // |#fix#| fix |#fix#| fix | fix | fix | fix | #fix# |#fix#| + // Note: This table will be stored as the final result into the MeasureResult. + + var dynamicConvention = ExpandStars(conventions, containerLength); + Clip(dynamicConvention, containerLength); + + // Returns the measuring result. + return new MeasureResult(containerLength, desiredLength, greedyDesiredLength, + conventions, dynamicConvention); + } + + /// + /// Run arrange procedure according to the and gets the . + /// + /// + /// The container length. Usually, it is the finalSize of the method. + /// + /// + /// The result that the measuring procedure returns. If it is null, a new measure procedure will run. + /// + /// + /// The measured result that containing the desired size and all the column/row length. + /// + [NotNull, Pure] + public ArrangeResult Arrange(double finalLength, [CanBeNull] MeasureResult measure) + { + measure = measure ?? Measure(finalLength); + + // If the arrange final length does not equal to the measure length, we should measure again. + if (finalLength - measure.ContainerLength > LayoutTolerance) + { + // If the final length is larger, we will rerun the whole measure. + measure = Measure(finalLength); + } + else if (finalLength - measure.ContainerLength < -LayoutTolerance) + { + // If the final length is smaller, we measure the M6/6 procedure only. + var dynamicConvention = ExpandStars(measure.LeanLengthList, finalLength); + measure = new MeasureResult(finalLength, measure.DesiredLength, measure.GreedyDesiredLength, + measure.LeanLengthList, dynamicConvention); + } + + return new ArrangeResult(measure.LengthList); + } + + /// + /// Use the to calculate the fixed length of the Auto column/row. + /// + /// The convention list that all the * with minimum length are fixed. + /// The column/row index that should be fixed. + /// The unit * length for the current rest length. + /// The final length of the Auto length column/row. + [Pure] + private double ApplyAdditionalConventionsForAuto(IReadOnlyList conventions, + int index, double starUnitLength) + { + // 1. Calculate all the * length with starUnitLength. + // 2. Exclude all the fixed length and all the * length. + // 3. Compare the rest of the desired length and the convention. + // +-----------------+ + // | * | A | * | + // +-----------------+ + // | exl | | exl | + // |< desired >| + // |< desired >| + + var more = 0.0; + foreach (var additional in _additionalConventions) + { + // If the additional convention's last column/row contains the Auto column/row, try to determine the Auto column/row length. + if (index == additional.Index + additional.Span - 1) + { + var min = Enumerable.Range(additional.Index, additional.Span) + .Select(x => + { + var c = conventions[x]; + if (c.Length.IsAbsolute) return c.Length.Value; + if (c.Length.IsStar) return c.Length.Value * starUnitLength; + return 0.0; + }).Sum(); + more = Math.Max(additional.Min - min, more); + } + } + + return Math.Min(conventions[index].MaxLength, more); + } + + /// + /// Calculate the total desired length of all the * length. + /// Bug Warning: + /// - The behavior of this method is undefined! Different UI Frameworks have different behaviors. + /// - We ignore all the span columns/rows and just take single cells into consideration. + /// + /// All the conventions that have almost been fixed except the rest *. + /// The total desired length of all the * length. + [Pure, MethodImpl(MethodImplOptions.AggressiveInlining)] + private double AggregateAdditionalConventionsForStars( + IReadOnlyList conventions) + { + // 1. Determine all one-span column's desired widths or row's desired heights. + // 2. Order the multi-span conventions by its last index + // (Notice that the sorted data is much smaller than the source.) + // 3. Determine each multi-span last index by calculating the maximun desired size. + + // Before we determine the behavior of this method, we just aggregate the one-span * columns. + + var fixedLength = conventions.Where(x => x.Length.IsAbsolute).Sum(x => x.Length.Value); + + // Prepare a lengthList variable indicating the fixed length of each column/row. + var lengthList = conventions.Select(x => x.Length.IsAbsolute ? x.Length.Value : 0.0).ToList(); + foreach (var group in _additionalConventions + .Where(x => x.Span == 1 && conventions[x.Index].Length.IsStar) + .ToLookup(x => x.Index)) + { + lengthList[group.Key] = Math.Max(lengthList[group.Key], group.Max(x => x.Min)); + } + + // Now the lengthList is fixed by every one-span columns/rows. + // Then we should determine the multi-span column's/row's length. + foreach (var group in _additionalConventions + .Where(x => x.Span > 1) + .ToLookup(x => x.Index + x.Span - 1) + // Order the multi-span columns/rows by last index. + .OrderBy(x => x.Key)) + { + var length = group.Max(x => x.Min - Enumerable.Range(x.Index, x.Span - 1).Sum(r => lengthList[r])); + lengthList[group.Key] = Math.Max(lengthList[group.Key], length > 0 ? length : 0); + } + + return lengthList.Sum() - fixedLength; + } + + /// + /// This method implements the last procedure (M7/7) of measure. + /// It expands all the * length to the fixed length according to the . + /// + /// All the conventions that have almost been fixed except the remaining *. + /// The container length. + /// The final pixel length list. + [Pure] + private static List ExpandStars(IEnumerable conventions, double constraint) + { + // Initial. + var dynamicConvention = conventions.Select(x => x.Clone()).ToList(); + constraint -= dynamicConvention.Where(x => x.Length.IsAbsolute).Sum(x => x.Length.Value); + var starUnitLength = 0.0; + + // M6/6. + if (constraint >= 0) + { + var starCount = dynamicConvention.Where(x => x.Length.IsStar).Sum(x => x.Length.Value); + + var shouldTestStarMax = true; + while (shouldTestStarMax) + { + var @fixed = false; + starUnitLength = constraint / starCount; + foreach (var convention in dynamicConvention.Where(x => + x.Length.IsStar && !double.IsPositiveInfinity(x.MaxLength))) + { + var (star, max) = (convention.Length.Value, convention.MaxLength); + var starLength = star * starUnitLength; + if (starLength > max) + { + convention.Fix(max); + starLength = max; + constraint -= starLength; + starCount -= star; + @fixed = true; + break; + } + } + + shouldTestStarMax = @fixed; + } + } + + Debug.Assert(dynamicConvention.All(x => !x.Length.IsAuto)); + + var starUnit = starUnitLength; + var result = dynamicConvention.Select(x => + { + if (x.Length.IsStar) + { + return double.IsInfinity(starUnit) ? double.PositiveInfinity : starUnit * x.Length.Value; + } + + return x.Length.Value; + }).ToList(); + + return result; + } + + /// + /// If the container length is not infinity. It may be not enough to contain all the columns/rows. + /// We should clip the columns/rows that have been out of the container bounds. + /// Note: This method may change the items value of . + /// + /// A list of all the column widths and row heights with a fixed pixel length + /// the container length. It can be positive infinity. + private static void Clip([NotNull] IList lengthList, double constraint) + { + if (double.IsInfinity(constraint)) + { + return; + } + + var measureLength = 0.0; + for (var i = 0; i < lengthList.Count; i++) + { + var length = lengthList[i]; + if (constraint - measureLength > length) + { + measureLength += length; + } + else + { + lengthList[i] = constraint - measureLength; + measureLength = constraint; + } + } + } + + /// + /// Contains the convention of each column/row. + /// This is mostly the same as or . + /// We use this because we can treat the column and the row the same. + /// + [DebuggerDisplay("{" + nameof(DebuggerDisplay) + ",nq}")] + internal class LengthConvention : ICloneable + { + /// + /// Initialize a new instance of . + /// + public LengthConvention() + { + Length = new GridLength(1.0, GridUnitType.Star); + MinLength = 0.0; + MaxLength = double.PositiveInfinity; + } + + /// + /// Initialize a new instance of . + /// + public LengthConvention(GridLength length, double minLength, double maxLength) + { + Length = length; + MinLength = minLength; + MaxLength = maxLength; + if (length.IsAbsolute) + { + _isFixed = true; + } + } + + /// + /// Gets the of a column or a row. + /// + internal GridLength Length { get; private set; } + + /// + /// Gets the minimum convention for a column or a row. + /// + internal double MinLength { get; } + + /// + /// Gets the maximum convention for a column or a row. + /// + internal double MaxLength { get; } + + /// + /// Fix the . + /// If all columns/rows are fixed, we can get the size of all columns/rows in pixels. + /// + /// + /// The pixel length that should be used to fix the convention. + /// + /// + /// If the convention is pixel length, this exception will throw. + /// + public void Fix(double pixel) + { + if (_isFixed) + { + throw new InvalidOperationException("Cannot fix the length convention if it is fixed."); + } + + Length = new GridLength(pixel); + _isFixed = true; + } + + /// + /// Gets a value that indicates whether this convention is fixed. + /// + private bool _isFixed; + + /// + /// Helps the debugger to display the intermediate column/row calculation result. + /// + private string DebuggerDisplay => + $"{(_isFixed ? Length.Value.ToString(CultureInfo.InvariantCulture) : (Length.GridUnitType == GridUnitType.Auto ? "Auto" : $"{Length.Value}*"))}, ∈[{MinLength}, {MaxLength}]"; + + /// + object ICloneable.Clone() => Clone(); + + /// + /// Get a deep copy of this convention list. + /// We need this because we want to store some intermediate states. + /// + internal LengthConvention Clone() => new LengthConvention(Length, MinLength, MaxLength); + } + + /// + /// Contains the convention that comes from the grid children. + /// Some children span multiple columns or rows, so even a simple column/row can have multiple conventions. + /// + [DebuggerDisplay("{" + nameof(DebuggerDisplay) + ",nq}")] + internal struct AdditionalLengthConvention + { + /// + /// Initialize a new instance of . + /// + public AdditionalLengthConvention(int index, int span, double min) + { + Index = index; + Span = span; + Min = min; + } + + /// + /// Gets the start index of this additional convention. + /// + public int Index { get; } + + /// + /// Gets the span of this additional convention. + /// + public int Span { get; } + + /// + /// Gets the minimum length of this additional convention. + /// This value is usually provided by the child's desired length. + /// + public double Min { get; } + + /// + /// Helps the debugger to display the intermediate column/row calculation result. + /// + private string DebuggerDisplay => + $"{{{string.Join(",", Enumerable.Range(Index, Span))}}}, ∈[{Min},∞)"; + } + + /// + /// Stores the result of the measuring procedure. + /// This result can be used to measure children and assign the desired size. + /// Passing this result to can reduce calculation. + /// + [DebuggerDisplay("{" + nameof(LengthList) + ",nq}")] + internal class MeasureResult + { + /// + /// Initialize a new instance of . + /// + internal MeasureResult(double containerLength, double desiredLength, double greedyDesiredLength, + IReadOnlyList leanConventions, IReadOnlyList expandedConventions) + { + ContainerLength = containerLength; + DesiredLength = desiredLength; + GreedyDesiredLength = greedyDesiredLength; + LeanLengthList = leanConventions; + LengthList = expandedConventions; + } + + /// + /// Gets the container length for this result. + /// This property will be used by to determine whether to measure again or not. + /// + public double ContainerLength { get; } + + /// + /// Gets the desired length of this result. + /// Just return this value as the desired size in . + /// + public double DesiredLength { get; } + + /// + /// Gets the desired length if the container has infinite length. + /// + public double GreedyDesiredLength { get; } + + /// + /// Contains the column/row calculation intermediate result. + /// This value is used by for reducing repeat calculation. + /// + public IReadOnlyList LeanLengthList { get; } + + /// + /// Gets the length list for each column/row. + /// + public IReadOnlyList LengthList { get; } + } + + /// + /// Stores the result of the measuring procedure. + /// This result can be used to arrange children and assign the render size. + /// + [DebuggerDisplay("{" + nameof(LengthList) + ",nq}")] + internal class ArrangeResult + { + /// + /// Initialize a new instance of . + /// + internal ArrangeResult(IReadOnlyList lengthList) + { + LengthList = lengthList; + } + + /// + /// Gets the length list for each column/row. + /// + public IReadOnlyList LengthList { get; } + } + } +} diff --git a/src/Avalonia.Controls/Window.cs b/src/Avalonia.Controls/Window.cs index c66209d3c6..91d6c874d3 100644 --- a/src/Avalonia.Controls/Window.cs +++ b/src/Avalonia.Controls/Window.cs @@ -95,6 +95,9 @@ namespace Avalonia.Controls o => o.WindowStartupLocation, (o, v) => o.WindowStartupLocation = v); + public static readonly StyledProperty CanResizeProperty = + AvaloniaProperty.Register(nameof(CanResize), true); + private readonly NameScope _nameScope = new NameScope(); private object _dialogResult; private readonly Size _maxPlatformClientSize; @@ -113,6 +116,8 @@ namespace Avalonia.Controls ShowInTaskbarProperty.Changed.AddClassHandler((w, e) => w.PlatformImpl?.ShowTaskbarIcon((bool)e.NewValue)); IconProperty.Changed.AddClassHandler((s, e) => s.PlatformImpl?.SetIcon(((WindowIcon)e.NewValue).PlatformImpl)); + + CanResizeProperty.Changed.AddClassHandler((w, e) => w.PlatformImpl?.CanResize((bool)e.NewValue)); } /// @@ -208,6 +213,17 @@ namespace Avalonia.Controls } } + /// + /// Enables or disables resizing of the window. + /// Note that if is set to False then this property + /// has no effect and should be treated as a recommendation for the user setting HasSystemDecorations. + /// + public bool CanResize + { + get { return GetValue(CanResizeProperty); } + set { SetValue(CanResizeProperty, value); } + } + /// /// Gets or sets the icon of the window. /// diff --git a/src/Avalonia.Controls/WindowBase.cs b/src/Avalonia.Controls/WindowBase.cs index 50068d280d..c427df1c26 100644 --- a/src/Avalonia.Controls/WindowBase.cs +++ b/src/Avalonia.Controls/WindowBase.cs @@ -47,6 +47,11 @@ namespace Avalonia.Controls { IsVisibleProperty.OverrideDefaultValue(false); IsVisibleProperty.Changed.AddClassHandler(x => x.IsVisibleChanged); + + MinWidthProperty.Changed.AddClassHandler((w, e) => w.PlatformImpl?.SetMinMaxSize(new Size((double)e.NewValue, w.MinHeight), new Size(w.MaxWidth, w.MaxHeight))); + MinHeightProperty.Changed.AddClassHandler((w, e) => w.PlatformImpl?.SetMinMaxSize(new Size(w.MinWidth, (double)e.NewValue), new Size(w.MaxWidth, w.MaxHeight))); + MaxWidthProperty.Changed.AddClassHandler((w, e) => w.PlatformImpl?.SetMinMaxSize(new Size(w.MinWidth, w.MinHeight), new Size((double)e.NewValue, w.MaxHeight))); + MaxHeightProperty.Changed.AddClassHandler((w, e) => w.PlatformImpl?.SetMinMaxSize(new Size(w.MinWidth, w.MinHeight), new Size(w.MaxWidth, (double)e.NewValue))); } public WindowBase(IWindowBaseImpl impl) : this(impl, AvaloniaLocator.Current) diff --git a/src/Avalonia.DesignerSupport/DesignWindowLoader.cs b/src/Avalonia.DesignerSupport/DesignWindowLoader.cs index 5235beee0f..d1958ac9bf 100644 --- a/src/Avalonia.DesignerSupport/DesignWindowLoader.cs +++ b/src/Avalonia.DesignerSupport/DesignWindowLoader.cs @@ -1,6 +1,7 @@ using System; using System.IO; using System.Linq; +using System.Reflection; using System.Text; using Avalonia.Controls; using Avalonia.Controls.Platform; @@ -30,7 +31,8 @@ namespace Avalonia.DesignerSupport new Uri("resm:Fake.xaml?assembly=" + Path.GetFileNameWithoutExtension(assemblyPath)); } - var loaded = loader.Load(stream, null, baseUri); + var localAsm = assemblyPath != null ? Assembly.LoadFile(Path.GetFullPath(assemblyPath)) : null; + var loaded = loader.Load(stream, localAsm, null, baseUri); var styles = loaded as Styles; if (styles != null) { diff --git a/src/Avalonia.DesignerSupport/DesignerAssist.cs b/src/Avalonia.DesignerSupport/DesignerAssist.cs index 65c4c14d83..f6f704b838 100644 --- a/src/Avalonia.DesignerSupport/DesignerAssist.cs +++ b/src/Avalonia.DesignerSupport/DesignerAssist.cs @@ -24,7 +24,7 @@ namespace Avalonia.DesignerSupport var loader = new AvaloniaXamlLoader(); var baseLight = (IStyle)loader.Load( - new Uri("resm:Avalonia.Themes.Default.Accents.BaseLight.xaml?assembly=Avalonia.Themes.Default")); + new Uri("resm:Avalonia.Themes.Default.Accents.BaseLight.xaml?assembly=Avalonia.Themes.Default"), null); Styles.Add(baseLight); } } diff --git a/src/Avalonia.DesignerSupport/Properties/AssemblyInfo.cs b/src/Avalonia.DesignerSupport/Properties/AssemblyInfo.cs deleted file mode 100644 index 1c43bc6d1f..0000000000 --- a/src/Avalonia.DesignerSupport/Properties/AssemblyInfo.cs +++ /dev/null @@ -1,8 +0,0 @@ -// Copyright (c) The Avalonia Project. All rights reserved. -// Licensed under the MIT license. See licence.md file in the project root for full license information. - -using System.Reflection; -using Avalonia.Metadata; - -[assembly: AssemblyTitle("Avalonia.Application")] -[assembly: XmlnsDefinition("https://github.com/avaloniaui", "Avalonia")] diff --git a/src/Avalonia.DesignerSupport/Remote/PreviewerWindowImpl.cs b/src/Avalonia.DesignerSupport/Remote/PreviewerWindowImpl.cs index fc9541abb7..9750b46aa2 100644 --- a/src/Avalonia.DesignerSupport/Remote/PreviewerWindowImpl.cs +++ b/src/Avalonia.DesignerSupport/Remote/PreviewerWindowImpl.cs @@ -67,6 +67,10 @@ namespace Avalonia.DesignerSupport.Remote RenderIfNeeded(); } + public void SetMinMaxSize(Size minSize, Size maxSize) + { + } + public IScreenImpl Screen { get; } = new ScreenStub(); public void Activate() @@ -93,5 +97,9 @@ namespace Avalonia.DesignerSupport.Remote public void ShowTaskbarIcon(bool value) { } + + public void CanResize(bool value) + { + } } } \ No newline at end of file diff --git a/src/Avalonia.DesignerSupport/Remote/Stubs.cs b/src/Avalonia.DesignerSupport/Remote/Stubs.cs index 560425286e..ee8569d748 100644 --- a/src/Avalonia.DesignerSupport/Remote/Stubs.cs +++ b/src/Avalonia.DesignerSupport/Remote/Stubs.cs @@ -78,6 +78,10 @@ namespace Avalonia.DesignerSupport.Remote public IScreenImpl Screen { get; } = new ScreenStub(); + public void SetMinMaxSize(Size minSize, Size maxSize) + { + } + public void SetTitle(string title) { } @@ -95,6 +99,10 @@ namespace Avalonia.DesignerSupport.Remote public void ShowTaskbarIcon(bool value) { } + + public void CanResize(bool value) + { + } } class ClipboardStub : IClipboard diff --git a/src/Avalonia.Diagnostics/Avalonia.Diagnostics.csproj b/src/Avalonia.Diagnostics/Avalonia.Diagnostics.csproj index 5f8e25269b..a2ff0ecf02 100644 --- a/src/Avalonia.Diagnostics/Avalonia.Diagnostics.csproj +++ b/src/Avalonia.Diagnostics/Avalonia.Diagnostics.csproj @@ -1,29 +1,6 @@  netstandard2.0 - false - - - true - full - false - bin\Debug\ - TRACE;DEBUG - prompt - 4 - bin\Debug\Avalonia.Diagnostics.xml - CS1591 - - - pdbonly - true - bin\Release\ - TRACE - prompt - 4 - bin\Release\Avalonia.Diagnostics.xml - CS1591 - true @@ -37,17 +14,7 @@ - - - - Properties\SharedAssemblyInfo.cs - - - %(Filename) - - - Designer - - + + \ No newline at end of file diff --git a/src/Avalonia.Diagnostics/Properties/AssemblyInfo.cs b/src/Avalonia.Diagnostics/Properties/AssemblyInfo.cs deleted file mode 100644 index f4100ef48b..0000000000 --- a/src/Avalonia.Diagnostics/Properties/AssemblyInfo.cs +++ /dev/null @@ -1,6 +0,0 @@ -// Copyright (c) The Avalonia Project. All rights reserved. -// Licensed under the MIT license. See licence.md file in the project root for full license information. - -using System.Reflection; - -[assembly: AssemblyTitle("Avalonia.Diagnostics")] diff --git a/src/Avalonia.Diagnostics/Views/TreePageView.xaml b/src/Avalonia.Diagnostics/Views/TreePageView.xaml index 244afda135..a715ca6fc5 100644 --- a/src/Avalonia.Diagnostics/Views/TreePageView.xaml +++ b/src/Avalonia.Diagnostics/Views/TreePageView.xaml @@ -1,5 +1,5 @@ + xmlns:vm="clr-namespace:Avalonia.Diagnostics.ViewModels"> diff --git a/src/Avalonia.DotNetCoreRuntime/Avalonia.DotNetCoreRuntime.csproj b/src/Avalonia.DotNetCoreRuntime/Avalonia.DotNetCoreRuntime.csproj index a7586ac7ac..5f40d347b7 100644 --- a/src/Avalonia.DotNetCoreRuntime/Avalonia.DotNetCoreRuntime.csproj +++ b/src/Avalonia.DotNetCoreRuntime/Avalonia.DotNetCoreRuntime.csproj @@ -4,14 +4,6 @@ false $(DefineConstants);DOTNETCORE - - bin\$(Configuration)\Avalonia.DotNetCoreRuntime.xml - - - - Properties\SharedAssemblyInfo.cs - - @@ -20,7 +12,7 @@ - + \ No newline at end of file diff --git a/src/Avalonia.DotNetCoreRuntime/Properties/AssemblyInfo.cs b/src/Avalonia.DotNetCoreRuntime/Properties/AssemblyInfo.cs deleted file mode 100644 index 51ec3975c9..0000000000 --- a/src/Avalonia.DotNetCoreRuntime/Properties/AssemblyInfo.cs +++ /dev/null @@ -1,6 +0,0 @@ -// Copyright (c) The Avalonia Project. All rights reserved. -// Licensed under the MIT license. See licence.md file in the project root for full license information. - -using System.Reflection; - -[assembly: AssemblyTitle("Avalonia.DotNetCoreRuntime")] diff --git a/src/Avalonia.DotNetFrameworkRuntime/Avalonia.DotNetFrameworkRuntime.csproj b/src/Avalonia.DotNetFrameworkRuntime/Avalonia.DotNetFrameworkRuntime.csproj index dc4a06d2a7..eca5b89419 100644 --- a/src/Avalonia.DotNetFrameworkRuntime/Avalonia.DotNetFrameworkRuntime.csproj +++ b/src/Avalonia.DotNetFrameworkRuntime/Avalonia.DotNetFrameworkRuntime.csproj @@ -7,15 +7,12 @@ true - - Properties\SharedAssemblyInfo.cs - - + - + \ No newline at end of file diff --git a/src/Avalonia.HtmlRenderer/Avalonia.HtmlRenderer.csproj b/src/Avalonia.HtmlRenderer/Avalonia.HtmlRenderer.csproj index fde486fa12..520687c3ef 100644 --- a/src/Avalonia.HtmlRenderer/Avalonia.HtmlRenderer.csproj +++ b/src/Avalonia.HtmlRenderer/Avalonia.HtmlRenderer.csproj @@ -6,124 +6,10 @@ false CS0436 - - true - full - false - bin\Debug\ - TRACE;DEBUG;PCL - prompt - 4 - - - pdbonly - true - bin\Release\ - TRACE;PCL - prompt - 4 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - @@ -134,5 +20,6 @@ + \ No newline at end of file diff --git a/src/Avalonia.Input/Avalonia.Input.csproj b/src/Avalonia.Input/Avalonia.Input.csproj index c82601ac2c..86d081abb8 100644 --- a/src/Avalonia.Input/Avalonia.Input.csproj +++ b/src/Avalonia.Input/Avalonia.Input.csproj @@ -1,29 +1,6 @@  netstandard2.0 - false - - - true - full - false - bin\Debug\ - DEBUG;TRACE - prompt - 4 - bin\Debug\Avalonia.Input.xml - CS1591 - - - pdbonly - true - bin\Release\ - TRACE - prompt - 4 - bin\Release\Avalonia.Input.xml - CS1591 - true @@ -31,11 +8,6 @@ - - - - Properties\SharedAssemblyInfo.cs - - + \ No newline at end of file diff --git a/src/Avalonia.Input/DragDropDevice.cs b/src/Avalonia.Input/DragDropDevice.cs index 2615e3a212..9fb100371f 100644 --- a/src/Avalonia.Input/DragDropDevice.cs +++ b/src/Avalonia.Input/DragDropDevice.cs @@ -19,11 +19,11 @@ namespace Avalonia.Input return null; } - private DragDropEffects RaiseDragEvent(Interactive target, RoutedEvent routedEvent, DragDropEffects operation, IDataObject data) + private DragDropEffects RaiseDragEvent(Interactive target, IInputElement inputRoot, Point point, RoutedEvent routedEvent, DragDropEffects operation, IDataObject data) { if (target == null) return DragDropEffects.None; - var args = new DragEventArgs(routedEvent, data) + var args = new DragEventArgs(routedEvent, data, target, inputRoot.TranslatePoint(point, target)) { RoutedEvent = routedEvent, DragEffects = operation @@ -35,7 +35,7 @@ namespace Avalonia.Input private DragDropEffects DragEnter(IInputElement inputRoot, Point point, IDataObject data, DragDropEffects effects) { _lastTarget = GetTarget(inputRoot, point); - return RaiseDragEvent(_lastTarget, DragDrop.DragEnterEvent, effects, data); + return RaiseDragEvent(_lastTarget, inputRoot, point, DragDrop.DragEnterEvent, effects, data); } private DragDropEffects DragOver(IInputElement inputRoot, Point point, IDataObject data, DragDropEffects effects) @@ -43,13 +43,13 @@ namespace Avalonia.Input var target = GetTarget(inputRoot, point); if (target == _lastTarget) - return RaiseDragEvent(target, DragDrop.DragOverEvent, effects, data); + return RaiseDragEvent(target, inputRoot, point, DragDrop.DragOverEvent, effects, data); try { if (_lastTarget != null) _lastTarget.RaiseEvent(new RoutedEventArgs(DragDrop.DragLeaveEvent)); - return RaiseDragEvent(target, DragDrop.DragEnterEvent, effects, data); + return RaiseDragEvent(target, inputRoot, point, DragDrop.DragEnterEvent, effects, data); } finally { @@ -75,7 +75,7 @@ namespace Avalonia.Input { try { - return RaiseDragEvent(_lastTarget, DragDrop.DropEvent, effects, data); + return RaiseDragEvent(_lastTarget, inputRoot, point, DragDrop.DropEvent, effects, data); } finally { diff --git a/src/Avalonia.Input/DragEventArgs.cs b/src/Avalonia.Input/DragEventArgs.cs index 12d5a8941e..669fd846a1 100644 --- a/src/Avalonia.Input/DragEventArgs.cs +++ b/src/Avalonia.Input/DragEventArgs.cs @@ -1,17 +1,40 @@ -using Avalonia.Interactivity; +using System; +using Avalonia.Interactivity; +using Avalonia.VisualTree; namespace Avalonia.Input { public class DragEventArgs : RoutedEventArgs { + private Interactive _target; + private Point _targetLocation; + public DragDropEffects DragEffects { get; set; } public IDataObject Data { get; private set; } - public DragEventArgs(RoutedEvent routedEvent, IDataObject data) + public Point GetPosition(IVisual relativeTo) + { + var point = new Point(0, 0); + + if (relativeTo == null) + { + throw new ArgumentNullException(nameof(relativeTo)); + } + + if (_target != null) + { + point = _target.TranslatePoint(_targetLocation, relativeTo); + } + return point; + } + + public DragEventArgs(RoutedEvent routedEvent, IDataObject data, Interactive target, Point targetLocation) : base(routedEvent) { this.Data = data; + this._target = target; + this._targetLocation = targetLocation; } } diff --git a/src/Avalonia.Input/Properties/AssemblyInfo.cs b/src/Avalonia.Input/Properties/AssemblyInfo.cs index 64b8e8f1a3..7025965f83 100644 --- a/src/Avalonia.Input/Properties/AssemblyInfo.cs +++ b/src/Avalonia.Input/Properties/AssemblyInfo.cs @@ -4,5 +4,4 @@ using System.Reflection; using Avalonia.Metadata; -[assembly: AssemblyTitle("Avalonia.Input")] [assembly: XmlnsDefinition("https://github.com/avaloniaui", "Avalonia.Input")] diff --git a/src/Avalonia.Interactivity/Avalonia.Interactivity.csproj b/src/Avalonia.Interactivity/Avalonia.Interactivity.csproj index 4f6f9ad6fe..66f1e8cc26 100644 --- a/src/Avalonia.Interactivity/Avalonia.Interactivity.csproj +++ b/src/Avalonia.Interactivity/Avalonia.Interactivity.csproj @@ -1,29 +1,6 @@  netstandard2.0 - false - - - true - full - false - bin\Debug\ - DEBUG;TRACE - prompt - 4 - bin\Debug\Avalonia.Interactivity.xml - CS1591 - - - pdbonly - true - bin\Release\ - TRACE - prompt - 4 - bin\Release\Avalonia.Interactivity.xml - CS1591 - true @@ -31,10 +8,7 @@ - - - Properties\SharedAssemblyInfo.cs - - - \ No newline at end of file + + + diff --git a/src/Avalonia.Interactivity/Properties/AssemblyInfo.cs b/src/Avalonia.Interactivity/Properties/AssemblyInfo.cs deleted file mode 100644 index 27c8765239..0000000000 --- a/src/Avalonia.Interactivity/Properties/AssemblyInfo.cs +++ /dev/null @@ -1,6 +0,0 @@ -// Copyright (c) The Avalonia Project. All rights reserved. -// Licensed under the MIT license. See licence.md file in the project root for full license information. - -using System.Reflection; - -[assembly: AssemblyTitle("Avalonia.Interactive")] diff --git a/src/Avalonia.Layout/Avalonia.Layout.csproj b/src/Avalonia.Layout/Avalonia.Layout.csproj index 58c18921f5..befa22ec46 100644 --- a/src/Avalonia.Layout/Avalonia.Layout.csproj +++ b/src/Avalonia.Layout/Avalonia.Layout.csproj @@ -1,39 +1,11 @@  netstandard2.0 - false - - - true - full - false - bin\Debug\ - DEBUG;TRACE - prompt - 4 - bin\Debug\Avalonia.Layout.xml - CS1591 - - - pdbonly - true - bin\Release\ - TRACE - prompt - 4 - bin\Release\Avalonia.Layout.xml - CS1591 - true - - - - Properties\SharedAssemblyInfo.cs - - + \ No newline at end of file diff --git a/src/Avalonia.Layout/Properties/AssemblyInfo.cs b/src/Avalonia.Layout/Properties/AssemblyInfo.cs deleted file mode 100644 index 126893e189..0000000000 --- a/src/Avalonia.Layout/Properties/AssemblyInfo.cs +++ /dev/null @@ -1,6 +0,0 @@ -// Copyright (c) The Avalonia Project. All rights reserved. -// Licensed under the MIT license. See licence.md file in the project root for full license information. - -using System.Reflection; - -[assembly: AssemblyTitle("Avalonia.Layout")] diff --git a/src/Avalonia.Logging.Serilog/Avalonia.Logging.Serilog.csproj b/src/Avalonia.Logging.Serilog/Avalonia.Logging.Serilog.csproj index 4d9d141d0d..456cd8c568 100644 --- a/src/Avalonia.Logging.Serilog/Avalonia.Logging.Serilog.csproj +++ b/src/Avalonia.Logging.Serilog/Avalonia.Logging.Serilog.csproj @@ -1,34 +1,10 @@  netstandard2.0 - false - - true - full - false - bin\Debug\ - DEBUG;TRACE - prompt - 4 - bin\Debug\Avalonia.Logging.Serilog.xml - - - pdbonly - true - bin\Release\ - TRACE - prompt - 4 - bin\Release\Avalonia.Logging.Serilog.xml - true - - - - - + \ No newline at end of file diff --git a/src/Avalonia.Logging.Serilog/Properties/AssemblyInfo.cs b/src/Avalonia.Logging.Serilog/Properties/AssemblyInfo.cs deleted file mode 100644 index 35b2e48f70..0000000000 --- a/src/Avalonia.Logging.Serilog/Properties/AssemblyInfo.cs +++ /dev/null @@ -1,3 +0,0 @@ -using System.Reflection; - -[assembly: AssemblyTitle("Avalonia.Serilog")] diff --git a/src/Avalonia.ReactiveUI/Avalonia.ReactiveUI.csproj b/src/Avalonia.ReactiveUI/Avalonia.ReactiveUI.csproj index b7ad36f8b2..ae1ef60464 100644 --- a/src/Avalonia.ReactiveUI/Avalonia.ReactiveUI.csproj +++ b/src/Avalonia.ReactiveUI/Avalonia.ReactiveUI.csproj @@ -1,10 +1,8 @@  netstandard2.0 - false - diff --git a/src/Avalonia.ReactiveUI/Properties/AssemblyInfo.cs b/src/Avalonia.ReactiveUI/Properties/AssemblyInfo.cs deleted file mode 100644 index c9ead6f6e6..0000000000 --- a/src/Avalonia.ReactiveUI/Properties/AssemblyInfo.cs +++ /dev/null @@ -1,6 +0,0 @@ -// Copyright (c) The Avalonia Project. All rights reserved. -// Licensed under the MIT license. See licence.md file in the project root for full license information. - -using System.Reflection; - -[assembly: AssemblyTitle("Avalonia.ReactiveUI")] diff --git a/src/Avalonia.Styling/Avalonia.Styling.csproj b/src/Avalonia.Styling/Avalonia.Styling.csproj index b8083b52db..3c9dce8dca 100644 --- a/src/Avalonia.Styling/Avalonia.Styling.csproj +++ b/src/Avalonia.Styling/Avalonia.Styling.csproj @@ -1,36 +1,9 @@  netstandard2.0 - false + Avalonia.Styling Avalonia - - true - full - false - bin\Debug\ - DEBUG;TRACE - prompt - 4 - bin\Debug\Avalonia.Styling.xml - CS1591 - - - pdbonly - true - bin\Release\ - TRACE - prompt - 4 - bin\Release\Avalonia.Styling.xml - CS1591 - true - - - - Properties\SharedAssemblyInfo.cs - - diff --git a/src/Avalonia.Styling/Properties/AssemblyInfo.cs b/src/Avalonia.Styling/Properties/AssemblyInfo.cs index 0a639139f7..9f1794c700 100644 --- a/src/Avalonia.Styling/Properties/AssemblyInfo.cs +++ b/src/Avalonia.Styling/Properties/AssemblyInfo.cs @@ -5,7 +5,6 @@ using System.Reflection; using System.Runtime.CompilerServices; using Avalonia.Metadata; -[assembly: AssemblyTitle("Avalonia.Styling")] [assembly: XmlnsDefinition("https://github.com/avaloniaui", "Avalonia.Controls")] [assembly: XmlnsDefinition("https://github.com/avaloniaui", "Avalonia.LogicalTree")] [assembly: XmlnsDefinition("https://github.com/avaloniaui", "Avalonia.Styling")] diff --git a/src/Avalonia.Themes.Default/Avalonia.Themes.Default.csproj b/src/Avalonia.Themes.Default/Avalonia.Themes.Default.csproj index d8260226e9..eceafc2371 100644 --- a/src/Avalonia.Themes.Default/Avalonia.Themes.Default.csproj +++ b/src/Avalonia.Themes.Default/Avalonia.Themes.Default.csproj @@ -1,27 +1,6 @@  netstandard2.0 - false - - - true - full - false - bin\Debug\ - DEBUG;TRACE - prompt - 4 - bin\Debug\Avalonia.Themes.Default.xml - - - pdbonly - true - bin\Release\ - TRACE - prompt - 4 - bin\Release\Avalonia.Themes.Default.xml - true @@ -33,17 +12,7 @@ - - - - Properties\SharedAssemblyInfo.cs - - - %(Filename) - - - Designer - - + + \ No newline at end of file diff --git a/src/Avalonia.Themes.Default/CalendarItem.xaml b/src/Avalonia.Themes.Default/CalendarItem.xaml index a9756fdb5f..3d3d75a39a 100644 --- a/src/Avalonia.Themes.Default/CalendarItem.xaml +++ b/src/Avalonia.Themes.Default/CalendarItem.xaml @@ -27,7 +27,7 @@ - + diff --git a/src/Avalonia.Themes.Default/MenuItem.xaml b/src/Avalonia.Themes.Default/MenuItem.xaml index 66f226d2f6..efb31175fa 100644 --- a/src/Avalonia.Themes.Default/MenuItem.xaml +++ b/src/Avalonia.Themes.Default/MenuItem.xaml @@ -45,7 +45,8 @@ + IsOpen="{TemplateBinding Path=IsSubMenuOpen, Mode=TwoWay}" + ObeyScreenEdges="True"> @@ -92,7 +93,8 @@ + StaysOpen="True" + ObeyScreenEdges="True"> diff --git a/src/Avalonia.Themes.Default/Properties/AssemblyInfo.cs b/src/Avalonia.Themes.Default/Properties/AssemblyInfo.cs deleted file mode 100644 index 3657673618..0000000000 --- a/src/Avalonia.Themes.Default/Properties/AssemblyInfo.cs +++ /dev/null @@ -1,6 +0,0 @@ -// Copyright (c) The Avalonia Project. All rights reserved. -// Licensed under the MIT license. See licence.md file in the project root for full license information. - -using System.Reflection; - -[assembly: AssemblyTitle("Avalonia.Themes.Default")] diff --git a/src/Avalonia.Visuals/Avalonia.Visuals.csproj b/src/Avalonia.Visuals/Avalonia.Visuals.csproj index fe18b0e446..f320644cd0 100644 --- a/src/Avalonia.Visuals/Avalonia.Visuals.csproj +++ b/src/Avalonia.Visuals/Avalonia.Visuals.csproj @@ -1,39 +1,11 @@  netstandard2.0 - false Avalonia - - true - full - false - bin\Debug\ - DEBUG;TRACE - prompt - 4 - bin\Debug\Avalonia.Visuals.xml - CS1591 - - - pdbonly - true - bin\Release\ - TRACE - prompt - 4 - bin\Release\Avalonia.Visuals.xml - CS1591 - true - - - - - Properties\SharedAssemblyInfo.cs - - + \ No newline at end of file diff --git a/src/Avalonia.Visuals/CornerRadius.cs b/src/Avalonia.Visuals/CornerRadius.cs index db0ac97d49..33a553d477 100644 --- a/src/Avalonia.Visuals/CornerRadius.cs +++ b/src/Avalonia.Visuals/CornerRadius.cs @@ -4,6 +4,7 @@ using System; using System.Globalization; using System.Linq; +using Avalonia.Utilities; namespace Avalonia { @@ -53,31 +54,26 @@ namespace Avalonia return $"{TopLeft},{TopRight},{BottomRight},{BottomLeft}"; } - public static CornerRadius Parse(string s, CultureInfo culture) + public static CornerRadius Parse(string s) { - var parts = s.Split(new[] { ',', ' ' }, StringSplitOptions.RemoveEmptyEntries) - .Select(x => x.Trim()) - .ToList(); - - switch (parts.Count) + using (var tokenizer = new StringTokenizer(s, CultureInfo.InvariantCulture, exceptionMessage: "Invalid Thickness")) { - case 1: - var uniform = double.Parse(parts[0], culture); - return new CornerRadius(uniform); - case 2: - var top = double.Parse(parts[0], culture); - var bottom = double.Parse(parts[1], culture); - return new CornerRadius(top, bottom); - case 4: - var topLeft = double.Parse(parts[0], culture); - var topRight = double.Parse(parts[1], culture); - var bottomRight = double.Parse(parts[2], culture); - var bottomLeft = double.Parse(parts[3], culture); - return new CornerRadius(topLeft, topRight, bottomRight, bottomLeft); - default: + if (tokenizer.TryReadDouble(out var a)) + { + if (tokenizer.TryReadDouble(out var b)) { - throw new FormatException("Invalid CornerRadius."); + if (tokenizer.TryReadDouble(out var c)) + { + return new CornerRadius(a, b, c, tokenizer.ReadDouble()); + } + + return new CornerRadius(a, b); } + + return new CornerRadius(a); + } + + throw new FormatException("Invalid CornerRadius."); } } @@ -85,7 +81,7 @@ namespace Avalonia { return cr1.TopLeft.Equals(cr2.TopLeft) && cr1.TopRight.Equals(cr2.TopRight) - && cr1.BottomRight.Equals(cr2.BottomRight) + && cr1.BottomRight.Equals(cr2.BottomRight) && cr1.BottomLeft.Equals(cr2.BottomLeft); } diff --git a/src/Avalonia.Visuals/Matrix.cs b/src/Avalonia.Visuals/Matrix.cs index 70804ee04f..b09ea2b68c 100644 --- a/src/Avalonia.Visuals/Matrix.cs +++ b/src/Avalonia.Visuals/Matrix.cs @@ -314,11 +314,10 @@ namespace Avalonia /// Parses a string. /// /// The string. - /// The current culture. /// The . - public static Matrix Parse(string s, CultureInfo culture) + public static Matrix Parse(string s) { - using (var tokenizer = new StringTokenizer(s, culture, exceptionMessage: "Invalid Matrix")) + using (var tokenizer = new StringTokenizer(s, CultureInfo.InvariantCulture, exceptionMessage: "Invalid Matrix")) { return new Matrix( tokenizer.ReadDouble(), diff --git a/src/Avalonia.Visuals/Point.cs b/src/Avalonia.Visuals/Point.cs index d0c3edfeb2..62be3bf276 100644 --- a/src/Avalonia.Visuals/Point.cs +++ b/src/Avalonia.Visuals/Point.cs @@ -170,11 +170,10 @@ namespace Avalonia /// Parses a string. /// /// The string. - /// The current culture. /// The . - public static Point Parse(string s, CultureInfo culture) + public static Point Parse(string s) { - using (var tokenizer = new StringTokenizer(s, culture, exceptionMessage: "Invalid Point")) + using (var tokenizer = new StringTokenizer(s, CultureInfo.InvariantCulture, exceptionMessage: "Invalid Point")) { return new Point( tokenizer.ReadDouble(), diff --git a/src/Avalonia.Visuals/Properties/AssemblyInfo.cs b/src/Avalonia.Visuals/Properties/AssemblyInfo.cs index 900746d05a..05c3d7e62a 100644 --- a/src/Avalonia.Visuals/Properties/AssemblyInfo.cs +++ b/src/Avalonia.Visuals/Properties/AssemblyInfo.cs @@ -5,7 +5,6 @@ using System.Reflection; using System.Runtime.CompilerServices; using Avalonia.Metadata; -[assembly: AssemblyTitle("Avalonia.Visuals")] [assembly: InternalsVisibleTo("Avalonia.Visuals.UnitTests")] [assembly: XmlnsDefinition("https://github.com/avaloniaui", "Avalonia.Animation")] [assembly: XmlnsDefinition("https://github.com/avaloniaui", "Avalonia.Media")] diff --git a/src/Avalonia.Visuals/Rect.cs b/src/Avalonia.Visuals/Rect.cs index 748928ada3..73021ca29a 100644 --- a/src/Avalonia.Visuals/Rect.cs +++ b/src/Avalonia.Visuals/Rect.cs @@ -487,11 +487,10 @@ namespace Avalonia /// Parses a string. /// /// The string. - /// The current culture. /// The parsed . - public static Rect Parse(string s, CultureInfo culture) + public static Rect Parse(string s) { - using (var tokenizer = new StringTokenizer(s, culture, exceptionMessage: "Invalid Rect")) + using (var tokenizer = new StringTokenizer(s, CultureInfo.InvariantCulture, exceptionMessage: "Invalid Rect")) { return new Rect( tokenizer.ReadDouble(), diff --git a/src/Avalonia.Visuals/RelativePoint.cs b/src/Avalonia.Visuals/RelativePoint.cs index a2ef0e6725..e4d7ea05cb 100644 --- a/src/Avalonia.Visuals/RelativePoint.cs +++ b/src/Avalonia.Visuals/RelativePoint.cs @@ -154,11 +154,10 @@ namespace Avalonia /// Parses a string. /// /// The string. - /// The current culture. /// The parsed . - public static RelativePoint Parse(string s, CultureInfo culture) + public static RelativePoint Parse(string s) { - using (var tokenizer = new StringTokenizer(s, culture, exceptionMessage: "Invalid RelativePoint")) + using (var tokenizer = new StringTokenizer(s, CultureInfo.InvariantCulture, exceptionMessage: "Invalid RelativePoint")) { var x = tokenizer.ReadString(); var y = tokenizer.ReadString(); @@ -180,8 +179,8 @@ namespace Avalonia } return new RelativePoint( - double.Parse(x, culture) * scale, - double.Parse(y, culture) * scale, + double.Parse(x, CultureInfo.InvariantCulture) * scale, + double.Parse(y, CultureInfo.InvariantCulture) * scale, unit); } } diff --git a/src/Avalonia.Visuals/RelativeRect.cs b/src/Avalonia.Visuals/RelativeRect.cs index c13c3282db..ad42e30057 100644 --- a/src/Avalonia.Visuals/RelativeRect.cs +++ b/src/Avalonia.Visuals/RelativeRect.cs @@ -167,11 +167,10 @@ namespace Avalonia /// Parses a string. /// /// The string. - /// The current culture. /// The parsed . - public static RelativeRect Parse(string s, CultureInfo culture) + public static RelativeRect Parse(string s) { - using (var tokenizer = new StringTokenizer(s, culture, exceptionMessage: "Invalid RelativeRect")) + using (var tokenizer = new StringTokenizer(s, exceptionMessage: "Invalid RelativeRect")) { var x = tokenizer.ReadString(); var y = tokenizer.ReadString(); @@ -202,10 +201,10 @@ namespace Avalonia } return new RelativeRect( - double.Parse(x, culture) * scale, - double.Parse(y, culture) * scale, - double.Parse(width, culture) * scale, - double.Parse(height, culture) * scale, + double.Parse(x, CultureInfo.InvariantCulture) * scale, + double.Parse(y, CultureInfo.InvariantCulture) * scale, + double.Parse(width, CultureInfo.InvariantCulture) * scale, + double.Parse(height, CultureInfo.InvariantCulture) * scale, unit); } } diff --git a/src/Avalonia.Visuals/Rendering/DefaultRenderLoop.cs b/src/Avalonia.Visuals/Rendering/DefaultRenderLoop.cs index 5dff3715b3..9cf849f59b 100644 --- a/src/Avalonia.Visuals/Rendering/DefaultRenderLoop.cs +++ b/src/Avalonia.Visuals/Rendering/DefaultRenderLoop.cs @@ -41,12 +41,12 @@ namespace Avalonia.Rendering { add { + _tick += value; + if (_subscriberCount++ == 0) { Start(); } - - _tick += value; } remove diff --git a/src/Avalonia.Visuals/Size.cs b/src/Avalonia.Visuals/Size.cs index c5eaa33b41..b6889af6f0 100644 --- a/src/Avalonia.Visuals/Size.cs +++ b/src/Avalonia.Visuals/Size.cs @@ -150,11 +150,10 @@ namespace Avalonia /// Parses a string. /// /// The string. - /// The current culture. /// The . - public static Size Parse(string s, CultureInfo culture) + public static Size Parse(string s) { - using (var tokenizer = new StringTokenizer(s, culture, exceptionMessage: "Invalid Size")) + using (var tokenizer = new StringTokenizer(s, CultureInfo.InvariantCulture, exceptionMessage: "Invalid Size")) { return new Size( tokenizer.ReadDouble(), diff --git a/src/Avalonia.Visuals/Thickness.cs b/src/Avalonia.Visuals/Thickness.cs index a5ca0a04a8..43a5fed9e7 100644 --- a/src/Avalonia.Visuals/Thickness.cs +++ b/src/Avalonia.Visuals/Thickness.cs @@ -165,25 +165,27 @@ namespace Avalonia /// Parses a string. /// /// The string. - /// The current culture. /// The . - public static Thickness Parse(string s, CultureInfo culture) + public static Thickness Parse(string s) { - using (var tokenizer = new StringTokenizer(s, culture, exceptionMessage: "Invalid Thickness")) + using (var tokenizer = new StringTokenizer(s, CultureInfo.InvariantCulture, exceptionMessage: "Invalid Thickness")) { - var a = tokenizer.ReadDouble(); - - if (tokenizer.TryReadDouble(out var b)) + if(tokenizer.TryReadDouble(out var a)) { - if (tokenizer.TryReadDouble(out var c)) + if (tokenizer.TryReadDouble(out var b)) { - return new Thickness(a, b, c, tokenizer.ReadDouble()); + if (tokenizer.TryReadDouble(out var c)) + { + return new Thickness(a, b, c, tokenizer.ReadDouble()); + } + + return new Thickness(a, b); } - return new Thickness(a, b); + return new Thickness(a); } - - return new Thickness(a); + + throw new FormatException("Invalid Thickness."); } } diff --git a/src/Directory.Build.props b/src/Directory.Build.props new file mode 100644 index 0000000000..7325bab2a3 --- /dev/null +++ b/src/Directory.Build.props @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/src/Gtk/Avalonia.Gtk3/Avalonia.Gtk3.csproj b/src/Gtk/Avalonia.Gtk3/Avalonia.Gtk3.csproj index f1b990f349..ef043959ad 100644 --- a/src/Gtk/Avalonia.Gtk3/Avalonia.Gtk3.csproj +++ b/src/Gtk/Avalonia.Gtk3/Avalonia.Gtk3.csproj @@ -1,30 +1,9 @@  netstandard2.0 - false - - - true - full - false - bin\Debug\ - TRACE;DEBUG;GTK3_PINVOKE - prompt - 4 - true - - - pdbonly - true - bin\Release\ - TRACE;GTK3_PINVOKE - prompt - 4 true + $(DefineConstants);GTK3_PINVOKE - - - diff --git a/src/Gtk/Avalonia.Gtk3/Interop/Native.cs b/src/Gtk/Avalonia.Gtk3/Interop/Native.cs index d4618e7bc1..1adaf9f4e1 100644 --- a/src/Gtk/Avalonia.Gtk3/Interop/Native.cs +++ b/src/Gtk/Avalonia.Gtk3/Interop/Native.cs @@ -115,6 +115,8 @@ namespace Avalonia.Gtk3.Interop [UnmanagedFunctionPointer(CallingConvention.Cdecl), GtkImport(GtkDll.Gtk)] public delegate void gtk_window_set_title(GtkWindow gtkWindow, Utf8Buffer title); + [UnmanagedFunctionPointer(CallingConvention.Cdecl), GtkImport(GtkDll.Gtk)] + public delegate void gtk_window_set_resizable(GtkWindow gtkWindow, bool resizable); [UnmanagedFunctionPointer(CallingConvention.Cdecl), GtkImport(GtkDll.Gtk)] public delegate void gtk_window_set_decorated(GtkWindow gtkWindow, bool decorated); @@ -263,7 +265,7 @@ namespace Avalonia.Gtk3.Interop [UnmanagedFunctionPointer(CallingConvention.Cdecl), GtkImport(GtkDll.Gtk)] public delegate void gtk_window_close(GtkWindow window); - [UnmanagedFunctionPointer(CallingConvention.Cdecl), GtkImport(GtkDll.Gdk)] + [UnmanagedFunctionPointer(CallingConvention.Cdecl), GtkImport(GtkDll.Gtk)] public delegate void gtk_window_set_geometry_hints(GtkWindow window, IntPtr geometry_widget, ref GdkGeometry geometry, GdkWindowHints geom_mask); [UnmanagedFunctionPointer(CallingConvention.Cdecl), GtkImport(GtkDll.Gdk)] @@ -395,6 +397,7 @@ namespace Avalonia.Gtk3.Interop public static D.gdk_screen_get_monitor_geometry GdkScreenGetMonitorGeometry; public static D.gdk_screen_get_monitor_workarea GdkScreenGetMonitorWorkarea; public static D.gtk_window_set_decorated GtkWindowSetDecorated; + public static D.gtk_window_set_resizable GtkWindowSetResizable; public static D.gtk_window_set_skip_taskbar_hint GtkWindowSetSkipTaskbarHint; public static D.gtk_window_get_skip_taskbar_hint GtkWindowGetSkipTaskbarHint; public static D.gtk_window_set_skip_pager_hint GtkWindowSetSkipPagerHint; @@ -421,6 +424,7 @@ namespace Avalonia.Gtk3.Interop public static D.gdk_window_set_override_redirect GdkWindowSetOverrideRedirect; public static D.gtk_widget_set_size_request GtkWindowSetSizeRequest; public static D.gtk_window_set_default_size GtkWindowSetDefaultSize; + public static D.gtk_window_set_geometry_hints GtkWindowSetGeometryHints; public static D.gtk_window_get_position GtkWindowGetPosition; public static D.gtk_window_move GtkWindowMove; public static D.gtk_file_chooser_dialog_new GtkFileChooserDialogNew; @@ -502,6 +506,7 @@ namespace Avalonia.Gtk3.Interop public static D.cairo_set_font_size CairoSetFontSize; public static D.cairo_move_to CairoMoveTo; public static D.cairo_destroy CairoDestroy; + public const int G_TYPE_OBJECT = 80; } @@ -739,19 +744,19 @@ namespace Avalonia.Gtk3.Interop } [StructLayout(LayoutKind.Sequential)] - struct GdkGeometry + public struct GdkGeometry { - gint min_width; - gint min_height; - gint max_width; - gint max_height; - gint base_width; - gint base_height; - gint width_inc; - gint height_inc; - gdouble min_aspect; - gdouble max_aspect; - gint win_gravity; + public gint min_width; + public gint min_height; + public gint max_width; + public gint max_height; + public gint base_width; + public gint base_height; + public gint width_inc; + public gint height_inc; + public gdouble min_aspect; + public gdouble max_aspect; + public gint win_gravity; } enum GdkWindowHints diff --git a/src/Gtk/Avalonia.Gtk3/Properties/AssemblyInfo.cs b/src/Gtk/Avalonia.Gtk3/Properties/AssemblyInfo.cs index 034b73f699..a27d631ee0 100644 --- a/src/Gtk/Avalonia.Gtk3/Properties/AssemblyInfo.cs +++ b/src/Gtk/Avalonia.Gtk3/Properties/AssemblyInfo.cs @@ -5,8 +5,6 @@ using Avalonia.Platform; // General Information about an assembly is controlled through the following // set of attributes. Change these attribute values to modify the information // associated with an assembly. -[assembly: AssemblyTitle("Avalonia.Gtk3")] - [assembly: ExportWindowingSubsystem(OperatingSystemType.WinNT, 2, "GTK3", typeof(Gtk3Platform), nameof(Gtk3Platform.Initialize))] [assembly: ExportWindowingSubsystem(OperatingSystemType.Linux, 1, "GTK3", typeof(Gtk3Platform), nameof(Gtk3Platform.Initialize))] [assembly: ExportWindowingSubsystem(OperatingSystemType.OSX, 2, "GTK3", typeof(Gtk3Platform), nameof(Gtk3Platform.Initialize))] \ No newline at end of file diff --git a/src/Gtk/Avalonia.Gtk3/WindowBaseImpl.cs b/src/Gtk/Avalonia.Gtk3/WindowBaseImpl.cs index a42c8a19b9..0ebfea998a 100644 --- a/src/Gtk/Avalonia.Gtk3/WindowBaseImpl.cs +++ b/src/Gtk/Avalonia.Gtk3/WindowBaseImpl.cs @@ -341,6 +341,20 @@ namespace Avalonia.Gtk3 } } + public void SetMinMaxSize(Size minSize, Size maxSize) + { + if (GtkWidget.IsClosed) + return; + + GdkGeometry geometry = new GdkGeometry(); + geometry.min_width = minSize.Width > 0 ? (int)minSize.Width : -1; + geometry.min_height = minSize.Height > 0 ? (int)minSize.Height : -1; + geometry.max_width = !Double.IsInfinity(maxSize.Width) && maxSize.Width > 0 ? (int)maxSize.Width : 999999; + geometry.max_height = !Double.IsInfinity(maxSize.Height) && maxSize.Height > 0 ? (int)maxSize.Height : 999999; + + Native.GtkWindowSetGeometryHints(GtkWidget, IntPtr.Zero, ref geometry, GdkWindowHints.GDK_HINT_MIN_SIZE | GdkWindowHints.GDK_HINT_MAX_SIZE); + } + public IMouseDevice MouseDevice => Gtk3Platform.Mouse; public double Scaling => LastKnownScaleFactor = (int) (Native.GtkWidgetGetScaleFactor?.Invoke(GtkWidget) ?? 1); @@ -431,6 +445,7 @@ namespace Avalonia.Gtk3 { if (GtkWidget.IsClosed) return; + Native.GtkWindowResize(GtkWidget, (int)value.Width, (int)value.Height); if (OverrideRedirect) { diff --git a/src/Gtk/Avalonia.Gtk3/WindowImpl.cs b/src/Gtk/Avalonia.Gtk3/WindowImpl.cs index c586661a7a..2d309e19d4 100644 --- a/src/Gtk/Avalonia.Gtk3/WindowImpl.cs +++ b/src/Gtk/Avalonia.Gtk3/WindowImpl.cs @@ -61,6 +61,8 @@ namespace Avalonia.Gtk3 } public void ShowTaskbarIcon(bool value) => Native.GtkWindowSetSkipTaskbarHint(GtkWidget, !value); + + public void CanResize(bool value) => Native.GtkWindowSetResizable(GtkWidget, value); class EmptyDisposable : IDisposable diff --git a/src/Linux/Avalonia.LinuxFramebuffer/Avalonia.LinuxFramebuffer.csproj b/src/Linux/Avalonia.LinuxFramebuffer/Avalonia.LinuxFramebuffer.csproj index 466b47f7a0..fb1fbdaf93 100644 --- a/src/Linux/Avalonia.LinuxFramebuffer/Avalonia.LinuxFramebuffer.csproj +++ b/src/Linux/Avalonia.LinuxFramebuffer/Avalonia.LinuxFramebuffer.csproj @@ -10,5 +10,5 @@ - + \ No newline at end of file diff --git a/src/Markup/Avalonia.Markup.Xaml/Avalonia.Markup.Xaml.csproj b/src/Markup/Avalonia.Markup.Xaml/Avalonia.Markup.Xaml.csproj index bd6acfdad1..49f6ca9eeb 100644 --- a/src/Markup/Avalonia.Markup.Xaml/Avalonia.Markup.Xaml.csproj +++ b/src/Markup/Avalonia.Markup.Xaml/Avalonia.Markup.Xaml.csproj @@ -26,10 +26,6 @@ CS1591 - - Properties\SharedAssemblyInfo.cs - - diff --git a/src/Markup/Avalonia.Markup.Xaml/AvaloniaXamlLoader.cs b/src/Markup/Avalonia.Markup.Xaml/AvaloniaXamlLoader.cs index 3142d954ff..79523dd498 100644 --- a/src/Markup/Avalonia.Markup.Xaml/AvaloniaXamlLoader.cs +++ b/src/Markup/Avalonia.Markup.Xaml/AvaloniaXamlLoader.cs @@ -1,11 +1,237 @@ -namespace Avalonia.Markup.Xaml +// Copyright (c) The Avalonia Project. All rights reserved. +// Licensed under the MIT license. See licence.md file in the project root for full license information. + +using Avalonia.Controls; +using Avalonia.Markup.Xaml.Data; +using Avalonia.Markup.Xaml.PortableXaml; +using Avalonia.Platform; +using Portable.Xaml; +using System; +using System.Collections.Generic; +using System.IO; +using System.Reflection; +using System.Text; + +namespace Avalonia.Markup.Xaml { - public class AvaloniaXamlLoader : AvaloniaXamlLoaderPortableXaml + /// + /// Loads XAML for a avalonia application. + /// + public class AvaloniaXamlLoader { - public static object Parse(string xaml) - => new AvaloniaXamlLoader().Load(xaml); + private readonly AvaloniaXamlSchemaContext _context = GetContext(); + + private static AvaloniaXamlSchemaContext GetContext() + { + var result = AvaloniaLocator.Current.GetService(); + + if (result == null) + { + result = AvaloniaXamlSchemaContext.Create(); + + AvaloniaLocator.CurrentMutable + .Bind() + .ToConstant(result); + } + + return result; + } + + /// + /// Initializes a new instance of the class. + /// + public AvaloniaXamlLoader() + { + } + + /// + /// Loads the XAML into a Avalonia component. + /// + /// The object to load the XAML into. + public static void Load(object obj) + { + Contract.Requires(obj != null); + + var loader = new AvaloniaXamlLoader(); + loader.Load(obj.GetType(), obj); + } + + /// + /// Loads the XAML for a type. + /// + /// The type. + /// + /// The optional instance into which the XAML should be loaded. + /// + /// The loaded object. + public object Load(Type type, object rootInstance = null) + { + Contract.Requires(type != null); + + // HACK: Currently Visual Studio is forcing us to change the extension of xaml files + // in certain situations, so we try to load .xaml and if that's not found we try .xaml. + // Ideally we'd be able to use .xaml everywhere + var assetLocator = AvaloniaLocator.Current.GetService(); + + if (assetLocator == null) + { + throw new InvalidOperationException( + "Could not create IAssetLoader : maybe Application.RegisterServices() wasn't called?"); + } + + foreach (var uri in GetUrisFor(type)) + { + if (assetLocator.Exists(uri)) + { + using (var stream = assetLocator.Open(uri)) + { + var initialize = rootInstance as ISupportInitialize; + initialize?.BeginInit(); + try + { + return Load(stream, type.Assembly, rootInstance, uri); + } + finally + { + initialize?.EndInit(); + } + } + } + } + + throw new FileNotFoundException("Unable to find view for " + type.FullName); + } + + /// + /// Loads XAML from a URI. + /// + /// The URI of the XAML file. + /// + /// A base URI to use if is relative. + /// + /// + /// The optional instance into which the XAML should be loaded. + /// + /// The loaded object. + public object Load(Uri uri, Uri baseUri = null, object rootInstance = null) + { + Contract.Requires(uri != null); + + var assetLocator = AvaloniaLocator.Current.GetService(); + + if (assetLocator == null) + { + throw new InvalidOperationException( + "Could not create IAssetLoader : maybe Application.RegisterServices() wasn't called?"); + } + + var asset = assetLocator.OpenAndGetAssembly(uri, baseUri); + using (var stream = asset.Item1) + { + try + { + return Load(stream, asset.Item2, rootInstance, uri); + } + catch (Exception e) + { + var uriString = uri.ToString(); + if (!uri.IsAbsoluteUri) + { + uriString = new Uri(baseUri, uri).AbsoluteUri; + } + throw new XamlLoadException("Error loading xaml at " + uriString, e); + } + } + } + + /// + /// Loads XAML from a string. + /// + /// The string containing the XAML. + /// Default assembly for clr-namespace: + /// + /// The optional instance into which the XAML should be loaded. + /// + /// The loaded object. + public object Load(string xaml, Assembly localAssembly = null, object rootInstance = null) + { + Contract.Requires(xaml != null); + + using (var stream = new MemoryStream(Encoding.UTF8.GetBytes(xaml))) + { + return Load(stream, localAssembly, rootInstance); + } + } + + /// + /// Loads XAML from a stream. + /// + /// The stream containing the XAML. + /// Default assembly for clr-namespace + /// + /// The optional instance into which the XAML should be loaded. + /// + /// The URI of the XAML + /// The loaded object. + public object Load(Stream stream, Assembly localAssembly, object rootInstance = null, Uri uri = null) + { + var readerSettings = new XamlXmlReaderSettings() + { + BaseUri = uri, + LocalAssembly = localAssembly + }; + + var reader = new XamlXmlReader(stream, _context, readerSettings); + + object result = LoadFromReader( + reader, + AvaloniaXamlContext.For(readerSettings, rootInstance)); + + var topLevel = result as TopLevel; + + if (topLevel != null) + { + DelayedBinding.ApplyBindings(topLevel); + } + + return result; + } + + internal static object LoadFromReader(XamlReader reader, AvaloniaXamlContext context = null, IAmbientProvider parentAmbientProvider = null) + { + var writer = AvaloniaXamlObjectWriter.Create( + reader.SchemaContext, + context, + parentAmbientProvider); + + XamlServices.Transform(reader, writer); + writer.ApplyAllDelayedProperties(); + return writer.Result; + } + + internal static object LoadFromReader(XamlReader reader) + { + //return XamlServices.Load(reader); + return LoadFromReader(reader, null); + } + + /// + /// Gets the URI for a type. + /// + /// The type. + /// The URI. + private static IEnumerable GetUrisFor(Type type) + { + var asm = type.GetTypeInfo().Assembly.GetName().Name; + var typeName = type.FullName; + yield return new Uri("resm:" + typeName + ".xaml?assembly=" + asm); + yield return new Uri("resm:" + typeName + ".paml?assembly=" + asm); + } + + public static object Parse(string xaml, Assembly localAssembly = null) + => new AvaloniaXamlLoader().Load(xaml, localAssembly); - public static T Parse(string xaml) - => (T)Parse(xaml); + public static T Parse(string xaml, Assembly localAssembly = null) + => (T)Parse(xaml, localAssembly); } } \ No newline at end of file diff --git a/src/Markup/Avalonia.Markup.Xaml/AvaloniaXamlLoaderPortableXaml.cs b/src/Markup/Avalonia.Markup.Xaml/AvaloniaXamlLoaderPortableXaml.cs deleted file mode 100644 index de2a79c54e..0000000000 --- a/src/Markup/Avalonia.Markup.Xaml/AvaloniaXamlLoaderPortableXaml.cs +++ /dev/null @@ -1,227 +0,0 @@ -// Copyright (c) The Avalonia Project. All rights reserved. -// Licensed under the MIT license. See licence.md file in the project root for full license information. - -using Avalonia.Controls; -using Avalonia.Markup.Xaml.Data; -using Avalonia.Markup.Xaml.PortableXaml; -using Avalonia.Platform; -using Portable.Xaml; -using System; -using System.Collections.Generic; -using System.IO; -using System.Reflection; -using System.Text; - -namespace Avalonia.Markup.Xaml -{ - /// - /// Loads XAML for a avalonia application. - /// - public class AvaloniaXamlLoaderPortableXaml - { - private readonly AvaloniaXamlSchemaContext _context = GetContext(); - - private static AvaloniaXamlSchemaContext GetContext() - { - var result = AvaloniaLocator.Current.GetService(); - - if (result == null) - { - result = AvaloniaXamlSchemaContext.Create(); - - AvaloniaLocator.CurrentMutable - .Bind() - .ToConstant(result); - } - - return result; - } - - /// - /// Initializes a new instance of the class. - /// - public AvaloniaXamlLoaderPortableXaml() - { - } - - /// - /// Loads the XAML into a Avalonia component. - /// - /// The object to load the XAML into. - public static void Load(object obj) - { - Contract.Requires(obj != null); - - var loader = new AvaloniaXamlLoader(); - loader.Load(obj.GetType(), obj); - } - - /// - /// Loads the XAML for a type. - /// - /// The type. - /// - /// The optional instance into which the XAML should be loaded. - /// - /// The loaded object. - public object Load(Type type, object rootInstance = null) - { - Contract.Requires(type != null); - - // HACK: Currently Visual Studio is forcing us to change the extension of xaml files - // in certain situations, so we try to load .xaml and if that's not found we try .xaml. - // Ideally we'd be able to use .xaml everywhere - var assetLocator = AvaloniaLocator.Current.GetService(); - - if (assetLocator == null) - { - throw new InvalidOperationException( - "Could not create IAssetLoader : maybe Application.RegisterServices() wasn't called?"); - } - - foreach (var uri in GetUrisFor(type)) - { - if (assetLocator.Exists(uri)) - { - using (var stream = assetLocator.Open(uri)) - { - var initialize = rootInstance as ISupportInitialize; - initialize?.BeginInit(); - try - { - return Load(stream, rootInstance, uri); - } - finally - { - initialize?.EndInit(); - } - } - } - } - - throw new FileNotFoundException("Unable to find view for " + type.FullName); - } - - /// - /// Loads XAML from a URI. - /// - /// The URI of the XAML file. - /// - /// A base URI to use if is relative. - /// - /// - /// The optional instance into which the XAML should be loaded. - /// - /// The loaded object. - public object Load(Uri uri, Uri baseUri = null, object rootInstance = null) - { - Contract.Requires(uri != null); - - var assetLocator = AvaloniaLocator.Current.GetService(); - - if (assetLocator == null) - { - throw new InvalidOperationException( - "Could not create IAssetLoader : maybe Application.RegisterServices() wasn't called?"); - } - - using (var stream = assetLocator.Open(uri, baseUri)) - { - try - { - return Load(stream, rootInstance, uri); - } - catch (Exception e) - { - var uriString = uri.ToString(); - if (!uri.IsAbsoluteUri) - { - uriString = new Uri(baseUri, uri).AbsoluteUri; - } - throw new XamlLoadException("Error loading xaml at " + uriString, e); - } - } - } - - /// - /// Loads XAML from a string. - /// - /// The string containing the XAML. - /// - /// The optional instance into which the XAML should be loaded. - /// - /// The loaded object. - public object Load(string xaml, object rootInstance = null) - { - Contract.Requires(xaml != null); - - using (var stream = new MemoryStream(Encoding.UTF8.GetBytes(xaml))) - { - return Load(stream, rootInstance); - } - } - - /// - /// Loads XAML from a stream. - /// - /// The stream containing the XAML. - /// - /// The optional instance into which the XAML should be loaded. - /// - /// The URI of the XAML - /// The loaded object. - public object Load(Stream stream, object rootInstance = null, Uri uri = null) - { - var readerSettings = new XamlXmlReaderSettings() - { - BaseUri = uri, - LocalAssembly = rootInstance?.GetType().GetTypeInfo().Assembly - }; - - var reader = new XamlXmlReader(stream, _context, readerSettings); - - object result = LoadFromReader( - reader, - AvaloniaXamlContext.For(readerSettings, rootInstance)); - - var topLevel = result as TopLevel; - - if (topLevel != null) - { - DelayedBinding.ApplyBindings(topLevel); - } - - return result; - } - - internal static object LoadFromReader(XamlReader reader, AvaloniaXamlContext context = null) - { - var writer = AvaloniaXamlObjectWriter.Create( - reader.SchemaContext, - context); - - XamlServices.Transform(reader, writer); - writer.ApplyAllDelayedProperties(); - return writer.Result; - } - - internal static object LoadFromReader(XamlReader reader) - { - //return XamlServices.Load(reader); - return LoadFromReader(reader, null); - } - - /// - /// Gets the URI for a type. - /// - /// The type. - /// The URI. - private static IEnumerable GetUrisFor(Type type) - { - var asm = type.GetTypeInfo().Assembly.GetName().Name; - var typeName = type.FullName; - yield return new Uri("resm:" + typeName + ".xaml?assembly=" + asm); - yield return new Uri("resm:" + typeName + ".paml?assembly=" + asm); - } - } -} \ No newline at end of file diff --git a/src/Markup/Avalonia.Markup.Xaml/Converters/AvaloniaPropertyTypeConverter.cs b/src/Markup/Avalonia.Markup.Xaml/Converters/AvaloniaPropertyTypeConverter.cs index a34ccaa413..bc3caff3b9 100644 --- a/src/Markup/Avalonia.Markup.Xaml/Converters/AvaloniaPropertyTypeConverter.cs +++ b/src/Markup/Avalonia.Markup.Xaml/Converters/AvaloniaPropertyTypeConverter.cs @@ -53,10 +53,7 @@ namespace Avalonia.Markup.Xaml.Converters } } - // First look for non-attached property on the type and then look for an attached property. - var property = AvaloniaPropertyRegistry.Instance.FindRegistered(type, s) ?? - AvaloniaPropertyRegistry.Instance.GetAttached(type) - .FirstOrDefault(x => x.Name == propertyName); + AvaloniaProperty property = AvaloniaPropertyRegistry.Instance.FindRegistered(type, propertyName); if (property == null) { diff --git a/src/Markup/Avalonia.Markup.Xaml/Converters/CornerRadiusTypeConverter.cs b/src/Markup/Avalonia.Markup.Xaml/Converters/CornerRadiusTypeConverter.cs index 5da0efae1b..d8c1ecd4eb 100644 --- a/src/Markup/Avalonia.Markup.Xaml/Converters/CornerRadiusTypeConverter.cs +++ b/src/Markup/Avalonia.Markup.Xaml/Converters/CornerRadiusTypeConverter.cs @@ -13,7 +13,7 @@ namespace Avalonia.Markup.Xaml.Converters public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value) { - return CornerRadius.Parse((string)value, culture); + return CornerRadius.Parse((string)value); } } } \ No newline at end of file diff --git a/src/Markup/Avalonia.Markup.Xaml/Converters/GridLengthTypeConverter.cs b/src/Markup/Avalonia.Markup.Xaml/Converters/GridLengthTypeConverter.cs index 1f72ca325c..05f3bed04d 100644 --- a/src/Markup/Avalonia.Markup.Xaml/Converters/GridLengthTypeConverter.cs +++ b/src/Markup/Avalonia.Markup.Xaml/Converters/GridLengthTypeConverter.cs @@ -18,7 +18,7 @@ namespace Avalonia.Markup.Xaml.Converters public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value) { - return GridLength.Parse((string)value, culture); + return GridLength.Parse((string)value); } } } \ No newline at end of file diff --git a/src/Markup/Avalonia.Markup.Xaml/Converters/MatrixTypeConverter.cs b/src/Markup/Avalonia.Markup.Xaml/Converters/MatrixTypeConverter.cs index c477ff5637..fec6e31771 100644 --- a/src/Markup/Avalonia.Markup.Xaml/Converters/MatrixTypeConverter.cs +++ b/src/Markup/Avalonia.Markup.Xaml/Converters/MatrixTypeConverter.cs @@ -17,7 +17,7 @@ namespace Avalonia.Markup.Xaml.Converters public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value) { - return Matrix.Parse((string)value, culture); + return Matrix.Parse((string)value); } } } \ No newline at end of file diff --git a/src/Markup/Avalonia.Markup.Xaml/Converters/PointTypeConverter.cs b/src/Markup/Avalonia.Markup.Xaml/Converters/PointTypeConverter.cs index 1381fe7a75..9ca7212c68 100644 --- a/src/Markup/Avalonia.Markup.Xaml/Converters/PointTypeConverter.cs +++ b/src/Markup/Avalonia.Markup.Xaml/Converters/PointTypeConverter.cs @@ -17,7 +17,7 @@ namespace Avalonia.Markup.Xaml.Converters public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value) { - return Point.Parse((string)value, culture); + return Point.Parse((string)value); } } } \ No newline at end of file diff --git a/src/Markup/Avalonia.Markup.Xaml/Converters/PointsListTypeConverter.cs b/src/Markup/Avalonia.Markup.Xaml/Converters/PointsListTypeConverter.cs index b6c6da3055..29c7dbfd39 100644 --- a/src/Markup/Avalonia.Markup.Xaml/Converters/PointsListTypeConverter.cs +++ b/src/Markup/Avalonia.Markup.Xaml/Converters/PointsListTypeConverter.cs @@ -23,7 +23,7 @@ namespace Avalonia.Markup.Xaml.Converters var result = new List(pointStrs.Length); foreach (var pointStr in pointStrs) { - result.Add(Point.Parse(pointStr, culture)); + result.Add(Point.Parse(pointStr)); } return result; diff --git a/src/Markup/Avalonia.Markup.Xaml/Converters/RectTypeConverter.cs b/src/Markup/Avalonia.Markup.Xaml/Converters/RectTypeConverter.cs index c9c6462f89..0946f95938 100644 --- a/src/Markup/Avalonia.Markup.Xaml/Converters/RectTypeConverter.cs +++ b/src/Markup/Avalonia.Markup.Xaml/Converters/RectTypeConverter.cs @@ -17,7 +17,7 @@ namespace Avalonia.Markup.Xaml.Converters public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value) { - return Rect.Parse((string)value, culture); + return Rect.Parse((string)value); } } } \ No newline at end of file diff --git a/src/Markup/Avalonia.Markup.Xaml/Converters/RelativePointTypeConverter.cs b/src/Markup/Avalonia.Markup.Xaml/Converters/RelativePointTypeConverter.cs index f68b8d66e6..b3178f1496 100644 --- a/src/Markup/Avalonia.Markup.Xaml/Converters/RelativePointTypeConverter.cs +++ b/src/Markup/Avalonia.Markup.Xaml/Converters/RelativePointTypeConverter.cs @@ -17,7 +17,7 @@ namespace Avalonia.Markup.Xaml.Converters public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value) { - return RelativePoint.Parse((string)value, culture); + return RelativePoint.Parse((string)value); } } } \ No newline at end of file diff --git a/src/Markup/Avalonia.Markup.Xaml/Converters/RelativeRectTypeConverter.cs b/src/Markup/Avalonia.Markup.Xaml/Converters/RelativeRectTypeConverter.cs index 64e39e224a..38c2833815 100644 --- a/src/Markup/Avalonia.Markup.Xaml/Converters/RelativeRectTypeConverter.cs +++ b/src/Markup/Avalonia.Markup.Xaml/Converters/RelativeRectTypeConverter.cs @@ -17,7 +17,7 @@ namespace Avalonia.Markup.Xaml.Converters public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value) { - return RelativeRect.Parse((string)value, culture); + return RelativeRect.Parse((string)value); } } } \ No newline at end of file diff --git a/src/Markup/Avalonia.Markup.Xaml/Converters/SizeTypeConverter.cs b/src/Markup/Avalonia.Markup.Xaml/Converters/SizeTypeConverter.cs index 73fef9ab1f..535e7948d5 100644 --- a/src/Markup/Avalonia.Markup.Xaml/Converters/SizeTypeConverter.cs +++ b/src/Markup/Avalonia.Markup.Xaml/Converters/SizeTypeConverter.cs @@ -17,7 +17,7 @@ namespace Avalonia.Markup.Xaml.Converters public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value) { - return Size.Parse((string)value, culture); + return Size.Parse((string)value); } } } \ No newline at end of file diff --git a/src/Markup/Avalonia.Markup.Xaml/Converters/ThicknessTypeConverter.cs b/src/Markup/Avalonia.Markup.Xaml/Converters/ThicknessTypeConverter.cs index 6ca5ec2f66..3a7652a153 100644 --- a/src/Markup/Avalonia.Markup.Xaml/Converters/ThicknessTypeConverter.cs +++ b/src/Markup/Avalonia.Markup.Xaml/Converters/ThicknessTypeConverter.cs @@ -17,7 +17,7 @@ namespace Avalonia.Markup.Xaml.Converters public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value) { - return Thickness.Parse((string)value, culture); + return Thickness.Parse((string)value); } } } \ No newline at end of file diff --git a/src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/StaticResourceExtension.cs b/src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/StaticResourceExtension.cs index 9089a13656..8e71c5f81b 100644 --- a/src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/StaticResourceExtension.cs +++ b/src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/StaticResourceExtension.cs @@ -36,13 +36,6 @@ namespace Avalonia.Markup.Xaml.MarkupExtensions // Look upwards though the ambient context for IResourceProviders which might be able // to give us the resource. - // - // TODO: If we're in a template then only the ambient values since the root of the - // template wil be included here. We need some way to get hold of the parent ambient - // context and search that. See the test: - // - // StaticResource_Can_Be_Assigned_To_Property_In_ControlTemplate_In_Styles_File - // foreach (var ambientValue in ambientValues) { // We override XamlType.CanAssignTo in BindingXamlType so the results we get back diff --git a/src/Markup/Avalonia.Markup.Xaml/PortableXaml/AvaloniaXamlObjectWriter.cs b/src/Markup/Avalonia.Markup.Xaml/PortableXaml/AvaloniaXamlObjectWriter.cs index e0e2553f46..240ca291a8 100644 --- a/src/Markup/Avalonia.Markup.Xaml/PortableXaml/AvaloniaXamlObjectWriter.cs +++ b/src/Markup/Avalonia.Markup.Xaml/PortableXaml/AvaloniaXamlObjectWriter.cs @@ -11,7 +11,8 @@ namespace Avalonia.Markup.Xaml.PortableXaml { public static AvaloniaXamlObjectWriter Create( XamlSchemaContext schemaContext, - AvaloniaXamlContext context) + AvaloniaXamlContext context, + IAmbientProvider parentAmbientProvider = null) { var nameScope = new AvaloniaNameScope { Instance = context?.RootInstance }; @@ -23,8 +24,9 @@ namespace Avalonia.Markup.Xaml.PortableXaml }; return new AvaloniaXamlObjectWriter(schemaContext, - writerSettings.WithContext(context), - nameScope); + writerSettings.WithContext(context), + nameScope, + parentAmbientProvider); } private readonly DelayedValuesHelper _delayedValuesHelper = new DelayedValuesHelper(); @@ -34,9 +36,9 @@ namespace Avalonia.Markup.Xaml.PortableXaml private AvaloniaXamlObjectWriter( XamlSchemaContext schemaContext, XamlObjectWriterSettings settings, - AvaloniaNameScope nameScope - ) - : base(schemaContext, settings) + AvaloniaNameScope nameScope, + IAmbientProvider parentAmbientProvider) + : base(schemaContext, settings, parentAmbientProvider) { _nameScope = nameScope; } diff --git a/src/Markup/Avalonia.Markup.Xaml/PortableXaml/AvaloniaXamlSchemaContext.cs b/src/Markup/Avalonia.Markup.Xaml/PortableXaml/AvaloniaXamlSchemaContext.cs index bdb21abd77..fda5da902a 100644 --- a/src/Markup/Avalonia.Markup.Xaml/PortableXaml/AvaloniaXamlSchemaContext.cs +++ b/src/Markup/Avalonia.Markup.Xaml/PortableXaml/AvaloniaXamlSchemaContext.cs @@ -200,8 +200,7 @@ namespace Avalonia.Markup.Xaml.PortableXaml var type = (getter ?? setter).DeclaringType; - var prop = AvaloniaPropertyRegistry.Instance.GetAttached(type) - .FirstOrDefault(v => v.Name == attachablePropertyName); + var prop = AvaloniaPropertyRegistry.Instance.FindRegistered(type, attachablePropertyName); if (prop != null) { diff --git a/src/Markup/Avalonia.Markup.Xaml/PortableXaml/AvaloniaXamlType.cs b/src/Markup/Avalonia.Markup.Xaml/PortableXaml/AvaloniaXamlType.cs index 7de96ea220..59dbba7084 100644 --- a/src/Markup/Avalonia.Markup.Xaml/PortableXaml/AvaloniaXamlType.cs +++ b/src/Markup/Avalonia.Markup.Xaml/PortableXaml/AvaloniaXamlType.cs @@ -19,16 +19,36 @@ namespace Avalonia.Markup.Xaml.PortableXaml public class AvaloniaXamlType : XamlType { + static readonly AvaloniaPropertyTypeConverter propertyTypeConverter = new AvaloniaPropertyTypeConverter(); + public AvaloniaXamlType(Type underlyingType, XamlSchemaContext schemaContext) : base(underlyingType, schemaContext) { } + protected override XamlMember LookupAttachableMember(string name) + { + var m = base.LookupAttachableMember(name); + + if (m == null) + { + // Might be an AddOwnered attached property. + var avProp = AvaloniaPropertyRegistry.Instance.FindRegistered(UnderlyingType, name); + + if (avProp?.IsAttached == true) + { + return new AvaloniaPropertyXamlMember(avProp, this); + } + } + + return m; + } + protected override XamlMember LookupMember(string name, bool skipReadOnlyCheck) { var m = base.LookupMember(name, skipReadOnlyCheck); - if (m == null) + if (m == null && !name.Contains(".")) { //so far Portable.xaml haven't found the member/property //but what if we have AvaloniaProperty diff --git a/src/Markup/Avalonia.Markup.Xaml/PortableXaml/portable.xaml.github b/src/Markup/Avalonia.Markup.Xaml/PortableXaml/portable.xaml.github index c066401445..cdf46d7892 160000 --- a/src/Markup/Avalonia.Markup.Xaml/PortableXaml/portable.xaml.github +++ b/src/Markup/Avalonia.Markup.Xaml/PortableXaml/portable.xaml.github @@ -1 +1 @@ -Subproject commit c0664014455392ac221a765e66f9837704339b6f +Subproject commit cdf46d7892def8a6ba29f12a9339147377f7cf5c diff --git a/src/Markup/Avalonia.Markup.Xaml/Templates/TemplateContent.cs b/src/Markup/Avalonia.Markup.Xaml/Templates/TemplateContent.cs index 1d4dafc413..63fb9f193c 100644 --- a/src/Markup/Avalonia.Markup.Xaml/Templates/TemplateContent.cs +++ b/src/Markup/Avalonia.Markup.Xaml/Templates/TemplateContent.cs @@ -10,8 +10,10 @@ namespace Avalonia.Markup.Xaml.Templates public class TemplateContent { - public TemplateContent(IEnumerable namespaces, XamlReader reader) + public TemplateContent(IEnumerable namespaces, XamlReader reader, + IAmbientProvider ambientProvider) { + ParentAmbientProvider = ambientProvider; List = new XamlNodeList(reader.SchemaContext); //we need to rpeserve all namespace and prefixes to writer @@ -26,9 +28,11 @@ namespace Avalonia.Markup.Xaml.Templates public XamlNodeList List { get; } + private IAmbientProvider ParentAmbientProvider { get; } + public IControl Load() { - return (IControl)AvaloniaXamlLoader.LoadFromReader(List.GetReader()); + return (IControl)AvaloniaXamlLoader.LoadFromReader(List.GetReader(), parentAmbientProvider: ParentAmbientProvider); } public static IControl Load(object templateContent) diff --git a/src/Markup/Avalonia.Markup.Xaml/Templates/TemplateLoader.cs b/src/Markup/Avalonia.Markup.Xaml/Templates/TemplateLoader.cs index 1085131230..e29485ddb0 100644 --- a/src/Markup/Avalonia.Markup.Xaml/Templates/TemplateLoader.cs +++ b/src/Markup/Avalonia.Markup.Xaml/Templates/TemplateLoader.cs @@ -14,7 +14,8 @@ namespace Avalonia.Markup.Xaml.Templates { var tdc = (ITypeDescriptorContext)serviceProvider; var ns = tdc.GetService(); - return new TemplateContent(ns.GetNamespacePrefixes(), xamlReader); + var ambientProvider = tdc.GetService(); + return new TemplateContent(ns.GetNamespacePrefixes(), xamlReader, ambientProvider); } public override XamlReader Save(object value, IServiceProvider serviceProvider) diff --git a/src/Markup/Avalonia.Markup/Avalonia.Markup.csproj b/src/Markup/Avalonia.Markup/Avalonia.Markup.csproj index a6d3c3336f..9707662701 100644 --- a/src/Markup/Avalonia.Markup/Avalonia.Markup.csproj +++ b/src/Markup/Avalonia.Markup/Avalonia.Markup.csproj @@ -3,33 +3,6 @@ netstandard2.0 false - - true - full - false - bin\Debug\ - DEBUG;TRACE - prompt - 4 - bin\Debug\Avalonia.Markup.xml - CS1591 - - - pdbonly - true - bin\Release\ - TRACE - prompt - 4 - bin\Release\Avalonia.Markup.xml - CS1591 - true - - - - Properties\SharedAssemblyInfo.cs - - diff --git a/src/Markup/Avalonia.Markup/Data/Plugins/AvaloniaPropertyAccessorPlugin.cs b/src/Markup/Avalonia.Markup/Data/Plugins/AvaloniaPropertyAccessorPlugin.cs index 90eabc69fb..ac64459dd7 100644 --- a/src/Markup/Avalonia.Markup/Data/Plugins/AvaloniaPropertyAccessorPlugin.cs +++ b/src/Markup/Avalonia.Markup/Data/Plugins/AvaloniaPropertyAccessorPlugin.cs @@ -2,6 +2,7 @@ // Licensed under the MIT license. See licence.md file in the project root for full license information. using System; +using System.Linq; using System.Reactive.Linq; using Avalonia.Data; @@ -15,9 +16,9 @@ namespace Avalonia.Markup.Data.Plugins /// public bool Match(object obj, string propertyName) { - if (obj is AvaloniaObject a) + if (obj is AvaloniaObject o) { - return AvaloniaPropertyRegistry.Instance.FindRegistered(a, propertyName) != null; + return LookupProperty(o, propertyName) != null; } return false; @@ -39,7 +40,7 @@ namespace Avalonia.Markup.Data.Plugins var instance = reference.Target; var o = (AvaloniaObject)instance; - var p = AvaloniaPropertyRegistry.Instance.FindRegistered(o, propertyName); + var p = LookupProperty(o, propertyName); if (p != null) { @@ -57,6 +58,54 @@ namespace Avalonia.Markup.Data.Plugins } } + private static AvaloniaProperty LookupProperty(AvaloniaObject o, string propertyName) + { + if (!propertyName.Contains(".")) + { + return AvaloniaPropertyRegistry.Instance.FindRegistered(o, propertyName); + } + else + { + var split = propertyName.Split('.'); + + if (split.Length == 2) + { + // HACK: We need a way to resolve types here using something like IXamlTypeResolver. + // We don't currently have that so we have to make our best guess. + var type = split[0]; + var name = split[1]; + var registry = AvaloniaPropertyRegistry.Instance; + var registered = registry.GetRegisteredAttached(o.GetType()) + .Concat(registry.GetRegistered(o.GetType())); + + foreach (var p in registered) + { + if (p.Name == name && IsOfType(p.OwnerType, type)) + { + return p; + } + } + } + } + + return null; + } + + private static bool IsOfType(Type type, string typeName) + { + while (type != null) + { + if (type.Name == typeName) + { + return true; + } + + type = type.BaseType; + } + + return false; + } + private class Accessor : PropertyAccessorBase { private readonly WeakReference _reference; diff --git a/src/Markup/Avalonia.Markup/Properties/AssemblyInfo.cs b/src/Markup/Avalonia.Markup/Properties/AssemblyInfo.cs index dd8c0a6bd3..15c0be673a 100644 --- a/src/Markup/Avalonia.Markup/Properties/AssemblyInfo.cs +++ b/src/Markup/Avalonia.Markup/Properties/AssemblyInfo.cs @@ -5,6 +5,5 @@ using System.Reflection; using Avalonia.Metadata; using System.Runtime.CompilerServices; -[assembly: AssemblyTitle("Avalonia.Markup")] [assembly: XmlnsDefinition("https://github.com/avaloniaui", "Avalonia.Markup")] [assembly: InternalsVisibleTo("Avalonia.Markup.UnitTests")] diff --git a/src/OSX/Avalonia.MonoMac/Avalonia.MonoMac.csproj b/src/OSX/Avalonia.MonoMac/Avalonia.MonoMac.csproj index c31c131ea9..cbf91e818a 100644 --- a/src/OSX/Avalonia.MonoMac/Avalonia.MonoMac.csproj +++ b/src/OSX/Avalonia.MonoMac/Avalonia.MonoMac.csproj @@ -5,7 +5,6 @@ false - diff --git a/src/OSX/Avalonia.MonoMac/WindowBaseImpl.cs b/src/OSX/Avalonia.MonoMac/WindowBaseImpl.cs index e5ba285f4f..8cbc6cbdd8 100644 --- a/src/OSX/Avalonia.MonoMac/WindowBaseImpl.cs +++ b/src/OSX/Avalonia.MonoMac/WindowBaseImpl.cs @@ -161,6 +161,10 @@ namespace Avalonia.MonoMac Position = pos; } + public void SetMinMaxSize(Size minSize, Size maxSize) + { + } + public IScreenImpl Screen { get; diff --git a/src/OSX/Avalonia.MonoMac/WindowImpl.cs b/src/OSX/Avalonia.MonoMac/WindowImpl.cs index 6825fce82e..d01cbd6ae3 100644 --- a/src/OSX/Avalonia.MonoMac/WindowImpl.cs +++ b/src/OSX/Avalonia.MonoMac/WindowImpl.cs @@ -9,6 +9,7 @@ namespace Avalonia.MonoMac class WindowImpl : WindowBaseImpl, IWindowImpl { public bool IsDecorated = true; + public bool IsResizable = true; public CGRect? UndecoratedLastUnmaximizedFrame; public WindowImpl() @@ -76,10 +77,15 @@ namespace Avalonia.MonoMac protected override NSWindowStyle GetStyle() { + var windowStyle = NSWindowStyle.Borderless; + if (IsDecorated) - return NSWindowStyle.Closable | NSWindowStyle.Resizable | NSWindowStyle.Miniaturizable | - NSWindowStyle.Titled; - return NSWindowStyle.Borderless; + windowStyle |= NSWindowStyle.Closable | NSWindowStyle.Miniaturizable | NSWindowStyle.Titled; + + if (IsResizable) + windowStyle |= NSWindowStyle.Resizable; + + return windowStyle; } public void SetSystemDecorations(bool enabled) @@ -88,6 +94,12 @@ namespace Avalonia.MonoMac UpdateStyle(); } + public void CanResize(bool value) + { + IsResizable = value; + UpdateStyle(); + } + public void SetTitle(string title) => Window.Title = title; class ModalDisposable : IDisposable diff --git a/src/Shared/PlatformSupport/AssetLoader.cs b/src/Shared/PlatformSupport/AssetLoader.cs index 4287fd0b9e..fa11edb57b 100644 --- a/src/Shared/PlatformSupport/AssetLoader.cs +++ b/src/Shared/PlatformSupport/AssetLoader.cs @@ -67,7 +67,23 @@ namespace Avalonia.Shared.PlatformSupport /// /// The resource was not found. /// - public Stream Open(Uri uri, Uri baseUri = null) + public Stream Open(Uri uri, Uri baseUri = null) => OpenAndGetAssembly(uri, baseUri).Item1; + + /// + /// Opens the resource with the requested URI and returns the resource string and the + /// assembly containing the resource. + /// + /// The URI. + /// + /// A base URI to use if is relative. + /// + /// + /// The stream containing the resource contents together with the assembly. + /// + /// + /// The resource was not found. + /// + public Tuple OpenAndGetAssembly(Uri uri, Uri baseUri = null) { var asset = GetAsset(uri, baseUri); @@ -76,7 +92,7 @@ namespace Avalonia.Shared.PlatformSupport throw new FileNotFoundException($"The resource {uri} could not be found."); } - return asset.GetStream(); + return Tuple.Create(asset.GetStream(), asset.Assembly); } private IAssetDescriptor GetAsset(Uri uri, Uri baseUri) @@ -162,6 +178,7 @@ namespace Avalonia.Shared.PlatformSupport private interface IAssetDescriptor { Stream GetStream(); + Assembly Assembly { get; } } private class AssemblyResourceDescriptor : IAssetDescriptor @@ -179,6 +196,8 @@ namespace Avalonia.Shared.PlatformSupport { return _asm.GetManifestResourceStream(_name); } + + public Assembly Assembly => _asm; } private class AssemblyDescriptor diff --git a/src/Skia/Avalonia.Skia/Avalonia.Skia.csproj b/src/Skia/Avalonia.Skia/Avalonia.Skia.csproj index f5ed89d154..ade7f8778a 100644 --- a/src/Skia/Avalonia.Skia/Avalonia.Skia.csproj +++ b/src/Skia/Avalonia.Skia/Avalonia.Skia.csproj @@ -5,13 +5,8 @@ Avalonia.Skia Avalonia.Skia true - true + true - - - Properties\SharedAssemblyInfo.cs - - @@ -21,6 +16,7 @@ + - + \ No newline at end of file diff --git a/src/Windows/Avalonia.Win32/DataObject.cs b/src/Windows/Avalonia.Win32/DataObject.cs index 34867765e5..13d5f662c2 100644 --- a/src/Windows/Avalonia.Win32/DataObject.cs +++ b/src/Windows/Avalonia.Win32/DataObject.cs @@ -7,7 +7,6 @@ using System.Text; using Avalonia.Input; using Avalonia.Win32.Interop; using IDataObject = Avalonia.Input.IDataObject; -using IOleDataObject = System.Runtime.InteropServices.ComTypes.IDataObject; using System.IO; using System.Runtime.Serialization.Formatters.Binary; diff --git a/src/Windows/Avalonia.Win32/Interop/UnmanagedMethods.cs b/src/Windows/Avalonia.Win32/Interop/UnmanagedMethods.cs index aa86ab0f8d..86dcec410b 100644 --- a/src/Windows/Avalonia.Win32/Interop/UnmanagedMethods.cs +++ b/src/Windows/Avalonia.Win32/Interop/UnmanagedMethods.cs @@ -558,7 +558,18 @@ namespace Avalonia.Win32.Interop { DIB_RGB_COLORS = 0, /* color table in RGBs */ DIB_PAL_COLORS /* color table in palette indices */ - }; + } + + public enum WindowLongParam + { + GWL_WNDPROC = -4, + GWL_HINSTANCE = -6, + GWL_HWNDPARENT = -8, + GWL_ID = -12, + GWL_STYLE = -16, + GWL_EXSTYLE = -20, + GWL_USERDATA = -21 + } [StructLayout(LayoutKind.Sequential)] public struct RGBQUAD @@ -615,6 +626,16 @@ namespace Avalonia.Win32.Interop public uint[] cols; } + [StructLayout(LayoutKind.Sequential)] + public struct MINMAXINFO + { + public POINT ptReserved; + public POINT ptMaxSize; + public POINT ptMaxPosition; + public POINT ptMinTrackSize; + public POINT ptMaxTrackSize; + } + public const int SizeOf_BITMAPINFOHEADER = 40; [DllImport("user32.dll")] @@ -849,6 +870,9 @@ namespace Avalonia.Win32.Interop return SetClassLong64(hWnd, nIndex, dwNewLong); } + [DllImport("user32.dll", EntryPoint = "SetCursor")] + internal static extern IntPtr SetCursor(IntPtr hCursor); + [DllImport("ole32.dll", PreserveSig = true)] internal static extern int CoCreateInstance(ref Guid clsid, IntPtr ignore1, int ignore2, ref Guid iid, [MarshalAs(UnmanagedType.IUnknown), Out] out object pUnkOuter); @@ -974,7 +998,7 @@ namespace Avalonia.Win32.Interop public static extern int DragQueryFile(IntPtr hDrop, int iFile, StringBuilder lpszFile, int cch); [DllImport("ole32.dll", CharSet = CharSet.Auto, ExactSpelling = true, PreserveSig = false)] - public static extern void DoDragDrop(IDataObject dataObject, IDropSource dropSource, int allowedEffects, int[] finalEffect); + public static extern void DoDragDrop(IOleDataObject dataObject, IDropSource dropSource, int allowedEffects, int[] finalEffect); @@ -1163,27 +1187,21 @@ namespace Avalonia.Win32.Interop [Flags] public enum OpenFileNameFlags { - OFN_ALLOWMULTISELECT = 0x00000200, - OFN_EXPLORER = 0x00080000, - OFN_HIDEREADONLY = 0x00000004, - OFN_NOREADONLYRETURN = 0x00008000, - OFN_OVERWRITEPROMPT = 0x00000002 - } - public enum HRESULT : long + public enum HRESULT : uint { S_FALSE = 0x0001, S_OK = 0x0000, E_INVALIDARG = 0x80070057, E_OUTOFMEMORY = 0x8007000E, E_NOTIMPL = 0x80004001, - E_UNEXPECTED = 0x8000FFFF, + E_UNEXPECTED = 0x8000FFFF } public enum Icons @@ -1366,13 +1384,13 @@ namespace Avalonia.Win32.Interop internal interface IDropTarget { [PreserveSig] - UnmanagedMethods.HRESULT DragEnter([MarshalAs(UnmanagedType.Interface)] [In] IDataObject pDataObj, [MarshalAs(UnmanagedType.U4)] [In] int grfKeyState, [MarshalAs(UnmanagedType.U8)] [In] long pt, [In] [Out] ref DropEffect pdwEffect); + UnmanagedMethods.HRESULT DragEnter([MarshalAs(UnmanagedType.Interface)] [In] IOleDataObject pDataObj, [MarshalAs(UnmanagedType.U4)] [In] int grfKeyState, [MarshalAs(UnmanagedType.U8)] [In] long pt, [In] [Out] ref DropEffect pdwEffect); [PreserveSig] UnmanagedMethods.HRESULT DragOver([MarshalAs(UnmanagedType.U4)] [In] int grfKeyState, [MarshalAs(UnmanagedType.U8)] [In] long pt, [In] [Out] ref DropEffect pdwEffect); [PreserveSig] UnmanagedMethods.HRESULT DragLeave(); [PreserveSig] - UnmanagedMethods.HRESULT Drop([MarshalAs(UnmanagedType.Interface)] [In] IDataObject pDataObj, [MarshalAs(UnmanagedType.U4)] [In] int grfKeyState, [MarshalAs(UnmanagedType.U8)] [In] long pt, [In] [Out] ref DropEffect pdwEffect); + UnmanagedMethods.HRESULT Drop([MarshalAs(UnmanagedType.Interface)] [In] IOleDataObject pDataObj, [MarshalAs(UnmanagedType.U4)] [In] int grfKeyState, [MarshalAs(UnmanagedType.U8)] [In] long pt, [In] [Out] ref DropEffect pdwEffect); } [ComImport] @@ -1387,6 +1405,27 @@ namespace Avalonia.Win32.Interop } + [ComImport] + [Guid("0000010E-0000-0000-C000-000000000046")] + [InterfaceType(ComInterfaceType.InterfaceIsIUnknown)] + public interface IOleDataObject + { + void GetData([In] ref FORMATETC format, out STGMEDIUM medium); + void GetDataHere([In] ref FORMATETC format, ref STGMEDIUM medium); + [PreserveSig] + int QueryGetData([In] ref FORMATETC format); + [PreserveSig] + int GetCanonicalFormatEtc([In] ref FORMATETC formatIn, out FORMATETC formatOut); + void SetData([In] ref FORMATETC formatIn, [In] ref STGMEDIUM medium, [MarshalAs(UnmanagedType.Bool)] bool release); + IEnumFORMATETC EnumFormatEtc(DATADIR direction); + [PreserveSig] + int DAdvise([In] ref FORMATETC pFormatetc, ADVF advf, IAdviseSink adviseSink, out int connection); + void DUnadvise(int connection); + [PreserveSig] + int EnumDAdvise(out IEnumSTATDATA enumAdvise); + } + + [StructLayoutAttribute(LayoutKind.Sequential)] internal struct _DROPFILES { diff --git a/src/Windows/Avalonia.Win32/OleContext.cs b/src/Windows/Avalonia.Win32/OleContext.cs index 085c0f8ea9..d454c797fa 100644 --- a/src/Windows/Avalonia.Win32/OleContext.cs +++ b/src/Windows/Avalonia.Win32/OleContext.cs @@ -1,4 +1,5 @@ using System; +using System.ComponentModel; using System.Threading; using Avalonia.Platform; using Avalonia.Threading; @@ -26,8 +27,11 @@ namespace Avalonia.Win32 private OleContext() { - if (UnmanagedMethods.OleInitialize(IntPtr.Zero) != UnmanagedMethods.HRESULT.S_OK) - throw new SystemException("Failed to initialize OLE"); + UnmanagedMethods.HRESULT res = UnmanagedMethods.OleInitialize(IntPtr.Zero); + + if (res != UnmanagedMethods.HRESULT.S_OK && + res != UnmanagedMethods.HRESULT.S_FALSE /*already initialized*/) + throw new Win32Exception((int)res, "Failed to initialize OLE"); } private static bool IsValidOleThread() diff --git a/src/Windows/Avalonia.Win32/OleDataObject.cs b/src/Windows/Avalonia.Win32/OleDataObject.cs index 85d1daadeb..d7b663e7bf 100644 --- a/src/Windows/Avalonia.Win32/OleDataObject.cs +++ b/src/Windows/Avalonia.Win32/OleDataObject.cs @@ -8,15 +8,14 @@ using System.Runtime.Serialization.Formatters.Binary; using System.Text; using Avalonia.Input; using Avalonia.Win32.Interop; -using IDataObject = System.Runtime.InteropServices.ComTypes.IDataObject; namespace Avalonia.Win32 { class OleDataObject : Avalonia.Input.IDataObject { - private IDataObject _wrapped; + private IOleDataObject _wrapped; - public OleDataObject(IDataObject wrapped) + public OleDataObject(IOleDataObject wrapped) { _wrapped = wrapped; } diff --git a/src/Windows/Avalonia.Win32/OleDropTarget.cs b/src/Windows/Avalonia.Win32/OleDropTarget.cs index 500c03e317..973564a3d1 100644 --- a/src/Windows/Avalonia.Win32/OleDropTarget.cs +++ b/src/Windows/Avalonia.Win32/OleDropTarget.cs @@ -3,7 +3,6 @@ using Avalonia.Input.Raw; using Avalonia.Platform; using Avalonia.Win32.Interop; using IDataObject = Avalonia.Input.IDataObject; -using IOleDataObject = System.Runtime.InteropServices.ComTypes.IDataObject; namespace Avalonia.Win32 { diff --git a/src/Windows/Avalonia.Win32/ScreenImpl.cs b/src/Windows/Avalonia.Win32/ScreenImpl.cs index 113b2811dc..e1df24151d 100644 --- a/src/Windows/Avalonia.Win32/ScreenImpl.cs +++ b/src/Windows/Avalonia.Win32/ScreenImpl.cs @@ -2,16 +2,9 @@ // Licensed under the MIT license. See licence.md file in the project root for full license information. using System; -using System.Linq; -using Avalonia.Controls; using Avalonia.Platform; -using Avalonia.Utilities; using static Avalonia.Win32.Interop.UnmanagedMethods; -#if NETSTANDARD -using Win32Exception = Avalonia.Win32.NetStandard.AvaloniaWin32Exception; -#endif - namespace Avalonia.Win32 { public class ScreenImpl : IScreenImpl diff --git a/src/Windows/Avalonia.Win32/Win32Platform.cs b/src/Windows/Avalonia.Win32/Win32Platform.cs index 902abaf65b..a5088e794c 100644 --- a/src/Windows/Avalonia.Win32/Win32Platform.cs +++ b/src/Windows/Avalonia.Win32/Win32Platform.cs @@ -1,27 +1,23 @@ // Copyright (c) The Avalonia Project. All rights reserved. // Licensed under the MIT license. See licence.md file in the project root for full license information. -using Avalonia.Input.Platform; using System; using System.Collections.Generic; -using System.Reactive.Disposables; +using System.ComponentModel; using System.Diagnostics.CodeAnalysis; +using System.IO; +using System.Reactive.Disposables; using System.Runtime.InteropServices; using System.Threading; +using Avalonia.Controls; using Avalonia.Controls.Platform; using Avalonia.Input; +using Avalonia.Input.Platform; using Avalonia.Platform; -using Avalonia.Win32.Input; -using Avalonia.Win32.Interop; -using Avalonia.Controls; using Avalonia.Rendering; using Avalonia.Threading; -using System.IO; -#if NETSTANDARD -using Win32Exception = Avalonia.Win32.NetStandard.AvaloniaWin32Exception; -#else -using System.ComponentModel; -#endif +using Avalonia.Win32.Input; +using Avalonia.Win32.Interop; namespace Avalonia { @@ -86,11 +82,11 @@ namespace Avalonia.Win32 .Bind().ToConstant(s_instance) .Bind().ToConstant(s_instance); - if (OleContext.Current != null) - AvaloniaLocator.CurrentMutable.Bind().ToSingleton(); - UseDeferredRendering = deferredRendering; _uiThread = UnmanagedMethods.GetCurrentThreadId(); + + if (OleContext.Current != null) + AvaloniaLocator.CurrentMutable.Bind().ToSingleton(); } public bool HasMessages() diff --git a/src/Windows/Avalonia.Win32/WindowImpl.cs b/src/Windows/Avalonia.Win32/WindowImpl.cs index bb3c4cf6e6..be28b64c5a 100644 --- a/src/Windows/Avalonia.Win32/WindowImpl.cs +++ b/src/Windows/Avalonia.Win32/WindowImpl.cs @@ -31,10 +31,14 @@ namespace Avalonia.Win32 private IInputRoot _owner; private bool _trackingMouse; private bool _decorated = true; + private bool _resizable = true; private double _scaling = 1; private WindowState _showWindowState; private FramebufferManager _framebuffer; private OleDropTarget _dropTarget; + private Size _minSize; + private Size _maxSize; + #if USE_MANAGED_DRAG private readonly ManagedWindowResizeDragHelper _managedDrag; #endif @@ -77,8 +81,8 @@ namespace Avalonia.Win32 { get { - var style = UnmanagedMethods.GetWindowLong(_hwnd, -16); - var exStyle = UnmanagedMethods.GetWindowLong(_hwnd, -20); + var style = UnmanagedMethods.GetWindowLong(_hwnd, (int)UnmanagedMethods.WindowLongParam.GWL_STYLE); + var exStyle = UnmanagedMethods.GetWindowLong(_hwnd, (int)UnmanagedMethods.WindowLongParam.GWL_EXSTYLE); var padding = new UnmanagedMethods.RECT(); if (UnmanagedMethods.AdjustWindowRectEx(ref padding, style, false, exStyle)) @@ -102,6 +106,12 @@ namespace Avalonia.Win32 } } + public void SetMinMaxSize(Size minSize, Size maxSize) + { + _minSize = minSize; + _maxSize = maxSize; + } + public IScreenImpl Screen { get; @@ -235,7 +245,7 @@ namespace Avalonia.Win32 return; } - var style = (UnmanagedMethods.WindowStyles)UnmanagedMethods.GetWindowLong(_hwnd, -16); + var style = (UnmanagedMethods.WindowStyles)UnmanagedMethods.GetWindowLong(_hwnd, (int)UnmanagedMethods.WindowLongParam.GWL_STYLE); style |= UnmanagedMethods.WindowStyles.WS_OVERLAPPEDWINDOW; @@ -251,7 +261,7 @@ namespace Avalonia.Win32 Rect newRect; var oldThickness = BorderThickness; - UnmanagedMethods.SetWindowLong(_hwnd, -16, (uint)style); + UnmanagedMethods.SetWindowLong(_hwnd, (int)UnmanagedMethods.WindowLongParam.GWL_STYLE, (uint)style); if (value) { @@ -277,6 +287,21 @@ namespace Avalonia.Win32 UnmanagedMethods.SetWindowPosFlags.SWP_NOZORDER | UnmanagedMethods.SetWindowPosFlags.SWP_NOACTIVATE); _decorated = value; + + if(_decorated) + { + if (_resizable) + { + // If we switch decorations back on we need to restore WS_SizeFrame. + _resizable = false; + CanResize(true); + } + else + { + _resizable = true; + CanResize(false); + } + } } public void Invalidate(Rect rect) @@ -383,8 +408,11 @@ namespace Avalonia.Win32 public void SetCursor(IPlatformHandle cursor) { - UnmanagedMethods.SetClassLong(_hwnd, UnmanagedMethods.ClassLongIndex.GCL_HCURSOR, - cursor?.Handle ?? DefaultCursor); + var hCursor = cursor?.Handle ?? DefaultCursor; + UnmanagedMethods.SetClassLong(_hwnd, UnmanagedMethods.ClassLongIndex.GCL_HCURSOR, hCursor); + + if (_owner.IsPointerOver) + UnmanagedMethods.SetCursor(hCursor); } protected virtual IntPtr CreateWindowOverride(ushort atom) @@ -611,7 +639,26 @@ namespace Avalonia.Win32 case UnmanagedMethods.WindowsMessage.WM_MOVE: PositionChanged?.Invoke(new Point((short)(ToInt32(lParam) & 0xffff), (short)(ToInt32(lParam) >> 16))); return IntPtr.Zero; - + + case UnmanagedMethods.WindowsMessage.WM_GETMINMAXINFO: + + MINMAXINFO mmi = Marshal.PtrToStructure(lParam); + + if (_minSize.Width > 0) + mmi.ptMinTrackSize.X = (int)((_minSize.Width * Scaling) + BorderThickness.Left + BorderThickness.Right); + + if (_minSize.Height > 0) + mmi.ptMinTrackSize.Y = (int)((_minSize.Height * Scaling) + BorderThickness.Top + BorderThickness.Bottom); + + if (!Double.IsInfinity(_maxSize.Width) && _maxSize.Width > 0) + mmi.ptMaxTrackSize.X = (int)((_maxSize.Width * Scaling) + BorderThickness.Left + BorderThickness.Right); + + if (!Double.IsInfinity(_maxSize.Height) && _maxSize.Height > 0) + mmi.ptMaxTrackSize.Y = (int)((_maxSize.Height * Scaling) + BorderThickness.Top + BorderThickness.Bottom); + + Marshal.StructureToPtr(mmi, lParam, true); + return IntPtr.Zero; + case UnmanagedMethods.WindowsMessage.WM_DISPLAYCHANGE: (Screen as ScreenImpl)?.InvalidateScreensCache(); return IntPtr.Zero; @@ -798,11 +845,11 @@ namespace Avalonia.Win32 public void ShowTaskbarIcon(bool value) { - var style = (UnmanagedMethods.WindowStyles)UnmanagedMethods.GetWindowLong(_hwnd, -20); - - style &= ~(UnmanagedMethods.WindowStyles.WS_VISIBLE); + var style = (UnmanagedMethods.WindowStyles)UnmanagedMethods.GetWindowLong(_hwnd, (int)UnmanagedMethods.WindowLongParam.GWL_EXSTYLE); + + style &= ~(UnmanagedMethods.WindowStyles.WS_VISIBLE); - style |= UnmanagedMethods.WindowStyles.WS_EX_TOOLWINDOW; + style |= UnmanagedMethods.WindowStyles.WS_EX_TOOLWINDOW; if (value) style |= UnmanagedMethods.WindowStyles.WS_EX_APPWINDOW; else @@ -813,9 +860,31 @@ namespace Avalonia.Win32 { //Toggle to make the styles stick UnmanagedMethods.ShowWindow(_hwnd, ShowWindowCommand.Hide); - UnmanagedMethods.SetWindowLong(_hwnd, -20, (uint)style); + UnmanagedMethods.SetWindowLong(_hwnd, (int)UnmanagedMethods.WindowLongParam.GWL_EXSTYLE, (uint)style); UnmanagedMethods.ShowWindow(_hwnd, windowPlacement.ShowCmd); } } + + public void CanResize(bool value) + { + if (value == _resizable) + { + return; + } + + if (_decorated) + { + var style = (UnmanagedMethods.WindowStyles)UnmanagedMethods.GetWindowLong(_hwnd, (int)UnmanagedMethods.WindowLongParam.GWL_STYLE); + + if (value) + style |= UnmanagedMethods.WindowStyles.WS_SIZEFRAME; + else + style &= ~(UnmanagedMethods.WindowStyles.WS_SIZEFRAME); + + UnmanagedMethods.SetWindowLong(_hwnd, (int)UnmanagedMethods.WindowLongParam.GWL_STYLE, (uint)style); + } + + _resizable = value; + } } } diff --git a/src/iOS/Avalonia.iOS/EmbeddableImpl.cs b/src/iOS/Avalonia.iOS/EmbeddableImpl.cs index 50a3cd9ec0..3d8bafeca9 100644 --- a/src/iOS/Avalonia.iOS/EmbeddableImpl.cs +++ b/src/iOS/Avalonia.iOS/EmbeddableImpl.cs @@ -14,6 +14,10 @@ namespace Avalonia.iOS } + public void SetMinMaxSize(Size minSize, Size maxSize) + { + } + public IDisposable ShowDialog() { return Disposable.Empty; diff --git a/tests/Avalonia.Base.UnitTests/AvaloniaObjectTests_AddOwner.cs b/tests/Avalonia.Base.UnitTests/AvaloniaObjectTests_AddOwner.cs new file mode 100644 index 0000000000..4e033be3fb --- /dev/null +++ b/tests/Avalonia.Base.UnitTests/AvaloniaObjectTests_AddOwner.cs @@ -0,0 +1,52 @@ +// Copyright (c) The Avalonia Project. All rights reserved. +// Licensed under the MIT license. See licence.md file in the project root for full license information. + +using System; +using Xunit; + +namespace Avalonia.Base.UnitTests +{ + public class AvaloniaObjectTests_AddOwner + { + [Fact] + public void AddOwnered_Property_Retains_Default_Value() + { + var target = new Class2(); + + Assert.Equal("foodefault", target.GetValue(Class2.FooProperty)); + } + + [Fact] + public void AddOwnered_Property_Does_Not_Retain_Validation() + { + var target = new Class2(); + + target.SetValue(Class2.FooProperty, "throw"); + } + + private class Class1 : AvaloniaObject + { + public static readonly StyledProperty FooProperty = + AvaloniaProperty.Register( + "Foo", + "foodefault", + validate: ValidateFoo); + + private static string ValidateFoo(AvaloniaObject arg1, string arg2) + { + if (arg2 == "throw") + { + throw new IndexOutOfRangeException(); + } + + return arg2; + } + } + + private class Class2 : AvaloniaObject + { + public static readonly StyledProperty FooProperty = + Class1.FooProperty.AddOwner(); + } + } +} diff --git a/tests/Avalonia.Base.UnitTests/AvaloniaObjectTests_Attached.cs b/tests/Avalonia.Base.UnitTests/AvaloniaObjectTests_Attached.cs new file mode 100644 index 0000000000..02600f5e00 --- /dev/null +++ b/tests/Avalonia.Base.UnitTests/AvaloniaObjectTests_Attached.cs @@ -0,0 +1,73 @@ +// Copyright (c) The Avalonia Project. All rights reserved. +// Licensed under the MIT license. See licence.md file in the project root for full license information. + +using System; +using Xunit; + +namespace Avalonia.Base.UnitTests +{ + public class AvaloniaObjectTests_Attached + { + [Fact] + public void AddOwnered_Property_Retains_Default_Value() + { + var target = new Class2(); + + Assert.Equal("foodefault", target.GetValue(Class2.FooProperty)); + } + + [Fact] + public void AddOwnered_Property_Retains_Validation() + { + var target = new Class2(); + + Assert.Throws(() => target.SetValue(Class2.FooProperty, "throw")); + } + + [Fact] + public void AvaloniaProperty_Initialized_Is_Called_For_Attached_Property() + { + bool raised = false; + + using (Class1.FooProperty.Initialized.Subscribe(x => raised = true)) + { + new Class3(); + } + + Assert.True(raised); + } + + private class Base : AvaloniaObject + { + } + + private class Class1 : Base + { + public static readonly AttachedProperty FooProperty = + AvaloniaProperty.RegisterAttached( + "Foo", + "foodefault", + validate: ValidateFoo); + + private static string ValidateFoo(AvaloniaObject arg1, string arg2) + { + if (arg2 == "throw") + { + throw new IndexOutOfRangeException(); + } + + return arg2; + } + } + + private class Class2 : Base + { + public static readonly AttachedProperty FooProperty = + Class1.FooProperty.AddOwner(); + } + + private class Class3 : Base + { + } + } +} diff --git a/tests/Avalonia.Base.UnitTests/AvaloniaObjectTests_Binding.cs b/tests/Avalonia.Base.UnitTests/AvaloniaObjectTests_Binding.cs index c75150ca6d..80cd52d529 100644 --- a/tests/Avalonia.Base.UnitTests/AvaloniaObjectTests_Binding.cs +++ b/tests/Avalonia.Base.UnitTests/AvaloniaObjectTests_Binding.cs @@ -92,14 +92,13 @@ namespace Avalonia.Base.UnitTests } [Fact] - public void Bind_Throws_Exception_For_Unregistered_Property() + public void Bind_Does_Not_Throw_Exception_For_Unregistered_Property() { Class1 target = new Class1(); - Assert.Throws(() => - { - target.Bind(Class2.BarProperty, Observable.Return("foo")); - }); + target.Bind(Class2.BarProperty, Observable.Never().StartWith("foo")); + + Assert.Equal("foo", target.GetValue(Class2.BarProperty)); } [Fact] diff --git a/tests/Avalonia.Base.UnitTests/AvaloniaObjectTests_GetValue.cs b/tests/Avalonia.Base.UnitTests/AvaloniaObjectTests_GetValue.cs index 98f7289228..740023fd37 100644 --- a/tests/Avalonia.Base.UnitTests/AvaloniaObjectTests_GetValue.cs +++ b/tests/Avalonia.Base.UnitTests/AvaloniaObjectTests_GetValue.cs @@ -46,11 +46,11 @@ namespace Avalonia.Base.UnitTests } [Fact] - public void GetValue_Throws_Exception_For_Unregistered_Property() + public void GetValue_Doesnt_Throw_Exception_For_Unregistered_Property() { var target = new Class3(); - Assert.Throws(() => target.GetValue(Class1.FooProperty)); + Assert.Equal("foodefault", target.GetValue(Class1.FooProperty)); } private class Class1 : AvaloniaObject diff --git a/tests/Avalonia.Base.UnitTests/AvaloniaObjectTests_SetValue.cs b/tests/Avalonia.Base.UnitTests/AvaloniaObjectTests_SetValue.cs index 1f9c47f8ae..a56cd717b9 100644 --- a/tests/Avalonia.Base.UnitTests/AvaloniaObjectTests_SetValue.cs +++ b/tests/Avalonia.Base.UnitTests/AvaloniaObjectTests_SetValue.cs @@ -30,6 +30,16 @@ namespace Avalonia.Base.UnitTests Assert.Equal("newvalue", target.GetValue(Class1.FooProperty)); } + [Fact] + public void SetValue_Sets_Attached_Value() + { + Class2 target = new Class2(); + + target.SetValue(AttachedOwner.AttachedProperty, "newvalue"); + + Assert.Equal("newvalue", target.GetValue(AttachedOwner.AttachedProperty)); + } + [Fact] public void SetValue_Raises_PropertyChanged() { @@ -84,14 +94,27 @@ namespace Avalonia.Base.UnitTests } [Fact] - public void SetValue_Throws_Exception_For_Unregistered_Property() + public void SetValue_Allows_Setting_Unregistered_Property() { Class1 target = new Class1(); - Assert.Throws(() => - { - target.SetValue(Class2.BarProperty, "invalid"); - }); + Assert.False(AvaloniaPropertyRegistry.Instance.IsRegistered(target, Class2.BarProperty)); + + target.SetValue(Class2.BarProperty, "bar"); + + Assert.Equal("bar", target.GetValue(Class2.BarProperty)); + } + + [Fact] + public void SetValue_Allows_Setting_Unregistered_Attached_Property() + { + Class1 target = new Class1(); + + Assert.False(AvaloniaPropertyRegistry.Instance.IsRegistered(target, AttachedOwner.AttachedProperty)); + + target.SetValue(AttachedOwner.AttachedProperty, "bar"); + + Assert.Equal("bar", target.GetValue(AttachedOwner.AttachedProperty)); } [Fact] @@ -189,6 +212,12 @@ namespace Avalonia.Base.UnitTests } } + private class AttachedOwner + { + public static readonly AttachedProperty AttachedProperty = + AvaloniaProperty.RegisterAttached("Attached"); + } + private class ImplictDouble { public ImplictDouble(double value) diff --git a/tests/Avalonia.Base.UnitTests/AvaloniaPropertyRegistryTests.cs b/tests/Avalonia.Base.UnitTests/AvaloniaPropertyRegistryTests.cs index da0b0252a3..8b2e500d37 100644 --- a/tests/Avalonia.Base.UnitTests/AvaloniaPropertyRegistryTests.cs +++ b/tests/Avalonia.Base.UnitTests/AvaloniaPropertyRegistryTests.cs @@ -4,12 +4,13 @@ using System.Linq; using System.Reactive.Linq; using Xunit; +using Xunit.Abstractions; namespace Avalonia.Base.UnitTests { public class AvaloniaPropertyRegistryTests { - public AvaloniaPropertyRegistryTests() + public AvaloniaPropertyRegistryTests(ITestOutputHelper s) { // Ensure properties are registered. AvaloniaProperty p; @@ -25,7 +26,7 @@ namespace Avalonia.Base.UnitTests .Select(x => x.Name) .ToArray(); - Assert.Equal(new[] { "Foo", "Baz", "Qux", "Attached" }, names); + Assert.Equal(new[] { "Foo", "Baz", "Qux" }, names); } [Fact] @@ -35,61 +36,41 @@ namespace Avalonia.Base.UnitTests .Select(x => x.Name) .ToArray(); - Assert.Equal(new[] { "Bar", "Flob", "Fred", "Foo", "Baz", "Qux", "Attached" }, names); + Assert.Equal(new[] { "Bar", "Flob", "Fred", "Foo", "Baz", "Qux" }, names); } [Fact] - public void GetAttached_Returns_Registered_Properties_For_Base_Types() + public void GetRegisteredAttached_Returns_Registered_Properties() { - string[] names = AvaloniaPropertyRegistry.Instance.GetAttached(typeof(AttachedOwner)).Select(x => x.Name).ToArray(); + string[] names = AvaloniaPropertyRegistry.Instance.GetRegisteredAttached(typeof(Class1)) + .Select(x => x.Name) + .ToArray(); Assert.Equal(new[] { "Attached" }, names); } [Fact] - public void FindRegistered_Finds_Untyped_Property() + public void GetRegisteredAttached_Returns_Registered_Properties_For_Base_Types() { - var result = AvaloniaPropertyRegistry.Instance.FindRegistered(typeof(Class1), "Foo"); + string[] names = AvaloniaPropertyRegistry.Instance.GetRegisteredAttached(typeof(Class2)) + .Select(x => x.Name) + .ToArray(); - Assert.Equal(Class1.FooProperty, result); + Assert.Equal(new[] { "Attached" }, names); } [Fact] - public void FindRegistered_Finds_Typed_Property() + public void FindRegistered_Finds_Property() { - var result = AvaloniaPropertyRegistry.Instance.FindRegistered(typeof(Class1), "Class1.Foo"); + var result = AvaloniaPropertyRegistry.Instance.FindRegistered(typeof(Class1), "Foo"); Assert.Equal(Class1.FooProperty, result); } [Fact] - public void FindRegistered_Finds_Typed_Inherited_Property() - { - var result = AvaloniaPropertyRegistry.Instance.FindRegistered(typeof(Class2), "Class1.Foo"); - - Assert.Equal(Class2.FooProperty, result); - } - - [Fact] - public void FindRegistered_Finds_Inherited_Property_With_Derived_Type_Name() - { - var result = AvaloniaPropertyRegistry.Instance.FindRegistered(typeof(Class2), "Class2.Foo"); - - Assert.Equal(Class2.FooProperty, result); - } - - [Fact] - public void FindRegistered_Finds_Attached_Property() - { - var result = AvaloniaPropertyRegistry.Instance.FindRegistered(typeof(Class2), "AttachedOwner.Attached"); - - Assert.Equal(AttachedOwner.AttachedProperty, result); - } - - [Fact] - public void FindRegistered_Doesnt_Finds_Unqualified_Attached_Property() + public void FindRegistered_Doesnt_Find_Nonregistered_Property() { - var result = AvaloniaPropertyRegistry.Instance.FindRegistered(typeof(Class2), "Attached"); + var result = AvaloniaPropertyRegistry.Instance.FindRegistered(typeof(Class1), "Bar"); Assert.Null(result); } @@ -99,55 +80,34 @@ namespace Avalonia.Base.UnitTests { var result = AvaloniaPropertyRegistry.Instance.FindRegistered(typeof(AttachedOwner), "Attached"); - Assert.True(AttachedOwner.AttachedProperty == result); + Assert.Same(AttachedOwner.AttachedProperty, result); } [Fact] - public void FindRegistered_Finds_AddOwnered_Untyped_Attached_Property() + public void FindRegistered_Finds_AddOwnered_Attached_Property() { var result = AvaloniaPropertyRegistry.Instance.FindRegistered(typeof(Class3), "Attached"); - Assert.True(AttachedOwner.AttachedProperty == result); + Assert.Same(AttachedOwner.AttachedProperty, result); } [Fact] - public void FindRegistered_Finds_AddOwnered_Typed_Attached_Property() + public void FindRegistered_Doesnt_Find_Non_AddOwnered_Attached_Property() { - var result = AvaloniaPropertyRegistry.Instance.FindRegistered(typeof(Class3), "Class3.Attached"); - - Assert.True(AttachedOwner.AttachedProperty == result); - } - - [Fact] - public void FindRegistered_Finds_AddOwnered_AttachedTyped_Attached_Property() - { - var result = AvaloniaPropertyRegistry.Instance.FindRegistered(typeof(Class3), "AttachedOwner.Attached"); - - Assert.True(AttachedOwner.AttachedProperty == result); - } - - [Fact] - public void FindRegistered_Finds_AddOwnered_BaseTyped_Attached_Property() - { - var result = AvaloniaPropertyRegistry.Instance.FindRegistered(typeof(Class3), "Class1.Attached"); - - Assert.True(AttachedOwner.AttachedProperty == result); - } - - [Fact] - public void FindRegistered_Doesnt_Find_Nonregistered_Property() - { - var result = AvaloniaPropertyRegistry.Instance.FindRegistered(typeof(Class1), "Bar"); + var result = AvaloniaPropertyRegistry.Instance.FindRegistered(typeof(Class2), "Attached"); Assert.Null(result); } [Fact] - public void FindRegistered_Doesnt_Find_Nonregistered_Attached_Property() + public void FindRegisteredAttached_Finds_Property() { - var result = AvaloniaPropertyRegistry.Instance.FindRegistered(typeof(Class4), "AttachedOwner.Attached"); + var result = AvaloniaPropertyRegistry.Instance.FindRegisteredAttached( + typeof(Class1), + typeof(AttachedOwner), + "Attached"); - Assert.Null(result); + Assert.Equal(AttachedOwner.AttachedProperty, result); } private class Class1 : AvaloniaObject @@ -176,18 +136,18 @@ namespace Avalonia.Base.UnitTests private class Class3 : Class1 { - public static readonly StyledProperty AttachedProperty = + public static readonly AttachedProperty AttachedProperty = AttachedOwner.AttachedProperty.AddOwner(); } - public class Class4 : AvaloniaObject - { - } - private class AttachedOwner : Class1 { public static readonly AttachedProperty AttachedProperty = AvaloniaProperty.RegisterAttached("Attached"); } + + private class AttachedOwner2 : AttachedOwner + { + } } } diff --git a/tests/Avalonia.Base.UnitTests/Utilities/StringTokenizerTests.cs b/tests/Avalonia.Base.UnitTests/Utilities/StringTokenizerTests.cs new file mode 100644 index 0000000000..4f94b1ca80 --- /dev/null +++ b/tests/Avalonia.Base.UnitTests/Utilities/StringTokenizerTests.cs @@ -0,0 +1,69 @@ +using System; +using Avalonia.Utilities; +using Xunit; + +namespace Avalonia.Base.UnitTests.Utilities +{ + public class StringTokenizerTests + { + [Fact] + public void ReadInt32_Reads_Values() + { + var target = new StringTokenizer("123,456"); + + Assert.Equal(123, target.ReadInt32()); + Assert.Equal(456, target.ReadInt32()); + Assert.Throws(() => target.ReadInt32()); + } + + [Fact] + public void ReadDouble_Reads_Values() + { + var target = new StringTokenizer("12.3,45.6"); + + Assert.Equal(12.3, target.ReadDouble()); + Assert.Equal(45.6, target.ReadDouble()); + Assert.Throws(() => target.ReadDouble()); + } + + [Fact] + public void TryReadInt32_Reads_Values() + { + var target = new StringTokenizer("123,456"); + + Assert.True(target.TryReadInt32(out var value)); + Assert.Equal(123, value); + Assert.True(target.TryReadInt32(out value)); + Assert.Equal(456, value); + Assert.False(target.TryReadInt32(out value)); + } + + [Fact] + public void TryReadInt32_Doesnt_Throw() + { + var target = new StringTokenizer("abc"); + + Assert.False(target.TryReadInt32(out var value)); + } + + [Fact] + public void TryReadDouble_Reads_Values() + { + var target = new StringTokenizer("12.3,45.6"); + + Assert.True(target.TryReadDouble(out var value)); + Assert.Equal(12.3, value); + Assert.True(target.TryReadDouble(out value)); + Assert.Equal(45.6, value); + Assert.False(target.TryReadDouble(out value)); + } + + [Fact] + public void TryReadDouble_Doesnt_Throw() + { + var target = new StringTokenizer("abc"); + + Assert.False(target.TryReadDouble(out var value)); + } + } +} diff --git a/tests/Avalonia.Controls.UnitTests/Avalonia.Controls.UnitTests.csproj b/tests/Avalonia.Controls.UnitTests/Avalonia.Controls.UnitTests.csproj index fe7e48e085..3706a50525 100644 --- a/tests/Avalonia.Controls.UnitTests/Avalonia.Controls.UnitTests.csproj +++ b/tests/Avalonia.Controls.UnitTests/Avalonia.Controls.UnitTests.csproj @@ -1,6 +1,7 @@  netcoreapp2.0 + latest Library diff --git a/tests/Avalonia.Controls.UnitTests/GridLayoutTests.cs b/tests/Avalonia.Controls.UnitTests/GridLayoutTests.cs new file mode 100644 index 0000000000..fbb90de505 --- /dev/null +++ b/tests/Avalonia.Controls.UnitTests/GridLayoutTests.cs @@ -0,0 +1,173 @@ +using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; +using System.Linq; +using Avalonia.Controls.Utils; +using Xunit; + +namespace Avalonia.Controls.UnitTests +{ + public class GridLayoutTests + { + private const double Inf = double.PositiveInfinity; + + [Theory] + [InlineData("100, 200, 300", 0d, 0d, new[] { 0d, 0d, 0d })] + [InlineData("100, 200, 300", 800d, 600d, new[] { 100d, 200d, 300d })] + [InlineData("100, 200, 300", 600d, 600d, new[] { 100d, 200d, 300d })] + [InlineData("100, 200, 300", 400d, 400d, new[] { 100d, 200d, 100d })] + public void MeasureArrange_AllPixelLength_Correct(string length, double containerLength, + double expectedDesiredLength, IList expectedLengthList) + { + TestRowDefinitionsOnly(length, containerLength, expectedDesiredLength, expectedLengthList); + } + + [Theory] + [InlineData("*,2*,3*", 0d, 0d, new[] { 0d, 0d, 0d })] + [InlineData("*,2*,3*", 600d, 0d, new[] { 100d, 200d, 300d })] + public void MeasureArrange_AllStarLength_Correct(string length, double containerLength, + double expectedDesiredLength, IList expectedLengthList) + { + TestRowDefinitionsOnly(length, containerLength, expectedDesiredLength, expectedLengthList); + } + + [Theory] + [InlineData("100,2*,3*", 0d, 0d, new[] { 0d, 0d, 0d })] + [InlineData("100,2*,3*", 600d, 100d, new[] { 100d, 200d, 300d })] + [InlineData("100,2*,3*", 100d, 100d, new[] { 100d, 0d, 0d })] + [InlineData("100,2*,3*", 50d, 50d, new[] { 50d, 0d, 0d })] + public void MeasureArrange_MixStarPixelLength_Correct(string length, double containerLength, + double expectedDesiredLength, IList expectedLengthList) + { + TestRowDefinitionsOnly(length, containerLength, expectedDesiredLength, expectedLengthList); + } + + [Theory] + [InlineData("100,200,Auto", 0d, 0d, new[] { 0d, 0d, 0d })] + [InlineData("100,200,Auto", 600d, 300d, new[] { 100d, 200d, 0d })] + [InlineData("100,200,Auto", 300d, 300d, new[] { 100d, 200d, 0d })] + [InlineData("100,200,Auto", 200d, 200d, new[] { 100d, 100d, 0d })] + [InlineData("100,200,Auto", 100d, 100d, new[] { 100d, 0d, 0d })] + [InlineData("100,200,Auto", 50d, 50d, new[] { 50d, 0d, 0d })] + public void MeasureArrange_MixAutoPixelLength_Correct(string length, double containerLength, + double expectedDesiredLength, IList expectedLengthList) + { + TestRowDefinitionsOnly(length, containerLength, expectedDesiredLength, expectedLengthList); + } + + [Theory] + [InlineData("*,2*,Auto", 0d, 0d, new[] { 0d, 0d, 0d })] + [InlineData("*,2*,Auto", 600d, 0d, new[] { 200d, 400d, 0d })] + public void MeasureArrange_MixAutoStarLength_Correct(string length, double containerLength, + double expectedDesiredLength, IList expectedLengthList) + { + TestRowDefinitionsOnly(length, containerLength, expectedDesiredLength, expectedLengthList); + } + + [Theory] + [InlineData("*,200,Auto", 0d, 0d, new[] { 0d, 0d, 0d })] + [InlineData("*,200,Auto", 600d, 200d, new[] { 400d, 200d, 0d })] + [InlineData("*,200,Auto", 200d, 200d, new[] { 0d, 200d, 0d })] + [InlineData("*,200,Auto", 100d, 100d, new[] { 0d, 100d, 0d })] + public void MeasureArrange_MixAutoStarPixelLength_Correct(string length, double containerLength, + double expectedDesiredLength, IList expectedLengthList) + { + TestRowDefinitionsOnly(length, containerLength, expectedDesiredLength, expectedLengthList); + } + + [SuppressMessage("ReSharper", "ParameterOnlyUsedForPreconditionCheck.Local")] + private static void TestRowDefinitionsOnly(string length, double containerLength, + double expectedDesiredLength, IList expectedLengthList) + { + // Arrange + var layout = new GridLayout(new RowDefinitions(length)); + + // Measure - Action & Assert + var measure = layout.Measure(containerLength); + Assert.Equal(expectedDesiredLength, measure.DesiredLength); + Assert.Equal(expectedLengthList, measure.LengthList); + + // Arrange - Action & Assert + var arrange = layout.Arrange(containerLength, measure); + Assert.Equal(expectedLengthList, arrange.LengthList); + } + + [Theory] + [InlineData("100, 200, 300", 600d, new[] { 100d, 200d, 300d }, new[] { 100d, 200d, 300d })] + [InlineData("*,2*,3*", 0d, new[] { Inf, Inf, Inf }, new[] { 0d, 0d, 0d })] + [InlineData("100,2*,3*", 100d, new[] { 100d, Inf, Inf }, new[] { 100d, 0d, 0d })] + [InlineData("100,200,Auto", 300d, new[] { 100d, 200d, 0d }, new[] { 100d, 200d, 0d })] + [InlineData("*,2*,Auto", 0d, new[] { Inf, Inf, 0d }, new[] { 0d, 0d, 0d })] + [InlineData("*,200,Auto", 200d, new[] { Inf, 200d, 0d }, new[] { 0d, 200d, 0d })] + public void MeasureArrange_InfiniteMeasure_Correct(string length, double expectedDesiredLength, + IList expectedMeasureList, IList expectedArrangeList) + { + // Arrange + var layout = new GridLayout(new RowDefinitions(length)); + + // Measure - Action & Assert + var measure = layout.Measure(Inf); + Assert.Equal(expectedDesiredLength, measure.DesiredLength); + Assert.Equal(expectedMeasureList, measure.LengthList); + + // Arrange - Action & Assert + var arrange = layout.Arrange(measure.DesiredLength, measure); + Assert.Equal(expectedArrangeList, arrange.LengthList); + } + + [Theory] + [InlineData("Auto,*,*", new[] { 100d, 100d, 100d }, 600d, 300d, new[] { 100d, 250d, 250d })] + public void MeasureArrange_ChildHasSize_Correct(string length, + IList childLengthList, double containerLength, + double expectedDesiredLength, IList expectedLengthList) + { + // Arrange + var lengthList = new ColumnDefinitions(length); + var layout = new GridLayout(lengthList); + layout.AppendMeasureConventions( + Enumerable.Range(0, lengthList.Count).ToDictionary(x => x, x => (x, 1)), + x => childLengthList[x]); + + // Measure - Action & Assert + var measure = layout.Measure(containerLength); + Assert.Equal(expectedDesiredLength, measure.DesiredLength); + Assert.Equal(expectedLengthList, measure.LengthList); + + // Arrange - Action & Assert + var arrange = layout.Arrange(containerLength, measure); + Assert.Equal(expectedLengthList, arrange.LengthList); + } + + [Theory] + [InlineData(Inf, 250d, new[] { 100d, Inf, Inf }, new[] { 100d, 50d, 100d })] + [InlineData(400d, 250d, new[] { 100d, 100d, 200d }, new[] { 100d, 100d, 200d })] + [InlineData(325d, 250d, new[] { 100d, 75d, 150d }, new[] { 100d, 75d, 150d })] + [InlineData(250d, 250d, new[] { 100d, 50d, 100d }, new[] { 100d, 50d, 100d })] + [InlineData(160d, 160d, new[] { 100d, 20d, 40d }, new[] { 100d, 20d, 40d })] + public void MeasureArrange_ChildHasSizeAndHasMultiSpan_Correct( + double containerLength, double expectedDesiredLength, + IList expectedMeasureLengthList, IList expectedArrangeLengthList) + { + var length = "100,*,2*"; + var childLengthList = new[] { 150d, 150d, 150d }; + var spans = new[] { 1, 2, 1 }; + + // Arrange + var lengthList = new ColumnDefinitions(length); + var layout = new GridLayout(lengthList); + layout.AppendMeasureConventions( + Enumerable.Range(0, lengthList.Count).ToDictionary(x => x, x => (x, spans[x])), + x => childLengthList[x]); + + // Measure - Action & Assert + var measure = layout.Measure(containerLength); + Assert.Equal(expectedDesiredLength, measure.DesiredLength); + Assert.Equal(expectedMeasureLengthList, measure.LengthList); + + // Arrange - Action & Assert + var arrange = layout.Arrange( + double.IsInfinity(containerLength) ? measure.DesiredLength : containerLength, + measure); + Assert.Equal(expectedArrangeLengthList, arrange.LengthList); + } + } +} diff --git a/tests/Avalonia.Controls.UnitTests/GridLengthTests.cs b/tests/Avalonia.Controls.UnitTests/GridLengthTests.cs index 0a811333d4..ab4da0ca7e 100644 --- a/tests/Avalonia.Controls.UnitTests/GridLengthTests.cs +++ b/tests/Avalonia.Controls.UnitTests/GridLengthTests.cs @@ -13,7 +13,7 @@ namespace Avalonia.Controls.UnitTests [Fact] public void Parse_Should_Parse_Auto() { - var result = GridLength.Parse("Auto", CultureInfo.InvariantCulture); + var result = GridLength.Parse("Auto"); Assert.Equal(GridLength.Auto, result); } @@ -21,7 +21,7 @@ namespace Avalonia.Controls.UnitTests [Fact] public void Parse_Should_Parse_Auto_Lowercase() { - var result = GridLength.Parse("auto", CultureInfo.InvariantCulture); + var result = GridLength.Parse("auto"); Assert.Equal(GridLength.Auto, result); } @@ -29,7 +29,7 @@ namespace Avalonia.Controls.UnitTests [Fact] public void Parse_Should_Parse_Star() { - var result = GridLength.Parse("*", CultureInfo.InvariantCulture); + var result = GridLength.Parse("*"); Assert.Equal(new GridLength(1, GridUnitType.Star), result); } @@ -37,7 +37,7 @@ namespace Avalonia.Controls.UnitTests [Fact] public void Parse_Should_Parse_Star_Value() { - var result = GridLength.Parse("2*", CultureInfo.InvariantCulture); + var result = GridLength.Parse("2*"); Assert.Equal(new GridLength(2, GridUnitType.Star), result); } @@ -45,7 +45,7 @@ namespace Avalonia.Controls.UnitTests [Fact] public void Parse_Should_Parse_Pixel_Value() { - var result = GridLength.Parse("2", CultureInfo.InvariantCulture); + var result = GridLength.Parse("2"); Assert.Equal(new GridLength(2, GridUnitType.Pixel), result); } @@ -53,13 +53,13 @@ namespace Avalonia.Controls.UnitTests [Fact] public void Parse_Should_Throw_FormatException_For_Invalid_String() { - Assert.Throws(() => GridLength.Parse("2x", CultureInfo.InvariantCulture)); + Assert.Throws(() => GridLength.Parse("2x")); } [Fact] public void ParseLengths_Accepts_Comma_Separators() { - var result = GridLength.ParseLengths("*,Auto,2*,4", CultureInfo.InvariantCulture).ToList(); + var result = GridLength.ParseLengths("*,Auto,2*,4").ToList(); Assert.Equal( new[] @@ -75,7 +75,7 @@ namespace Avalonia.Controls.UnitTests [Fact] public void ParseLengths_Accepts_Space_Separators() { - var result = GridLength.ParseLengths("* Auto 2* 4", CultureInfo.InvariantCulture).ToList(); + var result = GridLength.ParseLengths("* Auto 2* 4").ToList(); Assert.Equal( new[] @@ -91,7 +91,7 @@ namespace Avalonia.Controls.UnitTests [Fact] public void ParseLengths_Accepts_Comma_Separators_With_Spaces() { - var result = GridLength.ParseLengths("*, Auto, 2* ,4", CultureInfo.InvariantCulture).ToList(); + var result = GridLength.ParseLengths("*, Auto, 2* ,4").ToList(); Assert.Equal( new[] diff --git a/tests/Avalonia.Controls.UnitTests/GridMocks.cs b/tests/Avalonia.Controls.UnitTests/GridMocks.cs new file mode 100644 index 0000000000..23b975207c --- /dev/null +++ b/tests/Avalonia.Controls.UnitTests/GridMocks.cs @@ -0,0 +1,124 @@ +using System; +using System.Diagnostics.CodeAnalysis; +using Xunit; + +namespace Avalonia.Controls.UnitTests +{ + internal static class GridMock + { + /// + /// Create a mock grid to test its row layout. + /// This method contains Arrange (`new Grid()`) and Action (`Measure()`/`Arrange()`). + /// + /// The measure height of this grid. PositiveInfinity by default. + /// The arrange height of this grid. DesiredSize.Height by default. + /// The mock grid that its children bounds will be tested. + internal static Grid New(Size measure = default, Size arrange = default) + { + var grid = new Grid(); + grid.Children.Add(new Border()); + grid.Measure(measure == default ? new Size(double.PositiveInfinity, double.PositiveInfinity) : measure); + grid.Arrange(new Rect(default, arrange == default ? grid.DesiredSize : arrange)); + return grid; + } + + /// + /// Create a mock grid to test its row layout. + /// This method contains Arrange (`new Grid()`) and Action (`Measure()`/`Arrange()`). + /// + /// The row definitions of this mock grid. + /// The measure height of this grid. PositiveInfinity by default. + /// The arrange height of this grid. DesiredSize.Height by default. + /// The mock grid that its children bounds will be tested. + [SuppressMessage("ReSharper", "CompareOfFloatsByEqualityOperator")] + internal static Grid New(RowDefinitions rows, + double measure = default, double arrange = default) + { + var grid = new Grid { RowDefinitions = rows }; + for (var i = 0; i < rows.Count; i++) + { + grid.Children.Add(new Border { [Grid.RowProperty] = i }); + } + + grid.Measure(new Size(double.PositiveInfinity, measure == default ? double.PositiveInfinity : measure)); + if (arrange == default) + { + arrange = measure == default ? grid.DesiredSize.Width : measure; + } + + grid.Arrange(new Rect(0, 0, 0, arrange)); + + return grid; + } + + /// + /// Create a mock grid to test its column layout. + /// This method contains Arrange (`new Grid()`) and Action (`Measure()`/`Arrange()`). + /// + /// The column definitions of this mock grid. + /// The measure width of this grid. PositiveInfinity by default. + /// The arrange width of this grid. DesiredSize.Width by default. + /// The mock grid that its children bounds will be tested. + [SuppressMessage("ReSharper", "CompareOfFloatsByEqualityOperator")] + internal static Grid New(ColumnDefinitions columns, + double measure = default, double arrange = default) + { + var grid = new Grid { ColumnDefinitions = columns }; + for (var i = 0; i < columns.Count; i++) + { + grid.Children.Add(new Border { [Grid.ColumnProperty] = i }); + } + + grid.Measure(new Size(measure == default ? double.PositiveInfinity : measure, double.PositiveInfinity)); + if (arrange == default) + { + arrange = measure == default ? grid.DesiredSize.Width : measure; + } + + grid.Arrange(new Rect(0, 0, arrange, 0)); + + return grid; + } + } + + internal static class GridAssert + { + /// + /// Assert all the children heights. + /// This method will assume that the grid children count equals row count. + /// + /// The children will be fetched through it. + /// Expected row values of every children. + internal static void ChildrenHeight(Grid grid, params double[] rows) + { + if (grid.Children.Count != rows.Length) + { + throw new NotSupportedException(); + } + + for (var i = 0; i < rows.Length; i++) + { + Assert.Equal(rows[i], grid.Children[i].Bounds.Height); + } + } + + /// + /// Assert all the children widths. + /// This method will assume that the grid children count equals row count. + /// + /// The children will be fetched through it. + /// Expected column values of every children. + internal static void ChildrenWidth(Grid grid, params double[] columns) + { + if (grid.Children.Count != columns.Length) + { + throw new NotSupportedException(); + } + + for (var i = 0; i < columns.Length; i++) + { + Assert.Equal(columns[i], grid.Children[i].Bounds.Width); + } + } + } +} diff --git a/tests/Avalonia.Controls.UnitTests/GridTests.cs b/tests/Avalonia.Controls.UnitTests/GridTests.cs index c5aea6501f..4c79b7775b 100644 --- a/tests/Avalonia.Controls.UnitTests/GridTests.cs +++ b/tests/Avalonia.Controls.UnitTests/GridTests.cs @@ -1,7 +1,6 @@ // Copyright (c) The Avalonia Project. All rights reserved. // Licensed under the MIT license. See licence.md file in the project root for full license information. -using Avalonia.Controls; using Xunit; namespace Avalonia.Controls.UnitTests @@ -64,5 +63,96 @@ namespace Avalonia.Controls.UnitTests Assert.Equal(new Rect(0, 25, 150, 25), target.Children[1].Bounds); Assert.Equal(new Rect(154, 25, 50, 25), target.Children[2].Bounds); } + + [Fact] + public void Layout_EmptyColumnRow_LayoutLikeANormalPanel() + { + // Arrange & Action + var grid = GridMock.New(arrange: new Size(600, 200)); + + // Assert + GridAssert.ChildrenWidth(grid, 600); + GridAssert.ChildrenHeight(grid, 200); + } + + [Fact] + public void Layout_PixelRowColumn_BoundsCorrect() + { + // Arrange & Action + var rowGrid = GridMock.New(new RowDefinitions("100,200,300")); + var columnGrid = GridMock.New(new ColumnDefinitions("50,100,150")); + + // Assert + GridAssert.ChildrenHeight(rowGrid, 100, 200, 300); + GridAssert.ChildrenWidth(columnGrid, 50, 100, 150); + } + + [Fact] + public void Layout_StarRowColumn_BoundsCorrect() + { + // Arrange & Action + var rowGrid = GridMock.New(new RowDefinitions("1*,2*,3*"), 600); + var columnGrid = GridMock.New(new ColumnDefinitions("*,*,2*"), 600); + + // Assert + GridAssert.ChildrenHeight(rowGrid, 100, 200, 300); + GridAssert.ChildrenWidth(columnGrid, 150, 150, 300); + } + + [Fact] + public void Layout_MixPixelStarRowColumn_BoundsCorrect() + { + // Arrange & Action + var rowGrid = GridMock.New(new RowDefinitions("1*,2*,150"), 600); + var columnGrid = GridMock.New(new ColumnDefinitions("1*,2*,150"), 600); + + // Assert + GridAssert.ChildrenHeight(rowGrid, 150, 300, 150); + GridAssert.ChildrenWidth(columnGrid, 150, 300, 150); + } + + [Fact] + public void Layout_StarRowColumnWithMinLength_BoundsCorrect() + { + // Arrange & Action + var rowGrid = GridMock.New(new RowDefinitions + { + new RowDefinition(1, GridUnitType.Star) { MinHeight = 200 }, + new RowDefinition(1, GridUnitType.Star), + new RowDefinition(1, GridUnitType.Star), + }, 300); + var columnGrid = GridMock.New(new ColumnDefinitions + { + new ColumnDefinition(1, GridUnitType.Star) { MinWidth = 200 }, + new ColumnDefinition(1, GridUnitType.Star), + new ColumnDefinition(1, GridUnitType.Star), + }, 300); + + // Assert + GridAssert.ChildrenHeight(rowGrid, 200, 50, 50); + GridAssert.ChildrenWidth(columnGrid, 200, 50, 50); + } + + [Fact] + public void Layout_StarRowColumnWithMaxLength_BoundsCorrect() + { + // Arrange & Action + var rowGrid = GridMock.New(new RowDefinitions + { + new RowDefinition(1, GridUnitType.Star) { MaxHeight = 200 }, + new RowDefinition(1, GridUnitType.Star), + new RowDefinition(1, GridUnitType.Star), + }, 800); + var columnGrid = GridMock.New(new ColumnDefinitions + { + new ColumnDefinition(1, GridUnitType.Star) { MaxWidth = 200 }, + new ColumnDefinition(1, GridUnitType.Star), + new ColumnDefinition(1, GridUnitType.Star), + }, 800); + + // Assert + GridAssert.ChildrenHeight(rowGrid, 200, 300, 300); + GridAssert.ChildrenWidth(columnGrid, 200, 300, 300); + } } } diff --git a/tests/Avalonia.DesignerSupport.Tests/DesignerSupportTests.cs b/tests/Avalonia.DesignerSupport.Tests/DesignerSupportTests.cs index a64e5a8fe0..32da700acc 100644 --- a/tests/Avalonia.DesignerSupport.Tests/DesignerSupportTests.cs +++ b/tests/Avalonia.DesignerSupport.Tests/DesignerSupportTests.cs @@ -11,10 +11,10 @@ namespace Avalonia.DesignerSupport.Tests { public class DesignerSupportTests { - [Theory, + [Theory(Skip = "Skipping for now as failing on AppVeyor"), InlineData(@"Avalonia.DesignerSupport.TestApp.exe", @"..\..\tests\Avalonia.DesignerSupport.TestApp\MainWindow.xaml"), InlineData(@"..\..\samples\ControlCatalog.Desktop\bin\$BUILD\ControlCatalog.dll", @"..\..\samples\ControlCatalog\MainWindow.xaml")] - public void DesgignerApiShoudBeOperational(string outputDir, string xamlFile) + public void DesignerApiShoudBeOperational(string outputDir, string xamlFile) { var xaml = File.ReadAllText(xamlFile); #if DEBUG diff --git a/tests/Avalonia.Interactivity.UnitTests/Properties/AssemblyInfo.cs b/tests/Avalonia.Interactivity.UnitTests/Properties/AssemblyInfo.cs index 49d227c3e9..318b40828e 100644 --- a/tests/Avalonia.Interactivity.UnitTests/Properties/AssemblyInfo.cs +++ b/tests/Avalonia.Interactivity.UnitTests/Properties/AssemblyInfo.cs @@ -4,7 +4,7 @@ using System.Reflection; using Xunit; -[assembly: AssemblyTitle("Avalonia.Interactive.UnitTests")] +[assembly: AssemblyTitle("Avalonia.Interactivity.UnitTests")] // Don't run tests in parallel. [assembly: CollectionBehavior(DisableTestParallelization = true)] \ No newline at end of file diff --git a/tests/Avalonia.Markup.UnitTests/Data/BindingExpressionTests.cs b/tests/Avalonia.Markup.UnitTests/Data/BindingExpressionTests.cs index 6eeaa029de..2e0f048e21 100644 --- a/tests/Avalonia.Markup.UnitTests/Data/BindingExpressionTests.cs +++ b/tests/Avalonia.Markup.UnitTests/Data/BindingExpressionTests.cs @@ -58,7 +58,7 @@ namespace Avalonia.Markup.UnitTests.Data [Fact] public async Task Should_Convert_Get_String_To_Double() { - var data = new Class1 { StringValue = "5.6" }; + var data = new Class1 { StringValue = $"{5.6}" }; var target = new BindingExpression(new ExpressionObserver(data, "StringValue"), typeof(double)); var result = await target.Take(1); @@ -94,12 +94,12 @@ namespace Avalonia.Markup.UnitTests.Data [Fact] public void Should_Convert_Set_String_To_Double() { - var data = new Class1 { StringValue = (5.6).ToString() }; + var data = new Class1 { StringValue = $"{5.6}" }; var target = new BindingExpression(new ExpressionObserver(data, "StringValue"), typeof(double)); target.OnNext(6.7); - Assert.Equal((6.7).ToString(), data.StringValue); + Assert.Equal($"{6.7}", data.StringValue); GC.KeepAlive(data); } @@ -111,7 +111,7 @@ namespace Avalonia.Markup.UnitTests.Data var target = new BindingExpression(new ExpressionObserver(data, "DoubleValue"), typeof(string)); var result = await target.Take(1); - Assert.Equal((5.6).ToString(), result); + Assert.Equal($"{5.6}", result); GC.KeepAlive(data); } @@ -122,7 +122,7 @@ namespace Avalonia.Markup.UnitTests.Data var data = new Class1 { DoubleValue = 5.6 }; var target = new BindingExpression(new ExpressionObserver(data, "DoubleValue"), typeof(string)); - target.OnNext("6.7"); + target.OnNext($"{6.7}"); Assert.Equal(6.7, data.DoubleValue); @@ -318,15 +318,15 @@ namespace Avalonia.Markup.UnitTests.Data target.Subscribe(x => result.Add(x)); target.OnNext(1.2); - target.OnNext("3.4"); + target.OnNext($"{3.4}"); target.OnNext("bar"); Assert.Equal( new[] { - new BindingNotification("5.6"), - new BindingNotification("1.2"), - new BindingNotification("3.4"), + new BindingNotification($"{5.6}"), + new BindingNotification($"{1.2}"), + new BindingNotification($"{3.4}"), new BindingNotification( new InvalidCastException("'bar' is not a valid number."), BindingErrorType.Error) diff --git a/tests/Avalonia.Markup.Xaml.UnitTests/MarkupExtensions/BindingExtensionTests.cs b/tests/Avalonia.Markup.Xaml.UnitTests/MarkupExtensions/BindingExtensionTests.cs new file mode 100644 index 0000000000..4b0bee00f3 --- /dev/null +++ b/tests/Avalonia.Markup.Xaml.UnitTests/MarkupExtensions/BindingExtensionTests.cs @@ -0,0 +1,71 @@ +using System; +using System.Collections.Generic; +using System.Text; +using Avalonia.Controls; +using Avalonia.Controls.Presenters; +using Avalonia.Controls.Templates; +using Avalonia.Styling; +using Avalonia.UnitTests; +using Xunit; + +namespace Avalonia.Markup.Xaml.UnitTests.MarkupExtensions +{ + public class BindingExtensionTests + { + + [Fact] + public void BindingExtension_Binds_To_Source() + { + using (StyledWindow()) + { + var xaml = @" + + + foobar + + + +"; + + var loader = new AvaloniaXamlLoader(); + var window = (Window)loader.Load(xaml); + var textBlock = window.FindControl("textBlock"); + + window.Show(); + + Assert.Equal("foobar", textBlock.Text); + } + } + + private IDisposable StyledWindow(params (string, string)[] assets) + { + var services = TestServices.StyledWindow.With( + assetLoader: new MockAssetLoader(assets), + theme: () => new Styles + { + WindowStyle(), + }); + + return UnitTestApplication.Start(services); + } + + private Style WindowStyle() + { + return new Style(x => x.OfType()) + { + Setters = + { + new Setter( + Window.TemplateProperty, + new FuncControlTemplate(x => + new ContentPresenter + { + Name = "PART_ContentPresenter", + [!ContentPresenter.ContentProperty] = x[!Window.ContentProperty], + })) + } + }; + } + } +} diff --git a/tests/Avalonia.Markup.Xaml.UnitTests/MarkupExtensions/StaticResourceExtensionTests.cs b/tests/Avalonia.Markup.Xaml.UnitTests/MarkupExtensions/StaticResourceExtensionTests.cs index 862ce2b3c0..8615efd967 100644 --- a/tests/Avalonia.Markup.Xaml.UnitTests/MarkupExtensions/StaticResourceExtensionTests.cs +++ b/tests/Avalonia.Markup.Xaml.UnitTests/MarkupExtensions/StaticResourceExtensionTests.cs @@ -323,7 +323,7 @@ namespace Avalonia.Markup.Xaml.UnitTests.MarkupExtensions Assert.Equal(0xff506070, brush.Color.ToUint32()); } - [Fact(Skip = "Not yet supported by Portable.Xaml")] + [Fact] public void StaticResource_Can_Be_Assigned_To_Property_In_ControlTemplate_In_Styles_File() { var styleXaml = @" @@ -361,9 +361,7 @@ namespace Avalonia.Markup.Xaml.UnitTests.MarkupExtensions var border = (Border)button.GetVisualChildren().Single(); var brush = (SolidColorBrush)border.Background; - - // To make this work we somehow need to be able to get hold of the parent ambient - // context from Portable.Xaml. See TODO in StaticResourceExtension. + Assert.Equal(0xff506070, brush.Color.ToUint32()); } } @@ -417,6 +415,79 @@ namespace Avalonia.Markup.Xaml.UnitTests.MarkupExtensions } } + [Fact] + public void StaticResource_Can_Be_Assigned_To_Binding_Converter_In_DataTemplate() + { + using (StyledWindow()) + { + var xaml = @" + + + + + + + + + +"; + + var loader = new AvaloniaXamlLoader(); + var window = (Window)loader.Load(xaml); + + window.DataContext = "foo"; + var presenter = window.FindControl("presenter"); + + window.Show(); + + var textBlock = (TextBlock)presenter.GetVisualChildren().Single(); + + Assert.NotNull(textBlock); + Assert.Equal("foobar", textBlock.Text); + } + } + + [Fact] + public void StaticResource_Is_Correctly_Chosen_From_Within_DataTemplate() + { + // this tests if IAmbientProviders in DataTemplate contexts are in correct order + // if they wouldn't be, Purple brush would be bound to + using (StyledWindow()) + { + var xaml = @" + + + + + + + + + + + + + + + +"; + + var loader = new AvaloniaXamlLoader(); + var window = (Window)loader.Load(xaml); + + window.Show(); + + var textBlock = window.GetVisualDescendants().OfType().Single(); + + Assert.NotNull(textBlock); + Assert.Equal("White-bar", textBlock.Text); + } + } + [Fact] public void Control_Property_Is_Not_Updated_When_Parent_Is_Changed() { diff --git a/tests/Avalonia.Markup.Xaml.UnitTests/Xaml/AttachedPropertyOwner.cs b/tests/Avalonia.Markup.Xaml.UnitTests/Xaml/AttachedPropertyOwner.cs new file mode 100644 index 0000000000..aac5b01f96 --- /dev/null +++ b/tests/Avalonia.Markup.Xaml.UnitTests/Xaml/AttachedPropertyOwner.cs @@ -0,0 +1,14 @@ +using System; +using Avalonia.Controls; + +namespace Avalonia.Markup.Xaml.UnitTests.Xaml +{ + public class AttachedPropertyOwner + { + public static readonly AttachedProperty DoubleProperty = + AvaloniaProperty.RegisterAttached("Double"); + + public static double GetDouble(Control control) => control.GetValue(DoubleProperty); + public static void SetDouble(Control control, double value) => control.SetValue(DoubleProperty, value); + } +} diff --git a/tests/Avalonia.Markup.Xaml.UnitTests/Xaml/BasicTests.cs b/tests/Avalonia.Markup.Xaml.UnitTests/Xaml/BasicTests.cs index ba68838382..4213a31c39 100644 --- a/tests/Avalonia.Markup.Xaml.UnitTests/Xaml/BasicTests.cs +++ b/tests/Avalonia.Markup.Xaml.UnitTests/Xaml/BasicTests.cs @@ -11,6 +11,7 @@ using Avalonia.Media; using Avalonia.Media.Immutable; using Avalonia.Styling; using Avalonia.UnitTests; +using Portable.Xaml; using System.Collections; using System.ComponentModel; using System.Linq; @@ -80,6 +81,21 @@ namespace Avalonia.Markup.Xaml.UnitTests.Xaml Assert.Equal(21.0, TextBlock.GetFontSize(target)); } + [Fact] + public void Attached_Property_Is_Set_On_Control_Outside_Avalonia_Namspace() + { + // Test for issue #1548 + var xaml = +@" + +"; + + var target = AvaloniaXamlLoader.Parse(xaml); + + Assert.Equal(2, Grid.GetColumn((TestControl)target.Content)); + } + [Fact] public void Attached_Property_With_Namespace_Is_Set() { @@ -125,6 +141,24 @@ namespace Avalonia.Markup.Xaml.UnitTests.Xaml Assert.Equal("Foo", ToolTip.GetTip(target)); } + [Fact] + public void NonExistent_Property_Throws() + { + var xaml = + @""; + + Assert.Throws(() => AvaloniaXamlLoader.Parse(xaml)); + } + + [Fact] + public void Non_Attached_Property_With_Attached_Property_Syntax_Throws() + { + var xaml = + @""; + + Assert.Throws(() => AvaloniaXamlLoader.Parse(xaml)); + } + [Fact] public void ContentControl_ContentTemplate_Is_Functional() { diff --git a/tests/Avalonia.Markup.Xaml.UnitTests/Xaml/BindingTests.cs b/tests/Avalonia.Markup.Xaml.UnitTests/Xaml/BindingTests.cs index a44d09dee7..568c6482f5 100644 --- a/tests/Avalonia.Markup.Xaml.UnitTests/Xaml/BindingTests.cs +++ b/tests/Avalonia.Markup.Xaml.UnitTests/Xaml/BindingTests.cs @@ -215,5 +215,71 @@ namespace Avalonia.Markup.Xaml.UnitTests.Xaml Assert.Equal("bar", textBlock.Text); } } + + [Fact] + public void Binding_To_Namespaced_Attached_Property_Works() + { + using (UnitTestApplication.Start(TestServices.MockWindowingPlatform)) + { + var xaml = @" + + +"; + var loader = new AvaloniaXamlLoader(); + var window = (Window)loader.Load(xaml); + var textBlock = (TextBlock)window.Content; + + window.DataContext = 5.6; + window.ApplyTemplate(); + + Assert.Equal(5.6, AttachedPropertyOwner.GetDouble(textBlock)); + } + } + + [Fact] + public void Binding_To_AddOwnered_Attached_Property_Works() + { + using (UnitTestApplication.Start(TestServices.MockWindowingPlatform)) + { + var xaml = @" + + +"; + var loader = new AvaloniaXamlLoader(); + var window = (Window)loader.Load(xaml); + var testControl = (TestControl)window.Content; + + window.DataContext = 5.6; + window.ApplyTemplate(); + + Assert.Equal(5.6, testControl.Double); + } + } + + [Fact] + public void Binding_To_Attached_Property_Using_AddOwnered_Type_Works() + { + using (UnitTestApplication.Start(TestServices.MockWindowingPlatform)) + { + var xaml = @" + + +"; + var loader = new AvaloniaXamlLoader(); + var window = (Window)loader.Load(xaml); + var textBlock = (TextBlock)window.Content; + + window.DataContext = 5.6; + window.ApplyTemplate(); + + Assert.Equal(5.6, AttachedPropertyOwner.GetDouble(textBlock)); + } + } } } \ No newline at end of file diff --git a/tests/Avalonia.Markup.Xaml.UnitTests/Xaml/TestControl.cs b/tests/Avalonia.Markup.Xaml.UnitTests/Xaml/TestControl.cs new file mode 100644 index 0000000000..d0591e5ee6 --- /dev/null +++ b/tests/Avalonia.Markup.Xaml.UnitTests/Xaml/TestControl.cs @@ -0,0 +1,17 @@ +using System; +using Avalonia.Controls; + +namespace Avalonia.Markup.Xaml.UnitTests.Xaml +{ + public class TestControl : Control + { + public static readonly StyledProperty DoubleProperty = + AttachedPropertyOwner.DoubleProperty.AddOwner(); + + public double Double + { + get => GetValue(DoubleProperty); + set => SetValue(DoubleProperty, value); + } + } +} diff --git a/tests/Avalonia.UnitTests/MockAssetLoader.cs b/tests/Avalonia.UnitTests/MockAssetLoader.cs index 5cff20c6a1..d6b70aee16 100644 --- a/tests/Avalonia.UnitTests/MockAssetLoader.cs +++ b/tests/Avalonia.UnitTests/MockAssetLoader.cs @@ -27,6 +27,11 @@ namespace Avalonia.UnitTests { return new MemoryStream(Encoding.UTF8.GetBytes(_assets[uri])); } + + public Tuple OpenAndGetAssembly(Uri uri, Uri baseUri = null) + { + return Tuple.Create(Open(uri, baseUri), (Assembly)null); + } public void SetDefaultAssembly(Assembly asm) { diff --git a/tests/Avalonia.Visuals.UnitTests/CornerRadiusTests.cs b/tests/Avalonia.Visuals.UnitTests/CornerRadiusTests.cs index bc0bbdc867..56f9907409 100644 --- a/tests/Avalonia.Visuals.UnitTests/CornerRadiusTests.cs +++ b/tests/Avalonia.Visuals.UnitTests/CornerRadiusTests.cs @@ -11,7 +11,7 @@ namespace Avalonia.Visuals.UnitTests [Fact] public void Parse_Parses_Single_Uniform_Radius() { - var result = CornerRadius.Parse("3.4", CultureInfo.InvariantCulture); + var result = CornerRadius.Parse("3.4"); Assert.Equal(new CornerRadius(3.4), result); } @@ -19,7 +19,7 @@ namespace Avalonia.Visuals.UnitTests [Fact] public void Parse_Parses_Top_Bottom() { - var result = CornerRadius.Parse("1.1,2.2", CultureInfo.InvariantCulture); + var result = CornerRadius.Parse("1.1,2.2"); Assert.Equal(new CornerRadius(1.1, 2.2), result); } @@ -27,7 +27,7 @@ namespace Avalonia.Visuals.UnitTests [Fact] public void Parse_Parses_TopLeft_TopRight_BottomRight_BottomLeft() { - var result = CornerRadius.Parse("1.1,2.2,3.3,4.4", CultureInfo.InvariantCulture); + var result = CornerRadius.Parse("1.1,2.2,3.3,4.4"); Assert.Equal(new CornerRadius(1.1, 2.2, 3.3, 4.4), result); } @@ -35,7 +35,7 @@ namespace Avalonia.Visuals.UnitTests [Fact] public void Parse_Accepts_Spaces() { - var result = CornerRadius.Parse("1.1 2.2 3.3 4.4", CultureInfo.InvariantCulture); + var result = CornerRadius.Parse("1.1 2.2 3.3 4.4"); Assert.Equal(new CornerRadius(1.1, 2.2, 3.3, 4.4), result); } diff --git a/tests/Avalonia.Visuals.UnitTests/Media/MatrixTests.cs b/tests/Avalonia.Visuals.UnitTests/Media/MatrixTests.cs index 4c1e361952..ff1d17164e 100644 --- a/tests/Avalonia.Visuals.UnitTests/Media/MatrixTests.cs +++ b/tests/Avalonia.Visuals.UnitTests/Media/MatrixTests.cs @@ -8,7 +8,7 @@ namespace Avalonia.Visuals.UnitTests.Media [Fact] public void Parse_Parses() { - var matrix = Matrix.Parse("1,2,3,-4,5 6", CultureInfo.CurrentCulture); + var matrix = Matrix.Parse("1,2,3,-4,5 6"); var expected = new Matrix(1, 2, 3, -4, 5, 6); Assert.Equal(expected, matrix); } diff --git a/tests/Avalonia.Visuals.UnitTests/Media/RectTests.cs b/tests/Avalonia.Visuals.UnitTests/Media/RectTests.cs index 12070bfed3..cd0c7e1ace 100644 --- a/tests/Avalonia.Visuals.UnitTests/Media/RectTests.cs +++ b/tests/Avalonia.Visuals.UnitTests/Media/RectTests.cs @@ -8,7 +8,7 @@ namespace Avalonia.Visuals.UnitTests.Media [Fact] public void Parse_Parses() { - var rect = Rect.Parse("1,2 3,-4", CultureInfo.CurrentCulture); + var rect = Rect.Parse("1,2 3,-4"); var expected = new Rect(1, 2, 3, -4); Assert.Equal(expected, rect); } diff --git a/tests/Avalonia.Visuals.UnitTests/RelativePointTests.cs b/tests/Avalonia.Visuals.UnitTests/RelativePointTests.cs index f4a21fb6b4..b9eecc809f 100644 --- a/tests/Avalonia.Visuals.UnitTests/RelativePointTests.cs +++ b/tests/Avalonia.Visuals.UnitTests/RelativePointTests.cs @@ -11,7 +11,7 @@ namespace Avalonia.Visuals.UnitTests [Fact] public void Parse_Should_Accept_Absolute_Value() { - var result = RelativePoint.Parse("4,5", CultureInfo.InvariantCulture); + var result = RelativePoint.Parse("4,5"); Assert.Equal(new RelativePoint(4, 5, RelativeUnit.Absolute), result); } @@ -19,7 +19,7 @@ namespace Avalonia.Visuals.UnitTests [Fact] public void Parse_Should_Accept_Relative_Value() { - var result = RelativePoint.Parse("25%, 50%", CultureInfo.InvariantCulture); + var result = RelativePoint.Parse("25%, 50%"); Assert.Equal(new RelativePoint(0.25, 0.5, RelativeUnit.Relative), result); } diff --git a/tests/Avalonia.Visuals.UnitTests/RelativeRectTests.cs b/tests/Avalonia.Visuals.UnitTests/RelativeRectTests.cs index 9f25dcd413..68a0df2d9b 100644 --- a/tests/Avalonia.Visuals.UnitTests/RelativeRectTests.cs +++ b/tests/Avalonia.Visuals.UnitTests/RelativeRectTests.cs @@ -14,7 +14,7 @@ namespace Avalonia.Visuals.UnitTests [Fact] public void Parse_Should_Accept_Absolute_Value() { - var result = RelativeRect.Parse("4,5,50,60", CultureInfo.InvariantCulture); + var result = RelativeRect.Parse("4,5,50,60"); Assert.Equal(new RelativeRect(4, 5, 50, 60, RelativeUnit.Absolute), result, Compare); } @@ -22,7 +22,7 @@ namespace Avalonia.Visuals.UnitTests [Fact] public void Parse_Should_Accept_Relative_Value() { - var result = RelativeRect.Parse("10%, 20%, 40%, 70%", CultureInfo.InvariantCulture); + var result = RelativeRect.Parse("10%, 20%, 40%, 70%"); Assert.Equal(new RelativeRect(0.1, 0.2, 0.4, 0.7, RelativeUnit.Relative), result, Compare); } @@ -31,7 +31,7 @@ namespace Avalonia.Visuals.UnitTests public void Parse_Should_Throw_Mixed_Values() { Assert.Throws(() => - RelativeRect.Parse("10%, 20%, 40, 70%", CultureInfo.InvariantCulture)); + RelativeRect.Parse("10%, 20%, 40, 70%")); } } } diff --git a/tests/Avalonia.Visuals.UnitTests/ThicknessTests.cs b/tests/Avalonia.Visuals.UnitTests/ThicknessTests.cs index 03bf395d1e..ac4c6bc781 100644 --- a/tests/Avalonia.Visuals.UnitTests/ThicknessTests.cs +++ b/tests/Avalonia.Visuals.UnitTests/ThicknessTests.cs @@ -11,7 +11,7 @@ namespace Avalonia.Visuals.UnitTests [Fact] public void Parse_Parses_Single_Uniform_Size() { - var result = Thickness.Parse("1.2", CultureInfo.InvariantCulture); + var result = Thickness.Parse("1.2"); Assert.Equal(new Thickness(1.2), result); } @@ -19,7 +19,7 @@ namespace Avalonia.Visuals.UnitTests [Fact] public void Parse_Parses_Horizontal_Vertical() { - var result = Thickness.Parse("1.2,3.4", CultureInfo.InvariantCulture); + var result = Thickness.Parse("1.2,3.4"); Assert.Equal(new Thickness(1.2, 3.4), result); } @@ -27,7 +27,7 @@ namespace Avalonia.Visuals.UnitTests [Fact] public void Parse_Parses_Left_Top_Right_Bottom() { - var result = Thickness.Parse("1.2, 3.4, 5, 6", CultureInfo.InvariantCulture); + var result = Thickness.Parse("1.2, 3.4, 5, 6"); Assert.Equal(new Thickness(1.2, 3.4, 5, 6), result); } @@ -35,7 +35,7 @@ namespace Avalonia.Visuals.UnitTests [Fact] public void Parse_Accepts_Spaces() { - var result = Thickness.Parse("1.2 3.4 5 6", CultureInfo.InvariantCulture); + var result = Thickness.Parse("1.2 3.4 5 6"); Assert.Equal(new Thickness(1.2, 3.4, 5, 6), result); }