Browse Source

Merge branch 'master' into portablexaml

pull/916/head
danwalmsley 9 years ago
committed by GitHub
parent
commit
e0dc2ceff7
  1. 3
      .gitignore
  2. 43
      Avalonia.sln
  3. 1
      Avalonia.sln.DotSettings
  4. 1
      appveyor.yml
  5. 68
      build.cake
  6. 2
      build/Base.props
  7. 2
      build/Moq.props
  8. 3
      build/SkiaSharp.props
  9. 6
      build/XUnit.props
  10. 2
      docs/index.md
  11. 2
      docs/tutorial/from-wpf.md
  12. 2
      docs/tutorial/gettingstarted.md
  13. 45
      packages.cake
  14. 14
      parameters.cake
  15. 2
      readme.md
  16. 3
      samples/ControlCatalog.Desktop/ControlCatalog.Desktop.csproj
  17. 23
      samples/ControlCatalog/Pages/MenuPage.xaml
  18. 1
      samples/interop/Direct3DInteropSample/Direct3DInteropSample.csproj
  19. 2
      samples/interop/Direct3DInteropSample/MainWindow.cs
  20. 1
      samples/interop/GtkInteropDemo/GtkInteropDemo.csproj
  21. 14
      samples/interop/WindowsInteropTest/EmbedToWpfDemo.xaml
  22. 15
      samples/interop/WindowsInteropTest/EmbedToWpfDemo.xaml.cs
  23. 8
      samples/interop/WindowsInteropTest/WindowsInteropTest.csproj
  24. 5
      scripts/ReplaceNugetCache.ps1
  25. 7
      scripts/ReplaceNugetCache.sh
  26. 1
      src/Android/Avalonia.Android/AndroidPlatform.cs
  27. 2
      src/Android/Avalonia.Android/Platform/Input/AndroidMouseDevice.cs
  28. 2
      src/Android/Avalonia.Android/Platform/SkiaPlatform/AndroidFramebuffer.cs
  29. 3
      src/Android/Avalonia.Android/Platform/SkiaPlatform/TopLevelImpl.cs
  30. 2
      src/Android/Avalonia.Android/Platform/Specific/Helpers/AndroidTouchEventsHelper.cs
  31. 1
      src/Avalonia.Base/Avalonia.Base.csproj
  32. 11
      src/Avalonia.Base/AvaloniaObject.cs
  33. 25
      src/Avalonia.Base/Collections/AvaloniaDictionary.cs
  34. 123
      src/Avalonia.Base/Collections/AvaloniaListExtensions.cs
  35. 38
      src/Avalonia.Base/Data/BindingNotification.cs
  36. 2
      src/Avalonia.Base/PriorityValue.cs
  37. 24
      src/Avalonia.Controls/AppBuilderBase.cs
  38. 38
      src/Avalonia.Controls/Button.cs
  39. 2
      src/Avalonia.Controls/ContextMenu.cs
  40. 15
      src/Avalonia.Controls/Control.cs
  41. 16
      src/Avalonia.Controls/Embedding/EmbeddableControlRoot.cs
  42. 2
      src/Avalonia.Controls/ItemsControl.cs
  43. 2
      src/Avalonia.Controls/Menu.cs
  44. 11
      src/Avalonia.Controls/MenuItem.cs
  45. 7
      src/Avalonia.Controls/Platform/ITopLevelImpl.cs
  46. 38
      src/Avalonia.Controls/Presenters/ContentPresenter.cs
  47. 3
      src/Avalonia.Controls/Presenters/ItemVirtualizerSimple.cs
  48. 4
      src/Avalonia.Controls/Presenters/ScrollContentPresenter.cs
  49. 4
      src/Avalonia.Controls/Primitives/Popup.cs
  50. 7
      src/Avalonia.Controls/Primitives/PopupRoot.cs
  51. 11
      src/Avalonia.Controls/Primitives/TemplatedControl.cs
  52. 4
      src/Avalonia.Controls/Templates/TemplateExtensions.cs
  53. 7
      src/Avalonia.Controls/TextBox.cs
  54. 41
      src/Avalonia.Controls/ToolTip.cs
  55. 40
      src/Avalonia.Controls/TopLevel.cs
  56. 22
      src/Avalonia.Controls/TreeView.cs
  57. 43
      src/Avalonia.Controls/Window.cs
  58. 43
      src/Avalonia.Controls/WindowBase.cs
  59. 4
      src/Avalonia.DesignerSupport/DesignerAssist.cs
  60. 1
      src/Avalonia.Diagnostics/Avalonia.Diagnostics.csproj
  61. 30
      src/Avalonia.Diagnostics/DevTools.xaml.cs
  62. 3
      src/Avalonia.Diagnostics/ViewModels/ControlDetailsViewModel.cs
  63. 74
      src/Avalonia.Diagnostics/ViewModels/DevToolsViewModel.cs
  64. 4
      src/Avalonia.Diagnostics/ViewModels/LogicalTreeNode.cs
  65. 11
      src/Avalonia.Diagnostics/ViewModels/PropertyDetails.cs
  66. 10
      src/Avalonia.Diagnostics/ViewModels/TreeNode.cs
  67. 24
      src/Avalonia.Diagnostics/ViewModels/TreePageViewModel.cs
  68. 32
      src/Avalonia.Diagnostics/ViewModels/ViewModelBase.cs
  69. 7
      src/Avalonia.Diagnostics/ViewModels/VisualTreeNode.cs
  70. 20
      src/Avalonia.Diagnostics/Views/ControlDetailsView.cs
  71. 30
      src/Avalonia.Diagnostics/Views/PropertyChangedExtenions.cs
  72. 8
      src/Avalonia.DotNetFrameworkRuntime/AppBuilder.cs
  73. 5
      src/Avalonia.HtmlRenderer/Adapters/ControlAdapter.cs
  74. 3
      src/Avalonia.HtmlRenderer/HtmlControl.cs
  75. 5
      src/Avalonia.Input/FocusManager.cs
  76. 15
      src/Avalonia.Input/ICustomKeyboardNavigation.cs
  77. 7
      src/Avalonia.Input/IInputDevice.cs
  78. 8
      src/Avalonia.Input/IInputRoot.cs
  79. 3
      src/Avalonia.Input/IKeyboardDevice.cs
  80. 1
      src/Avalonia.Input/InputManager.cs
  81. 12
      src/Avalonia.Input/KeyboardDevice.cs
  82. 27
      src/Avalonia.Input/KeyboardNavigationHandler.cs
  83. 31
      src/Avalonia.Input/MouseDevice.cs
  84. 62
      src/Avalonia.Input/Navigation/DirectionalNavigation.cs
  85. 6
      src/Avalonia.Input/Navigation/FocusExtensions.cs
  86. 107
      src/Avalonia.Input/Navigation/TabNavigation.cs
  87. 10
      src/Avalonia.Layout/IEmbeddedLayoutRoot.cs
  88. 99
      src/Avalonia.Layout/LayoutManager.cs
  89. 34
      src/Avalonia.Layout/Layoutable.cs
  90. 2
      src/Avalonia.Styling/Controls/NameScope.cs
  91. 10
      src/Avalonia.Styling/LogicalTree/ILogical.cs
  92. 6
      src/Avalonia.Styling/LogicalTree/LogicalExtensions.cs
  93. 14
      src/Avalonia.Styling/Styling/DescendentSelector.cs
  94. 6
      src/Avalonia.Styling/Styling/Selectors.cs
  95. 4
      src/Avalonia.Styling/Styling/Style.cs
  96. 2
      src/Avalonia.Visuals/Platform/ILockedFramebuffer.cs
  97. 2
      src/Avalonia.Visuals/Rendering/ImmediateRenderer.cs
  98. 2
      src/Avalonia.Visuals/Rendering/ZIndexComparer.cs
  99. 13
      src/Avalonia.Visuals/Vector.cs
  100. 6
      src/Avalonia.Visuals/Visual.cs

3
.gitignore

@ -162,7 +162,8 @@ $RECYCLE.BIN/
################# #################
## Cake ## Cake
################# #################
tools/ tools/*
!tools/packages.config
.nuget .nuget
artifacts/ artifacts/
nuget nuget

43
Avalonia.sln

@ -193,6 +193,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Avalonia.LinuxFramebuffer",
EndProject EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Direct3DInteropSample", "samples\interop\Direct3DInteropSample\Direct3DInteropSample.csproj", "{638580B0-7910-40EF-B674-DCB34DA308CD}" Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Direct3DInteropSample", "samples\interop\Direct3DInteropSample\Direct3DInteropSample.csproj", "{638580B0-7910-40EF-B674-DCB34DA308CD}"
EndProject EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Avalonia.Win32.Interop", "src\Windows\Avalonia.Win32.Interop\Avalonia.Win32.Interop.csproj", "{CBC4FF2F-92D4-420B-BE21-9FE0B930B04E}"
EndProject
Global Global
GlobalSection(SharedMSBuildProjectFiles) = preSolution GlobalSection(SharedMSBuildProjectFiles) = preSolution
src\Skia\Avalonia.Skia\Avalonia.Skia.projitems*{2f59f3d0-748d-4652-b01e-e0d954756308}*SharedItemsImports = 13 src\Skia\Avalonia.Skia\Avalonia.Skia.projitems*{2f59f3d0-748d-4652-b01e-e0d954756308}*SharedItemsImports = 13
@ -3191,6 +3193,46 @@ Global
{638580B0-7910-40EF-B674-DCB34DA308CD}.Release|Mono.Build.0 = Release|Any CPU {638580B0-7910-40EF-B674-DCB34DA308CD}.Release|Mono.Build.0 = Release|Any CPU
{638580B0-7910-40EF-B674-DCB34DA308CD}.Release|x86.ActiveCfg = Release|Any CPU {638580B0-7910-40EF-B674-DCB34DA308CD}.Release|x86.ActiveCfg = Release|Any CPU
{638580B0-7910-40EF-B674-DCB34DA308CD}.Release|x86.Build.0 = Release|Any CPU {638580B0-7910-40EF-B674-DCB34DA308CD}.Release|x86.Build.0 = Release|Any CPU
{CBC4FF2F-92D4-420B-BE21-9FE0B930B04E}.Ad-Hoc|Any CPU.ActiveCfg = Release|Any CPU
{CBC4FF2F-92D4-420B-BE21-9FE0B930B04E}.Ad-Hoc|Any CPU.Build.0 = Release|Any CPU
{CBC4FF2F-92D4-420B-BE21-9FE0B930B04E}.Ad-Hoc|iPhone.ActiveCfg = Release|Any CPU
{CBC4FF2F-92D4-420B-BE21-9FE0B930B04E}.Ad-Hoc|iPhone.Build.0 = Release|Any CPU
{CBC4FF2F-92D4-420B-BE21-9FE0B930B04E}.Ad-Hoc|iPhoneSimulator.ActiveCfg = Release|Any CPU
{CBC4FF2F-92D4-420B-BE21-9FE0B930B04E}.Ad-Hoc|iPhoneSimulator.Build.0 = Release|Any CPU
{CBC4FF2F-92D4-420B-BE21-9FE0B930B04E}.Ad-Hoc|Mono.ActiveCfg = Release|Any CPU
{CBC4FF2F-92D4-420B-BE21-9FE0B930B04E}.Ad-Hoc|Mono.Build.0 = Release|Any CPU
{CBC4FF2F-92D4-420B-BE21-9FE0B930B04E}.Ad-Hoc|x86.ActiveCfg = Release|Any CPU
{CBC4FF2F-92D4-420B-BE21-9FE0B930B04E}.Ad-Hoc|x86.Build.0 = Release|Any CPU
{CBC4FF2F-92D4-420B-BE21-9FE0B930B04E}.AppStore|Any CPU.ActiveCfg = Release|Any CPU
{CBC4FF2F-92D4-420B-BE21-9FE0B930B04E}.AppStore|Any CPU.Build.0 = Release|Any CPU
{CBC4FF2F-92D4-420B-BE21-9FE0B930B04E}.AppStore|iPhone.ActiveCfg = Release|Any CPU
{CBC4FF2F-92D4-420B-BE21-9FE0B930B04E}.AppStore|iPhone.Build.0 = Release|Any CPU
{CBC4FF2F-92D4-420B-BE21-9FE0B930B04E}.AppStore|iPhoneSimulator.ActiveCfg = Release|Any CPU
{CBC4FF2F-92D4-420B-BE21-9FE0B930B04E}.AppStore|iPhoneSimulator.Build.0 = Release|Any CPU
{CBC4FF2F-92D4-420B-BE21-9FE0B930B04E}.AppStore|Mono.ActiveCfg = Release|Any CPU
{CBC4FF2F-92D4-420B-BE21-9FE0B930B04E}.AppStore|Mono.Build.0 = Release|Any CPU
{CBC4FF2F-92D4-420B-BE21-9FE0B930B04E}.AppStore|x86.ActiveCfg = Release|Any CPU
{CBC4FF2F-92D4-420B-BE21-9FE0B930B04E}.AppStore|x86.Build.0 = Release|Any CPU
{CBC4FF2F-92D4-420B-BE21-9FE0B930B04E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{CBC4FF2F-92D4-420B-BE21-9FE0B930B04E}.Debug|Any CPU.Build.0 = Debug|Any CPU
{CBC4FF2F-92D4-420B-BE21-9FE0B930B04E}.Debug|iPhone.ActiveCfg = Debug|Any CPU
{CBC4FF2F-92D4-420B-BE21-9FE0B930B04E}.Debug|iPhone.Build.0 = Debug|Any CPU
{CBC4FF2F-92D4-420B-BE21-9FE0B930B04E}.Debug|iPhoneSimulator.ActiveCfg = Debug|Any CPU
{CBC4FF2F-92D4-420B-BE21-9FE0B930B04E}.Debug|iPhoneSimulator.Build.0 = Debug|Any CPU
{CBC4FF2F-92D4-420B-BE21-9FE0B930B04E}.Debug|Mono.ActiveCfg = Debug|Any CPU
{CBC4FF2F-92D4-420B-BE21-9FE0B930B04E}.Debug|Mono.Build.0 = Debug|Any CPU
{CBC4FF2F-92D4-420B-BE21-9FE0B930B04E}.Debug|x86.ActiveCfg = Debug|Any CPU
{CBC4FF2F-92D4-420B-BE21-9FE0B930B04E}.Debug|x86.Build.0 = Debug|Any CPU
{CBC4FF2F-92D4-420B-BE21-9FE0B930B04E}.Release|Any CPU.ActiveCfg = Release|Any CPU
{CBC4FF2F-92D4-420B-BE21-9FE0B930B04E}.Release|Any CPU.Build.0 = Release|Any CPU
{CBC4FF2F-92D4-420B-BE21-9FE0B930B04E}.Release|iPhone.ActiveCfg = Release|Any CPU
{CBC4FF2F-92D4-420B-BE21-9FE0B930B04E}.Release|iPhone.Build.0 = Release|Any CPU
{CBC4FF2F-92D4-420B-BE21-9FE0B930B04E}.Release|iPhoneSimulator.ActiveCfg = Release|Any CPU
{CBC4FF2F-92D4-420B-BE21-9FE0B930B04E}.Release|iPhoneSimulator.Build.0 = Release|Any CPU
{CBC4FF2F-92D4-420B-BE21-9FE0B930B04E}.Release|Mono.ActiveCfg = Release|Any CPU
{CBC4FF2F-92D4-420B-BE21-9FE0B930B04E}.Release|Mono.Build.0 = Release|Any CPU
{CBC4FF2F-92D4-420B-BE21-9FE0B930B04E}.Release|x86.ActiveCfg = Release|Any CPU
{CBC4FF2F-92D4-420B-BE21-9FE0B930B04E}.Release|x86.Build.0 = Release|Any CPU
EndGlobalSection EndGlobalSection
GlobalSection(SolutionProperties) = preSolution GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE HideSolutionNode = FALSE
@ -3252,5 +3294,6 @@ Global
{4D6FAF79-58B4-482F-9122-0668C346364C} = {74487168-7D91-487E-BF93-055F2251461E} {4D6FAF79-58B4-482F-9122-0668C346364C} = {74487168-7D91-487E-BF93-055F2251461E}
{854568D5-13D1-4B4F-B50D-534DC7EFD3C9} = {86C53C40-57AA-45B8-AD42-FAE0EFDF0F2B} {854568D5-13D1-4B4F-B50D-534DC7EFD3C9} = {86C53C40-57AA-45B8-AD42-FAE0EFDF0F2B}
{638580B0-7910-40EF-B674-DCB34DA308CD} = {A0CC0258-D18C-4AB3-854F-7101680FC3F9} {638580B0-7910-40EF-B674-DCB34DA308CD} = {A0CC0258-D18C-4AB3-854F-7101680FC3F9}
{CBC4FF2F-92D4-420B-BE21-9FE0B930B04E} = {B39A8919-9F95-48FE-AD7B-76E08B509888}
EndGlobalSection EndGlobalSection
EndGlobal EndGlobal

1
Avalonia.sln.DotSettings

@ -1,4 +1,5 @@
<wpf:ResourceDictionary xml:space="preserve" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:s="clr-namespace:System;assembly=mscorlib" xmlns:ss="urn:shemas-jetbrains-com:settings-storage-xaml" xmlns:wpf="http://schemas.microsoft.com/winfx/2006/xaml/presentation"> <wpf:ResourceDictionary xml:space="preserve" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:s="clr-namespace:System;assembly=mscorlib" xmlns:ss="urn:shemas-jetbrains-com:settings-storage-xaml" xmlns:wpf="http://schemas.microsoft.com/winfx/2006/xaml/presentation">
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=3E53A01A_002DB331_002D47F3_002DB828_002D4A5717E77A24_002Fd_003Aglass/@EntryIndexedValue">ExplicitlyExcluded</s:String>
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=6417B24E_002D49C2_002D4985_002D8DB2_002D3AB9D898EC91/@EntryIndexedValue">ExplicitlyExcluded</s:String> <s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=6417B24E_002D49C2_002D4985_002D8DB2_002D3AB9D898EC91/@EntryIndexedValue">ExplicitlyExcluded</s:String>
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=E3A1060B_002D50D0_002D44E8_002D88B6_002DF44EF2E5BD72_002Ff_003Ahtml_002Ehtm/@EntryIndexedValue">ExplicitlyExcluded</s:String> <s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=E3A1060B_002D50D0_002D44E8_002D88B6_002DF44EF2E5BD72_002Ff_003Ahtml_002Ehtm/@EntryIndexedValue">ExplicitlyExcluded</s:String>
<s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=RedundantUsingDirective/@EntryIndexedValue">HINT</s:String> <s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=RedundantUsingDirective/@EntryIndexedValue">HINT</s:String>

1
appveyor.yml

@ -35,6 +35,7 @@ test: off
artifacts: artifacts:
- path: artifacts\nuget\*.nupkg - path: artifacts\nuget\*.nupkg
- path: artifacts\zip\*.zip - path: artifacts\zip\*.zip
- path: artifacts\inspectcode.xml
cache: cache:
- gtk-sharp-2.12.26.msi - gtk-sharp-2.12.26.msi
- dotnet-1.0.1.exe - dotnet-1.0.1.exe

68
build.cake

@ -5,12 +5,13 @@
#addin "nuget:?package=Polly&version=4.2.0" #addin "nuget:?package=Polly&version=4.2.0"
#addin "nuget:?package=NuGet.Core&version=2.12.0" #addin "nuget:?package=NuGet.Core&version=2.12.0"
#tool "nuget:https://dotnet.myget.org/F/nuget-build/?package=NuGet.CommandLine&version=4.3.0-preview1-3980&prerelease" #tool "nuget:https://dotnet.myget.org/F/nuget-build/?package=NuGet.CommandLine&version=4.3.0-preview1-3980&prerelease"
#tool "nuget:?package=JetBrains.dotMemoryUnit&version=2.1.20150828.125449" #tool "nuget:?package=JetBrains.dotMemoryUnit&version=2.3.20160517.113140"
#tool "JetBrains.ReSharper.CommandLineTools"
/////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////
// TOOLS // TOOLS
/////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////
#tool "nuget:?package=xunit.runner.console&version=2.1.0" #tool "nuget:?package=xunit.runner.console&version=2.2.0"
#tool "nuget:?package=OpenCover" #tool "nuget:?package=OpenCover"
/////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////
@ -97,14 +98,13 @@ Task("Clean")
CleanDirectory(parameters.TestsRoot); CleanDirectory(parameters.TestsRoot);
}); });
Task("Restore-NuGet-Packages") Task("Restore-NuGet-Packages")
.IsDependentOn("Clean") .IsDependentOn("Clean")
.WithCriteria(parameters.IsRunningOnWindows) .WithCriteria(parameters.IsRunningOnWindows)
.Does(() => .Does(() =>
{ {
var maxRetryCount = 5; var maxRetryCount = 5;
var toolTimeout = 1d; var toolTimeout = 2d;
Policy Policy
.Handle<Exception>() .Handle<Exception>()
.Retry(maxRetryCount, (exception, retryCount, context) => { .Retry(maxRetryCount, (exception, retryCount, context) => {
@ -170,23 +170,25 @@ void RunCoreTest(string dir, Parameters parameters, bool net461Only)
continue; continue;
Information("Running for " + fw); Information("Running for " + fw);
DotNetCoreTest(System.IO.Path.Combine(dir, System.IO.Path.GetFileName(dir)+".csproj"), DotNetCoreTest(System.IO.Path.Combine(dir, System.IO.Path.GetFileName(dir)+".csproj"),
new DotNetCoreTestSettings{Framework = fw}); new DotNetCoreTestSettings {
Configuration = parameters.Configuration,
Framework = fw
});
} }
} }
Task("Run-Net-Core-Unit-Tests") Task("Run-Net-Core-Unit-Tests")
.IsDependentOn("Clean") .IsDependentOn("Clean")
.Does(() => { .Does(() => {
RunCoreTest("./tests/Avalonia.Base.UnitTests", parameters, false); RunCoreTest("./tests/Avalonia.Base.UnitTests", parameters, false);
RunCoreTest("./tests/Avalonia.Controls.UnitTests", parameters, true); RunCoreTest("./tests/Avalonia.Controls.UnitTests", parameters, false);
RunCoreTest("./tests/Avalonia.Input.UnitTests", parameters, true); RunCoreTest("./tests/Avalonia.Input.UnitTests", parameters, false);
RunCoreTest("./tests/Avalonia.Interactivity.UnitTests", parameters, true); RunCoreTest("./tests/Avalonia.Interactivity.UnitTests", parameters, false);
RunCoreTest("./tests/Avalonia.Layout.UnitTests", parameters, true); RunCoreTest("./tests/Avalonia.Layout.UnitTests", parameters, false);
//RunCoreTest("./tests/Avalonia.Markup.UnitTests", parameters, true); RunCoreTest("./tests/Avalonia.Markup.UnitTests", parameters, false);
//RunCoreTest("./tests/Avalonia.Markup.Xaml.UnitTests", parameters, true); RunCoreTest("./tests/Avalonia.Markup.Xaml.UnitTests", parameters, false);
RunCoreTest("./tests/Avalonia.Styling.UnitTests", parameters, true); RunCoreTest("./tests/Avalonia.Styling.UnitTests", parameters, false);
RunCoreTest("./tests/Avalonia.Visuals.UnitTests", parameters, true); RunCoreTest("./tests/Avalonia.Visuals.UnitTests", parameters, false);
}); });
Task("Run-Unit-Tests") Task("Run-Unit-Tests")
@ -279,11 +281,15 @@ Task("Zip-Files")
Zip(parameters.ZipSourceControlCatalogDesktopDirs, Zip(parameters.ZipSourceControlCatalogDesktopDirs,
parameters.ZipTargetControlCatalogDesktopDirs, parameters.ZipTargetControlCatalogDesktopDirs,
GetFiles(parameters.ZipSourceControlCatalogDesktopDirs.FullPath + "/*.dll") + GetFiles(parameters.ZipSourceControlCatalogDesktopDirs.FullPath + "/*.dll") +
GetFiles(parameters.ZipSourceControlCatalogDesktopDirs.FullPath + "/*.config") +
GetFiles(parameters.ZipSourceControlCatalogDesktopDirs.FullPath + "/*.so") +
GetFiles(parameters.ZipSourceControlCatalogDesktopDirs.FullPath + "/*.dylib") +
GetFiles(parameters.ZipSourceControlCatalogDesktopDirs.FullPath + "/*.exe")); GetFiles(parameters.ZipSourceControlCatalogDesktopDirs.FullPath + "/*.exe"));
}); });
Task("Create-NuGet-Packages") Task("Create-NuGet-Packages")
.IsDependentOn("Run-Unit-Tests") .IsDependentOn("Run-Unit-Tests")
.IsDependentOn("Inspect")
.Does(() => .Does(() =>
{ {
foreach(var nuspec in packages.NuspecNuGetSettings) foreach(var nuspec in packages.NuspecNuGetSettings)
@ -331,7 +337,6 @@ Task("Publish-NuGet")
.WithCriteria(() => !parameters.IsLocalBuild) .WithCriteria(() => !parameters.IsLocalBuild)
.WithCriteria(() => !parameters.IsPullRequest) .WithCriteria(() => !parameters.IsPullRequest)
.WithCriteria(() => parameters.IsMainRepo) .WithCriteria(() => parameters.IsMainRepo)
.WithCriteria(() => parameters.IsMasterBranch)
.WithCriteria(() => parameters.IsNuGetRelease) .WithCriteria(() => parameters.IsNuGetRelease)
.Does(() => .Does(() =>
{ {
@ -360,6 +365,39 @@ Task("Publish-NuGet")
Information("Publish-NuGet Task failed, but continuing with next Task..."); Information("Publish-NuGet Task failed, but continuing with next Task...");
}); });
Task("Inspect")
.WithCriteria(parameters.IsRunningOnWindows)
.IsDependentOn("Restore-NuGet-Packages")
.Does(() =>
{
var badIssues = new []{"PossibleNullReferenceException"};
var whitelist = new []{"tests", "src\\android", "src\\ios",
"src\\windows\\avalonia.designer", "src\\avalonia.htmlrenderer\\external"};
Information("Running code inspections");
StartProcess("tools\\JetBrains.ReSharper.CommandLineTools\\tools\\inspectcode.exe",
new ProcessSettings{ Arguments = "--output=artifacts\\inspectcode.xml --profile=Avalonia.sln.DotSettings Avalonia.sln" });
Information("Analyzing report");
var doc = XDocument.Parse(System.IO.File.ReadAllText("artifacts\\inspectcode.xml"));
var failBuild = false;
foreach(var xml in doc.Descendants("Issue"))
{
var typeId = xml.Attribute("TypeId").Value.ToString();
if(badIssues.Contains(typeId))
{
var file = xml.Attribute("File").Value.ToString().ToLower();
if(whitelist.Any(wh => file.StartsWith(wh)))
continue;
var line = xml.Attribute("Line").Value.ToString();
Error(typeId + " - " + file + " on line " + line);
failBuild = true;
}
}
if(failBuild)
throw new Exception("Issues found");
});
/////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////
// TARGETS // TARGETS
/////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////

2
build/SkiaSharp.Desktop.props → build/Base.props

@ -1,5 +1,5 @@
<Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> <Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<ItemGroup> <ItemGroup>
<PackageReference Include="Avalonia.Skia.Linux.Natives" Version="1.56.1.3" /> <PackageReference Include="System.ValueTuple" Version="4.3.1" />
</ItemGroup> </ItemGroup>
</Project> </Project>

2
build/Moq.props

@ -1,5 +1,5 @@
<Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> <Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<ItemGroup> <ItemGroup>
<PackageReference Include="Moq" Version="4.7.1" /> <PackageReference Include="Moq" Version="4.7.25" />
</ItemGroup> </ItemGroup>
</Project> </Project>

3
build/SkiaSharp.props

@ -1,5 +1,6 @@
<Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> <Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<ItemGroup> <ItemGroup>
<PackageReference Include="SkiaSharp" Version="1.56.1-beta" /> <PackageReference Include="SkiaSharp" Version="1.57.1" />
<PackageReference Condition="'$(IncludeLinuxSkia)' == 'true'" Include="Avalonia.Skia.Linux.Natives" Version="1.57.1.3" />
</ItemGroup> </ItemGroup>
</Project> </Project>

6
build/XUnit.props

@ -7,7 +7,9 @@
<PackageReference Include="xunit.extensibility.core" Version="2.2.0" /> <PackageReference Include="xunit.extensibility.core" Version="2.2.0" />
<PackageReference Include="xunit.extensibility.execution" Version="2.2.0" /> <PackageReference Include="xunit.extensibility.execution" Version="2.2.0" />
<PackageReference Include="xunit.runner.console" Version="2.2.0" /> <PackageReference Include="xunit.runner.console" Version="2.2.0" />
<PackageReference Condition="'$(TargetFramework)' == 'net461'" Include="xunit.runner.visualstudio" Version="2.2.0" /> <PackageReference Include="xunit.runner.visualstudio" Version="2.2.0" />
<PackageReference Condition="'$(TargetFramework)' == 'netcoreapp1.1'" Include="Microsoft.NET.Test.Sdk" Version="15.0.0" /> </ItemGroup>
<ItemGroup Condition="'$(TargetFramework)' == 'netcoreapp1.1'">
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="15.0.0" />
</ItemGroup> </ItemGroup>
</Project> </Project>

2
docs/index.md

@ -10,7 +10,7 @@ What does alpha mean? Well, it means that it's now at a stage where you can have
## How do I try it out ## How do I try it out
The easiest way to try out Avalonia is to install the [Visual Studio Extension](https://visualstudiogallery.msdn.microsoft.com/a4542e8a-b56c-4295-8df1-7e220178b873). The easiest way to try out Avalonia is to install the [Visual Studio Extension](https://marketplace.visualstudio.com/items?itemName=AvaloniaTeam.AvaloniaforVisualStudio).
This will add a Avalonia project template and a Window template to the standard Visual Studo "Add" dialog (yes, icons still to come :) ): This will add a Avalonia project template and a Window template to the standard Visual Studo "Add" dialog (yes, icons still to come :) ):

2
docs/tutorial/from-wpf.md

@ -33,7 +33,7 @@ placed in a `DataTemplates` collection on each control (and on `Application`):
<TextBox Text="{Binding Name}"/> <TextBox Text="{Binding Name}"/>
</Border> </Border>
</DataTemplate> </DataTemplate>
</UserControl.Styles> </UserControl.DataTemplates>
<!-- Assuming that DataContext.Foo is an object of type <!-- Assuming that DataContext.Foo is an object of type
MyApp.ViewModels.FooViewModel then a red border with a corner MyApp.ViewModels.FooViewModel then a red border with a corner
radius of 8 containing a TextBox will be displayed here --> radius of 8 containing a TextBox will be displayed here -->

2
docs/tutorial/gettingstarted.md

@ -4,7 +4,7 @@
![](images/add-dialogs.png) ![](images/add-dialogs.png)
The easiest way to try out Avalonia is to install the [Visual Studio Extension](https://visualstudiogallery.msdn.microsoft.com/e1c6ae1f-6fd9-467d-8f62-1e28b4225213). The easiest way to try out Avalonia is to install the [Visual Studio Extension](https://marketplace.visualstudio.com/items?itemName=AvaloniaTeam.AvaloniaforVisualStudio).
This will add a Avalonia project template and a Window template to the standard Visual Studo “Add” This will add a Avalonia project template and a Window template to the standard Visual Studo “Add”
dialog (yes, icons still to come :) ): dialog (yes, icons still to come :) ):

45
packages.cake

@ -7,6 +7,7 @@ public class Packages
public FilePath[] BinFiles { get; private set; } public FilePath[] BinFiles { get; private set; }
public string NugetPackagesDir {get; private set;} public string NugetPackagesDir {get; private set;}
public string SkiaSharpVersion {get; private set; } public string SkiaSharpVersion {get; private set; }
public string SkiaSharpLinuxVersion {get; private set; }
public Packages(ICakeContext context, Parameters parameters) public Packages(ICakeContext context, Parameters parameters)
{ {
// NUGET NUSPECS // NUGET NUSPECS
@ -74,7 +75,9 @@ public class Packages
var SplatVersion = packageVersions["Splat"].FirstOrDefault().Item1; var SplatVersion = packageVersions["Splat"].FirstOrDefault().Item1;
var SpracheVersion = packageVersions["Sprache"].FirstOrDefault().Item1; var SpracheVersion = packageVersions["Sprache"].FirstOrDefault().Item1;
var SystemReactiveVersion = packageVersions["System.Reactive"].FirstOrDefault().Item1; var SystemReactiveVersion = packageVersions["System.Reactive"].FirstOrDefault().Item1;
var SystemValueTupleVersion = packageVersions["System.ValueTuple"].FirstOrDefault().Item1;
SkiaSharpVersion = packageVersions["SkiaSharp"].FirstOrDefault().Item1; SkiaSharpVersion = packageVersions["SkiaSharp"].FirstOrDefault().Item1;
SkiaSharpLinuxVersion = packageVersions["Avalonia.Skia.Linux.Natives"].FirstOrDefault().Item1;
var SharpDXVersion = packageVersions["SharpDX"].FirstOrDefault().Item1; var SharpDXVersion = packageVersions["SharpDX"].FirstOrDefault().Item1;
var SharpDXDirect2D1Version = packageVersions["SharpDX.Direct2D1"].FirstOrDefault().Item1; var SharpDXDirect2D1Version = packageVersions["SharpDX.Direct2D1"].FirstOrDefault().Item1;
var SharpDXDirect3D11Version = packageVersions["SharpDX.Direct3D11"].FirstOrDefault().Item1; var SharpDXDirect3D11Version = packageVersions["SharpDX.Direct3D11"].FirstOrDefault().Item1;
@ -84,7 +87,9 @@ public class Packages
context.Information("Package: Splat, version: {0}", SplatVersion); context.Information("Package: Splat, version: {0}", SplatVersion);
context.Information("Package: Sprache, version: {0}", SpracheVersion); context.Information("Package: Sprache, version: {0}", SpracheVersion);
context.Information("Package: System.Reactive, version: {0}", SystemReactiveVersion); context.Information("Package: System.Reactive, version: {0}", SystemReactiveVersion);
context.Information("Package: System.ValueTuple, version: {0}", SystemValueTupleVersion);
context.Information("Package: SkiaSharp, version: {0}", SkiaSharpVersion); context.Information("Package: SkiaSharp, version: {0}", SkiaSharpVersion);
context.Information("Package: Avalonia.Skia.Linux.Natives, version: {0}", SkiaSharpLinuxVersion);
context.Information("Package: SharpDX, version: {0}", SharpDXVersion); context.Information("Package: SharpDX, version: {0}", SharpDXVersion);
context.Information("Package: SharpDX.Direct2D1, version: {0}", SharpDXDirect2D1Version); context.Information("Package: SharpDX.Direct2D1, version: {0}", SharpDXDirect2D1Version);
context.Information("Package: SharpDX.Direct3D11, version: {0}", SharpDXDirect3D11Version); context.Information("Package: SharpDX.Direct3D11, version: {0}", SharpDXDirect3D11Version);
@ -194,6 +199,7 @@ public class Packages
new NuSpecDependency() { Id = "Splat", Version = SplatVersion }, new NuSpecDependency() { Id = "Splat", Version = SplatVersion },
new NuSpecDependency() { Id = "Sprache", Version = SpracheVersion }, new NuSpecDependency() { Id = "Sprache", Version = SpracheVersion },
new NuSpecDependency() { Id = "System.Reactive", Version = SystemReactiveVersion }, new NuSpecDependency() { Id = "System.Reactive", Version = SystemReactiveVersion },
new NuSpecDependency() { Id = "System.ValueTuple", Version = SystemValueTupleVersion },
//.NET Core //.NET Core
new NuSpecDependency() { Id = "System.Threading.ThreadPool", TargetFramework = "netcoreapp1.0", Version = "4.3.0" }, new NuSpecDependency() { Id = "System.Threading.ThreadPool", TargetFramework = "netcoreapp1.0", Version = "4.3.0" },
new NuSpecDependency() { Id = "Microsoft.Extensions.DependencyModel", TargetFramework = "netcoreapp1.0", Version = "1.1.0" }, new NuSpecDependency() { Id = "Microsoft.Extensions.DependencyModel", TargetFramework = "netcoreapp1.0", Version = "1.1.0" },
@ -201,7 +207,8 @@ public class Packages
new NuSpecDependency() { Id = "Splat", TargetFramework = "netcoreapp1.0", Version = SplatVersion }, new NuSpecDependency() { Id = "Splat", TargetFramework = "netcoreapp1.0", Version = SplatVersion },
new NuSpecDependency() { Id = "Serilog", TargetFramework = "netcoreapp1.0", Version = SerilogVersion }, new NuSpecDependency() { Id = "Serilog", TargetFramework = "netcoreapp1.0", Version = SerilogVersion },
new NuSpecDependency() { Id = "Sprache", TargetFramework = "netcoreapp1.0", Version = SpracheVersion }, new NuSpecDependency() { Id = "Sprache", TargetFramework = "netcoreapp1.0", Version = SpracheVersion },
new NuSpecDependency() { Id = "System.Reactive", TargetFramework = "netcoreapp1.0", Version = SystemReactiveVersion } new NuSpecDependency() { Id = "System.Reactive", TargetFramework = "netcoreapp1.0", Version = SystemReactiveVersion },
new NuSpecDependency() { Id = "System.ValueTuple", TargetFramework = "netcoreapp1.0", Version = SystemValueTupleVersion }
}, },
Files = coreLibrariesNuSpecContent Files = coreLibrariesNuSpecContent
.Concat(win32CoreLibrariesNuSpecContent).Concat(net45RuntimePlatform) .Concat(win32CoreLibrariesNuSpecContent).Concat(net45RuntimePlatform)
@ -425,10 +432,7 @@ public class Packages
{ {
new NuSpecDependency() { Id = "Avalonia", Version = parameters.Version }, new NuSpecDependency() { Id = "Avalonia", Version = parameters.Version },
new NuSpecDependency() { Id = "SkiaSharp", Version = SkiaSharpVersion }, new NuSpecDependency() { Id = "SkiaSharp", Version = SkiaSharpVersion },
//netstandard1.3 new NuSpecDependency() { Id = "Avalonia.Skia.Linux.Natives", Version = SkiaSharpLinuxVersion }
new NuSpecDependency() { Id = "Avalonia", TargetFramework = "netstandard1.3", Version = parameters.Version },
new NuSpecDependency() { Id = "SkiaSharp", TargetFramework = "netstandard1.3", Version = SkiaSharpVersion },
new NuSpecDependency() { Id = "NETStandard.Library", TargetFramework = "netstandard1.3", Version = "1.6.0" }
}, },
Files = new [] Files = new []
{ {
@ -446,11 +450,17 @@ public class Packages
Id = "Avalonia.Desktop", Id = "Avalonia.Desktop",
Dependencies = new [] Dependencies = new []
{ {
new NuSpecDependency() { Id = "Avalonia.Win32", Version = parameters.Version }, //Full .NET
new NuSpecDependency() { Id = "Avalonia.Direct2D1", Version = parameters.Version }, new NuSpecDependency() { Id = "Avalonia.Direct2D1", TargetFramework="net45", Version = parameters.Version },
new NuSpecDependency() { Id = "Avalonia.Gtk", Version = parameters.Version }, new NuSpecDependency() { Id = "Avalonia.Gtk", TargetFramework="net45", Version = parameters.Version },
new NuSpecDependency() { Id = "Avalonia.Cairo", Version = parameters.Version }, new NuSpecDependency() { Id = "Avalonia.Cairo", TargetFramework="net45", Version = parameters.Version },
new NuSpecDependency() { Id = "Avalonia.Skia.Desktop", Version = parameters.Version } new NuSpecDependency() { Id = "Avalonia.Win32", TargetFramework="net45", Version = parameters.Version },
new NuSpecDependency() { Id = "Avalonia.Skia.Desktop", TargetFramework="net45", Version = parameters.Version },
new NuSpecDependency() { Id = "Avalonia.Gtk3", TargetFramework="net45", Version = parameters.Version },
//.NET Core
new NuSpecDependency() { Id = "Avalonia.Win32", TargetFramework="netcoreapp1.1", Version = parameters.Version },
new NuSpecDependency() { Id = "Avalonia.Skia.Desktop", TargetFramework="netcoreapp1.1", Version = parameters.Version },
new NuSpecDependency() { Id = "Avalonia.Gtk3", TargetFramework="netcoreapp1.1", Version = parameters.Version }
}, },
Files = new NuSpecContent[] Files = new NuSpecContent[]
{ {
@ -459,6 +469,21 @@ public class Packages
BasePath = context.Directory("./"), BasePath = context.Directory("./"),
OutputDirectory = parameters.NugetRoot OutputDirectory = parameters.NugetRoot
}, },
new NuGetPackSettings()
{
Id = "Avalonia.Win32.Interoperability",
Dependencies = new []
{
new NuSpecDependency() { Id = "Avalonia.Win32", Version = parameters.Version },
new NuSpecDependency() { Id = "Avalonia.Direct2D1", Version = parameters.Version },
},
Files = new []
{
new NuSpecContent { Source = "Avalonia.Win32.Interop/bin/" + parameters.DirSuffix + "/Avalonia.Win32.Interop.dll", Target = "lib/net45" }
},
BasePath = context.Directory("./src/Windows"),
OutputDirectory = parameters.NugetRoot
},
/////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////
// Avalonia.LinuxFramebuffer // Avalonia.LinuxFramebuffer
/////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////

14
parameters.cake

@ -75,17 +75,25 @@ public class Parameters
IsReleasable = StringComparer.OrdinalIgnoreCase.Equals(ReleasePlatform, Platform) IsReleasable = StringComparer.OrdinalIgnoreCase.Equals(ReleasePlatform, Platform)
&& StringComparer.OrdinalIgnoreCase.Equals(ReleaseConfiguration, Configuration); && StringComparer.OrdinalIgnoreCase.Equals(ReleaseConfiguration, Configuration);
IsMyGetRelease = !IsTagged && IsReleasable; IsMyGetRelease = !IsTagged && IsReleasable;
IsNuGetRelease = IsTagged && IsReleasable;
// VERSION // VERSION
Version = context.Argument("force-nuget-version", context.ParseAssemblyInfo(AssemblyInfoPath).AssemblyVersion); Version = context.Argument("force-nuget-version", context.ParseAssemblyInfo(AssemblyInfoPath).AssemblyVersion);
if (IsRunningOnAppVeyor) if (IsRunningOnAppVeyor)
{ {
string tagVersion = null;
if (IsTagged) if (IsTagged)
{ {
// Use Tag Name as version var tag = buildSystem.AppVeyor.Environment.Repository.Tag.Name;
Version = buildSystem.AppVeyor.Environment.Repository.Tag.Name; var nugetReleasePrefix = "nuget-release-";
IsNuGetRelease = IsTagged && IsReleasable && tag.StartsWith(nugetReleasePrefix);
if(IsNuGetRelease)
tagVersion = tag.Substring(nugetReleasePrefix.Length);
}
if(tagVersion != null)
{
Version = tagVersion;
} }
else else
{ {

2
readme.md

@ -42,7 +42,7 @@ using Direct2D and other operating systems using Gtk & Cairo.
Avalonia is now in alpha. What does "alpha" mean? Well, it means that it's now at a stage where you Avalonia is now in alpha. What does "alpha" mean? Well, it means that it's now at a stage where you
can have a play and hopefully create simple applications. There's now a [Visual can have a play and hopefully create simple applications. There's now a [Visual
Studio Extension](https://visualstudiogallery.msdn.microsoft.com/e1c6ae1f-6fd9-467d-8f62-1e28b4225213) Studio Extension](https://marketplace.visualstudio.com/items?itemName=AvaloniaTeam.AvaloniaforVisualStudio)
containing project and item templates that will help you get started, and containing project and item templates that will help you get started, and
there's an initial complement of controls. There's still a lot missing, and you there's an initial complement of controls. There's still a lot missing, and you
*will* find bugs, and the API *will* change, but this represents the first time *will* find bugs, and the API *will* change, but this represents the first time

3
samples/ControlCatalog.Desktop/ControlCatalog.Desktop.csproj

@ -26,7 +26,7 @@
<WarningLevel>4</WarningLevel> <WarningLevel>4</WarningLevel>
</PropertyGroup> </PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' "> <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
<PlatformTarget>AnyCPU</PlatformTarget> <PlatformTarget>x86</PlatformTarget>
<DebugType>pdbonly</DebugType> <DebugType>pdbonly</DebugType>
<Optimize>true</Optimize> <Optimize>true</Optimize>
<OutputPath>bin\Release\</OutputPath> <OutputPath>bin\Release\</OutputPath>
@ -142,6 +142,7 @@
</ItemGroup> </ItemGroup>
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" /> <Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
<Import Project="..\..\build\Serilog.props" /> <Import Project="..\..\build\Serilog.props" />
<Import Project="..\..\build\SkiaSharp.props" />
<Import Project="..\..\build\Serilog.Sinks.Trace.props" /> <Import Project="..\..\build\Serilog.Sinks.Trace.props" />
<Import Project="$(MSBuildThisFileDirectory)..\..\src\Shared\nuget.workaround.targets" /> <Import Project="$(MSBuildThisFileDirectory)..\..\src\Shared\nuget.workaround.targets" />
</Project> </Project>

23
samples/ControlCatalog/Pages/MenuPage.xaml

@ -31,5 +31,28 @@
</MenuItem> </MenuItem>
</Menu> </Menu>
</StackPanel> </StackPanel>
<TextBlock Classes="h2" Text="A context menu (right click)">
<TextBlock.ContextMenu>
<ContextMenu>
<MenuItem Header="Standard _Menu Item"/>
<Separator/>
<MenuItem Header="Menu with _Submenu">
<MenuItem Header="Submenu _1"/>
<MenuItem Header="Submenu _2"/>
</MenuItem>
<MenuItem Header="Menu Item with _Icon">
<MenuItem.Icon>
<Image Source="resm:ControlCatalog.Assets.github_icon.png"/>
</MenuItem.Icon>
</MenuItem>
<MenuItem Header="Menu Item with _Checkbox">
<MenuItem.Icon>
<CheckBox BorderThickness="0" IsHitTestVisible="False" IsChecked="True"/>
</MenuItem.Icon>
</MenuItem>
</ContextMenu>
</TextBlock.ContextMenu>
</TextBlock>
</StackPanel> </StackPanel>
</UserControl> </UserControl>

1
samples/interop/Direct3DInteropSample/Direct3DInteropSample.csproj

@ -22,6 +22,7 @@
<ItemGroup> <ItemGroup>
<ProjectReference Include="..\..\..\src\Avalonia.DesignerSupport\Avalonia.DesignerSupport.csproj" /> <ProjectReference Include="..\..\..\src\Avalonia.DesignerSupport\Avalonia.DesignerSupport.csproj" />
<ProjectReference Include="..\..\..\src\Avalonia.DotNetFrameworkRuntime\Avalonia.DotNetFrameworkRuntime.csproj" /> <ProjectReference Include="..\..\..\src\Avalonia.DotNetFrameworkRuntime\Avalonia.DotNetFrameworkRuntime.csproj" />
<ProjectReference Include="..\..\..\src\Avalonia.ReactiveUI\Avalonia.ReactiveUI.csproj" />
<ProjectReference Include="..\..\..\src\Avalonia.Themes.Default\Avalonia.Themes.Default.csproj" /> <ProjectReference Include="..\..\..\src\Avalonia.Themes.Default\Avalonia.Themes.Default.csproj" />
<ProjectReference Include="..\..\..\src\Windows\Avalonia.Direct2D1\Avalonia.Direct2D1.csproj" /> <ProjectReference Include="..\..\..\src\Windows\Avalonia.Direct2D1\Avalonia.Direct2D1.csproj" />
<ProjectReference Include="..\..\..\src\Windows\Avalonia.Win32\Avalonia.Win32.csproj" /> <ProjectReference Include="..\..\..\src\Windows\Avalonia.Win32\Avalonia.Win32.csproj" />

2
samples/interop/Direct3DInteropSample/MainWindow.cs

@ -58,7 +58,7 @@ namespace Direct3DInteropSample
new ModeDescription((int)ClientSize.Width, (int)ClientSize.Height, new ModeDescription((int)ClientSize.Width, (int)ClientSize.Height,
new Rational(60, 1), Format.R8G8B8A8_UNorm), new Rational(60, 1), Format.R8G8B8A8_UNorm),
IsWindowed = true, IsWindowed = true,
OutputHandle = PlatformImpl.Handle.Handle, OutputHandle = PlatformImpl?.Handle.Handle ?? IntPtr.Zero,
SampleDescription = new SampleDescription(1, 0), SampleDescription = new SampleDescription(1, 0),
SwapEffect = SwapEffect.Discard, SwapEffect = SwapEffect.Discard,
Usage = Usage.RenderTargetOutput Usage = Usage.RenderTargetOutput

1
samples/interop/GtkInteropDemo/GtkInteropDemo.csproj

@ -149,6 +149,7 @@
<Name>ControlCatalog</Name> <Name>ControlCatalog</Name>
</ProjectReference> </ProjectReference>
</ItemGroup> </ItemGroup>
<Import Project="..\..\..\build\Rx.props" />
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" /> <Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
<Import Project="$(MSBuildThisFileDirectory)..\..\..\src\Shared\nuget.workaround.targets" /> <Import Project="$(MSBuildThisFileDirectory)..\..\..\src\Shared\nuget.workaround.targets" />
</Project> </Project>

14
samples/interop/WindowsInteropTest/EmbedToWpfDemo.xaml

@ -1,10 +1,12 @@
<Window x:Class="WindowsInteropTest.EmbedToWpfDemo" <Window x:Class="WindowsInteropTest.EmbedToWpfDemo"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:av="clr-namespace:Avalonia.Controls;assembly=Avalonia.Controls"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="clr-namespace:WindowsInteropTest" xmlns:local="clr-namespace:WindowsInteropTest"
xmlns:embedding="clr-namespace:Avalonia.Win32.Embedding;assembly=Avalonia.Win32" xmlns:embedding="clr-namespace:Avalonia.Win32.Embedding;assembly=Avalonia.Win32"
xmlns:wpf="clr-namespace:Avalonia.Win32.Interop.Wpf;assembly=Avalonia.Win32.Interop"
mc:Ignorable="d" mc:Ignorable="d"
d:DesignHeight="400" d:DesignWidth="400" MinWidth="500" MinHeight="400"> d:DesignHeight="400" d:DesignWidth="400" MinWidth="500" MinHeight="400">
<DockPanel> <DockPanel>
@ -14,8 +16,18 @@
<Calendar/> <Calendar/>
</StackPanel> </StackPanel>
</GroupBox> </GroupBox>
<GroupBox Header="Avalonia button" DockPanel.Dock="Bottom">
<wpf:WpfAvaloniaHost >
<av:Button Content="Avalonia button"/>
</wpf:WpfAvaloniaHost>
</GroupBox>
<GroupBox Header="AvBtn" DockPanel.Dock="Right">
<wpf:WpfAvaloniaHost x:Name="RightBtn">
<av:Button Content="Avalonia button 2"/>
</wpf:WpfAvaloniaHost>
</GroupBox>
<GroupBox Header="Avalonia"> <GroupBox Header="Avalonia">
<embedding:WpfAvaloniaControlHost x:Name="Host"/> <wpf:WpfAvaloniaHost x:Name="Host"/>
</GroupBox> </GroupBox>
</DockPanel> </DockPanel>
</Window> </Window>

15
samples/interop/WindowsInteropTest/EmbedToWpfDemo.xaml.cs

@ -11,7 +11,9 @@ using System.Windows.Media;
using System.Windows.Media.Imaging; using System.Windows.Media.Imaging;
using System.Windows.Navigation; using System.Windows.Navigation;
using System.Windows.Shapes; using System.Windows.Shapes;
using Avalonia;
using Avalonia.Controls; using Avalonia.Controls;
using Avalonia.VisualTree;
using ControlCatalog; using ControlCatalog;
using Window = System.Windows.Window; using Window = System.Windows.Window;
@ -25,7 +27,18 @@ namespace WindowsInteropTest
public EmbedToWpfDemo() public EmbedToWpfDemo()
{ {
InitializeComponent(); InitializeComponent();
Host.Content = new MainView(); var view = new MainView();
view.AttachedToVisualTree += delegate
{
((TopLevel) view.GetVisualRoot()).AttachDevTools();
};
Host.Content = view;
var btn = (Avalonia.Controls.Button) RightBtn.Content;
btn.Click += delegate
{
btn.Content += "!";
};
} }
} }
} }

8
samples/interop/WindowsInteropTest/WindowsInteropTest.csproj

@ -14,7 +14,7 @@
<AutoGenerateBindingRedirects>true</AutoGenerateBindingRedirects> <AutoGenerateBindingRedirects>true</AutoGenerateBindingRedirects>
</PropertyGroup> </PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' "> <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
<PlatformTarget>AnyCPU</PlatformTarget> <PlatformTarget>x86</PlatformTarget>
<DebugSymbols>true</DebugSymbols> <DebugSymbols>true</DebugSymbols>
<DebugType>full</DebugType> <DebugType>full</DebugType>
<Optimize>false</Optimize> <Optimize>false</Optimize>
@ -164,6 +164,10 @@
<Project>{3e908f67-5543-4879-a1dc-08eace79b3cd}</Project> <Project>{3e908f67-5543-4879-a1dc-08eace79b3cd}</Project>
<Name>Avalonia.Direct2D1</Name> <Name>Avalonia.Direct2D1</Name>
</ProjectReference> </ProjectReference>
<ProjectReference Include="..\..\..\src\Windows\Avalonia.Win32.Interop\Avalonia.Win32.Interop.csproj">
<Project>{cbc4ff2f-92d4-420b-be21-9fe0b930b04e}</Project>
<Name>Avalonia.Win32.Interop</Name>
</ProjectReference>
<ProjectReference Include="..\..\..\src\Windows\Avalonia.Win32\Avalonia.Win32.csproj"> <ProjectReference Include="..\..\..\src\Windows\Avalonia.Win32\Avalonia.Win32.csproj">
<Project>{811a76cf-1cf6-440f-963b-bbe31bd72a82}</Project> <Project>{811a76cf-1cf6-440f-963b-bbe31bd72a82}</Project>
<Name>Avalonia.Win32</Name> <Name>Avalonia.Win32</Name>
@ -179,6 +183,8 @@
<Generator>MSBuild:Compile</Generator> <Generator>MSBuild:Compile</Generator>
</Page> </Page>
</ItemGroup> </ItemGroup>
<Import Project="..\..\..\build\Rx.props" />
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" /> <Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
<Import Project="..\..\..\build\SkiaSharp.props" />
<Import Project="$(MSBuildThisFileDirectory)..\..\..\src\Shared\nuget.workaround.targets" /> <Import Project="$(MSBuildThisFileDirectory)..\..\..\src\Shared\nuget.workaround.targets" />
</Project> </Project>

5
scripts/ReplaceNugetCache.ps1

@ -0,0 +1,5 @@
copy ..\samples\ControlCatalog.NetCore\bin\Debug\netcoreapp1.1\Avalonia**.dll ~\.nuget\packages\avalonia\$args\lib\netcoreapp1.0\
copy ..\samples\ControlCatalog.NetCore.\bin\Debug\netcoreapp1.1\Avalonia**.dll ~\.nuget\packages\avalonia\$args\lib\netstandard1.1\
copy ..\samples\ControlCatalog.NetCore.\bin\Debug\netcoreapp1.1\Avalonia**.dll ~\.nuget\packages\avalonia.gtk3\$args\lib\netstandard1.1\
copy ..\samples\ControlCatalog.NetCore.\bin\Debug\netcoreapp1.1\Avalonia**.dll ~\.nuget\packages\avalonia.skia.desktop\$args\lib\netstandard1.3\
copy ..\samples\ControlCatalog.NetCore.\bin\Debug\netcoreapp1.1\Avalonia**.dll ~\.nuget\packages\avalonia.win32\$args\lib\netstandard1.1\

7
scripts/ReplaceNugetCache.sh

@ -0,0 +1,7 @@
#!/usr/bin/env bash
cp ../samples/ControlCatalog.NetCore/bin/Debug/netcoreapp1.1/Avalonia**.dll ~/.nuget/packages/avalonia/$1/lib/netcoreapp1.0/
cp ../samples/ControlCatalog.NetCore/bin/Debug/netcoreapp1.1/Avalonia**.dll ~/.nuget/packages/avalonia/$1/lib/netstandard1.1/
cp ../samples/ControlCatalog.NetCore/bin/Debug/netcoreapp1.1/Avalonia**.dll ~/.nuget/packages/avalonia.gtk3/$1/lib/netstandard1.1/
cp ../samples/ControlCatalog.NetCore/bin/Debug/netcoreapp1.1/Avalonia**.dll ~/.nuget/packages/avalonia.skia.desktop/$1/lib/netstandard1.3/

1
src/Android/Avalonia.Android/AndroidPlatform.cs

@ -51,7 +51,6 @@ namespace Avalonia.Android
.Bind<IClipboard>().ToTransient<ClipboardImpl>() .Bind<IClipboard>().ToTransient<ClipboardImpl>()
.Bind<IStandardCursorFactory>().ToTransient<CursorFactory>() .Bind<IStandardCursorFactory>().ToTransient<CursorFactory>()
.Bind<IKeyboardDevice>().ToSingleton<AndroidKeyboardDevice>() .Bind<IKeyboardDevice>().ToSingleton<AndroidKeyboardDevice>()
.Bind<IMouseDevice>().ToSingleton<AndroidMouseDevice>()
.Bind<IPlatformSettings>().ToConstant(Instance) .Bind<IPlatformSettings>().ToConstant(Instance)
.Bind<IRendererFactory>().ToConstant(ImmediateRenderer.Factory) .Bind<IRendererFactory>().ToConstant(ImmediateRenderer.Factory)
.Bind<IPlatformThreadingInterface>().ToConstant(new AndroidThreadingInterface()) .Bind<IPlatformThreadingInterface>().ToConstant(new AndroidThreadingInterface())

2
src/Android/Avalonia.Android/Platform/Input/AndroidMouseDevice.cs

@ -4,6 +4,8 @@ namespace Avalonia.Android.Platform.Input
{ {
public class AndroidMouseDevice : MouseDevice public class AndroidMouseDevice : MouseDevice
{ {
public static AndroidMouseDevice Instance { get; } = new AndroidMouseDevice();
public AndroidMouseDevice() public AndroidMouseDevice()
{ {

2
src/Android/Avalonia.Android/Platform/SkiaPlatform/AndroidFramebuffer.cs

@ -44,7 +44,7 @@ namespace Avalonia.Android.Platform.SkiaPlatform
public int Width { get; } public int Width { get; }
public int Height { get; } public int Height { get; }
public int RowBytes { get; } public int RowBytes { get; }
public Size Dpi { get; } = new Size(96, 96); public Vector Dpi { get; } = new Vector(96, 96);
public PixelFormat Format { get; } public PixelFormat Format { get; }
[DllImport("android")] [DllImport("android")]

3
src/Android/Avalonia.Android/Platform/SkiaPlatform/TopLevelImpl.cs

@ -10,6 +10,7 @@ using Avalonia.Platform;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Reactive.Disposables; using System.Reactive.Disposables;
using Avalonia.Android.Platform.Input;
using Avalonia.Controls; using Avalonia.Controls;
using Avalonia.Controls.Platform.Surfaces; using Avalonia.Controls.Platform.Surfaces;
@ -65,6 +66,8 @@ namespace Avalonia.Android.Platform.SkiaPlatform
} }
} }
public IMouseDevice MouseDevice => AndroidMouseDevice.Instance;
public Action Closed { get; set; } public Action Closed { get; set; }
public Action<RawInputEventArgs> Input { get; set; } public Action<RawInputEventArgs> Input { get; set; }

2
src/Android/Avalonia.Android/Platform/Specific/Helpers/AndroidTouchEventsHelper.cs

@ -71,7 +71,7 @@ namespace Avalonia.Android.Platform.Specific.Helpers
if (x <= _point.X && r >= _point.X && y <= _point.Y && b >= _point.Y) if (x <= _point.X && r >= _point.X && y <= _point.Y && b >= _point.Y)
{ {
var inputRoot = _getInputRoot(); var inputRoot = _getInputRoot();
var mouseDevice = MouseDevice.Instance; var mouseDevice = Avalonia.Android.Platform.Input.AndroidMouseDevice.Instance;
//in order the controls to work in a predictable way //in order the controls to work in a predictable way
//we need to generate mouse move before first mouse down event //we need to generate mouse move before first mouse down event

1
src/Avalonia.Base/Avalonia.Base.csproj

@ -30,6 +30,7 @@
<Link>Properties\SharedAssemblyInfo.cs</Link> <Link>Properties\SharedAssemblyInfo.cs</Link>
</Compile> </Compile>
</ItemGroup> </ItemGroup>
<Import Project="..\..\build\Base.props" />
<Import Project="..\..\build\Rx.props" /> <Import Project="..\..\build\Rx.props" />
<Import Project="..\..\build\JetBrains.Annotations.props" /> <Import Project="..\..\build\JetBrains.Annotations.props" />
</Project> </Project>

11
src/Avalonia.Base/AvaloniaObject.cs

@ -622,14 +622,9 @@ namespace Avalonia
/// <returns>The default value.</returns> /// <returns>The default value.</returns>
private object GetDefaultValue(AvaloniaProperty property) private object GetDefaultValue(AvaloniaProperty property)
{ {
if (property.Inherits && _inheritanceParent != null) if (property.Inherits && _inheritanceParent is AvaloniaObject aobj)
{ return aobj.GetValueInternal(property);
return (_inheritanceParent as AvaloniaObject).GetValueInternal(property); return ((IStyledPropertyAccessor) property).GetDefaultValue(GetType());
}
else
{
return ((IStyledPropertyAccessor)property).GetDefaultValue(GetType());
}
} }
/// <summary> /// <summary>

25
src/Avalonia.Base/Collections/AvaloniaDictionary.cs

@ -103,11 +103,9 @@ namespace Avalonia.Collections
_inner = new Dictionary<TKey, TValue>(); _inner = new Dictionary<TKey, TValue>();
if (PropertyChanged != null) PropertyChanged?.Invoke(this, new PropertyChangedEventArgs("Count"));
{ PropertyChanged?.Invoke(this, new PropertyChangedEventArgs($"Item[]"));
PropertyChanged(this, new PropertyChangedEventArgs("Count"));
PropertyChanged(this, new PropertyChangedEventArgs($"Item[]"));
}
if (CollectionChanged != null) if (CollectionChanged != null)
{ {
@ -144,12 +142,9 @@ namespace Avalonia.Collections
if (_inner.TryGetValue(key, out value)) if (_inner.TryGetValue(key, out value))
{ {
if (PropertyChanged != null) PropertyChanged?.Invoke(this, new PropertyChangedEventArgs("Count"));
{ PropertyChanged?.Invoke(this, new PropertyChangedEventArgs($"Item[{key}]"));
PropertyChanged(this, new PropertyChangedEventArgs("Count"));
PropertyChanged(this, new PropertyChangedEventArgs($"Item[{key}]"));
}
if (CollectionChanged != null) if (CollectionChanged != null)
{ {
var e = new NotifyCollectionChangedEventArgs( var e = new NotifyCollectionChangedEventArgs(
@ -199,11 +194,9 @@ namespace Avalonia.Collections
private void NotifyAdd(TKey key, TValue value) private void NotifyAdd(TKey key, TValue value)
{ {
if (PropertyChanged != null) PropertyChanged?.Invoke(this, new PropertyChangedEventArgs("Count"));
{ PropertyChanged?.Invoke(this, new PropertyChangedEventArgs($"Item[{key}]"));
PropertyChanged(this, new PropertyChangedEventArgs("Count"));
PropertyChanged(this, new PropertyChangedEventArgs($"Item[{key}]"));
}
if (CollectionChanged != null) if (CollectionChanged != null)
{ {

123
src/Avalonia.Base/Collections/AvaloniaListExtensions.cs

@ -2,6 +2,7 @@
// Licensed under the MIT license. See licence.md file in the project root for full license information. // Licensed under the MIT license. See licence.md file in the project root for full license information.
using System; using System;
using System.Collections;
using System.Collections.Generic; using System.Collections.Generic;
using System.Collections.Specialized; using System.Collections.Specialized;
using System.ComponentModel; using System.ComponentModel;
@ -59,7 +60,8 @@ namespace Avalonia.Collections
/// the index in the collection and the item. /// the index in the collection and the item.
/// </param> /// </param>
/// <param name="reset"> /// <param name="reset">
/// An action called when the collection is reset. /// An action called when the collection is reset. This will be followed by calls to
/// <paramref name="added"/> for each item present in the collection after the reset.
/// </param> /// </param>
/// <returns>A disposable used to terminate the subscription.</returns> /// <returns>A disposable used to terminate the subscription.</returns>
public static IDisposable ForEachItem<T>( public static IDisposable ForEachItem<T>(
@ -68,112 +70,38 @@ namespace Avalonia.Collections
Action<int, T> removed, Action<int, T> removed,
Action reset) Action reset)
{ {
int index; void Add(int index, IList items)
NotifyCollectionChangedEventHandler handler = (_, e) =>
{ {
switch (e.Action) foreach (T item in items)
{ {
case NotifyCollectionChangedAction.Add: added(index++, item);
index = e.NewStartingIndex;
foreach (T item in e.NewItems)
{
added(index++, item);
}
break;
case NotifyCollectionChangedAction.Replace:
index = e.OldStartingIndex;
foreach (T item in e.OldItems)
{
removed(index++, item);
}
index = e.NewStartingIndex;
foreach (T item in e.NewItems)
{
added(index++, item);
}
break;
case NotifyCollectionChangedAction.Remove:
index = e.OldStartingIndex;
foreach (T item in e.OldItems)
{
removed(index++, item);
}
break;
case NotifyCollectionChangedAction.Reset:
if (reset == null)
{
throw new InvalidOperationException(
"Reset called on collection without reset handler.");
}
reset();
break;
} }
}; }
index = 0; void Remove(int index, IList items)
foreach (T i in collection)
{ {
added(index++, i); for (var i = items.Count - 1; i >= 0; --i)
{
removed(index + i, (T)items[i]);
}
} }
collection.CollectionChanged += handler;
return Disposable.Create(() => collection.CollectionChanged -= handler);
}
/// <summary>
/// Invokes an action for each item in a collection and subsequently each item added or
/// removed from the collection.
/// </summary>
/// <typeparam name="T">The type of the collection items.</typeparam>
/// <param name="collection">The collection.</param>
/// <param name="added">
/// An action called initially with all items in the collection and subsequently with a
/// list of items added to the collection. The parameters passed are the index of the
/// first item added to the collection and the items added.
/// </param>
/// <param name="removed">
/// An action called with all items removed from the collection. The parameters passed
/// are the index of the first item removed from the collection and the items removed.
/// </param>
/// <param name="reset">
/// An action called when the collection is reset.
/// </param>
/// <returns>A disposable used to terminate the subscription.</returns>
public static IDisposable ForEachItem<T>(
this IAvaloniaReadOnlyList<T> collection,
Action<int, IEnumerable<T>> added,
Action<int, IEnumerable<T>> removed,
Action reset)
{
NotifyCollectionChangedEventHandler handler = (_, e) => NotifyCollectionChangedEventHandler handler = (_, e) =>
{ {
switch (e.Action) switch (e.Action)
{ {
case NotifyCollectionChangedAction.Add: case NotifyCollectionChangedAction.Add:
added(e.NewStartingIndex, e.NewItems.Cast<T>()); Add(e.NewStartingIndex, e.NewItems);
break; break;
case NotifyCollectionChangedAction.Move:
case NotifyCollectionChangedAction.Replace: case NotifyCollectionChangedAction.Replace:
removed(e.OldStartingIndex, e.OldItems.Cast<T>()); Remove(e.OldStartingIndex, e.OldItems);
added(e.NewStartingIndex, e.NewItems.Cast<T>()); Add(e.NewStartingIndex, e.NewItems);
break; break;
case NotifyCollectionChangedAction.Remove: case NotifyCollectionChangedAction.Remove:
removed(e.OldStartingIndex, e.OldItems.Cast<T>()); Remove(e.OldStartingIndex, e.OldItems);
break; break;
case NotifyCollectionChangedAction.Reset: case NotifyCollectionChangedAction.Reset:
@ -184,16 +112,31 @@ namespace Avalonia.Collections
} }
reset(); reset();
Add(0, (IList)collection);
break; break;
} }
}; };
added(0, collection); Add(0, (IList)collection);
collection.CollectionChanged += handler; collection.CollectionChanged += handler;
return Disposable.Create(() => collection.CollectionChanged -= handler); return Disposable.Create(() => collection.CollectionChanged -= handler);
} }
public static IAvaloniaReadOnlyList<TDerived> CreateDerivedList<TSource, TDerived>(
this IAvaloniaReadOnlyList<TSource> collection,
Func<TSource, TDerived> select)
{
var result = new AvaloniaList<TDerived>();
collection.ForEachItem(
(i, item) => result.Insert(i, select(item)),
(i, item) => result.RemoveAt(i),
() => result.Clear());
return result;
}
/// <summary> /// <summary>
/// Listens for property changed events from all items in a collection. /// Listens for property changed events from all items in a collection.
/// </summary> /// </summary>

38
src/Avalonia.Base/Data/BindingNotification.cs

@ -44,11 +44,7 @@ namespace Avalonia.Data
public static readonly BindingNotification UnsetValue = public static readonly BindingNotification UnsetValue =
new BindingNotification(AvaloniaProperty.UnsetValue); new BindingNotification(AvaloniaProperty.UnsetValue);
// Null cannot be held in WeakReference as it's indistinguishable from an expired value so private object _value;
// use this value in its place.
private static readonly object NullValue = new object();
private WeakReference<object> _value;
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="BindingNotification"/> class. /// Initializes a new instance of the <see cref="BindingNotification"/> class.
@ -56,7 +52,7 @@ namespace Avalonia.Data
/// <param name="value">The binding value.</param> /// <param name="value">The binding value.</param>
public BindingNotification(object value) public BindingNotification(object value)
{ {
_value = new WeakReference<object>(value ?? NullValue); _value = value;
} }
/// <summary> /// <summary>
@ -73,6 +69,7 @@ namespace Avalonia.Data
Error = error; Error = error;
ErrorType = errorType; ErrorType = errorType;
_value = AvaloniaProperty.UnsetValue;
} }
/// <summary> /// <summary>
@ -84,7 +81,7 @@ namespace Avalonia.Data
public BindingNotification(Exception error, BindingErrorType errorType, object fallbackValue) public BindingNotification(Exception error, BindingErrorType errorType, object fallbackValue)
: this(error, errorType) : this(error, errorType)
{ {
_value = new WeakReference<object>(fallbackValue ?? NullValue); _value = fallbackValue;
} }
/// <summary> /// <summary>
@ -95,31 +92,12 @@ namespace Avalonia.Data
/// If this property is read when <see cref="HasValue"/> is false then it will return /// If this property is read when <see cref="HasValue"/> is false then it will return
/// <see cref="AvaloniaProperty.UnsetValue"/>. /// <see cref="AvaloniaProperty.UnsetValue"/>.
/// </remarks> /// </remarks>
public object Value public object Value => _value;
{
get
{
if (_value != null)
{
object result;
if (_value.TryGetTarget(out result))
{
return result == NullValue ? null : result;
}
}
// There's the possibility of a race condition in that HasValue can return true,
// and then the value is GC'd before Value is read. We should be ok though as
// we return UnsetValue which should be a safe alternative.
return AvaloniaProperty.UnsetValue;
}
}
/// <summary> /// <summary>
/// Gets a value indicating whether <see cref="Value"/> should be pushed to the target. /// Gets a value indicating whether <see cref="Value"/> should be pushed to the target.
/// </summary> /// </summary>
public bool HasValue => _value != null; public bool HasValue => _value != AvaloniaProperty.UnsetValue;
/// <summary> /// <summary>
/// Gets the error that occurred on the source, if any. /// Gets the error that occurred on the source, if any.
@ -248,7 +226,7 @@ namespace Avalonia.Data
/// </summary> /// </summary>
public void ClearValue() public void ClearValue()
{ {
_value = null; _value = AvaloniaProperty.UnsetValue;
} }
/// <summary> /// <summary>
@ -256,7 +234,7 @@ namespace Avalonia.Data
/// </summary> /// </summary>
public void SetValue(object value) public void SetValue(object value)
{ {
_value = new WeakReference<object>(value ?? NullValue); _value = value;
} }
/// <inheritdoc/> /// <inheritdoc/>

2
src/Avalonia.Base/PriorityValue.cs

@ -285,7 +285,7 @@ namespace Avalonia
Property.Name, Property.Name,
_valueType, _valueType,
value, value,
value.GetType()); value?.GetType());
} }
} }
} }

24
src/Avalonia.Controls/AppBuilderBase.cs

@ -55,8 +55,7 @@ namespace Avalonia.Controls
public Action<TAppBuilder> AfterSetupCallback { get; private set; } = builder => { }; public Action<TAppBuilder> AfterSetupCallback { get; private set; } = builder => { };
/// <summary> /// <summary>
/// Gets or sets a method to call before <see cref="Start{TMainWindow}"/> is called on the /// Gets or sets a method to call before Startis called on the <see cref="Application"/>.
/// <see cref="Application"/>.
/// </summary> /// </summary>
public Action<TAppBuilder> BeforeStartCallback { get; private set; } = builder => { }; public Action<TAppBuilder> BeforeStartCallback { get; private set; } = builder => { };
@ -94,8 +93,7 @@ namespace Avalonia.Controls
protected TAppBuilder Self => (TAppBuilder) this; protected TAppBuilder Self => (TAppBuilder) this;
/// <summary> /// <summary>
/// Registers a callback to call before <see cref="Start{TMainWindow}"/> is called on the /// Registers a callback to call before Start is called on the <see cref="Application"/>.
/// <see cref="Application"/>.
/// </summary> /// </summary>
/// <param name="callback">The callback.</param> /// <param name="callback">The callback.</param>
/// <returns>An <typeparamref name="TAppBuilder"/> instance.</returns> /// <returns>An <typeparamref name="TAppBuilder"/> instance.</returns>
@ -129,6 +127,24 @@ namespace Avalonia.Controls
Instance.Run(window); Instance.Run(window);
} }
/// <summary>
/// Starts the application with the provided instance of <typeparamref name="TMainWindow"/>.
/// </summary>
/// <typeparam name="TMainWindow">The window type.</typeparam>
/// <param name="mainWindow">Instance of type TMainWindow to use when starting the app</param>
/// <param name="dataContextProvider">A delegate that will be called to create a data context for the window (optional).</param>
public void Start<TMainWindow>(TMainWindow mainWindow, Func<object> dataContextProvider = null)
where TMainWindow : Window
{
Setup();
BeforeStartCallback(Self);
if (dataContextProvider != null)
mainWindow.DataContext = dataContextProvider();
mainWindow.Show();
Instance.Run(mainWindow);
}
/// <summary> /// <summary>
/// Sets up the platform-specific services for the application, but does not run it. /// Sets up the platform-specific services for the application, but does not run it.
/// </summary> /// </summary>

38
src/Avalonia.Controls/Button.cs

@ -207,7 +207,11 @@ namespace Avalonia.Controls
/// <param name="e">The event args.</param> /// <param name="e">The event args.</param>
protected virtual void OnClick(RoutedEventArgs e) protected virtual void OnClick(RoutedEventArgs e)
{ {
Command?.Execute(CommandParameter); if (Command != null)
{
Command.Execute(CommandParameter);
e.Handled = true;
}
} }
/// <inheritdoc/> /// <inheritdoc/>
@ -215,13 +219,16 @@ namespace Avalonia.Controls
{ {
base.OnPointerPressed(e); base.OnPointerPressed(e);
PseudoClasses.Add(":pressed"); if (e.MouseButton == MouseButton.Left)
e.Device.Capture(this);
e.Handled = true;
if (ClickMode == ClickMode.Press)
{ {
RaiseClickEvent(); PseudoClasses.Add(":pressed");
e.Device.Capture(this);
e.Handled = true;
if (ClickMode == ClickMode.Press)
{
RaiseClickEvent();
}
} }
} }
@ -230,13 +237,16 @@ namespace Avalonia.Controls
{ {
base.OnPointerReleased(e); base.OnPointerReleased(e);
e.Device.Capture(null); if (e.MouseButton == MouseButton.Left)
PseudoClasses.Remove(":pressed");
e.Handled = true;
if (ClickMode == ClickMode.Release && Classes.Contains(":pointerover"))
{ {
RaiseClickEvent(); e.Device.Capture(null);
PseudoClasses.Remove(":pressed");
e.Handled = true;
if (ClickMode == ClickMode.Release && new Rect(Bounds.Size).Contains(e.GetPosition(this)))
{
RaiseClickEvent();
}
} }
} }
@ -275,7 +285,7 @@ namespace Avalonia.Controls
{ {
var button = e.Sender as Button; var button = e.Sender as Button;
var isDefault = (bool)e.NewValue; var isDefault = (bool)e.NewValue;
var inputRoot = button.VisualRoot as IInputElement; var inputRoot = button?.VisualRoot as IInputElement;
if (inputRoot != null) if (inputRoot != null)
{ {

2
src/Avalonia.Controls/ContextMenu.cs

@ -19,7 +19,7 @@ namespace Avalonia.Controls
{ {
ContextMenuProperty.Changed.Subscribe(ContextMenuChanged); ContextMenuProperty.Changed.Subscribe(ContextMenuChanged);
MenuItem.ClickEvent.AddClassHandler<ContextMenu>(x => x.OnContextMenuClick); MenuItem.ClickEvent.AddClassHandler<ContextMenu>(x => x.OnContextMenuClick, handledEventsToo: true);
} }
/// <summary> /// <summary>

15
src/Avalonia.Controls/Control.cs

@ -118,6 +118,7 @@ namespace Avalonia.Controls
public Control() public Control()
{ {
_nameScope = this as INameScope; _nameScope = this as INameScope;
_isAttachedToLogicalTree = this is IStyleRoot;
} }
/// <summary> /// <summary>
@ -379,6 +380,12 @@ namespace Avalonia.Controls
} }
} }
/// <inheritdoc/>
void ILogical.NotifyAttachedToLogicalTree(LogicalTreeAttachmentEventArgs e)
{
this.OnAttachedToLogicalTreeCore(e);
}
/// <inheritdoc/> /// <inheritdoc/>
void ILogical.NotifyDetachedFromLogicalTree(LogicalTreeAttachmentEventArgs e) void ILogical.NotifyDetachedFromLogicalTree(LogicalTreeAttachmentEventArgs e)
{ {
@ -428,7 +435,7 @@ namespace Avalonia.Controls
if (_isAttachedToLogicalTree) if (_isAttachedToLogicalTree)
{ {
var oldRoot = FindStyleRoot(old); var oldRoot = FindStyleRoot(old) ?? this as IStyleRoot;
if (oldRoot == null) if (oldRoot == null)
{ {
@ -446,7 +453,7 @@ namespace Avalonia.Controls
_parent = (IControl)parent; _parent = (IControl)parent;
if (_parent is IStyleRoot || _parent?.IsAttachedToLogicalTree == true) if (_parent is IStyleRoot || _parent?.IsAttachedToLogicalTree == true || this is IStyleRoot)
{ {
var newRoot = FindStyleRoot(this); var newRoot = FindStyleRoot(this);
@ -479,7 +486,7 @@ namespace Avalonia.Controls
{ {
if (!IsInitialized) if (!IsInitialized)
{ {
foreach (var i in this.GetSelfAndVisualDescendents()) foreach (var i in this.GetSelfAndVisualDescendants())
{ {
var c = i as IControl; var c = i as IControl;
@ -651,7 +658,7 @@ namespace Avalonia.Controls
if (_focusAdorner != null) if (_focusAdorner != null)
{ {
var adornerLayer = _focusAdorner.Parent as Panel; var adornerLayer = (IPanel)_focusAdorner.Parent;
adornerLayer.Children.Remove(_focusAdorner); adornerLayer.Children.Remove(_focusAdorner);
_focusAdorner = null; _focusAdorner = null;
} }

16
src/Avalonia.Controls/Embedding/EmbeddableControlRoot.cs

@ -4,6 +4,7 @@ using Avalonia.Input;
using Avalonia.Layout; using Avalonia.Layout;
using Avalonia.Platform; using Avalonia.Platform;
using Avalonia.Styling; using Avalonia.Styling;
using JetBrains.Annotations;
namespace Avalonia.Controls.Embedding namespace Avalonia.Controls.Embedding
{ {
@ -18,8 +19,11 @@ namespace Avalonia.Controls.Embedding
{ {
} }
[CanBeNull]
public new IEmbeddableWindowImpl PlatformImpl => (IEmbeddableWindowImpl) base.PlatformImpl; public new IEmbeddableWindowImpl PlatformImpl => (IEmbeddableWindowImpl) base.PlatformImpl;
protected bool EnforceClientSize { get; set; } = true;
public void Prepare() public void Prepare()
{ {
EnsureInitialized(); EnsureInitialized();
@ -36,11 +40,12 @@ namespace Avalonia.Controls.Embedding
init.EndInit(); init.EndInit();
} }
} }
protected override Size MeasureOverride(Size availableSize) protected override Size MeasureOverride(Size availableSize)
{ {
base.MeasureOverride(PlatformImpl.ClientSize); if (EnforceClientSize)
return PlatformImpl.ClientSize; availableSize = PlatformImpl?.ClientSize ?? default(Size);
return base.MeasureOverride(availableSize);
} }
private readonly NameScope _nameScope = new NameScope(); private readonly NameScope _nameScope = new NameScope();
@ -63,9 +68,6 @@ namespace Avalonia.Controls.Embedding
public void Unregister(string name) => _nameScope.Unregister(name); public void Unregister(string name) => _nameScope.Unregister(name);
Type IStyleable.StyleKey => typeof(EmbeddableControlRoot); Type IStyleable.StyleKey => typeof(EmbeddableControlRoot);
public void Dispose() public void Dispose() => PlatformImpl?.Dispose();
{
PlatformImpl.Dispose();
}
} }
} }

2
src/Avalonia.Controls/ItemsControl.cs

@ -354,7 +354,7 @@ namespace Avalonia.Controls
} }
var collection = sender as ICollection; var collection = sender as ICollection;
PseudoClasses.Set(":empty", collection.Count == 0); PseudoClasses.Set(":empty", collection == null || collection.Count == 0);
} }
/// <summary> /// <summary>

2
src/Avalonia.Controls/Menu.cs

@ -47,7 +47,7 @@ namespace Avalonia.Controls
static Menu() static Menu()
{ {
ItemsPanelProperty.OverrideDefaultValue(typeof(Menu), DefaultPanel); ItemsPanelProperty.OverrideDefaultValue(typeof(Menu), DefaultPanel);
MenuItem.ClickEvent.AddClassHandler<Menu>(x => x.OnMenuClick); MenuItem.ClickEvent.AddClassHandler<Menu>(x => x.OnMenuClick, handledEventsToo: true);
MenuItem.SubmenuOpenedEvent.AddClassHandler<Menu>(x => x.OnSubmenuOpened); MenuItem.SubmenuOpenedEvent.AddClassHandler<Menu>(x => x.OnSubmenuOpened);
} }

11
src/Avalonia.Controls/MenuItem.cs

@ -102,6 +102,11 @@ namespace Avalonia.Controls
AccessKeyHandler.AccessKeyPressedEvent.AddClassHandler<MenuItem>(x => x.AccessKeyPressed); AccessKeyHandler.AccessKeyPressedEvent.AddClassHandler<MenuItem>(x => x.AccessKeyPressed);
} }
public MenuItem()
{
}
/// <summary> /// <summary>
/// Occurs when a <see cref="MenuItem"/> without a submenu is clicked. /// Occurs when a <see cref="MenuItem"/> without a submenu is clicked.
/// </summary> /// </summary>
@ -192,7 +197,11 @@ namespace Avalonia.Controls
/// <param name="e">The click event args.</param> /// <param name="e">The click event args.</param>
protected virtual void OnClick(RoutedEventArgs e) protected virtual void OnClick(RoutedEventArgs e)
{ {
Command?.Execute(CommandParameter); if (Command != null)
{
Command.Execute(CommandParameter);
e.Handled = true;
}
} }
/// <summary> /// <summary>

7
src/Avalonia.Controls/Platform/ITopLevelImpl.cs

@ -5,6 +5,7 @@ using System;
using System.Collections.Generic; using System.Collections.Generic;
using Avalonia.Input; using Avalonia.Input;
using Avalonia.Input.Raw; using Avalonia.Input.Raw;
using JetBrains.Annotations;
namespace Avalonia.Platform namespace Avalonia.Platform
{ {
@ -93,5 +94,11 @@ namespace Avalonia.Platform
/// Gets or sets a method called when the underlying implementation is destroyed. /// Gets or sets a method called when the underlying implementation is destroyed.
/// </summary> /// </summary>
Action Closed { get; set; } Action Closed { get; set; }
/// <summary>
/// Gets a mouse device associated with toplevel
/// </summary>
[CanBeNull]
IMouseDevice MouseDevice { get; }
} }
} }

38
src/Avalonia.Controls/Presenters/ContentPresenter.cs

@ -8,6 +8,7 @@ using Avalonia.Controls.Templates;
using Avalonia.Layout; using Avalonia.Layout;
using Avalonia.LogicalTree; using Avalonia.LogicalTree;
using Avalonia.Media; using Avalonia.Media;
using Avalonia.VisualTree;
namespace Avalonia.Controls.Presenters namespace Avalonia.Controls.Presenters
{ {
@ -88,6 +89,7 @@ namespace Avalonia.Controls.Presenters
static ContentPresenter() static ContentPresenter()
{ {
ContentProperty.Changed.AddClassHandler<ContentPresenter>(x => x.ContentChanged); ContentProperty.Changed.AddClassHandler<ContentPresenter>(x => x.ContentChanged);
ContentTemplateProperty.Changed.AddClassHandler<ContentPresenter>(x => x.ContentChanged);
TemplatedParentProperty.Changed.AddClassHandler<ContentPresenter>(x => x.TemplatedParentChanged); TemplatedParentProperty.Changed.AddClassHandler<ContentPresenter>(x => x.TemplatedParentChanged);
} }
@ -313,27 +315,22 @@ namespace Avalonia.Controls.Presenters
if (content != null && newChild == null) if (content != null && newChild == null)
{ {
// We have content and it isn't a control, so first try to recycle the existing var dataTemplate = this.FindDataTemplate(content, ContentTemplate) ?? FuncDataTemplate.Default;
// child control to display the new data by querying if the template that created
// the child can recycle items and that it also matches the new data. // We have content and it isn't a control, so if the new data template is the same
if (oldChild != null && // as the old data template, try to recycle the existing child control to display
_dataTemplate != null && // the new data.
_dataTemplate.SupportsRecycling && if (dataTemplate == _dataTemplate && dataTemplate.SupportsRecycling)
_dataTemplate.Match(content))
{ {
newChild = oldChild; newChild = oldChild;
} }
else else
{ {
// We couldn't recycle an existing control so find a data template for the data _dataTemplate = dataTemplate;
// and use it to create a control.
_dataTemplate = this.FindDataTemplate(content, ContentTemplate) ?? FuncDataTemplate.Default;
newChild = _dataTemplate.Build(content); newChild = _dataTemplate.Build(content);
// Try to give the new control its own name scope. // Give the new control its own name scope.
var controlResult = newChild as Control; if (newChild is Control controlResult)
if (controlResult != null)
{ {
NameScope.SetNameScope(controlResult, new NameScope()); NameScope.SetNameScope(controlResult, new NameScope());
} }
@ -424,6 +421,19 @@ namespace Avalonia.Controls.Presenters
private void ContentChanged(AvaloniaPropertyChangedEventArgs e) private void ContentChanged(AvaloniaPropertyChangedEventArgs e)
{ {
_createdChild = false; _createdChild = false;
if (((ILogical)this).IsAttachedToLogicalTree)
{
UpdateChild();
}
else if (Child != null)
{
VisualChildren.Remove(Child);
LogicalChildren.Remove(Child);
Child = null;
_dataTemplate = null;
}
InvalidateMeasure(); InvalidateMeasure();
} }

3
src/Avalonia.Controls/Presenters/ItemVirtualizerSimple.cs

@ -155,8 +155,7 @@ namespace Avalonia.Controls.Presenters
case NotifyCollectionChangedAction.Add: case NotifyCollectionChangedAction.Add:
CreateAndRemoveContainers(); CreateAndRemoveContainers();
if (e.NewStartingIndex >= FirstIndex && if (e.NewStartingIndex < NextIndex)
e.NewStartingIndex < NextIndex)
{ {
RecycleContainers(); RecycleContainers();
} }

4
src/Avalonia.Controls/Presenters/ScrollContentPresenter.cs

@ -111,7 +111,7 @@ namespace Avalonia.Controls.Presenters
/// <param name="target">The target visual.</param> /// <param name="target">The target visual.</param>
/// <param name="targetRect">The portion of the target visual to bring into view.</param> /// <param name="targetRect">The portion of the target visual to bring into view.</param>
/// <returns>True if the scroll offset was changed; otherwise false.</returns> /// <returns>True if the scroll offset was changed; otherwise false.</returns>
public bool BringDescendentIntoView(IVisual target, Rect targetRect) public bool BringDescendantIntoView(IVisual target, Rect targetRect)
{ {
if (Child == null) if (Child == null)
{ {
@ -262,7 +262,7 @@ namespace Avalonia.Controls.Presenters
private void BringIntoViewRequested(object sender, RequestBringIntoViewEventArgs e) private void BringIntoViewRequested(object sender, RequestBringIntoViewEventArgs e)
{ {
e.Handled = BringDescendentIntoView(e.TargetObject, e.TargetRect); e.Handled = BringDescendantIntoView(e.TargetObject, e.TargetRect);
} }
private void ChildChanged(AvaloniaPropertyChangedEventArgs e) private void ChildChanged(AvaloniaPropertyChangedEventArgs e)

4
src/Avalonia.Controls/Primitives/Popup.cs

@ -340,11 +340,11 @@ namespace Avalonia.Controls.Primitives
switch (mode) switch (mode)
{ {
case PlacementMode.Pointer: case PlacementMode.Pointer:
if (MouseDevice.Instance != null) if(PopupRoot != null)
{ {
// Scales the Horizontal and Vertical offset to screen co-ordinates. // Scales the Horizontal and Vertical offset to screen co-ordinates.
var screenOffset = new Point(HorizontalOffset * (PopupRoot as ILayoutRoot).LayoutScaling, VerticalOffset * (PopupRoot as ILayoutRoot).LayoutScaling); var screenOffset = new Point(HorizontalOffset * (PopupRoot as ILayoutRoot).LayoutScaling, VerticalOffset * (PopupRoot as ILayoutRoot).LayoutScaling);
return MouseDevice.Instance.Position + screenOffset; return (((IInputRoot)PopupRoot)?.MouseDevice?.Position ?? default(Point)) + screenOffset;
} }
return default(Point); return default(Point);

7
src/Avalonia.Controls/Primitives/PopupRoot.cs

@ -9,6 +9,7 @@ using Avalonia.Layout;
using Avalonia.Media; using Avalonia.Media;
using Avalonia.Platform; using Avalonia.Platform;
using Avalonia.VisualTree; using Avalonia.VisualTree;
using JetBrains.Annotations;
namespace Avalonia.Controls.Primitives namespace Avalonia.Controls.Primitives
{ {
@ -49,6 +50,7 @@ namespace Avalonia.Controls.Primitives
/// <summary> /// <summary>
/// Gets the platform-specific window implementation. /// Gets the platform-specific window implementation.
/// </summary> /// </summary>
[CanBeNull]
public new IPopupImpl PlatformImpl => (IPopupImpl)base.PlatformImpl; public new IPopupImpl PlatformImpl => (IPopupImpl)base.PlatformImpl;
/// <summary> /// <summary>
@ -65,10 +67,7 @@ namespace Avalonia.Controls.Primitives
IVisual IHostedVisualTreeRoot.Host => Parent; IVisual IHostedVisualTreeRoot.Host => Parent;
/// <inheritdoc/> /// <inheritdoc/>
public void Dispose() public void Dispose() => PlatformImpl?.Dispose();
{
this.PlatformImpl.Dispose();
}
/// <inheritdoc/> /// <inheritdoc/>
protected override void OnTemplateApplied(TemplateAppliedEventArgs e) protected override void OnTemplateApplied(TemplateAppliedEventArgs e)

11
src/Avalonia.Controls/Primitives/TemplatedControl.cs

@ -285,6 +285,17 @@ namespace Avalonia.Controls.Primitives
return this; return this;
} }
/// <inheritdoc/>
protected override void OnAttachedToLogicalTree(LogicalTreeAttachmentEventArgs e)
{
if (VisualChildren.Count > 0)
{
((ILogical)VisualChildren[0]).NotifyAttachedToLogicalTree(e);
}
base.OnAttachedToLogicalTree(e);
}
/// <inheritdoc/> /// <inheritdoc/>
protected override void OnDetachedFromLogicalTree(LogicalTreeAttachmentEventArgs e) protected override void OnDetachedFromLogicalTree(LogicalTreeAttachmentEventArgs e)
{ {

4
src/Avalonia.Controls/Templates/TemplateExtensions.cs

@ -31,9 +31,9 @@ namespace Avalonia.Controls.Templates
if (child.TemplatedParent != null) if (child.TemplatedParent != null)
{ {
foreach (var descendent in GetTemplateChildren(child, templatedParent)) foreach (var descendant in GetTemplateChildren(child, templatedParent))
{ {
yield return descendent; yield return descendant;
} }
} }
} }

7
src/Avalonia.Controls/TextBox.cs

@ -720,7 +720,7 @@ namespace Avalonia.Controls
if (pos < text.Length) if (pos < text.Length)
{ {
--pos; --pos;
if (pos > 0 && Text[pos - 1] == '\r' && Text[pos] == '\n') if (pos > 0 && text[pos - 1] == '\r' && text[pos] == '\n')
{ {
--pos; --pos;
} }
@ -771,6 +771,9 @@ namespace Avalonia.Controls
private string GetSelection() private string GetSelection()
{ {
var text = Text;
if (string.IsNullOrEmpty(text))
return "";
var selectionStart = SelectionStart; var selectionStart = SelectionStart;
var selectionEnd = SelectionEnd; var selectionEnd = SelectionEnd;
var start = Math.Min(selectionStart, selectionEnd); var start = Math.Min(selectionStart, selectionEnd);
@ -779,7 +782,7 @@ namespace Avalonia.Controls
{ {
return ""; return "";
} }
return Text.Substring(start, end - start); return text.Substring(start, end - start);
} }
private int GetLine(int caretIndex, IList<FormattedTextLine> lines) private int GetLine(int caretIndex, IList<FormattedTextLine> lines)

41
src/Avalonia.Controls/ToolTip.cs

@ -105,17 +105,21 @@ namespace Avalonia.Controls
{ {
if (control != null && control.IsVisible && control.GetVisualRoot() != null) if (control != null && control.IsVisible && control.GetVisualRoot() != null)
{ {
if (s_popup != null) var cp = (control.GetVisualRoot() as IInputRoot)?.MouseDevice?.GetPosition(control);
var position = control.PointToScreen(cp ?? new Point(0, 0)) + new Vector(0, 22);
if (s_popup == null)
{ {
throw new AvaloniaInternalException("Previous ToolTip not disposed."); s_popup = new PopupRoot();
s_popup.Content = new ToolTip();
}
else
{
((ISetLogicalParent)s_popup).SetParent(null);
} }
var cp = MouseDevice.Instance?.GetPosition(control);
var position = control.PointToScreen(cp ?? new Point(0, 0)) + new Vector(0, 22);
s_popup = new PopupRoot();
((ISetLogicalParent)s_popup).SetParent(control); ((ISetLogicalParent)s_popup).SetParent(control);
s_popup.Content = new ToolTip { Content = GetTip(control) }; ((ToolTip)s_popup.Content).Content = GetTip(control);
s_popup.Position = position; s_popup.Position = position;
s_popup.Show(); s_popup.Show();
@ -147,16 +151,23 @@ namespace Avalonia.Controls
{ {
if (s_popup != null) if (s_popup != null)
{ {
// Clear the ToolTip's Content in case it has control content: this will DisposeTooltip();
// reset its visual parent allowing it to be used again. s_show.OnNext(null);
((ToolTip)s_popup.Content).Content = null;
// Dispose of the popup.
s_popup.Dispose();
s_popup = null;
} }
}
}
private static void DisposeTooltip()
{
if (s_popup != null)
{
// Clear the ToolTip's Content in case it has control content: this will
// reset its visual parent allowing it to be used again.
((ToolTip)s_popup.Content).Content = null;
s_show.OnNext(null); // Dispose of the popup.
s_popup.Dispose();
s_popup = null;
} }
} }
} }

40
src/Avalonia.Controls/TopLevel.cs

@ -14,6 +14,7 @@ using Avalonia.Platform;
using Avalonia.Rendering; using Avalonia.Rendering;
using Avalonia.Styling; using Avalonia.Styling;
using Avalonia.VisualTree; using Avalonia.VisualTree;
using JetBrains.Annotations;
namespace Avalonia.Controls namespace Avalonia.Controls
{ {
@ -92,25 +93,25 @@ namespace Avalonia.Controls
var rendererFactory = TryGetService<IRendererFactory>(dependencyResolver); var rendererFactory = TryGetService<IRendererFactory>(dependencyResolver);
Renderer = rendererFactory?.CreateRenderer(this, renderLoop); Renderer = rendererFactory?.CreateRenderer(this, renderLoop);
PlatformImpl.SetInputRoot(this); impl.SetInputRoot(this);
PlatformImpl.Closed = HandleClosed; impl.Closed = HandleClosed;
PlatformImpl.Input = HandleInput; impl.Input = HandleInput;
PlatformImpl.Paint = HandlePaint; impl.Paint = HandlePaint;
PlatformImpl.Resized = HandleResized; impl.Resized = HandleResized;
PlatformImpl.ScalingChanged = HandleScalingChanged; impl.ScalingChanged = HandleScalingChanged;
_keyboardNavigationHandler?.SetOwner(this); _keyboardNavigationHandler?.SetOwner(this);
_accessKeyHandler?.SetOwner(this); _accessKeyHandler?.SetOwner(this);
styler?.ApplyStyles(this); styler?.ApplyStyles(this);
ClientSize = PlatformImpl.ClientSize; ClientSize = impl.ClientSize;
this.GetObservable(PointerOverElementProperty) this.GetObservable(PointerOverElementProperty)
.Select( .Select(
x => (x as InputElement)?.GetObservable(CursorProperty) ?? Observable.Empty<Cursor>()) x => (x as InputElement)?.GetObservable(CursorProperty) ?? Observable.Empty<Cursor>())
.Switch().Subscribe(cursor => PlatformImpl.SetCursor(cursor?.PlatformCursor)); .Switch().Subscribe(cursor => PlatformImpl?.SetCursor(cursor?.PlatformCursor));
if (_applicationLifecycle != null) if (_applicationLifecycle != null)
{ {
@ -135,10 +136,8 @@ namespace Avalonia.Controls
/// <summary> /// <summary>
/// Gets the platform-specific window implementation. /// Gets the platform-specific window implementation.
/// </summary> /// </summary>
public ITopLevelImpl PlatformImpl [CanBeNull]
{ public ITopLevelImpl PlatformImpl { get; private set; }
get;
}
/// <summary> /// <summary>
/// Gets the renderer for the window. /// Gets the renderer for the window.
@ -164,6 +163,9 @@ namespace Avalonia.Controls
set { SetValue(PointerOverElementProperty, value); } set { SetValue(PointerOverElementProperty, value); }
} }
/// <inheritdoc/>
IMouseDevice IInputRoot.MouseDevice => PlatformImpl?.MouseDevice;
/// <summary> /// <summary>
/// Gets or sets a value indicating whether access keys are shown in the window. /// Gets or sets a value indicating whether access keys are shown in the window.
/// </summary> /// </summary>
@ -177,7 +179,7 @@ namespace Avalonia.Controls
Size ILayoutRoot.MaxClientSize => Size.Infinity; Size ILayoutRoot.MaxClientSize => Size.Infinity;
/// <inheritdoc/> /// <inheritdoc/>
double ILayoutRoot.LayoutScaling => PlatformImpl.Scaling; double ILayoutRoot.LayoutScaling => PlatformImpl?.Scaling ?? 1;
IStyleHost IStyleHost.StylingParent IStyleHost IStyleHost.StylingParent
{ {
@ -189,25 +191,27 @@ namespace Avalonia.Controls
/// <inheritdoc/> /// <inheritdoc/>
protected virtual IRenderTarget CreateRenderTarget() protected virtual IRenderTarget CreateRenderTarget()
{ {
if(PlatformImpl == null)
throw new InvalidOperationException("Cann't create render target, PlatformImpl is null (might be already disposed)");
return _renderInterface.CreateRenderTarget(PlatformImpl.Surfaces); return _renderInterface.CreateRenderTarget(PlatformImpl.Surfaces);
} }
/// <inheritdoc/> /// <inheritdoc/>
void IRenderRoot.Invalidate(Rect rect) void IRenderRoot.Invalidate(Rect rect)
{ {
PlatformImpl.Invalidate(rect); PlatformImpl?.Invalidate(rect);
} }
/// <inheritdoc/> /// <inheritdoc/>
Point IRenderRoot.PointToClient(Point p) Point IRenderRoot.PointToClient(Point p)
{ {
return PlatformImpl.PointToClient(p); return PlatformImpl?.PointToClient(p) ?? default(Point);
} }
/// <inheritdoc/> /// <inheritdoc/>
Point IRenderRoot.PointToScreen(Point p) Point IRenderRoot.PointToScreen(Point p)
{ {
return PlatformImpl.PointToScreen(p); return PlatformImpl?.PointToScreen(p) ?? default(Point);
} }
/// <summary> /// <summary>
@ -224,6 +228,8 @@ namespace Avalonia.Controls
/// </summary> /// </summary>
protected virtual void HandleClosed() protected virtual void HandleClosed()
{ {
PlatformImpl = null;
Closed?.Invoke(this, EventArgs.Empty); Closed?.Invoke(this, EventArgs.Empty);
Renderer?.Dispose(); Renderer?.Dispose();
Renderer = null; Renderer = null;
@ -250,7 +256,7 @@ namespace Avalonia.Controls
/// <param name="scaling">The window scaling.</param> /// <param name="scaling">The window scaling.</param>
protected virtual void HandleScalingChanged(double scaling) protected virtual void HandleScalingChanged(double scaling)
{ {
foreach (ILayoutable control in this.GetSelfAndVisualDescendents()) foreach (ILayoutable control in this.GetSelfAndVisualDescendants())
{ {
control.InvalidateMeasure(); control.InvalidateMeasure();
} }

22
src/Avalonia.Controls/TreeView.cs

@ -16,7 +16,7 @@ namespace Avalonia.Controls
/// <summary> /// <summary>
/// Displays a hierachical tree of data. /// Displays a hierachical tree of data.
/// </summary> /// </summary>
public class TreeView : ItemsControl public class TreeView : ItemsControl, ICustomKeyboardNavigation
{ {
/// <summary> /// <summary>
/// Defines the <see cref="AutoScrollToSelectedItem"/> property. /// Defines the <see cref="AutoScrollToSelectedItem"/> property.
@ -90,6 +90,26 @@ namespace Avalonia.Controls
} }
} }
(bool handled, IInputElement next) ICustomKeyboardNavigation.GetNext(IInputElement element, NavigationDirection direction)
{
if (direction == NavigationDirection.Next || direction == NavigationDirection.Previous)
{
if (!this.IsVisualAncestorOf(element))
{
IControl result = _selectedItem != null ?
ItemContainerGenerator.Index.ContainerFromItem(_selectedItem) :
ItemContainerGenerator.ContainerFromIndex(0);
return (true, result);
}
else
{
return (true, null);
}
}
return (false, null);
}
/// <inheritdoc/> /// <inheritdoc/>
protected override IItemContainerGenerator CreateItemContainerGenerator() protected override IItemContainerGenerator CreateItemContainerGenerator()
{ {

43
src/Avalonia.Controls/Window.cs

@ -12,6 +12,7 @@ using Avalonia.Platform;
using Avalonia.Styling; using Avalonia.Styling;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using JetBrains.Annotations;
namespace Avalonia.Controls namespace Avalonia.Controls
{ {
@ -46,12 +47,12 @@ namespace Avalonia.Controls
/// </summary> /// </summary>
public class Window : WindowBase, IStyleable, IFocusScope, ILayoutRoot, INameScope public class Window : WindowBase, IStyleable, IFocusScope, ILayoutRoot, INameScope
{ {
private static IList<Window> s_windows = new List<Window>(); private static List<Window> s_windows = new List<Window>();
/// <summary> /// <summary>
/// Retrieves an enumeration of all Windows in the currently running application. /// Retrieves an enumeration of all Windows in the currently running application.
/// </summary> /// </summary>
public static IList<Window> OpenWindows => s_windows; public static IReadOnlyList<Window> OpenWindows => s_windows;
/// <summary> /// <summary>
/// Defines the <see cref="SizeToContent"/> property. /// Defines the <see cref="SizeToContent"/> property.
@ -87,11 +88,11 @@ namespace Avalonia.Controls
static Window() static Window()
{ {
BackgroundProperty.OverrideDefaultValue(typeof(Window), Brushes.White); BackgroundProperty.OverrideDefaultValue(typeof(Window), Brushes.White);
TitleProperty.Changed.AddClassHandler<Window>((s, e) => s.PlatformImpl.SetTitle((string)e.NewValue)); TitleProperty.Changed.AddClassHandler<Window>((s, e) => s.PlatformImpl?.SetTitle((string)e.NewValue));
HasSystemDecorationsProperty.Changed.AddClassHandler<Window>( HasSystemDecorationsProperty.Changed.AddClassHandler<Window>(
(s, e) => s.PlatformImpl.SetSystemDecorations((bool) e.NewValue)); (s, e) => s.PlatformImpl?.SetSystemDecorations((bool) e.NewValue));
IconProperty.Changed.AddClassHandler<Window>((s, e) => s.PlatformImpl.SetIcon(((WindowIcon)e.NewValue).PlatformImpl)); IconProperty.Changed.AddClassHandler<Window>((s, e) => s.PlatformImpl?.SetIcon(((WindowIcon)e.NewValue).PlatformImpl));
} }
/// <summary> /// <summary>
@ -109,7 +110,7 @@ namespace Avalonia.Controls
public Window(IWindowImpl impl) public Window(IWindowImpl impl)
: base(impl) : base(impl)
{ {
_maxPlatformClientSize = this.PlatformImpl.MaxClientSize; _maxPlatformClientSize = PlatformImpl?.MaxClientSize ?? default(Size);
} }
/// <inheritdoc/> /// <inheritdoc/>
@ -129,6 +130,7 @@ namespace Avalonia.Controls
/// <summary> /// <summary>
/// Gets the platform-specific window implementation. /// Gets the platform-specific window implementation.
/// </summary> /// </summary>
[CanBeNull]
public new IWindowImpl PlatformImpl => (IWindowImpl)base.PlatformImpl; public new IWindowImpl PlatformImpl => (IWindowImpl)base.PlatformImpl;
/// <summary> /// <summary>
@ -164,8 +166,12 @@ namespace Avalonia.Controls
/// </summary> /// </summary>
public WindowState WindowState public WindowState WindowState
{ {
get { return this.PlatformImpl.WindowState; } get { return PlatformImpl?.WindowState ?? WindowState.Normal; }
set { this.PlatformImpl.WindowState = value; } set
{
if (PlatformImpl != null)
PlatformImpl.WindowState = value;
}
} }
/// <summary> /// <summary>
@ -189,7 +195,7 @@ namespace Avalonia.Controls
public void Close() public void Close()
{ {
s_windows.Remove(this); s_windows.Remove(this);
PlatformImpl.Dispose(); PlatformImpl?.Dispose();
IsVisible = false; IsVisible = false;
} }
@ -221,7 +227,7 @@ namespace Avalonia.Controls
{ {
using (BeginAutoSizing()) using (BeginAutoSizing())
{ {
PlatformImpl.Hide(); PlatformImpl?.Hide();
} }
IsVisible = false; IsVisible = false;
@ -232,6 +238,11 @@ namespace Avalonia.Controls
/// </summary> /// </summary>
public override void Show() public override void Show()
{ {
if (IsVisible)
{
return;
}
s_windows.Add(this); s_windows.Add(this);
EnsureInitialized(); EnsureInitialized();
@ -240,7 +251,7 @@ namespace Avalonia.Controls
using (BeginAutoSizing()) using (BeginAutoSizing())
{ {
PlatformImpl.Show(); PlatformImpl?.Show();
} }
} }
@ -266,6 +277,11 @@ namespace Avalonia.Controls
/// </returns> /// </returns>
public Task<TResult> ShowDialog<TResult>() public Task<TResult> ShowDialog<TResult>()
{ {
if (IsVisible)
{
throw new InvalidOperationException("The window is already being shown.");
}
s_windows.Add(this); s_windows.Add(this);
EnsureInitialized(); EnsureInitialized();
@ -278,7 +294,7 @@ namespace Avalonia.Controls
var activated = affectedWindows.Where(w => w.IsActive).FirstOrDefault(); var activated = affectedWindows.Where(w => w.IsActive).FirstOrDefault();
SetIsEnabled(affectedWindows, false); SetIsEnabled(affectedWindows, false);
var modal = PlatformImpl.ShowDialog(); var modal = PlatformImpl?.ShowDialog();
var result = new TaskCompletionSource<TResult>(); var result = new TaskCompletionSource<TResult>();
Observable.FromEventPattern<EventHandler, EventArgs>( Observable.FromEventPattern<EventHandler, EventArgs>(
@ -287,7 +303,7 @@ namespace Avalonia.Controls
.Take(1) .Take(1)
.Subscribe(_ => .Subscribe(_ =>
{ {
modal.Dispose(); modal?.Dispose();
SetIsEnabled(affectedWindows, true); SetIsEnabled(affectedWindows, true);
activated?.Activate(); activated?.Activate();
result.SetResult((TResult)_dialogResult); result.SetResult((TResult)_dialogResult);
@ -354,6 +370,7 @@ namespace Avalonia.Controls
protected override void HandleClosed() protected override void HandleClosed()
{ {
IsVisible = false; IsVisible = false;
s_windows.Remove(this);
base.HandleClosed(); base.HandleClosed();
} }

43
src/Avalonia.Controls/WindowBase.cs

@ -9,6 +9,7 @@ using Avalonia.Controls.Primitives;
using Avalonia.Input; using Avalonia.Input;
using Avalonia.Layout; using Avalonia.Layout;
using Avalonia.Platform; using Avalonia.Platform;
using JetBrains.Annotations;
namespace Avalonia.Controls namespace Avalonia.Controls
{ {
@ -28,6 +29,7 @@ namespace Avalonia.Controls
public static readonly DirectProperty<WindowBase, bool> IsActiveProperty = public static readonly DirectProperty<WindowBase, bool> IsActiveProperty =
AvaloniaProperty.RegisterDirect<WindowBase, bool>(nameof(IsActive), o => o.IsActive); AvaloniaProperty.RegisterDirect<WindowBase, bool>(nameof(IsActive), o => o.IsActive);
private bool _hasExecutedInitialLayoutPass;
private bool _isActive; private bool _isActive;
private bool _ignoreVisibilityChange; private bool _ignoreVisibilityChange;
@ -43,10 +45,10 @@ namespace Avalonia.Controls
public WindowBase(IWindowBaseImpl impl, IAvaloniaDependencyResolver dependencyResolver) : base(impl, dependencyResolver) public WindowBase(IWindowBaseImpl impl, IAvaloniaDependencyResolver dependencyResolver) : base(impl, dependencyResolver)
{ {
PlatformImpl.Activated = HandleActivated; impl.Activated = HandleActivated;
PlatformImpl.Deactivated = HandleDeactivated; impl.Deactivated = HandleDeactivated;
PlatformImpl.PositionChanged = HandlePositionChanged; impl.PositionChanged = HandlePositionChanged;
this.GetObservable(ClientSizeProperty).Skip(1).Subscribe(x => PlatformImpl.Resize(x)); this.GetObservable(ClientSizeProperty).Skip(1).Subscribe(x => PlatformImpl?.Resize(x));
} }
/// <summary> /// <summary>
@ -64,6 +66,7 @@ namespace Avalonia.Controls
/// </summary> /// </summary>
public event EventHandler<PointEventArgs> PositionChanged; public event EventHandler<PointEventArgs> PositionChanged;
[CanBeNull]
public new IWindowBaseImpl PlatformImpl => (IWindowBaseImpl) base.PlatformImpl; public new IWindowBaseImpl PlatformImpl => (IWindowBaseImpl) base.PlatformImpl;
/// <summary> /// <summary>
@ -80,8 +83,12 @@ namespace Avalonia.Controls
/// </summary> /// </summary>
public Point Position public Point Position
{ {
get { return PlatformImpl.Position; } get { return PlatformImpl?.Position ?? default(Point); }
set { PlatformImpl.Position = value; } set
{
if (PlatformImpl is IWindowBaseImpl impl)
impl.Position = value;
}
} }
/// <summary> /// <summary>
@ -98,7 +105,7 @@ namespace Avalonia.Controls
/// </summary> /// </summary>
public void Activate() public void Activate()
{ {
PlatformImpl.Activate(); PlatformImpl?.Activate();
} }
/// <summary> /// <summary>
@ -110,7 +117,7 @@ namespace Avalonia.Controls
try try
{ {
PlatformImpl.Hide(); PlatformImpl?.Hide();
IsVisible = false; IsVisible = false;
} }
finally finally
@ -130,8 +137,14 @@ namespace Avalonia.Controls
{ {
EnsureInitialized(); EnsureInitialized();
IsVisible = true; IsVisible = true;
LayoutManager.Instance.ExecuteInitialLayoutPass(this);
PlatformImpl.Show(); if (!_hasExecutedInitialLayoutPass)
{
LayoutManager.Instance.ExecuteInitialLayoutPass(this);
_hasExecutedInitialLayoutPass = true;
}
PlatformImpl?.Show();
} }
finally finally
{ {
@ -163,10 +176,10 @@ namespace Avalonia.Controls
{ {
using (BeginAutoSizing()) using (BeginAutoSizing())
{ {
PlatformImpl.Resize(finalSize); PlatformImpl?.Resize(finalSize);
} }
return base.ArrangeOverride(PlatformImpl.ClientSize); return base.ArrangeOverride(PlatformImpl?.ClientSize ?? default(Size));
} }
/// <summary> /// <summary>
@ -174,7 +187,7 @@ namespace Avalonia.Controls
/// </summary> /// </summary>
protected void EnsureInitialized() protected void EnsureInitialized()
{ {
if (!this.IsInitialized) if (!IsInitialized)
{ {
var init = (ISupportInitialize)this; var init = (ISupportInitialize)this;
init.BeginInit(); init.BeginInit();
@ -268,12 +281,12 @@ namespace Avalonia.Controls
/// <summary> /// <summary>
/// Starts moving a window with left button being held. Should be called from left mouse button press event handler /// Starts moving a window with left button being held. Should be called from left mouse button press event handler
/// </summary> /// </summary>
public void BeginMoveDrag() => PlatformImpl.BeginMoveDrag(); public void BeginMoveDrag() => PlatformImpl?.BeginMoveDrag();
/// <summary> /// <summary>
/// Starts resizing a window. This function is used if an application has window resizing controls. /// Starts resizing a window. This function is used if an application has window resizing controls.
/// Should be called from left mouse button press event handler /// Should be called from left mouse button press event handler
/// </summary> /// </summary>
public void BeginResizeDrag(WindowEdge edge) => PlatformImpl.BeginResizeDrag(edge); public void BeginResizeDrag(WindowEdge edge) => PlatformImpl?.BeginResizeDrag(edge);
} }
} }

4
src/Avalonia.DesignerSupport/DesignerAssist.cs

@ -75,7 +75,7 @@ namespace Avalonia.DesignerSupport
private static void SetScalingFactor(double factor) private static void SetScalingFactor(double factor)
{ {
PlatformManager.SetDesignerScalingFactor(factor); PlatformManager.SetDesignerScalingFactor(factor);
s_currentWindow?.PlatformImpl.Resize(s_currentWindow.ClientSize); s_currentWindow?.PlatformImpl?.Resize(s_currentWindow.ClientSize);
} }
static Window s_currentWindow; static Window s_currentWindow;
@ -149,6 +149,8 @@ namespace Avalonia.DesignerSupport
s_currentWindow = window; s_currentWindow = window;
window.Show(); window.Show();
Design.ApplyDesignerProperties(window, control); Design.ApplyDesignerProperties(window, control);
// ReSharper disable once PossibleNullReferenceException
// Always not null at this point
Api.OnWindowCreated?.Invoke(window.PlatformImpl.Handle.Handle); Api.OnWindowCreated?.Invoke(window.PlatformImpl.Handle.Handle);
Api.OnResize?.Invoke(); Api.OnResize?.Invoke();
} }

1
src/Avalonia.Diagnostics/Avalonia.Diagnostics.csproj

@ -34,7 +34,6 @@
<ProjectReference Include="..\Avalonia.Input\Avalonia.Input.csproj" /> <ProjectReference Include="..\Avalonia.Input\Avalonia.Input.csproj" />
<ProjectReference Include="..\Avalonia.Interactivity\Avalonia.Interactivity.csproj" /> <ProjectReference Include="..\Avalonia.Interactivity\Avalonia.Interactivity.csproj" />
<ProjectReference Include="..\Avalonia.Layout\Avalonia.Layout.csproj" /> <ProjectReference Include="..\Avalonia.Layout\Avalonia.Layout.csproj" />
<ProjectReference Include="..\Avalonia.ReactiveUI\Avalonia.ReactiveUI.csproj" />
<ProjectReference Include="..\Avalonia.Visuals\Avalonia.Visuals.csproj" /> <ProjectReference Include="..\Avalonia.Visuals\Avalonia.Visuals.csproj" />
<ProjectReference Include="..\Avalonia.Styling\Avalonia.Styling.csproj" /> <ProjectReference Include="..\Avalonia.Styling\Avalonia.Styling.csproj" />
<ProjectReference Include="..\Avalonia.Themes.Default\Avalonia.Themes.Default.csproj" /> <ProjectReference Include="..\Avalonia.Themes.Default\Avalonia.Themes.Default.csproj" />

30
src/Avalonia.Diagnostics/DevTools.xaml.cs

@ -11,15 +11,14 @@ using Avalonia.Input.Raw;
using Avalonia.Interactivity; using Avalonia.Interactivity;
using Avalonia.Markup.Xaml; using Avalonia.Markup.Xaml;
using Avalonia.VisualTree; using Avalonia.VisualTree;
using ReactiveUI;
namespace Avalonia namespace Avalonia
{ {
public static class WindowExtensions public static class DevToolsExtensions
{ {
public static void AttachDevTools(this Window window) public static void AttachDevTools(this TopLevel control)
{ {
Avalonia.Diagnostics.DevTools.Attach(window); Avalonia.Diagnostics.DevTools.Attach(control);
} }
} }
} }
@ -28,7 +27,7 @@ namespace Avalonia.Diagnostics
{ {
public class DevTools : UserControl public class DevTools : UserControl
{ {
private static Dictionary<Window, Window> s_open = new Dictionary<Window, Window>(); private static Dictionary<TopLevel, Window> s_open = new Dictionary<TopLevel, Window>();
private IDisposable _keySubscription; private IDisposable _keySubscription;
public DevTools(IControl root) public DevTools(IControl root)
@ -44,9 +43,9 @@ namespace Avalonia.Diagnostics
public IControl Root { get; } public IControl Root { get; }
public static IDisposable Attach(Window window) public static IDisposable Attach(TopLevel control)
{ {
return window.AddHandler( return control.AddHandler(
KeyDownEvent, KeyDownEvent,
WindowPreviewKeyDown, WindowPreviewKeyDown,
RoutingStrategies.Tunnel); RoutingStrategies.Tunnel);
@ -56,16 +55,16 @@ namespace Avalonia.Diagnostics
{ {
if (e.Key == Key.F12) if (e.Key == Key.F12)
{ {
var window = (Window)sender; var control = (TopLevel)sender;
var devToolsWindow = default(Window); var devToolsWindow = default(Window);
if (s_open.TryGetValue(window, out devToolsWindow)) if (s_open.TryGetValue(control, out devToolsWindow))
{ {
devToolsWindow.Activate(); devToolsWindow.Activate();
} }
else else
{ {
var devTools = new DevTools(window); var devTools = new DevTools(control);
devToolsWindow = new Window devToolsWindow = new Window
{ {
@ -74,12 +73,12 @@ namespace Avalonia.Diagnostics
Content = devTools, Content = devTools,
DataTemplates = new DataTemplates DataTemplates = new DataTemplates
{ {
new ViewLocator<ReactiveObject>(), new ViewLocator<ViewModelBase>(),
} }
}; };
devToolsWindow.Closed += devTools.DevToolsClosed; devToolsWindow.Closed += devTools.DevToolsClosed;
s_open.Add((Window)sender, devToolsWindow); s_open.Add(control, devToolsWindow);
devToolsWindow.Show(); devToolsWindow.Show();
} }
} }
@ -89,9 +88,7 @@ namespace Avalonia.Diagnostics
{ {
var devToolsWindow = (Window)sender; var devToolsWindow = (Window)sender;
var devTools = (DevTools)devToolsWindow.Content; var devTools = (DevTools)devToolsWindow.Content;
var window = (Window)devTools.Root; s_open.Remove((TopLevel)devTools.Root);
s_open.Remove(window);
_keySubscription.Dispose(); _keySubscription.Dispose();
devToolsWindow.Closed -= DevToolsClosed; devToolsWindow.Closed -= DevToolsClosed;
} }
@ -107,7 +104,8 @@ namespace Avalonia.Diagnostics
if ((e.Modifiers) == modifiers) if ((e.Modifiers) == modifiers)
{ {
var point = MouseDevice.Instance.GetPosition(Root);
var point = (Root.VisualRoot as IInputRoot)?.MouseDevice?.GetPosition(Root) ?? default(Point);
var control = Root.GetVisualsAt(point, x => (!(x is AdornerLayer) && x.IsVisible)) var control = Root.GetVisualsAt(point, x => (!(x is AdornerLayer) && x.IsVisible))
.FirstOrDefault(); .FirstOrDefault();

3
src/Avalonia.Diagnostics/ViewModels/ControlDetailsViewModel.cs

@ -4,11 +4,10 @@
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using Avalonia.VisualTree; using Avalonia.VisualTree;
using ReactiveUI;
namespace Avalonia.Diagnostics.ViewModels namespace Avalonia.Diagnostics.ViewModels
{ {
internal class ControlDetailsViewModel : ReactiveObject internal class ControlDetailsViewModel : ViewModelBase
{ {
public ControlDetailsViewModel(IVisual control) public ControlDetailsViewModel(IVisual control)
{ {

74
src/Avalonia.Diagnostics/ViewModels/DevToolsViewModel.cs

@ -5,32 +5,50 @@ using System;
using System.Reactive.Linq; using System.Reactive.Linq;
using Avalonia.Controls; using Avalonia.Controls;
using Avalonia.Input; using Avalonia.Input;
using ReactiveUI;
namespace Avalonia.Diagnostics.ViewModels namespace Avalonia.Diagnostics.ViewModels
{ {
internal class DevToolsViewModel : ReactiveObject internal class DevToolsViewModel : ViewModelBase
{ {
private ReactiveObject _content; private ViewModelBase _content;
private int _selectedTab; private int _selectedTab;
private TreePageViewModel _logicalTree; private TreePageViewModel _logicalTree;
private TreePageViewModel _visualTree; private TreePageViewModel _visualTree;
private string _focusedControl;
private readonly ObservableAsPropertyHelper<string> _focusedControl; private string _pointerOverElement;
private readonly ObservableAsPropertyHelper<string> _pointerOverElement;
public DevToolsViewModel(IControl root) public DevToolsViewModel(IControl root)
{ {
_logicalTree = new TreePageViewModel(LogicalTreeNode.Create(root)); _logicalTree = new TreePageViewModel(LogicalTreeNode.Create(root));
_visualTree = new TreePageViewModel(VisualTreeNode.Create(root)); _visualTree = new TreePageViewModel(VisualTreeNode.Create(root));
this.WhenAnyValue(x => x.SelectedTab).Subscribe(index => UpdateFocusedControl();
KeyboardDevice.Instance.PropertyChanged += (s, e) =>
{ {
switch (index) if (e.PropertyName == nameof(KeyboardDevice.Instance.FocusedElement))
{
UpdateFocusedControl();
}
};
root.GetObservable(TopLevel.PointerOverElementProperty)
.Subscribe(x => PointerOverElement = x?.GetType().Name);
}
public ViewModelBase Content
{
get { return _content; }
private set { RaiseAndSetIfChanged(ref _content, value); }
}
public int SelectedTab
{
get { return _selectedTab; }
set
{
_selectedTab = value;
switch (value)
{ {
case 0: case 0:
Content = _logicalTree; Content = _logicalTree;
@ -39,34 +57,23 @@ namespace Avalonia.Diagnostics.ViewModels
Content = _visualTree; Content = _visualTree;
break; break;
} }
});
_focusedControl = KeyboardDevice.Instance RaisePropertyChanged();
.WhenAnyValue(x => x.FocusedElement) }
.Select(x => x?.GetType().Name)
.ToProperty(this, x => x.FocusedControl);
_pointerOverElement = root.GetObservable(TopLevel.PointerOverElementProperty)
.Select(x => x?.GetType().Name)
.ToProperty(this, x => x.PointerOverElement);
} }
public ReactiveObject Content public string FocusedControl
{ {
get { return _content; } get { return _focusedControl; }
private set { this.RaiseAndSetIfChanged(ref _content, value); } private set { RaiseAndSetIfChanged(ref _focusedControl, value); }
} }
public int SelectedTab public string PointerOverElement
{ {
get { return _selectedTab; } get { return _pointerOverElement; }
set { this.RaiseAndSetIfChanged(ref _selectedTab, value); } private set { RaiseAndSetIfChanged(ref _pointerOverElement, value); }
} }
public string FocusedControl => _focusedControl.Value;
public string PointerOverElement => _pointerOverElement.Value;
public void SelectControl(IControl control) public void SelectControl(IControl control)
{ {
var tree = Content as TreePageViewModel; var tree = Content as TreePageViewModel;
@ -76,5 +83,10 @@ namespace Avalonia.Diagnostics.ViewModels
tree.SelectControl(control); tree.SelectControl(control);
} }
} }
private void UpdateFocusedControl()
{
_focusedControl = KeyboardDevice.Instance.FocusedElement?.GetType().Name;
}
} }
} }

4
src/Avalonia.Diagnostics/ViewModels/LogicalTreeNode.cs

@ -2,9 +2,9 @@
// Licensed under the MIT license. See licence.md file in the project root for full license information. // Licensed under the MIT license. See licence.md file in the project root for full license information.
using System; using System;
using Avalonia.Collections;
using Avalonia.Controls; using Avalonia.Controls;
using Avalonia.LogicalTree; using Avalonia.LogicalTree;
using ReactiveUI;
namespace Avalonia.Diagnostics.ViewModels namespace Avalonia.Diagnostics.ViewModels
{ {
@ -13,7 +13,7 @@ namespace Avalonia.Diagnostics.ViewModels
public LogicalTreeNode(ILogical logical, TreeNode parent) public LogicalTreeNode(ILogical logical, TreeNode parent)
: base((Control)logical, parent) : base((Control)logical, parent)
{ {
Children = logical.LogicalChildren.CreateDerivedCollection(x => new LogicalTreeNode(x, this)); Children = logical.LogicalChildren.CreateDerivedList(x => new LogicalTreeNode(x, this));
} }
public static LogicalTreeNode[] Create(object control) public static LogicalTreeNode[] Create(object control)

11
src/Avalonia.Diagnostics/ViewModels/PropertyDetails.cs

@ -3,16 +3,13 @@
using System; using System;
using Avalonia.Data; using Avalonia.Data;
using ReactiveUI;
namespace Avalonia.Diagnostics.ViewModels namespace Avalonia.Diagnostics.ViewModels
{ {
internal class PropertyDetails : ReactiveObject internal class PropertyDetails : ViewModelBase
{ {
private object _value; private object _value;
private string _priority; private string _priority;
private string _diagnostic; private string _diagnostic;
public PropertyDetails(AvaloniaObject o, AvaloniaProperty property) public PropertyDetails(AvaloniaObject o, AvaloniaProperty property)
@ -41,19 +38,19 @@ namespace Avalonia.Diagnostics.ViewModels
public string Priority public string Priority
{ {
get { return _priority; } get { return _priority; }
private set { this.RaiseAndSetIfChanged(ref _priority, value); } private set { RaiseAndSetIfChanged(ref _priority, value); }
} }
public string Diagnostic public string Diagnostic
{ {
get { return _diagnostic; } get { return _diagnostic; }
private set { this.RaiseAndSetIfChanged(ref _diagnostic, value); } private set { RaiseAndSetIfChanged(ref _diagnostic, value); }
} }
public object Value public object Value
{ {
get { return _value; } get { return _value; }
private set { this.RaiseAndSetIfChanged(ref _value, value); } private set { RaiseAndSetIfChanged(ref _value, value); }
} }
} }
} }

10
src/Avalonia.Diagnostics/ViewModels/TreeNode.cs

@ -5,13 +5,13 @@ using System;
using System.Collections.Specialized; using System.Collections.Specialized;
using System.Reactive; using System.Reactive;
using System.Reactive.Linq; using System.Reactive.Linq;
using Avalonia.Collections;
using Avalonia.Styling; using Avalonia.Styling;
using Avalonia.VisualTree; using Avalonia.VisualTree;
using ReactiveUI;
namespace Avalonia.Diagnostics.ViewModels namespace Avalonia.Diagnostics.ViewModels
{ {
internal class TreeNode : ReactiveObject internal class TreeNode : ViewModelBase
{ {
private string _classes; private string _classes;
private bool _isExpanded; private bool _isExpanded;
@ -47,7 +47,7 @@ namespace Avalonia.Diagnostics.ViewModels
} }
} }
public IReadOnlyReactiveList<TreeNode> Children public IAvaloniaReadOnlyList<TreeNode> Children
{ {
get; get;
protected set; protected set;
@ -56,7 +56,7 @@ namespace Avalonia.Diagnostics.ViewModels
public string Classes public string Classes
{ {
get { return _classes; } get { return _classes; }
private set { this.RaiseAndSetIfChanged(ref _classes, value); } private set { RaiseAndSetIfChanged(ref _classes, value); }
} }
public IVisual Visual public IVisual Visual
@ -67,7 +67,7 @@ namespace Avalonia.Diagnostics.ViewModels
public bool IsExpanded public bool IsExpanded
{ {
get { return _isExpanded; } get { return _isExpanded; }
set { this.RaiseAndSetIfChanged(ref _isExpanded, value); } set { RaiseAndSetIfChanged(ref _isExpanded, value); }
} }
public TreeNode Parent public TreeNode Parent

24
src/Avalonia.Diagnostics/ViewModels/TreePageViewModel.cs

@ -1,25 +1,19 @@
// Copyright (c) The Avalonia Project. All rights reserved. // 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. // Licensed under the MIT license. See licence.md file in the project root for full license information.
using System.Reactive.Linq;
using Avalonia.Controls; using Avalonia.Controls;
using Avalonia.VisualTree; using Avalonia.VisualTree;
using ReactiveUI;
namespace Avalonia.Diagnostics.ViewModels namespace Avalonia.Diagnostics.ViewModels
{ {
internal class TreePageViewModel : ReactiveObject internal class TreePageViewModel : ViewModelBase
{ {
private TreeNode _selected; private TreeNode _selected;
private ControlDetailsViewModel _details;
private readonly ObservableAsPropertyHelper<ControlDetailsViewModel> _details;
public TreePageViewModel(TreeNode[] nodes) public TreePageViewModel(TreeNode[] nodes)
{ {
Nodes = nodes; Nodes = nodes;
_details = this.WhenAnyValue(x => x.SelectedNode)
.Select(x => x != null ? new ControlDetailsViewModel(x.Visual) : null)
.ToProperty(this, x => x.Details);
} }
public TreeNode[] Nodes { get; protected set; } public TreeNode[] Nodes { get; protected set; }
@ -27,10 +21,20 @@ namespace Avalonia.Diagnostics.ViewModels
public TreeNode SelectedNode public TreeNode SelectedNode
{ {
get { return _selected; } get { return _selected; }
set { this.RaiseAndSetIfChanged(ref _selected, value); } set
{
if (RaiseAndSetIfChanged(ref _selected, value))
{
Details = value != null ? new ControlDetailsViewModel(value.Visual) : null;
}
}
} }
public ControlDetailsViewModel Details => _details.Value; public ControlDetailsViewModel Details
{
get { return _details; }
private set { RaiseAndSetIfChanged(ref _details, value); }
}
public TreeNode FindNode(IControl control) public TreeNode FindNode(IControl control)
{ {

32
src/Avalonia.Diagnostics/ViewModels/ViewModelBase.cs

@ -0,0 +1,32 @@
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Runtime.CompilerServices;
using JetBrains.Annotations;
namespace Avalonia.Diagnostics.ViewModels
{
public class ViewModelBase : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
[NotifyPropertyChangedInvocator]
protected bool RaiseAndSetIfChanged<T>(ref T field, T value, [CallerMemberName] string propertyName = null)
{
if (!EqualityComparer<T>.Default.Equals(field, value))
{
field = value;
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
return true;
}
return false;
}
[NotifyPropertyChangedInvocator]
protected void RaisePropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
}

7
src/Avalonia.Diagnostics/ViewModels/VisualTreeNode.cs

@ -1,10 +1,9 @@
// Copyright (c) The Avalonia Project. All rights reserved. // 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. // Licensed under the MIT license. See licence.md file in the project root for full license information.
using Avalonia.Controls; using Avalonia.Collections;
using Avalonia.Styling; using Avalonia.Styling;
using Avalonia.VisualTree; using Avalonia.VisualTree;
using ReactiveUI;
namespace Avalonia.Diagnostics.ViewModels namespace Avalonia.Diagnostics.ViewModels
{ {
@ -17,11 +16,11 @@ namespace Avalonia.Diagnostics.ViewModels
if (host?.Root == null) if (host?.Root == null)
{ {
Children = visual.VisualChildren.CreateDerivedCollection(x => new VisualTreeNode(x, this)); Children = visual.VisualChildren.CreateDerivedList(x => new VisualTreeNode(x, this));
} }
else else
{ {
Children = new ReactiveList<VisualTreeNode>(new[] { new VisualTreeNode(host.Root, this) }); Children = new AvaloniaList<VisualTreeNode>(new[] { new VisualTreeNode(host.Root, this) });
} }
if ((Visual is IStyleable styleable)) if ((Visual is IStyleable styleable))

20
src/Avalonia.Diagnostics/Views/ControlDetailsView.cs

@ -8,7 +8,6 @@ using Avalonia.Controls;
using Avalonia.Diagnostics.ViewModels; using Avalonia.Diagnostics.ViewModels;
using Avalonia.Media; using Avalonia.Media;
using Avalonia.Styling; using Avalonia.Styling;
using ReactiveUI;
namespace Avalonia.Diagnostics.Views namespace Avalonia.Diagnostics.Views
{ {
@ -16,6 +15,7 @@ namespace Avalonia.Diagnostics.Views
{ {
private static readonly StyledProperty<ControlDetailsViewModel> ViewModelProperty = private static readonly StyledProperty<ControlDetailsViewModel> ViewModelProperty =
AvaloniaProperty.Register<ControlDetailsView, ControlDetailsViewModel>("ViewModel"); AvaloniaProperty.Register<ControlDetailsView, ControlDetailsViewModel>("ViewModel");
private SimpleGrid _grid;
public ControlDetailsView() public ControlDetailsView()
{ {
@ -27,7 +27,11 @@ namespace Avalonia.Diagnostics.Views
public ControlDetailsViewModel ViewModel public ControlDetailsViewModel ViewModel
{ {
get { return GetValue(ViewModelProperty); } get { return GetValue(ViewModelProperty); }
private set { SetValue(ViewModelProperty, value); } private set
{
SetValue(ViewModelProperty, value);
_grid[GridRepeater.ItemsProperty] = value?.Properties;
}
} }
private void InitializeComponent() private void InitializeComponent()
@ -36,7 +40,7 @@ namespace Avalonia.Diagnostics.Views
Content = new ScrollViewer Content = new ScrollViewer
{ {
Content = new SimpleGrid Content = _grid = new SimpleGrid
{ {
Styles = new Styles Styles = new Styles
{ {
@ -49,7 +53,6 @@ namespace Avalonia.Diagnostics.Views
}, },
}, },
[GridRepeater.TemplateProperty] = pt, [GridRepeater.TemplateProperty] = pt,
[!GridRepeater.ItemsProperty] = this.WhenAnyValue(x => x.ViewModel.Properties).ToBinding(),
} }
}; };
} }
@ -62,16 +65,13 @@ namespace Avalonia.Diagnostics.Views
{ {
Text = property.Name, Text = property.Name,
TextWrapping = TextWrapping.NoWrap, TextWrapping = TextWrapping.NoWrap,
[!ToolTip.TipProperty] = property [!ToolTip.TipProperty] = property.GetObservable<string>(nameof(property.Diagnostic)).ToBinding(),
.WhenAnyValue(x => x.Diagnostic)
.ToBinding(),
}; };
yield return new TextBlock yield return new TextBlock
{ {
TextWrapping = TextWrapping.NoWrap, TextWrapping = TextWrapping.NoWrap,
[!TextBlock.TextProperty] = property [!TextBlock.TextProperty] = property.GetObservable<object>(nameof(property.Value))
.WhenAnyValue(v => v.Value)
.Select(v => v?.ToString()) .Select(v => v?.ToString())
.ToBinding(), .ToBinding(),
}; };
@ -79,7 +79,7 @@ namespace Avalonia.Diagnostics.Views
yield return new TextBlock yield return new TextBlock
{ {
TextWrapping = TextWrapping.NoWrap, TextWrapping = TextWrapping.NoWrap,
[!TextBlock.TextProperty] = property.WhenAnyValue(x => x.Priority).ToBinding(), [!TextBlock.TextProperty] = property.GetObservable<string>((nameof(property.Priority))).ToBinding(),
}; };
} }
} }

30
src/Avalonia.Diagnostics/Views/PropertyChangedExtenions.cs

@ -0,0 +1,30 @@
using System;
using System.ComponentModel;
using System.Reactive.Linq;
using System.Reflection;
namespace Avalonia.Diagnostics.Views
{
internal static class PropertyChangedExtenions
{
public static IObservable<T> GetObservable<T>(this INotifyPropertyChanged source, string propertyName)
{
Contract.Requires<ArgumentNullException>(source != null);
Contract.Requires<ArgumentNullException>(propertyName != null);
var property = source.GetType().GetTypeInfo().GetDeclaredProperty(propertyName);
if (property == null)
{
throw new ArgumentException($"Property '{propertyName}' not found on '{source}.");
}
return Observable.FromEventPattern<PropertyChangedEventHandler, PropertyChangedEventArgs>(
e => source.PropertyChanged += e,
e => source.PropertyChanged -= e)
.Where(e => e.EventArgs.PropertyName == propertyName)
.Select(_ => (T)property.GetValue(source))
.StartWith((T)property.GetValue(source));
}
}
}

8
src/Avalonia.DotNetFrameworkRuntime/AppBuilder.cs

@ -82,7 +82,13 @@ namespace Avalonia
private void LoadAssembliesInDirectory() private void LoadAssembliesInDirectory()
{ {
foreach (var file in new FileInfo(Assembly.GetEntryAssembly().Location).Directory.EnumerateFiles("*.dll")) var location = Assembly.GetEntryAssembly().Location;
if (string.IsNullOrWhiteSpace(location))
return;
var dir = new FileInfo(location).Directory;
if (dir == null)
return;
foreach (var file in dir.EnumerateFiles("*.dll"))
{ {
try try
{ {

5
src/Avalonia.HtmlRenderer/Adapters/ControlAdapter.cs

@ -10,9 +10,11 @@
// - Sun Tsu, // - Sun Tsu,
// "The Art of War" // "The Art of War"
using Avalonia;
using Avalonia.Controls; using Avalonia.Controls;
using Avalonia.Controls.Html; using Avalonia.Controls.Html;
using Avalonia.Input; using Avalonia.Input;
using Avalonia.VisualTree;
using TheArtOfDev.HtmlRenderer.Adapters; using TheArtOfDev.HtmlRenderer.Adapters;
using TheArtOfDev.HtmlRenderer.Adapters.Entities; using TheArtOfDev.HtmlRenderer.Adapters.Entities;
using TheArtOfDev.HtmlRenderer.Core.Utils; using TheArtOfDev.HtmlRenderer.Core.Utils;
@ -54,7 +56,8 @@ namespace TheArtOfDev.HtmlRenderer.Avalonia.Adapters
{ {
get get
{ {
return Util.Convert(MouseDevice.Instance.GetPosition(_control)); var pos = (_control.GetVisualRoot() as IInputRoot)?.MouseDevice?.Position ?? default(Point);
return Util.Convert(pos);
} }
} }

3
src/Avalonia.HtmlRenderer/HtmlControl.cs

@ -17,6 +17,7 @@ using Avalonia.Input;
using Avalonia.Interactivity; using Avalonia.Interactivity;
using Avalonia.Media; using Avalonia.Media;
using Avalonia.Threading; using Avalonia.Threading;
using Avalonia.VisualTree;
using TheArtOfDev.HtmlRenderer.Core; using TheArtOfDev.HtmlRenderer.Core;
using TheArtOfDev.HtmlRenderer.Core.Entities; using TheArtOfDev.HtmlRenderer.Core.Entities;
using TheArtOfDev.HtmlRenderer.Avalonia; using TheArtOfDev.HtmlRenderer.Avalonia;
@ -512,7 +513,7 @@ namespace Avalonia.Controls.Html
protected virtual void InvokeMouseMove() protected virtual void InvokeMouseMove()
{ {
_htmlContainer.HandleMouseMove(this, MouseDevice.Instance?.GetPosition(this) ?? default(Point)); _htmlContainer.HandleMouseMove(this, (this.GetVisualRoot() as IInputRoot)?.MouseDevice?.GetPosition(this) ?? default(Point));
} }
/// <summary> /// <summary>

5
src/Avalonia.Input/FocusManager.cs

@ -176,9 +176,10 @@ namespace Avalonia.Input
/// <param name="e">The event args.</param> /// <param name="e">The event args.</param>
private void OnPreviewPointerPressed(object sender, RoutedEventArgs e) private void OnPreviewPointerPressed(object sender, RoutedEventArgs e)
{ {
if (sender == e.Source) var ev = (PointerPressedEventArgs)e;
if (sender == e.Source && ev.MouseButton == MouseButton.Left)
{ {
var ev = (PointerPressedEventArgs)e;
var element = (ev.Device?.Captured as IInputElement) ?? (e.Source as IInputElement); var element = (ev.Device?.Captured as IInputElement) ?? (e.Source as IInputElement);
if (element == null || !CanFocus(element)) if (element == null || !CanFocus(element))

15
src/Avalonia.Input/ICustomKeyboardNavigation.cs

@ -0,0 +1,15 @@
// 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.Input
{
/// <summary>
/// Designates a control as handling its own keyboard navigation.
/// </summary>
public interface ICustomKeyboardNavigation
{
(bool handled, IInputElement next) GetNext(IInputElement element, NavigationDirection direction);
}
}

7
src/Avalonia.Input/IInputDevice.cs

@ -1,9 +1,16 @@
// Copyright (c) The Avalonia Project. All rights reserved. // 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. // Licensed under the MIT license. See licence.md file in the project root for full license information.
using Avalonia.Input.Raw;
namespace Avalonia.Input namespace Avalonia.Input
{ {
public interface IInputDevice public interface IInputDevice
{ {
/// <summary>
/// Processes raw event. Is called after preprocessing by InputManager
/// </summary>
/// <param name="ev"></param>
void ProcessRawEvent(RawInputEventArgs ev);
} }
} }

8
src/Avalonia.Input/IInputRoot.cs

@ -1,6 +1,8 @@
// Copyright (c) The Avalonia Project. All rights reserved. // 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. // Licensed under the MIT license. See licence.md file in the project root for full license information.
using JetBrains.Annotations;
namespace Avalonia.Input namespace Avalonia.Input
{ {
/// <summary> /// <summary>
@ -27,5 +29,11 @@ namespace Avalonia.Input
/// Gets or sets a value indicating whether access keys are shown in the window. /// Gets or sets a value indicating whether access keys are shown in the window.
/// </summary> /// </summary>
bool ShowAccessKeys { get; set; } bool ShowAccessKeys { get; set; }
/// <summary>
/// Gets associated mouse device
/// </summary>
[CanBeNull]
IMouseDevice MouseDevice { get; }
} }
} }

3
src/Avalonia.Input/IKeyboardDevice.cs

@ -2,6 +2,7 @@
// Licensed under the MIT license. See licence.md file in the project root for full license information. // Licensed under the MIT license. See licence.md file in the project root for full license information.
using System; using System;
using System.ComponentModel;
namespace Avalonia.Input namespace Avalonia.Input
{ {
@ -26,7 +27,7 @@ namespace Avalonia.Input
Toggled = 2, Toggled = 2,
} }
public interface IKeyboardDevice : IInputDevice public interface IKeyboardDevice : IInputDevice, INotifyPropertyChanged
{ {
IInputElement FocusedElement { get; } IInputElement FocusedElement { get; }

1
src/Avalonia.Input/InputManager.cs

@ -35,6 +35,7 @@ namespace Avalonia.Input
public void ProcessInput(RawInputEventArgs e) public void ProcessInput(RawInputEventArgs e)
{ {
_preProcess.OnNext(e); _preProcess.OnNext(e);
e.Device?.ProcessRawEvent(e);
_process.OnNext(e); _process.OnNext(e);
_postProcess.OnNext(e); _postProcess.OnNext(e);
} }

12
src/Avalonia.Input/KeyboardDevice.cs

@ -16,14 +16,6 @@ namespace Avalonia.Input
{ {
private IInputElement _focusedElement; private IInputElement _focusedElement;
public KeyboardDevice()
{
InputManager.Process
.OfType<RawInputEventArgs>()
.Where(e => e.Device == this && !e.Handled)
.Subscribe(ProcessRawEvent);
}
public event PropertyChangedEventHandler PropertyChanged; public event PropertyChangedEventHandler PropertyChanged;
public static IKeyboardDevice Instance => AvaloniaLocator.Current.GetService<IKeyboardDevice>(); public static IKeyboardDevice Instance => AvaloniaLocator.Current.GetService<IKeyboardDevice>();
@ -77,8 +69,10 @@ namespace Avalonia.Input
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
} }
private void ProcessRawEvent(RawInputEventArgs e) public void ProcessRawEvent(RawInputEventArgs e)
{ {
if(e.Handled)
return;
IInputElement element = FocusedElement; IInputElement element = FocusedElement;
if (element != null) if (element != null)

27
src/Avalonia.Input/KeyboardNavigationHandler.cs

@ -2,7 +2,9 @@
// Licensed under the MIT license. See licence.md file in the project root for full license information. // Licensed under the MIT license. See licence.md file in the project root for full license information.
using System; using System;
using System.Linq;
using Avalonia.Input.Navigation; using Avalonia.Input.Navigation;
using Avalonia.VisualTree;
namespace Avalonia.Input namespace Avalonia.Input
{ {
@ -52,6 +54,31 @@ namespace Avalonia.Input
{ {
Contract.Requires<ArgumentNullException>(element != null); Contract.Requires<ArgumentNullException>(element != null);
var customHandler = element.GetSelfAndVisualAncestors()
.OfType<ICustomKeyboardNavigation>()
.FirstOrDefault();
if (customHandler != null)
{
var (handled, next) = customHandler.GetNext(element, direction);
if (handled)
{
if (next != null)
{
return next;
}
else if (direction == NavigationDirection.Next || direction == NavigationDirection.Previous)
{
return TabNavigation.GetNextInTabOrder((IInputElement)customHandler, direction, true);
}
else
{
return null;
}
}
}
if (direction == NavigationDirection.Next || direction == NavigationDirection.Previous) if (direction == NavigationDirection.Next || direction == NavigationDirection.Previous)
{ {
return TabNavigation.GetNextInTabOrder(element, direction); return TabNavigation.GetNextInTabOrder(element, direction);

31
src/Avalonia.Input/MouseDevice.cs

@ -20,23 +20,7 @@ namespace Avalonia.Input
private int _clickCount; private int _clickCount;
private Rect _lastClickRect; private Rect _lastClickRect;
private uint _lastClickTime; private uint _lastClickTime;
/// <summary>
/// Intializes a new instance of <see cref="MouseDevice"/>.
/// </summary>
public MouseDevice()
{
InputManager.Process
.OfType<RawMouseEventArgs>()
.Where(e => e.Device == this && !e.Handled)
.Subscribe(ProcessRawEvent);
}
/// <summary>
/// Gets the current mouse device instance.
/// </summary>
public static IMouseDevice Instance => AvaloniaLocator.Current.GetService<IMouseDevice>();
/// <summary> /// <summary>
/// Gets the control that is currently capturing by the mouse, if any. /// Gets the control that is currently capturing by the mouse, if any.
/// </summary> /// </summary>
@ -50,12 +34,7 @@ namespace Avalonia.Input
get; get;
protected set; protected set;
} }
/// <summary>
/// Gets the application's input manager.
/// </summary>
public IInputManager InputManager => AvaloniaLocator.Current.GetService<IInputManager>();
/// <summary> /// <summary>
/// Gets the mouse position, in screen coordinates. /// Gets the mouse position, in screen coordinates.
/// </summary> /// </summary>
@ -102,6 +81,12 @@ namespace Avalonia.Input
return root.PointToClient(Position) - p; return root.PointToClient(Position) - p;
} }
public void ProcessRawEvent(RawInputEventArgs e)
{
if (!e.Handled && e is RawMouseEventArgs margs)
ProcessRawEvent(margs);
}
private void ProcessRawEvent(RawMouseEventArgs e) private void ProcessRawEvent(RawMouseEventArgs e)
{ {
Contract.Requires<ArgumentNullException>(e != null); Contract.Requires<ArgumentNullException>(e != null);

62
src/Avalonia.Input/Navigation/DirectionalNavigation.cs

@ -41,10 +41,10 @@ namespace Avalonia.Input.Navigation
{ {
case KeyboardNavigationMode.Continue: case KeyboardNavigationMode.Continue:
return GetNextInContainer(element, container, direction) ?? return GetNextInContainer(element, container, direction) ??
GetFirstInNextContainer(element, direction); GetFirstInNextContainer(element, element, direction);
case KeyboardNavigationMode.Cycle: case KeyboardNavigationMode.Cycle:
return GetNextInContainer(element, container, direction) ?? return GetNextInContainer(element, container, direction) ??
GetFocusableDescendent(container, direction); GetFocusableDescendant(container, direction);
case KeyboardNavigationMode.Contained: case KeyboardNavigationMode.Contained:
return GetNextInContainer(element, container, direction); return GetNextInContainer(element, container, direction);
default: default:
@ -53,7 +53,7 @@ namespace Avalonia.Input.Navigation
} }
else else
{ {
return GetFocusableDescendents(element).FirstOrDefault(); return GetFocusableDescendants(element).FirstOrDefault();
} }
} }
@ -71,24 +71,24 @@ namespace Avalonia.Input.Navigation
} }
/// <summary> /// <summary>
/// Gets the first or last focusable descendent of the specified element. /// Gets the first or last focusable descendant of the specified element.
/// </summary> /// </summary>
/// <param name="container">The element.</param> /// <param name="container">The element.</param>
/// <param name="direction">The direction to search.</param> /// <param name="direction">The direction to search.</param>
/// <returns>The element or null if not found.##</returns> /// <returns>The element or null if not found.##</returns>
private static IInputElement GetFocusableDescendent(IInputElement container, NavigationDirection direction) private static IInputElement GetFocusableDescendant(IInputElement container, NavigationDirection direction)
{ {
return IsForward(direction) ? return IsForward(direction) ?
GetFocusableDescendents(container).FirstOrDefault() : GetFocusableDescendants(container).FirstOrDefault() :
GetFocusableDescendents(container).LastOrDefault(); GetFocusableDescendants(container).LastOrDefault();
} }
/// <summary> /// <summary>
/// Gets the focusable descendents of the specified element. /// Gets the focusable descendants of the specified element.
/// </summary> /// </summary>
/// <param name="element">The element.</param> /// <param name="element">The element.</param>
/// <returns>The element's focusable descendents.</returns> /// <returns>The element's focusable descendants.</returns>
private static IEnumerable<IInputElement> GetFocusableDescendents(IInputElement element) private static IEnumerable<IInputElement> GetFocusableDescendants(IInputElement element)
{ {
var children = element.GetVisualChildren().OfType<IInputElement>(); var children = element.GetVisualChildren().OfType<IInputElement>();
@ -99,11 +99,11 @@ namespace Avalonia.Input.Navigation
yield return child; yield return child;
} }
if (child.CanFocusDescendents()) if (child.CanFocusDescendants())
{ {
foreach (var descendent in GetFocusableDescendents(child)) foreach (var descendant in GetFocusableDescendants(child))
{ {
yield return descendent; yield return descendant;
} }
} }
} }
@ -123,11 +123,11 @@ namespace Avalonia.Input.Navigation
{ {
if (direction == NavigationDirection.Down) if (direction == NavigationDirection.Down)
{ {
var descendent = GetFocusableDescendents(element).FirstOrDefault(); var descendant = GetFocusableDescendants(element).FirstOrDefault();
if (descendent != null) if (descendant != null)
{ {
return descendent; return descendant;
} }
} }
@ -156,11 +156,11 @@ namespace Avalonia.Input.Navigation
if (element != null && direction == NavigationDirection.Up) if (element != null && direction == NavigationDirection.Up)
{ {
var descendent = GetFocusableDescendents(element).LastOrDefault(); var descendant = GetFocusableDescendants(element).LastOrDefault();
if (descendent != null) if (descendant != null)
{ {
return descendent; return descendant;
} }
} }
@ -173,10 +173,12 @@ namespace Avalonia.Input.Navigation
/// <summary> /// <summary>
/// Gets the first item that should be focused in the next container. /// Gets the first item that should be focused in the next container.
/// </summary> /// </summary>
/// <param name="element">The element being navigated away from.</param>
/// <param name="container">The container.</param> /// <param name="container">The container.</param>
/// <param name="direction">The direction of the search.</param> /// <param name="direction">The direction of the search.</param>
/// <returns>The first element, or null if there are no more elements.</returns> /// <returns>The first element, or null if there are no more elements.</returns>
private static IInputElement GetFirstInNextContainer( private static IInputElement GetFirstInNextContainer(
IInputElement element,
IInputElement container, IInputElement container,
NavigationDirection direction) NavigationDirection direction)
{ {
@ -193,13 +195,23 @@ namespace Avalonia.Input.Navigation
var siblings = parent.GetVisualChildren() var siblings = parent.GetVisualChildren()
.OfType<IInputElement>() .OfType<IInputElement>()
.Where(FocusExtensions.CanFocusDescendents); .Where(FocusExtensions.CanFocusDescendants);
var sibling = isForward ? var sibling = isForward ?
siblings.SkipWhile(x => x != container).Skip(1).FirstOrDefault() : siblings.SkipWhile(x => x != container).Skip(1).FirstOrDefault() :
siblings.TakeWhile(x => x != container).LastOrDefault(); siblings.TakeWhile(x => x != container).LastOrDefault();
if (sibling != null) if (sibling != null)
{ {
if (sibling is ICustomKeyboardNavigation custom)
{
var (handled, customNext) = custom.GetNext(element, direction);
if (handled)
{
return customNext;
}
}
if (sibling.CanFocus()) if (sibling.CanFocus())
{ {
next = sibling; next = sibling;
@ -207,21 +219,21 @@ namespace Avalonia.Input.Navigation
else else
{ {
next = isForward ? next = isForward ?
GetFocusableDescendents(sibling).FirstOrDefault() : GetFocusableDescendants(sibling).FirstOrDefault() :
GetFocusableDescendents(sibling).LastOrDefault(); GetFocusableDescendants(sibling).LastOrDefault();
} }
} }
if (next == null) if (next == null)
{ {
next = GetFirstInNextContainer(parent, direction); next = GetFirstInNextContainer(element, parent, direction);
} }
} }
else else
{ {
next = isForward ? next = isForward ?
GetFocusableDescendents(container).FirstOrDefault() : GetFocusableDescendants(container).FirstOrDefault() :
GetFocusableDescendents(container).LastOrDefault(); GetFocusableDescendants(container).LastOrDefault();
} }
return next; return next;

6
src/Avalonia.Input/Navigation/FocusExtensions.cs

@ -16,10 +16,10 @@ namespace Avalonia.Input.Navigation
public static bool CanFocus(this IInputElement e) => e.Focusable && e.IsEnabledCore && e.IsVisible; public static bool CanFocus(this IInputElement e) => e.Focusable && e.IsEnabledCore && e.IsVisible;
/// <summary> /// <summary>
/// Checks if descendents of the specified element can be focused. /// Checks if descendants of the specified element can be focused.
/// </summary> /// </summary>
/// <param name="e">The element.</param> /// <param name="e">The element.</param>
/// <returns>True if descendents of the element can be focused.</returns> /// <returns>True if descendants of the element can be focused.</returns>
public static bool CanFocusDescendents(this IInputElement e) => e.IsEnabledCore && e.IsVisible; public static bool CanFocusDescendants(this IInputElement e) => e.IsEnabledCore && e.IsVisible;
} }
} }

107
src/Avalonia.Input/Navigation/TabNavigation.cs

@ -18,13 +18,17 @@ namespace Avalonia.Input.Navigation
/// </summary> /// </summary>
/// <param name="element">The element.</param> /// <param name="element">The element.</param>
/// <param name="direction">The tab direction. Must be Next or Previous.</param> /// <param name="direction">The tab direction. Must be Next or Previous.</param>
/// <param name="outsideElement">
/// If true will not descend into <paramref name="element"/> to find next control.
/// </param>
/// <returns> /// <returns>
/// The next element in the specified direction, or null if <paramref name="element"/> /// The next element in the specified direction, or null if <paramref name="element"/>
/// was the last in the requested direction. /// was the last in the requested direction.
/// </returns> /// </returns>
public static IInputElement GetNextInTabOrder( public static IInputElement GetNextInTabOrder(
IInputElement element, IInputElement element,
NavigationDirection direction) NavigationDirection direction,
bool outsideElement = false)
{ {
Contract.Requires<ArgumentNullException>(element != null); Contract.Requires<ArgumentNullException>(element != null);
Contract.Requires<ArgumentException>( Contract.Requires<ArgumentException>(
@ -40,42 +44,43 @@ namespace Avalonia.Input.Navigation
switch (mode) switch (mode)
{ {
case KeyboardNavigationMode.Continue: case KeyboardNavigationMode.Continue:
return GetNextInContainer(element, container, direction) ?? return GetNextInContainer(element, container, direction, outsideElement) ??
GetFirstInNextContainer(element, direction); GetFirstInNextContainer(element, element, direction);
case KeyboardNavigationMode.Cycle: case KeyboardNavigationMode.Cycle:
return GetNextInContainer(element, container, direction) ?? return GetNextInContainer(element, container, direction, outsideElement) ??
GetFocusableDescendent(container, direction); GetFocusableDescendant(container, direction);
case KeyboardNavigationMode.Contained: case KeyboardNavigationMode.Contained:
return GetNextInContainer(element, container, direction); return GetNextInContainer(element, container, direction, outsideElement);
default: default:
return GetFirstInNextContainer(container, direction); return GetFirstInNextContainer(element, container, direction);
} }
} }
else else
{ {
return GetFocusableDescendents(element).FirstOrDefault(); return GetFocusableDescendants(element, direction).FirstOrDefault();
} }
} }
/// <summary> /// <summary>
/// Gets the first or last focusable descendent of the specified element. /// Gets the first or last focusable descendant of the specified element.
/// </summary> /// </summary>
/// <param name="container">The element.</param> /// <param name="container">The element.</param>
/// <param name="direction">The direction to search.</param> /// <param name="direction">The direction to search.</param>
/// <returns>The element or null if not found.##</returns> /// <returns>The element or null if not found.##</returns>
private static IInputElement GetFocusableDescendent(IInputElement container, NavigationDirection direction) private static IInputElement GetFocusableDescendant(IInputElement container, NavigationDirection direction)
{ {
return direction == NavigationDirection.Next ? return direction == NavigationDirection.Next ?
GetFocusableDescendents(container).FirstOrDefault() : GetFocusableDescendants(container, direction).FirstOrDefault() :
GetFocusableDescendents(container).LastOrDefault(); GetFocusableDescendants(container, direction).LastOrDefault();
} }
/// <summary> /// <summary>
/// Gets the focusable descendents of the specified element. /// Gets the focusable descendants of the specified element.
/// </summary> /// </summary>
/// <param name="element">The element.</param> /// <param name="element">The element.</param>
/// <returns>The element's focusable descendents.</returns> /// <param name="direction">The tab direction. Must be Next or Previous.</param>
private static IEnumerable<IInputElement> GetFocusableDescendents(IInputElement element) /// <returns>The element's focusable descendants.</returns>
private static IEnumerable<IInputElement> GetFocusableDescendants(IInputElement element, NavigationDirection direction)
{ {
var mode = KeyboardNavigation.GetTabNavigation((InputElement)element); var mode = KeyboardNavigation.GetTabNavigation((InputElement)element);
@ -103,16 +108,25 @@ namespace Avalonia.Input.Navigation
foreach (var child in children) foreach (var child in children)
{ {
if (child.CanFocus()) var customNext = GetCustomNext(child, direction);
if (customNext.handled)
{ {
yield return child; yield return customNext.next;
} }
else
if (child.CanFocusDescendents())
{ {
foreach (var descendent in GetFocusableDescendents(child)) if (child.CanFocus())
{ {
yield return descendent; yield return child;
}
if (child.CanFocusDescendants())
{
foreach (var descendant in GetFocusableDescendants(child, direction))
{
yield return descendant;
}
} }
} }
} }
@ -124,19 +138,23 @@ namespace Avalonia.Input.Navigation
/// <param name="element">The starting element/</param> /// <param name="element">The starting element/</param>
/// <param name="container">The container.</param> /// <param name="container">The container.</param>
/// <param name="direction">The direction.</param> /// <param name="direction">The direction.</param>
/// <param name="outsideElement">
/// If true will not descend into <paramref name="element"/> to find next control.
/// </param>
/// <returns>The next element, or null if the element is the last.</returns> /// <returns>The next element, or null if the element is the last.</returns>
private static IInputElement GetNextInContainer( private static IInputElement GetNextInContainer(
IInputElement element, IInputElement element,
IInputElement container, IInputElement container,
NavigationDirection direction) NavigationDirection direction,
bool outsideElement)
{ {
if (direction == NavigationDirection.Next) if (direction == NavigationDirection.Next && !outsideElement)
{ {
var descendent = GetFocusableDescendents(element).FirstOrDefault(); var descendant = GetFocusableDescendants(element, direction).FirstOrDefault();
if (descendent != null) if (descendant != null)
{ {
return descendent; return descendant;
} }
} }
@ -167,11 +185,11 @@ namespace Avalonia.Input.Navigation
if (element != null && direction == NavigationDirection.Previous) if (element != null && direction == NavigationDirection.Previous)
{ {
var descendent = GetFocusableDescendents(element).LastOrDefault(); var descendant = GetFocusableDescendants(element, direction).LastOrDefault();
if (descendent != null) if (descendant != null)
{ {
return descendent; return descendant;
} }
} }
@ -184,10 +202,12 @@ namespace Avalonia.Input.Navigation
/// <summary> /// <summary>
/// Gets the first item that should be focused in the next container. /// Gets the first item that should be focused in the next container.
/// </summary> /// </summary>
/// <param name="element">The element being navigated away from.</param>
/// <param name="container">The container.</param> /// <param name="container">The container.</param>
/// <param name="direction">The direction of the search.</param> /// <param name="direction">The direction of the search.</param>
/// <returns>The first element, or null if there are no more elements.</returns> /// <returns>The first element, or null if there are no more elements.</returns>
private static IInputElement GetFirstInNextContainer( private static IInputElement GetFirstInNextContainer(
IInputElement element,
IInputElement container, IInputElement container,
NavigationDirection direction) NavigationDirection direction)
{ {
@ -203,13 +223,20 @@ namespace Avalonia.Input.Navigation
var siblings = parent.GetVisualChildren() var siblings = parent.GetVisualChildren()
.OfType<IInputElement>() .OfType<IInputElement>()
.Where(FocusExtensions.CanFocusDescendents); .Where(FocusExtensions.CanFocusDescendants);
var sibling = direction == NavigationDirection.Next ? var sibling = direction == NavigationDirection.Next ?
siblings.SkipWhile(x => x != container).Skip(1).FirstOrDefault() : siblings.SkipWhile(x => x != container).Skip(1).FirstOrDefault() :
siblings.TakeWhile(x => x != container).LastOrDefault(); siblings.TakeWhile(x => x != container).LastOrDefault();
if (sibling != null) if (sibling != null)
{ {
var customNext = GetCustomNext(sibling, direction);
if (customNext.handled)
{
return customNext.next;
}
if (sibling.CanFocus()) if (sibling.CanFocus())
{ {
next = sibling; next = sibling;
@ -217,24 +244,34 @@ namespace Avalonia.Input.Navigation
else else
{ {
next = direction == NavigationDirection.Next ? next = direction == NavigationDirection.Next ?
GetFocusableDescendents(sibling).FirstOrDefault() : GetFocusableDescendants(sibling, direction).FirstOrDefault() :
GetFocusableDescendents(sibling).LastOrDefault(); GetFocusableDescendants(sibling, direction).LastOrDefault();
} }
} }
if (next == null) if (next == null)
{ {
next = GetFirstInNextContainer(parent, direction); next = GetFirstInNextContainer(element, parent, direction);
} }
} }
else else
{ {
next = direction == NavigationDirection.Next ? next = direction == NavigationDirection.Next ?
GetFocusableDescendents(container).FirstOrDefault() : GetFocusableDescendants(container, direction).FirstOrDefault() :
GetFocusableDescendents(container).LastOrDefault(); GetFocusableDescendants(container, direction).LastOrDefault();
} }
return next; return next;
} }
private static (bool handled, IInputElement next) GetCustomNext(IInputElement element, NavigationDirection direction)
{
if (element is ICustomKeyboardNavigation custom)
{
return custom.GetNext(element, direction);
}
return (false, null);
}
} }
} }

10
src/Avalonia.Layout/IEmbeddedLayoutRoot.cs

@ -0,0 +1,10 @@
namespace Avalonia.Layout
{
/// <summary>
/// A special layout root with enforced size for Arrange pass
/// </summary>
public interface IEmbeddedLayoutRoot : ILayoutRoot
{
Size AllocatedSize { get; }
}
}

99
src/Avalonia.Layout/LayoutManager.cs

@ -14,8 +14,8 @@ namespace Avalonia.Layout
/// </summary> /// </summary>
public class LayoutManager : ILayoutManager public class LayoutManager : ILayoutManager
{ {
private readonly HashSet<ILayoutable> _toMeasure = new HashSet<ILayoutable>(); private readonly Queue<ILayoutable> _toMeasure = new Queue<ILayoutable>();
private readonly HashSet<ILayoutable> _toArrange = new HashSet<ILayoutable>(); private readonly Queue<ILayoutable> _toArrange = new Queue<ILayoutable>();
private bool _queued; private bool _queued;
private bool _running; private bool _running;
@ -30,8 +30,18 @@ namespace Avalonia.Layout
Contract.Requires<ArgumentNullException>(control != null); Contract.Requires<ArgumentNullException>(control != null);
Dispatcher.UIThread.VerifyAccess(); Dispatcher.UIThread.VerifyAccess();
_toMeasure.Add(control); if (!control.IsAttachedToVisualTree)
_toArrange.Add(control); {
#if DEBUG
throw new AvaloniaInternalException(
"LayoutManager.InvalidateMeasure called on a control that is detached from the visual tree.");
#else
return;
#endif
}
_toMeasure.Enqueue(control);
_toArrange.Enqueue(control);
QueueLayoutPass(); QueueLayoutPass();
} }
@ -41,7 +51,17 @@ namespace Avalonia.Layout
Contract.Requires<ArgumentNullException>(control != null); Contract.Requires<ArgumentNullException>(control != null);
Dispatcher.UIThread.VerifyAccess(); Dispatcher.UIThread.VerifyAccess();
_toArrange.Add(control); if (!control.IsAttachedToVisualTree)
{
#if DEBUG
throw new AvaloniaInternalException(
"LayoutManager.InvalidateArrange called on a control that is detached from the visual tree.");
#else
return;
#endif
}
_toArrange.Enqueue(control);
QueueLayoutPass(); QueueLayoutPass();
} }
@ -108,8 +128,12 @@ namespace Avalonia.Layout
{ {
while (_toMeasure.Count > 0) while (_toMeasure.Count > 0)
{ {
var next = _toMeasure.First(); var control = _toMeasure.Dequeue();
Measure(next);
if (!control.IsMeasureValid && control.IsAttachedToVisualTree)
{
Measure(control);
}
} }
} }
@ -117,53 +141,62 @@ namespace Avalonia.Layout
{ {
while (_toArrange.Count > 0 && _toMeasure.Count == 0) while (_toArrange.Count > 0 && _toMeasure.Count == 0)
{ {
var next = _toArrange.First(); var control = _toArrange.Dequeue();
Arrange(next);
if (!control.IsArrangeValid && control.IsAttachedToVisualTree)
{
Arrange(control);
}
} }
} }
private void Measure(ILayoutable control) private void Measure(ILayoutable control)
{ {
var root = control as ILayoutRoot; // Controls closest to the visual root need to be arranged first. We don't try to store
var parent = control.VisualParent as ILayoutable; // ordered invalidation lists, instead we traverse the tree upwards, measuring the
// controls closest to the root first. This has been shown by benchmarks to be the
if (root != null) // fastest and most memory-efficent algorithm.
{ if (control.VisualParent is ILayoutable parent)
root.Measure(root.MaxClientSize);
}
else if (parent != null)
{ {
Measure(parent); Measure(parent);
} }
if (!control.IsMeasureValid) // If the control being measured has IsMeasureValid == true here then its measure was
// handed by an ancestor and can be ignored. The measure may have also caused the
// control to be removed.
if (!control.IsMeasureValid && control.IsAttachedToVisualTree)
{ {
control.Measure(control.PreviousMeasure.Value); if (control is ILayoutRoot root)
{
root.Measure(Size.Infinity);
}
else
{
control.Measure(control.PreviousMeasure.Value);
}
} }
_toMeasure.Remove(control);
} }
private void Arrange(ILayoutable control) private void Arrange(ILayoutable control)
{ {
var root = control as ILayoutRoot; if (control.VisualParent is ILayoutable parent)
var parent = control.VisualParent as ILayoutable;
if (root != null)
{
root.Arrange(new Rect(root.DesiredSize));
}
else if (parent != null)
{ {
Arrange(parent); Arrange(parent);
} }
if (control.PreviousArrange.HasValue) if (!control.IsArrangeValid && control.IsAttachedToVisualTree)
{ {
control.Arrange(control.PreviousArrange.Value); if (control is IEmbeddedLayoutRoot embeddedRoot)
control.Arrange(new Rect(embeddedRoot.AllocatedSize));
else if (control is ILayoutRoot root)
control.Arrange(new Rect(root.DesiredSize));
else if (control.PreviousArrange != null)
{
// Has been observed that PreviousArrange sometimes is null, probably a bug somewhere else.
// Condition observed: control.VisualParent is Scrollbar, control is Border.
control.Arrange(control.PreviousArrange.Value);
}
} }
_toArrange.Remove(control);
} }
private void QueueLayoutPass() private void QueueLayoutPass()

34
src/Avalonia.Layout/Layoutable.cs

@ -367,6 +367,14 @@ namespace Avalonia.Layout
} }
} }
/// <summary>
/// Called by InvalidateMeasure
/// </summary>
protected virtual void OnMeasureInvalidated()
{
}
/// <summary> /// <summary>
/// Invalidates the measurement of the control and queues a new layout pass. /// Invalidates the measurement of the control and queues a new layout pass.
/// </summary> /// </summary>
@ -378,8 +386,13 @@ namespace Avalonia.Layout
IsMeasureValid = false; IsMeasureValid = false;
IsArrangeValid = false; IsArrangeValid = false;
LayoutManager.Instance?.InvalidateMeasure(this);
InvalidateVisual(); if (((ILayoutable)this).IsAttachedToVisualTree)
{
LayoutManager.Instance?.InvalidateMeasure(this);
InvalidateVisual();
}
OnMeasureInvalidated();
} }
} }
@ -393,8 +406,12 @@ namespace Avalonia.Layout
Logger.Verbose(LogArea.Layout, this, "Invalidated arrange"); Logger.Verbose(LogArea.Layout, this, "Invalidated arrange");
IsArrangeValid = false; IsArrangeValid = false;
LayoutManager.Instance?.InvalidateArrange(this);
InvalidateVisual(); if (((ILayoutable)this).IsAttachedToVisualTree)
{
LayoutManager.Instance?.InvalidateArrange(this);
InvalidateVisual();
}
} }
} }
@ -456,10 +473,9 @@ namespace Avalonia.Layout
ApplyTemplate(); ApplyTemplate();
var constrained = LayoutHelper var constrained = LayoutHelper.ApplyLayoutConstraints(
.ApplyLayoutConstraints(this, availableSize) this,
.Deflate(margin); availableSize.Deflate(margin));
var measured = MeasureOverride(constrained); var measured = MeasureOverride(constrained);
var width = measured.Width; var width = measured.Width;
@ -613,7 +629,7 @@ namespace Avalonia.Layout
/// <inheritdoc/> /// <inheritdoc/>
protected override sealed void OnVisualParentChanged(IVisual oldParent, IVisual newParent) protected override sealed void OnVisualParentChanged(IVisual oldParent, IVisual newParent)
{ {
foreach (ILayoutable i in this.GetSelfAndVisualDescendents()) foreach (ILayoutable i in this.GetSelfAndVisualDescendants())
{ {
i.InvalidateMeasure(); i.InvalidateMeasure();
} }

2
src/Avalonia.Styling/Controls/NameScope.cs

@ -50,7 +50,7 @@ namespace Avalonia.Controls
return result; return result;
} }
visual = (visual as ILogical).LogicalParent as Visual; visual = (visual as ILogical)?.LogicalParent as Visual;
} }
return null; return null;

10
src/Avalonia.Styling/LogicalTree/ILogical.cs

@ -36,6 +36,16 @@ namespace Avalonia.LogicalTree
/// </summary> /// </summary>
IAvaloniaReadOnlyList<ILogical> LogicalChildren { get; } IAvaloniaReadOnlyList<ILogical> LogicalChildren { get; }
/// <summary>
/// Notifies the control that it is being attached to a rooted logical tree.
/// </summary>
/// <param name="e">The event args.</param>
/// <remarks>
/// This method will be called automatically by the framework, you should not need to call
/// this method yourself.
/// </remarks>
void NotifyAttachedToLogicalTree(LogicalTreeAttachmentEventArgs e);
/// <summary> /// <summary>
/// Notifies the control that it is being detached from a rooted logical tree. /// Notifies the control that it is being detached from a rooted logical tree.
/// </summary> /// </summary>

6
src/Avalonia.Styling/LogicalTree/LogicalExtensions.cs

@ -37,15 +37,15 @@ namespace Avalonia.LogicalTree
return logical.LogicalChildren; return logical.LogicalChildren;
} }
public static IEnumerable<ILogical> GetLogicalDescendents(this ILogical logical) public static IEnumerable<ILogical> GetLogicalDescendants(this ILogical logical)
{ {
foreach (ILogical child in logical.LogicalChildren) foreach (ILogical child in logical.LogicalChildren)
{ {
yield return child; yield return child;
foreach (ILogical descendent in child.GetLogicalDescendents()) foreach (ILogical descendant in child.GetLogicalDescendants())
{ {
yield return descendent; yield return descendant;
} }
} }
} }

14
src/Avalonia.Styling/Styling/DescendentSelector.cs

@ -7,16 +7,16 @@ using Avalonia.LogicalTree;
namespace Avalonia.Styling namespace Avalonia.Styling
{ {
internal class DescendentSelector : Selector internal class DescendantSelector : Selector
{ {
private readonly Selector _parent; private readonly Selector _parent;
private string _selectorString; private string _selectorString;
public DescendentSelector(Selector parent) public DescendantSelector(Selector parent)
{ {
if (parent == null) if (parent == null)
{ {
throw new InvalidOperationException("Descendent selector must be preceeded by a selector."); throw new InvalidOperationException("Descendant selector must be preceeded by a selector.");
} }
_parent = parent; _parent = parent;
@ -41,7 +41,7 @@ namespace Avalonia.Styling
protected override SelectorMatch Evaluate(IStyleable control, bool subscribe) protected override SelectorMatch Evaluate(IStyleable control, bool subscribe)
{ {
ILogical c = (ILogical)control; ILogical c = (ILogical)control;
List<IObservable<bool>> descendentMatches = new List<IObservable<bool>>(); List<IObservable<bool>> descendantMatches = new List<IObservable<bool>>();
while (c != null) while (c != null)
{ {
@ -60,14 +60,14 @@ namespace Avalonia.Styling
} }
else else
{ {
descendentMatches.Add(match.ObservableResult); descendantMatches.Add(match.ObservableResult);
} }
} }
} }
if (descendentMatches.Count > 0) if (descendantMatches.Count > 0)
{ {
return new SelectorMatch(StyleActivator.Or(descendentMatches)); return new SelectorMatch(StyleActivator.Or(descendantMatches));
} }
else else
{ {

6
src/Avalonia.Styling/Styling/Selectors.cs

@ -42,13 +42,13 @@ namespace Avalonia.Styling
} }
/// <summary> /// <summary>
/// Returns a selector which matches a descendent of a previous selector. /// Returns a selector which matches a descendant of a previous selector.
/// </summary> /// </summary>
/// <param name="previous">The previous selector.</param> /// <param name="previous">The previous selector.</param>
/// <returns>The selector.</returns> /// <returns>The selector.</returns>
public static Selector Descendent(this Selector previous) public static Selector Descendant(this Selector previous)
{ {
return new DescendentSelector(previous); return new DescendantSelector(previous);
} }
/// <summary> /// <summary>

4
src/Avalonia.Styling/Styling/Style.cs

@ -66,12 +66,12 @@ namespace Avalonia.Styling
} }
/// <summary> /// <summary>
/// Gets or sets style's selector. /// Gets or sets the style's selector.
/// </summary> /// </summary>
public Selector Selector { get; set; } public Selector Selector { get; set; }
/// <summary> /// <summary>
/// Gets or sets style's setters. /// Gets or sets the style's setters.
/// </summary> /// </summary>
[Content] [Content]
public IList<ISetter> Setters { get; set; } = new List<ISetter>(); public IList<ISetter> Setters { get; set; } = new List<ISetter>();

2
src/Avalonia.Visuals/Platform/ILockedFramebuffer.cs

@ -27,7 +27,7 @@ namespace Avalonia.Platform
/// <summary> /// <summary>
/// DPI of underling screen /// DPI of underling screen
/// </summary> /// </summary>
Size Dpi { get; } Vector Dpi { get; }
/// <summary> /// <summary>
/// Pixel format /// Pixel format

2
src/Avalonia.Visuals/Rendering/ImmediateRenderer.cs

@ -164,7 +164,7 @@ namespace Avalonia.Rendering
private static void ClearTransformedBounds(IVisual visual) private static void ClearTransformedBounds(IVisual visual)
{ {
foreach (var e in visual.GetSelfAndVisualDescendents()) foreach (var e in visual.GetSelfAndVisualDescendants())
{ {
BoundsTracker.SetTransformedBounds((Visual)visual, null); BoundsTracker.SetTransformedBounds((Visual)visual, null);
} }

2
src/Avalonia.Visuals/Rendering/ZIndexComparer.cs

@ -8,6 +8,6 @@ namespace Avalonia.Rendering
{ {
public static readonly ZIndexComparer Instance = new ZIndexComparer(); public static readonly ZIndexComparer Instance = new ZIndexComparer();
public int Compare(IVisual x, IVisual y) => x.ZIndex.CompareTo(y.ZIndex); public int Compare(IVisual x, IVisual y) => (x?.ZIndex ?? 0).CompareTo(y?.ZIndex ?? 0);
} }
} }

13
src/Avalonia.Visuals/Vector.cs

@ -52,8 +52,6 @@ namespace Avalonia
return new Point(a._x, a._y); return new Point(a._x, a._y);
} }
/// <summary> /// <summary>
/// Calculates the dot product of two vectors /// Calculates the dot product of two vectors
/// </summary> /// </summary>
@ -65,6 +63,17 @@ namespace Avalonia
return a.X*b.X + a.Y*b.Y; return a.X*b.X + a.Y*b.Y;
} }
/// <summary>
/// Scales a vector.
/// </summary>
/// <param name="vector">The vector</param>
/// <param name="scale">The scaling factor.</param>
/// <returns>The scaled vector.</returns>
public static Vector operator *(Vector vector, double scale)
{
return new Vector(vector._x * scale, vector._y * scale);
}
/// <summary> /// <summary>
/// Length of the vector /// Length of the vector
/// </summary> /// </summary>

6
src/Avalonia.Visuals/Visual.cs

@ -314,7 +314,7 @@ namespace Avalonia
/// <summary> /// <summary>
/// Calls the <see cref="OnAttachedToVisualTree(VisualTreeAttachmentEventArgs)"/> method /// Calls the <see cref="OnAttachedToVisualTree(VisualTreeAttachmentEventArgs)"/> method
/// for this control and all of its visual descendents. /// for this control and all of its visual descendants.
/// </summary> /// </summary>
/// <param name="e">The event args.</param> /// <param name="e">The event args.</param>
protected virtual void OnAttachedToVisualTreeCore(VisualTreeAttachmentEventArgs e) protected virtual void OnAttachedToVisualTreeCore(VisualTreeAttachmentEventArgs e)
@ -342,7 +342,7 @@ namespace Avalonia
/// <summary> /// <summary>
/// Calls the <see cref="OnDetachedFromVisualTree(VisualTreeAttachmentEventArgs)"/> method /// Calls the <see cref="OnDetachedFromVisualTree(VisualTreeAttachmentEventArgs)"/> method
/// for this control and all of its visual descendents. /// for this control and all of its visual descendants.
/// </summary> /// </summary>
/// <param name="e">The event args.</param> /// <param name="e">The event args.</param>
protected virtual void OnDetachedFromVisualTreeCore(VisualTreeAttachmentEventArgs e) protected virtual void OnDetachedFromVisualTreeCore(VisualTreeAttachmentEventArgs e)
@ -422,7 +422,7 @@ namespace Avalonia
if (visual == null) if (visual == null)
{ {
throw new ArgumentException("'visual' is not a descendent of 'ancestor'."); throw new ArgumentException("'visual' is not a descendant of 'ancestor'.");
} }
} }

Some files were not shown because too many files changed in this diff

Loading…
Cancel
Save