diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml new file mode 100644 index 0000000000..c5a719ce90 --- /dev/null +++ b/.github/FUNDING.yml @@ -0,0 +1 @@ +open_collective: avalonia diff --git a/.gitignore b/.gitignore index 2b2c9c3d0d..b5a46e16f4 100644 --- a/.gitignore +++ b/.gitignore @@ -196,3 +196,13 @@ ModuleCache.noindex/ Build/Intermediates.noindex/ info.plist build-intermediate +obj-Direct2D1/ +obj-Skia/ + +################## +# Vim +################## +.vim +coc-settings.json +.ccls-cache +.ccls diff --git a/.gitmodules b/.gitmodules index 22c56307b0..10c780c09f 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,6 +1,6 @@ -[submodule "src/Markup/Avalonia.Markup.Xaml/PortableXaml/portable.xaml.github"] - path = src/Markup/Avalonia.Markup.Xaml/PortableXaml/portable.xaml.github - url = https://github.com/AvaloniaUI/Portable.Xaml.git [submodule "nukebuild/Numerge"] path = nukebuild/Numerge url = https://github.com/kekekeks/Numerge.git +[submodule "src/Markup/Avalonia.Markup.Xaml/XamlIl/xamlil.github"] + path = src/Markup/Avalonia.Markup.Xaml/XamlIl/xamlil.github + url = https://github.com/kekekeks/XamlIl.git diff --git a/.ncrunch/Avalonia.Animation.UnitTests.net47.v3.ncrunchproject b/.ncrunch/Avalonia.Animation.UnitTests.net47.v3.ncrunchproject new file mode 100644 index 0000000000..e9d39b0c74 --- /dev/null +++ b/.ncrunch/Avalonia.Animation.UnitTests.net47.v3.ncrunchproject @@ -0,0 +1,7 @@ + + + + MissingOrIgnoredProjectReference + + + \ No newline at end of file diff --git a/.ncrunch/Avalonia.Animation.UnitTests.netcoreapp2.0.v3.ncrunchproject b/.ncrunch/Avalonia.Animation.UnitTests.netcoreapp2.0.v3.ncrunchproject new file mode 100644 index 0000000000..e9d39b0c74 --- /dev/null +++ b/.ncrunch/Avalonia.Animation.UnitTests.netcoreapp2.0.v3.ncrunchproject @@ -0,0 +1,7 @@ + + + + MissingOrIgnoredProjectReference + + + \ No newline at end of file diff --git a/.ncrunch/Avalonia.Animation.v3.ncrunchproject b/.ncrunch/Avalonia.Animation.v3.ncrunchproject index 6800b4a3fe..e12537d535 100644 --- a/.ncrunch/Avalonia.Animation.v3.ncrunchproject +++ b/.ncrunch/Avalonia.Animation.v3.ncrunchproject @@ -1,5 +1,8 @@  + + MissingOrIgnoredProjectReference + True \ No newline at end of file diff --git a/.ncrunch/Avalonia.Base.UnitTests.net47.v3.ncrunchproject b/.ncrunch/Avalonia.Base.UnitTests.net47.v3.ncrunchproject new file mode 100644 index 0000000000..e9d39b0c74 --- /dev/null +++ b/.ncrunch/Avalonia.Base.UnitTests.net47.v3.ncrunchproject @@ -0,0 +1,7 @@ + + + + MissingOrIgnoredProjectReference + + + \ No newline at end of file diff --git a/.ncrunch/Avalonia.Base.UnitTests.netcoreapp2.0.v3.ncrunchproject b/.ncrunch/Avalonia.Base.UnitTests.netcoreapp2.0.v3.ncrunchproject new file mode 100644 index 0000000000..e9d39b0c74 --- /dev/null +++ b/.ncrunch/Avalonia.Base.UnitTests.netcoreapp2.0.v3.ncrunchproject @@ -0,0 +1,7 @@ + + + + MissingOrIgnoredProjectReference + + + \ No newline at end of file diff --git a/.ncrunch/Avalonia.Base.v3.ncrunchproject b/.ncrunch/Avalonia.Base.v3.ncrunchproject index 6800b4a3fe..e12537d535 100644 --- a/.ncrunch/Avalonia.Base.v3.ncrunchproject +++ b/.ncrunch/Avalonia.Base.v3.ncrunchproject @@ -1,5 +1,8 @@  + + MissingOrIgnoredProjectReference + True \ No newline at end of file diff --git a/.ncrunch/Avalonia.Benchmarks.v3.ncrunchproject b/.ncrunch/Avalonia.Benchmarks.v3.ncrunchproject index 6800b4a3fe..e12537d535 100644 --- a/.ncrunch/Avalonia.Benchmarks.v3.ncrunchproject +++ b/.ncrunch/Avalonia.Benchmarks.v3.ncrunchproject @@ -1,5 +1,8 @@  + + MissingOrIgnoredProjectReference + True \ No newline at end of file diff --git a/.ncrunch/Avalonia.Build.Tasks.v3.ncrunchproject b/.ncrunch/Avalonia.Build.Tasks.v3.ncrunchproject new file mode 100644 index 0000000000..319cd523ce --- /dev/null +++ b/.ncrunch/Avalonia.Build.Tasks.v3.ncrunchproject @@ -0,0 +1,5 @@ + + + True + + \ No newline at end of file diff --git a/.ncrunch/Avalonia.Controls.DataGrid.v3.ncrunchproject b/.ncrunch/Avalonia.Controls.DataGrid.v3.ncrunchproject new file mode 100644 index 0000000000..e9d39b0c74 --- /dev/null +++ b/.ncrunch/Avalonia.Controls.DataGrid.v3.ncrunchproject @@ -0,0 +1,7 @@ + + + + MissingOrIgnoredProjectReference + + + \ No newline at end of file diff --git a/.ncrunch/Avalonia.Controls.UnitTests.net47.v3.ncrunchproject b/.ncrunch/Avalonia.Controls.UnitTests.net47.v3.ncrunchproject new file mode 100644 index 0000000000..e9d39b0c74 --- /dev/null +++ b/.ncrunch/Avalonia.Controls.UnitTests.net47.v3.ncrunchproject @@ -0,0 +1,7 @@ + + + + MissingOrIgnoredProjectReference + + + \ No newline at end of file diff --git a/.ncrunch/Avalonia.Controls.UnitTests.netcoreapp2.0.v3.ncrunchproject b/.ncrunch/Avalonia.Controls.UnitTests.netcoreapp2.0.v3.ncrunchproject new file mode 100644 index 0000000000..e9d39b0c74 --- /dev/null +++ b/.ncrunch/Avalonia.Controls.UnitTests.netcoreapp2.0.v3.ncrunchproject @@ -0,0 +1,7 @@ + + + + MissingOrIgnoredProjectReference + + + \ No newline at end of file diff --git a/.ncrunch/Avalonia.Controls.v3.ncrunchproject b/.ncrunch/Avalonia.Controls.v3.ncrunchproject index 6800b4a3fe..e12537d535 100644 --- a/.ncrunch/Avalonia.Controls.v3.ncrunchproject +++ b/.ncrunch/Avalonia.Controls.v3.ncrunchproject @@ -1,5 +1,8 @@  + + MissingOrIgnoredProjectReference + True \ No newline at end of file diff --git a/.ncrunch/Avalonia.Designer.HostApp.net461.v3.ncrunchproject b/.ncrunch/Avalonia.Designer.HostApp.net461.v3.ncrunchproject new file mode 100644 index 0000000000..e9d39b0c74 --- /dev/null +++ b/.ncrunch/Avalonia.Designer.HostApp.net461.v3.ncrunchproject @@ -0,0 +1,7 @@ + + + + MissingOrIgnoredProjectReference + + + \ No newline at end of file diff --git a/.ncrunch/Avalonia.Designer.HostApp.netcoreapp2.0.v3.ncrunchproject b/.ncrunch/Avalonia.Designer.HostApp.netcoreapp2.0.v3.ncrunchproject new file mode 100644 index 0000000000..e9d39b0c74 --- /dev/null +++ b/.ncrunch/Avalonia.Designer.HostApp.netcoreapp2.0.v3.ncrunchproject @@ -0,0 +1,7 @@ + + + + MissingOrIgnoredProjectReference + + + \ No newline at end of file diff --git a/.ncrunch/Avalonia.DesignerSupport.v3.ncrunchproject b/.ncrunch/Avalonia.DesignerSupport.v3.ncrunchproject index 6800b4a3fe..e12537d535 100644 --- a/.ncrunch/Avalonia.DesignerSupport.v3.ncrunchproject +++ b/.ncrunch/Avalonia.DesignerSupport.v3.ncrunchproject @@ -1,5 +1,8 @@  + + MissingOrIgnoredProjectReference + True \ No newline at end of file diff --git a/.ncrunch/Avalonia.DesktopRuntime.net461.v3.ncrunchproject b/.ncrunch/Avalonia.DesktopRuntime.net461.v3.ncrunchproject new file mode 100644 index 0000000000..e9d39b0c74 --- /dev/null +++ b/.ncrunch/Avalonia.DesktopRuntime.net461.v3.ncrunchproject @@ -0,0 +1,7 @@ + + + + MissingOrIgnoredProjectReference + + + \ No newline at end of file diff --git a/.ncrunch/Avalonia.DesktopRuntime.netcoreapp2.0.v3.ncrunchproject b/.ncrunch/Avalonia.DesktopRuntime.netcoreapp2.0.v3.ncrunchproject new file mode 100644 index 0000000000..e9d39b0c74 --- /dev/null +++ b/.ncrunch/Avalonia.DesktopRuntime.netcoreapp2.0.v3.ncrunchproject @@ -0,0 +1,7 @@ + + + + MissingOrIgnoredProjectReference + + + \ No newline at end of file diff --git a/.ncrunch/Avalonia.Diagnostics.v3.ncrunchproject b/.ncrunch/Avalonia.Diagnostics.v3.ncrunchproject index 6800b4a3fe..e12537d535 100644 --- a/.ncrunch/Avalonia.Diagnostics.v3.ncrunchproject +++ b/.ncrunch/Avalonia.Diagnostics.v3.ncrunchproject @@ -1,5 +1,8 @@  + + MissingOrIgnoredProjectReference + True \ No newline at end of file diff --git a/.ncrunch/Avalonia.Direct2D1.RenderTests.v3.ncrunchproject b/.ncrunch/Avalonia.Direct2D1.RenderTests.v3.ncrunchproject index 2627a59093..0846098ce5 100644 --- a/.ncrunch/Avalonia.Direct2D1.RenderTests.v3.ncrunchproject +++ b/.ncrunch/Avalonia.Direct2D1.RenderTests.v3.ncrunchproject @@ -4,6 +4,9 @@ ..\TestFiles\Direct2D1\**.* 3000 + + MissingOrIgnoredProjectReference + True \ No newline at end of file diff --git a/.ncrunch/Avalonia.Direct2D1.UnitTests.v3.ncrunchproject b/.ncrunch/Avalonia.Direct2D1.UnitTests.v3.ncrunchproject index 6800b4a3fe..e12537d535 100644 --- a/.ncrunch/Avalonia.Direct2D1.UnitTests.v3.ncrunchproject +++ b/.ncrunch/Avalonia.Direct2D1.UnitTests.v3.ncrunchproject @@ -1,5 +1,8 @@  + + MissingOrIgnoredProjectReference + True \ No newline at end of file diff --git a/.ncrunch/Avalonia.Direct2D1.v3.ncrunchproject b/.ncrunch/Avalonia.Direct2D1.v3.ncrunchproject index 6800b4a3fe..e12537d535 100644 --- a/.ncrunch/Avalonia.Direct2D1.v3.ncrunchproject +++ b/.ncrunch/Avalonia.Direct2D1.v3.ncrunchproject @@ -1,5 +1,8 @@  + + MissingOrIgnoredProjectReference + True \ No newline at end of file diff --git a/.ncrunch/Avalonia.Gtk3.v3.ncrunchproject b/.ncrunch/Avalonia.Gtk3.v3.ncrunchproject index 95a483b433..e9d39b0c74 100644 --- a/.ncrunch/Avalonia.Gtk3.v3.ncrunchproject +++ b/.ncrunch/Avalonia.Gtk3.v3.ncrunchproject @@ -1,3 +1,7 @@  - + + + MissingOrIgnoredProjectReference + + \ No newline at end of file diff --git a/.ncrunch/Avalonia.Input.UnitTests.net47.v3.ncrunchproject b/.ncrunch/Avalonia.Input.UnitTests.net47.v3.ncrunchproject new file mode 100644 index 0000000000..e9d39b0c74 --- /dev/null +++ b/.ncrunch/Avalonia.Input.UnitTests.net47.v3.ncrunchproject @@ -0,0 +1,7 @@ + + + + MissingOrIgnoredProjectReference + + + \ No newline at end of file diff --git a/.ncrunch/Avalonia.Input.UnitTests.netcoreapp2.0.v3.ncrunchproject b/.ncrunch/Avalonia.Input.UnitTests.netcoreapp2.0.v3.ncrunchproject new file mode 100644 index 0000000000..e9d39b0c74 --- /dev/null +++ b/.ncrunch/Avalonia.Input.UnitTests.netcoreapp2.0.v3.ncrunchproject @@ -0,0 +1,7 @@ + + + + MissingOrIgnoredProjectReference + + + \ No newline at end of file diff --git a/.ncrunch/Avalonia.Input.v3.ncrunchproject b/.ncrunch/Avalonia.Input.v3.ncrunchproject index 6800b4a3fe..e12537d535 100644 --- a/.ncrunch/Avalonia.Input.v3.ncrunchproject +++ b/.ncrunch/Avalonia.Input.v3.ncrunchproject @@ -1,5 +1,8 @@  + + MissingOrIgnoredProjectReference + True \ No newline at end of file diff --git a/.ncrunch/Avalonia.Interactivity.UnitTests.v3.ncrunchproject b/.ncrunch/Avalonia.Interactivity.UnitTests.v3.ncrunchproject index 6800b4a3fe..e12537d535 100644 --- a/.ncrunch/Avalonia.Interactivity.UnitTests.v3.ncrunchproject +++ b/.ncrunch/Avalonia.Interactivity.UnitTests.v3.ncrunchproject @@ -1,5 +1,8 @@  + + MissingOrIgnoredProjectReference + True \ No newline at end of file diff --git a/.ncrunch/Avalonia.Interactivity.v3.ncrunchproject b/.ncrunch/Avalonia.Interactivity.v3.ncrunchproject index 6800b4a3fe..e12537d535 100644 --- a/.ncrunch/Avalonia.Interactivity.v3.ncrunchproject +++ b/.ncrunch/Avalonia.Interactivity.v3.ncrunchproject @@ -1,5 +1,8 @@  + + MissingOrIgnoredProjectReference + True \ No newline at end of file diff --git a/.ncrunch/Avalonia.Layout.UnitTests.v3.ncrunchproject b/.ncrunch/Avalonia.Layout.UnitTests.v3.ncrunchproject index 6800b4a3fe..e12537d535 100644 --- a/.ncrunch/Avalonia.Layout.UnitTests.v3.ncrunchproject +++ b/.ncrunch/Avalonia.Layout.UnitTests.v3.ncrunchproject @@ -1,5 +1,8 @@  + + MissingOrIgnoredProjectReference + True \ No newline at end of file diff --git a/.ncrunch/Avalonia.Layout.v3.ncrunchproject b/.ncrunch/Avalonia.Layout.v3.ncrunchproject index 6800b4a3fe..e12537d535 100644 --- a/.ncrunch/Avalonia.Layout.v3.ncrunchproject +++ b/.ncrunch/Avalonia.Layout.v3.ncrunchproject @@ -1,5 +1,8 @@  + + MissingOrIgnoredProjectReference + True \ No newline at end of file diff --git a/.ncrunch/Avalonia.LeakTests.v3.ncrunchproject b/.ncrunch/Avalonia.LeakTests.v3.ncrunchproject index 6800b4a3fe..e12537d535 100644 --- a/.ncrunch/Avalonia.LeakTests.v3.ncrunchproject +++ b/.ncrunch/Avalonia.LeakTests.v3.ncrunchproject @@ -1,5 +1,8 @@  + + MissingOrIgnoredProjectReference + True \ No newline at end of file diff --git a/.ncrunch/Avalonia.LinuxFramebuffer.v3.ncrunchproject b/.ncrunch/Avalonia.LinuxFramebuffer.v3.ncrunchproject index 6800b4a3fe..e12537d535 100644 --- a/.ncrunch/Avalonia.LinuxFramebuffer.v3.ncrunchproject +++ b/.ncrunch/Avalonia.LinuxFramebuffer.v3.ncrunchproject @@ -1,5 +1,8 @@  + + MissingOrIgnoredProjectReference + True \ No newline at end of file diff --git a/.ncrunch/Avalonia.Logging.Serilog.v3.ncrunchproject b/.ncrunch/Avalonia.Logging.Serilog.v3.ncrunchproject index 6800b4a3fe..e12537d535 100644 --- a/.ncrunch/Avalonia.Logging.Serilog.v3.ncrunchproject +++ b/.ncrunch/Avalonia.Logging.Serilog.v3.ncrunchproject @@ -1,5 +1,8 @@  + + MissingOrIgnoredProjectReference + True \ No newline at end of file diff --git a/.ncrunch/Avalonia.Markup.UnitTests.net47.v3.ncrunchproject b/.ncrunch/Avalonia.Markup.UnitTests.net47.v3.ncrunchproject new file mode 100644 index 0000000000..e9d39b0c74 --- /dev/null +++ b/.ncrunch/Avalonia.Markup.UnitTests.net47.v3.ncrunchproject @@ -0,0 +1,7 @@ + + + + MissingOrIgnoredProjectReference + + + \ No newline at end of file diff --git a/.ncrunch/Avalonia.Markup.UnitTests.netcoreapp2.0.v3.ncrunchproject b/.ncrunch/Avalonia.Markup.UnitTests.netcoreapp2.0.v3.ncrunchproject new file mode 100644 index 0000000000..e9d39b0c74 --- /dev/null +++ b/.ncrunch/Avalonia.Markup.UnitTests.netcoreapp2.0.v3.ncrunchproject @@ -0,0 +1,7 @@ + + + + MissingOrIgnoredProjectReference + + + \ No newline at end of file diff --git a/.ncrunch/Avalonia.Markup.Xaml.UnitTests.net47.v3.ncrunchproject b/.ncrunch/Avalonia.Markup.Xaml.UnitTests.net47.v3.ncrunchproject new file mode 100644 index 0000000000..e9d39b0c74 --- /dev/null +++ b/.ncrunch/Avalonia.Markup.Xaml.UnitTests.net47.v3.ncrunchproject @@ -0,0 +1,7 @@ + + + + MissingOrIgnoredProjectReference + + + \ No newline at end of file diff --git a/.ncrunch/Avalonia.Markup.Xaml.UnitTests.netcoreapp2.0.v3.ncrunchproject b/.ncrunch/Avalonia.Markup.Xaml.UnitTests.netcoreapp2.0.v3.ncrunchproject new file mode 100644 index 0000000000..e9d39b0c74 --- /dev/null +++ b/.ncrunch/Avalonia.Markup.Xaml.UnitTests.netcoreapp2.0.v3.ncrunchproject @@ -0,0 +1,7 @@ + + + + MissingOrIgnoredProjectReference + + + \ No newline at end of file diff --git a/.ncrunch/Avalonia.Markup.Xaml.v3.ncrunchproject b/.ncrunch/Avalonia.Markup.Xaml.v3.ncrunchproject index 6800b4a3fe..e12537d535 100644 --- a/.ncrunch/Avalonia.Markup.Xaml.v3.ncrunchproject +++ b/.ncrunch/Avalonia.Markup.Xaml.v3.ncrunchproject @@ -1,5 +1,8 @@  + + MissingOrIgnoredProjectReference + True \ No newline at end of file diff --git a/.ncrunch/Avalonia.Markup.v3.ncrunchproject b/.ncrunch/Avalonia.Markup.v3.ncrunchproject index 6800b4a3fe..e12537d535 100644 --- a/.ncrunch/Avalonia.Markup.v3.ncrunchproject +++ b/.ncrunch/Avalonia.Markup.v3.ncrunchproject @@ -1,5 +1,8 @@  + + MissingOrIgnoredProjectReference + True \ No newline at end of file diff --git a/.ncrunch/Avalonia.OpenGL.v3.ncrunchproject b/.ncrunch/Avalonia.OpenGL.v3.ncrunchproject new file mode 100644 index 0000000000..e9d39b0c74 --- /dev/null +++ b/.ncrunch/Avalonia.OpenGL.v3.ncrunchproject @@ -0,0 +1,7 @@ + + + + MissingOrIgnoredProjectReference + + + \ No newline at end of file diff --git a/.ncrunch/Avalonia.ReactiveUI.UnitTests.v3.ncrunchproject b/.ncrunch/Avalonia.ReactiveUI.UnitTests.v3.ncrunchproject new file mode 100644 index 0000000000..e9d39b0c74 --- /dev/null +++ b/.ncrunch/Avalonia.ReactiveUI.UnitTests.v3.ncrunchproject @@ -0,0 +1,7 @@ + + + + MissingOrIgnoredProjectReference + + + \ No newline at end of file diff --git a/.ncrunch/Avalonia.ReactiveUI.v3.ncrunchproject b/.ncrunch/Avalonia.ReactiveUI.v3.ncrunchproject index 6800b4a3fe..e12537d535 100644 --- a/.ncrunch/Avalonia.ReactiveUI.v3.ncrunchproject +++ b/.ncrunch/Avalonia.ReactiveUI.v3.ncrunchproject @@ -1,5 +1,8 @@  + + MissingOrIgnoredProjectReference + True \ No newline at end of file diff --git a/.ncrunch/Avalonia.Skia.RenderTests.v3.ncrunchproject b/.ncrunch/Avalonia.Skia.RenderTests.v3.ncrunchproject index 7fe2430013..2966be8f73 100644 --- a/.ncrunch/Avalonia.Skia.RenderTests.v3.ncrunchproject +++ b/.ncrunch/Avalonia.Skia.RenderTests.v3.ncrunchproject @@ -4,6 +4,9 @@ ..\TestFiles\Skia\**.* 3000 + + MissingOrIgnoredProjectReference + True \ No newline at end of file diff --git a/.ncrunch/Avalonia.Skia.UnitTests.v3.ncrunchproject b/.ncrunch/Avalonia.Skia.UnitTests.v3.ncrunchproject new file mode 100644 index 0000000000..e9d39b0c74 --- /dev/null +++ b/.ncrunch/Avalonia.Skia.UnitTests.v3.ncrunchproject @@ -0,0 +1,7 @@ + + + + MissingOrIgnoredProjectReference + + + \ No newline at end of file diff --git a/.ncrunch/Avalonia.Skia.v3.ncrunchproject b/.ncrunch/Avalonia.Skia.v3.ncrunchproject index 95a483b433..e9d39b0c74 100644 --- a/.ncrunch/Avalonia.Skia.v3.ncrunchproject +++ b/.ncrunch/Avalonia.Skia.v3.ncrunchproject @@ -1,3 +1,7 @@  - + + + MissingOrIgnoredProjectReference + + \ No newline at end of file diff --git a/.ncrunch/Avalonia.Styling.UnitTests.net47.v3.ncrunchproject b/.ncrunch/Avalonia.Styling.UnitTests.net47.v3.ncrunchproject new file mode 100644 index 0000000000..e9d39b0c74 --- /dev/null +++ b/.ncrunch/Avalonia.Styling.UnitTests.net47.v3.ncrunchproject @@ -0,0 +1,7 @@ + + + + MissingOrIgnoredProjectReference + + + \ No newline at end of file diff --git a/.ncrunch/Avalonia.Styling.UnitTests.netcoreapp2.0.v3.ncrunchproject b/.ncrunch/Avalonia.Styling.UnitTests.netcoreapp2.0.v3.ncrunchproject new file mode 100644 index 0000000000..e9d39b0c74 --- /dev/null +++ b/.ncrunch/Avalonia.Styling.UnitTests.netcoreapp2.0.v3.ncrunchproject @@ -0,0 +1,7 @@ + + + + MissingOrIgnoredProjectReference + + + \ No newline at end of file diff --git a/.ncrunch/Avalonia.Styling.v3.ncrunchproject b/.ncrunch/Avalonia.Styling.v3.ncrunchproject index 6800b4a3fe..e12537d535 100644 --- a/.ncrunch/Avalonia.Styling.v3.ncrunchproject +++ b/.ncrunch/Avalonia.Styling.v3.ncrunchproject @@ -1,5 +1,8 @@  + + MissingOrIgnoredProjectReference + True \ No newline at end of file diff --git a/.ncrunch/Avalonia.Themes.Default.v3.ncrunchproject b/.ncrunch/Avalonia.Themes.Default.v3.ncrunchproject index 6800b4a3fe..e12537d535 100644 --- a/.ncrunch/Avalonia.Themes.Default.v3.ncrunchproject +++ b/.ncrunch/Avalonia.Themes.Default.v3.ncrunchproject @@ -1,5 +1,8 @@  + + MissingOrIgnoredProjectReference + True \ No newline at end of file diff --git a/.ncrunch/Avalonia.UnitTests.v3.ncrunchproject b/.ncrunch/Avalonia.UnitTests.v3.ncrunchproject index 6800b4a3fe..e12537d535 100644 --- a/.ncrunch/Avalonia.UnitTests.v3.ncrunchproject +++ b/.ncrunch/Avalonia.UnitTests.v3.ncrunchproject @@ -1,5 +1,8 @@  + + MissingOrIgnoredProjectReference + True \ No newline at end of file diff --git a/.ncrunch/Avalonia.Visuals.UnitTests.net47.v3.ncrunchproject b/.ncrunch/Avalonia.Visuals.UnitTests.net47.v3.ncrunchproject new file mode 100644 index 0000000000..e9d39b0c74 --- /dev/null +++ b/.ncrunch/Avalonia.Visuals.UnitTests.net47.v3.ncrunchproject @@ -0,0 +1,7 @@ + + + + MissingOrIgnoredProjectReference + + + \ No newline at end of file diff --git a/.ncrunch/Avalonia.Visuals.UnitTests.netcoreapp2.0.v3.ncrunchproject b/.ncrunch/Avalonia.Visuals.UnitTests.netcoreapp2.0.v3.ncrunchproject new file mode 100644 index 0000000000..e9d39b0c74 --- /dev/null +++ b/.ncrunch/Avalonia.Visuals.UnitTests.netcoreapp2.0.v3.ncrunchproject @@ -0,0 +1,7 @@ + + + + MissingOrIgnoredProjectReference + + + \ No newline at end of file diff --git a/.ncrunch/Avalonia.Visuals.v3.ncrunchproject b/.ncrunch/Avalonia.Visuals.v3.ncrunchproject index 6800b4a3fe..e12537d535 100644 --- a/.ncrunch/Avalonia.Visuals.v3.ncrunchproject +++ b/.ncrunch/Avalonia.Visuals.v3.ncrunchproject @@ -1,5 +1,8 @@  + + MissingOrIgnoredProjectReference + True \ No newline at end of file diff --git a/.ncrunch/Avalonia.Win32.Interop.v3.ncrunchproject b/.ncrunch/Avalonia.Win32.Interop.v3.ncrunchproject index 6800b4a3fe..e12537d535 100644 --- a/.ncrunch/Avalonia.Win32.Interop.v3.ncrunchproject +++ b/.ncrunch/Avalonia.Win32.Interop.v3.ncrunchproject @@ -1,5 +1,8 @@  + + MissingOrIgnoredProjectReference + True \ No newline at end of file diff --git a/.ncrunch/Avalonia.Win32.v3.ncrunchproject b/.ncrunch/Avalonia.Win32.v3.ncrunchproject index 6800b4a3fe..e12537d535 100644 --- a/.ncrunch/Avalonia.Win32.v3.ncrunchproject +++ b/.ncrunch/Avalonia.Win32.v3.ncrunchproject @@ -1,5 +1,8 @@  + + MissingOrIgnoredProjectReference + True \ No newline at end of file diff --git a/.ncrunch/Avalonia.X11.v3.ncrunchproject b/.ncrunch/Avalonia.X11.v3.ncrunchproject new file mode 100644 index 0000000000..e9d39b0c74 --- /dev/null +++ b/.ncrunch/Avalonia.X11.v3.ncrunchproject @@ -0,0 +1,7 @@ + + + + MissingOrIgnoredProjectReference + + + \ No newline at end of file diff --git a/.ncrunch/Avalonia.netcoreapp2.0.v3.ncrunchproject b/.ncrunch/Avalonia.netcoreapp2.0.v3.ncrunchproject new file mode 100644 index 0000000000..3f3c53a7fd --- /dev/null +++ b/.ncrunch/Avalonia.netcoreapp2.0.v3.ncrunchproject @@ -0,0 +1,8 @@ + + + + DerivedFilesIncludedInWorkspace + MissingOrIgnoredProjectReference + + + \ No newline at end of file diff --git a/.ncrunch/Avalonia.netstandard2.0.v3.ncrunchproject b/.ncrunch/Avalonia.netstandard2.0.v3.ncrunchproject new file mode 100644 index 0000000000..3f3c53a7fd --- /dev/null +++ b/.ncrunch/Avalonia.netstandard2.0.v3.ncrunchproject @@ -0,0 +1,8 @@ + + + + DerivedFilesIncludedInWorkspace + MissingOrIgnoredProjectReference + + + \ No newline at end of file diff --git a/.ncrunch/Direct3DInteropSample.v3.ncrunchproject b/.ncrunch/Direct3DInteropSample.v3.ncrunchproject index 6800b4a3fe..e12537d535 100644 --- a/.ncrunch/Direct3DInteropSample.v3.ncrunchproject +++ b/.ncrunch/Direct3DInteropSample.v3.ncrunchproject @@ -1,5 +1,8 @@  + + MissingOrIgnoredProjectReference + True \ No newline at end of file diff --git a/.ncrunch/PlatformSanityChecks.v3.ncrunchproject b/.ncrunch/PlatformSanityChecks.v3.ncrunchproject new file mode 100644 index 0000000000..e9d39b0c74 --- /dev/null +++ b/.ncrunch/PlatformSanityChecks.v3.ncrunchproject @@ -0,0 +1,7 @@ + + + + MissingOrIgnoredProjectReference + + + \ No newline at end of file diff --git a/Avalonia.sln b/Avalonia.sln index 484d7a4cde..ac678ba9ba 100644 --- a/Avalonia.sln +++ b/Avalonia.sln @@ -63,8 +63,6 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Shared", "Shared", "{A689DE src\Shared\SharedAssemblyInfo.cs = src\Shared\SharedAssemblyInfo.cs EndProjectSection EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Gtk", "Gtk", "{B9894058-278A-46B5-B6ED-AD613FCC03B3}" -EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Avalonia.ReactiveUI", "src\Avalonia.ReactiveUI\Avalonia.ReactiveUI.csproj", "{6417B24E-49C2-4985-8DB2-3AB9D898EC91}" EndProject Project("{D954291E-2A0B-460D-934E-DC6B0785DB48}") = "PlatformSupport", "src\Shared\PlatformSupport\PlatformSupport.shproj", "{E4D9629C-F168-4224-3F51-A5E482FFBC42}" @@ -123,8 +121,6 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ControlCatalog.Android", "s EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Avalonia.Skia", "src\Skia\Avalonia.Skia\Avalonia.Skia.csproj", "{7D2D3083-71DD-4CC9-8907-39A0D86FB322}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Avalonia.Gtk3", "src\Gtk\Avalonia.Gtk3\Avalonia.Gtk3.csproj", "{BB1F7BB5-6AD4-4776-94D9-C09D0A972658}" -EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ControlCatalog.NetCore", "samples\ControlCatalog.NetCore\ControlCatalog.NetCore.csproj", "{39D7B147-1A5B-47C2-9D01-21FB7C47C4B3}" EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Props", "Props", "{F3AC8BC1-27F5-4255-9AFC-04ABFD11683A}" @@ -146,7 +142,6 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Props", "Props", "{F3AC8BC1 build\Serilog.props = build\Serilog.props build\SharpDX.props = build\SharpDX.props build\SkiaSharp.props = build\SkiaSharp.props - build\Splat.props = build\Splat.props build\System.Memory.props = build\System.Memory.props build\XUnit.props = build\XUnit.props EndProjectSection @@ -1319,30 +1314,6 @@ Global {7D2D3083-71DD-4CC9-8907-39A0D86FB322}.Release|iPhone.Build.0 = Release|Any CPU {7D2D3083-71DD-4CC9-8907-39A0D86FB322}.Release|iPhoneSimulator.ActiveCfg = Release|Any CPU {7D2D3083-71DD-4CC9-8907-39A0D86FB322}.Release|iPhoneSimulator.Build.0 = Release|Any CPU - {BB1F7BB5-6AD4-4776-94D9-C09D0A972658}.Ad-Hoc|Any CPU.ActiveCfg = Release|Any CPU - {BB1F7BB5-6AD4-4776-94D9-C09D0A972658}.Ad-Hoc|Any CPU.Build.0 = Release|Any CPU - {BB1F7BB5-6AD4-4776-94D9-C09D0A972658}.Ad-Hoc|iPhone.ActiveCfg = Release|Any CPU - {BB1F7BB5-6AD4-4776-94D9-C09D0A972658}.Ad-Hoc|iPhone.Build.0 = Release|Any CPU - {BB1F7BB5-6AD4-4776-94D9-C09D0A972658}.Ad-Hoc|iPhoneSimulator.ActiveCfg = Release|Any CPU - {BB1F7BB5-6AD4-4776-94D9-C09D0A972658}.Ad-Hoc|iPhoneSimulator.Build.0 = Release|Any CPU - {BB1F7BB5-6AD4-4776-94D9-C09D0A972658}.AppStore|Any CPU.ActiveCfg = Release|Any CPU - {BB1F7BB5-6AD4-4776-94D9-C09D0A972658}.AppStore|Any CPU.Build.0 = Release|Any CPU - {BB1F7BB5-6AD4-4776-94D9-C09D0A972658}.AppStore|iPhone.ActiveCfg = Release|Any CPU - {BB1F7BB5-6AD4-4776-94D9-C09D0A972658}.AppStore|iPhone.Build.0 = Release|Any CPU - {BB1F7BB5-6AD4-4776-94D9-C09D0A972658}.AppStore|iPhoneSimulator.ActiveCfg = Release|Any CPU - {BB1F7BB5-6AD4-4776-94D9-C09D0A972658}.AppStore|iPhoneSimulator.Build.0 = Release|Any CPU - {BB1F7BB5-6AD4-4776-94D9-C09D0A972658}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {BB1F7BB5-6AD4-4776-94D9-C09D0A972658}.Debug|Any CPU.Build.0 = Debug|Any CPU - {BB1F7BB5-6AD4-4776-94D9-C09D0A972658}.Debug|iPhone.ActiveCfg = Debug|Any CPU - {BB1F7BB5-6AD4-4776-94D9-C09D0A972658}.Debug|iPhone.Build.0 = Debug|Any CPU - {BB1F7BB5-6AD4-4776-94D9-C09D0A972658}.Debug|iPhoneSimulator.ActiveCfg = Debug|Any CPU - {BB1F7BB5-6AD4-4776-94D9-C09D0A972658}.Debug|iPhoneSimulator.Build.0 = Debug|Any CPU - {BB1F7BB5-6AD4-4776-94D9-C09D0A972658}.Release|Any CPU.ActiveCfg = Release|Any CPU - {BB1F7BB5-6AD4-4776-94D9-C09D0A972658}.Release|Any CPU.Build.0 = Release|Any CPU - {BB1F7BB5-6AD4-4776-94D9-C09D0A972658}.Release|iPhone.ActiveCfg = Release|Any CPU - {BB1F7BB5-6AD4-4776-94D9-C09D0A972658}.Release|iPhone.Build.0 = Release|Any CPU - {BB1F7BB5-6AD4-4776-94D9-C09D0A972658}.Release|iPhoneSimulator.ActiveCfg = Release|Any CPU - {BB1F7BB5-6AD4-4776-94D9-C09D0A972658}.Release|iPhoneSimulator.Build.0 = Release|Any CPU {39D7B147-1A5B-47C2-9D01-21FB7C47C4B3}.Ad-Hoc|Any CPU.ActiveCfg = Debug|Any CPU {39D7B147-1A5B-47C2-9D01-21FB7C47C4B3}.Ad-Hoc|Any CPU.Build.0 = Debug|Any CPU {39D7B147-1A5B-47C2-9D01-21FB7C47C4B3}.Ad-Hoc|iPhone.ActiveCfg = Debug|Any CPU @@ -1912,7 +1883,6 @@ Global {F1FDC5B0-4654-416F-AE69-E3E9BBD87801} = {9B9E3891-2366-4253-A952-D08BCEB71098} {29132311-1848-4FD6-AE0C-4FF841151BD3} = {9B9E3891-2366-4253-A952-D08BCEB71098} {7D2D3083-71DD-4CC9-8907-39A0D86FB322} = {3743B0F2-CC41-4F14-A8C8-267F579BF91E} - {BB1F7BB5-6AD4-4776-94D9-C09D0A972658} = {B9894058-278A-46B5-B6ED-AD613FCC03B3} {39D7B147-1A5B-47C2-9D01-21FB7C47C4B3} = {9B9E3891-2366-4253-A952-D08BCEB71098} {854568D5-13D1-4B4F-B50D-534DC7EFD3C9} = {86C53C40-57AA-45B8-AD42-FAE0EFDF0F2B} {638580B0-7910-40EF-B674-DCB34DA308CD} = {A0CC0258-D18C-4AB3-854F-7101680FC3F9} diff --git a/azure-pipelines.yml b/azure-pipelines.yml index 39333f37ba..7e3532ee23 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -32,7 +32,7 @@ jobs: - job: macOS pool: - vmImage: 'xcode9-macos10.13' + vmImage: 'macOS-10.14' steps: - task: DotNetCoreInstaller@0 inputs: @@ -49,7 +49,7 @@ jobs: inputs: actions: 'build' scheme: '' - sdk: 'macosx10.13' + sdk: 'macosx10.14' configuration: 'Release' xcWorkspacePath: '**/*.xcodeproj/project.xcworkspace' xcodeVersion: 'default' # Options: 8, 9, default, specifyPath @@ -102,7 +102,7 @@ jobs: - job: Windows pool: - vmImage: 'vs2017-win2016' + vmImage: 'windows-2019' steps: - task: CmdLine@2 displayName: 'Install Nuke' diff --git a/build-native.sh b/build-native.sh old mode 100644 new mode 100755 diff --git a/build/Base.props b/build/Base.props index e565ab1664..a60373ebb3 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 index 219ffb2e42..7ce0366dea 100644 --- a/build/EmbedXaml.props +++ b/build/EmbedXaml.props @@ -4,8 +4,8 @@ %(Filename) - + Designer - + \ No newline at end of file diff --git a/build/SampleApp.props b/build/SampleApp.props index 5580a4c2c9..285f880129 100644 --- a/build/SampleApp.props +++ b/build/SampleApp.props @@ -5,4 +5,9 @@ + + + diff --git a/build/SharedVersion.props b/build/SharedVersion.props index b46ac16a79..76abcf6912 100644 --- a/build/SharedVersion.props +++ b/build/SharedVersion.props @@ -2,8 +2,8 @@ xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> Avalonia - 0.7.1 - Copyright 2018 © The AvaloniaUI Project + 0.8.999 + Copyright 2019 © The AvaloniaUI Project https://github.com/AvaloniaUI/Avalonia/blob/master/licence.md https://github.com/AvaloniaUI/Avalonia/ https://github.com/AvaloniaUI/Avalonia/ @@ -11,4 +11,4 @@ CS1591 latest - \ No newline at end of file + diff --git a/dirs.proj b/dirs.proj index 0f24632b72..e56320e73f 100644 --- a/dirs.proj +++ b/dirs.proj @@ -6,12 +6,13 @@ + - + diff --git a/native/Avalonia.Native/inc/avalonia-native.h b/native/Avalonia.Native/inc/avalonia-native.h index 4b814a9cfb..a35f4f3eeb 100644 --- a/native/Avalonia.Native/inc/avalonia-native.h +++ b/native/Avalonia.Native/inc/avalonia-native.h @@ -144,6 +144,7 @@ enum AvnStandardCursorType CursorDragMove, CursorDragCopy, CursorDragLink, + CursorNone }; enum AvnWindowEdge diff --git a/native/Avalonia.Native/src/OSX/KeyTransform.mm b/native/Avalonia.Native/src/OSX/KeyTransform.mm index 7486aaad69..971bcf24f8 100644 --- a/native/Avalonia.Native/src/OSX/KeyTransform.mm +++ b/native/Avalonia.Native/src/OSX/KeyTransform.mm @@ -26,7 +26,7 @@ const int kVK_ANSI_3 = 0x14; const int kVK_ANSI_4 = 0x15; const int kVK_ANSI_6 = 0x16; const int kVK_ANSI_5 = 0x17; -//const int kVK_ANSI_Equal = 0x18; +const int kVK_ANSI_Equal = 0x18; const int kVK_ANSI_9 = 0x19; const int kVK_ANSI_7 = 0x1A; const int kVK_ANSI_Minus = 0x1B; @@ -45,11 +45,11 @@ const int kVK_ANSI_K = 0x28; const int kVK_ANSI_Semicolon = 0x29; const int kVK_ANSI_Backslash = 0x2A; const int kVK_ANSI_Comma = 0x2B; -//const int kVK_ANSI_Slash = 0x2C; +const int kVK_ANSI_Slash = 0x2C; const int kVK_ANSI_N = 0x2D; const int kVK_ANSI_M = 0x2E; const int kVK_ANSI_Period = 0x2F; -//const int kVK_ANSI_Grave = 0x32; +const int kVK_ANSI_Grave = 0x32; const int kVK_ANSI_KeypadDecimal = 0x41; const int kVK_ANSI_KeypadMultiply = 0x43; const int kVK_ANSI_KeypadPlus = 0x45; @@ -57,7 +57,7 @@ const int kVK_ANSI_KeypadClear = 0x47; const int kVK_ANSI_KeypadDivide = 0x4B; const int kVK_ANSI_KeypadEnter = 0x4C; const int kVK_ANSI_KeypadMinus = 0x4E; -//const int kVK_ANSI_KeypadEquals = 0x51; +const int kVK_ANSI_KeypadEquals = 0x51; const int kVK_ANSI_Keypad0 = 0x52; const int kVK_ANSI_Keypad1 = 0x53; const int kVK_ANSI_Keypad2 = 0x54; @@ -121,7 +121,7 @@ const int kVK_UpArrow = 0x7E; //const int kVK_JIS_Underscore = 0x5E; //const int kVK_JIS_KeypadComma = 0x5F; //const int kVK_JIS_Eisu = 0x66; -//const int kVK_JIS_Kana = 0x68; +const int kVK_JIS_Kana = 0x68; std::map s_KeyMap = { @@ -148,7 +148,7 @@ const int kVK_UpArrow = 0x7E; {kVK_ANSI_4, D4}, {kVK_ANSI_6, D6}, {kVK_ANSI_5, D5}, - //{kVK_ANSI_Equal, ?}, + {kVK_ANSI_Equal, OemPlus}, {kVK_ANSI_9, D9}, {kVK_ANSI_7, D7}, {kVK_ANSI_Minus, OemMinus}, @@ -167,11 +167,11 @@ const int kVK_UpArrow = 0x7E; {kVK_ANSI_Semicolon, OemSemicolon}, {kVK_ANSI_Backslash, OemBackslash}, {kVK_ANSI_Comma, OemComma}, - //{kVK_ANSI_Slash, ?}, + {kVK_ANSI_Slash, Oem2}, {kVK_ANSI_N, N}, {kVK_ANSI_M, M}, {kVK_ANSI_Period, OemPeriod}, - //{kVK_ANSI_Grave, ?}, + {kVK_ANSI_Grave, OemTilde}, {kVK_ANSI_KeypadDecimal, Decimal}, {kVK_ANSI_KeypadMultiply, Multiply}, {kVK_ANSI_KeypadPlus, OemPlus}, @@ -179,7 +179,7 @@ const int kVK_UpArrow = 0x7E; {kVK_ANSI_KeypadDivide, Divide}, {kVK_ANSI_KeypadEnter, AvnKeyEnter}, {kVK_ANSI_KeypadMinus, OemMinus}, - //{kVK_ANSI_KeypadEquals, ?}, + {kVK_ANSI_KeypadEquals, OemPlus}, {kVK_ANSI_Keypad0, NumPad0}, {kVK_ANSI_Keypad1, NumPad1}, {kVK_ANSI_Keypad2, NumPad2}, @@ -237,5 +237,6 @@ const int kVK_UpArrow = 0x7E; {kVK_LeftArrow, Left}, {kVK_RightArrow, Right}, {kVK_DownArrow, Down}, - {kVK_UpArrow, Up} + {kVK_UpArrow, Up}, + {kVK_JIS_Kana, AvnKeyKanaMode}, }; diff --git a/native/Avalonia.Native/src/OSX/SystemDialogs.mm b/native/Avalonia.Native/src/OSX/SystemDialogs.mm index 7049f77d20..567dd7f747 100644 --- a/native/Avalonia.Native/src/OSX/SystemDialogs.mm +++ b/native/Avalonia.Native/src/OSX/SystemDialogs.mm @@ -45,8 +45,7 @@ public: { auto url = [urls objectAtIndex:i]; - auto string = [url absoluteString]; - string = [string substringFromIndex:7]; + auto string = [url path]; strings[i] = (void*)[string UTF8String]; } @@ -137,8 +136,7 @@ public: { auto url = [urls objectAtIndex:i]; - auto string = [url absoluteString]; - string = [string substringFromIndex:7]; + auto string = [url path]; strings[i] = (void*)[string UTF8String]; } @@ -220,8 +218,7 @@ public: auto url = [panel URL]; - auto string = [url absoluteString]; - string = [string substringFromIndex:7]; + auto string = [url path]; strings[0] = (void*)[string UTF8String]; events->OnCompleted(1, &strings[0]); diff --git a/native/Avalonia.Native/src/OSX/cursor.h b/native/Avalonia.Native/src/OSX/cursor.h index a8eb49c0b9..cfe91955d8 100644 --- a/native/Avalonia.Native/src/OSX/cursor.h +++ b/native/Avalonia.Native/src/OSX/cursor.h @@ -11,18 +11,24 @@ class Cursor : public ComSingleObject { private: NSCursor * _native; - + bool _isHidden; public: FORWARD_IUNKNOWN() - Cursor(NSCursor * cursor) + Cursor(NSCursor * cursor, bool isHidden = false) { _native = cursor; + _isHidden = isHidden; } NSCursor* GetNative() { return _native; } + + bool IsHidden () + { + return _isHidden; + } }; extern std::map s_cursorMap; diff --git a/native/Avalonia.Native/src/OSX/cursor.mm b/native/Avalonia.Native/src/OSX/cursor.mm index bd2c94a4d8..799fa9e8e6 100644 --- a/native/Avalonia.Native/src/OSX/cursor.mm +++ b/native/Avalonia.Native/src/OSX/cursor.mm @@ -21,6 +21,7 @@ class CursorFactory : public ComSingleObject s_cursorMap = { @@ -46,11 +47,13 @@ class CursorFactory : public ComSingleObject(cursor); this->cursor = avnCursor->GetNative(); UpdateCursor(); + + if(avnCursor->IsHidden()) + { + [NSCursor hide]; + } + else + { + [NSCursor unhide]; + } + return S_OK; } } diff --git a/nukebuild/Build.cs b/nukebuild/Build.cs index bb31034299..dd2f27116d 100644 --- a/nukebuild/Build.cs +++ b/nukebuild/Build.cs @@ -19,7 +19,7 @@ using static Nuke.Common.IO.PathConstruction; using static Nuke.Common.Tools.MSBuild.MSBuildTasks; using static Nuke.Common.Tools.DotNet.DotNetTasks; using static Nuke.Common.Tools.Xunit.XunitTasks; - +using static Nuke.Common.Tools.VSWhere.VSWhereTasks; /* Before editing this file, install support plugin for your IDE, @@ -30,7 +30,26 @@ using static Nuke.Common.Tools.Xunit.XunitTasks; */ partial class Build : NukeBuild -{ +{ + static Lazy MsBuildExe = new Lazy(() => + { + if (!RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) + return null; + + var msBuildDirectory = VSWhere("-latest -nologo -property installationPath -format value -prerelease").FirstOrDefault().Text; + + if (!string.IsNullOrWhiteSpace(msBuildDirectory)) + { + string msBuildExe = Path.Combine(msBuildDirectory, @"MSBuild\Current\Bin\MSBuild.exe"); + if (!System.IO.File.Exists(msBuildExe)) + msBuildExe = Path.Combine(msBuildDirectory, @"MSBuild\15.0\Bin\MSBuild.exe"); + + return msBuildExe; + } + + return null; + }, false); + BuildParameters Parameters { get; set; } protected override void OnBuildInitialized() { @@ -70,6 +89,29 @@ partial class Build : NukeBuild } + IReadOnlyCollection MsBuildCommon( + string projectFile, + Configure configurator = null) + { + return MSBuild(projectFile, c => + { + // This is required for VS2019 image on Azure Pipelines + if (Parameters.IsRunningOnWindows && Parameters.IsRunningOnAzure) + { + var javaSdk = Environment.GetEnvironmentVariable("JAVA_HOME_8_X64"); + if (javaSdk != null) + c = c.AddProperty("JavaSdkDirectory", javaSdk); + } + + c = c.AddProperty("PackageVersion", Parameters.Version) + .AddProperty("iOSRoslynPathHackRequired", "true") + .SetToolPath(MsBuildExe.Value) + .SetConfiguration(Parameters.Configuration) + .SetVerbosity(MSBuildVerbosity.Minimal); + c = configurator?.Invoke(c) ?? c; + return c; + }); + } Target Clean => _ => _.Executes(() => { DeleteDirectories(Parameters.BuildDirs); @@ -85,15 +127,9 @@ partial class Build : NukeBuild .DependsOn(Clean) .Executes(() => { - if (Parameters.IsRunningOnWindows) - MSBuild(Parameters.MSBuildSolution, c => c + MsBuildCommon(Parameters.MSBuildSolution, c => c .SetArgumentConfigurator(a => a.Add("/r")) - .SetConfiguration(Parameters.Configuration) - .SetVerbosity(MSBuildVerbosity.Minimal) - .AddProperty("PackageVersion", Parameters.Version) - .AddProperty("iOSRoslynPathHackRequired", "true") - .SetToolsVersion(MSBuildToolsVersion._15_0) .AddTargets("Build") ); @@ -122,6 +158,14 @@ partial class Build : NukeBuild foreach(var fw in frameworks) { + if (fw.StartsWith("net4") + && RuntimeInformation.IsOSPlatform(OSPlatform.Linux) + && Environment.GetEnvironmentVariable("FORCE_LINUX_TESTS") != "1") + { + Information($"Skipping {fw} tests on Linux - https://github.com/mono/mono/issues/13969"); + continue; + } + Information("Running for " + fw); DotNetTest(c => { @@ -211,12 +255,7 @@ partial class Build : NukeBuild { if (Parameters.IsRunningOnWindows) - MSBuild(Parameters.MSBuildSolution, c => c - .SetConfiguration(Parameters.Configuration) - .SetVerbosity(MSBuildVerbosity.Minimal) - .AddProperty("PackageVersion", Parameters.Version) - .AddProperty("iOSRoslynPathHackRequired", "true") - .SetToolsVersion(MSBuildToolsVersion._15_0) + MsBuildCommon(Parameters.MSBuildSolution, c => c .AddTargets("Pack")); else DotNetPack(Parameters.MSBuildSolution, c => diff --git a/nukebuild/_build.csproj b/nukebuild/_build.csproj index e02acff007..2a736e4653 100644 --- a/nukebuild/_build.csproj +++ b/nukebuild/_build.csproj @@ -1,4 +1,4 @@ - + Exe @@ -13,6 +13,7 @@ + diff --git a/packages/Avalonia/Avalonia.csproj b/packages/Avalonia/Avalonia.csproj index 489cb228aa..2c5a09bee7 100644 --- a/packages/Avalonia/Avalonia.csproj +++ b/packages/Avalonia/Avalonia.csproj @@ -1,6 +1,7 @@  netstandard2.0;net461;netcoreapp2.0 + Avalonia diff --git a/packages/Avalonia/AvaloniaBuildTasks.targets b/packages/Avalonia/AvaloniaBuildTasks.targets index 10f971cc4c..a455e2b3fc 100644 --- a/packages/Avalonia/AvaloniaBuildTasks.targets +++ b/packages/Avalonia/AvaloniaBuildTasks.targets @@ -8,6 +8,10 @@ AssemblyFile="$(AvaloniaBuildTasksLocation)" /> + + @@ -20,11 +24,15 @@ + + $(BuildAvaloniaResourcesDependsOn);AddAvaloniaResources;ResolveReferences + + + DependsOnTargets="$(BuildAvaloniaResourcesDependsOn)"> + Command="dotnet msbuild /nodereuse:false $(MSBuildProjectFile) /t:GenerateAvaloniaResources /p:_AvaloniaForceInternalMSBuild=true /p:Configuration=$(Configuration) /p:TargetFramework=$(TargetFramework) /p:BuildProjectReferences=false"/> + + + + $(IntermediateOutputPath)/Avalonia/references + $(IntermediateOutputPath)/Avalonia/original.dll + + + + + + diff --git a/readme.md b/readme.md index 12f683bd55..ee44a0cc3f 100644 --- a/readme.md +++ b/readme.md @@ -8,9 +8,9 @@ ## About -Avalonia is a WPF-inspired cross-platform XAML-based UI framework providing a flexible styling system and supporting a wide range of OSs: Windows (.NET Framework, .NET Core), Linux (GTK), MacOS, Android and iOS. +**Avalonia** is a WPF/UWP-inspired cross-platform XAML-based UI framework providing a flexible styling system and supporting a wide range of Operating Systems such as Windows (.NET Framework, .NET Core), Linux (via Xorg), MacOS and with experimental support for Android and iOS. -**Avalonia is currently in beta** which means that the framework is generally usable for writing applications, but there may be some bugs and breaking changes as we continue development. +**Avalonia** is ready for **General-Purpose Desktop App Development**. However there may be some bugs and breaking changes as we continue along into this project's development. To see the status for some of our features, please see our [Roadmap here](https://github.com/AvaloniaUI/Avalonia/issues/2239). | Control catalog | Desktop platforms | Mobile platforms | |---|---|---| @@ -32,19 +32,16 @@ Install-Package Avalonia.Desktop ## Bleeding Edge Builds -Try out the latest build of Avalonia available for download here: -https://ci.appveyor.com/project/AvaloniaUI/Avalonia/branch/master/artifacts - or use nightly build feeds as described here: https://github.com/AvaloniaUI/Avalonia/wiki/Using-nightly-build-feed ## Documentation -As mentioned above, Avalonia is still in beta and as such there's not much documentation yet. You can take a look at the [getting started page](http://avaloniaui.net/docs/quickstart/) for an overview of how to get started but probably the best thing to do for now is to already know a little bit about WPF/Silverlight/UWP/XAML and ask questions in our [Gitter room](https://gitter.im/AvaloniaUI/Avalonia). +You can take a look at the [getting started page](http://avaloniaui.net/docs/quickstart/) for an overview of how to get started but probably the best thing to do for now is to already know a little bit about WPF/Silverlight/UWP/XAML and ask questions in our [Gitter room](https://gitter.im/AvaloniaUI/Avalonia). There's also a high-level [architecture document](http://avaloniaui.net/architecture/project-structure) that is currently a little bit out of date, and I've also started writing blog posts on Avalonia at http://grokys.github.io/. -Contributions are always welcome! +Contributions for our docs are always welcome! ## Building and Using @@ -80,6 +77,4 @@ Support this project by becoming a sponsor. Your logo will show up here with a l - - - + diff --git a/samples/BindingDemo/App.xaml b/samples/BindingDemo/App.xaml index d9630eef58..9260dd280f 100644 --- a/samples/BindingDemo/App.xaml +++ b/samples/BindingDemo/App.xaml @@ -1,6 +1,9 @@ - - - - - + + + + + \ No newline at end of file diff --git a/samples/BindingDemo/App.xaml.cs b/samples/BindingDemo/App.xaml.cs index 01c52a2a49..f2f44cd502 100644 --- a/samples/BindingDemo/App.xaml.cs +++ b/samples/BindingDemo/App.xaml.cs @@ -3,6 +3,7 @@ using Avalonia; using Avalonia.Controls; using Avalonia.Logging.Serilog; using Avalonia.Markup.Xaml; +using Avalonia.ReactiveUI; using Serilog; namespace BindingDemo diff --git a/samples/BindingDemo/BindingDemo.csproj b/samples/BindingDemo/BindingDemo.csproj index b28ab5fd8a..3005fdbc51 100644 --- a/samples/BindingDemo/BindingDemo.csproj +++ b/samples/BindingDemo/BindingDemo.csproj @@ -14,4 +14,5 @@ + diff --git a/samples/BindingDemo/MainWindow.xaml b/samples/BindingDemo/MainWindow.xaml index 95713dc22f..a232a06383 100644 --- a/samples/BindingDemo/MainWindow.xaml +++ b/samples/BindingDemo/MainWindow.xaml @@ -1,6 +1,7 @@ + diff --git a/samples/ControlCatalog.Android/ControlCatalog.Android.csproj b/samples/ControlCatalog.Android/ControlCatalog.Android.csproj index 272632e7eb..bc76a39f08 100644 --- a/samples/ControlCatalog.Android/ControlCatalog.Android.csproj +++ b/samples/ControlCatalog.Android/ControlCatalog.Android.csproj @@ -16,7 +16,7 @@ Resources\Resource.Designer.cs Off False - v4.4 + v8.0 Properties\AndroidManifest.xml @@ -32,7 +32,7 @@ True False False - armeabi;armeabi-v7a;x86 + armeabi-v7a;x86 Xamarin False False @@ -51,7 +51,7 @@ True False False - armeabi,armeabi-v7a,x86 + armeabi-v7a,x86 Xamarin False False diff --git a/samples/ControlCatalog.Desktop/Program.cs b/samples/ControlCatalog.Desktop/Program.cs index dd5644dd6b..2a8d288614 100644 --- a/samples/ControlCatalog.Desktop/Program.cs +++ b/samples/ControlCatalog.Desktop/Program.cs @@ -4,6 +4,7 @@ using Avalonia; using Avalonia.Controls; using Avalonia.Logging.Serilog; using Avalonia.Platform; +using Avalonia.ReactiveUI; using Serilog; namespace ControlCatalog @@ -25,8 +26,7 @@ namespace ControlCatalog => AppBuilder.Configure() .LogToDebug() .UsePlatformDetect() - .UseReactiveUI() - .UseDataGrid(); + .UseReactiveUI(); private static void ConfigureAssetAssembly(AppBuilder builder) { diff --git a/samples/ControlCatalog.NetCore/ControlCatalog.NetCore.csproj b/samples/ControlCatalog.NetCore/ControlCatalog.NetCore.csproj index 589f41c06b..6a40f7187d 100644 --- a/samples/ControlCatalog.NetCore/ControlCatalog.NetCore.csproj +++ b/samples/ControlCatalog.NetCore/ControlCatalog.NetCore.csproj @@ -3,6 +3,7 @@ Exe netcoreapp2.0 + true @@ -10,6 +11,7 @@ + diff --git a/samples/ControlCatalog.NetCore/Program.cs b/samples/ControlCatalog.NetCore/Program.cs index d13a5b5ef3..40321496c0 100644 --- a/samples/ControlCatalog.NetCore/Program.cs +++ b/samples/ControlCatalog.NetCore/Program.cs @@ -3,14 +3,16 @@ using System.Diagnostics; using System.Linq; using System.Threading; using Avalonia; +using Avalonia.Controls; using Avalonia.Skia; +using Avalonia.ReactiveUI; namespace ControlCatalog.NetCore { static class Program { - static void Main(string[] args) + static int Main(string[] args) { Thread.CurrentThread.TrySetApartmentState(ApartmentState.STA); if (args.Contains("--wait-for-attach")) @@ -24,30 +26,30 @@ namespace ControlCatalog.NetCore } } + var builder = BuildAvaloniaApp(); if (args.Contains("--fbdev")) - AppBuilder.Configure().InitializeWithLinuxFramebuffer(tl => - { - tl.Content = new MainView(); - System.Threading.ThreadPool.QueueUserWorkItem(_ => ConsoleSilencer()); - }); + { + System.Threading.ThreadPool.QueueUserWorkItem(_ => ConsoleSilencer()); + return builder.StartLinuxFramebuffer(args); + } else - BuildAvaloniaApp().Start(AppMain, args); - } - - static void AppMain(Application app, string[] args) - { - app.Run(new MainWindow()); + return builder.StartWithClassicDesktopLifetime(args); } - + /// /// This method is needed for IDE previewer infrastructure /// public static AppBuilder BuildAvaloniaApp() => AppBuilder.Configure() .UsePlatformDetect() + .With(new X11PlatformOptions {EnableMultiTouch = true}) + .With(new Win32PlatformOptions + { + EnableMultitouch = true, + AllowEglInitialization = true + }) .UseSkia() - .UseReactiveUI() - .UseDataGrid(); + .UseReactiveUI(); static void ConsoleSilencer() { diff --git a/samples/ControlCatalog/App.xaml b/samples/ControlCatalog/App.xaml index d20e0100a0..2f6d25c089 100644 --- a/samples/ControlCatalog/App.xaml +++ b/samples/ControlCatalog/App.xaml @@ -4,7 +4,7 @@ - + - + DataGrid A control for displaying and interacting with a data source. @@ -52,4 +52,4 @@ - \ No newline at end of file + diff --git a/samples/ControlCatalog/Pages/MenuPage.xaml.cs b/samples/ControlCatalog/Pages/MenuPage.xaml.cs index 0a77607719..46dbe3dcad 100644 --- a/samples/ControlCatalog/Pages/MenuPage.xaml.cs +++ b/samples/ControlCatalog/Pages/MenuPage.xaml.cs @@ -1,3 +1,4 @@ +using System; using System.Collections.Generic; using System.Reactive; using System.Threading.Tasks; @@ -21,5 +22,18 @@ namespace ControlCatalog.Pages { AvaloniaXamlLoader.Load(this); } + + private MenuPageViewModel _model; + protected override void OnDataContextChanged(EventArgs e) + { + if (_model != null) + _model.View = null; + _model = DataContext as MenuPageViewModel; + if (_model != null) + _model.View = this; + + base.OnDataContextChanged(e); + } + } } diff --git a/samples/ControlCatalog/Pages/NotificationsPage.xaml b/samples/ControlCatalog/Pages/NotificationsPage.xaml new file mode 100644 index 0000000000..94e2314dc7 --- /dev/null +++ b/samples/ControlCatalog/Pages/NotificationsPage.xaml @@ -0,0 +1,10 @@ + + + Notifications + + + - + diff --git a/samples/VirtualizationDemo/Program.cs b/samples/VirtualizationDemo/Program.cs index 98f1f08d6c..9d8f7c1a3d 100644 --- a/samples/VirtualizationDemo/Program.cs +++ b/samples/VirtualizationDemo/Program.cs @@ -5,6 +5,7 @@ using System; using Avalonia; using Avalonia.Controls; using Avalonia.Logging.Serilog; +using Avalonia.ReactiveUI; using Serilog; namespace VirtualizationDemo diff --git a/samples/VirtualizationDemo/ViewModels/ItemViewModel.cs b/samples/VirtualizationDemo/ViewModels/ItemViewModel.cs index e883cdfeb9..4401a2dfeb 100644 --- a/samples/VirtualizationDemo/ViewModels/ItemViewModel.cs +++ b/samples/VirtualizationDemo/ViewModels/ItemViewModel.cs @@ -10,6 +10,7 @@ namespace VirtualizationDemo.ViewModels { private string _prefix; private int _index; + private double _height = double.NaN; public ItemViewModel(int index, string prefix = "Item") { @@ -18,5 +19,11 @@ namespace VirtualizationDemo.ViewModels } public string Header => $"{_prefix} {_index}"; + + public double Height + { + get => _height; + set => this.RaiseAndSetIfChanged(ref _height, value); + } } } diff --git a/samples/VirtualizationDemo/ViewModels/MainWindowViewModel.cs b/samples/VirtualizationDemo/ViewModels/MainWindowViewModel.cs index eba17f92e4..80e0fb2586 100644 --- a/samples/VirtualizationDemo/ViewModels/MainWindowViewModel.cs +++ b/samples/VirtualizationDemo/ViewModels/MainWindowViewModel.cs @@ -98,6 +98,24 @@ namespace VirtualizationDemo.ViewModels public ReactiveCommand SelectFirstCommand { get; private set; } public ReactiveCommand SelectLastCommand { get; private set; } + public void RandomizeSize() + { + var random = new Random(); + + foreach (var i in Items) + { + i.Height = random.Next(240) + 10; + } + } + + public void ResetSize() + { + foreach (var i in Items) + { + i.Height = double.NaN; + } + } + private void ResizeItems(int count) { if (Items == null) diff --git a/samples/VirtualizationDemo/VirtualizationDemo.csproj b/samples/VirtualizationDemo/VirtualizationDemo.csproj index b28ab5fd8a..3005fdbc51 100644 --- a/samples/VirtualizationDemo/VirtualizationDemo.csproj +++ b/samples/VirtualizationDemo/VirtualizationDemo.csproj @@ -14,4 +14,5 @@ + diff --git a/samples/interop/WindowsInteropTest/WindowsInteropTest.csproj b/samples/interop/WindowsInteropTest/WindowsInteropTest.csproj index 33c7b85f06..c067d38595 100644 --- a/samples/interop/WindowsInteropTest/WindowsInteropTest.csproj +++ b/samples/interop/WindowsInteropTest/WindowsInteropTest.csproj @@ -104,6 +104,10 @@ {b09b78d8-9b26-48b0-9149-d64a2f120f3f} Avalonia.Base + + {3278f3a9-9509-4a3f-a15b-bdc8b5bff632} + Avalonia.Controls.DataGrid + {d2221c82-4a25-4583-9b43-d791e3f6820c} Avalonia.Controls @@ -185,6 +189,5 @@ - \ No newline at end of file diff --git a/src/Android/Avalonia.Android/Avalonia.Android.csproj b/src/Android/Avalonia.Android/Avalonia.Android.csproj index 9c3d4fb3a1..0089ea3b8d 100644 --- a/src/Android/Avalonia.Android/Avalonia.Android.csproj +++ b/src/Android/Avalonia.Android/Avalonia.Android.csproj @@ -1,6 +1,6 @@  - monoandroid44 + monoandroid80 true diff --git a/src/Android/Avalonia.Android/Platform/Specific/Helpers/AndroidTouchEventsHelper.cs b/src/Android/Avalonia.Android/Platform/Specific/Helpers/AndroidTouchEventsHelper.cs index 112925ab0f..463d499aad 100644 --- a/src/Android/Avalonia.Android/Platform/Specific/Helpers/AndroidTouchEventsHelper.cs +++ b/src/Android/Avalonia.Android/Platform/Specific/Helpers/AndroidTouchEventsHelper.cs @@ -33,7 +33,7 @@ namespace Avalonia.Android.Platform.Specific.Helpers return null; } - RawMouseEventType? mouseEventType = null; + RawPointerEventType? mouseEventType = null; var eventTime = DateTime.Now; //Basic touch support switch (e.Action) @@ -42,17 +42,17 @@ namespace Avalonia.Android.Platform.Specific.Helpers //may be bot flood the evnt system with too many event especially on not so powerfull mobile devices if ((eventTime - _lastTouchMoveEventTime).TotalMilliseconds > 10) { - mouseEventType = RawMouseEventType.Move; + mouseEventType = RawPointerEventType.Move; } break; case MotionEventActions.Down: - mouseEventType = RawMouseEventType.LeftButtonDown; + mouseEventType = RawPointerEventType.LeftButtonDown; break; case MotionEventActions.Up: - mouseEventType = RawMouseEventType.LeftButtonUp; + mouseEventType = RawPointerEventType.LeftButtonUp; break; } @@ -75,14 +75,14 @@ namespace Avalonia.Android.Platform.Specific.Helpers //we need to generate mouse move before first mouse down event //as this is the way buttons are working every time //otherwise there is a problem sometimes - if (mouseEventType == RawMouseEventType.LeftButtonDown) + if (mouseEventType == RawPointerEventType.LeftButtonDown) { - var me = new RawMouseEventArgs(mouseDevice, (uint)eventTime.Ticks, inputRoot, - RawMouseEventType.Move, _point, InputModifiers.None); + var me = new RawPointerEventArgs(mouseDevice, (uint)eventTime.Ticks, inputRoot, + RawPointerEventType.Move, _point, InputModifiers.None); _view.Input(me); } - var mouseEvent = new RawMouseEventArgs(mouseDevice, (uint)eventTime.Ticks, inputRoot, + var mouseEvent = new RawPointerEventArgs(mouseDevice, (uint)eventTime.Ticks, inputRoot, mouseEventType.Value, _point, InputModifiers.LeftMouseButton); _view.Input(mouseEvent); diff --git a/src/Android/Avalonia.AndroidTestApplication/Avalonia.AndroidTestApplication.csproj b/src/Android/Avalonia.AndroidTestApplication/Avalonia.AndroidTestApplication.csproj index b493fd1ef2..1b2b205d45 100644 --- a/src/Android/Avalonia.AndroidTestApplication/Avalonia.AndroidTestApplication.csproj +++ b/src/Android/Avalonia.AndroidTestApplication/Avalonia.AndroidTestApplication.csproj @@ -16,7 +16,7 @@ Resources\Resource.Designer.cs Off False - v4.4 + v8.0 Properties\AndroidManifest.xml @@ -32,7 +32,7 @@ True False False - armeabi;armeabi-v7a;x86 + armeabi-v7a;x86 Xamarin False True @@ -52,7 +52,7 @@ True False False - armeabi,armeabi-v7a,x86 + armeabi-v7a,x86 Xamarin False False @@ -150,6 +150,7 @@ + diff --git a/src/Avalonia.Animation/Animatable.cs b/src/Avalonia.Animation/Animatable.cs index 4b0f76c5d5..3a3d00b94a 100644 --- a/src/Avalonia.Animation/Animatable.cs +++ b/src/Avalonia.Animation/Animatable.cs @@ -7,7 +7,7 @@ using System.Linq; using System.Reactive.Linq; using Avalonia.Collections; using Avalonia.Data; -using Avalonia.Animation.Animators; +using Avalonia.Animation.Animators; namespace Avalonia.Animation { @@ -36,13 +36,27 @@ namespace Avalonia.Animation private Transitions _transitions; + private Dictionary _previousTransitions; + /// /// Gets or sets the property transitions for the control. /// public Transitions Transitions { - get { return _transitions ?? (_transitions = new Transitions()); } - set { SetAndRaise(TransitionsProperty, ref _transitions, value); } + get + { + if (_transitions == null) + _transitions = new Transitions(); + + if (_previousTransitions == null) + _previousTransitions = new Dictionary(); + + return _transitions; + } + set + { + SetAndRaise(TransitionsProperty, ref _transitions, value); + } } /// @@ -52,13 +66,18 @@ namespace Avalonia.Animation /// The event args. protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs e) { - if (e.Priority != BindingPriority.Animation && Transitions != null) + if (e.Priority != BindingPriority.Animation && Transitions != null && _previousTransitions != null) { var match = Transitions.FirstOrDefault(x => x.Property == e.Property); if (match != null) { - match.Apply(this, Clock ?? Avalonia.Animation.Clock.GlobalClock, e.OldValue, e.NewValue); + if (_previousTransitions.TryGetValue(e.Property, out var dispose)) + dispose.Dispose(); + + var instance = match.Apply(this, Clock ?? Avalonia.Animation.Clock.GlobalClock, e.OldValue, e.NewValue); + + _previousTransitions[e.Property] = instance; } } } diff --git a/src/Avalonia.Animation/Properties/AssemblyInfo.cs b/src/Avalonia.Animation/Properties/AssemblyInfo.cs index 985a8e5bfe..eb38a66a84 100644 --- a/src/Avalonia.Animation/Properties/AssemblyInfo.cs +++ b/src/Avalonia.Animation/Properties/AssemblyInfo.cs @@ -3,7 +3,10 @@ using Avalonia.Metadata; using System.Reflection; +using System.Runtime.CompilerServices; [assembly: XmlnsDefinition("https://github.com/avaloniaui", "Avalonia.Animation")] [assembly: XmlnsDefinition("https://github.com/avaloniaui", "Avalonia.Animation.Easings")] -[assembly: XmlnsDefinition("https://github.com/avaloniaui", "Avalonia.Animation.Animators")] \ No newline at end of file +[assembly: XmlnsDefinition("https://github.com/avaloniaui", "Avalonia.Animation.Animators")] + +[assembly: InternalsVisibleTo("Avalonia.LeakTests")] diff --git a/src/Avalonia.Animation/TransitionInstance.cs b/src/Avalonia.Animation/TransitionInstance.cs index eff2c4e9f3..10ea6bf523 100644 --- a/src/Avalonia.Animation/TransitionInstance.cs +++ b/src/Avalonia.Animation/TransitionInstance.cs @@ -30,13 +30,16 @@ namespace Avalonia.Animation { var interpVal = (double)t.Ticks / _duration.Ticks; - if (interpVal > 1d || interpVal < 0d) + // Clamp interpolation value. + if (interpVal >= 1d | interpVal < 0d) { + PublishNext(1d); PublishCompleted(); - return; } - - PublishNext(interpVal); + else + { + PublishNext(interpVal); + } } protected override void Unsubscribed() diff --git a/src/Avalonia.Base/AvaloniaProperty.cs b/src/Avalonia.Base/AvaloniaProperty.cs index 953132116c..1de5cb06c6 100644 --- a/src/Avalonia.Base/AvaloniaProperty.cs +++ b/src/Avalonia.Base/AvaloniaProperty.cs @@ -19,7 +19,7 @@ namespace Avalonia /// /// Represents an unset property value. /// - public static readonly object UnsetValue = new Unset(); + public static readonly object UnsetValue = new UnsetValueType(); private static int s_nextId; private readonly Subject _initialized; @@ -546,16 +546,17 @@ namespace Avalonia } } + + } + /// + /// Class representing the . + /// + public class UnsetValueType + { /// - /// Class representing the . + /// Returns the string representation of the . /// - private class Unset - { - /// - /// Returns the string representation of the . - /// - /// The string "(unset)". - public override string ToString() => "(unset)"; - } + /// The string "(unset)". + public override string ToString() => "(unset)"; } } diff --git a/src/Avalonia.Base/Data/BindingOperations.cs b/src/Avalonia.Base/Data/BindingOperations.cs index ca148659e6..44b47329ac 100644 --- a/src/Avalonia.Base/Data/BindingOperations.cs +++ b/src/Avalonia.Base/Data/BindingOperations.cs @@ -10,6 +10,8 @@ namespace Avalonia.Data { public static class BindingOperations { + public static readonly object DoNothing = new object(); + /// /// Applies an a property on an . /// @@ -65,7 +67,11 @@ namespace Avalonia.Data return Disposable.Empty; } case BindingMode.OneWayToSource: - return target.GetObservable(property).Subscribe(binding.Subject); + return Observable.CombineLatest( + binding.Observable, + target.GetObservable(property), + (_, v) => v) + .Subscribe(x => binding.Subject.OnNext(x)); default: throw new ArgumentException("Invalid binding mode."); } diff --git a/src/Avalonia.Base/Data/Converters/DefaultValueConverter.cs b/src/Avalonia.Base/Data/Converters/DefaultValueConverter.cs index 990a4b04f2..0ffd6a9539 100644 --- a/src/Avalonia.Base/Data/Converters/DefaultValueConverter.cs +++ b/src/Avalonia.Base/Data/Converters/DefaultValueConverter.cs @@ -31,7 +31,7 @@ namespace Avalonia.Data.Converters { if (value == null) { - return AvaloniaProperty.UnsetValue; + return targetType.IsValueType ? AvaloniaProperty.UnsetValue : null; } if (typeof(ICommand).IsAssignableFrom(targetType) && value is Delegate d && d.Method.GetParameters().Length <= 1) diff --git a/src/Avalonia.Base/Data/Converters/StringFormatMultiValueConverter.cs b/src/Avalonia.Base/Data/Converters/StringFormatMultiValueConverter.cs new file mode 100644 index 0000000000..b190f06be5 --- /dev/null +++ b/src/Avalonia.Base/Data/Converters/StringFormatMultiValueConverter.cs @@ -0,0 +1,46 @@ +using System; +using System.Collections.Generic; +using System.Globalization; +using System.Linq; + +namespace Avalonia.Data.Converters +{ + /// + /// A multi-value converter which calls + /// + public class StringFormatMultiValueConverter : IMultiValueConverter + { + /// + /// Initializes a new instance of the class. + /// + /// The format string. + /// + /// An optional inner converter to be called before the format takes place. + /// + public StringFormatMultiValueConverter(string format, IMultiValueConverter inner) + { + Contract.Requires(format != null); + + Format = format; + Inner = inner; + } + + /// + /// Gets an inner value converter which will be called before the string format takes place. + /// + public IMultiValueConverter Inner { get; } + + /// + /// Gets the format string. + /// + public string Format { get; } + + /// + public object Convert(IList values, Type targetType, object parameter, CultureInfo culture) + { + return Inner == null + ? string.Format(culture, Format, values.ToArray()) + : string.Format(culture, Format, Inner.Convert(values, targetType, parameter, culture)); + } + } +} diff --git a/src/Avalonia.Base/Data/Core/BindingExpression.cs b/src/Avalonia.Base/Data/Core/BindingExpression.cs index f1717bde3b..7f8396cdfa 100644 --- a/src/Avalonia.Base/Data/Core/BindingExpression.cs +++ b/src/Avalonia.Base/Data/Core/BindingExpression.cs @@ -114,6 +114,11 @@ namespace Avalonia.Data.Core /// public void OnNext(object value) { + if (value == BindingOperations.DoNothing) + { + return; + } + using (_inner.Subscribe(_ => { })) { var type = _inner.ResultType; @@ -126,6 +131,11 @@ namespace Avalonia.Data.Core ConverterParameter, CultureInfo.CurrentCulture); + if (converted == BindingOperations.DoNothing) + { + return; + } + if (converted == AvaloniaProperty.UnsetValue) { converted = TypeUtilities.Default(type); @@ -186,6 +196,11 @@ namespace Avalonia.Data.Core /// private object ConvertValue(object value) { + if (value == BindingOperations.DoNothing) + { + return value; + } + var notification = value as BindingNotification; if (notification == null) @@ -196,6 +211,11 @@ namespace Avalonia.Data.Core ConverterParameter, CultureInfo.CurrentCulture); + if (converted == BindingOperations.DoNothing) + { + return converted; + } + notification = converted as BindingNotification; if (notification?.ErrorType == BindingErrorType.None) @@ -327,7 +347,18 @@ namespace Avalonia.Data.Core public void OnNext(object value) { + if (value == BindingOperations.DoNothing) + { + return; + } + var converted = _owner.ConvertValue(value); + + if (converted == BindingOperations.DoNothing) + { + return; + } + _owner._value = new WeakReference(converted); _owner.PublishNext(converted); } diff --git a/src/Avalonia.Base/Data/Core/ExpressionParseException.cs b/src/Avalonia.Base/Data/Core/ExpressionParseException.cs index 0365ead24d..2432410203 100644 --- a/src/Avalonia.Base/Data/Core/ExpressionParseException.cs +++ b/src/Avalonia.Base/Data/Core/ExpressionParseException.cs @@ -9,7 +9,10 @@ namespace Avalonia.Data.Core /// Exception thrown when could not parse the provided /// expression string. /// - public class ExpressionParseException : Exception +#if !BUILDTASK + public +#endif + class ExpressionParseException : Exception { /// /// Initializes a new instance of the class. diff --git a/src/Avalonia.Base/Data/Core/PropertyAccessorNode.cs b/src/Avalonia.Base/Data/Core/PropertyAccessorNode.cs index a916142675..df8f46a7d7 100644 --- a/src/Avalonia.Base/Data/Core/PropertyAccessorNode.cs +++ b/src/Avalonia.Base/Data/Core/PropertyAccessorNode.cs @@ -59,8 +59,8 @@ namespace Avalonia.Data.Core $"Could not find a matching property accessor for {PropertyName}."); } - accessor.Subscribe(ValueChanged); _accessor = accessor; + accessor.Subscribe(ValueChanged); } protected override void StopListeningCore() diff --git a/src/Avalonia.Base/Metadata/UsableDuringInitializationAttribute.cs b/src/Avalonia.Base/Metadata/UsableDuringInitializationAttribute.cs new file mode 100644 index 0000000000..753a96b9ce --- /dev/null +++ b/src/Avalonia.Base/Metadata/UsableDuringInitializationAttribute.cs @@ -0,0 +1,10 @@ +using System; + +namespace Avalonia.Metadata +{ + [AttributeUsage(AttributeTargets.Class)] + public class UsableDuringInitializationAttribute : Attribute + { + + } +} diff --git a/src/Avalonia.Base/Platform/IAssetLoader.cs b/src/Avalonia.Base/Platform/IAssetLoader.cs index 880ed62046..4356fac4c0 100644 --- a/src/Avalonia.Base/Platform/IAssetLoader.cs +++ b/src/Avalonia.Base/Platform/IAssetLoader.cs @@ -61,6 +61,16 @@ namespace Avalonia.Platform /// (Stream stream, Assembly assembly) OpenAndGetAssembly(Uri uri, Uri baseUri = null); + /// + /// Extracts assembly information from URI + /// + /// The URI. + /// + /// A base URI to use if is relative. + /// + /// Assembly associated with the Uri + Assembly GetAssembly(Uri uri, Uri baseUri = null); + /// /// Gets all assets of a folder and subfolders that match specified uri. /// diff --git a/src/Avalonia.Base/Utilities/AvaloniaResourcesIndex.cs b/src/Avalonia.Base/Utilities/AvaloniaResourcesIndex.cs index 66024236da..ed5bc697b0 100644 --- a/src/Avalonia.Base/Utilities/AvaloniaResourcesIndex.cs +++ b/src/Avalonia.Base/Utilities/AvaloniaResourcesIndex.cs @@ -46,6 +46,36 @@ namespace Avalonia.Utilities Entries = entries }); } + + public static byte[] Create(Dictionary data) + { + var sources = data.ToList(); + var offsets = new Dictionary(); + var coffset = 0; + foreach (var s in sources) + { + offsets[s.Key] = coffset; + coffset += s.Value.Length; + } + var index = sources.Select(s => new AvaloniaResourcesIndexEntry + { + Path = s.Key, + Size = s.Value.Length, + Offset = offsets[s.Key] + }).ToList(); + var output = new MemoryStream(); + var ms = new MemoryStream(); + AvaloniaResourcesIndexReaderWriter.Write(ms, index); + new BinaryWriter(output).Write((int)ms.Length); + ms.Position = 0; + ms.CopyTo(output); + foreach (var s in sources) + { + output.Write(s.Value,0,s.Value.Length); + } + + return output.ToArray(); + } } [DataContract] diff --git a/src/Avalonia.Base/Utilities/CharacterReader.cs b/src/Avalonia.Base/Utilities/CharacterReader.cs index 56f3f7abd4..9be2d230d4 100644 --- a/src/Avalonia.Base/Utilities/CharacterReader.cs +++ b/src/Avalonia.Base/Utilities/CharacterReader.cs @@ -5,7 +5,10 @@ using System; namespace Avalonia.Utilities { - public ref struct CharacterReader +#if !BUILDTASK + public +#endif + ref struct CharacterReader { private ReadOnlySpan _s; diff --git a/src/Avalonia.Base/Utilities/IdentifierParser.cs b/src/Avalonia.Base/Utilities/IdentifierParser.cs index 09c50bf147..a57a2b7ba5 100644 --- a/src/Avalonia.Base/Utilities/IdentifierParser.cs +++ b/src/Avalonia.Base/Utilities/IdentifierParser.cs @@ -6,7 +6,10 @@ using System.Globalization; namespace Avalonia.Utilities { - public static class IdentifierParser +#if !BUILDTASK + public +#endif + static class IdentifierParser { public static ReadOnlySpan ParseIdentifier(this ref CharacterReader r) { diff --git a/src/Avalonia.Base/Utilities/MathUtilities.cs b/src/Avalonia.Base/Utilities/MathUtilities.cs index dcb3ef4a2b..41b57b6e70 100644 --- a/src/Avalonia.Base/Utilities/MathUtilities.cs +++ b/src/Avalonia.Base/Utilities/MathUtilities.cs @@ -1,6 +1,9 @@ // 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 System.Runtime.InteropServices; + namespace Avalonia.Utilities { /// @@ -8,6 +11,89 @@ namespace Avalonia.Utilities /// public static class MathUtilities { + /// + /// AreClose - Returns whether or not two doubles are "close". That is, whether or + /// not they are within epsilon of each other. + /// + /// The first double to compare. + /// The second double to compare. + public static bool AreClose(double value1, double value2) + { + //in case they are Infinities (then epsilon check does not work) + if (value1 == value2) return true; + double eps = (Math.Abs(value1) + Math.Abs(value2) + 10.0) * double.Epsilon; + double delta = value1 - value2; + return (-eps < delta) && (eps > delta); + } + + /// + /// LessThan - Returns whether or not the first double is less than the second double. + /// That is, whether or not the first is strictly less than *and* not within epsilon of + /// the other number. + /// + /// The first double to compare. + /// The second double to compare. + public static bool LessThan(double value1, double value2) + { + return (value1 < value2) && !AreClose(value1, value2); + } + + /// + /// GreaterThan - Returns whether or not the first double is greater than the second double. + /// That is, whether or not the first is strictly greater than *and* not within epsilon of + /// the other number. + /// + /// The first double to compare. + /// The second double to compare. + public static bool GreaterThan(double value1, double value2) + { + return (value1 > value2) && !AreClose(value1, value2); + } + + /// + /// LessThanOrClose - Returns whether or not the first double is less than or close to + /// the second double. That is, whether or not the first is strictly less than or within + /// epsilon of the other number. + /// + /// The first double to compare. + /// The second double to compare. + public static bool LessThanOrClose(double value1, double value2) + { + return (value1 < value2) || AreClose(value1, value2); + } + + /// + /// GreaterThanOrClose - Returns whether or not the first double is greater than or close to + /// the second double. That is, whether or not the first is strictly greater than or within + /// epsilon of the other number. + /// + /// The first double to compare. + /// The second double to compare. + public static bool GreaterThanOrClose(double value1, double value2) + { + return (value1 > value2) || AreClose(value1, value2); + } + + /// + /// IsOne - Returns whether or not the double is "close" to 1. Same as AreClose(double, 1), + /// but this is faster. + /// + /// The double to compare to 1. + public static bool IsOne(double value) + { + return Math.Abs(value - 1.0) < 10.0 * double.Epsilon; + } + + /// + /// IsZero - Returns whether or not the double is "close" to 0. Same as AreClose(double, 0), + /// but this is faster. + /// + /// The double to compare to 0. + public static bool IsZero(double value) + { + return Math.Abs(value) < 10.0 * double.Epsilon; + } + /// /// Clamps a value between a minimum and maximum value. /// @@ -31,6 +117,39 @@ namespace Avalonia.Utilities } } + /// + /// Calculates the value to be used for layout rounding at high DPI. + /// + /// Input value to be rounded. + /// Ratio of screen's DPI to layout DPI + /// Adjusted value that will produce layout rounding on screen at high dpi. + /// This is a layout helper method. It takes DPI into account and also does not return + /// the rounded value if it is unacceptable for layout, e.g. Infinity or NaN. It's a helper associated with + /// UseLayoutRounding property and should not be used as a general rounding utility. + public static double RoundLayoutValue(double value, double dpiScale) + { + double newValue; + + // If DPI == 1, don't use DPI-aware rounding. + if (!MathUtilities.AreClose(dpiScale, 1.0)) + { + newValue = Math.Round(value * dpiScale) / dpiScale; + // If rounding produces a value unacceptable to layout (NaN, Infinity or MaxValue), use the original value. + if (double.IsNaN(newValue) || + double.IsInfinity(newValue) || + MathUtilities.AreClose(newValue, double.MaxValue)) + { + newValue = value; + } + } + else + { + newValue = Math.Round(value); + } + + return newValue; + } + /// /// Clamps a value between a minimum and maximum value. /// diff --git a/src/Avalonia.Base/Utilities/SynchronousCompletionAsyncResult.cs b/src/Avalonia.Base/Utilities/SynchronousCompletionAsyncResult.cs new file mode 100644 index 0000000000..f321b2b2f1 --- /dev/null +++ b/src/Avalonia.Base/Utilities/SynchronousCompletionAsyncResult.cs @@ -0,0 +1,106 @@ +using System; +using System.Collections.Generic; +using System.Runtime.CompilerServices; + +namespace Avalonia.Utilities +{ + /// + /// A task-like operation that is guaranteed to finish continuations synchronously, + /// can be used for parametrized one-shot events + /// + public struct SynchronousCompletionAsyncResult : INotifyCompletion + { + private readonly SynchronousCompletionAsyncResultSource _source; + private readonly T _result; + private readonly bool _isValid; + internal SynchronousCompletionAsyncResult(SynchronousCompletionAsyncResultSource source) + { + _source = source; + _result = default; + _isValid = true; + } + + public SynchronousCompletionAsyncResult(T result) + { + _result = result; + _source = null; + _isValid = true; + } + + static void ThrowNotInitialized() => + throw new InvalidOperationException("This SynchronousCompletionAsyncResult was not initialized"); + + public bool IsCompleted + { + get + { + if (!_isValid) + ThrowNotInitialized(); + return _source == null || _source.IsCompleted; + } + } + + public T GetResult() + { + if (!_isValid) + ThrowNotInitialized(); + return _source == null ? _result : _source.Result; + } + + + public void OnCompleted(Action continuation) + { + if (!_isValid) + ThrowNotInitialized(); + if (_source == null) + continuation(); + else + _source.OnCompleted(continuation); + } + + public SynchronousCompletionAsyncResult GetAwaiter() => this; + } + + /// + /// Source for incomplete SynchronousCompletionAsyncResult + /// + /// + public class SynchronousCompletionAsyncResultSource + { + private T _result; + internal bool IsCompleted { get; private set; } + public SynchronousCompletionAsyncResult AsyncResult => new SynchronousCompletionAsyncResult(this); + + internal T Result => IsCompleted ? + _result : + throw new InvalidOperationException("Asynchronous operation is not yet completed"); + + private List _continuations; + + internal void OnCompleted(Action continuation) + { + if(_continuations==null) + _continuations = new List(); + _continuations.Add(continuation); + } + + public void SetResult(T result) + { + if (IsCompleted) + throw new InvalidOperationException("Asynchronous operation is already completed"); + _result = result; + IsCompleted = true; + if(_continuations!=null) + foreach (var c in _continuations) + c(); + _continuations = null; + } + + public void TrySetResult(T result) + { + if(IsCompleted) + return; + SetResult(result); + } + } +} diff --git a/src/Avalonia.Base/Utilities/WeakEventHandlerManager.cs b/src/Avalonia.Base/Utilities/WeakEventHandlerManager.cs index 0ade1af249..b59ed166bc 100644 --- a/src/Avalonia.Base/Utilities/WeakEventHandlerManager.cs +++ b/src/Avalonia.Base/Utilities/WeakEventHandlerManager.cs @@ -19,6 +19,7 @@ namespace Avalonia.Utilities /// /// The type of the target. /// The type of the event arguments. + /// The type of the subscriber. /// The event source. /// The name of the event. /// The subscriber. @@ -40,6 +41,7 @@ namespace Avalonia.Utilities /// Unsubscribes from an event. /// /// The type of the event arguments. + /// The type of the subscriber. /// The event source. /// The name of the event. /// The subscriber. diff --git a/src/Avalonia.Build.Tasks/Avalonia.Build.Tasks.csproj b/src/Avalonia.Build.Tasks/Avalonia.Build.Tasks.csproj index ce767aaa87..582e4499c5 100644 --- a/src/Avalonia.Build.Tasks/Avalonia.Build.Tasks.csproj +++ b/src/Avalonia.Build.Tasks/Avalonia.Build.Tasks.csproj @@ -1,9 +1,13 @@ - - + - netstandard2.0 + netstandard2.0 + netstandard2.0;netcoreapp2.0 + exe + false tools - $(DefineConstants);BUILDTASK + $(DefineConstants);BUILDTASK;XAMLIL_CECIL_INTERNAL;XAMLIL_INTERNAL + true + NU1605 @@ -13,6 +17,54 @@ Shared/AvaloniaResourceXamlInfo.cs - + + XamlIlExtensions/%(RecursiveDir)%(FileName)%(Extension) + + + + XamlIl/%(RecursiveDir)%(FileName)%(Extension) + + + XamlIl.Cecil/%(RecursiveDir)%(FileName)%(Extension) + + + Markup/%(RecursiveDir)%(FileName)%(Extension) + + + Markup/%(RecursiveDir)%(FileName)%(Extension) + + + Markup/%(RecursiveDir)%(FileName)%(Extension) + + + Markup/%(RecursiveDir)%(FileName)%(Extension) + + + Markup/%(RecursiveDir)%(FileName)%(Extension) + + + + + + + + + + + + + $(MSBuildThisFileDirectory)bin\$(Configuration)\$(TargetFramework) + + + + + + + + + + + + diff --git a/src/Avalonia.Build.Tasks/CompileAvaloniaXamlTask.cs b/src/Avalonia.Build.Tasks/CompileAvaloniaXamlTask.cs new file mode 100644 index 0000000000..61303dbbfe --- /dev/null +++ b/src/Avalonia.Build.Tasks/CompileAvaloniaXamlTask.cs @@ -0,0 +1,73 @@ +using System; +using System.Diagnostics; +using System.IO; +using System.Linq; +using System.Reflection; +using System.Threading; +using Microsoft.Build.Framework; + +namespace Avalonia.Build.Tasks +{ + public class CompileAvaloniaXamlTask: ITask + { + public bool Execute() + { + OutputPath = OutputPath ?? AssemblyFile; + var outputPdb = GetPdbPath(OutputPath); + var input = AssemblyFile; + var inputPdb = GetPdbPath(input); + // Make a copy and delete the original file to prevent MSBuild from thinking that everything is OK + if (OriginalCopyPath != null) + { + File.Copy(AssemblyFile, OriginalCopyPath, true); + input = OriginalCopyPath; + File.Delete(AssemblyFile); + + if (File.Exists(inputPdb)) + { + var copyPdb = GetPdbPath(OriginalCopyPath); + File.Copy(inputPdb, copyPdb, true); + File.Delete(inputPdb); + inputPdb = copyPdb; + } + } + + var res = XamlCompilerTaskExecutor.Compile(BuildEngine, input, + File.ReadAllLines(ReferencesFilePath).Where(l => !string.IsNullOrWhiteSpace(l)).ToArray(), + ProjectDirectory, OutputPath); + if (!res.Success) + return false; + if (!res.WrittenFile) + { + File.Copy(input, OutputPath, true); + if(File.Exists(inputPdb)) + File.Copy(inputPdb, outputPdb, true); + } + return true; + } + + string GetPdbPath(string p) + { + var d = Path.GetDirectoryName(p); + var f = Path.GetFileNameWithoutExtension(p); + var rv = f + ".pdb"; + if (d != null) + rv = Path.Combine(d, rv); + return rv; + } + + [Required] + public string AssemblyFile { get; set; } + [Required] + public string ReferencesFilePath { get; set; } + [Required] + public string OriginalCopyPath { get; set; } + [Required] + public string ProjectDirectory { get; set; } + + public string OutputPath { get; set; } + + public IBuildEngine BuildEngine { get; set; } + public ITaskHost HostObject { get; set; } + } +} diff --git a/src/Avalonia.Build.Tasks/Extensions.cs b/src/Avalonia.Build.Tasks/Extensions.cs index faecee0f37..440c6d7489 100644 --- a/src/Avalonia.Build.Tasks/Extensions.cs +++ b/src/Avalonia.Build.Tasks/Extensions.cs @@ -1,8 +1,9 @@ +using System; using Microsoft.Build.Framework; namespace Avalonia.Build.Tasks { - public static class Extensions + static class Extensions { static string FormatErrorCode(BuildEngineErrorCode code) => $"AVLN:{(int)code:0000}"; diff --git a/src/Avalonia.Build.Tasks/Program.cs b/src/Avalonia.Build.Tasks/Program.cs new file mode 100644 index 0000000000..d356b15408 --- /dev/null +++ b/src/Avalonia.Build.Tasks/Program.cs @@ -0,0 +1,68 @@ +using System; +using System.Collections; +using System.IO; +using System.Linq; +using Microsoft.Build.Framework; + +namespace Avalonia.Build.Tasks +{ + public class Program + { + static int Main(string[] args) + { + if (args.Length != 3) + { + if (args.Length == 1) + args = new[] {"original.dll", "references", "out.dll"} + .Select(x => Path.Combine(args[0], x)).ToArray(); + else + { + Console.Error.WriteLine("input references output"); + return 1; + } + } + + return new CompileAvaloniaXamlTask() + { + AssemblyFile = args[0], + ReferencesFilePath = args[1], + OutputPath = args[2], + BuildEngine = new ConsoleBuildEngine(), + ProjectDirectory = Directory.GetCurrentDirectory() + }.Execute() ? + 0 : + 2; + } + + class ConsoleBuildEngine : IBuildEngine + { + public void LogErrorEvent(BuildErrorEventArgs e) + { + Console.WriteLine($"ERROR: {e.Code} {e.Message} in {e.File} {e.LineNumber}:{e.ColumnNumber}-{e.EndLineNumber}:{e.EndColumnNumber}"); + } + + public void LogWarningEvent(BuildWarningEventArgs e) + { + Console.WriteLine($"WARNING: {e.Code} {e.Message} in {e.File} {e.LineNumber}:{e.ColumnNumber}-{e.EndLineNumber}:{e.EndColumnNumber}"); + } + + public void LogMessageEvent(BuildMessageEventArgs e) + { + Console.WriteLine($"MESSAGE: {e.Code} {e.Message} in {e.File} {e.LineNumber}:{e.ColumnNumber}-{e.EndLineNumber}:{e.EndColumnNumber}"); + } + + public void LogCustomEvent(CustomBuildEventArgs e) + { + Console.WriteLine($"CUSTOM: {e.Message}"); + } + + public bool BuildProjectFile(string projectFileName, string[] targetNames, IDictionary globalProperties, + IDictionary targetOutputs) => throw new NotSupportedException(); + + public bool ContinueOnError { get; } + public int LineNumberOfTaskNode { get; } + public int ColumnNumberOfTaskNode { get; } + public string ProjectFileOfTaskNode { get; } + } + } +} diff --git a/src/Avalonia.Build.Tasks/SpanCompat.cs b/src/Avalonia.Build.Tasks/SpanCompat.cs new file mode 100644 index 0000000000..d5c406293e --- /dev/null +++ b/src/Avalonia.Build.Tasks/SpanCompat.cs @@ -0,0 +1,73 @@ +namespace System +{ + // This is a hack to enable our span code to work inside MSBuild task without referencing System.Memory + struct ReadOnlySpan + { + private string _s; + private int _start; + private int _length; + public int Length => _length; + + public ReadOnlySpan(string s) : this(s, 0, s.Length) + { + + } + public ReadOnlySpan(string s, int start, int len) + { + _s = s; + _length = len; + _start = start; + if (_start > s.Length) + _length = 0; + else if (_start + _length > s.Length) + _length = s.Length - _start; + } + + public char this[int c] => _s[_start + c]; + + public bool IsEmpty => _length == 0; + + public ReadOnlySpan Slice(int start, int len) + { + return new ReadOnlySpan(_s, _start + start, len); + } + + public static ReadOnlySpan Empty => default; + + public ReadOnlySpan Slice(int start) + { + return new ReadOnlySpan(_s, _start + start, _length - start); + } + + public bool SequenceEqual(ReadOnlySpan other) + { + if (_length != other.Length) + return false; + for(var c=0; c<_length;c++) + if (this[c] != other[c]) + return false; + return true; + } + + public ReadOnlySpan TrimStart() + { + int start = 0; + for (; start < Length; start++) + { + if (!char.IsWhiteSpace(this[start])) + { + break; + } + } + return Slice(start); + } + + public override string ToString() => _length == 0 ? string.Empty : _s.Substring(_start, _length); + } + + static class SpanCompatExtensions + { + public static ReadOnlySpan AsSpan(this string s) => new ReadOnlySpan(s); + } + +} diff --git a/src/Avalonia.Build.Tasks/XamlCompilerTaskExecutor.Helpers.cs b/src/Avalonia.Build.Tasks/XamlCompilerTaskExecutor.Helpers.cs new file mode 100644 index 0000000000..f94f10f792 --- /dev/null +++ b/src/Avalonia.Build.Tasks/XamlCompilerTaskExecutor.Helpers.cs @@ -0,0 +1,181 @@ +using System.Collections.Generic; +using System.IO; +using System.Linq; +using Avalonia.Utilities; +using Mono.Cecil; +using Mono.Cecil.Cil; +using Mono.Collections.Generic; +using XamlIl.TypeSystem; + +namespace Avalonia.Build.Tasks +{ + public static partial class XamlCompilerTaskExecutor + { + interface IResource : IFileSource + { + string Uri { get; } + string Name { get; } + void Remove(); + + } + + interface IResourceGroup + { + string Name { get; } + IEnumerable Resources { get; } + } + + class EmbeddedResources : IResourceGroup + { + private readonly AssemblyDefinition _asm; + public string Name => "EmbeddedResource"; + + public IEnumerable Resources => _asm.MainModule.Resources.OfType() + .Select(r => new WrappedResource(_asm, r)).ToList(); + + public EmbeddedResources(AssemblyDefinition asm) + { + _asm = asm; + } + class WrappedResource : IResource + { + private readonly AssemblyDefinition _asm; + private readonly EmbeddedResource _res; + + public WrappedResource(AssemblyDefinition asm, EmbeddedResource res) + { + _asm = asm; + _res = res; + } + + public string Uri => $"resm:{Name}?assembly={_asm.Name.Name}"; + public string Name => _res.Name; + public string FilePath => Name; + public byte[] FileContents => _res.GetResourceData(); + + public void Remove() => _asm.MainModule.Resources.Remove(_res); + } + } + + class AvaloniaResources : IResourceGroup + { + private readonly AssemblyDefinition _asm; + Dictionary _resources = new Dictionary(); + private EmbeddedResource _embedded; + public AvaloniaResources(AssemblyDefinition asm, string projectDir) + { + _asm = asm; + _embedded = ((EmbeddedResource)asm.MainModule.Resources.FirstOrDefault(r => + r.ResourceType == ResourceType.Embedded && r.Name == "!AvaloniaResources")); + if (_embedded == null) + return; + using (var stream = _embedded.GetResourceStream()) + { + var br = new BinaryReader(stream); + var index = AvaloniaResourcesIndexReaderWriter.Read(new MemoryStream(br.ReadBytes(br.ReadInt32()))); + var baseOffset = stream.Position; + foreach (var e in index) + { + stream.Position = e.Offset + baseOffset; + _resources[e.Path] = new AvaloniaResource(this, projectDir, e.Path, br.ReadBytes(e.Size)); + } + } + } + + public void Save() + { + if (_embedded != null) + { + _asm.MainModule.Resources.Remove(_embedded); + _embedded = null; + } + + if (_resources.Count == 0) + return; + + _embedded = new EmbeddedResource("!AvaloniaResources", ManifestResourceAttributes.Public, + AvaloniaResourcesIndexReaderWriter.Create(_resources.ToDictionary(x => x.Key, + x => x.Value.FileContents))); + _asm.MainModule.Resources.Add(_embedded); + } + + public string Name => "AvaloniaResources"; + public IEnumerable Resources => _resources.Values.ToList(); + + class AvaloniaResource : IResource + { + private readonly AvaloniaResources _grp; + private readonly byte[] _data; + + public AvaloniaResource(AvaloniaResources grp, + string projectDir, + string name, byte[] data) + { + _grp = grp; + _data = data; + Name = name; + FilePath = Path.Combine(projectDir, name.TrimStart('/')); + Uri = $"avares://{grp._asm.Name.Name}/{name.TrimStart('/')}"; + } + public string Uri { get; } + public string Name { get; } + public string FilePath { get; } + public byte[] FileContents => _data; + + public void Remove() => _grp._resources.Remove(Name); + } + } + + static void CopyDebugDocument(MethodDefinition method, MethodDefinition copyFrom) + { + if (!copyFrom.DebugInformation.HasSequencePoints) + return; + var dbg = method.DebugInformation; + + dbg.Scope = new ScopeDebugInformation(method.Body.Instructions.First(), method.Body.Instructions.First()) + { + End = new InstructionOffset(), + Import = new ImportDebugInformation() + }; + dbg.SequencePoints.Add(new SequencePoint(method.Body.Instructions.First(), + copyFrom.DebugInformation.SequencePoints.First().Document) + { + StartLine = 0xfeefee, + EndLine = 0xfeefee + }); + + } + + + private static bool MatchThisCall(Collection instructions, int idx) + { + var i = instructions[idx]; + // A "normal" way of passing `this` to a static method: + + // ldarg.0 + // call void [Avalonia.Markup.Xaml]Avalonia.Markup.Xaml.AvaloniaXamlLoader::Load(object) + + if (i.OpCode == OpCodes.Ldarg_0 || (i.OpCode == OpCodes.Ldarg && i.Operand?.Equals(0) == true)) + return true; + + /* F# way of using `this` in constructor emits a monstrosity like this: + IL_01c7: ldarg.0 + IL_01c8: ldfld class [FSharp.Core]Microsoft.FSharp.Core.FSharpRef`1 FVim.Cursor::this + IL_01cd: call instance !0 class [FSharp.Core]Microsoft.FSharp.Core.FSharpRef`1::get_contents() + IL_01d2: call !!0 [FSharp.Core]Microsoft.FSharp.Core.LanguagePrimitives/IntrinsicFunctions::CheckThis(!!0) + IL_01d7: call void [Avalonia.Markup.Xaml]Avalonia.Markup.Xaml.AvaloniaXamlLoader::Load(object) + + We check for the previous call to be Microsoft.FSharp.Core.LanguagePrimitives/IntrinsicFunctions::CheckThis + since it actually returns `this` + */ + if (i.OpCode == OpCodes.Call + && i.Operand is GenericInstanceMethod gim + && gim.Name == "CheckThis" + && gim.DeclaringType.FullName == "Microsoft.FSharp.Core.LanguagePrimitives/IntrinsicFunctions") + return true; + + return false; + } + } + +} diff --git a/src/Avalonia.Build.Tasks/XamlCompilerTaskExecutor.cs b/src/Avalonia.Build.Tasks/XamlCompilerTaskExecutor.cs new file mode 100644 index 0000000000..0dd52c9b48 --- /dev/null +++ b/src/Avalonia.Build.Tasks/XamlCompilerTaskExecutor.cs @@ -0,0 +1,360 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Reflection; +using System.Text; +using Avalonia.Markup.Xaml.XamlIl.CompilerExtensions; +using Microsoft.Build.Framework; +using Mono.Cecil; +using XamlIl.TypeSystem; +using Avalonia.Utilities; +using Mono.Cecil.Cil; +using Mono.Cecil.Rocks; +using XamlIl; +using XamlIl.Ast; +using XamlIl.Parsers; +using XamlIl.Transform; +using FieldAttributes = Mono.Cecil.FieldAttributes; +using MethodAttributes = Mono.Cecil.MethodAttributes; +using TypeAttributes = Mono.Cecil.TypeAttributes; + +namespace Avalonia.Build.Tasks +{ + + public static partial class XamlCompilerTaskExecutor + { + static bool CheckXamlName(IResource r) => r.Name.ToLowerInvariant().EndsWith(".xaml") + || r.Name.ToLowerInvariant().EndsWith(".paml"); + + public class CompileResult + { + public bool Success { get; set; } + public bool WrittenFile { get; } + + public CompileResult(bool success, bool writtenFile = false) + { + Success = success; + WrittenFile = writtenFile; + } + } + + public static CompileResult Compile(IBuildEngine engine, string input, string[] references, string projectDirectory, + string output) + { + var typeSystem = new CecilTypeSystem(references.Concat(new[] {input}), input); + var asm = typeSystem.TargetAssemblyDefinition; + var emres = new EmbeddedResources(asm); + var avares = new AvaloniaResources(asm, projectDirectory); + if (avares.Resources.Count(CheckXamlName) == 0 && emres.Resources.Count(CheckXamlName) == 0) + // Nothing to do + return new CompileResult(true); + + var xamlLanguage = AvaloniaXamlIlLanguage.Configure(typeSystem); + var compilerConfig = new XamlIlTransformerConfiguration(typeSystem, + typeSystem.TargetAssembly, + xamlLanguage, + XamlIlXmlnsMappings.Resolve(typeSystem, xamlLanguage), + AvaloniaXamlIlLanguage.CustomValueConverter); + + + var contextDef = new TypeDefinition("CompiledAvaloniaXaml", "XamlIlContext", + TypeAttributes.Class, asm.MainModule.TypeSystem.Object); + asm.MainModule.Types.Add(contextDef); + + var contextClass = XamlIlContextDefinition.GenerateContextClass(typeSystem.CreateTypeBuilder(contextDef), typeSystem, + xamlLanguage); + + var compiler = new AvaloniaXamlIlCompiler(compilerConfig, contextClass); + + var editorBrowsableAttribute = typeSystem + .GetTypeReference(typeSystem.FindType("System.ComponentModel.EditorBrowsableAttribute")) + .Resolve(); + var editorBrowsableCtor = + asm.MainModule.ImportReference(editorBrowsableAttribute.GetConstructors() + .First(c => c.Parameters.Count == 1)); + + var runtimeHelpers = typeSystem.GetType("Avalonia.Markup.Xaml.XamlIl.Runtime.XamlIlRuntimeHelpers"); + var createRootServiceProviderMethod = asm.MainModule.ImportReference( + typeSystem.GetTypeReference(runtimeHelpers).Resolve().Methods + .First(x => x.Name == "CreateRootServiceProviderV2")); + + var loaderDispatcherDef = new TypeDefinition("CompiledAvaloniaXaml", "!XamlLoader", + TypeAttributes.Class, asm.MainModule.TypeSystem.Object); + + + loaderDispatcherDef.CustomAttributes.Add(new CustomAttribute(editorBrowsableCtor) + { + ConstructorArguments = {new CustomAttributeArgument(editorBrowsableCtor.Parameters[0].ParameterType, 1)} + }); + + + var loaderDispatcherMethod = new MethodDefinition("TryLoad", + MethodAttributes.Static | MethodAttributes.Public, + asm.MainModule.TypeSystem.Object) + { + Parameters = {new ParameterDefinition(asm.MainModule.TypeSystem.String)} + }; + loaderDispatcherDef.Methods.Add(loaderDispatcherMethod); + asm.MainModule.Types.Add(loaderDispatcherDef); + + var stringEquals = asm.MainModule.ImportReference(asm.MainModule.TypeSystem.String.Resolve().Methods.First( + m => + m.IsStatic && m.Name == "Equals" && m.Parameters.Count == 2 && + m.ReturnType.FullName == "System.Boolean" + && m.Parameters[0].ParameterType.FullName == "System.String" + && m.Parameters[1].ParameterType.FullName == "System.String")); + + bool CompileGroup(IResourceGroup group) + { + var typeDef = new TypeDefinition("CompiledAvaloniaXaml", "!"+ group.Name, + TypeAttributes.Class, asm.MainModule.TypeSystem.Object); + + typeDef.CustomAttributes.Add(new CustomAttribute(editorBrowsableCtor) + { + ConstructorArguments = {new CustomAttributeArgument(editorBrowsableCtor.Parameters[0].ParameterType, 1)} + }); + asm.MainModule.Types.Add(typeDef); + var builder = typeSystem.CreateTypeBuilder(typeDef); + + foreach (var res in group.Resources.Where(CheckXamlName)) + { + try + { + // StreamReader is needed here to handle BOM + var xaml = new StreamReader(new MemoryStream(res.FileContents)).ReadToEnd(); + var parsed = XDocumentXamlIlParser.Parse(xaml); + + var initialRoot = (XamlIlAstObjectNode)parsed.Root; + + + var precompileDirective = initialRoot.Children.OfType() + .FirstOrDefault(d => d.Namespace == XamlNamespaces.Xaml2006 && d.Name == "Precompile"); + if (precompileDirective != null) + { + var precompileText = (precompileDirective.Values[0] as XamlIlAstTextNode)?.Text.Trim() + .ToLowerInvariant(); + if (precompileText == "false") + continue; + if (precompileText != "true") + throw new XamlIlParseException("Invalid value for x:Precompile", precompileDirective); + } + + var classDirective = initialRoot.Children.OfType() + .FirstOrDefault(d => d.Namespace == XamlNamespaces.Xaml2006 && d.Name == "Class"); + IXamlIlType classType = null; + if (classDirective != null) + { + if (classDirective.Values.Count != 1 || !(classDirective.Values[0] is XamlIlAstTextNode tn)) + throw new XamlIlParseException("x:Class should have a string value", classDirective); + classType = typeSystem.TargetAssembly.FindType(tn.Text); + if (classType == null) + throw new XamlIlParseException($"Unable to find type `{tn.Text}`", classDirective); + compiler.OverrideRootType(parsed, + new XamlIlAstClrTypeReference(classDirective, classType, false)); + initialRoot.Children.Remove(classDirective); + } + + + compiler.Transform(parsed); + var populateName = classType == null ? "Populate:" + res.Name : "!XamlIlPopulate"; + var buildName = classType == null ? "Build:" + res.Name : null; + + var classTypeDefinition = + classType == null ? null : typeSystem.GetTypeReference(classType).Resolve(); + + + var populateBuilder = classTypeDefinition == null ? + builder : + typeSystem.CreateTypeBuilder(classTypeDefinition); + compiler.Compile(parsed, contextClass, + compiler.DefinePopulateMethod(populateBuilder, parsed, populateName, + classTypeDefinition == null), + buildName == null ? null : compiler.DefineBuildMethod(builder, parsed, buildName, true), + builder.DefineSubType(compilerConfig.WellKnownTypes.Object, "NamespaceInfo:" + res.Name, + true), + (closureName, closureBaseType) => + populateBuilder.DefineSubType(closureBaseType, closureName, false), + res.Uri, res + ); + + + if (classTypeDefinition != null) + { + var compiledPopulateMethod = typeSystem.GetTypeReference(populateBuilder).Resolve() + .Methods.First(m => m.Name == populateName); + + var designLoaderFieldType = typeSystem + .GetType("System.Action`1") + .MakeGenericType(typeSystem.GetType("System.Object")); + + var designLoaderFieldTypeReference = (GenericInstanceType)typeSystem.GetTypeReference(designLoaderFieldType); + designLoaderFieldTypeReference.GenericArguments[0] = + asm.MainModule.ImportReference(designLoaderFieldTypeReference.GenericArguments[0]); + designLoaderFieldTypeReference = (GenericInstanceType) + asm.MainModule.ImportReference(designLoaderFieldTypeReference); + + var designLoaderLoad = + typeSystem.GetMethodReference( + designLoaderFieldType.Methods.First(m => m.Name == "Invoke")); + designLoaderLoad = + asm.MainModule.ImportReference(designLoaderLoad); + designLoaderLoad.DeclaringType = designLoaderFieldTypeReference; + + var designLoaderField = new FieldDefinition("!XamlIlPopulateOverride", + FieldAttributes.Static | FieldAttributes.Private, designLoaderFieldTypeReference); + classTypeDefinition.Fields.Add(designLoaderField); + + const string TrampolineName = "!XamlIlPopulateTrampoline"; + var trampoline = new MethodDefinition(TrampolineName, + MethodAttributes.Static | MethodAttributes.Private, asm.MainModule.TypeSystem.Void); + trampoline.Parameters.Add(new ParameterDefinition(classTypeDefinition)); + classTypeDefinition.Methods.Add(trampoline); + + var regularStart = Instruction.Create(OpCodes.Call, createRootServiceProviderMethod); + + trampoline.Body.Instructions.Add(Instruction.Create(OpCodes.Ldsfld, designLoaderField)); + trampoline.Body.Instructions.Add(Instruction.Create(OpCodes.Brfalse, regularStart)); + trampoline.Body.Instructions.Add(Instruction.Create(OpCodes.Ldsfld, designLoaderField)); + trampoline.Body.Instructions.Add(Instruction.Create(OpCodes.Ldarg_0)); + trampoline.Body.Instructions.Add(Instruction.Create(OpCodes.Call, designLoaderLoad)); + trampoline.Body.Instructions.Add(Instruction.Create(OpCodes.Ret)); + + trampoline.Body.Instructions.Add(regularStart); + trampoline.Body.Instructions.Add(Instruction.Create(OpCodes.Ldarg_0)); + trampoline.Body.Instructions.Add(Instruction.Create(OpCodes.Call, compiledPopulateMethod)); + trampoline.Body.Instructions.Add(Instruction.Create(OpCodes.Ret)); + CopyDebugDocument(trampoline, compiledPopulateMethod); + + var foundXamlLoader = false; + // Find AvaloniaXamlLoader.Load(this) and replace it with !XamlIlPopulateTrampoline(this) + foreach (var method in classTypeDefinition.Methods + .Where(m => !m.Attributes.HasFlag(MethodAttributes.Static))) + { + var i = method.Body.Instructions; + for (var c = 1; c < i.Count; c++) + { + if (i[c].OpCode == OpCodes.Call) + { + var op = i[c].Operand as MethodReference; + + // TODO: Throw an error + // This usually happens when same XAML resource was added twice for some weird reason + // We currently support it for dual-named default theme resource + if (op != null + && op.Name == TrampolineName) + { + foundXamlLoader = true; + break; + } + if (op != null + && op.Name == "Load" + && op.Parameters.Count == 1 + && op.Parameters[0].ParameterType.FullName == "System.Object" + && op.DeclaringType.FullName == "Avalonia.Markup.Xaml.AvaloniaXamlLoader") + { + if (MatchThisCall(i, c - 1)) + { + i[c].Operand = trampoline; + foundXamlLoader = true; + } + } + } + } + } + + if (!foundXamlLoader) + { + var ctors = classTypeDefinition.GetConstructors() + .Where(c => !c.IsStatic).ToList(); + // We can inject xaml loader into default constructor + if (ctors.Count == 1 && ctors[0].Body.Instructions.Count(o=>o.OpCode != OpCodes.Nop) == 3) + { + var i = ctors[0].Body.Instructions; + var retIdx = i.IndexOf(i.Last(x => x.OpCode == OpCodes.Ret)); + i.Insert(retIdx, Instruction.Create(OpCodes.Call, trampoline)); + i.Insert(retIdx, Instruction.Create(OpCodes.Ldarg_0)); + } + else + { + throw new InvalidProgramException( + $"No call to AvaloniaXamlLoader.Load(this) call found anywhere in the type {classType.FullName} and type seems to have custom constructors."); + } + } + + } + + if (buildName != null || classTypeDefinition != null) + { + var compiledBuildMethod = buildName == null ? + null : + typeSystem.GetTypeReference(builder).Resolve() + .Methods.First(m => m.Name == buildName); + var parameterlessConstructor = compiledBuildMethod != null ? + null : + classTypeDefinition.GetConstructors().FirstOrDefault(c => + c.IsPublic && !c.IsStatic && !c.HasParameters); + + if (compiledBuildMethod != null || parameterlessConstructor != null) + { + var i = loaderDispatcherMethod.Body.Instructions; + var nop = Instruction.Create(OpCodes.Nop); + i.Add(Instruction.Create(OpCodes.Ldarg_0)); + i.Add(Instruction.Create(OpCodes.Ldstr, res.Uri)); + i.Add(Instruction.Create(OpCodes.Call, stringEquals)); + i.Add(Instruction.Create(OpCodes.Brfalse, nop)); + if (parameterlessConstructor != null) + i.Add(Instruction.Create(OpCodes.Newobj, parameterlessConstructor)); + else + { + i.Add(Instruction.Create(OpCodes.Call, createRootServiceProviderMethod)); + i.Add(Instruction.Create(OpCodes.Call, compiledBuildMethod)); + } + + i.Add(Instruction.Create(OpCodes.Ret)); + i.Add(nop); + } + } + + } + catch (Exception e) + { + int lineNumber = 0, linePosition = 0; + if (e is XamlIlParseException xe) + { + lineNumber = xe.LineNumber; + linePosition = xe.LinePosition; + } + engine.LogErrorEvent(new BuildErrorEventArgs("Avalonia", "XAMLIL", res.FilePath, + lineNumber, linePosition, lineNumber, linePosition, + e.Message, "", "Avalonia")); + return false; + } + res.Remove(); + } + return true; + } + + if (emres.Resources.Count(CheckXamlName) != 0) + if (!CompileGroup(emres)) + return new CompileResult(false); + if (avares.Resources.Count(CheckXamlName) != 0) + { + if (!CompileGroup(avares)) + return new CompileResult(false); + avares.Save(); + } + + loaderDispatcherMethod.Body.Instructions.Add(Instruction.Create(OpCodes.Ldnull)); + loaderDispatcherMethod.Body.Instructions.Add(Instruction.Create(OpCodes.Ret)); + + asm.Write(output, new WriterParameters + { + WriteSymbols = asm.MainModule.HasSymbols + }); + + return new CompileResult(true, true); + } + + } +} diff --git a/src/Avalonia.Controls.DataGrid/AppBuilderExtensions.cs b/src/Avalonia.Controls.DataGrid/AppBuilderExtensions.cs deleted file mode 100644 index bdb9bf182c..0000000000 --- a/src/Avalonia.Controls.DataGrid/AppBuilderExtensions.cs +++ /dev/null @@ -1,20 +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.Threading; - -namespace Avalonia -{ - public static class AppBuilderExtensions - { - public static TAppBuilder UseDataGrid(this TAppBuilder builder) - where TAppBuilder : AppBuilderBase, new() - { - // Portable.Xaml doesn't correctly load referenced assemblies and so doesn't - // find `DataGrid` when loading XAML. Call this method from AppBuilder as a - // temporary workaround until we fix XAML. - return builder; - } - } -} diff --git a/src/Avalonia.Controls.DataGrid/Avalonia.Controls.DataGrid.csproj b/src/Avalonia.Controls.DataGrid/Avalonia.Controls.DataGrid.csproj index 889ed84993..27853a1540 100644 --- a/src/Avalonia.Controls.DataGrid/Avalonia.Controls.DataGrid.csproj +++ b/src/Avalonia.Controls.DataGrid/Avalonia.Controls.DataGrid.csproj @@ -1,6 +1,7 @@ - + netstandard2.0 + Avalonia.Controls.DataGrid @@ -11,10 +12,14 @@ + + + + diff --git a/src/Avalonia.Controls.DataGrid/Collections/DataGridCollectionView.cs b/src/Avalonia.Controls.DataGrid/Collections/DataGridCollectionView.cs index 4b4203ba40..92734b128d 100644 --- a/src/Avalonia.Controls.DataGrid/Collections/DataGridCollectionView.cs +++ b/src/Avalonia.Controls.DataGrid/Collections/DataGridCollectionView.cs @@ -927,7 +927,7 @@ namespace Avalonia.Collections /// ///

/// Clear a sort criteria by assigning SortDescription.Empty to this property. - /// One or more sort criteria in form of + /// One or more sort criteria in form of /// can be used, each specifying a property and direction to sort by. ///

///
@@ -4312,4 +4312,4 @@ namespace Avalonia.Collections } } } -} \ No newline at end of file +} diff --git a/src/Avalonia.Controls.DataGrid/DataGridBoundColumn.cs b/src/Avalonia.Controls.DataGrid/DataGridBoundColumn.cs index d213285aff..09c3d07a41 100644 --- a/src/Avalonia.Controls.DataGrid/DataGridBoundColumn.cs +++ b/src/Avalonia.Controls.DataGrid/DataGridBoundColumn.cs @@ -26,6 +26,7 @@ namespace Avalonia.Controls /// Gets or sets the binding that associates the column with a property in the data source. /// //TODO Binding + [AssignBinding] public virtual IBinding Binding { get diff --git a/src/Avalonia.Controls.DataGrid/DataGridRow.cs b/src/Avalonia.Controls.DataGrid/DataGridRow.cs index 8bc76543ba..04a1575486 100644 --- a/src/Avalonia.Controls.DataGrid/DataGridRow.cs +++ b/src/Avalonia.Controls.DataGrid/DataGridRow.cs @@ -881,10 +881,10 @@ namespace Avalonia.Controls && (double.IsNaN(_detailsContent.Height)) && (AreDetailsVisible) && (!double.IsNaN(_detailsDesiredHeight)) - && !DoubleUtil.AreClose(_detailsContent.Bounds.Height, _detailsDesiredHeight) + && !DoubleUtil.AreClose(_detailsContent.Bounds.Inflate(_detailsContent.Margin).Height, _detailsDesiredHeight) && Slot != -1) { - _detailsDesiredHeight = _detailsContent.Bounds.Height; + _detailsDesiredHeight = _detailsContent.Bounds.Inflate(_detailsContent.Margin).Height; if (true) { @@ -943,6 +943,16 @@ namespace Avalonia.Controls _previousDetailsHeight = newValue.Height; } } + private void DetailsContent_BoundsChanged(Rect newValue) + { + if(_detailsContent != null) + DetailsContent_SizeChanged(newValue.Inflate(_detailsContent.Margin)); + } + private void DetailsContent_MarginChanged(Thickness newValue) + { + if (_detailsContent != null) + DetailsContent_SizeChanged(_detailsContent.Bounds.Inflate(newValue)); + } //TODO Animation // Sets AreDetailsVisible on the row and animates if necessary @@ -997,7 +1007,7 @@ namespace Avalonia.Controls } } } - + internal void ApplyDetailsTemplate(bool initializeDetailsPreferredHeight) { if (_detailsElement != null && AreDetailsVisible) @@ -1023,8 +1033,11 @@ namespace Avalonia.Controls if (_detailsContent != null) { _detailsContentSizeSubscription = - _detailsContent.GetObservable(BoundsProperty) - .Subscribe(DetailsContent_SizeChanged); + System.Reactive.Disposables.StableCompositeDisposable.Create( + _detailsContent.GetObservable(BoundsProperty) + .Subscribe(DetailsContent_BoundsChanged), + _detailsContent.GetObservable(MarginProperty) + .Subscribe(DetailsContent_MarginChanged)); _detailsElement.Children.Add(_detailsContent); } } @@ -1053,4 +1066,4 @@ namespace Avalonia.Controls //TODO Styles -} \ No newline at end of file +} diff --git a/src/Avalonia.Controls.DataGrid/Properties/AssemblyInfo.cs b/src/Avalonia.Controls.DataGrid/Properties/AssemblyInfo.cs index d5ad4c75f8..f15442addf 100644 --- a/src/Avalonia.Controls.DataGrid/Properties/AssemblyInfo.cs +++ b/src/Avalonia.Controls.DataGrid/Properties/AssemblyInfo.cs @@ -8,7 +8,6 @@ using Avalonia.Metadata; [assembly: InternalsVisibleTo("Avalonia.Controls.UnitTests")] [assembly: InternalsVisibleTo("Avalonia.DesignerSupport")] -[assembly: XmlnsDefinition("https://github.com/avaloniaui", "Avalonia")] [assembly: XmlnsDefinition("https://github.com/avaloniaui", "Avalonia.Controls")] -[assembly: XmlnsDefinition("https://github.com/avaloniaui", "Avalonia.Controls.Primitives")] [assembly: XmlnsDefinition("https://github.com/avaloniaui", "Avalonia.Controls.Collections")] +[assembly: XmlnsDefinition("https://github.com/avaloniaui", "Avalonia.Controls.Primitives")] diff --git a/src/Avalonia.Controls.DataGrid/Themes/Default.xaml b/src/Avalonia.Controls.DataGrid/Themes/Default.xaml index 2889a5c77c..eaa267ba66 100644 --- a/src/Avalonia.Controls.DataGrid/Themes/Default.xaml +++ b/src/Avalonia.Controls.DataGrid/Themes/Default.xaml @@ -195,7 +195,6 @@ - @@ -230,4 +229,4 @@ - \ No newline at end of file + diff --git a/src/Avalonia.Controls/AppBuilderBase.cs b/src/Avalonia.Controls/AppBuilderBase.cs index a26b7d7405..59ff35be76 100644 --- a/src/Avalonia.Controls/AppBuilderBase.cs +++ b/src/Avalonia.Controls/AppBuilderBase.cs @@ -4,6 +4,7 @@ using System; using System.Reflection; using System.Linq; +using Avalonia.Controls.ApplicationLifetimes; using Avalonia.Platform; namespace Avalonia.Controls @@ -57,10 +58,6 @@ namespace Avalonia.Controls /// public Action AfterSetupCallback { get; private set; } = builder => { }; - /// - /// Gets or sets a method to call before Start is called on the . - /// - public Action BeforeStartCallback { get; private set; } = builder => { }; protected AppBuilderBase(IRuntimePlatform platform, Action platformServices) { @@ -95,17 +92,6 @@ namespace Avalonia.Controls protected TAppBuilder Self => (TAppBuilder)this; - /// - /// Registers a callback to call before Start is called on the . - /// - /// The callback. - /// An instance. - public TAppBuilder BeforeStarting(Action callback) - { - BeforeStartCallback = (Action)Delegate.Combine(BeforeStartCallback, callback); - return Self; - } - public TAppBuilder AfterSetup(Action callback) { AfterSetupCallback = (Action)Delegate.Combine(AfterSetupCallback, callback); @@ -117,44 +103,40 @@ namespace Avalonia.Controls /// /// The window type. /// A delegate that will be called to create a data context for the window (optional). + [Obsolete("Use either lifetimes or AppMain overload. See see https://github.com/AvaloniaUI/Avalonia/wiki/Application-lifetimes for details")] public void Start(Func dataContextProvider = null) where TMainWindow : Window, new() { - Setup(); - BeforeStartCallback(Self); - - var window = new TMainWindow(); - if (dataContextProvider != null) - window.DataContext = dataContextProvider(); - Instance.Run(window); + AfterSetup(builder => + { + var window = new TMainWindow(); + if (dataContextProvider != null) + window.DataContext = dataContextProvider(); + ((IClassicDesktopStyleApplicationLifetime)builder.Instance.ApplicationLifetime) + .MainWindow = window; + }); + + // Copy-pasted because we can't call extension methods due to generic constraints + var lifetime = new ClassicDesktopStyleApplicationLifetime(Instance) {ShutdownMode = ShutdownMode.OnMainWindowClose}; + Instance.ApplicationLifetime = lifetime; + SetupWithoutStarting(); + lifetime.Start(Array.Empty()); } - /// - /// Starts the application with the provided instance of . - /// - /// The window type. - /// Instance of type TMainWindow to use when starting the app - /// A delegate that will be called to create a data context for the window (optional). - public void Start(TMainWindow mainWindow, Func dataContextProvider = null) - where TMainWindow : Window - { - Setup(); - BeforeStartCallback(Self); + public delegate void AppMainDelegate(Application app, string[] args); - if (dataContextProvider != null) - mainWindow.DataContext = dataContextProvider(); - Instance.Run(mainWindow); + [Obsolete("Use either lifetimes or AppMain overload. See see https://github.com/AvaloniaUI/Avalonia/wiki/Application-lifetimes for details", true)] + public void Start() + { + throw new NotSupportedException(); } - public delegate void AppMainDelegate(Application app, string[] args); - public void Start(AppMainDelegate main, string[] args) { Setup(); - BeforeStartCallback(Self); main(Instance, args); } - + /// /// Sets up the platform-specific services for the application, but does not run it. /// @@ -217,17 +199,6 @@ namespace Avalonia.Controls public TAppBuilder UseAvaloniaModules() => AfterSetup(builder => SetupAvaloniaModules()); - /// - /// Sets the shutdown mode of the application. - /// - /// The shutdown mode. - /// - public TAppBuilder SetExitMode(ExitMode exitMode) - { - Instance.ExitMode = exitMode; - return Self; - } - protected virtual bool CheckSetup => true; private void SetupAvaloniaModules() @@ -306,6 +277,7 @@ namespace Avalonia.Controls Instance.RegisterServices(); Instance.Initialize(); AfterSetupCallback(Self); + Instance.OnFrameworkInitializationCompleted(); } } } diff --git a/src/Avalonia.Controls/Application.cs b/src/Avalonia.Controls/Application.cs index 1d4e4cbeaa..382106de65 100644 --- a/src/Avalonia.Controls/Application.cs +++ b/src/Avalonia.Controls/Application.cs @@ -6,6 +6,7 @@ using System.Reactive.Concurrency; using System.Threading; using Avalonia.Animation; using Avalonia.Controls; +using Avalonia.Controls.ApplicationLifetimes; using Avalonia.Controls.Templates; using Avalonia.Input; using Avalonia.Input.Platform; @@ -31,7 +32,7 @@ namespace Avalonia /// method. /// - Tracks the lifetime of the application. /// - public class Application : IApplicationLifecycle, IGlobalDataTemplates, IGlobalStyles, IStyleRoot, IResourceNode + public class Application : IGlobalDataTemplates, IGlobalStyles, IStyleRoot, IResourceNode { /// /// The application-global data templates. @@ -44,18 +45,6 @@ namespace Avalonia private Styles _styles; private IResourceDictionary _resources; - private CancellationTokenSource _mainLoopCancellationTokenSource; - - /// - /// Initializes a new instance of the class. - /// - public Application() - { - Windows = new WindowCollection(this); - - OnExit += OnExiting; - } - /// public event EventHandler ResourcesChanged; @@ -162,160 +151,29 @@ namespace Avalonia /// IResourceNode IResourceNode.ResourceParent => null; - - /// - /// Gets or sets the . This property indicates whether the application exits explicitly or implicitly. - /// If is set to OnExplicitExit the application is only closes if Exit is called. - /// The default is OnLastWindowClose - /// - /// - /// The shutdown mode. - /// - public ExitMode ExitMode { get; set; } - - /// - /// Gets or sets the main window of the application. - /// - /// - /// The main window. - /// - public Window MainWindow { get; set; } - - /// - /// Gets the open windows of the application. - /// - /// - /// The windows. - /// - public WindowCollection Windows { get; } - + /// - /// Gets or sets a value indicating whether this instance is existing. + /// Application lifetime, use it for things like setting the main window and exiting the app from code + /// Currently supported lifetimes are: + /// - + /// - + /// - /// - /// - /// true if this instance is existing; otherwise, false. - /// - internal bool IsExiting { get; set; } + public IApplicationLifetime ApplicationLifetime { get; set; } /// /// Initializes the application by loading XAML etc. /// - public virtual void Initialize() - { - } - - /// - /// Runs the application's main loop until the is closed. - /// - /// The closable to track - public void Run(ICloseable closable) - { - if (_mainLoopCancellationTokenSource != null) - { - throw new Exception("Run should only called once"); - } - - closable.Closed += (s, e) => Exit(); - - _mainLoopCancellationTokenSource = new CancellationTokenSource(); - - Dispatcher.UIThread.MainLoop(_mainLoopCancellationTokenSource.Token); - - // Make sure we call OnExit in case an error happened and Exit() wasn't called explicitly - if (!IsExiting) - { - OnExit?.Invoke(this, EventArgs.Empty); - } - } - - /// - /// Runs the application's main loop until some condition occurs that is specified by ExitMode. - /// - /// The main window - public void Run(Window mainWindow) - { - if (_mainLoopCancellationTokenSource != null) - { - throw new Exception("Run should only called once"); - } - - _mainLoopCancellationTokenSource = new CancellationTokenSource(); - - if (MainWindow == null) - { - if (mainWindow == null) - { - throw new ArgumentNullException(nameof(mainWindow)); - } - - if (!mainWindow.IsVisible) - { - mainWindow.Show(); - } - - MainWindow = mainWindow; - } - - Dispatcher.UIThread.MainLoop(_mainLoopCancellationTokenSource.Token); - - // Make sure we call OnExit in case an error happened and Exit() wasn't called explicitly - if (!IsExiting) - { - OnExit?.Invoke(this, EventArgs.Empty); - } - } - - /// - /// Runs the application's main loop until the is canceled. - /// - /// The token to track - public void Run(CancellationToken token) - { - Dispatcher.UIThread.MainLoop(token); - - // Make sure we call OnExit in case an error happened and Exit() wasn't called explicitly - if (!IsExiting) - { - OnExit?.Invoke(this, EventArgs.Empty); - } - } - - /// - /// Exits the application - /// - public void Exit() - { - IsExiting = true; - - Windows.Clear(); - - OnExit?.Invoke(this, EventArgs.Empty); - - _mainLoopCancellationTokenSource?.Cancel(); - } + public virtual void Initialize() { } /// - bool IResourceProvider.TryGetResource(string key, out object value) + bool IResourceProvider.TryGetResource(object key, out object value) { value = null; return (_resources?.TryGetResource(key, out value) ?? false) || Styles.TryGetResource(key, out value); } - /// - /// Sent when the application is exiting. - /// - public event EventHandler OnExit; - - /// - /// Called when the application is exiting. - /// - /// - /// - protected virtual void OnExiting(object sender, EventArgs e) - { - } - /// /// Register's the services needed by Avalonia. /// @@ -333,7 +191,6 @@ namespace Avalonia .Bind().ToConstant(InputManager) .Bind().ToTransient() .Bind().ToConstant(_styler) - .Bind().ToConstant(this) .Bind().ToConstant(AvaloniaScheduler.Instance) .Bind().ToConstant(DragDropDevice.Instance) .Bind().ToTransient(); @@ -344,6 +201,11 @@ namespace Avalonia .GetService()?.Add(clock); } + public virtual void OnFrameworkInitializationCompleted() + { + + } + private void ThisResourcesChanged(object sender, ResourcesChangedEventArgs e) { ResourcesChanged?.Invoke(this, e); diff --git a/src/Avalonia.Controls/ApplicationLifetimes/ClassicDesktopStyleApplicationLifetime.cs b/src/Avalonia.Controls/ApplicationLifetimes/ClassicDesktopStyleApplicationLifetime.cs new file mode 100644 index 0000000000..abca7a64ee --- /dev/null +++ b/src/Avalonia.Controls/ApplicationLifetimes/ClassicDesktopStyleApplicationLifetime.cs @@ -0,0 +1,133 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading; +using Avalonia.Controls; +using Avalonia.Controls.ApplicationLifetimes; +using Avalonia.Interactivity; + +namespace Avalonia.Controls.ApplicationLifetimes +{ + public class ClassicDesktopStyleApplicationLifetime : IClassicDesktopStyleApplicationLifetime, IDisposable + { + private readonly Application _app; + private int _exitCode; + private CancellationTokenSource _cts; + private bool _isShuttingDown; + private HashSet _windows = new HashSet(); + + private static ClassicDesktopStyleApplicationLifetime _activeLifetime; + static ClassicDesktopStyleApplicationLifetime() + { + Window.WindowOpenedEvent.AddClassHandler(typeof(Window), OnWindowOpened); + Window.WindowClosedEvent.AddClassHandler(typeof(Window), WindowClosedEvent); + } + + private static void WindowClosedEvent(object sender, RoutedEventArgs e) + { + _activeLifetime?._windows.Remove((Window)sender); + _activeLifetime?.HandleWindowClosed((Window)sender); + } + + private static void OnWindowOpened(object sender, RoutedEventArgs e) + { + _activeLifetime?._windows.Add((Window)sender); + } + + public ClassicDesktopStyleApplicationLifetime(Application app) + { + if (_activeLifetime != null) + throw new InvalidOperationException( + "Can not have multiple active ClassicDesktopStyleApplicationLifetime instances and the previously created one was not disposed"); + _app = app; + _activeLifetime = this; + } + + /// + public event EventHandler Startup; + /// + public event EventHandler Exit; + + /// + public ShutdownMode ShutdownMode { get; set; } + + /// + public Window MainWindow { get; set; } + + public IReadOnlyList Windows => _windows.ToList(); + + private void HandleWindowClosed(Window window) + { + if (window == null) + return; + + if (_isShuttingDown) + return; + + if (ShutdownMode == ShutdownMode.OnLastWindowClose && _windows.Count == 0) + Shutdown(); + else if (ShutdownMode == ShutdownMode.OnMainWindowClose && window == MainWindow) + Shutdown(); + } + + + + + public void Shutdown(int exitCode = 0) + { + if (_isShuttingDown) + throw new InvalidOperationException("Application is already shutting down."); + + _exitCode = exitCode; + _isShuttingDown = true; + + try + { + foreach (var w in Windows) + w.Close(); + var e = new ControlledApplicationLifetimeExitEventArgs(exitCode); + Exit?.Invoke(this, e); + _exitCode = e.ApplicationExitCode; + } + finally + { + _cts?.Cancel(); + _cts = null; + _isShuttingDown = false; + } + } + + + public int Start(string[] args) + { + Startup?.Invoke(this, new ControlledApplicationLifetimeStartupEventArgs(args)); + _cts = new CancellationTokenSource(); + MainWindow?.Show(); + _app.Run(_cts.Token); + Environment.ExitCode = _exitCode; + return _exitCode; + } + + public void Dispose() + { + if (_activeLifetime == this) + _activeLifetime = null; + } + } +} + +namespace Avalonia +{ + public static class ClassicDesktopStyleApplicationLifetimeExtensions + { + public static int StartWithClassicDesktopLifetime( + this T builder, string[] args, ShutdownMode shutdownMode = ShutdownMode.OnLastWindowClose) + where T : AppBuilderBase, new() + { + var lifetime = new ClassicDesktopStyleApplicationLifetime(builder.Instance) {ShutdownMode = shutdownMode}; + builder.Instance.ApplicationLifetime = lifetime; + builder.SetupWithoutStarting(); + return lifetime.Start(args); + } + } +} diff --git a/src/Avalonia.Controls/ApplicationLifetimes/ControlledApplicationLifetimeExitEventArgs.cs b/src/Avalonia.Controls/ApplicationLifetimes/ControlledApplicationLifetimeExitEventArgs.cs new file mode 100644 index 0000000000..2963c019cc --- /dev/null +++ b/src/Avalonia.Controls/ApplicationLifetimes/ControlledApplicationLifetimeExitEventArgs.cs @@ -0,0 +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 System; + +namespace Avalonia.Controls.ApplicationLifetimes +{ + /// + /// Contains the arguments for the event. + /// + public class ControlledApplicationLifetimeExitEventArgs : EventArgs + { + public ControlledApplicationLifetimeExitEventArgs(int applicationExitCode) + { + ApplicationExitCode = applicationExitCode; + } + + /// + /// Gets or sets the exit code that an application returns to the operating system when the application exits. + /// + public int ApplicationExitCode { get; set; } + } +} diff --git a/src/Avalonia.Controls/ApplicationLifetimes/IApplicationLifetime.cs b/src/Avalonia.Controls/ApplicationLifetimes/IApplicationLifetime.cs new file mode 100644 index 0000000000..9860d0cb38 --- /dev/null +++ b/src/Avalonia.Controls/ApplicationLifetimes/IApplicationLifetime.cs @@ -0,0 +1,7 @@ +namespace Avalonia.Controls.ApplicationLifetimes +{ + public interface IApplicationLifetime + { + + } +} diff --git a/src/Avalonia.Controls/ApplicationLifetimes/IClassicDesktopStyleApplicationLifetime.cs b/src/Avalonia.Controls/ApplicationLifetimes/IClassicDesktopStyleApplicationLifetime.cs new file mode 100644 index 0000000000..a1006d907b --- /dev/null +++ b/src/Avalonia.Controls/ApplicationLifetimes/IClassicDesktopStyleApplicationLifetime.cs @@ -0,0 +1,31 @@ +using System; +using System.Collections.Generic; + +namespace Avalonia.Controls.ApplicationLifetimes +{ + /// + /// Controls application lifetime in classic desktop style + /// + public interface IClassicDesktopStyleApplicationLifetime : IControlledApplicationLifetime + { + /// + /// Gets or sets the . This property indicates whether the application is shutdown explicitly or implicitly. + /// If is set to OnExplicitShutdown the application is only closes if Shutdown is called. + /// The default is OnLastWindowClose + /// + /// + /// The shutdown mode. + /// + ShutdownMode ShutdownMode { get; set; } + + /// + /// Gets or sets the main window of the application. + /// + /// + /// The main window. + /// + Window MainWindow { get; set; } + + IReadOnlyList Windows { get; } + } +} diff --git a/src/Avalonia.Controls/ApplicationLifetimes/IControlledApplicationLifetime.cs b/src/Avalonia.Controls/ApplicationLifetimes/IControlledApplicationLifetime.cs new file mode 100644 index 0000000000..3f61aeb536 --- /dev/null +++ b/src/Avalonia.Controls/ApplicationLifetimes/IControlledApplicationLifetime.cs @@ -0,0 +1,23 @@ +using System; + +namespace Avalonia.Controls.ApplicationLifetimes +{ + public interface IControlledApplicationLifetime : IApplicationLifetime + { + /// + /// Sent when the application is starting up. + /// + event EventHandler Startup; + + /// + /// Sent when the application is exiting. + /// + event EventHandler Exit; + + /// + /// Shuts down the application and sets the exit code that is returned to the operating system when the application exits. + /// + /// An integer exit code for an application. The default exit code is 0. + void Shutdown(int exitCode = 0); + } +} diff --git a/src/Avalonia.Controls/ApplicationLifetimes/ISingleViewApplicationLifetime.cs b/src/Avalonia.Controls/ApplicationLifetimes/ISingleViewApplicationLifetime.cs new file mode 100644 index 0000000000..eb451f51af --- /dev/null +++ b/src/Avalonia.Controls/ApplicationLifetimes/ISingleViewApplicationLifetime.cs @@ -0,0 +1,7 @@ +namespace Avalonia.Controls.ApplicationLifetimes +{ + public interface ISingleViewApplicationLifetime : IApplicationLifetime + { + Control MainView { get; set; } + } +} diff --git a/src/Avalonia.Controls/ApplicationLifetimes/StartupEventArgs.cs b/src/Avalonia.Controls/ApplicationLifetimes/StartupEventArgs.cs new file mode 100644 index 0000000000..423832793e --- /dev/null +++ b/src/Avalonia.Controls/ApplicationLifetimes/StartupEventArgs.cs @@ -0,0 +1,22 @@ +// 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 System.Collections.Generic; +using System.Linq; + +namespace Avalonia.Controls.ApplicationLifetimes +{ + /// + /// Contains the arguments for the event. + /// + public class ControlledApplicationLifetimeStartupEventArgs : EventArgs + { + public ControlledApplicationLifetimeStartupEventArgs(IEnumerable args) + { + Args = args?.ToArray() ?? Array.Empty(); + } + + public string[] Args { get; } + } +} diff --git a/src/Avalonia.Controls/AutoCompleteBox.cs b/src/Avalonia.Controls/AutoCompleteBox.cs index b87e10d284..1e2fc9f9d0 100644 --- a/src/Avalonia.Controls/AutoCompleteBox.cs +++ b/src/Avalonia.Controls/AutoCompleteBox.cs @@ -345,7 +345,6 @@ namespace Avalonia.Controls /// private IDisposable _collectionChangeSubscription; - private IMemberSelector _valueMemberSelector; private Func>> _asyncPopulator; private CancellationTokenSource _populationCancellationTokenSource; @@ -541,12 +540,6 @@ namespace Avalonia.Controls o => o.Items, (o, v) => o.Items = v); - public static readonly DirectProperty ValueMemberSelectorProperty = - AvaloniaProperty.RegisterDirect( - nameof(ValueMemberSelector), - o => o.ValueMemberSelector, - (o, v) => o.ValueMemberSelector = v); - public static readonly DirectProperty>>> AsyncPopulatorProperty = AvaloniaProperty.RegisterDirect>>>( nameof(AsyncPopulator), @@ -795,7 +788,7 @@ namespace Avalonia.Controls var template = new FuncDataTemplate( typeof(object), - o => + (o, _) => { var control = new ContentControl(); control.Bind(ContentControl.ContentProperty, value); @@ -958,20 +951,6 @@ namespace Avalonia.Controls } } - /// - /// Gets or sets the MemberSelector that is used to get values for - /// display in the text portion of the - /// control. - /// - /// The MemberSelector that is used to get values for display in - /// the text portion of the - /// control. - public IMemberSelector ValueMemberSelector - { - get { return _valueMemberSelector; } - set { SetAndRaise(ValueMemberSelectorProperty, ref _valueMemberSelector, value); } - } - /// /// Gets or sets the selected item in the drop-down. /// @@ -1841,11 +1820,6 @@ namespace Avalonia.Controls return _valueBindingEvaluator.GetDynamicValue(value) ?? String.Empty; } - if (_valueMemberSelector != null) - { - value = _valueMemberSelector.Select(value); - } - return value == null ? String.Empty : value.ToString(); } diff --git a/src/Avalonia.Controls/Button.cs b/src/Avalonia.Controls/Button.cs index cc9e6b7444..b09d3bddff 100644 --- a/src/Avalonia.Controls/Button.cs +++ b/src/Avalonia.Controls/Button.cs @@ -33,8 +33,6 @@ namespace Avalonia.Controls /// public class Button : ContentControl { - private ICommand _command; - /// /// Defines the property. /// @@ -75,6 +73,9 @@ namespace Avalonia.Controls public static readonly StyledProperty IsPressedProperty = AvaloniaProperty.Register(nameof(IsPressed)); + private ICommand _command; + private bool _commandCanExecute = true; + /// /// Initializes static members of the class. /// @@ -147,6 +148,8 @@ namespace Avalonia.Controls private set { SetValue(IsPressedProperty, value); } } + protected override bool IsEnabledCore => base.IsEnabledCore && _commandCanExecute; + /// protected override void OnAttachedToVisualTree(VisualTreeAttachmentEventArgs e) { @@ -252,7 +255,6 @@ namespace Avalonia.Controls if (e.MouseButton == MouseButton.Left) { - e.Device.Capture(this); IsPressed = true; e.Handled = true; @@ -270,7 +272,6 @@ namespace Avalonia.Controls if (IsPressed && e.MouseButton == MouseButton.Left) { - e.Device.Capture(null); IsPressed = false; e.Handled = true; @@ -282,6 +283,11 @@ namespace Avalonia.Controls } } + protected override void OnPointerCaptureLost(PointerCaptureLostEventArgs e) + { + IsPressed = false; + } + protected override void UpdateDataValidation(AvaloniaProperty property, BindingNotification status) { base.UpdateDataValidation(property, status); @@ -289,7 +295,11 @@ namespace Avalonia.Controls { if (status?.ErrorType == BindingErrorType.Error) { - IsEnabled = false; + if (_commandCanExecute) + { + _commandCanExecute = false; + UpdateIsEffectivelyEnabled(); + } } } } @@ -348,9 +358,13 @@ namespace Avalonia.Controls /// The event args. private void CanExecuteChanged(object sender, EventArgs e) { - // HACK: Just set the IsEnabled property for the moment. This needs to be changed to - // use IsEnabledCore etc. but it will do for now. - IsEnabled = Command == null || Command.CanExecute(CommandParameter); + var canExecute = Command == null || Command.CanExecute(CommandParameter); + + if (canExecute != _commandCanExecute) + { + _commandCanExecute = canExecute; + UpdateIsEffectivelyEnabled(); + } } /// diff --git a/src/Avalonia.Controls/Calendar/CalendarButton.cs b/src/Avalonia.Controls/Calendar/CalendarButton.cs index 224d9b782b..53852defb3 100644 --- a/src/Avalonia.Controls/Calendar/CalendarButton.cs +++ b/src/Avalonia.Controls/Calendar/CalendarButton.cs @@ -176,18 +176,5 @@ namespace Avalonia.Controls.Primitives if (e.MouseButton == MouseButton.Left) CalendarLeftMouseButtonUp?.Invoke(this, e); } - - /// - /// We need to simulate the MouseLeftButtonUp event for the - /// CalendarButton that stays in Pressed state after MouseCapture is - /// released since there is no actual MouseLeftButtonUp event for the - /// release. - /// - /// Event arguments. - internal void SendMouseLeftButtonUp(PointerReleasedEventArgs e) - { - e.Handled = false; - base.OnPointerReleased(e); - } } } diff --git a/src/Avalonia.Controls/Calendar/CalendarDayButton.cs b/src/Avalonia.Controls/Calendar/CalendarDayButton.cs index 1b36f92fd3..cb2a98e5ca 100644 --- a/src/Avalonia.Controls/Calendar/CalendarDayButton.cs +++ b/src/Avalonia.Controls/Calendar/CalendarDayButton.cs @@ -234,18 +234,5 @@ namespace Avalonia.Controls.Primitives if (e.MouseButton == MouseButton.Left) CalendarDayButtonMouseUp?.Invoke(this, e); } - - /// - /// We need to simulate the MouseLeftButtonUp event for the - /// CalendarDayButton that stays in Pressed state after MouseCapture is - /// released since there is no actual MouseLeftButtonUp event for the - /// release. - /// - /// Event arguments. - internal void SendMouseLeftButtonUp(PointerReleasedEventArgs e) - { - e.Handled = false; - base.OnPointerReleased(e); - } } } diff --git a/src/Avalonia.Controls/Calendar/CalendarItem.cs b/src/Avalonia.Controls/Calendar/CalendarItem.cs index fb6dacaf81..8232697c18 100644 --- a/src/Avalonia.Controls/Calendar/CalendarItem.cs +++ b/src/Avalonia.Controls/Calendar/CalendarItem.cs @@ -934,22 +934,6 @@ namespace Avalonia.Controls.Primitives // The button is in Pressed state. Change the state to normal. if (e.Device.Captured == b) e.Device.Capture(null); - // null check is added for unit tests - if (_downEventArg != null) - { - var arg = - new PointerReleasedEventArgs() - { - Device = _downEventArg.Device, - MouseButton = _downEventArg.MouseButton, - Handled = _downEventArg.Handled, - InputModifiers = _downEventArg.InputModifiers, - Route = _downEventArg.Route, - Source = _downEventArg.Source - }; - - b.SendMouseLeftButtonUp(arg); - } _lastCalendarDayButton = b; } } @@ -1221,21 +1205,7 @@ namespace Avalonia.Controls.Primitives if (e.Device.Captured == b) e.Device.Capture(null); //b.ReleaseMouseCapture(); - if (_downEventArgYearView != null) - { - var args = - new PointerReleasedEventArgs() - { - Device = _downEventArgYearView.Device, - MouseButton = _downEventArgYearView.MouseButton, - Handled = _downEventArgYearView.Handled, - InputModifiers = _downEventArgYearView.InputModifiers, - Route = _downEventArgYearView.Route, - Source = _downEventArgYearView.Source - }; - - b.SendMouseLeftButtonUp(args); - } + _lastCalendarButton = b; } } diff --git a/src/Avalonia.Controls/ColumnDefinition.cs b/src/Avalonia.Controls/ColumnDefinition.cs index d316881a05..9c520c434e 100644 --- a/src/Avalonia.Controls/ColumnDefinition.cs +++ b/src/Avalonia.Controls/ColumnDefinition.cs @@ -55,19 +55,22 @@ namespace Avalonia.Controls /// /// Gets the actual calculated width of the column. /// - public double ActualWidth - { - get; - internal set; - } + public double ActualWidth => Parent?.GetFinalColumnDefinitionWidth(Index) ?? 0d; /// /// Gets or sets the maximum width of the column in DIPs. /// public double MaxWidth { - get { return GetValue(MaxWidthProperty); } - set { SetValue(MaxWidthProperty, value); } + get + { + return GetValue(MaxWidthProperty); + } + set + { + Parent?.InvalidateMeasure(); + SetValue(MaxWidthProperty, value); + } } /// @@ -75,8 +78,15 @@ namespace Avalonia.Controls /// public double MinWidth { - get { return GetValue(MinWidthProperty); } - set { SetValue(MinWidthProperty, value); } + get + { + return GetValue(MinWidthProperty); + } + set + { + Parent?.InvalidateMeasure(); + SetValue(MinWidthProperty, value); + } } /// @@ -84,8 +94,19 @@ namespace Avalonia.Controls /// public GridLength Width { - get { return GetValue(WidthProperty); } - set { SetValue(WidthProperty, value); } + get + { + return GetValue(WidthProperty); + } + set + { + Parent?.InvalidateMeasure(); + SetValue(WidthProperty, value); + } } + + internal override GridLength UserSizeValueCache => this.Width; + internal override double UserMinSizeValueCache => this.MinWidth; + internal override double UserMaxSizeValueCache => this.MaxWidth; } } diff --git a/src/Avalonia.Controls/ColumnDefinitions.cs b/src/Avalonia.Controls/ColumnDefinitions.cs index ecfe6027ac..d4fc2ee0a1 100644 --- a/src/Avalonia.Controls/ColumnDefinitions.cs +++ b/src/Avalonia.Controls/ColumnDefinitions.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; +using System.Collections.Specialized; using System.Linq; using Avalonia.Collections; @@ -9,14 +11,13 @@ namespace Avalonia.Controls /// /// A collection of s. /// - public class ColumnDefinitions : AvaloniaList + public class ColumnDefinitions : DefinitionList { /// /// Initializes a new instance of the class. /// - public ColumnDefinitions() + public ColumnDefinitions() : base () { - ResetBehavior = ResetBehavior.Remove; } /// diff --git a/src/Avalonia.Controls/ComboBox.cs b/src/Avalonia.Controls/ComboBox.cs index bf79e192c5..f32b8fabc6 100644 --- a/src/Avalonia.Controls/ComboBox.cs +++ b/src/Avalonia.Controls/ComboBox.cs @@ -302,7 +302,7 @@ namespace Avalonia.Controls } } - private bool CanFocus(IControl control) => control.Focusable && control.IsEnabledCore && control.IsVisible; + private bool CanFocus(IControl control) => control.Focusable && control.IsEffectivelyEnabled && control.IsVisible; private void UpdateSelectionBoxItem(object item) { @@ -333,8 +333,7 @@ namespace Avalonia.Controls } else { - var selector = MemberSelector; - SelectionBoxItem = selector != null ? selector.Select(item) : item; + SelectionBoxItem = item; } } diff --git a/src/Avalonia.Controls/ContextMenu.cs b/src/Avalonia.Controls/ContextMenu.cs index 92293a32d6..58b4324a3e 100644 --- a/src/Avalonia.Controls/ContextMenu.cs +++ b/src/Avalonia.Controls/ContextMenu.cs @@ -1,12 +1,12 @@ using System; using System.ComponentModel; using System.Linq; -using System.Reactive.Linq; using Avalonia.Controls.Generators; using Avalonia.Controls.Platform; using Avalonia.Controls.Primitives; using Avalonia.Controls.Templates; using Avalonia.Input; +using Avalonia.Interactivity; using Avalonia.LogicalTree; namespace Avalonia.Controls @@ -90,9 +90,14 @@ namespace Avalonia.Controls /// The control. public void Open(Control control) { + if (IsOpen) + { + return; + } + if (_popup == null) { - _popup = new Popup() + _popup = new Popup { PlacementMode = PlacementMode.Pointer, PlacementTarget = control, @@ -107,7 +112,14 @@ namespace Avalonia.Controls ((ISetLogicalParent)_popup).SetParent(control); _popup.Child = this; _popup.IsOpen = true; + IsOpen = true; + + RaiseEvent(new RoutedEventArgs + { + RoutedEvent = MenuOpenedEvent, + Source = this, + }); } /// @@ -115,13 +127,15 @@ namespace Avalonia.Controls /// public override void Close() { + if (!IsOpen) + { + return; + } + if (_popup != null && _popup.IsVisible) { _popup.IsOpen = false; } - - SelectedIndex = -1; - IsOpen = false; } protected override IItemContainerGenerator CreateItemContainerGenerator() @@ -129,6 +143,18 @@ namespace Avalonia.Controls return new MenuItemContainerGenerator(this); } + private void CloseCore() + { + SelectedIndex = -1; + IsOpen = false; + + RaiseEvent(new RoutedEventArgs + { + RoutedEvent = MenuClosedEvent, + Source = this, + }); + } + private void PopupOpened(object sender, EventArgs e) { Focus(); @@ -145,8 +171,7 @@ namespace Avalonia.Controls i.IsSubMenuOpen = false; } - contextMenu.IsOpen = false; - contextMenu.SelectedIndex = -1; + contextMenu.CloseCore(); } } diff --git a/src/Avalonia.Controls/Control.cs b/src/Avalonia.Controls/Control.cs index a7ee027e70..ca5edae4a9 100644 --- a/src/Avalonia.Controls/Control.cs +++ b/src/Avalonia.Controls/Control.cs @@ -1,6 +1,7 @@ // 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 System.ComponentModel; using Avalonia.Controls.Primitives; using Avalonia.Controls.Templates; @@ -20,7 +21,7 @@ namespace Avalonia.Controls /// /// - A property to allow user-defined data to be attached to the control. /// - public class Control : InputElement, IControl, INamed, ISupportInitialize, IVisualBrushInitialize, IRequiresTemplateInSetter + public class Control : InputElement, IControl, INamed, ISupportInitialize, IVisualBrushInitialize, ISetterValue { /// /// Defines the property. @@ -90,6 +91,13 @@ namespace Avalonia.Controls /// bool IDataTemplateHost.IsDataTemplatesInitialized => _dataTemplates != null; + /// + void ISetterValue.Initialize(ISetter setter) + { + throw new InvalidOperationException( + "Cannot use a control as a Setter value. Wrap the control in a