Browse Source

Merge branch 'master' into SourceLink

pull/1619/head
Steven Kirk 8 years ago
committed by GitHub
parent
commit
301159e33d
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 4
      .gitmodules
  2. 48
      Avalonia.sln
  3. 2
      build.cake
  4. 8
      build/Binding.props
  5. 17
      packages.cake
  6. 4
      samples/ControlCatalog.Android/ControlCatalog.Android.csproj
  7. 4
      samples/ControlCatalog.iOS/ControlCatalog.iOS.csproj
  8. 1
      samples/ControlCatalog/ControlCatalog.csproj
  9. 4
      samples/ControlCatalog/Pages/AutoCompleteBoxPage.xaml.cs
  10. 1
      samples/Previewer/Previewer.csproj
  11. 20
      samples/RenderTest/Pages/AnimationsPage.xaml
  12. 2
      samples/RenderTest/Pages/ClippingPage.xaml
  13. 7
      src/Android/Avalonia.Android/Resources/Resource.Designer.cs
  14. 4
      src/Android/Avalonia.AndroidTestApplication/Avalonia.AndroidTestApplication.csproj
  15. 33
      src/Avalonia.Animation/Animation.cs
  16. 17
      src/Avalonia.Animation/AnimationSetter.cs
  17. 18
      src/Avalonia.Animation/AnimatorAttribute.cs
  18. 21
      src/Avalonia.Animation/DoubleSetter.cs
  19. 1
      src/Avalonia.Base/Avalonia.Base.csproj
  20. 2
      src/Avalonia.Base/AvaloniaObject.cs
  21. 5
      src/Avalonia.Base/Collections/AvaloniaDictionary.cs
  22. 3
      src/Avalonia.Base/Data/BindingNotification.cs
  23. 2
      src/Avalonia.Base/Data/Converters/AlwaysEnabledDelegateCommand.cs
  24. 2
      src/Avalonia.Base/Data/Converters/BoolConverters.cs
  25. 2
      src/Avalonia.Base/Data/Converters/DefaultValueConverter.cs
  26. 2
      src/Avalonia.Base/Data/Converters/FuncMultiValueConverter.cs
  27. 2
      src/Avalonia.Base/Data/Converters/FuncValueConverter.cs
  28. 2
      src/Avalonia.Base/Data/Converters/IMultiValueConverter.cs
  29. 2
      src/Avalonia.Base/Data/Converters/IValueConverter.cs
  30. 2
      src/Avalonia.Base/Data/Converters/StringConverters.cs
  31. 4
      src/Avalonia.Base/Data/Core/BindingExpression.cs
  32. 2
      src/Avalonia.Base/Data/Core/CommonPropertyNames.cs
  33. 2
      src/Avalonia.Base/Data/Core/EmptyExpressionNode.cs
  34. 2
      src/Avalonia.Base/Data/Core/ExpressionNode.cs
  35. 4
      src/Avalonia.Base/Data/Core/ExpressionNodeBuilder.cs
  36. 45
      src/Avalonia.Base/Data/Core/ExpressionObserver.cs
  37. 4
      src/Avalonia.Base/Data/Core/ExpressionParseException.cs
  38. 2
      src/Avalonia.Base/Data/Core/ISettableNode.cs
  39. 2
      src/Avalonia.Base/Data/Core/ITransformNode.cs
  40. 2
      src/Avalonia.Base/Data/Core/IndexerNode.cs
  41. 2
      src/Avalonia.Base/Data/Core/LogicalNotNode.cs
  42. 3
      src/Avalonia.Base/Data/Core/MarkupBindingChainException.cs
  43. 2
      src/Avalonia.Base/Data/Core/Parsers/ArgumentListParser.cs
  44. 2
      src/Avalonia.Base/Data/Core/Parsers/ExpressionParser.cs
  45. 2
      src/Avalonia.Base/Data/Core/Parsers/IdentifierParser.cs
  46. 2
      src/Avalonia.Base/Data/Core/Parsers/Reader.cs
  47. 2
      src/Avalonia.Base/Data/Core/Plugins/AvaloniaPropertyAccessorPlugin.cs
  48. 2
      src/Avalonia.Base/Data/Core/Plugins/DataAnnotationsValidationPlugin.cs
  49. 2
      src/Avalonia.Base/Data/Core/Plugins/DataValidatiorBase.cs
  50. 2
      src/Avalonia.Base/Data/Core/Plugins/ExceptionValidationPlugin.cs
  51. 2
      src/Avalonia.Base/Data/Core/Plugins/IDataValidationPlugin.cs
  52. 2
      src/Avalonia.Base/Data/Core/Plugins/IPropertyAccessor.cs
  53. 2
      src/Avalonia.Base/Data/Core/Plugins/IPropertyAccessorPlugin.cs
  54. 2
      src/Avalonia.Base/Data/Core/Plugins/IStreamPlugin.cs
  55. 2
      src/Avalonia.Base/Data/Core/Plugins/IndeiValidationPlugin.cs
  56. 2
      src/Avalonia.Base/Data/Core/Plugins/InpcPropertyAccessorPlugin.cs
  57. 2
      src/Avalonia.Base/Data/Core/Plugins/MethodAccessorPlugin.cs
  58. 2
      src/Avalonia.Base/Data/Core/Plugins/ObservableStreamPlugin.cs
  59. 2
      src/Avalonia.Base/Data/Core/Plugins/PropertyAccessorBase.cs
  60. 2
      src/Avalonia.Base/Data/Core/Plugins/PropertyError.cs
  61. 2
      src/Avalonia.Base/Data/Core/Plugins/TaskStreamPlugin.cs
  62. 4
      src/Avalonia.Base/Data/Core/PropertyAccessorNode.cs
  63. 2
      src/Avalonia.Base/Data/Core/StreamNode.cs
  64. 3
      src/Avalonia.Base/Properties/AssemblyInfo.cs
  65. 764
      src/Avalonia.Controls/Control.cs
  66. 15
      src/Avalonia.Controls/ControlExtensions.cs
  67. 80
      src/Avalonia.Controls/DropDown.cs
  68. 35
      src/Avalonia.Controls/DropDownItem.cs
  69. 6
      src/Avalonia.Controls/HotkeyManager.cs
  70. 30
      src/Avalonia.Controls/IControl.cs
  71. 24
      src/Avalonia.Controls/Utils/AncestorFinder.cs
  72. 129
      src/Avalonia.HtmlRenderer/Adapters/AvaloniaAdapter.cs
  73. 47
      src/Avalonia.HtmlRenderer/Adapters/BrushAdapter.cs
  74. 51
      src/Avalonia.HtmlRenderer/Adapters/ContextMenuAdapter.cs
  75. 111
      src/Avalonia.HtmlRenderer/Adapters/ControlAdapter.cs
  76. 106
      src/Avalonia.HtmlRenderer/Adapters/FontAdapter.cs
  77. 29
      src/Avalonia.HtmlRenderer/Adapters/FontFamilyAdapter.cs
  78. 299
      src/Avalonia.HtmlRenderer/Adapters/GraphicsAdapter.cs
  79. 67
      src/Avalonia.HtmlRenderer/Adapters/GraphicsPathAdapter.cs
  80. 52
      src/Avalonia.HtmlRenderer/Adapters/ImageAdapter.cs
  81. 79
      src/Avalonia.HtmlRenderer/Adapters/PenAdapter.cs
  82. 24
      src/Avalonia.HtmlRenderer/Avalonia.HtmlRenderer.csproj
  83. 2
      src/Avalonia.HtmlRenderer/Avalonia.HtmlRenderer.csproj.DotSettings
  84. 16
      src/Avalonia.HtmlRenderer/Compat/Api.cs
  85. 28
      src/Avalonia.HtmlRenderer/Compat/Attributes.cs
  86. 22
      src/Avalonia.HtmlRenderer/Compat/ThreadPool.cs
  87. 471
      src/Avalonia.HtmlRenderer/HtmlContainer.cs
  88. 616
      src/Avalonia.HtmlRenderer/HtmlControl.cs
  89. 112
      src/Avalonia.HtmlRenderer/HtmlLabel.cs
  90. 14
      src/Avalonia.HtmlRenderer/HtmlRendererRoutedEventArgs.cs
  91. 33
      src/Avalonia.HtmlRenderer/Properties/AssemblyInfo.cs
  92. 24
      src/Avalonia.HtmlRenderer/PropertyHelper.cs
  93. 123
      src/Avalonia.HtmlRenderer/Utilities/Util.cs
  94. 1
      src/Avalonia.HtmlRenderer/external
  95. 4
      src/Avalonia.Input/InputElement.cs
  96. 3
      src/Avalonia.Styling/Avalonia.Styling.csproj
  97. 16
      src/Avalonia.Styling/Controls/Classes.cs
  98. 0
      src/Avalonia.Styling/Controls/IPseudoClasses.cs
  99. 2
      src/Avalonia.Styling/Controls/ISetInheritanceParent.cs
  100. 2
      src/Avalonia.Styling/Controls/ISetLogicalParent.cs

4
.gitmodules

@ -1,7 +1,3 @@
[submodule "src/Avalonia.HtmlRenderer/external"]
path = src/Avalonia.HtmlRenderer/external
url = https://github.com/AvaloniaUI/HTML-Renderer.git
branch = perspex-pcl
[submodule "src/Markup/Avalonia.Markup.Xaml/PortableXaml/portable.xaml.github"]
path = src/Markup/Avalonia.Markup.Xaml/PortableXaml/portable.xaml.github
url = https://github.com/AvaloniaUI/Portable.Xaml.git

48
Avalonia.sln

@ -1,4 +1,4 @@

Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio 15
VisualStudioVersion = 15.0.27130.2027
@ -68,8 +68,6 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Gtk", "Gtk", "{B9894058-278
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Avalonia.ReactiveUI", "src\Avalonia.ReactiveUI\Avalonia.ReactiveUI.csproj", "{6417B24E-49C2-4985-8DB2-3AB9D898EC91}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Avalonia.HtmlRenderer", "src\Avalonia.HtmlRenderer\Avalonia.HtmlRenderer.csproj", "{5FB2B005-0A7F-4DAD-ADD4-3ED01444E63D}"
EndProject
Project("{D954291E-2A0B-460D-934E-DC6B0785DB48}") = "PlatformSupport", "src\Shared\PlatformSupport\PlatformSupport.shproj", "{E4D9629C-F168-4224-3F51-A5E482FFBC42}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Avalonia.Markup", "src\Markup\Avalonia.Markup\Avalonia.Markup.csproj", "{6417E941-21BC-467B-A771-0DE389353CE6}"
@ -134,10 +132,10 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Avalonia.Gtk3", "src\Gtk\Av
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ControlCatalog.NetCore", "samples\ControlCatalog.NetCore\ControlCatalog.NetCore.csproj", "{39D7B147-1A5B-47C2-9D01-21FB7C47C4B3}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Build", "Build", "{74487168-7D91-487E-BF93-055F2251461E}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Props", "Props", "{F3AC8BC1-27F5-4255-9AFC-04ABFD11683A}"
ProjectSection(SolutionItems) = preProject
build\Base.props = build\Base.props
build\Binding.props = build\Binding.props
build\JetBrains.Annotations.props = build\JetBrains.Annotations.props
build\JetBrains.dotMemoryUnit.props = build\JetBrains.dotMemoryUnit.props
build\Magick.NET-Q16-AnyCPU.props = build\Magick.NET-Q16-AnyCPU.props
@ -1162,44 +1160,6 @@ Global
{6417B24E-49C2-4985-8DB2-3AB9D898EC91}.Release|NetCoreOnly.Build.0 = Release|Any CPU
{6417B24E-49C2-4985-8DB2-3AB9D898EC91}.Release|x86.ActiveCfg = Release|Any CPU
{6417B24E-49C2-4985-8DB2-3AB9D898EC91}.Release|x86.Build.0 = Release|Any CPU
{5FB2B005-0A7F-4DAD-ADD4-3ED01444E63D}.Ad-Hoc|Any CPU.ActiveCfg = Release|Any CPU
{5FB2B005-0A7F-4DAD-ADD4-3ED01444E63D}.Ad-Hoc|Any CPU.Build.0 = Release|Any CPU
{5FB2B005-0A7F-4DAD-ADD4-3ED01444E63D}.Ad-Hoc|iPhone.ActiveCfg = Release|Any CPU
{5FB2B005-0A7F-4DAD-ADD4-3ED01444E63D}.Ad-Hoc|iPhone.Build.0 = Release|Any CPU
{5FB2B005-0A7F-4DAD-ADD4-3ED01444E63D}.Ad-Hoc|iPhoneSimulator.ActiveCfg = Release|Any CPU
{5FB2B005-0A7F-4DAD-ADD4-3ED01444E63D}.Ad-Hoc|iPhoneSimulator.Build.0 = Release|Any CPU
{5FB2B005-0A7F-4DAD-ADD4-3ED01444E63D}.Ad-Hoc|NetCoreOnly.ActiveCfg = Debug|Any CPU
{5FB2B005-0A7F-4DAD-ADD4-3ED01444E63D}.Ad-Hoc|x86.ActiveCfg = Debug|Any CPU
{5FB2B005-0A7F-4DAD-ADD4-3ED01444E63D}.Ad-Hoc|x86.Build.0 = Debug|Any CPU
{5FB2B005-0A7F-4DAD-ADD4-3ED01444E63D}.AppStore|Any CPU.ActiveCfg = Release|Any CPU
{5FB2B005-0A7F-4DAD-ADD4-3ED01444E63D}.AppStore|Any CPU.Build.0 = Release|Any CPU
{5FB2B005-0A7F-4DAD-ADD4-3ED01444E63D}.AppStore|iPhone.ActiveCfg = Release|Any CPU
{5FB2B005-0A7F-4DAD-ADD4-3ED01444E63D}.AppStore|iPhone.Build.0 = Release|Any CPU
{5FB2B005-0A7F-4DAD-ADD4-3ED01444E63D}.AppStore|iPhoneSimulator.ActiveCfg = Release|Any CPU
{5FB2B005-0A7F-4DAD-ADD4-3ED01444E63D}.AppStore|iPhoneSimulator.Build.0 = Release|Any CPU
{5FB2B005-0A7F-4DAD-ADD4-3ED01444E63D}.AppStore|NetCoreOnly.ActiveCfg = Debug|Any CPU
{5FB2B005-0A7F-4DAD-ADD4-3ED01444E63D}.AppStore|x86.ActiveCfg = Debug|Any CPU
{5FB2B005-0A7F-4DAD-ADD4-3ED01444E63D}.AppStore|x86.Build.0 = Debug|Any CPU
{5FB2B005-0A7F-4DAD-ADD4-3ED01444E63D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{5FB2B005-0A7F-4DAD-ADD4-3ED01444E63D}.Debug|Any CPU.Build.0 = Debug|Any CPU
{5FB2B005-0A7F-4DAD-ADD4-3ED01444E63D}.Debug|iPhone.ActiveCfg = Debug|Any CPU
{5FB2B005-0A7F-4DAD-ADD4-3ED01444E63D}.Debug|iPhone.Build.0 = Debug|Any CPU
{5FB2B005-0A7F-4DAD-ADD4-3ED01444E63D}.Debug|iPhoneSimulator.ActiveCfg = Debug|Any CPU
{5FB2B005-0A7F-4DAD-ADD4-3ED01444E63D}.Debug|iPhoneSimulator.Build.0 = Debug|Any CPU
{5FB2B005-0A7F-4DAD-ADD4-3ED01444E63D}.Debug|NetCoreOnly.ActiveCfg = Debug|Any CPU
{5FB2B005-0A7F-4DAD-ADD4-3ED01444E63D}.Debug|NetCoreOnly.Build.0 = Debug|Any CPU
{5FB2B005-0A7F-4DAD-ADD4-3ED01444E63D}.Debug|x86.ActiveCfg = Debug|Any CPU
{5FB2B005-0A7F-4DAD-ADD4-3ED01444E63D}.Debug|x86.Build.0 = Debug|Any CPU
{5FB2B005-0A7F-4DAD-ADD4-3ED01444E63D}.Release|Any CPU.ActiveCfg = Release|Any CPU
{5FB2B005-0A7F-4DAD-ADD4-3ED01444E63D}.Release|Any CPU.Build.0 = Release|Any CPU
{5FB2B005-0A7F-4DAD-ADD4-3ED01444E63D}.Release|iPhone.ActiveCfg = Release|Any CPU
{5FB2B005-0A7F-4DAD-ADD4-3ED01444E63D}.Release|iPhone.Build.0 = Release|Any CPU
{5FB2B005-0A7F-4DAD-ADD4-3ED01444E63D}.Release|iPhoneSimulator.ActiveCfg = Release|Any CPU
{5FB2B005-0A7F-4DAD-ADD4-3ED01444E63D}.Release|iPhoneSimulator.Build.0 = Release|Any CPU
{5FB2B005-0A7F-4DAD-ADD4-3ED01444E63D}.Release|NetCoreOnly.ActiveCfg = Release|Any CPU
{5FB2B005-0A7F-4DAD-ADD4-3ED01444E63D}.Release|NetCoreOnly.Build.0 = Release|Any CPU
{5FB2B005-0A7F-4DAD-ADD4-3ED01444E63D}.Release|x86.ActiveCfg = Release|Any CPU
{5FB2B005-0A7F-4DAD-ADD4-3ED01444E63D}.Release|x86.Build.0 = Release|Any CPU
{6417E941-21BC-467B-A771-0DE389353CE6}.Ad-Hoc|Any CPU.ActiveCfg = Release|Any CPU
{6417E941-21BC-467B-A771-0DE389353CE6}.Ad-Hoc|Any CPU.Build.0 = Release|Any CPU
{6417E941-21BC-467B-A771-0DE389353CE6}.Ad-Hoc|iPhone.ActiveCfg = Release|Any CPU
@ -2582,8 +2542,6 @@ Global
{7D2D3083-71DD-4CC9-8907-39A0D86FB322} = {3743B0F2-CC41-4F14-A8C8-267F579BF91E}
{BB1F7BB5-6AD4-4776-94D9-C09D0A972658} = {B9894058-278A-46B5-B6ED-AD613FCC03B3}
{39D7B147-1A5B-47C2-9D01-21FB7C47C4B3} = {9B9E3891-2366-4253-A952-D08BCEB71098}
{F3AC8BC1-27F5-4255-9AFC-04ABFD11683A} = {74487168-7D91-487E-BF93-055F2251461E}
{4D6FAF79-58B4-482F-9122-0668C346364C} = {74487168-7D91-487E-BF93-055F2251461E}
{854568D5-13D1-4B4F-B50D-534DC7EFD3C9} = {86C53C40-57AA-45B8-AD42-FAE0EFDF0F2B}
{638580B0-7910-40EF-B674-DCB34DA308CD} = {A0CC0258-D18C-4AB3-854F-7101680FC3F9}
{CBC4FF2F-92D4-420B-BE21-9FE0B930B04E} = {B39A8919-9F95-48FE-AD7B-76E08B509888}

2
build.cake

@ -376,7 +376,7 @@ Task("Inspect")
{
var badIssues = new []{"PossibleNullReferenceException"};
var whitelist = new []{"tests", "src\\android", "src\\ios",
"src\\windows\\avalonia.designer", "src\\avalonia.htmlrenderer\\external",
"src\\windows\\avalonia.designer",
"src\\markup\\avalonia.markup.xaml\\portablexaml\\portable.xaml.github"};
Information("Running code inspections");

8
build/Binding.props

@ -0,0 +1,8 @@
<Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<ItemGroup>
<PackageReference Include="System.ComponentModel.Annotations" Version="4.3.0" />
<PackageReference Include="System.ComponentModel.TypeConverter" Version="4.3.0" />
<PackageReference Include="System.ComponentModel.Primitives" Version="4.3.0" />
<PackageReference Include="System.Runtime.Serialization.Primitives" Version="4.3.0" />
</ItemGroup>
</Project>

17
packages.cake

@ -262,23 +262,6 @@ public class Packages
OutputDirectory = parameters.NugetRoot
},
///////////////////////////////////////////////////////////////////////////////
// Avalonia.HtmlRenderer
///////////////////////////////////////////////////////////////////////////////
new NuGetPackSettings()
{
Id = "Avalonia.HtmlRenderer",
Dependencies = new []
{
new NuSpecDependency() { Id = "Avalonia", Version = parameters.Version }
},
Files = new []
{
new NuSpecContent { Source = "Avalonia.HtmlRenderer.dll", Target = "lib/netstandard2.0" }
},
BasePath = context.Directory("./src/Avalonia.HtmlRenderer/bin/" + parameters.DirSuffix + "/netstandard2.0"),
OutputDirectory = parameters.NugetRoot
},
///////////////////////////////////////////////////////////////////////////////
// Avalonia.ReactiveUI
///////////////////////////////////////////////////////////////////////////////
new NuGetPackSettings()

4
samples/ControlCatalog.Android/ControlCatalog.Android.csproj

@ -111,10 +111,6 @@
<Project>{7062ae20-5dcc-4442-9645-8195bdece63e}</Project>
<Name>Avalonia.Diagnostics</Name>
</ProjectReference>
<ProjectReference Include="..\..\src\Avalonia.HtmlRenderer\Avalonia.HtmlRenderer.csproj">
<Project>{5fb2b005-0a7f-4dad-add4-3ed01444e63d}</Project>
<Name>Avalonia.HtmlRenderer</Name>
</ProjectReference>
<ProjectReference Include="..\..\src\Avalonia.Input\Avalonia.Input.csproj">
<Project>{62024b2d-53eb-4638-b26b-85eeaa54866e}</Project>
<Name>Avalonia.Input</Name>

4
samples/ControlCatalog.iOS/ControlCatalog.iOS.csproj

@ -141,10 +141,6 @@
<Project>{7062AE20-5DCC-4442-9645-8195BDECE63E}</Project>
<Name>Avalonia.Diagnostics</Name>
</ProjectReference>
<ProjectReference Include="..\..\src\Avalonia.HtmlRenderer\Avalonia.HtmlRenderer.csproj">
<Project>{5FB2B005-0A7F-4DAD-ADD4-3ED01444E63D}</Project>
<Name>Avalonia.HtmlRenderer</Name>
</ProjectReference>
<ProjectReference Include="..\..\src\Avalonia.Input\Avalonia.Input.csproj">
<Project>{62024B2D-53EB-4638-B26B-85EEAA54866E}</Project>
<Name>Avalonia.Input</Name>

1
samples/ControlCatalog/ControlCatalog.csproj

@ -25,7 +25,6 @@
<ProjectReference Include="..\..\src\Avalonia.Base\Avalonia.Base.csproj" />
<ProjectReference Include="..\..\src\Avalonia.Controls\Avalonia.Controls.csproj" />
<ProjectReference Include="..\..\src\Avalonia.Diagnostics\Avalonia.Diagnostics.csproj" />
<ProjectReference Include="..\..\src\Avalonia.HtmlRenderer\Avalonia.HtmlRenderer.csproj" />
<ProjectReference Include="..\..\src\Avalonia.Input\Avalonia.Input.csproj" />
<ProjectReference Include="..\..\src\Avalonia.Interactivity\Avalonia.Interactivity.csproj" />
<ProjectReference Include="..\..\src\Avalonia.Layout\Avalonia.Layout.csproj" />

4
samples/ControlCatalog/Pages/AutoCompleteBoxPage.xaml.cs

@ -2,12 +2,14 @@ using Avalonia.Controls;
using Avalonia.LogicalTree;
using Avalonia.Markup;
using Avalonia.Markup.Xaml;
using Avalonia.Markup.Xaml.Data;
using Avalonia.Markup.Data;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Avalonia.Data.Converters;
using Avalonia.Data;
namespace ControlCatalog.Pages
{

1
samples/Previewer/Previewer.csproj

@ -15,7 +15,6 @@
<ProjectReference Include="..\..\src\Avalonia.Base\Avalonia.Base.csproj" />
<ProjectReference Include="..\..\src\Avalonia.Controls\Avalonia.Controls.csproj" />
<ProjectReference Include="..\..\src\Avalonia.Diagnostics\Avalonia.Diagnostics.csproj" />
<ProjectReference Include="..\..\src\Avalonia.HtmlRenderer\Avalonia.HtmlRenderer.csproj" />
<ProjectReference Include="..\..\src\Avalonia.Input\Avalonia.Input.csproj" />
<ProjectReference Include="..\..\src\Avalonia.Interactivity\Avalonia.Interactivity.csproj" />
<ProjectReference Include="..\..\src\Avalonia.Layout\Avalonia.Layout.csproj" />

20
samples/RenderTest/Pages/AnimationsPage.xaml

@ -48,13 +48,13 @@
PlaybackDirection="AlternateReverse"
Easing="SineEaseInOut">
<KeyFrame Cue="20%">
<TransformSetter Property="RotateTransform.Angle" Value="45"/>
<Setter Property="RotateTransform.Angle" Value="45"/>
</KeyFrame>
<KeyFrame Cue="50%">
<TransformSetter Property="ScaleTransform.ScaleX" Value="1.5"/>
<Setter Property="ScaleTransform.ScaleX" Value="1.5"/>
</KeyFrame>
<KeyFrame Cue="80%">
<TransformSetter Property="RotateTransform.Angle" Value="120"/>
<Setter Property="RotateTransform.Angle" Value="120"/>
</KeyFrame>
</Animation>
</Style.Animations>
@ -63,8 +63,8 @@
<Style.Animations>
<Animation Duration="0:0:0.5" Easing="SineEaseInOut">
<KeyFrame Cue="50%">
<TransformSetter Property="ScaleTransform.ScaleX" Value="0.8"/>
<TransformSetter Property="ScaleTransform.ScaleY" Value="0.8"/>
<Setter Property="ScaleTransform.ScaleX" Value="0.8"/>
<Setter Property="ScaleTransform.ScaleY" Value="0.8"/>
</KeyFrame>
</Animation>
</Style.Animations>
@ -76,8 +76,8 @@
Easing="QuadraticEaseInOut"
RepeatCount="Loop">
<KeyFrame Cue="50%">
<TransformSetter Property="ScaleTransform.ScaleX" Value="0.8"/>
<TransformSetter Property="ScaleTransform.ScaleY" Value="0.8"/>
<Setter Property="ScaleTransform.ScaleX" Value="0.8"/>
<Setter Property="ScaleTransform.ScaleY" Value="0.8"/>
</KeyFrame>
</Animation>
</Style.Animations>
@ -86,7 +86,7 @@
<Style.Animations>
<Animation Duration="0:0:3" Easing="BounceEaseInOut">
<KeyFrame Cue="48%">
<TransformSetter Property="TranslateTransform.Y" Value="-100"/>
<Setter Property="TranslateTransform.Y" Value="-100"/>
</KeyFrame>
</Animation>
</Style.Animations>
@ -95,10 +95,10 @@
<Style.Animations>
<Animation Duration="0:0:3" Easing="CircularEaseInOut">
<KeyFrame Cue="25%">
<TransformSetter Property="SkewTransform.AngleX" Value="-20"/>
<Setter Property="SkewTransform.AngleX" Value="-20"/>
</KeyFrame>
<KeyFrame Cue="75%">
<TransformSetter Property="SkewTransform.AngleX" Value="20"/>
<Setter Property="SkewTransform.AngleX" Value="20"/>
</KeyFrame>
</Animation>
</Style.Animations>

2
samples/RenderTest/Pages/ClippingPage.xaml

@ -10,7 +10,7 @@
<Style.Animations>
<Animation Duration="0:0:2" RepeatCount="Loop">
<KeyFrame Cue="100%">
<TransformSetter Property="RotateTransform.Angle" Value="360"/>
<Setter Property="RotateTransform.Angle" Value="360"/>
</KeyFrame>
</Animation>
</Style.Animations>

7
src/Android/Avalonia.Android/Resources/Resource.Designer.cs

@ -40,11 +40,14 @@ namespace Avalonia.Android
public partial class String
{
// aapt resource value: 0x7f020002
public static int ApplicationName = 2130837506;
// aapt resource value: 0x7f020001
public static int ApplicationName = 2130837505;
public static int Hello = 2130837505;
// aapt resource value: 0x7f020000
public static int Hello = 2130837504;
public static int library_name = 2130837504;
static String()
{

4
src/Android/Avalonia.AndroidTestApplication/Avalonia.AndroidTestApplication.csproj

@ -143,10 +143,6 @@
<Project>{3e10a5fa-e8da-48b1-ad44-6a5b6cb7750f}</Project>
<Name>Avalonia.Themes.Default</Name>
</ProjectReference>
<ProjectReference Include="..\..\Avalonia.HtmlRenderer\Avalonia.HtmlRenderer.csproj">
<Project>{5fb2b005-0a7f-4dad-add4-3ed01444e63d}</Project>
<Name>Avalonia.HtmlRenderer</Name>
</ProjectReference>
<ProjectReference Include="..\..\Skia\Avalonia.Skia\Avalonia.Skia.csproj">
<Project>{7d2d3083-71dd-4cc9-8907-39a0d86fb322}</Project>
<Name>Avalonia.Skia</Name>

33
src/Avalonia.Animation/Animation.cs

@ -18,6 +18,28 @@ namespace Avalonia.Animation
/// </summary>
public class Animation : AvaloniaList<KeyFrame>, IDisposable, IAnimation
{
private readonly static List<(Func<AvaloniaProperty, bool> Condition, Type Animator)> Animators = new List<(Func<AvaloniaProperty, bool>, Type)>
{
( prop => typeof(double).IsAssignableFrom(prop.PropertyType), typeof(DoubleAnimator) )
};
public static void RegisterAnimator<TAnimator>(Func<AvaloniaProperty, bool> condition)
where TAnimator: IAnimator
{
Animators.Insert(0, (condition, typeof(TAnimator)));
}
private static Type GetAnimatorType(AvaloniaProperty property)
{
foreach (var (condition, type) in Animators)
{
if (condition(property))
{
return type;
}
}
return null;
}
private bool _isChildrenChanged = false;
private List<IDisposable> _subscription = new List<IDisposable>();
@ -67,14 +89,13 @@ namespace Avalonia.Animation
{
foreach (var setter in keyframe)
{
var custAttr = setter.GetType()
.GetCustomAttributes()
.Where(p => p.GetType() == typeof(AnimatorAttribute));
var handler = GetAnimatorType(setter.Property);
if (!custAttr.Any())
throw new InvalidProgramException($"Type {setter.GetType()} doesn't have Animator attribute.");
if (handler == null)
{
throw new InvalidOperationException($"No animator registered for the property {setter.Property}. Add an animator to the Animation.Animators collection that matches this property to animate it.");
}
var handler = ((AnimatorAttribute)custAttr.First()).HandlerType;
if (!handlerList.Contains((handler, setter.Property)))
handlerList.Add((handler, setter.Property));

17
src/Avalonia.Animation/AnimationSetter.cs

@ -1,17 +0,0 @@
using System;
using System.Collections.Generic;
using System.Text;
using System.Linq;
using System.Reactive.Linq;
using System.Diagnostics;
using Avalonia.Animation.Utils;
using Avalonia.Data;
namespace Avalonia.Animation
{
public abstract class AnimationSetter : IAnimationSetter
{
public AvaloniaProperty Property { get; set; }
public object Value { get; set; }
}
}

18
src/Avalonia.Animation/AnimatorAttribute.cs

@ -1,18 +0,0 @@
using System;
namespace Avalonia.Animation
{
/// <summary>
/// Attribute for <see cref="IAnimationSetter"/> objects
/// that maps the setter to it's <see cref="Animator{T}"/>.
/// </summary>
public class AnimatorAttribute : Attribute
{
public Type HandlerType;
public AnimatorAttribute(Type handler)
{
this.HandlerType = handler;
}
}
}

21
src/Avalonia.Animation/DoubleSetter.cs

@ -1,21 +0,0 @@
using System;
using System.Collections.Generic;
using System.Text;
using System.Linq;
using System.Reactive.Linq;
using System.Diagnostics;
using Avalonia.Animation.Utils;
using Avalonia.Data;
namespace Avalonia.Animation
{
/// <summary>
/// Setter that handles <see cref="double"/> properties
/// in the target.
/// </summary>
[Animator(typeof(DoubleAnimator))]
public class DoubleSetter : AnimationSetter
{
}
}

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

@ -5,6 +5,7 @@
<RootNamespace>Avalonia</RootNamespace>
</PropertyGroup>
<Import Project="..\..\build\Base.props" />
<Import Project="..\..\build\Binding.props" />
<Import Project="..\..\build\Rx.props" />
<Import Project="..\..\build\JetBrains.Annotations.props" />
</Project>

2
src/Avalonia.Base/AvaloniaObject.cs

@ -703,7 +703,7 @@ namespace Avalonia
/// <returns>The default value.</returns>
private object GetDefaultValue(AvaloniaProperty property)
{
if (property.Inherits && _inheritanceParent is AvaloniaObject aobj)
if (property.Inherits && InheritanceParent is AvaloniaObject aobj)
return aobj.GetValueInternal(property);
return ((IStyledPropertyAccessor) property).GetDefaultValue(GetType());
}

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

@ -7,6 +7,7 @@ using System.Collections.Generic;
using System.Collections.Specialized;
using System.ComponentModel;
using System.Linq;
using Avalonia.Data.Core;
namespace Avalonia.Collections
{
@ -116,8 +117,8 @@ namespace Avalonia.Collections
_inner = new Dictionary<TKey, TValue>();
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs("Count"));
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs("Item[]"));
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(Count)));
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(CommonPropertyNames.IndexerName));
if (CollectionChanged != null)

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

@ -171,8 +171,7 @@ namespace Avalonia.Data
/// </remarks>
public static object ExtractError(object o)
{
var notification = o as BindingNotification;
return notification != null ? notification.Error : o;
return o is BindingNotification notification ? notification.Error : o;
}
/// <summary>

2
src/Markup/Avalonia.Markup/AlwaysEnabledDelegateCommand.cs → src/Avalonia.Base/Data/Converters/AlwaysEnabledDelegateCommand.cs

@ -6,7 +6,7 @@ using System.Reflection;
using System.Text;
using System.Windows.Input;
namespace Avalonia.Markup
namespace Avalonia.Data.Converters
{
class AlwaysEnabledDelegateCommand : ICommand
{

2
src/Markup/Avalonia.Markup/BoolConverters.cs → src/Avalonia.Base/Data/Converters/BoolConverters.cs

@ -3,7 +3,7 @@
using System.Linq;
namespace Avalonia.Markup
namespace Avalonia.Data.Converters
{
/// <summary>
/// Provides a set of useful <see cref="IValueConverter"/>s for working with string values.

2
src/Markup/Avalonia.Markup/DefaultValueConverter.cs → src/Avalonia.Base/Data/Converters/DefaultValueConverter.cs

@ -7,7 +7,7 @@ using Avalonia.Data;
using Avalonia.Utilities;
using System.Windows.Input;
namespace Avalonia.Markup
namespace Avalonia.Data.Converters
{
/// <summary>
/// Provides a default set of value conversions for bindings that do not specify a value

2
src/Markup/Avalonia.Markup/FuncMultiValueConverter.cs → src/Avalonia.Base/Data/Converters/FuncMultiValueConverter.cs

@ -6,7 +6,7 @@ using System.Collections.Generic;
using System.Globalization;
using System.Linq;
namespace Avalonia.Markup
namespace Avalonia.Data.Converters
{
/// <summary>
/// A general purpose <see cref="IValueConverter"/> that uses a <see cref="Func{T1, TResult}"/>

2
src/Markup/Avalonia.Markup/FuncValueConverter.cs → src/Avalonia.Base/Data/Converters/FuncValueConverter.cs

@ -5,7 +5,7 @@ using System;
using System.Globalization;
using Avalonia.Utilities;
namespace Avalonia.Markup
namespace Avalonia.Data.Converters
{
/// <summary>
/// A general purpose <see cref="IValueConverter"/> that uses a <see cref="Func{T1, TResult}"/>

2
src/Markup/Avalonia.Markup/IMultiValueConverter.cs → src/Avalonia.Base/Data/Converters/IMultiValueConverter.cs

@ -5,7 +5,7 @@ using System;
using System.Collections.Generic;
using System.Globalization;
namespace Avalonia.Markup
namespace Avalonia.Data.Converters
{
/// <summary>
/// Converts multi-binding inputs to a final value.

2
src/Markup/Avalonia.Markup/IValueConverter.cs → src/Avalonia.Base/Data/Converters/IValueConverter.cs

@ -5,7 +5,7 @@ using System;
using System.Globalization;
using Avalonia.Data;
namespace Avalonia.Markup
namespace Avalonia.Data.Converters
{
/// <summary>
/// Converts a binding value.

2
src/Markup/Avalonia.Markup/StringConverters.cs → src/Avalonia.Base/Data/Converters/StringConverters.cs

@ -5,7 +5,7 @@ using System;
using System.Globalization;
using Avalonia.Utilities;
namespace Avalonia.Markup
namespace Avalonia.Data.Converters
{
/// <summary>
/// Provides a set of useful <see cref="IValueConverter"/>s for working with string values.

4
src/Markup/Avalonia.Markup/Data/BindingExpression.cs → src/Avalonia.Base/Data/Core/BindingExpression.cs

@ -5,11 +5,11 @@ using System;
using System.Globalization;
using System.Reactive.Linq;
using System.Reactive.Subjects;
using Avalonia.Data;
using Avalonia.Data.Converters;
using Avalonia.Logging;
using Avalonia.Utilities;
namespace Avalonia.Markup.Data
namespace Avalonia.Data.Core
{
/// <summary>
/// Binds to an expression on an object using a type value converter to convert the values

2
src/Markup/Avalonia.Markup/Data/CommonPropertyNames.cs → src/Avalonia.Base/Data/Core/CommonPropertyNames.cs

@ -1,7 +1,7 @@
// Copyright (c) The Avalonia Project. All rights reserved.
// Licensed under the MIT license. See licence.md file in the project root for full license information.
namespace Avalonia.Markup.Data
namespace Avalonia.Data.Core
{
public static class CommonPropertyNames
{

2
src/Markup/Avalonia.Markup/Data/EmptyExpressionNode.cs → src/Avalonia.Base/Data/Core/EmptyExpressionNode.cs

@ -4,7 +4,7 @@
using System;
using System.Reactive.Linq;
namespace Avalonia.Markup.Data
namespace Avalonia.Data.Core
{
internal class EmptyExpressionNode : ExpressionNode
{

2
src/Markup/Avalonia.Markup/Data/ExpressionNode.cs → src/Avalonia.Base/Data/Core/ExpressionNode.cs

@ -7,7 +7,7 @@ using System.Reactive.Linq;
using System.Reactive.Subjects;
using Avalonia.Data;
namespace Avalonia.Markup.Data
namespace Avalonia.Data.Core
{
internal abstract class ExpressionNode : ISubject<object>
{

4
src/Markup/Avalonia.Markup/Data/ExpressionNodeBuilder.cs → src/Avalonia.Base/Data/Core/ExpressionNodeBuilder.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.Markup.Data.Parsers;
using Avalonia.Data.Core.Parsers;
namespace Avalonia.Markup.Data
namespace Avalonia.Data.Core
{
internal static class ExpressionNodeBuilder
{

45
src/Markup/Avalonia.Markup/Data/ExpressionObserver.cs → src/Avalonia.Base/Data/Core/ExpressionObserver.cs

@ -8,9 +8,9 @@ using System.Reactive.Disposables;
using System.Reactive.Linq;
using System.Reactive.Subjects;
using Avalonia.Data;
using Avalonia.Markup.Data.Plugins;
using Avalonia.Data.Core.Plugins;
namespace Avalonia.Markup.Data
namespace Avalonia.Data.Core
{
/// <summary>
/// Observes and sets the value of an expression on an object.
@ -245,40 +245,35 @@ namespace Avalonia.Markup.Data
private object Translate(object o)
{
var weak = o as WeakReference;
if (weak != null)
if (o is WeakReference weak)
{
return weak.Target;
}
else
else if (BindingNotification.ExtractError(o) is MarkupBindingChainException broken)
{
var broken = BindingNotification.ExtractError(o) as MarkupBindingChainException;
if (broken != null)
{
broken.Commit(Description);
}
return o;
broken.Commit(Description);
}
return o;
}
private IDisposable StartRoot()
{
var observable = _root as IObservable<object>;
if (observable != null)
switch (_root)
{
return observable.Subscribe(
x => _node.Target = new WeakReference(x != AvaloniaProperty.UnsetValue ? x : null),
_ => _finished.OnNext(Unit.Default),
() => _finished.OnNext(Unit.Default));
}
else
{
_node.Target = (WeakReference)_root;
return Disposable.Empty;
case IObservable<object> observable:
return observable.Subscribe(
x => _node.Target = new WeakReference(x != AvaloniaProperty.UnsetValue ? x : null),
_ => _finished.OnNext(Unit.Default),
() => _finished.OnNext(Unit.Default));
case WeakReference weak:
_node.Target = weak;
break;
default:
throw new AvaloniaInternalException("The ExpressionObserver._root member should only be either an observable or WeakReference.");
}
return Disposable.Empty;
}
}
}

4
src/Markup/Avalonia.Markup/Data/ExpressionParseException.cs → src/Avalonia.Base/Data/Core/ExpressionParseException.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.Markup.Data.Parsers;
using Avalonia.Data.Core.Parsers;
namespace Avalonia.Markup.Data
namespace Avalonia.Data.Core
{
/// <summary>
/// Exception thrown when <see cref="ExpressionObserver"/> could not parse the provided

2
src/Markup/Avalonia.Markup/Data/ISettableNode.cs → src/Avalonia.Base/Data/Core/ISettableNode.cs

@ -5,7 +5,7 @@ using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Avalonia.Markup.Data
namespace Avalonia.Data.Core
{
interface ISettableNode
{

2
src/Markup/Avalonia.Markup/Data/ITransformNode.cs → src/Avalonia.Base/Data/Core/ITransformNode.cs

@ -2,7 +2,7 @@
using System.Collections.Generic;
using System.Text;
namespace Avalonia.Markup.Data
namespace Avalonia.Data.Core
{
interface ITransformNode
{

2
src/Markup/Avalonia.Markup/Data/IndexerNode.cs → src/Avalonia.Base/Data/Core/IndexerNode.cs

@ -13,7 +13,7 @@ using System.Reflection;
using System.Reactive.Linq;
using Avalonia.Data;
namespace Avalonia.Markup.Data
namespace Avalonia.Data.Core
{
internal class IndexerNode : ExpressionNode, ISettableNode
{

2
src/Markup/Avalonia.Markup/Data/LogicalNotNode.cs → src/Avalonia.Base/Data/Core/LogicalNotNode.cs

@ -5,7 +5,7 @@ using System;
using System.Globalization;
using Avalonia.Data;
namespace Avalonia.Markup.Data
namespace Avalonia.Data.Core
{
internal class LogicalNotNode : ExpressionNode, ITransformNode
{

3
src/Markup/Avalonia.Markup/Data/MarkupBindingChainException.cs → src/Avalonia.Base/Data/Core/MarkupBindingChainException.cs

@ -1,9 +1,8 @@
using System;
using System.Collections.Generic;
using System.Linq;
using Avalonia.Data;
namespace Avalonia.Markup.Data
namespace Avalonia.Data.Core
{
internal class MarkupBindingChainException : BindingChainException
{

2
src/Markup/Avalonia.Markup/Data/Parsers/ArgumentListParser.cs → src/Avalonia.Base/Data/Core/Parsers/ArgumentListParser.cs

@ -5,7 +5,7 @@ using System;
using System.Collections.Generic;
using System.Text;
namespace Avalonia.Markup.Data.Parsers
namespace Avalonia.Data.Core.Parsers
{
internal static class ArgumentListParser
{

2
src/Markup/Avalonia.Markup/Data/Parsers/ExpressionParser.cs → src/Avalonia.Base/Data/Core/Parsers/ExpressionParser.cs

@ -5,7 +5,7 @@ using System;
using System.Collections.Generic;
using System.Linq;
namespace Avalonia.Markup.Data.Parsers
namespace Avalonia.Data.Core.Parsers
{
internal class ExpressionParser
{

2
src/Markup/Avalonia.Markup/Data/Parsers/IdentifierParser.cs → src/Avalonia.Base/Data/Core/Parsers/IdentifierParser.cs

@ -4,7 +4,7 @@
using System.Globalization;
using System.Text;
namespace Avalonia.Markup.Data.Parsers
namespace Avalonia.Data.Core.Parsers
{
internal static class IdentifierParser
{

2
src/Markup/Avalonia.Markup/Data/Parsers/Reader.cs → src/Avalonia.Base/Data/Core/Parsers/Reader.cs

@ -3,7 +3,7 @@
using System;
namespace Avalonia.Markup.Data.Parsers
namespace Avalonia.Data.Core.Parsers
{
internal class Reader
{

2
src/Markup/Avalonia.Markup/Data/Plugins/AvaloniaPropertyAccessorPlugin.cs → src/Avalonia.Base/Data/Core/Plugins/AvaloniaPropertyAccessorPlugin.cs

@ -6,7 +6,7 @@ using System.Linq;
using System.Reactive.Linq;
using Avalonia.Data;
namespace Avalonia.Markup.Data.Plugins
namespace Avalonia.Data.Core.Plugins
{
/// <summary>
/// Reads a property from a <see cref="AvaloniaObject"/>.

2
src/Markup/Avalonia.Markup/Data/Plugins/DataAnnotationsValidationPlugin.cs → src/Avalonia.Base/Data/Core/Plugins/DataAnnotationsValidationPlugin.cs

@ -8,7 +8,7 @@ using System.Linq;
using System.Reflection;
using Avalonia.Data;
namespace Avalonia.Markup.Data.Plugins
namespace Avalonia.Data.Core.Plugins
{
/// <summary>
/// Validates properties on that have <see cref="ValidationAttribute"/>s.

2
src/Markup/Avalonia.Markup/Data/Plugins/DataValidatiorBase.cs → src/Avalonia.Base/Data/Core/Plugins/DataValidatiorBase.cs

@ -4,7 +4,7 @@
using System;
using Avalonia.Data;
namespace Avalonia.Markup.Data.Plugins
namespace Avalonia.Data.Core.Plugins
{
/// <summary>
/// Base class for data validators.

2
src/Markup/Avalonia.Markup/Data/Plugins/ExceptionValidationPlugin.cs → src/Avalonia.Base/Data/Core/Plugins/ExceptionValidationPlugin.cs

@ -5,7 +5,7 @@ using Avalonia.Data;
using System;
using System.Reflection;
namespace Avalonia.Markup.Data.Plugins
namespace Avalonia.Data.Core.Plugins
{
/// <summary>
/// Validates properties that report errors by throwing exceptions.

2
src/Markup/Avalonia.Markup/Data/Plugins/IDataValidationPlugin.cs → src/Avalonia.Base/Data/Core/Plugins/IDataValidationPlugin.cs

@ -4,7 +4,7 @@
using System;
using Avalonia.Data;
namespace Avalonia.Markup.Data.Plugins
namespace Avalonia.Data.Core.Plugins
{
/// <summary>
/// Defines how data validation is observed by an <see cref="ExpressionObserver"/>.

2
src/Markup/Avalonia.Markup/Data/Plugins/IPropertyAccessor.cs → src/Avalonia.Base/Data/Core/Plugins/IPropertyAccessor.cs

@ -4,7 +4,7 @@
using System;
using Avalonia.Data;
namespace Avalonia.Markup.Data.Plugins
namespace Avalonia.Data.Core.Plugins
{
/// <summary>
/// Defines an accessor to a property on an object returned by a

2
src/Markup/Avalonia.Markup/Data/Plugins/IPropertyAccessorPlugin.cs → src/Avalonia.Base/Data/Core/Plugins/IPropertyAccessorPlugin.cs

@ -3,7 +3,7 @@
using System;
namespace Avalonia.Markup.Data.Plugins
namespace Avalonia.Data.Core.Plugins
{
/// <summary>
/// Defines how a member is read, written and observed by an

2
src/Markup/Avalonia.Markup/Data/Plugins/IStreamPlugin.cs → src/Avalonia.Base/Data/Core/Plugins/IStreamPlugin.cs

@ -3,7 +3,7 @@
using System;
namespace Avalonia.Markup.Data.Plugins
namespace Avalonia.Data.Core.Plugins
{
/// <summary>
/// Defines a plugin that handles the '^' stream binding operator.

2
src/Markup/Avalonia.Markup/Data/Plugins/IndeiValidationPlugin.cs → src/Avalonia.Base/Data/Core/Plugins/IndeiValidationPlugin.cs

@ -8,7 +8,7 @@ using System.Linq;
using Avalonia.Data;
using Avalonia.Utilities;
namespace Avalonia.Markup.Data.Plugins
namespace Avalonia.Data.Core.Plugins
{
/// <summary>
/// Validates properties on objects that implement <see cref="INotifyDataErrorInfo"/>.

2
src/Markup/Avalonia.Markup/Data/Plugins/InpcPropertyAccessorPlugin.cs → src/Avalonia.Base/Data/Core/Plugins/InpcPropertyAccessorPlugin.cs

@ -10,7 +10,7 @@ using Avalonia.Data;
using Avalonia.Logging;
using Avalonia.Utilities;
namespace Avalonia.Markup.Data.Plugins
namespace Avalonia.Data.Core.Plugins
{
/// <summary>
/// Reads a property from a standard C# object that optionally supports the

2
src/Markup/Avalonia.Markup/Data/Plugins/MethodAccessorPlugin.cs → src/Avalonia.Base/Data/Core/Plugins/MethodAccessorPlugin.cs

@ -5,7 +5,7 @@ using Avalonia.Data;
using System.Reflection;
using System.Linq;
namespace Avalonia.Markup.Data.Plugins
namespace Avalonia.Data.Core.Plugins
{
class MethodAccessorPlugin : IPropertyAccessorPlugin
{

2
src/Markup/Avalonia.Markup/Data/Plugins/ObservableStreamPlugin.cs → src/Avalonia.Base/Data/Core/Plugins/ObservableStreamPlugin.cs

@ -3,7 +3,7 @@
using System;
namespace Avalonia.Markup.Data.Plugins
namespace Avalonia.Data.Core.Plugins
{
/// <summary>
/// Handles binding to <see cref="IObservable{T}"/>s for the '^' stream binding operator.

2
src/Markup/Avalonia.Markup/Data/Plugins/PropertyAccessorBase.cs → src/Avalonia.Base/Data/Core/Plugins/PropertyAccessorBase.cs

@ -4,7 +4,7 @@
using System;
using Avalonia.Data;
namespace Avalonia.Markup.Data.Plugins
namespace Avalonia.Data.Core.Plugins
{
/// <summary>
/// Defines a default base implementation for a <see cref="IPropertyAccessor"/>.

2
src/Markup/Avalonia.Markup/Data/Plugins/PropertyError.cs → src/Avalonia.Base/Data/Core/Plugins/PropertyError.cs

@ -2,7 +2,7 @@ using System;
using System.Reactive.Disposables;
using Avalonia.Data;
namespace Avalonia.Markup.Data.Plugins
namespace Avalonia.Data.Core.Plugins
{
/// <summary>
/// An <see cref="IPropertyAccessor"/> that represents an error.

2
src/Markup/Avalonia.Markup/Data/Plugins/TaskStreamPlugin.cs → src/Avalonia.Base/Data/Core/Plugins/TaskStreamPlugin.cs

@ -9,7 +9,7 @@ using System.Reflection;
using System.Threading.Tasks;
using Avalonia.Data;
namespace Avalonia.Markup.Data.Plugins
namespace Avalonia.Data.Core.Plugins
{
/// <summary>
/// Handles binding to <see cref="Task"/>s for the '^' stream binding operator.

4
src/Markup/Avalonia.Markup/Data/PropertyAccessorNode.cs → src/Avalonia.Base/Data/Core/PropertyAccessorNode.cs

@ -6,9 +6,9 @@ using System.Linq;
using System.Reactive.Disposables;
using System.Reactive.Linq;
using Avalonia.Data;
using Avalonia.Markup.Data.Plugins;
using Avalonia.Data.Core.Plugins;
namespace Avalonia.Markup.Data
namespace Avalonia.Data.Core
{
internal class PropertyAccessorNode : ExpressionNode, ISettableNode
{

2
src/Markup/Avalonia.Markup/Data/StreamNode.cs → src/Avalonia.Base/Data/Core/StreamNode.cs

@ -6,7 +6,7 @@ using System.Globalization;
using Avalonia.Data;
using System.Reactive.Linq;
namespace Avalonia.Markup.Data
namespace Avalonia.Data.Core
{
internal class StreamNode : ExpressionNode
{

3
src/Avalonia.Base/Properties/AssemblyInfo.cs

@ -1,9 +1,10 @@
// Copyright (c) The Avalonia Project. All rights reserved.
// Licensed under the MIT license. See licence.md file in the project root for full license information.
using System.Reflection;
using System.Runtime.CompilerServices;
using Avalonia.Metadata;
[assembly: XmlnsDefinition("https://github.com/avaloniaui", "Avalonia.Data.Converters")]
[assembly: InternalsVisibleTo("Avalonia.Base.UnitTests")]
[assembly: InternalsVisibleTo("Avalonia.UnitTests")]
[assembly: InternalsVisibleTo("DynamicProxyGenAssembly2")]

764
src/Avalonia.Controls/Control.cs

@ -29,53 +29,22 @@ namespace Avalonia.Controls
/// <remarks>
/// The control class extends <see cref="InputElement"/> and adds the following features:
///
/// - An inherited <see cref="DataContext"/>.
/// - A <see cref="Tag"/> property to allow user-defined data to be attached to the control.
/// - A collection of class strings for custom styling.
/// - Implements <see cref="IStyleable"/> to allow styling to work on the control.
/// - Implements <see cref="ILogical"/> to form part of a logical tree.
/// </remarks>
public class Control : InputElement, IControl, INamed, ISetInheritanceParent, ISetLogicalParent, ISupportInitialize, IVisualBrushInitialize
public class Control : InputElement, IControl, INamed, ISupportInitialize, IVisualBrushInitialize, IRequiresTemplateInSetter
{
/// <summary>
/// Defines the <see cref="DataContext"/> property.
/// </summary>
public static readonly StyledProperty<object> DataContextProperty =
AvaloniaProperty.Register<Control, object>(
nameof(DataContext),
inherits: true,
notifying: DataContextNotifying);
/// <summary>
/// Defines the <see cref="FocusAdorner"/> property.
/// </summary>
public static readonly StyledProperty<ITemplate<IControl>> FocusAdornerProperty =
AvaloniaProperty.Register<Control, ITemplate<IControl>>(nameof(FocusAdorner));
/// <summary>
/// Defines the <see cref="Name"/> property.
/// </summary>
public static readonly DirectProperty<Control, string> NameProperty =
AvaloniaProperty.RegisterDirect<Control, string>(nameof(Name), o => o.Name, (o, v) => o.Name = v);
/// <summary>
/// Defines the <see cref="Parent"/> property.
/// </summary>
public static readonly DirectProperty<Control, IControl> ParentProperty =
AvaloniaProperty.RegisterDirect<Control, IControl>(nameof(Parent), o => o.Parent);
/// <summary>
/// Defines the <see cref="Tag"/> property.
/// </summary>
public static readonly StyledProperty<object> TagProperty =
AvaloniaProperty.Register<Control, object>(nameof(Tag));
/// <summary>
/// Defines the <see cref="TemplatedParent"/> property.
/// </summary>
public static readonly StyledProperty<ITemplatedControl> TemplatedParentProperty =
AvaloniaProperty.Register<Control, ITemplatedControl>(nameof(TemplatedParent), inherits: true);
/// <summary>
/// Defines the <see cref="ContextMenu"/> property.
/// </summary>
@ -88,152 +57,8 @@ namespace Avalonia.Controls
public static readonly RoutedEvent<RequestBringIntoViewEventArgs> RequestBringIntoViewEvent =
RoutedEvent.Register<Control, RequestBringIntoViewEventArgs>("RequestBringIntoView", RoutingStrategies.Bubble);
private int _initCount;
private string _name;
private IControl _parent;
private readonly Classes _classes = new Classes();
private DataTemplates _dataTemplates;
private IControl _focusAdorner;
private bool _isAttachedToLogicalTree;
private IAvaloniaList<ILogical> _logicalChildren;
private INameScope _nameScope;
private IResourceDictionary _resources;
private Styles _styles;
private bool _styled;
private Subject<IStyleable> _styleDetach = new Subject<IStyleable>();
private bool _dataContextUpdating;
/// <summary>
/// Initializes static members of the <see cref="Control"/> class.
/// </summary>
static Control()
{
AffectsMeasure(IsVisibleProperty);
PseudoClass(IsEnabledCoreProperty, x => !x, ":disabled");
PseudoClass(IsFocusedProperty, ":focus");
PseudoClass(IsPointerOverProperty, ":pointerover");
DataContextProperty.Changed.AddClassHandler<Control>(x => x.OnDataContextChangedCore);
}
/// <summary>
/// Initializes a new instance of the <see cref="Control"/> class.
/// </summary>
public Control()
{
_nameScope = this as INameScope;
_isAttachedToLogicalTree = this is IStyleRoot;
}
/// <summary>
/// Raised when the control is attached to a rooted logical tree.
/// </summary>
public event EventHandler<LogicalTreeAttachmentEventArgs> AttachedToLogicalTree;
/// <summary>
/// Raised when the control is detached from a rooted logical tree.
/// </summary>
public event EventHandler<LogicalTreeAttachmentEventArgs> DetachedFromLogicalTree;
/// <summary>
/// Occurs when the <see cref="DataContext"/> property changes.
/// </summary>
/// <remarks>
/// This event will be raised when the <see cref="DataContext"/> property has changed and
/// all subscribers to that change have been notified.
/// </remarks>
public event EventHandler DataContextChanged;
/// <summary>
/// Occurs when the control has finished initialization.
/// </summary>
/// <remarks>
/// The Initialized event indicates that all property values on the control have been set.
/// When loading the control from markup, it occurs when
/// <see cref="ISupportInitialize.EndInit"/> is called *and* the control
/// is attached to a rooted logical tree. When the control is created by code and
/// <see cref="ISupportInitialize"/> is not used, it is called when the control is attached
/// to the visual tree.
/// </remarks>
public event EventHandler Initialized;
/// <summary>
/// Occurs when a resource in this control or a parent control has changed.
/// </summary>
public event EventHandler<ResourcesChangedEventArgs> ResourcesChanged;
/// <summary>
/// Gets or sets the name of the control.
/// </summary>
/// <remarks>
/// An element's name is used to uniquely identify a control within the control's name
/// scope. Once the element is added to a logical tree, its name cannot be changed.
/// </remarks>
public string Name
{
get
{
return _name;
}
set
{
if (String.IsNullOrWhiteSpace(value))
{
throw new InvalidOperationException("Cannot set Name to null or empty string.");
}
if (_styled)
{
throw new InvalidOperationException("Cannot set Name : control already styled.");
}
_name = value;
}
}
/// <summary>
/// Gets or sets the control's classes.
/// </summary>
/// <remarks>
/// <para>
/// Classes can be used to apply user-defined styling to controls, or to allow controls
/// that share a common purpose to be easily selected.
/// </para>
/// <para>
/// Even though this property can be set, the setter is only intended for use in object
/// initializers. Assigning to this property does not change the underlying collection,
/// it simply clears the existing collection and addds the contents of the assigned
/// collection.
/// </para>
/// </remarks>
public Classes Classes
{
get
{
return _classes;
}
set
{
if (_classes != value)
{
_classes.Replace(value);
}
}
}
/// <summary>
/// Gets or sets the control's data context.
/// </summary>
/// <remarks>
/// The data context is an inherited property that specifies the default object that will
/// be used for data binding.
/// </remarks>
public object DataContext
{
get { return GetValue(DataContextProperty); }
set { SetValue(DataContextProperty, value); }
}
/// <summary>
/// Gets or sets the control's focus adorner.
@ -253,55 +78,6 @@ namespace Avalonia.Controls
/// </remarks>
public DataTemplates DataTemplates => _dataTemplates ?? (_dataTemplates = new DataTemplates());
/// <summary>
/// Gets a value that indicates whether the element has finished initialization.
/// </summary>
/// <remarks>
/// For more information about when IsInitialized is set, see the <see cref="Initialized"/>
/// event.
/// </remarks>
public bool IsInitialized { get; private set; }
/// <summary>
/// Gets the styles for the control.
/// </summary>
/// <remarks>
/// Styles for the entire application are added to the Application.Styles collection, but
/// each control may in addition define its own styles which are applied to the control
/// itself and its children.
/// </remarks>
public Styles Styles
{
get { return _styles ?? (Styles = new Styles()); }
set
{
Contract.Requires<ArgumentNullException>(value != null);
if (_styles != value)
{
if (_styles != null)
{
(_styles as ISetStyleParent)?.SetParent(null);
_styles.ResourcesChanged -= ThisResourcesChanged;
}
_styles = value;
if (value is ISetStyleParent setParent && setParent.ResourceParent == null)
{
setParent.SetParent(this);
}
_styles.ResourcesChanged += ThisResourcesChanged;
}
}
}
/// <summary>
/// Gets the control's logical parent.
/// </summary>
public IControl Parent => _parent;
/// <summary>
/// Gets or sets a context menu to the control.
/// </summary>
@ -311,34 +87,6 @@ namespace Avalonia.Controls
set { SetValue(ContextMenuProperty, value); }
}
/// <summary>
/// Gets or sets the control's resource dictionary.
/// </summary>
public IResourceDictionary Resources
{
get => _resources ?? (Resources = new ResourceDictionary());
set
{
Contract.Requires<ArgumentNullException>(value != null);
var hadResources = false;
if (_resources != null)
{
hadResources = _resources.Count > 0;
_resources.ResourcesChanged -= ThisResourcesChanged;
}
_resources = value;
_resources.ResourcesChanged += ThisResourcesChanged;
if (hadResources || _resources.Count > 0)
{
((ILogical)this).NotifyResourcesChanged(new ResourcesChangedEventArgs());
}
}
}
/// <summary>
/// Gets or sets a user-defined object attached to the control.
/// </summary>
@ -348,226 +96,11 @@ namespace Avalonia.Controls
set { SetValue(TagProperty, value); }
}
/// <summary>
/// Gets the control whose lookless template this control is part of.
/// </summary>
public ITemplatedControl TemplatedParent
{
get { return GetValue(TemplatedParentProperty); }
internal set { SetValue(TemplatedParentProperty, value); }
}
/// <summary>
/// Gets the control's logical children.
/// </summary>
protected IAvaloniaList<ILogical> LogicalChildren
{
get
{
if (_logicalChildren == null)
{
var list = new AvaloniaList<ILogical>();
list.ResetBehavior = ResetBehavior.Remove;
list.Validate = ValidateLogicalChild;
list.CollectionChanged += LogicalChildrenCollectionChanged;
_logicalChildren = list;
}
return _logicalChildren;
}
}
public new IControl Parent => (IControl)base.Parent;
/// <inheritdoc/>
bool IDataTemplateHost.IsDataTemplatesInitialized => _dataTemplates != null;
/// <summary>
/// Gets the <see cref="Classes"/> collection in a form that allows adding and removing
/// pseudoclasses.
/// </summary>
protected IPseudoClasses PseudoClasses => Classes;
/// <summary>
/// Gets a value indicating whether the element is attached to a rooted logical tree.
/// </summary>
bool ILogical.IsAttachedToLogicalTree => _isAttachedToLogicalTree;
/// <summary>
/// Gets the control's logical parent.
/// </summary>
ILogical ILogical.LogicalParent => Parent;
/// <summary>
/// Gets the control's logical children.
/// </summary>
IAvaloniaReadOnlyList<ILogical> ILogical.LogicalChildren => LogicalChildren;
/// <inheritdoc/>
bool IResourceProvider.HasResources => _resources?.Count > 0 || Styles.HasResources;
/// <inheritdoc/>
IResourceNode IResourceNode.ResourceParent => ((IStyleHost)this).StylingParent as IResourceNode;
/// <inheritdoc/>
IAvaloniaReadOnlyList<string> IStyleable.Classes => Classes;
/// <summary>
/// Gets the type by which the control is styled.
/// </summary>
/// <remarks>
/// Usually controls are styled by their own type, but there are instances where you want
/// a control to be styled by its base type, e.g. creating SpecialButton that
/// derives from Button and adds extra functionality but is still styled as a regular
/// Button.
/// </remarks>
Type IStyleable.StyleKey => GetType();
/// <inheritdoc/>
IObservable<IStyleable> IStyleable.StyleDetach => _styleDetach;
/// <inheritdoc/>
bool IStyleHost.IsStylesInitialized => _styles != null;
/// <inheritdoc/>
IStyleHost IStyleHost.StylingParent => (IStyleHost)InheritanceParent;
/// <inheritdoc/>
public virtual void BeginInit()
{
++_initCount;
}
/// <inheritdoc/>
public virtual void EndInit()
{
if (_initCount == 0)
{
throw new InvalidOperationException("BeginInit was not called.");
}
if (--_initCount == 0 && _isAttachedToLogicalTree)
{
InitializeStylesIfNeeded();
InitializeIfNeeded();
}
}
private void InitializeStylesIfNeeded(bool force = false)
{
if (_initCount == 0 && (!_styled || force))
{
RegisterWithNameScope();
ApplyStyling();
_styled = true;
}
}
private void InitializeIfNeeded()
{
if (_initCount == 0 && !IsInitialized)
{
IsInitialized = true;
Initialized?.Invoke(this, EventArgs.Empty);
}
}
/// <inheritdoc/>
void ILogical.NotifyAttachedToLogicalTree(LogicalTreeAttachmentEventArgs e)
{
this.OnAttachedToLogicalTreeCore(e);
}
/// <inheritdoc/>
void ILogical.NotifyDetachedFromLogicalTree(LogicalTreeAttachmentEventArgs e)
{
this.OnDetachedFromLogicalTreeCore(e);
}
/// <inheritdoc/>
void ILogical.NotifyResourcesChanged(ResourcesChangedEventArgs e)
{
ResourcesChanged?.Invoke(this, new ResourcesChangedEventArgs());
}
/// <inheritdoc/>
bool IResourceProvider.TryGetResource(string key, out object value)
{
value = null;
return (_resources?.TryGetResource(key, out value) ?? false) ||
(_styles?.TryGetResource(key, out value) ?? false);
}
/// <summary>
/// Sets the control's logical parent.
/// </summary>
/// <param name="parent">The parent.</param>
void ISetLogicalParent.SetParent(ILogical parent)
{
var old = Parent;
if (parent != old)
{
if (old != null && parent != null)
{
throw new InvalidOperationException("The Control already has a parent.");
}
if (_isAttachedToLogicalTree)
{
var oldRoot = FindStyleRoot(old) ?? this as IStyleRoot;
if (oldRoot == null)
{
throw new AvaloniaInternalException("Was attached to logical tree but cannot find root.");
}
var e = new LogicalTreeAttachmentEventArgs(oldRoot);
OnDetachedFromLogicalTreeCore(e);
}
if (InheritanceParent == null || parent == null)
{
InheritanceParent = parent as AvaloniaObject;
}
_parent = (IControl)parent;
if (old != null)
{
old.ResourcesChanged -= ThisResourcesChanged;
}
if (_parent != null)
{
_parent.ResourcesChanged += ThisResourcesChanged;
}
((ILogical)this).NotifyResourcesChanged(new ResourcesChangedEventArgs());
if (_parent is IStyleRoot || _parent?.IsAttachedToLogicalTree == true || this is IStyleRoot)
{
var newRoot = FindStyleRoot(this);
if (newRoot == null)
{
throw new AvaloniaInternalException("Parent is atttached to logical tree but cannot find root.");
}
var e = new LogicalTreeAttachmentEventArgs(newRoot);
OnAttachedToLogicalTreeCore(e);
}
RaisePropertyChanged(ParentProperty, old, _parent, BindingPriority.LocalValue);
}
}
/// <summary>
/// Sets the control's inheritance parent.
/// </summary>
/// <param name="parent">The parent.</param>
void ISetInheritanceParent.SetParent(IAvaloniaObject parent)
{
InheritanceParent = parent;
}
/// <inheritdoc/>
void IVisualBrushInitialize.EnsureInitialized()
{
@ -600,52 +133,6 @@ namespace Avalonia.Controls
}
}
/// <summary>
/// Adds a pseudo-class to be set when a property is true.
/// </summary>
/// <param name="property">The property.</param>
/// <param name="className">The pseudo-class.</param>
protected static void PseudoClass(AvaloniaProperty<bool> property, string className)
{
PseudoClass(property, x => x, className);
}
/// <summary>
/// Adds a pseudo-class to be set when a property equals a certain value.
/// </summary>
/// <typeparam name="T">The type of the property.</typeparam>
/// <param name="property">The property.</param>
/// <param name="selector">Returns a boolean value based on the property value.</param>
/// <param name="className">The pseudo-class.</param>
protected static void PseudoClass<T>(
AvaloniaProperty<T> property,
Func<T, bool> selector,
string className)
{
Contract.Requires<ArgumentNullException>(property != null);
Contract.Requires<ArgumentNullException>(selector != null);
Contract.Requires<ArgumentNullException>(className != null);
if (string.IsNullOrWhiteSpace(className))
{
throw new ArgumentException("Cannot supply an empty className.");
}
property.Changed.Merge(property.Initialized)
.Where(e => e.Sender is Control)
.Subscribe(e =>
{
if (selector((T)e.NewValue))
{
((Control)e.Sender).PseudoClasses.Add(className);
}
else
{
((Control)e.Sender).PseudoClasses.Remove(className);
}
});
}
/// <summary>
/// Gets the element that recieves the focus adorner.
/// </summary>
@ -655,22 +142,6 @@ namespace Avalonia.Controls
return this;
}
/// <summary>
/// Called when the control is added to a rooted logical tree.
/// </summary>
/// <param name="e">The event args.</param>
protected virtual void OnAttachedToLogicalTree(LogicalTreeAttachmentEventArgs e)
{
}
/// <summary>
/// Called when the control is removed from a rooted logical tree.
/// </summary>
/// <param name="e">The event args.</param>
protected virtual void OnDetachedFromLogicalTree(LogicalTreeAttachmentEventArgs e)
{
}
/// <inheritdoc/>
protected sealed override void OnAttachedToVisualTreeCore(VisualTreeAttachmentEventArgs e)
{
@ -685,29 +156,6 @@ namespace Avalonia.Controls
base.OnDetachedFromVisualTreeCore(e);
}
/// <summary>
/// Called when the <see cref="DataContext"/> property changes.
/// </summary>
/// <param name="e">The event args.</param>
protected virtual void OnDataContextChanged(EventArgs e)
{
DataContextChanged?.Invoke(this, EventArgs.Empty);
}
/// <summary>
/// Called when the <see cref="DataContext"/> begins updating.
/// </summary>
protected virtual void OnDataContextBeginUpdate()
{
}
/// <summary>
/// Called when the <see cref="DataContext"/> finishes updating.
/// </summary>
protected virtual void OnDataContextEndUpdate()
{
}
/// <inheritdoc/>
protected override void OnGotFocus(GotFocusEventArgs e)
{
@ -757,211 +205,5 @@ namespace Avalonia.Controls
_focusAdorner = null;
}
}
private static void DataContextNotifying(IAvaloniaObject o, bool notifying)
{
if (o is Control control)
{
DataContextNotifying(control, notifying);
}
}
private static void DataContextNotifying(Control control, bool notifying)
{
if (notifying)
{
if (!control._dataContextUpdating)
{
control._dataContextUpdating = true;
control.OnDataContextBeginUpdate();
foreach (var child in control.LogicalChildren)
{
if (child is Control c &&
c.InheritanceParent == control &&
!c.IsSet(DataContextProperty))
{
DataContextNotifying(c, notifying);
}
}
}
}
else
{
if (control._dataContextUpdating)
{
control.OnDataContextEndUpdate();
control._dataContextUpdating = false;
}
}
}
private static IStyleRoot FindStyleRoot(IStyleHost e)
{
while (e != null)
{
if (e is IRenderRoot root)
{
return root as IStyleRoot;
}
e = e.StylingParent;
}
return null;
}
private void ApplyStyling()
{
AvaloniaLocator.Current.GetService<IStyler>()?.ApplyStyles(this);
}
private void RegisterWithNameScope()
{
if (_nameScope == null)
{
_nameScope = NameScope.GetNameScope(this) ?? ((Control)Parent)?._nameScope;
}
if (Name != null)
{
_nameScope?.Register(Name, this);
var visualParent = Parent as Visual;
if (this is INameScope && visualParent != null)
{
// If we have e.g. a named UserControl in a window then we want that control
// to be findable by name from the Window, so register with both name scopes.
// This differs from WPF's behavior in that XAML manually registers controls
// with name scopes based on the XAML file in which the name attribute appears,
// but we're trying to avoid XAML magic in Avalonia in order to made code-
// created UIs easy. This will cause problems if a UserControl declares a name
// in its XAML and that control is included multiple times in a parent control
// (as the name will be duplicated), however at the moment I'm fine with saying
// "don't do that".
var parentNameScope = NameScope.FindNameScope(visualParent);
parentNameScope?.Register(Name, this);
}
}
}
private static void ValidateLogicalChild(ILogical c)
{
if (c == null)
{
throw new ArgumentException("Cannot add null to LogicalChildren.");
}
}
private void OnAttachedToLogicalTreeCore(LogicalTreeAttachmentEventArgs e)
{
// This method can be called when a control is already attached to the logical tree
// in the following scenario:
// - ListBox gets assigned Items containing ListBoxItem
// - ListBox makes ListBoxItem a logical child
// - ListBox template gets applied; making its Panel get attached to logical tree
// - That AttachedToLogicalTree signal travels down to the ListBoxItem
if (!_isAttachedToLogicalTree)
{
_isAttachedToLogicalTree = true;
InitializeStylesIfNeeded(true);
OnAttachedToLogicalTree(e);
AttachedToLogicalTree?.Invoke(this, e);
}
foreach (var child in LogicalChildren.OfType<Control>())
{
child.OnAttachedToLogicalTreeCore(e);
}
}
private void OnDetachedFromLogicalTreeCore(LogicalTreeAttachmentEventArgs e)
{
if (_isAttachedToLogicalTree)
{
if (Name != null)
{
_nameScope?.Unregister(Name);
}
_isAttachedToLogicalTree = false;
_styleDetach.OnNext(this);
OnDetachedFromLogicalTree(e);
DetachedFromLogicalTree?.Invoke(this, e);
foreach (var child in LogicalChildren.OfType<Control>())
{
child.OnDetachedFromLogicalTreeCore(e);
}
#if DEBUG
if (((INotifyCollectionChangedDebug)_classes).GetCollectionChangedSubscribers()?.Length > 0)
{
Logger.Warning(
LogArea.Control,
this,
"{Type} detached from logical tree but still has class listeners",
this.GetType());
}
#endif
}
}
private void OnDataContextChangedCore(AvaloniaPropertyChangedEventArgs e)
{
OnDataContextChanged(EventArgs.Empty);
}
private void LogicalChildrenCollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
{
switch (e.Action)
{
case NotifyCollectionChangedAction.Add:
SetLogicalParent(e.NewItems.Cast<ILogical>());
break;
case NotifyCollectionChangedAction.Remove:
ClearLogicalParent(e.OldItems.Cast<ILogical>());
break;
case NotifyCollectionChangedAction.Replace:
ClearLogicalParent(e.OldItems.Cast<ILogical>());
SetLogicalParent(e.NewItems.Cast<ILogical>());
break;
case NotifyCollectionChangedAction.Reset:
throw new NotSupportedException("Reset should not be signalled on LogicalChildren collection");
}
}
private void SetLogicalParent(IEnumerable<ILogical> children)
{
foreach (var i in children)
{
if (i.LogicalParent == null)
{
((ISetLogicalParent)i).SetParent(this);
}
}
}
private void ClearLogicalParent(IEnumerable<ILogical> children)
{
foreach (var i in children)
{
if (i.LogicalParent == this)
{
((ISetLogicalParent)i).SetParent(null);
}
}
}
private void ThisResourcesChanged(object sender, ResourcesChangedEventArgs e)
{
((ILogical)this).NotifyResourcesChanged(e);
}
}
}

15
src/Avalonia.Controls/ControlExtensions.cs

@ -66,21 +66,6 @@ namespace Avalonia.Controls
return nameScope.Find<T>(name);
}
/// <summary>
/// Finds the name scope for a control by searching up the logical tree.
/// </summary>
/// <param name="control">The control.</param>
/// <returns>The control's name scope, or null if not found.</returns>
public static INameScope FindNameScope(this IControl control)
{
Contract.Requires<ArgumentNullException>(control != null);
return control.GetSelfAndLogicalAncestors()
.OfType<Control>()
.Select(x => (x as INameScope) ?? NameScope.GetNameScope(x))
.FirstOrDefault(x => x != null);
}
/// <summary>
/// Adds or removes a pseudoclass depending on a boolean value.
/// </summary>

80
src/Avalonia.Controls/DropDown.cs

@ -6,7 +6,6 @@ using Avalonia.Controls.Generators;
using Avalonia.Controls.Primitives;
using Avalonia.Controls.Shapes;
using Avalonia.Input;
using Avalonia.Layout;
using Avalonia.LogicalTree;
using Avalonia.Media;
using Avalonia.VisualTree;
@ -51,6 +50,7 @@ namespace Avalonia.Controls
{
FocusableProperty.OverrideDefaultValue<DropDown>(true);
SelectedItemProperty.Changed.AddClassHandler<DropDown>(x => x.SelectedItemChanged);
KeyDownEvent.AddClassHandler<DropDown>(x => x.OnKeyDown, Interactivity.RoutingStrategies.Tunnel);
}
/// <summary>
@ -96,54 +96,46 @@ namespace Avalonia.Controls
this.UpdateSelectionBoxItem(this.SelectedItem);
}
protected override void OnGotFocus(GotFocusEventArgs e)
{
base.OnGotFocus(e);
if (!e.Handled && e.NavigationMethod == NavigationMethod.Directional)
{
e.Handled = UpdateSelectionFromEventSource(e.Source);
}
}
/// <inheritdoc/>
protected override void OnKeyDown(KeyEventArgs e)
{
base.OnKeyDown(e);
if (!e.Handled)
if (e.Handled)
return;
if (e.Key == Key.F4 ||
((e.Key == Key.Down || e.Key == Key.Up) && ((e.Modifiers & InputModifiers.Alt) != 0)))
{
if (e.Key == Key.F4 ||
((e.Key == Key.Down || e.Key == Key.Up) && ((e.Modifiers & InputModifiers.Alt) != 0)))
IsDropDownOpen = !IsDropDownOpen;
e.Handled = true;
}
else if (IsDropDownOpen && e.Key == Key.Escape)
{
IsDropDownOpen = false;
e.Handled = true;
}
else if (IsDropDownOpen && e.Key == Key.Enter)
{
SelectFocusedItem();
IsDropDownOpen = false;
e.Handled = true;
}
else if (!IsDropDownOpen)
{
if (e.Key == Key.Down)
{
IsDropDownOpen = !IsDropDownOpen;
if (++SelectedIndex >= ItemCount)
SelectedIndex = 0;
e.Handled = true;
}
else if (IsDropDownOpen && (e.Key == Key.Escape || e.Key == Key.Enter))
else if (e.Key == Key.Up)
{
IsDropDownOpen = false;
e.Handled = true;
}
if (--SelectedIndex < 0)
SelectedIndex = ItemCount - 1;
if (!IsDropDownOpen)
{
if (e.Key == Key.Down)
{
if (SelectedIndex == -1)
SelectedIndex = 0;
if (++SelectedIndex >= ItemCount)
SelectedIndex = 0;
e.Handled = true;
}
else if (e.Key == Key.Up)
{
if (--SelectedIndex < 0)
SelectedIndex = ItemCount - 1;
e.Handled = true;
}
e.Handled = true;
}
}
}
@ -230,5 +222,17 @@ namespace Avalonia.Controls
SelectionBoxItem = item;
}
}
private void SelectFocusedItem()
{
foreach (ItemContainerInfo dropdownItem in ItemContainerGenerator.Containers)
{
if (dropdownItem.ContainerControl.IsFocused)
{
SelectedIndex = dropdownItem.Index;
break;
}
}
}
}
}

35
src/Avalonia.Controls/DropDownItem.cs

@ -1,12 +1,45 @@
// Copyright (c) The Avalonia Project. All rights reserved.
// Licensed under the MIT license. See licence.md file in the project root for full license information.
using System;
namespace Avalonia.Controls
{
/// <summary>
/// A selectable item in a <see cref="DropDown"/>.
/// </summary>
public class DropDownItem : ListBoxItem
public class DropDownItem : ContentControl, ISelectable
{
/// <summary>
/// Defines the <see cref="IsSelected"/> property.
/// </summary>
public static readonly StyledProperty<bool> IsSelectedProperty =
AvaloniaProperty.Register<DropDownItem, bool>(nameof(IsSelected));
/// <summary>
/// Initializes static members of the <see cref="DropDownItem"/> class.
/// </summary>
static DropDownItem()
{
FocusableProperty.OverrideDefaultValue<DropDownItem>(true);
IsFocusedProperty.Changed.Subscribe(x =>
{
var sender = x.Sender as IControl;
if (sender != null)
{
((IPseudoClasses)sender.Classes).Set(":selected", (bool)x.NewValue);
}
});
}
/// <summary>
/// Gets or sets the selection state of the item.
/// </summary>
public bool IsSelected
{
get { return GetValue(IsSelectedProperty); }
set { SetValue(IsSelectedProperty, value); }
}
}
}

6
src/Avalonia.Controls/HotkeyManager.cs

@ -55,13 +55,13 @@ namespace Avalonia.Controls
public void Init()
{
_hotkeySub = _control.GetObservable(HotKeyProperty).Subscribe(OnHotkeyChanged);
_parentSub = AncestorFinder.Create(_control, typeof (TopLevel)).Subscribe(OnParentChanged);
_parentSub = AncestorFinder.Create<TopLevel>(_control).Subscribe(OnParentChanged);
}
private void OnParentChanged(IControl control)
private void OnParentChanged(TopLevel control)
{
Unregister();
_root = (TopLevel) control;
_root = control;
Register();
}

30
src/Avalonia.Controls/IControl.cs

@ -16,37 +16,11 @@ namespace Avalonia.Controls
/// </summary>
public interface IControl : IVisual,
IDataTemplateHost,
ILogical,
ILayoutable,
IInputElement,
INamed,
IResourceNode,
IStyleable,
IStyleHost
IStyledElement
{
/// <summary>
/// Occurs when the control has finished initialization.
/// </summary>
event EventHandler Initialized;
/// <summary>
/// Gets or sets the control's styling classes.
/// </summary>
new Classes Classes { get; set; }
/// <summary>
/// Gets or sets the control's data context.
/// </summary>
object DataContext { get; set; }
/// <summary>
/// Gets a value that indicates whether the element has finished initialization.
/// </summary>
bool IsInitialized { get; }
/// <summary>
/// Gets the control's logical parent.
/// </summary>
IControl Parent { get; }
new IControl Parent { get; }
}
}

24
src/Avalonia.Controls/Utils/AncestorFinder.cs

@ -3,6 +3,7 @@ using System.Collections.Generic;
using System.Linq;
using System.Reactive;
using System.Reactive.Disposables;
using System.Reactive.Linq;
using System.Reactive.Subjects;
using System.Reflection;
using System.Text;
@ -14,15 +15,15 @@ namespace Avalonia.Controls.Utils
{
class FinderNode : IDisposable
{
private readonly IControl _control;
private readonly IStyledElement _control;
private readonly TypeInfo _ancestorType;
public IObservable<IControl> Observable => _subject;
private readonly Subject<IControl> _subject = new Subject<IControl>();
public IObservable<IStyledElement> Observable => _subject;
private readonly Subject<IStyledElement> _subject = new Subject<IStyledElement>();
private FinderNode _child;
private IDisposable _disposable;
public FinderNode(IControl control, TypeInfo ancestorType)
public FinderNode(IStyledElement control, TypeInfo ancestorType)
{
_control = control;
_ancestorType = ancestorType;
@ -33,7 +34,7 @@ namespace Avalonia.Controls.Utils
_disposable = _control.GetObservable(Control.ParentProperty).Subscribe(OnValueChanged);
}
private void OnValueChanged(IControl next)
private void OnValueChanged(IStyledElement next)
{
if (next == null || _ancestorType.IsAssignableFrom(next.GetType().GetTypeInfo()))
_subject.OnNext(next);
@ -46,7 +47,7 @@ namespace Avalonia.Controls.Utils
}
}
private void OnChildValueChanged(IControl control) => _subject.OnNext(control);
private void OnChildValueChanged(IStyledElement control) => _subject.OnNext(control);
public void Dispose()
@ -55,10 +56,15 @@ namespace Avalonia.Controls.Utils
}
}
public static IObservable<T> Create<T>(IStyledElement control)
where T : IStyledElement
{
return Create(control, typeof(T)).Cast<T>();
}
public static IObservable<IControl> Create(IControl control, Type ancestorType)
public static IObservable<IStyledElement> Create(IStyledElement control, Type ancestorType)
{
return new AnonymousObservable<IControl>(observer =>
return new AnonymousObservable<IStyledElement>(observer =>
{
var finder = new FinderNode(control, ancestorType.GetTypeInfo());
var subscription = finder.Observable.Subscribe(observer);
@ -70,8 +76,6 @@ namespace Avalonia.Controls.Utils
finder.Dispose();
});
});
}
}
}

129
src/Avalonia.HtmlRenderer/Adapters/AvaloniaAdapter.cs

@ -1,129 +0,0 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Text;
using System.Threading.Tasks;
using Avalonia;
using Avalonia.Input.Platform;
using Avalonia.Media;
using Avalonia.Media.Imaging;
using TheArtOfDev.HtmlRenderer.Adapters;
using TheArtOfDev.HtmlRenderer.Adapters.Entities;
using TheArtOfDev.HtmlRenderer.Avalonia.Utilities;
namespace TheArtOfDev.HtmlRenderer.Avalonia.Adapters
{
class AvaloniaAdapter : RAdapter
{
public static AvaloniaAdapter Instance { get; } = new AvaloniaAdapter();
/// <summary>
/// List of valid predefined color names in lower-case
/// </summary>
private static readonly Dictionary<string, Color> ColorNameDic = new Dictionary<string, Color>();
static AvaloniaAdapter()
{
foreach (var colorProp in typeof(Colors).GetRuntimeProperties()
.Where(p=>p.PropertyType == typeof(Color)))
{
ColorNameDic[colorProp.Name.ToLower()] = (Color)colorProp.GetValue(null);
}
}
protected override RColor GetColorInt(string colorName)
{
Color c;
if(!ColorNameDic.TryGetValue(colorName.ToLower(), out c))
return RColor.Empty;
return Util.Convert(c);
}
protected override RPen CreatePen(RColor color)
{
return new PenAdapter(GetSolidColorBrush(color));
}
/// <summary>
/// Get solid color brush for the given color.
/// </summary>
private static IBrush GetSolidColorBrush(RColor color)
{
IBrush solidBrush;
if (color == RColor.White)
solidBrush = Brushes.White;
else if (color == RColor.Black)
solidBrush = Brushes.Black;
else if (color.A < 1)
solidBrush = Brushes.Transparent;
else
solidBrush = new SolidColorBrush(Util.Convert(color));
return solidBrush;
}
protected override RBrush CreateSolidBrush(RColor color)
{
return new BrushAdapter(GetSolidColorBrush(color));
}
protected override RBrush CreateLinearGradientBrush(RRect rect, RColor color1, RColor color2, double angle)
{
var startColor = angle <= 180 ? Util.Convert(color1) : Util.Convert(color2);
var endColor = angle <= 180 ? Util.Convert(color2) : Util.Convert(color1);
angle = angle <= 180 ? angle : angle - 180;
double x = angle < 135 ? Math.Max((angle - 45) / 90, 0) : 1;
double y = angle <= 45 ? Math.Max(0.5 - angle / 90, 0) : angle > 135 ? Math.Abs(1.5 - angle / 90) : 0;
return new BrushAdapter(new LinearGradientBrush
{
StartPoint = new RelativePoint(x, y, RelativeUnit.Relative),
EndPoint = new RelativePoint(1 - x, 1 - y, RelativeUnit.Relative),
GradientStops = new[]
{
new GradientStop(startColor, 0),
new GradientStop(endColor, 1)
}
});
}
protected override RImage ConvertImageInt(object image)
{
return image != null ? new ImageAdapter((Bitmap)image) : null;
}
protected override RImage ImageFromStreamInt(Stream memoryStream)
{
//TODO: Implement bitmap loader
return null;
}
protected override RFont CreateFontInt(string family, double size, RFontStyle style)
{
return new FontAdapter(family, size, style);
}
protected override RFont CreateFontInt(RFontFamily family, double size, RFontStyle style)
{
return new FontAdapter(family.Name, size, style);
}
protected override void SetToClipboardInt(string html, string plainText)
{
SetToClipboardInt(plainText);
}
protected override void SetToClipboardInt(string text)
{
AvaloniaLocator.Current.GetService<IClipboard>().SetTextAsync(text);
}
protected override void SetToClipboardInt(RImage image)
{
//Do not crash, just ignore
//TODO: implement image clipboard support
}
}
}

47
src/Avalonia.HtmlRenderer/Adapters/BrushAdapter.cs

@ -1,47 +0,0 @@
// "Therefore those skilled at the unorthodox
// are infinite as heaven and earth,
// inexhaustible as the great rivers.
// When they come to an end,
// they begin again,
// like the days and months;
// they die and are reborn,
// like the four seasons."
//
// - Sun Tsu,
// "The Art of War"
using Avalonia.Media;
using TheArtOfDev.HtmlRenderer.Adapters;
namespace TheArtOfDev.HtmlRenderer.Avalonia.Adapters
{
/// <summary>
/// Adapter for Avalonia brushes.
/// </summary>
internal sealed class BrushAdapter : RBrush
{
/// <summary>
/// The actual Avalonia brush instance.
/// </summary>
private readonly IBrush _brush;
/// <summary>
/// Init.
/// </summary>
public BrushAdapter(IBrush brush)
{
_brush = brush;
}
/// <summary>
/// The actual Avalonia brush instance.
/// </summary>
public IBrush Brush
{
get { return _brush; }
}
public override void Dispose()
{ }
}
}

51
src/Avalonia.HtmlRenderer/Adapters/ContextMenuAdapter.cs

@ -1,51 +0,0 @@
// "Therefore those skilled at the unorthodox
// are infinite as heaven and earth,
// inexhaustible as the great rivers.
// When they come to an end,
// they begin again,
// like the days and months;
// they die and are reborn,
// like the four seasons."
//
// - Sun Tsu,
// "The Art of War"
using System;
using TheArtOfDev.HtmlRenderer.Adapters;
using TheArtOfDev.HtmlRenderer.Adapters.Entities;
namespace TheArtOfDev.HtmlRenderer.Avalonia.Adapters
{
/// <summary>
/// Adapter for Avalonia context menu for core.
/// </summary>
internal sealed class NullContextMenuAdapter : RContextMenu
{
//TODO: actually implement context menu
private int _itemCount;
public override int ItemsCount => _itemCount;
public override void AddDivider()
{
}
public override void AddItem(string text, bool enabled, EventHandler onClick)
{
_itemCount++;
}
public override void RemoveLastDivider()
{
_itemCount++;
}
public override void Show(RControl parent, RPoint location)
{
}
public override void Dispose()
{
}
}
}

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

@ -1,111 +0,0 @@
// "Therefore those skilled at the unorthodox
// are infinite as heaven and earth,
// inexhaustible as the great rivers.
// When they come to an end,
// they begin again,
// like the days and months;
// they die and are reborn,
// like the four seasons."
//
// - 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;
using TheArtOfDev.HtmlRenderer.Avalonia.Utilities;
// ReSharper disable ConvertPropertyToExpressionBody
namespace TheArtOfDev.HtmlRenderer.Avalonia.Adapters
{
/// <summary>
/// Adapter for Avalonia Control for core.
/// </summary>
internal sealed class ControlAdapter : RControl
{
/// <summary>
/// the underline Avalonia control.
/// </summary>
private readonly Control _control;
/// <summary>
/// Init.
/// </summary>
public ControlAdapter(Control control)
: base(AvaloniaAdapter.Instance)
{
ArgChecker.AssertArgNotNull(control, "control");
_control = control;
}
/// <summary>
/// Get the underline Avalonia control
/// </summary>
public Control Control
{
get { return _control; }
}
public override RPoint MouseLocation
{
get
{
var pos = (_control.GetVisualRoot() as IInputRoot)?.MouseDevice?.Position ?? default(Point);
return Util.Convert(pos);
}
}
private bool _leftMouseButton;
public override bool LeftMouseButton => (_control as HtmlControl)?.LeftMouseButton ?? false;
public override bool RightMouseButton
{
get
{
return false;
//TODO: Implement right mouse click
//return Mouse.RightButton == MouseButtonState.Pressed;
}
}
public override void SetCursorDefault()
{
_control.Cursor = new Cursor(StandardCursorType.Arrow);
}
public override void SetCursorHand()
{
_control.Cursor = new Cursor(StandardCursorType.Hand);
}
public override void SetCursorIBeam()
{
_control.Cursor = new Cursor(StandardCursorType.Ibeam);
}
public override void DoDragDropCopy(object dragDropData)
{
//TODO: Implement DragDropCopy
//DragDrop.DoDragDrop(_control, dragDropData, DragDropEffects.Copy);
}
public override void MeasureString(string str, RFont font, double maxWidth, out int charFit, out double charFitWidth)
{
using (var g = new GraphicsAdapter())
{
g.MeasureString(str, font, maxWidth, out charFit, out charFitWidth);
}
}
public override void Invalidate()
{
_control.InvalidateVisual();
}
}
}

106
src/Avalonia.HtmlRenderer/Adapters/FontAdapter.cs

@ -1,106 +0,0 @@
// "Therefore those skilled at the unorthodox
// are infinite as heaven and earth,
// inexhaustible as the great rivers.
// When they come to an end,
// they begin again,
// like the days and months;
// they die and are reborn,
// like the four seasons."
//
// - Sun Tsu,
// "The Art of War"
using Avalonia.Media;
using TheArtOfDev.HtmlRenderer.Adapters;
using TheArtOfDev.HtmlRenderer.Adapters.Entities;
namespace TheArtOfDev.HtmlRenderer.Avalonia.Adapters
{
/// <summary>
/// Adapter for Avalonia Font.
/// </summary>
internal sealed class FontAdapter : RFont
{
public RFontStyle Style { get; }
#region Fields and Consts
/// <summary>
/// the size of the font
/// </summary>
private readonly double _size;
/// <summary>
/// the vertical offset of the font underline location from the top of the font.
/// </summary>
private readonly double _underlineOffset = -1;
/// <summary>
/// Cached font height.
/// </summary>
private readonly double _height = -1;
/// <summary>
/// Cached font whitespace width.
/// </summary>
private double _whitespaceWidth = -1;
#endregion
/// <summary>
/// Init.
/// </summary>
public FontAdapter(string fontFamily, double size, RFontStyle style)
{
Style = style;
Name = fontFamily;
_size = size;
//TODO: Somehow get proper line spacing and underlinePosition
var lineSpacing = 1;
var underlinePosition = 0;
_height = 96d / 72d * _size * lineSpacing;
_underlineOffset = 96d / 72d * _size * (lineSpacing + underlinePosition);
}
public string Name { get; set; }
public override double Size
{
get { return _size; }
}
public override double UnderlineOffset
{
get { return _underlineOffset; }
}
public override double Height
{
get { return _height; }
}
public override double LeftPadding
{
get { return _height / 6f; }
}
public override double GetWhitespaceWidth(RGraphics graphics)
{
if (_whitespaceWidth < 0)
{
_whitespaceWidth = graphics.MeasureString(" ", this).Width;
}
return _whitespaceWidth;
}
public FontStyle FontStyle => Style.HasFlag(RFontStyle.Italic) ? FontStyle.Italic : FontStyle.Normal;
public FontWeight Weight => Style.HasFlag(RFontStyle.Bold) ? FontWeight.Bold : FontWeight.Normal;
}
}

29
src/Avalonia.HtmlRenderer/Adapters/FontFamilyAdapter.cs

@ -1,29 +0,0 @@
// "Therefore those skilled at the unorthodox
// are infinite as heaven and earth,
// inexhaustible as the great rivers.
// When they come to an end,
// they begin again,
// like the days and months;
// they die and are reborn,
// like the four seasons."
//
// - Sun Tsu,
// "The Art of War"
using TheArtOfDev.HtmlRenderer.Adapters;
namespace TheArtOfDev.HtmlRenderer.Avalonia.Adapters
{
/// <summary>
/// Adapter for Avalonia Font family object for core.
/// </summary>
internal sealed class FontFamilyAdapter : RFontFamily
{
public FontFamilyAdapter(string fontFamily)
{
Name = fontFamily;
}
public override string Name { get; }
}
}

299
src/Avalonia.HtmlRenderer/Adapters/GraphicsAdapter.cs

@ -1,299 +0,0 @@
// "Therefore those skilled at the unorthodox
// are infinite as heaven and earth,
// inexhaustible as the great rivers.
// When they come to an end,
// they begin again,
// like the days and months;
// they die and are reborn,
// like the four seasons."
//
// - Sun Tsu,
// "The Art of War"
using System;
using System.Collections.Generic;
using System.Globalization;
using Avalonia;
using Avalonia.Media;
using TheArtOfDev.HtmlRenderer.Adapters;
using TheArtOfDev.HtmlRenderer.Adapters.Entities;
using TheArtOfDev.HtmlRenderer.Core.Utils;
using TheArtOfDev.HtmlRenderer.Avalonia.Utilities;
namespace TheArtOfDev.HtmlRenderer.Avalonia.Adapters
{
/// <summary>
/// Adapter for Avalonia Graphics.
/// </summary>
internal sealed class GraphicsAdapter : RGraphics
{
#region Fields and Consts
/// <summary>
/// The wrapped Avalonia graphics object
/// </summary>
private readonly DrawingContext _g;
/// <summary>
/// if to release the graphics object on dispose
/// </summary>
private readonly bool _releaseGraphics;
#endregion
private readonly Stack<IDisposable> _clipStack = new Stack<IDisposable>();
/// <summary>
/// Init.
/// </summary>
/// <param name="g">the Avalonia graphics object to use</param>
/// <param name="initialClip">the initial clip of the graphics</param>
/// <param name="releaseGraphics">optional: if to release the graphics object on dispose (default - false)</param>
public GraphicsAdapter(DrawingContext g, RRect initialClip, bool releaseGraphics = false)
: base(AvaloniaAdapter.Instance, initialClip)
{
ArgChecker.AssertArgNotNull(g, "g");
_g = g;
_releaseGraphics = releaseGraphics;
}
/// <summary>
/// Init.
/// </summary>
public GraphicsAdapter()
: base(AvaloniaAdapter.Instance, RRect.Empty)
{
_g = null;
_releaseGraphics = false;
}
public override void PopClip()
{
_clipStack.Pop()?.Dispose();
}
public override void PushClip(RRect rect)
{
_clipStack.Push(_g.PushClip(Util.Convert(rect)));
//_clipStack.Push(rect);
//_g.PushClip(new RectangleGeometry(Utils.Convert(rect)));
}
public override void PushClipExclude(RRect rect)
{
_clipStack.Push(null);
//TODO: Implement exclude rect, see #128
//var geometry = new CombinedGeometry();
//geometry.Geometry1 = new RectangleGeometry(Utils.Convert(_clipStack.Peek()));
//geometry.Geometry2 = new RectangleGeometry(Utils.Convert(rect));
//geometry.GeometryCombineMode = GeometryCombineMode.Exclude;
//_clipStack.Push(_clipStack.Peek());
//_g.PushClip(geometry);
}
public override Object SetAntiAliasSmoothingMode()
{
return null;
}
public override void ReturnPreviousSmoothingMode(Object prevMode)
{ }
public override RSize MeasureString(string str, RFont font)
{
var text = GetText(str, font);
var measure = text.Measure();
return new RSize(measure.Width, measure.Height);
}
FormattedText GetText(string str, RFont font)
{
var f = ((FontAdapter)font);
return new FormattedText
{
Text = str,
Typeface = new Typeface(f.Name, font.Size, f.FontStyle, f.Weight),
};
}
public override void MeasureString(string str, RFont font, double maxWidth, out int charFit, out double charFitWidth)
{
var text = GetText(str, font);
var fullLength = text.Measure().Width;
if (fullLength < maxWidth)
{
charFitWidth = fullLength;
charFit = str.Length;
return;
}
int lastLen = 0;
double lastMeasure = 0;
BinarySearch(len =>
{
text = GetText(str.Substring(0, len), font);
var size = text.Measure().Width;
lastMeasure = size;
lastLen = len;
if (size <= maxWidth)
return -1;
return 1;
}, 0, str.Length);
if (lastMeasure > maxWidth)
{
lastLen--;
lastMeasure = GetText(str.Substring(0, lastLen), font).Measure().Width;
}
charFit = lastLen;
charFitWidth = lastMeasure;
}
private static int BinarySearch(Func<int, int> condition, int start, int end)
{
do
{
int ind = start + (end - start)/2;
int res = condition(ind);
if (res == 0)
return ind;
else if (res > 0)
{
if (start != ind)
start = ind;
else
start = ind + 1;
}
else
end = ind;
} while (end > start);
return -1;
}
public override void DrawString(string str, RFont font, RColor color, RPoint point, RSize size, bool rtl)
{
var text = GetText(str, font);
text.Constraint = Util.Convert(size);
_g.DrawText(new SolidColorBrush(Util.Convert(color)), Util.Convert(point), text);
}
public override RBrush GetTextureBrush(RImage image, RRect dstRect, RPoint translateTransformLocation)
{
//TODO: Implement texture brush
return AvaloniaAdapter.Instance.GetSolidBrush(Util.Convert(Colors.Magenta));
//var brush = new ImageBrush(((ImageAdapter)image).Image);
//brush.Stretch = Stretch.None;
//brush.TileMode = TileMode.Tile;
//brush.Viewport = Utils.Convert(dstRect);
//brush.ViewportUnits = BrushMappingMode.Absolute;
//brush.Transform = new TranslateTransform(translateTransformLocation.X, translateTransformLocation.Y);
//brush.Freeze();
//return new BrushAdapter(brush);
}
public override RGraphicsPath GetGraphicsPath()
{
return new GraphicsPathAdapter();
}
public override void Dispose()
{
while (_clipStack.Count != 0)
PopClip();
if (_releaseGraphics)
_g.Dispose();
}
#region Delegate graphics methods
public override void DrawLine(RPen pen, double x1, double y1, double x2, double y2)
{
x1 = (int)x1;
x2 = (int)x2;
y1 = (int)y1;
y2 = (int)y2;
var adj = pen.Width;
if (Math.Abs(x1 - x2) < .1 && Math.Abs(adj % 2 - 1) < .1)
{
x1 += .5;
x2 += .5;
}
if (Math.Abs(y1 - y2) < .1 && Math.Abs(adj % 2 - 1) < .1)
{
y1 += .5;
y2 += .5;
}
_g.DrawLine(((PenAdapter)pen).CreatePen(), new Point(x1, y1), new Point(x2, y2));
}
public override void DrawRectangle(RPen pen, double x, double y, double width, double height)
{
var adj = pen.Width;
if (Math.Abs(adj % 2 - 1) < .1)
{
x += .5;
y += .5;
}
_g.DrawRectangle(((PenAdapter) pen).CreatePen(), new Rect(x, y, width, height));
}
public override void DrawRectangle(RBrush brush, double x, double y, double width, double height)
{
_g.FillRectangle(((BrushAdapter) brush).Brush, new Rect(x, y, width, height));
}
public override void DrawImage(RImage image, RRect destRect, RRect srcRect)
{
_g.DrawImage(((ImageAdapter) image).Image, 1, Util.Convert(srcRect), Util.Convert(destRect));
}
public override void DrawImage(RImage image, RRect destRect)
{
_g.DrawImage(((ImageAdapter) image).Image, 1, new Rect(0, 0, image.Width, image.Height),
Util.Convert(destRect));
}
public override void DrawPath(RPen pen, RGraphicsPath path)
{
_g.DrawGeometry(null, ((PenAdapter)pen).CreatePen(), ((GraphicsPathAdapter)path).GetClosedGeometry());
}
public override void DrawPath(RBrush brush, RGraphicsPath path)
{
_g.DrawGeometry(((BrushAdapter)brush).Brush, null, ((GraphicsPathAdapter)path).GetClosedGeometry());
}
public override void DrawPolygon(RBrush brush, RPoint[] points)
{
if (points != null && points.Length > 0)
{
var g = new StreamGeometry();
using (var context = g.Open())
{
context.BeginFigure(Util.Convert(points[0]), true);
for (int i = 1; i < points.Length; i++)
context.LineTo(Util.Convert(points[i]));
context.EndFigure(false);
}
_g.DrawGeometry(((BrushAdapter)brush).Brush, null, g);
}
}
#endregion
}
}

67
src/Avalonia.HtmlRenderer/Adapters/GraphicsPathAdapter.cs

@ -1,67 +0,0 @@
// "Therefore those skilled at the unorthodox
// are infinite as heaven and earth,
// inexhaustible as the great rivers.
// When they come to an end,
// they begin again,
// like the days and months;
// they die and are reborn,
// like the four seasons."
//
// - Sun Tsu,
// "The Art of War"
using Avalonia;
using Avalonia.Media;
using TheArtOfDev.HtmlRenderer.Adapters;
namespace TheArtOfDev.HtmlRenderer.Avalonia.Adapters
{
/// <summary>
/// Adapter for Avalonia graphics path object for core.
/// </summary>
internal sealed class GraphicsPathAdapter : RGraphicsPath
{
/// <summary>
/// The actual Avalonia graphics geometry instance.
/// </summary>
private readonly StreamGeometry _geometry = new StreamGeometry();
/// <summary>
/// The context used in Avalonia geometry to render path
/// </summary>
private readonly StreamGeometryContext _geometryContext;
public GraphicsPathAdapter()
{
_geometryContext = _geometry.Open();
}
public override void Start(double x, double y)
{
_geometryContext.BeginFigure(new Point(x, y), true);
}
public override void LineTo(double x, double y)
{
_geometryContext.LineTo(new Point(x, y));
}
public override void ArcTo(double x, double y, double size, Corner corner)
{
_geometryContext.ArcTo(new Point(x, y), new Size(size, size), 0, false, SweepDirection.Clockwise);
}
/// <summary>
/// Close the geometry to so no more path adding is allowed and return the instance so it can be rendered.
/// </summary>
public StreamGeometry GetClosedGeometry()
{
_geometryContext.EndFigure(true);
_geometryContext.Dispose();
return _geometry;
}
public override void Dispose()
{ }
}
}

52
src/Avalonia.HtmlRenderer/Adapters/ImageAdapter.cs

@ -1,52 +0,0 @@
// "Therefore those skilled at the unorthodox
// are infinite as heaven and earth,
// inexhaustible as the great rivers.
// When they come to an end,
// they begin again,
// like the days and months;
// they die and are reborn,
// like the four seasons."
//
// - Sun Tsu,
// "The Art of War"
using Avalonia.Media.Imaging;
using TheArtOfDev.HtmlRenderer.Adapters;
namespace TheArtOfDev.HtmlRenderer.Avalonia.Adapters
{
/// <summary>
/// Adapter for Avalonia Image object for core.
/// </summary>
internal sealed class ImageAdapter : RImage
{
/// <summary>
/// the underline Avalonia image.
/// </summary>
private readonly Bitmap _image;
/// <summary>
/// Init.
/// </summary>
public ImageAdapter(Bitmap image)
{
_image = image;
}
/// <summary>
/// the underline Avalonia image.
/// </summary>
public Bitmap Image => _image;
public override double Width => _image.PixelWidth;
public override double Height => _image.PixelHeight;
public override void Dispose()
{
//TODO: Implement image disposal
/*if (_image.StreamSource != null)
_image.StreamSource.Dispose();*/
}
}
}

79
src/Avalonia.HtmlRenderer/Adapters/PenAdapter.cs

@ -1,79 +0,0 @@
// "Therefore those skilled at the unorthodox
// are infinite as heaven and earth,
// inexhaustible as the great rivers.
// When they come to an end,
// they begin again,
// like the days and months;
// they die and are reborn,
// like the four seasons."
//
// - Sun Tsu,
// "The Art of War"
using System.Collections.Generic;
using Avalonia.Media;
using TheArtOfDev.HtmlRenderer.Adapters;
using TheArtOfDev.HtmlRenderer.Adapters.Entities;
namespace TheArtOfDev.HtmlRenderer.Avalonia.Adapters
{
/// <summary>
/// Adapter for Avalonia pens objects for core.
/// </summary>
internal sealed class PenAdapter : RPen
{
/// <summary>
/// The actual Avalonia brush instance.
/// </summary>
private readonly IBrush _brush;
/// <summary>
/// the width of the pen
/// </summary>
private double _width;
private DashStyle _dashStyle;
/// <summary>
/// the dash style of the pen
/// </summary>
//private DashStyle _dashStyle = DashStyles.Solid;
/// <summary>
/// Init.
/// </summary>
public PenAdapter(IBrush brush)
{
_brush = brush;
}
public override double Width
{
get { return _width; }
set { _width = value; }
}
public override RDashStyle DashStyle
{
set { DashStyles.TryGetValue(value, out _dashStyle); }
}
private static readonly Dictionary<RDashStyle, DashStyle> DashStyles = new Dictionary<RDashStyle, DashStyle>
{
{RDashStyle.Solid,null },
{RDashStyle.Dash, global::Avalonia.Media.DashStyle.Dash },
{RDashStyle.DashDot, global::Avalonia.Media.DashStyle.DashDot },
{RDashStyle.DashDotDot, global::Avalonia.Media.DashStyle.DashDotDot },
{RDashStyle.Dot, global::Avalonia.Media.DashStyle.Dot }
};
/// <summary>
/// Create the actual Avalonia pen instance.
/// </summary>
public Pen CreatePen()
{
var pen = new Pen(_brush, _width, _dashStyle);
return pen;
}
}
}

24
src/Avalonia.HtmlRenderer/Avalonia.HtmlRenderer.csproj

@ -1,24 +0,0 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netstandard2.0</TargetFramework>
<EnableDefaultCompileItems>False</EnableDefaultCompileItems>
<EnableDefaultItems>False</EnableDefaultItems>
<NoWarn>CS0436</NoWarn>
</PropertyGroup>
<ItemGroup>
<Content Include="external\Source\HtmlRenderer\Core\Utils\ImageError.png" />
<Content Include="external\Source\HtmlRenderer\Core\Utils\ImageLoad.png" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\Avalonia.Animation\Avalonia.Animation.csproj" />
<ProjectReference Include="..\Avalonia.Base\Avalonia.Base.csproj" />
<ProjectReference Include="..\Avalonia.Controls\Avalonia.Controls.csproj" />
<ProjectReference Include="..\Avalonia.Input\Avalonia.Input.csproj" />
<ProjectReference Include="..\Avalonia.Interactivity\Avalonia.Interactivity.csproj" />
<ProjectReference Include="..\Avalonia.Layout\Avalonia.Layout.csproj" />
<ProjectReference Include="..\Avalonia.Visuals\Avalonia.Visuals.csproj" />
<ProjectReference Include="..\Avalonia.Styling\Avalonia.Styling.csproj" />
</ItemGroup>
<Import Project="..\..\build\Rx.props" />
</Project>

2
src/Avalonia.HtmlRenderer/Avalonia.HtmlRenderer.csproj.DotSettings

@ -1,2 +0,0 @@
<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/CSharpLanguageProject/LanguageLevel/@EntryValue">CSharp60</s:String></wpf:ResourceDictionary>

16
src/Avalonia.HtmlRenderer/Compat/Api.cs

@ -1,16 +0,0 @@
using System;
using System.Collections.Generic;
using System.Text;
namespace System.Net
{
class WebException : Exception
{
public object Response { get; set; }
}
class HttpWebResponse
{
public HttpStatusCode StatusCode { get; set; }
}
}

28
src/Avalonia.HtmlRenderer/Compat/Attributes.cs

@ -1,28 +0,0 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
internal class CategoryAttribute : Attribute
{
public CategoryAttribute(string s)
{
}
}
internal class DescriptionAttribute : Attribute
{
public DescriptionAttribute(string s)
{
}
}
internal class BrowsableAttribute : Attribute
{
public BrowsableAttribute(bool b)
{
}
}

22
src/Avalonia.HtmlRenderer/Compat/ThreadPool.cs

@ -1,22 +0,0 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using TheArtOfDev.HtmlRenderer.Core.Handlers;
namespace System.Threading
{
class ThreadPool
{
public static void QueueUserWorkItem(Action<object> cb, object state)
{
Task.Factory.StartNew(() => cb(state));
}
public static void QueueUserWorkItem(Action<object> cb)
{
Task.Factory.StartNew(() => cb(null));
}
}
}

471
src/Avalonia.HtmlRenderer/HtmlContainer.cs

@ -1,471 +0,0 @@
// "Therefore those skilled at the unorthodox
// are infinite as heaven and earth,
// inexhaustible as the great rivers.
// When they come to an end,
// they begin again,
// like the days and months;
// they die and are reborn,
// like the four seasons."
//
// - Sun Tsu,
// "The Art of War"
using System;
using System.Collections.Generic;
using Avalonia;
using Avalonia.Controls;
using Avalonia.Controls.Html;
using Avalonia.Input;
using Avalonia.Media;
using TheArtOfDev.HtmlRenderer.Adapters.Entities;
using TheArtOfDev.HtmlRenderer.Core;
using TheArtOfDev.HtmlRenderer.Core.Entities;
using TheArtOfDev.HtmlRenderer.Core.Parse;
using TheArtOfDev.HtmlRenderer.Core.Utils;
using TheArtOfDev.HtmlRenderer.Avalonia.Adapters;
using TheArtOfDev.HtmlRenderer.Avalonia.Utilities;
namespace TheArtOfDev.HtmlRenderer.Avalonia
{
/// <summary>
/// Low level handling of Html Renderer logic, this class is used by <see cref="HtmlParser"/>,
/// <see cref="HtmlLabel"/>, <see cref="HtmlToolTip"/> and <see cref="HtmlRender"/>.<br/>
/// </summary>
/// <seealso cref="HtmlContainerInt"/>
public sealed class HtmlContainer : IDisposable
{
#region Fields and Consts
/// <summary>
/// The internal core html container
/// </summary>
private readonly HtmlContainerInt _htmlContainerInt;
#endregion
/// <summary>
/// Init.
/// </summary>
public HtmlContainer()
{
_htmlContainerInt = new HtmlContainerInt(AvaloniaAdapter.Instance);
}
/// <summary>
/// Raised when the set html document has been fully loaded.<br/>
/// Allows manipulation of the html dom, scroll position, etc.
/// </summary>
public event EventHandler LoadComplete
{
add { _htmlContainerInt.LoadComplete += value; }
remove { _htmlContainerInt.LoadComplete -= value; }
}
/// <summary>
/// Raised when the user clicks on a link in the html.<br/>
/// Allows canceling the execution of the link.
/// </summary>
public event EventHandler<HtmlLinkClickedEventArgs> LinkClicked
{
add { _htmlContainerInt.LinkClicked += value; }
remove { _htmlContainerInt.LinkClicked -= value; }
}
/// <summary>
/// Raised when html renderer requires refresh of the control hosting (invalidation and re-layout).
/// </summary>
/// <remarks>
/// There is no guarantee that the event will be raised on the main thread, it can be raised on thread-pool thread.
/// </remarks>
public event EventHandler<HtmlRefreshEventArgs> Refresh
{
add { _htmlContainerInt.Refresh += value; }
remove { _htmlContainerInt.Refresh -= value; }
}
/// <summary>
/// Raised when Html Renderer request scroll to specific location.<br/>
/// This can occur on document anchor click.
/// </summary>
public event EventHandler<HtmlScrollEventArgs> ScrollChange
{
add { _htmlContainerInt.ScrollChange += value; }
remove { _htmlContainerInt.ScrollChange -= value; }
}
/// <summary>
/// Raised when an error occurred during html rendering.<br/>
/// </summary>
/// <remarks>
/// There is no guarantee that the event will be raised on the main thread, it can be raised on thread-pool thread.
/// </remarks>
public event EventHandler<HtmlRenderErrorEventArgs> RenderError
{
add { _htmlContainerInt.RenderError += value; }
remove { _htmlContainerInt.RenderError -= value; }
}
/// <summary>
/// Raised when a stylesheet is about to be loaded by file path or URI by link element.<br/>
/// This event allows to provide the stylesheet manually or provide new source (file or Uri) to load from.<br/>
/// If no alternative data is provided the original source will be used.<br/>
/// </summary>
public event EventHandler<HtmlStylesheetLoadEventArgs> StylesheetLoad
{
add { _htmlContainerInt.StylesheetLoad += value; }
remove { _htmlContainerInt.StylesheetLoad -= value; }
}
/// <summary>
/// Raised when an image is about to be loaded by file path or URI.<br/>
/// This event allows to provide the image manually, if not handled the image will be loaded from file or download from URI.
/// </summary>
public event EventHandler<HtmlImageLoadEventArgs> ImageLoad
{
add { _htmlContainerInt.ImageLoad += value; }
remove { _htmlContainerInt.ImageLoad -= value; }
}
/// <summary>
/// The internal core html container
/// </summary>
internal HtmlContainerInt HtmlContainerInt
{
get { return _htmlContainerInt; }
}
/// <summary>
/// the parsed stylesheet data used for handling the html
/// </summary>
public CssData CssData
{
get { return _htmlContainerInt.CssData; }
}
/// <summary>
/// Gets or sets a value indicating if image asynchronous loading should be avoided (default - false).<br/>
/// True - images are loaded synchronously during html parsing.<br/>
/// False - images are loaded asynchronously to html parsing when downloaded from URL or loaded from disk.<br/>
/// </summary>
/// <remarks>
/// Asynchronously image loading allows to unblock html rendering while image is downloaded or loaded from disk using IO
/// ports to achieve better performance.<br/>
/// Asynchronously image loading should be avoided when the full html content must be available during render, like render to image.
/// </remarks>
public bool AvoidAsyncImagesLoading
{
get { return _htmlContainerInt.AvoidAsyncImagesLoading; }
set { _htmlContainerInt.AvoidAsyncImagesLoading = value; }
}
/// <summary>
/// Gets or sets a value indicating if image loading only when visible should be avoided (default - false).<br/>
/// True - images are loaded as soon as the html is parsed.<br/>
/// False - images that are not visible because of scroll location are not loaded until they are scrolled to.
/// </summary>
/// <remarks>
/// Images late loading improve performance if the page contains image outside the visible scroll area, especially if there is large
/// amount of images, as all image loading is delayed (downloading and loading into memory).<br/>
/// Late image loading may effect the layout and actual size as image without set size will not have actual size until they are loaded
/// resulting in layout change during user scroll.<br/>
/// Early image loading may also effect the layout if image without known size above the current scroll location are loaded as they
/// will push the html elements down.
/// </remarks>
public bool AvoidImagesLateLoading
{
get { return _htmlContainerInt.AvoidImagesLateLoading; }
set { _htmlContainerInt.AvoidImagesLateLoading = value; }
}
/// <summary>
/// Is content selection is enabled for the rendered html (default - true).<br/>
/// If set to 'false' the rendered html will be static only with ability to click on links.
/// </summary>
public bool IsSelectionEnabled
{
get { return _htmlContainerInt.IsSelectionEnabled; }
set { _htmlContainerInt.IsSelectionEnabled = value; }
}
/// <summary>
/// Is the build-in context menu enabled and will be shown on mouse right click (default - true)
/// </summary>
public bool IsContextMenuEnabled
{
get { return _htmlContainerInt.IsContextMenuEnabled; }
set { _htmlContainerInt.IsContextMenuEnabled = value; }
}
/// <summary>
/// The scroll offset of the html.<br/>
/// This will adjust the rendered html by the given offset so the content will be "scrolled".<br/>
/// </summary>
/// <example>
/// Element that is rendered at location (50,100) with offset of (0,200) will not be rendered as it
/// will be at -100 therefore outside the client Rect.
/// </example>
public Point ScrollOffset
{
get { return Util.Convert(_htmlContainerInt.ScrollOffset); }
set { _htmlContainerInt.ScrollOffset = Util.Convert(value); }
}
/// <summary>
/// The top-left most location of the rendered html.<br/>
/// This will offset the top-left corner of the rendered html.
/// </summary>
public Point Location
{
get { return Util.Convert(_htmlContainerInt.Location); }
set { _htmlContainerInt.Location = Util.Convert(value); }
}
/// <summary>
/// The max width and height of the rendered html.<br/>
/// The max width will effect the html layout wrapping lines, resize images and tables where possible.<br/>
/// The max height does NOT effect layout, but will not render outside it (clip).<br/>
/// <see cref="ActualSize"/> can be exceed the max size by layout restrictions (unwrappable line, set image size, etc.).<br/>
/// Set zero for unlimited (width\height separately).<br/>
/// </summary>
public Size MaxSize
{
get { return Util.Convert(_htmlContainerInt.MaxSize); }
set { _htmlContainerInt.MaxSize = Util.Convert(value); }
}
/// <summary>
/// The actual size of the rendered html (after layout)
/// </summary>
public Size ActualSize
{
get { return Util.Convert(_htmlContainerInt.ActualSize); }
internal set { _htmlContainerInt.ActualSize = Util.Convert(value); }
}
/// <summary>
/// Get the currently selected text segment in the html.
/// </summary>
public string SelectedText
{
get { return _htmlContainerInt.SelectedText; }
}
/// <summary>
/// Copy the currently selected html segment with style.
/// </summary>
public string SelectedHtml
{
get { return _htmlContainerInt.SelectedHtml; }
}
/// <summary>
/// Clear the current selection.
/// </summary>
public void ClearSelection()
{
HtmlContainerInt.ClearSelection();
}
/// <summary>
/// Init with optional document and stylesheet.
/// </summary>
/// <param name="htmlSource">the html to init with, init empty if not given</param>
/// <param name="baseCssData">optional: the stylesheet to init with, init default if not given</param>
public void SetHtml(string htmlSource, CssData baseCssData = null)
{
_htmlContainerInt.SetHtml(htmlSource, baseCssData);
}
/// <summary>
/// Clear the content of the HTML container releasing any resources used to render previously existing content.
/// </summary>
public void Clear()
{
_htmlContainerInt.Clear();
}
/// <summary>
/// Get html from the current DOM tree with style if requested.
/// </summary>
/// <param name="styleGen">Optional: controls the way styles are generated when html is generated (default: <see cref="HtmlGenerationStyle.Inline"/>)</param>
/// <returns>generated html</returns>
public string GetHtml(HtmlGenerationStyle styleGen = HtmlGenerationStyle.Inline)
{
return _htmlContainerInt.GetHtml(styleGen);
}
/// <summary>
/// Get attribute value of element at the given x,y location by given key.<br/>
/// If more than one element exist with the attribute at the location the inner most is returned.
/// </summary>
/// <param name="location">the location to find the attribute at</param>
/// <param name="attribute">the attribute key to get value by</param>
/// <returns>found attribute value or null if not found</returns>
public string GetAttributeAt(Point location, string attribute)
{
return _htmlContainerInt.GetAttributeAt(Util.Convert(location), attribute);
}
/// <summary>
/// Get all the links in the HTML with the element Rect and href data.
/// </summary>
/// <returns>collection of all the links in the HTML</returns>
public List<LinkElementData<Rect>> GetLinks()
{
var linkElements = new List<LinkElementData<Rect>>();
foreach (var link in HtmlContainerInt.GetLinks())
{
linkElements.Add(new LinkElementData<Rect>(link.Id, link.Href, Util.Convert(link.Rectangle)));
}
return linkElements;
}
/// <summary>
/// Get css link href at the given x,y location.
/// </summary>
/// <param name="location">the location to find the link at</param>
/// <returns>css link href if exists or null</returns>
public string GetLinkAt(Point location)
{
return _htmlContainerInt.GetLinkAt(Util.Convert(location));
}
/// <summary>
/// Get the Rect of html element as calculated by html layout.<br/>
/// Element if found by id (id attribute on the html element).<br/>
/// Note: to get the screen Rect you need to adjust by the hosting control.<br/>
/// </summary>
/// <param name="elementId">the id of the element to get its Rect</param>
/// <returns>the Rect of the element or null if not found</returns>
public Rect? GetElementRectangle(string elementId)
{
var r = _htmlContainerInt.GetElementRectangle(elementId);
return r.HasValue ? Util.Convert(r.Value) : (Rect?)null;
}
/// <summary>
/// Measures the bounds of box and children, recursively.
/// </summary>
public void PerformLayout()
{
using (var ig = new GraphicsAdapter())
{
_htmlContainerInt.PerformLayout(ig);
}
}
/// <summary>
/// Render the html using the given device.
/// </summary>
/// <param name="g">the device to use to render</param>
/// <param name="clip">the clip rectangle of the html container</param>
public void PerformPaint(DrawingContext g, Rect clip)
{
ArgChecker.AssertArgNotNull(g, "g");
using (var ig = new GraphicsAdapter(g, Util.Convert(clip)))
{
_htmlContainerInt.PerformPaint(ig);
}
}
/// <summary>
/// Handle mouse down to handle selection.
/// </summary>
/// <param name="parent">the control hosting the html to invalidate</param>
/// <param name="e">the mouse event args</param>
public void HandleLeftMouseDown(Control parent, PointerEventArgs e)
{
ArgChecker.AssertArgNotNull(parent, "parent");
ArgChecker.AssertArgNotNull(e, "e");
_htmlContainerInt.HandleMouseDown(new ControlAdapter(parent), Util.Convert(e.GetPosition(parent)));
}
/// <summary>
/// Handle mouse up to handle selection and link click.
/// </summary>
/// <param name="parent">the control hosting the html to invalidate</param>
/// <param name="e">the mouse event args</param>
public void HandleLeftMouseUp(Control parent, PointerEventArgs e)
{
ArgChecker.AssertArgNotNull(parent, "parent");
ArgChecker.AssertArgNotNull(e, "e");
var mouseEvent = new RMouseEvent(true);
_htmlContainerInt.HandleMouseUp(new ControlAdapter(parent), Util.Convert(e.GetPosition(parent)), mouseEvent);
}
/// <summary>
/// Handle mouse double click to select word under the mouse.
/// </summary>
/// <param name="parent">the control hosting the html to set cursor and invalidate</param>
/// <param name="e">mouse event args</param>
public void HandleMouseDoubleClick(Control parent, PointerEventArgs e)
{
ArgChecker.AssertArgNotNull(parent, "parent");
ArgChecker.AssertArgNotNull(e, "e");
_htmlContainerInt.HandleMouseDoubleClick(new ControlAdapter(parent), Util.Convert(e.GetPosition(parent)));
}
/// <summary>
/// Handle mouse move to handle hover cursor and text selection.
/// </summary>
/// <param name="parent">the control hosting the html to set cursor and invalidate</param>
/// <param name="mousePos">the mouse event args</param>
public void HandleMouseMove(Control parent, Point mousePos)
{
ArgChecker.AssertArgNotNull(parent, "parent");
_htmlContainerInt.HandleMouseMove(new ControlAdapter(parent), Util.Convert(mousePos));
}
/// <summary>
/// Handle mouse leave to handle hover cursor.
/// </summary>
/// <param name="parent">the control hosting the html to set cursor and invalidate</param>
public void HandleMouseLeave(Control parent)
{
ArgChecker.AssertArgNotNull(parent, "parent");
_htmlContainerInt.HandleMouseLeave(new ControlAdapter(parent));
}
/// <summary>
/// Handle key down event for selection and copy.
/// </summary>
/// <param name="parent">the control hosting the html to invalidate</param>
/// <param name="e">the pressed key</param>
public void HandleKeyDown(Control parent, KeyEventArgs e)
{
ArgChecker.AssertArgNotNull(parent, "parent");
ArgChecker.AssertArgNotNull(e, "e");
_htmlContainerInt.HandleKeyDown(new ControlAdapter(parent), CreateKeyEevent(e));
}
/// <summary>
/// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.
/// </summary>
public void Dispose()
{
_htmlContainerInt.Dispose();
}
#region Private methods
/// <summary>
/// Create HtmlRenderer key event from Avalonia key event.
/// </summary>
private static RKeyEvent CreateKeyEevent(KeyEventArgs e)
{
var control = (e.Modifiers & InputModifiers.Control) == InputModifiers.Control;
return new RKeyEvent(control, e.Key == Key.A, e.Key == Key.C);
}
#endregion
}
}

616
src/Avalonia.HtmlRenderer/HtmlControl.cs

@ -1,616 +0,0 @@
// "Therefore those skilled at the unorthodox
// are infinite as heaven and earth,
// inexhaustible as the great rivers.
// When they come to an end,
// they begin again,
// like the days and months;
// they die and are reborn,
// like the four seasons."
//
// - Sun Tsu,
// "The Art of War"
using System;
using Avalonia.Controls.Primitives;
using Avalonia.HtmlRenderer;
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;
using TheArtOfDev.HtmlRenderer.Avalonia.Adapters;
namespace Avalonia.Controls.Html
{
/// <summary>
/// Provides HTML rendering using the text property.<br/>
/// Avalonia control that will render html content in it's client rectangle.<br/>
/// The control will handle mouse and keyboard events on it to support html text selection, copy-paste and mouse clicks.<br/>
/// <para>
/// The major differential to use HtmlPanel or HtmlLabel is size and scrollbars.<br/>
/// If the size of the control depends on the html content the HtmlLabel should be used.<br/>
/// If the size is set by some kind of layout then HtmlPanel is more suitable, also shows scrollbars if the html contents is larger than the control client rectangle.<br/>
/// </para>
/// <para>
/// <h4>LinkClicked event:</h4>
/// Raised when the user clicks on a link in the html.<br/>
/// Allows canceling the execution of the link.
/// </para>
/// <para>
/// <h4>StylesheetLoad event:</h4>
/// Raised when a stylesheet is about to be loaded by file path or URI by link element.<br/>
/// This event allows to provide the stylesheet manually or provide new source (file or uri) to load from.<br/>
/// If no alternative data is provided the original source will be used.<br/>
/// </para>
/// <para>
/// <h4>ImageLoad event:</h4>
/// Raised when an image is about to be loaded by file path or URI.<br/>
/// This event allows to provide the image manually, if not handled the image will be loaded from file or download from URI.
/// </para>
/// <para>
/// <h4>RenderError event:</h4>
/// Raised when an error occurred during html rendering.<br/>
/// </para>
/// </summary>
public class HtmlControl : Control
{
/// <summary>
/// Underline html container instance.
/// </summary>
protected readonly HtmlContainer _htmlContainer;
/// <summary>
/// the base stylesheet data used in the control
/// </summary>
protected CssData _baseCssData;
/// <summary>
/// The last position of the scrollbars to know if it has changed to update mouse
/// </summary>
protected Point _lastScrollOffset;
public static readonly AvaloniaProperty AvoidImagesLateLoadingProperty =
PropertyHelper.Register<HtmlControl, bool>(nameof(AvoidImagesLateLoading), false, OnAvaloniaProperty_valueChanged);
public static readonly AvaloniaProperty IsSelectionEnabledProperty =
PropertyHelper.Register<HtmlControl, bool>(nameof(IsSelectionEnabled), true, OnAvaloniaProperty_valueChanged);
public static readonly AvaloniaProperty IsContextMenuEnabledProperty =
PropertyHelper.Register<HtmlControl, bool>(nameof(IsContextMenuEnabled), true, OnAvaloniaProperty_valueChanged);
public static readonly AvaloniaProperty BaseStylesheetProperty =
PropertyHelper.Register<HtmlControl, string>(nameof(BaseStylesheet), null, OnAvaloniaProperty_valueChanged);
public static readonly AvaloniaProperty TextProperty =
PropertyHelper.Register<HtmlControl, string>(nameof(Text), null, OnAvaloniaProperty_valueChanged);
public static readonly StyledProperty<IBrush> BackgroundProperty =
Border.BackgroundProperty.AddOwner<HtmlControl>();
public static readonly AvaloniaProperty BorderThicknessProperty =
AvaloniaProperty.Register<HtmlControl, Thickness>(nameof(BorderThickness), new Thickness(0));
public static readonly AvaloniaProperty BorderBrushProperty =
AvaloniaProperty.Register<HtmlControl, IBrush>(nameof(BorderBrush));
public static readonly AvaloniaProperty PaddingProperty =
AvaloniaProperty.Register<HtmlControl, Thickness>(nameof(Padding), new Thickness(0));
public static readonly RoutedEvent LoadCompleteEvent =
RoutedEvent.Register<RoutedEventArgs>("LoadComplete", RoutingStrategies.Bubble, typeof(HtmlControl));
public static readonly RoutedEvent LinkClickedEvent =
RoutedEvent.Register<HtmlRendererRoutedEventArgs<HtmlLinkClickedEventArgs>>("LinkClicked", RoutingStrategies.Bubble, typeof(HtmlControl));
public static readonly RoutedEvent RenderErrorEvent
= RoutedEvent.Register<HtmlRendererRoutedEventArgs<HtmlRenderErrorEventArgs>>("RenderError", RoutingStrategies.Bubble, typeof(HtmlControl));
public static readonly RoutedEvent RefreshEvent
= RoutedEvent.Register<HtmlRendererRoutedEventArgs<HtmlRefreshEventArgs>>("Refresh", RoutingStrategies.Bubble, typeof(HtmlControl));
public static readonly RoutedEvent StylesheetLoadEvent
= RoutedEvent.Register<HtmlRendererRoutedEventArgs<HtmlStylesheetLoadEventArgs>>("StylesheetLoad", RoutingStrategies.Bubble, typeof(HtmlControl));
public static readonly RoutedEvent ImageLoadEvent
= RoutedEvent.Register<HtmlRendererRoutedEventArgs<HtmlImageLoadEventArgs>>("ImageLoad", RoutingStrategies.Bubble,
typeof (HtmlControl));
static HtmlControl()
{
FocusableProperty.OverrideDefaultValue(typeof(HtmlControl), true);
}
/// <summary>
/// Creates a new HtmlPanel and sets a basic css for it's styling.
/// </summary>
protected HtmlControl()
{
_htmlContainer = new HtmlContainer();
_htmlContainer.LoadComplete += (_, e) => OnLoadComplete(e);
_htmlContainer.LinkClicked += (_, e) => OnLinkClicked(e);
_htmlContainer.RenderError += (_, e) => OnRenderError(e);
_htmlContainer.Refresh += (_, e) => OnRefresh(e);
_htmlContainer.StylesheetLoad += (_, e) => OnStylesheetLoad(e);
_htmlContainer.ImageLoad += (_, e) => OnImageLoad(e);
}
//Hack for adapter
internal bool LeftMouseButton { get; private set; }
/// <summary>
/// Raised when the set html document has been fully loaded.<br/>
/// Allows manipulation of the html dom, scroll position, etc.
/// </summary>
public event EventHandler<HtmlRendererRoutedEventArgs<EventArgs>> LoadComplete
{
add { AddHandler(LoadCompleteEvent, value); }
remove { RemoveHandler(LoadCompleteEvent, value); }
}
/// <summary>
/// Raised when the user clicks on a link in the html.<br/>
/// Allows canceling the execution of the link.
/// </summary>
public event EventHandler<HtmlRendererRoutedEventArgs<HtmlLinkClickedEventArgs>> LinkClicked
{
add { AddHandler(LinkClickedEvent, value); }
remove { RemoveHandler(LinkClickedEvent, value); }
}
/// <summary>
/// Raised when an error occurred during html rendering.<br/>
/// </summary>
public event EventHandler<HtmlRendererRoutedEventArgs<HtmlRenderErrorEventArgs>> RenderError
{
add { AddHandler(RenderErrorEvent, value); }
remove { RemoveHandler(RenderErrorEvent, value); }
}
/// <summary>
/// Raised when a stylesheet is about to be loaded by file path or URI by link element.<br/>
/// This event allows to provide the stylesheet manually or provide new source (file or uri) to load from.<br/>
/// If no alternative data is provided the original source will be used.<br/>
/// </summary>
public event EventHandler<HtmlRendererRoutedEventArgs<HtmlStylesheetLoadEventArgs>> StylesheetLoad
{
add { AddHandler(StylesheetLoadEvent, value); }
remove { RemoveHandler(StylesheetLoadEvent, value); }
}
/// <summary>
/// Raised when an image is about to be loaded by file path or URI.<br/>
/// This event allows to provide the image manually, if not handled the image will be loaded from file or download from URI.
/// </summary>
public event EventHandler<HtmlRendererRoutedEventArgs<HtmlImageLoadEventArgs>> ImageLoad
{
add { AddHandler(ImageLoadEvent, value); }
remove { RemoveHandler(ImageLoadEvent, value); }
}
/// <summary>
/// Gets or sets a value indicating if image loading only when visible should be avoided (default - false).<br/>
/// True - images are loaded as soon as the html is parsed.<br/>
/// False - images that are not visible because of scroll location are not loaded until they are scrolled to.
/// </summary>
/// <remarks>
/// Images late loading improve performance if the page contains image outside the visible scroll area, especially if there is large
/// amount of images, as all image loading is delayed (downloading and loading into memory).<br/>
/// Late image loading may effect the layout and actual size as image without set size will not have actual size until they are loaded
/// resulting in layout change during user scroll.<br/>
/// Early image loading may also effect the layout if image without known size above the current scroll location are loaded as they
/// will push the html elements down.
/// </remarks>
[Category("Behavior")]
[Description("If image loading only when visible should be avoided")]
public bool AvoidImagesLateLoading
{
get { return (bool)GetValue(AvoidImagesLateLoadingProperty); }
set { SetValue(AvoidImagesLateLoadingProperty, value); }
}
/// <summary>
/// Is content selection is enabled for the rendered html (default - true).<br/>
/// If set to 'false' the rendered html will be static only with ability to click on links.
/// </summary>
[Category("Behavior")]
[Description("Is content selection is enabled for the rendered html.")]
public bool IsSelectionEnabled
{
get { return (bool)GetValue(IsSelectionEnabledProperty); }
set { SetValue(IsSelectionEnabledProperty, value); }
}
/// <summary>
/// Is the build-in context menu enabled and will be shown on mouse right click (default - true)
/// </summary>
[Category("Behavior")]
[Description("Is the build-in context menu enabled and will be shown on mouse right click.")]
public bool IsContextMenuEnabled
{
get { return (bool)GetValue(IsContextMenuEnabledProperty); }
set { SetValue(IsContextMenuEnabledProperty, value); }
}
/// <summary>
/// Set base stylesheet to be used by html rendered in the panel.
/// </summary>
[Category("Appearance")]
[Description("Set base stylesheet to be used by html rendered in the control.")]
public string BaseStylesheet
{
get { return (string)GetValue(BaseStylesheetProperty); }
set { SetValue(BaseStylesheetProperty, value); }
}
/// <summary>
/// Gets or sets the text of this panel
/// </summary>
[Description("Sets the html of this control.")]
public string Text
{
get { return (string)GetValue(TextProperty); }
set { SetValue(TextProperty, value); }
}
public Thickness BorderThickness
{
get { return (Thickness) GetValue(BorderThicknessProperty); }
set { SetValue(BorderThicknessProperty, value); }
}
public IBrush BorderBrush
{
get { return (IBrush)GetValue(BorderBrushProperty); }
set { SetValue(BorderThicknessProperty, value); }
}
public Thickness Padding
{
get { return (Thickness)GetValue(PaddingProperty); }
set { SetValue(PaddingProperty, value); }
}
public IBrush Background
{
get { return (IBrush) GetValue(BackgroundProperty); }
set { SetValue(BackgroundProperty, value);}
}
/// <summary>
/// Get the currently selected text segment in the html.
/// </summary>
[Browsable(false)]
public virtual string SelectedText
{
get { return _htmlContainer.SelectedText; }
}
/// <summary>
/// Copy the currently selected html segment with style.
/// </summary>
[Browsable(false)]
public virtual string SelectedHtml
{
get { return _htmlContainer.SelectedHtml; }
}
/// <summary>
/// Get html from the current DOM tree with inline style.
/// </summary>
/// <returns>generated html</returns>
public virtual string GetHtml()
{
return _htmlContainer != null ? _htmlContainer.GetHtml() : null;
}
/// <summary>
/// Get the rectangle of html element as calculated by html layout.<br/>
/// Element if found by id (id attribute on the html element).<br/>
/// Note: to get the screen rectangle you need to adjust by the hosting control.<br/>
/// </summary>
/// <param name="elementId">the id of the element to get its rectangle</param>
/// <returns>the rectangle of the element or null if not found</returns>
public virtual Rect? GetElementRectangle(string elementId)
{
return _htmlContainer != null ? _htmlContainer.GetElementRectangle(elementId) : null;
}
/// <summary>
/// Clear the current selection.
/// </summary>
public void ClearSelection()
{
if (_htmlContainer != null)
_htmlContainer.ClearSelection();
}
//HACK: We don't have support for RenderSize for now
private Size RenderSize => new Size(Bounds.Width, Bounds.Height);
public override void Render(DrawingContext context)
{
context.FillRectangle(Background, new Rect(RenderSize));
if (BorderThickness != new Thickness(0) && BorderBrush != null)
{
var brush = new SolidColorBrush(Colors.Black);
if (BorderThickness.Top > 0)
context.FillRectangle(brush, new Rect(0, 0, RenderSize.Width, BorderThickness.Top));
if (BorderThickness.Bottom > 0)
context.FillRectangle(brush, new Rect(0, RenderSize.Height - BorderThickness.Bottom, RenderSize.Width, BorderThickness.Bottom));
if (BorderThickness.Left > 0)
context.FillRectangle(brush, new Rect(0, 0, BorderThickness.Left, RenderSize.Height));
if (BorderThickness.Right > 0)
context.FillRectangle(brush, new Rect(RenderSize.Width - BorderThickness.Right, 0, BorderThickness.Right, RenderSize.Height));
}
var htmlWidth = HtmlWidth(RenderSize);
var htmlHeight = HtmlHeight(RenderSize);
if (_htmlContainer != null && htmlWidth > 0 && htmlHeight > 0)
{
/*
//TODO: Revert antialiasing fixes
var windows = Window.GetWindow(this);
if (windows != null)
{
// adjust render location to round point so we won't get anti-alias smugness
var wPoint = TranslatePoint(new Point(0, 0), windows);
wPoint.Offset(-(int)wPoint.X, -(int)wPoint.Y);
var xTrans = wPoint.X < .5 ? -wPoint.X : 1 - wPoint.X;
var yTrans = wPoint.Y < .5 ? -wPoint.Y : 1 - wPoint.Y;
context.PushTransform(new TranslateTransform(xTrans, yTrans));
}*/
using (context.PushClip(new Rect(Padding.Left + BorderThickness.Left, Padding.Top + BorderThickness.Top,
htmlWidth, (int) htmlHeight)))
{
_htmlContainer.Location = new Point(Padding.Left + BorderThickness.Left,
Padding.Top + BorderThickness.Top);
_htmlContainer.PerformPaint(context,
new Rect(Padding.Left + BorderThickness.Left, Padding.Top + BorderThickness.Top, htmlWidth,
htmlHeight));
}
if (!_lastScrollOffset.Equals(_htmlContainer.ScrollOffset))
{
_lastScrollOffset = _htmlContainer.ScrollOffset;
InvokeMouseMove();
}
}
}
/// <summary>
/// Handle mouse move to handle hover cursor and text selection.
/// </summary>
protected override void OnPointerMoved(PointerEventArgs e)
{
base.OnPointerMoved(e);
if (_htmlContainer != null)
_htmlContainer.HandleMouseMove(this, e.GetPosition(this));
}
/// <summary>
/// Handle mouse leave to handle cursor change.
/// </summary>
protected override void OnPointerLeave(PointerEventArgs e)
{
base.OnPointerLeave(e);
if (_htmlContainer != null)
_htmlContainer.HandleMouseLeave(this);
}
/// <summary>
/// Handle mouse down to handle selection.
/// </summary>
protected override void OnPointerPressed(PointerPressedEventArgs e)
{
base.OnPointerPressed(e);
LeftMouseButton = true;
_htmlContainer?.HandleLeftMouseDown(this, e);
}
/// <summary>
/// Handle mouse up to handle selection and link click.
/// </summary>
protected override void OnPointerReleased(PointerReleasedEventArgs e)
{
base.OnPointerReleased(e);
LeftMouseButton = false;
if (_htmlContainer != null)
_htmlContainer.HandleLeftMouseUp(this, e);
}
//TODO: Implement double click
/*
/// <summary>
/// Handle mouse double click to select word under the mouse.
/// </summary>
protected override void OnMouseDoubleClick(MouseButtonEventArgs e)
{
base.OnMouseDoubleClick(e);
if (_htmlContainer != null)
_htmlContainer.HandleMouseDoubleClick(this, e);
}
*/
/// <summary>
/// Handle key down event for selection, copy and scrollbars handling.
/// </summary>
protected override void OnKeyDown(KeyEventArgs e)
{
base.OnKeyDown(e);
if (_htmlContainer != null)
_htmlContainer.HandleKeyDown(this, e);
}
void RaiseRouted<T>(RoutedEvent ev, T arg)
{
var e =new HtmlRendererRoutedEventArgs<T>
{
Event = arg,
Source = this,
RoutedEvent = ev,
Route = ev.RoutingStrategies
};
RaiseEvent(e);
}
/// <summary>
/// Propagate the LoadComplete event from root container.
/// </summary>
protected virtual void OnLoadComplete(EventArgs e) => RaiseRouted(LoadCompleteEvent, e);
/// <summary>
/// Propagate the LinkClicked event from root container.
/// </summary>
protected virtual void OnLinkClicked(HtmlLinkClickedEventArgs e) => RaiseRouted(LinkClickedEvent, e);
/// <summary>
/// Propagate the Render Error event from root container.
/// </summary>
protected virtual void OnRenderError(HtmlRenderErrorEventArgs e) => RaiseRouted(RenderErrorEvent, e);
/// <summary>
/// Propagate the stylesheet load event from root container.
/// </summary>
protected virtual void OnStylesheetLoad(HtmlStylesheetLoadEventArgs e) => RaiseRouted(StylesheetLoadEvent, e);
/// <summary>
/// Propagate the image load event from root container.
/// </summary>
protected virtual void OnImageLoad(HtmlImageLoadEventArgs e) => RaiseRouted(ImageLoadEvent, e);
/// <summary>
/// Handle html renderer invalidate and re-layout as requested.
/// </summary>
protected virtual void OnRefresh(HtmlRefreshEventArgs e)
{
if (e.Layout)
InvalidateMeasure();
InvalidateVisual();
}
/// <summary>
/// Get the width the HTML has to render in (not including vertical scroll iff it is visible)
/// </summary>
protected virtual double HtmlWidth(Size size)
{
return size.Width - Padding.Left - Padding.Right - BorderThickness.Left - BorderThickness.Right;
}
/// <summary>
/// Get the width the HTML has to render in (not including vertical scroll iff it is visible)
/// </summary>
protected virtual double HtmlHeight(Size size)
{
return size.Height - Padding.Top - Padding.Bottom - BorderThickness.Top - BorderThickness.Bottom;
}
/// <summary>
/// call mouse move to handle paint after scroll or html change affecting mouse cursor.
/// </summary>
protected virtual void InvokeMouseMove()
{
_htmlContainer.HandleMouseMove(this, (this.GetVisualRoot() as IInputRoot)?.MouseDevice?.GetPosition(this) ?? default(Point));
}
/// <summary>
/// Handle when dependency property value changes to update the underline HtmlContainer with the new value.
/// </summary>
private static void OnAvaloniaProperty_valueChanged(AvaloniaObject AvaloniaObject,
AvaloniaPropertyChangedEventArgs e)
{
var control = AvaloniaObject as HtmlControl;
if (control != null)
{
var htmlContainer = control._htmlContainer;
if (e.Property == AvoidImagesLateLoadingProperty)
{
htmlContainer.AvoidImagesLateLoading = (bool) e.NewValue;
}
else if (e.Property == IsSelectionEnabledProperty)
{
htmlContainer.IsSelectionEnabled = (bool) e.NewValue;
}
else if (e.Property == IsContextMenuEnabledProperty)
{
htmlContainer.IsContextMenuEnabled = (bool) e.NewValue;
}
else if (e.Property == BaseStylesheetProperty)
{
var baseCssData = CssData.Parse(AvaloniaAdapter.Instance, (string) e.NewValue);
control._baseCssData = baseCssData;
htmlContainer.SetHtml(control.Text, baseCssData);
}
else if (e.Property == TextProperty)
{
htmlContainer.ScrollOffset = new Point(0, 0);
htmlContainer.SetHtml((string) e.NewValue, control._baseCssData);
control.InvalidateMeasure();
control.InvalidateVisual();
if (control.VisualRoot != null)
{
control.InvokeMouseMove();
}
}
}
}
//TODO: Implement CheckAccess calls
/*
private void OnLoadComplete(object sender, EventArgs e)
{
if (CheckAccess())
OnLoadComplete(e);
else
Dispatcher.UIThread.Invoke(new Action<HtmlLinkClickedEventArgs>(OnLinkClicked), e);
}
private void OnLinkClicked(object sender, HtmlLinkClickedEventArgs e)
{
if (CheckAccess())
OnLinkClicked(e);
else
Dispatcher.UIThread.Invoke(new Action<HtmlLinkClickedEventArgs>(OnLinkClicked), e);
}
private void OnRenderError(object sender, HtmlRenderErrorEventArgs e)
{
if (CheckAccess())
OnRenderError(e);
else
Dispatcher.UIThread.Invoke(new Action<HtmlRenderErrorEventArgs>(OnRenderError), e);
}
private void OnStylesheetLoad(object sender, HtmlStylesheetLoadEventArgs e)
{
if (CheckAccess())
OnStylesheetLoad(e);
else
Dispatcher.UIThread.Invoke(new Action<HtmlStylesheetLoadEventArgs>(OnStylesheetLoad), e);
}
private void OnImageLoad(object sender, HtmlImageLoadEventArgs e)
{
if (CheckAccess())
OnImageLoad(e);
else
Dispatcher.UIThread.Invoke(new Action<HtmlImageLoadEventArgs>(OnImageLoad), e);
}
private void OnRefresh(object sender, HtmlRefreshEventArgs e)
{
if (CheckAccess())
OnRefresh(e);
else
Dispatcher.UIThread.Invoke(new Action<HtmlRefreshEventArgs>(OnRefresh), e);
}
*/
}
}

112
src/Avalonia.HtmlRenderer/HtmlLabel.cs

@ -1,112 +0,0 @@
// "Therefore those skilled at the unorthodox
// are infinite as heaven and earth,
// inexhaustible as the great rivers.
// When they come to an end,
// they begin again,
// like the days and months;
// they die and are reborn,
// like the four seasons."
//
// - Sun Tsu,
// "The Art of War"
using System;
using Avalonia.HtmlRenderer;
using Avalonia.Media;
using TheArtOfDev.HtmlRenderer.Adapters.Entities;
using TheArtOfDev.HtmlRenderer.Core;
using TheArtOfDev.HtmlRenderer.Avalonia.Adapters;
namespace Avalonia.Controls.Html
{
/// <summary>
/// Provides HTML rendering using the text property.<br/>
/// WPF control that will render html content in it's client rectangle.<br/>
/// Using <see cref="AutoSize"/> and <see cref="AutoSizeHeightOnly"/> client can control how the html content effects the
/// size of the label. Either case scrollbars are never shown and html content outside of client bounds will be clipped.
/// MaxWidth/MaxHeight and MinWidth/MinHeight with AutoSize can limit the max/min size of the control<br/>
/// The control will handle mouse and keyboard events on it to support html text selection, copy-paste and mouse clicks.<br/>
/// </summary>
/// <remarks>
/// See <see cref="HtmlControl"/> for more info.
/// </remarks>
public class HtmlLabel : HtmlControl
{
#region Dependency properties
public static readonly AvaloniaProperty AutoSizeProperty = PropertyHelper.Register<HtmlLabel, bool>("AutoSize", true, OnAvaloniaProperty_valueChanged);
public static readonly AvaloniaProperty AutoSizeHeightOnlyProperty = PropertyHelper.Register<HtmlLabel, bool>("AutoSizeHeightOnly", false, OnAvaloniaProperty_valueChanged);
#endregion
/// <summary>
/// Init.
/// </summary>
static HtmlLabel()
{
BackgroundProperty.OverrideDefaultValue<HtmlLabel>(Brushes.Transparent);
}
#region Private methods
/// <summary>
/// Perform the layout of the html in the control.
/// </summary>
protected override Size MeasureOverride(Size constraint)
{
if (_htmlContainer != null)
{
using (var ig = new GraphicsAdapter())
{
var horizontal = Padding.Left + Padding.Right + BorderThickness.Left + BorderThickness.Right;
var vertical = Padding.Top + Padding.Bottom + BorderThickness.Top + BorderThickness.Bottom;
var size = new Size(Math.Min(MaxWidth, constraint.Width), Math.Min(MaxHeight, constraint.Height));
var maxSize = new RSize(size.Width < Double.PositiveInfinity ? size.Width - horizontal : 0, size.Height < Double.PositiveInfinity ? size.Height - vertical : 0);
_htmlContainer.HtmlContainerInt.MaxSize = maxSize;
_htmlContainer.HtmlContainerInt.PerformLayout(ig);
var newSize = _htmlContainer.ActualSize;
constraint = new Size(newSize.Width + horizontal, newSize.Height + vertical);
}
}
if (double.IsPositiveInfinity(constraint.Width) || double.IsPositiveInfinity(constraint.Height))
constraint = Size.Empty;
return constraint;
}
/// <summary>
/// Handle when dependency property value changes to update the underline HtmlContainer with the new value.
/// </summary>
private static void OnAvaloniaProperty_valueChanged(AvaloniaObject AvaloniaObject, AvaloniaPropertyChangedEventArgs e)
{
var control = AvaloniaObject as HtmlLabel;
if (control != null)
{
if (e.Property == AutoSizeProperty)
{
if ((bool)e.NewValue)
{
AvaloniaObject.SetValue(AutoSizeHeightOnlyProperty, false);
control.InvalidateMeasure();
control.InvalidateVisual();
}
}
else if (e.Property == AutoSizeHeightOnlyProperty)
{
if ((bool)e.NewValue)
{
AvaloniaObject.SetValue(AutoSizeProperty, false);
control.InvalidateMeasure();
control.InvalidateVisual();
}
}
}
}
#endregion
}
}

14
src/Avalonia.HtmlRenderer/HtmlRendererRoutedEventArgs.cs

@ -1,14 +0,0 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Avalonia.Interactivity;
namespace Avalonia.Controls.Html
{
public class HtmlRendererRoutedEventArgs<T> : RoutedEventArgs
{
public T Event { get; set; }
}
}

33
src/Avalonia.HtmlRenderer/Properties/AssemblyInfo.cs

@ -1,33 +0,0 @@
using System.Resources;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using Avalonia.Metadata;
// General Information about an assembly is controlled through the following
// set of attributes. Change these attribute values to modify the information
// associated with an assembly.
[assembly: AssemblyTitle("Avalonia.HtmlRenderer")]
[assembly: AssemblyDescription("")]
[assembly: AssemblyConfiguration("")]
[assembly: AssemblyCompany("")]
[assembly: AssemblyProduct("Avalonia.HtmlRenderer")]
[assembly: AssemblyCopyright("Copyright © 2015")]
[assembly: AssemblyTrademark("")]
[assembly: AssemblyCulture("")]
[assembly: NeutralResourcesLanguage("en")]
// Version information for an assembly consists of the following four values:
//
// Major Version
// Minor Version
// Build Number
// Revision
//
// You can specify all the values or you can default the Build and Revision Numbers
// by using the '*' as shown below:
// [assembly: AssemblyVersion("1.0.*")]
[assembly: AssemblyVersion("1.0.0.0")]
[assembly: AssemblyFileVersion("1.0.0.0")]
[assembly: XmlnsDefinition("https://github.com/avaloniaui", "Avalonia.Controls.Html")]

24
src/Avalonia.HtmlRenderer/PropertyHelper.cs

@ -1,24 +0,0 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Avalonia.HtmlRenderer
{
static class PropertyHelper
{
public static AvaloniaProperty Register<TOwner, T>(string name, T def, Action<AvaloniaObject, AvaloniaPropertyChangedEventArgs> changed) where TOwner : AvaloniaObject
{
var pp = AvaloniaProperty.Register<TOwner, T>(name, def);
Action<AvaloniaPropertyChangedEventArgs> cb = args =>
{
changed(args.Sender, args);
};
pp.Changed.Subscribe(cb);
return pp;
}
}
}

123
src/Avalonia.HtmlRenderer/Utilities/Util.cs

@ -1,123 +0,0 @@
// "Therefore those skilled at the unorthodox
// are infinite as heaven and earth,
// inexhaustible as the great rivers.
// When they come to an end,
// they begin again,
// like the days and months;
// they die and are reborn,
// like the four seasons."
//
// - Sun Tsu,
// "The Art of War"
using Avalonia;
using Avalonia.Media;
using TheArtOfDev.HtmlRenderer.Adapters.Entities;
namespace TheArtOfDev.HtmlRenderer.Avalonia.Utilities
{
/// <summary>
/// Utilities for converting WPF entities to HtmlRenderer core entities.
/// </summary>
internal static class Util
{
/// <summary>
/// Convert from WPF point to core point.
/// </summary>
public static RPoint Convert(Point p)
{
return new RPoint(p.X, p.Y);
}
/// <summary>
/// Convert from WPF point to core point.
/// </summary>
public static Point[] Convert(RPoint[] points)
{
Point[] myPoints = new Point[points.Length];
for (int i = 0; i < points.Length; i++)
myPoints[i] = Convert(points[i]);
return myPoints;
}
/// <summary>
/// Convert from core point to WPF point.
/// </summary>
public static Point Convert(RPoint p)
{
return new Point(p.X, p.Y);
}
/// <summary>
/// Convert from core point to WPF point.
/// </summary>
public static Point ConvertRound(RPoint p)
{
return new Point((int)p.X, (int)p.Y);
}
/// <summary>
/// Convert from WPF size to core size.
/// </summary>
public static RSize Convert(Size s)
{
return new RSize(s.Width, s.Height);
}
/// <summary>
/// Convert from core size to WPF size.
/// </summary>
public static Size Convert(RSize s)
{
return new Size(s.Width, s.Height);
}
/// <summary>
/// Convert from core point to WPF point.
/// </summary>
public static Size ConvertRound(RSize s)
{
return new Size((int)s.Width, (int)s.Height);
}
/// <summary>
/// Convert from WPF rectangle to core rectangle.
/// </summary>
public static RRect Convert(Rect r)
{
return new RRect(r.X, r.Y, r.Width, r.Height);
}
/// <summary>
/// Convert from core rectangle to WPF rectangle.
/// </summary>
public static Rect Convert(RRect r)
{
return new Rect(r.X, r.Y, r.Width, r.Height);
}
/// <summary>
/// Convert from core rectangle to WPF rectangle.
/// </summary>
public static Rect ConvertRound(RRect r)
{
return new Rect((int)r.X, (int)r.Y, (int)r.Width, (int)r.Height);
}
/// <summary>
/// Convert from WPF color to core color.
/// </summary>
public static RColor Convert(Color c)
{
return RColor.FromArgb(c.A, c.R, c.G, c.B);
}
/// <summary>
/// Convert from core color to WPF color.
/// </summary>
public static Color Convert(RColor c)
{
return Color.FromArgb(c.A, c.R, c.G, c.B);
}
}
}

1
src/Avalonia.HtmlRenderer/external

@ -1 +0,0 @@
Subproject commit 43df78f4d1bb09d05021f062cb15e939e3e1771d

4
src/Avalonia.Input/InputElement.cs

@ -168,6 +168,10 @@ namespace Avalonia.Input
PointerPressedEvent.AddClassHandler<InputElement>(x => x.OnPointerPressed);
PointerReleasedEvent.AddClassHandler<InputElement>(x => x.OnPointerReleased);
PointerWheelChangedEvent.AddClassHandler<InputElement>(x => x.OnPointerWheelChanged);
PseudoClass(IsEnabledCoreProperty, x => !x, ":disabled");
PseudoClass(IsFocusedProperty, ":focus");
PseudoClass(IsPointerOverProperty, ":pointerover");
}
/// <summary>

3
src/Avalonia.Styling/Avalonia.Styling.csproj

@ -5,9 +5,8 @@
<RootNamespace>Avalonia</RootNamespace>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\Avalonia.Base\Avalonia.Base.csproj" />
<ProjectReference Include="..\Avalonia.Visuals\Avalonia.Visuals.csproj" />
<ProjectReference Include="..\Avalonia.Animation\Avalonia.Animation.csproj" />
<ProjectReference Include="..\Avalonia.Base\Avalonia.Base.csproj" />
</ItemGroup>
<Import Project="..\..\build\Rx.props" />
</Project>

16
src/Avalonia.Controls/Classes.cs → src/Avalonia.Styling/Controls/Classes.cs

@ -9,7 +9,7 @@ using Avalonia.Collections;
namespace Avalonia.Controls
{
/// <summary>
/// Holds a collection of style classes for an <see cref="IControl"/>.
/// Holds a collection of style classes for an <see cref="IStyledElement"/>.
/// </summary>
/// <remarks>
/// Similar to CSS, each control may have any number of styling classes applied.
@ -54,7 +54,7 @@ namespace Avalonia.Controls
/// <param name="name">The class name.</param>
/// <remarks>
/// Only standard classes may be added via this method. To add pseudoclasses (classes
/// beginning with a ':' character) use the protected <see cref="Control.PseudoClasses"/>
/// beginning with a ':' character) use the protected <see cref="StyledElement.PseudoClasses"/>
/// property.
/// </remarks>
public override void Add(string name)
@ -73,7 +73,7 @@ namespace Avalonia.Controls
/// <param name="names">The class names.</param>
/// <remarks>
/// Only standard classes may be added via this method. To add pseudoclasses (classes
/// beginning with a ':' character) use the protected <see cref="Control.PseudoClasses"/>
/// beginning with a ':' character) use the protected <see cref="StyledElement.PseudoClasses"/>
/// property.
/// </remarks>
public override void AddRange(IEnumerable<string> names)
@ -114,7 +114,7 @@ namespace Avalonia.Controls
/// <param name="name">The class name.</param>
/// <remarks>
/// Only standard classes may be added via this method. To add pseudoclasses (classes
/// beginning with a ':' character) use the protected <see cref="Control.PseudoClasses"/>
/// beginning with a ':' character) use the protected <see cref="StyledElement.PseudoClasses"/>
/// property.
/// </remarks>
public override void Insert(int index, string name)
@ -134,7 +134,7 @@ namespace Avalonia.Controls
/// <param name="names">The class names.</param>
/// <remarks>
/// Only standard classes may be added via this method. To add pseudoclasses (classes
/// beginning with a ':' character) use the protected <see cref="Control.PseudoClasses"/>
/// beginning with a ':' character) use the protected <see cref="StyledElement.PseudoClasses"/>
/// property.
/// </remarks>
public override void InsertRange(int index, IEnumerable<string> names)
@ -160,7 +160,7 @@ namespace Avalonia.Controls
/// <param name="name">The class name.</param>
/// <remarks>
/// Only standard classes may be removed via this method. To remove pseudoclasses (classes
/// beginning with a ':' character) use the protected <see cref="Control.PseudoClasses"/>
/// beginning with a ':' character) use the protected <see cref="StyledElement.PseudoClasses"/>
/// property.
/// </remarks>
public override bool Remove(string name)
@ -175,7 +175,7 @@ namespace Avalonia.Controls
/// <param name="names">The class name.</param>
/// <remarks>
/// Only standard classes may be removed via this method. To remove pseudoclasses (classes
/// beginning with a ':' character) use the protected <see cref="Control.PseudoClasses"/>
/// beginning with a ':' character) use the protected <see cref="StyledElement.PseudoClasses"/>
/// property.
/// </remarks>
public override void RemoveAll(IEnumerable<string> names)
@ -201,7 +201,7 @@ namespace Avalonia.Controls
/// <param name="index">The index of the class in the collection.</param>
/// <remarks>
/// Only standard classes may be removed via this method. To remove pseudoclasses (classes
/// beginning with a ':' character) use the protected <see cref="Control.PseudoClasses"/>
/// beginning with a ':' character) use the protected <see cref="StyledElement.PseudoClasses"/>
/// property.
/// </remarks>
public override void RemoveAt(int index)

0
src/Avalonia.Controls/IPseudoClasses.cs → src/Avalonia.Styling/Controls/IPseudoClasses.cs

2
src/Avalonia.Controls/ISetInheritanceParent.cs → src/Avalonia.Styling/Controls/ISetInheritanceParent.cs

@ -4,7 +4,7 @@
namespace Avalonia.Controls
{
/// <summary>
/// Defines an interface through which a <see cref="Control"/>'s inheritance parent can be set.
/// Defines an interface through which a <see cref="StyledElement"/>'s inheritance parent can be set.
/// </summary>
/// <remarks>
/// You should not usually need to use this interface - it is for advanced scenarios only.

2
src/Avalonia.Controls/ISetLogicalParent.cs → src/Avalonia.Styling/Controls/ISetLogicalParent.cs

@ -6,7 +6,7 @@ using Avalonia.LogicalTree;
namespace Avalonia.Controls
{
/// <summary>
/// Defines an interface through which a <see cref="Control"/>'s logical parent can be set.
/// Defines an interface through which a <see cref="StyledElement"/>'s logical parent can be set.
/// </summary>
/// <remarks>
/// You should not usually need to use this interface - it is for advanced scenarios only.

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

Loading…
Cancel
Save