committed by
GitHub
278 changed files with 7957 additions and 3715 deletions
@ -1,11 +1,159 @@ |
|||
; This file is for unifying the coding style for different editors and IDEs. |
|||
; More information at http://EditorConfig.org |
|||
# editorconfig.org |
|||
|
|||
# top-most EditorConfig file |
|||
root = true |
|||
|
|||
# Default settings: |
|||
# A newline ending every file |
|||
# Use 4 spaces as indentation |
|||
[*] |
|||
end_of_line = CRLF |
|||
insert_final_newline = true |
|||
indent_style = space |
|||
indent_size = 4 |
|||
|
|||
# C# files |
|||
[*.cs] |
|||
indent_style = space |
|||
# New line preferences |
|||
csharp_new_line_before_open_brace = all |
|||
csharp_new_line_before_else = true |
|||
csharp_new_line_before_catch = true |
|||
csharp_new_line_before_finally = true |
|||
csharp_new_line_before_members_in_object_initializers = true |
|||
csharp_new_line_before_members_in_anonymous_types = true |
|||
csharp_new_line_between_query_expression_clauses = true |
|||
|
|||
# Indentation preferences |
|||
csharp_indent_block_contents = true |
|||
csharp_indent_braces = false |
|||
csharp_indent_case_contents = true |
|||
csharp_indent_switch_labels = true |
|||
csharp_indent_labels = one_less_than_current |
|||
|
|||
# avoid this. unless absolutely necessary |
|||
dotnet_style_qualification_for_field = false:suggestion |
|||
dotnet_style_qualification_for_property = false:suggestion |
|||
dotnet_style_qualification_for_method = false:suggestion |
|||
dotnet_style_qualification_for_event = false:suggestion |
|||
|
|||
# prefer var |
|||
csharp_style_var_for_built_in_types = true |
|||
csharp_style_var_when_type_is_apparent = true |
|||
csharp_style_var_elsewhere = true:suggestion |
|||
|
|||
# use language keywords instead of BCL types |
|||
dotnet_style_predefined_type_for_locals_parameters_members = true:suggestion |
|||
dotnet_style_predefined_type_for_member_access = true:suggestion |
|||
|
|||
# name all constant fields using PascalCase |
|||
dotnet_naming_rule.constant_fields_should_be_pascal_case.severity = suggestion |
|||
dotnet_naming_rule.constant_fields_should_be_pascal_case.symbols = constant_fields |
|||
dotnet_naming_rule.constant_fields_should_be_pascal_case.style = pascal_case_style |
|||
|
|||
dotnet_naming_symbols.constant_fields.applicable_kinds = field |
|||
dotnet_naming_symbols.constant_fields.required_modifiers = const |
|||
|
|||
dotnet_naming_style.pascal_case_style.capitalization = pascal_case |
|||
|
|||
# static fields should have s_ prefix |
|||
dotnet_naming_rule.static_fields_should_have_prefix.severity = suggestion |
|||
dotnet_naming_rule.static_fields_should_have_prefix.symbols = static_fields |
|||
dotnet_naming_rule.static_fields_should_have_prefix.style = static_prefix_style |
|||
|
|||
dotnet_naming_symbols.static_fields.applicable_kinds = field |
|||
dotnet_naming_symbols.static_fields.required_modifiers = static |
|||
|
|||
dotnet_naming_style.static_prefix_style.required_prefix = s_ |
|||
dotnet_naming_style.static_prefix_style.capitalization = camel_case |
|||
|
|||
# internal and private fields should be _camelCase |
|||
dotnet_naming_rule.camel_case_for_private_internal_fields.severity = suggestion |
|||
dotnet_naming_rule.camel_case_for_private_internal_fields.symbols = private_internal_fields |
|||
dotnet_naming_rule.camel_case_for_private_internal_fields.style = camel_case_underscore_style |
|||
|
|||
dotnet_naming_symbols.private_internal_fields.applicable_kinds = field |
|||
dotnet_naming_symbols.private_internal_fields.applicable_accessibilities = private, internal |
|||
|
|||
dotnet_naming_style.camel_case_underscore_style.required_prefix = _ |
|||
dotnet_naming_style.camel_case_underscore_style.capitalization = camel_case |
|||
|
|||
# use accessibility modifiers |
|||
dotnet_style_require_accessibility_modifiers = for_non_interface_members:suggestion |
|||
|
|||
# Code style defaults |
|||
dotnet_sort_system_directives_first = true |
|||
csharp_preserve_single_line_blocks = true |
|||
csharp_preserve_single_line_statements = false |
|||
|
|||
# Expression-level preferences |
|||
dotnet_style_object_initializer = true:suggestion |
|||
dotnet_style_collection_initializer = true:suggestion |
|||
dotnet_style_explicit_tuple_names = true:suggestion |
|||
dotnet_style_coalesce_expression = true:suggestion |
|||
dotnet_style_null_propagation = true:suggestion |
|||
|
|||
# Expression-bodied members |
|||
csharp_style_expression_bodied_methods = false:none |
|||
csharp_style_expression_bodied_constructors = false:none |
|||
csharp_style_expression_bodied_operators = false:none |
|||
csharp_style_expression_bodied_properties = true:none |
|||
csharp_style_expression_bodied_indexers = true:none |
|||
csharp_style_expression_bodied_accessors = true:none |
|||
|
|||
# Pattern matching |
|||
csharp_style_pattern_matching_over_is_with_cast_check = true:suggestion |
|||
csharp_style_pattern_matching_over_as_with_null_check = true:suggestion |
|||
csharp_style_inlined_variable_declaration = true:suggestion |
|||
|
|||
# Null checking preferences |
|||
csharp_style_throw_expression = true:suggestion |
|||
csharp_style_conditional_delegate_call = true:suggestion |
|||
|
|||
# Space preferences |
|||
csharp_space_after_cast = false |
|||
csharp_space_after_colon_in_inheritance_clause = true |
|||
csharp_space_after_comma = true |
|||
csharp_space_after_dot = false |
|||
csharp_space_after_keywords_in_control_flow_statements = true |
|||
csharp_space_after_semicolon_in_for_statement = true |
|||
csharp_space_around_binary_operators = before_and_after |
|||
csharp_space_around_declaration_statements = do_not_ignore |
|||
csharp_space_before_colon_in_inheritance_clause = true |
|||
csharp_space_before_comma = false |
|||
csharp_space_before_dot = false |
|||
csharp_space_before_open_square_brackets = false |
|||
csharp_space_before_semicolon_in_for_statement = false |
|||
csharp_space_between_empty_square_brackets = false |
|||
csharp_space_between_method_call_empty_parameter_list_parentheses = false |
|||
csharp_space_between_method_call_name_and_opening_parenthesis = false |
|||
csharp_space_between_method_call_parameter_list_parentheses = false |
|||
csharp_space_between_method_declaration_empty_parameter_list_parentheses = false |
|||
csharp_space_between_method_declaration_name_and_open_parenthesis = false |
|||
csharp_space_between_method_declaration_parameter_list_parentheses = false |
|||
csharp_space_between_parentheses = false |
|||
csharp_space_between_square_brackets = false |
|||
|
|||
# Xaml files |
|||
[*.xaml] |
|||
indent_size = 4 |
|||
|
|||
# Xml project files |
|||
[*.{csproj,vcxproj,vcxproj.filters,proj,nativeproj,locproj}] |
|||
indent_size = 2 |
|||
|
|||
# Xml build files |
|||
[*.builds] |
|||
indent_size = 2 |
|||
|
|||
# Xml files |
|||
[*.{xml,stylecop,resx,ruleset}] |
|||
indent_size = 2 |
|||
|
|||
# Xml config files |
|||
[*.{props,targets,config,nuspec}] |
|||
indent_size = 2 |
|||
|
|||
# Shell scripts |
|||
[*.sh] |
|||
end_of_line = lf |
|||
[*.{cmd, bat}] |
|||
end_of_line = crlf |
|||
|
|||
@ -0,0 +1,5 @@ |
|||
<ProjectConfiguration> |
|||
<Settings> |
|||
<IgnoreThisComponentCompletely>True</IgnoreThisComponentCompletely> |
|||
</Settings> |
|||
</ProjectConfiguration> |
|||
@ -0,0 +1,5 @@ |
|||
<ProjectConfiguration> |
|||
<Settings> |
|||
<IgnoreThisComponentCompletely>True</IgnoreThisComponentCompletely> |
|||
</Settings> |
|||
</ProjectConfiguration> |
|||
@ -0,0 +1,5 @@ |
|||
<ProjectConfiguration> |
|||
<Settings> |
|||
<IgnoreThisComponentCompletely>True</IgnoreThisComponentCompletely> |
|||
</Settings> |
|||
</ProjectConfiguration> |
|||
@ -0,0 +1,5 @@ |
|||
<ProjectConfiguration> |
|||
<Settings> |
|||
<IgnoreThisComponentCompletely>True</IgnoreThisComponentCompletely> |
|||
</Settings> |
|||
</ProjectConfiguration> |
|||
@ -0,0 +1,5 @@ |
|||
<ProjectConfiguration> |
|||
<Settings> |
|||
<IgnoreThisComponentCompletely>True</IgnoreThisComponentCompletely> |
|||
</Settings> |
|||
</ProjectConfiguration> |
|||
@ -0,0 +1,5 @@ |
|||
<ProjectConfiguration> |
|||
<Settings> |
|||
<IgnoreThisComponentCompletely>True</IgnoreThisComponentCompletely> |
|||
</Settings> |
|||
</ProjectConfiguration> |
|||
@ -0,0 +1,5 @@ |
|||
<ProjectConfiguration> |
|||
<Settings> |
|||
<IgnoreThisComponentCompletely>True</IgnoreThisComponentCompletely> |
|||
</Settings> |
|||
</ProjectConfiguration> |
|||
@ -0,0 +1,5 @@ |
|||
<ProjectConfiguration> |
|||
<Settings> |
|||
<IgnoreThisComponentCompletely>True</IgnoreThisComponentCompletely> |
|||
</Settings> |
|||
</ProjectConfiguration> |
|||
@ -0,0 +1,5 @@ |
|||
<ProjectConfiguration> |
|||
<Settings> |
|||
<IgnoreThisComponentCompletely>True</IgnoreThisComponentCompletely> |
|||
</Settings> |
|||
</ProjectConfiguration> |
|||
@ -0,0 +1,5 @@ |
|||
<Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> |
|||
<ItemGroup> |
|||
<PackageReference Include="System.Memory" Version="4.5.0" /> |
|||
</ItemGroup> |
|||
</Project> |
|||
@ -0,0 +1,60 @@ |
|||
using System; |
|||
using System.Collections.Generic; |
|||
using System.Reactive.Linq; |
|||
using System.Text; |
|||
using Avalonia.Reactive; |
|||
|
|||
namespace Avalonia.Data.Core |
|||
{ |
|||
public class AvaloniaPropertyAccessorNode : SettableNode |
|||
{ |
|||
private IDisposable _subscription; |
|||
private readonly bool _enableValidation; |
|||
private readonly AvaloniaProperty _property; |
|||
|
|||
public AvaloniaPropertyAccessorNode(AvaloniaProperty property, bool enableValidation) |
|||
{ |
|||
_property = property; |
|||
_enableValidation = enableValidation; |
|||
} |
|||
|
|||
public override string Description => PropertyName; |
|||
public string PropertyName { get; } |
|||
public override Type PropertyType => _property.PropertyType; |
|||
|
|||
protected override bool SetTargetValueCore(object value, BindingPriority priority) |
|||
{ |
|||
try |
|||
{ |
|||
if (Target.IsAlive && Target.Target is IAvaloniaObject obj) |
|||
{ |
|||
obj.SetValue(_property, value, priority); |
|||
return true; |
|||
} |
|||
return false; |
|||
} |
|||
catch |
|||
{ |
|||
return false; |
|||
} |
|||
} |
|||
|
|||
protected override void StartListeningCore(WeakReference reference) |
|||
{ |
|||
if (reference.Target is IAvaloniaObject obj) |
|||
{ |
|||
_subscription = new AvaloniaPropertyObservable<object>(obj, _property).Subscribe(ValueChanged); |
|||
} |
|||
else |
|||
{ |
|||
_subscription = null; |
|||
} |
|||
} |
|||
|
|||
protected override void StopListeningCore() |
|||
{ |
|||
_subscription?.Dispose(); |
|||
_subscription = null; |
|||
} |
|||
} |
|||
} |
|||
@ -1,30 +0,0 @@ |
|||
// 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 Avalonia.Data.Core.Parsers; |
|||
|
|||
namespace Avalonia.Data.Core |
|||
{ |
|||
internal static class ExpressionNodeBuilder |
|||
{ |
|||
public static ExpressionNode Build(string expression, bool enableValidation = false) |
|||
{ |
|||
if (string.IsNullOrWhiteSpace(expression)) |
|||
{ |
|||
throw new ArgumentException("'expression' may not be empty."); |
|||
} |
|||
|
|||
var reader = new Reader(expression); |
|||
var parser = new ExpressionParser(enableValidation); |
|||
var node = parser.Parse(reader); |
|||
|
|||
if (!reader.End) |
|||
{ |
|||
throw new ExpressionParseException(reader.Position, "Expected end of expression."); |
|||
} |
|||
|
|||
return node; |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,71 @@ |
|||
using System; |
|||
using System.Collections.Generic; |
|||
using System.ComponentModel; |
|||
using System.Linq.Expressions; |
|||
using System.Reflection; |
|||
using System.Text; |
|||
using Avalonia.Data; |
|||
|
|||
namespace Avalonia.Data.Core |
|||
{ |
|||
class IndexerExpressionNode : IndexerNodeBase |
|||
{ |
|||
private readonly ParameterExpression _parameter; |
|||
private readonly IndexExpression _expression; |
|||
private readonly Delegate _setDelegate; |
|||
private readonly Delegate _getDelegate; |
|||
private readonly Delegate _firstArgumentDelegate; |
|||
|
|||
public IndexerExpressionNode(IndexExpression expression) |
|||
{ |
|||
_parameter = Expression.Parameter(expression.Object.Type); |
|||
_expression = expression.Update(_parameter, expression.Arguments); |
|||
|
|||
_getDelegate = Expression.Lambda(_expression, _parameter).Compile(); |
|||
|
|||
var valueParameter = Expression.Parameter(expression.Type); |
|||
|
|||
_setDelegate = Expression.Lambda(Expression.Assign(_expression, valueParameter), _parameter, valueParameter).Compile(); |
|||
|
|||
_firstArgumentDelegate = Expression.Lambda(_expression.Arguments[0], _parameter).Compile(); |
|||
} |
|||
|
|||
public override Type PropertyType => _expression.Type; |
|||
|
|||
public override string Description => _expression.ToString(); |
|||
|
|||
protected override bool SetTargetValueCore(object value, BindingPriority priority) |
|||
{ |
|||
try |
|||
{ |
|||
_setDelegate.DynamicInvoke(Target.Target, value); |
|||
return true; |
|||
} |
|||
catch (Exception) |
|||
{ |
|||
return false; |
|||
} |
|||
} |
|||
|
|||
protected override object GetValue(object target) |
|||
{ |
|||
try |
|||
{ |
|||
return _getDelegate.DynamicInvoke(target); |
|||
} |
|||
catch (TargetInvocationException e) when (e.InnerException is ArgumentOutOfRangeException |
|||
|| e.InnerException is IndexOutOfRangeException |
|||
|| e.InnerException is KeyNotFoundException) |
|||
{ |
|||
return AvaloniaProperty.UnsetValue; |
|||
} |
|||
} |
|||
|
|||
protected override bool ShouldUpdate(object sender, PropertyChangedEventArgs e) |
|||
{ |
|||
return _expression.Indexer == null || _expression.Indexer.Name == e.PropertyName; |
|||
} |
|||
|
|||
protected override int? TryGetFirstArgumentAsInt() => _firstArgumentDelegate.DynamicInvoke(Target.Target) as int?; |
|||
} |
|||
} |
|||
@ -0,0 +1,92 @@ |
|||
using System; |
|||
using System.Collections; |
|||
using System.Collections.Generic; |
|||
using System.Collections.Specialized; |
|||
using System.ComponentModel; |
|||
using System.Globalization; |
|||
using System.Linq; |
|||
using System.Reactive.Linq; |
|||
using System.Reflection; |
|||
using System.Text; |
|||
using Avalonia.Data; |
|||
using Avalonia.Utilities; |
|||
|
|||
namespace Avalonia.Data.Core |
|||
{ |
|||
public abstract class IndexerNodeBase : SettableNode |
|||
{ |
|||
private IDisposable _subscription; |
|||
|
|||
protected override void StartListeningCore(WeakReference reference) |
|||
{ |
|||
var target = reference.Target; |
|||
var incc = target as INotifyCollectionChanged; |
|||
var inpc = target as INotifyPropertyChanged; |
|||
var inputs = new List<IObservable<object>>(); |
|||
|
|||
if (incc != null) |
|||
{ |
|||
inputs.Add(WeakObservable.FromEventPattern<INotifyCollectionChanged, NotifyCollectionChangedEventArgs>( |
|||
incc, |
|||
nameof(incc.CollectionChanged)) |
|||
.Where(x => ShouldUpdate(x.Sender, x.EventArgs)) |
|||
.Select(_ => GetValue(target))); |
|||
} |
|||
|
|||
if (inpc != null) |
|||
{ |
|||
inputs.Add(WeakObservable.FromEventPattern<INotifyPropertyChanged, PropertyChangedEventArgs>( |
|||
inpc, |
|||
nameof(inpc.PropertyChanged)) |
|||
.Where(x => ShouldUpdate(x.Sender, x.EventArgs)) |
|||
.Select(_ => GetValue(target))); |
|||
} |
|||
|
|||
_subscription = Observable.Merge(inputs).StartWith(GetValue(target)).Subscribe(ValueChanged); |
|||
} |
|||
|
|||
protected override void StopListeningCore() |
|||
{ |
|||
_subscription.Dispose(); |
|||
} |
|||
|
|||
protected abstract object GetValue(object target); |
|||
|
|||
protected abstract int? TryGetFirstArgumentAsInt(); |
|||
|
|||
private bool ShouldUpdate(object sender, NotifyCollectionChangedEventArgs e) |
|||
{ |
|||
if (sender is IList) |
|||
{ |
|||
var index = TryGetFirstArgumentAsInt(); |
|||
|
|||
if (index == null) |
|||
{ |
|||
return false; |
|||
} |
|||
|
|||
switch (e.Action) |
|||
{ |
|||
case NotifyCollectionChangedAction.Add: |
|||
return index >= e.NewStartingIndex; |
|||
case NotifyCollectionChangedAction.Remove: |
|||
return index >= e.OldStartingIndex; |
|||
case NotifyCollectionChangedAction.Replace: |
|||
return index >= e.NewStartingIndex && |
|||
index < e.NewStartingIndex + e.NewItems.Count; |
|||
case NotifyCollectionChangedAction.Move: |
|||
return (index >= e.NewStartingIndex && |
|||
index < e.NewStartingIndex + e.NewItems.Count) || |
|||
(index >= e.OldStartingIndex && |
|||
index < e.OldStartingIndex + e.OldItems.Count); |
|||
case NotifyCollectionChangedAction.Reset: |
|||
return true; |
|||
} |
|||
} |
|||
|
|||
return true; // Implementation defined meaning for the index, so just try to update anyway
|
|||
} |
|||
|
|||
protected abstract bool ShouldUpdate(object sender, PropertyChangedEventArgs e); |
|||
} |
|||
} |
|||
@ -1,199 +0,0 @@ |
|||
// 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.Collections.Generic; |
|||
using System.Linq; |
|||
|
|||
namespace Avalonia.Data.Core.Parsers |
|||
{ |
|||
internal class ExpressionParser |
|||
{ |
|||
private bool _enableValidation; |
|||
|
|||
public ExpressionParser(bool enableValidation) |
|||
{ |
|||
_enableValidation = enableValidation; |
|||
} |
|||
|
|||
public ExpressionNode Parse(Reader r) |
|||
{ |
|||
var nodes = new List<ExpressionNode>(); |
|||
var state = State.Start; |
|||
|
|||
while (!r.End && state != State.End) |
|||
{ |
|||
switch (state) |
|||
{ |
|||
case State.Start: |
|||
state = ParseStart(r, nodes); |
|||
break; |
|||
|
|||
case State.AfterMember: |
|||
state = ParseAfterMember(r, nodes); |
|||
break; |
|||
|
|||
case State.BeforeMember: |
|||
state = ParseBeforeMember(r, nodes); |
|||
break; |
|||
|
|||
case State.AttachedProperty: |
|||
state = ParseAttachedProperty(r, nodes); |
|||
break; |
|||
|
|||
case State.Indexer: |
|||
state = ParseIndexer(r, nodes); |
|||
break; |
|||
} |
|||
} |
|||
|
|||
if (state == State.BeforeMember) |
|||
{ |
|||
throw new ExpressionParseException(r.Position, "Unexpected end of expression."); |
|||
} |
|||
|
|||
for (int n = 0; n < nodes.Count - 1; ++n) |
|||
{ |
|||
nodes[n].Next = nodes[n + 1]; |
|||
} |
|||
|
|||
return nodes.FirstOrDefault(); |
|||
} |
|||
|
|||
private State ParseStart(Reader r, IList<ExpressionNode> nodes) |
|||
{ |
|||
if (ParseNot(r)) |
|||
{ |
|||
nodes.Add(new LogicalNotNode()); |
|||
return State.Start; |
|||
} |
|||
else if (ParseOpenBrace(r)) |
|||
{ |
|||
return State.AttachedProperty; |
|||
} |
|||
else if (PeekOpenBracket(r)) |
|||
{ |
|||
return State.Indexer; |
|||
} |
|||
else |
|||
{ |
|||
var identifier = IdentifierParser.Parse(r); |
|||
|
|||
if (identifier != null) |
|||
{ |
|||
nodes.Add(new PropertyAccessorNode(identifier, _enableValidation)); |
|||
return State.AfterMember; |
|||
} |
|||
} |
|||
|
|||
return State.End; |
|||
} |
|||
|
|||
private static State ParseAfterMember(Reader r, IList<ExpressionNode> nodes) |
|||
{ |
|||
if (ParseMemberAccessor(r)) |
|||
{ |
|||
return State.BeforeMember; |
|||
} |
|||
else if (ParseStreamOperator(r)) |
|||
{ |
|||
nodes.Add(new StreamNode()); |
|||
return State.AfterMember; |
|||
} |
|||
else if (PeekOpenBracket(r)) |
|||
{ |
|||
return State.Indexer; |
|||
} |
|||
|
|||
return State.End; |
|||
} |
|||
|
|||
private State ParseBeforeMember(Reader r, IList<ExpressionNode> nodes) |
|||
{ |
|||
if (ParseOpenBrace(r)) |
|||
{ |
|||
return State.AttachedProperty; |
|||
} |
|||
else |
|||
{ |
|||
var identifier = IdentifierParser.Parse(r); |
|||
|
|||
if (identifier != null) |
|||
{ |
|||
nodes.Add(new PropertyAccessorNode(identifier, _enableValidation)); |
|||
return State.AfterMember; |
|||
} |
|||
|
|||
return State.End; |
|||
} |
|||
} |
|||
|
|||
private State ParseAttachedProperty(Reader r, List<ExpressionNode> nodes) |
|||
{ |
|||
var owner = IdentifierParser.Parse(r); |
|||
|
|||
if (r.End || !r.TakeIf('.')) |
|||
{ |
|||
throw new ExpressionParseException(r.Position, "Invalid attached property name."); |
|||
} |
|||
|
|||
var name = IdentifierParser.Parse(r); |
|||
|
|||
if (r.End || !r.TakeIf(')')) |
|||
{ |
|||
throw new ExpressionParseException(r.Position, "Expected ')'."); |
|||
} |
|||
|
|||
nodes.Add(new PropertyAccessorNode(owner + '.' + name, _enableValidation)); |
|||
return State.AfterMember; |
|||
} |
|||
|
|||
private State ParseIndexer(Reader r, List<ExpressionNode> nodes) |
|||
{ |
|||
var args = ArgumentListParser.Parse(r, '[', ']'); |
|||
|
|||
if (args.Count == 0) |
|||
{ |
|||
throw new ExpressionParseException(r.Position, "Indexer may not be empty."); |
|||
} |
|||
|
|||
nodes.Add(new IndexerNode(args)); |
|||
return State.AfterMember; |
|||
} |
|||
|
|||
private static bool ParseNot(Reader r) |
|||
{ |
|||
return !r.End && r.TakeIf('!'); |
|||
} |
|||
|
|||
private static bool ParseMemberAccessor(Reader r) |
|||
{ |
|||
return !r.End && r.TakeIf('.'); |
|||
} |
|||
|
|||
private static bool ParseOpenBrace(Reader r) |
|||
{ |
|||
return !r.End && r.TakeIf('('); |
|||
} |
|||
|
|||
private static bool PeekOpenBracket(Reader r) |
|||
{ |
|||
return !r.End && r.Peek == '['; |
|||
} |
|||
|
|||
private static bool ParseStreamOperator(Reader r) |
|||
{ |
|||
return !r.End && r.TakeIf('^'); |
|||
} |
|||
|
|||
private enum State |
|||
{ |
|||
Start, |
|||
AfterMember, |
|||
BeforeMember, |
|||
AttachedProperty, |
|||
Indexer, |
|||
End, |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,27 @@ |
|||
using System; |
|||
using System.Collections.Generic; |
|||
using System.Linq; |
|||
using System.Linq.Expressions; |
|||
using System.Text; |
|||
|
|||
namespace Avalonia.Data.Core.Parsers |
|||
{ |
|||
static class ExpressionTreeParser |
|||
{ |
|||
public static ExpressionNode Parse(Expression expr, bool enableDataValidation) |
|||
{ |
|||
var visitor = new ExpressionVisitorNodeBuilder(enableDataValidation); |
|||
|
|||
visitor.Visit(expr); |
|||
|
|||
var nodes = visitor.Nodes; |
|||
|
|||
for (int n = 0; n < nodes.Count - 1; ++n) |
|||
{ |
|||
nodes[n].Next = nodes[n + 1]; |
|||
} |
|||
|
|||
return nodes.FirstOrDefault() ?? new EmptyExpressionNode(); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,219 @@ |
|||
using System; |
|||
using System.Collections.Generic; |
|||
using System.Diagnostics; |
|||
using System.Linq; |
|||
using System.Linq.Expressions; |
|||
using System.Reflection; |
|||
using System.Text; |
|||
|
|||
namespace Avalonia.Data.Core.Parsers |
|||
{ |
|||
class ExpressionVisitorNodeBuilder : ExpressionVisitor |
|||
{ |
|||
private const string MultiDimensionalArrayGetterMethodName = "Get"; |
|||
private static PropertyInfo AvaloniaObjectIndexer; |
|||
private static MethodInfo CreateDelegateMethod; |
|||
|
|||
private readonly bool _enableDataValidation; |
|||
|
|||
static ExpressionVisitorNodeBuilder() |
|||
{ |
|||
AvaloniaObjectIndexer = typeof(AvaloniaObject).GetProperty("Item", new[] { typeof(AvaloniaProperty) }); |
|||
CreateDelegateMethod = typeof(MethodInfo).GetMethod("CreateDelegate", new[] { typeof(Type), typeof(object) }); |
|||
} |
|||
|
|||
public List<ExpressionNode> Nodes { get; } |
|||
|
|||
public ExpressionVisitorNodeBuilder(bool enableDataValidation) |
|||
{ |
|||
_enableDataValidation = enableDataValidation; |
|||
Nodes = new List<ExpressionNode>(); |
|||
} |
|||
|
|||
protected override Expression VisitUnary(UnaryExpression node) |
|||
{ |
|||
if (node.NodeType == ExpressionType.Not && node.Type == typeof(bool)) |
|||
{ |
|||
Nodes.Add(new LogicalNotNode()); |
|||
} |
|||
else if (node.NodeType == ExpressionType.Convert) |
|||
{ |
|||
if (node.Operand.Type.IsAssignableFrom(node.Type)) |
|||
{ |
|||
// Ignore inheritance casts
|
|||
} |
|||
else |
|||
{ |
|||
throw new ExpressionParseException(0, $"Cannot parse non-inheritance casts in a binding expression."); |
|||
} |
|||
} |
|||
else if (node.NodeType == ExpressionType.TypeAs) |
|||
{ |
|||
// Ignore as operator.
|
|||
} |
|||
else |
|||
{ |
|||
throw new ExpressionParseException(0, $"Unable to parse unary operator {node.NodeType} in a binding expression"); |
|||
} |
|||
|
|||
return base.VisitUnary(node); |
|||
} |
|||
|
|||
protected override Expression VisitMember(MemberExpression node) |
|||
{ |
|||
var visited = base.VisitMember(node); |
|||
Nodes.Add(new PropertyAccessorNode(node.Member.Name, _enableDataValidation)); |
|||
return visited; |
|||
} |
|||
|
|||
protected override Expression VisitIndex(IndexExpression node) |
|||
{ |
|||
Visit(node.Object); |
|||
|
|||
if (node.Indexer == AvaloniaObjectIndexer) |
|||
{ |
|||
var property = GetArgumentExpressionValue<AvaloniaProperty>(node.Arguments[0]); |
|||
Nodes.Add(new AvaloniaPropertyAccessorNode(property, _enableDataValidation)); |
|||
} |
|||
else |
|||
{ |
|||
Nodes.Add(new IndexerExpressionNode(node)); |
|||
} |
|||
|
|||
return node; |
|||
} |
|||
|
|||
private T GetArgumentExpressionValue<T>(Expression expr) |
|||
{ |
|||
try |
|||
{ |
|||
return Expression.Lambda<Func<T>>(expr).Compile(preferInterpretation: true)(); |
|||
} |
|||
catch (InvalidOperationException ex) |
|||
{ |
|||
throw new ExpressionParseException(0, "Unable to parse indexer value.", ex); |
|||
} |
|||
} |
|||
|
|||
protected override Expression VisitBinary(BinaryExpression node) |
|||
{ |
|||
if (node.NodeType == ExpressionType.ArrayIndex) |
|||
{ |
|||
return Visit(Expression.MakeIndex(node.Left, null, new[] { node.Right })); |
|||
} |
|||
throw new ExpressionParseException(0, $"Invalid expression type in binding expression: {node.NodeType}."); |
|||
} |
|||
|
|||
protected override Expression VisitBlock(BlockExpression node) |
|||
{ |
|||
throw new ExpressionParseException(0, $"Invalid expression type in binding expression: {node.NodeType}."); |
|||
} |
|||
|
|||
protected override CatchBlock VisitCatchBlock(CatchBlock node) |
|||
{ |
|||
throw new ExpressionParseException(0, $"Catch blocks are not allowed in binding expressions."); |
|||
} |
|||
|
|||
protected override Expression VisitConditional(ConditionalExpression node) |
|||
{ |
|||
throw new ExpressionParseException(0, $"Invalid expression type in binding expression: {node.NodeType}."); |
|||
} |
|||
|
|||
protected override Expression VisitDynamic(DynamicExpression node) |
|||
{ |
|||
throw new ExpressionParseException(0, $"Dynamic expressions are not allowed in binding expressions."); |
|||
} |
|||
|
|||
protected override ElementInit VisitElementInit(ElementInit node) |
|||
{ |
|||
throw new ExpressionParseException(0, $"Element init expressions are not valid in a binding expression."); |
|||
} |
|||
|
|||
protected override Expression VisitGoto(GotoExpression node) |
|||
{ |
|||
throw new ExpressionParseException(0, $"Goto expressions not supported in binding expressions."); |
|||
} |
|||
|
|||
protected override Expression VisitInvocation(InvocationExpression node) |
|||
{ |
|||
throw new ExpressionParseException(0, $"Invalid expression type in binding expression: {node.NodeType}."); |
|||
} |
|||
|
|||
protected override Expression VisitLabel(LabelExpression node) |
|||
{ |
|||
throw new ExpressionParseException(0, $"Invalid expression type in binding expression: {node.NodeType}."); |
|||
} |
|||
|
|||
protected override Expression VisitListInit(ListInitExpression node) |
|||
{ |
|||
throw new ExpressionParseException(0, $"Invalid expression type in binding expression: {node.NodeType}."); |
|||
} |
|||
|
|||
protected override Expression VisitLoop(LoopExpression node) |
|||
{ |
|||
throw new ExpressionParseException(0, $"Invalid expression type in binding expression: {node.NodeType}."); |
|||
} |
|||
|
|||
protected override MemberAssignment VisitMemberAssignment(MemberAssignment node) |
|||
{ |
|||
throw new ExpressionParseException(0, $"Member assignments not supported in binding expressions."); |
|||
} |
|||
|
|||
protected override Expression VisitMethodCall(MethodCallExpression node) |
|||
{ |
|||
if (node.Method == CreateDelegateMethod) |
|||
{ |
|||
var visited = Visit(node.Arguments[1]); |
|||
Nodes.Add(new PropertyAccessorNode(GetArgumentExpressionValue<MethodInfo>(node.Object).Name, _enableDataValidation)); |
|||
return node; |
|||
} |
|||
else if (node.Method.Name == StreamBindingExtensions.StreamBindingName || node.Method.Name.StartsWith(StreamBindingExtensions.StreamBindingName + '`')) |
|||
{ |
|||
if (node.Method.IsStatic) |
|||
{ |
|||
Visit(node.Arguments[0]); |
|||
} |
|||
else |
|||
{ |
|||
Visit(node.Object); |
|||
} |
|||
Nodes.Add(new StreamNode()); |
|||
return node; |
|||
} |
|||
|
|||
var property = TryGetPropertyFromMethod(node.Method); |
|||
|
|||
if (property != null) |
|||
{ |
|||
return Visit(Expression.MakeIndex(node.Object, property, node.Arguments)); |
|||
} |
|||
else if (node.Object.Type.IsArray && node.Method.Name == MultiDimensionalArrayGetterMethodName) |
|||
{ |
|||
return Visit(Expression.MakeIndex(node.Object, null, node.Arguments)); |
|||
} |
|||
|
|||
throw new ExpressionParseException(0, $"Invalid method call in binding expression: '{node.Method.DeclaringType.AssemblyQualifiedName}.{node.Method.Name}'."); |
|||
} |
|||
|
|||
private PropertyInfo TryGetPropertyFromMethod(MethodInfo method) |
|||
{ |
|||
var type = method.DeclaringType; |
|||
return type.GetRuntimeProperties().FirstOrDefault(prop => prop.GetMethod == method); |
|||
} |
|||
|
|||
protected override Expression VisitSwitch(SwitchExpression node) |
|||
{ |
|||
throw new ExpressionParseException(0, $"Invalid expression type in binding expression: {node.NodeType}."); |
|||
} |
|||
|
|||
protected override Expression VisitTry(TryExpression node) |
|||
{ |
|||
throw new ExpressionParseException(0, $"Invalid expression type in binding expression: {node.NodeType}."); |
|||
} |
|||
|
|||
protected override Expression VisitTypeBinary(TypeBinaryExpression node) |
|||
{ |
|||
throw new ExpressionParseException(0, $"Invalid expression type in binding expression: {node.NodeType}."); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,27 @@ |
|||
using System; |
|||
using System.Collections.Generic; |
|||
using System.Text; |
|||
using System.Threading.Tasks; |
|||
|
|||
namespace Avalonia |
|||
{ |
|||
public static class StreamBindingExtensions |
|||
{ |
|||
internal static string StreamBindingName = "StreamBinding"; |
|||
|
|||
public static T StreamBinding<T>(this Task<T> @this) |
|||
{ |
|||
throw new InvalidOperationException("This should be used only in a binding expression"); |
|||
} |
|||
|
|||
public static object StreamBinding(this Task @this) |
|||
{ |
|||
throw new InvalidOperationException("This should be used only in a binding expression"); |
|||
} |
|||
|
|||
public static T StreamBinding<T>(this IObservable<T> @this) |
|||
{ |
|||
throw new InvalidOperationException("This should be used only in a binding expression"); |
|||
} |
|||
} |
|||
} |
|||
@ -1,42 +0,0 @@ |
|||
// 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.Reactive; |
|||
using System.Reactive.Disposables; |
|||
|
|||
namespace Avalonia.Reactive |
|||
{ |
|||
/// <summary>
|
|||
/// An <see cref="IObservable{T}"/> with an additional description.
|
|||
/// </summary>
|
|||
/// <typeparam name="T">The type of the elements in the sequence.</typeparam>
|
|||
public class AvaloniaObservable<T> : ObservableBase<T>, IDescription |
|||
{ |
|||
private readonly Func<IObserver<T>, IDisposable> _subscribe; |
|||
|
|||
/// <summary>
|
|||
/// Initializes a new instance of the <see cref="AvaloniaObservable{T}"/> class.
|
|||
/// </summary>
|
|||
/// <param name="subscribe">The subscribe function.</param>
|
|||
/// <param name="description">The description of the observable.</param>
|
|||
public AvaloniaObservable(Func<IObserver<T>, IDisposable> subscribe, string description) |
|||
{ |
|||
Contract.Requires<ArgumentNullException>(subscribe != null); |
|||
|
|||
_subscribe = subscribe; |
|||
Description = description; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Gets the description of the observable.
|
|||
/// </summary>
|
|||
public string Description { get; } |
|||
|
|||
/// <inheritdoc/>
|
|||
protected override IDisposable SubscribeCore(IObserver<T> observer) |
|||
{ |
|||
return _subscribe(observer) ?? Disposable.Empty; |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,46 @@ |
|||
using System; |
|||
|
|||
namespace Avalonia.Reactive |
|||
{ |
|||
internal class AvaloniaPropertyChangedObservable : |
|||
LightweightObservableBase<AvaloniaPropertyChangedEventArgs>, |
|||
IDescription |
|||
{ |
|||
private readonly WeakReference<IAvaloniaObject> _target; |
|||
private readonly AvaloniaProperty _property; |
|||
|
|||
public AvaloniaPropertyChangedObservable( |
|||
IAvaloniaObject target, |
|||
AvaloniaProperty property) |
|||
{ |
|||
_target = new WeakReference<IAvaloniaObject>(target); |
|||
_property = property; |
|||
} |
|||
|
|||
public string Description => $"{_target.GetType().Name}.{_property.Name}"; |
|||
|
|||
protected override void Initialize() |
|||
{ |
|||
if (_target.TryGetTarget(out var target)) |
|||
{ |
|||
target.PropertyChanged += PropertyChanged; |
|||
} |
|||
} |
|||
|
|||
protected override void Deinitialize() |
|||
{ |
|||
if (_target.TryGetTarget(out var target)) |
|||
{ |
|||
target.PropertyChanged -= PropertyChanged; |
|||
} |
|||
} |
|||
|
|||
private void PropertyChanged(object sender, AvaloniaPropertyChangedEventArgs e) |
|||
{ |
|||
if (e.Property == _property) |
|||
{ |
|||
PublishNext(e); |
|||
} |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,52 @@ |
|||
using System; |
|||
|
|||
namespace Avalonia.Reactive |
|||
{ |
|||
internal class AvaloniaPropertyObservable<T> : LightweightObservableBase<T>, IDescription |
|||
{ |
|||
private readonly WeakReference<IAvaloniaObject> _target; |
|||
private readonly AvaloniaProperty _property; |
|||
private T _value; |
|||
|
|||
public AvaloniaPropertyObservable( |
|||
IAvaloniaObject target, |
|||
AvaloniaProperty property) |
|||
{ |
|||
_target = new WeakReference<IAvaloniaObject>(target); |
|||
_property = property; |
|||
} |
|||
|
|||
public string Description => $"{_target.GetType().Name}.{_property.Name}"; |
|||
|
|||
protected override void Initialize() |
|||
{ |
|||
if (_target.TryGetTarget(out var target)) |
|||
{ |
|||
_value = (T)target.GetValue(_property); |
|||
target.PropertyChanged += PropertyChanged; |
|||
} |
|||
} |
|||
|
|||
protected override void Deinitialize() |
|||
{ |
|||
if (_target.TryGetTarget(out var target)) |
|||
{ |
|||
target.PropertyChanged -= PropertyChanged; |
|||
} |
|||
} |
|||
|
|||
protected override void Subscribed(IObserver<T> observer, bool first) |
|||
{ |
|||
observer.OnNext(_value); |
|||
} |
|||
|
|||
private void PropertyChanged(object sender, AvaloniaPropertyChangedEventArgs e) |
|||
{ |
|||
if (e.Property == _property) |
|||
{ |
|||
_value = (T)e.NewValue; |
|||
PublishNext(_value); |
|||
} |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,202 @@ |
|||
using System; |
|||
using System.Collections.Generic; |
|||
using System.Reactive; |
|||
using System.Reactive.Disposables; |
|||
using System.Threading; |
|||
using Avalonia.Threading; |
|||
|
|||
namespace Avalonia.Reactive |
|||
{ |
|||
/// <summary>
|
|||
/// Lightweight base class for observable implementations.
|
|||
/// </summary>
|
|||
/// <typeparam name="T">The observable type.</typeparam>
|
|||
/// <remarks>
|
|||
/// <see cref="ObservableBase{T}"/> is rather heavyweight in terms of allocations and memory
|
|||
/// usage. This class provides a more lightweight base for some internal observable types
|
|||
/// in the Avalonia framework.
|
|||
/// </remarks>
|
|||
public abstract class LightweightObservableBase<T> : IObservable<T> |
|||
{ |
|||
private Exception _error; |
|||
private List<IObserver<T>> _observers = new List<IObserver<T>>(); |
|||
|
|||
public IDisposable Subscribe(IObserver<T> observer) |
|||
{ |
|||
Contract.Requires<ArgumentNullException>(observer != null); |
|||
Dispatcher.UIThread.VerifyAccess(); |
|||
|
|||
var first = false; |
|||
|
|||
for (; ; ) |
|||
{ |
|||
if (Volatile.Read(ref _observers) == null) |
|||
{ |
|||
if (_error != null) |
|||
{ |
|||
observer.OnError(_error); |
|||
} |
|||
else |
|||
{ |
|||
observer.OnCompleted(); |
|||
} |
|||
|
|||
return Disposable.Empty; |
|||
} |
|||
|
|||
lock (this) |
|||
{ |
|||
if (_observers == null) |
|||
{ |
|||
continue; |
|||
} |
|||
|
|||
first = _observers.Count == 0; |
|||
_observers.Add(observer); |
|||
break; |
|||
} |
|||
} |
|||
|
|||
if (first) |
|||
{ |
|||
Initialize(); |
|||
} |
|||
|
|||
Subscribed(observer, first); |
|||
|
|||
return new RemoveObserver(this, observer); |
|||
} |
|||
|
|||
void Remove(IObserver<T> observer) |
|||
{ |
|||
if (Volatile.Read(ref _observers) != null) |
|||
{ |
|||
lock (this) |
|||
{ |
|||
var observers = _observers; |
|||
|
|||
if (observers != null) |
|||
{ |
|||
observers.Remove(observer); |
|||
|
|||
if (observers.Count == 0) |
|||
{ |
|||
observers.TrimExcess(); |
|||
} |
|||
else |
|||
{ |
|||
return; |
|||
} |
|||
} else |
|||
{ |
|||
return; |
|||
} |
|||
} |
|||
|
|||
Deinitialize(); |
|||
} |
|||
} |
|||
|
|||
sealed class RemoveObserver : IDisposable |
|||
{ |
|||
LightweightObservableBase<T> _parent; |
|||
|
|||
IObserver<T> _observer; |
|||
|
|||
public RemoveObserver(LightweightObservableBase<T> parent, IObserver<T> observer) |
|||
{ |
|||
_parent = parent; |
|||
Volatile.Write(ref _observer, observer); |
|||
} |
|||
|
|||
public void Dispose() |
|||
{ |
|||
var observer = _observer; |
|||
Interlocked.Exchange(ref _parent, null)?.Remove(observer); |
|||
_observer = null; |
|||
} |
|||
} |
|||
|
|||
protected abstract void Initialize(); |
|||
protected abstract void Deinitialize(); |
|||
|
|||
protected void PublishNext(T value) |
|||
{ |
|||
if (Volatile.Read(ref _observers) != null) |
|||
{ |
|||
IObserver<T>[] observers; |
|||
|
|||
lock (this) |
|||
{ |
|||
if (_observers == null) |
|||
{ |
|||
return; |
|||
} |
|||
observers = _observers.ToArray(); |
|||
} |
|||
|
|||
foreach (var observer in observers) |
|||
{ |
|||
observer.OnNext(value); |
|||
} |
|||
} |
|||
} |
|||
|
|||
protected void PublishCompleted() |
|||
{ |
|||
if (Volatile.Read(ref _observers) != null) |
|||
{ |
|||
IObserver<T>[] observers; |
|||
|
|||
lock (this) |
|||
{ |
|||
if (_observers == null) |
|||
{ |
|||
return; |
|||
} |
|||
observers = _observers.ToArray(); |
|||
Volatile.Write(ref _observers, null); |
|||
} |
|||
|
|||
foreach (var observer in observers) |
|||
{ |
|||
observer.OnCompleted(); |
|||
} |
|||
|
|||
Deinitialize(); |
|||
} |
|||
} |
|||
|
|||
protected void PublishError(Exception error) |
|||
{ |
|||
if (Volatile.Read(ref _observers) != null) |
|||
{ |
|||
|
|||
IObserver<T>[] observers; |
|||
|
|||
lock (this) |
|||
{ |
|||
if (_observers == null) |
|||
{ |
|||
return; |
|||
} |
|||
|
|||
_error = error; |
|||
observers = _observers.ToArray(); |
|||
Volatile.Write(ref _observers, null); |
|||
} |
|||
|
|||
foreach (var observer in observers) |
|||
{ |
|||
observer.OnError(error); |
|||
} |
|||
|
|||
Deinitialize(); |
|||
} |
|||
} |
|||
|
|||
protected virtual void Subscribed(IObserver<T> observer, bool first) |
|||
{ |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,76 @@ |
|||
using System; |
|||
using Avalonia.Threading; |
|||
|
|||
namespace Avalonia.Reactive |
|||
{ |
|||
public abstract class SingleSubscriberObservableBase<T> : IObservable<T>, IDisposable |
|||
{ |
|||
private Exception _error; |
|||
private IObserver<T> _observer; |
|||
private bool _completed; |
|||
|
|||
public IDisposable Subscribe(IObserver<T> observer) |
|||
{ |
|||
Contract.Requires<ArgumentNullException>(observer != null); |
|||
Dispatcher.UIThread.VerifyAccess(); |
|||
|
|||
if (_observer != null) |
|||
{ |
|||
throw new InvalidOperationException("The observable can only be subscribed once."); |
|||
} |
|||
|
|||
if (_error != null) |
|||
{ |
|||
observer.OnError(_error); |
|||
} |
|||
else if (_completed) |
|||
{ |
|||
observer.OnCompleted(); |
|||
} |
|||
else |
|||
{ |
|||
_observer = observer; |
|||
Subscribed(); |
|||
} |
|||
|
|||
return this; |
|||
} |
|||
|
|||
void IDisposable.Dispose() |
|||
{ |
|||
Unsubscribed(); |
|||
_observer = null; |
|||
} |
|||
|
|||
protected abstract void Unsubscribed(); |
|||
|
|||
protected void PublishNext(T value) |
|||
{ |
|||
_observer?.OnNext(value); |
|||
} |
|||
|
|||
protected void PublishCompleted() |
|||
{ |
|||
if (_observer != null) |
|||
{ |
|||
_observer.OnCompleted(); |
|||
_completed = true; |
|||
Unsubscribed(); |
|||
_observer = null; |
|||
} |
|||
} |
|||
|
|||
protected void PublishError(Exception error) |
|||
{ |
|||
if (_observer != null) |
|||
{ |
|||
_observer.OnError(error); |
|||
_error = error; |
|||
Unsubscribed(); |
|||
_observer = null; |
|||
} |
|||
} |
|||
|
|||
protected abstract void Subscribed(); |
|||
} |
|||
} |
|||
@ -1,85 +0,0 @@ |
|||
// 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.Reactive; |
|||
using System.Reactive.Disposables; |
|||
using System.Reactive.Linq; |
|||
using System.Reactive.Subjects; |
|||
using Avalonia.Utilities; |
|||
|
|||
namespace Avalonia.Reactive |
|||
{ |
|||
internal class WeakPropertyChangedObservable : ObservableBase<object>, |
|||
IWeakSubscriber<AvaloniaPropertyChangedEventArgs>, IDescription |
|||
{ |
|||
private WeakReference<IAvaloniaObject> _sourceReference; |
|||
private readonly AvaloniaProperty _property; |
|||
private readonly Subject<object> _changed = new Subject<object>(); |
|||
|
|||
private int _count; |
|||
|
|||
public WeakPropertyChangedObservable( |
|||
WeakReference<IAvaloniaObject> source, |
|||
AvaloniaProperty property, |
|||
string description) |
|||
{ |
|||
_sourceReference = source; |
|||
_property = property; |
|||
Description = description; |
|||
} |
|||
|
|||
public string Description { get; } |
|||
|
|||
public void OnEvent(object sender, AvaloniaPropertyChangedEventArgs e) |
|||
{ |
|||
if (e.Property == _property) |
|||
{ |
|||
_changed.OnNext(e.NewValue); |
|||
} |
|||
} |
|||
|
|||
protected override IDisposable SubscribeCore(IObserver<object> observer) |
|||
{ |
|||
IAvaloniaObject instance; |
|||
|
|||
if (_sourceReference.TryGetTarget(out instance)) |
|||
{ |
|||
if (_count++ == 0) |
|||
{ |
|||
WeakSubscriptionManager.Subscribe( |
|||
instance, |
|||
nameof(instance.PropertyChanged), |
|||
this); |
|||
} |
|||
|
|||
observer.OnNext(instance.GetValue(_property)); |
|||
|
|||
return Observable.Using(() => Disposable.Create(DecrementCount), _ => _changed) |
|||
.Subscribe(observer); |
|||
} |
|||
else |
|||
{ |
|||
_changed.OnCompleted(); |
|||
observer.OnCompleted(); |
|||
return Disposable.Empty; |
|||
} |
|||
} |
|||
|
|||
private void DecrementCount() |
|||
{ |
|||
if (--_count == 0) |
|||
{ |
|||
IAvaloniaObject instance; |
|||
|
|||
if (_sourceReference.TryGetTarget(out instance)) |
|||
{ |
|||
WeakSubscriptionManager.Unsubscribe( |
|||
instance, |
|||
nameof(instance.PropertyChanged), |
|||
this); |
|||
} |
|||
} |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,58 @@ |
|||
using System; |
|||
using System.Collections.Generic; |
|||
using System.Text; |
|||
|
|||
namespace Avalonia.Utilities |
|||
{ |
|||
/// <summary>
|
|||
/// FIFO Queue optimized for holding zero or one items.
|
|||
/// </summary>
|
|||
/// <typeparam name="T">The type of items held in the queue.</typeparam>
|
|||
public class SingleOrQueue<T> |
|||
{ |
|||
private T _head; |
|||
private Queue<T> _tail; |
|||
|
|||
private Queue<T> Tail => _tail ?? (_tail = new Queue<T>()); |
|||
|
|||
private bool HasTail => _tail != null; |
|||
|
|||
public bool Empty { get; private set; } = true; |
|||
|
|||
public void Enqueue(T value) |
|||
{ |
|||
if (Empty) |
|||
{ |
|||
_head = value; |
|||
} |
|||
else |
|||
{ |
|||
Tail.Enqueue(value); |
|||
} |
|||
|
|||
Empty = false; |
|||
} |
|||
|
|||
public T Dequeue() |
|||
{ |
|||
if (Empty) |
|||
{ |
|||
throw new InvalidOperationException("Cannot dequeue from an empty queue!"); |
|||
} |
|||
|
|||
var result = _head; |
|||
|
|||
if (HasTail && Tail.Count != 0) |
|||
{ |
|||
_head = Tail.Dequeue(); |
|||
} |
|||
else |
|||
{ |
|||
_head = default; |
|||
Empty = true; |
|||
} |
|||
|
|||
return result; |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,182 @@ |
|||
using System; |
|||
using System.Collections.Generic; |
|||
using Avalonia.Data; |
|||
using Avalonia.Utilities; |
|||
|
|||
namespace Avalonia |
|||
{ |
|||
internal class ValueStore : IPriorityValueOwner |
|||
{ |
|||
private readonly AvaloniaObject _owner; |
|||
private readonly Dictionary<AvaloniaProperty, object> _values = |
|||
new Dictionary<AvaloniaProperty, object>(); |
|||
|
|||
public ValueStore(AvaloniaObject owner) |
|||
{ |
|||
_owner = owner; |
|||
} |
|||
|
|||
public IDisposable AddBinding( |
|||
AvaloniaProperty property, |
|||
IObservable<object> source, |
|||
BindingPriority priority) |
|||
{ |
|||
PriorityValue priorityValue; |
|||
|
|||
if (_values.TryGetValue(property, out var v)) |
|||
{ |
|||
priorityValue = v as PriorityValue; |
|||
|
|||
if (priorityValue == null) |
|||
{ |
|||
priorityValue = CreatePriorityValue(property); |
|||
priorityValue.SetValue(v, (int)BindingPriority.LocalValue); |
|||
_values[property] = priorityValue; |
|||
} |
|||
} |
|||
else |
|||
{ |
|||
priorityValue = CreatePriorityValue(property); |
|||
_values.Add(property, priorityValue); |
|||
} |
|||
|
|||
return priorityValue.Add(source, (int)priority); |
|||
} |
|||
|
|||
public void AddValue(AvaloniaProperty property, object value, int priority) |
|||
{ |
|||
PriorityValue priorityValue; |
|||
|
|||
if (_values.TryGetValue(property, out var v)) |
|||
{ |
|||
priorityValue = v as PriorityValue; |
|||
|
|||
if (priorityValue == null) |
|||
{ |
|||
if (priority == (int)BindingPriority.LocalValue) |
|||
{ |
|||
_values[property] = Validate(property, value); |
|||
Changed(property, priority, v, value); |
|||
return; |
|||
} |
|||
else |
|||
{ |
|||
priorityValue = CreatePriorityValue(property); |
|||
priorityValue.SetValue(v, (int)BindingPriority.LocalValue); |
|||
_values[property] = priorityValue; |
|||
} |
|||
} |
|||
} |
|||
else |
|||
{ |
|||
if (value == AvaloniaProperty.UnsetValue) |
|||
{ |
|||
return; |
|||
} |
|||
|
|||
if (priority == (int)BindingPriority.LocalValue) |
|||
{ |
|||
_values.Add(property, Validate(property, value)); |
|||
Changed(property, priority, AvaloniaProperty.UnsetValue, value); |
|||
return; |
|||
} |
|||
else |
|||
{ |
|||
priorityValue = CreatePriorityValue(property); |
|||
_values.Add(property, priorityValue); |
|||
} |
|||
} |
|||
|
|||
priorityValue.SetValue(value, priority); |
|||
} |
|||
|
|||
public void BindingNotificationReceived(AvaloniaProperty property, BindingNotification notification) |
|||
{ |
|||
_owner.BindingNotificationReceived(property, notification); |
|||
} |
|||
|
|||
public void Changed(AvaloniaProperty property, int priority, object oldValue, object newValue) |
|||
{ |
|||
_owner.PriorityValueChanged(property, priority, oldValue, newValue); |
|||
} |
|||
|
|||
public IDictionary<AvaloniaProperty, object> GetSetValues() => _values; |
|||
|
|||
public object GetValue(AvaloniaProperty property) |
|||
{ |
|||
var result = AvaloniaProperty.UnsetValue; |
|||
|
|||
if (_values.TryGetValue(property, out var value)) |
|||
{ |
|||
result = (value is PriorityValue priorityValue) ? priorityValue.Value : value; |
|||
} |
|||
|
|||
return result; |
|||
} |
|||
|
|||
public bool IsAnimating(AvaloniaProperty property) |
|||
{ |
|||
return _values.TryGetValue(property, out var value) && value is PriorityValue priority && priority.IsAnimating; |
|||
} |
|||
|
|||
public bool IsSet(AvaloniaProperty property) |
|||
{ |
|||
if (_values.TryGetValue(property, out var value)) |
|||
{ |
|||
return ((value as PriorityValue)?.Value ?? value) != AvaloniaProperty.UnsetValue; |
|||
} |
|||
|
|||
return false; |
|||
} |
|||
|
|||
public void Revalidate(AvaloniaProperty property) |
|||
{ |
|||
if (_values.TryGetValue(property, out var value)) |
|||
{ |
|||
(value as PriorityValue)?.Revalidate(); |
|||
} |
|||
} |
|||
|
|||
public void VerifyAccess() => _owner.VerifyAccess(); |
|||
|
|||
private PriorityValue CreatePriorityValue(AvaloniaProperty property) |
|||
{ |
|||
var validate = ((IStyledPropertyAccessor)property).GetValidationFunc(_owner.GetType()); |
|||
Func<object, object> validate2 = null; |
|||
|
|||
if (validate != null) |
|||
{ |
|||
validate2 = v => validate(_owner, v); |
|||
} |
|||
|
|||
return new PriorityValue( |
|||
this, |
|||
property, |
|||
property.PropertyType, |
|||
validate2); |
|||
} |
|||
|
|||
private object Validate(AvaloniaProperty property, object value) |
|||
{ |
|||
var validate = ((IStyledPropertyAccessor)property).GetValidationFunc(_owner.GetType()); |
|||
|
|||
if (validate != null && value != AvaloniaProperty.UnsetValue) |
|||
{ |
|||
return validate(_owner, value); |
|||
} |
|||
|
|||
return value; |
|||
} |
|||
|
|||
private DeferredSetter<object> _defferedSetter; |
|||
|
|||
public DeferredSetter<object> Setter |
|||
{ |
|||
get |
|||
{ |
|||
return _defferedSetter ?? |
|||
(_defferedSetter = new DeferredSetter<object>()); |
|||
} |
|||
} |
|||
} |
|||
} |
|||
Some files were not shown because too many files changed in this diff
Loading…
Reference in new issue