diff --git a/Avalonia.sln b/Avalonia.sln
index e40ebae4d6..810ba5c12f 100644
--- a/Avalonia.sln
+++ b/Avalonia.sln
@@ -204,6 +204,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Avalonia.Dialogs", "src\Ava
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Avalonia.FreeDesktop", "src\Avalonia.FreeDesktop\Avalonia.FreeDesktop.csproj", "{4D36CEC8-53F2-40A5-9A37-79AAE356E2DA}"
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
GlobalSection(SharedMSBuildProjectFiles) = preSolution
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|iPhoneSimulator.ActiveCfg = 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
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
@@ -1951,6 +1977,7 @@ Global
{41B02319-965D-4945-8005-C1A3D1224165} = {86C53C40-57AA-45B8-AD42-FAE0EFDF0F2B}
{D775DECB-4E00-4ED5-A75A-5FCE58ADFF0B} = {9B9E3891-2366-4253-A952-D08BCEB71098}
{AF915D5C-AB00-4EA0-B5E6-001F4AE84E68} = {C5A00AC3-B34C-4564-9BDD-2DA473EF4D8B}
+ {351337F5-D66F-461B-A957-4EF60BDB4BA6} = {C5A00AC3-B34C-4564-9BDD-2DA473EF4D8B}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {87366D66-1391-4D90-8999-95A620AD786A}
diff --git a/azure-pipelines.yml b/azure-pipelines.yml
index 603308ef9a..accad63faa 100644
--- a/azure-pipelines.yml
+++ b/azure-pipelines.yml
@@ -34,9 +34,17 @@ jobs:
pool:
vmImage: 'macOS-10.14'
steps:
- - task: DotNetCoreInstaller@0
+ - task: UseDotNet@2
+ displayName: 'Use .NET Core SDK 3.0.x'
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
displayName: 'Install Mono 5.18'
diff --git a/build/HarfBuzzSharp.props b/build/HarfBuzzSharp.props
index f8767c7599..873048ef21 100644
--- a/build/HarfBuzzSharp.props
+++ b/build/HarfBuzzSharp.props
@@ -1,6 +1,6 @@
-
-
+
+
diff --git a/build/SharedVersion.props b/build/SharedVersion.props
index 44d5c239ef..897e70ff81 100644
--- a/build/SharedVersion.props
+++ b/build/SharedVersion.props
@@ -2,7 +2,7 @@
xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
Avalonia
- 0.8.999
+ 0.9.999
Copyright 2019 © The AvaloniaUI Project
https://avaloniaui.net
https://github.com/AvaloniaUI/Avalonia/
diff --git a/build/SkiaSharp.props b/build/SkiaSharp.props
index 796bd8e596..08a9aa3ceb 100644
--- a/build/SkiaSharp.props
+++ b/build/SkiaSharp.props
@@ -1,6 +1,6 @@
-
-
+
+
diff --git a/native/Avalonia.Native/inc/avalonia-native.h b/native/Avalonia.Native/inc/avalonia-native.h
index f1c7664c3e..677fa2f4c5 100644
--- a/native/Avalonia.Native/inc/avalonia-native.h
+++ b/native/Avalonia.Native/inc/avalonia-native.h
@@ -92,9 +92,17 @@ enum AvnRawMouseEventType
RightButtonUp,
MiddleButtonDown,
MiddleButtonUp,
+ XButton1Down,
+ XButton1Up,
+ XButton2Down,
+ XButton2Up,
Move,
Wheel,
- NonClientLeftButtonDown
+ NonClientLeftButtonDown,
+ TouchBegin,
+ TouchUpdate,
+ TouchEnd,
+ TouchCancel
};
enum AvnRawKeyEventType
@@ -112,7 +120,9 @@ enum AvnInputModifiers
Windows = 8,
LeftMouseButton = 16,
RightMouseButton = 32,
- MiddleMouseButton = 64
+ MiddleMouseButton = 64,
+ XButton1MouseButton = 128,
+ XButton2MouseButton = 256
};
enum AvnWindowState
@@ -212,6 +222,10 @@ AVNCOM(IAvnWindowBase, 02) : IUnknown
virtual HRESULT GetSoftwareFramebuffer(AvnFramebuffer*ret) = 0;
virtual HRESULT SetMainMenu(IAvnAppMenu* menu) = 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 void Unlock() = 0;
};
diff --git a/native/Avalonia.Native/src/OSX/app.mm b/native/Avalonia.Native/src/OSX/app.mm
index 81855995b7..5c50aad4cc 100644
--- a/native/Avalonia.Native/src/OSX/app.mm
+++ b/native/Avalonia.Native/src/OSX/app.mm
@@ -1,16 +1,25 @@
#include "common.h"
@interface AvnAppDelegate : NSObject
@end
+
extern NSApplicationActivationPolicy AvnDesiredActivationPolicy = NSApplicationActivationPolicyRegular;
@implementation AvnAppDelegate
- (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
{
- [NSApp activateIgnoringOtherApps:true];
+ [[NSRunningApplication currentApplication] activateWithOptions:NSApplicationActivateIgnoringOtherApps];
}
@end
@@ -20,5 +29,4 @@ extern void InitializeAvnApp()
NSApplication* app = [NSApplication sharedApplication];
id delegate = [AvnAppDelegate new];
[app setDelegate:delegate];
-
}
diff --git a/native/Avalonia.Native/src/OSX/gl.mm b/native/Avalonia.Native/src/OSX/gl.mm
index 083adc927d..feb0643654 100644
--- a/native/Avalonia.Native/src/OSX/gl.mm
+++ b/native/Avalonia.Native/src/OSX/gl.mm
@@ -1,6 +1,7 @@
#include "common.h"
#include
#include
+#include "window.h"
template char (&ArrayCounter(T (&a)[N]))[N];
#define ARRAY_COUNT(a) (sizeof(ArrayCounter(a)))
@@ -181,12 +182,12 @@ extern IAvnGlFeature* GetGlFeature()
class AvnGlRenderingSession : public ComSingleObject
{
- NSView* _view;
- NSWindow* _window;
+ AvnView* _view;
+ AvnWindow* _window;
NSOpenGLContext* _context;
public:
FORWARD_IUNKNOWN()
- AvnGlRenderingSession(NSWindow*window, NSView* view, NSOpenGLContext* context)
+ AvnGlRenderingSession(AvnWindow*window, AvnView* view, NSOpenGLContext* context)
{
_context = context;
_window = window;
@@ -195,14 +196,12 @@ public:
virtual HRESULT GetPixelSize(AvnPixelSize* ret) override
{
- auto fsize = [_view convertSizeToBacking: [_view frame].size];
- ret->Width = (int)fsize.width;
- ret->Height = (int)fsize.height;
+ *ret = [_view getPixelSize];
return S_OK;
}
virtual HRESULT GetScaling(double* ret) override
{
- *ret = [_window backingScaleFactor];
+ *ret = [_window getScaling];
return S_OK;
}
@@ -234,8 +233,17 @@ public:
auto f = GetFeature();
if(f == NULL)
return E_FAIL;
- if(![_view lockFocusIfCanDraw])
+
+ @try
+ {
+ if(![_view lockFocusIfCanDraw])
+ return E_ABORT;
+ }
+ @catch(NSException* exception)
+ {
return E_ABORT;
+ }
+
auto gl = _context;
CGLLockContext([_context CGLContextObj]);
diff --git a/native/Avalonia.Native/src/OSX/window.h b/native/Avalonia.Native/src/OSX/window.h
index 932bc56a2e..3e626675d2 100644
--- a/native/Avalonia.Native/src/OSX/window.h
+++ b/native/Avalonia.Native/src/OSX/window.h
@@ -12,6 +12,7 @@ class WindowBaseImpl;
-(AvnPoint) translateLocalPoint:(AvnPoint)pt;
-(void) setSwRenderedFrame: (AvnFramebuffer* _Nonnull) fb dispose: (IUnknown* _Nonnull) dispose;
-(void) onClosed;
+-(AvnPixelSize) getPixelSize;
@end
@interface AvnWindow : NSWindow
@@ -22,6 +23,7 @@ class WindowBaseImpl;
-(void) restoreParentWindow;
-(bool) shouldTryToHandleEvents;
-(void) applyMenu:(NSMenu *)menu;
+-(double) getScaling;
@end
struct INSWindowHolder
diff --git a/native/Avalonia.Native/src/OSX/window.mm b/native/Avalonia.Native/src/OSX/window.mm
index 0e85332555..3acc5e365b 100644
--- a/native/Avalonia.Native/src/OSX/window.mm
+++ b/native/Avalonia.Native/src/OSX/window.mm
@@ -83,6 +83,54 @@ public:
[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
{
return Window;
@@ -147,7 +195,11 @@ public:
{
@autoreleasepool
{
- [Window close];
+ if (Window != nullptr)
+ {
+ [Window close];
+ }
+
return S_OK;
}
}
@@ -243,7 +295,14 @@ public:
{
@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;
bool _queuedDisplayFromThread;
NSTrackingArea* _area;
- bool _isLeftPressed, _isMiddlePressed, _isRightPressed, _isMouseOver;
+ bool _isLeftPressed, _isMiddlePressed, _isRightPressed, _isXButton1Pressed, _isXButton2Pressed, _isMouseOver;
NSEvent* _lastMouseDownEvent;
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
@@ -691,8 +768,11 @@ NSArray* AllLoopModes = [NSArray arrayWithObjects: NSDefaultRunLoopMode, NSEvent
{
self = [super init];
[self setWantsBestResolutionOpenGLSurface:true];
+ [self setWantsLayer:YES];
_parent = parent;
_area = nullptr;
+ _lastPixelSize.Height = 100;
+ _lastPixelSize.Width = 100;
return self;
}
@@ -734,6 +814,10 @@ NSArray* AllLoopModes = [NSArray arrayWithObjects: NSDefaultRunLoopMode, NSEvent
[self addTrackingArea:_area];
_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});
}
@@ -763,7 +847,13 @@ NSArray* AllLoopModes = [NSArray arrayWithObjects: NSDefaultRunLoopMode, NSEvent
- (void)drawRect:(NSRect)dirtyRect
{
+ if (_parent == nullptr)
+ {
+ return;
+ }
+
_parent->BaseEvents->RunRenderPriorityJobs();
+
@synchronized (self) {
if(_swRenderedFrame != NULL)
{
@@ -830,7 +920,12 @@ NSArray* AllLoopModes = [NSArray arrayWithObjects: NSDefaultRunLoopMode, NSEvent
- (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]);
+
[super viewDidChangeBackingProperties];
}
@@ -893,9 +988,23 @@ NSArray* AllLoopModes = [NSArray arrayWithObjects: NSDefaultRunLoopMode, NSEvent
- (void)otherMouseDown:(NSEvent *)event
{
- _isMiddlePressed = true;
_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
@@ -913,8 +1022,21 @@ NSArray* AllLoopModes = [NSArray arrayWithObjects: NSDefaultRunLoopMode, NSEvent
- (void)otherMouseUp:(NSEvent *)event
{
- _isMiddlePressed = false;
- [self mouseEvent:event withType:MiddleButtonUp];
+ switch(event.buttonNumber)
+ {
+ 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
@@ -1013,6 +1135,10 @@ NSArray* AllLoopModes = [NSArray arrayWithObjects: NSDefaultRunLoopMode, NSEvent
rv |= MiddleMouseButton;
if (_isRightPressed)
rv |= RightMouseButton;
+ if (_isXButton1Pressed)
+ rv |= XButton1MouseButton;
+ if (_isXButton2Pressed)
+ rv |= XButton2MouseButton;
return (AvnInputModifiers)rv;
}
@@ -1081,6 +1207,12 @@ NSArray* AllLoopModes = [NSArray arrayWithObjects: NSDefaultRunLoopMode, NSEvent
bool _closed;
NSMenu* _menu;
bool _isAppMenuApplied;
+ double _lastScaling;
+}
+
+-(double) getScaling
+{
+ return _lastScaling;
}
+(void)closeAll
@@ -1094,10 +1226,6 @@ NSArray* AllLoopModes = [NSArray arrayWithObjects: NSDefaultRunLoopMode, NSEvent
}
}
-- (void)dealloc
-{
-}
-
- (void)pollModalSession:(nonnull NSModalSession)session
{
auto response = [NSApp runModalSession:session];
@@ -1152,6 +1280,12 @@ NSArray* AllLoopModes = [NSArray arrayWithObjects: NSDefaultRunLoopMode, NSEvent
[self setReleasedWhenClosed:false];
_parent = parent;
[self setDelegate:self];
+ _closed = false;
+
+ _lastScaling = [self backingScaleFactor];
+ [self setOpaque:NO];
+ [self setBackgroundColor: [NSColor clearColor]];
+ [self invalidateShadow];
return self;
}
@@ -1167,6 +1301,11 @@ NSArray* AllLoopModes = [NSArray arrayWithObjects: NSDefaultRunLoopMode, NSEvent
return true;
}
+- (void)windowDidChangeBackingProperties:(NSNotification *)notification
+{
+ _lastScaling = [self backingScaleFactor];
+}
+
- (void)windowWillClose:(NSNotification *)notification
{
_closed = true;
@@ -1177,9 +1316,6 @@ NSArray* AllLoopModes = [NSArray arrayWithObjects: NSDefaultRunLoopMode, NSEvent
[self restoreParentWindow];
parent->BaseEvents->Closed();
[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->BaseEvents->PositionChanged(position);
}
-
-// TODO this breaks resizing.
-/*- (void)windowDidResize:(NSNotification *)notification
-{
-
- auto parent = dynamic_cast(_parent.operator->());
-
- if(parent != nullptr)
- {
- parent->WindowStateChanged();
- }
-}*/
@end
class PopupImpl : public virtual WindowBaseImpl, public IAvnPopup
diff --git a/nukebuild/Build.cs b/nukebuild/Build.cs
index dd2f27116d..7b3b8465ce 100644
--- a/nukebuild/Build.cs
+++ b/nukebuild/Build.cs
@@ -191,6 +191,7 @@ partial class Build : NukeBuild
RunCoreTest("./tests/Avalonia.Animation.UnitTests");
RunCoreTest("./tests/Avalonia.Base.UnitTests");
RunCoreTest("./tests/Avalonia.Controls.UnitTests");
+ RunCoreTest("./tests/Avalonia.Controls.DataGrid.UnitTests");
RunCoreTest("./tests/Avalonia.Input.UnitTests");
RunCoreTest("./tests/Avalonia.Interactivity.UnitTests");
RunCoreTest("./tests/Avalonia.Layout.UnitTests");
diff --git a/readme.md b/readme.md
index 512b35a454..97c6509362 100644
--- a/readme.md
+++ b/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).
-Avalonia is delivered via NuGet 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 NuGet 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:
```
diff --git a/samples/ControlCatalog/App.xaml.cs b/samples/ControlCatalog/App.xaml.cs
index 958729e2e8..52a9591c94 100644
--- a/samples/ControlCatalog/App.xaml.cs
+++ b/samples/ControlCatalog/App.xaml.cs
@@ -1,6 +1,4 @@
-using System;
using Avalonia;
-using Avalonia.Controls;
using Avalonia.Controls.ApplicationLifetimes;
using Avalonia.Markup.Xaml;
@@ -19,7 +17,7 @@ namespace ControlCatalog
desktopLifetime.MainWindow = new MainWindow();
else if (ApplicationLifetime is ISingleViewApplicationLifetime singleViewLifetime)
singleViewLifetime.MainView = new MainView();
-
+
base.OnFrameworkInitializationCompleted();
}
}
diff --git a/samples/ControlCatalog/MainView.xaml b/samples/ControlCatalog/MainView.xaml
index 874560a294..cbe2c62890 100644
--- a/samples/ControlCatalog/MainView.xaml
+++ b/samples/ControlCatalog/MainView.xaml
@@ -32,7 +32,11 @@
-
+
+
+
diff --git a/samples/ControlCatalog/Pages/ComboBoxPage.xaml.cs b/samples/ControlCatalog/Pages/ComboBoxPage.xaml.cs
index 3eb6d5b595..d50b051d9f 100644
--- a/samples/ControlCatalog/Pages/ComboBoxPage.xaml.cs
+++ b/samples/ControlCatalog/Pages/ComboBoxPage.xaml.cs
@@ -1,5 +1,7 @@
+using System.Linq;
using Avalonia.Controls;
using Avalonia.Markup.Xaml;
+using Avalonia.Media;
namespace ControlCatalog.Pages
{
@@ -14,7 +16,7 @@ namespace ControlCatalog.Pages
{
AvaloniaXamlLoader.Load(this);
var fontComboBox = this.Find("fontComboBox");
- fontComboBox.Items = Avalonia.Media.FontFamily.SystemFontFamilies;
+ fontComboBox.Items = FontManager.Current.GetInstalledFontFamilyNames().Select(x => new FontFamily(x));
fontComboBox.SelectedIndex = 0;
}
}
diff --git a/samples/ControlCatalog/Pages/ImagePage.xaml b/samples/ControlCatalog/Pages/ImagePage.xaml
index b44fac27cb..9b8f8af765 100644
--- a/samples/ControlCatalog/Pages/ImagePage.xaml
+++ b/samples/ControlCatalog/Pages/ImagePage.xaml
@@ -1,45 +1,52 @@
-
- Image
- Displays an image
-
-
-
- No Stretch
-
-
-
-
- Fill
-
-
+
+
+ Image
+ Displays an image
+
-
- Uniform
-
-
+
+
+
+ Bitmap
+
+ None
+ Fill
+ Uniform
+ UniformToFill
+
+
+
-
- UniformToFill
-
-
-
-
- Window Icon as an Image
-
-
-
+
+ Drawing
+
+ None
+ Fill
+ Uniform
+ UniformToFill
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/samples/ControlCatalog/Pages/ImagePage.xaml.cs b/samples/ControlCatalog/Pages/ImagePage.xaml.cs
index 792b25963e..bbe89d1dfd 100644
--- a/samples/ControlCatalog/Pages/ImagePage.xaml.cs
+++ b/samples/ControlCatalog/Pages/ImagePage.xaml.cs
@@ -1,40 +1,41 @@
-using System.IO;
-using Avalonia;
using Avalonia.Controls;
using Avalonia.Markup.Xaml;
-using Avalonia.Media.Imaging;
+using Avalonia.Media;
namespace ControlCatalog.Pages
{
public class ImagePage : UserControl
{
- private Image iconImage;
+ private readonly Image _bitmapImage;
+ private readonly Image _drawingImage;
+
public ImagePage()
{
- this.InitializeComponent();
+ InitializeComponent();
+ _bitmapImage = this.FindControl("bitmapImage");
+ _drawingImage = this.FindControl("drawingImage");
}
private void InitializeComponent()
{
AvaloniaXamlLoader.Load(this);
- iconImage = this.Get("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 (iconImage.Source == null)
+ if (_drawingImage != null)
{
- var windowRoot = e.Root as Window;
- if (windowRoot != null)
- {
- using (var stream = new MemoryStream())
- {
- windowRoot.Icon.Save(stream);
- stream.Seek(0, SeekOrigin.Begin);
- iconImage.Source = new Bitmap(stream);
- }
- }
+ var comboxBox = (ComboBox)sender;
+ _drawingImage.Stretch = (Stretch)comboxBox.SelectedIndex;
}
}
}
diff --git a/samples/RenderDemo/MainWindow.xaml b/samples/RenderDemo/MainWindow.xaml
index 7f63e7725f..b17520a466 100644
--- a/samples/RenderDemo/MainWindow.xaml
+++ b/samples/RenderDemo/MainWindow.xaml
@@ -41,6 +41,9 @@
+
+
+
diff --git a/samples/RenderDemo/Pages/GlyphRunPage.xaml b/samples/RenderDemo/Pages/GlyphRunPage.xaml
new file mode 100644
index 0000000000..fb3e318a0e
--- /dev/null
+++ b/samples/RenderDemo/Pages/GlyphRunPage.xaml
@@ -0,0 +1,14 @@
+
+
+
+
+
+
diff --git a/samples/RenderDemo/Pages/GlyphRunPage.xaml.cs b/samples/RenderDemo/Pages/GlyphRunPage.xaml.cs
new file mode 100644
index 0000000000..7f15845596
--- /dev/null
+++ b/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");
+
+ 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;
+ }
+ }
+}
diff --git a/samples/RenderDemo/Pages/RenderTargetBitmapPage.cs b/samples/RenderDemo/Pages/RenderTargetBitmapPage.cs
index 3eb2276c48..f263786ab7 100644
--- a/samples/RenderDemo/Pages/RenderTargetBitmapPage.cs
+++ b/samples/RenderDemo/Pages/RenderTargetBitmapPage.cs
@@ -39,7 +39,7 @@ namespace RenderDemo.Pages
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));
Dispatcher.UIThread.Post(InvalidateVisual, DispatcherPriority.Background);
diff --git a/scripts/avalonia-rename.ps1 b/scripts/avalonia-rename.ps1
deleted file mode 100644
index c77dffb55d..0000000000
--- a/scripts/avalonia-rename.ps1
+++ /dev/null
@@ -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 .
diff --git a/src/Avalonia.Animation/Animatable.cs b/src/Avalonia.Animation/Animatable.cs
index ac2fd5b984..cc1ac8ded6 100644
--- a/src/Avalonia.Animation/Animatable.cs
+++ b/src/Avalonia.Animation/Animatable.cs
@@ -85,8 +85,8 @@ namespace Avalonia.Animation
var instance = transition.Apply(
this,
Clock ?? Avalonia.Animation.Clock.GlobalClock,
- oldValue.ValueOrDefault(),
- newValue.ValueOrDefault());
+ oldValue.GetValueOrDefault(),
+ newValue.GetValueOrDefault());
_previousTransitions[property] = instance;
return;
diff --git a/src/Avalonia.Animation/IterationCount.cs b/src/Avalonia.Animation/IterationCount.cs
index e9cd0686d8..9f57455639 100644
--- a/src/Avalonia.Animation/IterationCount.cs
+++ b/src/Avalonia.Animation/IterationCount.cs
@@ -63,7 +63,7 @@ namespace Avalonia.Animation
public IterationType RepeatType => _type;
///
- /// Gets a value that indicates whether the is set to loop.
+ /// Gets a value that indicates whether the is set to Infinite.
///
public bool IsInfinite => _type == IterationType.Infinite;
diff --git a/src/Avalonia.Base/AttachedProperty.cs b/src/Avalonia.Base/AttachedProperty.cs
index fdb04b6dfc..d1df5fa5e3 100644
--- a/src/Avalonia.Base/AttachedProperty.cs
+++ b/src/Avalonia.Base/AttachedProperty.cs
@@ -18,12 +18,14 @@ namespace Avalonia
/// The class that is registering the property.
/// The property metadata.
/// Whether the property inherits its value.
+ /// A value validation callback.
public AttachedProperty(
string name,
- Type ownerType,
+ Type ownerType,
StyledPropertyMetadata metadata,
- bool inherits = false)
- : base(name, ownerType, metadata, inherits)
+ bool inherits = false,
+ Func validate = null)
+ : base(name, ownerType, metadata, inherits, validate)
{
}
diff --git a/src/Avalonia.Base/AvaloniaObject.cs b/src/Avalonia.Base/AvaloniaObject.cs
index e427c64945..ddc3d8d081 100644
--- a/src/Avalonia.Base/AvaloniaObject.cs
+++ b/src/Avalonia.Base/AvaloniaObject.cs
@@ -229,24 +229,6 @@ namespace Avalonia
return property.RouteGetValue(this);
}
- ///
- /// Gets a value.
- ///
- /// The type of the property.
- /// The property.
- /// The value.
- public T GetValue(AvaloniaProperty property)
- {
- property = property ?? throw new ArgumentNullException(nameof(property));
-
- return property switch
- {
- StyledPropertyBase styled => GetValue(styled),
- DirectPropertyBase direct => GetValue(direct),
- _ => throw new NotSupportedException("Unsupported AvaloniaProperty type.")
- };
- }
-
///
/// Gets a value.
///
@@ -322,33 +304,6 @@ namespace Avalonia
property.RouteSetValue(this, value, priority);
}
- ///
- /// Sets a value.
- ///
- /// The type of the property.
- /// The property.
- /// The value.
- /// The priority of the value.
- public void SetValue(
- AvaloniaProperty property,
- T value,
- BindingPriority priority = BindingPriority.LocalValue)
- {
- property = property ?? throw new ArgumentNullException(nameof(property));
-
- switch (property)
- {
- case StyledPropertyBase styled:
- SetValue(styled, value, priority);
- break;
- case DirectPropertyBase direct:
- SetValue(direct, value);
- break;
- default:
- throw new NotSupportedException("Unsupported AvaloniaProperty type.");
- }
- }
-
///
/// Sets a value.
///
@@ -375,7 +330,7 @@ namespace Avalonia
else
{
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))
@@ -399,52 +354,6 @@ namespace Avalonia
SetDirectValueUnchecked(property, value);
}
- ///
- /// Binds a to an observable.
- ///
- /// The property.
- /// The observable.
- /// The priority of the binding.
- ///
- /// A disposable which can be used to terminate the binding.
- ///
- public IDisposable Bind(
- AvaloniaProperty property,
- IObservable> 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);
- }
-
- ///
- /// Binds a to an observable.
- ///
- /// The type of the property.
- /// The property.
- /// The observable.
- /// The priority of the binding.
- ///
- /// A disposable which can be used to terminate the binding.
- ///
- public IDisposable Bind(
- AvaloniaProperty property,
- IObservable> source,
- BindingPriority priority = BindingPriority.LocalValue)
- {
- property = property ?? throw new ArgumentNullException(nameof(property));
- source = source ?? throw new ArgumentNullException(nameof(source));
-
- return property switch
- {
- StyledPropertyBase styled => Bind(styled, source, priority),
- DirectPropertyBase direct => Bind(direct, source),
- _ => throw new NotSupportedException("Unsupported AvaloniaProperty type."),
- };
- }
-
///
/// Binds a to an observable.
///
@@ -531,7 +440,7 @@ namespace Avalonia
Optional oldValue,
Optional newValue)
{
- if (property.Inherits && !IsSet(property))
+ if (property.Inherits && (_values == null || !_values.IsSet(property)))
{
RaisePropertyChanged(property, oldValue, newValue, BindingPriority.LocalValue);
}
diff --git a/src/Avalonia.Base/AvaloniaProperty.cs b/src/Avalonia.Base/AvaloniaProperty.cs
index cd9b40f94a..aa7a675764 100644
--- a/src/Avalonia.Base/AvaloniaProperty.cs
+++ b/src/Avalonia.Base/AvaloniaProperty.cs
@@ -3,9 +3,7 @@
using System;
using System.Collections.Generic;
-using System.Diagnostics;
using System.Reactive.Subjects;
-using System.Reflection;
using Avalonia.Data;
using Avalonia.Utilities;
@@ -285,22 +283,25 @@ namespace Avalonia
/// Whether the property inherits its value.
/// The default binding mode for the property.
/// A value validation callback.
+ /// A value coercion callback.
/// A
public static AttachedProperty RegisterAttached(
string name,
TValue defaultValue = default(TValue),
bool inherits = false,
BindingMode defaultBindingMode = BindingMode.OneWay,
- Func validate = null)
+ Func validate = null,
+ Func coerce = null)
where THost : IAvaloniaObject
{
Contract.Requires(name != null);
var metadata = new StyledPropertyMetadata(
defaultValue,
- defaultBindingMode: defaultBindingMode);
+ defaultBindingMode: defaultBindingMode,
+ coerce: coerce);
- var result = new AttachedProperty(name, typeof(TOwner), metadata, inherits);
+ var result = new AttachedProperty(name, typeof(TOwner), metadata, inherits, validate);
var registry = AvaloniaPropertyRegistry.Instance;
registry.Register(typeof(TOwner), result);
registry.RegisterAttached(typeof(THost), result);
@@ -317,22 +318,27 @@ namespace Avalonia
/// The default value of the property.
/// Whether the property inherits its value.
/// The default binding mode for the property.
+ /// A value validation callback.
+ /// A value coercion callback.
/// A
public static AttachedProperty RegisterAttached(
string name,
Type ownerType,
TValue defaultValue = default(TValue),
bool inherits = false,
- BindingMode defaultBindingMode = BindingMode.OneWay)
+ BindingMode defaultBindingMode = BindingMode.OneWay,
+ Func validate = null,
+ Func coerce = null)
where THost : IAvaloniaObject
{
Contract.Requires(name != null);
var metadata = new StyledPropertyMetadata(
defaultValue,
- defaultBindingMode: defaultBindingMode);
+ defaultBindingMode: defaultBindingMode,
+ coerce: coerce);
- var result = new AttachedProperty(name, ownerType, metadata, inherits);
+ var result = new AttachedProperty(name, ownerType, metadata, inherits, validate);
var registry = AvaloniaPropertyRegistry.Instance;
registry.Register(ownerType, result);
registry.RegisterAttached(typeof(THost), result);
@@ -555,7 +561,7 @@ namespace Avalonia
return result;
}
- currentType = currentType.GetTypeInfo().BaseType;
+ currentType = currentType.BaseType;
}
_metadataCache[type] = _defaultMetadata;
diff --git a/src/Avalonia.Base/AvaloniaPropertyChangedEventArgs`1.cs b/src/Avalonia.Base/AvaloniaPropertyChangedEventArgs`1.cs
index 0c7cd87897..d8ac3752b3 100644
--- a/src/Avalonia.Base/AvaloniaPropertyChangedEventArgs`1.cs
+++ b/src/Avalonia.Base/AvaloniaPropertyChangedEventArgs`1.cs
@@ -60,8 +60,8 @@ namespace Avalonia
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);
}
}
diff --git a/src/Avalonia.Base/AvaloniaPropertyRegistry.cs b/src/Avalonia.Base/AvaloniaPropertyRegistry.cs
index 50ffb51550..29ab10278b 100644
--- a/src/Avalonia.Base/AvaloniaPropertyRegistry.cs
+++ b/src/Avalonia.Base/AvaloniaPropertyRegistry.cs
@@ -224,12 +224,20 @@ namespace Avalonia
Contract.Requires(type != null);
Contract.Requires(name != null);
- if (name.Contains('.'))
+ if (name.Contains("."))
{
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;
}
///
diff --git a/src/Avalonia.Base/Collections/AvaloniaList.cs b/src/Avalonia.Base/Collections/AvaloniaList.cs
index 3c8d4ca7e6..ca51822eed 100644
--- a/src/Avalonia.Base/Collections/AvaloniaList.cs
+++ b/src/Avalonia.Base/Collections/AvaloniaList.cs
@@ -327,6 +327,8 @@ namespace Avalonia.Collections
}
else
{
+ EnsureCapacity(_inner.Count + list.Count);
+
using (IEnumerator en = items.GetEnumerator())
{
int insertIndex = index;
@@ -550,6 +552,24 @@ namespace Avalonia.Collections
///
Delegate[] INotifyCollectionChangedDebug.GetCollectionChangedSubscribers() => _collectionChanged?.GetInvocationList();
+ private void EnsureCapacity(int capacity)
+ {
+ // Adapted from List implementation.
+ var currentCapacity = _inner.Capacity;
+
+ if (currentCapacity < capacity)
+ {
+ var newCapacity = currentCapacity == 0 ? 4 : currentCapacity * 2;
+
+ if (newCapacity < capacity)
+ {
+ newCapacity = capacity;
+ }
+
+ _inner.Capacity = newCapacity;
+ }
+ }
+
///
/// Raises the event with an add action.
///
diff --git a/src/Avalonia.Base/Collections/Pooled/ClearMode.cs b/src/Avalonia.Base/Collections/Pooled/ClearMode.cs
new file mode 100644
index 0000000000..d78ac8feab
--- /dev/null
+++ b/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
+{
+ ///
+ /// 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.
+ ///
+ public enum ClearMode
+ {
+ ///
+ /// Auto has different behavior depending on the host project's target framework.
+ /// .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.
+ /// .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.
+ ///
+ Auto = 0,
+ ///
+ /// The Always setting has the effect of always clearing user types before returning to the pool.
+ /// This is the default behavior on .NET Standard.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.
+ ///
+ Always = 1,
+ ///
+ /// Never will cause pooled collections to never clear user types before returning them to the pool.
+ /// 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.
+ /// 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.
+ ///
+ Never = 2
+ }
+}
diff --git a/src/Avalonia.Base/Collections/Pooled/ICollectionDebugView.cs b/src/Avalonia.Base/Collections/Pooled/ICollectionDebugView.cs
new file mode 100644
index 0000000000..2b15388a13
--- /dev/null
+++ b/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
+ {
+ private readonly ICollection _collection;
+
+ public ICollectionDebugView(ICollection 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;
+ }
+ }
+ }
+}
diff --git a/src/Avalonia.Base/Collections/Pooled/IReadOnlyPooledList.cs b/src/Avalonia.Base/Collections/Pooled/IReadOnlyPooledList.cs
new file mode 100644
index 0000000000..9bc3609dc5
--- /dev/null
+++ b/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
+{
+ ///
+ /// Represents a read-only collection of pooled elements that can be accessed by index
+ ///
+ /// The type of elements in the read-only pooled list.
+
+ public interface IReadOnlyPooledList : IReadOnlyList
+ {
+ ///
+ /// Gets a for the items currently in the collection.
+ ///
+ ReadOnlySpan Span { get; }
+ }
+}
diff --git a/src/Avalonia.Base/Collections/Pooled/PooledList.cs b/src/Avalonia.Base/Collections/Pooled/PooledList.cs
new file mode 100644
index 0000000000..f0d6b292cc
--- /dev/null
+++ b/src/Avalonia.Base/Collections/Pooled/PooledList.cs
@@ -0,0 +1,1531 @@
+// 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.Buffers;
+using System.Collections;
+using System.Collections.Generic;
+using System.Collections.ObjectModel;
+using System.Diagnostics;
+using System.Runtime.CompilerServices;
+using System.Runtime.Serialization;
+using System.Threading;
+
+namespace Avalonia.Collections.Pooled
+{
+ ///
+ /// Implements a variable-size list that uses a pooled array to store the
+ /// elements. A PooledList has a capacity, which is the allocated length
+ /// of the internal array. As elements are added to a PooledList, the capacity
+ /// of the PooledList is automatically increased as required by reallocating the
+ /// internal array.
+ ///
+ ///
+ /// This class is based on the code for but it supports
+ /// and uses when allocating internal arrays.
+ ///
+ [DebuggerDisplay("Count = {Count}")]
+ [DebuggerTypeProxy(typeof(ICollectionDebugView<>))]
+ [Serializable]
+ public class PooledList : IList, IReadOnlyPooledList, IList, IDisposable, IDeserializationCallback
+ {
+ // internal constant copied from Array.MaxArrayLength
+ private const int MaxArrayLength = 0x7FEFFFFF;
+ private const int DefaultCapacity = 4;
+ private static readonly T[] s_emptyArray = Array.Empty();
+
+ [NonSerialized]
+ private ArrayPool _pool;
+ [NonSerialized]
+ private object _syncRoot;
+
+ private T[] _items; // Do not rename (binary serialization)
+ private int _size; // Do not rename (binary serialization)
+ private int _version; // Do not rename (binary serialization)
+ private readonly bool _clearOnFree;
+
+ #region Constructors
+
+ ///
+ /// Constructs a PooledList. The list is initially empty and has a capacity
+ /// of zero. Upon adding the first element to the list the capacity is
+ /// increased to DefaultCapacity, and then increased in multiples of two
+ /// as required.
+ ///
+ public PooledList() : this(ClearMode.Auto, ArrayPool.Shared) { }
+
+ ///
+ /// Constructs a PooledList. The list is initially empty and has a capacity
+ /// of zero. Upon adding the first element to the list the capacity is
+ /// increased to DefaultCapacity, and then increased in multiples of two
+ /// as required.
+ ///
+ public PooledList(ClearMode clearMode) : this(clearMode, ArrayPool.Shared) { }
+
+ ///
+ /// Constructs a PooledList. The list is initially empty and has a capacity
+ /// of zero. Upon adding the first element to the list the capacity is
+ /// increased to DefaultCapacity, and then increased in multiples of two
+ /// as required.
+ ///
+ public PooledList(ArrayPool customPool) : this(ClearMode.Auto, customPool) { }
+
+ ///
+ /// Constructs a PooledList. The list is initially empty and has a capacity
+ /// of zero. Upon adding the first element to the list the capacity is
+ /// increased to DefaultCapacity, and then increased in multiples of two
+ /// as required.
+ ///
+ public PooledList(ClearMode clearMode, ArrayPool customPool)
+ {
+ _items = s_emptyArray;
+ _pool = customPool ?? ArrayPool.Shared;
+ _clearOnFree = ShouldClear(clearMode);
+ }
+
+ ///
+ /// Constructs a List with a given initial capacity. The list is
+ /// initially empty, but will have room for the given number of elements
+ /// before any reallocations are required.
+ ///
+ public PooledList(int capacity) : this(capacity, ClearMode.Auto, ArrayPool.Shared) { }
+
+ ///
+ /// Constructs a List with a given initial capacity. The list is
+ /// initially empty, but will have room for the given number of elements
+ /// before any reallocations are required.
+ ///
+ public PooledList(int capacity, bool sizeToCapacity) : this(capacity, ClearMode.Auto, ArrayPool.Shared, sizeToCapacity) { }
+
+ ///
+ /// Constructs a List with a given initial capacity. The list is
+ /// initially empty, but will have room for the given number of elements
+ /// before any reallocations are required.
+ ///
+ public PooledList(int capacity, ClearMode clearMode) : this(capacity, clearMode, ArrayPool.Shared) { }
+
+ ///
+ /// Constructs a List with a given initial capacity. The list is
+ /// initially empty, but will have room for the given number of elements
+ /// before any reallocations are required.
+ ///
+ public PooledList(int capacity, ClearMode clearMode, bool sizeToCapacity) : this(capacity, clearMode, ArrayPool.Shared, sizeToCapacity) { }
+
+ ///
+ /// Constructs a List with a given initial capacity. The list is
+ /// initially empty, but will have room for the given number of elements
+ /// before any reallocations are required.
+ ///
+ public PooledList(int capacity, ArrayPool customPool) : this(capacity, ClearMode.Auto, customPool) { }
+
+ ///
+ /// Constructs a List with a given initial capacity. The list is
+ /// initially empty, but will have room for the given number of elements
+ /// before any reallocations are required.
+ ///
+ public PooledList(int capacity, ArrayPool customPool, bool sizeToCapacity) : this(capacity, ClearMode.Auto, customPool, sizeToCapacity) { }
+
+ ///
+ /// Constructs a List with a given initial capacity. The list is
+ /// initially empty, but will have room for the given number of elements
+ /// before any reallocations are required.
+ ///
+ public PooledList(int capacity, ClearMode clearMode, ArrayPool customPool) : this(capacity, clearMode, customPool, false) { }
+
+ ///
+ /// Constructs a List with a given initial capacity. The list is
+ /// initially empty, but will have room for the given number of elements
+ /// before any reallocations are required.
+ ///
+ /// If true, Count of list equals capacity. Depending on ClearMode, rented items may or may not hold dirty values.
+ public PooledList(int capacity, ClearMode clearMode, ArrayPool customPool, bool sizeToCapacity)
+ {
+ if (capacity < 0)
+ ThrowHelper.ThrowArgumentOutOfRangeException(ExceptionArgument.capacity, ExceptionResource.ArgumentOutOfRange_NeedNonNegNum);
+
+ _pool = customPool ?? ArrayPool.Shared;
+ _clearOnFree = ShouldClear(clearMode);
+
+ if (capacity == 0)
+ {
+ _items = s_emptyArray;
+ }
+ else
+ {
+ _items = _pool.Rent(capacity);
+ }
+
+ if (sizeToCapacity)
+ {
+ _size = capacity;
+ if (clearMode != ClearMode.Never)
+ {
+ Array.Clear(_items, 0, _size);
+ }
+ }
+ }
+
+ ///
+ /// Constructs a PooledList, copying the contents of the given collection. The
+ /// size and capacity of the new list will both be equal to the size of the
+ /// given collection.
+ ///
+ public PooledList(T[] array) : this(array.AsSpan(), ClearMode.Auto, ArrayPool.Shared) { }
+
+ ///
+ /// Constructs a PooledList, copying the contents of the given collection. The
+ /// size and capacity of the new list will both be equal to the size of the
+ /// given collection.
+ ///
+ public PooledList(T[] array, ClearMode clearMode) : this(array.AsSpan(), clearMode, ArrayPool.Shared) { }
+
+ ///
+ /// Constructs a PooledList, copying the contents of the given collection. The
+ /// size and capacity of the new list will both be equal to the size of the
+ /// given collection.
+ ///
+ public PooledList(T[] array, ArrayPool customPool) : this(array.AsSpan(), ClearMode.Auto, customPool) { }
+
+ ///
+ /// Constructs a PooledList, copying the contents of the given collection. The
+ /// size and capacity of the new list will both be equal to the size of the
+ /// given collection.
+ ///
+ public PooledList(T[] array, ClearMode clearMode, ArrayPool customPool) : this(array.AsSpan(), clearMode, customPool) { }
+
+ ///
+ /// Constructs a PooledList, copying the contents of the given collection. The
+ /// size and capacity of the new list will both be equal to the size of the
+ /// given collection.
+ ///
+ public PooledList(ReadOnlySpan span) : this(span, ClearMode.Auto, ArrayPool.Shared) { }
+
+ ///
+ /// Constructs a PooledList, copying the contents of the given collection. The
+ /// size and capacity of the new list will both be equal to the size of the
+ /// given collection.
+ ///
+ public PooledList(ReadOnlySpan span, ClearMode clearMode) : this(span, clearMode, ArrayPool.Shared) { }
+
+ ///
+ /// Constructs a PooledList, copying the contents of the given collection. The
+ /// size and capacity of the new list will both be equal to the size of the
+ /// given collection.
+ ///
+ public PooledList(ReadOnlySpan span, ArrayPool customPool) : this(span, ClearMode.Auto, customPool) { }
+
+ ///
+ /// Constructs a PooledList, copying the contents of the given collection. The
+ /// size and capacity of the new list will both be equal to the size of the
+ /// given collection.
+ ///
+ public PooledList(ReadOnlySpan span, ClearMode clearMode, ArrayPool customPool)
+ {
+ _pool = customPool ?? ArrayPool.Shared;
+ _clearOnFree = ShouldClear(clearMode);
+
+ int count = span.Length;
+ if (count == 0)
+ {
+ _items = s_emptyArray;
+ }
+ else
+ {
+ _items = _pool.Rent(count);
+ span.CopyTo(_items);
+ _size = count;
+ }
+ }
+
+ ///
+ /// Constructs a PooledList, copying the contents of the given collection. The
+ /// size and capacity of the new list will both be equal to the size of the
+ /// given collection.
+ ///
+ public PooledList(IEnumerable collection) : this(collection, ClearMode.Auto, ArrayPool.Shared) { }
+
+ ///
+ /// Constructs a PooledList, copying the contents of the given collection. The
+ /// size and capacity of the new list will both be equal to the size of the
+ /// given collection.
+ ///
+ public PooledList(IEnumerable collection, ClearMode clearMode) : this(collection, clearMode, ArrayPool.Shared) { }
+
+ ///
+ /// Constructs a PooledList, copying the contents of the given collection. The
+ /// size and capacity of the new list will both be equal to the size of the
+ /// given collection.
+ ///
+ public PooledList(IEnumerable collection, ArrayPool customPool) : this(collection, ClearMode.Auto, customPool) { }
+
+ ///
+ /// Constructs a PooledList, copying the contents of the given collection. The
+ /// size and capacity of the new list will both be equal to the size of the
+ /// given collection.
+ ///
+ public PooledList(IEnumerable collection, ClearMode clearMode, ArrayPool customPool)
+ {
+ _pool = customPool ?? ArrayPool.Shared;
+ _clearOnFree = ShouldClear(clearMode);
+
+ switch (collection)
+ {
+ case null:
+ ThrowHelper.ThrowArgumentNullException(ExceptionArgument.collection);
+ break;
+
+ case ICollection c:
+ int count = c.Count;
+ if (count == 0)
+ {
+ _items = s_emptyArray;
+ }
+ else
+ {
+ _items = _pool.Rent(count);
+ c.CopyTo(_items, 0);
+ _size = count;
+ }
+ break;
+
+ default:
+ _size = 0;
+ _items = s_emptyArray;
+ using (var en = collection.GetEnumerator())
+ {
+ while (en.MoveNext())
+ Add(en.Current);
+ }
+ break;
+ }
+ }
+
+ #endregion
+
+ ///
+ /// Gets a for the items currently in the collection.
+ ///
+ public Span Span => _items.AsSpan(0, _size);
+
+ ///
+ ReadOnlySpan IReadOnlyPooledList.Span => Span;
+
+ ///
+ /// Gets and sets the capacity of this list. The capacity is the size of
+ /// the internal array used to hold items. When set, the internal
+ /// Memory of the list is reallocated to the given capacity.
+ /// Note that the return value for this property may be larger than the property was set to.
+ ///
+ public int Capacity
+ {
+ get => _items.Length;
+ set
+ {
+ if (value < _size)
+ {
+ ThrowHelper.ThrowArgumentOutOfRangeException(ExceptionArgument.value, ExceptionResource.ArgumentOutOfRange_SmallCapacity);
+ }
+
+ if (value != _items.Length)
+ {
+ if (value > 0)
+ {
+ var newItems = _pool.Rent(value);
+ if (_size > 0)
+ {
+ Array.Copy(_items, newItems, _size);
+ }
+ ReturnArray();
+ _items = newItems;
+ }
+ else
+ {
+ ReturnArray();
+ _size = 0;
+ }
+ }
+ }
+ }
+
+ ///
+ /// Read-only property describing how many elements are in the List.
+ ///
+ public int Count => _size;
+
+ ///
+ /// Returns the ClearMode behavior for the collection, denoting whether values are
+ /// cleared from internal arrays before returning them to the pool.
+ ///
+ public ClearMode ClearMode => _clearOnFree ? ClearMode.Always : ClearMode.Never;
+
+ bool IList.IsFixedSize => false;
+
+ bool ICollection.IsReadOnly => false;
+
+ bool IList.IsReadOnly => false;
+
+ int ICollection.Count => _size;
+
+ bool ICollection.IsSynchronized => false;
+
+ // Synchronization root for this object.
+ object ICollection.SyncRoot
+ {
+ get
+ {
+ if (_syncRoot == null)
+ {
+ Interlocked.CompareExchange
+ ///
+ /// This class is very similar to , but where
+ /// 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.
+ ///
public class BindingNotification
{
///
diff --git a/src/Avalonia.Base/Data/BindingOperations.cs b/src/Avalonia.Base/Data/BindingOperations.cs
index 653388958a..1b47cc7490 100644
--- a/src/Avalonia.Base/Data/BindingOperations.cs
+++ b/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.
using System;
-using System.Linq;
using System.Reactive.Disposables;
using System.Reactive.Linq;
using Avalonia.Reactive;
@@ -57,22 +56,37 @@ namespace Avalonia.Data
if (source != null)
{
+ // Perf: Avoid allocating closure in the outer scope.
+ var targetCopy = target;
+ var propertyCopy = property;
+ var bindingCopy = binding;
+
return source
.Where(x => BindingNotification.ExtractValue(x) != AvaloniaProperty.UnsetValue)
.Take(1)
- .Subscribe(x => target.SetValue(property, x, binding.Priority));
+ .Subscribe(x => targetCopy.SetValue(
+ propertyCopy,
+ BindingNotification.ExtractValue(x),
+ bindingCopy.Priority));
}
else
{
target.SetValue(property, binding.Value, binding.Priority);
return Disposable.Empty;
}
+
case BindingMode.OneWayToSource:
+ {
+ // Perf: Avoid allocating closure in the outer scope.
+ var bindingCopy = binding;
+
return Observable.CombineLatest(
binding.Observable,
target.GetObservable(property),
(_, v) => v)
- .Subscribe(x => binding.Subject.OnNext(x));
+ .Subscribe(x => bindingCopy.Subject.OnNext(x));
+ }
+
default:
throw new ArgumentException("Invalid binding mode.");
}
diff --git a/src/Avalonia.Base/Data/BindingValue.cs b/src/Avalonia.Base/Data/BindingValue.cs
index 50e71cf3fc..cecdd33e7b 100644
--- a/src/Avalonia.Base/Data/BindingValue.cs
+++ b/src/Avalonia.Base/Data/BindingValue.cs
@@ -8,6 +8,7 @@ namespace Avalonia.Data
///
/// Describes the type of a .
///
+ [Flags]
public enum BindingValueType
{
///
@@ -135,10 +136,10 @@ namespace Avalonia.Data
/// Converts the binding value to an .
///
///
- public Optional ToOptional() => HasValue ? new Optional(Value) : default;
+ public Optional ToOptional() => HasValue ? new Optional(_value) : default;
///
- public override string ToString() => HasError ? $"Error: {Error!.Message}" : Value?.ToString() ?? "(null)";
+ public override string ToString() => HasError ? $"Error: {Error!.Message}" : _value?.ToString() ?? "(null)";
///
/// Converts the value to untyped representation, using ,
@@ -152,7 +153,7 @@ namespace Avalonia.Data
{
BindingValueType.UnsetValue => AvaloniaProperty.UnsetValue,
BindingValueType.DoNothing => BindingOperations.DoNothing,
- BindingValueType.Value => Value,
+ BindingValueType.Value => _value,
BindingValueType.BindingError =>
new BindingNotification(Error, BindingErrorType.Error),
BindingValueType.BindingErrorWithFallback =>
@@ -161,7 +162,7 @@ namespace Avalonia.Data
new BindingNotification(Error, BindingErrorType.DataValidationError),
BindingValueType.DataValidationErrorWithFallback =>
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(type | BindingValueType.HasValue, value, Error);
}
+ ///
+ /// Gets the value of the binding value if present, otherwise the default value.
+ ///
+ /// The value.
+ public T GetValueOrDefault() => HasValue ? _value : default;
+
///
/// Gets the value of the binding value if present, otherwise a default value.
///
/// The default value.
/// The value.
- public T ValueOrDefault(T defaultValue = default) => HasValue ? Value : defaultValue;
+ public T GetValueOrDefault(T defaultValue) => HasValue ? _value : defaultValue;
+
+ ///
+ /// Gets the value if present, otherwise the default value.
+ ///
+ ///
+ /// The value if present and of the correct type, `default(TResult)` if the value is
+ /// not present or of an incorrect type.
+ ///
+ public TResult GetValueOrDefault()
+ {
+ return HasValue ?
+ _value is TResult result ? result : default
+ : default;
+ }
///
/// 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 if the
/// value is not present.
///
- public TResult ValueOrDefault(TResult defaultValue = default)
+ public TResult GetValueOrDefault(TResult defaultValue)
{
return HasValue ?
- Value is TResult result ? result : default
+ _value is TResult result ? result : default
: defaultValue;
}
diff --git a/src/Avalonia.Base/Data/Core/BindingExpression.cs b/src/Avalonia.Base/Data/Core/BindingExpression.cs
index 986e2cf012..9eec5d6b2b 100644
--- a/src/Avalonia.Base/Data/Core/BindingExpression.cs
+++ b/src/Avalonia.Base/Data/Core/BindingExpression.cs
@@ -21,6 +21,7 @@ namespace Avalonia.Data.Core
private readonly ExpressionObserver _inner;
private readonly Type _targetType;
private readonly object _fallbackValue;
+ private readonly object _targetNullValue;
private readonly BindingPriority _priority;
InnerListener _innerListener;
WeakReference _value;
@@ -51,7 +52,7 @@ namespace Avalonia.Data.Core
IValueConverter converter,
object converterParameter = null,
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
///
/// The value to use when the binding is unable to produce a value.
///
+ ///
+ /// The value to use when the binding result is null.
+ ///
/// The value converter to use.
///
/// A parameter to pass to .
@@ -72,6 +76,7 @@ namespace Avalonia.Data.Core
ExpressionObserver inner,
Type targetType,
object fallbackValue,
+ object targetNullValue,
IValueConverter converter,
object converterParameter = null,
BindingPriority priority = BindingPriority.LocalValue)
@@ -85,6 +90,7 @@ namespace Avalonia.Data.Core
Converter = converter;
ConverterParameter = converterParameter;
_fallbackValue = fallbackValue;
+ _targetNullValue = targetNullValue;
_priority = priority;
}
@@ -196,6 +202,11 @@ namespace Avalonia.Data.Core
///
private object ConvertValue(object value)
{
+ if (value == null && _targetNullValue != AvaloniaProperty.UnsetValue)
+ {
+ return _targetNullValue;
+ }
+
if (value == BindingOperations.DoNothing)
{
return value;
diff --git a/src/Avalonia.Base/Data/Core/ExpressionObserver.cs b/src/Avalonia.Base/Data/Core/ExpressionObserver.cs
index 7060fd3451..91a27be634 100644
--- a/src/Avalonia.Base/Data/Core/ExpressionObserver.cs
+++ b/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
/// the reading and subscription of property values on a type.
///
- public static readonly IList PropertyAccessors =
+ public static readonly List PropertyAccessors =
new List
{
new AvaloniaPropertyAccessorPlugin(),
@@ -33,7 +33,7 @@ namespace Avalonia.Data.Core
/// An ordered collection of validation checker plugins that can be used to customize
/// the validation of view model and model data.
///
- public static readonly IList DataValidators =
+ public static readonly List DataValidators =
new List
{
new DataAnnotationsValidationPlugin(),
@@ -45,7 +45,7 @@ namespace Avalonia.Data.Core
/// An ordered collection of stream plugins that can be used to customize the behavior
/// of the '^' stream binding operator.
///
- public static readonly IList StreamHandlers =
+ public static readonly List StreamHandlers =
new List
{
new TaskStreamPlugin(),
diff --git a/src/Avalonia.Base/Data/Core/Plugins/AvaloniaPropertyAccessorPlugin.cs b/src/Avalonia.Base/Data/Core/Plugins/AvaloniaPropertyAccessorPlugin.cs
index ab4a109cc2..fa72235a89 100644
--- a/src/Avalonia.Base/Data/Core/Plugins/AvaloniaPropertyAccessorPlugin.cs
+++ b/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.
using System;
-using System.Reactive.Linq;
+using System.Runtime.ExceptionServices;
namespace Avalonia.Data.Core.Plugins
{
@@ -76,7 +76,7 @@ namespace Avalonia.Data.Core.Plugins
return false;
}
- private class Accessor : PropertyAccessorBase
+ private class Accessor : PropertyAccessorBase, IObserver
{
private readonly WeakReference _reference;
private readonly AvaloniaProperty _property;
@@ -117,7 +117,7 @@ namespace Avalonia.Data.Core.Plugins
protected override void SubscribeCore()
{
- _subscription = Instance?.GetObservable(_property).Subscribe(PublishValue);
+ _subscription = Instance?.GetObservable(_property).Subscribe(this);
}
protected override void UnsubscribeCore()
@@ -125,6 +125,20 @@ namespace Avalonia.Data.Core.Plugins
_subscription?.Dispose();
_subscription = null;
}
+
+ void IObserver.OnCompleted()
+ {
+ }
+
+ void IObserver.OnError(Exception error)
+ {
+ ExceptionDispatchInfo.Capture(error).Throw();
+ }
+
+ void IObserver.OnNext(object value)
+ {
+ PublishValue(value);
+ }
}
}
}
diff --git a/src/Avalonia.Base/Data/Core/PropertyAccessorNode.cs b/src/Avalonia.Base/Data/Core/PropertyAccessorNode.cs
index 70f53b8b88..45b7207413 100644
--- a/src/Avalonia.Base/Data/Core/PropertyAccessorNode.cs
+++ b/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.
using System;
-using System.Linq;
-using System.Reactive.Linq;
using Avalonia.Data.Core.Plugins;
namespace Avalonia.Data.Core
@@ -41,7 +39,17 @@ namespace Avalonia.Data.Core
{
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);
if (_enableValidation && Next == null)
diff --git a/src/Avalonia.Base/Data/Optional.cs b/src/Avalonia.Base/Data/Optional.cs
index 3fbcd356ff..dd952c895c 100644
--- a/src/Avalonia.Base/Data/Optional.cs
+++ b/src/Avalonia.Base/Data/Optional.cs
@@ -54,23 +54,43 @@ namespace Avalonia.Data
public bool Equals(Optional other) => this == other;
///
- public override int GetHashCode() => HasValue ? Value!.GetHashCode() : 0;
+ public override int GetHashCode() => HasValue ? _value?.GetHashCode() ?? 0 : 0;
///
/// Casts the value (if any) to an .
///
/// The cast optional value.
- public Optional ToObject() => HasValue ? new Optional(Value) : default;
+ public Optional ToObject() => HasValue ? new Optional(_value) : default;
///
- public override string ToString() => HasValue ? Value?.ToString() ?? "(null)" : "(empty)";
+ public override string ToString() => HasValue ? _value?.ToString() ?? "(null)" : "(empty)";
+
+ ///
+ /// Gets the value if present, otherwise the default value.
+ ///
+ /// The value.
+ public T GetValueOrDefault() => HasValue ? _value : default;
///
/// Gets the value if present, otherwise a default value.
///
/// The default value.
/// The value.
- public T ValueOrDefault(T defaultValue = default) => HasValue ? Value : defaultValue;
+ public T GetValueOrDefault(T defaultValue) => HasValue ? _value : defaultValue;
+
+ ///
+ /// Gets the value if present, otherwise the default value.
+ ///
+ ///
+ /// The value if present and of the correct type, `default(TResult)` if the value is
+ /// not present or of an incorrect type.
+ ///
+ public TResult GetValueOrDefault()
+ {
+ return HasValue ?
+ _value is TResult result ? result : default
+ : default;
+ }
///
/// 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 if the
/// value is not present.
///
- public TResult ValueOrDefault(TResult defaultValue = default)
+ public TResult GetValueOrDefault(TResult defaultValue)
{
return HasValue ?
- Value is TResult result ? result : default
+ _value is TResult result ? result : default
: defaultValue;
}
diff --git a/src/Avalonia.Base/IAvaloniaObject.cs b/src/Avalonia.Base/IAvaloniaObject.cs
index 1ccdaa8f0b..fb85ae222c 100644
--- a/src/Avalonia.Base/IAvaloniaObject.cs
+++ b/src/Avalonia.Base/IAvaloniaObject.cs
@@ -118,7 +118,7 @@ namespace Avalonia
///
/// The inheritance child.
///
- /// Inheritance children will recieve a call to
+ /// Inheritance children will receive a call to
///
/// when an inheritable property value changes on the parent.
///
diff --git a/src/Avalonia.Base/Platform/IMacOSTopLevelPlatformHandle.cs b/src/Avalonia.Base/Platform/IMacOSTopLevelPlatformHandle.cs
new file mode 100644
index 0000000000..837ea1abcd
--- /dev/null
+++ b/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();
+ }
+}
diff --git a/src/Avalonia.Base/PropertyStore/BindingEntry.cs b/src/Avalonia.Base/PropertyStore/BindingEntry.cs
index a53dda2aef..09a0f169df 100644
--- a/src/Avalonia.Base/PropertyStore/BindingEntry.cs
+++ b/src/Avalonia.Base/PropertyStore/BindingEntry.cs
@@ -6,10 +6,17 @@ using Avalonia.Threading;
namespace Avalonia.PropertyStore
{
+ ///
+ /// Represents an untyped interface to .
+ ///
internal interface IBindingEntry : IPriorityValueEntry, IDisposable
{
}
+ ///
+ /// Stores a binding in a or .
+ ///
+ /// The property type.
internal class BindingEntry : IBindingEntry, IPriorityValueEntry, IObserver>
{
private readonly IAvaloniaObject _owner;
diff --git a/src/Avalonia.Base/PropertyStore/ConstantValueEntry.cs b/src/Avalonia.Base/PropertyStore/ConstantValueEntry.cs
index bc75eac4ef..f15f56e32b 100644
--- a/src/Avalonia.Base/PropertyStore/ConstantValueEntry.cs
+++ b/src/Avalonia.Base/PropertyStore/ConstantValueEntry.cs
@@ -5,6 +5,11 @@ using Avalonia.Data;
namespace Avalonia.PropertyStore
{
+ ///
+ /// Stores a value with a priority in a or
+ /// .
+ ///
+ /// The property type.
internal class ConstantValueEntry : IPriorityValueEntry
{
public ConstantValueEntry(
@@ -19,7 +24,7 @@ namespace Avalonia.PropertyStore
public StyledPropertyBase Property { get; }
public BindingPriority Priority { get; }
- public Optional Value { get; private set; }
+ public Optional Value { get; }
Optional IValue.Value => Value.ToObject();
BindingPriority IValue.ValuePriority => Priority;
diff --git a/src/Avalonia.Base/PropertyStore/IPriorityValueEntry.cs b/src/Avalonia.Base/PropertyStore/IPriorityValueEntry.cs
index 8e239e03c9..6ed6c2ef52 100644
--- a/src/Avalonia.Base/PropertyStore/IPriorityValueEntry.cs
+++ b/src/Avalonia.Base/PropertyStore/IPriorityValueEntry.cs
@@ -5,6 +5,9 @@ using Avalonia.Data;
namespace Avalonia.PropertyStore
{
+ ///
+ /// Represents an untyped interface to .
+ ///
internal interface IPriorityValueEntry : IValue
{
BindingPriority Priority { get; }
@@ -12,6 +15,10 @@ namespace Avalonia.PropertyStore
void Reparent(IValueSink sink);
}
+ ///
+ /// Represents an object that can act as an entry in a .
+ ///
+ /// The property type.
internal interface IPriorityValueEntry : IPriorityValueEntry, IValue
{
}
diff --git a/src/Avalonia.Base/PropertyStore/IValue.cs b/src/Avalonia.Base/PropertyStore/IValue.cs
index 7d1eaa337f..0ce7fb8308 100644
--- a/src/Avalonia.Base/PropertyStore/IValue.cs
+++ b/src/Avalonia.Base/PropertyStore/IValue.cs
@@ -4,12 +4,19 @@
namespace Avalonia.PropertyStore
{
+ ///
+ /// Represents an untyped interface to .
+ ///
internal interface IValue
{
Optional Value { get; }
BindingPriority ValuePriority { get; }
}
+ ///
+ /// Represents an object that can act as an entry in a .
+ ///
+ /// The property type.
internal interface IValue : IValue
{
new Optional Value { get; }
diff --git a/src/Avalonia.Base/PropertyStore/IValueSink.cs b/src/Avalonia.Base/PropertyStore/IValueSink.cs
index faccd9e75a..223b0058c1 100644
--- a/src/Avalonia.Base/PropertyStore/IValueSink.cs
+++ b/src/Avalonia.Base/PropertyStore/IValueSink.cs
@@ -1,10 +1,12 @@
-using System;
-using Avalonia.Data;
+using Avalonia.Data;
#nullable enable
namespace Avalonia.PropertyStore
{
+ ///
+ /// Represents an entity that can receive change notifications in a .
+ ///
internal interface IValueSink
{
void ValueChanged(
diff --git a/src/Avalonia.Base/PropertyStore/LocalValueEntry.cs b/src/Avalonia.Base/PropertyStore/LocalValueEntry.cs
index 067ed7b966..22258390da 100644
--- a/src/Avalonia.Base/PropertyStore/LocalValueEntry.cs
+++ b/src/Avalonia.Base/PropertyStore/LocalValueEntry.cs
@@ -4,6 +4,11 @@
namespace Avalonia.PropertyStore
{
+ ///
+ /// Stores a value with local value priority in a or
+ /// .
+ ///
+ /// The property type.
internal class LocalValueEntry : IValue
{
private T _value;
diff --git a/src/Avalonia.Base/PropertyStore/PriorityValue.cs b/src/Avalonia.Base/PropertyStore/PriorityValue.cs
index 60baf9d405..2785dc6840 100644
--- a/src/Avalonia.Base/PropertyStore/PriorityValue.cs
+++ b/src/Avalonia.Base/PropertyStore/PriorityValue.cs
@@ -6,6 +6,17 @@ using Avalonia.Data;
namespace Avalonia.PropertyStore
{
+ ///
+ /// Stores a set of prioritized values and bindings in a .
+ ///
+ /// The property type.
+ ///
+ /// When more than a single value or binding is applied to a property in an
+ /// , the entry in the is converted into
+ /// a . This class holds any number of
+ /// entries (sorted first by priority and then in the order
+ /// they were added) plus a local value.
+ ///
internal class PriorityValue : IValue, IValueSink
{
private readonly IAvaloniaObject _owner;
diff --git a/src/Avalonia.Base/Reactive/AvaloniaPropertyBindingObservable.cs b/src/Avalonia.Base/Reactive/AvaloniaPropertyBindingObservable.cs
index 9501640626..be044b0559 100644
--- a/src/Avalonia.Base/Reactive/AvaloniaPropertyBindingObservable.cs
+++ b/src/Avalonia.Base/Reactive/AvaloniaPropertyBindingObservable.cs
@@ -12,6 +12,7 @@ namespace Avalonia.Reactive
private readonly AvaloniaProperty _property;
private T _value;
+#nullable disable
public AvaloniaPropertyBindingObservable(
IAvaloniaObject target,
AvaloniaProperty property)
@@ -19,6 +20,7 @@ namespace Avalonia.Reactive
_target = new WeakReference(target);
_property = property;
}
+#nullable enable
public string Description => $"{_target.GetType().Name}.{_property.Name}";
diff --git a/src/Avalonia.Base/Reactive/AvaloniaPropertyObservable.cs b/src/Avalonia.Base/Reactive/AvaloniaPropertyObservable.cs
index 100330ed1d..238aba5c96 100644
--- a/src/Avalonia.Base/Reactive/AvaloniaPropertyObservable.cs
+++ b/src/Avalonia.Base/Reactive/AvaloniaPropertyObservable.cs
@@ -44,7 +44,16 @@ namespace Avalonia.Reactive
{
if (e.Property == _property)
{
- var newValue = e.Sender.GetValue(e.Property);
+ T newValue;
+
+ if (e is AvaloniaPropertyChangedEventArgs typed)
+ {
+ newValue = typed.Sender.GetValue(typed.Property);
+ }
+ else
+ {
+ newValue = (T)e.Sender.GetValue(e.Property);
+ }
if (!Equals(newValue, _value))
{
diff --git a/src/Avalonia.Base/Reactive/BindingValueExtensions.cs b/src/Avalonia.Base/Reactive/BindingValueExtensions.cs
index 6871602ad9..6f0d29dd0f 100644
--- a/src/Avalonia.Base/Reactive/BindingValueExtensions.cs
+++ b/src/Avalonia.Base/Reactive/BindingValueExtensions.cs
@@ -20,13 +20,13 @@ namespace Avalonia.Reactive
return new BindingValueSubjectAdapter(source);
}
- public static IObservable ToUntyped(this IObservable> source)
+ public static IObservable ToUntyped(this IObservable> source)
{
source = source ?? throw new ArgumentNullException(nameof(source));
return new UntypedBindingAdapter(source);
}
- public static ISubject ToUntyped(this ISubject> source)
+ public static ISubject ToUntyped(this ISubject> source)
{
source = source ?? throw new ArgumentNullException(nameof(source));
return new UntypedBindingSubjectAdapter(source);
diff --git a/src/Avalonia.Base/Reactive/LightweightObservableBase.cs b/src/Avalonia.Base/Reactive/LightweightObservableBase.cs
index 41009e4cd3..f5052e5858 100644
--- a/src/Avalonia.Base/Reactive/LightweightObservableBase.cs
+++ b/src/Avalonia.Base/Reactive/LightweightObservableBase.cs
@@ -116,20 +116,33 @@ namespace Avalonia.Reactive
{
if (Volatile.Read(ref _observers) != null)
{
- IObserver[] observers;
-
+ IObserver[] observers = null;
+ IObserver singleObserver = null;
lock (this)
{
if (_observers == null)
{
return;
}
- observers = _observers.ToArray();
+ if (_observers.Count == 1)
+ {
+ singleObserver = _observers[0];
+ }
+ else
+ {
+ observers = _observers.ToArray();
+ }
}
-
- foreach (var observer in observers)
+ if (singleObserver != null)
{
- observer.OnNext(value);
+ singleObserver.OnNext(value);
+ }
+ else
+ {
+ foreach (var observer in observers)
+ {
+ observer.OnNext(value);
+ }
}
}
}
diff --git a/src/Avalonia.Base/StyledPropertyMetadata`1.cs b/src/Avalonia.Base/StyledPropertyMetadata`1.cs
index bd6c125776..f96298a298 100644
--- a/src/Avalonia.Base/StyledPropertyMetadata`1.cs
+++ b/src/Avalonia.Base/StyledPropertyMetadata`1.cs
@@ -33,7 +33,7 @@ namespace Avalonia
///
/// Gets the default value for the property.
///
- public TValue DefaultValue => _defaultValue.ValueOrDefault();
+ public TValue DefaultValue => _defaultValue.GetValueOrDefault();
///
/// Gets the value coercion callback, if any.
@@ -60,18 +60,5 @@ namespace Avalonia
}
}
}
-
- [DebuggerHidden]
- private static Func Cast(Func f)
- {
- if (f == null)
- {
- return null;
- }
- else
- {
- return (o, v) => f(o, (TValue)v);
- }
- }
}
}
diff --git a/src/Avalonia.Base/Utilities/IdentifierParser.cs b/src/Avalonia.Base/Utilities/IdentifierParser.cs
index a57a2b7ba5..973b0aa641 100644
--- a/src/Avalonia.Base/Utilities/IdentifierParser.cs
+++ b/src/Avalonia.Base/Utilities/IdentifierParser.cs
@@ -15,7 +15,7 @@ namespace Avalonia.Utilities
{
if (IsValidIdentifierStart(r.Peek))
{
- return r.TakeWhile(IsValidIdentifierChar);
+ return r.TakeWhile(c => IsValidIdentifierChar(c));
}
else
{
diff --git a/src/Avalonia.Base/Utilities/ImmutableReadOnlyListStructEnumerator.cs b/src/Avalonia.Base/Utilities/ImmutableReadOnlyListStructEnumerator.cs
new file mode 100644
index 0000000000..a1246c57b5
--- /dev/null
+++ b/src/Avalonia.Base/Utilities/ImmutableReadOnlyListStructEnumerator.cs
@@ -0,0 +1,48 @@
+using System.Collections;
+using System.Collections.Generic;
+
+namespace Avalonia.Utilities
+{
+ public struct ImmutableReadOnlyListStructEnumerator : IEnumerator, IEnumerator
+ {
+ private readonly IReadOnlyList _readOnlyList;
+ private int _pos;
+
+ public ImmutableReadOnlyListStructEnumerator(IReadOnlyList 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;
+ }
+ }
+}
diff --git a/src/Avalonia.Base/Utilities/TypeUtilities.cs b/src/Avalonia.Base/Utilities/TypeUtilities.cs
index 3cf05509d4..d1393a9c0d 100644
--- a/src/Avalonia.Base/Utilities/TypeUtilities.cs
+++ b/src/Avalonia.Base/Utilities/TypeUtilities.cs
@@ -92,8 +92,7 @@ namespace Avalonia.Utilities
/// True if the type accepts null values; otherwise false.
public static bool AcceptsNull(Type type)
{
- var t = type.GetTypeInfo();
- return !t.IsValueType || (t.IsGenericType && (t.GetGenericTypeDefinition() == typeof(Nullable<>)));
+ return !type.IsValueType || IsNullableType(type);
}
///
@@ -119,10 +118,8 @@ namespace Avalonia.Utilities
}
var from = value.GetType();
- var fromTypeInfo = from.GetTypeInfo();
- var toTypeInfo = to.GetTypeInfo();
- if (toTypeInfo.IsAssignableFrom(fromTypeInfo))
+ if (to.IsAssignableFrom(from))
{
result = value;
return true;
@@ -134,7 +131,7 @@ namespace Avalonia.Utilities
return true;
}
- if (toTypeInfo.IsEnum && from == typeof(string))
+ if (to.IsEnum && from == typeof(string))
{
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;
@@ -154,7 +151,7 @@ namespace Avalonia.Utilities
}
}
- if (fromTypeInfo.IsEnum && IsNumeric(to))
+ if (from.IsEnum && IsNumeric(to))
{
try
{
@@ -223,10 +220,8 @@ namespace Avalonia.Utilities
}
var from = value.GetType();
- var fromTypeInfo = from.GetTypeInfo();
- var toTypeInfo = to.GetTypeInfo();
- if (toTypeInfo.IsAssignableFrom(fromTypeInfo))
+ if (to.IsAssignableFrom(from))
{
result = value;
return true;
@@ -307,9 +302,7 @@ namespace Avalonia.Utilities
/// The default value.
public static object Default(Type type)
{
- var typeInfo = type.GetTypeInfo();
-
- if (typeInfo.IsValueType)
+ if (type.IsValueType)
{
return Activator.CreateInstance(type);
}
@@ -335,9 +328,11 @@ namespace Avalonia.Utilities
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
{
@@ -352,6 +347,11 @@ namespace Avalonia.Utilities
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)
{
const string implicitName = "op_Implicit";
diff --git a/src/Avalonia.Base/Utilities/ValueSingleOrList.cs b/src/Avalonia.Base/Utilities/ValueSingleOrList.cs
new file mode 100644
index 0000000000..dc32cedb76
--- /dev/null
+++ b/src/Avalonia.Base/Utilities/ValueSingleOrList.cs
@@ -0,0 +1,94 @@
+using System.Collections.Generic;
+
+namespace Avalonia.Utilities
+{
+ ///
+ /// A list like struct optimized for holding zero or one items.
+ ///
+ /// The type of items held in the list.
+ ///
+ /// Once more than value has been added to this storage it will switch to using internally.
+ ///
+ public ref struct ValueSingleOrList
+ {
+ private bool _isSingleSet;
+
+ ///
+ /// Single contained value. Only valid if is set.
+ ///
+ public T Single { get; private set; }
+
+ ///
+ /// List of values.
+ ///
+ public List List { get; private set; }
+
+ ///
+ /// If this struct is backed by a list.
+ ///
+ public bool HasList => List != null;
+
+ ///
+ /// If this struct contains only single value and storage was not promoted to a list.
+ ///
+ public bool IsSingle => List == null && _isSingleSet;
+
+ ///
+ /// Adds a value.
+ ///
+ /// Value to add.
+ public void Add(T value)
+ {
+ if (List != null)
+ {
+ List.Add(value);
+ }
+ else
+ {
+ if (!_isSingleSet)
+ {
+ Single = value;
+
+ _isSingleSet = true;
+ }
+ else
+ {
+ List = new List();
+
+ List.Add(Single);
+ List.Add(value);
+
+ Single = default;
+ }
+ }
+ }
+
+ ///
+ /// Removes a value.
+ ///
+ /// Value to remove.
+ public bool Remove(T value)
+ {
+ if (List != null)
+ {
+ return List.Remove(value);
+ }
+
+ if (!_isSingleSet)
+ {
+ return false;
+ }
+
+ if (EqualityComparer.Default.Equals(Single, value))
+ {
+ Single = default;
+
+ _isSingleSet = false;
+
+ return true;
+ }
+
+ return false;
+ }
+ }
+}
diff --git a/src/Avalonia.Base/ValueStore.cs b/src/Avalonia.Base/ValueStore.cs
index dec1f62386..58ebc48652 100644
--- a/src/Avalonia.Base/ValueStore.cs
+++ b/src/Avalonia.Base/ValueStore.cs
@@ -1,5 +1,4 @@
using System;
-using System.Collections.Generic;
using Avalonia.Data;
using Avalonia.PropertyStore;
using Avalonia.Utilities;
@@ -8,26 +7,37 @@ using Avalonia.Utilities;
namespace Avalonia
{
+ ///
+ /// Stores styled property values for an .
+ ///
+ ///
+ /// At its core this class consists of an to
+ /// mapping which holds the current values for each set property. This
+ /// can be in one of 4 states:
+ ///
+ /// - For a single local value it will be an instance of .
+ /// - For a single value of a priority other than LocalValue it will be an instance of
+ /// `
+ /// - For a single binding it will be an instance of
+ /// - For all other cases it will be an instance of
+ ///
internal class ValueStore : IValueSink
{
private readonly AvaloniaObject _owner;
private readonly IValueSink _sink;
- private readonly AvaloniaPropertyValueStore _values;
+ private readonly AvaloniaPropertyValueStore _values;
public ValueStore(AvaloniaObject owner)
{
_sink = _owner = owner;
- _values = new AvaloniaPropertyValueStore();
+ _values = new AvaloniaPropertyValueStore();
}
public bool IsAnimating(AvaloniaProperty property)
{
if (_values.TryGetValue(property, out var slot))
{
- if (slot is IValue v)
- {
- return v.ValuePriority < BindingPriority.LocalValue;
- }
+ return slot.ValuePriority < BindingPriority.LocalValue;
}
return false;
@@ -37,10 +47,7 @@ namespace Avalonia
{
if (_values.TryGetValue(property, out var slot))
{
- if (slot is IValue v)
- {
- return v.Value.HasValue;
- }
+ return slot.Value.HasValue;
}
return false;
@@ -50,13 +57,12 @@ namespace Avalonia
{
if (_values.TryGetValue(property, out var slot))
{
- if (slot is IValue v)
+ var v = (IValue)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 (slot is IValue value)
- {
- return new Diagnostics.AvaloniaPropertyValue(
- property,
- value.Value.HasValue ? (object)value.Value : AvaloniaProperty.UnsetValue,
- value.ValuePriority,
- null);
- }
+ return new Diagnostics.AvaloniaPropertyValue(
+ property,
+ slot.Value.HasValue ? (object)slot.Value : AvaloniaProperty.UnsetValue,
+ slot.ValuePriority,
+ null);
}
return null;
diff --git a/src/Avalonia.Controls.DataGrid/Collections/DataGridSortDescription.cs b/src/Avalonia.Controls.DataGrid/Collections/DataGridSortDescription.cs
index 86113da87e..bea6f01243 100644
--- a/src/Avalonia.Controls.DataGrid/Collections/DataGridSortDescription.cs
+++ b/src/Avalonia.Controls.DataGrid/Collections/DataGridSortDescription.cs
@@ -238,7 +238,7 @@ namespace Avalonia.Collections
}
else
{
- return seq.ThenByDescending(o => GetValue(o), InternalComparer);
+ return seq.ThenBy(o => GetValue(o), InternalComparer);
}
}
diff --git a/src/Avalonia.Controls.DataGrid/Primitives/DataGridFrozenGrid.cs b/src/Avalonia.Controls.DataGrid/Primitives/DataGridFrozenGrid.cs
index 060922238d..9feca71cda 100644
--- a/src/Avalonia.Controls.DataGrid/Primitives/DataGridFrozenGrid.cs
+++ b/src/Avalonia.Controls.DataGrid/Primitives/DataGridFrozenGrid.cs
@@ -14,7 +14,7 @@ namespace Avalonia.Controls.Primitives
///
public class DataGridFrozenGrid : Grid
{
- public static readonly AvaloniaProperty IsFrozenProperty =
+ public static readonly StyledProperty IsFrozenProperty =
AvaloniaProperty.RegisterAttached("IsFrozen");
///
diff --git a/src/Avalonia.Controls.DataGrid/Properties/AssemblyInfo.cs b/src/Avalonia.Controls.DataGrid/Properties/AssemblyInfo.cs
index f15442addf..489bfc31d0 100644
--- a/src/Avalonia.Controls.DataGrid/Properties/AssemblyInfo.cs
+++ b/src/Avalonia.Controls.DataGrid/Properties/AssemblyInfo.cs
@@ -5,7 +5,7 @@ using System.Reflection;
using System.Runtime.CompilerServices;
using Avalonia.Metadata;
-[assembly: InternalsVisibleTo("Avalonia.Controls.UnitTests")]
+[assembly: InternalsVisibleTo("Avalonia.Controls.DataGrid.UnitTests")]
[assembly: InternalsVisibleTo("Avalonia.DesignerSupport")]
[assembly: XmlnsDefinition("https://github.com/avaloniaui", "Avalonia.Controls")]
diff --git a/src/Avalonia.Controls/Application.cs b/src/Avalonia.Controls/Application.cs
index 59c6c47ed9..c49f7c3d09 100644
--- a/src/Avalonia.Controls/Application.cs
+++ b/src/Avalonia.Controls/Application.cs
@@ -32,7 +32,7 @@ namespace Avalonia
/// method.
/// - Tracks the lifetime of the application.
///
- public class Application : AvaloniaObject, IGlobalDataTemplates, IGlobalStyles, IStyleRoot, IResourceNode
+ public class Application : AvaloniaObject, IDataContextProvider, IGlobalDataTemplates, IGlobalStyles, IResourceNode
{
///
/// The application-global data templates.
@@ -45,6 +45,12 @@ namespace Avalonia
private Styles _styles;
private IResourceDictionary _resources;
+ ///
+ /// Defines the property.
+ ///
+ public static readonly StyledProperty DataContextProperty =
+ StyledElement.DataContextProperty.AddOwner();
+
///
public event EventHandler ResourcesChanged;
@@ -56,6 +62,19 @@ namespace Avalonia
Name = "Avalonia Application";
}
+ ///
+ /// Gets or sets the Applications's data context.
+ ///
+ ///
+ /// The data context property specifies the default object that will
+ /// be used for data binding.
+ ///
+ public object DataContext
+ {
+ get { return GetValue(DataContextProperty); }
+ set { SetValue(DataContextProperty, value); }
+ }
+
///
/// Gets the current instance of the class.
///
diff --git a/src/Avalonia.Controls/Button.cs b/src/Avalonia.Controls/Button.cs
index 1e0fa01cc3..0bc6394b19 100644
--- a/src/Avalonia.Controls/Button.cs
+++ b/src/Avalonia.Controls/Button.cs
@@ -326,7 +326,7 @@ namespace Avalonia.Controls
if (property == IsPressedProperty)
{
- UpdatePseudoClasses(newValue.ValueOrDefault());
+ UpdatePseudoClasses(newValue.GetValueOrDefault());
}
}
diff --git a/src/Avalonia.Controls/ButtonSpinner.cs b/src/Avalonia.Controls/ButtonSpinner.cs
index 6d4e2e3373..2ac9319478 100644
--- a/src/Avalonia.Controls/ButtonSpinner.cs
+++ b/src/Avalonia.Controls/ButtonSpinner.cs
@@ -215,7 +215,7 @@ namespace Avalonia.Controls
if (property == ButtonSpinnerLocationProperty)
{
- UpdatePseudoClasses(newValue.ValueOrDefault());
+ UpdatePseudoClasses(newValue.GetValueOrDefault());
}
}
diff --git a/src/Avalonia.Controls/DefinitionBase.cs b/src/Avalonia.Controls/DefinitionBase.cs
index 6f121afbef..38ebbe5bf9 100644
--- a/src/Avalonia.Controls/DefinitionBase.cs
+++ b/src/Avalonia.Controls/DefinitionBase.cs
@@ -358,7 +358,11 @@ namespace Avalonia.Controls
///
private static bool SharedSizeGroupPropertyValueValid(string value)
{
- Contract.Requires(value != null);
+ // null is default value
+ if (value == null)
+ {
+ return true;
+ }
string id = (string)value;
diff --git a/src/Avalonia.Controls/DrawingPresenter.cs b/src/Avalonia.Controls/DrawingPresenter.cs
index b30a8668fd..ee27aa7ec1 100644
--- a/src/Avalonia.Controls/DrawingPresenter.cs
+++ b/src/Avalonia.Controls/DrawingPresenter.cs
@@ -1,9 +1,11 @@
-using Avalonia.Controls.Shapes;
+using System;
+using Avalonia.Controls.Shapes;
using Avalonia.Media;
using Avalonia.Metadata;
namespace Avalonia.Controls
{
+ [Obsolete("Use Image control with DrawingImage source")]
public class DrawingPresenter : Control
{
static DrawingPresenter()
diff --git a/src/Avalonia.Controls/Expander.cs b/src/Avalonia.Controls/Expander.cs
index b16159fea0..e42d3ec1e5 100644
--- a/src/Avalonia.Controls/Expander.cs
+++ b/src/Avalonia.Controls/Expander.cs
@@ -88,7 +88,7 @@ namespace Avalonia.Controls
if (property == ExpandDirectionProperty)
{
- UpdatePseudoClasses(newValue.ValueOrDefault());
+ UpdatePseudoClasses(newValue.GetValueOrDefault());
}
}
diff --git a/src/Avalonia.Controls/Generators/ITreeItemContainerGenerator.cs b/src/Avalonia.Controls/Generators/ITreeItemContainerGenerator.cs
index e2e591215e..5c931bc771 100644
--- a/src/Avalonia.Controls/Generators/ITreeItemContainerGenerator.cs
+++ b/src/Avalonia.Controls/Generators/ITreeItemContainerGenerator.cs
@@ -12,5 +12,10 @@ namespace Avalonia.Controls.Generators
/// Gets the container index for the tree.
///
TreeContainerIndex Index { get; }
+
+ ///
+ /// Updates the index based on the parent .
+ ///
+ void UpdateIndex();
}
}
diff --git a/src/Avalonia.Controls/Generators/TreeItemContainerGenerator.cs b/src/Avalonia.Controls/Generators/TreeItemContainerGenerator.cs
index c06a64443c..22e06a40b1 100644
--- a/src/Avalonia.Controls/Generators/TreeItemContainerGenerator.cs
+++ b/src/Avalonia.Controls/Generators/TreeItemContainerGenerator.cs
@@ -3,8 +3,10 @@
using System;
using System.Collections.Generic;
+using System.Linq;
using Avalonia.Controls.Templates;
using Avalonia.Data;
+using Avalonia.LogicalTree;
namespace Avalonia.Controls.Generators
{
@@ -15,6 +17,8 @@ namespace Avalonia.Controls.Generators
public class TreeItemContainerGenerator : ItemContainerGenerator, ITreeItemContainerGenerator
where T : class, IControl, new()
{
+ private TreeView _treeView;
+
///
/// Initializes a new instance of the class.
///
@@ -23,31 +27,28 @@ namespace Avalonia.Controls.Generators
/// The container's ContentTemplate property.
/// The container's Items property.
/// The container's IsExpanded property.
- /// The container index for the tree
public TreeItemContainerGenerator(
IControl owner,
AvaloniaProperty contentProperty,
AvaloniaProperty contentTemplateProperty,
AvaloniaProperty itemsProperty,
- AvaloniaProperty isExpandedProperty,
- TreeContainerIndex index)
+ AvaloniaProperty isExpandedProperty)
: base(owner, contentProperty, contentTemplateProperty)
{
Contract.Requires(owner != null);
Contract.Requires(contentProperty != null);
Contract.Requires(itemsProperty != null);
Contract.Requires(isExpandedProperty != null);
- Contract.Requires(index != null);
ItemsProperty = itemsProperty;
IsExpandedProperty = isExpandedProperty;
- Index = index;
+ UpdateIndex();
}
///
/// Gets the container index for the tree.
///
- public TreeContainerIndex Index { get; }
+ public TreeContainerIndex Index { get; private set; }
///
/// Gets the item container's Items property.
@@ -70,7 +71,7 @@ namespace Avalonia.Controls.Generators
}
else if (container != null)
{
- Index.Add(item, container);
+ Index?.Add(item, container);
return container;
}
else
@@ -92,7 +93,7 @@ namespace Avalonia.Controls.Generators
result.DataContext = item;
}
- Index.Add(item, result);
+ Index?.Add(item, result);
return result;
}
@@ -101,24 +102,44 @@ namespace Avalonia.Controls.Generators
public override IEnumerable Clear()
{
var items = base.Clear();
- Index.Remove(0, items);
+ Index?.Remove(0, items);
return items;
}
public override IEnumerable Dematerialize(int startingIndex, int count)
{
- Index.Remove(startingIndex, GetContainerRange(startingIndex, count));
+ Index?.Remove(startingIndex, GetContainerRange(startingIndex, count));
return base.Dematerialize(startingIndex, count);
}
public override IEnumerable RemoveRange(int startingIndex, int count)
{
- Index.Remove(startingIndex, GetContainerRange(startingIndex, count));
+ Index?.Remove(startingIndex, GetContainerRange(startingIndex, count));
return base.RemoveRange(startingIndex, count);
}
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().FirstOrDefault();
+
+ if (treeView != _treeView)
+ {
+ Clear();
+ Index = treeView?.ItemContainerGenerator?.Index;
+ _treeView = treeView;
+ }
+ }
+ }
+
class WrapperTreeDataTemplate : ITreeDataTemplate
{
private readonly IDataTemplate _inner;
diff --git a/src/Avalonia.Controls/GridSplitter.cs b/src/Avalonia.Controls/GridSplitter.cs
index a2fefa0548..a2d53f4f06 100644
--- a/src/Avalonia.Controls/GridSplitter.cs
+++ b/src/Avalonia.Controls/GridSplitter.cs
@@ -23,37 +23,37 @@ namespace Avalonia.Controls
///
/// Defines the property.
///
- public static readonly AvaloniaProperty ResizeDirectionProperty =
+ public static readonly StyledProperty ResizeDirectionProperty =
AvaloniaProperty.Register(nameof(ResizeDirection));
///
/// Defines the property.
///
- public static readonly AvaloniaProperty ResizeBehaviorProperty =
+ public static readonly StyledProperty ResizeBehaviorProperty =
AvaloniaProperty.Register(nameof(ResizeBehavior));
///
/// Defines the property.
///
- public static readonly AvaloniaProperty ShowsPreviewProperty =
+ public static readonly StyledProperty ShowsPreviewProperty =
AvaloniaProperty.Register(nameof(ShowsPreview));
///
/// Defines the property.
///
- public static readonly AvaloniaProperty KeyboardIncrementProperty =
+ public static readonly StyledProperty KeyboardIncrementProperty =
AvaloniaProperty.Register(nameof(KeyboardIncrement), 10d);
///
/// Defines the property.
///
- public static readonly AvaloniaProperty DragIncrementProperty =
+ public static readonly StyledProperty DragIncrementProperty =
AvaloniaProperty.Register(nameof(DragIncrement), 1d);
///
/// Defines the property.
///
- public static readonly AvaloniaProperty> PreviewContentProperty =
+ public static readonly StyledProperty> PreviewContentProperty =
AvaloniaProperty.Register>(nameof(PreviewContent));
private static readonly Cursor s_columnSplitterCursor = new Cursor(StandardCursorType.SizeWestEast);
diff --git a/src/Avalonia.Controls/Image.cs b/src/Avalonia.Controls/Image.cs
index ff6cd482df..41b6e5449a 100644
--- a/src/Avalonia.Controls/Image.cs
+++ b/src/Avalonia.Controls/Image.cs
@@ -14,8 +14,8 @@ namespace Avalonia.Controls
///
/// Defines the property.
///
- public static readonly StyledProperty SourceProperty =
- AvaloniaProperty.Register(nameof(Source));
+ public static readonly StyledProperty SourceProperty =
+ AvaloniaProperty.Register(nameof(Source));
///
/// Defines the property.
@@ -23,6 +23,14 @@ namespace Avalonia.Controls
public static readonly StyledProperty StretchProperty =
AvaloniaProperty.Register(nameof(Stretch), Stretch.Uniform);
+ ///
+ /// Defines the property.
+ ///
+ public static readonly StyledProperty StretchDirectionProperty =
+ AvaloniaProperty.Register(
+ nameof(StretchDirection),
+ StretchDirection.Both);
+
static Image()
{
AffectsRender(SourceProperty, StretchProperty);
@@ -30,9 +38,9 @@ namespace Avalonia.Controls
}
///
- /// Gets or sets the bitmap image that will be displayed.
+ /// Gets or sets the image that will be displayed.
///
- public IBitmap Source
+ public IImage Source
{
get { return GetValue(SourceProperty); }
set { SetValue(SourceProperty, value); }
@@ -43,10 +51,19 @@ namespace Avalonia.Controls
///
public Stretch Stretch
{
- get { return (Stretch)GetValue(StretchProperty); }
+ get { return GetValue(StretchProperty); }
set { SetValue(StretchProperty, value); }
}
+ ///
+ /// Gets or sets a value controlling in what direction the image will be stretched.
+ ///
+ public StretchDirection StretchDirection
+ {
+ get { return GetValue(StretchDirectionProperty); }
+ set { SetValue(StretchDirectionProperty, value); }
+ }
+
///
/// Renders the control.
///
@@ -58,8 +75,8 @@ namespace Avalonia.Controls
if (source != null)
{
Rect viewPort = new Rect(Bounds.Size);
- Size sourceSize = new Size(source.PixelSize.Width, source.PixelSize.Height);
- Vector scale = Stretch.CalculateScaling(Bounds.Size, sourceSize);
+ Size sourceSize = source.Size;
+ Vector scale = Stretch.CalculateScaling(Bounds.Size, sourceSize, StretchDirection);
Size scaledSize = sourceSize * scale;
Rect destRect = viewPort
.CenterRect(new Rect(scaledSize))
@@ -69,7 +86,7 @@ namespace Avalonia.Controls
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)
{
- Size sourceSize = new Size(source.PixelSize.Width, source.PixelSize.Height);
- if (double.IsInfinity(availableSize.Width) || double.IsInfinity(availableSize.Height))
- {
- result = sourceSize;
- }
- else
- {
- result = Stretch.CalculateSize(availableSize, sourceSize);
- }
+ result = Stretch.CalculateSize(availableSize, source.Size, StretchDirection);
}
return result;
@@ -106,7 +115,7 @@ namespace Avalonia.Controls
if (source != null)
{
- var sourceSize = new Size(source.PixelSize.Width, source.PixelSize.Height);
+ var sourceSize = source.Size;
var result = Stretch.CalculateSize(finalSize, sourceSize);
return result;
}
diff --git a/src/Avalonia.Controls/ItemsControl.cs b/src/Avalonia.Controls/ItemsControl.cs
index bf22f0a08a..94c34b28d6 100644
--- a/src/Avalonia.Controls/ItemsControl.cs
+++ b/src/Avalonia.Controls/ItemsControl.cs
@@ -5,7 +5,6 @@ using System;
using System.Collections;
using System.Collections.Generic;
using System.Collections.Specialized;
-using System.Linq;
using Avalonia.Collections;
using Avalonia.Controls.Generators;
using Avalonia.Controls.Presenters;
@@ -324,20 +323,24 @@ namespace Avalonia.Controls
return;
}
- var current = focus.Current
- .GetSelfAndVisualAncestors()
- .OfType()
- .FirstOrDefault(x => x.VisualParent == container);
+ IVisual current = focus.Current;
- if (current != null)
+ while (current != null)
{
- var next = GetNextControl(container, direction.Value, current, false);
-
- if (next != null)
+ if (current.VisualParent == container && current is IInputElement inputElement)
{
- focus.Focus(next, NavigationMethod.Directional);
- e.Handled = true;
+ IInputElement next = GetNextControl(container, direction.Value, inputElement, false);
+
+ if (next != null)
+ {
+ focus.Focus(next, NavigationMethod.Directional);
+ e.Handled = true;
+ }
+
+ break;
}
+
+ current = current.VisualParent;
}
}
diff --git a/src/Avalonia.Controls/LayoutTransformControl.cs b/src/Avalonia.Controls/LayoutTransformControl.cs
index db67a24159..a3bb654629 100644
--- a/src/Avalonia.Controls/LayoutTransformControl.cs
+++ b/src/Avalonia.Controls/LayoutTransformControl.cs
@@ -17,10 +17,10 @@ namespace Avalonia.Controls
///
public class LayoutTransformControl : Decorator
{
- public static readonly AvaloniaProperty LayoutTransformProperty =
+ public static readonly StyledProperty LayoutTransformProperty =
AvaloniaProperty.Register(nameof(LayoutTransform));
- public static readonly AvaloniaProperty UseRenderTransformProperty =
+ public static readonly StyledProperty UseRenderTransformProperty =
AvaloniaProperty.Register(nameof(LayoutTransform));
static LayoutTransformControl()
diff --git a/src/Avalonia.Controls/ListBox.cs b/src/Avalonia.Controls/ListBox.cs
index 4966e669ed..1761eeb3cf 100644
--- a/src/Avalonia.Controls/ListBox.cs
+++ b/src/Avalonia.Controls/ListBox.cs
@@ -124,7 +124,7 @@ namespace Avalonia.Controls
e.Handled = UpdateSelectionFromEventSource(
e.Source,
true,
- (e.InputModifiers & InputModifiers.Shift) != 0);
+ (e.KeyModifiers & KeyModifiers.Shift) != 0);
}
}
diff --git a/src/Avalonia.Controls/Notifications/WindowNotificationManager.cs b/src/Avalonia.Controls/Notifications/WindowNotificationManager.cs
index ec3bd7d688..56044ea522 100644
--- a/src/Avalonia.Controls/Notifications/WindowNotificationManager.cs
+++ b/src/Avalonia.Controls/Notifications/WindowNotificationManager.cs
@@ -7,6 +7,7 @@ using System.Linq;
using System.Reactive.Linq;
using System.Threading.Tasks;
using Avalonia.Controls.Primitives;
+using Avalonia.Rendering;
using Avalonia.Data;
using Avalonia.VisualTree;
@@ -15,7 +16,7 @@ namespace Avalonia.Controls.Notifications
///
/// An that displays notifications in a .
///
- public class WindowNotificationManager : TemplatedControl, IManagedNotificationManager
+ public class WindowNotificationManager : TemplatedControl, IManagedNotificationManager, ICustomSimpleHitTest
{
private IList _items;
@@ -150,7 +151,7 @@ namespace Avalonia.Controls.Notifications
if (property == PositionProperty)
{
- UpdatePseudoClasses(newValue.ValueOrDefault());
+ UpdatePseudoClasses(newValue.GetValueOrDefault());
}
}
@@ -161,15 +162,9 @@ namespace Avalonia.Controls.Notifications
/// The that will be the host.
private void Install(Window host)
{
- var adornerLayer = host.GetVisualDescendants()
- .OfType()
- .FirstOrDefault()
- ?.AdornerLayer;
+ var adornerLayer = host.FindDescendantOfType()?.AdornerLayer;
- if (adornerLayer != null)
- {
- adornerLayer.Children.Add(this);
- }
+ adornerLayer?.Children.Add(this);
}
private void UpdatePseudoClasses(NotificationPosition position)
@@ -179,5 +174,7 @@ namespace Avalonia.Controls.Notifications
PseudoClasses.Set(":bottomleft", position == NotificationPosition.BottomLeft);
PseudoClasses.Set(":bottomright", position == NotificationPosition.BottomRight);
}
+
+ public bool HitTest(Point point) => VisualChildren.HitTestCustom(point);
}
}
diff --git a/src/Avalonia.Controls/Platform/DefaultMenuInteractionHandler.cs b/src/Avalonia.Controls/Platform/DefaultMenuInteractionHandler.cs
index 97aeead608..bf811ad008 100644
--- a/src/Avalonia.Controls/Platform/DefaultMenuInteractionHandler.cs
+++ b/src/Avalonia.Controls/Platform/DefaultMenuInteractionHandler.cs
@@ -6,6 +6,8 @@ using Avalonia.LogicalTree;
using Avalonia.Rendering;
using Avalonia.Threading;
+#nullable enable
+
namespace Avalonia.Controls.Platform
{
///
@@ -14,8 +16,8 @@ namespace Avalonia.Controls.Platform
public class DefaultMenuInteractionHandler : IMenuInteractionHandler
{
private readonly bool _isContextMenu;
- private IDisposable _inputManagerSubscription;
- private IRenderRoot _root;
+ private IDisposable? _inputManagerSubscription;
+ private IRenderRoot? _root;
public DefaultMenuInteractionHandler(bool isContextMenu)
: this(isContextMenu, Input.InputManager.Instance, DefaultDelayRun)
@@ -24,9 +26,11 @@ namespace Avalonia.Controls.Platform
public DefaultMenuInteractionHandler(
bool isContextMenu,
- IInputManager inputManager,
+ IInputManager? inputManager,
Action delayRun)
{
+ delayRun = delayRun ?? throw new ArgumentNullException(nameof(delayRun));
+
_isContextMenu = isContextMenu;
InputManager = inputManager;
DelayRun = delayRun;
@@ -92,7 +96,7 @@ namespace Avalonia.Controls.Platform
root.Deactivated -= WindowDeactivated;
}
- _inputManagerSubscription.Dispose();
+ _inputManagerSubscription!.Dispose();
Menu = null;
_root = null;
@@ -100,9 +104,9 @@ namespace Avalonia.Controls.Platform
protected Action 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);
@@ -131,7 +135,7 @@ namespace Avalonia.Controls.Platform
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)
{
@@ -200,7 +204,7 @@ namespace Avalonia.Controls.Platform
}
else
{
- Menu.Close();
+ Menu!.Close();
}
e.Handled = true;
@@ -213,12 +217,12 @@ namespace Avalonia.Controls.Platform
{
if (item == null && _isContextMenu)
{
- if (Menu.MoveSelection(direction.Value, true) == true)
+ if (Menu!.MoveSelection(direction.Value, true) == 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,
// 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)
{
- var current = (IMenuElement)item;
+ var current = (IMenuElement?)item;
while (current != null && !(current is IMenu))
{
@@ -456,7 +460,7 @@ namespace Avalonia.Controls.Platform
protected void SelectItemAndAncestors(IMenuItem item)
{
- var current = item;
+ var current = (IMenuItem?)item;
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)
{
diff --git a/src/Avalonia.Controls/Presenters/ContentPresenter.cs b/src/Avalonia.Controls/Presenters/ContentPresenter.cs
index 5e1a844720..9b1215c9ae 100644
--- a/src/Avalonia.Controls/Presenters/ContentPresenter.cs
+++ b/src/Avalonia.Controls/Presenters/ContentPresenter.cs
@@ -28,7 +28,7 @@ namespace Avalonia.Controls.Presenters
///
/// Defines the property.
///
- public static readonly AvaloniaProperty BorderBrushProperty =
+ public static readonly StyledProperty BorderBrushProperty =
Border.BorderBrushProperty.AddOwner();
///
diff --git a/src/Avalonia.Controls/Presenters/ItemVirtualizerSimple.cs b/src/Avalonia.Controls/Presenters/ItemVirtualizerSimple.cs
index cd14211075..d27de7a80d 100644
--- a/src/Avalonia.Controls/Presenters/ItemVirtualizerSimple.cs
+++ b/src/Avalonia.Controls/Presenters/ItemVirtualizerSimple.cs
@@ -188,8 +188,8 @@ namespace Avalonia.Controls.Presenters
break;
case NotifyCollectionChangedAction.Remove:
- if (e.OldStartingIndex >= FirstIndex &&
- e.OldStartingIndex < NextIndex)
+ if ((e.OldStartingIndex >= FirstIndex && e.OldStartingIndex < NextIndex) ||
+ panel.Children.Count > ItemCount)
{
RecycleContainersOnRemove();
}
diff --git a/src/Avalonia.Controls/Presenters/TextPresenter.cs b/src/Avalonia.Controls/Presenters/TextPresenter.cs
index e0cc9aa128..9084012619 100644
--- a/src/Avalonia.Controls/Presenters/TextPresenter.cs
+++ b/src/Avalonia.Controls/Presenters/TextPresenter.cs
@@ -297,7 +297,7 @@ namespace Avalonia.Controls.Presenters
return new FormattedText
{
Text = "X",
- Typeface = new Typeface(FontFamily, FontWeight, FontStyle),
+ Typeface = FontManager.Current?.GetOrAddTypeface(FontFamily, FontWeight, FontStyle),
FontSize = FontSize,
TextAlignment = TextAlignment,
Constraint = availableSize,
diff --git a/src/Avalonia.Controls/Primitives/AdornerLayer.cs b/src/Avalonia.Controls/Primitives/AdornerLayer.cs
index ebe5e0a93e..9a2f0310d7 100644
--- a/src/Avalonia.Controls/Primitives/AdornerLayer.cs
+++ b/src/Avalonia.Controls/Primitives/AdornerLayer.cs
@@ -138,10 +138,7 @@ namespace Avalonia.Controls.Primitives
}
}
- public bool HitTest(Point point)
- {
- return Children.Any(ctrl => ctrl.TransformedBounds?.Contains(point) == true);
- }
+ public bool HitTest(Point point) => Children.HitTestCustom(point);
private class AdornedElementInfo
{
diff --git a/src/Avalonia.Controls/Primitives/OverlayLayer.cs b/src/Avalonia.Controls/Primitives/OverlayLayer.cs
index 487a5e91e4..5150033a53 100644
--- a/src/Avalonia.Controls/Primitives/OverlayLayer.cs
+++ b/src/Avalonia.Controls/Primitives/OverlayLayer.cs
@@ -21,11 +21,8 @@ namespace Avalonia.Controls.Primitives
return null;
}
-
- public bool HitTest(Point point)
- {
- return Children.Any(ctrl => ctrl.TransformedBounds?.Contains(point) == true);
- }
+
+ public bool HitTest(Point point) => Children.HitTestCustom(point);
protected override Size ArrangeOverride(Size finalSize)
{
diff --git a/src/Avalonia.Controls/Primitives/PopupPositioning/IPopupPositioner.cs b/src/Avalonia.Controls/Primitives/PopupPositioning/IPopupPositioner.cs
index 3010a3d8a8..f0358ec04f 100644
--- a/src/Avalonia.Controls/Primitives/PopupPositioning/IPopupPositioner.cs
+++ b/src/Avalonia.Controls/Primitives/PopupPositioning/IPopupPositioner.cs
@@ -306,7 +306,7 @@ namespace Avalonia.Controls.Primitives.PopupPositioning
if (placement == PlacementMode.Pointer)
{
positionerParameters.AnchorRectangle = new Rect(pointer, new Size(1, 1));
- positionerParameters.Anchor = PopupPositioningEdge.BottomRight;
+ positionerParameters.Anchor = PopupPositioningEdge.TopLeft;
positionerParameters.Gravity = PopupPositioningEdge.BottomRight;
}
else
diff --git a/src/Avalonia.Controls/Primitives/RangeBase.cs b/src/Avalonia.Controls/Primitives/RangeBase.cs
index f1ee7c0e1a..baa51f92ec 100644
--- a/src/Avalonia.Controls/Primitives/RangeBase.cs
+++ b/src/Avalonia.Controls/Primitives/RangeBase.cs
@@ -75,7 +75,10 @@ namespace Avalonia.Controls.Primitives
set
{
- ValidateDouble(value, "Minimum");
+ if (!ValidateDouble(value))
+ {
+ return;
+ }
if (IsInitialized)
{
@@ -102,7 +105,10 @@ namespace Avalonia.Controls.Primitives
set
{
- ValidateDouble(value, "Maximum");
+ if (!ValidateDouble(value))
+ {
+ return;
+ }
if (IsInitialized)
{
@@ -129,7 +135,10 @@ namespace Avalonia.Controls.Primitives
set
{
- ValidateDouble(value, "Value");
+ if (!ValidateDouble(value))
+ {
+ return;
+ }
if (IsInitialized)
{
@@ -164,16 +173,12 @@ namespace Avalonia.Controls.Primitives
}
///
- /// Throws an exception if the double value is NaN or Inf.
+ /// Checks if the double value is not inifinity nor NaN.
///
/// The value.
- /// The name of the property being set.
- private static void ValidateDouble(double value, string property)
+ private static bool ValidateDouble(double value)
{
- if (double.IsInfinity(value) || double.IsNaN(value))
- {
- throw new ArgumentException($"{value} is not a valid value for {property}.");
- }
+ return !double.IsInfinity(value) || !double.IsNaN(value);
}
///
diff --git a/src/Avalonia.Controls/Primitives/ScrollBar.cs b/src/Avalonia.Controls/Primitives/ScrollBar.cs
index d7ba899ff3..4d3ae3c5b8 100644
--- a/src/Avalonia.Controls/Primitives/ScrollBar.cs
+++ b/src/Avalonia.Controls/Primitives/ScrollBar.cs
@@ -151,7 +151,7 @@ namespace Avalonia.Controls.Primitives
if (property == OrientationProperty)
{
- UpdatePseudoClasses(newValue.ValueOrDefault());
+ UpdatePseudoClasses(newValue.GetValueOrDefault());
}
}
diff --git a/src/Avalonia.Controls/Primitives/SelectingItemsControl.cs b/src/Avalonia.Controls/Primitives/SelectingItemsControl.cs
index 329b086a7c..69da211aa4 100644
--- a/src/Avalonia.Controls/Primitives/SelectingItemsControl.cs
+++ b/src/Avalonia.Controls/Primitives/SelectingItemsControl.cs
@@ -5,6 +5,7 @@ using System;
using System.Collections;
using System.Collections.Generic;
using System.Collections.Specialized;
+using System.Diagnostics;
using System.Linq;
using Avalonia.Collections;
using Avalonia.Controls.Generators;
@@ -13,7 +14,6 @@ using Avalonia.Input;
using Avalonia.Input.Platform;
using Avalonia.Interactivity;
using Avalonia.Logging;
-using Avalonia.Styling;
using Avalonia.VisualTree;
namespace Avalonia.Controls.Primitives
@@ -241,17 +241,14 @@ namespace Avalonia.Controls.Primitives
public override void BeginInit()
{
base.BeginInit();
- ++_updateCount;
- _updateSelectedIndex = int.MinValue;
+
+ InternalBeginInit();
}
///
public override void EndInit()
{
- if (--_updateCount == 0)
- {
- UpdateFinished();
- }
+ InternalEndInit();
base.EndInit();
}
@@ -269,11 +266,20 @@ namespace Avalonia.Controls.Primitives
/// The container or null if the event did not originate in a container.
protected IControl GetContainerFromEventSource(IInteractive eventSource)
{
- var item = ((IVisual)eventSource).GetSelfAndVisualAncestors()
- .OfType()
- .FirstOrDefault(x => x.LogicalParent == this && ItemContainerGenerator?.IndexFromContainer(x) != -1);
+ var parent = (IVisual)eventSource;
- return item;
+ while (parent != null)
+ {
+ if (parent is IControl control && control.LogicalParent == this
+ && ItemContainerGenerator?.IndexFromContainer(control) != -1)
+ {
+ return control;
+ }
+
+ parent = parent.VisualParent;
+ }
+
+ return null;
}
///
@@ -429,7 +435,8 @@ namespace Avalonia.Controls.Primitives
protected override void OnDataContextBeginUpdate()
{
base.OnDataContextBeginUpdate();
- ++_updateCount;
+
+ InternalBeginInit();
}
///
@@ -437,10 +444,7 @@ namespace Avalonia.Controls.Primitives
{
base.OnDataContextEndUpdate();
- if (--_updateCount == 0)
- {
- UpdateFinished();
- }
+ InternalEndInit();
}
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
{
private readonly List _list = new List();
diff --git a/src/Avalonia.Controls/Primitives/ToggleButton.cs b/src/Avalonia.Controls/Primitives/ToggleButton.cs
index 1b3cb09c1f..c4f1f2c7e7 100644
--- a/src/Avalonia.Controls/Primitives/ToggleButton.cs
+++ b/src/Avalonia.Controls/Primitives/ToggleButton.cs
@@ -1,12 +1,20 @@
// Copyright (c) The Avalonia Project. All rights reserved.
// Licensed under the MIT license. See licence.md file in the project root for full license information.
+using System;
using Avalonia.Data;
+using Avalonia.Interactivity;
namespace Avalonia.Controls.Primitives
{
+ ///
+ /// Represents a control that a user can select (check) or clear (uncheck). Base class for controls that can switch states.
+ ///
public class ToggleButton : Button
{
+ ///
+ /// Defines the property.
+ ///
public static readonly DirectProperty IsCheckedProperty =
AvaloniaProperty.RegisterDirect(
nameof(IsChecked),
@@ -15,19 +23,71 @@ namespace Avalonia.Controls.Primitives
unsetValue: null,
defaultBindingMode: BindingMode.TwoWay);
+ ///
+ /// Defines the property.
+ ///
public static readonly StyledProperty IsThreeStateProperty =
AvaloniaProperty.Register(nameof(IsThreeState));
+ ///
+ /// Defines the event.
+ ///
+ public static readonly RoutedEvent CheckedEvent =
+ RoutedEvent.Register(nameof(Checked), RoutingStrategies.Bubble);
+
+ ///
+ /// Defines the event.
+ ///
+ public static readonly RoutedEvent UncheckedEvent =
+ RoutedEvent.Register(nameof(Unchecked), RoutingStrategies.Bubble);
+
+ ///
+ /// Defines the event.
+ ///
+ public static readonly RoutedEvent IndeterminateEvent =
+ RoutedEvent.Register(nameof(Indeterminate), RoutingStrategies.Bubble);
+
private bool? _isChecked = false;
public ToggleButton()
{
UpdatePseudoClasses(IsChecked);
+ IsCheckedProperty.Changed.AddClassHandler((x, e) => x.OnIsCheckedChanged(e));
+ }
+
+ ///
+ /// Raised when a is checked.
+ ///
+ public event EventHandler Checked
+ {
+ add => AddHandler(CheckedEvent, value);
+ remove => RemoveHandler(CheckedEvent, value);
+ }
+
+ ///
+ /// Raised when a is unchecked.
+ ///
+ public event EventHandler Unchecked
+ {
+ add => AddHandler(UncheckedEvent, value);
+ remove => RemoveHandler(UncheckedEvent, value);
}
+ ///
+ /// Raised when a is neither checked nor unchecked.
+ ///
+ public event EventHandler Indeterminate
+ {
+ add => AddHandler(IndeterminateEvent, value);
+ remove => RemoveHandler(IndeterminateEvent, value);
+ }
+
+ ///
+ /// Gets or sets whether the is checked.
+ ///
public bool? IsChecked
{
- get { return _isChecked; }
+ get => _isChecked;
set
{
SetAndRaise(IsCheckedProperty, ref _isChecked, value);
@@ -35,6 +95,9 @@ namespace Avalonia.Controls.Primitives
}
}
+ ///
+ /// Gets or sets a value that indicates whether the control supports three states.
+ ///
public bool IsThreeState
{
get => GetValue(IsThreeStateProperty);
@@ -47,18 +110,78 @@ namespace Avalonia.Controls.Primitives
base.OnClick();
}
+ ///
+ /// Toggles the property.
+ ///
protected virtual void Toggle()
{
if (IsChecked.HasValue)
+ {
if (IsChecked.Value)
+ {
if (IsThreeState)
+ {
IsChecked = null;
+ }
else
+ {
IsChecked = false;
+ }
+ }
else
+ {
IsChecked = true;
+ }
+ }
else
+ {
IsChecked = false;
+ }
+ }
+
+ ///
+ /// Called when becomes true.
+ ///
+ /// Event arguments for the routed event that is raised by the default implementation of this method.
+ protected virtual void OnChecked(RoutedEventArgs e)
+ {
+ RaiseEvent(e);
+ }
+
+ ///
+ /// Called when becomes false.
+ ///
+ /// Event arguments for the routed event that is raised by the default implementation of this method.
+ protected virtual void OnUnchecked(RoutedEventArgs e)
+ {
+ RaiseEvent(e);
+ }
+
+ ///
+ /// Called when becomes null.
+ ///
+ /// Event arguments for the routed event that is raised by the default implementation of this method.
+ 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)
diff --git a/src/Avalonia.Controls/Primitives/Track.cs b/src/Avalonia.Controls/Primitives/Track.cs
index e4de6ac00f..1e02d70fff 100644
--- a/src/Avalonia.Controls/Primitives/Track.cs
+++ b/src/Avalonia.Controls/Primitives/Track.cs
@@ -290,7 +290,7 @@ namespace Avalonia.Controls.Primitives
if (property == OrientationProperty)
{
- UpdatePseudoClasses(newValue.ValueOrDefault());
+ UpdatePseudoClasses(newValue.GetValueOrDefault());
}
}
diff --git a/src/Avalonia.Controls/Primitives/VisualLayerManager.cs b/src/Avalonia.Controls/Primitives/VisualLayerManager.cs
index b7229eb121..385e7cc567 100644
--- a/src/Avalonia.Controls/Primitives/VisualLayerManager.cs
+++ b/src/Avalonia.Controls/Primitives/VisualLayerManager.cs
@@ -1,6 +1,5 @@
using System.Collections.Generic;
using Avalonia.LogicalTree;
-using Avalonia.Styling;
namespace Avalonia.Controls.Primitives
{
@@ -8,7 +7,7 @@ namespace Avalonia.Controls.Primitives
{
private const int AdornerZIndex = int.MaxValue - 100;
private const int OverlayZIndex = int.MaxValue - 99;
- private IStyleHost _styleRoot;
+ private ILogicalRoot _logicalRoot;
private readonly List _layers = new List();
@@ -53,7 +52,7 @@ namespace Avalonia.Controls.Primitives
layer.ZIndex = zindex;
VisualChildren.Add(layer);
if (((ILogical)this).IsAttachedToLogicalTree)
- ((ILogical)layer).NotifyAttachedToLogicalTree(new LogicalTreeAttachmentEventArgs(_styleRoot));
+ ((ILogical)layer).NotifyAttachedToLogicalTree(new LogicalTreeAttachmentEventArgs(_logicalRoot, layer, this));
InvalidateArrange();
}
@@ -61,7 +60,7 @@ namespace Avalonia.Controls.Primitives
protected override void OnAttachedToLogicalTree(LogicalTreeAttachmentEventArgs e)
{
base.OnAttachedToLogicalTree(e);
- _styleRoot = e.Root;
+ _logicalRoot = e.Root;
foreach (var l in _layers)
((ILogical)l).NotifyAttachedToLogicalTree(e);
@@ -69,7 +68,7 @@ namespace Avalonia.Controls.Primitives
protected override void OnDetachedFromLogicalTree(LogicalTreeAttachmentEventArgs e)
{
- _styleRoot = null;
+ _logicalRoot = null;
base.OnDetachedFromLogicalTree(e);
foreach (var l in _layers)
((ILogical)l).NotifyDetachedFromLogicalTree(e);
diff --git a/src/Avalonia.Controls/ProgressBar.cs b/src/Avalonia.Controls/ProgressBar.cs
index bfa31e3676..753c6e1b71 100644
--- a/src/Avalonia.Controls/ProgressBar.cs
+++ b/src/Avalonia.Controls/ProgressBar.cs
@@ -87,11 +87,11 @@ namespace Avalonia.Controls
if (property == IsIndeterminateProperty)
{
- UpdatePseudoClasses(newValue.ValueOrDefault(), null);
+ UpdatePseudoClasses(newValue.GetValueOrDefault(), null);
}
else if (property == OrientationProperty)
{
- UpdatePseudoClasses(null, newValue.ValueOrDefault());
+ UpdatePseudoClasses(null, newValue.GetValueOrDefault());
}
}
diff --git a/src/Avalonia.Controls/Remote/RemoteWidget.cs b/src/Avalonia.Controls/Remote/RemoteWidget.cs
index 539fe1ec4b..c7a1a24c25 100644
--- a/src/Avalonia.Controls/Remote/RemoteWidget.cs
+++ b/src/Avalonia.Controls/Remote/RemoteWidget.cs
@@ -83,7 +83,7 @@ namespace Avalonia.Controls.Remote
Marshal.Copy(_lastFrame.Data, y * _lastFrame.Stride,
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));
}
base.Render(context);
diff --git a/src/Avalonia.Controls/Repeater/ItemsRepeater.cs b/src/Avalonia.Controls/Repeater/ItemsRepeater.cs
index 457d72bd60..086599d0bb 100644
--- a/src/Avalonia.Controls/Repeater/ItemsRepeater.cs
+++ b/src/Avalonia.Controls/Repeater/ItemsRepeater.cs
@@ -22,7 +22,7 @@ namespace Avalonia.Controls
///
/// Defines the property.
///
- public static readonly AvaloniaProperty HorizontalCacheLengthProperty =
+ public static readonly StyledProperty HorizontalCacheLengthProperty =
AvaloniaProperty.Register(nameof(HorizontalCacheLength), 2.0);
///
@@ -40,16 +40,16 @@ namespace Avalonia.Controls
///
/// Defines the property.
///
- public static readonly AvaloniaProperty LayoutProperty =
+ public static readonly StyledProperty LayoutProperty =
AvaloniaProperty.Register(nameof(Layout), new StackLayout());
///
/// Defines the property.
///
- public static readonly AvaloniaProperty VerticalCacheLengthProperty =
+ public static readonly StyledProperty VerticalCacheLengthProperty =
AvaloniaProperty.Register(nameof(VerticalCacheLength), 2.0);
- private static readonly AttachedProperty VirtualizationInfoProperty =
+ private static readonly StyledProperty VirtualizationInfoProperty =
AvaloniaProperty.RegisterAttached("VirtualizationInfo");
internal static readonly Rect InvalidRect = new Rect(-1, -1, -1, -1);
@@ -379,7 +379,7 @@ namespace Avalonia.Controls
{
if (property == ItemsProperty)
{
- var newEnumerable = newValue.ValueOrDefault();
+ var newEnumerable = newValue.GetValueOrDefault();
var newDataSource = newEnumerable as ItemsSourceView;
if (newEnumerable != null && newDataSource == null)
{
@@ -390,19 +390,19 @@ namespace Avalonia.Controls
}
else if (property == ItemTemplateProperty)
{
- OnItemTemplateChanged(oldValue.ValueOrDefault(), newValue.ValueOrDefault());
+ OnItemTemplateChanged(oldValue.GetValueOrDefault(), newValue.GetValueOrDefault());
}
else if (property == LayoutProperty)
{
- OnLayoutChanged(oldValue.ValueOrDefault(), newValue.ValueOrDefault());
+ OnLayoutChanged(oldValue.GetValueOrDefault(), newValue.GetValueOrDefault());
}
else if (property == HorizontalCacheLengthProperty)
{
- _viewportManager.HorizontalCacheLength = newValue.ValueOrDefault();
+ _viewportManager.HorizontalCacheLength = newValue.GetValueOrDefault();
}
else if (property == VerticalCacheLengthProperty)
{
- _viewportManager.VerticalCacheLength = newValue.ValueOrDefault();
+ _viewportManager.VerticalCacheLength = newValue.GetValueOrDefault();
}
base.OnPropertyChanged(property, oldValue, newValue, priority);
@@ -559,33 +559,35 @@ namespace Avalonia.Controls
if (Layout != null)
{
- if (Layout is VirtualizingLayout virtualLayout)
- {
- var args = new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset);
+ var args = new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset);
+ try
+ {
_processingItemsSourceChange = args;
- try
+ if (Layout is VirtualizingLayout virtualLayout)
{
virtualLayout.OnItemsChanged(GetLayoutContext(), newValue, args);
}
- finally
- {
- _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)
+ else if (Layout is NonVirtualizingLayout nonVirtualLayout)
{
- 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();
}
diff --git a/src/Avalonia.Controls/Repeater/ViewManager.cs b/src/Avalonia.Controls/Repeater/ViewManager.cs
index 51c14d47d6..7d005a30b4 100644
--- a/src/Avalonia.Controls/Repeater/ViewManager.cs
+++ b/src/Avalonia.Controls/Repeater/ViewManager.cs
@@ -109,11 +109,22 @@ namespace Avalonia.Controls
public void ClearElementToElementFactory(IControl element)
{
- var virtInfo = ItemsRepeater.GetVirtualizationInfo(element);
- var clearedIndex = virtInfo.Index;
_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();
if (_lastFocusedElement == element)
@@ -121,9 +132,8 @@ namespace Avalonia.Controls
// 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
// realized elements.
- MoveFocusFromClearedIndex(clearedIndex);
+ MoveFocusFromClearedIndex(virtInfo.Index);
}
-
}
private void MoveFocusFromClearedIndex(int clearedIndex)
@@ -190,7 +200,8 @@ namespace Avalonia.Controls
{
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;
@@ -515,21 +526,52 @@ namespace Avalonia.Controls
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)
{
// 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;
- if (itemTemplateFactory == null)
+ return providedElementFactory;
+ }
+
+ IControl GetElement()
{
- // If no ItemTemplate was provided, use a default
- var factory = FuncDataTemplate.Default;
- _owner.ItemTemplate = factory;
- itemTemplateFactory = _owner.ItemTemplateShim;
+ if (providedElementFactory == null)
+ {
+ if (data is IControl dataAsElement)
+ {
+ return dataAsElement;
+ }
+ }
+
+ var elementFactory = GetElementFactory();
+ return elementFactory.GetElement(_owner, data);
}
- var data = _owner.ItemsSourceView.GetAt(index);
- var element = itemTemplateFactory.GetElement(_owner, data);
+ var element = GetElement();
var virtInfo = ItemsRepeater.TryGetVirtualizationInfo(element);
if (virtInfo == null)
@@ -537,8 +579,11 @@ namespace Avalonia.Controls
virtInfo = ItemsRepeater.CreateAndInitializeVirtualizationInfo(element);
}
- // Prepare the element
- element.DataContext = data;
+ if (data != element)
+ {
+ // Prepare the element
+ element.DataContext = data;
+ }
virtInfo.MoveOwnershipToLayoutFromElementFactory(
index,
diff --git a/src/Avalonia.Controls/Slider.cs b/src/Avalonia.Controls/Slider.cs
index 380dd0dd0e..e88a8bfb1b 100644
--- a/src/Avalonia.Controls/Slider.cs
+++ b/src/Avalonia.Controls/Slider.cs
@@ -147,7 +147,7 @@ namespace Avalonia.Controls
if (property == OrientationProperty)
{
- UpdatePseudoClasses(newValue.ValueOrDefault());
+ UpdatePseudoClasses(newValue.GetValueOrDefault());
}
}
diff --git a/src/Avalonia.Controls/Templates/FuncDataTemplate.cs b/src/Avalonia.Controls/Templates/FuncDataTemplate.cs
index 204540e431..76813af8e5 100644
--- a/src/Avalonia.Controls/Templates/FuncDataTemplate.cs
+++ b/src/Avalonia.Controls/Templates/FuncDataTemplate.cs
@@ -3,7 +3,6 @@
using System;
using System.Reactive.Linq;
-using System.Reflection;
namespace Avalonia.Controls.Templates
{
@@ -102,7 +101,7 @@ namespace Avalonia.Controls.Templates
///
private static bool IsInstance(object o, Type t)
{
- return (o != null) && t.GetTypeInfo().IsAssignableFrom(o.GetType().GetTypeInfo());
+ return t.IsInstanceOfType(o);
}
}
}
diff --git a/src/Avalonia.Controls/Templates/FuncTreeDataTemplate.cs b/src/Avalonia.Controls/Templates/FuncTreeDataTemplate.cs
index 998ef0e9f2..eba503e815 100644
--- a/src/Avalonia.Controls/Templates/FuncTreeDataTemplate.cs
+++ b/src/Avalonia.Controls/Templates/FuncTreeDataTemplate.cs
@@ -3,7 +3,6 @@
using System;
using System.Collections;
-using System.Reflection;
using Avalonia.Data;
namespace Avalonia.Controls.Templates
@@ -75,7 +74,7 @@ namespace Avalonia.Controls.Templates
///
private static bool IsInstance(object o, Type t)
{
- return (o != null) && t.GetTypeInfo().IsAssignableFrom(o.GetType().GetTypeInfo());
+ return t.IsInstanceOfType(o);
}
}
}
diff --git a/src/Avalonia.Controls/TextBlock.cs b/src/Avalonia.Controls/TextBlock.cs
index c7855ddfd1..8b8c7285be 100644
--- a/src/Avalonia.Controls/TextBlock.cs
+++ b/src/Avalonia.Controls/TextBlock.cs
@@ -352,7 +352,7 @@ namespace Avalonia.Controls
return new FormattedText
{
Constraint = constraint,
- Typeface = new Typeface(FontFamily, FontWeight, FontStyle),
+ Typeface = FontManager.Current?.GetOrAddTypeface(FontFamily, FontWeight, FontStyle),
FontSize = FontSize,
Text = text ?? string.Empty,
TextAlignment = TextAlignment,
diff --git a/src/Avalonia.Controls/TextBox.cs b/src/Avalonia.Controls/TextBox.cs
index becc4c8a61..b561a3423f 100644
--- a/src/Avalonia.Controls/TextBox.cs
+++ b/src/Avalonia.Controls/TextBox.cs
@@ -390,8 +390,10 @@ namespace Avalonia.Controls
{
return;
}
+
_undoRedoHelper.Snapshot();
HandleTextInput(text);
+ _undoRedoHelper.Snapshot();
}
protected override void OnKeyDown(KeyEventArgs e)
@@ -401,12 +403,12 @@ namespace Avalonia.Controls
bool movement = false;
bool selection = false;
bool handled = false;
- var modifiers = e.Modifiers;
+ var modifiers = e.KeyModifiers;
var keymap = AvaloniaLocator.Current.GetService();
bool Match(List gestures) => gestures.Any(g => g.Matches(e));
- bool DetectSelection() => e.Modifiers.HasFlag(keymap.SelectionModifiers);
+ bool DetectSelection() => e.KeyModifiers.HasFlag(keymap.SelectionModifiers);
if (Match(keymap.SelectAll))
{
diff --git a/src/Avalonia.Controls/TopLevel.cs b/src/Avalonia.Controls/TopLevel.cs
index 293809bf51..cabadac7d6 100644
--- a/src/Avalonia.Controls/TopLevel.cs
+++ b/src/Avalonia.Controls/TopLevel.cs
@@ -2,9 +2,7 @@
// Licensed under the MIT license. See licence.md file in the project root for full license information.
using System;
-using System.Linq;
using System.Reactive.Linq;
-using Avalonia.Controls.Notifications;
using Avalonia.Controls.Primitives;
using Avalonia.Input;
using Avalonia.Input.Raw;
@@ -15,7 +13,6 @@ using Avalonia.Platform;
using Avalonia.Rendering;
using Avalonia.Styling;
using Avalonia.Utilities;
-using Avalonia.VisualTree;
using JetBrains.Annotations;
namespace Avalonia.Controls
@@ -33,7 +30,8 @@ namespace Avalonia.Controls
ILayoutRoot,
IRenderRoot,
ICloseable,
- IStyleRoot,
+ IStyleHost,
+ ILogicalRoot,
IWeakSubscriber
{
///
@@ -269,6 +267,12 @@ namespace Avalonia.Controls
///
protected virtual void HandleClosed()
{
+ var logicalArgs = new LogicalTreeAttachmentEventArgs(this, this, null);
+ ((ILogical)this).NotifyDetachedFromLogicalTree(logicalArgs);
+
+ var visualArgs = new VisualTreeAttachmentEventArgs(this, this);
+ OnDetachedFromVisualTreeCore(visualArgs);
+
(this as IInputRoot).MouseDevice?.TopLevelClosed(this);
PlatformImpl = null;
OnClosed(EventArgs.Empty);
@@ -296,10 +300,7 @@ namespace Avalonia.Controls
/// The window scaling.
protected virtual void HandleScalingChanged(double scaling)
{
- foreach (ILayoutable control in this.GetSelfAndVisualDescendants())
- {
- control.InvalidateMeasure();
- }
+ LayoutHelper.InvalidateSelfAndChildrenMeasure(this);
}
///
diff --git a/src/Avalonia.Controls/TreeView.cs b/src/Avalonia.Controls/TreeView.cs
index 59844be8a6..6c8d58a8dd 100644
--- a/src/Avalonia.Controls/TreeView.cs
+++ b/src/Avalonia.Controls/TreeView.cs
@@ -393,8 +393,7 @@ namespace Avalonia.Controls
TreeViewItem.HeaderProperty,
TreeViewItem.ItemTemplateProperty,
TreeViewItem.ItemsProperty,
- TreeViewItem.IsExpandedProperty,
- new TreeContainerIndex());
+ TreeViewItem.IsExpandedProperty);
result.Index.Materialized += ContainerMaterialized;
return result;
}
@@ -407,7 +406,7 @@ namespace Avalonia.Controls
e.Handled = UpdateSelectionFromEventSource(
e.Source,
true,
- (e.InputModifiers & InputModifiers.Shift) != 0);
+ (e.KeyModifiers & KeyModifiers.Shift) != 0);
}
}
diff --git a/src/Avalonia.Controls/TreeViewItem.cs b/src/Avalonia.Controls/TreeViewItem.cs
index 07d5497c14..4d24337c3a 100644
--- a/src/Avalonia.Controls/TreeViewItem.cs
+++ b/src/Avalonia.Controls/TreeViewItem.cs
@@ -98,17 +98,18 @@ namespace Avalonia.Controls
TreeViewItem.HeaderProperty,
TreeViewItem.ItemTemplateProperty,
TreeViewItem.ItemsProperty,
- TreeViewItem.IsExpandedProperty,
- _treeView?.ItemContainerGenerator.Index ?? new TreeContainerIndex());
+ TreeViewItem.IsExpandedProperty);
}
///
protected override void OnAttachedToLogicalTree(LogicalTreeAttachmentEventArgs e)
{
base.OnAttachedToLogicalTree(e);
+
_treeView = this.GetLogicalAncestors().OfType().FirstOrDefault();
-
+
Level = CalculateDistanceFromLogicalParent(this) - 1;
+ ItemContainerGenerator.UpdateIndex();
if (ItemTemplate == null && _treeView?.ItemTemplate != null)
{
@@ -119,7 +120,7 @@ namespace Avalonia.Controls
protected override void OnDetachedFromLogicalTree(LogicalTreeAttachmentEventArgs e)
{
base.OnDetachedFromLogicalTree(e);
- ItemContainerGenerator.Clear();
+ ItemContainerGenerator.UpdateIndex();
}
protected virtual void OnRequestBringIntoView(RequestBringIntoViewEventArgs e)
diff --git a/src/Avalonia.Controls/Utils/AncestorFinder.cs b/src/Avalonia.Controls/Utils/AncestorFinder.cs
index d02202ca02..529aec1393 100644
--- a/src/Avalonia.Controls/Utils/AncestorFinder.cs
+++ b/src/Avalonia.Controls/Utils/AncestorFinder.cs
@@ -1,10 +1,8 @@
using System;
-using System.Linq;
using System.Reactive;
using System.Reactive.Disposables;
using System.Reactive.Linq;
using System.Reactive.Subjects;
-using System.Reflection;
namespace Avalonia.Controls.Utils
{
@@ -13,14 +11,14 @@ namespace Avalonia.Controls.Utils
class FinderNode : IDisposable
{
private readonly IStyledElement _control;
- private readonly TypeInfo _ancestorType;
+ private readonly Type _ancestorType;
public IObservable Observable => _subject;
private readonly Subject _subject = new Subject();
private FinderNode _child;
private IDisposable _disposable;
- public FinderNode(IStyledElement control, TypeInfo ancestorType)
+ public FinderNode(IStyledElement control, Type ancestorType)
{
_control = control;
_ancestorType = ancestorType;
@@ -33,7 +31,7 @@ namespace Avalonia.Controls.Utils
private void OnValueChanged(IStyledElement next)
{
- if (next == null || _ancestorType.IsAssignableFrom(next.GetType().GetTypeInfo()))
+ if (next == null || _ancestorType.IsAssignableFrom(next.GetType()))
_subject.OnNext(next);
else
{
@@ -63,7 +61,7 @@ namespace Avalonia.Controls.Utils
{
return new AnonymousObservable(observer =>
{
- var finder = new FinderNode(control, ancestorType.GetTypeInfo());
+ var finder = new FinderNode(control, ancestorType);
var subscription = finder.Observable.Subscribe(observer);
finder.Init();
diff --git a/src/Avalonia.Controls/Utils/BorderRenderHelper.cs b/src/Avalonia.Controls/Utils/BorderRenderHelper.cs
index a91e6a3a48..d6cf3df536 100644
--- a/src/Avalonia.Controls/Utils/BorderRenderHelper.cs
+++ b/src/Avalonia.Controls/Utils/BorderRenderHelper.cs
@@ -90,23 +90,17 @@ namespace Avalonia.Controls.Utils
{
var borderThickness = borders.Top;
var top = borderThickness * 0.5;
- var cornerRadius = (float)Math.Max(0, radii.TopLeft - borderThickness - top);
- if (background != null)
- {
- var topLeft = new Point(borders.Left, borders.Top);
- var bottomRight = new Point(size.Width - borders.Right, size.Height - borders.Bottom);
- var innerRect = new Rect(topLeft, bottomRight);
- context.FillRectangle(background, innerRect, cornerRadius);
- }
+ IPen pen = null;
- if (borderBrush != null && borderThickness > 0)
+ if (borderThickness > 0)
{
- var topLeft = new Point(top, top);
- var bottomRight = new Point(size.Width - top, size.Height - top);
- var outerRect = new Rect(topLeft, bottomRight);
- context.DrawRectangle(new Pen(borderBrush, borderThickness), outerRect, (float)radii.TopLeft);
+ pen = new Pen(borderBrush, borderThickness);
}
+
+ var rect = new Rect(top, top, size.Width - borderThickness, size.Height - borderThickness);
+
+ context.DrawRectangle(background, pen, rect, radii.TopLeft, radii.TopLeft);
}
}
diff --git a/src/Avalonia.Controls/Window.cs b/src/Avalonia.Controls/Window.cs
index 1816a6c81d..f66a248aaf 100644
--- a/src/Avalonia.Controls/Window.cs
+++ b/src/Avalonia.Controls/Window.cs
@@ -336,7 +336,6 @@ namespace Avalonia.Controls
if (close)
{
PlatformImpl?.Dispose();
- HandleClosed();
}
}
}
diff --git a/src/Avalonia.FreeDesktop/LinuxMountedVolumeInfoListener.cs b/src/Avalonia.FreeDesktop/LinuxMountedVolumeInfoListener.cs
index 8081528e55..f9737b461d 100644
--- a/src/Avalonia.FreeDesktop/LinuxMountedVolumeInfoListener.cs
+++ b/src/Avalonia.FreeDesktop/LinuxMountedVolumeInfoListener.cs
@@ -47,7 +47,8 @@ namespace Avalonia.FreeDesktop
var fProcMounts = File.ReadAllLines(ProcMountsDir)
.Select(x => x.Split(' '))
- .Select(x => (x[0], x[1]));
+ .Select(x => (x[0], x[1]))
+ .Where(x => !x.Item2.StartsWith("/snap/", StringComparison.InvariantCultureIgnoreCase));
var labelDirEnum = Directory.Exists(DevByLabelDir) ?
new DirectoryInfo(DevByLabelDir).GetFiles() : Enumerable.Empty();
diff --git a/src/Avalonia.Input/AccessKeyHandler.cs b/src/Avalonia.Input/AccessKeyHandler.cs
index aa009770f6..00e68d629b 100644
--- a/src/Avalonia.Input/AccessKeyHandler.cs
+++ b/src/Avalonia.Input/AccessKeyHandler.cs
@@ -140,7 +140,7 @@ namespace Avalonia.Input
/// The event args.
protected virtual void OnPreviewKeyDown(object sender, KeyEventArgs e)
{
- if (e.Key == Key.LeftAlt)
+ if (e.Key == Key.LeftAlt || e.Key == Key.RightAlt)
{
_altIsDown = true;
@@ -218,6 +218,7 @@ namespace Avalonia.Input
switch (e.Key)
{
case Key.LeftAlt:
+ case Key.RightAlt:
_altIsDown = false;
if (_ignoreAltUp)
diff --git a/src/Avalonia.Input/DragDrop.cs b/src/Avalonia.Input/DragDrop.cs
index d39659cee3..723d577964 100644
--- a/src/Avalonia.Input/DragDrop.cs
+++ b/src/Avalonia.Input/DragDrop.cs
@@ -23,7 +23,7 @@ namespace Avalonia.Input
///
public static readonly RoutedEvent DropEvent = RoutedEvent.Register("Drop", RoutingStrategies.Bubble, typeof(DragDrop));
- public static readonly AvaloniaProperty AllowDropProperty = AvaloniaProperty.RegisterAttached("AllowDrop", typeof(DragDrop), inherits: true);
+ public static readonly AttachedProperty AllowDropProperty = AvaloniaProperty.RegisterAttached("AllowDrop", typeof(DragDrop), inherits: true);
///
/// Gets a value indicating whether the given element can be used as the target of a drag-and-drop operation.
diff --git a/src/Avalonia.Input/DragDropDevice.cs b/src/Avalonia.Input/DragDropDevice.cs
index 25d3b6887f..bcd962bc31 100644
--- a/src/Avalonia.Input/DragDropDevice.cs
+++ b/src/Avalonia.Input/DragDropDevice.cs
@@ -19,7 +19,7 @@ namespace Avalonia.Input
return null;
}
- private DragDropEffects RaiseDragEvent(Interactive target, IInputRoot inputRoot, Point point, RoutedEvent routedEvent, DragDropEffects operation, IDataObject data, InputModifiers modifiers)
+ private DragDropEffects RaiseDragEvent(Interactive target, IInputRoot inputRoot, Point point, RoutedEvent routedEvent, DragDropEffects operation, IDataObject data, KeyModifiers modifiers)
{
if (target == null)
return DragDropEffects.None;
@@ -38,13 +38,13 @@ namespace Avalonia.Input
return args.DragEffects;
}
- private DragDropEffects DragEnter(IInputRoot inputRoot, Point point, IDataObject data, DragDropEffects effects, InputModifiers modifiers)
+ private DragDropEffects DragEnter(IInputRoot inputRoot, Point point, IDataObject data, DragDropEffects effects, KeyModifiers modifiers)
{
_lastTarget = GetTarget(inputRoot, point);
return RaiseDragEvent(_lastTarget, inputRoot, point, DragDrop.DragEnterEvent, effects, data, modifiers);
}
- private DragDropEffects DragOver(IInputRoot inputRoot, Point point, IDataObject data, DragDropEffects effects, InputModifiers modifiers)
+ private DragDropEffects DragOver(IInputRoot inputRoot, Point point, IDataObject data, DragDropEffects effects, KeyModifiers modifiers)
{
var target = GetTarget(inputRoot, point);
@@ -77,7 +77,7 @@ namespace Avalonia.Input
}
}
- private DragDropEffects Drop(IInputRoot inputRoot, Point point, IDataObject data, DragDropEffects effects, InputModifiers modifiers)
+ private DragDropEffects Drop(IInputRoot inputRoot, Point point, IDataObject data, DragDropEffects effects, KeyModifiers modifiers)
{
try
{
@@ -100,16 +100,16 @@ namespace Avalonia.Input
switch (e.Type)
{
case RawDragEventType.DragEnter:
- e.Effects = DragEnter(e.Root, e.Location, e.Data, e.Effects, e.Modifiers);
+ e.Effects = DragEnter(e.Root, e.Location, e.Data, e.Effects, e.KeyModifiers);
break;
case RawDragEventType.DragOver:
- e.Effects = DragOver(e.Root, e.Location, e.Data, e.Effects, e.Modifiers);
+ e.Effects = DragOver(e.Root, e.Location, e.Data, e.Effects, e.KeyModifiers);
break;
case RawDragEventType.DragLeave:
DragLeave(e.Root);
break;
case RawDragEventType.Drop:
- e.Effects = Drop(e.Root, e.Location, e.Data, e.Effects, e.Modifiers);
+ e.Effects = Drop(e.Root, e.Location, e.Data, e.Effects, e.KeyModifiers);
break;
}
}
diff --git a/src/Avalonia.Input/DragEventArgs.cs b/src/Avalonia.Input/DragEventArgs.cs
index dc0b76b225..08e9eb7d33 100644
--- a/src/Avalonia.Input/DragEventArgs.cs
+++ b/src/Avalonia.Input/DragEventArgs.cs
@@ -13,8 +13,11 @@ namespace Avalonia.Input
public IDataObject Data { get; private set; }
+ [Obsolete("Use KeyModifiers")]
public InputModifiers Modifiers { get; private set; }
+ public KeyModifiers KeyModifiers { get; private set; }
+
public Point GetPosition(IVisual relativeTo)
{
var point = new Point(0, 0);
@@ -32,13 +35,25 @@ namespace Avalonia.Input
return point;
}
+ [Obsolete("Use constructor taking KeyModifiers")]
public DragEventArgs(RoutedEvent routedEvent, IDataObject data, Interactive target, Point targetLocation, InputModifiers modifiers)
: base(routedEvent)
{
- this.Data = data;
- this._target = target;
- this._targetLocation = targetLocation;
- this.Modifiers = modifiers;
+ Data = data;
+ _target = target;
+ _targetLocation = targetLocation;
+ Modifiers = modifiers;
+ KeyModifiers = (KeyModifiers)(((int)modifiers) & 0xF);
+ }
+
+ public DragEventArgs(RoutedEvent routedEvent, IDataObject data, Interactive target, Point targetLocation, KeyModifiers keyModifiers)
+ : base(routedEvent)
+ {
+ Data = data;
+ _target = target;
+ _targetLocation = targetLocation;
+ Modifiers = (InputModifiers)keyModifiers;
+ KeyModifiers = keyModifiers;
}
}
diff --git a/src/Avalonia.Input/FocusManager.cs b/src/Avalonia.Input/FocusManager.cs
index 104ac9cb61..a9ce8ee494 100644
--- a/src/Avalonia.Input/FocusManager.cs
+++ b/src/Avalonia.Input/FocusManager.cs
@@ -4,6 +4,7 @@
using System;
using System.Collections.Generic;
using System.Linq;
+using System.Runtime.CompilerServices;
using Avalonia.Interactivity;
using Avalonia.VisualTree;
@@ -17,8 +18,8 @@ namespace Avalonia.Input
///
/// The focus scopes in which the focus is currently defined.
///
- private readonly Dictionary _focusScopes =
- new Dictionary();
+ private readonly ConditionalWeakTable _focusScopes =
+ new ConditionalWeakTable();
///
/// Initializes a new instance of the class.
@@ -110,7 +111,18 @@ namespace Avalonia.Input
{
Contract.Requires(scope != null);
- _focusScopes[scope] = element;
+ if (_focusScopes.TryGetValue(scope, out IInputElement existingElement))
+ {
+ if (element != existingElement)
+ {
+ _focusScopes.Remove(scope);
+ _focusScopes.Add(scope, element);
+ }
+ }
+ else
+ {
+ _focusScopes.Add(scope, element);
+ }
if (Scope == scope)
{
@@ -180,18 +192,18 @@ namespace Avalonia.Input
if (sender == e.Source && ev.MouseButton == MouseButton.Left)
{
- var element = (ev.Pointer?.Captured as IInputElement) ?? (e.Source as IInputElement);
+ IVisual element = ev.Pointer?.Captured ?? e.Source as IInputElement;
- if (element == null || !CanFocus(element))
+ while (element != null)
{
- element = element.GetSelfAndVisualAncestors()
- .OfType()
- .FirstOrDefault(CanFocus);
- }
+ if (element is IInputElement inputElement && CanFocus(inputElement))
+ {
+ Instance?.Focus(inputElement, NavigationMethod.Pointer, ev.InputModifiers);
- if (element != null)
- {
- Instance?.Focus(element, NavigationMethod.Pointer, ev.InputModifiers);
+ break;
+ }
+
+ element = element.VisualParent;
}
}
}
diff --git a/src/Avalonia.Input/Gestures.cs b/src/Avalonia.Input/Gestures.cs
index a5bd4feb64..a656d08c16 100644
--- a/src/Avalonia.Input/Gestures.cs
+++ b/src/Avalonia.Input/Gestures.cs
@@ -79,7 +79,7 @@ namespace Avalonia.Input
{
s_lastPress = new WeakReference(e.Source);
}
- else if (s_lastPress != null && e.ClickCount == 2 && e.MouseButton != MouseButton.Right)
+ else if (s_lastPress != null && e.ClickCount == 2 && e.MouseButton == MouseButton.Left)
{
if (s_lastPress.TryGetTarget(out var target) && target == e.Source)
{
diff --git a/src/Avalonia.Input/GotFocusEventArgs.cs b/src/Avalonia.Input/GotFocusEventArgs.cs
index 01e978a55c..8f9305e472 100644
--- a/src/Avalonia.Input/GotFocusEventArgs.cs
+++ b/src/Avalonia.Input/GotFocusEventArgs.cs
@@ -1,6 +1,7 @@
// Copyright (c) The Avalonia Project. All rights reserved.
// Licensed under the MIT license. See licence.md file in the project root for full license information.
+using System;
using Avalonia.Interactivity;
namespace Avalonia.Input
@@ -18,6 +19,16 @@ namespace Avalonia.Input
///
/// Gets or sets any input modifiers active at the time of focus.
///
- public InputModifiers InputModifiers { get; set; }
+ [Obsolete("Use KeyModifiers")]
+ public InputModifiers InputModifiers
+ {
+ get => (InputModifiers)KeyModifiers;
+ set => KeyModifiers = (KeyModifiers)((int)value & 0xF);
+ }
+
+ ///
+ /// Gets or sets any key modifiers active at the time of focus.
+ ///
+ public KeyModifiers KeyModifiers { get; set; }
}
}
diff --git a/src/Avalonia.Input/IKeyboardDevice.cs b/src/Avalonia.Input/IKeyboardDevice.cs
index 1a82f7d671..144979523f 100644
--- a/src/Avalonia.Input/IKeyboardDevice.cs
+++ b/src/Avalonia.Input/IKeyboardDevice.cs
@@ -48,6 +48,8 @@ namespace Avalonia.Input
LeftMouseButton = 16,
RightMouseButton = 32,
MiddleMouseButton = 64,
+ XButton1MouseButton = 128,
+ XButton2MouseButton = 256,
KeyboardMask = Alt | Control | Shift | Meta
}
diff --git a/src/Avalonia.Input/InputElement.cs b/src/Avalonia.Input/InputElement.cs
index 59dd6a7d4f..121412f2e4 100644
--- a/src/Avalonia.Input/InputElement.cs
+++ b/src/Avalonia.Input/InputElement.cs
@@ -345,7 +345,7 @@ namespace Avalonia.Input
}
///
- /// Gets or sets a value indicating whether the control is focused.
+ /// Gets a value indicating whether the control is focused.
///
public bool IsFocused
{
@@ -363,7 +363,7 @@ namespace Avalonia.Input
}
///
- /// Gets or sets a value indicating whether the pointer is currently over the control.
+ /// Gets a value indicating whether the pointer is currently over the control.
///
public bool IsPointerOver
{
@@ -535,11 +535,11 @@ namespace Avalonia.Input
if (property == IsFocusedProperty)
{
- UpdatePseudoClasses(newValue.ValueOrDefault(), null);
+ UpdatePseudoClasses(newValue.GetValueOrDefault(), null);
}
else if (property == IsPointerOverProperty)
{
- UpdatePseudoClasses(null, newValue.ValueOrDefault());
+ UpdatePseudoClasses(null, newValue.GetValueOrDefault());
}
}
diff --git a/src/Avalonia.Input/InputExtensions.cs b/src/Avalonia.Input/InputExtensions.cs
index f83c41e266..4dcee0bbc8 100644
--- a/src/Avalonia.Input/InputExtensions.cs
+++ b/src/Avalonia.Input/InputExtensions.cs
@@ -38,7 +38,9 @@ namespace Avalonia.Input
/// The topmost at the specified position.
public static IInputElement InputHitTest(this IInputElement element, Point p)
{
- return element.GetInputElementsAt(p).FirstOrDefault();
+ Contract.Requires(element != null);
+
+ return element.GetVisualAt(p, s_hitTestDelegate) as IInputElement;
}
private static bool IsHitTestVisible(IVisual visual)
diff --git a/src/Avalonia.Input/MouseDevice.cs b/src/Avalonia.Input/MouseDevice.cs
index 4dcf0eee53..c74498171e 100644
--- a/src/Avalonia.Input/MouseDevice.cs
+++ b/src/Avalonia.Input/MouseDevice.cs
@@ -120,6 +120,10 @@ namespace Avalonia.Input
rv++;
if (props.IsRightButtonPressed)
rv++;
+ if (props.IsXButton1Pressed)
+ rv++;
+ if (props.IsXButton2Pressed)
+ rv++;
return rv;
}
@@ -142,6 +146,8 @@ namespace Avalonia.Input
case RawPointerEventType.LeftButtonDown:
case RawPointerEventType.RightButtonDown:
case RawPointerEventType.MiddleButtonDown:
+ case RawPointerEventType.XButton1Down:
+ case RawPointerEventType.XButton2Down:
if (ButtonCount(props) > 1)
e.Handled = MouseMove(mouse, e.Timestamp, e.Root, e.Position, props, keyModifiers);
else
@@ -151,6 +157,8 @@ namespace Avalonia.Input
case RawPointerEventType.LeftButtonUp:
case RawPointerEventType.RightButtonUp:
case RawPointerEventType.MiddleButtonUp:
+ case RawPointerEventType.XButton1Up:
+ case RawPointerEventType.XButton2Up:
if (ButtonCount(props) != 0)
e.Handled = MouseMove(mouse, e.Timestamp, e.Root, e.Position, props, keyModifiers);
else
@@ -186,12 +194,20 @@ namespace Avalonia.Input
kind = PointerUpdateKind.MiddleButtonPressed;
if (args.Type == RawPointerEventType.RightButtonDown)
kind = PointerUpdateKind.RightButtonPressed;
+ if (args.Type == RawPointerEventType.XButton1Down)
+ kind = PointerUpdateKind.XButton1Pressed;
+ if (args.Type == RawPointerEventType.XButton2Down)
+ kind = PointerUpdateKind.XButton2Pressed;
if (args.Type == RawPointerEventType.LeftButtonUp)
kind = PointerUpdateKind.LeftButtonReleased;
if (args.Type == RawPointerEventType.MiddleButtonUp)
kind = PointerUpdateKind.MiddleButtonReleased;
if (args.Type == RawPointerEventType.RightButtonUp)
kind = PointerUpdateKind.RightButtonReleased;
+ if (args.Type == RawPointerEventType.XButton1Up)
+ kind = PointerUpdateKind.XButton1Released;
+ if (args.Type == RawPointerEventType.XButton2Up)
+ kind = PointerUpdateKind.XButton2Released;
return new PointerPointProperties(args.InputModifiers, kind);
}
diff --git a/src/Avalonia.Input/Platform/PlatformHotkeyConfiguration.cs b/src/Avalonia.Input/Platform/PlatformHotkeyConfiguration.cs
index a758a328be..053f894755 100644
--- a/src/Avalonia.Input/Platform/PlatformHotkeyConfiguration.cs
+++ b/src/Avalonia.Input/Platform/PlatformHotkeyConfiguration.cs
@@ -4,14 +4,14 @@ namespace Avalonia.Input.Platform
{
public class PlatformHotkeyConfiguration
{
- public PlatformHotkeyConfiguration() : this(InputModifiers.Control)
+ public PlatformHotkeyConfiguration() : this(KeyModifiers.Control)
{
}
- public PlatformHotkeyConfiguration(InputModifiers commandModifiers,
- InputModifiers selectionModifiers = InputModifiers.Shift,
- InputModifiers wholeWordTextActionModifiers = InputModifiers.Control)
+ public PlatformHotkeyConfiguration(KeyModifiers commandModifiers,
+ KeyModifiers selectionModifiers = KeyModifiers.Shift,
+ KeyModifiers wholeWordTextActionModifiers = KeyModifiers.Control)
{
CommandModifiers = commandModifiers;
SelectionModifiers = selectionModifiers;
@@ -75,9 +75,9 @@ namespace Avalonia.Input.Platform
};
}
- public InputModifiers CommandModifiers { get; set; }
- public InputModifiers WholeWordTextActionModifiers { get; set; }
- public InputModifiers SelectionModifiers { get; set; }
+ public KeyModifiers CommandModifiers { get; set; }
+ public KeyModifiers WholeWordTextActionModifiers { get; set; }
+ public KeyModifiers SelectionModifiers { get; set; }
public List Copy { get; set; }
public List Cut { get; set; }
public List Paste { get; set; }
diff --git a/src/Avalonia.Input/Pointer.cs b/src/Avalonia.Input/Pointer.cs
index 819d231b31..00222e92cf 100644
--- a/src/Avalonia.Input/Pointer.cs
+++ b/src/Avalonia.Input/Pointer.cs
@@ -55,9 +55,11 @@ namespace Avalonia.Input
Captured.DetachedFromVisualTree += OnCaptureDetached;
}
- IInputElement GetNextCapture(IVisual parent) =>
- parent as IInputElement ?? parent.GetVisualAncestors().OfType().FirstOrDefault();
-
+ IInputElement GetNextCapture(IVisual parent)
+ {
+ return parent as IInputElement ?? parent.FindAncestorOfType();
+ }
+
private void OnCaptureDetached(object sender, VisualTreeAttachmentEventArgs e)
{
Capture(GetNextCapture(e.Parent));
diff --git a/src/Avalonia.Input/PointerPoint.cs b/src/Avalonia.Input/PointerPoint.cs
index 1068a0d4d4..96132fdc90 100644
--- a/src/Avalonia.Input/PointerPoint.cs
+++ b/src/Avalonia.Input/PointerPoint.cs
@@ -21,18 +21,24 @@ namespace Avalonia.Input
public bool IsLeftButtonPressed { get; }
public bool IsMiddleButtonPressed { get; }
public bool IsRightButtonPressed { get; }
+ public bool IsXButton1Pressed { get; }
+ public bool IsXButton2Pressed { get; }
+
public PointerUpdateKind PointerUpdateKind { get; }
+
private PointerPointProperties()
- {
-
+ {
}
public PointerPointProperties(RawInputModifiers modifiers, PointerUpdateKind kind)
{
PointerUpdateKind = kind;
+
IsLeftButtonPressed = modifiers.HasFlagCustom(RawInputModifiers.LeftMouseButton);
IsMiddleButtonPressed = modifiers.HasFlagCustom(RawInputModifiers.MiddleMouseButton);
IsRightButtonPressed = modifiers.HasFlagCustom(RawInputModifiers.RightMouseButton);
+ IsXButton1Pressed = modifiers.HasFlagCustom(RawInputModifiers.XButton1MouseButton);
+ IsXButton2Pressed = modifiers.HasFlagCustom(RawInputModifiers.XButton2MouseButton);
// The underlying input source might be reporting the previous state,
// so make sure that we reflect the current state
@@ -49,6 +55,14 @@ namespace Avalonia.Input
IsRightButtonPressed = true;
if (kind == PointerUpdateKind.RightButtonReleased)
IsRightButtonPressed = false;
+ if (kind == PointerUpdateKind.XButton1Pressed)
+ IsXButton1Pressed = true;
+ if (kind == PointerUpdateKind.XButton1Released)
+ IsXButton1Pressed = false;
+ if (kind == PointerUpdateKind.XButton2Pressed)
+ IsXButton2Pressed = true;
+ if (kind == PointerUpdateKind.XButton2Released)
+ IsXButton2Pressed = false;
}
public static PointerPointProperties None { get; } = new PointerPointProperties();
@@ -59,9 +73,13 @@ namespace Avalonia.Input
LeftButtonPressed,
MiddleButtonPressed,
RightButtonPressed,
+ XButton1Pressed,
+ XButton2Pressed,
LeftButtonReleased,
MiddleButtonReleased,
RightButtonReleased,
+ XButton1Released,
+ XButton2Released,
Other
}
diff --git a/src/Avalonia.Input/Raw/RawDragEvent.cs b/src/Avalonia.Input/Raw/RawDragEvent.cs
index 5722e17593..4193cdafc5 100644
--- a/src/Avalonia.Input/Raw/RawDragEvent.cs
+++ b/src/Avalonia.Input/Raw/RawDragEvent.cs
@@ -1,4 +1,6 @@
-namespace Avalonia.Input.Raw
+using System;
+
+namespace Avalonia.Input.Raw
{
public class RawDragEvent : RawInputEventArgs
{
@@ -6,7 +8,9 @@
public IDataObject Data { get; }
public DragDropEffects Effects { get; set; }
public RawDragEventType Type { get; }
+ [Obsolete("Use KeyModifiers")]
public InputModifiers Modifiers { get; }
+ public KeyModifiers KeyModifiers { get; }
public RawDragEvent(IDragDropDevice inputDevice, RawDragEventType type,
IInputRoot root, Point location, IDataObject data, DragDropEffects effects, RawInputModifiers modifiers)
@@ -17,6 +21,7 @@
Data = data;
Effects = effects;
Modifiers = (InputModifiers)modifiers;
+ KeyModifiers = KeyModifiersUtils.ConvertToKey(modifiers);
}
}
}
diff --git a/src/Avalonia.Input/Raw/RawPointerEventArgs.cs b/src/Avalonia.Input/Raw/RawPointerEventArgs.cs
index 56854c7d29..4b2c95943d 100644
--- a/src/Avalonia.Input/Raw/RawPointerEventArgs.cs
+++ b/src/Avalonia.Input/Raw/RawPointerEventArgs.cs
@@ -14,6 +14,10 @@ namespace Avalonia.Input.Raw
RightButtonUp,
MiddleButtonDown,
MiddleButtonUp,
+ XButton1Down,
+ XButton1Up,
+ XButton2Down,
+ XButton2Up,
Move,
Wheel,
NonClientLeftButtonDown,
diff --git a/src/Avalonia.Interactivity/RoutedEvent.cs b/src/Avalonia.Interactivity/RoutedEvent.cs
index bc5dec9a90..55d9e61d87 100644
--- a/src/Avalonia.Interactivity/RoutedEvent.cs
+++ b/src/Avalonia.Interactivity/RoutedEvent.cs
@@ -3,7 +3,6 @@
using System;
using System.Reactive.Subjects;
-using System.Reflection;
namespace Avalonia.Interactivity
{
@@ -29,7 +28,7 @@ namespace Avalonia.Interactivity
Contract.Requires(name != null);
Contract.Requires(eventArgsType != null);
Contract.Requires(ownerType != null);
- Contract.Requires(typeof(RoutedEventArgs).GetTypeInfo().IsAssignableFrom(eventArgsType.GetTypeInfo()));
+ Contract.Requires(typeof(RoutedEventArgs).IsAssignableFrom(eventArgsType));
EventArgsType = eventArgsType;
Name = name;
diff --git a/src/Avalonia.Layout/FlowLayoutAlgorithm.cs b/src/Avalonia.Layout/FlowLayoutAlgorithm.cs
index 615ce725bd..7f44c80a64 100644
--- a/src/Avalonia.Layout/FlowLayoutAlgorithm.cs
+++ b/src/Avalonia.Layout/FlowLayoutAlgorithm.cs
@@ -72,6 +72,7 @@ namespace Avalonia.Layout
bool isWrapping,
double minItemSpacing,
double lineSpacing,
+ int maxItemsPerLine,
ScrollOrientation orientation,
string layoutId)
{
@@ -94,14 +95,14 @@ namespace Avalonia.Layout
_elementManager.OnBeginMeasure(orientation);
int anchorIndex = GetAnchorIndex(availableSize, isWrapping, minItemSpacing, layoutId);
- Generate(GenerateDirection.Forward, anchorIndex, availableSize, minItemSpacing, lineSpacing, layoutId);
- Generate(GenerateDirection.Backward, anchorIndex, availableSize, minItemSpacing, lineSpacing, layoutId);
+ Generate(GenerateDirection.Forward, anchorIndex, availableSize, minItemSpacing, lineSpacing, maxItemsPerLine, layoutId);
+ Generate(GenerateDirection.Backward, anchorIndex, availableSize, minItemSpacing, lineSpacing, maxItemsPerLine, layoutId);
if (isWrapping && IsReflowRequired())
{
var firstElementBounds = _elementManager.GetLayoutBoundsForRealizedIndex(0);
_orientation.SetMinorStart(ref firstElementBounds, 0);
_elementManager.SetLayoutBoundsForRealizedIndex(0, firstElementBounds);
- Generate(GenerateDirection.Forward, 0 /*anchorIndex*/, availableSize, minItemSpacing, lineSpacing, layoutId);
+ Generate(GenerateDirection.Forward, 0 /*anchorIndex*/, availableSize, minItemSpacing, lineSpacing, maxItemsPerLine, layoutId);
}
RaiseLineArranged();
@@ -115,10 +116,11 @@ namespace Avalonia.Layout
public Size Arrange(
Size finalSize,
VirtualizingLayoutContext context,
+ bool isWrapping,
LineAlignment lineAlignment,
string layoutId)
{
- ArrangeVirtualizingLayout(finalSize, lineAlignment, layoutId);
+ ArrangeVirtualizingLayout(finalSize, lineAlignment, isWrapping, layoutId);
return new Size(
Math.Max(finalSize.Width, _lastExtent.Width),
@@ -270,6 +272,7 @@ namespace Avalonia.Layout
Size availableSize,
double minItemSpacing,
double lineSpacing,
+ int maxItemsPerLine,
string layoutId)
{
if (anchorIndex != -1)
@@ -280,7 +283,7 @@ namespace Avalonia.Layout
var anchorBounds = _elementManager.GetLayoutBoundsForDataIndex(anchorIndex);
var lineOffset = _orientation.MajorStart(anchorBounds);
var lineMajorSize = _orientation.MajorSize(anchorBounds);
- int countInLine = 1;
+ var countInLine = 1;
int count = 0;
bool lineNeedsReposition = false;
@@ -301,7 +304,7 @@ namespace Avalonia.Layout
if (direction == GenerateDirection.Forward)
{
double remainingSpace = _orientation.Minor(availableSize) - (_orientation.MinorStart(previousElementBounds) + _orientation.MinorSize(previousElementBounds) + minItemSpacing + _orientation.Minor(desiredSize));
- if (_algorithmCallbacks.Algorithm_ShouldBreakLine(currentIndex, remainingSpace))
+ if (countInLine >= maxItemsPerLine || _algorithmCallbacks.Algorithm_ShouldBreakLine(currentIndex, remainingSpace))
{
// No more space in this row. wrap to next row.
_orientation.SetMinorStart(ref currentBounds, 0);
@@ -339,7 +342,7 @@ namespace Avalonia.Layout
{
// Backward
double remainingSpace = _orientation.MinorStart(previousElementBounds) - (_orientation.Minor(desiredSize) + minItemSpacing);
- if (_algorithmCallbacks.Algorithm_ShouldBreakLine(currentIndex, remainingSpace))
+ if (countInLine >= maxItemsPerLine || _algorithmCallbacks.Algorithm_ShouldBreakLine(currentIndex, remainingSpace))
{
// Does not fit, wrap to the previous row
var availableSizeMinor = _orientation.Minor(availableSize);
@@ -544,6 +547,7 @@ namespace Avalonia.Layout
private void ArrangeVirtualizingLayout(
Size finalSize,
LineAlignment lineAlignment,
+ bool isWrapping,
string layoutId)
{
// Walk through the realized elements one line at a time and
@@ -563,7 +567,7 @@ namespace Avalonia.Layout
if (_orientation.MajorStart(currentBounds) != currentLineOffset)
{
spaceAtLineEnd = _orientation.Minor(finalSize) - _orientation.MinorStart(previousElementBounds) - _orientation.MinorSize(previousElementBounds);
- PerformLineAlignment(i - countInLine, countInLine, spaceAtLineStart, spaceAtLineEnd, currentLineSize, lineAlignment, layoutId);
+ PerformLineAlignment(i - countInLine, countInLine, spaceAtLineStart, spaceAtLineEnd, currentLineSize, lineAlignment, isWrapping, finalSize, layoutId);
spaceAtLineStart = _orientation.MinorStart(currentBounds);
countInLine = 0;
currentLineOffset = _orientation.MajorStart(currentBounds);
@@ -580,7 +584,7 @@ namespace Avalonia.Layout
if (countInLine > 0)
{
var spaceAtEnd = _orientation.Minor(finalSize) - _orientation.MinorStart(previousElementBounds) - _orientation.MinorSize(previousElementBounds);
- PerformLineAlignment(realizedElementCount - countInLine, countInLine, spaceAtLineStart, spaceAtEnd, currentLineSize, lineAlignment, layoutId);
+ PerformLineAlignment(realizedElementCount - countInLine, countInLine, spaceAtLineStart, spaceAtEnd, currentLineSize, lineAlignment, isWrapping, finalSize, layoutId);
}
}
}
@@ -594,6 +598,8 @@ namespace Avalonia.Layout
double spaceAtLineEnd,
double lineSize,
LineAlignment lineAlignment,
+ bool isWrapping,
+ Size finalSize,
string layoutId)
{
for (int rangeIndex = lineStartIndex; rangeIndex < lineStartIndex + countInLine; ++rangeIndex)
@@ -659,6 +665,14 @@ namespace Avalonia.Layout
}
bounds = bounds.Translate(-_lastExtent.Position);
+
+ if (!isWrapping)
+ {
+ _orientation.SetMinorSize(
+ ref bounds,
+ Math.Max(_orientation.MinorSize(bounds), _orientation.Minor(finalSize)));
+ }
+
var element = _elementManager.GetAt(rangeIndex);
element.Arrange(bounds);
}
diff --git a/src/Avalonia.Layout/LayoutHelper.cs b/src/Avalonia.Layout/LayoutHelper.cs
index cfb4b14b1c..c235bcd90f 100644
--- a/src/Avalonia.Layout/LayoutHelper.cs
+++ b/src/Avalonia.Layout/LayoutHelper.cs
@@ -2,6 +2,7 @@
// Licensed under the MIT license. See licence.md file in the project root for full license information.
using System;
+using Avalonia.VisualTree;
namespace Avalonia.Layout
{
@@ -61,5 +62,31 @@ namespace Avalonia.Layout
return availableSize;
}
+
+ ///
+ /// Invalidates measure for given control and all visual children recursively.
+ ///
+ public static void InvalidateSelfAndChildrenMeasure(ILayoutable control)
+ {
+ void InnerInvalidateMeasure(IVisual target)
+ {
+ if (target is ILayoutable targetLayoutable)
+ {
+ targetLayoutable.InvalidateMeasure();
+ }
+
+ var visualChildren = target.VisualChildren;
+ var visualChildrenCount = visualChildren.Count;
+
+ for (int i = 0; i < visualChildrenCount; i++)
+ {
+ IVisual child = visualChildren[i];
+
+ InnerInvalidateMeasure(child);
+ }
+ }
+
+ InnerInvalidateMeasure(control);
+ }
}
}
diff --git a/src/Avalonia.Layout/Layoutable.cs b/src/Avalonia.Layout/Layoutable.cs
index 4732808b91..9d0a5c57ee 100644
--- a/src/Avalonia.Layout/Layoutable.cs
+++ b/src/Avalonia.Layout/Layoutable.cs
@@ -2,7 +2,6 @@
// Licensed under the MIT license. See licence.md file in the project root for full license information.
using System;
-using System.Linq;
using Avalonia.Logging;
using Avalonia.VisualTree;
@@ -694,12 +693,9 @@ namespace Avalonia.Layout
}
///
- protected override sealed void OnVisualParentChanged(IVisual oldParent, IVisual newParent)
+ protected sealed override void OnVisualParentChanged(IVisual oldParent, IVisual newParent)
{
- foreach (ILayoutable i in this.GetSelfAndVisualDescendants())
- {
- i.InvalidateMeasure();
- }
+ LayoutHelper.InvalidateSelfAndChildrenMeasure(this);
base.OnVisualParentChanged(oldParent, newParent);
}
diff --git a/src/Avalonia.Layout/NonVirtualizingLayout.cs b/src/Avalonia.Layout/NonVirtualizingLayout.cs
index fba91e66c7..5d27ba9199 100644
--- a/src/Avalonia.Layout/NonVirtualizingLayout.cs
+++ b/src/Avalonia.Layout/NonVirtualizingLayout.cs
@@ -20,25 +20,25 @@ namespace Avalonia.Layout
///
public sealed override void InitializeForContext(LayoutContext context)
{
- InitializeForContextCore((VirtualizingLayoutContext)context);
+ InitializeForContextCore((NonVirtualizingLayoutContext)context);
}
///
public sealed override void UninitializeForContext(LayoutContext context)
{
- UninitializeForContextCore((VirtualizingLayoutContext)context);
+ UninitializeForContextCore((NonVirtualizingLayoutContext)context);
}
///
public sealed override Size Measure(LayoutContext context, Size availableSize)
{
- return MeasureOverride((VirtualizingLayoutContext)context, availableSize);
+ return MeasureOverride((NonVirtualizingLayoutContext)context, availableSize);
}
///
public sealed override Size Arrange(LayoutContext context, Size finalSize)
{
- return ArrangeOverride((VirtualizingLayoutContext)context, finalSize);
+ return ArrangeOverride((NonVirtualizingLayoutContext)context, finalSize);
}
///
@@ -49,7 +49,7 @@ namespace Avalonia.Layout
/// The context object that facilitates communication between the layout and its host
/// container.
///
- protected virtual void InitializeForContextCore(VirtualizingLayoutContext context)
+ protected virtual void InitializeForContextCore(LayoutContext context)
{
}
@@ -61,7 +61,7 @@ namespace Avalonia.Layout
/// The context object that facilitates communication between the layout and its host
/// container.
///
- protected virtual void UninitializeForContextCore(VirtualizingLayoutContext context)
+ protected virtual void UninitializeForContextCore(LayoutContext context)
{
}
@@ -83,7 +83,7 @@ namespace Avalonia.Layout
/// of the allocated sizes for child objects or based on other considerations such as a
/// fixed container size.
///
- protected abstract Size MeasureOverride(VirtualizingLayoutContext context, Size availableSize);
+ protected abstract Size MeasureOverride(NonVirtualizingLayoutContext context, Size availableSize);
///
/// When implemented in a derived class, provides the behavior for the "Arrange" pass of
@@ -98,6 +98,6 @@ namespace Avalonia.Layout
/// its children.
///
/// The actual size that is used after the element is arranged in layout.
- protected virtual Size ArrangeOverride(VirtualizingLayoutContext context, Size finalSize) => finalSize;
+ protected virtual Size ArrangeOverride(NonVirtualizingLayoutContext context, Size finalSize) => finalSize;
}
}
diff --git a/src/Avalonia.Layout/NonVirtualizingLayoutContext.cs b/src/Avalonia.Layout/NonVirtualizingLayoutContext.cs
new file mode 100644
index 0000000000..d3dec83e9b
--- /dev/null
+++ b/src/Avalonia.Layout/NonVirtualizingLayoutContext.cs
@@ -0,0 +1,14 @@
+// This source file is adapted from the WinUI project.
+// (https://github.com/microsoft/microsoft-ui-xaml)
+//
+// Licensed to The Avalonia Project under MIT License, courtesy of The .NET Foundation.
+
+namespace Avalonia.Layout
+{
+ ///
+ /// Represents the base class for layout context types that do not support virtualization.
+ ///
+ public abstract class NonVirtualizingLayoutContext : LayoutContext
+ {
+ }
+}
diff --git a/src/Avalonia.Layout/StackLayout.cs b/src/Avalonia.Layout/StackLayout.cs
index e95a87dd16..e8ad49e9b9 100644
--- a/src/Avalonia.Layout/StackLayout.cs
+++ b/src/Avalonia.Layout/StackLayout.cs
@@ -268,6 +268,7 @@ namespace Avalonia.Layout
false,
0,
Spacing,
+ int.MaxValue,
_orientation.ScrollOrientation,
LayoutId);
@@ -279,6 +280,7 @@ namespace Avalonia.Layout
var value = GetFlowAlgorithm(context).Arrange(
finalSize,
context,
+ false,
FlowLayoutAlgorithm.LineAlignment.Start,
LayoutId);
@@ -298,7 +300,7 @@ namespace Avalonia.Layout
{
if (property == OrientationProperty)
{
- var orientation = newValue.ValueOrDefault();
+ var orientation = newValue.GetValueOrDefault();
//Note: For StackLayout Vertical Orientation means we have a Vertical ScrollOrientation.
//Horizontal Orientation means we have a Horizontal ScrollOrientation.
diff --git a/src/Avalonia.Layout/UniformGridLayout.cs b/src/Avalonia.Layout/UniformGridLayout.cs
index feaf98553d..54c3ccbb90 100644
--- a/src/Avalonia.Layout/UniformGridLayout.cs
+++ b/src/Avalonia.Layout/UniformGridLayout.cs
@@ -111,6 +111,12 @@ namespace Avalonia.Layout
public static readonly StyledProperty MinRowSpacingProperty =
AvaloniaProperty.Register(nameof(MinRowSpacing));
+ ///
+ /// Defines the property.
+ ///
+ public static readonly StyledProperty MaximumRowsOrColumnsProperty =
+ AvaloniaProperty.Register(nameof(MinItemWidth));
+
///
/// Defines the property.
///
@@ -124,6 +130,7 @@ namespace Avalonia.Layout
private double _minColumnSpacing;
private UniformGridLayoutItemsJustification _itemsJustification;
private UniformGridLayoutItemsStretch _itemsStretch;
+ private int _maximumRowsOrColumns = int.MaxValue;
///
/// Initializes a new instance of the class.
@@ -220,6 +227,15 @@ namespace Avalonia.Layout
set => SetValue(MinRowSpacingProperty, value);
}
+ ///
+ /// Gets or sets the maximum row or column count.
+ ///
+ public int MaximumRowsOrColumns
+ {
+ get => GetValue(MaximumRowsOrColumnsProperty);
+ set => SetValue(MaximumRowsOrColumnsProperty, value);
+ }
+
///
/// Gets or sets the axis along which items are laid out.
///
@@ -270,15 +286,17 @@ namespace Avalonia.Layout
{
var gridState = (UniformGridLayoutState)context.LayoutState;
var lastExtent = gridState.FlowAlgorithm.LastExtent;
- int itemsPerLine = Math.Max(1, (int)(_orientation.Minor(availableSize) / GetMinorSizeWithSpacing(context)));
- double majorSize = (itemsCount / itemsPerLine) * GetMajorSizeWithSpacing(context);
- double realizationWindowStartWithinExtent = _orientation.MajorStart(realizationRect) - _orientation.MajorStart(lastExtent);
+ var itemsPerLine = Math.Min( // note use of unsigned ints
+ Math.Max(1u, (uint)(_orientation.Minor(availableSize) / GetMinorSizeWithSpacing(context))),
+ Math.Max(1u, (uint)_maximumRowsOrColumns));
+ var majorSize = (itemsCount / itemsPerLine) * GetMajorSizeWithSpacing(context);
+ var realizationWindowStartWithinExtent = _orientation.MajorStart(realizationRect) - _orientation.MajorStart(lastExtent);
if ((realizationWindowStartWithinExtent + _orientation.MajorSize(realizationRect)) >= 0 && realizationWindowStartWithinExtent <= majorSize)
{
double offset = Math.Max(0.0, _orientation.MajorStart(realizationRect) - _orientation.MajorStart(lastExtent));
int anchorRowIndex = (int)(offset / GetMajorSizeWithSpacing(context));
- anchorIndex = Math.Max(0, Math.Min(itemsCount - 1, anchorRowIndex * itemsPerLine));
+ anchorIndex = (int)Math.Max(0, Math.Min(itemsCount - 1, anchorRowIndex * itemsPerLine));
bounds = GetLayoutRectForDataIndex(availableSize, anchorIndex, lastExtent, context);
}
}
@@ -300,7 +318,9 @@ namespace Avalonia.Layout
int count = context.ItemCount;
if (targetIndex >= 0 && targetIndex < count)
{
- int itemsPerLine = Math.Max(1, (int)(_orientation.Minor(availableSize) / GetMinorSizeWithSpacing(context)));
+ int itemsPerLine = (int)Math.Min( // note use of unsigned ints
+ Math.Max(1u, (uint)(_orientation.Minor(availableSize) / GetMinorSizeWithSpacing(context))),
+ Math.Max(1u, _maximumRowsOrColumns));
int indexOfFirstInLine = (targetIndex / itemsPerLine) * itemsPerLine;
index = indexOfFirstInLine;
var state = context.LayoutState as UniformGridLayoutState;
@@ -330,17 +350,21 @@ namespace Avalonia.Layout
// Constants
int itemsCount = context.ItemCount;
double availableSizeMinor = _orientation.Minor(availableSize);
- int itemsPerLine = Math.Max(1, !double.IsInfinity(availableSizeMinor) ?
- (int)(availableSizeMinor / GetMinorSizeWithSpacing(context)) : itemsCount);
+ int itemsPerLine =
+ (int)Math.Min( // note use of unsigned ints
+ Math.Max(1u, !double.IsInfinity(availableSizeMinor)
+ ? (uint)(availableSizeMinor / GetMinorSizeWithSpacing(context))
+ : (uint)itemsCount),
+ Math.Max(1u, _maximumRowsOrColumns));
double lineSize = GetMajorSizeWithSpacing(context);
if (itemsCount > 0)
{
_orientation.SetMinorSize(
ref extent,
- !double.IsInfinity(availableSizeMinor) ?
+ !double.IsInfinity(availableSizeMinor) && _itemsStretch == UniformGridLayoutItemsStretch.Fill ?
availableSizeMinor :
- Math.Max(0.0, itemsCount * GetMinorSizeWithSpacing(context) - (double)MinItemSpacing));
+ Math.Max(0.0, itemsPerLine * GetMinorSizeWithSpacing(context) - (double)MinItemSpacing));
_orientation.SetMajorSize(
ref extent,
Math.Max(0.0, (itemsCount / itemsPerLine) * lineSize - (double)LineSpacing));
@@ -399,7 +423,7 @@ namespace Avalonia.Layout
// Set the width and height on the grid state. If the user already set them then use the preset.
// If not, we have to measure the first element and get back a size which we're going to be using for the rest of the items.
var gridState = (UniformGridLayoutState)context.LayoutState;
- gridState.EnsureElementSize(availableSize, context, _minItemWidth, _minItemHeight, _itemsStretch, Orientation, MinRowSpacing, MinColumnSpacing);
+ gridState.EnsureElementSize(availableSize, context, _minItemWidth, _minItemHeight, _itemsStretch, Orientation, MinRowSpacing, MinColumnSpacing, _maximumRowsOrColumns);
var desiredSize = GetFlowAlgorithm(context).Measure(
availableSize,
@@ -407,6 +431,7 @@ namespace Avalonia.Layout
true,
MinItemSpacing,
LineSpacing,
+ _maximumRowsOrColumns,
_orientation.ScrollOrientation,
LayoutId);
@@ -422,6 +447,7 @@ namespace Avalonia.Layout
var value = GetFlowAlgorithm(context).Arrange(
finalSize,
context,
+ true,
(FlowLayoutAlgorithm.LineAlignment)_itemsJustification,
LayoutId);
return new Size(value.Width, value.Height);
@@ -441,7 +467,7 @@ namespace Avalonia.Layout
{
if (property == OrientationProperty)
{
- var orientation = newValue.ValueOrDefault();
+ var orientation = newValue.GetValueOrDefault();
//Note: For UniformGridLayout Vertical Orientation means we have a Horizontal ScrollOrientation. Horizontal Orientation means we have a Vertical ScrollOrientation.
//i.e. the properties are the inverse of each other.
@@ -450,29 +476,32 @@ namespace Avalonia.Layout
}
else if (property == MinColumnSpacingProperty)
{
- _minColumnSpacing = newValue.ValueOrDefault();
+ _minColumnSpacing = newValue.GetValueOrDefault();
}
else if (property == MinRowSpacingProperty)
{
- _minRowSpacing = newValue.ValueOrDefault();
+ _minRowSpacing = newValue.GetValueOrDefault();
}
else if (property == ItemsJustificationProperty)
{
- _itemsJustification = newValue.ValueOrDefault();
+ _itemsJustification = newValue.GetValueOrDefault();
;
}
else if (property == ItemsStretchProperty)
{
- _itemsStretch = newValue.ValueOrDefault();
- ;
+ _itemsStretch = newValue.GetValueOrDefault();
}
else if (property == MinItemWidthProperty)
{
- _minItemWidth = newValue.ValueOrDefault();
+ _minItemWidth = newValue.GetValueOrDefault();
}
else if (property == MinItemHeightProperty)
{
- _minItemHeight = newValue.ValueOrDefault();
+ _minItemHeight = newValue.GetValueOrDefault();
+ }
+ else if (property == MaximumRowsOrColumnsProperty)
+ {
+ _maximumRowsOrColumns = newValue.GetValueOrDefault();
}
InvalidateLayout();
@@ -502,7 +531,9 @@ namespace Avalonia.Layout
Rect lastExtent,
VirtualizingLayoutContext context)
{
- int itemsPerLine = Math.Max(1, (int)(_orientation.Minor(availableSize) / GetMinorSizeWithSpacing(context)));
+ int itemsPerLine = (int)Math.Min( //note use of unsigned ints
+ Math.Max(1u, (uint)(_orientation.Minor(availableSize) / GetMinorSizeWithSpacing(context))),
+ Math.Max(1u, _maximumRowsOrColumns));
int rowIndex = (int)(index / itemsPerLine);
int indexInRow = index - (rowIndex * itemsPerLine);
diff --git a/src/Avalonia.Layout/UniformGridLayoutState.cs b/src/Avalonia.Layout/UniformGridLayoutState.cs
index e6d75bcf35..62c5174775 100644
--- a/src/Avalonia.Layout/UniformGridLayoutState.cs
+++ b/src/Avalonia.Layout/UniformGridLayoutState.cs
@@ -48,8 +48,14 @@ namespace Avalonia.Layout
UniformGridLayoutItemsStretch stretch,
Orientation orientation,
double minRowSpacing,
- double minColumnSpacing)
+ double minColumnSpacing,
+ int maxItemsPerLine)
{
+ if (maxItemsPerLine == 0)
+ {
+ maxItemsPerLine = 1;
+ }
+
if (context.ItemCount > 0)
{
// If the first element is realized we don't need to cache it or to get it from the context
@@ -57,7 +63,7 @@ namespace Avalonia.Layout
if (realizedElement != null)
{
realizedElement.Measure(availableSize);
- SetSize(realizedElement, layoutItemWidth, LayoutItemHeight, availableSize, stretch, orientation, minRowSpacing, minColumnSpacing);
+ SetSize(realizedElement, layoutItemWidth, LayoutItemHeight, availableSize, stretch, orientation, minRowSpacing, minColumnSpacing, maxItemsPerLine);
_cachedFirstElement = null;
}
else
@@ -72,7 +78,7 @@ namespace Avalonia.Layout
_cachedFirstElement.Measure(availableSize);
- SetSize(_cachedFirstElement, layoutItemWidth, LayoutItemHeight, availableSize, stretch, orientation, minRowSpacing, minColumnSpacing);
+ SetSize(_cachedFirstElement, layoutItemWidth, LayoutItemHeight, availableSize, stretch, orientation, minRowSpacing, minColumnSpacing, maxItemsPerLine);
// See if we can move ownership to the flow algorithm. If we can, we do not need a local cache.
bool added = FlowAlgorithm.TryAddElement0(_cachedFirstElement);
@@ -92,8 +98,14 @@ namespace Avalonia.Layout
UniformGridLayoutItemsStretch stretch,
Orientation orientation,
double minRowSpacing,
- double minColumnSpacing)
+ double minColumnSpacing,
+ int maxItemsPerLine)
{
+ if (maxItemsPerLine == 0)
+ {
+ maxItemsPerLine = 1;
+ }
+
EffectiveItemWidth = (double.IsNaN(layoutItemWidth) ? element.DesiredSize.Width : layoutItemWidth);
EffectiveItemHeight = (double.IsNaN(LayoutItemHeight) ? element.DesiredSize.Height : LayoutItemHeight);
@@ -101,11 +113,17 @@ namespace Avalonia.Layout
var minorItemSpacing = orientation == Orientation.Vertical ? minRowSpacing : minColumnSpacing;
var itemSizeMinor = orientation == Orientation.Horizontal ? EffectiveItemWidth : EffectiveItemHeight;
- itemSizeMinor += minorItemSpacing;
- var numItemsPerColumn = (int)(Math.Max(1.0, availableSizeMinor / itemSizeMinor));
- var remainingSpace = ((int)availableSizeMinor) % ((int)itemSizeMinor);
- var extraMinorPixelsForEachItem = remainingSpace / numItemsPerColumn;
+ double extraMinorPixelsForEachItem = 0.0;
+ if (!double.IsInfinity(availableSizeMinor))
+ {
+ var numItemsPerColumn = Math.Min(
+ maxItemsPerLine,
+ Math.Max(1.0, availableSizeMinor / (itemSizeMinor + minorItemSpacing)));
+ var usedSpace = (numItemsPerColumn * (itemSizeMinor + minorItemSpacing)) - minorItemSpacing;
+ var remainingSpace = ((int)(availableSizeMinor - usedSpace));
+ extraMinorPixelsForEachItem = remainingSpace / ((int)numItemsPerColumn);
+ }
if (stretch == UniformGridLayoutItemsStretch.Fill)
{
diff --git a/src/Avalonia.Native/Avalonia.Native.csproj b/src/Avalonia.Native/Avalonia.Native.csproj
index 65c2a75b1c..1a2bdeef1e 100644
--- a/src/Avalonia.Native/Avalonia.Native.csproj
+++ b/src/Avalonia.Native/Avalonia.Native.csproj
@@ -19,8 +19,9 @@
-
-
+
+
+
diff --git a/src/Avalonia.Native/AvaloniaNativePlatform.cs b/src/Avalonia.Native/AvaloniaNativePlatform.cs
index 571475c7ea..756619fa9f 100644
--- a/src/Avalonia.Native/AvaloniaNativePlatform.cs
+++ b/src/Avalonia.Native/AvaloniaNativePlatform.cs
@@ -101,7 +101,7 @@ namespace Avalonia.Native
.Bind().ToConstant(new DefaultRenderTimer(60))
.Bind().ToConstant(new SystemDialogs(_factory.CreateSystemDialogs()))
.Bind().ToConstant(new GlPlatformFeature(_factory.ObtainGlFeature()))
- .Bind().ToConstant(new PlatformHotkeyConfiguration(InputModifiers.Windows))
+ .Bind().ToConstant(new PlatformHotkeyConfiguration(KeyModifiers.Meta))
.Bind().ToConstant(new MacOSMountedVolumeInfoProvider());
}
diff --git a/src/Avalonia.Native/CallbackBase.cs b/src/Avalonia.Native/CallbackBase.cs
index 67c383f6ae..1356dd58ff 100644
--- a/src/Avalonia.Native/CallbackBase.cs
+++ b/src/Avalonia.Native/CallbackBase.cs
@@ -2,11 +2,13 @@
// Licensed under the MIT license. See licence.md file in the project root for full license information.
using System;
+using System.Runtime.ExceptionServices;
using SharpGen.Runtime;
+using Avalonia.Platform;
namespace Avalonia.Native
{
- public class CallbackBase : SharpGen.Runtime.IUnknown
+ public class CallbackBase : SharpGen.Runtime.IUnknown, IExceptionCallback
{
private uint _refCount;
private bool _disposed;
@@ -76,5 +78,15 @@ namespace Avalonia.Native
{
}
+
+ public void RaiseException(Exception e)
+ {
+ if (AvaloniaLocator.Current.GetService() is PlatformThreadingInterface threadingInterface)
+ {
+ threadingInterface.TerminateNativeApp();
+
+ threadingInterface.DispatchException(ExceptionDispatchInfo.Capture(e));
+ }
+ }
}
}
diff --git a/src/Avalonia.Native/PlatformThreadingInterface.cs b/src/Avalonia.Native/PlatformThreadingInterface.cs
index 353124a9d1..ba8cc83e83 100644
--- a/src/Avalonia.Native/PlatformThreadingInterface.cs
+++ b/src/Avalonia.Native/PlatformThreadingInterface.cs
@@ -2,6 +2,7 @@
// Licensed under the MIT license. See licence.md file in the project root for full license information.
using System;
+using System.Runtime.ExceptionServices;
using System.Threading;
using Avalonia.Native.Interop;
using Avalonia.Platform;
@@ -43,6 +44,8 @@ namespace Avalonia.Native
}
readonly IAvnPlatformThreadingInterface _native;
+ private ExceptionDispatchInfo _exceptionDispatchInfo;
+ private CancellationTokenSource _exceptionCancellationSource;
public PlatformThreadingInterface(IAvnPlatformThreadingInterface native)
{
@@ -57,32 +60,49 @@ namespace Avalonia.Native
public void RunLoop(CancellationToken cancellationToken)
{
- if (cancellationToken.CanBeCanceled == false)
- _native.RunLoop(null);
- else
+ _exceptionDispatchInfo?.Throw();
+ var l = new object();
+ _exceptionCancellationSource = new CancellationTokenSource();
+
+ var compositeCancellation = CancellationTokenSource
+ .CreateLinkedTokenSource(cancellationToken, _exceptionCancellationSource.Token).Token;
+
+ var cancellation = _native.CreateLoopCancellation();
+ compositeCancellation.Register(() =>
{
- var l = new object();
- var cancellation = _native.CreateLoopCancellation();
- cancellationToken.Register(() =>
+ lock (l)
{
- lock (l)
- {
- cancellation?.Cancel();
- }
- });
- try
- {
- _native.RunLoop(cancellation);
+ cancellation?.Cancel();
}
- finally
+ });
+
+ try
+ {
+ _native.RunLoop(cancellation);
+ }
+ finally
+ {
+ lock (l)
{
- lock(l)
- {
- cancellation?.Dispose();
- cancellation = null;
- }
+ cancellation?.Dispose();
+ cancellation = null;
}
}
+
+ if (_exceptionDispatchInfo != null)
+ {
+ _exceptionDispatchInfo.Throw();
+ }
+ }
+
+ public void DispatchException (ExceptionDispatchInfo exceptionInfo)
+ {
+ _exceptionDispatchInfo = exceptionInfo;
+ }
+
+ public void TerminateNativeApp()
+ {
+ _exceptionCancellationSource?.Cancel();
}
public void Signal(DispatcherPriority priority)
diff --git a/src/Avalonia.Native/WindowImplBase.cs b/src/Avalonia.Native/WindowImplBase.cs
index 8b397403ca..5d701dc8df 100644
--- a/src/Avalonia.Native/WindowImplBase.cs
+++ b/src/Avalonia.Native/WindowImplBase.cs
@@ -16,6 +16,34 @@ using Avalonia.Threading;
namespace Avalonia.Native
{
+ public class MacOSTopLevelWindowHandle : IPlatformHandle, IMacOSTopLevelPlatformHandle
+ {
+ IAvnWindowBase _native;
+
+ public MacOSTopLevelWindowHandle(IAvnWindowBase native)
+ {
+ _native = native;
+ }
+
+ public IntPtr Handle => NSWindow;
+
+ public string HandleDescriptor => "NSWindow";
+
+ public IntPtr NSView => _native.ObtainNSViewHandle();
+
+ public IntPtr NSWindow => _native.ObtainNSWindowHandle();
+
+ public IntPtr GetNSViewRetained()
+ {
+ return _native.ObtainNSViewHandleRetained();
+ }
+
+ public IntPtr GetNSWindowRetained()
+ {
+ return _native.ObtainNSWindowHandleRetained();
+ }
+ }
+
public abstract class WindowBaseImpl : IWindowBaseImpl,
IFramebufferPlatformSurface
{
@@ -45,6 +73,9 @@ namespace Avalonia.Native
protected void Init(IAvnWindowBase window, IAvnScreens screens)
{
_native = window;
+
+ Handle = new MacOSTopLevelWindowHandle(window);
+
_glSurface = new GlPlatformSurface(window);
Screen = new ScreenImpl(screens);
_savedLogicalSize = ClientSize;
@@ -322,6 +353,11 @@ namespace Avalonia.Native
public void SetCursor(IPlatformHandle cursor)
{
+ if (_native == null)
+ {
+ return;
+ }
+
var newCursor = cursor as AvaloniaNativeCursor;
newCursor = newCursor ?? (_cursorFactory.GetCursor(StandardCursorType.Arrow) as AvaloniaNativeCursor);
_native.Cursor = newCursor.Cursor;
@@ -349,6 +385,6 @@ namespace Avalonia.Native
}
- public IPlatformHandle Handle => new PlatformHandle(IntPtr.Zero, "NOT SUPPORTED");
+ public IPlatformHandle Handle { get; private set; }
}
}
diff --git a/src/Avalonia.ReactiveUI/AvaloniaActivationForViewFetcher.cs b/src/Avalonia.ReactiveUI/AvaloniaActivationForViewFetcher.cs
index 5a4d625c41..f7437e5930 100644
--- a/src/Avalonia.ReactiveUI/AvaloniaActivationForViewFetcher.cs
+++ b/src/Avalonia.ReactiveUI/AvaloniaActivationForViewFetcher.cs
@@ -2,9 +2,7 @@
// Licensed under the MIT license. See licence.md file in the project root for full license information.
using System;
-using System.Reflection;
using System.Reactive.Linq;
-using Avalonia;
using Avalonia.VisualTree;
using Avalonia.Controls;
using ReactiveUI;
@@ -21,7 +19,7 @@ namespace Avalonia.ReactiveUI
///
public int GetAffinityForView(Type view)
{
- return typeof(IVisual).GetTypeInfo().IsAssignableFrom(view.GetTypeInfo()) ? 10 : 0;
+ return typeof(IVisual).IsAssignableFrom(view) ? 10 : 0;
}
///
diff --git a/src/Avalonia.ReactiveUI/ReactiveUserControl.cs b/src/Avalonia.ReactiveUI/ReactiveUserControl.cs
index 010acc3ae0..3a39beae94 100644
--- a/src/Avalonia.ReactiveUI/ReactiveUserControl.cs
+++ b/src/Avalonia.ReactiveUI/ReactiveUserControl.cs
@@ -15,7 +15,7 @@ namespace Avalonia.ReactiveUI
/// ViewModel type.
public class ReactiveUserControl : UserControl, IViewFor where TViewModel : class
{
- public static readonly AvaloniaProperty ViewModelProperty = AvaloniaProperty
+ public static readonly StyledProperty ViewModelProperty = AvaloniaProperty
.Register, TViewModel>(nameof(ViewModel));
///
diff --git a/src/Avalonia.ReactiveUI/ReactiveWindow.cs b/src/Avalonia.ReactiveUI/ReactiveWindow.cs
index f0f115afbc..10ae610345 100644
--- a/src/Avalonia.ReactiveUI/ReactiveWindow.cs
+++ b/src/Avalonia.ReactiveUI/ReactiveWindow.cs
@@ -15,7 +15,7 @@ namespace Avalonia.ReactiveUI
/// ViewModel type.
public class ReactiveWindow : Window, IViewFor where TViewModel : class
{
- public static readonly AvaloniaProperty ViewModelProperty = AvaloniaProperty
+ public static readonly StyledProperty ViewModelProperty = AvaloniaProperty
.Register, TViewModel>(nameof(ViewModel));
///
@@ -41,4 +41,4 @@ namespace Avalonia.ReactiveUI
set => ViewModel = (TViewModel)value;
}
}
-}
\ No newline at end of file
+}
diff --git a/src/Avalonia.ReactiveUI/RoutedViewHost.cs b/src/Avalonia.ReactiveUI/RoutedViewHost.cs
index ac5db32c14..e111b15c75 100644
--- a/src/Avalonia.ReactiveUI/RoutedViewHost.cs
+++ b/src/Avalonia.ReactiveUI/RoutedViewHost.cs
@@ -58,7 +58,7 @@ namespace Avalonia.ReactiveUI
///
/// for the property.
///
- public static readonly AvaloniaProperty RouterProperty =
+ public static readonly StyledProperty RouterProperty =
AvaloniaProperty.Register(nameof(Router));
///
@@ -118,4 +118,4 @@ namespace Avalonia.ReactiveUI
Content = viewInstance;
}
}
-}
\ No newline at end of file
+}
diff --git a/src/Avalonia.ReactiveUI/TransitioningContentControl.cs b/src/Avalonia.ReactiveUI/TransitioningContentControl.cs
index 1bec5fc365..85768a39c3 100644
--- a/src/Avalonia.ReactiveUI/TransitioningContentControl.cs
+++ b/src/Avalonia.ReactiveUI/TransitioningContentControl.cs
@@ -16,14 +16,14 @@ namespace Avalonia.ReactiveUI
///
/// for the property.
///
- public static readonly AvaloniaProperty PageTransitionProperty =
+ public static readonly StyledProperty PageTransitionProperty =
AvaloniaProperty.Register(nameof(PageTransition),
new CrossFade(TimeSpan.FromSeconds(0.5)));
///
/// for the property.
///
- public static readonly AvaloniaProperty DefaultContentProperty =
+ public static readonly StyledProperty DefaultContentProperty =
AvaloniaProperty.Register(nameof(DefaultContent));
///
@@ -72,4 +72,4 @@ namespace Avalonia.ReactiveUI
await PageTransition.Start(null, this, true);
}
}
-}
\ No newline at end of file
+}
diff --git a/src/Avalonia.Styling/Styling/ISetStyleParent.cs b/src/Avalonia.Styling/Controls/ISetResourceParent.cs
similarity index 61%
rename from src/Avalonia.Styling/Styling/ISetStyleParent.cs
rename to src/Avalonia.Styling/Controls/ISetResourceParent.cs
index bca3d9d714..a1264adc34 100644
--- a/src/Avalonia.Styling/Styling/ISetStyleParent.cs
+++ b/src/Avalonia.Styling/Controls/ISetResourceParent.cs
@@ -1,29 +1,27 @@
-using Avalonia.Controls;
-
-namespace Avalonia.Styling
+namespace Avalonia.Controls
{
///
- /// Defines an interface through which a 's parent can be set.
+ /// Defines an interface through which an 's parent can be set.
///
///
/// You should not usually need to use this interface - it is for internal use only.
///
- public interface ISetStyleParent : IStyle
+ public interface ISetResourceParent : IResourceNode
{
///
- /// Sets the style parent.
+ /// Sets the resource parent.
///
/// The parent.
void SetParent(IResourceNode parent);
///
- /// Notifies the style that a change has been made to resources that apply to it.
+ /// Notifies the resource node that a change has been made to the resources in its parent.
///
/// The event args.
///
/// This method will be called automatically by the framework, you should not need to call
/// this method yourself.
///
- void NotifyResourcesChanged(ResourcesChangedEventArgs e);
+ void ParentResourcesChanged(ResourcesChangedEventArgs e);
}
}
diff --git a/src/Avalonia.Styling/Controls/ResourceDictionary.cs b/src/Avalonia.Styling/Controls/ResourceDictionary.cs
index 901e27b7b7..acc2db1ff7 100644
--- a/src/Avalonia.Styling/Controls/ResourceDictionary.cs
+++ b/src/Avalonia.Styling/Controls/ResourceDictionary.cs
@@ -12,8 +12,12 @@ namespace Avalonia.Controls
///
/// An indexed dictionary of resources.
///
- public class ResourceDictionary : AvaloniaDictionary, IResourceDictionary
+ public class ResourceDictionary : AvaloniaDictionary,
+ IResourceDictionary,
+ IResourceNode,
+ ISetResourceParent
{
+ private IResourceNode _parent;
private AvaloniaList _mergedDictionaries;
///
@@ -39,6 +43,12 @@ namespace Avalonia.Controls
_mergedDictionaries.ForEachItem(
x =>
{
+ if (x is ISetResourceParent setParent)
+ {
+ setParent.SetParent(this);
+ setParent.ParentResourcesChanged(new ResourcesChangedEventArgs());
+ }
+
if (x.HasResources)
{
OnResourcesChanged();
@@ -48,11 +58,18 @@ namespace Avalonia.Controls
},
x =>
{
+ if (x is ISetResourceParent setParent)
+ {
+ setParent.SetParent(null);
+ setParent.ParentResourcesChanged(new ResourcesChangedEventArgs());
+ }
+
if (x.HasResources)
{
OnResourcesChanged();
}
+ (x as ISetResourceParent)?.SetParent(null);
x.ResourcesChanged -= MergedDictionaryResourcesChanged;
},
() => { });
@@ -68,6 +85,27 @@ namespace Avalonia.Controls
get => Count > 0 || (_mergedDictionaries?.Any(x => x.HasResources) ?? false);
}
+ ///
+ IResourceNode IResourceNode.ResourceParent => _parent;
+
+ ///
+ void ISetResourceParent.ParentResourcesChanged(ResourcesChangedEventArgs e)
+ {
+ NotifyMergedDictionariesResourcesChanged(e);
+ ResourcesChanged?.Invoke(this, e);
+ }
+
+ ///
+ void ISetResourceParent.SetParent(IResourceNode parent)
+ {
+ if (_parent != null && parent != null)
+ {
+ throw new InvalidOperationException("The ResourceDictionary already has a parent.");
+ }
+
+ _parent = parent;
+ }
+
///
public bool TryGetResource(object key, out object value)
{
@@ -95,7 +133,27 @@ namespace Avalonia.Controls
ResourcesChanged?.Invoke(this, new ResourcesChangedEventArgs());
}
- private void OnCollectionChanged(object sender, NotifyCollectionChangedEventArgs e) => OnResourcesChanged();
+ private void NotifyMergedDictionariesResourcesChanged(ResourcesChangedEventArgs e)
+ {
+ if (_mergedDictionaries != null)
+ {
+ for (var i = _mergedDictionaries.Count - 1; i >= 0; --i)
+ {
+ if (_mergedDictionaries[i] is ISetResourceParent merged)
+ {
+ merged.ParentResourcesChanged(e);
+ }
+ }
+ }
+ }
+
+ private void OnCollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
+ {
+ var ev = new ResourcesChangedEventArgs();
+ NotifyMergedDictionariesResourcesChanged(ev);
+ OnResourcesChanged();
+ }
+
private void MergedDictionaryResourcesChanged(object sender, ResourcesChangedEventArgs e) => OnResourcesChanged();
}
}
diff --git a/src/Avalonia.Styling/IDataContextProvider.cs b/src/Avalonia.Styling/IDataContextProvider.cs
new file mode 100644
index 0000000000..31639c5784
--- /dev/null
+++ b/src/Avalonia.Styling/IDataContextProvider.cs
@@ -0,0 +1,13 @@
+namespace Avalonia
+{
+ ///
+ /// Defines an element with a data context that can be used for binding.
+ ///
+ public interface IDataContextProvider : IAvaloniaObject
+ {
+ ///
+ /// Gets or sets the element's data context.
+ ///
+ object DataContext { get; set; }
+ }
+}
diff --git a/src/Avalonia.Styling/IStyledElement.cs b/src/Avalonia.Styling/IStyledElement.cs
index bcf1898c4c..d4d0f179c3 100644
--- a/src/Avalonia.Styling/IStyledElement.cs
+++ b/src/Avalonia.Styling/IStyledElement.cs
@@ -10,7 +10,8 @@ namespace Avalonia
IStyleHost,
ILogical,
IResourceProvider,
- IResourceNode
+ IResourceNode,
+ IDataContextProvider
{
///
/// Occurs when the control has finished initialization.
@@ -27,11 +28,6 @@ namespace Avalonia
///
new Classes Classes { get; set; }
- ///
- /// Gets or sets the control's data context.
- ///
- object DataContext { get; set; }
-
///
/// Gets the control's logical parent.
///
diff --git a/src/Avalonia.Styling/LogicalTree/ControlLocator.cs b/src/Avalonia.Styling/LogicalTree/ControlLocator.cs
index 491f38c153..a8ec84bfa6 100644
--- a/src/Avalonia.Styling/LogicalTree/ControlLocator.cs
+++ b/src/Avalonia.Styling/LogicalTree/ControlLocator.cs
@@ -3,9 +3,6 @@
using System;
using System.Linq;
-using System.Reactive.Linq;
-using System.Reflection;
-using Avalonia.Controls;
using Avalonia.Reactive;
namespace Avalonia.LogicalTree
@@ -25,7 +22,7 @@ namespace Avalonia.LogicalTree
private readonly ILogical _relativeTo;
private readonly int _ancestorLevel;
private readonly Type _ancestorType;
- ILogical _value;
+ private ILogical _value;
public ControlTracker(ILogical relativeTo, int ancestorLevel, Type ancestorType)
{
@@ -69,7 +66,7 @@ namespace Avalonia.LogicalTree
private void Update()
{
_value = _relativeTo.GetLogicalAncestors()
- .Where(x => _ancestorType?.GetTypeInfo().IsAssignableFrom(x.GetType().GetTypeInfo()) ?? true)
+ .Where(x => _ancestorType?.IsAssignableFrom(x.GetType()) ?? true)
.ElementAtOrDefault(_ancestorLevel);
}
}
diff --git a/src/Avalonia.Styling/LogicalTree/ILogicalRoot.cs b/src/Avalonia.Styling/LogicalTree/ILogicalRoot.cs
new file mode 100644
index 0000000000..4a61544a6f
--- /dev/null
+++ b/src/Avalonia.Styling/LogicalTree/ILogicalRoot.cs
@@ -0,0 +1,9 @@
+namespace Avalonia.LogicalTree
+{
+ ///
+ /// Represents a root of a logical tree.
+ ///
+ public interface ILogicalRoot : ILogical
+ {
+ }
+}
diff --git a/src/Avalonia.Styling/LogicalTree/LogicalTreeAttachmentEventArgs.cs b/src/Avalonia.Styling/LogicalTree/LogicalTreeAttachmentEventArgs.cs
index 1b0eb2b61b..20ea82202e 100644
--- a/src/Avalonia.Styling/LogicalTree/LogicalTreeAttachmentEventArgs.cs
+++ b/src/Avalonia.Styling/LogicalTree/LogicalTreeAttachmentEventArgs.cs
@@ -2,7 +2,6 @@
// Licensed under the MIT license. See licence.md file in the project root for full license information.
using System;
-using Avalonia.Styling;
namespace Avalonia.LogicalTree
{
@@ -16,16 +15,44 @@ namespace Avalonia.LogicalTree
/// Initializes a new instance of the class.
///
/// The root of the logical tree.
- public LogicalTreeAttachmentEventArgs(IStyleHost root)
+ /// The control being attached/detached.
+ /// The .
+ public LogicalTreeAttachmentEventArgs(
+ ILogicalRoot root,
+ ILogical source,
+ ILogical parent)
{
Contract.Requires(root != null);
+ Contract.Requires(source != null);
Root = root;
+ Source = source;
+ Parent = parent;
}
///
/// Gets the root of the logical tree that the control is being attached to or detached from.
///
- public IStyleHost Root { get; }
+ public ILogicalRoot Root { get; }
+
+ ///
+ /// Gets the control that was attached or detached from the logical tree.
+ ///
+ ///
+ /// Logical tree attachment events travel down the attached logical tree from the point of
+ /// attachment/detachment, so this control may be different from the control that the
+ /// event is being raised on.
+ ///
+ public ILogical Source { get; }
+
+ ///
+ /// Gets the control that is being attached to or detached from.
+ ///
+ ///
+ /// For logical tree attachment, holds the new logical parent of . For
+ /// detachment, holds the old logical parent of . If the detachment event
+ /// was caused by a top-level control being closed, then this property will be null.
+ ///
+ public ILogical Parent { get; }
}
}
diff --git a/src/Avalonia.Styling/StyledElement.cs b/src/Avalonia.Styling/StyledElement.cs
index 44cd1c4687..5768a25435 100644
--- a/src/Avalonia.Styling/StyledElement.cs
+++ b/src/Avalonia.Styling/StyledElement.cs
@@ -24,7 +24,7 @@ namespace Avalonia
/// - Implements to form part of a logical tree.
/// - A collection of class strings for custom styling.
///
- public class StyledElement : Animatable, IStyledElement, ISetLogicalParent, ISetInheritanceParent
+ public class StyledElement : Animatable, IDataContextProvider, IStyledElement, ISetLogicalParent, ISetInheritanceParent
{
///
/// Defines the property.
@@ -59,7 +59,7 @@ namespace Avalonia
private int _initCount;
private string _name;
private readonly Classes _classes = new Classes();
- private bool _isAttachedToLogicalTree;
+ private ILogicalRoot _logicalRoot;
private IAvaloniaList _logicalChildren;
private IResourceDictionary _resources;
private Styles _styles;
@@ -81,7 +81,7 @@ namespace Avalonia
///
public StyledElement()
{
- _isAttachedToLogicalTree = this is IStyleRoot;
+ _logicalRoot = this as ILogicalRoot;
}
///
@@ -223,13 +223,13 @@ namespace Avalonia
{
if (_styles != null)
{
- (_styles as ISetStyleParent)?.SetParent(null);
+ (_styles as ISetResourceParent)?.SetParent(null);
_styles.ResourcesChanged -= ThisResourcesChanged;
}
_styles = value;
- if (value is ISetStyleParent setParent && setParent.ResourceParent == null)
+ if (value is ISetResourceParent setParent && setParent.ResourceParent == null)
{
setParent.SetParent(this);
}
@@ -288,7 +288,7 @@ namespace Avalonia
var list = new AvaloniaList
{
ResetBehavior = ResetBehavior.Remove,
- Validate = ValidateLogicalChild
+ Validate = logical => ValidateLogicalChild(logical)
};
list.CollectionChanged += LogicalChildrenCollectionChanged;
_logicalChildren = list;
@@ -307,7 +307,7 @@ namespace Avalonia
///
/// Gets a value indicating whether the element is attached to a rooted logical tree.
///
- bool ILogical.IsAttachedToLogicalTree => _isAttachedToLogicalTree;
+ bool ILogical.IsAttachedToLogicalTree => _logicalRoot != null;
///
/// Gets the styled element's logical parent.
@@ -367,7 +367,7 @@ namespace Avalonia
throw new InvalidOperationException("BeginInit was not called.");
}
- if (--_initCount == 0 && _isAttachedToLogicalTree)
+ if (--_initCount == 0 && _logicalRoot != null)
{
InitializeStylesIfNeeded();
@@ -435,19 +435,6 @@ namespace Avalonia
throw new InvalidOperationException("The Control already has a parent.");
}
- if (_isAttachedToLogicalTree)
- {
- var oldRoot = FindStyleRoot(old) ?? this as IStyleRoot;
-
- if (oldRoot == null)
- {
- throw new AvaloniaInternalException("Was attached to logical tree but cannot find root.");
- }
-
- var e = new LogicalTreeAttachmentEventArgs(oldRoot);
- OnDetachedFromLogicalTreeCore(e);
- }
-
if (InheritanceParent == null || parent == null)
{
InheritanceParent = parent as AvaloniaObject;
@@ -455,6 +442,12 @@ namespace Avalonia
Parent = (IStyledElement)parent;
+ if (_logicalRoot != null)
+ {
+ var e = new LogicalTreeAttachmentEventArgs(_logicalRoot, this, old);
+ OnDetachedFromLogicalTreeCore(e);
+ }
+
if (old != null)
{
old.ResourcesChanged -= ThisResourcesChanged;
@@ -465,16 +458,16 @@ namespace Avalonia
}
((ILogical)this).NotifyResourcesChanged(new ResourcesChangedEventArgs());
- if (Parent is IStyleRoot || Parent?.IsAttachedToLogicalTree == true || this is IStyleRoot)
+ if (Parent is ILogicalRoot || Parent?.IsAttachedToLogicalTree == true || this is ILogicalRoot)
{
- var newRoot = FindStyleRoot(this);
+ var newRoot = FindLogicalRoot(this);
if (newRoot == null)
{
throw new AvaloniaInternalException("Parent is attached to logical tree but cannot find root.");
}
- var e = new LogicalTreeAttachmentEventArgs(newRoot);
+ var e = new LogicalTreeAttachmentEventArgs(newRoot, this, parent);
OnAttachedToLogicalTreeCore(e);
}
@@ -601,11 +594,11 @@ namespace Avalonia
}
}
- private static IStyleRoot FindStyleRoot(IStyleHost e)
+ private static ILogicalRoot FindLogicalRoot(IStyleHost e)
{
while (e != null)
{
- if (e is IStyleRoot root)
+ if (e is ILogicalRoot root)
{
return root;
}
@@ -631,15 +624,21 @@ namespace Avalonia
private void OnAttachedToLogicalTreeCore(LogicalTreeAttachmentEventArgs e)
{
+ if (this.GetLogicalParent() == null && !(this is ILogicalRoot))
+ {
+ throw new InvalidOperationException(
+ $"AttachedToLogicalTreeCore called for '{GetType().Name}' but control has no logical parent.");
+ }
+
// This method can be called when a control is already attached to the logical tree
// in the following scenario:
// - ListBox gets assigned Items containing ListBoxItem
// - ListBox makes ListBoxItem a logical child
// - ListBox template gets applied; making its Panel get attached to logical tree
// - That AttachedToLogicalTree signal travels down to the ListBoxItem
- if (!_isAttachedToLogicalTree)
+ if (_logicalRoot == null)
{
- _isAttachedToLogicalTree = true;
+ _logicalRoot = e.Root;
InitializeStylesIfNeeded(true);
@@ -655,9 +654,9 @@ namespace Avalonia
private void OnDetachedFromLogicalTreeCore(LogicalTreeAttachmentEventArgs e)
{
- if (_isAttachedToLogicalTree)
+ if (_logicalRoot != null)
{
- _isAttachedToLogicalTree = false;
+ _logicalRoot = null;
_styleDetach.OnNext(this);
OnDetachedFromLogicalTree(e);
DetachedFromLogicalTree?.Invoke(this, e);
diff --git a/src/Avalonia.Styling/Styling/IGlobalStyles.cs b/src/Avalonia.Styling/Styling/IGlobalStyles.cs
index 8bdf6f869b..51393ef0b3 100644
--- a/src/Avalonia.Styling/Styling/IGlobalStyles.cs
+++ b/src/Avalonia.Styling/Styling/IGlobalStyles.cs
@@ -6,7 +6,7 @@ namespace Avalonia.Styling
///
/// Defines the style host that provides styles global to the application.
///
- public interface IGlobalStyles : IStyleRoot
+ public interface IGlobalStyles : IStyleHost
{
}
}
diff --git a/src/Avalonia.Styling/Styling/IStyleRoot.cs b/src/Avalonia.Styling/Styling/IStyleRoot.cs
deleted file mode 100644
index 56778dcb9f..0000000000
--- a/src/Avalonia.Styling/Styling/IStyleRoot.cs
+++ /dev/null
@@ -1,12 +0,0 @@
-// Copyright (c) The Avalonia Project. All rights reserved.
-// Licensed under the MIT license. See licence.md file in the project root for full license information.
-
-namespace Avalonia.Styling
-{
- ///
- /// Denotes the root in a tree.
- ///
- public interface IStyleRoot : IStyleHost
- {
- }
-}
diff --git a/src/Avalonia.Styling/Styling/Selector.cs b/src/Avalonia.Styling/Styling/Selector.cs
index af209ea970..7d4e92baeb 100644
--- a/src/Avalonia.Styling/Styling/Selector.cs
+++ b/src/Avalonia.Styling/Styling/Selector.cs
@@ -3,6 +3,8 @@
using System;
using System.Collections.Generic;
+using System.Diagnostics;
+using Avalonia.Utilities;
namespace Avalonia.Styling
{
@@ -41,7 +43,8 @@ namespace Avalonia.Styling
/// A .
public SelectorMatch Match(IStyleable control, bool subscribe = true)
{
- var inputs = new List>();
+ ValueSingleOrList> inputs = default;
+
var selector = this;
var alwaysThisType = true;
var hitCombinator = false;
@@ -66,19 +69,25 @@ namespace Avalonia.Styling
}
else if (match.Result == SelectorMatchResult.Sometimes)
{
+ Debug.Assert(match.Activator != null);
+
inputs.Add(match.Activator);
}
selector = selector.MovePrevious();
}
- if (inputs.Count > 0)
+ if (inputs.HasList)
+ {
+ return new SelectorMatch(StyleActivator.And(inputs.List));
+ }
+ else if (inputs.IsSingle)
{
- return new SelectorMatch(StyleActivator.And(inputs));
+ return new SelectorMatch(inputs.Single);
}
else
{
- return alwaysThisType && !hitCombinator ?
+ return alwaysThisType && !hitCombinator ?
SelectorMatch.AlwaysThisType :
SelectorMatch.AlwaysThisInstance;
}
diff --git a/src/Avalonia.Styling/Styling/Setter.cs b/src/Avalonia.Styling/Styling/Setter.cs
index 9312d38c51..9244232ff5 100644
--- a/src/Avalonia.Styling/Styling/Setter.cs
+++ b/src/Avalonia.Styling/Styling/Setter.cs
@@ -3,9 +3,7 @@
using System;
using System.Reactive.Disposables;
-using System.Reflection;
using Avalonia.Animation;
-using Avalonia.Controls;
using Avalonia.Data;
using Avalonia.Metadata;
using Avalonia.Reactive;
@@ -92,14 +90,15 @@ namespace Avalonia.Styling
if (binding == null)
{
- var template = value as ITemplate;
- bool isPropertyOfTypeITemplate = typeof(ITemplate).GetTypeInfo()
- .IsAssignableFrom(Property.PropertyType.GetTypeInfo());
-
- if (template != null && !isPropertyOfTypeITemplate)
+ if (value is ITemplate template)
{
- var materialized = template.Build();
- value = materialized;
+ bool isPropertyOfTypeITemplate = typeof(ITemplate).IsAssignableFrom(Property.PropertyType);
+
+ if (!isPropertyOfTypeITemplate)
+ {
+ var materialized = template.Build();
+ value = materialized;
+ }
}
if (activator == null)
diff --git a/src/Avalonia.Styling/Styling/Style.cs b/src/Avalonia.Styling/Styling/Style.cs
index 3ce82b4160..99ee8d8563 100644
--- a/src/Avalonia.Styling/Styling/Style.cs
+++ b/src/Avalonia.Styling/Styling/Style.cs
@@ -14,7 +14,7 @@ namespace Avalonia.Styling
///
/// Defines a style.
///
- public class Style : AvaloniaObject, IStyle, ISetStyleParent
+ public class Style : AvaloniaObject, IStyle, ISetResourceParent
{
private static Dictionary _applied =
new Dictionary();
@@ -59,16 +59,16 @@ namespace Avalonia.Styling
if (_resources != null)
{
- hadResources = _resources.Count > 0;
+ hadResources = _resources.HasResources;
_resources.ResourcesChanged -= ResourceDictionaryChanged;
}
_resources = value;
_resources.ResourcesChanged += ResourceDictionaryChanged;
- if (hadResources || _resources.Count > 0)
+ if (hadResources || _resources.HasResources)
{
- ((ISetStyleParent)this).NotifyResourcesChanged(new ResourcesChangedEventArgs());
+ ((ISetResourceParent)this).ParentResourcesChanged(new ResourcesChangedEventArgs());
}
}
}
@@ -194,13 +194,13 @@ namespace Avalonia.Styling
}
///
- void ISetStyleParent.NotifyResourcesChanged(ResourcesChangedEventArgs e)
+ void ISetResourceParent.ParentResourcesChanged(ResourcesChangedEventArgs e)
{
ResourcesChanged?.Invoke(this, e);
}
///
- void ISetStyleParent.SetParent(IResourceNode parent)
+ void ISetResourceParent.SetParent(IResourceNode parent)
{
if (_parent != null && parent != null)
{
diff --git a/src/Avalonia.Styling/Styling/Styles.cs b/src/Avalonia.Styling/Styling/Styles.cs
index a4563110a9..0226288998 100644
--- a/src/Avalonia.Styling/Styling/Styles.cs
+++ b/src/Avalonia.Styling/Styling/Styles.cs
@@ -14,7 +14,7 @@ namespace Avalonia.Styling
///
/// A style that consists of a number of child styles.
///
- public class Styles : AvaloniaObject, IAvaloniaList, IStyle, ISetStyleParent
+ public class Styles : AvaloniaObject, IAvaloniaList, IStyle, ISetResourceParent
{
private IResourceNode _parent;
private IResourceDictionary _resources;
@@ -27,10 +27,10 @@ namespace Avalonia.Styling
_styles.ForEachItem(
x =>
{
- if (x.ResourceParent == null && x is ISetStyleParent setParent)
+ if (x.ResourceParent == null && x is ISetResourceParent setParent)
{
setParent.SetParent(this);
- setParent.NotifyResourcesChanged(new ResourcesChangedEventArgs());
+ setParent.ParentResourcesChanged(new ResourcesChangedEventArgs());
}
if (x.HasResources)
@@ -43,10 +43,10 @@ namespace Avalonia.Styling
},
x =>
{
- if (x.ResourceParent == this && x is ISetStyleParent setParent)
+ if (x.ResourceParent == this && x is ISetResourceParent setParent)
{
setParent.SetParent(null);
- setParent.NotifyResourcesChanged(new ResourcesChangedEventArgs());
+ setParent.ParentResourcesChanged(new ResourcesChangedEventArgs());
}
if (x.HasResources)
@@ -98,7 +98,7 @@ namespace Avalonia.Styling
if (hadResources || _resources.Count > 0)
{
- ((ISetStyleParent)this).NotifyResourcesChanged(new ResourcesChangedEventArgs());
+ ((ISetResourceParent)this).ParentResourcesChanged(new ResourcesChangedEventArgs());
}
}
}
@@ -246,7 +246,7 @@ namespace Avalonia.Styling
IEnumerator IEnumerable.GetEnumerator() => _styles.GetEnumerator();
///
- void ISetStyleParent.SetParent(IResourceNode parent)
+ void ISetResourceParent.SetParent(IResourceNode parent)
{
if (_parent != null && parent != null)
{
@@ -257,7 +257,7 @@ namespace Avalonia.Styling
}
///
- void ISetStyleParent.NotifyResourcesChanged(ResourcesChangedEventArgs e)
+ void ISetResourceParent.ParentResourcesChanged(ResourcesChangedEventArgs e)
{
ResourcesChanged?.Invoke(this, e);
}
@@ -266,7 +266,7 @@ namespace Avalonia.Styling
{
foreach (var child in this)
{
- (child as ISetStyleParent)?.NotifyResourcesChanged(e);
+ (child as ISetResourceParent)?.ParentResourcesChanged(e);
}
ResourcesChanged?.Invoke(this, e);
@@ -280,7 +280,7 @@ namespace Avalonia.Styling
{
if (foundSource)
{
- (child as ISetStyleParent)?.NotifyResourcesChanged(e);
+ (child as ISetResourceParent)?.ParentResourcesChanged(e);
}
foundSource |= child == sender;
diff --git a/src/Avalonia.Styling/Styling/TypeNameAndClassSelector.cs b/src/Avalonia.Styling/Styling/TypeNameAndClassSelector.cs
index f1fd2f6c7f..401fa54fb5 100644
--- a/src/Avalonia.Styling/Styling/TypeNameAndClassSelector.cs
+++ b/src/Avalonia.Styling/Styling/TypeNameAndClassSelector.cs
@@ -114,7 +114,7 @@ namespace Avalonia.Styling
}
else
{
- if (!TargetType.GetTypeInfo().IsAssignableFrom(controlType.GetTypeInfo()))
+ if (!TargetType.IsAssignableFrom(controlType))
{
return SelectorMatch.NeverThisType;
}
diff --git a/src/Avalonia.Themes.Default/ItemsControl.xaml b/src/Avalonia.Themes.Default/ItemsControl.xaml
index f3def542fc..8bb0fc297c 100644
--- a/src/Avalonia.Themes.Default/ItemsControl.xaml
+++ b/src/Avalonia.Themes.Default/ItemsControl.xaml
@@ -1,10 +1,15 @@
diff --git a/src/Avalonia.Themes.Default/RadioButton.xaml b/src/Avalonia.Themes.Default/RadioButton.xaml
index e7cbed8ec5..4cdb116cdd 100644
--- a/src/Avalonia.Themes.Default/RadioButton.xaml
+++ b/src/Avalonia.Themes.Default/RadioButton.xaml
@@ -13,7 +13,6 @@
Height="18"
VerticalAlignment="Center"/>
+
-
\ No newline at end of file
+
diff --git a/src/Avalonia.Visuals/Matrix.cs b/src/Avalonia.Visuals/Matrix.cs
index 92b7dae904..d05dbac574 100644
--- a/src/Avalonia.Visuals/Matrix.cs
+++ b/src/Avalonia.Visuals/Matrix.cs
@@ -306,7 +306,7 @@ namespace Avalonia
///
/// Parses a string.
///
- /// The string.
+ /// Six comma-delimited double values (m11, m12, m21, m22, offsetX, offsetY) that describe the new
/// The .
public static Matrix Parse(string s)
{
diff --git a/src/Avalonia.Visuals/Media/CharacterHit.cs b/src/Avalonia.Visuals/Media/CharacterHit.cs
new file mode 100644
index 0000000000..978a5b0c4c
--- /dev/null
+++ b/src/Avalonia.Visuals/Media/CharacterHit.cs
@@ -0,0 +1,68 @@
+// 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.Media
+{
+ ///
+ /// Represents information about a character hit within a glyph run.
+ ///
+ ///
+ /// The CharacterHit structure provides information about the index of the first
+ /// character that got hit as well as information about leading or trailing edge.
+ ///
+ public readonly struct CharacterHit : IEquatable
+ {
+ ///
+ /// Initializes a new instance of the structure.
+ ///
+ /// Index of the first character that got hit.
+ /// In the case of a leading edge, this value is 0. In the case of a trailing edge,
+ /// this value is the number of code points until the next valid caret position.
+ public CharacterHit(int firstCharacterIndex, int trailingLength = 0)
+ {
+ FirstCharacterIndex = firstCharacterIndex;
+
+ TrailingLength = trailingLength;
+ }
+
+ ///
+ /// Gets the index of the first character that got hit.
+ ///
+ public int FirstCharacterIndex { get; }
+
+ ///
+ /// Gets the trailing length value for the character that got hit.
+ ///
+ public int TrailingLength { get; }
+
+ public bool Equals(CharacterHit other)
+ {
+ return FirstCharacterIndex == other.FirstCharacterIndex && TrailingLength == other.TrailingLength;
+ }
+
+ public override bool Equals(object obj)
+ {
+ return obj is CharacterHit other && Equals(other);
+ }
+
+ public override int GetHashCode()
+ {
+ unchecked
+ {
+ return FirstCharacterIndex * 397 ^ TrailingLength;
+ }
+ }
+
+ public static bool operator ==(CharacterHit left, CharacterHit right)
+ {
+ return left.Equals(right);
+ }
+
+ public static bool operator !=(CharacterHit left, CharacterHit right)
+ {
+ return !left.Equals(right);
+ }
+ }
+}
diff --git a/src/Avalonia.Visuals/Media/Color.cs b/src/Avalonia.Visuals/Media/Color.cs
index a37463a0f0..3eb4f575c7 100644
--- a/src/Avalonia.Visuals/Media/Color.cs
+++ b/src/Avalonia.Visuals/Media/Color.cs
@@ -3,7 +3,6 @@
using System;
using System.Globalization;
-using Avalonia.Animation;
using Avalonia.Animation.Animators;
namespace Avalonia.Media
@@ -11,7 +10,7 @@ namespace Avalonia.Media
///
/// An ARGB color.
///
- public readonly struct Color
+ public readonly struct Color : IEquatable
{
static Color()
{
@@ -19,22 +18,22 @@ namespace Avalonia.Media
}
///
- /// Gets or sets the Alpha component of the color.
+ /// Gets the Alpha component of the color.
///
public byte A { get; }
///
- /// Gets or sets the Red component of the color.
+ /// Gets the Red component of the color.
///
public byte R { get; }
///
- /// Gets or sets the Green component of the color.
+ /// Gets the Green component of the color.
///
public byte G { get; }
///
- /// Gets or sets the Blue component of the color.
+ /// Gets the Blue component of the color.
///
public byte B { get; }
@@ -144,5 +143,40 @@ namespace Avalonia.Media
{
return ((uint)A << 24) | ((uint)R << 16) | ((uint)G << 8) | (uint)B;
}
+
+ ///
+ /// Check if two colors are equal.
+ ///
+ public bool Equals(Color other)
+ {
+ return A == other.A && R == other.R && G == other.G && B == other.B;
+ }
+
+ public override bool Equals(object obj)
+ {
+ return obj is Color other && Equals(other);
+ }
+
+ public override int GetHashCode()
+ {
+ unchecked
+ {
+ int hashCode = A.GetHashCode();
+ hashCode = (hashCode * 397) ^ R.GetHashCode();
+ hashCode = (hashCode * 397) ^ G.GetHashCode();
+ hashCode = (hashCode * 397) ^ B.GetHashCode();
+ return hashCode;
+ }
+ }
+
+ public static bool operator ==(Color left, Color right)
+ {
+ return left.Equals(right);
+ }
+
+ public static bool operator !=(Color left, Color right)
+ {
+ return !left.Equals(right);
+ }
}
}
diff --git a/src/Avalonia.Visuals/Media/DashStyle.cs b/src/Avalonia.Visuals/Media/DashStyle.cs
index 7784c73736..1e813edc13 100644
--- a/src/Avalonia.Visuals/Media/DashStyle.cs
+++ b/src/Avalonia.Visuals/Media/DashStyle.cs
@@ -14,13 +14,13 @@ namespace Avalonia.Media
///
/// Defines the property.
///
- public static readonly AvaloniaProperty> DashesProperty =
+ public static readonly StyledProperty> DashesProperty =
AvaloniaProperty.Register>(nameof(Dashes));
///
/// Defines the property.
///
- public static readonly AvaloniaProperty OffsetProperty =
+ public static readonly StyledProperty OffsetProperty =
AvaloniaProperty.Register(nameof(Offset));
private static ImmutableDashStyle s_dash;
diff --git a/src/Avalonia.Visuals/Media/Drawing.cs b/src/Avalonia.Visuals/Media/Drawing.cs
index a60c591edc..6bc808e407 100644
--- a/src/Avalonia.Visuals/Media/Drawing.cs
+++ b/src/Avalonia.Visuals/Media/Drawing.cs
@@ -1,4 +1,6 @@
-namespace Avalonia.Media
+using Avalonia.Platform;
+
+namespace Avalonia.Media
{
public abstract class Drawing : AvaloniaObject
{
@@ -6,4 +8,4 @@
public abstract Rect GetBounds();
}
-}
\ No newline at end of file
+}
diff --git a/src/Avalonia.Visuals/Media/DrawingContext.cs b/src/Avalonia.Visuals/Media/DrawingContext.cs
index 4c9bf9ebd4..4045b92c0c 100644
--- a/src/Avalonia.Visuals/Media/DrawingContext.cs
+++ b/src/Avalonia.Visuals/Media/DrawingContext.cs
@@ -74,18 +74,29 @@ namespace Avalonia.Media
public Matrix CurrentContainerTransform => _currentContainerTransform;
///