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
#################
tools/
tools/*
!tools/packages.config
.nuget
artifacts/
nuget

43
Avalonia.sln

@ -193,6 +193,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Avalonia.LinuxFramebuffer",
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Direct3DInteropSample", "samples\interop\Direct3DInteropSample\Direct3DInteropSample.csproj", "{638580B0-7910-40EF-B674-DCB34DA308CD}"
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
GlobalSection(SharedMSBuildProjectFiles) = preSolution
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|x86.ActiveCfg = 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
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
@ -3252,5 +3294,6 @@ Global
{4D6FAF79-58B4-482F-9122-0668C346364C} = {74487168-7D91-487E-BF93-055F2251461E}
{854568D5-13D1-4B4F-B50D-534DC7EFD3C9} = {86C53C40-57AA-45B8-AD42-FAE0EFDF0F2B}
{638580B0-7910-40EF-B674-DCB34DA308CD} = {A0CC0258-D18C-4AB3-854F-7101680FC3F9}
{CBC4FF2F-92D4-420B-BE21-9FE0B930B04E} = {B39A8919-9F95-48FE-AD7B-76E08B509888}
EndGlobalSection
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">
<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/=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>

1
appveyor.yml

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

68
build.cake

@ -5,12 +5,13 @@
#addin "nuget:?package=Polly&version=4.2.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:?package=JetBrains.dotMemoryUnit&version=2.1.20150828.125449"
#tool "nuget:?package=JetBrains.dotMemoryUnit&version=2.3.20160517.113140"
#tool "JetBrains.ReSharper.CommandLineTools"
///////////////////////////////////////////////////////////////////////////////
// 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"
///////////////////////////////////////////////////////////////////////////////
@ -97,14 +98,13 @@ Task("Clean")
CleanDirectory(parameters.TestsRoot);
});
Task("Restore-NuGet-Packages")
.IsDependentOn("Clean")
.WithCriteria(parameters.IsRunningOnWindows)
.Does(() =>
{
var maxRetryCount = 5;
var toolTimeout = 1d;
var toolTimeout = 2d;
Policy
.Handle<Exception>()
.Retry(maxRetryCount, (exception, retryCount, context) => {
@ -170,23 +170,25 @@ void RunCoreTest(string dir, Parameters parameters, bool net461Only)
continue;
Information("Running for " + fw);
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")
.IsDependentOn("Clean")
.Does(() => {
RunCoreTest("./tests/Avalonia.Base.UnitTests", parameters, false);
RunCoreTest("./tests/Avalonia.Controls.UnitTests", parameters, true);
RunCoreTest("./tests/Avalonia.Input.UnitTests", parameters, true);
RunCoreTest("./tests/Avalonia.Interactivity.UnitTests", parameters, true);
RunCoreTest("./tests/Avalonia.Layout.UnitTests", parameters, true);
//RunCoreTest("./tests/Avalonia.Markup.UnitTests", parameters, true);
//RunCoreTest("./tests/Avalonia.Markup.Xaml.UnitTests", parameters, true);
RunCoreTest("./tests/Avalonia.Styling.UnitTests", parameters, true);
RunCoreTest("./tests/Avalonia.Visuals.UnitTests", parameters, true);
RunCoreTest("./tests/Avalonia.Controls.UnitTests", parameters, false);
RunCoreTest("./tests/Avalonia.Input.UnitTests", parameters, false);
RunCoreTest("./tests/Avalonia.Interactivity.UnitTests", parameters, false);
RunCoreTest("./tests/Avalonia.Layout.UnitTests", parameters, false);
RunCoreTest("./tests/Avalonia.Markup.UnitTests", parameters, false);
RunCoreTest("./tests/Avalonia.Markup.Xaml.UnitTests", parameters, false);
RunCoreTest("./tests/Avalonia.Styling.UnitTests", parameters, false);
RunCoreTest("./tests/Avalonia.Visuals.UnitTests", parameters, false);
});
Task("Run-Unit-Tests")
@ -279,11 +281,15 @@ Task("Zip-Files")
Zip(parameters.ZipSourceControlCatalogDesktopDirs,
parameters.ZipTargetControlCatalogDesktopDirs,
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"));
});
Task("Create-NuGet-Packages")
.IsDependentOn("Run-Unit-Tests")
.IsDependentOn("Inspect")
.Does(() =>
{
foreach(var nuspec in packages.NuspecNuGetSettings)
@ -331,7 +337,6 @@ Task("Publish-NuGet")
.WithCriteria(() => !parameters.IsLocalBuild)
.WithCriteria(() => !parameters.IsPullRequest)
.WithCriteria(() => parameters.IsMainRepo)
.WithCriteria(() => parameters.IsMasterBranch)
.WithCriteria(() => parameters.IsNuGetRelease)
.Does(() =>
{
@ -360,6 +365,39 @@ Task("Publish-NuGet")
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
///////////////////////////////////////////////////////////////////////////////

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

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

2
build/Moq.props

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

3
build/SkiaSharp.props

@ -1,5 +1,6 @@
<Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<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>
</Project>

6
build/XUnit.props

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

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}"/>
</Border>
</DataTemplate>
</UserControl.Styles>
</UserControl.DataTemplates>
<!-- Assuming that DataContext.Foo is an object of type
MyApp.ViewModels.FooViewModel then a red border with a corner
radius of 8 containing a TextBox will be displayed here -->

2
docs/tutorial/gettingstarted.md

@ -4,7 +4,7 @@
![](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”
dialog (yes, icons still to come :) ):

45
packages.cake

@ -7,6 +7,7 @@ public class Packages
public FilePath[] BinFiles { get; private set; }
public string NugetPackagesDir {get; private set;}
public string SkiaSharpVersion {get; private set; }
public string SkiaSharpLinuxVersion {get; private set; }
public Packages(ICakeContext context, Parameters parameters)
{
// NUGET NUSPECS
@ -74,7 +75,9 @@ public class Packages
var SplatVersion = packageVersions["Splat"].FirstOrDefault().Item1;
var SpracheVersion = packageVersions["Sprache"].FirstOrDefault().Item1;
var SystemReactiveVersion = packageVersions["System.Reactive"].FirstOrDefault().Item1;
var SystemValueTupleVersion = packageVersions["System.ValueTuple"].FirstOrDefault().Item1;
SkiaSharpVersion = packageVersions["SkiaSharp"].FirstOrDefault().Item1;
SkiaSharpLinuxVersion = packageVersions["Avalonia.Skia.Linux.Natives"].FirstOrDefault().Item1;
var SharpDXVersion = packageVersions["SharpDX"].FirstOrDefault().Item1;
var SharpDXDirect2D1Version = packageVersions["SharpDX.Direct2D1"].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: Sprache, version: {0}", SpracheVersion);
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: Avalonia.Skia.Linux.Natives, version: {0}", SkiaSharpLinuxVersion);
context.Information("Package: SharpDX, version: {0}", SharpDXVersion);
context.Information("Package: SharpDX.Direct2D1, version: {0}", SharpDXDirect2D1Version);
context.Information("Package: SharpDX.Direct3D11, version: {0}", SharpDXDirect3D11Version);
@ -194,6 +199,7 @@ public class Packages
new NuSpecDependency() { Id = "Splat", Version = SplatVersion },
new NuSpecDependency() { Id = "Sprache", Version = SpracheVersion },
new NuSpecDependency() { Id = "System.Reactive", Version = SystemReactiveVersion },
new NuSpecDependency() { Id = "System.ValueTuple", Version = SystemValueTupleVersion },
//.NET Core
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" },
@ -201,7 +207,8 @@ public class Packages
new NuSpecDependency() { Id = "Splat", TargetFramework = "netcoreapp1.0", Version = SplatVersion },
new NuSpecDependency() { Id = "Serilog", TargetFramework = "netcoreapp1.0", Version = SerilogVersion },
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
.Concat(win32CoreLibrariesNuSpecContent).Concat(net45RuntimePlatform)
@ -425,10 +432,7 @@ public class Packages
{
new NuSpecDependency() { Id = "Avalonia", Version = parameters.Version },
new NuSpecDependency() { Id = "SkiaSharp", Version = SkiaSharpVersion },
//netstandard1.3
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" }
new NuSpecDependency() { Id = "Avalonia.Skia.Linux.Natives", Version = SkiaSharpLinuxVersion }
},
Files = new []
{
@ -446,11 +450,17 @@ public class Packages
Id = "Avalonia.Desktop",
Dependencies = new []
{
new NuSpecDependency() { Id = "Avalonia.Win32", Version = parameters.Version },
new NuSpecDependency() { Id = "Avalonia.Direct2D1", Version = parameters.Version },
new NuSpecDependency() { Id = "Avalonia.Gtk", Version = parameters.Version },
new NuSpecDependency() { Id = "Avalonia.Cairo", Version = parameters.Version },
new NuSpecDependency() { Id = "Avalonia.Skia.Desktop", Version = parameters.Version }
//Full .NET
new NuSpecDependency() { Id = "Avalonia.Direct2D1", TargetFramework="net45", Version = parameters.Version },
new NuSpecDependency() { Id = "Avalonia.Gtk", TargetFramework="net45", Version = parameters.Version },
new NuSpecDependency() { Id = "Avalonia.Cairo", TargetFramework="net45", 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[]
{
@ -459,6 +469,21 @@ public class Packages
BasePath = context.Directory("./"),
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
///////////////////////////////////////////////////////////////////////////////

14
parameters.cake

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

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
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
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

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

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

23
samples/ControlCatalog/Pages/MenuPage.xaml

@ -31,5 +31,28 @@
</MenuItem>
</Menu>
</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>
</UserControl>

1
samples/interop/Direct3DInteropSample/Direct3DInteropSample.csproj

@ -22,6 +22,7 @@
<ItemGroup>
<ProjectReference Include="..\..\..\src\Avalonia.DesignerSupport\Avalonia.DesignerSupport.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\Windows\Avalonia.Direct2D1\Avalonia.Direct2D1.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 Rational(60, 1), Format.R8G8B8A8_UNorm),
IsWindowed = true,
OutputHandle = PlatformImpl.Handle.Handle,
OutputHandle = PlatformImpl?.Handle.Handle ?? IntPtr.Zero,
SampleDescription = new SampleDescription(1, 0),
SwapEffect = SwapEffect.Discard,
Usage = Usage.RenderTargetOutput

1
samples/interop/GtkInteropDemo/GtkInteropDemo.csproj

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

14
samples/interop/WindowsInteropTest/EmbedToWpfDemo.xaml

@ -1,10 +1,12 @@
<Window x:Class="WindowsInteropTest.EmbedToWpfDemo"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
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:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="clr-namespace:WindowsInteropTest"
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"
d:DesignHeight="400" d:DesignWidth="400" MinWidth="500" MinHeight="400">
<DockPanel>
@ -14,8 +16,18 @@
<Calendar/>
</StackPanel>
</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">
<embedding:WpfAvaloniaControlHost x:Name="Host"/>
<wpf:WpfAvaloniaHost x:Name="Host"/>
</GroupBox>
</DockPanel>
</Window>

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

@ -11,7 +11,9 @@ using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
using Avalonia;
using Avalonia.Controls;
using Avalonia.VisualTree;
using ControlCatalog;
using Window = System.Windows.Window;
@ -25,7 +27,18 @@ namespace WindowsInteropTest
public EmbedToWpfDemo()
{
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>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
<PlatformTarget>AnyCPU</PlatformTarget>
<PlatformTarget>x86</PlatformTarget>
<DebugSymbols>true</DebugSymbols>
<DebugType>full</DebugType>
<Optimize>false</Optimize>
@ -164,6 +164,10 @@
<Project>{3e908f67-5543-4879-a1dc-08eace79b3cd}</Project>
<Name>Avalonia.Direct2D1</Name>
</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">
<Project>{811a76cf-1cf6-440f-963b-bbe31bd72a82}</Project>
<Name>Avalonia.Win32</Name>
@ -179,6 +183,8 @@
<Generator>MSBuild:Compile</Generator>
</Page>
</ItemGroup>
<Import Project="..\..\..\build\Rx.props" />
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
<Import Project="..\..\..\build\SkiaSharp.props" />
<Import Project="$(MSBuildThisFileDirectory)..\..\..\src\Shared\nuget.workaround.targets" />
</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<IStandardCursorFactory>().ToTransient<CursorFactory>()
.Bind<IKeyboardDevice>().ToSingleton<AndroidKeyboardDevice>()
.Bind<IMouseDevice>().ToSingleton<AndroidMouseDevice>()
.Bind<IPlatformSettings>().ToConstant(Instance)
.Bind<IRendererFactory>().ToConstant(ImmediateRenderer.Factory)
.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 static AndroidMouseDevice Instance { get; } = new 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 Height { 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; }
[DllImport("android")]

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

@ -10,6 +10,7 @@ using Avalonia.Platform;
using System;
using System.Collections.Generic;
using System.Reactive.Disposables;
using Avalonia.Android.Platform.Input;
using Avalonia.Controls;
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<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)
{
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
//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>
</Compile>
</ItemGroup>
<Import Project="..\..\build\Base.props" />
<Import Project="..\..\build\Rx.props" />
<Import Project="..\..\build\JetBrains.Annotations.props" />
</Project>

11
src/Avalonia.Base/AvaloniaObject.cs

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

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

@ -103,11 +103,9 @@ namespace Avalonia.Collections
_inner = new Dictionary<TKey, TValue>();
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs("Count"));
PropertyChanged(this, new PropertyChangedEventArgs($"Item[]"));
}
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs("Count"));
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs($"Item[]"));
if (CollectionChanged != null)
{
@ -144,12 +142,9 @@ namespace Avalonia.Collections
if (_inner.TryGetValue(key, out value))
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs("Count"));
PropertyChanged(this, new PropertyChangedEventArgs($"Item[{key}]"));
}
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs("Count"));
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs($"Item[{key}]"));
if (CollectionChanged != null)
{
var e = new NotifyCollectionChangedEventArgs(
@ -199,11 +194,9 @@ namespace Avalonia.Collections
private void NotifyAdd(TKey key, TValue value)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs("Count"));
PropertyChanged(this, new PropertyChangedEventArgs($"Item[{key}]"));
}
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs("Count"));
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs($"Item[{key}]"));
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.
using System;
using System.Collections;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.ComponentModel;
@ -59,7 +60,8 @@ namespace Avalonia.Collections
/// the index in the collection and the item.
/// </param>
/// <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>
/// <returns>A disposable used to terminate the subscription.</returns>
public static IDisposable ForEachItem<T>(
@ -68,112 +70,38 @@ namespace Avalonia.Collections
Action<int, T> removed,
Action reset)
{
int index;
NotifyCollectionChangedEventHandler handler = (_, e) =>
void Add(int index, IList items)
{
switch (e.Action)
foreach (T item in items)
{
case NotifyCollectionChangedAction.Add:
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;
added(index++, item);
}
};
}
index = 0;
foreach (T i in collection)
void Remove(int index, IList items)
{
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) =>
{
switch (e.Action)
{
case NotifyCollectionChangedAction.Add:
added(e.NewStartingIndex, e.NewItems.Cast<T>());
Add(e.NewStartingIndex, e.NewItems);
break;
case NotifyCollectionChangedAction.Move:
case NotifyCollectionChangedAction.Replace:
removed(e.OldStartingIndex, e.OldItems.Cast<T>());
added(e.NewStartingIndex, e.NewItems.Cast<T>());
Remove(e.OldStartingIndex, e.OldItems);
Add(e.NewStartingIndex, e.NewItems);
break;
case NotifyCollectionChangedAction.Remove:
removed(e.OldStartingIndex, e.OldItems.Cast<T>());
Remove(e.OldStartingIndex, e.OldItems);
break;
case NotifyCollectionChangedAction.Reset:
@ -184,16 +112,31 @@ namespace Avalonia.Collections
}
reset();
Add(0, (IList)collection);
break;
}
};
added(0, collection);
Add(0, (IList)collection);
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>
/// Listens for property changed events from all items in a collection.
/// </summary>

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

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

2
src/Avalonia.Base/PriorityValue.cs

@ -285,7 +285,7 @@ namespace Avalonia
Property.Name,
_valueType,
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 => { };
/// <summary>
/// Gets or sets a method to call before <see cref="Start{TMainWindow}"/> is called on the
/// <see cref="Application"/>.
/// Gets or sets a method to call before Startis called on the <see cref="Application"/>.
/// </summary>
public Action<TAppBuilder> BeforeStartCallback { get; private set; } = builder => { };
@ -94,8 +93,7 @@ namespace Avalonia.Controls
protected TAppBuilder Self => (TAppBuilder) this;
/// <summary>
/// Registers a callback to call before <see cref="Start{TMainWindow}"/> is called on the
/// <see cref="Application"/>.
/// Registers a callback to call before Start is called on the <see cref="Application"/>.
/// </summary>
/// <param name="callback">The callback.</param>
/// <returns>An <typeparamref name="TAppBuilder"/> instance.</returns>
@ -129,6 +127,24 @@ namespace Avalonia.Controls
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>
/// Sets up the platform-specific services for the application, but does not run it.
/// </summary>

38
src/Avalonia.Controls/Button.cs

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

2
src/Avalonia.Controls/ContextMenu.cs

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

15
src/Avalonia.Controls/Control.cs

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

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

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

2
src/Avalonia.Controls/ItemsControl.cs

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

2
src/Avalonia.Controls/Menu.cs

@ -47,7 +47,7 @@ namespace Avalonia.Controls
static Menu()
{
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);
}

11
src/Avalonia.Controls/MenuItem.cs

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

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

@ -5,6 +5,7 @@ using System;
using System.Collections.Generic;
using Avalonia.Input;
using Avalonia.Input.Raw;
using JetBrains.Annotations;
namespace Avalonia.Platform
{
@ -93,5 +94,11 @@ namespace Avalonia.Platform
/// Gets or sets a method called when the underlying implementation is destroyed.
/// </summary>
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.LogicalTree;
using Avalonia.Media;
using Avalonia.VisualTree;
namespace Avalonia.Controls.Presenters
{
@ -88,6 +89,7 @@ namespace Avalonia.Controls.Presenters
static ContentPresenter()
{
ContentProperty.Changed.AddClassHandler<ContentPresenter>(x => x.ContentChanged);
ContentTemplateProperty.Changed.AddClassHandler<ContentPresenter>(x => x.ContentChanged);
TemplatedParentProperty.Changed.AddClassHandler<ContentPresenter>(x => x.TemplatedParentChanged);
}
@ -313,27 +315,22 @@ namespace Avalonia.Controls.Presenters
if (content != null && newChild == null)
{
// We have content and it isn't a control, so first try to recycle the existing
// 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.
if (oldChild != null &&
_dataTemplate != null &&
_dataTemplate.SupportsRecycling &&
_dataTemplate.Match(content))
var dataTemplate = this.FindDataTemplate(content, ContentTemplate) ?? FuncDataTemplate.Default;
// We have content and it isn't a control, so if the new data template is the same
// as the old data template, try to recycle the existing child control to display
// the new data.
if (dataTemplate == _dataTemplate && dataTemplate.SupportsRecycling)
{
newChild = oldChild;
}
else
{
// We couldn't recycle an existing control so find a data template for the data
// and use it to create a control.
_dataTemplate = this.FindDataTemplate(content, ContentTemplate) ?? FuncDataTemplate.Default;
_dataTemplate = dataTemplate;
newChild = _dataTemplate.Build(content);
// Try to give the new control its own name scope.
var controlResult = newChild as Control;
if (controlResult != null)
// Give the new control its own name scope.
if (newChild is Control controlResult)
{
NameScope.SetNameScope(controlResult, new NameScope());
}
@ -424,6 +421,19 @@ namespace Avalonia.Controls.Presenters
private void ContentChanged(AvaloniaPropertyChangedEventArgs e)
{
_createdChild = false;
if (((ILogical)this).IsAttachedToLogicalTree)
{
UpdateChild();
}
else if (Child != null)
{
VisualChildren.Remove(Child);
LogicalChildren.Remove(Child);
Child = null;
_dataTemplate = null;
}
InvalidateMeasure();
}

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

@ -155,8 +155,7 @@ namespace Avalonia.Controls.Presenters
case NotifyCollectionChangedAction.Add:
CreateAndRemoveContainers();
if (e.NewStartingIndex >= FirstIndex &&
e.NewStartingIndex < NextIndex)
if (e.NewStartingIndex < NextIndex)
{
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="targetRect">The portion of the target visual to bring into view.</param>
/// <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)
{
@ -262,7 +262,7 @@ namespace Avalonia.Controls.Presenters
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)

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

@ -340,11 +340,11 @@ namespace Avalonia.Controls.Primitives
switch (mode)
{
case PlacementMode.Pointer:
if (MouseDevice.Instance != null)
if(PopupRoot != null)
{
// Scales the Horizontal and Vertical offset to screen co-ordinates.
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);

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

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

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

@ -285,6 +285,17 @@ namespace Avalonia.Controls.Primitives
return this;
}
/// <inheritdoc/>
protected override void OnAttachedToLogicalTree(LogicalTreeAttachmentEventArgs e)
{
if (VisualChildren.Count > 0)
{
((ILogical)VisualChildren[0]).NotifyAttachedToLogicalTree(e);
}
base.OnAttachedToLogicalTree(e);
}
/// <inheritdoc/>
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)
{
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)
{
--pos;
if (pos > 0 && Text[pos - 1] == '\r' && Text[pos] == '\n')
if (pos > 0 && text[pos - 1] == '\r' && text[pos] == '\n')
{
--pos;
}
@ -771,6 +771,9 @@ namespace Avalonia.Controls
private string GetSelection()
{
var text = Text;
if (string.IsNullOrEmpty(text))
return "";
var selectionStart = SelectionStart;
var selectionEnd = SelectionEnd;
var start = Math.Min(selectionStart, selectionEnd);
@ -779,7 +782,7 @@ namespace Avalonia.Controls
{
return "";
}
return Text.Substring(start, end - start);
return text.Substring(start, end - start);
}
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 (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);
s_popup.Content = new ToolTip { Content = GetTip(control) };
((ToolTip)s_popup.Content).Content = GetTip(control);
s_popup.Position = position;
s_popup.Show();
@ -147,16 +151,23 @@ namespace Avalonia.Controls
{
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;
// Dispose of the popup.
s_popup.Dispose();
s_popup = null;
DisposeTooltip();
s_show.OnNext(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.Styling;
using Avalonia.VisualTree;
using JetBrains.Annotations;
namespace Avalonia.Controls
{
@ -92,25 +93,25 @@ namespace Avalonia.Controls
var rendererFactory = TryGetService<IRendererFactory>(dependencyResolver);
Renderer = rendererFactory?.CreateRenderer(this, renderLoop);
PlatformImpl.SetInputRoot(this);
impl.SetInputRoot(this);
PlatformImpl.Closed = HandleClosed;
PlatformImpl.Input = HandleInput;
PlatformImpl.Paint = HandlePaint;
PlatformImpl.Resized = HandleResized;
PlatformImpl.ScalingChanged = HandleScalingChanged;
impl.Closed = HandleClosed;
impl.Input = HandleInput;
impl.Paint = HandlePaint;
impl.Resized = HandleResized;
impl.ScalingChanged = HandleScalingChanged;
_keyboardNavigationHandler?.SetOwner(this);
_accessKeyHandler?.SetOwner(this);
styler?.ApplyStyles(this);
ClientSize = PlatformImpl.ClientSize;
ClientSize = impl.ClientSize;
this.GetObservable(PointerOverElementProperty)
.Select(
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)
{
@ -135,10 +136,8 @@ namespace Avalonia.Controls
/// <summary>
/// Gets the platform-specific window implementation.
/// </summary>
public ITopLevelImpl PlatformImpl
{
get;
}
[CanBeNull]
public ITopLevelImpl PlatformImpl { get; private set; }
/// <summary>
/// Gets the renderer for the window.
@ -164,6 +163,9 @@ namespace Avalonia.Controls
set { SetValue(PointerOverElementProperty, value); }
}
/// <inheritdoc/>
IMouseDevice IInputRoot.MouseDevice => PlatformImpl?.MouseDevice;
/// <summary>
/// Gets or sets a value indicating whether access keys are shown in the window.
/// </summary>
@ -177,7 +179,7 @@ namespace Avalonia.Controls
Size ILayoutRoot.MaxClientSize => Size.Infinity;
/// <inheritdoc/>
double ILayoutRoot.LayoutScaling => PlatformImpl.Scaling;
double ILayoutRoot.LayoutScaling => PlatformImpl?.Scaling ?? 1;
IStyleHost IStyleHost.StylingParent
{
@ -189,25 +191,27 @@ namespace Avalonia.Controls
/// <inheritdoc/>
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);
}
/// <inheritdoc/>
void IRenderRoot.Invalidate(Rect rect)
{
PlatformImpl.Invalidate(rect);
PlatformImpl?.Invalidate(rect);
}
/// <inheritdoc/>
Point IRenderRoot.PointToClient(Point p)
{
return PlatformImpl.PointToClient(p);
return PlatformImpl?.PointToClient(p) ?? default(Point);
}
/// <inheritdoc/>
Point IRenderRoot.PointToScreen(Point p)
{
return PlatformImpl.PointToScreen(p);
return PlatformImpl?.PointToScreen(p) ?? default(Point);
}
/// <summary>
@ -224,6 +228,8 @@ namespace Avalonia.Controls
/// </summary>
protected virtual void HandleClosed()
{
PlatformImpl = null;
Closed?.Invoke(this, EventArgs.Empty);
Renderer?.Dispose();
Renderer = null;
@ -250,7 +256,7 @@ namespace Avalonia.Controls
/// <param name="scaling">The window scaling.</param>
protected virtual void HandleScalingChanged(double scaling)
{
foreach (ILayoutable control in this.GetSelfAndVisualDescendents())
foreach (ILayoutable control in this.GetSelfAndVisualDescendants())
{
control.InvalidateMeasure();
}

22
src/Avalonia.Controls/TreeView.cs

@ -16,7 +16,7 @@ namespace Avalonia.Controls
/// <summary>
/// Displays a hierachical tree of data.
/// </summary>
public class TreeView : ItemsControl
public class TreeView : ItemsControl, ICustomKeyboardNavigation
{
/// <summary>
/// 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/>
protected override IItemContainerGenerator CreateItemContainerGenerator()
{

43
src/Avalonia.Controls/Window.cs

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

43
src/Avalonia.Controls/WindowBase.cs

@ -9,6 +9,7 @@ using Avalonia.Controls.Primitives;
using Avalonia.Input;
using Avalonia.Layout;
using Avalonia.Platform;
using JetBrains.Annotations;
namespace Avalonia.Controls
{
@ -28,6 +29,7 @@ namespace Avalonia.Controls
public static readonly DirectProperty<WindowBase, bool> IsActiveProperty =
AvaloniaProperty.RegisterDirect<WindowBase, bool>(nameof(IsActive), o => o.IsActive);
private bool _hasExecutedInitialLayoutPass;
private bool _isActive;
private bool _ignoreVisibilityChange;
@ -43,10 +45,10 @@ namespace Avalonia.Controls
public WindowBase(IWindowBaseImpl impl, IAvaloniaDependencyResolver dependencyResolver) : base(impl, dependencyResolver)
{
PlatformImpl.Activated = HandleActivated;
PlatformImpl.Deactivated = HandleDeactivated;
PlatformImpl.PositionChanged = HandlePositionChanged;
this.GetObservable(ClientSizeProperty).Skip(1).Subscribe(x => PlatformImpl.Resize(x));
impl.Activated = HandleActivated;
impl.Deactivated = HandleDeactivated;
impl.PositionChanged = HandlePositionChanged;
this.GetObservable(ClientSizeProperty).Skip(1).Subscribe(x => PlatformImpl?.Resize(x));
}
/// <summary>
@ -64,6 +66,7 @@ namespace Avalonia.Controls
/// </summary>
public event EventHandler<PointEventArgs> PositionChanged;
[CanBeNull]
public new IWindowBaseImpl PlatformImpl => (IWindowBaseImpl) base.PlatformImpl;
/// <summary>
@ -80,8 +83,12 @@ namespace Avalonia.Controls
/// </summary>
public Point Position
{
get { return PlatformImpl.Position; }
set { PlatformImpl.Position = value; }
get { return PlatformImpl?.Position ?? default(Point); }
set
{
if (PlatformImpl is IWindowBaseImpl impl)
impl.Position = value;
}
}
/// <summary>
@ -98,7 +105,7 @@ namespace Avalonia.Controls
/// </summary>
public void Activate()
{
PlatformImpl.Activate();
PlatformImpl?.Activate();
}
/// <summary>
@ -110,7 +117,7 @@ namespace Avalonia.Controls
try
{
PlatformImpl.Hide();
PlatformImpl?.Hide();
IsVisible = false;
}
finally
@ -130,8 +137,14 @@ namespace Avalonia.Controls
{
EnsureInitialized();
IsVisible = true;
LayoutManager.Instance.ExecuteInitialLayoutPass(this);
PlatformImpl.Show();
if (!_hasExecutedInitialLayoutPass)
{
LayoutManager.Instance.ExecuteInitialLayoutPass(this);
_hasExecutedInitialLayoutPass = true;
}
PlatformImpl?.Show();
}
finally
{
@ -163,10 +176,10 @@ namespace Avalonia.Controls
{
using (BeginAutoSizing())
{
PlatformImpl.Resize(finalSize);
PlatformImpl?.Resize(finalSize);
}
return base.ArrangeOverride(PlatformImpl.ClientSize);
return base.ArrangeOverride(PlatformImpl?.ClientSize ?? default(Size));
}
/// <summary>
@ -174,7 +187,7 @@ namespace Avalonia.Controls
/// </summary>
protected void EnsureInitialized()
{
if (!this.IsInitialized)
if (!IsInitialized)
{
var init = (ISupportInitialize)this;
init.BeginInit();
@ -268,12 +281,12 @@ namespace Avalonia.Controls
/// <summary>
/// Starts moving a window with left button being held. Should be called from left mouse button press event handler
/// </summary>
public void BeginMoveDrag() => PlatformImpl.BeginMoveDrag();
public void BeginMoveDrag() => PlatformImpl?.BeginMoveDrag();
/// <summary>
/// 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
/// </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)
{
PlatformManager.SetDesignerScalingFactor(factor);
s_currentWindow?.PlatformImpl.Resize(s_currentWindow.ClientSize);
s_currentWindow?.PlatformImpl?.Resize(s_currentWindow.ClientSize);
}
static Window s_currentWindow;
@ -149,6 +149,8 @@ namespace Avalonia.DesignerSupport
s_currentWindow = window;
window.Show();
Design.ApplyDesignerProperties(window, control);
// ReSharper disable once PossibleNullReferenceException
// Always not null at this point
Api.OnWindowCreated?.Invoke(window.PlatformImpl.Handle.Handle);
Api.OnResize?.Invoke();
}

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

@ -34,7 +34,6 @@
<ProjectReference Include="..\Avalonia.Input\Avalonia.Input.csproj" />
<ProjectReference Include="..\Avalonia.Interactivity\Avalonia.Interactivity.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.Styling\Avalonia.Styling.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.Markup.Xaml;
using Avalonia.VisualTree;
using ReactiveUI;
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
{
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;
public DevTools(IControl root)
@ -44,9 +43,9 @@ namespace Avalonia.Diagnostics
public IControl Root { get; }
public static IDisposable Attach(Window window)
public static IDisposable Attach(TopLevel control)
{
return window.AddHandler(
return control.AddHandler(
KeyDownEvent,
WindowPreviewKeyDown,
RoutingStrategies.Tunnel);
@ -56,16 +55,16 @@ namespace Avalonia.Diagnostics
{
if (e.Key == Key.F12)
{
var window = (Window)sender;
var control = (TopLevel)sender;
var devToolsWindow = default(Window);
if (s_open.TryGetValue(window, out devToolsWindow))
if (s_open.TryGetValue(control, out devToolsWindow))
{
devToolsWindow.Activate();
}
else
{
var devTools = new DevTools(window);
var devTools = new DevTools(control);
devToolsWindow = new Window
{
@ -74,12 +73,12 @@ namespace Avalonia.Diagnostics
Content = devTools,
DataTemplates = new DataTemplates
{
new ViewLocator<ReactiveObject>(),
new ViewLocator<ViewModelBase>(),
}
};
devToolsWindow.Closed += devTools.DevToolsClosed;
s_open.Add((Window)sender, devToolsWindow);
s_open.Add(control, devToolsWindow);
devToolsWindow.Show();
}
}
@ -89,9 +88,7 @@ namespace Avalonia.Diagnostics
{
var devToolsWindow = (Window)sender;
var devTools = (DevTools)devToolsWindow.Content;
var window = (Window)devTools.Root;
s_open.Remove(window);
s_open.Remove((TopLevel)devTools.Root);
_keySubscription.Dispose();
devToolsWindow.Closed -= DevToolsClosed;
}
@ -107,7 +104,8 @@ namespace Avalonia.Diagnostics
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))
.FirstOrDefault();

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

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

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

@ -5,32 +5,50 @@ using System;
using System.Reactive.Linq;
using Avalonia.Controls;
using Avalonia.Input;
using ReactiveUI;
namespace Avalonia.Diagnostics.ViewModels
{
internal class DevToolsViewModel : ReactiveObject
internal class DevToolsViewModel : ViewModelBase
{
private ReactiveObject _content;
private ViewModelBase _content;
private int _selectedTab;
private TreePageViewModel _logicalTree;
private TreePageViewModel _visualTree;
private readonly ObservableAsPropertyHelper<string> _focusedControl;
private readonly ObservableAsPropertyHelper<string> _pointerOverElement;
private string _focusedControl;
private string _pointerOverElement;
public DevToolsViewModel(IControl root)
{
_logicalTree = new TreePageViewModel(LogicalTreeNode.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:
Content = _logicalTree;
@ -39,34 +57,23 @@ namespace Avalonia.Diagnostics.ViewModels
Content = _visualTree;
break;
}
});
_focusedControl = KeyboardDevice.Instance
.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);
RaisePropertyChanged();
}
}
public ReactiveObject Content
public string FocusedControl
{
get { return _content; }
private set { this.RaiseAndSetIfChanged(ref _content, value); }
get { return _focusedControl; }
private set { RaiseAndSetIfChanged(ref _focusedControl, value); }
}
public int SelectedTab
public string PointerOverElement
{
get { return _selectedTab; }
set { this.RaiseAndSetIfChanged(ref _selectedTab, value); }
get { return _pointerOverElement; }
private set { RaiseAndSetIfChanged(ref _pointerOverElement, value); }
}
public string FocusedControl => _focusedControl.Value;
public string PointerOverElement => _pointerOverElement.Value;
public void SelectControl(IControl control)
{
var tree = Content as TreePageViewModel;
@ -76,5 +83,10 @@ namespace Avalonia.Diagnostics.ViewModels
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.
using System;
using Avalonia.Collections;
using Avalonia.Controls;
using Avalonia.LogicalTree;
using ReactiveUI;
namespace Avalonia.Diagnostics.ViewModels
{
@ -13,7 +13,7 @@ namespace Avalonia.Diagnostics.ViewModels
public LogicalTreeNode(ILogical logical, TreeNode 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)

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

@ -3,16 +3,13 @@
using System;
using Avalonia.Data;
using ReactiveUI;
namespace Avalonia.Diagnostics.ViewModels
{
internal class PropertyDetails : ReactiveObject
internal class PropertyDetails : ViewModelBase
{
private object _value;
private string _priority;
private string _diagnostic;
public PropertyDetails(AvaloniaObject o, AvaloniaProperty property)
@ -41,19 +38,19 @@ namespace Avalonia.Diagnostics.ViewModels
public string Priority
{
get { return _priority; }
private set { this.RaiseAndSetIfChanged(ref _priority, value); }
private set { RaiseAndSetIfChanged(ref _priority, value); }
}
public string Diagnostic
{
get { return _diagnostic; }
private set { this.RaiseAndSetIfChanged(ref _diagnostic, value); }
private set { RaiseAndSetIfChanged(ref _diagnostic, value); }
}
public object 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.Reactive;
using System.Reactive.Linq;
using Avalonia.Collections;
using Avalonia.Styling;
using Avalonia.VisualTree;
using ReactiveUI;
namespace Avalonia.Diagnostics.ViewModels
{
internal class TreeNode : ReactiveObject
internal class TreeNode : ViewModelBase
{
private string _classes;
private bool _isExpanded;
@ -47,7 +47,7 @@ namespace Avalonia.Diagnostics.ViewModels
}
}
public IReadOnlyReactiveList<TreeNode> Children
public IAvaloniaReadOnlyList<TreeNode> Children
{
get;
protected set;
@ -56,7 +56,7 @@ namespace Avalonia.Diagnostics.ViewModels
public string Classes
{
get { return _classes; }
private set { this.RaiseAndSetIfChanged(ref _classes, value); }
private set { RaiseAndSetIfChanged(ref _classes, value); }
}
public IVisual Visual
@ -67,7 +67,7 @@ namespace Avalonia.Diagnostics.ViewModels
public bool IsExpanded
{
get { return _isExpanded; }
set { this.RaiseAndSetIfChanged(ref _isExpanded, value); }
set { RaiseAndSetIfChanged(ref _isExpanded, value); }
}
public TreeNode Parent

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

@ -1,25 +1,19 @@
// 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.Reactive.Linq;
using Avalonia.Controls;
using Avalonia.VisualTree;
using ReactiveUI;
namespace Avalonia.Diagnostics.ViewModels
{
internal class TreePageViewModel : ReactiveObject
internal class TreePageViewModel : ViewModelBase
{
private TreeNode _selected;
private readonly ObservableAsPropertyHelper<ControlDetailsViewModel> _details;
private ControlDetailsViewModel _details;
public TreePageViewModel(TreeNode[] 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; }
@ -27,10 +21,20 @@ namespace Avalonia.Diagnostics.ViewModels
public TreeNode SelectedNode
{
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)
{

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.
// 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.VisualTree;
using ReactiveUI;
namespace Avalonia.Diagnostics.ViewModels
{
@ -17,11 +16,11 @@ namespace Avalonia.Diagnostics.ViewModels
if (host?.Root == null)
{
Children = visual.VisualChildren.CreateDerivedCollection(x => new VisualTreeNode(x, this));
Children = visual.VisualChildren.CreateDerivedList(x => new VisualTreeNode(x, this));
}
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))

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

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

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

@ -10,9 +10,11 @@
// - Sun Tsu,
// "The Art of War"
using Avalonia;
using Avalonia.Controls;
using Avalonia.Controls.Html;
using Avalonia.Input;
using Avalonia.VisualTree;
using TheArtOfDev.HtmlRenderer.Adapters;
using TheArtOfDev.HtmlRenderer.Adapters.Entities;
using TheArtOfDev.HtmlRenderer.Core.Utils;
@ -54,7 +56,8 @@ namespace TheArtOfDev.HtmlRenderer.Avalonia.Adapters
{
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.Media;
using Avalonia.Threading;
using Avalonia.VisualTree;
using TheArtOfDev.HtmlRenderer.Core;
using TheArtOfDev.HtmlRenderer.Core.Entities;
using TheArtOfDev.HtmlRenderer.Avalonia;
@ -512,7 +513,7 @@ namespace Avalonia.Controls.Html
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>

5
src/Avalonia.Input/FocusManager.cs

@ -176,9 +176,10 @@ namespace Avalonia.Input
/// <param name="e">The event args.</param>
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);
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.
// Licensed under the MIT license. See licence.md file in the project root for full license information.
using Avalonia.Input.Raw;
namespace Avalonia.Input
{
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.
// Licensed under the MIT license. See licence.md file in the project root for full license information.
using JetBrains.Annotations;
namespace Avalonia.Input
{
/// <summary>
@ -27,5 +29,11 @@ namespace Avalonia.Input
/// Gets or sets a value indicating whether access keys are shown in the window.
/// </summary>
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.
using System;
using System.ComponentModel;
namespace Avalonia.Input
{
@ -26,7 +27,7 @@ namespace Avalonia.Input
Toggled = 2,
}
public interface IKeyboardDevice : IInputDevice
public interface IKeyboardDevice : IInputDevice, INotifyPropertyChanged
{
IInputElement FocusedElement { get; }

1
src/Avalonia.Input/InputManager.cs

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

12
src/Avalonia.Input/KeyboardDevice.cs

@ -16,14 +16,6 @@ namespace Avalonia.Input
{
private IInputElement _focusedElement;
public KeyboardDevice()
{
InputManager.Process
.OfType<RawInputEventArgs>()
.Where(e => e.Device == this && !e.Handled)
.Subscribe(ProcessRawEvent);
}
public event PropertyChangedEventHandler PropertyChanged;
public static IKeyboardDevice Instance => AvaloniaLocator.Current.GetService<IKeyboardDevice>();
@ -77,8 +69,10 @@ namespace Avalonia.Input
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
private void ProcessRawEvent(RawInputEventArgs e)
public void ProcessRawEvent(RawInputEventArgs e)
{
if(e.Handled)
return;
IInputElement element = FocusedElement;
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.
using System;
using System.Linq;
using Avalonia.Input.Navigation;
using Avalonia.VisualTree;
namespace Avalonia.Input
{
@ -52,6 +54,31 @@ namespace Avalonia.Input
{
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)
{
return TabNavigation.GetNextInTabOrder(element, direction);

31
src/Avalonia.Input/MouseDevice.cs

@ -20,23 +20,7 @@ namespace Avalonia.Input
private int _clickCount;
private Rect _lastClickRect;
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>
/// Gets the control that is currently capturing by the mouse, if any.
/// </summary>
@ -50,12 +34,7 @@ namespace Avalonia.Input
get;
protected set;
}
/// <summary>
/// Gets the application's input manager.
/// </summary>
public IInputManager InputManager => AvaloniaLocator.Current.GetService<IInputManager>();
/// <summary>
/// Gets the mouse position, in screen coordinates.
/// </summary>
@ -102,6 +81,12 @@ namespace Avalonia.Input
return root.PointToClient(Position) - p;
}
public void ProcessRawEvent(RawInputEventArgs e)
{
if (!e.Handled && e is RawMouseEventArgs margs)
ProcessRawEvent(margs);
}
private void ProcessRawEvent(RawMouseEventArgs e)
{
Contract.Requires<ArgumentNullException>(e != null);

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

@ -41,10 +41,10 @@ namespace Avalonia.Input.Navigation
{
case KeyboardNavigationMode.Continue:
return GetNextInContainer(element, container, direction) ??
GetFirstInNextContainer(element, direction);
GetFirstInNextContainer(element, element, direction);
case KeyboardNavigationMode.Cycle:
return GetNextInContainer(element, container, direction) ??
GetFocusableDescendent(container, direction);
GetFocusableDescendant(container, direction);
case KeyboardNavigationMode.Contained:
return GetNextInContainer(element, container, direction);
default:
@ -53,7 +53,7 @@ namespace Avalonia.Input.Navigation
}
else
{
return GetFocusableDescendents(element).FirstOrDefault();
return GetFocusableDescendants(element).FirstOrDefault();
}
}
@ -71,24 +71,24 @@ namespace Avalonia.Input.Navigation
}
/// <summary>
/// Gets the first or last focusable descendent of the specified element.
/// Gets the first or last focusable descendant of the specified element.
/// </summary>
/// <param name="container">The element.</param>
/// <param name="direction">The direction to search.</param>
/// <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) ?
GetFocusableDescendents(container).FirstOrDefault() :
GetFocusableDescendents(container).LastOrDefault();
GetFocusableDescendants(container).FirstOrDefault() :
GetFocusableDescendants(container).LastOrDefault();
}
/// <summary>
/// Gets the focusable descendents of the specified element.
/// Gets the focusable descendants of the specified element.
/// </summary>
/// <param name="element">The element.</param>
/// <returns>The element's focusable descendents.</returns>
private static IEnumerable<IInputElement> GetFocusableDescendents(IInputElement element)
/// <returns>The element's focusable descendants.</returns>
private static IEnumerable<IInputElement> GetFocusableDescendants(IInputElement element)
{
var children = element.GetVisualChildren().OfType<IInputElement>();
@ -99,11 +99,11 @@ namespace Avalonia.Input.Navigation
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)
{
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)
{
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>
/// Gets the first item that should be focused in the next container.
/// </summary>
/// <param name="element">The element being navigated away from.</param>
/// <param name="container">The container.</param>
/// <param name="direction">The direction of the search.</param>
/// <returns>The first element, or null if there are no more elements.</returns>
private static IInputElement GetFirstInNextContainer(
IInputElement element,
IInputElement container,
NavigationDirection direction)
{
@ -193,13 +195,23 @@ namespace Avalonia.Input.Navigation
var siblings = parent.GetVisualChildren()
.OfType<IInputElement>()
.Where(FocusExtensions.CanFocusDescendents);
.Where(FocusExtensions.CanFocusDescendants);
var sibling = isForward ?
siblings.SkipWhile(x => x != container).Skip(1).FirstOrDefault() :
siblings.TakeWhile(x => x != container).LastOrDefault();
if (sibling != null)
{
if (sibling is ICustomKeyboardNavigation custom)
{
var (handled, customNext) = custom.GetNext(element, direction);
if (handled)
{
return customNext;
}
}
if (sibling.CanFocus())
{
next = sibling;
@ -207,21 +219,21 @@ namespace Avalonia.Input.Navigation
else
{
next = isForward ?
GetFocusableDescendents(sibling).FirstOrDefault() :
GetFocusableDescendents(sibling).LastOrDefault();
GetFocusableDescendants(sibling).FirstOrDefault() :
GetFocusableDescendants(sibling).LastOrDefault();
}
}
if (next == null)
{
next = GetFirstInNextContainer(parent, direction);
next = GetFirstInNextContainer(element, parent, direction);
}
}
else
{
next = isForward ?
GetFocusableDescendents(container).FirstOrDefault() :
GetFocusableDescendents(container).LastOrDefault();
GetFocusableDescendants(container).FirstOrDefault() :
GetFocusableDescendants(container).LastOrDefault();
}
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;
/// <summary>
/// Checks if descendents of the specified element can be focused.
/// Checks if descendants of the specified element can be focused.
/// </summary>
/// <param name="e">The element.</param>
/// <returns>True if descendents of the element can be focused.</returns>
public static bool CanFocusDescendents(this IInputElement e) => e.IsEnabledCore && e.IsVisible;
/// <returns>True if descendants of the element can be focused.</returns>
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>
/// <param name="element">The element.</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>
/// The next element in the specified direction, or null if <paramref name="element"/>
/// was the last in the requested direction.
/// </returns>
public static IInputElement GetNextInTabOrder(
IInputElement element,
NavigationDirection direction)
NavigationDirection direction,
bool outsideElement = false)
{
Contract.Requires<ArgumentNullException>(element != null);
Contract.Requires<ArgumentException>(
@ -40,42 +44,43 @@ namespace Avalonia.Input.Navigation
switch (mode)
{
case KeyboardNavigationMode.Continue:
return GetNextInContainer(element, container, direction) ??
GetFirstInNextContainer(element, direction);
return GetNextInContainer(element, container, direction, outsideElement) ??
GetFirstInNextContainer(element, element, direction);
case KeyboardNavigationMode.Cycle:
return GetNextInContainer(element, container, direction) ??
GetFocusableDescendent(container, direction);
return GetNextInContainer(element, container, direction, outsideElement) ??
GetFocusableDescendant(container, direction);
case KeyboardNavigationMode.Contained:
return GetNextInContainer(element, container, direction);
return GetNextInContainer(element, container, direction, outsideElement);
default:
return GetFirstInNextContainer(container, direction);
return GetFirstInNextContainer(element, container, direction);
}
}
else
{
return GetFocusableDescendents(element).FirstOrDefault();
return GetFocusableDescendants(element, direction).FirstOrDefault();
}
}
/// <summary>
/// Gets the first or last focusable descendent of the specified element.
/// Gets the first or last focusable descendant of the specified element.
/// </summary>
/// <param name="container">The element.</param>
/// <param name="direction">The direction to search.</param>
/// <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 ?
GetFocusableDescendents(container).FirstOrDefault() :
GetFocusableDescendents(container).LastOrDefault();
GetFocusableDescendants(container, direction).FirstOrDefault() :
GetFocusableDescendants(container, direction).LastOrDefault();
}
/// <summary>
/// Gets the focusable descendents of the specified element.
/// Gets the focusable descendants of the specified element.
/// </summary>
/// <param name="element">The element.</param>
/// <returns>The element's focusable descendents.</returns>
private static IEnumerable<IInputElement> GetFocusableDescendents(IInputElement element)
/// <param name="direction">The tab direction. Must be Next or Previous.</param>
/// <returns>The element's focusable descendants.</returns>
private static IEnumerable<IInputElement> GetFocusableDescendants(IInputElement element, NavigationDirection direction)
{
var mode = KeyboardNavigation.GetTabNavigation((InputElement)element);
@ -103,16 +108,25 @@ namespace Avalonia.Input.Navigation
foreach (var child in children)
{
if (child.CanFocus())
var customNext = GetCustomNext(child, direction);
if (customNext.handled)
{
yield return child;
yield return customNext.next;
}
if (child.CanFocusDescendents())
else
{
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="container">The container.</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>
private static IInputElement GetNextInContainer(
IInputElement element,
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)
{
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>
/// Gets the first item that should be focused in the next container.
/// </summary>
/// <param name="element">The element being navigated away from.</param>
/// <param name="container">The container.</param>
/// <param name="direction">The direction of the search.</param>
/// <returns>The first element, or null if there are no more elements.</returns>
private static IInputElement GetFirstInNextContainer(
IInputElement element,
IInputElement container,
NavigationDirection direction)
{
@ -203,13 +223,20 @@ namespace Avalonia.Input.Navigation
var siblings = parent.GetVisualChildren()
.OfType<IInputElement>()
.Where(FocusExtensions.CanFocusDescendents);
.Where(FocusExtensions.CanFocusDescendants);
var sibling = direction == NavigationDirection.Next ?
siblings.SkipWhile(x => x != container).Skip(1).FirstOrDefault() :
siblings.TakeWhile(x => x != container).LastOrDefault();
if (sibling != null)
{
var customNext = GetCustomNext(sibling, direction);
if (customNext.handled)
{
return customNext.next;
}
if (sibling.CanFocus())
{
next = sibling;
@ -217,24 +244,34 @@ namespace Avalonia.Input.Navigation
else
{
next = direction == NavigationDirection.Next ?
GetFocusableDescendents(sibling).FirstOrDefault() :
GetFocusableDescendents(sibling).LastOrDefault();
GetFocusableDescendants(sibling, direction).FirstOrDefault() :
GetFocusableDescendants(sibling, direction).LastOrDefault();
}
}
if (next == null)
{
next = GetFirstInNextContainer(parent, direction);
next = GetFirstInNextContainer(element, parent, direction);
}
}
else
{
next = direction == NavigationDirection.Next ?
GetFocusableDescendents(container).FirstOrDefault() :
GetFocusableDescendents(container).LastOrDefault();
GetFocusableDescendants(container, direction).FirstOrDefault() :
GetFocusableDescendants(container, direction).LastOrDefault();
}
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>
public class LayoutManager : ILayoutManager
{
private readonly HashSet<ILayoutable> _toMeasure = new HashSet<ILayoutable>();
private readonly HashSet<ILayoutable> _toArrange = new HashSet<ILayoutable>();
private readonly Queue<ILayoutable> _toMeasure = new Queue<ILayoutable>();
private readonly Queue<ILayoutable> _toArrange = new Queue<ILayoutable>();
private bool _queued;
private bool _running;
@ -30,8 +30,18 @@ namespace Avalonia.Layout
Contract.Requires<ArgumentNullException>(control != null);
Dispatcher.UIThread.VerifyAccess();
_toMeasure.Add(control);
_toArrange.Add(control);
if (!control.IsAttachedToVisualTree)
{
#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();
}
@ -41,7 +51,17 @@ namespace Avalonia.Layout
Contract.Requires<ArgumentNullException>(control != null);
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();
}
@ -108,8 +128,12 @@ namespace Avalonia.Layout
{
while (_toMeasure.Count > 0)
{
var next = _toMeasure.First();
Measure(next);
var control = _toMeasure.Dequeue();
if (!control.IsMeasureValid && control.IsAttachedToVisualTree)
{
Measure(control);
}
}
}
@ -117,53 +141,62 @@ namespace Avalonia.Layout
{
while (_toArrange.Count > 0 && _toMeasure.Count == 0)
{
var next = _toArrange.First();
Arrange(next);
var control = _toArrange.Dequeue();
if (!control.IsArrangeValid && control.IsAttachedToVisualTree)
{
Arrange(control);
}
}
}
private void Measure(ILayoutable control)
{
var root = control as ILayoutRoot;
var parent = control.VisualParent as ILayoutable;
if (root != null)
{
root.Measure(root.MaxClientSize);
}
else if (parent != null)
// Controls closest to the visual root need to be arranged first. We don't try to store
// 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
// fastest and most memory-efficent algorithm.
if (control.VisualParent is ILayoutable 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)
{
var root = control as ILayoutRoot;
var parent = control.VisualParent as ILayoutable;
if (root != null)
{
root.Arrange(new Rect(root.DesiredSize));
}
else if (parent != null)
if (control.VisualParent is ILayoutable 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()

34
src/Avalonia.Layout/Layoutable.cs

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

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

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

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

@ -36,6 +36,16 @@ namespace Avalonia.LogicalTree
/// </summary>
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>
/// Notifies the control that it is being detached from a rooted logical tree.
/// </summary>

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

@ -37,15 +37,15 @@ namespace Avalonia.LogicalTree
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)
{
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
{
internal class DescendentSelector : Selector
internal class DescendantSelector : Selector
{
private readonly Selector _parent;
private string _selectorString;
public DescendentSelector(Selector parent)
public DescendantSelector(Selector parent)
{
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;
@ -41,7 +41,7 @@ namespace Avalonia.Styling
protected override SelectorMatch Evaluate(IStyleable control, bool subscribe)
{
ILogical c = (ILogical)control;
List<IObservable<bool>> descendentMatches = new List<IObservable<bool>>();
List<IObservable<bool>> descendantMatches = new List<IObservable<bool>>();
while (c != null)
{
@ -60,14 +60,14 @@ namespace Avalonia.Styling
}
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
{

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

@ -42,13 +42,13 @@ namespace Avalonia.Styling
}
/// <summary>
/// Returns a selector which matches a descendent of a previous selector.
/// Returns a selector which matches a descendant of a previous selector.
/// </summary>
/// <param name="previous">The previous selector.</param>
/// <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>

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

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

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

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

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

@ -164,7 +164,7 @@ namespace Avalonia.Rendering
private static void ClearTransformedBounds(IVisual visual)
{
foreach (var e in visual.GetSelfAndVisualDescendents())
foreach (var e in visual.GetSelfAndVisualDescendants())
{
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 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);
}
/// <summary>
/// Calculates the dot product of two vectors
/// </summary>
@ -65,6 +63,17 @@ namespace Avalonia
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>
/// Length of the vector
/// </summary>

6
src/Avalonia.Visuals/Visual.cs

@ -314,7 +314,7 @@ namespace Avalonia
/// <summary>
/// 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>
/// <param name="e">The event args.</param>
protected virtual void OnAttachedToVisualTreeCore(VisualTreeAttachmentEventArgs e)
@ -342,7 +342,7 @@ namespace Avalonia
/// <summary>
/// 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>
/// <param name="e">The event args.</param>
protected virtual void OnDetachedFromVisualTreeCore(VisualTreeAttachmentEventArgs e)
@ -422,7 +422,7 @@ namespace Avalonia
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