diff --git a/build/AndroidWorkarounds.props b/build/AndroidWorkarounds.props
index 8a5c18e1ae..67947296b3 100644
--- a/build/AndroidWorkarounds.props
+++ b/build/AndroidWorkarounds.props
@@ -5,4 +5,12 @@
+
+
+
+
+
+
+ false
+
diff --git a/build/SharedVersion.props b/build/SharedVersion.props
index 76abcf6912..44d5c239ef 100644
--- a/build/SharedVersion.props
+++ b/build/SharedVersion.props
@@ -4,11 +4,16 @@
Avalonia
0.8.999
Copyright 2019 © The AvaloniaUI Project
- https://github.com/AvaloniaUI/Avalonia/blob/master/licence.md
- https://github.com/AvaloniaUI/Avalonia/
+ https://avaloniaui.net
https://github.com/AvaloniaUI/Avalonia/
true
CS1591
latest
+ MIT
+ https://avatars2.githubusercontent.com/u/14075148?s=200
+ Avalonia is a WPF/UWP-inspired cross-platform XAML-based UI framework providing a flexible styling system and supporting a wide range of Operating Systems such as Windows (.NET Framework, .NET Core), Linux (via Xorg), MacOS and with experimental support for Android and iOS.
+ avalonia;avaloniaui;mvvm;rx;reactive extensions;android;ios;mac;forms;wpf;net;netstandard;net461;uwp;xamarin
+ https://github.com/AvaloniaUI/Avalonia/releases
+ git
diff --git a/native/Avalonia.Native/inc/avalonia-native.h b/native/Avalonia.Native/inc/avalonia-native.h
index a35f4f3eeb..36e16c24d1 100644
--- a/native/Avalonia.Native/inc/avalonia-native.h
+++ b/native/Avalonia.Native/inc/avalonia-native.h
@@ -280,7 +280,7 @@ AVNCOM(IAvnPlatformThreadingInterface, 0b) : IUnknown
virtual bool GetCurrentThreadIsLoopThread() = 0;
virtual void SetSignaledCallback(IAvnSignaledCallback* cb) = 0;
virtual IAvnLoopCancellation* CreateLoopCancellation() = 0;
- virtual void RunLoop(IAvnLoopCancellation* cancel) = 0;
+ virtual HRESULT RunLoop(IAvnLoopCancellation* cancel) = 0;
// Can't pass int* to sharpgentools for some reason
virtual void Signal(int priority) = 0;
virtual IUnknown* StartTimer(int priority, int ms, IAvnActionCallback* callback) = 0;
diff --git a/native/Avalonia.Native/src/OSX/Avalonia.Native.OSX.xcodeproj/project.pbxproj b/native/Avalonia.Native/src/OSX/Avalonia.Native.OSX.xcodeproj/project.pbxproj
index cc74d5669f..1870ef7ab3 100644
--- a/native/Avalonia.Native/src/OSX/Avalonia.Native.OSX.xcodeproj/project.pbxproj
+++ b/native/Avalonia.Native/src/OSX/Avalonia.Native.OSX.xcodeproj/project.pbxproj
@@ -7,6 +7,7 @@
objects = {
/* Begin PBXBuildFile section */
+ 1A002B9E232135EE00021753 /* app.mm in Sources */ = {isa = PBXBuildFile; fileRef = 1A002B9D232135EE00021753 /* app.mm */; };
37A517B32159597E00FBA241 /* Screens.mm in Sources */ = {isa = PBXBuildFile; fileRef = 37A517B22159597E00FBA241 /* Screens.mm */; };
37C09D8821580FE4006A6758 /* SystemDialogs.mm in Sources */ = {isa = PBXBuildFile; fileRef = 37C09D8721580FE4006A6758 /* SystemDialogs.mm */; };
37DDA9B0219330F8002E132B /* AvnString.mm in Sources */ = {isa = PBXBuildFile; fileRef = 37DDA9AF219330F8002E132B /* AvnString.mm */; };
@@ -22,6 +23,7 @@
/* End PBXBuildFile section */
/* Begin PBXFileReference section */
+ 1A002B9D232135EE00021753 /* app.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = app.mm; sourceTree = ""; };
379860FE214DA0C000CD0246 /* KeyTransform.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = KeyTransform.h; sourceTree = ""; };
37A4E71A2178846A00EACBCD /* headers */ = {isa = PBXFileReference; lastKnownFileType = folder; name = headers; path = ../../inc; sourceTree = ""; };
37A517B22159597E00FBA241 /* Screens.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = Screens.mm; sourceTree = ""; };
@@ -68,6 +70,7 @@
AB7A61E62147C814003C5833 = {
isa = PBXGroup;
children = (
+ 1A002B9D232135EE00021753 /* app.mm */,
37DDA9B121933371002E132B /* AvnString.h */,
37DDA9AF219330F8002E132B /* AvnString.mm */,
37A4E71A2178846A00EACBCD /* headers */,
@@ -164,6 +167,7 @@
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
+ 1A002B9E232135EE00021753 /* app.mm in Sources */,
5B8BD94F215BFEA6005ED2A7 /* clipboard.mm in Sources */,
5B21A982216530F500CEE36E /* cursor.mm in Sources */,
37DDA9B0219330F8002E132B /* AvnString.mm in Sources */,
diff --git a/native/Avalonia.Native/src/OSX/app.mm b/native/Avalonia.Native/src/OSX/app.mm
new file mode 100644
index 0000000000..81855995b7
--- /dev/null
+++ b/native/Avalonia.Native/src/OSX/app.mm
@@ -0,0 +1,24 @@
+#include "common.h"
+@interface AvnAppDelegate : NSObject
+@end
+extern NSApplicationActivationPolicy AvnDesiredActivationPolicy = NSApplicationActivationPolicyRegular;
+@implementation AvnAppDelegate
+- (void)applicationWillFinishLaunching:(NSNotification *)notification
+{
+ [[NSApplication sharedApplication] setActivationPolicy: AvnDesiredActivationPolicy];
+}
+
+- (void)applicationDidFinishLaunching:(NSNotification *)notification
+{
+ [NSApp activateIgnoringOtherApps:true];
+}
+
+@end
+
+extern void InitializeAvnApp()
+{
+ NSApplication* app = [NSApplication sharedApplication];
+ id delegate = [AvnAppDelegate new];
+ [app setDelegate:delegate];
+
+}
diff --git a/native/Avalonia.Native/src/OSX/common.h b/native/Avalonia.Native/src/OSX/common.h
index f748dd59ad..45ec40c361 100644
--- a/native/Avalonia.Native/src/OSX/common.h
+++ b/native/Avalonia.Native/src/OSX/common.h
@@ -19,12 +19,12 @@ extern IAvnClipboard* CreateClipboard();
extern IAvnCursorFactory* CreateCursorFactory();
extern IAvnGlFeature* GetGlFeature();
extern IAvnGlSurfaceRenderTarget* CreateGlRenderTarget(NSWindow* window, NSView* view);
-
+extern void InitializeAvnApp();
+extern NSApplicationActivationPolicy AvnDesiredActivationPolicy;
extern NSPoint ToNSPoint (AvnPoint p);
extern AvnPoint ToAvnPoint (NSPoint p);
extern AvnPoint ConvertPointY (AvnPoint p);
extern NSSize ToNSSize (AvnSize s);
-
#ifdef DEBUG
#define NSDebugLog(...) NSLog(__VA_ARGS__)
#else
diff --git a/native/Avalonia.Native/src/OSX/main.mm b/native/Avalonia.Native/src/OSX/main.mm
index 5043246c53..70bd1e67f6 100644
--- a/native/Avalonia.Native/src/OSX/main.mm
+++ b/native/Avalonia.Native/src/OSX/main.mm
@@ -5,21 +5,14 @@
#define COM_GUIDS_MATERIALIZE
#include "common.h"
-static BOOL ShowInDock = 1;
-
-static void SetActivationPolicy()
-{
- [[NSApplication sharedApplication] setActivationPolicy: (ShowInDock ? NSApplicationActivationPolicyRegular : NSApplicationActivationPolicyAccessory)];
-}
-
class MacOptions : public ComSingleObject
{
public:
FORWARD_IUNKNOWN()
virtual HRESULT SetShowInDock(int show) override
{
- ShowInDock = show;
- SetActivationPolicy();
+ AvnDesiredActivationPolicy = show
+ ? NSApplicationActivationPolicyRegular : NSApplicationActivationPolicyAccessory;
return S_OK;
}
};
@@ -64,8 +57,9 @@ public:
{
@autoreleasepool{
[[ThreadingInitializer new] do];
- return S_OK;
}
+ InitializeAvnApp();
+ return S_OK;
};
virtual IAvnMacOptions* GetMacOptions() override
diff --git a/native/Avalonia.Native/src/OSX/platformthreading.mm b/native/Avalonia.Native/src/OSX/platformthreading.mm
index 0c814b2088..297097584a 100644
--- a/native/Avalonia.Native/src/OSX/platformthreading.mm
+++ b/native/Avalonia.Native/src/OSX/platformthreading.mm
@@ -57,16 +57,36 @@ class PlatformThreadingInterface : public ComSingleObject
{
public:
FORWARD_IUNKNOWN()
- bool Cancelled = 0;
- virtual void Cancel() override
+ bool Running = false;
+ bool Cancelled = false;
+ virtual void Cancel()
{
- Cancelled = 1;
+ Cancelled = true;
+ if(Running)
+ {
+ Running = false;
+ dispatch_async(dispatch_get_main_queue(), ^{
+ [[NSApplication sharedApplication] stop:nil];
+ NSEvent* event = [NSEvent otherEventWithType:NSEventTypeApplicationDefined
+ location:NSMakePoint(0, 0)
+ modifierFlags:0
+ timestamp:0
+ windowNumber:0
+ context:nil
+ subtype:0
+ data1:0
+ data2:0];
+ [NSApp postEvent:event atStart:YES];
+ });
+ }
}
+
};
public:
@@ -99,30 +119,17 @@ public:
return new LoopCancellation();
}
- virtual void RunLoop(IAvnLoopCancellation* cancel) override
+ virtual HRESULT RunLoop(IAvnLoopCancellation* cancel) override
{
- @autoreleasepool {
- auto can = dynamic_cast(cancel);
- [[NSApplication sharedApplication] activateIgnoringOtherApps:true];
- while(true)
- {
- @autoreleasepool
- {
- if(can != NULL && can->Cancelled)
- return;
- NSEvent* ev = [[NSApplication sharedApplication]
- nextEventMatchingMask:NSEventMaskAny
- untilDate: [NSDate dateWithTimeIntervalSinceNow:1]
- inMode:NSDefaultRunLoopMode
- dequeue:true];
- if(can != NULL && can->Cancelled)
- return;
- if(ev != NULL)
- [[NSApplication sharedApplication] sendEvent:ev];
- }
- }
- NSDebugLog(@"RunLoop exited");
- }
+ auto can = dynamic_cast(cancel);
+ if(can->Cancelled)
+ return S_OK;
+ if(_wasRunningAtLeastOnce)
+ return E_FAIL;
+ can->Running = true;
+ _wasRunningAtLeastOnce = true;
+ [NSApp run];
+ return S_OK;
}
virtual void Signal(int priority) override
diff --git a/samples/ControlCatalog.NetCore/Program.cs b/samples/ControlCatalog.NetCore/Program.cs
index 5aef0b5520..d683092edf 100644
--- a/samples/ControlCatalog.NetCore/Program.cs
+++ b/samples/ControlCatalog.NetCore/Program.cs
@@ -4,22 +4,16 @@ using System.Globalization;
using System.Linq;
using System.Threading;
using Avalonia;
-using Avalonia.Controls;
-using Avalonia.LinuxFramebuffer.Output;
-using Avalonia.Skia;
using Avalonia.ReactiveUI;
using Avalonia.Dialogs;
-using System.Collections.Generic;
-using System.Threading.Tasks;
namespace ControlCatalog.NetCore
{
static class Program
{
-
+ [STAThread]
static int Main(string[] args)
{
- Thread.CurrentThread.TrySetApartmentState(ApartmentState.STA);
if (args.Contains("--wait-for-attach"))
{
Console.WriteLine("Attach debugger and use 'Set next statement'");
diff --git a/scripts/ReplaceNugetCache.sh b/scripts/ReplaceNugetCache.sh
index 4cc11edd60..e1c0487d60 100755
--- a/scripts/ReplaceNugetCache.sh
+++ b/scripts/ReplaceNugetCache.sh
@@ -2,7 +2,6 @@
cp ../samples/ControlCatalog.NetCore/bin/Debug/netcoreapp2.0/Avalonia**.dll ~/.nuget/packages/avalonia/$1/lib/netcoreapp2.0/
cp ../samples/ControlCatalog.NetCore/bin/Debug/netcoreapp2.0/Avalonia**.dll ~/.nuget/packages/avalonia/$1/lib/netstandard2.0/
- cp ../samples/ControlCatalog.NetCore/bin/Debug/netcoreapp2.0/Avalonia**.dll ~/.nuget/packages/avalonia.gtk3/$1/lib/netstandard2.0/
cp ../samples/ControlCatalog.NetCore/bin/Debug/netcoreapp2.0/Avalonia**.dll ~/.nuget/packages/avalonia.skia/$1/lib/netstandard2.0/
cp ../samples/ControlCatalog.NetCore/bin/Debug/netcoreapp2.0/Avalonia**.dll ~/.nuget/packages/avalonia.native/$1/lib/netstandard2.0/
diff --git a/src/Avalonia.Animation/Animatable.cs b/src/Avalonia.Animation/Animatable.cs
index 3a3d00b94a..2c321b8b28 100644
--- a/src/Avalonia.Animation/Animatable.cs
+++ b/src/Avalonia.Animation/Animatable.cs
@@ -45,16 +45,17 @@ namespace Avalonia.Animation
{
get
{
- if (_transitions == null)
+ if (_transitions is null)
_transitions = new Transitions();
- if (_previousTransitions == null)
+ if (_previousTransitions is null)
_previousTransitions = new Dictionary();
return _transitions;
}
set
{
+
SetAndRaise(TransitionsProperty, ref _transitions, value);
}
}
@@ -66,18 +67,20 @@ namespace Avalonia.Animation
/// The event args.
protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs e)
{
- if (e.Priority != BindingPriority.Animation && Transitions != null && _previousTransitions != null)
- {
- var match = Transitions.FirstOrDefault(x => x.Property == e.Property);
+ if (_transitions is null || _previousTransitions is null || e.Priority == BindingPriority.Animation) return;
- if (match != null)
+ // PERF-SENSITIVE: Called on every property change. Don't use LINQ here (too many allocations).
+ foreach (var transition in Transitions)
+ {
+ if (transition.Property == e.Property)
{
if (_previousTransitions.TryGetValue(e.Property, out var dispose))
dispose.Dispose();
- var instance = match.Apply(this, Clock ?? Avalonia.Animation.Clock.GlobalClock, e.OldValue, e.NewValue);
+ var instance = transition.Apply(this, Clock ?? Avalonia.Animation.Clock.GlobalClock, e.OldValue, e.NewValue);
_previousTransitions[e.Property] = instance;
+ return;
}
}
}
diff --git a/src/Avalonia.Base/AvaloniaObject.cs b/src/Avalonia.Base/AvaloniaObject.cs
index 0e2f0feada..8cc512d132 100644
--- a/src/Avalonia.Base/AvaloniaObject.cs
+++ b/src/Avalonia.Base/AvaloniaObject.cs
@@ -82,6 +82,7 @@ namespace Avalonia
set
{
+ VerifyAccess();
if (_inheritanceParent != value)
{
if (_inheritanceParent != null)
@@ -89,25 +90,33 @@ namespace Avalonia
_inheritanceParent.InheritablePropertyChanged -= ParentPropertyChanged;
}
- var properties = AvaloniaPropertyRegistry.Instance.GetRegistered(this)
- .Concat(AvaloniaPropertyRegistry.Instance.GetRegisteredAttached(this.GetType()));
- var inherited = (from property in properties
- where property.Inherits
- select new
- {
- Property = property,
- Value = GetValue(property),
- }).ToList();
-
+ var oldInheritanceParent = _inheritanceParent;
_inheritanceParent = value;
+ var valuestore = _values;
- foreach (var i in inherited)
+ foreach (var property in AvaloniaPropertyRegistry.Instance.GetRegisteredInherited(GetType()))
{
- object newValue = GetValue(i.Property);
+ if (valuestore != null && valuestore.GetValue(property) != AvaloniaProperty.UnsetValue)
+ {
+ // if local value set there can be no change
+ continue;
+ }
+ // get the value as it would have been with the previous InheritanceParent
+ object oldValue;
+ if (oldInheritanceParent is AvaloniaObject aobj)
+ {
+ oldValue = aobj.GetValueOrDefaultUnchecked(property);
+ }
+ else
+ {
+ oldValue = ((IStyledPropertyAccessor)property).GetDefaultValue(GetType());
+ }
- if (!Equals(i.Value, newValue))
+ object newValue = GetDefaultValue(property);
+
+ if (!Equals(oldValue, newValue))
{
- RaisePropertyChanged(i.Property, i.Value, newValue, BindingPriority.LocalValue);
+ RaisePropertyChanged(property, oldValue, newValue, BindingPriority.LocalValue);
}
}
@@ -208,20 +217,9 @@ namespace Avalonia
{
return ((IDirectPropertyAccessor)GetRegistered(property)).GetValue(this);
}
- else if (_values != null)
- {
- var result = Values.GetValue(property);
-
- if (result == AvaloniaProperty.UnsetValue)
- {
- result = GetDefaultValue(property);
- }
-
- return result;
- }
else
{
- return GetDefaultValue(property);
+ return GetValueOrDefaultUnchecked(property);
}
}
@@ -598,10 +596,46 @@ namespace Avalonia
private object GetDefaultValue(AvaloniaProperty property)
{
if (property.Inherits && InheritanceParent is AvaloniaObject aobj)
- return aobj.GetValue(property);
+ return aobj.GetValueOrDefaultUnchecked(property);
return ((IStyledPropertyAccessor) property).GetDefaultValue(GetType());
}
+ ///
+ /// Gets the value or default value for a property.
+ ///
+ /// The property.
+ /// The default value.
+ private object GetValueOrDefaultUnchecked(AvaloniaProperty property)
+ {
+ var aobj = this;
+ var valuestore = aobj._values;
+ if (valuestore != null)
+ {
+ var result = valuestore.GetValue(property);
+ if (result != AvaloniaProperty.UnsetValue)
+ {
+ return result;
+ }
+ }
+ if (property.Inherits)
+ {
+ while (aobj.InheritanceParent is AvaloniaObject parent)
+ {
+ aobj = parent;
+ valuestore = aobj._values;
+ if (valuestore != null)
+ {
+ var result = valuestore.GetValue(property);
+ if (result != AvaloniaProperty.UnsetValue)
+ {
+ return result;
+ }
+ }
+ }
+ }
+ return ((IStyledPropertyAccessor)property).GetDefaultValue(GetType());
+ }
+
///
/// Sets the value of a direct property.
///
diff --git a/src/Avalonia.Base/AvaloniaProperty.cs b/src/Avalonia.Base/AvaloniaProperty.cs
index 1de5cb06c6..56ad241187 100644
--- a/src/Avalonia.Base/AvaloniaProperty.cs
+++ b/src/Avalonia.Base/AvaloniaProperty.cs
@@ -492,6 +492,11 @@ namespace Avalonia
return Name;
}
+ ///
+ /// True if has any observers.
+ ///
+ internal bool HasNotifyInitializedObservers => _initialized.HasObservers;
+
///
/// Notifies the observable.
///
diff --git a/src/Avalonia.Base/AvaloniaPropertyRegistry.cs b/src/Avalonia.Base/AvaloniaPropertyRegistry.cs
index 037e0dd72e..d718f5917c 100644
--- a/src/Avalonia.Base/AvaloniaPropertyRegistry.cs
+++ b/src/Avalonia.Base/AvaloniaPropertyRegistry.cs
@@ -24,8 +24,10 @@ namespace Avalonia
new Dictionary>();
private readonly Dictionary> _attachedCache =
new Dictionary>();
- private readonly Dictionary>> _initializedCache =
- new Dictionary>>();
+ private readonly Dictionary> _initializedCache =
+ new Dictionary>();
+ private readonly Dictionary> _inheritedCache =
+ new Dictionary>();
///
/// Gets the instance
@@ -103,6 +105,46 @@ namespace Avalonia
return result;
}
+ ///
+ /// Gets all inherited s registered on a type.
+ ///
+ /// The type.
+ /// A collection of definitions.
+ public IEnumerable GetRegisteredInherited(Type type)
+ {
+ Contract.Requires(type != null);
+
+ if (_inheritedCache.TryGetValue(type, out var result))
+ {
+ return result;
+ }
+
+ result = new List();
+ var visited = new HashSet();
+
+ foreach (var property in GetRegistered(type))
+ {
+ if (property.Inherits)
+ {
+ result.Add(property);
+ visited.Add(property);
+ }
+ }
+ foreach (var property in GetRegisteredAttached(type))
+ {
+ if (property.Inherits)
+ {
+ if (!visited.Contains(property))
+ {
+ result.Add(property);
+ }
+ }
+ }
+
+ _inheritedCache.Add(type, result);
+ return result;
+ }
+
///
/// Gets all s registered on a object.
///
@@ -230,6 +272,7 @@ namespace Avalonia
_registeredCache.Clear();
_initializedCache.Clear();
+ _inheritedCache.Clear();
}
///
@@ -266,6 +309,7 @@ namespace Avalonia
_attachedCache.Clear();
_initializedCache.Clear();
+ _inheritedCache.Clear();
}
internal void NotifyInitialized(AvaloniaObject o)
@@ -286,35 +330,73 @@ namespace Avalonia
property.NotifyInitialized(e);
}
- if (!_initializedCache.TryGetValue(type, out var items))
+ if (!_initializedCache.TryGetValue(type, out var initializationData))
{
- var build = new Dictionary();
+ var visited = new HashSet();
+
+ initializationData = new List();
- foreach (var property in GetRegistered(type))
+ foreach (AvaloniaProperty property in GetRegistered(type))
{
- var value = !property.IsDirect ?
- ((IStyledPropertyAccessor)property).GetDefaultValue(type) :
- null;
- build.Add(property, value);
+ if (property.IsDirect)
+ {
+ initializationData.Add(new PropertyInitializationData(property, (IDirectPropertyAccessor)property));
+ }
+ else
+ {
+ initializationData.Add(new PropertyInitializationData(property, (IStyledPropertyAccessor)property, type));
+ }
+
+ visited.Add(property);
}
- foreach (var property in GetRegisteredAttached(type))
+ foreach (AvaloniaProperty property in GetRegisteredAttached(type))
{
- if (!build.ContainsKey(property))
+ if (!visited.Contains(property))
{
- var value = ((IStyledPropertyAccessor)property).GetDefaultValue(type);
- build.Add(property, value);
+ initializationData.Add(new PropertyInitializationData(property, (IStyledPropertyAccessor)property, type));
+
+ visited.Add(property);
}
}
- items = build.ToList();
- _initializedCache.Add(type, items);
+ _initializedCache.Add(type, initializationData);
+ }
+
+ foreach (PropertyInitializationData data in initializationData)
+ {
+ if (!data.Property.HasNotifyInitializedObservers)
+ {
+ continue;
+ }
+
+ object value = data.IsDirect ? data.DirectAccessor.GetValue(o) : data.Value;
+
+ Notify(data.Property, value);
+ }
+ }
+
+ private readonly struct PropertyInitializationData
+ {
+ public AvaloniaProperty Property { get; }
+ public object Value { get; }
+ public bool IsDirect { get; }
+ public IDirectPropertyAccessor DirectAccessor { get; }
+
+ public PropertyInitializationData(AvaloniaProperty property, IDirectPropertyAccessor directAccessor)
+ {
+ Property = property;
+ Value = null;
+ IsDirect = true;
+ DirectAccessor = directAccessor;
}
- foreach (var i in items)
+ public PropertyInitializationData(AvaloniaProperty property, IStyledPropertyAccessor styledAccessor, Type type)
{
- var value = i.Key.IsDirect ? o.GetValue(i.Key) : i.Value;
- Notify(i.Key, value);
+ Property = property;
+ Value = styledAccessor.GetDefaultValue(type);
+ IsDirect = false;
+ DirectAccessor = null;
}
}
}
diff --git a/src/Avalonia.Base/BoxedValue.cs b/src/Avalonia.Base/BoxedValue.cs
new file mode 100644
index 0000000000..5fc515f299
--- /dev/null
+++ b/src/Avalonia.Base/BoxedValue.cs
@@ -0,0 +1,28 @@
+// 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
+{
+ ///
+ /// Represents boxed value of type .
+ ///
+ /// Type of stored value.
+ internal readonly struct BoxedValue
+ {
+ public BoxedValue(T value)
+ {
+ Boxed = value;
+ Typed = value;
+ }
+
+ ///
+ /// Boxed value.
+ ///
+ public object Boxed { get; }
+
+ ///
+ /// Typed value.
+ ///
+ public T Typed { get; }
+ }
+}
diff --git a/src/Avalonia.Base/Collections/AvaloniaList.cs b/src/Avalonia.Base/Collections/AvaloniaList.cs
index 4d4a561b08..3c8d4ca7e6 100644
--- a/src/Avalonia.Base/Collections/AvaloniaList.cs
+++ b/src/Avalonia.Base/Collections/AvaloniaList.cs
@@ -55,15 +55,15 @@ namespace Avalonia.Collections
///
public class AvaloniaList : IAvaloniaList, IList, INotifyCollectionChangedDebug
{
- private List _inner;
+ private readonly List _inner;
private NotifyCollectionChangedEventHandler _collectionChanged;
///
/// Initializes a new instance of the class.
///
public AvaloniaList()
- : this(Enumerable.Empty())
{
+ _inner = new List();
}
///
@@ -89,8 +89,8 @@ namespace Avalonia.Collections
///
public event NotifyCollectionChangedEventHandler CollectionChanged
{
- add { _collectionChanged += value; }
- remove { _collectionChanged -= value; }
+ add => _collectionChanged += value;
+ remove => _collectionChanged -= value;
}
///
@@ -150,7 +150,7 @@ namespace Avalonia.Collections
T old = _inner[index];
- if (!object.Equals(old, value))
+ if (!EqualityComparer.Default.Equals(old, value))
{
_inner[index] = value;
@@ -187,45 +187,38 @@ namespace Avalonia.Collections
Validate?.Invoke(item);
int index = _inner.Count;
_inner.Add(item);
- NotifyAdd(new[] { item }, index);
+ NotifyAdd(item, index);
}
///
/// Adds multiple items to the collection.
///
/// The items.
- public virtual void AddRange(IEnumerable items)
- {
- Contract.Requires(items != null);
-
- var list = (items as IList) ?? items.ToList();
-
- if (list.Count > 0)
- {
- if (Validate != null)
- {
- foreach (var item in list)
- {
- Validate((T)item);
- }
- }
-
- int index = _inner.Count;
- _inner.AddRange(items);
- NotifyAdd(list, index);
- }
- }
+ public virtual void AddRange(IEnumerable items) => InsertRange(_inner.Count, items);
///
/// Removes all items from the collection.
///
public virtual void Clear()
{
- if (this.Count > 0)
+ if (Count > 0)
{
- var old = _inner;
- _inner = new List();
- NotifyReset(old);
+ if (_collectionChanged != null)
+ {
+ var e = ResetBehavior == ResetBehavior.Reset ?
+ EventArgsCache.ResetCollectionChanged :
+ new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Remove, _inner.ToList(), 0);
+
+ _inner.Clear();
+
+ _collectionChanged(this, e);
+ }
+ else
+ {
+ _inner.Clear();
+ }
+
+ NotifyCountChanged();
}
}
@@ -253,9 +246,20 @@ namespace Avalonia.Collections
/// Returns an enumerator that enumerates the items in the collection.
///
/// An .
- public IEnumerator GetEnumerator()
+ IEnumerator IEnumerable.GetEnumerator()
{
- return _inner.GetEnumerator();
+ return new Enumerator(_inner);
+ }
+
+ ///
+ IEnumerator IEnumerable.GetEnumerator()
+ {
+ return new Enumerator(_inner);
+ }
+
+ public Enumerator GetEnumerator()
+ {
+ return new Enumerator(_inner);
}
///
@@ -289,7 +293,7 @@ namespace Avalonia.Collections
{
Validate?.Invoke(item);
_inner.Insert(index, item);
- NotifyAdd(new[] { item }, index);
+ NotifyAdd(item, index);
}
///
@@ -301,20 +305,83 @@ namespace Avalonia.Collections
{
Contract.Requires(items != null);
- var list = (items as IList) ?? items.ToList();
+ bool willRaiseCollectionChanged = _collectionChanged != null;
+ bool hasValidation = Validate != null;
- if (list.Count > 0)
+ if (items is IList list)
{
- if (Validate != null)
+ if (list.Count > 0)
{
- foreach (var item in list)
+ if (list is ICollection collection)
{
- Validate((T)item);
+ if (hasValidation)
+ {
+ foreach (T item in collection)
+ {
+ Validate(item);
+ }
+ }
+
+ _inner.InsertRange(index, collection);
+ NotifyAdd(list, index);
+ }
+ else
+ {
+ using (IEnumerator en = items.GetEnumerator())
+ {
+ int insertIndex = index;
+
+ while (en.MoveNext())
+ {
+ T item = en.Current;
+
+ if (hasValidation)
+ {
+ Validate(item);
+ }
+
+ _inner.Insert(insertIndex++, item);
+ }
+ }
+
+ NotifyAdd(list, index);
}
}
+ }
+ else
+ {
+ using (IEnumerator en = items.GetEnumerator())
+ {
+ if (en.MoveNext())
+ {
+ // Avoid allocating list for collection notification if there is no event subscriptions.
+ List notificationItems = willRaiseCollectionChanged ?
+ new List() :
+ null;
+
+ int insertIndex = index;
+
+ do
+ {
+ T item = en.Current;
+
+ if (hasValidation)
+ {
+ Validate(item);
+ }
- _inner.InsertRange(index, items);
- NotifyAdd((items as IList) ?? items.ToList(), index);
+ _inner.Insert(insertIndex++, item);
+
+ if (willRaiseCollectionChanged)
+ {
+ notificationItems.Add(item);
+ }
+
+ } while (en.MoveNext());
+
+ NotifyAdd(notificationItems, index);
+ }
+ }
}
}
@@ -382,7 +449,7 @@ namespace Avalonia.Collections
if (index != -1)
{
_inner.RemoveAt(index);
- NotifyRemove(new[] { item }, index);
+ NotifyRemove(item , index);
return true;
}
@@ -412,7 +479,7 @@ namespace Avalonia.Collections
{
T item = _inner[index];
_inner.RemoveAt(index);
- NotifyRemove(new[] { item }, index);
+ NotifyRemove(item , index);
}
///
@@ -480,12 +547,6 @@ namespace Avalonia.Collections
_inner.CopyTo((T[])array, index);
}
- ///
- IEnumerator IEnumerable.GetEnumerator()
- {
- return _inner.GetEnumerator();
- }
-
///
Delegate[] INotifyCollectionChangedDebug.GetCollectionChangedSubscribers() => _collectionChanged?.GetInvocationList();
@@ -505,13 +566,29 @@ namespace Avalonia.Collections
NotifyCountChanged();
}
+ ///
+ /// Raises the event with a add action.
+ ///
+ /// The item that was added.
+ /// The starting index.
+ private void NotifyAdd(T item, int index)
+ {
+ if (_collectionChanged != null)
+ {
+ var e = new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, new[] { item }, index);
+ _collectionChanged(this, e);
+ }
+
+ NotifyCountChanged();
+ }
+
///
/// Raises the event when the property
/// changes.
///
private void NotifyCountChanged()
{
- PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(Count)));
+ PropertyChanged?.Invoke(this, EventArgsCache.CountPropertyChanged);
}
///
@@ -531,23 +608,57 @@ namespace Avalonia.Collections
}
///
- /// Raises the event with a reset action.
+ /// Raises the event with a remove action.
///
- /// The items that were removed.
- private void NotifyReset(IList t)
+ /// The item that was removed.
+ /// The starting index.
+ private void NotifyRemove(T item, int index)
{
if (_collectionChanged != null)
{
- NotifyCollectionChangedEventArgs e;
-
- e = ResetBehavior == ResetBehavior.Reset ?
- new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset) :
- new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Remove, t, 0);
-
+ var e = new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Remove, new[] { item }, index);
_collectionChanged(this, e);
}
NotifyCountChanged();
}
+
+ ///
+ /// Enumerates the elements of a .
+ ///
+ public struct Enumerator : IEnumerator
+ {
+ private List.Enumerator _innerEnumerator;
+
+ public Enumerator(List inner)
+ {
+ _innerEnumerator = inner.GetEnumerator();
+ }
+
+ public bool MoveNext()
+ {
+ return _innerEnumerator.MoveNext();
+ }
+
+ void IEnumerator.Reset()
+ {
+ ((IEnumerator)_innerEnumerator).Reset();
+ }
+
+ public T Current => _innerEnumerator.Current;
+
+ object IEnumerator.Current => Current;
+
+ public void Dispose()
+ {
+ _innerEnumerator.Dispose();
+ }
+ }
+ }
+
+ internal static class EventArgsCache
+ {
+ internal static readonly PropertyChangedEventArgs CountPropertyChanged = new PropertyChangedEventArgs(nameof(AvaloniaList