committed by
GitHub
275 changed files with 6643 additions and 3217 deletions
@ -1,11 +1,159 @@ |
|||||
; This file is for unifying the coding style for different editors and IDEs. |
# editorconfig.org |
||||
; More information at http://EditorConfig.org |
|
||||
|
|
||||
|
# top-most EditorConfig file |
||||
root = true |
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] |
[*.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 |
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> |
||||
@ -1,6 +1,6 @@ |
|||||
<Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> |
<Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> |
||||
<ItemGroup> |
<ItemGroup> |
||||
<PackageReference Include="SkiaSharp" Version="1.57.1" /> |
<PackageReference Include="SkiaSharp" Version="1.60.0" /> |
||||
<PackageReference Condition="'$(IncludeLinuxSkia)' == 'true'" Include="Avalonia.Skia.Linux.Natives" Version="1.57.1.3" /> |
<PackageReference Condition="'$(IncludeLinuxSkia)' == 'true'" Include="Avalonia.Skia.Linux.Natives" Version="1.60.0.1" /> |
||||
</ItemGroup> |
</ItemGroup> |
||||
</Project> |
</Project> |
||||
|
|||||
@ -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,15 @@ |
|||||
|
; This is the default configuration file for Cake. |
||||
|
; This file was downloaded from https://github.com/cake-build/resources |
||||
|
|
||||
|
[Nuget] |
||||
|
Source=https://api.nuget.org/v3/index.json |
||||
|
UseInProcessClient=true |
||||
|
LoadDependencies=false |
||||
|
|
||||
|
[Paths] |
||||
|
Tools=./tools |
||||
|
Addins=./tools/Addins |
||||
|
Modules=./tools/Modules |
||||
|
|
||||
|
[Settings] |
||||
|
SkipVerification=false |
||||
@ -1,7 +1,7 @@ |
|||||
<Window xmlns="https://github.com/avaloniaui" |
<Window xmlns="https://github.com/avaloniaui" |
||||
xmlns:x='http://schemas.microsoft.com/winfx/2006/xaml' |
xmlns:x='http://schemas.microsoft.com/winfx/2006/xaml' |
||||
xmlns:vm="clr-namespace:BindingTest.ViewModels" |
xmlns:vm="clr-namespace:BindingDemo.ViewModels" |
||||
xmlns:local="clr-namespace:BindingTest" |
xmlns:local="clr-namespace:BindingDemo" |
||||
Title="AvaloniaUI Bindings Test" |
Title="AvaloniaUI Bindings Test" |
||||
Width="800" |
Width="800" |
||||
Height="600"> |
Height="600"> |
||||
@ -1,9 +1,9 @@ |
|||||
using BindingTest.ViewModels; |
using BindingDemo.ViewModels; |
||||
using Avalonia; |
using Avalonia; |
||||
using Avalonia.Controls; |
using Avalonia.Controls; |
||||
using Avalonia.Markup.Xaml; |
using Avalonia.Markup.Xaml; |
||||
|
|
||||
namespace BindingTest |
namespace BindingDemo |
||||
{ |
{ |
||||
public class MainWindow : Window |
public class MainWindow : Window |
||||
{ |
{ |
||||
@ -1,7 +1,7 @@ |
|||||
using Avalonia.Controls; |
using Avalonia.Controls; |
||||
using Avalonia.Markup.Xaml; |
using Avalonia.Markup.Xaml; |
||||
|
|
||||
namespace BindingTest |
namespace BindingDemo |
||||
{ |
{ |
||||
public class TestItemView : UserControl |
public class TestItemView : UserControl |
||||
{ |
{ |
||||
@ -1,6 +1,6 @@ |
|||||
using ReactiveUI; |
using ReactiveUI; |
||||
|
|
||||
namespace BindingTest.ViewModels |
namespace BindingDemo.ViewModels |
||||
{ |
{ |
||||
public class TestItem : ReactiveObject |
public class TestItem : ReactiveObject |
||||
{ |
{ |
||||
@ -1,6 +1,6 @@ |
|||||
<Window xmlns="https://github.com/avaloniaui" |
<Window xmlns="https://github.com/avaloniaui" |
||||
Title="AvaloniaUI Rendering Test" |
Title="AvaloniaUI Rendering Test" |
||||
xmlns:pages="clr-namespace:RenderTest.Pages" |
xmlns:pages="clr-namespace:RenderDemo.Pages" |
||||
Width="800" |
Width="800" |
||||
Height="600"> |
Height="600"> |
||||
<DockPanel> |
<DockPanel> |
||||
@ -1,7 +1,7 @@ |
|||||
using Avalonia.Controls; |
using Avalonia.Controls; |
||||
using Avalonia.Markup.Xaml; |
using Avalonia.Markup.Xaml; |
||||
|
|
||||
namespace RenderTest.Pages |
namespace RenderDemo.Pages |
||||
{ |
{ |
||||
public class DrawingPage : UserControl |
public class DrawingPage : UserControl |
||||
{ |
{ |
||||
@ -1,7 +1,7 @@ |
|||||
using System; |
using System; |
||||
using ReactiveUI; |
using ReactiveUI; |
||||
|
|
||||
namespace RenderTest.ViewModels |
namespace RenderDemo.ViewModels |
||||
{ |
{ |
||||
public class MainWindowViewModel : ReactiveObject |
public class MainWindowViewModel : ReactiveObject |
||||
{ |
{ |
||||
@ -1,15 +0,0 @@ |
|||||
using Avalonia.Data; |
|
||||
using System; |
|
||||
using System.Collections.Generic; |
|
||||
using System.Linq; |
|
||||
using System.Text; |
|
||||
using System.Threading.Tasks; |
|
||||
|
|
||||
namespace Avalonia.Data.Core |
|
||||
{ |
|
||||
interface ISettableNode |
|
||||
{ |
|
||||
bool SetTargetValue(object value, BindingPriority priority); |
|
||||
Type PropertyType { get; } |
|
||||
} |
|
||||
} |
|
||||
@ -0,0 +1,38 @@ |
|||||
|
using Avalonia.Data; |
||||
|
using System; |
||||
|
using System.Collections.Generic; |
||||
|
using System.Linq; |
||||
|
using System.Text; |
||||
|
using System.Threading.Tasks; |
||||
|
|
||||
|
namespace Avalonia.Data.Core |
||||
|
{ |
||||
|
internal abstract class SettableNode : ExpressionNode |
||||
|
{ |
||||
|
public bool SetTargetValue(object value, BindingPriority priority) |
||||
|
{ |
||||
|
if (ShouldNotSet(value)) |
||||
|
{ |
||||
|
return true; |
||||
|
} |
||||
|
return SetTargetValueCore(value, priority); |
||||
|
} |
||||
|
|
||||
|
private bool ShouldNotSet(object value) |
||||
|
{ |
||||
|
if (PropertyType == null) |
||||
|
{ |
||||
|
return false; |
||||
|
} |
||||
|
if (PropertyType.IsValueType) |
||||
|
{ |
||||
|
return LastValue?.Target != null && LastValue.Target.Equals(value); |
||||
|
} |
||||
|
return LastValue != null && Object.ReferenceEquals(LastValue?.Target, value); |
||||
|
} |
||||
|
|
||||
|
protected abstract bool SetTargetValueCore(object value, BindingPriority priority); |
||||
|
|
||||
|
public abstract Type PropertyType { get; } |
||||
|
} |
||||
|
} |
||||
@ -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,172 @@ |
|||||
|
using System; |
||||
|
using System.Collections.Generic; |
||||
|
using Avalonia.Data; |
||||
|
|
||||
|
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) |
||||
|
{ |
||||
|
((IPriorityValueOwner)_owner).BindingNotificationReceived(property, notification); |
||||
|
} |
||||
|
|
||||
|
public void Changed(AvaloniaProperty property, int priority, object oldValue, object newValue) |
||||
|
{ |
||||
|
((IPriorityValueOwner)_owner).Changed(property, priority, oldValue, newValue); |
||||
|
} |
||||
|
|
||||
|
public IDictionary<AvaloniaProperty, PriorityValue> GetSetValues() => throw new NotImplementedException(); |
||||
|
|
||||
|
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 as PriorityValue)?.IsAnimating ?? false : false; |
||||
|
} |
||||
|
|
||||
|
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); |
||||
|
} |
||||
|
|
||||
|
PriorityValue result = new PriorityValue( |
||||
|
this, |
||||
|
property, |
||||
|
property.PropertyType, |
||||
|
validate2); |
||||
|
|
||||
|
return result; |
||||
|
} |
||||
|
|
||||
|
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; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
Some files were not shown because too many files changed in this diff
Loading…
Reference in new issue