diff --git a/Avalonia.sln b/Avalonia.sln index 539ba04728..7b4a359f06 100644 --- a/Avalonia.sln +++ b/Avalonia.sln @@ -134,6 +134,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ControlCatalog.NetCore", "s 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 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/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/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/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/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.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