diff --git a/.gitmodules b/.gitmodules index 98b6d076c1..2d2a9ac497 100644 --- a/.gitmodules +++ b/.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 diff --git a/Avalonia.sln b/Avalonia.sln index 47ee4c1ad9..7b4a359f06 100644 --- a/Avalonia.sln +++ b/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} diff --git a/build.cake b/build.cake index 20d77e25d3..aa35d88aac 100644 --- a/build.cake +++ b/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"); diff --git a/build/Binding.props b/build/Binding.props new file mode 100644 index 0000000000..a512ee1d9c --- /dev/null +++ b/build/Binding.props @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/packages.cake b/packages.cake index e996544b68..2ad09b5eae 100644 --- a/packages.cake +++ b/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() diff --git a/samples/ControlCatalog.Android/ControlCatalog.Android.csproj b/samples/ControlCatalog.Android/ControlCatalog.Android.csproj index 2a91d6a3da..fab8ecd768 100644 --- a/samples/ControlCatalog.Android/ControlCatalog.Android.csproj +++ b/samples/ControlCatalog.Android/ControlCatalog.Android.csproj @@ -111,10 +111,6 @@ {7062ae20-5dcc-4442-9645-8195bdece63e} Avalonia.Diagnostics - - {5fb2b005-0a7f-4dad-add4-3ed01444e63d} - Avalonia.HtmlRenderer - {62024b2d-53eb-4638-b26b-85eeaa54866e} Avalonia.Input diff --git a/samples/ControlCatalog.iOS/ControlCatalog.iOS.csproj b/samples/ControlCatalog.iOS/ControlCatalog.iOS.csproj index c1c5cdcaf7..a60fd242e4 100644 --- a/samples/ControlCatalog.iOS/ControlCatalog.iOS.csproj +++ b/samples/ControlCatalog.iOS/ControlCatalog.iOS.csproj @@ -141,10 +141,6 @@ {7062AE20-5DCC-4442-9645-8195BDECE63E} Avalonia.Diagnostics - - {5FB2B005-0A7F-4DAD-ADD4-3ED01444E63D} - Avalonia.HtmlRenderer - {62024B2D-53EB-4638-B26B-85EEAA54866E} Avalonia.Input diff --git a/samples/ControlCatalog/ControlCatalog.csproj b/samples/ControlCatalog/ControlCatalog.csproj index c37002ef6a..dea9b35e24 100644 --- a/samples/ControlCatalog/ControlCatalog.csproj +++ b/samples/ControlCatalog/ControlCatalog.csproj @@ -25,7 +25,6 @@ - diff --git a/samples/ControlCatalog/Pages/AutoCompleteBoxPage.xaml.cs b/samples/ControlCatalog/Pages/AutoCompleteBoxPage.xaml.cs index 6f3b8361cd..f9d6a72a3a 100644 --- a/samples/ControlCatalog/Pages/AutoCompleteBoxPage.xaml.cs +++ b/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 { diff --git a/samples/Previewer/Previewer.csproj b/samples/Previewer/Previewer.csproj index 13e2418d67..b4dda473f2 100644 --- a/samples/Previewer/Previewer.csproj +++ b/samples/Previewer/Previewer.csproj @@ -15,7 +15,6 @@ - diff --git a/samples/RenderTest/Pages/AnimationsPage.xaml b/samples/RenderTest/Pages/AnimationsPage.xaml index 415c08728f..5287e4e373 100644 --- a/samples/RenderTest/Pages/AnimationsPage.xaml +++ b/samples/RenderTest/Pages/AnimationsPage.xaml @@ -48,13 +48,13 @@ PlaybackDirection="AlternateReverse" Easing="SineEaseInOut"> - + - + - + @@ -63,8 +63,8 @@ - - + + @@ -76,8 +76,8 @@ Easing="QuadraticEaseInOut" RepeatCount="Loop"> - - + + @@ -86,7 +86,7 @@ - + @@ -95,10 +95,10 @@ - + - + diff --git a/samples/RenderTest/Pages/ClippingPage.xaml b/samples/RenderTest/Pages/ClippingPage.xaml index 434468888a..2e7944b0fc 100644 --- a/samples/RenderTest/Pages/ClippingPage.xaml +++ b/samples/RenderTest/Pages/ClippingPage.xaml @@ -10,7 +10,7 @@ - + diff --git a/src/Android/Avalonia.Android/Resources/Resource.Designer.cs b/src/Android/Avalonia.Android/Resources/Resource.Designer.cs index 80cbbc51ec..e66c2800d3 100644 --- a/src/Android/Avalonia.Android/Resources/Resource.Designer.cs +++ b/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() { diff --git a/src/Android/Avalonia.AndroidTestApplication/Avalonia.AndroidTestApplication.csproj b/src/Android/Avalonia.AndroidTestApplication/Avalonia.AndroidTestApplication.csproj index 9977d77978..359adaa1f0 100644 --- a/src/Android/Avalonia.AndroidTestApplication/Avalonia.AndroidTestApplication.csproj +++ b/src/Android/Avalonia.AndroidTestApplication/Avalonia.AndroidTestApplication.csproj @@ -143,10 +143,6 @@ {3e10a5fa-e8da-48b1-ad44-6a5b6cb7750f} Avalonia.Themes.Default - - {5fb2b005-0a7f-4dad-add4-3ed01444e63d} - Avalonia.HtmlRenderer - {7d2d3083-71dd-4cc9-8907-39a0d86fb322} Avalonia.Skia diff --git a/src/Avalonia.Animation/Animation.cs b/src/Avalonia.Animation/Animation.cs index 525be53fc2..aa436f5f4e 100644 --- a/src/Avalonia.Animation/Animation.cs +++ b/src/Avalonia.Animation/Animation.cs @@ -18,6 +18,28 @@ namespace Avalonia.Animation /// public class Animation : AvaloniaList, IDisposable, IAnimation { + private readonly static List<(Func Condition, Type Animator)> Animators = new List<(Func, Type)> + { + ( prop => typeof(double).IsAssignableFrom(prop.PropertyType), typeof(DoubleAnimator) ) + }; + + public static void RegisterAnimator(Func 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 _subscription = new List(); @@ -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)); diff --git a/src/Avalonia.Animation/AnimationSetter.cs b/src/Avalonia.Animation/AnimationSetter.cs deleted file mode 100644 index 1194e4606b..0000000000 --- a/src/Avalonia.Animation/AnimationSetter.cs +++ /dev/null @@ -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; } - } -} diff --git a/src/Avalonia.Animation/AnimatorAttribute.cs b/src/Avalonia.Animation/AnimatorAttribute.cs deleted file mode 100644 index 58ee823a87..0000000000 --- a/src/Avalonia.Animation/AnimatorAttribute.cs +++ /dev/null @@ -1,18 +0,0 @@ -using System; - -namespace Avalonia.Animation -{ - /// - /// Attribute for objects - /// that maps the setter to it's . - /// - public class AnimatorAttribute : Attribute - { - public Type HandlerType; - - public AnimatorAttribute(Type handler) - { - this.HandlerType = handler; - } - } -} \ No newline at end of file diff --git a/src/Avalonia.Animation/DoubleSetter.cs b/src/Avalonia.Animation/DoubleSetter.cs deleted file mode 100644 index e524324a8c..0000000000 --- a/src/Avalonia.Animation/DoubleSetter.cs +++ /dev/null @@ -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 -{ - /// - /// Setter that handles properties - /// in the target. - /// - [Animator(typeof(DoubleAnimator))] - public class DoubleSetter : AnimationSetter - { - - } -} \ No newline at end of file diff --git a/src/Avalonia.Base/Avalonia.Base.csproj b/src/Avalonia.Base/Avalonia.Base.csproj index 35adcbeb92..26397a6f32 100644 --- a/src/Avalonia.Base/Avalonia.Base.csproj +++ b/src/Avalonia.Base/Avalonia.Base.csproj @@ -5,6 +5,7 @@ Avalonia + \ No newline at end of file diff --git a/src/Avalonia.Base/AvaloniaObject.cs b/src/Avalonia.Base/AvaloniaObject.cs index 1a2db9fc3d..48e72db126 100644 --- a/src/Avalonia.Base/AvaloniaObject.cs +++ b/src/Avalonia.Base/AvaloniaObject.cs @@ -703,7 +703,7 @@ namespace Avalonia /// The default value. 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()); } diff --git a/src/Avalonia.Base/Collections/AvaloniaDictionary.cs b/src/Avalonia.Base/Collections/AvaloniaDictionary.cs index 84ac85d3db..e8dc2a5ed7 100644 --- a/src/Avalonia.Base/Collections/AvaloniaDictionary.cs +++ b/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(); - 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) diff --git a/src/Avalonia.Base/Data/BindingNotification.cs b/src/Avalonia.Base/Data/BindingNotification.cs index 5510a73b91..5d3e6b26f4 100644 --- a/src/Avalonia.Base/Data/BindingNotification.cs +++ b/src/Avalonia.Base/Data/BindingNotification.cs @@ -171,8 +171,7 @@ namespace Avalonia.Data /// 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; } /// diff --git a/src/Markup/Avalonia.Markup/AlwaysEnabledDelegateCommand.cs b/src/Avalonia.Base/Data/Converters/AlwaysEnabledDelegateCommand.cs similarity index 97% rename from src/Markup/Avalonia.Markup/AlwaysEnabledDelegateCommand.cs rename to src/Avalonia.Base/Data/Converters/AlwaysEnabledDelegateCommand.cs index 48db99b43c..d2fb48ffb8 100644 --- a/src/Markup/Avalonia.Markup/AlwaysEnabledDelegateCommand.cs +++ b/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 { diff --git a/src/Markup/Avalonia.Markup/BoolConverters.cs b/src/Avalonia.Base/Data/Converters/BoolConverters.cs similarity index 94% rename from src/Markup/Avalonia.Markup/BoolConverters.cs rename to src/Avalonia.Base/Data/Converters/BoolConverters.cs index e049dccc06..6b429e1087 100644 --- a/src/Markup/Avalonia.Markup/BoolConverters.cs +++ b/src/Avalonia.Base/Data/Converters/BoolConverters.cs @@ -3,7 +3,7 @@ using System.Linq; -namespace Avalonia.Markup +namespace Avalonia.Data.Converters { /// /// Provides a set of useful s for working with string values. diff --git a/src/Markup/Avalonia.Markup/DefaultValueConverter.cs b/src/Avalonia.Base/Data/Converters/DefaultValueConverter.cs similarity index 98% rename from src/Markup/Avalonia.Markup/DefaultValueConverter.cs rename to src/Avalonia.Base/Data/Converters/DefaultValueConverter.cs index 28d64eb561..ec75076892 100644 --- a/src/Markup/Avalonia.Markup/DefaultValueConverter.cs +++ b/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 { /// /// Provides a default set of value conversions for bindings that do not specify a value diff --git a/src/Markup/Avalonia.Markup/FuncMultiValueConverter.cs b/src/Avalonia.Base/Data/Converters/FuncMultiValueConverter.cs similarity index 97% rename from src/Markup/Avalonia.Markup/FuncMultiValueConverter.cs rename to src/Avalonia.Base/Data/Converters/FuncMultiValueConverter.cs index 75aace0bd9..6e1c4cb0e3 100644 --- a/src/Markup/Avalonia.Markup/FuncMultiValueConverter.cs +++ b/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 { /// /// A general purpose that uses a diff --git a/src/Markup/Avalonia.Markup/FuncValueConverter.cs b/src/Avalonia.Base/Data/Converters/FuncValueConverter.cs similarity index 97% rename from src/Markup/Avalonia.Markup/FuncValueConverter.cs rename to src/Avalonia.Base/Data/Converters/FuncValueConverter.cs index 109de9371d..b747587b4a 100644 --- a/src/Markup/Avalonia.Markup/FuncValueConverter.cs +++ b/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 { /// /// A general purpose that uses a diff --git a/src/Markup/Avalonia.Markup/IMultiValueConverter.cs b/src/Avalonia.Base/Data/Converters/IMultiValueConverter.cs similarity index 97% rename from src/Markup/Avalonia.Markup/IMultiValueConverter.cs rename to src/Avalonia.Base/Data/Converters/IMultiValueConverter.cs index be75dabb1c..3f84fcb3e7 100644 --- a/src/Markup/Avalonia.Markup/IMultiValueConverter.cs +++ b/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 { /// /// Converts multi-binding inputs to a final value. diff --git a/src/Markup/Avalonia.Markup/IValueConverter.cs b/src/Avalonia.Base/Data/Converters/IValueConverter.cs similarity index 98% rename from src/Markup/Avalonia.Markup/IValueConverter.cs rename to src/Avalonia.Base/Data/Converters/IValueConverter.cs index 10d5c626c2..b55a2c4fe8 100644 --- a/src/Markup/Avalonia.Markup/IValueConverter.cs +++ b/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 { /// /// Converts a binding value. diff --git a/src/Markup/Avalonia.Markup/StringConverters.cs b/src/Avalonia.Base/Data/Converters/StringConverters.cs similarity index 96% rename from src/Markup/Avalonia.Markup/StringConverters.cs rename to src/Avalonia.Base/Data/Converters/StringConverters.cs index fda79c76a3..470f0d2289 100644 --- a/src/Markup/Avalonia.Markup/StringConverters.cs +++ b/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 { /// /// Provides a set of useful s for working with string values. diff --git a/src/Markup/Avalonia.Markup/Data/BindingExpression.cs b/src/Avalonia.Base/Data/Core/BindingExpression.cs similarity index 99% rename from src/Markup/Avalonia.Markup/Data/BindingExpression.cs rename to src/Avalonia.Base/Data/Core/BindingExpression.cs index 5b9959e42e..4b41d1568c 100644 --- a/src/Markup/Avalonia.Markup/Data/BindingExpression.cs +++ b/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 { /// /// Binds to an expression on an object using a type value converter to convert the values diff --git a/src/Markup/Avalonia.Markup/Data/CommonPropertyNames.cs b/src/Avalonia.Base/Data/Core/CommonPropertyNames.cs similarity index 89% rename from src/Markup/Avalonia.Markup/Data/CommonPropertyNames.cs rename to src/Avalonia.Base/Data/Core/CommonPropertyNames.cs index f91940baf7..6760c3f259 100644 --- a/src/Markup/Avalonia.Markup/Data/CommonPropertyNames.cs +++ b/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 { diff --git a/src/Markup/Avalonia.Markup/Data/EmptyExpressionNode.cs b/src/Avalonia.Base/Data/Core/EmptyExpressionNode.cs similarity index 94% rename from src/Markup/Avalonia.Markup/Data/EmptyExpressionNode.cs rename to src/Avalonia.Base/Data/Core/EmptyExpressionNode.cs index 02ecd817da..93e0d5947a 100644 --- a/src/Markup/Avalonia.Markup/Data/EmptyExpressionNode.cs +++ b/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 { diff --git a/src/Markup/Avalonia.Markup/Data/ExpressionNode.cs b/src/Avalonia.Base/Data/Core/ExpressionNode.cs similarity index 99% rename from src/Markup/Avalonia.Markup/Data/ExpressionNode.cs rename to src/Avalonia.Base/Data/Core/ExpressionNode.cs index 56c0072eaa..ae70cacdba 100644 --- a/src/Markup/Avalonia.Markup/Data/ExpressionNode.cs +++ b/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 { diff --git a/src/Markup/Avalonia.Markup/Data/ExpressionNodeBuilder.cs b/src/Avalonia.Base/Data/Core/ExpressionNodeBuilder.cs similarity index 92% rename from src/Markup/Avalonia.Markup/Data/ExpressionNodeBuilder.cs rename to src/Avalonia.Base/Data/Core/ExpressionNodeBuilder.cs index 013299c1d7..8e9e9fc3c1 100644 --- a/src/Markup/Avalonia.Markup/Data/ExpressionNodeBuilder.cs +++ b/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 { diff --git a/src/Markup/Avalonia.Markup/Data/ExpressionObserver.cs b/src/Avalonia.Base/Data/Core/ExpressionObserver.cs similarity index 90% rename from src/Markup/Avalonia.Markup/Data/ExpressionObserver.cs rename to src/Avalonia.Base/Data/Core/ExpressionObserver.cs index dd9718a0f6..7719f93a02 100644 --- a/src/Markup/Avalonia.Markup/Data/ExpressionObserver.cs +++ b/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 { /// /// 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; - - 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 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; } } } diff --git a/src/Markup/Avalonia.Markup/Data/ExpressionParseException.cs b/src/Avalonia.Base/Data/Core/ExpressionParseException.cs similarity index 93% rename from src/Markup/Avalonia.Markup/Data/ExpressionParseException.cs rename to src/Avalonia.Base/Data/Core/ExpressionParseException.cs index d06bdd1e52..3d7bce4080 100644 --- a/src/Markup/Avalonia.Markup/Data/ExpressionParseException.cs +++ b/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 { /// /// Exception thrown when could not parse the provided diff --git a/src/Markup/Avalonia.Markup/Data/ISettableNode.cs b/src/Avalonia.Base/Data/Core/ISettableNode.cs similarity index 90% rename from src/Markup/Avalonia.Markup/Data/ISettableNode.cs rename to src/Avalonia.Base/Data/Core/ISettableNode.cs index 8ee4f1de20..7788407833 100644 --- a/src/Markup/Avalonia.Markup/Data/ISettableNode.cs +++ b/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 { diff --git a/src/Markup/Avalonia.Markup/Data/ITransformNode.cs b/src/Avalonia.Base/Data/Core/ITransformNode.cs similarity index 83% rename from src/Markup/Avalonia.Markup/Data/ITransformNode.cs rename to src/Avalonia.Base/Data/Core/ITransformNode.cs index f33ecd3722..7638db8302 100644 --- a/src/Markup/Avalonia.Markup/Data/ITransformNode.cs +++ b/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 { diff --git a/src/Markup/Avalonia.Markup/Data/IndexerNode.cs b/src/Avalonia.Base/Data/Core/IndexerNode.cs similarity index 99% rename from src/Markup/Avalonia.Markup/Data/IndexerNode.cs rename to src/Avalonia.Base/Data/Core/IndexerNode.cs index 4e2914a148..47e82fa2d3 100644 --- a/src/Markup/Avalonia.Markup/Data/IndexerNode.cs +++ b/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 { diff --git a/src/Markup/Avalonia.Markup/Data/LogicalNotNode.cs b/src/Avalonia.Base/Data/Core/LogicalNotNode.cs similarity index 98% rename from src/Markup/Avalonia.Markup/Data/LogicalNotNode.cs rename to src/Avalonia.Base/Data/Core/LogicalNotNode.cs index ae68867e82..f277005cec 100644 --- a/src/Markup/Avalonia.Markup/Data/LogicalNotNode.cs +++ b/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 { diff --git a/src/Markup/Avalonia.Markup/Data/MarkupBindingChainException.cs b/src/Avalonia.Base/Data/Core/MarkupBindingChainException.cs similarity index 95% rename from src/Markup/Avalonia.Markup/Data/MarkupBindingChainException.cs rename to src/Avalonia.Base/Data/Core/MarkupBindingChainException.cs index ddfcf531eb..a9b31c7617 100644 --- a/src/Markup/Avalonia.Markup/Data/MarkupBindingChainException.cs +++ b/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 { diff --git a/src/Markup/Avalonia.Markup/Data/Parsers/ArgumentListParser.cs b/src/Avalonia.Base/Data/Core/Parsers/ArgumentListParser.cs similarity index 97% rename from src/Markup/Avalonia.Markup/Data/Parsers/ArgumentListParser.cs rename to src/Avalonia.Base/Data/Core/Parsers/ArgumentListParser.cs index 05b3b29be0..17200a62b1 100644 --- a/src/Markup/Avalonia.Markup/Data/Parsers/ArgumentListParser.cs +++ b/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 { diff --git a/src/Markup/Avalonia.Markup/Data/Parsers/ExpressionParser.cs b/src/Avalonia.Base/Data/Core/Parsers/ExpressionParser.cs similarity index 99% rename from src/Markup/Avalonia.Markup/Data/Parsers/ExpressionParser.cs rename to src/Avalonia.Base/Data/Core/Parsers/ExpressionParser.cs index f31f6eccb7..5c74c5cd13 100644 --- a/src/Markup/Avalonia.Markup/Data/Parsers/ExpressionParser.cs +++ b/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 { diff --git a/src/Markup/Avalonia.Markup/Data/Parsers/IdentifierParser.cs b/src/Avalonia.Base/Data/Core/Parsers/IdentifierParser.cs similarity index 97% rename from src/Markup/Avalonia.Markup/Data/Parsers/IdentifierParser.cs rename to src/Avalonia.Base/Data/Core/Parsers/IdentifierParser.cs index fa9051af06..b0a9ff4df2 100644 --- a/src/Markup/Avalonia.Markup/Data/Parsers/IdentifierParser.cs +++ b/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 { diff --git a/src/Markup/Avalonia.Markup/Data/Parsers/Reader.cs b/src/Avalonia.Base/Data/Core/Parsers/Reader.cs similarity index 95% rename from src/Markup/Avalonia.Markup/Data/Parsers/Reader.cs rename to src/Avalonia.Base/Data/Core/Parsers/Reader.cs index 414648a10c..14187c769a 100644 --- a/src/Markup/Avalonia.Markup/Data/Parsers/Reader.cs +++ b/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 { diff --git a/src/Markup/Avalonia.Markup/Data/Plugins/AvaloniaPropertyAccessorPlugin.cs b/src/Avalonia.Base/Data/Core/Plugins/AvaloniaPropertyAccessorPlugin.cs similarity index 99% rename from src/Markup/Avalonia.Markup/Data/Plugins/AvaloniaPropertyAccessorPlugin.cs rename to src/Avalonia.Base/Data/Core/Plugins/AvaloniaPropertyAccessorPlugin.cs index ac64459dd7..8cbcaa8233 100644 --- a/src/Markup/Avalonia.Markup/Data/Plugins/AvaloniaPropertyAccessorPlugin.cs +++ b/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 { /// /// Reads a property from a . diff --git a/src/Markup/Avalonia.Markup/Data/Plugins/DataAnnotationsValidationPlugin.cs b/src/Avalonia.Base/Data/Core/Plugins/DataAnnotationsValidationPlugin.cs similarity index 98% rename from src/Markup/Avalonia.Markup/Data/Plugins/DataAnnotationsValidationPlugin.cs rename to src/Avalonia.Base/Data/Core/Plugins/DataAnnotationsValidationPlugin.cs index 859438636a..47e4d91e13 100644 --- a/src/Markup/Avalonia.Markup/Data/Plugins/DataAnnotationsValidationPlugin.cs +++ b/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 { /// /// Validates properties on that have s. diff --git a/src/Markup/Avalonia.Markup/Data/Plugins/DataValidatiorBase.cs b/src/Avalonia.Base/Data/Core/Plugins/DataValidatiorBase.cs similarity index 98% rename from src/Markup/Avalonia.Markup/Data/Plugins/DataValidatiorBase.cs rename to src/Avalonia.Base/Data/Core/Plugins/DataValidatiorBase.cs index 95d269f437..bd429f04d6 100644 --- a/src/Markup/Avalonia.Markup/Data/Plugins/DataValidatiorBase.cs +++ b/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 { /// /// Base class for data validators. diff --git a/src/Markup/Avalonia.Markup/Data/Plugins/ExceptionValidationPlugin.cs b/src/Avalonia.Base/Data/Core/Plugins/ExceptionValidationPlugin.cs similarity index 97% rename from src/Markup/Avalonia.Markup/Data/Plugins/ExceptionValidationPlugin.cs rename to src/Avalonia.Base/Data/Core/Plugins/ExceptionValidationPlugin.cs index e0b6bcfd7c..35f9f7e59a 100644 --- a/src/Markup/Avalonia.Markup/Data/Plugins/ExceptionValidationPlugin.cs +++ b/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 { /// /// Validates properties that report errors by throwing exceptions. diff --git a/src/Markup/Avalonia.Markup/Data/Plugins/IDataValidationPlugin.cs b/src/Avalonia.Base/Data/Core/Plugins/IDataValidationPlugin.cs similarity index 97% rename from src/Markup/Avalonia.Markup/Data/Plugins/IDataValidationPlugin.cs rename to src/Avalonia.Base/Data/Core/Plugins/IDataValidationPlugin.cs index 0952e2edab..c55917b088 100644 --- a/src/Markup/Avalonia.Markup/Data/Plugins/IDataValidationPlugin.cs +++ b/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 { /// /// Defines how data validation is observed by an . diff --git a/src/Markup/Avalonia.Markup/Data/Plugins/IPropertyAccessor.cs b/src/Avalonia.Base/Data/Core/Plugins/IPropertyAccessor.cs similarity index 97% rename from src/Markup/Avalonia.Markup/Data/Plugins/IPropertyAccessor.cs rename to src/Avalonia.Base/Data/Core/Plugins/IPropertyAccessor.cs index 9e686baf10..d7dda57a72 100644 --- a/src/Markup/Avalonia.Markup/Data/Plugins/IPropertyAccessor.cs +++ b/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 { /// /// Defines an accessor to a property on an object returned by a diff --git a/src/Markup/Avalonia.Markup/Data/Plugins/IPropertyAccessorPlugin.cs b/src/Avalonia.Base/Data/Core/Plugins/IPropertyAccessorPlugin.cs similarity index 97% rename from src/Markup/Avalonia.Markup/Data/Plugins/IPropertyAccessorPlugin.cs rename to src/Avalonia.Base/Data/Core/Plugins/IPropertyAccessorPlugin.cs index ebfdf6ebe4..539f518083 100644 --- a/src/Markup/Avalonia.Markup/Data/Plugins/IPropertyAccessorPlugin.cs +++ b/src/Avalonia.Base/Data/Core/Plugins/IPropertyAccessorPlugin.cs @@ -3,7 +3,7 @@ using System; -namespace Avalonia.Markup.Data.Plugins +namespace Avalonia.Data.Core.Plugins { /// /// Defines how a member is read, written and observed by an diff --git a/src/Markup/Avalonia.Markup/Data/Plugins/IStreamPlugin.cs b/src/Avalonia.Base/Data/Core/Plugins/IStreamPlugin.cs similarity index 96% rename from src/Markup/Avalonia.Markup/Data/Plugins/IStreamPlugin.cs rename to src/Avalonia.Base/Data/Core/Plugins/IStreamPlugin.cs index efb2e2d93a..b80d9d75c8 100644 --- a/src/Markup/Avalonia.Markup/Data/Plugins/IStreamPlugin.cs +++ b/src/Avalonia.Base/Data/Core/Plugins/IStreamPlugin.cs @@ -3,7 +3,7 @@ using System; -namespace Avalonia.Markup.Data.Plugins +namespace Avalonia.Data.Core.Plugins { /// /// Defines a plugin that handles the '^' stream binding operator. diff --git a/src/Markup/Avalonia.Markup/Data/Plugins/IndeiValidationPlugin.cs b/src/Avalonia.Base/Data/Core/Plugins/IndeiValidationPlugin.cs similarity index 99% rename from src/Markup/Avalonia.Markup/Data/Plugins/IndeiValidationPlugin.cs rename to src/Avalonia.Base/Data/Core/Plugins/IndeiValidationPlugin.cs index 82bc87c207..436046f3fa 100644 --- a/src/Markup/Avalonia.Markup/Data/Plugins/IndeiValidationPlugin.cs +++ b/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 { /// /// Validates properties on objects that implement . diff --git a/src/Markup/Avalonia.Markup/Data/Plugins/InpcPropertyAccessorPlugin.cs b/src/Avalonia.Base/Data/Core/Plugins/InpcPropertyAccessorPlugin.cs similarity index 99% rename from src/Markup/Avalonia.Markup/Data/Plugins/InpcPropertyAccessorPlugin.cs rename to src/Avalonia.Base/Data/Core/Plugins/InpcPropertyAccessorPlugin.cs index 3bdaba6fd9..ba4e60eb74 100644 --- a/src/Markup/Avalonia.Markup/Data/Plugins/InpcPropertyAccessorPlugin.cs +++ b/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 { /// /// Reads a property from a standard C# object that optionally supports the diff --git a/src/Markup/Avalonia.Markup/Data/Plugins/MethodAccessorPlugin.cs b/src/Avalonia.Base/Data/Core/Plugins/MethodAccessorPlugin.cs similarity index 98% rename from src/Markup/Avalonia.Markup/Data/Plugins/MethodAccessorPlugin.cs rename to src/Avalonia.Base/Data/Core/Plugins/MethodAccessorPlugin.cs index db0d3e0299..b2b3a107fa 100644 --- a/src/Markup/Avalonia.Markup/Data/Plugins/MethodAccessorPlugin.cs +++ b/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 { diff --git a/src/Markup/Avalonia.Markup/Data/Plugins/ObservableStreamPlugin.cs b/src/Avalonia.Base/Data/Core/Plugins/ObservableStreamPlugin.cs similarity index 96% rename from src/Markup/Avalonia.Markup/Data/Plugins/ObservableStreamPlugin.cs rename to src/Avalonia.Base/Data/Core/Plugins/ObservableStreamPlugin.cs index a1da42d28f..14ca8ee79e 100644 --- a/src/Markup/Avalonia.Markup/Data/Plugins/ObservableStreamPlugin.cs +++ b/src/Avalonia.Base/Data/Core/Plugins/ObservableStreamPlugin.cs @@ -3,7 +3,7 @@ using System; -namespace Avalonia.Markup.Data.Plugins +namespace Avalonia.Data.Core.Plugins { /// /// Handles binding to s for the '^' stream binding operator. diff --git a/src/Markup/Avalonia.Markup/Data/Plugins/PropertyAccessorBase.cs b/src/Avalonia.Base/Data/Core/Plugins/PropertyAccessorBase.cs similarity index 98% rename from src/Markup/Avalonia.Markup/Data/Plugins/PropertyAccessorBase.cs rename to src/Avalonia.Base/Data/Core/Plugins/PropertyAccessorBase.cs index 9aa858e0eb..9cc78369a7 100644 --- a/src/Markup/Avalonia.Markup/Data/Plugins/PropertyAccessorBase.cs +++ b/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 { /// /// Defines a default base implementation for a . diff --git a/src/Markup/Avalonia.Markup/Data/Plugins/PropertyError.cs b/src/Avalonia.Base/Data/Core/Plugins/PropertyError.cs similarity index 96% rename from src/Markup/Avalonia.Markup/Data/Plugins/PropertyError.cs rename to src/Avalonia.Base/Data/Core/Plugins/PropertyError.cs index b351ef39bd..647adc36cb 100644 --- a/src/Markup/Avalonia.Markup/Data/Plugins/PropertyError.cs +++ b/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 { /// /// An that represents an error. diff --git a/src/Markup/Avalonia.Markup/Data/Plugins/TaskStreamPlugin.cs b/src/Avalonia.Base/Data/Core/Plugins/TaskStreamPlugin.cs similarity index 98% rename from src/Markup/Avalonia.Markup/Data/Plugins/TaskStreamPlugin.cs rename to src/Avalonia.Base/Data/Core/Plugins/TaskStreamPlugin.cs index 02fe8104b8..cc9b3abd56 100644 --- a/src/Markup/Avalonia.Markup/Data/Plugins/TaskStreamPlugin.cs +++ b/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 { /// /// Handles binding to s for the '^' stream binding operator. diff --git a/src/Markup/Avalonia.Markup/Data/PropertyAccessorNode.cs b/src/Avalonia.Base/Data/Core/PropertyAccessorNode.cs similarity index 96% rename from src/Markup/Avalonia.Markup/Data/PropertyAccessorNode.cs rename to src/Avalonia.Base/Data/Core/PropertyAccessorNode.cs index f6040d3f15..4dbff4602f 100644 --- a/src/Markup/Avalonia.Markup/Data/PropertyAccessorNode.cs +++ b/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 { diff --git a/src/Markup/Avalonia.Markup/Data/StreamNode.cs b/src/Avalonia.Base/Data/Core/StreamNode.cs similarity index 96% rename from src/Markup/Avalonia.Markup/Data/StreamNode.cs rename to src/Avalonia.Base/Data/Core/StreamNode.cs index ebcbfc9598..187c79af49 100644 --- a/src/Markup/Avalonia.Markup/Data/StreamNode.cs +++ b/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 { diff --git a/src/Avalonia.Base/Properties/AssemblyInfo.cs b/src/Avalonia.Base/Properties/AssemblyInfo.cs index 0a276aa2aa..75d58f45d5 100644 --- a/src/Avalonia.Base/Properties/AssemblyInfo.cs +++ b/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")] \ No newline at end of file diff --git a/src/Avalonia.Controls/Control.cs b/src/Avalonia.Controls/Control.cs index 89721e2e05..67288972b6 100644 --- a/src/Avalonia.Controls/Control.cs +++ b/src/Avalonia.Controls/Control.cs @@ -29,53 +29,22 @@ namespace Avalonia.Controls /// /// The control class extends and adds the following features: /// - /// - An inherited . /// - A property to allow user-defined data to be attached to the control. - /// - A collection of class strings for custom styling. - /// - Implements to allow styling to work on the control. - /// - Implements to form part of a logical tree. /// - public class Control : InputElement, IControl, INamed, ISetInheritanceParent, ISetLogicalParent, ISupportInitialize, IVisualBrushInitialize + public class Control : InputElement, IControl, INamed, ISupportInitialize, IVisualBrushInitialize, IRequiresTemplateInSetter { - /// - /// Defines the property. - /// - public static readonly StyledProperty DataContextProperty = - AvaloniaProperty.Register( - nameof(DataContext), - inherits: true, - notifying: DataContextNotifying); - /// /// Defines the property. /// public static readonly StyledProperty> FocusAdornerProperty = AvaloniaProperty.Register>(nameof(FocusAdorner)); - /// - /// Defines the property. - /// - public static readonly DirectProperty NameProperty = - AvaloniaProperty.RegisterDirect(nameof(Name), o => o.Name, (o, v) => o.Name = v); - - /// - /// Defines the property. - /// - public static readonly DirectProperty ParentProperty = - AvaloniaProperty.RegisterDirect(nameof(Parent), o => o.Parent); - /// /// Defines the property. /// public static readonly StyledProperty TagProperty = AvaloniaProperty.Register(nameof(Tag)); - - /// - /// Defines the property. - /// - public static readonly StyledProperty TemplatedParentProperty = - AvaloniaProperty.Register(nameof(TemplatedParent), inherits: true); - + /// /// Defines the property. /// @@ -88,152 +57,8 @@ namespace Avalonia.Controls public static readonly RoutedEvent RequestBringIntoViewEvent = RoutedEvent.Register("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 _logicalChildren; - private INameScope _nameScope; - private IResourceDictionary _resources; - private Styles _styles; - private bool _styled; - private Subject _styleDetach = new Subject(); - private bool _dataContextUpdating; - - /// - /// Initializes static members of the class. - /// - static Control() - { - AffectsMeasure(IsVisibleProperty); - PseudoClass(IsEnabledCoreProperty, x => !x, ":disabled"); - PseudoClass(IsFocusedProperty, ":focus"); - PseudoClass(IsPointerOverProperty, ":pointerover"); - DataContextProperty.Changed.AddClassHandler(x => x.OnDataContextChangedCore); - } - - /// - /// Initializes a new instance of the class. - /// - public Control() - { - _nameScope = this as INameScope; - _isAttachedToLogicalTree = this is IStyleRoot; - } - - /// - /// Raised when the control is attached to a rooted logical tree. - /// - public event EventHandler AttachedToLogicalTree; - - /// - /// Raised when the control is detached from a rooted logical tree. - /// - public event EventHandler DetachedFromLogicalTree; - - /// - /// Occurs when the property changes. - /// - /// - /// This event will be raised when the property has changed and - /// all subscribers to that change have been notified. - /// - public event EventHandler DataContextChanged; - - /// - /// Occurs when the control has finished initialization. - /// - /// - /// The Initialized event indicates that all property values on the control have been set. - /// When loading the control from markup, it occurs when - /// is called *and* the control - /// is attached to a rooted logical tree. When the control is created by code and - /// is not used, it is called when the control is attached - /// to the visual tree. - /// - public event EventHandler Initialized; - - /// - /// Occurs when a resource in this control or a parent control has changed. - /// - public event EventHandler ResourcesChanged; - - /// - /// Gets or sets the name of the control. - /// - /// - /// 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. - /// - 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; - } - } - - /// - /// Gets or sets the control's classes. - /// - /// - /// - /// Classes can be used to apply user-defined styling to controls, or to allow controls - /// that share a common purpose to be easily selected. - /// - /// - /// 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. - /// - /// - public Classes Classes - { - get - { - return _classes; - } - - set - { - if (_classes != value) - { - _classes.Replace(value); - } - } - } - - /// - /// Gets or sets the control's data context. - /// - /// - /// The data context is an inherited property that specifies the default object that will - /// be used for data binding. - /// - public object DataContext - { - get { return GetValue(DataContextProperty); } - set { SetValue(DataContextProperty, value); } - } /// /// Gets or sets the control's focus adorner. @@ -253,55 +78,6 @@ namespace Avalonia.Controls /// public DataTemplates DataTemplates => _dataTemplates ?? (_dataTemplates = new DataTemplates()); - /// - /// Gets a value that indicates whether the element has finished initialization. - /// - /// - /// For more information about when IsInitialized is set, see the - /// event. - /// - public bool IsInitialized { get; private set; } - - /// - /// Gets the styles for the control. - /// - /// - /// 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. - /// - public Styles Styles - { - get { return _styles ?? (Styles = new Styles()); } - set - { - Contract.Requires(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; - } - } - } - - /// - /// Gets the control's logical parent. - /// - public IControl Parent => _parent; - /// /// Gets or sets a context menu to the control. /// @@ -311,34 +87,6 @@ namespace Avalonia.Controls set { SetValue(ContextMenuProperty, value); } } - /// - /// Gets or sets the control's resource dictionary. - /// - public IResourceDictionary Resources - { - get => _resources ?? (Resources = new ResourceDictionary()); - set - { - Contract.Requires(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()); - } - } - } - /// /// Gets or sets a user-defined object attached to the control. /// @@ -348,226 +96,11 @@ namespace Avalonia.Controls set { SetValue(TagProperty, value); } } - /// - /// Gets the control whose lookless template this control is part of. - /// - public ITemplatedControl TemplatedParent - { - get { return GetValue(TemplatedParentProperty); } - internal set { SetValue(TemplatedParentProperty, value); } - } - - /// - /// Gets the control's logical children. - /// - protected IAvaloniaList LogicalChildren - { - get - { - if (_logicalChildren == null) - { - var list = new AvaloniaList(); - list.ResetBehavior = ResetBehavior.Remove; - list.Validate = ValidateLogicalChild; - list.CollectionChanged += LogicalChildrenCollectionChanged; - _logicalChildren = list; - } - - return _logicalChildren; - } - } + public new IControl Parent => (IControl)base.Parent; /// bool IDataTemplateHost.IsDataTemplatesInitialized => _dataTemplates != null; - /// - /// Gets the collection in a form that allows adding and removing - /// pseudoclasses. - /// - protected IPseudoClasses PseudoClasses => Classes; - - /// - /// Gets a value indicating whether the element is attached to a rooted logical tree. - /// - bool ILogical.IsAttachedToLogicalTree => _isAttachedToLogicalTree; - - /// - /// Gets the control's logical parent. - /// - ILogical ILogical.LogicalParent => Parent; - - /// - /// Gets the control's logical children. - /// - IAvaloniaReadOnlyList ILogical.LogicalChildren => LogicalChildren; - - /// - bool IResourceProvider.HasResources => _resources?.Count > 0 || Styles.HasResources; - - /// - IResourceNode IResourceNode.ResourceParent => ((IStyleHost)this).StylingParent as IResourceNode; - - /// - IAvaloniaReadOnlyList IStyleable.Classes => Classes; - - /// - /// Gets the type by which the control is styled. - /// - /// - /// 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. - /// - Type IStyleable.StyleKey => GetType(); - - /// - IObservable IStyleable.StyleDetach => _styleDetach; - - /// - bool IStyleHost.IsStylesInitialized => _styles != null; - - /// - IStyleHost IStyleHost.StylingParent => (IStyleHost)InheritanceParent; - - /// - public virtual void BeginInit() - { - ++_initCount; - } - - /// - 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); - } - } - - /// - void ILogical.NotifyAttachedToLogicalTree(LogicalTreeAttachmentEventArgs e) - { - this.OnAttachedToLogicalTreeCore(e); - } - - /// - void ILogical.NotifyDetachedFromLogicalTree(LogicalTreeAttachmentEventArgs e) - { - this.OnDetachedFromLogicalTreeCore(e); - } - - /// - void ILogical.NotifyResourcesChanged(ResourcesChangedEventArgs e) - { - ResourcesChanged?.Invoke(this, new ResourcesChangedEventArgs()); - } - - /// - bool IResourceProvider.TryGetResource(string key, out object value) - { - value = null; - return (_resources?.TryGetResource(key, out value) ?? false) || - (_styles?.TryGetResource(key, out value) ?? false); - } - - /// - /// Sets the control's logical parent. - /// - /// The parent. - 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); - } - } - - /// - /// Sets the control's inheritance parent. - /// - /// The parent. - void ISetInheritanceParent.SetParent(IAvaloniaObject parent) - { - InheritanceParent = parent; - } - /// void IVisualBrushInitialize.EnsureInitialized() { @@ -600,52 +133,6 @@ namespace Avalonia.Controls } } - /// - /// Adds a pseudo-class to be set when a property is true. - /// - /// The property. - /// The pseudo-class. - protected static void PseudoClass(AvaloniaProperty property, string className) - { - PseudoClass(property, x => x, className); - } - - /// - /// Adds a pseudo-class to be set when a property equals a certain value. - /// - /// The type of the property. - /// The property. - /// Returns a boolean value based on the property value. - /// The pseudo-class. - protected static void PseudoClass( - AvaloniaProperty property, - Func selector, - string className) - { - Contract.Requires(property != null); - Contract.Requires(selector != null); - Contract.Requires(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); - } - }); - } - /// /// Gets the element that recieves the focus adorner. /// @@ -655,22 +142,6 @@ namespace Avalonia.Controls return this; } - /// - /// Called when the control is added to a rooted logical tree. - /// - /// The event args. - protected virtual void OnAttachedToLogicalTree(LogicalTreeAttachmentEventArgs e) - { - } - - /// - /// Called when the control is removed from a rooted logical tree. - /// - /// The event args. - protected virtual void OnDetachedFromLogicalTree(LogicalTreeAttachmentEventArgs e) - { - } - /// protected sealed override void OnAttachedToVisualTreeCore(VisualTreeAttachmentEventArgs e) { @@ -685,29 +156,6 @@ namespace Avalonia.Controls base.OnDetachedFromVisualTreeCore(e); } - /// - /// Called when the property changes. - /// - /// The event args. - protected virtual void OnDataContextChanged(EventArgs e) - { - DataContextChanged?.Invoke(this, EventArgs.Empty); - } - - /// - /// Called when the begins updating. - /// - protected virtual void OnDataContextBeginUpdate() - { - } - - /// - /// Called when the finishes updating. - /// - protected virtual void OnDataContextEndUpdate() - { - } - /// 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()?.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()) - { - 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()) - { - 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()); - break; - - case NotifyCollectionChangedAction.Remove: - ClearLogicalParent(e.OldItems.Cast()); - break; - - case NotifyCollectionChangedAction.Replace: - ClearLogicalParent(e.OldItems.Cast()); - SetLogicalParent(e.NewItems.Cast()); - break; - - case NotifyCollectionChangedAction.Reset: - throw new NotSupportedException("Reset should not be signalled on LogicalChildren collection"); - } - } - - private void SetLogicalParent(IEnumerable children) - { - foreach (var i in children) - { - if (i.LogicalParent == null) - { - ((ISetLogicalParent)i).SetParent(this); - } - } - } - - private void ClearLogicalParent(IEnumerable 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); - } } } diff --git a/src/Avalonia.Controls/ControlExtensions.cs b/src/Avalonia.Controls/ControlExtensions.cs index 60a940627f..45a7554f1c 100644 --- a/src/Avalonia.Controls/ControlExtensions.cs +++ b/src/Avalonia.Controls/ControlExtensions.cs @@ -66,21 +66,6 @@ namespace Avalonia.Controls return nameScope.Find(name); } - /// - /// Finds the name scope for a control by searching up the logical tree. - /// - /// The control. - /// The control's name scope, or null if not found. - public static INameScope FindNameScope(this IControl control) - { - Contract.Requires(control != null); - - return control.GetSelfAndLogicalAncestors() - .OfType() - .Select(x => (x as INameScope) ?? NameScope.GetNameScope(x)) - .FirstOrDefault(x => x != null); - } - /// /// Adds or removes a pseudoclass depending on a boolean value. /// diff --git a/src/Avalonia.Controls/DropDown.cs b/src/Avalonia.Controls/DropDown.cs index 932179028e..5b7213257d 100644 --- a/src/Avalonia.Controls/DropDown.cs +++ b/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(true); SelectedItemProperty.Changed.AddClassHandler(x => x.SelectedItemChanged); + KeyDownEvent.AddClassHandler(x => x.OnKeyDown, Interactivity.RoutingStrategies.Tunnel); } /// @@ -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); - } - } - /// 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; + } + } + } } } diff --git a/src/Avalonia.Controls/DropDownItem.cs b/src/Avalonia.Controls/DropDownItem.cs index 1a5cbb5014..3fd80c4562 100644 --- a/src/Avalonia.Controls/DropDownItem.cs +++ b/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 { /// /// A selectable item in a . /// - public class DropDownItem : ListBoxItem + public class DropDownItem : ContentControl, ISelectable { + /// + /// Defines the property. + /// + public static readonly StyledProperty IsSelectedProperty = + AvaloniaProperty.Register(nameof(IsSelected)); + + /// + /// Initializes static members of the class. + /// + static DropDownItem() + { + FocusableProperty.OverrideDefaultValue(true); + IsFocusedProperty.Changed.Subscribe(x => + { + var sender = x.Sender as IControl; + + if (sender != null) + { + ((IPseudoClasses)sender.Classes).Set(":selected", (bool)x.NewValue); + } + }); + } + + /// + /// Gets or sets the selection state of the item. + /// + public bool IsSelected + { + get { return GetValue(IsSelectedProperty); } + set { SetValue(IsSelectedProperty, value); } + } } } diff --git a/src/Avalonia.Controls/HotkeyManager.cs b/src/Avalonia.Controls/HotkeyManager.cs index 35c9d6a9c5..a59fb86fb7 100644 --- a/src/Avalonia.Controls/HotkeyManager.cs +++ b/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(_control).Subscribe(OnParentChanged); } - private void OnParentChanged(IControl control) + private void OnParentChanged(TopLevel control) { Unregister(); - _root = (TopLevel) control; + _root = control; Register(); } diff --git a/src/Avalonia.Controls/IControl.cs b/src/Avalonia.Controls/IControl.cs index 36e09b2ea1..e7f2903249 100644 --- a/src/Avalonia.Controls/IControl.cs +++ b/src/Avalonia.Controls/IControl.cs @@ -16,37 +16,11 @@ namespace Avalonia.Controls /// public interface IControl : IVisual, IDataTemplateHost, - ILogical, ILayoutable, IInputElement, INamed, - IResourceNode, - IStyleable, - IStyleHost + IStyledElement { - /// - /// Occurs when the control has finished initialization. - /// - event EventHandler Initialized; - - /// - /// Gets or sets the control's styling classes. - /// - new Classes Classes { get; set; } - - /// - /// Gets or sets the control's data context. - /// - object DataContext { get; set; } - - /// - /// Gets a value that indicates whether the element has finished initialization. - /// - bool IsInitialized { get; } - - /// - /// Gets the control's logical parent. - /// - IControl Parent { get; } + new IControl Parent { get; } } } \ No newline at end of file diff --git a/src/Avalonia.Controls/Utils/AncestorFinder.cs b/src/Avalonia.Controls/Utils/AncestorFinder.cs index 7e6523261a..6ee284e05d 100644 --- a/src/Avalonia.Controls/Utils/AncestorFinder.cs +++ b/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 Observable => _subject; - private readonly Subject _subject = new Subject(); + public IObservable Observable => _subject; + private readonly Subject _subject = new Subject(); 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 Create(IStyledElement control) + where T : IStyledElement + { + return Create(control, typeof(T)).Cast(); + } - public static IObservable Create(IControl control, Type ancestorType) + public static IObservable Create(IStyledElement control, Type ancestorType) { - return new AnonymousObservable(observer => + return new AnonymousObservable(observer => { var finder = new FinderNode(control, ancestorType.GetTypeInfo()); var subscription = finder.Observable.Subscribe(observer); @@ -70,8 +76,6 @@ namespace Avalonia.Controls.Utils finder.Dispose(); }); }); - - } } } diff --git a/src/Avalonia.HtmlRenderer/Adapters/AvaloniaAdapter.cs b/src/Avalonia.HtmlRenderer/Adapters/AvaloniaAdapter.cs deleted file mode 100644 index aab65fcbc7..0000000000 --- a/src/Avalonia.HtmlRenderer/Adapters/AvaloniaAdapter.cs +++ /dev/null @@ -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(); - - /// - /// List of valid predefined color names in lower-case - /// - private static readonly Dictionary ColorNameDic = new Dictionary(); - - - 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)); - } - - /// - /// Get solid color brush for the given color. - /// - 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().SetTextAsync(text); - } - - protected override void SetToClipboardInt(RImage image) - { - //Do not crash, just ignore - //TODO: implement image clipboard support - } - } -} diff --git a/src/Avalonia.HtmlRenderer/Adapters/BrushAdapter.cs b/src/Avalonia.HtmlRenderer/Adapters/BrushAdapter.cs deleted file mode 100644 index 8ea67274b1..0000000000 --- a/src/Avalonia.HtmlRenderer/Adapters/BrushAdapter.cs +++ /dev/null @@ -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 -{ - /// - /// Adapter for Avalonia brushes. - /// - internal sealed class BrushAdapter : RBrush - { - /// - /// The actual Avalonia brush instance. - /// - private readonly IBrush _brush; - - /// - /// Init. - /// - public BrushAdapter(IBrush brush) - { - _brush = brush; - } - - /// - /// The actual Avalonia brush instance. - /// - public IBrush Brush - { - get { return _brush; } - } - - public override void Dispose() - { } - } -} \ No newline at end of file diff --git a/src/Avalonia.HtmlRenderer/Adapters/ContextMenuAdapter.cs b/src/Avalonia.HtmlRenderer/Adapters/ContextMenuAdapter.cs deleted file mode 100644 index 380108c01a..0000000000 --- a/src/Avalonia.HtmlRenderer/Adapters/ContextMenuAdapter.cs +++ /dev/null @@ -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 -{ - /// - /// Adapter for Avalonia context menu for core. - /// - 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() - { - } - } -} \ No newline at end of file diff --git a/src/Avalonia.HtmlRenderer/Adapters/ControlAdapter.cs b/src/Avalonia.HtmlRenderer/Adapters/ControlAdapter.cs deleted file mode 100644 index 9a55768f6a..0000000000 --- a/src/Avalonia.HtmlRenderer/Adapters/ControlAdapter.cs +++ /dev/null @@ -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 -{ - /// - /// Adapter for Avalonia Control for core. - /// - internal sealed class ControlAdapter : RControl - { - /// - /// the underline Avalonia control. - /// - private readonly Control _control; - - /// - /// Init. - /// - public ControlAdapter(Control control) - : base(AvaloniaAdapter.Instance) - { - ArgChecker.AssertArgNotNull(control, "control"); - - _control = control; - } - - /// - /// Get the underline Avalonia control - /// - 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(); - } - } -} \ No newline at end of file diff --git a/src/Avalonia.HtmlRenderer/Adapters/FontAdapter.cs b/src/Avalonia.HtmlRenderer/Adapters/FontAdapter.cs deleted file mode 100644 index 7e70967ac1..0000000000 --- a/src/Avalonia.HtmlRenderer/Adapters/FontAdapter.cs +++ /dev/null @@ -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 -{ - /// - /// Adapter for Avalonia Font. - /// - internal sealed class FontAdapter : RFont - { - public RFontStyle Style { get; } - - #region Fields and Consts - - - /// - /// the size of the font - /// - private readonly double _size; - - /// - /// the vertical offset of the font underline location from the top of the font. - /// - private readonly double _underlineOffset = -1; - - /// - /// Cached font height. - /// - private readonly double _height = -1; - - /// - /// Cached font whitespace width. - /// - private double _whitespaceWidth = -1; - - - #endregion - - - /// - /// Init. - /// - 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; - } -} \ No newline at end of file diff --git a/src/Avalonia.HtmlRenderer/Adapters/FontFamilyAdapter.cs b/src/Avalonia.HtmlRenderer/Adapters/FontFamilyAdapter.cs deleted file mode 100644 index 063d8386ba..0000000000 --- a/src/Avalonia.HtmlRenderer/Adapters/FontFamilyAdapter.cs +++ /dev/null @@ -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 -{ - /// - /// Adapter for Avalonia Font family object for core. - /// - internal sealed class FontFamilyAdapter : RFontFamily - { - public FontFamilyAdapter(string fontFamily) - { - Name = fontFamily; - } - - public override string Name { get; } - } -} \ No newline at end of file diff --git a/src/Avalonia.HtmlRenderer/Adapters/GraphicsAdapter.cs b/src/Avalonia.HtmlRenderer/Adapters/GraphicsAdapter.cs deleted file mode 100644 index 93f8790741..0000000000 --- a/src/Avalonia.HtmlRenderer/Adapters/GraphicsAdapter.cs +++ /dev/null @@ -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 -{ - /// - /// Adapter for Avalonia Graphics. - /// - internal sealed class GraphicsAdapter : RGraphics - { - #region Fields and Consts - - /// - /// The wrapped Avalonia graphics object - /// - private readonly DrawingContext _g; - - /// - /// if to release the graphics object on dispose - /// - private readonly bool _releaseGraphics; - - #endregion - - - private readonly Stack _clipStack = new Stack(); - - - /// - /// Init. - /// - /// the Avalonia graphics object to use - /// the initial clip of the graphics - /// optional: if to release the graphics object on dispose (default - false) - public GraphicsAdapter(DrawingContext g, RRect initialClip, bool releaseGraphics = false) - : base(AvaloniaAdapter.Instance, initialClip) - { - ArgChecker.AssertArgNotNull(g, "g"); - - _g = g; - _releaseGraphics = releaseGraphics; - } - - /// - /// Init. - /// - 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 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 - } -} \ No newline at end of file diff --git a/src/Avalonia.HtmlRenderer/Adapters/GraphicsPathAdapter.cs b/src/Avalonia.HtmlRenderer/Adapters/GraphicsPathAdapter.cs deleted file mode 100644 index f448c0ce09..0000000000 --- a/src/Avalonia.HtmlRenderer/Adapters/GraphicsPathAdapter.cs +++ /dev/null @@ -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 -{ - /// - /// Adapter for Avalonia graphics path object for core. - /// - internal sealed class GraphicsPathAdapter : RGraphicsPath - { - /// - /// The actual Avalonia graphics geometry instance. - /// - private readonly StreamGeometry _geometry = new StreamGeometry(); - - /// - /// The context used in Avalonia geometry to render path - /// - 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); - } - - /// - /// Close the geometry to so no more path adding is allowed and return the instance so it can be rendered. - /// - public StreamGeometry GetClosedGeometry() - { - _geometryContext.EndFigure(true); - _geometryContext.Dispose(); - return _geometry; - } - - public override void Dispose() - { } - } -} \ No newline at end of file diff --git a/src/Avalonia.HtmlRenderer/Adapters/ImageAdapter.cs b/src/Avalonia.HtmlRenderer/Adapters/ImageAdapter.cs deleted file mode 100644 index 3ee0b40771..0000000000 --- a/src/Avalonia.HtmlRenderer/Adapters/ImageAdapter.cs +++ /dev/null @@ -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 -{ - /// - /// Adapter for Avalonia Image object for core. - /// - internal sealed class ImageAdapter : RImage - { - /// - /// the underline Avalonia image. - /// - private readonly Bitmap _image; - - /// - /// Init. - /// - public ImageAdapter(Bitmap image) - { - _image = image; - } - - /// - /// the underline Avalonia image. - /// - 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();*/ - } - } -} \ No newline at end of file diff --git a/src/Avalonia.HtmlRenderer/Adapters/PenAdapter.cs b/src/Avalonia.HtmlRenderer/Adapters/PenAdapter.cs deleted file mode 100644 index 42025afc63..0000000000 --- a/src/Avalonia.HtmlRenderer/Adapters/PenAdapter.cs +++ /dev/null @@ -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 -{ - /// - /// Adapter for Avalonia pens objects for core. - /// - internal sealed class PenAdapter : RPen - { - /// - /// The actual Avalonia brush instance. - /// - private readonly IBrush _brush; - - /// - /// the width of the pen - /// - private double _width; - - private DashStyle _dashStyle; - - /// - /// the dash style of the pen - /// - //private DashStyle _dashStyle = DashStyles.Solid; - - /// - /// Init. - /// - 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 DashStyles = new Dictionary - { - {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 } - }; - - /// - /// Create the actual Avalonia pen instance. - /// - public Pen CreatePen() - { - var pen = new Pen(_brush, _width, _dashStyle); - return pen; - } - } -} \ No newline at end of file diff --git a/src/Avalonia.HtmlRenderer/Avalonia.HtmlRenderer.csproj b/src/Avalonia.HtmlRenderer/Avalonia.HtmlRenderer.csproj deleted file mode 100644 index 6e65264db0..0000000000 --- a/src/Avalonia.HtmlRenderer/Avalonia.HtmlRenderer.csproj +++ /dev/null @@ -1,24 +0,0 @@ - - - netstandard2.0 - False - False - CS0436 - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/src/Avalonia.HtmlRenderer/Avalonia.HtmlRenderer.csproj.DotSettings b/src/Avalonia.HtmlRenderer/Avalonia.HtmlRenderer.csproj.DotSettings deleted file mode 100644 index 73e96563f9..0000000000 --- a/src/Avalonia.HtmlRenderer/Avalonia.HtmlRenderer.csproj.DotSettings +++ /dev/null @@ -1,2 +0,0 @@ - - CSharp60 \ No newline at end of file diff --git a/src/Avalonia.HtmlRenderer/Compat/Api.cs b/src/Avalonia.HtmlRenderer/Compat/Api.cs deleted file mode 100644 index d4f4c6abbe..0000000000 --- a/src/Avalonia.HtmlRenderer/Compat/Api.cs +++ /dev/null @@ -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; } - } -} diff --git a/src/Avalonia.HtmlRenderer/Compat/Attributes.cs b/src/Avalonia.HtmlRenderer/Compat/Attributes.cs deleted file mode 100644 index 311b2ae4e8..0000000000 --- a/src/Avalonia.HtmlRenderer/Compat/Attributes.cs +++ /dev/null @@ -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) - { - - } -} \ No newline at end of file diff --git a/src/Avalonia.HtmlRenderer/Compat/ThreadPool.cs b/src/Avalonia.HtmlRenderer/Compat/ThreadPool.cs deleted file mode 100644 index 9a664745a2..0000000000 --- a/src/Avalonia.HtmlRenderer/Compat/ThreadPool.cs +++ /dev/null @@ -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 cb, object state) - { - Task.Factory.StartNew(() => cb(state)); - } - - public static void QueueUserWorkItem(Action cb) - { - Task.Factory.StartNew(() => cb(null)); - } - } -} diff --git a/src/Avalonia.HtmlRenderer/HtmlContainer.cs b/src/Avalonia.HtmlRenderer/HtmlContainer.cs deleted file mode 100644 index c3345589ce..0000000000 --- a/src/Avalonia.HtmlRenderer/HtmlContainer.cs +++ /dev/null @@ -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 -{ - /// - /// Low level handling of Html Renderer logic, this class is used by , - /// , and .
- ///
- /// - public sealed class HtmlContainer : IDisposable - { - #region Fields and Consts - - /// - /// The internal core html container - /// - private readonly HtmlContainerInt _htmlContainerInt; - - #endregion - - - /// - /// Init. - /// - public HtmlContainer() - { - _htmlContainerInt = new HtmlContainerInt(AvaloniaAdapter.Instance); - } - - /// - /// Raised when the set html document has been fully loaded.
- /// Allows manipulation of the html dom, scroll position, etc. - ///
- public event EventHandler LoadComplete - { - add { _htmlContainerInt.LoadComplete += value; } - remove { _htmlContainerInt.LoadComplete -= value; } - } - - /// - /// Raised when the user clicks on a link in the html.
- /// Allows canceling the execution of the link. - ///
- public event EventHandler LinkClicked - { - add { _htmlContainerInt.LinkClicked += value; } - remove { _htmlContainerInt.LinkClicked -= value; } - } - - /// - /// Raised when html renderer requires refresh of the control hosting (invalidation and re-layout). - /// - /// - /// There is no guarantee that the event will be raised on the main thread, it can be raised on thread-pool thread. - /// - public event EventHandler Refresh - { - add { _htmlContainerInt.Refresh += value; } - remove { _htmlContainerInt.Refresh -= value; } - } - - /// - /// Raised when Html Renderer request scroll to specific location.
- /// This can occur on document anchor click. - ///
- public event EventHandler ScrollChange - { - add { _htmlContainerInt.ScrollChange += value; } - remove { _htmlContainerInt.ScrollChange -= value; } - } - - /// - /// Raised when an error occurred during html rendering.
- ///
- /// - /// There is no guarantee that the event will be raised on the main thread, it can be raised on thread-pool thread. - /// - public event EventHandler RenderError - { - add { _htmlContainerInt.RenderError += value; } - remove { _htmlContainerInt.RenderError -= value; } - } - - /// - /// Raised when a stylesheet is about to be loaded by file path or URI by link element.
- /// This event allows to provide the stylesheet manually or provide new source (file or Uri) to load from.
- /// If no alternative data is provided the original source will be used.
- ///
- public event EventHandler StylesheetLoad - { - add { _htmlContainerInt.StylesheetLoad += value; } - remove { _htmlContainerInt.StylesheetLoad -= value; } - } - - /// - /// Raised when an image is about to be loaded by file path or URI.
- /// This event allows to provide the image manually, if not handled the image will be loaded from file or download from URI. - ///
- public event EventHandler ImageLoad - { - add { _htmlContainerInt.ImageLoad += value; } - remove { _htmlContainerInt.ImageLoad -= value; } - } - - /// - /// The internal core html container - /// - internal HtmlContainerInt HtmlContainerInt - { - get { return _htmlContainerInt; } - } - - /// - /// the parsed stylesheet data used for handling the html - /// - public CssData CssData - { - get { return _htmlContainerInt.CssData; } - } - - /// - /// Gets or sets a value indicating if image asynchronous loading should be avoided (default - false).
- /// True - images are loaded synchronously during html parsing.
- /// False - images are loaded asynchronously to html parsing when downloaded from URL or loaded from disk.
- ///
- /// - /// Asynchronously image loading allows to unblock html rendering while image is downloaded or loaded from disk using IO - /// ports to achieve better performance.
- /// Asynchronously image loading should be avoided when the full html content must be available during render, like render to image. - ///
- public bool AvoidAsyncImagesLoading - { - get { return _htmlContainerInt.AvoidAsyncImagesLoading; } - set { _htmlContainerInt.AvoidAsyncImagesLoading = value; } - } - - /// - /// Gets or sets a value indicating if image loading only when visible should be avoided (default - false).
- /// True - images are loaded as soon as the html is parsed.
- /// False - images that are not visible because of scroll location are not loaded until they are scrolled to. - ///
- /// - /// 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).
- /// 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.
- /// 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. - ///
- public bool AvoidImagesLateLoading - { - get { return _htmlContainerInt.AvoidImagesLateLoading; } - set { _htmlContainerInt.AvoidImagesLateLoading = value; } - } - - /// - /// Is content selection is enabled for the rendered html (default - true).
- /// If set to 'false' the rendered html will be static only with ability to click on links. - ///
- public bool IsSelectionEnabled - { - get { return _htmlContainerInt.IsSelectionEnabled; } - set { _htmlContainerInt.IsSelectionEnabled = value; } - } - - /// - /// Is the build-in context menu enabled and will be shown on mouse right click (default - true) - /// - public bool IsContextMenuEnabled - { - get { return _htmlContainerInt.IsContextMenuEnabled; } - set { _htmlContainerInt.IsContextMenuEnabled = value; } - } - - /// - /// The scroll offset of the html.
- /// This will adjust the rendered html by the given offset so the content will be "scrolled".
- ///
- /// - /// 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. - /// - public Point ScrollOffset - { - get { return Util.Convert(_htmlContainerInt.ScrollOffset); } - set { _htmlContainerInt.ScrollOffset = Util.Convert(value); } - } - - /// - /// The top-left most location of the rendered html.
- /// This will offset the top-left corner of the rendered html. - ///
- public Point Location - { - get { return Util.Convert(_htmlContainerInt.Location); } - set { _htmlContainerInt.Location = Util.Convert(value); } - } - - /// - /// The max width and height of the rendered html.
- /// The max width will effect the html layout wrapping lines, resize images and tables where possible.
- /// The max height does NOT effect layout, but will not render outside it (clip).
- /// can be exceed the max size by layout restrictions (unwrappable line, set image size, etc.).
- /// Set zero for unlimited (width\height separately).
- ///
- public Size MaxSize - { - get { return Util.Convert(_htmlContainerInt.MaxSize); } - set { _htmlContainerInt.MaxSize = Util.Convert(value); } - } - - /// - /// The actual size of the rendered html (after layout) - /// - public Size ActualSize - { - get { return Util.Convert(_htmlContainerInt.ActualSize); } - internal set { _htmlContainerInt.ActualSize = Util.Convert(value); } - } - - /// - /// Get the currently selected text segment in the html. - /// - public string SelectedText - { - get { return _htmlContainerInt.SelectedText; } - } - - /// - /// Copy the currently selected html segment with style. - /// - public string SelectedHtml - { - get { return _htmlContainerInt.SelectedHtml; } - } - - /// - /// Clear the current selection. - /// - public void ClearSelection() - { - HtmlContainerInt.ClearSelection(); - } - - /// - /// Init with optional document and stylesheet. - /// - /// the html to init with, init empty if not given - /// optional: the stylesheet to init with, init default if not given - public void SetHtml(string htmlSource, CssData baseCssData = null) - { - _htmlContainerInt.SetHtml(htmlSource, baseCssData); - } - - /// - /// Clear the content of the HTML container releasing any resources used to render previously existing content. - /// - public void Clear() - { - _htmlContainerInt.Clear(); - } - - /// - /// Get html from the current DOM tree with style if requested. - /// - /// Optional: controls the way styles are generated when html is generated (default: ) - /// generated html - public string GetHtml(HtmlGenerationStyle styleGen = HtmlGenerationStyle.Inline) - { - return _htmlContainerInt.GetHtml(styleGen); - } - - /// - /// Get attribute value of element at the given x,y location by given key.
- /// If more than one element exist with the attribute at the location the inner most is returned. - ///
- /// the location to find the attribute at - /// the attribute key to get value by - /// found attribute value or null if not found - public string GetAttributeAt(Point location, string attribute) - { - return _htmlContainerInt.GetAttributeAt(Util.Convert(location), attribute); - } - - /// - /// Get all the links in the HTML with the element Rect and href data. - /// - /// collection of all the links in the HTML - public List> GetLinks() - { - var linkElements = new List>(); - foreach (var link in HtmlContainerInt.GetLinks()) - { - linkElements.Add(new LinkElementData(link.Id, link.Href, Util.Convert(link.Rectangle))); - } - return linkElements; - } - - /// - /// Get css link href at the given x,y location. - /// - /// the location to find the link at - /// css link href if exists or null - public string GetLinkAt(Point location) - { - return _htmlContainerInt.GetLinkAt(Util.Convert(location)); - } - - /// - /// Get the Rect of html element as calculated by html layout.
- /// Element if found by id (id attribute on the html element).
- /// Note: to get the screen Rect you need to adjust by the hosting control.
- ///
- /// the id of the element to get its Rect - /// the Rect of the element or null if not found - public Rect? GetElementRectangle(string elementId) - { - var r = _htmlContainerInt.GetElementRectangle(elementId); - return r.HasValue ? Util.Convert(r.Value) : (Rect?)null; - } - - /// - /// Measures the bounds of box and children, recursively. - /// - public void PerformLayout() - { - using (var ig = new GraphicsAdapter()) - { - _htmlContainerInt.PerformLayout(ig); - } - } - - /// - /// Render the html using the given device. - /// - /// the device to use to render - /// the clip rectangle of the html container - public void PerformPaint(DrawingContext g, Rect clip) - { - ArgChecker.AssertArgNotNull(g, "g"); - - using (var ig = new GraphicsAdapter(g, Util.Convert(clip))) - { - _htmlContainerInt.PerformPaint(ig); - } - } - - /// - /// Handle mouse down to handle selection. - /// - /// the control hosting the html to invalidate - /// the mouse event args - 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))); - } - - /// - /// Handle mouse up to handle selection and link click. - /// - /// the control hosting the html to invalidate - /// the mouse event args - 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); - } - - /// - /// Handle mouse double click to select word under the mouse. - /// - /// the control hosting the html to set cursor and invalidate - /// mouse event args - 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))); - } - - /// - /// Handle mouse move to handle hover cursor and text selection. - /// - /// the control hosting the html to set cursor and invalidate - /// the mouse event args - public void HandleMouseMove(Control parent, Point mousePos) - { - ArgChecker.AssertArgNotNull(parent, "parent"); - - _htmlContainerInt.HandleMouseMove(new ControlAdapter(parent), Util.Convert(mousePos)); - } - - /// - /// Handle mouse leave to handle hover cursor. - /// - /// the control hosting the html to set cursor and invalidate - public void HandleMouseLeave(Control parent) - { - ArgChecker.AssertArgNotNull(parent, "parent"); - - _htmlContainerInt.HandleMouseLeave(new ControlAdapter(parent)); - } - - /// - /// Handle key down event for selection and copy. - /// - /// the control hosting the html to invalidate - /// the pressed key - public void HandleKeyDown(Control parent, KeyEventArgs e) - { - ArgChecker.AssertArgNotNull(parent, "parent"); - ArgChecker.AssertArgNotNull(e, "e"); - - _htmlContainerInt.HandleKeyDown(new ControlAdapter(parent), CreateKeyEevent(e)); - } - - /// - /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources. - /// - public void Dispose() - { - _htmlContainerInt.Dispose(); - } - - - #region Private methods - - /// - /// Create HtmlRenderer key event from Avalonia key event. - /// - 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 - } -} \ No newline at end of file diff --git a/src/Avalonia.HtmlRenderer/HtmlControl.cs b/src/Avalonia.HtmlRenderer/HtmlControl.cs deleted file mode 100644 index 94b2d56f5f..0000000000 --- a/src/Avalonia.HtmlRenderer/HtmlControl.cs +++ /dev/null @@ -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 -{ - /// - /// Provides HTML rendering using the text property.
- /// Avalonia control that will render html content in it's client rectangle.
- /// The control will handle mouse and keyboard events on it to support html text selection, copy-paste and mouse clicks.
- /// - /// The major differential to use HtmlPanel or HtmlLabel is size and scrollbars.
- /// If the size of the control depends on the html content the HtmlLabel should be used.
- /// 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.
- ///
- /// - ///

LinkClicked event:

- /// Raised when the user clicks on a link in the html.
- /// Allows canceling the execution of the link. - ///
- /// - ///

StylesheetLoad event:

- /// Raised when a stylesheet is about to be loaded by file path or URI by link element.
- /// This event allows to provide the stylesheet manually or provide new source (file or uri) to load from.
- /// If no alternative data is provided the original source will be used.
- ///
- /// - ///

ImageLoad event:

- /// Raised when an image is about to be loaded by file path or URI.
- /// This event allows to provide the image manually, if not handled the image will be loaded from file or download from URI. - ///
- /// - ///

RenderError event:

- /// Raised when an error occurred during html rendering.
- ///
- ///
- - public class HtmlControl : Control - { - /// - /// Underline html container instance. - /// - protected readonly HtmlContainer _htmlContainer; - - /// - /// the base stylesheet data used in the control - /// - protected CssData _baseCssData; - - /// - /// The last position of the scrollbars to know if it has changed to update mouse - /// - protected Point _lastScrollOffset; - - public static readonly AvaloniaProperty AvoidImagesLateLoadingProperty = - PropertyHelper.Register(nameof(AvoidImagesLateLoading), false, OnAvaloniaProperty_valueChanged); - public static readonly AvaloniaProperty IsSelectionEnabledProperty = - PropertyHelper.Register(nameof(IsSelectionEnabled), true, OnAvaloniaProperty_valueChanged); - public static readonly AvaloniaProperty IsContextMenuEnabledProperty = - PropertyHelper.Register(nameof(IsContextMenuEnabled), true, OnAvaloniaProperty_valueChanged); - - public static readonly AvaloniaProperty BaseStylesheetProperty = - PropertyHelper.Register(nameof(BaseStylesheet), null, OnAvaloniaProperty_valueChanged); - - public static readonly AvaloniaProperty TextProperty = - PropertyHelper.Register(nameof(Text), null, OnAvaloniaProperty_valueChanged); - - public static readonly StyledProperty BackgroundProperty = - Border.BackgroundProperty.AddOwner(); - - public static readonly AvaloniaProperty BorderThicknessProperty = - AvaloniaProperty.Register(nameof(BorderThickness), new Thickness(0)); - - public static readonly AvaloniaProperty BorderBrushProperty = - AvaloniaProperty.Register(nameof(BorderBrush)); - - public static readonly AvaloniaProperty PaddingProperty = - AvaloniaProperty.Register(nameof(Padding), new Thickness(0)); - - public static readonly RoutedEvent LoadCompleteEvent = - RoutedEvent.Register("LoadComplete", RoutingStrategies.Bubble, typeof(HtmlControl)); - public static readonly RoutedEvent LinkClickedEvent = - RoutedEvent.Register>("LinkClicked", RoutingStrategies.Bubble, typeof(HtmlControl)); - public static readonly RoutedEvent RenderErrorEvent - = RoutedEvent.Register>("RenderError", RoutingStrategies.Bubble, typeof(HtmlControl)); - public static readonly RoutedEvent RefreshEvent - = RoutedEvent.Register>("Refresh", RoutingStrategies.Bubble, typeof(HtmlControl)); - public static readonly RoutedEvent StylesheetLoadEvent - = RoutedEvent.Register>("StylesheetLoad", RoutingStrategies.Bubble, typeof(HtmlControl)); - - public static readonly RoutedEvent ImageLoadEvent - = RoutedEvent.Register>("ImageLoad", RoutingStrategies.Bubble, - typeof (HtmlControl)); - - static HtmlControl() - { - FocusableProperty.OverrideDefaultValue(typeof(HtmlControl), true); - } - - /// - /// Creates a new HtmlPanel and sets a basic css for it's styling. - /// - 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; } - - /// - /// Raised when the set html document has been fully loaded.
- /// Allows manipulation of the html dom, scroll position, etc. - ///
- public event EventHandler> LoadComplete - { - add { AddHandler(LoadCompleteEvent, value); } - remove { RemoveHandler(LoadCompleteEvent, value); } - } - - /// - /// Raised when the user clicks on a link in the html.
- /// Allows canceling the execution of the link. - ///
- public event EventHandler> LinkClicked - { - add { AddHandler(LinkClickedEvent, value); } - remove { RemoveHandler(LinkClickedEvent, value); } - } - - /// - /// Raised when an error occurred during html rendering.
- ///
- public event EventHandler> RenderError - { - add { AddHandler(RenderErrorEvent, value); } - remove { RemoveHandler(RenderErrorEvent, value); } - } - - /// - /// Raised when a stylesheet is about to be loaded by file path or URI by link element.
- /// This event allows to provide the stylesheet manually or provide new source (file or uri) to load from.
- /// If no alternative data is provided the original source will be used.
- ///
- public event EventHandler> StylesheetLoad - { - add { AddHandler(StylesheetLoadEvent, value); } - remove { RemoveHandler(StylesheetLoadEvent, value); } - } - - /// - /// Raised when an image is about to be loaded by file path or URI.
- /// This event allows to provide the image manually, if not handled the image will be loaded from file or download from URI. - ///
- public event EventHandler> ImageLoad - { - add { AddHandler(ImageLoadEvent, value); } - remove { RemoveHandler(ImageLoadEvent, value); } - } - - /// - /// Gets or sets a value indicating if image loading only when visible should be avoided (default - false).
- /// True - images are loaded as soon as the html is parsed.
- /// False - images that are not visible because of scroll location are not loaded until they are scrolled to. - ///
- /// - /// 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).
- /// 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.
- /// 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. - ///
- [Category("Behavior")] - [Description("If image loading only when visible should be avoided")] - public bool AvoidImagesLateLoading - { - get { return (bool)GetValue(AvoidImagesLateLoadingProperty); } - set { SetValue(AvoidImagesLateLoadingProperty, value); } - } - - /// - /// Is content selection is enabled for the rendered html (default - true).
- /// If set to 'false' the rendered html will be static only with ability to click on links. - ///
- [Category("Behavior")] - [Description("Is content selection is enabled for the rendered html.")] - public bool IsSelectionEnabled - { - get { return (bool)GetValue(IsSelectionEnabledProperty); } - set { SetValue(IsSelectionEnabledProperty, value); } - } - - /// - /// Is the build-in context menu enabled and will be shown on mouse right click (default - true) - /// - [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); } - } - - /// - /// Set base stylesheet to be used by html rendered in the panel. - /// - [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); } - } - - /// - /// Gets or sets the text of this panel - /// - [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);} - } - - /// - /// Get the currently selected text segment in the html. - /// - [Browsable(false)] - public virtual string SelectedText - { - get { return _htmlContainer.SelectedText; } - } - - /// - /// Copy the currently selected html segment with style. - /// - [Browsable(false)] - public virtual string SelectedHtml - { - get { return _htmlContainer.SelectedHtml; } - } - - /// - /// Get html from the current DOM tree with inline style. - /// - /// generated html - public virtual string GetHtml() - { - return _htmlContainer != null ? _htmlContainer.GetHtml() : null; - } - - /// - /// Get the rectangle of html element as calculated by html layout.
- /// Element if found by id (id attribute on the html element).
- /// Note: to get the screen rectangle you need to adjust by the hosting control.
- ///
- /// the id of the element to get its rectangle - /// the rectangle of the element or null if not found - public virtual Rect? GetElementRectangle(string elementId) - { - return _htmlContainer != null ? _htmlContainer.GetElementRectangle(elementId) : null; - } - - /// - /// Clear the current selection. - /// - 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(); - } - } - } - - /// - /// Handle mouse move to handle hover cursor and text selection. - /// - protected override void OnPointerMoved(PointerEventArgs e) - { - base.OnPointerMoved(e); - if (_htmlContainer != null) - _htmlContainer.HandleMouseMove(this, e.GetPosition(this)); - } - /// - /// Handle mouse leave to handle cursor change. - /// - protected override void OnPointerLeave(PointerEventArgs e) - { - base.OnPointerLeave(e); - if (_htmlContainer != null) - _htmlContainer.HandleMouseLeave(this); - } - - /// - /// Handle mouse down to handle selection. - /// - protected override void OnPointerPressed(PointerPressedEventArgs e) - { - base.OnPointerPressed(e); - LeftMouseButton = true; - _htmlContainer?.HandleLeftMouseDown(this, e); - } - - - - /// - /// Handle mouse up to handle selection and link click. - /// - protected override void OnPointerReleased(PointerReleasedEventArgs e) - { - base.OnPointerReleased(e); - LeftMouseButton = false; - if (_htmlContainer != null) - _htmlContainer.HandleLeftMouseUp(this, e); - } - - //TODO: Implement double click - /* - /// - /// Handle mouse double click to select word under the mouse. - /// - protected override void OnMouseDoubleClick(MouseButtonEventArgs e) - { - base.OnMouseDoubleClick(e); - if (_htmlContainer != null) - _htmlContainer.HandleMouseDoubleClick(this, e); - } - */ - /// - /// Handle key down event for selection, copy and scrollbars handling. - /// - protected override void OnKeyDown(KeyEventArgs e) - { - base.OnKeyDown(e); - if (_htmlContainer != null) - _htmlContainer.HandleKeyDown(this, e); - } - - void RaiseRouted(RoutedEvent ev, T arg) - { - var e =new HtmlRendererRoutedEventArgs - { - Event = arg, - Source = this, - RoutedEvent = ev, - Route = ev.RoutingStrategies - }; - RaiseEvent(e); - } - - /// - /// Propagate the LoadComplete event from root container. - /// - protected virtual void OnLoadComplete(EventArgs e) => RaiseRouted(LoadCompleteEvent, e); - - /// - /// Propagate the LinkClicked event from root container. - /// - protected virtual void OnLinkClicked(HtmlLinkClickedEventArgs e) => RaiseRouted(LinkClickedEvent, e); - - /// - /// Propagate the Render Error event from root container. - /// - protected virtual void OnRenderError(HtmlRenderErrorEventArgs e) => RaiseRouted(RenderErrorEvent, e); - - /// - /// Propagate the stylesheet load event from root container. - /// - protected virtual void OnStylesheetLoad(HtmlStylesheetLoadEventArgs e) => RaiseRouted(StylesheetLoadEvent, e); - - /// - /// Propagate the image load event from root container. - /// - protected virtual void OnImageLoad(HtmlImageLoadEventArgs e) => RaiseRouted(ImageLoadEvent, e); - - /// - /// Handle html renderer invalidate and re-layout as requested. - /// - protected virtual void OnRefresh(HtmlRefreshEventArgs e) - { - if (e.Layout) - InvalidateMeasure(); - InvalidateVisual(); - } - - /// - /// Get the width the HTML has to render in (not including vertical scroll iff it is visible) - /// - protected virtual double HtmlWidth(Size size) - { - return size.Width - Padding.Left - Padding.Right - BorderThickness.Left - BorderThickness.Right; - } - - /// - /// Get the width the HTML has to render in (not including vertical scroll iff it is visible) - /// - protected virtual double HtmlHeight(Size size) - { - return size.Height - Padding.Top - Padding.Bottom - BorderThickness.Top - BorderThickness.Bottom; - } - - /// - /// call mouse move to handle paint after scroll or html change affecting mouse cursor. - /// - protected virtual void InvokeMouseMove() - { - - _htmlContainer.HandleMouseMove(this, (this.GetVisualRoot() as IInputRoot)?.MouseDevice?.GetPosition(this) ?? default(Point)); - } - - /// - /// Handle when dependency property value changes to update the underline HtmlContainer with the new value. - /// - 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(OnLinkClicked), e); - - } - - private void OnLinkClicked(object sender, HtmlLinkClickedEventArgs e) - { - if (CheckAccess()) - OnLinkClicked(e); - else - Dispatcher.UIThread.Invoke(new Action(OnLinkClicked), e); - } - - private void OnRenderError(object sender, HtmlRenderErrorEventArgs e) - { - if (CheckAccess()) - OnRenderError(e); - else - Dispatcher.UIThread.Invoke(new Action(OnRenderError), e); - } - - private void OnStylesheetLoad(object sender, HtmlStylesheetLoadEventArgs e) - { - if (CheckAccess()) - OnStylesheetLoad(e); - else - Dispatcher.UIThread.Invoke(new Action(OnStylesheetLoad), e); - } - - private void OnImageLoad(object sender, HtmlImageLoadEventArgs e) - { - if (CheckAccess()) - OnImageLoad(e); - else - Dispatcher.UIThread.Invoke(new Action(OnImageLoad), e); - } - - private void OnRefresh(object sender, HtmlRefreshEventArgs e) - { - if (CheckAccess()) - OnRefresh(e); - else - Dispatcher.UIThread.Invoke(new Action(OnRefresh), e); - } - */ - } -} \ No newline at end of file diff --git a/src/Avalonia.HtmlRenderer/HtmlLabel.cs b/src/Avalonia.HtmlRenderer/HtmlLabel.cs deleted file mode 100644 index 547a3a10fa..0000000000 --- a/src/Avalonia.HtmlRenderer/HtmlLabel.cs +++ /dev/null @@ -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 -{ - /// - /// Provides HTML rendering using the text property.
- /// WPF control that will render html content in it's client rectangle.
- /// Using and 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
- /// The control will handle mouse and keyboard events on it to support html text selection, copy-paste and mouse clicks.
- ///
- /// - /// See for more info. - /// - public class HtmlLabel : HtmlControl - { - #region Dependency properties - - public static readonly AvaloniaProperty AutoSizeProperty = PropertyHelper.Register("AutoSize", true, OnAvaloniaProperty_valueChanged); - public static readonly AvaloniaProperty AutoSizeHeightOnlyProperty = PropertyHelper.Register("AutoSizeHeightOnly", false, OnAvaloniaProperty_valueChanged); - - #endregion - - - /// - /// Init. - /// - static HtmlLabel() - { - BackgroundProperty.OverrideDefaultValue(Brushes.Transparent); - } - - #region Private methods - - /// - /// Perform the layout of the html in the control. - /// - 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; - } - - /// - /// Handle when dependency property value changes to update the underline HtmlContainer with the new value. - /// - 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 - } -} \ No newline at end of file diff --git a/src/Avalonia.HtmlRenderer/HtmlRendererRoutedEventArgs.cs b/src/Avalonia.HtmlRenderer/HtmlRendererRoutedEventArgs.cs deleted file mode 100644 index 3136624b7f..0000000000 --- a/src/Avalonia.HtmlRenderer/HtmlRendererRoutedEventArgs.cs +++ /dev/null @@ -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 : RoutedEventArgs - { - public T Event { get; set; } - } -} diff --git a/src/Avalonia.HtmlRenderer/Properties/AssemblyInfo.cs b/src/Avalonia.HtmlRenderer/Properties/AssemblyInfo.cs deleted file mode 100644 index d77b5b900e..0000000000 --- a/src/Avalonia.HtmlRenderer/Properties/AssemblyInfo.cs +++ /dev/null @@ -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")] \ No newline at end of file diff --git a/src/Avalonia.HtmlRenderer/PropertyHelper.cs b/src/Avalonia.HtmlRenderer/PropertyHelper.cs deleted file mode 100644 index 56257ed478..0000000000 --- a/src/Avalonia.HtmlRenderer/PropertyHelper.cs +++ /dev/null @@ -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(string name, T def, Action changed) where TOwner : AvaloniaObject - { - var pp = AvaloniaProperty.Register(name, def); - Action cb = args => - { - changed(args.Sender, args); - }; - - pp.Changed.Subscribe(cb); - return pp; - } - } -} diff --git a/src/Avalonia.HtmlRenderer/Utilities/Util.cs b/src/Avalonia.HtmlRenderer/Utilities/Util.cs deleted file mode 100644 index 3a19eaef92..0000000000 --- a/src/Avalonia.HtmlRenderer/Utilities/Util.cs +++ /dev/null @@ -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 -{ - /// - /// Utilities for converting WPF entities to HtmlRenderer core entities. - /// - internal static class Util - { - /// - /// Convert from WPF point to core point. - /// - public static RPoint Convert(Point p) - { - return new RPoint(p.X, p.Y); - } - - /// - /// Convert from WPF point to core point. - /// - 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; - } - - /// - /// Convert from core point to WPF point. - /// - public static Point Convert(RPoint p) - { - return new Point(p.X, p.Y); - } - - /// - /// Convert from core point to WPF point. - /// - public static Point ConvertRound(RPoint p) - { - return new Point((int)p.X, (int)p.Y); - } - - /// - /// Convert from WPF size to core size. - /// - public static RSize Convert(Size s) - { - return new RSize(s.Width, s.Height); - } - - /// - /// Convert from core size to WPF size. - /// - public static Size Convert(RSize s) - { - return new Size(s.Width, s.Height); - } - - /// - /// Convert from core point to WPF point. - /// - public static Size ConvertRound(RSize s) - { - return new Size((int)s.Width, (int)s.Height); - } - - /// - /// Convert from WPF rectangle to core rectangle. - /// - public static RRect Convert(Rect r) - { - return new RRect(r.X, r.Y, r.Width, r.Height); - } - - /// - /// Convert from core rectangle to WPF rectangle. - /// - public static Rect Convert(RRect r) - { - return new Rect(r.X, r.Y, r.Width, r.Height); - } - - /// - /// Convert from core rectangle to WPF rectangle. - /// - public static Rect ConvertRound(RRect r) - { - return new Rect((int)r.X, (int)r.Y, (int)r.Width, (int)r.Height); - } - - /// - /// Convert from WPF color to core color. - /// - public static RColor Convert(Color c) - { - return RColor.FromArgb(c.A, c.R, c.G, c.B); - } - - /// - /// Convert from core color to WPF color. - /// - public static Color Convert(RColor c) - { - return Color.FromArgb(c.A, c.R, c.G, c.B); - } - } -} \ No newline at end of file diff --git a/src/Avalonia.HtmlRenderer/external b/src/Avalonia.HtmlRenderer/external deleted file mode 160000 index 43df78f4d1..0000000000 --- a/src/Avalonia.HtmlRenderer/external +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 43df78f4d1bb09d05021f062cb15e939e3e1771d diff --git a/src/Avalonia.Input/InputElement.cs b/src/Avalonia.Input/InputElement.cs index 8ac49df4cd..9935dbe27c 100644 --- a/src/Avalonia.Input/InputElement.cs +++ b/src/Avalonia.Input/InputElement.cs @@ -168,6 +168,10 @@ namespace Avalonia.Input PointerPressedEvent.AddClassHandler(x => x.OnPointerPressed); PointerReleasedEvent.AddClassHandler(x => x.OnPointerReleased); PointerWheelChangedEvent.AddClassHandler(x => x.OnPointerWheelChanged); + + PseudoClass(IsEnabledCoreProperty, x => !x, ":disabled"); + PseudoClass(IsFocusedProperty, ":focus"); + PseudoClass(IsPointerOverProperty, ":pointerover"); } /// diff --git a/src/Avalonia.Styling/Avalonia.Styling.csproj b/src/Avalonia.Styling/Avalonia.Styling.csproj index 61827bc59c..1050d20523 100644 --- a/src/Avalonia.Styling/Avalonia.Styling.csproj +++ b/src/Avalonia.Styling/Avalonia.Styling.csproj @@ -5,9 +5,8 @@ Avalonia - - + \ No newline at end of file diff --git a/src/Avalonia.Controls/Classes.cs b/src/Avalonia.Styling/Controls/Classes.cs similarity index 95% rename from src/Avalonia.Controls/Classes.cs rename to src/Avalonia.Styling/Controls/Classes.cs index 48ce69f0c9..85935b030b 100644 --- a/src/Avalonia.Controls/Classes.cs +++ b/src/Avalonia.Styling/Controls/Classes.cs @@ -9,7 +9,7 @@ using Avalonia.Collections; namespace Avalonia.Controls { /// - /// Holds a collection of style classes for an . + /// Holds a collection of style classes for an . /// /// /// Similar to CSS, each control may have any number of styling classes applied. @@ -54,7 +54,7 @@ namespace Avalonia.Controls /// The class name. /// /// Only standard classes may be added via this method. To add pseudoclasses (classes - /// beginning with a ':' character) use the protected + /// beginning with a ':' character) use the protected /// property. /// public override void Add(string name) @@ -73,7 +73,7 @@ namespace Avalonia.Controls /// The class names. /// /// Only standard classes may be added via this method. To add pseudoclasses (classes - /// beginning with a ':' character) use the protected + /// beginning with a ':' character) use the protected /// property. /// public override void AddRange(IEnumerable names) @@ -114,7 +114,7 @@ namespace Avalonia.Controls /// The class name. /// /// Only standard classes may be added via this method. To add pseudoclasses (classes - /// beginning with a ':' character) use the protected + /// beginning with a ':' character) use the protected /// property. /// public override void Insert(int index, string name) @@ -134,7 +134,7 @@ namespace Avalonia.Controls /// The class names. /// /// Only standard classes may be added via this method. To add pseudoclasses (classes - /// beginning with a ':' character) use the protected + /// beginning with a ':' character) use the protected /// property. /// public override void InsertRange(int index, IEnumerable names) @@ -160,7 +160,7 @@ namespace Avalonia.Controls /// The class name. /// /// Only standard classes may be removed via this method. To remove pseudoclasses (classes - /// beginning with a ':' character) use the protected + /// beginning with a ':' character) use the protected /// property. /// public override bool Remove(string name) @@ -175,7 +175,7 @@ namespace Avalonia.Controls /// The class name. /// /// Only standard classes may be removed via this method. To remove pseudoclasses (classes - /// beginning with a ':' character) use the protected + /// beginning with a ':' character) use the protected /// property. /// public override void RemoveAll(IEnumerable names) @@ -201,7 +201,7 @@ namespace Avalonia.Controls /// The index of the class in the collection. /// /// Only standard classes may be removed via this method. To remove pseudoclasses (classes - /// beginning with a ':' character) use the protected + /// beginning with a ':' character) use the protected /// property. /// public override void RemoveAt(int index) diff --git a/src/Avalonia.Controls/IPseudoClasses.cs b/src/Avalonia.Styling/Controls/IPseudoClasses.cs similarity index 100% rename from src/Avalonia.Controls/IPseudoClasses.cs rename to src/Avalonia.Styling/Controls/IPseudoClasses.cs diff --git a/src/Avalonia.Controls/ISetInheritanceParent.cs b/src/Avalonia.Styling/Controls/ISetInheritanceParent.cs similarity index 88% rename from src/Avalonia.Controls/ISetInheritanceParent.cs rename to src/Avalonia.Styling/Controls/ISetInheritanceParent.cs index 788ab77246..1a778fa0f9 100644 --- a/src/Avalonia.Controls/ISetInheritanceParent.cs +++ b/src/Avalonia.Styling/Controls/ISetInheritanceParent.cs @@ -4,7 +4,7 @@ namespace Avalonia.Controls { /// - /// Defines an interface through which a 's inheritance parent can be set. + /// Defines an interface through which a 's inheritance parent can be set. /// /// /// You should not usually need to use this interface - it is for advanced scenarios only. diff --git a/src/Avalonia.Controls/ISetLogicalParent.cs b/src/Avalonia.Styling/Controls/ISetLogicalParent.cs similarity index 85% rename from src/Avalonia.Controls/ISetLogicalParent.cs rename to src/Avalonia.Styling/Controls/ISetLogicalParent.cs index 4422033634..5fb450c896 100644 --- a/src/Avalonia.Controls/ISetLogicalParent.cs +++ b/src/Avalonia.Styling/Controls/ISetLogicalParent.cs @@ -6,7 +6,7 @@ using Avalonia.LogicalTree; namespace Avalonia.Controls { /// - /// Defines an interface through which a 's logical parent can be set. + /// Defines an interface through which a 's logical parent can be set. /// /// /// You should not usually need to use this interface - it is for advanced scenarios only. diff --git a/src/Avalonia.Styling/Controls/NameScope.cs b/src/Avalonia.Styling/Controls/NameScope.cs index 8b5bd81d3c..e3a29af541 100644 --- a/src/Avalonia.Styling/Controls/NameScope.cs +++ b/src/Avalonia.Styling/Controls/NameScope.cs @@ -16,7 +16,7 @@ namespace Avalonia.Controls /// Defines the NameScope attached property. /// public static readonly AttachedProperty NameScopeProperty = - AvaloniaProperty.RegisterAttached("NameScope"); + AvaloniaProperty.RegisterAttached("NameScope"); private readonly Dictionary _inner = new Dictionary(); @@ -31,53 +31,53 @@ namespace Avalonia.Controls public event EventHandler Unregistered; /// - /// Finds the containing name scope for a visual. + /// Finds the containing name scope for a styled element. /// - /// The visual. + /// The styled element. /// The containing name scope. - public static INameScope FindNameScope(Visual visual) + public static INameScope FindNameScope(StyledElement styled) { - Contract.Requires(visual != null); + Contract.Requires(styled != null); INameScope result; - while (visual != null) + while (styled != null) { - result = visual as INameScope ?? GetNameScope(visual); + result = styled as INameScope ?? GetNameScope(styled); if (result != null) { return result; } - visual = (visual as ILogical)?.LogicalParent as Visual; + styled = (styled as ILogical)?.LogicalParent as StyledElement; } return null; } /// - /// Gets the value of the attached on a visual. + /// Gets the value of the attached on a styled element. /// - /// The visual. + /// The styled element. /// The value of the NameScope attached property. - public static INameScope GetNameScope(Visual visual) + public static INameScope GetNameScope(StyledElement styled) { - Contract.Requires(visual != null); + Contract.Requires(styled != null); - return visual.GetValue(NameScopeProperty); + return styled.GetValue(NameScopeProperty); } /// - /// Sets the value of the attached on a visual. + /// Sets the value of the attached on a styled element. /// - /// The visual. + /// The styled element. /// The value to set. - public static void SetNameScope(Visual visual, INameScope value) + public static void SetNameScope(StyledElement styled, INameScope value) { - Contract.Requires(visual != null); + Contract.Requires(styled != null); - visual.SetValue(NameScopeProperty, value); + styled.SetValue(NameScopeProperty, value); } /// diff --git a/src/Avalonia.Styling/Controls/NameScopeExtensions.cs b/src/Avalonia.Styling/Controls/NameScopeExtensions.cs index b8c26c8d49..491e4d71a7 100644 --- a/src/Avalonia.Styling/Controls/NameScopeExtensions.cs +++ b/src/Avalonia.Styling/Controls/NameScopeExtensions.cs @@ -3,6 +3,8 @@ using System; using System.Collections.Generic; +using System.Linq; +using Avalonia.LogicalTree; namespace Avalonia.Controls { @@ -64,5 +66,15 @@ namespace Avalonia.Controls return (T)result; } + + public static INameScope FindNameScope(this ILogical control) + { + Contract.Requires(control != null); + + return control.GetSelfAndLogicalAncestors() + .OfType() + .Select(x => (x as INameScope) ?? NameScope.GetNameScope(x)) + .FirstOrDefault(x => x != null); + } } } diff --git a/src/Avalonia.Visuals/INamed.cs b/src/Avalonia.Styling/INamed.cs similarity index 100% rename from src/Avalonia.Visuals/INamed.cs rename to src/Avalonia.Styling/INamed.cs diff --git a/src/Avalonia.Styling/IStyledElement.cs b/src/Avalonia.Styling/IStyledElement.cs new file mode 100644 index 0000000000..8369717233 --- /dev/null +++ b/src/Avalonia.Styling/IStyledElement.cs @@ -0,0 +1,42 @@ +using System; +using System.Collections.Generic; +using System.Text; +using Avalonia.Controls; +using Avalonia.LogicalTree; +using Avalonia.Styling; + +namespace Avalonia +{ + public interface IStyledElement : + IStyleable, + IStyleHost, + ILogical, + IResourceProvider, + IResourceNode + { + /// + /// Occurs when the control has finished initialization. + /// + event EventHandler Initialized; + + /// + /// Gets a value that indicates whether the element has finished initialization. + /// + bool IsInitialized { get; } + + /// + /// Gets or sets the control's styling classes. + /// + new Classes Classes { get; set; } + + /// + /// Gets or sets the control's data context. + /// + object DataContext { get; set; } + + /// + /// Gets the control's logical parent. + /// + IStyledElement Parent { get; } + } +} diff --git a/src/Avalonia.Styling/LogicalTree/ControlLocator.cs b/src/Avalonia.Styling/LogicalTree/ControlLocator.cs new file mode 100644 index 0000000000..2858d11d9d --- /dev/null +++ b/src/Avalonia.Styling/LogicalTree/ControlLocator.cs @@ -0,0 +1,97 @@ +// Copyright (c) The Avalonia Project. All rights reserved. +// Licensed under the MIT license. See licence.md file in the project root for full license information. + +using System; +using System.Linq; +using System.Reactive.Linq; +using System.Reflection; +using Avalonia.Controls; + +namespace Avalonia.LogicalTree +{ + /// + /// Locates controls relative to other controls. + /// + public static class ControlLocator + { + /// + /// Tracks a named control relative to another control. + /// + /// + /// The control relative from which the other control should be found. + /// + /// The name of the control to find. + public static IObservable Track(ILogical relativeTo, string name) + { + var attached = Observable.FromEventPattern( + x => relativeTo.AttachedToLogicalTree += x, + x => relativeTo.AttachedToLogicalTree -= x) + .Select(x => ((ILogical)x.Sender).FindNameScope()) + .StartWith(relativeTo.FindNameScope()); + + var detached = Observable.FromEventPattern( + x => relativeTo.DetachedFromLogicalTree += x, + x => relativeTo.DetachedFromLogicalTree -= x) + .Select(x => (INameScope)null); + + return attached.Merge(detached).Select(nameScope => + { + if (nameScope != null) + { + var registered = Observable.FromEventPattern( + x => nameScope.Registered += x, + x => nameScope.Registered -= x) + .Where(x => x.EventArgs.Name == name) + .Select(x => x.EventArgs.Element) + .OfType(); + var unregistered = Observable.FromEventPattern( + x => nameScope.Unregistered += x, + x => nameScope.Unregistered -= x) + .Where(x => x.EventArgs.Name == name) + .Select(_ => (ILogical)null); + return registered + .StartWith(nameScope.Find(name)) + .Merge(unregistered); + } + else + { + return Observable.Return(null); + } + }).Switch(); + } + + public static IObservable Track(ILogical relativeTo, int ancestorLevel, Type ancestorType = null) + { + return TrackAttachmentToTree(relativeTo).Select(isAttachedToTree => + { + if (isAttachedToTree) + { + return relativeTo.GetLogicalAncestors() + .Where(x => ancestorType?.GetTypeInfo().IsAssignableFrom(x.GetType().GetTypeInfo()) ?? true) + .ElementAtOrDefault(ancestorLevel); + } + else + { + return null; + } + }); + } + + private static IObservable TrackAttachmentToTree(ILogical relativeTo) + { + var attached = Observable.FromEventPattern( + x => relativeTo.AttachedToLogicalTree += x, + x => relativeTo.AttachedToLogicalTree -= x) + .Select(x => true) + .StartWith(relativeTo.IsAttachedToLogicalTree); + + var detached = Observable.FromEventPattern( + x => relativeTo.DetachedFromLogicalTree += x, + x => relativeTo.DetachedFromLogicalTree -= x) + .Select(x => false); + + var attachmentStatus = attached.Merge(detached); + return attachmentStatus; + } + } +} diff --git a/src/Avalonia.Styling/StyledElement.cs b/src/Avalonia.Styling/StyledElement.cs new file mode 100644 index 0000000000..bd0d0b8c1f --- /dev/null +++ b/src/Avalonia.Styling/StyledElement.cs @@ -0,0 +1,783 @@ +using System; +using System.Collections.Generic; +using System.Collections.Specialized; +using System.Linq; +using System.Reactive.Linq; +using System.Reactive.Subjects; +using System.Text; +using Avalonia.Animation; +using Avalonia.Collections; +using Avalonia.Controls; +using Avalonia.Data; +using Avalonia.Diagnostics; +using Avalonia.Logging; +using Avalonia.LogicalTree; +using Avalonia.Styling; + +namespace Avalonia +{ + /// + /// Extends an with the following features: + /// + /// - An inherited . + /// - Implements to allow styling to work on the styled element. + /// - Implements to form part of a logical tree. + /// - A collection of class strings for custom styling. + /// + public class StyledElement : Animatable, IStyledElement, ISetLogicalParent, ISetInheritanceParent + { + /// + /// Defines the property. + /// + public static readonly StyledProperty DataContextProperty = + AvaloniaProperty.Register( + nameof(DataContext), + inherits: true, + notifying: DataContextNotifying); + + /// + /// Defines the property. + /// + public static readonly DirectProperty NameProperty = + AvaloniaProperty.RegisterDirect(nameof(Name), o => o.Name, (o, v) => o.Name = v); + + /// + /// Defines the property. + /// + public static readonly DirectProperty ParentProperty = + AvaloniaProperty.RegisterDirect(nameof(Parent), o => o.Parent); + + /// + /// Defines the property. + /// + public static readonly StyledProperty TemplatedParentProperty = + AvaloniaProperty.Register(nameof(TemplatedParent), inherits: true); + + private int _initCount; + private string _name; + private readonly Classes _classes = new Classes(); + private bool _isAttachedToLogicalTree; + private IAvaloniaList _logicalChildren; + private INameScope _nameScope; + private IResourceDictionary _resources; + private Styles _styles; + private bool _styled; + private Subject _styleDetach = new Subject(); + private bool _dataContextUpdating; + + /// + /// Initializes static members of the class. + /// + static StyledElement() + { + DataContextProperty.Changed.AddClassHandler(x => x.OnDataContextChangedCore); + } + + /// + /// Initializes a new instance of the class. + /// + public StyledElement() + { + _nameScope = this as INameScope; + _isAttachedToLogicalTree = this is IStyleRoot; + } + + /// + /// Raised when the styled element is attached to a rooted logical tree. + /// + public event EventHandler AttachedToLogicalTree; + + /// + /// Raised when the styled element is detached from a rooted logical tree. + /// + public event EventHandler DetachedFromLogicalTree; + + /// + /// Occurs when the property changes. + /// + /// + /// This event will be raised when the property has changed and + /// all subscribers to that change have been notified. + /// + public event EventHandler DataContextChanged; + + /// + /// Occurs when the styled element has finished initialization. + /// + /// + /// The Initialized event indicates that all property values on the styled element have been set. + /// When loading the styled element from markup, it occurs when + /// is called *and* the styled element + /// is attached to a rooted logical tree. When the styled element is created by code and + /// is not used, it is called when the styled element is attached + /// to the visual tree. + /// + public event EventHandler Initialized; + + /// + /// Occurs when a resource in this styled element or a parent styled element has changed. + /// + public event EventHandler ResourcesChanged; + + /// + /// Gets or sets the name of the styled element. + /// + /// + /// An element's name is used to uniquely identify an element within the element's name + /// scope. Once the element is added to a logical tree, its name cannot be changed. + /// + 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 : styled element already styled."); + } + + _name = value; + } + } + + /// + /// Gets or sets the styled element's classes. + /// + /// + /// + /// Classes can be used to apply user-defined styling to styled elements, or to allow styled elements + /// that share a common purpose to be easily selected. + /// + /// + /// 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. + /// + /// + public Classes Classes + { + get + { + return _classes; + } + + set + { + if (_classes != value) + { + _classes.Replace(value); + } + } + } + + /// + /// Gets or sets the control's data context. + /// + /// + /// The data context is an inherited property that specifies the default object that will + /// be used for data binding. + /// + public object DataContext + { + get { return GetValue(DataContextProperty); } + set { SetValue(DataContextProperty, value); } + } + + /// + /// Gets a value that indicates whether the element has finished initialization. + /// + /// + /// For more information about when IsInitialized is set, see the + /// event. + /// + public bool IsInitialized { get; private set; } + + /// + /// Gets the styles for the styled element. + /// + /// + /// Styles for the entire application are added to the Application.Styles collection, but + /// each styled element may in addition define its own styles which are applied to the styled element + /// itself and its children. + /// + public Styles Styles + { + get { return _styles ?? (Styles = new Styles()); } + set + { + Contract.Requires(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; + } + } + } + + /// + /// Gets or sets the styled element's resource dictionary. + /// + public IResourceDictionary Resources + { + get => _resources ?? (Resources = new ResourceDictionary()); + set + { + Contract.Requires(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()); + } + } + } + + /// + /// Gets the styled element whose lookless template this styled element is part of. + /// + public ITemplatedControl TemplatedParent + { + get { return GetValue(TemplatedParentProperty); } + internal set { SetValue(TemplatedParentProperty, value); } + } + + /// + /// Gets the styled element's logical children. + /// + protected IAvaloniaList LogicalChildren + { + get + { + if (_logicalChildren == null) + { + var list = new AvaloniaList + { + ResetBehavior = ResetBehavior.Remove, + Validate = ValidateLogicalChild + }; + list.CollectionChanged += LogicalChildrenCollectionChanged; + _logicalChildren = list; + } + + return _logicalChildren; + } + } + + /// + /// Gets the collection in a form that allows adding and removing + /// pseudoclasses. + /// + protected IPseudoClasses PseudoClasses => Classes; + + /// + /// Gets a value indicating whether the element is attached to a rooted logical tree. + /// + bool ILogical.IsAttachedToLogicalTree => _isAttachedToLogicalTree; + + /// + /// Gets the styled element's logical parent. + /// + public IStyledElement Parent { get; private set; } + + /// + /// Gets the styled element's logical parent. + /// + ILogical ILogical.LogicalParent => Parent; + + /// + /// Gets the styled element's logical children. + /// + IAvaloniaReadOnlyList ILogical.LogicalChildren => LogicalChildren; + + /// + bool IResourceProvider.HasResources => _resources?.Count > 0 || Styles.HasResources; + + /// + IResourceNode IResourceNode.ResourceParent => ((IStyleHost)this).StylingParent as IResourceNode; + + /// + IAvaloniaReadOnlyList IStyleable.Classes => Classes; + + /// + /// Gets the type by which the styled element is styled. + /// + /// + /// Usually controls are styled by their own type, but there are instances where you want + /// a styled element 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. + /// + Type IStyleable.StyleKey => GetType(); + + /// + IObservable IStyleable.StyleDetach => _styleDetach; + + /// + bool IStyleHost.IsStylesInitialized => _styles != null; + + /// + IStyleHost IStyleHost.StylingParent => (IStyleHost)InheritanceParent; + + /// + public virtual void BeginInit() + { + ++_initCount; + } + + /// + 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; + } + } + + protected void InitializeIfNeeded() + { + if (_initCount == 0 && !IsInitialized) + { + IsInitialized = true; + Initialized?.Invoke(this, EventArgs.Empty); + } + } + + /// + void ILogical.NotifyAttachedToLogicalTree(LogicalTreeAttachmentEventArgs e) + { + this.OnAttachedToLogicalTreeCore(e); + } + + /// + void ILogical.NotifyDetachedFromLogicalTree(LogicalTreeAttachmentEventArgs e) + { + this.OnDetachedFromLogicalTreeCore(e); + } + + /// + void ILogical.NotifyResourcesChanged(ResourcesChangedEventArgs e) + { + ResourcesChanged?.Invoke(this, new ResourcesChangedEventArgs()); + } + + /// + bool IResourceProvider.TryGetResource(string key, out object value) + { + value = null; + return (_resources?.TryGetResource(key, out value) ?? false) || + (_styles?.TryGetResource(key, out value) ?? false); + } + + /// + /// Sets the styled element's logical parent. + /// + /// The parent. + 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 = (IStyledElement)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); + } + } + + /// + /// Sets the styled element's inheritance parent. + /// + /// The parent. + void ISetInheritanceParent.SetParent(IAvaloniaObject parent) + { + InheritanceParent = parent; + } + + /// + /// Adds a pseudo-class to be set when a property is true. + /// + /// The property. + /// The pseudo-class. + protected static void PseudoClass(AvaloniaProperty property, string className) + { + PseudoClass(property, x => x, className); + } + + /// + /// Adds a pseudo-class to be set when a property equals a certain value. + /// + /// The type of the property. + /// The property. + /// Returns a boolean value based on the property value. + /// The pseudo-class. + protected static void PseudoClass( + AvaloniaProperty property, + Func selector, + string className) + { + Contract.Requires(property != null); + Contract.Requires(selector != null); + Contract.Requires(className != null); + + if (string.IsNullOrWhiteSpace(className)) + { + throw new ArgumentException("Cannot supply an empty className."); + } + + property.Changed.Merge(property.Initialized) + .Where(e => e.Sender is StyledElement) + .Subscribe(e => + { + if (selector((T)e.NewValue)) + { + ((StyledElement)e.Sender).PseudoClasses.Add(className); + } + else + { + ((StyledElement)e.Sender).PseudoClasses.Remove(className); + } + }); + } + + /// + /// Called when the styled element is added to a rooted logical tree. + /// + /// The event args. + protected virtual void OnAttachedToLogicalTree(LogicalTreeAttachmentEventArgs e) + { + } + + /// + /// Called when the styled element is removed from a rooted logical tree. + /// + /// The event args. + protected virtual void OnDetachedFromLogicalTree(LogicalTreeAttachmentEventArgs e) + { + } + + /// + /// Called when the property changes. + /// + /// The event args. + protected virtual void OnDataContextChanged(EventArgs e) + { + DataContextChanged?.Invoke(this, EventArgs.Empty); + } + + /// + /// Called when the begins updating. + /// + protected virtual void OnDataContextBeginUpdate() + { + } + + /// + /// Called when the finishes updating. + /// + protected virtual void OnDataContextEndUpdate() + { + } + + private static void DataContextNotifying(IAvaloniaObject o, bool updateStarted) + { + if (o is StyledElement element) + { + DataContextNotifying(element, updateStarted); + } + } + + private static void DataContextNotifying(StyledElement element, bool updateStarted) + { + if (updateStarted) + { + if (!element._dataContextUpdating) + { + element._dataContextUpdating = true; + element.OnDataContextBeginUpdate(); + + foreach (var child in element.LogicalChildren) + { + if (child is StyledElement s && + s.InheritanceParent == element && + !s.IsSet(DataContextProperty)) + { + DataContextNotifying(s, updateStarted); + } + } + } + } + else + { + if (element._dataContextUpdating) + { + element.OnDataContextEndUpdate(); + element._dataContextUpdating = false; + } + } + } + + private static IStyleRoot FindStyleRoot(IStyleHost e) + { + while (e != null) + { + if (e is IStyleRoot root) + { + return root; + } + + e = e.StylingParent; + } + + return null; + } + + private void ApplyStyling() + { + AvaloniaLocator.Current.GetService()?.ApplyStyles(this); + } + + private void RegisterWithNameScope() + { + if (_nameScope == null) + { + _nameScope = NameScope.GetNameScope(this) ?? ((StyledElement)Parent)?._nameScope; + } + + if (Name != null) + { + _nameScope?.Register(Name, this); + + var visualParent = Parent as StyledElement; + + 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()) + { + 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()) + { + 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()); + break; + + case NotifyCollectionChangedAction.Remove: + ClearLogicalParent(e.OldItems.Cast()); + break; + + case NotifyCollectionChangedAction.Replace: + ClearLogicalParent(e.OldItems.Cast()); + SetLogicalParent(e.NewItems.Cast()); + break; + + case NotifyCollectionChangedAction.Reset: + throw new NotSupportedException("Reset should not be signalled on LogicalChildren collection"); + } + } + + private void SetLogicalParent(IEnumerable children) + { + foreach (var i in children) + { + if (i.LogicalParent == null) + { + ((ISetLogicalParent)i).SetParent(this); + } + } + } + + private void ClearLogicalParent(IEnumerable 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); + } + } +} diff --git a/src/Avalonia.Styling/Styling/IRequiresTemplateInSetter.cs b/src/Avalonia.Styling/Styling/IRequiresTemplateInSetter.cs new file mode 100644 index 0000000000..1f64326526 --- /dev/null +++ b/src/Avalonia.Styling/Styling/IRequiresTemplateInSetter.cs @@ -0,0 +1,14 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace Avalonia.Styling +{ + /// + /// This is an interface for advanced scenarios to assist users in correct style development. + /// You as a user will not need to use this interface directly. + /// + public interface IRequiresTemplateInSetter + { + } +} diff --git a/src/Avalonia.Styling/Styling/IStyleable.cs b/src/Avalonia.Styling/Styling/IStyleable.cs index e03231e4ac..98c8501681 100644 --- a/src/Avalonia.Styling/Styling/IStyleable.cs +++ b/src/Avalonia.Styling/Styling/IStyleable.cs @@ -3,6 +3,7 @@ using System; using Avalonia.Collections; +using Avalonia.LogicalTree; namespace Avalonia.Styling { diff --git a/src/Avalonia.Styling/Styling/Setter.cs b/src/Avalonia.Styling/Styling/Setter.cs index e4d0ce5fab..1a78e0f4d7 100644 --- a/src/Avalonia.Styling/Styling/Setter.cs +++ b/src/Avalonia.Styling/Styling/Setter.cs @@ -5,6 +5,7 @@ using System; using System.Reactive.Disposables; using System.Reactive.Subjects; using System.Reflection; +using Avalonia.Animation; using Avalonia.Controls; using Avalonia.Data; using Avalonia.Metadata; @@ -19,7 +20,7 @@ namespace Avalonia.Styling /// A is used to set a value on a /// depending on a condition. /// - public class Setter : ISetter + public class Setter : ISetter, IAnimationSetter { private object _value; @@ -65,10 +66,10 @@ namespace Avalonia.Styling set { - if (value is IStyleable) + if (value is IRequiresTemplateInSetter) { throw new ArgumentException( - "Cannot assign a control to Style.Value. Wrap the control in a