Browse Source

Merge branch 'master' into refactor/remove-property-initialized

pull/3292/head
Steven Kirk 7 years ago
parent
commit
5d48222418
  1. 27
      Avalonia.sln
  2. 12
      azure-pipelines.yml
  3. 4
      build/HarfBuzzSharp.props
  4. 2
      build/SharedVersion.props
  5. 4
      build/SkiaSharp.props
  6. 18
      native/Avalonia.Native/inc/avalonia-native.h
  7. 14
      native/Avalonia.Native/src/OSX/app.mm
  8. 24
      native/Avalonia.Native/src/OSX/gl.mm
  9. 2
      native/Avalonia.Native/src/OSX/window.h
  10. 182
      native/Avalonia.Native/src/OSX/window.mm
  11. 1
      nukebuild/Build.cs
  12. 2
      readme.md
  13. 4
      samples/ControlCatalog/App.xaml.cs
  14. 6
      samples/ControlCatalog/MainView.xaml
  15. 4
      samples/ControlCatalog/Pages/ComboBoxPage.xaml.cs
  16. 85
      samples/ControlCatalog/Pages/ImagePage.xaml
  17. 39
      samples/ControlCatalog/Pages/ImagePage.xaml.cs
  18. 3
      samples/RenderDemo/MainWindow.xaml
  19. 14
      samples/RenderDemo/Pages/GlyphRunPage.xaml
  20. 80
      samples/RenderDemo/Pages/GlyphRunPage.xaml.cs
  21. 2
      samples/RenderDemo/Pages/RenderTargetBitmapPage.cs
  22. 64
      scripts/avalonia-rename.ps1
  23. 4
      src/Avalonia.Animation/Animatable.cs
  24. 2
      src/Avalonia.Animation/IterationCount.cs
  25. 8
      src/Avalonia.Base/AttachedProperty.cs
  26. 95
      src/Avalonia.Base/AvaloniaObject.cs
  27. 24
      src/Avalonia.Base/AvaloniaProperty.cs
  28. 4
      src/Avalonia.Base/AvaloniaPropertyChangedEventArgs`1.cs
  29. 12
      src/Avalonia.Base/AvaloniaPropertyRegistry.cs
  30. 20
      src/Avalonia.Base/Collections/AvaloniaList.cs
  31. 40
      src/Avalonia.Base/Collections/Pooled/ClearMode.cs
  32. 31
      src/Avalonia.Base/Collections/Pooled/ICollectionDebugView.cs
  33. 21
      src/Avalonia.Base/Collections/Pooled/IReadOnlyPooledList.cs
  34. 1531
      src/Avalonia.Base/Collections/Pooled/PooledList.cs
  35. 699
      src/Avalonia.Base/Collections/Pooled/PooledStack.cs
  36. 28
      src/Avalonia.Base/Collections/Pooled/StackDebugView.cs
  37. 691
      src/Avalonia.Base/Collections/Pooled/ThrowHelper.cs
  38. 6
      src/Avalonia.Base/Data/BindingNotification.cs
  39. 20
      src/Avalonia.Base/Data/BindingOperations.cs
  40. 35
      src/Avalonia.Base/Data/BindingValue.cs
  41. 13
      src/Avalonia.Base/Data/Core/BindingExpression.cs
  42. 6
      src/Avalonia.Base/Data/Core/ExpressionObserver.cs
  43. 20
      src/Avalonia.Base/Data/Core/Plugins/AvaloniaPropertyAccessorPlugin.cs
  44. 14
      src/Avalonia.Base/Data/Core/PropertyAccessorNode.cs
  45. 32
      src/Avalonia.Base/Data/Optional.cs
  46. 2
      src/Avalonia.Base/IAvaloniaObject.cs
  47. 15
      src/Avalonia.Base/Platform/IMacOSTopLevelPlatformHandle.cs
  48. 7
      src/Avalonia.Base/PropertyStore/BindingEntry.cs
  49. 7
      src/Avalonia.Base/PropertyStore/ConstantValueEntry.cs
  50. 7
      src/Avalonia.Base/PropertyStore/IPriorityValueEntry.cs
  51. 7
      src/Avalonia.Base/PropertyStore/IValue.cs
  52. 6
      src/Avalonia.Base/PropertyStore/IValueSink.cs
  53. 5
      src/Avalonia.Base/PropertyStore/LocalValueEntry.cs
  54. 11
      src/Avalonia.Base/PropertyStore/PriorityValue.cs
  55. 2
      src/Avalonia.Base/Reactive/AvaloniaPropertyBindingObservable.cs
  56. 11
      src/Avalonia.Base/Reactive/AvaloniaPropertyObservable.cs
  57. 4
      src/Avalonia.Base/Reactive/BindingValueExtensions.cs
  58. 25
      src/Avalonia.Base/Reactive/LightweightObservableBase.cs
  59. 15
      src/Avalonia.Base/StyledPropertyMetadata`1.cs
  60. 2
      src/Avalonia.Base/Utilities/IdentifierParser.cs
  61. 48
      src/Avalonia.Base/Utilities/ImmutableReadOnlyListStructEnumerator.cs
  62. 32
      src/Avalonia.Base/Utilities/TypeUtilities.cs
  63. 94
      src/Avalonia.Base/Utilities/ValueSingleOrList.cs
  64. 53
      src/Avalonia.Base/ValueStore.cs
  65. 2
      src/Avalonia.Controls.DataGrid/Collections/DataGridSortDescription.cs
  66. 2
      src/Avalonia.Controls.DataGrid/Primitives/DataGridFrozenGrid.cs
  67. 2
      src/Avalonia.Controls.DataGrid/Properties/AssemblyInfo.cs
  68. 21
      src/Avalonia.Controls/Application.cs
  69. 2
      src/Avalonia.Controls/Button.cs
  70. 2
      src/Avalonia.Controls/ButtonSpinner.cs
  71. 6
      src/Avalonia.Controls/DefinitionBase.cs
  72. 4
      src/Avalonia.Controls/DrawingPresenter.cs
  73. 2
      src/Avalonia.Controls/Expander.cs
  74. 5
      src/Avalonia.Controls/Generators/ITreeItemContainerGenerator.cs
  75. 43
      src/Avalonia.Controls/Generators/TreeItemContainerGenerator.cs
  76. 12
      src/Avalonia.Controls/GridSplitter.cs
  77. 45
      src/Avalonia.Controls/Image.cs
  78. 25
      src/Avalonia.Controls/ItemsControl.cs
  79. 4
      src/Avalonia.Controls/LayoutTransformControl.cs
  80. 2
      src/Avalonia.Controls/ListBox.cs
  81. 17
      src/Avalonia.Controls/Notifications/WindowNotificationManager.cs
  82. 30
      src/Avalonia.Controls/Platform/DefaultMenuInteractionHandler.cs
  83. 2
      src/Avalonia.Controls/Presenters/ContentPresenter.cs
  84. 4
      src/Avalonia.Controls/Presenters/ItemVirtualizerSimple.cs
  85. 2
      src/Avalonia.Controls/Presenters/TextPresenter.cs
  86. 5
      src/Avalonia.Controls/Primitives/AdornerLayer.cs
  87. 7
      src/Avalonia.Controls/Primitives/OverlayLayer.cs
  88. 2
      src/Avalonia.Controls/Primitives/PopupPositioning/IPopupPositioner.cs
  89. 25
      src/Avalonia.Controls/Primitives/RangeBase.cs
  90. 2
      src/Avalonia.Controls/Primitives/ScrollBar.cs
  91. 56
      src/Avalonia.Controls/Primitives/SelectingItemsControl.cs
  92. 125
      src/Avalonia.Controls/Primitives/ToggleButton.cs
  93. 2
      src/Avalonia.Controls/Primitives/Track.cs
  94. 9
      src/Avalonia.Controls/Primitives/VisualLayerManager.cs
  95. 4
      src/Avalonia.Controls/ProgressBar.cs
  96. 2
      src/Avalonia.Controls/Remote/RemoteWidget.cs
  97. 52
      src/Avalonia.Controls/Repeater/ItemsRepeater.cs
  98. 77
      src/Avalonia.Controls/Repeater/ViewManager.cs
  99. 2
      src/Avalonia.Controls/Slider.cs
  100. 3
      src/Avalonia.Controls/Templates/FuncDataTemplate.cs

27
Avalonia.sln

@ -204,6 +204,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Avalonia.Dialogs", "src\Ava
EndProject EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Avalonia.FreeDesktop", "src\Avalonia.FreeDesktop\Avalonia.FreeDesktop.csproj", "{4D36CEC8-53F2-40A5-9A37-79AAE356E2DA}" Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Avalonia.FreeDesktop", "src\Avalonia.FreeDesktop\Avalonia.FreeDesktop.csproj", "{4D36CEC8-53F2-40A5-9A37-79AAE356E2DA}"
EndProject EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Avalonia.Controls.DataGrid.UnitTests", "tests\Avalonia.Controls.DataGrid.UnitTests\Avalonia.Controls.DataGrid.UnitTests.csproj", "{351337F5-D66F-461B-A957-4EF60BDB4BA6}"
EndProject
Global Global
GlobalSection(SharedMSBuildProjectFiles) = preSolution GlobalSection(SharedMSBuildProjectFiles) = preSolution
src\Shared\RenderHelpers\RenderHelpers.projitems*{3c4c0cb4-0c0f-4450-a37b-148c84ff905f}*SharedItemsImports = 13 src\Shared\RenderHelpers\RenderHelpers.projitems*{3c4c0cb4-0c0f-4450-a37b-148c84ff905f}*SharedItemsImports = 13
@ -1895,6 +1897,30 @@ Global
{4D36CEC8-53F2-40A5-9A37-79AAE356E2DA}.Release|iPhone.Build.0 = Release|Any CPU {4D36CEC8-53F2-40A5-9A37-79AAE356E2DA}.Release|iPhone.Build.0 = Release|Any CPU
{4D36CEC8-53F2-40A5-9A37-79AAE356E2DA}.Release|iPhoneSimulator.ActiveCfg = Release|Any CPU {4D36CEC8-53F2-40A5-9A37-79AAE356E2DA}.Release|iPhoneSimulator.ActiveCfg = Release|Any CPU
{4D36CEC8-53F2-40A5-9A37-79AAE356E2DA}.Release|iPhoneSimulator.Build.0 = Release|Any CPU {4D36CEC8-53F2-40A5-9A37-79AAE356E2DA}.Release|iPhoneSimulator.Build.0 = Release|Any CPU
{351337F5-D66F-461B-A957-4EF60BDB4BA6}.Ad-Hoc|Any CPU.ActiveCfg = Debug|Any CPU
{351337F5-D66F-461B-A957-4EF60BDB4BA6}.Ad-Hoc|Any CPU.Build.0 = Debug|Any CPU
{351337F5-D66F-461B-A957-4EF60BDB4BA6}.Ad-Hoc|iPhone.ActiveCfg = Debug|Any CPU
{351337F5-D66F-461B-A957-4EF60BDB4BA6}.Ad-Hoc|iPhone.Build.0 = Debug|Any CPU
{351337F5-D66F-461B-A957-4EF60BDB4BA6}.Ad-Hoc|iPhoneSimulator.ActiveCfg = Debug|Any CPU
{351337F5-D66F-461B-A957-4EF60BDB4BA6}.Ad-Hoc|iPhoneSimulator.Build.0 = Debug|Any CPU
{351337F5-D66F-461B-A957-4EF60BDB4BA6}.AppStore|Any CPU.ActiveCfg = Debug|Any CPU
{351337F5-D66F-461B-A957-4EF60BDB4BA6}.AppStore|Any CPU.Build.0 = Debug|Any CPU
{351337F5-D66F-461B-A957-4EF60BDB4BA6}.AppStore|iPhone.ActiveCfg = Debug|Any CPU
{351337F5-D66F-461B-A957-4EF60BDB4BA6}.AppStore|iPhone.Build.0 = Debug|Any CPU
{351337F5-D66F-461B-A957-4EF60BDB4BA6}.AppStore|iPhoneSimulator.ActiveCfg = Debug|Any CPU
{351337F5-D66F-461B-A957-4EF60BDB4BA6}.AppStore|iPhoneSimulator.Build.0 = Debug|Any CPU
{351337F5-D66F-461B-A957-4EF60BDB4BA6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{351337F5-D66F-461B-A957-4EF60BDB4BA6}.Debug|Any CPU.Build.0 = Debug|Any CPU
{351337F5-D66F-461B-A957-4EF60BDB4BA6}.Debug|iPhone.ActiveCfg = Debug|Any CPU
{351337F5-D66F-461B-A957-4EF60BDB4BA6}.Debug|iPhone.Build.0 = Debug|Any CPU
{351337F5-D66F-461B-A957-4EF60BDB4BA6}.Debug|iPhoneSimulator.ActiveCfg = Debug|Any CPU
{351337F5-D66F-461B-A957-4EF60BDB4BA6}.Debug|iPhoneSimulator.Build.0 = Debug|Any CPU
{351337F5-D66F-461B-A957-4EF60BDB4BA6}.Release|Any CPU.ActiveCfg = Release|Any CPU
{351337F5-D66F-461B-A957-4EF60BDB4BA6}.Release|Any CPU.Build.0 = Release|Any CPU
{351337F5-D66F-461B-A957-4EF60BDB4BA6}.Release|iPhone.ActiveCfg = Release|Any CPU
{351337F5-D66F-461B-A957-4EF60BDB4BA6}.Release|iPhone.Build.0 = Release|Any CPU
{351337F5-D66F-461B-A957-4EF60BDB4BA6}.Release|iPhoneSimulator.ActiveCfg = Release|Any CPU
{351337F5-D66F-461B-A957-4EF60BDB4BA6}.Release|iPhoneSimulator.Build.0 = Release|Any CPU
EndGlobalSection EndGlobalSection
GlobalSection(SolutionProperties) = preSolution GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE HideSolutionNode = FALSE
@ -1951,6 +1977,7 @@ Global
{41B02319-965D-4945-8005-C1A3D1224165} = {86C53C40-57AA-45B8-AD42-FAE0EFDF0F2B} {41B02319-965D-4945-8005-C1A3D1224165} = {86C53C40-57AA-45B8-AD42-FAE0EFDF0F2B}
{D775DECB-4E00-4ED5-A75A-5FCE58ADFF0B} = {9B9E3891-2366-4253-A952-D08BCEB71098} {D775DECB-4E00-4ED5-A75A-5FCE58ADFF0B} = {9B9E3891-2366-4253-A952-D08BCEB71098}
{AF915D5C-AB00-4EA0-B5E6-001F4AE84E68} = {C5A00AC3-B34C-4564-9BDD-2DA473EF4D8B} {AF915D5C-AB00-4EA0-B5E6-001F4AE84E68} = {C5A00AC3-B34C-4564-9BDD-2DA473EF4D8B}
{351337F5-D66F-461B-A957-4EF60BDB4BA6} = {C5A00AC3-B34C-4564-9BDD-2DA473EF4D8B}
EndGlobalSection EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {87366D66-1391-4D90-8999-95A620AD786A} SolutionGuid = {87366D66-1391-4D90-8999-95A620AD786A}

12
azure-pipelines.yml

@ -34,9 +34,17 @@ jobs:
pool: pool:
vmImage: 'macOS-10.14' vmImage: 'macOS-10.14'
steps: steps:
- task: DotNetCoreInstaller@0 - task: UseDotNet@2
displayName: 'Use .NET Core SDK 3.0.x'
inputs: inputs:
version: '2.1.403' packageType: sdk
version: 3.0.x
- task: UseDotNet@2
displayName: 'Use .NET Core Runtime 2.1.x'
inputs:
packageType: runtime
version: 2.1.x
- task: CmdLine@2 - task: CmdLine@2
displayName: 'Install Mono 5.18' displayName: 'Install Mono 5.18'

4
build/HarfBuzzSharp.props

@ -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="HarfBuzzSharp" Version="2.6.1-rc.153" /> <PackageReference Include="HarfBuzzSharp" Version="2.6.1" />
<PackageReference Condition="'$(IncludeLinuxSkia)' == 'true'" Include="HarfBuzzSharp.NativeAssets.Linux" Version="2.6.1-rc.153" /> <PackageReference Condition="'$(IncludeLinuxSkia)' == 'true'" Include="HarfBuzzSharp.NativeAssets.Linux" Version="2.6.1" />
</ItemGroup> </ItemGroup>
</Project> </Project>

2
build/SharedVersion.props

@ -2,7 +2,7 @@
xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup> <PropertyGroup>
<Product>Avalonia</Product> <Product>Avalonia</Product>
<Version>0.8.999</Version> <Version>0.9.999</Version>
<Copyright>Copyright 2019 &#169; The AvaloniaUI Project</Copyright> <Copyright>Copyright 2019 &#169; The AvaloniaUI Project</Copyright>
<PackageProjectUrl>https://avaloniaui.net</PackageProjectUrl> <PackageProjectUrl>https://avaloniaui.net</PackageProjectUrl>
<RepositoryUrl>https://github.com/AvaloniaUI/Avalonia/</RepositoryUrl> <RepositoryUrl>https://github.com/AvaloniaUI/Avalonia/</RepositoryUrl>

4
build/SkiaSharp.props

@ -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.68.1-rc.153" /> <PackageReference Include="SkiaSharp" Version="1.68.1" />
<PackageReference Condition="'$(IncludeLinuxSkia)' == 'true'" Include="SkiaSharp.NativeAssets.Linux" Version="1.68.1-rc.153" /> <PackageReference Condition="'$(IncludeLinuxSkia)' == 'true'" Include="SkiaSharp.NativeAssets.Linux" Version="1.68.1" />
</ItemGroup> </ItemGroup>
</Project> </Project>

18
native/Avalonia.Native/inc/avalonia-native.h

@ -92,9 +92,17 @@ enum AvnRawMouseEventType
RightButtonUp, RightButtonUp,
MiddleButtonDown, MiddleButtonDown,
MiddleButtonUp, MiddleButtonUp,
XButton1Down,
XButton1Up,
XButton2Down,
XButton2Up,
Move, Move,
Wheel, Wheel,
NonClientLeftButtonDown NonClientLeftButtonDown,
TouchBegin,
TouchUpdate,
TouchEnd,
TouchCancel
}; };
enum AvnRawKeyEventType enum AvnRawKeyEventType
@ -112,7 +120,9 @@ enum AvnInputModifiers
Windows = 8, Windows = 8,
LeftMouseButton = 16, LeftMouseButton = 16,
RightMouseButton = 32, RightMouseButton = 32,
MiddleMouseButton = 64 MiddleMouseButton = 64,
XButton1MouseButton = 128,
XButton2MouseButton = 256
}; };
enum AvnWindowState enum AvnWindowState
@ -212,6 +222,10 @@ AVNCOM(IAvnWindowBase, 02) : IUnknown
virtual HRESULT GetSoftwareFramebuffer(AvnFramebuffer*ret) = 0; virtual HRESULT GetSoftwareFramebuffer(AvnFramebuffer*ret) = 0;
virtual HRESULT SetMainMenu(IAvnAppMenu* menu) = 0; virtual HRESULT SetMainMenu(IAvnAppMenu* menu) = 0;
virtual HRESULT ObtainMainMenu(IAvnAppMenu** retOut) = 0; virtual HRESULT ObtainMainMenu(IAvnAppMenu** retOut) = 0;
virtual HRESULT ObtainNSWindowHandle(void** retOut) = 0;
virtual HRESULT ObtainNSWindowHandleRetained(void** retOut) = 0;
virtual HRESULT ObtainNSViewHandle(void** retOut) = 0;
virtual HRESULT ObtainNSViewHandleRetained(void** retOut) = 0;
virtual bool TryLock() = 0; virtual bool TryLock() = 0;
virtual void Unlock() = 0; virtual void Unlock() = 0;
}; };

14
native/Avalonia.Native/src/OSX/app.mm

@ -1,16 +1,25 @@
#include "common.h" #include "common.h"
@interface AvnAppDelegate : NSObject<NSApplicationDelegate> @interface AvnAppDelegate : NSObject<NSApplicationDelegate>
@end @end
extern NSApplicationActivationPolicy AvnDesiredActivationPolicy = NSApplicationActivationPolicyRegular; extern NSApplicationActivationPolicy AvnDesiredActivationPolicy = NSApplicationActivationPolicyRegular;
@implementation AvnAppDelegate @implementation AvnAppDelegate
- (void)applicationWillFinishLaunching:(NSNotification *)notification - (void)applicationWillFinishLaunching:(NSNotification *)notification
{ {
[[NSApplication sharedApplication] setActivationPolicy: AvnDesiredActivationPolicy]; if([[NSApplication sharedApplication] activationPolicy] != AvnDesiredActivationPolicy)
{
for (NSRunningApplication * app in [NSRunningApplication runningApplicationsWithBundleIdentifier:@"com.apple.dock"]) {
[app activateWithOptions:NSApplicationActivateIgnoringOtherApps];
break;
}
[[NSApplication sharedApplication] setActivationPolicy: AvnDesiredActivationPolicy];
}
} }
- (void)applicationDidFinishLaunching:(NSNotification *)notification - (void)applicationDidFinishLaunching:(NSNotification *)notification
{ {
[NSApp activateIgnoringOtherApps:true]; [[NSRunningApplication currentApplication] activateWithOptions:NSApplicationActivateIgnoringOtherApps];
} }
@end @end
@ -20,5 +29,4 @@ extern void InitializeAvnApp()
NSApplication* app = [NSApplication sharedApplication]; NSApplication* app = [NSApplication sharedApplication];
id delegate = [AvnAppDelegate new]; id delegate = [AvnAppDelegate new];
[app setDelegate:delegate]; [app setDelegate:delegate];
} }

24
native/Avalonia.Native/src/OSX/gl.mm

@ -1,6 +1,7 @@
#include "common.h" #include "common.h"
#include <OpenGL/gl.h> #include <OpenGL/gl.h>
#include <dlfcn.h> #include <dlfcn.h>
#include "window.h"
template <typename T, size_t N> char (&ArrayCounter(T (&a)[N]))[N]; template <typename T, size_t N> char (&ArrayCounter(T (&a)[N]))[N];
#define ARRAY_COUNT(a) (sizeof(ArrayCounter(a))) #define ARRAY_COUNT(a) (sizeof(ArrayCounter(a)))
@ -181,12 +182,12 @@ extern IAvnGlFeature* GetGlFeature()
class AvnGlRenderingSession : public ComSingleObject<IAvnGlSurfaceRenderingSession, &IID_IAvnGlSurfaceRenderingSession> class AvnGlRenderingSession : public ComSingleObject<IAvnGlSurfaceRenderingSession, &IID_IAvnGlSurfaceRenderingSession>
{ {
NSView* _view; AvnView* _view;
NSWindow* _window; AvnWindow* _window;
NSOpenGLContext* _context; NSOpenGLContext* _context;
public: public:
FORWARD_IUNKNOWN() FORWARD_IUNKNOWN()
AvnGlRenderingSession(NSWindow*window, NSView* view, NSOpenGLContext* context) AvnGlRenderingSession(AvnWindow*window, AvnView* view, NSOpenGLContext* context)
{ {
_context = context; _context = context;
_window = window; _window = window;
@ -195,14 +196,12 @@ public:
virtual HRESULT GetPixelSize(AvnPixelSize* ret) override virtual HRESULT GetPixelSize(AvnPixelSize* ret) override
{ {
auto fsize = [_view convertSizeToBacking: [_view frame].size]; *ret = [_view getPixelSize];
ret->Width = (int)fsize.width;
ret->Height = (int)fsize.height;
return S_OK; return S_OK;
} }
virtual HRESULT GetScaling(double* ret) override virtual HRESULT GetScaling(double* ret) override
{ {
*ret = [_window backingScaleFactor]; *ret = [_window getScaling];
return S_OK; return S_OK;
} }
@ -234,8 +233,17 @@ public:
auto f = GetFeature(); auto f = GetFeature();
if(f == NULL) if(f == NULL)
return E_FAIL; return E_FAIL;
if(![_view lockFocusIfCanDraw])
@try
{
if(![_view lockFocusIfCanDraw])
return E_ABORT;
}
@catch(NSException* exception)
{
return E_ABORT; return E_ABORT;
}
auto gl = _context; auto gl = _context;
CGLLockContext([_context CGLContextObj]); CGLLockContext([_context CGLContextObj]);

2
native/Avalonia.Native/src/OSX/window.h

@ -12,6 +12,7 @@ class WindowBaseImpl;
-(AvnPoint) translateLocalPoint:(AvnPoint)pt; -(AvnPoint) translateLocalPoint:(AvnPoint)pt;
-(void) setSwRenderedFrame: (AvnFramebuffer* _Nonnull) fb dispose: (IUnknown* _Nonnull) dispose; -(void) setSwRenderedFrame: (AvnFramebuffer* _Nonnull) fb dispose: (IUnknown* _Nonnull) dispose;
-(void) onClosed; -(void) onClosed;
-(AvnPixelSize) getPixelSize;
@end @end
@interface AvnWindow : NSWindow <NSWindowDelegate> @interface AvnWindow : NSWindow <NSWindowDelegate>
@ -22,6 +23,7 @@ class WindowBaseImpl;
-(void) restoreParentWindow; -(void) restoreParentWindow;
-(bool) shouldTryToHandleEvents; -(bool) shouldTryToHandleEvents;
-(void) applyMenu:(NSMenu *)menu; -(void) applyMenu:(NSMenu *)menu;
-(double) getScaling;
@end @end
struct INSWindowHolder struct INSWindowHolder

182
native/Avalonia.Native/src/OSX/window.mm

@ -83,6 +83,54 @@ public:
[Window setContentView: View]; [Window setContentView: View];
} }
virtual HRESULT ObtainNSWindowHandle(void** ret) override
{
if (ret == nullptr)
{
return E_POINTER;
}
*ret = (__bridge void*)Window;
return S_OK;
}
virtual HRESULT ObtainNSWindowHandleRetained(void** ret) override
{
if (ret == nullptr)
{
return E_POINTER;
}
*ret = (__bridge_retained void*)Window;
return S_OK;
}
virtual HRESULT ObtainNSViewHandle(void** ret) override
{
if (ret == nullptr)
{
return E_POINTER;
}
*ret = (__bridge void*)View;
return S_OK;
}
virtual HRESULT ObtainNSViewHandleRetained(void** ret) override
{
if (ret == nullptr)
{
return E_POINTER;
}
*ret = (__bridge_retained void*)View;
return S_OK;
}
virtual AvnWindow* GetNSWindow() override virtual AvnWindow* GetNSWindow() override
{ {
return Window; return Window;
@ -147,7 +195,11 @@ public:
{ {
@autoreleasepool @autoreleasepool
{ {
[Window close]; if (Window != nullptr)
{
[Window close];
}
return S_OK; return S_OK;
} }
} }
@ -243,7 +295,14 @@ public:
{ {
@autoreleasepool @autoreleasepool
{ {
return [View lockFocusIfCanDraw] == YES; @try
{
return [View lockFocusIfCanDraw] == YES;
}
@catch (NSException*)
{
return NO;
}
} }
} }
@ -668,18 +727,36 @@ NSArray* AllLoopModes = [NSArray arrayWithObjects: NSDefaultRunLoopMode, NSEvent
AvnFramebuffer _swRenderedFrameBuffer; AvnFramebuffer _swRenderedFrameBuffer;
bool _queuedDisplayFromThread; bool _queuedDisplayFromThread;
NSTrackingArea* _area; NSTrackingArea* _area;
bool _isLeftPressed, _isMiddlePressed, _isRightPressed, _isMouseOver; bool _isLeftPressed, _isMiddlePressed, _isRightPressed, _isXButton1Pressed, _isXButton2Pressed, _isMouseOver;
NSEvent* _lastMouseDownEvent; NSEvent* _lastMouseDownEvent;
bool _lastKeyHandled; bool _lastKeyHandled;
AvnPixelSize _lastPixelSize;
} }
- (void)dealloc - (void)onClosed
{ {
@synchronized (self)
{
_parent = nullptr;
}
} }
- (void)onClosed - (BOOL)lockFocusIfCanDraw
{ {
_parent = NULL; @synchronized (self)
{
if(_parent == nullptr)
{
return NO;
}
}
return [super lockFocusIfCanDraw];
}
-(AvnPixelSize) getPixelSize
{
return _lastPixelSize;
} }
- (NSEvent*) lastMouseDownEvent - (NSEvent*) lastMouseDownEvent
@ -691,8 +768,11 @@ NSArray* AllLoopModes = [NSArray arrayWithObjects: NSDefaultRunLoopMode, NSEvent
{ {
self = [super init]; self = [super init];
[self setWantsBestResolutionOpenGLSurface:true]; [self setWantsBestResolutionOpenGLSurface:true];
[self setWantsLayer:YES];
_parent = parent; _parent = parent;
_area = nullptr; _area = nullptr;
_lastPixelSize.Height = 100;
_lastPixelSize.Width = 100;
return self; return self;
} }
@ -734,6 +814,10 @@ NSArray* AllLoopModes = [NSArray arrayWithObjects: NSDefaultRunLoopMode, NSEvent
[self addTrackingArea:_area]; [self addTrackingArea:_area];
_parent->UpdateCursor(); _parent->UpdateCursor();
auto fsize = [self convertSizeToBacking: [self frame].size];
_lastPixelSize.Width = (int)fsize.width;
_lastPixelSize.Height = (int)fsize.height;
_parent->BaseEvents->Resized(AvnSize{newSize.width, newSize.height}); _parent->BaseEvents->Resized(AvnSize{newSize.width, newSize.height});
} }
@ -763,7 +847,13 @@ NSArray* AllLoopModes = [NSArray arrayWithObjects: NSDefaultRunLoopMode, NSEvent
- (void)drawRect:(NSRect)dirtyRect - (void)drawRect:(NSRect)dirtyRect
{ {
if (_parent == nullptr)
{
return;
}
_parent->BaseEvents->RunRenderPriorityJobs(); _parent->BaseEvents->RunRenderPriorityJobs();
@synchronized (self) { @synchronized (self) {
if(_swRenderedFrame != NULL) if(_swRenderedFrame != NULL)
{ {
@ -830,7 +920,12 @@ NSArray* AllLoopModes = [NSArray arrayWithObjects: NSDefaultRunLoopMode, NSEvent
- (void) viewDidChangeBackingProperties - (void) viewDidChangeBackingProperties
{ {
auto fsize = [self convertSizeToBacking: [self frame].size];
_lastPixelSize.Width = (int)fsize.width;
_lastPixelSize.Height = (int)fsize.height;
_parent->BaseEvents->ScalingChanged([_parent->Window backingScaleFactor]); _parent->BaseEvents->ScalingChanged([_parent->Window backingScaleFactor]);
[super viewDidChangeBackingProperties]; [super viewDidChangeBackingProperties];
} }
@ -893,9 +988,23 @@ NSArray* AllLoopModes = [NSArray arrayWithObjects: NSDefaultRunLoopMode, NSEvent
- (void)otherMouseDown:(NSEvent *)event - (void)otherMouseDown:(NSEvent *)event
{ {
_isMiddlePressed = true;
_lastMouseDownEvent = event; _lastMouseDownEvent = event;
[self mouseEvent:event withType:MiddleButtonDown];
switch(event.buttonNumber)
{
case 3:
_isMiddlePressed = true;
[self mouseEvent:event withType:MiddleButtonDown];
break;
case 4:
_isXButton1Pressed = true;
[self mouseEvent:event withType:XButton1Down];
break;
case 5:
_isXButton2Pressed = true;
[self mouseEvent:event withType:XButton2Down];
break;
}
} }
- (void)rightMouseDown:(NSEvent *)event - (void)rightMouseDown:(NSEvent *)event
@ -913,8 +1022,21 @@ NSArray* AllLoopModes = [NSArray arrayWithObjects: NSDefaultRunLoopMode, NSEvent
- (void)otherMouseUp:(NSEvent *)event - (void)otherMouseUp:(NSEvent *)event
{ {
_isMiddlePressed = false; switch(event.buttonNumber)
[self mouseEvent:event withType:MiddleButtonUp]; {
case 3:
_isMiddlePressed = false;
[self mouseEvent:event withType:MiddleButtonUp];
break;
case 4:
_isXButton1Pressed = false;
[self mouseEvent:event withType:XButton1Up];
break;
case 5:
_isXButton2Pressed = false;
[self mouseEvent:event withType:XButton2Up];
break;
}
} }
- (void)rightMouseUp:(NSEvent *)event - (void)rightMouseUp:(NSEvent *)event
@ -1013,6 +1135,10 @@ NSArray* AllLoopModes = [NSArray arrayWithObjects: NSDefaultRunLoopMode, NSEvent
rv |= MiddleMouseButton; rv |= MiddleMouseButton;
if (_isRightPressed) if (_isRightPressed)
rv |= RightMouseButton; rv |= RightMouseButton;
if (_isXButton1Pressed)
rv |= XButton1MouseButton;
if (_isXButton2Pressed)
rv |= XButton2MouseButton;
return (AvnInputModifiers)rv; return (AvnInputModifiers)rv;
} }
@ -1081,6 +1207,12 @@ NSArray* AllLoopModes = [NSArray arrayWithObjects: NSDefaultRunLoopMode, NSEvent
bool _closed; bool _closed;
NSMenu* _menu; NSMenu* _menu;
bool _isAppMenuApplied; bool _isAppMenuApplied;
double _lastScaling;
}
-(double) getScaling
{
return _lastScaling;
} }
+(void)closeAll +(void)closeAll
@ -1094,10 +1226,6 @@ NSArray* AllLoopModes = [NSArray arrayWithObjects: NSDefaultRunLoopMode, NSEvent
} }
} }
- (void)dealloc
{
}
- (void)pollModalSession:(nonnull NSModalSession)session - (void)pollModalSession:(nonnull NSModalSession)session
{ {
auto response = [NSApp runModalSession:session]; auto response = [NSApp runModalSession:session];
@ -1152,6 +1280,12 @@ NSArray* AllLoopModes = [NSArray arrayWithObjects: NSDefaultRunLoopMode, NSEvent
[self setReleasedWhenClosed:false]; [self setReleasedWhenClosed:false];
_parent = parent; _parent = parent;
[self setDelegate:self]; [self setDelegate:self];
_closed = false;
_lastScaling = [self backingScaleFactor];
[self setOpaque:NO];
[self setBackgroundColor: [NSColor clearColor]];
[self invalidateShadow];
return self; return self;
} }
@ -1167,6 +1301,11 @@ NSArray* AllLoopModes = [NSArray arrayWithObjects: NSDefaultRunLoopMode, NSEvent
return true; return true;
} }
- (void)windowDidChangeBackingProperties:(NSNotification *)notification
{
_lastScaling = [self backingScaleFactor];
}
- (void)windowWillClose:(NSNotification *)notification - (void)windowWillClose:(NSNotification *)notification
{ {
_closed = true; _closed = true;
@ -1177,9 +1316,6 @@ NSArray* AllLoopModes = [NSArray arrayWithObjects: NSDefaultRunLoopMode, NSEvent
[self restoreParentWindow]; [self restoreParentWindow];
parent->BaseEvents->Closed(); parent->BaseEvents->Closed();
[parent->View onClosed]; [parent->View onClosed];
dispatch_async(dispatch_get_main_queue(), ^{
[self setContentView: nil];
});
} }
} }
@ -1326,18 +1462,6 @@ NSArray* AllLoopModes = [NSArray arrayWithObjects: NSDefaultRunLoopMode, NSEvent
_parent->GetPosition(&position); _parent->GetPosition(&position);
_parent->BaseEvents->PositionChanged(position); _parent->BaseEvents->PositionChanged(position);
} }
// TODO this breaks resizing.
/*- (void)windowDidResize:(NSNotification *)notification
{
auto parent = dynamic_cast<IWindowStateChanged*>(_parent.operator->());
if(parent != nullptr)
{
parent->WindowStateChanged();
}
}*/
@end @end
class PopupImpl : public virtual WindowBaseImpl, public IAvnPopup class PopupImpl : public virtual WindowBaseImpl, public IAvnPopup

1
nukebuild/Build.cs

@ -191,6 +191,7 @@ partial class Build : NukeBuild
RunCoreTest("./tests/Avalonia.Animation.UnitTests"); RunCoreTest("./tests/Avalonia.Animation.UnitTests");
RunCoreTest("./tests/Avalonia.Base.UnitTests"); RunCoreTest("./tests/Avalonia.Base.UnitTests");
RunCoreTest("./tests/Avalonia.Controls.UnitTests"); RunCoreTest("./tests/Avalonia.Controls.UnitTests");
RunCoreTest("./tests/Avalonia.Controls.DataGrid.UnitTests");
RunCoreTest("./tests/Avalonia.Input.UnitTests"); RunCoreTest("./tests/Avalonia.Input.UnitTests");
RunCoreTest("./tests/Avalonia.Interactivity.UnitTests"); RunCoreTest("./tests/Avalonia.Interactivity.UnitTests");
RunCoreTest("./tests/Avalonia.Layout.UnitTests"); RunCoreTest("./tests/Avalonia.Layout.UnitTests");

2
readme.md

@ -22,7 +22,7 @@ Avalonia [Visual Studio Extension](https://marketplace.visualstudio.com/items?it
For those without Visual Studio, a starter guide for .NET Core CLI can be found [here](http://avaloniaui.net/docs/quickstart/create-new-project#net-core). For those without Visual Studio, a starter guide for .NET Core CLI can be found [here](http://avaloniaui.net/docs/quickstart/create-new-project#net-core).
Avalonia is delivered via <b>NuGet</b> package manager. You can find the packages here: ([stable(ish)](https://www.nuget.org/packages/Avalonia/), [nightly](https://github.com/AvaloniaUI/Avalonia/wiki/Using-nightly-build-feed)) Avalonia is delivered via <b>NuGet</b> package manager. You can find the packages here: [stable(ish)](https://www.nuget.org/packages/Avalonia/)
Use these commands in the Package Manager console to install Avalonia manually: Use these commands in the Package Manager console to install Avalonia manually:
``` ```

4
samples/ControlCatalog/App.xaml.cs

@ -1,6 +1,4 @@
using System;
using Avalonia; using Avalonia;
using Avalonia.Controls;
using Avalonia.Controls.ApplicationLifetimes; using Avalonia.Controls.ApplicationLifetimes;
using Avalonia.Markup.Xaml; using Avalonia.Markup.Xaml;
@ -19,7 +17,7 @@ namespace ControlCatalog
desktopLifetime.MainWindow = new MainWindow(); desktopLifetime.MainWindow = new MainWindow();
else if (ApplicationLifetime is ISingleViewApplicationLifetime singleViewLifetime) else if (ApplicationLifetime is ISingleViewApplicationLifetime singleViewLifetime)
singleViewLifetime.MainView = new MainView(); singleViewLifetime.MainView = new MainView();
base.OnFrameworkInitializationCompleted(); base.OnFrameworkInitializationCompleted();
} }
} }

6
samples/ControlCatalog/MainView.xaml

@ -32,7 +32,11 @@
<TabItem Header="DatePicker"><pages:DatePickerPage/></TabItem> <TabItem Header="DatePicker"><pages:DatePickerPage/></TabItem>
<TabItem Header="Drag+Drop"><pages:DragAndDropPage/></TabItem> <TabItem Header="Drag+Drop"><pages:DragAndDropPage/></TabItem>
<TabItem Header="Expander"><pages:ExpanderPage/></TabItem> <TabItem Header="Expander"><pages:ExpanderPage/></TabItem>
<TabItem Header="Image"><pages:ImagePage/></TabItem> <TabItem Header="Image"
ScrollViewer.VerticalScrollBarVisibility="Disabled"
ScrollViewer.HorizontalScrollBarVisibility="Disabled">
<pages:ImagePage/>
</TabItem>
<TabItem Header="ItemsRepeater" <TabItem Header="ItemsRepeater"
ScrollViewer.VerticalScrollBarVisibility="Disabled" ScrollViewer.VerticalScrollBarVisibility="Disabled"
ScrollViewer.HorizontalScrollBarVisibility="Disabled"> ScrollViewer.HorizontalScrollBarVisibility="Disabled">

4
samples/ControlCatalog/Pages/ComboBoxPage.xaml.cs

@ -1,5 +1,7 @@
using System.Linq;
using Avalonia.Controls; using Avalonia.Controls;
using Avalonia.Markup.Xaml; using Avalonia.Markup.Xaml;
using Avalonia.Media;
namespace ControlCatalog.Pages namespace ControlCatalog.Pages
{ {
@ -14,7 +16,7 @@ namespace ControlCatalog.Pages
{ {
AvaloniaXamlLoader.Load(this); AvaloniaXamlLoader.Load(this);
var fontComboBox = this.Find<ComboBox>("fontComboBox"); var fontComboBox = this.Find<ComboBox>("fontComboBox");
fontComboBox.Items = Avalonia.Media.FontFamily.SystemFontFamilies; fontComboBox.Items = FontManager.Current.GetInstalledFontFamilyNames().Select(x => new FontFamily(x));
fontComboBox.SelectedIndex = 0; fontComboBox.SelectedIndex = 0;
} }
} }

85
samples/ControlCatalog/Pages/ImagePage.xaml

@ -1,45 +1,52 @@
<UserControl xmlns="https://github.com/avaloniaui" <UserControl xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
x:Class="ControlCatalog.Pages.ImagePage"> x:Class="ControlCatalog.Pages.ImagePage">
<StackPanel Orientation="Vertical" Spacing="4"> <DockPanel>
<TextBlock Classes="h1">Image</TextBlock> <StackPanel DockPanel.Dock="Top" Orientation="Vertical" Spacing="4">
<TextBlock Classes="h2">Displays an image</TextBlock> <TextBlock Classes="h1">Image</TextBlock>
<TextBlock Classes="h2">Displays an image</TextBlock>
<StackPanel Orientation="Horizontal" </StackPanel>
Margin="0,16,0,0"
HorizontalAlignment="Center"
Spacing="16">
<StackPanel Orientation="Vertical">
<TextBlock>No Stretch</TextBlock>
<Image Source="/Assets/delicate-arch-896885_640.jpg"
Width="100" Height="200"
Stretch="None"/>
</StackPanel>
<StackPanel Orientation="Vertical">
<TextBlock>Fill</TextBlock>
<Image Source="/Assets/delicate-arch-896885_640.jpg"
Width="100" Height="200"
Stretch="Fill"/>
</StackPanel>
<StackPanel Orientation="Vertical"> <Grid ColumnDefinitions="*,*" RowDefinitions="Auto,*" Margin="64">
<TextBlock>Uniform</TextBlock>
<Image Source="/Assets/delicate-arch-896885_640.jpg" <DockPanel Grid.Column="0" Grid.Row="1" Margin="16">
Width="100" Height="200" <TextBlock DockPanel.Dock="Top" Classes="h3" Margin="0 8">Bitmap</TextBlock>
Stretch="Uniform"/> <ComboBox Name="bitmapStretch" DockPanel.Dock="Top" SelectedIndex="2" SelectionChanged="BitmapStretchChanged">
</StackPanel> <ComboBoxItem>None</ComboBoxItem>
<ComboBoxItem>Fill</ComboBoxItem>
<ComboBoxItem>Uniform</ComboBoxItem>
<ComboBoxItem>UniformToFill</ComboBoxItem>
</ComboBox>
<Image Name="bitmapImage"
Source="/Assets/delicate-arch-896885_640.jpg"/>
</DockPanel>
<StackPanel Orientation="Vertical"> <DockPanel Grid.Column="1" Grid.Row="1" Margin="16">
<TextBlock>UniformToFill</TextBlock> <TextBlock DockPanel.Dock="Top" Classes="h3" Margin="0 8">Drawing</TextBlock>
<Image Source="/Assets/delicate-arch-896885_640.jpg" <ComboBox Name="drawingStretch" DockPanel.Dock="Top" SelectedIndex="2" SelectionChanged="DrawingStretchChanged">
Width="100" Height="200" <ComboBoxItem>None</ComboBoxItem>
Stretch="UniformToFill"/> <ComboBoxItem>Fill</ComboBoxItem>
</StackPanel> <ComboBoxItem>Uniform</ComboBoxItem>
</StackPanel> <ComboBoxItem>UniformToFill</ComboBoxItem>
<StackPanel Orientation="Vertical"> </ComboBox>
<TextBlock>Window Icon as an Image</TextBlock> <Image Name="drawingImage">
<Image Name="Icon" Width="100" Height="200" Stretch="None" /> <Image.Source>
</StackPanel> <DrawingImage>
</StackPanel> <GeometryDrawing Brush="Red">
<PathGeometry>
<PathFigure StartPoint="0,0" IsClosed="True">
<QuadraticBezierSegment Point1="50,0" Point2="50,-50" />
<QuadraticBezierSegment Point1="100,-50" Point2="100,0" />
<LineSegment Point="50,0" />
<LineSegment Point="50,50" />
</PathFigure>
</PathGeometry>
</GeometryDrawing>
</DrawingImage>
</Image.Source>
</Image>
</DockPanel>
</Grid>
</DockPanel>
</UserControl> </UserControl>

39
samples/ControlCatalog/Pages/ImagePage.xaml.cs

@ -1,40 +1,41 @@
using System.IO;
using Avalonia;
using Avalonia.Controls; using Avalonia.Controls;
using Avalonia.Markup.Xaml; using Avalonia.Markup.Xaml;
using Avalonia.Media.Imaging; using Avalonia.Media;
namespace ControlCatalog.Pages namespace ControlCatalog.Pages
{ {
public class ImagePage : UserControl public class ImagePage : UserControl
{ {
private Image iconImage; private readonly Image _bitmapImage;
private readonly Image _drawingImage;
public ImagePage() public ImagePage()
{ {
this.InitializeComponent(); InitializeComponent();
_bitmapImage = this.FindControl<Image>("bitmapImage");
_drawingImage = this.FindControl<Image>("drawingImage");
} }
private void InitializeComponent() private void InitializeComponent()
{ {
AvaloniaXamlLoader.Load(this); AvaloniaXamlLoader.Load(this);
iconImage = this.Get<Image>("Icon");
} }
protected override void OnAttachedToVisualTree(VisualTreeAttachmentEventArgs e) public void BitmapStretchChanged(object sender, SelectionChangedEventArgs e)
{
if (_bitmapImage != null)
{
var comboxBox = (ComboBox)sender;
_bitmapImage.Stretch = (Stretch)comboxBox.SelectedIndex;
}
}
public void DrawingStretchChanged(object sender, SelectionChangedEventArgs e)
{ {
base.OnAttachedToVisualTree(e); if (_drawingImage != null)
if (iconImage.Source == null)
{ {
var windowRoot = e.Root as Window; var comboxBox = (ComboBox)sender;
if (windowRoot != null) _drawingImage.Stretch = (Stretch)comboxBox.SelectedIndex;
{
using (var stream = new MemoryStream())
{
windowRoot.Icon.Save(stream);
stream.Seek(0, SeekOrigin.Begin);
iconImage.Source = new Bitmap(stream);
}
}
} }
} }
} }

3
samples/RenderDemo/MainWindow.xaml

@ -41,6 +41,9 @@
<TabItem Header="RenderTargetBitmap"> <TabItem Header="RenderTargetBitmap">
<pages:RenderTargetBitmapPage/> <pages:RenderTargetBitmapPage/>
</TabItem> </TabItem>
<TabItem Header="GlyphRun">
<pages:GlyphRunPage/>
</TabItem>
</TabControl> </TabControl>
</DockPanel> </DockPanel>
</Window> </Window>

14
samples/RenderDemo/Pages/GlyphRunPage.xaml

@ -0,0 +1,14 @@
<UserControl xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
x:Class="RenderDemo.Pages.GlyphRunPage">
<Border
Background="White">
<DrawingPresenter
x:Name="drawingPresenter"
Stretch="None">
</DrawingPresenter>
</Border>
</UserControl>

80
samples/RenderDemo/Pages/GlyphRunPage.xaml.cs

@ -0,0 +1,80 @@
using System;
using Avalonia;
using Avalonia.Controls;
using Avalonia.Markup.Xaml;
using Avalonia.Media;
using Avalonia.Threading;
namespace RenderDemo.Pages
{
public class GlyphRunPage : UserControl
{
private DrawingPresenter _drawingPresenter;
private GlyphTypeface _glyphTypeface = Typeface.Default.GlyphTypeface;
private readonly Random _rand = new Random();
private ushort[] _glyphIndices = new ushort[1];
private float _fontSize = 20;
private int _direction = 10;
public GlyphRunPage()
{
this.InitializeComponent();
}
private void InitializeComponent()
{
AvaloniaXamlLoader.Load(this);
_drawingPresenter = this.FindControl<DrawingPresenter>("drawingPresenter");
DispatcherTimer.Run(() =>
{
UpdateGlyphRun();
return true;
}, TimeSpan.FromSeconds(1));
}
private void UpdateGlyphRun()
{
var c = (uint)_rand.Next(65, 90);
if (_fontSize + _direction > 200)
{
_direction = -10;
}
if (_fontSize + _direction < 20)
{
_direction = 10;
}
_fontSize += _direction;
_glyphIndices[0] = _glyphTypeface.GetGlyph(c);
var scale = (double)_fontSize / _glyphTypeface.DesignEmHeight;
var drawingGroup = new DrawingGroup();
var glyphRunDrawing = new GlyphRunDrawing
{
Foreground = Brushes.Black,
GlyphRun = new GlyphRun(_glyphTypeface, _fontSize, _glyphIndices),
BaselineOrigin = new Point(0, -_glyphTypeface.Ascent * scale)
};
drawingGroup.Children.Add(glyphRunDrawing);
var geometryDrawing = new GeometryDrawing
{
Pen = new Pen(Brushes.Black),
Geometry = new RectangleGeometry { Rect = glyphRunDrawing.GlyphRun.Bounds }
};
drawingGroup.Children.Add(geometryDrawing);
_drawingPresenter.Drawing = drawingGroup;
}
}
}

2
samples/RenderDemo/Pages/RenderTargetBitmapPage.cs

@ -39,7 +39,7 @@ namespace RenderDemo.Pages
ctx.FillRectangle(Brushes.Fuchsia, new Rect(50, 50, 100, 100)); ctx.FillRectangle(Brushes.Fuchsia, new Rect(50, 50, 100, 100));
} }
context.DrawImage(_bitmap, 1, context.DrawImage(_bitmap,
new Rect(0, 0, 200, 200), new Rect(0, 0, 200, 200),
new Rect(0, 0, 200, 200)); new Rect(0, 0, 200, 200));
Dispatcher.UIThread.Post(InvalidateVisual, DispatcherPriority.Background); Dispatcher.UIThread.Post(InvalidateVisual, DispatcherPriority.Background);

64
scripts/avalonia-rename.ps1

@ -1,64 +0,0 @@
function Get-NewDirectoryName {
param ([System.IO.DirectoryInfo]$item)
$name = $item.Name.Replace("perspex", "avalonia")
$name = $name.Replace("Perspex", "Avalonia")
Join-Path $item.Parent.FullName $name
}
function Get-NewFileName {
param ([System.IO.FileInfo]$item)
$name = $item.Name.Replace("perspex", "avalonia")
$name = $name.Replace("Perspex", "Avalonia")
Join-Path $item.DirectoryName $name
}
function Rename-Contents {
param ([System.IO.FileInfo] $file)
$extensions = @(".cs",".xaml",".csproj",".sln",".md",".json",".yml",".partial",".ps1",".nuspec",".htm",".html",".gitmodules".".xml",".plist",".targets",".projitems",".shproj",".xib")
if ($extensions.Contains($file.Extension)) {
$text = [IO.File]::ReadAllText($file.FullName)
$text = $text.Replace("github.com/perspex", "github.com/avaloniaui")
$text = $text.Replace("github.com/Perspex", "github.com/AvaloniaUI")
$text = $text.Replace("perspex", "avalonia")
$text = $text.Replace("Perspex", "Avalonia")
$text = $text.Replace("PERSPEX", "AVALONIA")
[IO.File]::WriteAllText($file.FullName, $text)
}
}
function Process-Files {
param ([System.IO.DirectoryInfo] $item)
$dirs = Get-ChildItem -Path $item.FullName -Directory
$files = Get-ChildItem -Path $item.FullName -File
foreach ($dir in $dirs) {
Process-Files $dir.FullName
}
foreach ($file in $files) {
Rename-Contents $file
$renamed = Get-NewFileName $file
if ($file.FullName -ne $renamed) {
Write-Host git mv $file.FullName $renamed
& git mv $file.FullName $renamed
}
}
$renamed = Get-NewDirectoryName $item
if ($item.FullName -ne $renamed) {
Write-Host git mv $item.FullName $renamed
& git mv $item.FullName $renamed
}
}
& git submodule deinit .
& git clean -xdf
Process-Files .

4
src/Avalonia.Animation/Animatable.cs

@ -85,8 +85,8 @@ namespace Avalonia.Animation
var instance = transition.Apply( var instance = transition.Apply(
this, this,
Clock ?? Avalonia.Animation.Clock.GlobalClock, Clock ?? Avalonia.Animation.Clock.GlobalClock,
oldValue.ValueOrDefault(), oldValue.GetValueOrDefault(),
newValue.ValueOrDefault()); newValue.GetValueOrDefault());
_previousTransitions[property] = instance; _previousTransitions[property] = instance;
return; return;

2
src/Avalonia.Animation/IterationCount.cs

@ -63,7 +63,7 @@ namespace Avalonia.Animation
public IterationType RepeatType => _type; public IterationType RepeatType => _type;
/// <summary> /// <summary>
/// Gets a value that indicates whether the <see cref="IterationCount"/> is set to loop. /// Gets a value that indicates whether the <see cref="IterationCount"/> is set to Infinite.
/// </summary> /// </summary>
public bool IsInfinite => _type == IterationType.Infinite; public bool IsInfinite => _type == IterationType.Infinite;

8
src/Avalonia.Base/AttachedProperty.cs

@ -18,12 +18,14 @@ namespace Avalonia
/// <param name="ownerType">The class that is registering the property.</param> /// <param name="ownerType">The class that is registering the property.</param>
/// <param name="metadata">The property metadata.</param> /// <param name="metadata">The property metadata.</param>
/// <param name="inherits">Whether the property inherits its value.</param> /// <param name="inherits">Whether the property inherits its value.</param>
/// <param name="validate">A value validation callback.</param>
public AttachedProperty( public AttachedProperty(
string name, string name,
Type ownerType, Type ownerType,
StyledPropertyMetadata<TValue> metadata, StyledPropertyMetadata<TValue> metadata,
bool inherits = false) bool inherits = false,
: base(name, ownerType, metadata, inherits) Func<TValue, bool> validate = null)
: base(name, ownerType, metadata, inherits, validate)
{ {
} }

95
src/Avalonia.Base/AvaloniaObject.cs

@ -229,24 +229,6 @@ namespace Avalonia
return property.RouteGetValue(this); return property.RouteGetValue(this);
} }
/// <summary>
/// Gets a <see cref="AvaloniaProperty"/> value.
/// </summary>
/// <typeparam name="T">The type of the property.</typeparam>
/// <param name="property">The property.</param>
/// <returns>The value.</returns>
public T GetValue<T>(AvaloniaProperty<T> property)
{
property = property ?? throw new ArgumentNullException(nameof(property));
return property switch
{
StyledPropertyBase<T> styled => GetValue(styled),
DirectPropertyBase<T> direct => GetValue(direct),
_ => throw new NotSupportedException("Unsupported AvaloniaProperty type.")
};
}
/// <summary> /// <summary>
/// Gets a <see cref="AvaloniaProperty"/> value. /// Gets a <see cref="AvaloniaProperty"/> value.
/// </summary> /// </summary>
@ -322,33 +304,6 @@ namespace Avalonia
property.RouteSetValue(this, value, priority); property.RouteSetValue(this, value, priority);
} }
/// <summary>
/// Sets a <see cref="AvaloniaProperty"/> value.
/// </summary>
/// <typeparam name="T">The type of the property.</typeparam>
/// <param name="property">The property.</param>
/// <param name="value">The value.</param>
/// <param name="priority">The priority of the value.</param>
public void SetValue<T>(
AvaloniaProperty<T> property,
T value,
BindingPriority priority = BindingPriority.LocalValue)
{
property = property ?? throw new ArgumentNullException(nameof(property));
switch (property)
{
case StyledPropertyBase<T> styled:
SetValue(styled, value, priority);
break;
case DirectPropertyBase<T> direct:
SetValue(direct, value);
break;
default:
throw new NotSupportedException("Unsupported AvaloniaProperty type.");
}
}
/// <summary> /// <summary>
/// Sets a <see cref="AvaloniaProperty"/> value. /// Sets a <see cref="AvaloniaProperty"/> value.
/// </summary> /// </summary>
@ -375,7 +330,7 @@ namespace Avalonia
else else
{ {
throw new NotSupportedException( throw new NotSupportedException(
"Canot set property to Unset at non-local value priority."); "Cannot set property to Unset at non-local value priority.");
} }
} }
else if (!(value is DoNothingType)) else if (!(value is DoNothingType))
@ -399,52 +354,6 @@ namespace Avalonia
SetDirectValueUnchecked(property, value); SetDirectValueUnchecked(property, value);
} }
/// <summary>
/// Binds a <see cref="AvaloniaProperty"/> to an observable.
/// </summary>
/// <param name="property">The property.</param>
/// <param name="source">The observable.</param>
/// <param name="priority">The priority of the binding.</param>
/// <returns>
/// A disposable which can be used to terminate the binding.
/// </returns>
public IDisposable Bind(
AvaloniaProperty property,
IObservable<BindingValue<object>> source,
BindingPriority priority = BindingPriority.LocalValue)
{
property = property ?? throw new ArgumentNullException(nameof(property));
source = source ?? throw new ArgumentNullException(nameof(source));
return property.RouteBind(this, source, priority);
}
/// <summary>
/// Binds a <see cref="AvaloniaProperty"/> to an observable.
/// </summary>
/// <typeparam name="T">The type of the property.</typeparam>
/// <param name="property">The property.</param>
/// <param name="source">The observable.</param>
/// <param name="priority">The priority of the binding.</param>
/// <returns>
/// A disposable which can be used to terminate the binding.
/// </returns>
public IDisposable Bind<T>(
AvaloniaProperty<T> property,
IObservable<BindingValue<T>> source,
BindingPriority priority = BindingPriority.LocalValue)
{
property = property ?? throw new ArgumentNullException(nameof(property));
source = source ?? throw new ArgumentNullException(nameof(source));
return property switch
{
StyledPropertyBase<T> styled => Bind(styled, source, priority),
DirectPropertyBase<T> direct => Bind(direct, source),
_ => throw new NotSupportedException("Unsupported AvaloniaProperty type."),
};
}
/// <summary> /// <summary>
/// Binds a <see cref="AvaloniaProperty"/> to an observable. /// Binds a <see cref="AvaloniaProperty"/> to an observable.
/// </summary> /// </summary>
@ -531,7 +440,7 @@ namespace Avalonia
Optional<T> oldValue, Optional<T> oldValue,
Optional<T> newValue) Optional<T> newValue)
{ {
if (property.Inherits && !IsSet(property)) if (property.Inherits && (_values == null || !_values.IsSet(property)))
{ {
RaisePropertyChanged(property, oldValue, newValue, BindingPriority.LocalValue); RaisePropertyChanged(property, oldValue, newValue, BindingPriority.LocalValue);
} }

24
src/Avalonia.Base/AvaloniaProperty.cs

@ -3,9 +3,7 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Diagnostics;
using System.Reactive.Subjects; using System.Reactive.Subjects;
using System.Reflection;
using Avalonia.Data; using Avalonia.Data;
using Avalonia.Utilities; using Avalonia.Utilities;
@ -285,22 +283,25 @@ namespace Avalonia
/// <param name="inherits">Whether the property inherits its value.</param> /// <param name="inherits">Whether the property inherits its value.</param>
/// <param name="defaultBindingMode">The default binding mode for the property.</param> /// <param name="defaultBindingMode">The default binding mode for the property.</param>
/// <param name="validate">A value validation callback.</param> /// <param name="validate">A value validation callback.</param>
/// <param name="coerce">A value coercion callback.</param>
/// <returns>A <see cref="AvaloniaProperty{TValue}"/></returns> /// <returns>A <see cref="AvaloniaProperty{TValue}"/></returns>
public static AttachedProperty<TValue> RegisterAttached<TOwner, THost, TValue>( public static AttachedProperty<TValue> RegisterAttached<TOwner, THost, TValue>(
string name, string name,
TValue defaultValue = default(TValue), TValue defaultValue = default(TValue),
bool inherits = false, bool inherits = false,
BindingMode defaultBindingMode = BindingMode.OneWay, BindingMode defaultBindingMode = BindingMode.OneWay,
Func<TValue, bool> validate = null) Func<TValue, bool> validate = null,
Func<IAvaloniaObject, TValue, TValue> coerce = null)
where THost : IAvaloniaObject where THost : IAvaloniaObject
{ {
Contract.Requires<ArgumentNullException>(name != null); Contract.Requires<ArgumentNullException>(name != null);
var metadata = new StyledPropertyMetadata<TValue>( var metadata = new StyledPropertyMetadata<TValue>(
defaultValue, defaultValue,
defaultBindingMode: defaultBindingMode); defaultBindingMode: defaultBindingMode,
coerce: coerce);
var result = new AttachedProperty<TValue>(name, typeof(TOwner), metadata, inherits); var result = new AttachedProperty<TValue>(name, typeof(TOwner), metadata, inherits, validate);
var registry = AvaloniaPropertyRegistry.Instance; var registry = AvaloniaPropertyRegistry.Instance;
registry.Register(typeof(TOwner), result); registry.Register(typeof(TOwner), result);
registry.RegisterAttached(typeof(THost), result); registry.RegisterAttached(typeof(THost), result);
@ -317,22 +318,27 @@ namespace Avalonia
/// <param name="defaultValue">The default value of the property.</param> /// <param name="defaultValue">The default value of the property.</param>
/// <param name="inherits">Whether the property inherits its value.</param> /// <param name="inherits">Whether the property inherits its value.</param>
/// <param name="defaultBindingMode">The default binding mode for the property.</param> /// <param name="defaultBindingMode">The default binding mode for the property.</param>
/// <param name="validate">A value validation callback.</param>
/// <param name="coerce">A value coercion callback.</param>
/// <returns>A <see cref="AvaloniaProperty{TValue}"/></returns> /// <returns>A <see cref="AvaloniaProperty{TValue}"/></returns>
public static AttachedProperty<TValue> RegisterAttached<THost, TValue>( public static AttachedProperty<TValue> RegisterAttached<THost, TValue>(
string name, string name,
Type ownerType, Type ownerType,
TValue defaultValue = default(TValue), TValue defaultValue = default(TValue),
bool inherits = false, bool inherits = false,
BindingMode defaultBindingMode = BindingMode.OneWay) BindingMode defaultBindingMode = BindingMode.OneWay,
Func<TValue, bool> validate = null,
Func<IAvaloniaObject, TValue, TValue> coerce = null)
where THost : IAvaloniaObject where THost : IAvaloniaObject
{ {
Contract.Requires<ArgumentNullException>(name != null); Contract.Requires<ArgumentNullException>(name != null);
var metadata = new StyledPropertyMetadata<TValue>( var metadata = new StyledPropertyMetadata<TValue>(
defaultValue, defaultValue,
defaultBindingMode: defaultBindingMode); defaultBindingMode: defaultBindingMode,
coerce: coerce);
var result = new AttachedProperty<TValue>(name, ownerType, metadata, inherits); var result = new AttachedProperty<TValue>(name, ownerType, metadata, inherits, validate);
var registry = AvaloniaPropertyRegistry.Instance; var registry = AvaloniaPropertyRegistry.Instance;
registry.Register(ownerType, result); registry.Register(ownerType, result);
registry.RegisterAttached(typeof(THost), result); registry.RegisterAttached(typeof(THost), result);
@ -555,7 +561,7 @@ namespace Avalonia
return result; return result;
} }
currentType = currentType.GetTypeInfo().BaseType; currentType = currentType.BaseType;
} }
_metadataCache[type] = _defaultMetadata; _metadataCache[type] = _defaultMetadata;

4
src/Avalonia.Base/AvaloniaPropertyChangedEventArgs`1.cs

@ -60,8 +60,8 @@ namespace Avalonia
protected override AvaloniaProperty GetProperty() => Property; protected override AvaloniaProperty GetProperty() => Property;
protected override object? GetOldValue() => OldValue.ValueOrDefault(AvaloniaProperty.UnsetValue); protected override object? GetOldValue() => OldValue.GetValueOrDefault(AvaloniaProperty.UnsetValue);
protected override object? GetNewValue() => NewValue.ValueOrDefault(AvaloniaProperty.UnsetValue); protected override object? GetNewValue() => NewValue.GetValueOrDefault(AvaloniaProperty.UnsetValue);
} }
} }

12
src/Avalonia.Base/AvaloniaPropertyRegistry.cs

@ -224,12 +224,20 @@ namespace Avalonia
Contract.Requires<ArgumentNullException>(type != null); Contract.Requires<ArgumentNullException>(type != null);
Contract.Requires<ArgumentNullException>(name != null); Contract.Requires<ArgumentNullException>(name != null);
if (name.Contains('.')) if (name.Contains("."))
{ {
throw new InvalidOperationException("Attached properties not supported."); throw new InvalidOperationException("Attached properties not supported.");
} }
return GetRegistered(type).FirstOrDefault(x => x.Name == name); foreach (AvaloniaProperty x in GetRegistered(type))
{
if (x.Name == name)
{
return x;
}
}
return null;
} }
/// <summary> /// <summary>

20
src/Avalonia.Base/Collections/AvaloniaList.cs

@ -327,6 +327,8 @@ namespace Avalonia.Collections
} }
else else
{ {
EnsureCapacity(_inner.Count + list.Count);
using (IEnumerator<T> en = items.GetEnumerator()) using (IEnumerator<T> en = items.GetEnumerator())
{ {
int insertIndex = index; int insertIndex = index;
@ -550,6 +552,24 @@ namespace Avalonia.Collections
/// <inheritdoc/> /// <inheritdoc/>
Delegate[] INotifyCollectionChangedDebug.GetCollectionChangedSubscribers() => _collectionChanged?.GetInvocationList(); Delegate[] INotifyCollectionChangedDebug.GetCollectionChangedSubscribers() => _collectionChanged?.GetInvocationList();
private void EnsureCapacity(int capacity)
{
// Adapted from List<T> implementation.
var currentCapacity = _inner.Capacity;
if (currentCapacity < capacity)
{
var newCapacity = currentCapacity == 0 ? 4 : currentCapacity * 2;
if (newCapacity < capacity)
{
newCapacity = capacity;
}
_inner.Capacity = newCapacity;
}
}
/// <summary> /// <summary>
/// Raises the <see cref="CollectionChanged"/> event with an add action. /// Raises the <see cref="CollectionChanged"/> event with an add action.
/// </summary> /// </summary>

40
src/Avalonia.Base/Collections/Pooled/ClearMode.cs

@ -0,0 +1,40 @@
// This source file is adapted from the Collections.Pooled.
// (https://github.com/jtmueller/Collections.Pooled/tree/master/Collections.Pooled/)
namespace Avalonia.Collections.Pooled
{
/// <summary>
/// This enum allows control over how data is treated when internal
/// arrays are returned to the ArrayPool. Be careful to understand
/// what each option does before using anything other than the default
/// of Auto.
/// </summary>
public enum ClearMode
{
/// <summary>
/// <para><code>Auto</code> has different behavior depending on the host project's target framework.</para>
/// <para>.NET Core 2.1: Reference types and value types that contain reference types are cleared
/// when the internal arrays are returned to the pool. Value types that do not contain reference
/// types are not cleared when returned to the pool.</para>
/// <para>.NET Standard 2.0: All user types are cleared before returning to the pool, in case they
/// contain reference types.
/// For .NET Standard, Auto and Always have the same behavior.</para>
/// </summary>
Auto = 0,
/// <summary>
/// The <para><code>Always</code> setting has the effect of always clearing user types before returning to the pool.
/// This is the default behavior on .NET Standard.</para><para>You might want to turn this on in a .NET Core project
/// if you were concerned about sensitive data stored in value types leaking to other pars of your application.</para>
/// </summary>
Always = 1,
/// <summary>
/// <para><code>Never</code> will cause pooled collections to never clear user types before returning them to the pool.</para>
/// <para>You might want to use this setting in a .NET Standard project when you know that a particular collection stores
/// only value types and you want the performance benefit of not taking time to reset array items to their default value.</para>
/// <para>Be careful with this setting: if used for a collection that contains reference types, or value types that contain
/// reference types, this setting could cause memory issues by making the garbage collector unable to clean up instances
/// that are still being referenced by arrays sitting in the ArrayPool.</para>
/// </summary>
Never = 2
}
}

31
src/Avalonia.Base/Collections/Pooled/ICollectionDebugView.cs

@ -0,0 +1,31 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System;
using System.Collections.Generic;
using System.Diagnostics;
namespace Avalonia.Collections.Pooled
{
internal sealed class ICollectionDebugView<T>
{
private readonly ICollection<T> _collection;
public ICollectionDebugView(ICollection<T> collection)
{
_collection = collection ?? throw new ArgumentNullException(nameof(collection));
}
[DebuggerBrowsable(DebuggerBrowsableState.RootHidden)]
public T[] Items
{
get
{
T[] items = new T[_collection.Count];
_collection.CopyTo(items, 0);
return items;
}
}
}
}

21
src/Avalonia.Base/Collections/Pooled/IReadOnlyPooledList.cs

@ -0,0 +1,21 @@
// This source file is adapted from the Collections.Pooled.
// (https://github.com/jtmueller/Collections.Pooled/tree/master/Collections.Pooled/)
using System;
using System.Collections.Generic;
namespace Avalonia.Collections.Pooled
{
/// <summary>
/// Represents a read-only collection of pooled elements that can be accessed by index
/// </summary>
/// <typeparam name="T">The type of elements in the read-only pooled list.</typeparam>
public interface IReadOnlyPooledList<T> : IReadOnlyList<T>
{
/// <summary>
/// Gets a <see cref="System.ReadOnlySpan{T}"/> for the items currently in the collection.
/// </summary>
ReadOnlySpan<T> Span { get; }
}
}

1531
src/Avalonia.Base/Collections/Pooled/PooledList.cs

File diff suppressed because it is too large

699
src/Avalonia.Base/Collections/Pooled/PooledStack.cs

@ -0,0 +1,699 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
/*=============================================================================
**
**
** Purpose: An array implementation of a generic stack.
**
**
=============================================================================*/
using System;
using System.Buffers;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Runtime.CompilerServices;
using System.Runtime.Serialization;
using System.Threading;
namespace Avalonia.Collections.Pooled
{
/// <summary>
/// A simple stack of objects. Internally it is implemented as an array,
/// so Push can be O(n). Pop is O(1).
/// </summary>
[DebuggerTypeProxy(typeof(StackDebugView<>))]
[DebuggerDisplay("Count = {Count}")]
[Serializable]
public class PooledStack<T> : IEnumerable<T>, ICollection, IReadOnlyCollection<T>, IDisposable, IDeserializationCallback
{
[NonSerialized]
private ArrayPool<T> _pool;
[NonSerialized]
private object _syncRoot;
private T[] _array; // Storage for stack elements. Do not rename (binary serialization)
private int _size; // Number of items in the stack. Do not rename (binary serialization)
private int _version; // Used to keep enumerator in sync w/ collection. Do not rename (binary serialization)
private readonly bool _clearOnFree;
private const int DefaultCapacity = 4;
#region Constructors
/// <summary>
/// Create a stack with the default initial capacity.
/// </summary>
public PooledStack() : this(ClearMode.Auto, ArrayPool<T>.Shared) { }
/// <summary>
/// Create a stack with the default initial capacity.
/// </summary>
public PooledStack(ClearMode clearMode) : this(clearMode, ArrayPool<T>.Shared) { }
/// <summary>
/// Create a stack with the default initial capacity.
/// </summary>
public PooledStack(ArrayPool<T> customPool) : this(ClearMode.Auto, customPool) { }
/// <summary>
/// Create a stack with the default initial capacity and a custom ArrayPool.
/// </summary>
public PooledStack(ClearMode clearMode, ArrayPool<T> customPool)
{
_pool = customPool ?? ArrayPool<T>.Shared;
_array = Array.Empty<T>();
_clearOnFree = ShouldClear(clearMode);
}
/// <summary>
/// Create a stack with a specific initial capacity. The initial capacity
/// must be a non-negative number.
/// </summary>
public PooledStack(int capacity) : this(capacity, ClearMode.Auto, ArrayPool<T>.Shared) { }
/// <summary>
/// Create a stack with a specific initial capacity. The initial capacity
/// must be a non-negative number.
/// </summary>
public PooledStack(int capacity, ClearMode clearMode) : this(capacity, clearMode, ArrayPool<T>.Shared) { }
/// <summary>
/// Create a stack with a specific initial capacity. The initial capacity
/// must be a non-negative number.
/// </summary>
public PooledStack(int capacity, ArrayPool<T> customPool) : this(capacity, ClearMode.Auto, customPool) { }
/// <summary>
/// Create a stack with a specific initial capacity. The initial capacity
/// must be a non-negative number.
/// </summary>
public PooledStack(int capacity, ClearMode clearMode, ArrayPool<T> customPool)
{
if (capacity < 0)
{
ThrowHelper.ThrowArgumentOutOfRangeException(ExceptionArgument.capacity,
ExceptionResource.ArgumentOutOfRange_NeedNonNegNum);
}
_pool = customPool ?? ArrayPool<T>.Shared;
_array = _pool.Rent(capacity);
_clearOnFree = ShouldClear(clearMode);
}
/// <summary>
/// Fills a Stack with the contents of a particular collection. The items are
/// pushed onto the stack in the same order they are read by the enumerator.
/// </summary>
public PooledStack(IEnumerable<T> enumerable) : this(enumerable, ClearMode.Auto, ArrayPool<T>.Shared) { }
/// <summary>
/// Fills a Stack with the contents of a particular collection. The items are
/// pushed onto the stack in the same order they are read by the enumerator.
/// </summary>
public PooledStack(IEnumerable<T> enumerable, ClearMode clearMode) : this(enumerable, clearMode, ArrayPool<T>.Shared) { }
/// <summary>
/// Fills a Stack with the contents of a particular collection. The items are
/// pushed onto the stack in the same order they are read by the enumerator.
/// </summary>
public PooledStack(IEnumerable<T> enumerable, ArrayPool<T> customPool) : this(enumerable, ClearMode.Auto, customPool) { }
/// <summary>
/// Fills a Stack with the contents of a particular collection. The items are
/// pushed onto the stack in the same order they are read by the enumerator.
/// </summary>
public PooledStack(IEnumerable<T> enumerable, ClearMode clearMode, ArrayPool<T> customPool)
{
_pool = customPool ?? ArrayPool<T>.Shared;
_clearOnFree = ShouldClear(clearMode);
switch (enumerable)
{
case null:
ThrowHelper.ThrowArgumentNullException(ExceptionArgument.enumerable);
break;
case ICollection<T> collection:
if (collection.Count == 0)
{
_array = Array.Empty<T>();
}
else
{
_array = _pool.Rent(collection.Count);
collection.CopyTo(_array, 0);
_size = collection.Count;
}
break;
default:
using (var list = new PooledList<T>(enumerable))
{
_array = _pool.Rent(list.Count);
list.Span.CopyTo(_array);
_size = list.Count;
}
break;
}
}
/// <summary>
/// Fills a Stack with the contents of a particular collection. The items are
/// pushed onto the stack in the same order they are read by the enumerator.
/// </summary>
public PooledStack(T[] array) : this(array.AsSpan(), ClearMode.Auto, ArrayPool<T>.Shared) { }
/// <summary>
/// Fills a Stack with the contents of a particular collection. The items are
/// pushed onto the stack in the same order they are read by the enumerator.
/// </summary>
public PooledStack(T[] array, ClearMode clearMode) : this(array.AsSpan(), clearMode, ArrayPool<T>.Shared) { }
/// <summary>
/// Fills a Stack with the contents of a particular collection. The items are
/// pushed onto the stack in the same order they are read by the enumerator.
/// </summary>
public PooledStack(T[] array, ArrayPool<T> customPool) : this(array.AsSpan(), ClearMode.Auto, customPool) { }
/// <summary>
/// Fills a Stack with the contents of a particular collection. The items are
/// pushed onto the stack in the same order they are read by the enumerator.
/// </summary>
public PooledStack(T[] array, ClearMode clearMode, ArrayPool<T> customPool) : this(array.AsSpan(), clearMode, customPool) { }
/// <summary>
/// Fills a Stack with the contents of a particular collection. The items are
/// pushed onto the stack in the same order they are read by the enumerator.
/// </summary>
public PooledStack(ReadOnlySpan<T> span) : this(span, ClearMode.Auto, ArrayPool<T>.Shared) { }
/// <summary>
/// Fills a Stack with the contents of a particular collection. The items are
/// pushed onto the stack in the same order they are read by the enumerator.
/// </summary>
public PooledStack(ReadOnlySpan<T> span, ClearMode clearMode) : this(span, clearMode, ArrayPool<T>.Shared) { }
/// <summary>
/// Fills a Stack with the contents of a particular collection. The items are
/// pushed onto the stack in the same order they are read by the enumerator.
/// </summary>
public PooledStack(ReadOnlySpan<T> span, ArrayPool<T> customPool) : this(span, ClearMode.Auto, customPool) { }
/// <summary>
/// Fills a Stack with the contents of a particular collection. The items are
/// pushed onto the stack in the same order they are read by the enumerator.
/// </summary>
public PooledStack(ReadOnlySpan<T> span, ClearMode clearMode, ArrayPool<T> customPool)
{
_pool = customPool ?? ArrayPool<T>.Shared;
_clearOnFree = ShouldClear(clearMode);
_array = _pool.Rent(span.Length);
span.CopyTo(_array);
_size = span.Length;
}
#endregion
/// <summary>
/// The number of items in the stack.
/// </summary>
public int Count => _size;
/// <summary>
/// Returns the ClearMode behavior for the collection, denoting whether values are
/// cleared from internal arrays before returning them to the pool.
/// </summary>
public ClearMode ClearMode => _clearOnFree ? ClearMode.Always : ClearMode.Never;
bool ICollection.IsSynchronized => false;
object ICollection.SyncRoot
{
get
{
if (_syncRoot == null)
{
Interlocked.CompareExchange<object>(ref _syncRoot, new object(), null);
}
return _syncRoot;
}
}
/// <summary>
/// Removes all Objects from the Stack.
/// </summary>
public void Clear()
{
if (_clearOnFree)
{
Array.Clear(_array, 0, _size); // clear the elements so that the gc can reclaim the references.
}
_size = 0;
_version++;
}
/// <summary>
/// Compares items using the default equality comparer
/// </summary>
public bool Contains(T item)
{
// PERF: Internally Array.LastIndexOf calls
// EqualityComparer<T>.Default.LastIndexOf, which
// is specialized for different types. This
// boosts performance since instead of making a
// virtual method call each iteration of the loop,
// via EqualityComparer<T>.Default.Equals, we
// only make one virtual call to EqualityComparer.LastIndexOf.
return _size != 0 && Array.LastIndexOf(_array, item, _size - 1) != -1;
}
/// <summary>
/// This method removes all items which match the predicate.
/// The complexity is O(n).
/// </summary>
public int RemoveWhere(Func<T, bool> match)
{
if (match == null)
ThrowHelper.ThrowArgumentNullException(ExceptionArgument.match);
int freeIndex = 0; // the first free slot in items array
// Find the first item which needs to be removed.
while (freeIndex < _size && !match(_array[freeIndex]))
freeIndex++;
if (freeIndex >= _size)
return 0;
int current = freeIndex + 1;
while (current < _size)
{
// Find the first item which needs to be kept.
while (current < _size && match(_array[current]))
current++;
if (current < _size)
{
// copy item to the free slot.
_array[freeIndex++] = _array[current++];
}
}
if (_clearOnFree)
{
// Clear the removed elements so that the gc can reclaim the references.
Array.Clear(_array, freeIndex, _size - freeIndex);
}
int result = _size - freeIndex;
_size = freeIndex;
_version++;
return result;
}
// Copies the stack into an array.
public void CopyTo(T[] array, int arrayIndex)
{
if (array == null)
{
ThrowHelper.ThrowArgumentNullException(ExceptionArgument.array);
}
if (arrayIndex < 0 || arrayIndex > array.Length)
{
ThrowHelper.ThrowArgumentOutOfRangeException(ExceptionArgument.arrayIndex);
}
if (array.Length - arrayIndex < _size)
{
ThrowHelper.ThrowArgumentException(ExceptionResource.Arg_ArrayPlusOffTooSmall);
}
Debug.Assert(array != _array);
int srcIndex = 0;
int dstIndex = arrayIndex + _size;
while (srcIndex < _size)
{
array[--dstIndex] = _array[srcIndex++];
}
}
public void CopyTo(Span<T> span)
{
if (span.Length < _size)
{
ThrowHelper.ThrowArgumentException_DestinationTooShort();
}
int srcIndex = 0;
int dstIndex = _size;
while (srcIndex < _size)
{
span[--dstIndex] = _array[srcIndex++];
}
}
void ICollection.CopyTo(Array array, int arrayIndex)
{
if (array == null)
{
ThrowHelper.ThrowArgumentNullException(ExceptionArgument.array);
}
if (array.Rank != 1)
{
ThrowHelper.ThrowArgumentException(ExceptionResource.Arg_RankMultiDimNotSupported);
}
if (array.GetLowerBound(0) != 0)
{
ThrowHelper.ThrowArgumentException(ExceptionResource.Arg_NonZeroLowerBound, ExceptionArgument.array);
}
if (arrayIndex < 0 || arrayIndex > array.Length)
{
ThrowHelper.ThrowArgumentOutOfRangeException(ExceptionArgument.arrayIndex);
}
if (array.Length - arrayIndex < _size)
{
ThrowHelper.ThrowArgumentException(ExceptionResource.Argument_InvalidOffLen);
}
try
{
Array.Copy(_array, 0, array, arrayIndex, _size);
Array.Reverse(array, arrayIndex, _size);
}
catch (ArrayTypeMismatchException)
{
ThrowHelper.ThrowArgumentException_Argument_InvalidArrayType();
}
}
/// <summary>
/// Returns an IEnumerator for this PooledStack.
/// </summary>
/// <returns></returns>
public Enumerator GetEnumerator()
=> new Enumerator(this);
/// <internalonly/>
IEnumerator<T> IEnumerable<T>.GetEnumerator()
=> new Enumerator(this);
IEnumerator IEnumerable.GetEnumerator()
=> new Enumerator(this);
public void TrimExcess()
{
if (_size == 0)
{
ReturnArray(replaceWith: Array.Empty<T>());
_version++;
return;
}
int threshold = (int)(_array.Length * 0.9);
if (_size < threshold)
{
var newArray = _pool.Rent(_size);
if (newArray.Length < _array.Length)
{
Array.Copy(_array, newArray, _size);
ReturnArray(replaceWith: newArray);
_version++;
}
else
{
// The array from the pool wasn't any smaller than the one we already had,
// (we can only control minimum size) so return it and do nothing.
// If we create an exact-sized array not from the pool, we'll
// get an exception when returning it to the pool.
_pool.Return(newArray);
}
}
}
/// <summary>
/// Returns the top object on the stack without removing it. If the stack
/// is empty, Peek throws an InvalidOperationException.
/// </summary>
public T Peek()
{
int size = _size - 1;
T[] array = _array;
if ((uint)size >= (uint)array.Length)
{
ThrowForEmptyStack();
}
return array[size];
}
public bool TryPeek(out T result)
{
int size = _size - 1;
T[] array = _array;
if ((uint)size >= (uint)array.Length)
{
result = default;
return false;
}
result = array[size];
return true;
}
/// <summary>
/// Pops an item from the top of the stack. If the stack is empty, Pop
/// throws an InvalidOperationException.
/// </summary>
public T Pop()
{
int size = _size - 1;
T[] array = _array;
// if (_size == 0) is equivalent to if (size == -1), and this case
// is covered with (uint)size, thus allowing bounds check elimination
// https://github.com/dotnet/coreclr/pull/9773
if ((uint)size >= (uint)array.Length)
{
ThrowForEmptyStack();
}
_version++;
_size = size;
T item = array[size];
if (_clearOnFree)
{
array[size] = default; // Free memory quicker.
}
return item;
}
public bool TryPop(out T result)
{
int size = _size - 1;
T[] array = _array;
if ((uint)size >= (uint)array.Length)
{
result = default;
return false;
}
_version++;
_size = size;
result = array[size];
if (_clearOnFree)
{
array[size] = default; // Free memory quicker.
}
return true;
}
/// <summary>
/// Pushes an item to the top of the stack.
/// </summary>
public void Push(T item)
{
int size = _size;
T[] array = _array;
if ((uint)size < (uint)array.Length)
{
array[size] = item;
_version++;
_size = size + 1;
}
else
{
PushWithResize(item);
}
}
// Non-inline from Stack.Push to improve its code quality as uncommon path
[MethodImpl(MethodImplOptions.NoInlining)]
private void PushWithResize(T item)
{
var newArray = _pool.Rent((_array.Length == 0) ? DefaultCapacity : 2 * _array.Length);
Array.Copy(_array, newArray, _size);
ReturnArray(replaceWith: newArray);
_array[_size] = item;
_version++;
_size++;
}
/// <summary>
/// Copies the Stack to an array, in the same order Pop would return the items.
/// </summary>
public T[] ToArray()
{
if (_size == 0)
return Array.Empty<T>();
T[] objArray = new T[_size];
int i = 0;
while (i < _size)
{
objArray[i] = _array[_size - i - 1];
i++;
}
return objArray;
}
private void ThrowForEmptyStack()
{
Debug.Assert(_size == 0);
throw new InvalidOperationException("Stack was empty.");
}
private void ReturnArray(T[] replaceWith = null)
{
if (_array?.Length > 0)
{
try
{
_pool.Return(_array, clearArray: _clearOnFree);
}
catch (ArgumentException)
{
// oh well, the array pool didn't like our array
}
}
if (!(replaceWith is null))
{
_array = replaceWith;
}
}
private static bool ShouldClear(ClearMode mode)
{
#if NETCOREAPP2_1
return mode == ClearMode.Always
|| (mode == ClearMode.Auto && RuntimeHelpers.IsReferenceOrContainsReferences<T>());
#else
return mode != ClearMode.Never;
#endif
}
public void Dispose()
{
ReturnArray(replaceWith: Array.Empty<T>());
_size = 0;
_version++;
}
void IDeserializationCallback.OnDeserialization(object sender)
{
// We can't serialize array pools, so deserialized PooledStacks will
// have to use the shared pool, even if they were using a custom pool
// before serialization.
_pool = ArrayPool<T>.Shared;
}
[SuppressMessage("Microsoft.Performance", "CA1815:OverrideEqualsAndOperatorEqualsOnValueTypes", Justification = "not an expected scenario")]
public struct Enumerator : IEnumerator<T>, IEnumerator
{
private readonly PooledStack<T> _stack;
private readonly int _version;
private int _index;
private T _currentElement;
internal Enumerator(PooledStack<T> stack)
{
_stack = stack;
_version = stack._version;
_index = -2;
_currentElement = default;
}
public void Dispose()
{
_index = -1;
}
public bool MoveNext()
{
bool retval;
if (_version != _stack._version)
throw new InvalidOperationException("Collection was modified during enumeration.");
if (_index == -2)
{ // First call to enumerator.
_index = _stack._size - 1;
retval = (_index >= 0);
if (retval)
_currentElement = _stack._array[_index];
return retval;
}
if (_index == -1)
{ // End of enumeration.
return false;
}
retval = (--_index >= 0);
if (retval)
_currentElement = _stack._array[_index];
else
_currentElement = default;
return retval;
}
public T Current
{
get
{
if (_index < 0)
ThrowEnumerationNotStartedOrEnded();
return _currentElement;
}
}
private void ThrowEnumerationNotStartedOrEnded()
{
Debug.Assert(_index == -1 || _index == -2);
throw new InvalidOperationException(_index == -2 ? "Enumeration was not started." : "Enumeration has ended.");
}
object IEnumerator.Current
{
get { return Current; }
}
void IEnumerator.Reset()
{
if (_version != _stack._version)
throw new InvalidOperationException("Collection was modified during enumeration.");
_index = -2;
_currentElement = default;
}
}
}
}

28
src/Avalonia.Base/Collections/Pooled/StackDebugView.cs

@ -0,0 +1,28 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System;
using System.Diagnostics;
namespace Avalonia.Collections.Pooled
{
internal sealed class StackDebugView<T>
{
private readonly PooledStack<T> _stack;
public StackDebugView(PooledStack<T> stack)
{
_stack = stack ?? throw new ArgumentNullException(nameof(stack));
}
[DebuggerBrowsable(DebuggerBrowsableState.RootHidden)]
public T[] Items
{
get
{
return _stack.ToArray();
}
}
}
}

691
src/Avalonia.Base/Collections/Pooled/ThrowHelper.cs

@ -0,0 +1,691 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
// This file defines an internal class used to throw exceptions in BCL code.
// The main purpose is to reduce code size.
//
// The old way to throw an exception generates quite a lot IL code and assembly code.
// Following is an example:
// C# source
// throw new ArgumentNullException(nameof(key), SR.ArgumentNull_Key);
// IL code:
// IL_0003: ldstr "key"
// IL_0008: ldstr "ArgumentNull_Key"
// IL_000d: call string System.Environment::GetResourceString(string)
// IL_0012: newobj instance void System.ArgumentNullException::.ctor(string,string)
// IL_0017: throw
// which is 21bytes in IL.
//
// So we want to get rid of the ldstr and call to Environment.GetResource in IL.
// In order to do that, I created two enums: ExceptionResource, ExceptionArgument to represent the
// argument name and resource name in a small integer. The source code will be changed to
// ThrowHelper.ThrowArgumentNullException(ExceptionArgument.key, ExceptionResource.ArgumentNull_Key);
//
// The IL code will be 7 bytes.
// IL_0008: ldc.i4.4
// IL_0009: ldc.i4.4
// IL_000a: call void System.ThrowHelper::ThrowArgumentNullException(valuetype System.ExceptionArgument)
// IL_000f: ldarg.0
//
// This will also reduce the Jitted code size a lot.
//
// It is very important we do this for generic classes because we can easily generate the same code
// multiple times for different instantiation.
//
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Runtime.CompilerServices;
using System.Runtime.Serialization;
namespace Avalonia.Collections.Pooled
{
internal static class ThrowHelper
{
internal static void ThrowArrayTypeMismatchException()
{
throw new ArrayTypeMismatchException();
}
internal static void ThrowIndexOutOfRangeException()
{
throw new IndexOutOfRangeException();
}
internal static void ThrowArgumentOutOfRangeException()
{
throw new ArgumentOutOfRangeException();
}
internal static void ThrowArgumentException_DestinationTooShort()
{
throw new ArgumentException("Destination too short.");
}
internal static void ThrowArgumentException_OverlapAlignmentMismatch()
{
throw new ArgumentException("Overlap alignment mismatch.");
}
internal static void ThrowArgumentOutOfRange_IndexException()
{
throw GetArgumentOutOfRangeException(ExceptionArgument.index,
ExceptionResource.ArgumentOutOfRange_Index);
}
internal static void ThrowIndexArgumentOutOfRange_NeedNonNegNumException()
{
throw GetArgumentOutOfRangeException(ExceptionArgument.index,
ExceptionResource.ArgumentOutOfRange_NeedNonNegNum);
}
internal static void ThrowValueArgumentOutOfRange_NeedNonNegNumException()
{
throw GetArgumentOutOfRangeException(ExceptionArgument.value,
ExceptionResource.ArgumentOutOfRange_NeedNonNegNum);
}
internal static void ThrowLengthArgumentOutOfRange_ArgumentOutOfRange_NeedNonNegNum()
{
throw GetArgumentOutOfRangeException(ExceptionArgument.length,
ExceptionResource.ArgumentOutOfRange_NeedNonNegNum);
}
internal static void ThrowStartIndexArgumentOutOfRange_ArgumentOutOfRange_Index()
{
throw GetArgumentOutOfRangeException(ExceptionArgument.startIndex,
ExceptionResource.ArgumentOutOfRange_Index);
}
internal static void ThrowCountArgumentOutOfRange_ArgumentOutOfRange_Count()
{
throw GetArgumentOutOfRangeException(ExceptionArgument.count,
ExceptionResource.ArgumentOutOfRange_Count);
}
internal static void ThrowWrongKeyTypeArgumentException<T>(T key, Type targetType)
{
// Generic key to move the boxing to the right hand side of throw
throw GetWrongKeyTypeArgumentException((object)key, targetType);
}
internal static void ThrowWrongValueTypeArgumentException<T>(T value, Type targetType)
{
// Generic key to move the boxing to the right hand side of throw
throw GetWrongValueTypeArgumentException((object)value, targetType);
}
private static ArgumentException GetAddingDuplicateWithKeyArgumentException(object key)
{
return new ArgumentException($"Error adding duplicate with key: {key}.");
}
internal static void ThrowAddingDuplicateWithKeyArgumentException<T>(T key)
{
// Generic key to move the boxing to the right hand side of throw
throw GetAddingDuplicateWithKeyArgumentException((object)key);
}
internal static void ThrowKeyNotFoundException<T>(T key)
{
// Generic key to move the boxing to the right hand side of throw
throw GetKeyNotFoundException((object)key);
}
internal static void ThrowArgumentException(ExceptionResource resource)
{
throw GetArgumentException(resource);
}
internal static void ThrowArgumentException(ExceptionResource resource, ExceptionArgument argument)
{
throw GetArgumentException(resource, argument);
}
private static ArgumentNullException GetArgumentNullException(ExceptionArgument argument)
{
return new ArgumentNullException(GetArgumentName(argument));
}
internal static void ThrowArgumentNullException(ExceptionArgument argument)
{
throw GetArgumentNullException(argument);
}
internal static void ThrowArgumentNullException(ExceptionResource resource)
{
throw new ArgumentNullException(GetResourceString(resource));
}
internal static void ThrowArgumentNullException(ExceptionArgument argument, ExceptionResource resource)
{
throw new ArgumentNullException(GetArgumentName(argument), GetResourceString(resource));
}
internal static void ThrowArgumentOutOfRangeException(ExceptionArgument argument)
{
throw new ArgumentOutOfRangeException(GetArgumentName(argument));
}
internal static void ThrowArgumentOutOfRangeException(ExceptionArgument argument, ExceptionResource resource)
{
throw GetArgumentOutOfRangeException(argument, resource);
}
internal static void ThrowArgumentOutOfRangeException(ExceptionArgument argument, int paramNumber, ExceptionResource resource)
{
throw GetArgumentOutOfRangeException(argument, paramNumber, resource);
}
internal static void ThrowInvalidOperationException(ExceptionResource resource)
{
throw GetInvalidOperationException(resource);
}
internal static void ThrowInvalidOperationException(ExceptionResource resource, Exception e)
{
throw new InvalidOperationException(GetResourceString(resource), e);
}
internal static void ThrowSerializationException(ExceptionResource resource)
{
throw new SerializationException(GetResourceString(resource));
}
internal static void ThrowSecurityException(ExceptionResource resource)
{
throw new System.Security.SecurityException(GetResourceString(resource));
}
internal static void ThrowRankException(ExceptionResource resource)
{
throw new RankException(GetResourceString(resource));
}
internal static void ThrowNotSupportedException(ExceptionResource resource)
{
throw new NotSupportedException(GetResourceString(resource));
}
internal static void ThrowUnauthorizedAccessException(ExceptionResource resource)
{
throw new UnauthorizedAccessException(GetResourceString(resource));
}
internal static void ThrowObjectDisposedException(string objectName, ExceptionResource resource)
{
throw new ObjectDisposedException(objectName, GetResourceString(resource));
}
internal static void ThrowObjectDisposedException(ExceptionResource resource)
{
throw new ObjectDisposedException(null, GetResourceString(resource));
}
internal static void ThrowNotSupportedException()
{
throw new NotSupportedException();
}
internal static void ThrowAggregateException(List<Exception> exceptions)
{
throw new AggregateException(exceptions);
}
internal static void ThrowOutOfMemoryException()
{
throw new OutOfMemoryException();
}
internal static void ThrowArgumentException_Argument_InvalidArrayType()
{
throw new ArgumentException("Invalid array type.");
}
internal static void ThrowInvalidOperationException_InvalidOperation_EnumNotStarted()
{
throw new InvalidOperationException("Enumeration has not started.");
}
internal static void ThrowInvalidOperationException_InvalidOperation_EnumEnded()
{
throw new InvalidOperationException("Enumeration has ended.");
}
internal static void ThrowInvalidOperationException_EnumCurrent(int index)
{
throw GetInvalidOperationException_EnumCurrent(index);
}
internal static void ThrowInvalidOperationException_InvalidOperation_EnumFailedVersion()
{
throw new InvalidOperationException("Collection was modified during enumeration.");
}
internal static void ThrowInvalidOperationException_InvalidOperation_EnumOpCantHappen()
{
throw new InvalidOperationException("Invalid enumerator state: enumeration cannot proceed.");
}
internal static void ThrowInvalidOperationException_InvalidOperation_NoValue()
{
throw new InvalidOperationException("No value provided.");
}
internal static void ThrowInvalidOperationException_ConcurrentOperationsNotSupported()
{
throw new InvalidOperationException("Concurrent operations are not supported.");
}
internal static void ThrowInvalidOperationException_HandleIsNotInitialized()
{
throw new InvalidOperationException("Handle is not initialized.");
}
internal static void ThrowFormatException_BadFormatSpecifier()
{
throw new FormatException("Bad format specifier.");
}
private static ArgumentException GetArgumentException(ExceptionResource resource)
{
return new ArgumentException(GetResourceString(resource));
}
private static InvalidOperationException GetInvalidOperationException(ExceptionResource resource)
{
return new InvalidOperationException(GetResourceString(resource));
}
private static ArgumentException GetWrongKeyTypeArgumentException(object key, Type targetType)
{
return new ArgumentException($"Wrong key type. Expected {targetType}, got: '{key}'.", nameof(key));
}
private static ArgumentException GetWrongValueTypeArgumentException(object value, Type targetType)
{
return new ArgumentException($"Wrong value type. Expected {targetType}, got: '{value}'.", nameof(value));
}
private static KeyNotFoundException GetKeyNotFoundException(object key)
{
return new KeyNotFoundException($"Key not found: {key}");
}
private static ArgumentOutOfRangeException GetArgumentOutOfRangeException(ExceptionArgument argument, ExceptionResource resource)
{
return new ArgumentOutOfRangeException(GetArgumentName(argument), GetResourceString(resource));
}
private static ArgumentException GetArgumentException(ExceptionResource resource, ExceptionArgument argument)
{
return new ArgumentException(GetResourceString(resource), GetArgumentName(argument));
}
private static ArgumentOutOfRangeException GetArgumentOutOfRangeException(ExceptionArgument argument, int paramNumber, ExceptionResource resource)
{
return new ArgumentOutOfRangeException(GetArgumentName(argument) + "[" + paramNumber.ToString() + "]", GetResourceString(resource));
}
private static InvalidOperationException GetInvalidOperationException_EnumCurrent(int index)
{
return new InvalidOperationException(
index < 0 ?
"Enumeration has not started" :
"Enumeration has ended");
}
// Allow nulls for reference types and Nullable<U>, but not for value types.
// Aggressively inline so the jit evaluates the if in place and either drops the call altogether
// Or just leaves null test and call to the Non-returning ThrowHelper.ThrowArgumentNullException
[MethodImpl(MethodImplOptions.AggressiveInlining)]
internal static void IfNullAndNullsAreIllegalThenThrow<T>(object value, ExceptionArgument argName)
{
// Note that default(T) is not equal to null for value types except when T is Nullable<U>.
if (!(default(T) == null) && value == null)
ThrowHelper.ThrowArgumentNullException(argName);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
internal static void ThrowForUnsupportedVectorBaseType<T>() where T : struct
{
if (typeof(T) != typeof(byte) && typeof(T) != typeof(sbyte) &&
typeof(T) != typeof(short) && typeof(T) != typeof(ushort) &&
typeof(T) != typeof(int) && typeof(T) != typeof(uint) &&
typeof(T) != typeof(long) && typeof(T) != typeof(ulong) &&
typeof(T) != typeof(float) && typeof(T) != typeof(double))
{
ThrowNotSupportedException(ExceptionResource.Arg_TypeNotSupported);
}
}
#if false // Reflection-based implementation does not work for CoreRT/ProjectN
// This function will convert an ExceptionArgument enum value to the argument name string.
[MethodImpl(MethodImplOptions.NoInlining)]
private static string GetArgumentName(ExceptionArgument argument)
{
Debug.Assert(Enum.IsDefined(typeof(ExceptionArgument), argument),
"The enum value is not defined, please check the ExceptionArgument Enum.");
return argument.ToString();
}
#endif
private static string GetArgumentName(ExceptionArgument argument)
{
switch (argument)
{
case ExceptionArgument.obj:
return "obj";
case ExceptionArgument.dictionary:
return "dictionary";
case ExceptionArgument.array:
return "array";
case ExceptionArgument.info:
return "info";
case ExceptionArgument.key:
return "key";
case ExceptionArgument.text:
return "text";
case ExceptionArgument.values:
return "values";
case ExceptionArgument.value:
return "value";
case ExceptionArgument.startIndex:
return "startIndex";
case ExceptionArgument.task:
return "task";
case ExceptionArgument.ch:
return "ch";
case ExceptionArgument.s:
return "s";
case ExceptionArgument.input:
return "input";
case ExceptionArgument.list:
return "list";
case ExceptionArgument.index:
return "index";
case ExceptionArgument.capacity:
return "capacity";
case ExceptionArgument.collection:
return "collection";
case ExceptionArgument.item:
return "item";
case ExceptionArgument.converter:
return "converter";
case ExceptionArgument.match:
return "match";
case ExceptionArgument.count:
return "count";
case ExceptionArgument.action:
return "action";
case ExceptionArgument.comparison:
return "comparison";
case ExceptionArgument.exceptions:
return "exceptions";
case ExceptionArgument.exception:
return "exception";
case ExceptionArgument.enumerable:
return "enumerable";
case ExceptionArgument.start:
return "start";
case ExceptionArgument.format:
return "format";
case ExceptionArgument.culture:
return "culture";
case ExceptionArgument.comparer:
return "comparer";
case ExceptionArgument.comparable:
return "comparable";
case ExceptionArgument.source:
return "source";
case ExceptionArgument.state:
return "state";
case ExceptionArgument.length:
return "length";
case ExceptionArgument.comparisonType:
return "comparisonType";
case ExceptionArgument.manager:
return "manager";
case ExceptionArgument.sourceBytesToCopy:
return "sourceBytesToCopy";
case ExceptionArgument.callBack:
return "callBack";
case ExceptionArgument.creationOptions:
return "creationOptions";
case ExceptionArgument.function:
return "function";
case ExceptionArgument.delay:
return "delay";
case ExceptionArgument.millisecondsDelay:
return "millisecondsDelay";
case ExceptionArgument.millisecondsTimeout:
return "millisecondsTimeout";
case ExceptionArgument.timeout:
return "timeout";
case ExceptionArgument.type:
return "type";
case ExceptionArgument.sourceIndex:
return "sourceIndex";
case ExceptionArgument.sourceArray:
return "sourceArray";
case ExceptionArgument.destinationIndex:
return "destinationIndex";
case ExceptionArgument.destinationArray:
return "destinationArray";
case ExceptionArgument.other:
return "other";
case ExceptionArgument.newSize:
return "newSize";
case ExceptionArgument.lowerBounds:
return "lowerBounds";
case ExceptionArgument.lengths:
return "lengths";
case ExceptionArgument.len:
return "len";
case ExceptionArgument.keys:
return "keys";
case ExceptionArgument.indices:
return "indices";
case ExceptionArgument.endIndex:
return "endIndex";
case ExceptionArgument.elementType:
return "elementType";
case ExceptionArgument.arrayIndex:
return "arrayIndex";
default:
Debug.Fail("The enum value is not defined, please check the ExceptionArgument Enum.");
return argument.ToString();
}
}
#if false // Reflection-based implementation does not work for CoreRT/ProjectN
// This function will convert an ExceptionResource enum value to the resource string.
[MethodImpl(MethodImplOptions.NoInlining)]
private static string GetResourceString(ExceptionResource resource)
{
Debug.Assert(Enum.IsDefined(typeof(ExceptionResource), resource),
"The enum value is not defined, please check the ExceptionResource Enum.");
return SR.GetResourceString(resource.ToString());
}
#endif
private static string GetResourceString(ExceptionResource resource)
{
switch (resource)
{
case ExceptionResource.ArgumentOutOfRange_Index:
return "Argument 'index' was out of the range of valid values.";
case ExceptionResource.ArgumentOutOfRange_Count:
return "Argument 'count' was out of the range of valid values.";
case ExceptionResource.Arg_ArrayPlusOffTooSmall:
return "Array plus offset too small.";
case ExceptionResource.NotSupported_ReadOnlyCollection:
return "This operation is not supported on a read-only collection.";
case ExceptionResource.Arg_RankMultiDimNotSupported:
return "Multi-dimensional arrays are not supported.";
case ExceptionResource.Arg_NonZeroLowerBound:
return "Arrays with a non-zero lower bound are not supported.";
case ExceptionResource.ArgumentOutOfRange_ListInsert:
return "Insertion index was out of the range of valid values.";
case ExceptionResource.ArgumentOutOfRange_NeedNonNegNum:
return "The number must be non-negative.";
case ExceptionResource.ArgumentOutOfRange_SmallCapacity:
return "The capacity cannot be set below the current Count.";
case ExceptionResource.Argument_InvalidOffLen:
return "Invalid offset length.";
case ExceptionResource.ArgumentOutOfRange_BiggerThanCollection:
return "The given value was larger than the size of the collection.";
case ExceptionResource.Serialization_MissingKeys:
return "Serialization error: missing keys.";
case ExceptionResource.Serialization_NullKey:
return "Serialization error: null key.";
case ExceptionResource.NotSupported_KeyCollectionSet:
return "The KeyCollection does not support modification.";
case ExceptionResource.NotSupported_ValueCollectionSet:
return "The ValueCollection does not support modification.";
case ExceptionResource.InvalidOperation_NullArray:
return "Null arrays are not supported.";
case ExceptionResource.InvalidOperation_HSCapacityOverflow:
return "Set hash capacity overflow. Cannot increase size.";
case ExceptionResource.NotSupported_StringComparison:
return "String comparison not supported.";
case ExceptionResource.ConcurrentCollection_SyncRoot_NotSupported:
return "SyncRoot not supported.";
case ExceptionResource.ArgumentException_OtherNotArrayOfCorrectLength:
return "The other array is not of the correct length.";
case ExceptionResource.ArgumentOutOfRange_EndIndexStartIndex:
return "The end index does not come after the start index.";
case ExceptionResource.ArgumentOutOfRange_HugeArrayNotSupported:
return "Huge arrays are not supported.";
case ExceptionResource.Argument_AddingDuplicate:
return "Duplicate item added.";
case ExceptionResource.Argument_InvalidArgumentForComparison:
return "Invalid argument for comparison.";
case ExceptionResource.Arg_LowerBoundsMustMatch:
return "Array lower bounds must match.";
case ExceptionResource.Arg_MustBeType:
return "Argument must be of type: ";
case ExceptionResource.InvalidOperation_IComparerFailed:
return "IComparer failed.";
case ExceptionResource.NotSupported_FixedSizeCollection:
return "This operation is not suppored on a fixed-size collection.";
case ExceptionResource.Rank_MultiDimNotSupported:
return "Multi-dimensional arrays are not supported.";
case ExceptionResource.Arg_TypeNotSupported:
return "Type not supported.";
default:
Debug.Assert(false,
"The enum value is not defined, please check the ExceptionResource Enum.");
return resource.ToString();
}
}
}
//
// The convention for this enum is using the argument name as the enum name
//
internal enum ExceptionArgument
{
obj,
dictionary,
array,
info,
key,
text,
values,
value,
startIndex,
task,
ch,
s,
input,
list,
index,
capacity,
collection,
item,
converter,
match,
count,
action,
comparison,
exceptions,
exception,
enumerable,
start,
format,
culture,
comparer,
comparable,
source,
state,
length,
comparisonType,
manager,
sourceBytesToCopy,
callBack,
creationOptions,
function,
delay,
millisecondsDelay,
millisecondsTimeout,
timeout,
type,
sourceIndex,
sourceArray,
destinationIndex,
destinationArray,
other,
newSize,
lowerBounds,
lengths,
len,
keys,
indices,
endIndex,
elementType,
arrayIndex
}
//
// The convention for this enum is using the resource name as the enum name
//
internal enum ExceptionResource
{
ArgumentOutOfRange_Index,
ArgumentOutOfRange_Count,
Arg_ArrayPlusOffTooSmall,
NotSupported_ReadOnlyCollection,
Arg_RankMultiDimNotSupported,
Arg_NonZeroLowerBound,
ArgumentOutOfRange_ListInsert,
ArgumentOutOfRange_NeedNonNegNum,
ArgumentOutOfRange_SmallCapacity,
Argument_InvalidOffLen,
ArgumentOutOfRange_BiggerThanCollection,
Serialization_MissingKeys,
Serialization_NullKey,
NotSupported_KeyCollectionSet,
NotSupported_ValueCollectionSet,
InvalidOperation_NullArray,
InvalidOperation_HSCapacityOverflow,
NotSupported_StringComparison,
ConcurrentCollection_SyncRoot_NotSupported,
ArgumentException_OtherNotArrayOfCorrectLength,
ArgumentOutOfRange_EndIndexStartIndex,
ArgumentOutOfRange_HugeArrayNotSupported,
Argument_AddingDuplicate,
Argument_InvalidArgumentForComparison,
Arg_LowerBoundsMustMatch,
Arg_MustBeType,
InvalidOperation_IComparerFailed,
NotSupported_FixedSizeCollection,
Rank_MultiDimNotSupported,
Arg_TypeNotSupported,
}
}

6
src/Avalonia.Base/Data/BindingNotification.cs

@ -30,6 +30,12 @@ namespace Avalonia.Data
/// Represents a binding notification that can be a valid binding value, or a binding or /// Represents a binding notification that can be a valid binding value, or a binding or
/// data validation error. /// data validation error.
/// </summary> /// </summary>
/// <remarks>
/// This class is very similar to <see cref="BindingValue{T}"/>, but where <see cref="BindingValue{T}"/>
/// is used by typed bindings, this class is used to hold binding and data validation errors in
/// untyped bindings. As Avalonia moves towards using typed bindings by default we may want to remove
/// this class.
/// </remarks>
public class BindingNotification public class BindingNotification
{ {
/// <summary> /// <summary>

20
src/Avalonia.Base/Data/BindingOperations.cs

@ -2,7 +2,6 @@
// Licensed under the MIT license. See licence.md file in the project root for full license information. // Licensed under the MIT license. See licence.md file in the project root for full license information.
using System; using System;
using System.Linq;
using System.Reactive.Disposables; using System.Reactive.Disposables;
using System.Reactive.Linq; using System.Reactive.Linq;
using Avalonia.Reactive; using Avalonia.Reactive;
@ -57,22 +56,37 @@ namespace Avalonia.Data
if (source != null) if (source != null)
{ {
// Perf: Avoid allocating closure in the outer scope.
var targetCopy = target;
var propertyCopy = property;
var bindingCopy = binding;
return source return source
.Where(x => BindingNotification.ExtractValue(x) != AvaloniaProperty.UnsetValue) .Where(x => BindingNotification.ExtractValue(x) != AvaloniaProperty.UnsetValue)
.Take(1) .Take(1)
.Subscribe(x => target.SetValue(property, x, binding.Priority)); .Subscribe(x => targetCopy.SetValue(
propertyCopy,
BindingNotification.ExtractValue(x),
bindingCopy.Priority));
} }
else else
{ {
target.SetValue(property, binding.Value, binding.Priority); target.SetValue(property, binding.Value, binding.Priority);
return Disposable.Empty; return Disposable.Empty;
} }
case BindingMode.OneWayToSource: case BindingMode.OneWayToSource:
{
// Perf: Avoid allocating closure in the outer scope.
var bindingCopy = binding;
return Observable.CombineLatest( return Observable.CombineLatest(
binding.Observable, binding.Observable,
target.GetObservable(property), target.GetObservable(property),
(_, v) => v) (_, v) => v)
.Subscribe(x => binding.Subject.OnNext(x)); .Subscribe(x => bindingCopy.Subject.OnNext(x));
}
default: default:
throw new ArgumentException("Invalid binding mode."); throw new ArgumentException("Invalid binding mode.");
} }

35
src/Avalonia.Base/Data/BindingValue.cs

@ -8,6 +8,7 @@ namespace Avalonia.Data
/// <summary> /// <summary>
/// Describes the type of a <see cref="BindingValue{T}"/>. /// Describes the type of a <see cref="BindingValue{T}"/>.
/// </summary> /// </summary>
[Flags]
public enum BindingValueType public enum BindingValueType
{ {
/// <summary> /// <summary>
@ -135,10 +136,10 @@ namespace Avalonia.Data
/// Converts the binding value to an <see cref="Optional{T}"/>. /// Converts the binding value to an <see cref="Optional{T}"/>.
/// </summary> /// </summary>
/// <returns></returns> /// <returns></returns>
public Optional<T> ToOptional() => HasValue ? new Optional<T>(Value) : default; public Optional<T> ToOptional() => HasValue ? new Optional<T>(_value) : default;
/// <inheritdoc/> /// <inheritdoc/>
public override string ToString() => HasError ? $"Error: {Error!.Message}" : Value?.ToString() ?? "(null)"; public override string ToString() => HasError ? $"Error: {Error!.Message}" : _value?.ToString() ?? "(null)";
/// <summary> /// <summary>
/// Converts the value to untyped representation, using <see cref="AvaloniaProperty.UnsetValue"/>, /// Converts the value to untyped representation, using <see cref="AvaloniaProperty.UnsetValue"/>,
@ -152,7 +153,7 @@ namespace Avalonia.Data
{ {
BindingValueType.UnsetValue => AvaloniaProperty.UnsetValue, BindingValueType.UnsetValue => AvaloniaProperty.UnsetValue,
BindingValueType.DoNothing => BindingOperations.DoNothing, BindingValueType.DoNothing => BindingOperations.DoNothing,
BindingValueType.Value => Value, BindingValueType.Value => _value,
BindingValueType.BindingError => BindingValueType.BindingError =>
new BindingNotification(Error, BindingErrorType.Error), new BindingNotification(Error, BindingErrorType.Error),
BindingValueType.BindingErrorWithFallback => BindingValueType.BindingErrorWithFallback =>
@ -161,7 +162,7 @@ namespace Avalonia.Data
new BindingNotification(Error, BindingErrorType.DataValidationError), new BindingNotification(Error, BindingErrorType.DataValidationError),
BindingValueType.DataValidationErrorWithFallback => BindingValueType.DataValidationErrorWithFallback =>
new BindingNotification(Error, BindingErrorType.DataValidationError, Value), new BindingNotification(Error, BindingErrorType.DataValidationError, Value),
_ => throw new NotSupportedException("Invalida BindingValueType."), _ => throw new NotSupportedException("Invalid BindingValueType."),
}; };
} }
@ -185,12 +186,32 @@ namespace Avalonia.Data
return new BindingValue<T>(type | BindingValueType.HasValue, value, Error); return new BindingValue<T>(type | BindingValueType.HasValue, value, Error);
} }
/// <summary>
/// Gets the value of the binding value if present, otherwise the default value.
/// </summary>
/// <returns>The value.</returns>
public T GetValueOrDefault() => HasValue ? _value : default;
/// <summary> /// <summary>
/// Gets the value of the binding value if present, otherwise a default value. /// Gets the value of the binding value if present, otherwise a default value.
/// </summary> /// </summary>
/// <param name="defaultValue">The default value.</param> /// <param name="defaultValue">The default value.</param>
/// <returns>The value.</returns> /// <returns>The value.</returns>
public T ValueOrDefault(T defaultValue = default) => HasValue ? Value : defaultValue; public T GetValueOrDefault(T defaultValue) => HasValue ? _value : defaultValue;
/// <summary>
/// Gets the value if present, otherwise the default value.
/// </summary>
/// <returns>
/// The value if present and of the correct type, `default(TResult)` if the value is
/// not present or of an incorrect type.
/// </returns>
public TResult GetValueOrDefault<TResult>()
{
return HasValue ?
_value is TResult result ? result : default
: default;
}
/// <summary> /// <summary>
/// Gets the value of the binding value if present, otherwise a default value. /// Gets the value of the binding value if present, otherwise a default value.
@ -201,10 +222,10 @@ namespace Avalonia.Data
/// present but not of the correct type or null, or <paramref name="defaultValue"/> if the /// present but not of the correct type or null, or <paramref name="defaultValue"/> if the
/// value is not present. /// value is not present.
/// </returns> /// </returns>
public TResult ValueOrDefault<TResult>(TResult defaultValue = default) public TResult GetValueOrDefault<TResult>(TResult defaultValue)
{ {
return HasValue ? return HasValue ?
Value is TResult result ? result : default _value is TResult result ? result : default
: defaultValue; : defaultValue;
} }

13
src/Avalonia.Base/Data/Core/BindingExpression.cs

@ -21,6 +21,7 @@ namespace Avalonia.Data.Core
private readonly ExpressionObserver _inner; private readonly ExpressionObserver _inner;
private readonly Type _targetType; private readonly Type _targetType;
private readonly object _fallbackValue; private readonly object _fallbackValue;
private readonly object _targetNullValue;
private readonly BindingPriority _priority; private readonly BindingPriority _priority;
InnerListener _innerListener; InnerListener _innerListener;
WeakReference<object> _value; WeakReference<object> _value;
@ -51,7 +52,7 @@ namespace Avalonia.Data.Core
IValueConverter converter, IValueConverter converter,
object converterParameter = null, object converterParameter = null,
BindingPriority priority = BindingPriority.LocalValue) BindingPriority priority = BindingPriority.LocalValue)
: this(inner, targetType, AvaloniaProperty.UnsetValue, converter, converterParameter, priority) : this(inner, targetType, AvaloniaProperty.UnsetValue, AvaloniaProperty.UnsetValue, converter, converterParameter, priority)
{ {
} }
@ -63,6 +64,9 @@ namespace Avalonia.Data.Core
/// <param name="fallbackValue"> /// <param name="fallbackValue">
/// The value to use when the binding is unable to produce a value. /// The value to use when the binding is unable to produce a value.
/// </param> /// </param>
/// <param name="targetNullValue">
/// The value to use when the binding result is null.
/// </param>
/// <param name="converter">The value converter to use.</param> /// <param name="converter">The value converter to use.</param>
/// <param name="converterParameter"> /// <param name="converterParameter">
/// A parameter to pass to <paramref name="converter"/>. /// A parameter to pass to <paramref name="converter"/>.
@ -72,6 +76,7 @@ namespace Avalonia.Data.Core
ExpressionObserver inner, ExpressionObserver inner,
Type targetType, Type targetType,
object fallbackValue, object fallbackValue,
object targetNullValue,
IValueConverter converter, IValueConverter converter,
object converterParameter = null, object converterParameter = null,
BindingPriority priority = BindingPriority.LocalValue) BindingPriority priority = BindingPriority.LocalValue)
@ -85,6 +90,7 @@ namespace Avalonia.Data.Core
Converter = converter; Converter = converter;
ConverterParameter = converterParameter; ConverterParameter = converterParameter;
_fallbackValue = fallbackValue; _fallbackValue = fallbackValue;
_targetNullValue = targetNullValue;
_priority = priority; _priority = priority;
} }
@ -196,6 +202,11 @@ namespace Avalonia.Data.Core
/// <inheritdoc/> /// <inheritdoc/>
private object ConvertValue(object value) private object ConvertValue(object value)
{ {
if (value == null && _targetNullValue != AvaloniaProperty.UnsetValue)
{
return _targetNullValue;
}
if (value == BindingOperations.DoNothing) if (value == BindingOperations.DoNothing)
{ {
return value; return value;

6
src/Avalonia.Base/Data/Core/ExpressionObserver.cs

@ -21,7 +21,7 @@ namespace Avalonia.Data.Core
/// An ordered collection of property accessor plugins that can be used to customize /// An ordered collection of property accessor plugins that can be used to customize
/// the reading and subscription of property values on a type. /// the reading and subscription of property values on a type.
/// </summary> /// </summary>
public static readonly IList<IPropertyAccessorPlugin> PropertyAccessors = public static readonly List<IPropertyAccessorPlugin> PropertyAccessors =
new List<IPropertyAccessorPlugin> new List<IPropertyAccessorPlugin>
{ {
new AvaloniaPropertyAccessorPlugin(), new AvaloniaPropertyAccessorPlugin(),
@ -33,7 +33,7 @@ namespace Avalonia.Data.Core
/// An ordered collection of validation checker plugins that can be used to customize /// An ordered collection of validation checker plugins that can be used to customize
/// the validation of view model and model data. /// the validation of view model and model data.
/// </summary> /// </summary>
public static readonly IList<IDataValidationPlugin> DataValidators = public static readonly List<IDataValidationPlugin> DataValidators =
new List<IDataValidationPlugin> new List<IDataValidationPlugin>
{ {
new DataAnnotationsValidationPlugin(), new DataAnnotationsValidationPlugin(),
@ -45,7 +45,7 @@ namespace Avalonia.Data.Core
/// An ordered collection of stream plugins that can be used to customize the behavior /// An ordered collection of stream plugins that can be used to customize the behavior
/// of the '^' stream binding operator. /// of the '^' stream binding operator.
/// </summary> /// </summary>
public static readonly IList<IStreamPlugin> StreamHandlers = public static readonly List<IStreamPlugin> StreamHandlers =
new List<IStreamPlugin> new List<IStreamPlugin>
{ {
new TaskStreamPlugin(), new TaskStreamPlugin(),

20
src/Avalonia.Base/Data/Core/Plugins/AvaloniaPropertyAccessorPlugin.cs

@ -2,7 +2,7 @@
// Licensed under the MIT license. See licence.md file in the project root for full license information. // Licensed under the MIT license. See licence.md file in the project root for full license information.
using System; using System;
using System.Reactive.Linq; using System.Runtime.ExceptionServices;
namespace Avalonia.Data.Core.Plugins namespace Avalonia.Data.Core.Plugins
{ {
@ -76,7 +76,7 @@ namespace Avalonia.Data.Core.Plugins
return false; return false;
} }
private class Accessor : PropertyAccessorBase private class Accessor : PropertyAccessorBase, IObserver<object>
{ {
private readonly WeakReference<AvaloniaObject> _reference; private readonly WeakReference<AvaloniaObject> _reference;
private readonly AvaloniaProperty _property; private readonly AvaloniaProperty _property;
@ -117,7 +117,7 @@ namespace Avalonia.Data.Core.Plugins
protected override void SubscribeCore() protected override void SubscribeCore()
{ {
_subscription = Instance?.GetObservable(_property).Subscribe(PublishValue); _subscription = Instance?.GetObservable(_property).Subscribe(this);
} }
protected override void UnsubscribeCore() protected override void UnsubscribeCore()
@ -125,6 +125,20 @@ namespace Avalonia.Data.Core.Plugins
_subscription?.Dispose(); _subscription?.Dispose();
_subscription = null; _subscription = null;
} }
void IObserver<object>.OnCompleted()
{
}
void IObserver<object>.OnError(Exception error)
{
ExceptionDispatchInfo.Capture(error).Throw();
}
void IObserver<object>.OnNext(object value)
{
PublishValue(value);
}
} }
} }
} }

14
src/Avalonia.Base/Data/Core/PropertyAccessorNode.cs

@ -2,8 +2,6 @@
// Licensed under the MIT license. See licence.md file in the project root for full license information. // Licensed under the MIT license. See licence.md file in the project root for full license information.
using System; using System;
using System.Linq;
using System.Reactive.Linq;
using Avalonia.Data.Core.Plugins; using Avalonia.Data.Core.Plugins;
namespace Avalonia.Data.Core namespace Avalonia.Data.Core
@ -41,7 +39,17 @@ namespace Avalonia.Data.Core
{ {
reference.TryGetTarget(out object target); reference.TryGetTarget(out object target);
var plugin = ExpressionObserver.PropertyAccessors.FirstOrDefault(x => x.Match(target, PropertyName)); IPropertyAccessorPlugin plugin = null;
foreach (IPropertyAccessorPlugin x in ExpressionObserver.PropertyAccessors)
{
if (x.Match(target, PropertyName))
{
plugin = x;
break;
}
}
var accessor = plugin?.Start(reference, PropertyName); var accessor = plugin?.Start(reference, PropertyName);
if (_enableValidation && Next == null) if (_enableValidation && Next == null)

32
src/Avalonia.Base/Data/Optional.cs

@ -54,23 +54,43 @@ namespace Avalonia.Data
public bool Equals(Optional<T> other) => this == other; public bool Equals(Optional<T> other) => this == other;
/// <inheritdoc/> /// <inheritdoc/>
public override int GetHashCode() => HasValue ? Value!.GetHashCode() : 0; public override int GetHashCode() => HasValue ? _value?.GetHashCode() ?? 0 : 0;
/// <summary> /// <summary>
/// Casts the value (if any) to an <see cref="object"/>. /// Casts the value (if any) to an <see cref="object"/>.
/// </summary> /// </summary>
/// <returns>The cast optional value.</returns> /// <returns>The cast optional value.</returns>
public Optional<object> ToObject() => HasValue ? new Optional<object>(Value) : default; public Optional<object> ToObject() => HasValue ? new Optional<object>(_value) : default;
/// <inheritdoc/> /// <inheritdoc/>
public override string ToString() => HasValue ? Value?.ToString() ?? "(null)" : "(empty)"; public override string ToString() => HasValue ? _value?.ToString() ?? "(null)" : "(empty)";
/// <summary>
/// Gets the value if present, otherwise the default value.
/// </summary>
/// <returns>The value.</returns>
public T GetValueOrDefault() => HasValue ? _value : default;
/// <summary> /// <summary>
/// Gets the value if present, otherwise a default value. /// Gets the value if present, otherwise a default value.
/// </summary> /// </summary>
/// <param name="defaultValue">The default value.</param> /// <param name="defaultValue">The default value.</param>
/// <returns>The value.</returns> /// <returns>The value.</returns>
public T ValueOrDefault(T defaultValue = default) => HasValue ? Value : defaultValue; public T GetValueOrDefault(T defaultValue) => HasValue ? _value : defaultValue;
/// <summary>
/// Gets the value if present, otherwise the default value.
/// </summary>
/// <returns>
/// The value if present and of the correct type, `default(TResult)` if the value is
/// not present or of an incorrect type.
/// </returns>
public TResult GetValueOrDefault<TResult>()
{
return HasValue ?
_value is TResult result ? result : default
: default;
}
/// <summary> /// <summary>
/// Gets the value if present, otherwise a default value. /// Gets the value if present, otherwise a default value.
@ -81,10 +101,10 @@ namespace Avalonia.Data
/// present but not of the correct type or null, or <paramref name="defaultValue"/> if the /// present but not of the correct type or null, or <paramref name="defaultValue"/> if the
/// value is not present. /// value is not present.
/// </returns> /// </returns>
public TResult ValueOrDefault<TResult>(TResult defaultValue = default) public TResult GetValueOrDefault<TResult>(TResult defaultValue)
{ {
return HasValue ? return HasValue ?
Value is TResult result ? result : default _value is TResult result ? result : default
: defaultValue; : defaultValue;
} }

2
src/Avalonia.Base/IAvaloniaObject.cs

@ -118,7 +118,7 @@ namespace Avalonia
/// </summary> /// </summary>
/// <param name="child">The inheritance child.</param> /// <param name="child">The inheritance child.</param>
/// <remarks> /// <remarks>
/// Inheritance children will recieve a call to /// Inheritance children will receive a call to
/// <see cref="InheritedPropertyChanged{T}(AvaloniaProperty{T}, Optional{T}, Optional{T})"/> /// <see cref="InheritedPropertyChanged{T}(AvaloniaProperty{T}, Optional{T}, Optional{T})"/>
/// when an inheritable property value changes on the parent. /// when an inheritable property value changes on the parent.
/// </remarks> /// </remarks>

15
src/Avalonia.Base/Platform/IMacOSTopLevelPlatformHandle.cs

@ -0,0 +1,15 @@
// Copyright (c) The Avalonia Project. All rights reserved.
// Licensed under the MIT license. See licence.md file in the project root for full license information.
using System;
namespace Avalonia.Platform
{
public interface IMacOSTopLevelPlatformHandle
{
IntPtr NSView { get; }
IntPtr GetNSViewRetained();
IntPtr NSWindow { get; }
IntPtr GetNSWindowRetained();
}
}

7
src/Avalonia.Base/PropertyStore/BindingEntry.cs

@ -6,10 +6,17 @@ using Avalonia.Threading;
namespace Avalonia.PropertyStore namespace Avalonia.PropertyStore
{ {
/// <summary>
/// Represents an untyped interface to <see cref="BindingEntry{T}"/>.
/// </summary>
internal interface IBindingEntry : IPriorityValueEntry, IDisposable internal interface IBindingEntry : IPriorityValueEntry, IDisposable
{ {
} }
/// <summary>
/// Stores a binding in a <see cref="ValueStore"/> or <see cref="PriorityValue{T}"/>.
/// </summary>
/// <typeparam name="T">The property type.</typeparam>
internal class BindingEntry<T> : IBindingEntry, IPriorityValueEntry<T>, IObserver<BindingValue<T>> internal class BindingEntry<T> : IBindingEntry, IPriorityValueEntry<T>, IObserver<BindingValue<T>>
{ {
private readonly IAvaloniaObject _owner; private readonly IAvaloniaObject _owner;

7
src/Avalonia.Base/PropertyStore/ConstantValueEntry.cs

@ -5,6 +5,11 @@ using Avalonia.Data;
namespace Avalonia.PropertyStore namespace Avalonia.PropertyStore
{ {
/// <summary>
/// Stores a value with a priority in a <see cref="ValueStore"/> or
/// <see cref="PriorityValue{T}"/>.
/// </summary>
/// <typeparam name="T">The property type.</typeparam>
internal class ConstantValueEntry<T> : IPriorityValueEntry<T> internal class ConstantValueEntry<T> : IPriorityValueEntry<T>
{ {
public ConstantValueEntry( public ConstantValueEntry(
@ -19,7 +24,7 @@ namespace Avalonia.PropertyStore
public StyledPropertyBase<T> Property { get; } public StyledPropertyBase<T> Property { get; }
public BindingPriority Priority { get; } public BindingPriority Priority { get; }
public Optional<T> Value { get; private set; } public Optional<T> Value { get; }
Optional<object> IValue.Value => Value.ToObject(); Optional<object> IValue.Value => Value.ToObject();
BindingPriority IValue.ValuePriority => Priority; BindingPriority IValue.ValuePriority => Priority;

7
src/Avalonia.Base/PropertyStore/IPriorityValueEntry.cs

@ -5,6 +5,9 @@ using Avalonia.Data;
namespace Avalonia.PropertyStore namespace Avalonia.PropertyStore
{ {
/// <summary>
/// Represents an untyped interface to <see cref="IPriorityValueEntry{T}"/>.
/// </summary>
internal interface IPriorityValueEntry : IValue internal interface IPriorityValueEntry : IValue
{ {
BindingPriority Priority { get; } BindingPriority Priority { get; }
@ -12,6 +15,10 @@ namespace Avalonia.PropertyStore
void Reparent(IValueSink sink); void Reparent(IValueSink sink);
} }
/// <summary>
/// Represents an object that can act as an entry in a <see cref="PriorityValue{T}"/>.
/// </summary>
/// <typeparam name="T">The property type.</typeparam>
internal interface IPriorityValueEntry<T> : IPriorityValueEntry, IValue<T> internal interface IPriorityValueEntry<T> : IPriorityValueEntry, IValue<T>
{ {
} }

7
src/Avalonia.Base/PropertyStore/IValue.cs

@ -4,12 +4,19 @@
namespace Avalonia.PropertyStore namespace Avalonia.PropertyStore
{ {
/// <summary>
/// Represents an untyped interface to <see cref="IValue{T}"/>.
/// </summary>
internal interface IValue internal interface IValue
{ {
Optional<object> Value { get; } Optional<object> Value { get; }
BindingPriority ValuePriority { get; } BindingPriority ValuePriority { get; }
} }
/// <summary>
/// Represents an object that can act as an entry in a <see cref="ValueStore"/>.
/// </summary>
/// <typeparam name="T">The property type.</typeparam>
internal interface IValue<T> : IValue internal interface IValue<T> : IValue
{ {
new Optional<T> Value { get; } new Optional<T> Value { get; }

6
src/Avalonia.Base/PropertyStore/IValueSink.cs

@ -1,10 +1,12 @@
using System; using Avalonia.Data;
using Avalonia.Data;
#nullable enable #nullable enable
namespace Avalonia.PropertyStore namespace Avalonia.PropertyStore
{ {
/// <summary>
/// Represents an entity that can receive change notifications in a <see cref="ValueStore"/>.
/// </summary>
internal interface IValueSink internal interface IValueSink
{ {
void ValueChanged<T>( void ValueChanged<T>(

5
src/Avalonia.Base/PropertyStore/LocalValueEntry.cs

@ -4,6 +4,11 @@
namespace Avalonia.PropertyStore namespace Avalonia.PropertyStore
{ {
/// <summary>
/// Stores a value with local value priority in a <see cref="ValueStore"/> or
/// <see cref="PriorityValue{T}"/>.
/// </summary>
/// <typeparam name="T">The property type.</typeparam>
internal class LocalValueEntry<T> : IValue<T> internal class LocalValueEntry<T> : IValue<T>
{ {
private T _value; private T _value;

11
src/Avalonia.Base/PropertyStore/PriorityValue.cs

@ -6,6 +6,17 @@ using Avalonia.Data;
namespace Avalonia.PropertyStore namespace Avalonia.PropertyStore
{ {
/// <summary>
/// Stores a set of prioritized values and bindings in a <see cref="ValueStore"/>.
/// </summary>
/// <typeparam name="T">The property type.</typeparam>
/// <remarks>
/// When more than a single value or binding is applied to a property in an
/// <see cref="AvaloniaObject"/>, the entry in the <see cref="ValueStore"/> is converted into
/// a <see cref="PriorityValue{T}"/>. This class holds any number of
/// <see cref="IPriorityValueEntry{T}"/> entries (sorted first by priority and then in the order
/// they were added) plus a local value.
/// </remarks>
internal class PriorityValue<T> : IValue<T>, IValueSink internal class PriorityValue<T> : IValue<T>, IValueSink
{ {
private readonly IAvaloniaObject _owner; private readonly IAvaloniaObject _owner;

2
src/Avalonia.Base/Reactive/AvaloniaPropertyBindingObservable.cs

@ -12,6 +12,7 @@ namespace Avalonia.Reactive
private readonly AvaloniaProperty _property; private readonly AvaloniaProperty _property;
private T _value; private T _value;
#nullable disable
public AvaloniaPropertyBindingObservable( public AvaloniaPropertyBindingObservable(
IAvaloniaObject target, IAvaloniaObject target,
AvaloniaProperty property) AvaloniaProperty property)
@ -19,6 +20,7 @@ namespace Avalonia.Reactive
_target = new WeakReference<IAvaloniaObject>(target); _target = new WeakReference<IAvaloniaObject>(target);
_property = property; _property = property;
} }
#nullable enable
public string Description => $"{_target.GetType().Name}.{_property.Name}"; public string Description => $"{_target.GetType().Name}.{_property.Name}";

11
src/Avalonia.Base/Reactive/AvaloniaPropertyObservable.cs

@ -44,7 +44,16 @@ namespace Avalonia.Reactive
{ {
if (e.Property == _property) if (e.Property == _property)
{ {
var newValue = e.Sender.GetValue(e.Property); T newValue;
if (e is AvaloniaPropertyChangedEventArgs<T> typed)
{
newValue = typed.Sender.GetValue(typed.Property);
}
else
{
newValue = (T)e.Sender.GetValue(e.Property);
}
if (!Equals(newValue, _value)) if (!Equals(newValue, _value))
{ {

4
src/Avalonia.Base/Reactive/BindingValueExtensions.cs

@ -20,13 +20,13 @@ namespace Avalonia.Reactive
return new BindingValueSubjectAdapter<T>(source); return new BindingValueSubjectAdapter<T>(source);
} }
public static IObservable<object> ToUntyped<T>(this IObservable<BindingValue<T>> source) public static IObservable<object?> ToUntyped<T>(this IObservable<BindingValue<T>> source)
{ {
source = source ?? throw new ArgumentNullException(nameof(source)); source = source ?? throw new ArgumentNullException(nameof(source));
return new UntypedBindingAdapter<T>(source); return new UntypedBindingAdapter<T>(source);
} }
public static ISubject<object> ToUntyped<T>(this ISubject<BindingValue<T>> source) public static ISubject<object?> ToUntyped<T>(this ISubject<BindingValue<T>> source)
{ {
source = source ?? throw new ArgumentNullException(nameof(source)); source = source ?? throw new ArgumentNullException(nameof(source));
return new UntypedBindingSubjectAdapter<T>(source); return new UntypedBindingSubjectAdapter<T>(source);

25
src/Avalonia.Base/Reactive/LightweightObservableBase.cs

@ -116,20 +116,33 @@ namespace Avalonia.Reactive
{ {
if (Volatile.Read(ref _observers) != null) if (Volatile.Read(ref _observers) != null)
{ {
IObserver<T>[] observers; IObserver<T>[] observers = null;
IObserver<T> singleObserver = null;
lock (this) lock (this)
{ {
if (_observers == null) if (_observers == null)
{ {
return; return;
} }
observers = _observers.ToArray(); if (_observers.Count == 1)
{
singleObserver = _observers[0];
}
else
{
observers = _observers.ToArray();
}
} }
if (singleObserver != null)
foreach (var observer in observers)
{ {
observer.OnNext(value); singleObserver.OnNext(value);
}
else
{
foreach (var observer in observers)
{
observer.OnNext(value);
}
} }
} }
} }

15
src/Avalonia.Base/StyledPropertyMetadata`1.cs

@ -33,7 +33,7 @@ namespace Avalonia
/// <summary> /// <summary>
/// Gets the default value for the property. /// Gets the default value for the property.
/// </summary> /// </summary>
public TValue DefaultValue => _defaultValue.ValueOrDefault(); public TValue DefaultValue => _defaultValue.GetValueOrDefault();
/// <summary> /// <summary>
/// Gets the value coercion callback, if any. /// Gets the value coercion callback, if any.
@ -60,18 +60,5 @@ namespace Avalonia
} }
} }
} }
[DebuggerHidden]
private static Func<IAvaloniaObject, object, object> Cast(Func<IAvaloniaObject, TValue, TValue> f)
{
if (f == null)
{
return null;
}
else
{
return (o, v) => f(o, (TValue)v);
}
}
} }
} }

2
src/Avalonia.Base/Utilities/IdentifierParser.cs

@ -15,7 +15,7 @@ namespace Avalonia.Utilities
{ {
if (IsValidIdentifierStart(r.Peek)) if (IsValidIdentifierStart(r.Peek))
{ {
return r.TakeWhile(IsValidIdentifierChar); return r.TakeWhile(c => IsValidIdentifierChar(c));
} }
else else
{ {

48
src/Avalonia.Base/Utilities/ImmutableReadOnlyListStructEnumerator.cs

@ -0,0 +1,48 @@
using System.Collections;
using System.Collections.Generic;
namespace Avalonia.Utilities
{
public struct ImmutableReadOnlyListStructEnumerator<T> : IEnumerator, IEnumerator<T>
{
private readonly IReadOnlyList<T> _readOnlyList;
private int _pos;
public ImmutableReadOnlyListStructEnumerator(IReadOnlyList<T> readOnlyList)
{
_readOnlyList = readOnlyList;
_pos = -1;
Current = default;
}
public T Current
{
get;
private set;
}
object IEnumerator.Current => Current;
public void Dispose() { }
public bool MoveNext()
{
if (_pos >= _readOnlyList.Count - 1)
{
return false;
}
Current = _readOnlyList[++_pos];
return true;
}
public void Reset()
{
_pos = -1;
Current = default;
}
}
}

32
src/Avalonia.Base/Utilities/TypeUtilities.cs

@ -92,8 +92,7 @@ namespace Avalonia.Utilities
/// <returns>True if the type accepts null values; otherwise false.</returns> /// <returns>True if the type accepts null values; otherwise false.</returns>
public static bool AcceptsNull(Type type) public static bool AcceptsNull(Type type)
{ {
var t = type.GetTypeInfo(); return !type.IsValueType || IsNullableType(type);
return !t.IsValueType || (t.IsGenericType && (t.GetGenericTypeDefinition() == typeof(Nullable<>)));
} }
/// <summary> /// <summary>
@ -119,10 +118,8 @@ namespace Avalonia.Utilities
} }
var from = value.GetType(); var from = value.GetType();
var fromTypeInfo = from.GetTypeInfo();
var toTypeInfo = to.GetTypeInfo();
if (toTypeInfo.IsAssignableFrom(fromTypeInfo)) if (to.IsAssignableFrom(from))
{ {
result = value; result = value;
return true; return true;
@ -134,7 +131,7 @@ namespace Avalonia.Utilities
return true; return true;
} }
if (toTypeInfo.IsEnum && from == typeof(string)) if (to.IsEnum && from == typeof(string))
{ {
if (Enum.IsDefined(to, (string)value)) if (Enum.IsDefined(to, (string)value))
{ {
@ -143,7 +140,7 @@ namespace Avalonia.Utilities
} }
} }
if (!fromTypeInfo.IsEnum && toTypeInfo.IsEnum) if (!from.IsEnum && to.IsEnum)
{ {
result = null; result = null;
@ -154,7 +151,7 @@ namespace Avalonia.Utilities
} }
} }
if (fromTypeInfo.IsEnum && IsNumeric(to)) if (from.IsEnum && IsNumeric(to))
{ {
try try
{ {
@ -223,10 +220,8 @@ namespace Avalonia.Utilities
} }
var from = value.GetType(); var from = value.GetType();
var fromTypeInfo = from.GetTypeInfo();
var toTypeInfo = to.GetTypeInfo();
if (toTypeInfo.IsAssignableFrom(fromTypeInfo)) if (to.IsAssignableFrom(from))
{ {
result = value; result = value;
return true; return true;
@ -307,9 +302,7 @@ namespace Avalonia.Utilities
/// <returns>The default value.</returns> /// <returns>The default value.</returns>
public static object Default(Type type) public static object Default(Type type)
{ {
var typeInfo = type.GetTypeInfo(); if (type.IsValueType)
if (typeInfo.IsValueType)
{ {
return Activator.CreateInstance(type); return Activator.CreateInstance(type);
} }
@ -335,9 +328,11 @@ namespace Avalonia.Utilities
return false; return false;
} }
if (type.GetTypeInfo().IsGenericType && type.GetGenericTypeDefinition() == typeof(Nullable<>)) Type underlyingType = Nullable.GetUnderlyingType(type);
if (underlyingType != null)
{ {
return IsNumeric(Nullable.GetUnderlyingType(type)); return IsNumeric(underlyingType);
} }
else else
{ {
@ -352,6 +347,11 @@ namespace Avalonia.Utilities
Explicit = 2 Explicit = 2
} }
private static bool IsNullableType(Type type)
{
return type.IsGenericType && type.GetGenericTypeDefinition() == typeof(Nullable<>);
}
private static MethodInfo FindTypeConversionOperatorMethod(Type fromType, Type toType, OperatorType operatorType) private static MethodInfo FindTypeConversionOperatorMethod(Type fromType, Type toType, OperatorType operatorType)
{ {
const string implicitName = "op_Implicit"; const string implicitName = "op_Implicit";

94
src/Avalonia.Base/Utilities/ValueSingleOrList.cs

@ -0,0 +1,94 @@
using System.Collections.Generic;
namespace Avalonia.Utilities
{
/// <summary>
/// A list like struct optimized for holding zero or one items.
/// </summary>
/// <typeparam name="T">The type of items held in the list.</typeparam>
/// <remarks>
/// Once more than value has been added to this storage it will switch to using <see cref="List"/> internally.
/// </remarks>
public ref struct ValueSingleOrList<T>
{
private bool _isSingleSet;
/// <summary>
/// Single contained value. Only valid if <see cref="IsSingle"/> is set.
/// </summary>
public T Single { get; private set; }
/// <summary>
/// List of values.
/// </summary>
public List<T> List { get; private set; }
/// <summary>
/// If this struct is backed by a list.
/// </summary>
public bool HasList => List != null;
/// <summary>
/// If this struct contains only single value and storage was not promoted to a list.
/// </summary>
public bool IsSingle => List == null && _isSingleSet;
/// <summary>
/// Adds a value.
/// </summary>
/// <param name="value">Value to add.</param>
public void Add(T value)
{
if (List != null)
{
List.Add(value);
}
else
{
if (!_isSingleSet)
{
Single = value;
_isSingleSet = true;
}
else
{
List = new List<T>();
List.Add(Single);
List.Add(value);
Single = default;
}
}
}
/// <summary>
/// Removes a value.
/// </summary>
/// <param name="value">Value to remove.</param>
public bool Remove(T value)
{
if (List != null)
{
return List.Remove(value);
}
if (!_isSingleSet)
{
return false;
}
if (EqualityComparer<T>.Default.Equals(Single, value))
{
Single = default;
_isSingleSet = false;
return true;
}
return false;
}
}
}

53
src/Avalonia.Base/ValueStore.cs

@ -1,5 +1,4 @@
using System; using System;
using System.Collections.Generic;
using Avalonia.Data; using Avalonia.Data;
using Avalonia.PropertyStore; using Avalonia.PropertyStore;
using Avalonia.Utilities; using Avalonia.Utilities;
@ -8,26 +7,37 @@ using Avalonia.Utilities;
namespace Avalonia namespace Avalonia
{ {
/// <summary>
/// Stores styled property values for an <see cref="AvaloniaObject"/>.
/// </summary>
/// <remarks>
/// At its core this class consists of an <see cref="AvaloniaProperty"/> to
/// <see cref="IValue"/> mapping which holds the current values for each set property. This
/// <see cref="IValue"/> can be in one of 4 states:
///
/// - For a single local value it will be an instance of <see cref="LocalValueEntry{T}"/>.
/// - For a single value of a priority other than LocalValue it will be an instance of
/// <see cref="ConstantValueEntry{T}"/>`
/// - For a single binding it will be an instance of <see cref="BindingEntry{T}"/>
/// - For all other cases it will be an instance of <see cref="PriorityValue{T}"/>
/// </remarks>
internal class ValueStore : IValueSink internal class ValueStore : IValueSink
{ {
private readonly AvaloniaObject _owner; private readonly AvaloniaObject _owner;
private readonly IValueSink _sink; private readonly IValueSink _sink;
private readonly AvaloniaPropertyValueStore<object> _values; private readonly AvaloniaPropertyValueStore<IValue> _values;
public ValueStore(AvaloniaObject owner) public ValueStore(AvaloniaObject owner)
{ {
_sink = _owner = owner; _sink = _owner = owner;
_values = new AvaloniaPropertyValueStore<object>(); _values = new AvaloniaPropertyValueStore<IValue>();
} }
public bool IsAnimating(AvaloniaProperty property) public bool IsAnimating(AvaloniaProperty property)
{ {
if (_values.TryGetValue(property, out var slot)) if (_values.TryGetValue(property, out var slot))
{ {
if (slot is IValue v) return slot.ValuePriority < BindingPriority.LocalValue;
{
return v.ValuePriority < BindingPriority.LocalValue;
}
} }
return false; return false;
@ -37,10 +47,7 @@ namespace Avalonia
{ {
if (_values.TryGetValue(property, out var slot)) if (_values.TryGetValue(property, out var slot))
{ {
if (slot is IValue v) return slot.Value.HasValue;
{
return v.Value.HasValue;
}
} }
return false; return false;
@ -50,13 +57,12 @@ namespace Avalonia
{ {
if (_values.TryGetValue(property, out var slot)) if (_values.TryGetValue(property, out var slot))
{ {
if (slot is IValue<T> v) var v = (IValue<T>)slot;
if (v.Value.HasValue)
{ {
if (v.Value.HasValue) value = v.Value.Value;
{ return true;
value = v.Value.Value;
return true;
}
} }
} }
@ -165,14 +171,11 @@ namespace Avalonia
{ {
if (_values.TryGetValue(property, out var slot)) if (_values.TryGetValue(property, out var slot))
{ {
if (slot is IValue value) return new Diagnostics.AvaloniaPropertyValue(
{ property,
return new Diagnostics.AvaloniaPropertyValue( slot.Value.HasValue ? (object)slot.Value : AvaloniaProperty.UnsetValue,
property, slot.ValuePriority,
value.Value.HasValue ? (object)value.Value : AvaloniaProperty.UnsetValue, null);
value.ValuePriority,
null);
}
} }
return null; return null;

2
src/Avalonia.Controls.DataGrid/Collections/DataGridSortDescription.cs

@ -238,7 +238,7 @@ namespace Avalonia.Collections
} }
else else
{ {
return seq.ThenByDescending(o => GetValue(o), InternalComparer); return seq.ThenBy(o => GetValue(o), InternalComparer);
} }
} }

2
src/Avalonia.Controls.DataGrid/Primitives/DataGridFrozenGrid.cs

@ -14,7 +14,7 @@ namespace Avalonia.Controls.Primitives
/// </summary> /// </summary>
public class DataGridFrozenGrid : Grid public class DataGridFrozenGrid : Grid
{ {
public static readonly AvaloniaProperty<bool> IsFrozenProperty = public static readonly StyledProperty<bool> IsFrozenProperty =
AvaloniaProperty.RegisterAttached<DataGridFrozenGrid, Control, bool>("IsFrozen"); AvaloniaProperty.RegisterAttached<DataGridFrozenGrid, Control, bool>("IsFrozen");
/// <summary> /// <summary>

2
src/Avalonia.Controls.DataGrid/Properties/AssemblyInfo.cs

@ -5,7 +5,7 @@ using System.Reflection;
using System.Runtime.CompilerServices; using System.Runtime.CompilerServices;
using Avalonia.Metadata; using Avalonia.Metadata;
[assembly: InternalsVisibleTo("Avalonia.Controls.UnitTests")] [assembly: InternalsVisibleTo("Avalonia.Controls.DataGrid.UnitTests")]
[assembly: InternalsVisibleTo("Avalonia.DesignerSupport")] [assembly: InternalsVisibleTo("Avalonia.DesignerSupport")]
[assembly: XmlnsDefinition("https://github.com/avaloniaui", "Avalonia.Controls")] [assembly: XmlnsDefinition("https://github.com/avaloniaui", "Avalonia.Controls")]

21
src/Avalonia.Controls/Application.cs

@ -32,7 +32,7 @@ namespace Avalonia
/// method. /// method.
/// - Tracks the lifetime of the application. /// - Tracks the lifetime of the application.
/// </remarks> /// </remarks>
public class Application : AvaloniaObject, IGlobalDataTemplates, IGlobalStyles, IStyleRoot, IResourceNode public class Application : AvaloniaObject, IDataContextProvider, IGlobalDataTemplates, IGlobalStyles, IResourceNode
{ {
/// <summary> /// <summary>
/// The application-global data templates. /// The application-global data templates.
@ -45,6 +45,12 @@ namespace Avalonia
private Styles _styles; private Styles _styles;
private IResourceDictionary _resources; private IResourceDictionary _resources;
/// <summary>
/// Defines the <see cref="DataContext"/> property.
/// </summary>
public static readonly StyledProperty<object> DataContextProperty =
StyledElement.DataContextProperty.AddOwner<Application>();
/// <inheritdoc/> /// <inheritdoc/>
public event EventHandler<ResourcesChangedEventArgs> ResourcesChanged; public event EventHandler<ResourcesChangedEventArgs> ResourcesChanged;
@ -56,6 +62,19 @@ namespace Avalonia
Name = "Avalonia Application"; Name = "Avalonia Application";
} }
/// <summary>
/// Gets or sets the Applications's data context.
/// </summary>
/// <remarks>
/// The data context property specifies the default object that will
/// be used for data binding.
/// </remarks>
public object DataContext
{
get { return GetValue(DataContextProperty); }
set { SetValue(DataContextProperty, value); }
}
/// <summary> /// <summary>
/// Gets the current instance of the <see cref="Application"/> class. /// Gets the current instance of the <see cref="Application"/> class.
/// </summary> /// </summary>

2
src/Avalonia.Controls/Button.cs

@ -326,7 +326,7 @@ namespace Avalonia.Controls
if (property == IsPressedProperty) if (property == IsPressedProperty)
{ {
UpdatePseudoClasses(newValue.ValueOrDefault<bool>()); UpdatePseudoClasses(newValue.GetValueOrDefault<bool>());
} }
} }

2
src/Avalonia.Controls/ButtonSpinner.cs

@ -215,7 +215,7 @@ namespace Avalonia.Controls
if (property == ButtonSpinnerLocationProperty) if (property == ButtonSpinnerLocationProperty)
{ {
UpdatePseudoClasses(newValue.ValueOrDefault<Location>()); UpdatePseudoClasses(newValue.GetValueOrDefault<Location>());
} }
} }

6
src/Avalonia.Controls/DefinitionBase.cs

@ -358,7 +358,11 @@ namespace Avalonia.Controls
/// </remarks> /// </remarks>
private static bool SharedSizeGroupPropertyValueValid(string value) private static bool SharedSizeGroupPropertyValueValid(string value)
{ {
Contract.Requires<ArgumentNullException>(value != null); // null is default value
if (value == null)
{
return true;
}
string id = (string)value; string id = (string)value;

4
src/Avalonia.Controls/DrawingPresenter.cs

@ -1,9 +1,11 @@
using Avalonia.Controls.Shapes; using System;
using Avalonia.Controls.Shapes;
using Avalonia.Media; using Avalonia.Media;
using Avalonia.Metadata; using Avalonia.Metadata;
namespace Avalonia.Controls namespace Avalonia.Controls
{ {
[Obsolete("Use Image control with DrawingImage source")]
public class DrawingPresenter : Control public class DrawingPresenter : Control
{ {
static DrawingPresenter() static DrawingPresenter()

2
src/Avalonia.Controls/Expander.cs

@ -88,7 +88,7 @@ namespace Avalonia.Controls
if (property == ExpandDirectionProperty) if (property == ExpandDirectionProperty)
{ {
UpdatePseudoClasses(newValue.ValueOrDefault<ExpandDirection>()); UpdatePseudoClasses(newValue.GetValueOrDefault<ExpandDirection>());
} }
} }

5
src/Avalonia.Controls/Generators/ITreeItemContainerGenerator.cs

@ -12,5 +12,10 @@ namespace Avalonia.Controls.Generators
/// Gets the container index for the tree. /// Gets the container index for the tree.
/// </summary> /// </summary>
TreeContainerIndex Index { get; } TreeContainerIndex Index { get; }
/// <summary>
/// Updates the index based on the parent <see cref="TreeView"/>.
/// </summary>
void UpdateIndex();
} }
} }

43
src/Avalonia.Controls/Generators/TreeItemContainerGenerator.cs

@ -3,8 +3,10 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq;
using Avalonia.Controls.Templates; using Avalonia.Controls.Templates;
using Avalonia.Data; using Avalonia.Data;
using Avalonia.LogicalTree;
namespace Avalonia.Controls.Generators namespace Avalonia.Controls.Generators
{ {
@ -15,6 +17,8 @@ namespace Avalonia.Controls.Generators
public class TreeItemContainerGenerator<T> : ItemContainerGenerator<T>, ITreeItemContainerGenerator public class TreeItemContainerGenerator<T> : ItemContainerGenerator<T>, ITreeItemContainerGenerator
where T : class, IControl, new() where T : class, IControl, new()
{ {
private TreeView _treeView;
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="TreeItemContainerGenerator{T}"/> class. /// Initializes a new instance of the <see cref="TreeItemContainerGenerator{T}"/> class.
/// </summary> /// </summary>
@ -23,31 +27,28 @@ namespace Avalonia.Controls.Generators
/// <param name="contentTemplateProperty">The container's ContentTemplate property.</param> /// <param name="contentTemplateProperty">The container's ContentTemplate property.</param>
/// <param name="itemsProperty">The container's Items property.</param> /// <param name="itemsProperty">The container's Items property.</param>
/// <param name="isExpandedProperty">The container's IsExpanded property.</param> /// <param name="isExpandedProperty">The container's IsExpanded property.</param>
/// <param name="index">The container index for the tree</param>
public TreeItemContainerGenerator( public TreeItemContainerGenerator(
IControl owner, IControl owner,
AvaloniaProperty contentProperty, AvaloniaProperty contentProperty,
AvaloniaProperty contentTemplateProperty, AvaloniaProperty contentTemplateProperty,
AvaloniaProperty itemsProperty, AvaloniaProperty itemsProperty,
AvaloniaProperty isExpandedProperty, AvaloniaProperty isExpandedProperty)
TreeContainerIndex index)
: base(owner, contentProperty, contentTemplateProperty) : base(owner, contentProperty, contentTemplateProperty)
{ {
Contract.Requires<ArgumentNullException>(owner != null); Contract.Requires<ArgumentNullException>(owner != null);
Contract.Requires<ArgumentNullException>(contentProperty != null); Contract.Requires<ArgumentNullException>(contentProperty != null);
Contract.Requires<ArgumentNullException>(itemsProperty != null); Contract.Requires<ArgumentNullException>(itemsProperty != null);
Contract.Requires<ArgumentNullException>(isExpandedProperty != null); Contract.Requires<ArgumentNullException>(isExpandedProperty != null);
Contract.Requires<ArgumentNullException>(index != null);
ItemsProperty = itemsProperty; ItemsProperty = itemsProperty;
IsExpandedProperty = isExpandedProperty; IsExpandedProperty = isExpandedProperty;
Index = index; UpdateIndex();
} }
/// <summary> /// <summary>
/// Gets the container index for the tree. /// Gets the container index for the tree.
/// </summary> /// </summary>
public TreeContainerIndex Index { get; } public TreeContainerIndex Index { get; private set; }
/// <summary> /// <summary>
/// Gets the item container's Items property. /// Gets the item container's Items property.
@ -70,7 +71,7 @@ namespace Avalonia.Controls.Generators
} }
else if (container != null) else if (container != null)
{ {
Index.Add(item, container); Index?.Add(item, container);
return container; return container;
} }
else else
@ -92,7 +93,7 @@ namespace Avalonia.Controls.Generators
result.DataContext = item; result.DataContext = item;
} }
Index.Add(item, result); Index?.Add(item, result);
return result; return result;
} }
@ -101,24 +102,44 @@ namespace Avalonia.Controls.Generators
public override IEnumerable<ItemContainerInfo> Clear() public override IEnumerable<ItemContainerInfo> Clear()
{ {
var items = base.Clear(); var items = base.Clear();
Index.Remove(0, items); Index?.Remove(0, items);
return items; return items;
} }
public override IEnumerable<ItemContainerInfo> Dematerialize(int startingIndex, int count) public override IEnumerable<ItemContainerInfo> Dematerialize(int startingIndex, int count)
{ {
Index.Remove(startingIndex, GetContainerRange(startingIndex, count)); Index?.Remove(startingIndex, GetContainerRange(startingIndex, count));
return base.Dematerialize(startingIndex, count); return base.Dematerialize(startingIndex, count);
} }
public override IEnumerable<ItemContainerInfo> RemoveRange(int startingIndex, int count) public override IEnumerable<ItemContainerInfo> RemoveRange(int startingIndex, int count)
{ {
Index.Remove(startingIndex, GetContainerRange(startingIndex, count)); Index?.Remove(startingIndex, GetContainerRange(startingIndex, count));
return base.RemoveRange(startingIndex, count); return base.RemoveRange(startingIndex, count);
} }
public override bool TryRecycle(int oldIndex, int newIndex, object item) => false; public override bool TryRecycle(int oldIndex, int newIndex, object item) => false;
public void UpdateIndex()
{
if (Owner is TreeView treeViewOwner && Index == null)
{
Index = new TreeContainerIndex();
_treeView = treeViewOwner;
}
else
{
var treeView = Owner.GetSelfAndLogicalAncestors().OfType<TreeView>().FirstOrDefault();
if (treeView != _treeView)
{
Clear();
Index = treeView?.ItemContainerGenerator?.Index;
_treeView = treeView;
}
}
}
class WrapperTreeDataTemplate : ITreeDataTemplate class WrapperTreeDataTemplate : ITreeDataTemplate
{ {
private readonly IDataTemplate _inner; private readonly IDataTemplate _inner;

12
src/Avalonia.Controls/GridSplitter.cs

@ -23,37 +23,37 @@ namespace Avalonia.Controls
/// <summary> /// <summary>
/// Defines the <see cref="ResizeDirection"/> property. /// Defines the <see cref="ResizeDirection"/> property.
/// </summary> /// </summary>
public static readonly AvaloniaProperty<GridResizeDirection> ResizeDirectionProperty = public static readonly StyledProperty<GridResizeDirection> ResizeDirectionProperty =
AvaloniaProperty.Register<GridSplitter, GridResizeDirection>(nameof(ResizeDirection)); AvaloniaProperty.Register<GridSplitter, GridResizeDirection>(nameof(ResizeDirection));
/// <summary> /// <summary>
/// Defines the <see cref="ResizeBehavior"/> property. /// Defines the <see cref="ResizeBehavior"/> property.
/// </summary> /// </summary>
public static readonly AvaloniaProperty<GridResizeBehavior> ResizeBehaviorProperty = public static readonly StyledProperty<GridResizeBehavior> ResizeBehaviorProperty =
AvaloniaProperty.Register<GridSplitter, GridResizeBehavior>(nameof(ResizeBehavior)); AvaloniaProperty.Register<GridSplitter, GridResizeBehavior>(nameof(ResizeBehavior));
/// <summary> /// <summary>
/// Defines the <see cref="ShowsPreview"/> property. /// Defines the <see cref="ShowsPreview"/> property.
/// </summary> /// </summary>
public static readonly AvaloniaProperty<bool> ShowsPreviewProperty = public static readonly StyledProperty<bool> ShowsPreviewProperty =
AvaloniaProperty.Register<GridSplitter, bool>(nameof(ShowsPreview)); AvaloniaProperty.Register<GridSplitter, bool>(nameof(ShowsPreview));
/// <summary> /// <summary>
/// Defines the <see cref="KeyboardIncrement"/> property. /// Defines the <see cref="KeyboardIncrement"/> property.
/// </summary> /// </summary>
public static readonly AvaloniaProperty<double> KeyboardIncrementProperty = public static readonly StyledProperty<double> KeyboardIncrementProperty =
AvaloniaProperty.Register<GridSplitter, double>(nameof(KeyboardIncrement), 10d); AvaloniaProperty.Register<GridSplitter, double>(nameof(KeyboardIncrement), 10d);
/// <summary> /// <summary>
/// Defines the <see cref="DragIncrement"/> property. /// Defines the <see cref="DragIncrement"/> property.
/// </summary> /// </summary>
public static readonly AvaloniaProperty<double> DragIncrementProperty = public static readonly StyledProperty<double> DragIncrementProperty =
AvaloniaProperty.Register<GridSplitter, double>(nameof(DragIncrement), 1d); AvaloniaProperty.Register<GridSplitter, double>(nameof(DragIncrement), 1d);
/// <summary> /// <summary>
/// Defines the <see cref="PreviewContent"/> property. /// Defines the <see cref="PreviewContent"/> property.
/// </summary> /// </summary>
public static readonly AvaloniaProperty<ITemplate<IControl>> PreviewContentProperty = public static readonly StyledProperty<ITemplate<IControl>> PreviewContentProperty =
AvaloniaProperty.Register<GridSplitter, ITemplate<IControl>>(nameof(PreviewContent)); AvaloniaProperty.Register<GridSplitter, ITemplate<IControl>>(nameof(PreviewContent));
private static readonly Cursor s_columnSplitterCursor = new Cursor(StandardCursorType.SizeWestEast); private static readonly Cursor s_columnSplitterCursor = new Cursor(StandardCursorType.SizeWestEast);

45
src/Avalonia.Controls/Image.cs

@ -14,8 +14,8 @@ namespace Avalonia.Controls
/// <summary> /// <summary>
/// Defines the <see cref="Source"/> property. /// Defines the <see cref="Source"/> property.
/// </summary> /// </summary>
public static readonly StyledProperty<IBitmap> SourceProperty = public static readonly StyledProperty<IImage> SourceProperty =
AvaloniaProperty.Register<Image, IBitmap>(nameof(Source)); AvaloniaProperty.Register<Image, IImage>(nameof(Source));
/// <summary> /// <summary>
/// Defines the <see cref="Stretch"/> property. /// Defines the <see cref="Stretch"/> property.
@ -23,6 +23,14 @@ namespace Avalonia.Controls
public static readonly StyledProperty<Stretch> StretchProperty = public static readonly StyledProperty<Stretch> StretchProperty =
AvaloniaProperty.Register<Image, Stretch>(nameof(Stretch), Stretch.Uniform); AvaloniaProperty.Register<Image, Stretch>(nameof(Stretch), Stretch.Uniform);
/// <summary>
/// Defines the <see cref="StretchDirection"/> property.
/// </summary>
public static readonly StyledProperty<StretchDirection> StretchDirectionProperty =
AvaloniaProperty.Register<Image, StretchDirection>(
nameof(StretchDirection),
StretchDirection.Both);
static Image() static Image()
{ {
AffectsRender<Image>(SourceProperty, StretchProperty); AffectsRender<Image>(SourceProperty, StretchProperty);
@ -30,9 +38,9 @@ namespace Avalonia.Controls
} }
/// <summary> /// <summary>
/// Gets or sets the bitmap image that will be displayed. /// Gets or sets the image that will be displayed.
/// </summary> /// </summary>
public IBitmap Source public IImage Source
{ {
get { return GetValue(SourceProperty); } get { return GetValue(SourceProperty); }
set { SetValue(SourceProperty, value); } set { SetValue(SourceProperty, value); }
@ -43,10 +51,19 @@ namespace Avalonia.Controls
/// </summary> /// </summary>
public Stretch Stretch public Stretch Stretch
{ {
get { return (Stretch)GetValue(StretchProperty); } get { return GetValue(StretchProperty); }
set { SetValue(StretchProperty, value); } set { SetValue(StretchProperty, value); }
} }
/// <summary>
/// Gets or sets a value controlling in what direction the image will be stretched.
/// </summary>
public StretchDirection StretchDirection
{
get { return GetValue(StretchDirectionProperty); }
set { SetValue(StretchDirectionProperty, value); }
}
/// <summary> /// <summary>
/// Renders the control. /// Renders the control.
/// </summary> /// </summary>
@ -58,8 +75,8 @@ namespace Avalonia.Controls
if (source != null) if (source != null)
{ {
Rect viewPort = new Rect(Bounds.Size); Rect viewPort = new Rect(Bounds.Size);
Size sourceSize = new Size(source.PixelSize.Width, source.PixelSize.Height); Size sourceSize = source.Size;
Vector scale = Stretch.CalculateScaling(Bounds.Size, sourceSize); Vector scale = Stretch.CalculateScaling(Bounds.Size, sourceSize, StretchDirection);
Size scaledSize = sourceSize * scale; Size scaledSize = sourceSize * scale;
Rect destRect = viewPort Rect destRect = viewPort
.CenterRect(new Rect(scaledSize)) .CenterRect(new Rect(scaledSize))
@ -69,7 +86,7 @@ namespace Avalonia.Controls
var interpolationMode = RenderOptions.GetBitmapInterpolationMode(this); var interpolationMode = RenderOptions.GetBitmapInterpolationMode(this);
context.DrawImage(source, 1, sourceRect, destRect, interpolationMode); context.DrawImage(source, sourceRect, destRect, interpolationMode);
} }
} }
@ -85,15 +102,7 @@ namespace Avalonia.Controls
if (source != null) if (source != null)
{ {
Size sourceSize = new Size(source.PixelSize.Width, source.PixelSize.Height); result = Stretch.CalculateSize(availableSize, source.Size, StretchDirection);
if (double.IsInfinity(availableSize.Width) || double.IsInfinity(availableSize.Height))
{
result = sourceSize;
}
else
{
result = Stretch.CalculateSize(availableSize, sourceSize);
}
} }
return result; return result;
@ -106,7 +115,7 @@ namespace Avalonia.Controls
if (source != null) if (source != null)
{ {
var sourceSize = new Size(source.PixelSize.Width, source.PixelSize.Height); var sourceSize = source.Size;
var result = Stretch.CalculateSize(finalSize, sourceSize); var result = Stretch.CalculateSize(finalSize, sourceSize);
return result; return result;
} }

25
src/Avalonia.Controls/ItemsControl.cs

@ -5,7 +5,6 @@ using System;
using System.Collections; using System.Collections;
using System.Collections.Generic; using System.Collections.Generic;
using System.Collections.Specialized; using System.Collections.Specialized;
using System.Linq;
using Avalonia.Collections; using Avalonia.Collections;
using Avalonia.Controls.Generators; using Avalonia.Controls.Generators;
using Avalonia.Controls.Presenters; using Avalonia.Controls.Presenters;
@ -324,20 +323,24 @@ namespace Avalonia.Controls
return; return;
} }
var current = focus.Current IVisual current = focus.Current;
.GetSelfAndVisualAncestors()
.OfType<IInputElement>()
.FirstOrDefault(x => x.VisualParent == container);
if (current != null) while (current != null)
{ {
var next = GetNextControl(container, direction.Value, current, false); if (current.VisualParent == container && current is IInputElement inputElement)
if (next != null)
{ {
focus.Focus(next, NavigationMethod.Directional); IInputElement next = GetNextControl(container, direction.Value, inputElement, false);
e.Handled = true;
if (next != null)
{
focus.Focus(next, NavigationMethod.Directional);
e.Handled = true;
}
break;
} }
current = current.VisualParent;
} }
} }

4
src/Avalonia.Controls/LayoutTransformControl.cs

@ -17,10 +17,10 @@ namespace Avalonia.Controls
/// </summary> /// </summary>
public class LayoutTransformControl : Decorator public class LayoutTransformControl : Decorator
{ {
public static readonly AvaloniaProperty<Transform> LayoutTransformProperty = public static readonly StyledProperty<Transform> LayoutTransformProperty =
AvaloniaProperty.Register<LayoutTransformControl, Transform>(nameof(LayoutTransform)); AvaloniaProperty.Register<LayoutTransformControl, Transform>(nameof(LayoutTransform));
public static readonly AvaloniaProperty<bool> UseRenderTransformProperty = public static readonly StyledProperty<bool> UseRenderTransformProperty =
AvaloniaProperty.Register<LayoutTransformControl, bool>(nameof(LayoutTransform)); AvaloniaProperty.Register<LayoutTransformControl, bool>(nameof(LayoutTransform));
static LayoutTransformControl() static LayoutTransformControl()

2
src/Avalonia.Controls/ListBox.cs

@ -124,7 +124,7 @@ namespace Avalonia.Controls
e.Handled = UpdateSelectionFromEventSource( e.Handled = UpdateSelectionFromEventSource(
e.Source, e.Source,
true, true,
(e.InputModifiers & InputModifiers.Shift) != 0); (e.KeyModifiers & KeyModifiers.Shift) != 0);
} }
} }

17
src/Avalonia.Controls/Notifications/WindowNotificationManager.cs

@ -7,6 +7,7 @@ using System.Linq;
using System.Reactive.Linq; using System.Reactive.Linq;
using System.Threading.Tasks; using System.Threading.Tasks;
using Avalonia.Controls.Primitives; using Avalonia.Controls.Primitives;
using Avalonia.Rendering;
using Avalonia.Data; using Avalonia.Data;
using Avalonia.VisualTree; using Avalonia.VisualTree;
@ -15,7 +16,7 @@ namespace Avalonia.Controls.Notifications
/// <summary> /// <summary>
/// An <see cref="INotificationManager"/> that displays notifications in a <see cref="Window"/>. /// An <see cref="INotificationManager"/> that displays notifications in a <see cref="Window"/>.
/// </summary> /// </summary>
public class WindowNotificationManager : TemplatedControl, IManagedNotificationManager public class WindowNotificationManager : TemplatedControl, IManagedNotificationManager, ICustomSimpleHitTest
{ {
private IList _items; private IList _items;
@ -150,7 +151,7 @@ namespace Avalonia.Controls.Notifications
if (property == PositionProperty) if (property == PositionProperty)
{ {
UpdatePseudoClasses(newValue.ValueOrDefault<NotificationPosition>()); UpdatePseudoClasses(newValue.GetValueOrDefault<NotificationPosition>());
} }
} }
@ -161,15 +162,9 @@ namespace Avalonia.Controls.Notifications
/// <param name="host">The <see cref="Window"/> that will be the host.</param> /// <param name="host">The <see cref="Window"/> that will be the host.</param>
private void Install(Window host) private void Install(Window host)
{ {
var adornerLayer = host.GetVisualDescendants() var adornerLayer = host.FindDescendantOfType<VisualLayerManager>()?.AdornerLayer;
.OfType<VisualLayerManager>()
.FirstOrDefault()
?.AdornerLayer;
if (adornerLayer != null) adornerLayer?.Children.Add(this);
{
adornerLayer.Children.Add(this);
}
} }
private void UpdatePseudoClasses(NotificationPosition position) private void UpdatePseudoClasses(NotificationPosition position)
@ -179,5 +174,7 @@ namespace Avalonia.Controls.Notifications
PseudoClasses.Set(":bottomleft", position == NotificationPosition.BottomLeft); PseudoClasses.Set(":bottomleft", position == NotificationPosition.BottomLeft);
PseudoClasses.Set(":bottomright", position == NotificationPosition.BottomRight); PseudoClasses.Set(":bottomright", position == NotificationPosition.BottomRight);
} }
public bool HitTest(Point point) => VisualChildren.HitTestCustom(point);
} }
} }

30
src/Avalonia.Controls/Platform/DefaultMenuInteractionHandler.cs

@ -6,6 +6,8 @@ using Avalonia.LogicalTree;
using Avalonia.Rendering; using Avalonia.Rendering;
using Avalonia.Threading; using Avalonia.Threading;
#nullable enable
namespace Avalonia.Controls.Platform namespace Avalonia.Controls.Platform
{ {
/// <summary> /// <summary>
@ -14,8 +16,8 @@ namespace Avalonia.Controls.Platform
public class DefaultMenuInteractionHandler : IMenuInteractionHandler public class DefaultMenuInteractionHandler : IMenuInteractionHandler
{ {
private readonly bool _isContextMenu; private readonly bool _isContextMenu;
private IDisposable _inputManagerSubscription; private IDisposable? _inputManagerSubscription;
private IRenderRoot _root; private IRenderRoot? _root;
public DefaultMenuInteractionHandler(bool isContextMenu) public DefaultMenuInteractionHandler(bool isContextMenu)
: this(isContextMenu, Input.InputManager.Instance, DefaultDelayRun) : this(isContextMenu, Input.InputManager.Instance, DefaultDelayRun)
@ -24,9 +26,11 @@ namespace Avalonia.Controls.Platform
public DefaultMenuInteractionHandler( public DefaultMenuInteractionHandler(
bool isContextMenu, bool isContextMenu,
IInputManager inputManager, IInputManager? inputManager,
Action<Action, TimeSpan> delayRun) Action<Action, TimeSpan> delayRun)
{ {
delayRun = delayRun ?? throw new ArgumentNullException(nameof(delayRun));
_isContextMenu = isContextMenu; _isContextMenu = isContextMenu;
InputManager = inputManager; InputManager = inputManager;
DelayRun = delayRun; DelayRun = delayRun;
@ -92,7 +96,7 @@ namespace Avalonia.Controls.Platform
root.Deactivated -= WindowDeactivated; root.Deactivated -= WindowDeactivated;
} }
_inputManagerSubscription.Dispose(); _inputManagerSubscription!.Dispose();
Menu = null; Menu = null;
_root = null; _root = null;
@ -100,9 +104,9 @@ namespace Avalonia.Controls.Platform
protected Action<Action, TimeSpan> DelayRun { get; } protected Action<Action, TimeSpan> DelayRun { get; }
protected IInputManager InputManager { get; } protected IInputManager? InputManager { get; }
protected IMenu Menu { get; private set; } protected IMenu? Menu { get; private set; }
protected static TimeSpan MenuShowDelay { get; } = TimeSpan.FromMilliseconds(400); protected static TimeSpan MenuShowDelay { get; } = TimeSpan.FromMilliseconds(400);
@ -131,7 +135,7 @@ namespace Avalonia.Controls.Platform
KeyDown(GetMenuItem(e.Source as IControl), e); KeyDown(GetMenuItem(e.Source as IControl), e);
} }
protected internal virtual void KeyDown(IMenuItem item, KeyEventArgs e) protected internal virtual void KeyDown(IMenuItem? item, KeyEventArgs e)
{ {
switch (e.Key) switch (e.Key)
{ {
@ -200,7 +204,7 @@ namespace Avalonia.Controls.Platform
} }
else else
{ {
Menu.Close(); Menu!.Close();
} }
e.Handled = true; e.Handled = true;
@ -213,12 +217,12 @@ namespace Avalonia.Controls.Platform
{ {
if (item == null && _isContextMenu) if (item == null && _isContextMenu)
{ {
if (Menu.MoveSelection(direction.Value, true) == true) if (Menu!.MoveSelection(direction.Value, true) == true)
{ {
e.Handled = true; e.Handled = true;
} }
} }
else if (item.Parent?.MoveSelection(direction.Value, true) == true) else if (item?.Parent?.MoveSelection(direction.Value, true) == true)
{ {
// If the the parent is an IMenu which successfully moved its selection, // If the the parent is an IMenu which successfully moved its selection,
// and the current menu is open then close the current menu and open the // and the current menu is open then close the current menu and open the
@ -408,7 +412,7 @@ namespace Avalonia.Controls.Platform
protected void CloseMenu(IMenuItem item) protected void CloseMenu(IMenuItem item)
{ {
var current = (IMenuElement)item; var current = (IMenuElement?)item;
while (current != null && !(current is IMenu)) while (current != null && !(current is IMenu))
{ {
@ -456,7 +460,7 @@ namespace Avalonia.Controls.Platform
protected void SelectItemAndAncestors(IMenuItem item) protected void SelectItemAndAncestors(IMenuItem item)
{ {
var current = item; var current = (IMenuItem?)item;
while (current?.Parent != null) while (current?.Parent != null)
{ {
@ -465,7 +469,7 @@ namespace Avalonia.Controls.Platform
} }
} }
protected static IMenuItem GetMenuItem(IControl item) protected static IMenuItem? GetMenuItem(IControl? item)
{ {
while (true) while (true)
{ {

2
src/Avalonia.Controls/Presenters/ContentPresenter.cs

@ -28,7 +28,7 @@ namespace Avalonia.Controls.Presenters
/// <summary> /// <summary>
/// Defines the <see cref="BorderBrush"/> property. /// Defines the <see cref="BorderBrush"/> property.
/// </summary> /// </summary>
public static readonly AvaloniaProperty<IBrush> BorderBrushProperty = public static readonly StyledProperty<IBrush> BorderBrushProperty =
Border.BorderBrushProperty.AddOwner<ContentPresenter>(); Border.BorderBrushProperty.AddOwner<ContentPresenter>();
/// <summary> /// <summary>

4
src/Avalonia.Controls/Presenters/ItemVirtualizerSimple.cs

@ -188,8 +188,8 @@ namespace Avalonia.Controls.Presenters
break; break;
case NotifyCollectionChangedAction.Remove: case NotifyCollectionChangedAction.Remove:
if (e.OldStartingIndex >= FirstIndex && if ((e.OldStartingIndex >= FirstIndex && e.OldStartingIndex < NextIndex) ||
e.OldStartingIndex < NextIndex) panel.Children.Count > ItemCount)
{ {
RecycleContainersOnRemove(); RecycleContainersOnRemove();
} }

2
src/Avalonia.Controls/Presenters/TextPresenter.cs

@ -297,7 +297,7 @@ namespace Avalonia.Controls.Presenters
return new FormattedText return new FormattedText
{ {
Text = "X", Text = "X",
Typeface = new Typeface(FontFamily, FontWeight, FontStyle), Typeface = FontManager.Current?.GetOrAddTypeface(FontFamily, FontWeight, FontStyle),
FontSize = FontSize, FontSize = FontSize,
TextAlignment = TextAlignment, TextAlignment = TextAlignment,
Constraint = availableSize, Constraint = availableSize,

5
src/Avalonia.Controls/Primitives/AdornerLayer.cs

@ -138,10 +138,7 @@ namespace Avalonia.Controls.Primitives
} }
} }
public bool HitTest(Point point) public bool HitTest(Point point) => Children.HitTestCustom(point);
{
return Children.Any(ctrl => ctrl.TransformedBounds?.Contains(point) == true);
}
private class AdornedElementInfo private class AdornedElementInfo
{ {

7
src/Avalonia.Controls/Primitives/OverlayLayer.cs

@ -21,11 +21,8 @@ namespace Avalonia.Controls.Primitives
return null; return null;
} }
public bool HitTest(Point point) public bool HitTest(Point point) => Children.HitTestCustom(point);
{
return Children.Any(ctrl => ctrl.TransformedBounds?.Contains(point) == true);
}
protected override Size ArrangeOverride(Size finalSize) protected override Size ArrangeOverride(Size finalSize)
{ {

2
src/Avalonia.Controls/Primitives/PopupPositioning/IPopupPositioner.cs

@ -306,7 +306,7 @@ namespace Avalonia.Controls.Primitives.PopupPositioning
if (placement == PlacementMode.Pointer) if (placement == PlacementMode.Pointer)
{ {
positionerParameters.AnchorRectangle = new Rect(pointer, new Size(1, 1)); positionerParameters.AnchorRectangle = new Rect(pointer, new Size(1, 1));
positionerParameters.Anchor = PopupPositioningEdge.BottomRight; positionerParameters.Anchor = PopupPositioningEdge.TopLeft;
positionerParameters.Gravity = PopupPositioningEdge.BottomRight; positionerParameters.Gravity = PopupPositioningEdge.BottomRight;
} }
else else

25
src/Avalonia.Controls/Primitives/RangeBase.cs

@ -75,7 +75,10 @@ namespace Avalonia.Controls.Primitives
set set
{ {
ValidateDouble(value, "Minimum"); if (!ValidateDouble(value))
{
return;
}
if (IsInitialized) if (IsInitialized)
{ {
@ -102,7 +105,10 @@ namespace Avalonia.Controls.Primitives
set set
{ {
ValidateDouble(value, "Maximum"); if (!ValidateDouble(value))
{
return;
}
if (IsInitialized) if (IsInitialized)
{ {
@ -129,7 +135,10 @@ namespace Avalonia.Controls.Primitives
set set
{ {
ValidateDouble(value, "Value"); if (!ValidateDouble(value))
{
return;
}
if (IsInitialized) if (IsInitialized)
{ {
@ -164,16 +173,12 @@ namespace Avalonia.Controls.Primitives
} }
/// <summary> /// <summary>
/// Throws an exception if the double value is NaN or Inf. /// Checks if the double value is not inifinity nor NaN.
/// </summary> /// </summary>
/// <param name="value">The value.</param> /// <param name="value">The value.</param>
/// <param name="property">The name of the property being set.</param> private static bool ValidateDouble(double value)
private static void ValidateDouble(double value, string property)
{ {
if (double.IsInfinity(value) || double.IsNaN(value)) return !double.IsInfinity(value) || !double.IsNaN(value);
{
throw new ArgumentException($"{value} is not a valid value for {property}.");
}
} }
/// <summary> /// <summary>

2
src/Avalonia.Controls/Primitives/ScrollBar.cs

@ -151,7 +151,7 @@ namespace Avalonia.Controls.Primitives
if (property == OrientationProperty) if (property == OrientationProperty)
{ {
UpdatePseudoClasses(newValue.ValueOrDefault<Orientation>()); UpdatePseudoClasses(newValue.GetValueOrDefault<Orientation>());
} }
} }

56
src/Avalonia.Controls/Primitives/SelectingItemsControl.cs

@ -5,6 +5,7 @@ using System;
using System.Collections; using System.Collections;
using System.Collections.Generic; using System.Collections.Generic;
using System.Collections.Specialized; using System.Collections.Specialized;
using System.Diagnostics;
using System.Linq; using System.Linq;
using Avalonia.Collections; using Avalonia.Collections;
using Avalonia.Controls.Generators; using Avalonia.Controls.Generators;
@ -13,7 +14,6 @@ using Avalonia.Input;
using Avalonia.Input.Platform; using Avalonia.Input.Platform;
using Avalonia.Interactivity; using Avalonia.Interactivity;
using Avalonia.Logging; using Avalonia.Logging;
using Avalonia.Styling;
using Avalonia.VisualTree; using Avalonia.VisualTree;
namespace Avalonia.Controls.Primitives namespace Avalonia.Controls.Primitives
@ -241,17 +241,14 @@ namespace Avalonia.Controls.Primitives
public override void BeginInit() public override void BeginInit()
{ {
base.BeginInit(); base.BeginInit();
++_updateCount;
_updateSelectedIndex = int.MinValue; InternalBeginInit();
} }
/// <inheritdoc/> /// <inheritdoc/>
public override void EndInit() public override void EndInit()
{ {
if (--_updateCount == 0) InternalEndInit();
{
UpdateFinished();
}
base.EndInit(); base.EndInit();
} }
@ -269,11 +266,20 @@ namespace Avalonia.Controls.Primitives
/// <returns>The container or null if the event did not originate in a container.</returns> /// <returns>The container or null if the event did not originate in a container.</returns>
protected IControl GetContainerFromEventSource(IInteractive eventSource) protected IControl GetContainerFromEventSource(IInteractive eventSource)
{ {
var item = ((IVisual)eventSource).GetSelfAndVisualAncestors() var parent = (IVisual)eventSource;
.OfType<IControl>()
.FirstOrDefault(x => x.LogicalParent == this && ItemContainerGenerator?.IndexFromContainer(x) != -1);
return item; while (parent != null)
{
if (parent is IControl control && control.LogicalParent == this
&& ItemContainerGenerator?.IndexFromContainer(control) != -1)
{
return control;
}
parent = parent.VisualParent;
}
return null;
} }
/// <inheritdoc/> /// <inheritdoc/>
@ -429,7 +435,8 @@ namespace Avalonia.Controls.Primitives
protected override void OnDataContextBeginUpdate() protected override void OnDataContextBeginUpdate()
{ {
base.OnDataContextBeginUpdate(); base.OnDataContextBeginUpdate();
++_updateCount;
InternalBeginInit();
} }
/// <inheritdoc/> /// <inheritdoc/>
@ -437,10 +444,7 @@ namespace Avalonia.Controls.Primitives
{ {
base.OnDataContextEndUpdate(); base.OnDataContextEndUpdate();
if (--_updateCount == 0) InternalEndInit();
{
UpdateFinished();
}
} }
protected override void OnKeyDown(KeyEventArgs e) protected override void OnKeyDown(KeyEventArgs e)
@ -1110,6 +1114,26 @@ namespace Avalonia.Controls.Primitives
} }
} }
private void InternalBeginInit()
{
if (_updateCount == 0)
{
_updateSelectedIndex = int.MinValue;
}
++_updateCount;
}
private void InternalEndInit()
{
Debug.Assert(_updateCount > 0);
if (--_updateCount == 0)
{
UpdateFinished();
}
}
private class Selection : IEnumerable<int> private class Selection : IEnumerable<int>
{ {
private readonly List<int> _list = new List<int>(); private readonly List<int> _list = new List<int>();

125
src/Avalonia.Controls/Primitives/ToggleButton.cs

@ -1,12 +1,20 @@
// Copyright (c) The Avalonia Project. All rights reserved. // 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. // Licensed under the MIT license. See licence.md file in the project root for full license information.
using System;
using Avalonia.Data; using Avalonia.Data;
using Avalonia.Interactivity;
namespace Avalonia.Controls.Primitives namespace Avalonia.Controls.Primitives
{ {
/// <summary>
/// Represents a control that a user can select (check) or clear (uncheck). Base class for controls that can switch states.
/// </summary>
public class ToggleButton : Button public class ToggleButton : Button
{ {
/// <summary>
/// Defines the <see cref="IsChecked"/> property.
/// </summary>
public static readonly DirectProperty<ToggleButton, bool?> IsCheckedProperty = public static readonly DirectProperty<ToggleButton, bool?> IsCheckedProperty =
AvaloniaProperty.RegisterDirect<ToggleButton, bool?>( AvaloniaProperty.RegisterDirect<ToggleButton, bool?>(
nameof(IsChecked), nameof(IsChecked),
@ -15,19 +23,71 @@ namespace Avalonia.Controls.Primitives
unsetValue: null, unsetValue: null,
defaultBindingMode: BindingMode.TwoWay); defaultBindingMode: BindingMode.TwoWay);
/// <summary>
/// Defines the <see cref="IsThreeState"/> property.
/// </summary>
public static readonly StyledProperty<bool> IsThreeStateProperty = public static readonly StyledProperty<bool> IsThreeStateProperty =
AvaloniaProperty.Register<ToggleButton, bool>(nameof(IsThreeState)); AvaloniaProperty.Register<ToggleButton, bool>(nameof(IsThreeState));
/// <summary>
/// Defines the <see cref="Checked"/> event.
/// </summary>
public static readonly RoutedEvent<RoutedEventArgs> CheckedEvent =
RoutedEvent.Register<ToggleButton, RoutedEventArgs>(nameof(Checked), RoutingStrategies.Bubble);
/// <summary>
/// Defines the <see cref="Unchecked"/> event.
/// </summary>
public static readonly RoutedEvent<RoutedEventArgs> UncheckedEvent =
RoutedEvent.Register<ToggleButton, RoutedEventArgs>(nameof(Unchecked), RoutingStrategies.Bubble);
/// <summary>
/// Defines the <see cref="Unchecked"/> event.
/// </summary>
public static readonly RoutedEvent<RoutedEventArgs> IndeterminateEvent =
RoutedEvent.Register<ToggleButton, RoutedEventArgs>(nameof(Indeterminate), RoutingStrategies.Bubble);
private bool? _isChecked = false; private bool? _isChecked = false;
public ToggleButton() public ToggleButton()
{ {
UpdatePseudoClasses(IsChecked); UpdatePseudoClasses(IsChecked);
IsCheckedProperty.Changed.AddClassHandler<ToggleButton>((x, e) => x.OnIsCheckedChanged(e));
}
/// <summary>
/// Raised when a <see cref="ToggleButton"/> is checked.
/// </summary>
public event EventHandler<RoutedEventArgs> Checked
{
add => AddHandler(CheckedEvent, value);
remove => RemoveHandler(CheckedEvent, value);
}
/// <summary>
/// Raised when a <see cref="ToggleButton"/> is unchecked.
/// </summary>
public event EventHandler<RoutedEventArgs> Unchecked
{
add => AddHandler(UncheckedEvent, value);
remove => RemoveHandler(UncheckedEvent, value);
} }
/// <summary>
/// Raised when a <see cref="ToggleButton"/> is neither checked nor unchecked.
/// </summary>
public event EventHandler<RoutedEventArgs> Indeterminate
{
add => AddHandler(IndeterminateEvent, value);
remove => RemoveHandler(IndeterminateEvent, value);
}
/// <summary>
/// Gets or sets whether the <see cref="ToggleButton"/> is checked.
/// </summary>
public bool? IsChecked public bool? IsChecked
{ {
get { return _isChecked; } get => _isChecked;
set set
{ {
SetAndRaise(IsCheckedProperty, ref _isChecked, value); SetAndRaise(IsCheckedProperty, ref _isChecked, value);
@ -35,6 +95,9 @@ namespace Avalonia.Controls.Primitives
} }
} }
/// <summary>
/// Gets or sets a value that indicates whether the control supports three states.
/// </summary>
public bool IsThreeState public bool IsThreeState
{ {
get => GetValue(IsThreeStateProperty); get => GetValue(IsThreeStateProperty);
@ -47,18 +110,78 @@ namespace Avalonia.Controls.Primitives
base.OnClick(); base.OnClick();
} }
/// <summary>
/// Toggles the <see cref="IsChecked"/> property.
/// </summary>
protected virtual void Toggle() protected virtual void Toggle()
{ {
if (IsChecked.HasValue) if (IsChecked.HasValue)
{
if (IsChecked.Value) if (IsChecked.Value)
{
if (IsThreeState) if (IsThreeState)
{
IsChecked = null; IsChecked = null;
}
else else
{
IsChecked = false; IsChecked = false;
}
}
else else
{
IsChecked = true; IsChecked = true;
}
}
else else
{
IsChecked = false; IsChecked = false;
}
}
/// <summary>
/// Called when <see cref="IsChecked"/> becomes true.
/// </summary>
/// <param name="e">Event arguments for the routed event that is raised by the default implementation of this method.</param>
protected virtual void OnChecked(RoutedEventArgs e)
{
RaiseEvent(e);
}
/// <summary>
/// Called when <see cref="IsChecked"/> becomes false.
/// </summary>
/// <param name="e">Event arguments for the routed event that is raised by the default implementation of this method.</param>
protected virtual void OnUnchecked(RoutedEventArgs e)
{
RaiseEvent(e);
}
/// <summary>
/// Called when <see cref="IsChecked"/> becomes null.
/// </summary>
/// <param name="e">Event arguments for the routed event that is raised by the default implementation of this method.</param>
protected virtual void OnIndeterminate(RoutedEventArgs e)
{
RaiseEvent(e);
}
private void OnIsCheckedChanged(AvaloniaPropertyChangedEventArgs e)
{
var newValue = (bool?)e.NewValue;
switch (newValue)
{
case true:
OnChecked(new RoutedEventArgs(CheckedEvent));
break;
case false:
OnUnchecked(new RoutedEventArgs(UncheckedEvent));
break;
default:
OnIndeterminate(new RoutedEventArgs(IndeterminateEvent));
break;
}
} }
private void UpdatePseudoClasses(bool? isChecked) private void UpdatePseudoClasses(bool? isChecked)

2
src/Avalonia.Controls/Primitives/Track.cs

@ -290,7 +290,7 @@ namespace Avalonia.Controls.Primitives
if (property == OrientationProperty) if (property == OrientationProperty)
{ {
UpdatePseudoClasses(newValue.ValueOrDefault<Orientation>()); UpdatePseudoClasses(newValue.GetValueOrDefault<Orientation>());
} }
} }

9
src/Avalonia.Controls/Primitives/VisualLayerManager.cs

@ -1,6 +1,5 @@
using System.Collections.Generic; using System.Collections.Generic;
using Avalonia.LogicalTree; using Avalonia.LogicalTree;
using Avalonia.Styling;
namespace Avalonia.Controls.Primitives namespace Avalonia.Controls.Primitives
{ {
@ -8,7 +7,7 @@ namespace Avalonia.Controls.Primitives
{ {
private const int AdornerZIndex = int.MaxValue - 100; private const int AdornerZIndex = int.MaxValue - 100;
private const int OverlayZIndex = int.MaxValue - 99; private const int OverlayZIndex = int.MaxValue - 99;
private IStyleHost _styleRoot; private ILogicalRoot _logicalRoot;
private readonly List<Control> _layers = new List<Control>(); private readonly List<Control> _layers = new List<Control>();
@ -53,7 +52,7 @@ namespace Avalonia.Controls.Primitives
layer.ZIndex = zindex; layer.ZIndex = zindex;
VisualChildren.Add(layer); VisualChildren.Add(layer);
if (((ILogical)this).IsAttachedToLogicalTree) if (((ILogical)this).IsAttachedToLogicalTree)
((ILogical)layer).NotifyAttachedToLogicalTree(new LogicalTreeAttachmentEventArgs(_styleRoot)); ((ILogical)layer).NotifyAttachedToLogicalTree(new LogicalTreeAttachmentEventArgs(_logicalRoot, layer, this));
InvalidateArrange(); InvalidateArrange();
} }
@ -61,7 +60,7 @@ namespace Avalonia.Controls.Primitives
protected override void OnAttachedToLogicalTree(LogicalTreeAttachmentEventArgs e) protected override void OnAttachedToLogicalTree(LogicalTreeAttachmentEventArgs e)
{ {
base.OnAttachedToLogicalTree(e); base.OnAttachedToLogicalTree(e);
_styleRoot = e.Root; _logicalRoot = e.Root;
foreach (var l in _layers) foreach (var l in _layers)
((ILogical)l).NotifyAttachedToLogicalTree(e); ((ILogical)l).NotifyAttachedToLogicalTree(e);
@ -69,7 +68,7 @@ namespace Avalonia.Controls.Primitives
protected override void OnDetachedFromLogicalTree(LogicalTreeAttachmentEventArgs e) protected override void OnDetachedFromLogicalTree(LogicalTreeAttachmentEventArgs e)
{ {
_styleRoot = null; _logicalRoot = null;
base.OnDetachedFromLogicalTree(e); base.OnDetachedFromLogicalTree(e);
foreach (var l in _layers) foreach (var l in _layers)
((ILogical)l).NotifyDetachedFromLogicalTree(e); ((ILogical)l).NotifyDetachedFromLogicalTree(e);

4
src/Avalonia.Controls/ProgressBar.cs

@ -87,11 +87,11 @@ namespace Avalonia.Controls
if (property == IsIndeterminateProperty) if (property == IsIndeterminateProperty)
{ {
UpdatePseudoClasses(newValue.ValueOrDefault<bool>(), null); UpdatePseudoClasses(newValue.GetValueOrDefault<bool>(), null);
} }
else if (property == OrientationProperty) else if (property == OrientationProperty)
{ {
UpdatePseudoClasses(null, newValue.ValueOrDefault<Orientation>()); UpdatePseudoClasses(null, newValue.GetValueOrDefault<Orientation>());
} }
} }

2
src/Avalonia.Controls/Remote/RemoteWidget.cs

@ -83,7 +83,7 @@ namespace Avalonia.Controls.Remote
Marshal.Copy(_lastFrame.Data, y * _lastFrame.Stride, Marshal.Copy(_lastFrame.Data, y * _lastFrame.Stride,
new IntPtr(l.Address.ToInt64() + l.RowBytes * y), lineLen); new IntPtr(l.Address.ToInt64() + l.RowBytes * y), lineLen);
} }
context.DrawImage(_bitmap, 1, new Rect(0, 0, _bitmap.PixelSize.Width, _bitmap.PixelSize.Height), context.DrawImage(_bitmap, new Rect(0, 0, _bitmap.PixelSize.Width, _bitmap.PixelSize.Height),
new Rect(Bounds.Size)); new Rect(Bounds.Size));
} }
base.Render(context); base.Render(context);

52
src/Avalonia.Controls/Repeater/ItemsRepeater.cs

@ -22,7 +22,7 @@ namespace Avalonia.Controls
/// <summary> /// <summary>
/// Defines the <see cref="HorizontalCacheLength"/> property. /// Defines the <see cref="HorizontalCacheLength"/> property.
/// </summary> /// </summary>
public static readonly AvaloniaProperty<double> HorizontalCacheLengthProperty = public static readonly StyledProperty<double> HorizontalCacheLengthProperty =
AvaloniaProperty.Register<ItemsRepeater, double>(nameof(HorizontalCacheLength), 2.0); AvaloniaProperty.Register<ItemsRepeater, double>(nameof(HorizontalCacheLength), 2.0);
/// <summary> /// <summary>
@ -40,16 +40,16 @@ namespace Avalonia.Controls
/// <summary> /// <summary>
/// Defines the <see cref="Layout"/> property. /// Defines the <see cref="Layout"/> property.
/// </summary> /// </summary>
public static readonly AvaloniaProperty<AttachedLayout> LayoutProperty = public static readonly StyledProperty<AttachedLayout> LayoutProperty =
AvaloniaProperty.Register<ItemsRepeater, AttachedLayout>(nameof(Layout), new StackLayout()); AvaloniaProperty.Register<ItemsRepeater, AttachedLayout>(nameof(Layout), new StackLayout());
/// <summary> /// <summary>
/// Defines the <see cref="VerticalCacheLength"/> property. /// Defines the <see cref="VerticalCacheLength"/> property.
/// </summary> /// </summary>
public static readonly AvaloniaProperty<double> VerticalCacheLengthProperty = public static readonly StyledProperty<double> VerticalCacheLengthProperty =
AvaloniaProperty.Register<ItemsRepeater, double>(nameof(VerticalCacheLength), 2.0); AvaloniaProperty.Register<ItemsRepeater, double>(nameof(VerticalCacheLength), 2.0);
private static readonly AttachedProperty<VirtualizationInfo> VirtualizationInfoProperty = private static readonly StyledProperty<VirtualizationInfo> VirtualizationInfoProperty =
AvaloniaProperty.RegisterAttached<ItemsRepeater, IControl, VirtualizationInfo>("VirtualizationInfo"); AvaloniaProperty.RegisterAttached<ItemsRepeater, IControl, VirtualizationInfo>("VirtualizationInfo");
internal static readonly Rect InvalidRect = new Rect(-1, -1, -1, -1); internal static readonly Rect InvalidRect = new Rect(-1, -1, -1, -1);
@ -379,7 +379,7 @@ namespace Avalonia.Controls
{ {
if (property == ItemsProperty) if (property == ItemsProperty)
{ {
var newEnumerable = newValue.ValueOrDefault<IEnumerable>(); var newEnumerable = newValue.GetValueOrDefault<IEnumerable>();
var newDataSource = newEnumerable as ItemsSourceView; var newDataSource = newEnumerable as ItemsSourceView;
if (newEnumerable != null && newDataSource == null) if (newEnumerable != null && newDataSource == null)
{ {
@ -390,19 +390,19 @@ namespace Avalonia.Controls
} }
else if (property == ItemTemplateProperty) else if (property == ItemTemplateProperty)
{ {
OnItemTemplateChanged(oldValue.ValueOrDefault<IDataTemplate>(), newValue.ValueOrDefault<IDataTemplate>()); OnItemTemplateChanged(oldValue.GetValueOrDefault<IDataTemplate>(), newValue.GetValueOrDefault<IDataTemplate>());
} }
else if (property == LayoutProperty) else if (property == LayoutProperty)
{ {
OnLayoutChanged(oldValue.ValueOrDefault<AttachedLayout>(), newValue.ValueOrDefault<AttachedLayout>()); OnLayoutChanged(oldValue.GetValueOrDefault<AttachedLayout>(), newValue.GetValueOrDefault<AttachedLayout>());
} }
else if (property == HorizontalCacheLengthProperty) else if (property == HorizontalCacheLengthProperty)
{ {
_viewportManager.HorizontalCacheLength = newValue.ValueOrDefault<double>(); _viewportManager.HorizontalCacheLength = newValue.GetValueOrDefault<double>();
} }
else if (property == VerticalCacheLengthProperty) else if (property == VerticalCacheLengthProperty)
{ {
_viewportManager.VerticalCacheLength = newValue.ValueOrDefault<double>(); _viewportManager.VerticalCacheLength = newValue.GetValueOrDefault<double>();
} }
base.OnPropertyChanged(property, oldValue, newValue, priority); base.OnPropertyChanged(property, oldValue, newValue, priority);
@ -559,33 +559,35 @@ namespace Avalonia.Controls
if (Layout != null) if (Layout != null)
{ {
if (Layout is VirtualizingLayout virtualLayout) var args = new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset);
{
var args = new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset);
try
{
_processingItemsSourceChange = args; _processingItemsSourceChange = args;
try if (Layout is VirtualizingLayout virtualLayout)
{ {
virtualLayout.OnItemsChanged(GetLayoutContext(), newValue, args); virtualLayout.OnItemsChanged(GetLayoutContext(), newValue, args);
} }
finally else if (Layout is NonVirtualizingLayout nonVirtualLayout)
{
_processingItemsSourceChange = null;
}
}
else if (Layout is NonVirtualizingLayout nonVirtualLayout)
{
// Walk through all the elements and make sure they are cleared for
// non-virtualizing layouts.
foreach (var element in Children)
{ {
if (GetVirtualizationInfo(element).IsRealized) // Walk through all the elements and make sure they are cleared for
// non-virtualizing layouts.
foreach (var element in Children)
{ {
ClearElementImpl(element); if (GetVirtualizationInfo(element).IsRealized)
{
ClearElementImpl(element);
}
} }
Children.Clear();
} }
} }
finally
{
_processingItemsSourceChange = null;
}
InvalidateMeasure(); InvalidateMeasure();
} }

77
src/Avalonia.Controls/Repeater/ViewManager.cs

@ -109,11 +109,22 @@ namespace Avalonia.Controls
public void ClearElementToElementFactory(IControl element) public void ClearElementToElementFactory(IControl element)
{ {
var virtInfo = ItemsRepeater.GetVirtualizationInfo(element);
var clearedIndex = virtInfo.Index;
_owner.OnElementClearing(element); _owner.OnElementClearing(element);
_owner.ItemTemplateShim.RecycleElement(_owner, element);
if (_owner.ItemTemplateShim != null)
{
_owner.ItemTemplateShim.RecycleElement(_owner, element);
}
else
{
// No ItemTemplate to recycle to, remove the element from the children collection.
if (!_owner.Children.Remove(element))
{
throw new InvalidOperationException("ItemsRepeater's child not found in its Children collection.");
}
}
var virtInfo = ItemsRepeater.GetVirtualizationInfo(element);
virtInfo.MoveOwnershipToElementFactory(); virtInfo.MoveOwnershipToElementFactory();
if (_lastFocusedElement == element) if (_lastFocusedElement == element)
@ -121,9 +132,8 @@ namespace Avalonia.Controls
// Focused element is going away. Remove the tracked last focused element // Focused element is going away. Remove the tracked last focused element
// and pick a reasonable next focus if we can find one within the layout // and pick a reasonable next focus if we can find one within the layout
// realized elements. // realized elements.
MoveFocusFromClearedIndex(clearedIndex); MoveFocusFromClearedIndex(virtInfo.Index);
} }
} }
private void MoveFocusFromClearedIndex(int clearedIndex) private void MoveFocusFromClearedIndex(int clearedIndex)
@ -190,7 +200,8 @@ namespace Avalonia.Controls
{ {
if (virtInfo == null) if (virtInfo == null)
{ {
throw new ArgumentException("Element is not a child of this ItemsRepeater."); //Element is not a child of this ItemsRepeater.
return -1;
} }
return virtInfo.IsRealized || virtInfo.IsInUniqueIdResetPool ? virtInfo.Index : -1; return virtInfo.IsRealized || virtInfo.IsInUniqueIdResetPool ? virtInfo.Index : -1;
@ -515,21 +526,52 @@ namespace Avalonia.Controls
return element; return element;
} }
// There are several cases handled here with respect to which element gets returned and when DataContext is modified.
//
// 1. If there is no ItemTemplate:
// 1.1 If data is an IControl -> the data is returned
// 1.2 If data is not an IControl -> a default DataTemplate is used to fetch element and DataContext is set to data
//
// 2. If there is an ItemTemplate:
// 2.1 If data is not an IControl -> Element is fetched from ElementFactory and DataContext is set to the data
// 2.2 If data is an IControl:
// 2.2.1 If Element returned by the ElementFactory is the same as the data -> Element (a.k.a. data) is returned as is
// 2.2.2 If Element returned by the ElementFactory is not the same as the data
// -> Element that is fetched from the ElementFactory is returned and
// DataContext is set to the data's DataContext (if it exists), otherwise it is set to the data itself
private IControl GetElementFromElementFactory(int index) private IControl GetElementFromElementFactory(int index)
{ {
// The view generator is the provider of last resort. // The view generator is the provider of last resort.
var data = _owner.ItemsSourceView.GetAt(index);
var providedElementFactory = _owner.ItemTemplateShim;
ItemTemplateWrapper GetElementFactory()
{
if (providedElementFactory == null)
{
var factory = FuncDataTemplate.Default;
_owner.ItemTemplate = factory;
return _owner.ItemTemplateShim;
}
var itemTemplateFactory = _owner.ItemTemplateShim; return providedElementFactory;
if (itemTemplateFactory == null) }
IControl GetElement()
{ {
// If no ItemTemplate was provided, use a default if (providedElementFactory == null)
var factory = FuncDataTemplate.Default; {
_owner.ItemTemplate = factory; if (data is IControl dataAsElement)
itemTemplateFactory = _owner.ItemTemplateShim; {
return dataAsElement;
}
}
var elementFactory = GetElementFactory();
return elementFactory.GetElement(_owner, data);
} }
var data = _owner.ItemsSourceView.GetAt(index); var element = GetElement();
var element = itemTemplateFactory.GetElement(_owner, data);
var virtInfo = ItemsRepeater.TryGetVirtualizationInfo(element); var virtInfo = ItemsRepeater.TryGetVirtualizationInfo(element);
if (virtInfo == null) if (virtInfo == null)
@ -537,8 +579,11 @@ namespace Avalonia.Controls
virtInfo = ItemsRepeater.CreateAndInitializeVirtualizationInfo(element); virtInfo = ItemsRepeater.CreateAndInitializeVirtualizationInfo(element);
} }
// Prepare the element if (data != element)
element.DataContext = data; {
// Prepare the element
element.DataContext = data;
}
virtInfo.MoveOwnershipToLayoutFromElementFactory( virtInfo.MoveOwnershipToLayoutFromElementFactory(
index, index,

2
src/Avalonia.Controls/Slider.cs

@ -147,7 +147,7 @@ namespace Avalonia.Controls
if (property == OrientationProperty) if (property == OrientationProperty)
{ {
UpdatePseudoClasses(newValue.ValueOrDefault<Orientation>()); UpdatePseudoClasses(newValue.GetValueOrDefault<Orientation>());
} }
} }

3
src/Avalonia.Controls/Templates/FuncDataTemplate.cs

@ -3,7 +3,6 @@
using System; using System;
using System.Reactive.Linq; using System.Reactive.Linq;
using System.Reflection;
namespace Avalonia.Controls.Templates namespace Avalonia.Controls.Templates
{ {
@ -102,7 +101,7 @@ namespace Avalonia.Controls.Templates
/// </returns> /// </returns>
private static bool IsInstance(object o, Type t) private static bool IsInstance(object o, Type t)
{ {
return (o != null) && t.GetTypeInfo().IsAssignableFrom(o.GetType().GetTypeInfo()); return t.IsInstanceOfType(o);
} }
} }
} }

Some files were not shown because too many files changed in this diff

Loading…
Cancel
Save