diff --git a/build/CoreLibraries.props b/build/CoreLibraries.props
index 3923bdeeda..d61268173d 100644
--- a/build/CoreLibraries.props
+++ b/build/CoreLibraries.props
@@ -4,7 +4,6 @@
-
diff --git a/build/SharedVersion.props b/build/SharedVersion.props
index 897e70ff81..bd183faab3 100644
--- a/build/SharedVersion.props
+++ b/build/SharedVersion.props
@@ -3,7 +3,7 @@
Avalonia
0.9.999
- Copyright 2019 © The AvaloniaUI Project
+ Copyright 2020 © The AvaloniaUI Project
https://avaloniaui.net
https://github.com/AvaloniaUI/Avalonia/
true
diff --git a/native/Avalonia.Native/inc/avalonia-native.h b/native/Avalonia.Native/inc/avalonia-native.h
index 677fa2f4c5..4a960d47a1 100644
--- a/native/Avalonia.Native/inc/avalonia-native.h
+++ b/native/Avalonia.Native/inc/avalonia-native.h
@@ -177,14 +177,14 @@ AVNCOM(IAvaloniaNativeFactory, 01) : IUnknown
public:
virtual HRESULT Initialize() = 0;
virtual IAvnMacOptions* GetMacOptions() = 0;
- virtual HRESULT CreateWindow(IAvnWindowEvents* cb, IAvnWindow** ppv) = 0;
- virtual HRESULT CreatePopup (IAvnWindowEvents* cb, IAvnPopup** ppv) = 0;
+ virtual HRESULT CreateWindow(IAvnWindowEvents* cb, IAvnGlContext* gl, IAvnWindow** ppv) = 0;
+ virtual HRESULT CreatePopup (IAvnWindowEvents* cb, IAvnGlContext* gl, IAvnPopup** ppv) = 0;
virtual HRESULT CreatePlatformThreadingInterface(IAvnPlatformThreadingInterface** ppv) = 0;
virtual HRESULT CreateSystemDialogs (IAvnSystemDialogs** ppv) = 0;
virtual HRESULT CreateScreens (IAvnScreens** ppv) = 0;
virtual HRESULT CreateClipboard(IAvnClipboard** ppv) = 0;
virtual HRESULT CreateCursorFactory(IAvnCursorFactory** ppv) = 0;
- virtual HRESULT ObtainGlFeature(IAvnGlFeature** ppv) = 0;
+ virtual HRESULT ObtainGlDisplay(IAvnGlDisplay** ppv) = 0;
virtual HRESULT ObtainAppMenu(IAvnAppMenu** retOut) = 0;
virtual HRESULT SetAppMenu(IAvnAppMenu* menu) = 0;
virtual HRESULT CreateMenu (IAvnAppMenu** ppv) = 0;
@@ -219,15 +219,12 @@ AVNCOM(IAvnWindowBase, 02) : IUnknown
virtual HRESULT SetTopMost (bool value) = 0;
virtual HRESULT SetCursor(IAvnCursor* cursor) = 0;
virtual HRESULT CreateGlRenderTarget(IAvnGlSurfaceRenderTarget** ret) = 0;
- 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;
};
AVNCOM(IAvnPopup, 03) : virtual IAvnWindowBase
@@ -360,24 +357,21 @@ AVNCOM(IAvnCursorFactory, 11) : IUnknown
virtual HRESULT GetCursor (AvnStandardCursorType cursorType, IAvnCursor** retOut) = 0;
};
-
-AVNCOM(IAvnGlFeature, 12) : IUnknown
-{
- virtual HRESULT ObtainDisplay(IAvnGlDisplay**retOut) = 0;
- virtual HRESULT ObtainImmediateContext(IAvnGlContext**retOut) = 0;
-};
-
AVNCOM(IAvnGlDisplay, 13) : IUnknown
{
- virtual HRESULT GetSampleCount(int* ret) = 0;
- virtual HRESULT GetStencilSize(int* ret) = 0;
- virtual HRESULT ClearContext() = 0;
+ virtual HRESULT CreateContext(IAvnGlContext* share, IAvnGlContext**ppv) = 0;
+ virtual void LegacyClearCurrentContext() = 0;
+ virtual HRESULT WrapContext(void* native, IAvnGlContext**ppv) = 0;
virtual void* GetProcAddress(char* proc) = 0;
};
AVNCOM(IAvnGlContext, 14) : IUnknown
{
- virtual HRESULT MakeCurrent() = 0;
+ virtual HRESULT MakeCurrent(IUnknown** ppv) = 0;
+ virtual HRESULT LegacyMakeCurrent() = 0;
+ virtual int GetSampleCount() = 0;
+ virtual int GetStencilSize() = 0;
+ virtual void* GetNativeHandle() = 0;
};
AVNCOM(IAvnGlSurfaceRenderTarget, 15) : IUnknown
diff --git a/native/Avalonia.Native/inc/comimpl.h b/native/Avalonia.Native/inc/comimpl.h
index cf1aa4c735..0ff64b7215 100644
--- a/native/Avalonia.Native/inc/comimpl.h
+++ b/native/Avalonia.Native/inc/comimpl.h
@@ -162,6 +162,19 @@ public:
return _obj;
}
+ TInterface* getRetainedReference()
+ {
+ if(_obj == NULL)
+ return NULL;
+ _obj->AddRef();
+ return _obj;
+ }
+
+ TInterface** getPPV()
+ {
+ return &_obj;
+ }
+
operator TInterface*() const
{
return _obj;
diff --git a/native/Avalonia.Native/inc/rendertarget.h b/native/Avalonia.Native/inc/rendertarget.h
new file mode 100644
index 0000000000..2b0338d099
--- /dev/null
+++ b/native/Avalonia.Native/inc/rendertarget.h
@@ -0,0 +1,12 @@
+
+@protocol IRenderTarget
+-(void) setNewLayer: (CALayer*) layer;
+-(HRESULT) setSwFrame: (AvnFramebuffer*) fb;
+-(void) resize: (AvnPixelSize) size withScale: (float) scale;
+-(AvnPixelSize) pixelSize;
+-(IAvnGlSurfaceRenderTarget*) createSurfaceRenderTarget;
+@end
+
+@interface IOSurfaceRenderTarget : NSObject
+-(IOSurfaceRenderTarget*) initWithOpenGlContext: (IAvnGlContext*) context;
+@end
diff --git a/native/Avalonia.Native/src/OSX/Avalonia.Native.OSX.xcodeproj/project.pbxproj b/native/Avalonia.Native/src/OSX/Avalonia.Native.OSX.xcodeproj/project.pbxproj
index c0a49382a7..50a85bdf9f 100644
--- a/native/Avalonia.Native/src/OSX/Avalonia.Native.OSX.xcodeproj/project.pbxproj
+++ b/native/Avalonia.Native/src/OSX/Avalonia.Native.OSX.xcodeproj/project.pbxproj
@@ -8,6 +8,10 @@
/* Begin PBXBuildFile section */
1A002B9E232135EE00021753 /* app.mm in Sources */ = {isa = PBXBuildFile; fileRef = 1A002B9D232135EE00021753 /* app.mm */; };
+ 1A3E5EA823E9E83B00EDE661 /* rendertarget.mm in Sources */ = {isa = PBXBuildFile; fileRef = 1A3E5EA723E9E83B00EDE661 /* rendertarget.mm */; };
+ 1A3E5EAA23E9F26C00EDE661 /* IOSurface.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 1A3E5EA923E9F26C00EDE661 /* IOSurface.framework */; };
+ 1A3E5EAE23E9FB1300EDE661 /* cgl.mm in Sources */ = {isa = PBXBuildFile; fileRef = 1A3E5EAD23E9FB1300EDE661 /* cgl.mm */; };
+ 1A3E5EB023E9FE8300EDE661 /* QuartzCore.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 1A3E5EAF23E9FE8300EDE661 /* QuartzCore.framework */; };
37155CE4233C00EB0034DCE9 /* menu.h in Headers */ = {isa = PBXBuildFile; fileRef = 37155CE3233C00EB0034DCE9 /* menu.h */; };
37A517B32159597E00FBA241 /* Screens.mm in Sources */ = {isa = PBXBuildFile; fileRef = 37A517B22159597E00FBA241 /* Screens.mm */; };
37C09D8821580FE4006A6758 /* SystemDialogs.mm in Sources */ = {isa = PBXBuildFile; fileRef = 37C09D8721580FE4006A6758 /* SystemDialogs.mm */; };
@@ -18,7 +22,6 @@
5B8BD94F215BFEA6005ED2A7 /* clipboard.mm in Sources */ = {isa = PBXBuildFile; fileRef = 5B8BD94E215BFEA6005ED2A7 /* clipboard.mm */; };
AB00E4F72147CA920032A60A /* main.mm in Sources */ = {isa = PBXBuildFile; fileRef = AB00E4F62147CA920032A60A /* main.mm */; };
AB1E522C217613570091CD71 /* OpenGL.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = AB1E522B217613570091CD71 /* OpenGL.framework */; };
- AB573DC4217605E400D389A2 /* gl.mm in Sources */ = {isa = PBXBuildFile; fileRef = AB573DC3217605E400D389A2 /* gl.mm */; };
AB661C1E2148230F00291242 /* AppKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = AB661C1D2148230F00291242 /* AppKit.framework */; };
AB661C202148286E00291242 /* window.mm in Sources */ = {isa = PBXBuildFile; fileRef = AB661C1F2148286E00291242 /* window.mm */; };
AB8F7D6B21482D7F0057DBA5 /* platformthreading.mm in Sources */ = {isa = PBXBuildFile; fileRef = AB8F7D6A21482D7F0057DBA5 /* platformthreading.mm */; };
@@ -26,6 +29,10 @@
/* Begin PBXFileReference section */
1A002B9D232135EE00021753 /* app.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = app.mm; sourceTree = ""; };
+ 1A3E5EA723E9E83B00EDE661 /* rendertarget.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = rendertarget.mm; sourceTree = ""; };
+ 1A3E5EA923E9F26C00EDE661 /* IOSurface.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = IOSurface.framework; path = System/Library/Frameworks/IOSurface.framework; sourceTree = SDKROOT; };
+ 1A3E5EAD23E9FB1300EDE661 /* cgl.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = cgl.mm; sourceTree = ""; };
+ 1A3E5EAF23E9FE8300EDE661 /* QuartzCore.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = QuartzCore.framework; path = System/Library/Frameworks/QuartzCore.framework; sourceTree = SDKROOT; };
37155CE3233C00EB0034DCE9 /* menu.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = menu.h; sourceTree = ""; };
379860FE214DA0C000CD0246 /* KeyTransform.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = KeyTransform.h; sourceTree = ""; };
37A4E71A2178846A00EACBCD /* headers */ = {isa = PBXFileReference; lastKnownFileType = folder; name = headers; path = ../../inc; sourceTree = ""; };
@@ -41,7 +48,6 @@
5BF943652167AD1D009CAE35 /* cursor.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = cursor.h; sourceTree = ""; };
AB00E4F62147CA920032A60A /* main.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = main.mm; sourceTree = ""; };
AB1E522B217613570091CD71 /* OpenGL.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = OpenGL.framework; path = System/Library/Frameworks/OpenGL.framework; sourceTree = SDKROOT; };
- AB573DC3217605E400D389A2 /* gl.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = gl.mm; sourceTree = ""; };
AB661C1D2148230F00291242 /* AppKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = AppKit.framework; path = System/Library/Frameworks/AppKit.framework; sourceTree = SDKROOT; };
AB661C1F2148286E00291242 /* window.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = window.mm; sourceTree = ""; };
AB661C212148288600291242 /* common.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = common.h; sourceTree = ""; };
@@ -54,6 +60,8 @@
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
+ 1A3E5EB023E9FE8300EDE661 /* QuartzCore.framework in Frameworks */,
+ 1A3E5EAA23E9F26C00EDE661 /* IOSurface.framework in Frameworks */,
AB1E522C217613570091CD71 /* OpenGL.framework in Frameworks */,
AB661C1E2148230F00291242 /* AppKit.framework in Frameworks */,
);
@@ -65,6 +73,8 @@
AB661C1C2148230E00291242 /* Frameworks */ = {
isa = PBXGroup;
children = (
+ 1A3E5EAF23E9FE8300EDE661 /* QuartzCore.framework */,
+ 1A3E5EA923E9F26C00EDE661 /* IOSurface.framework */,
AB1E522B217613570091CD71 /* OpenGL.framework */,
AB661C1D2148230F00291242 /* AppKit.framework */,
);
@@ -78,7 +88,7 @@
37DDA9B121933371002E132B /* AvnString.h */,
37DDA9AF219330F8002E132B /* AvnString.mm */,
37A4E71A2178846A00EACBCD /* headers */,
- AB573DC3217605E400D389A2 /* gl.mm */,
+ 1A3E5EAD23E9FB1300EDE661 /* cgl.mm */,
5BF943652167AD1D009CAE35 /* cursor.h */,
5B21A981216530F500CEE36E /* cursor.mm */,
5B8BD94E215BFEA6005ED2A7 /* clipboard.mm */,
@@ -91,6 +101,7 @@
AB00E4F62147CA920032A60A /* main.mm */,
37155CE3233C00EB0034DCE9 /* menu.h */,
520624B222973F4100C4DCEF /* menu.mm */,
+ 1A3E5EA723E9E83B00EDE661 /* rendertarget.mm */,
37A517B22159597E00FBA241 /* Screens.mm */,
37C09D8721580FE4006A6758 /* SystemDialogs.mm */,
AB7A61F02147C815003C5833 /* Products */,
@@ -180,12 +191,13 @@
5B21A982216530F500CEE36E /* cursor.mm in Sources */,
37DDA9B0219330F8002E132B /* AvnString.mm in Sources */,
AB8F7D6B21482D7F0057DBA5 /* platformthreading.mm in Sources */,
+ 1A3E5EA823E9E83B00EDE661 /* rendertarget.mm in Sources */,
+ 1A3E5EAE23E9FB1300EDE661 /* cgl.mm in Sources */,
37E2330F21583241000CB7E2 /* KeyTransform.mm in Sources */,
520624B322973F4100C4DCEF /* menu.mm in Sources */,
37A517B32159597E00FBA241 /* Screens.mm in Sources */,
AB00E4F72147CA920032A60A /* main.mm in Sources */,
37C09D8821580FE4006A6758 /* SystemDialogs.mm in Sources */,
- AB573DC4217605E400D389A2 /* gl.mm in Sources */,
AB661C202148286E00291242 /* window.mm in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
diff --git a/native/Avalonia.Native/src/OSX/cgl.mm b/native/Avalonia.Native/src/OSX/cgl.mm
new file mode 100644
index 0000000000..a9d94cdf04
--- /dev/null
+++ b/native/Avalonia.Native/src/OSX/cgl.mm
@@ -0,0 +1,166 @@
+#include "common.h"
+#include
+
+static CGLContextObj CreateCglContext(CGLContextObj share)
+{
+ int attributes[] = {
+ kCGLPFAAccelerated,
+ kCGLPFAOpenGLProfile, (CGLPixelFormatAttribute)kCGLOGLPVersion_3_2_Core,
+ kCGLPFADepthSize, 8,
+ kCGLPFAStencilSize, 8,
+ kCGLPFAColorSize, 32,
+ 0
+ };
+
+ CGLPixelFormatObj pix;
+ CGLError errorCode;
+ GLint num; // stores the number of possible pixel formats
+ errorCode = CGLChoosePixelFormat( (CGLPixelFormatAttribute*)attributes, &pix, &num );
+ if(errorCode != 0)
+ return nil;
+ CGLContextObj ctx = nil;
+ errorCode = CGLCreateContext(pix, share, &ctx );
+ CGLDestroyPixelFormat( pix );
+ if(errorCode != 0)
+ return nil;
+ return ctx;
+};
+
+
+
+class AvnGlContext : public virtual ComSingleObject
+{
+ // Debug
+ int _usageCount = 0;
+public:
+ CGLContextObj Context;
+ int SampleCount = 0, StencilBits = 0;
+ FORWARD_IUNKNOWN()
+
+ class SavedGlContext : public virtual ComUnknownObject
+ {
+ CGLContextObj _savedContext;
+ ComPtr _parent;
+ public:
+ SavedGlContext(CGLContextObj saved, AvnGlContext* parent)
+ {
+ _savedContext = saved;
+ _parent = parent;
+ _parent->_usageCount++;
+ }
+
+ ~SavedGlContext()
+ {
+ if(_parent->Context == CGLGetCurrentContext())
+ CGLSetCurrentContext(_savedContext);
+ _parent->_usageCount--;
+ CGLUnlockContext(_parent->Context);
+ }
+ };
+
+ AvnGlContext(CGLContextObj context)
+ {
+ Context = context;
+ CGLPixelFormatObj fmt = CGLGetPixelFormat(context);
+ CGLDescribePixelFormat(fmt, 0, kCGLPFASamples, &SampleCount);
+ CGLDescribePixelFormat(fmt, 0, kCGLPFAStencilSize, &StencilBits);
+
+ }
+
+ virtual HRESULT LegacyMakeCurrent() override
+ {
+ if(CGLSetCurrentContext(Context) != 0)
+ return E_FAIL;
+ return S_OK;
+ }
+
+ virtual HRESULT MakeCurrent(IUnknown** ppv) override
+ {
+ CGLContextObj saved = CGLGetCurrentContext();
+ CGLLockContext(Context);
+ if(CGLSetCurrentContext(Context) != 0)
+ {
+ CGLUnlockContext(Context);
+ return E_FAIL;
+ }
+ *ppv = new SavedGlContext(saved, this);
+
+ return S_OK;
+ }
+
+ virtual int GetSampleCount() override
+ {
+ return SampleCount;
+ }
+
+ virtual int GetStencilSize() override
+ {
+ return StencilBits;
+ }
+
+ virtual void* GetNativeHandle() override
+ {
+ return Context;
+ }
+
+ ~AvnGlContext()
+ {
+ CGLReleaseContext(Context);
+ }
+};
+
+class AvnGlDisplay : public virtual ComSingleObject
+{
+ void* _libgl;
+
+public:
+ FORWARD_IUNKNOWN()
+
+ AvnGlDisplay()
+ {
+ _libgl = dlopen("/System/Library/Frameworks/OpenGL.framework/Versions/A/Libraries/libGL.dylib", RTLD_LAZY);
+ }
+
+ virtual void* GetProcAddress(char* proc) override
+ {
+ return dlsym(_libgl, proc);
+ }
+
+ virtual HRESULT CreateContext(IAvnGlContext* share, IAvnGlContext**ppv) override
+ {
+ CGLContextObj shareContext = nil;
+ if(share != nil)
+ {
+ AvnGlContext* shareCtx = dynamic_cast(share);
+ if(shareCtx != nil)
+ shareContext = shareCtx->Context;
+ }
+ CGLContextObj ctx = ::CreateCglContext(shareContext);
+ if(ctx == nil)
+ return E_FAIL;
+ *ppv = new AvnGlContext(ctx);
+ return S_OK;
+ }
+
+ virtual HRESULT WrapContext(void* native, IAvnGlContext**ppv) override
+ {
+ if(native == nil)
+ return E_INVALIDARG;
+ *ppv = new AvnGlContext((CGLContextObj) native);
+ return S_OK;
+ }
+
+ virtual void LegacyClearCurrentContext() override
+ {
+ CGLSetCurrentContext(nil);
+ }
+};
+
+static IAvnGlDisplay* GlDisplay = new AvnGlDisplay();
+
+
+extern IAvnGlDisplay* GetGlDisplay()
+{
+ return GlDisplay;
+};
+
diff --git a/native/Avalonia.Native/src/OSX/common.h b/native/Avalonia.Native/src/OSX/common.h
index 10534dea26..d7eda20f65 100644
--- a/native/Avalonia.Native/src/OSX/common.h
+++ b/native/Avalonia.Native/src/OSX/common.h
@@ -11,14 +11,13 @@
#include
extern IAvnPlatformThreadingInterface* CreatePlatformThreading();
-extern IAvnWindow* CreateAvnWindow(IAvnWindowEvents*events);
-extern IAvnPopup* CreateAvnPopup(IAvnWindowEvents*events);
+extern IAvnWindow* CreateAvnWindow(IAvnWindowEvents*events, IAvnGlContext* gl);
+extern IAvnPopup* CreateAvnPopup(IAvnWindowEvents*events, IAvnGlContext* gl);
extern IAvnSystemDialogs* CreateSystemDialogs();
extern IAvnScreens* CreateScreens();
extern IAvnClipboard* CreateClipboard();
extern IAvnCursorFactory* CreateCursorFactory();
-extern IAvnGlFeature* GetGlFeature();
-extern IAvnGlSurfaceRenderTarget* CreateGlRenderTarget(NSWindow* window, NSView* view);
+extern IAvnGlDisplay* GetGlDisplay();
extern IAvnAppMenu* CreateAppMenu();
extern IAvnAppMenuItem* CreateAppMenuItem();
extern IAvnAppMenuItem* CreateAppMenuItemSeperator();
diff --git a/native/Avalonia.Native/src/OSX/gl.mm b/native/Avalonia.Native/src/OSX/gl.mm
deleted file mode 100644
index feb0643654..0000000000
--- a/native/Avalonia.Native/src/OSX/gl.mm
+++ /dev/null
@@ -1,261 +0,0 @@
-#include "common.h"
-#include
-#include
-#include "window.h"
-
-template char (&ArrayCounter(T (&a)[N]))[N];
-#define ARRAY_COUNT(a) (sizeof(ArrayCounter(a)))
-
-NSOpenGLPixelFormat* CreateFormat()
-{
- NSOpenGLPixelFormatAttribute attribs[] =
- {
- NSOpenGLPFADoubleBuffer,
- NSOpenGLPFAColorSize, 32,
- NSOpenGLPFAStencilSize, 8,
- NSOpenGLPFADepthSize, 8,
- 0
- };
- return [[NSOpenGLPixelFormat alloc] initWithAttributes:attribs];
-}
-
-class AvnGlContext : public virtual ComSingleObject
-{
-public:
- FORWARD_IUNKNOWN()
- NSOpenGLContext* GlContext;
- GLuint Framebuffer, RenderBuffer, StencilBuffer;
- AvnGlContext(NSOpenGLContext* gl, bool offscreen)
- {
- Framebuffer = 0;
- RenderBuffer = 0;
- StencilBuffer = 0;
- GlContext = gl;
- if(offscreen)
- {
- [GlContext makeCurrentContext];
-
- glGenFramebuffersEXT(1, &Framebuffer);
- glBindFramebufferEXT(GL_FRAMEBUFFER, Framebuffer);
- glGenRenderbuffersEXT(1, &RenderBuffer);
- glGenRenderbuffersEXT(1, &StencilBuffer);
-
- glBindRenderbufferEXT(GL_RENDERBUFFER, StencilBuffer);
- glFramebufferRenderbufferEXT(GL_FRAMEBUFFER, GL_STENCIL_ATTACHMENT, GL_RENDERBUFFER, StencilBuffer);
- glBindRenderbufferEXT(GL_RENDERBUFFER, RenderBuffer);
- glFramebufferRenderbufferEXT(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, RenderBuffer);
- }
-
- }
-
-
- virtual HRESULT MakeCurrent() override
- {
- [GlContext makeCurrentContext];/*
- glBindFramebufferEXT(GL_FRAMEBUFFER, Framebuffer);
- glBindRenderbufferEXT(GL_RENDERBUFFER, RenderBuffer);
- glFramebufferRenderbufferEXT(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, RenderBuffer);
- glBindRenderbufferEXT(GL_RENDERBUFFER, StencilBuffer);
- glFramebufferRenderbufferEXT(GL_FRAMEBUFFER, GL_STENCIL_ATTACHMENT, GL_RENDERBUFFER, StencilBuffer);*/
- return S_OK;
- }
-};
-
-class AvnGlDisplay : public virtual ComSingleObject
-{
- int _sampleCount, _stencilSize;
- void* _libgl;
-
-public:
- FORWARD_IUNKNOWN()
-
- AvnGlDisplay(int sampleCount, int stencilSize)
- {
- _sampleCount = sampleCount;
- _stencilSize = stencilSize;
- _libgl = dlopen("/System/Library/Frameworks/OpenGL.framework/Versions/A/Libraries/libGL.dylib", RTLD_LAZY);
- }
-
- virtual HRESULT GetSampleCount(int* ret) override
- {
- *ret = _sampleCount;
- return S_OK;
- }
- virtual HRESULT GetStencilSize(int* ret) override
- {
- *ret = _stencilSize;
- return S_OK;
- }
-
- virtual HRESULT ClearContext() override
- {
- [NSOpenGLContext clearCurrentContext];
- return S_OK;
- }
-
- virtual void* GetProcAddress(char* proc) override
- {
- return dlsym(_libgl, proc);
- }
-};
-
-
-class GlFeature : public virtual ComSingleObject
-{
- IAvnGlDisplay* _display;
- AvnGlContext *_immediate;
- NSOpenGLContext* _shared;
-public:
- FORWARD_IUNKNOWN()
- NSOpenGLPixelFormat* _format;
- GlFeature(IAvnGlDisplay* display, AvnGlContext* immediate, NSOpenGLPixelFormat* format)
- {
- _display = display;
- _immediate = immediate;
- _format = format;
- _shared = [[NSOpenGLContext alloc] initWithFormat:_format shareContext:_immediate->GlContext];
- }
-
- NSOpenGLContext* CreateContext()
- {
- return _shared;
- //return [[NSOpenGLContext alloc] initWithFormat:_format shareContext:nil];
- }
-
- virtual HRESULT ObtainDisplay(IAvnGlDisplay**retOut) override
- {
- *retOut = _display;
- _display->AddRef();
- return S_OK;
- }
-
- virtual HRESULT ObtainImmediateContext(IAvnGlContext**retOut) override
- {
- *retOut = _immediate;
- _immediate->AddRef();
- return S_OK;
- }
-};
-
-static GlFeature* Feature;
-
-GlFeature* CreateGlFeature()
-{
- auto format = CreateFormat();
- if(format == nil)
- {
- NSLog(@"Unable to choose pixel format");
- return NULL;
- }
-
- auto immediateContext = [[NSOpenGLContext alloc] initWithFormat:format shareContext:nil];
- if(immediateContext == nil)
- {
- NSLog(@"Unable to create NSOpenGLContext");
- return NULL;
- }
-
- int stencilBits = 0, sampleCount = 0;
-
- auto fmt = CGLGetPixelFormat([immediateContext CGLContextObj]);
- CGLDescribePixelFormat(fmt, 0, kCGLPFASamples, &sampleCount);
- CGLDescribePixelFormat(fmt, 0, kCGLPFAStencilSize, &stencilBits);
-
- auto offscreen = new AvnGlContext(immediateContext, true);
- auto display = new AvnGlDisplay(sampleCount, stencilBits);
-
- return new GlFeature(display, offscreen, format);
-}
-
-
-static GlFeature* GetFeature()
-{
- if(Feature == nil)
- Feature = CreateGlFeature();
- return Feature;
-}
-
-extern IAvnGlFeature* GetGlFeature()
-{
- return GetFeature();
-}
-
-class AvnGlRenderingSession : public ComSingleObject
-{
- AvnView* _view;
- AvnWindow* _window;
- NSOpenGLContext* _context;
-public:
- FORWARD_IUNKNOWN()
- AvnGlRenderingSession(AvnWindow*window, AvnView* view, NSOpenGLContext* context)
- {
- _context = context;
- _window = window;
- _view = view;
- }
-
- virtual HRESULT GetPixelSize(AvnPixelSize* ret) override
- {
- *ret = [_view getPixelSize];
- return S_OK;
- }
- virtual HRESULT GetScaling(double* ret) override
- {
- *ret = [_window getScaling];
- return S_OK;
- }
-
- virtual ~AvnGlRenderingSession()
- {
- [_context flushBuffer];
- [NSOpenGLContext clearCurrentContext];
- CGLUnlockContext([_context CGLContextObj]);
- [_view unlockFocus];
- }
-};
-
-class AvnGlRenderTarget : public ComSingleObject
-{
- NSView* _view;
- NSWindow* _window;
- NSOpenGLContext* _context;
-public:
- FORWARD_IUNKNOWN()
- AvnGlRenderTarget(NSWindow* window, NSView*view)
- {
- _window = window;
- _view = view;
- _context = GetFeature()->CreateContext();
- }
-
- virtual HRESULT BeginDrawing(IAvnGlSurfaceRenderingSession** ret) override
- {
- auto f = GetFeature();
- if(f == NULL)
- return E_FAIL;
-
- @try
- {
- if(![_view lockFocusIfCanDraw])
- return E_ABORT;
- }
- @catch(NSException* exception)
- {
- return E_ABORT;
- }
-
-
- auto gl = _context;
- CGLLockContext([_context CGLContextObj]);
- [gl setView: _view];
- [gl update];
- [gl makeCurrentContext];
- *ret = new AvnGlRenderingSession(_window, _view, gl);
- return S_OK;
- }
-};
-
-extern IAvnGlSurfaceRenderTarget* CreateGlRenderTarget(NSWindow* window, NSView* view)
-{
- return new AvnGlRenderTarget(window, view);
-}
diff --git a/native/Avalonia.Native/src/OSX/main.mm b/native/Avalonia.Native/src/OSX/main.mm
index 9418782fd1..2b6b24cfda 100644
--- a/native/Avalonia.Native/src/OSX/main.mm
+++ b/native/Avalonia.Native/src/OSX/main.mm
@@ -174,20 +174,20 @@ public:
return (IAvnMacOptions*)new MacOptions();
}
- virtual HRESULT CreateWindow(IAvnWindowEvents* cb, IAvnWindow** ppv) override
+ virtual HRESULT CreateWindow(IAvnWindowEvents* cb, IAvnGlContext* gl, IAvnWindow** ppv) override
{
if(cb == nullptr || ppv == nullptr)
return E_POINTER;
- *ppv = CreateAvnWindow(cb);
+ *ppv = CreateAvnWindow(cb, gl);
return S_OK;
};
- virtual HRESULT CreatePopup(IAvnWindowEvents* cb, IAvnPopup** ppv) override
+ virtual HRESULT CreatePopup(IAvnWindowEvents* cb, IAvnGlContext* gl, IAvnPopup** ppv) override
{
if(cb == nullptr || ppv == nullptr)
return E_POINTER;
- *ppv = CreateAvnPopup(cb);
+ *ppv = CreateAvnPopup(cb, gl);
return S_OK;
}
@@ -221,9 +221,9 @@ public:
return S_OK;
}
- virtual HRESULT ObtainGlFeature(IAvnGlFeature** ppv) override
+ virtual HRESULT ObtainGlDisplay(IAvnGlDisplay** ppv) override
{
- auto rv = ::GetGlFeature();
+ auto rv = ::GetGlDisplay();
if(rv == NULL)
return E_FAIL;
rv->AddRef();
diff --git a/native/Avalonia.Native/src/OSX/platformthreading.mm b/native/Avalonia.Native/src/OSX/platformthreading.mm
index e7abedae51..ed00f8c5e1 100644
--- a/native/Avalonia.Native/src/OSX/platformthreading.mm
+++ b/native/Avalonia.Native/src/OSX/platformthreading.mm
@@ -157,11 +157,14 @@ NSArray* _modes;
-(void) perform
{
+ ComPtr cb;
@synchronized (self) {
_signaled = false;
- if(_parent != NULL && _parent->SignaledCallback != NULL)
- _parent->SignaledCallback->Signaled(0, false);
+ if(_parent != NULL)
+ cb = _parent->SignaledCallback;
}
+ if(cb != nullptr)
+ cb->Signaled(0, false);
}
-(void) setParent:(PlatformThreadingInterface *)parent
diff --git a/native/Avalonia.Native/src/OSX/rendertarget.mm b/native/Avalonia.Native/src/OSX/rendertarget.mm
new file mode 100644
index 0000000000..1565417c1a
--- /dev/null
+++ b/native/Avalonia.Native/src/OSX/rendertarget.mm
@@ -0,0 +1,284 @@
+#include "common.h"
+#include "rendertarget.h"
+#import
+#import
+
+#include
+#include
+#include
+#include
+#include
+
+@interface IOSurfaceHolder : NSObject
+@end
+
+@implementation IOSurfaceHolder
+{
+ @public IOSurfaceRef surface;
+ @public AvnPixelSize size;
+ @public float scale;
+ ComPtr _context;
+ GLuint _framebuffer, _texture, _renderbuffer;
+}
+
+- (IOSurfaceHolder*) initWithSize: (AvnPixelSize) size
+ withScale: (float)scale
+ withOpenGlContext: (IAvnGlContext*) context
+{
+ long bytesPerRow = IOSurfaceAlignProperty(kIOSurfaceBytesPerRow, size.Width * 4);
+ long allocSize = IOSurfaceAlignProperty(kIOSurfaceAllocSize, size.Height * bytesPerRow);
+ NSDictionary* options = @{
+ (id)kIOSurfaceWidth: @(size.Width),
+ (id)kIOSurfaceHeight: @(size.Height),
+ (id)kIOSurfacePixelFormat: @((uint)'BGRA'),
+ (id)kIOSurfaceBytesPerElement: @(4),
+ (id)kIOSurfaceBytesPerRow: @(bytesPerRow),
+ (id)kIOSurfaceAllocSize: @(allocSize),
+
+ //(id)kIOSurfaceCacheMode: @(kIOMapWriteCombineCache),
+ (id)kIOSurfaceElementWidth: @(1),
+ (id)kIOSurfaceElementHeight: @(1)
+ };
+
+ surface = IOSurfaceCreate((CFDictionaryRef)options);
+ self->scale = scale;
+ self->size = size;
+ self->_context = context;
+ return self;
+}
+
+-(HRESULT) prepareForGlRender
+{
+ if(_context == nil)
+ return E_FAIL;
+ if(CGLGetCurrentContext() != _context->GetNativeHandle())
+ return E_FAIL;
+ if(_framebuffer == 0)
+ glGenFramebuffersEXT(1, &_framebuffer);
+
+
+ glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, _framebuffer);
+ if(_texture == 0)
+ {
+ glGenTextures(1, &_texture);
+
+ glBindTexture(GL_TEXTURE_RECTANGLE_EXT, _texture);
+ CGLError res = CGLTexImageIOSurface2D((CGLContextObj)_context->GetNativeHandle(),
+ GL_TEXTURE_RECTANGLE_EXT, GL_RGBA8,
+ size.Width, size.Height, GL_BGRA, GL_UNSIGNED_INT_8_8_8_8_REV, surface, 0);
+ glBindTexture(GL_TEXTURE_RECTANGLE_EXT, 0);
+
+ if(res != 0)
+ {
+ glDeleteTextures(1, &_texture);
+ _texture = 0;
+ glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, 0);
+ return E_FAIL;
+ }
+ glFramebufferTexture2DEXT(GL_FRAMEBUFFER_EXT, GL_COLOR_ATTACHMENT0_EXT, GL_TEXTURE_RECTANGLE_EXT, _texture, 0);
+ }
+
+ if(_renderbuffer == 0)
+ {
+ glGenRenderbuffers(1, &_renderbuffer);
+ glBindRenderbuffer(GL_RENDERBUFFER, _renderbuffer);
+ glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH_COMPONENT, size.Width, size.Height);
+ glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_RENDERBUFFER, _renderbuffer);
+ }
+
+ return S_OK;
+}
+
+-(void) finishDraw
+{
+ ComPtr release;
+ _context->MakeCurrent(release.getPPV());
+ glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, 0);
+ glFlush();
+}
+
+-(void) dealloc
+{
+
+ if(_framebuffer != 0)
+ {
+ ComPtr release;
+ _context->MakeCurrent(release.getPPV());
+ glDeleteFramebuffers(1, &_framebuffer);
+ if(_texture != 0)
+ glDeleteTextures(1, &_texture);
+ if(_renderbuffer != 0)
+ glDeleteRenderbuffers(1, &_renderbuffer);
+ }
+ IOSurfaceDecrementUseCount(surface);
+}
+@end
+
+static IAvnGlSurfaceRenderTarget* CreateGlRenderTarget(IOSurfaceRenderTarget* target);
+
+@implementation IOSurfaceRenderTarget
+{
+ CALayer* _layer;
+ @public IOSurfaceHolder* surface;
+ @public NSObject* lock;
+ ComPtr _glContext;
+}
+
+- (IOSurfaceRenderTarget*) initWithOpenGlContext: (IAvnGlContext*) context;
+{
+ self = [super init];
+ _glContext = context;
+ lock = [NSObject new];
+ surface = nil;
+ [self resize:{1,1} withScale: 1];
+
+ return self;
+}
+
+- (AvnPixelSize) pixelSize {
+ return {1, 1};
+}
+
+- (CALayer *)layer {
+ return _layer;
+}
+
+- (void)resize:(AvnPixelSize)size withScale: (float) scale;{
+ @synchronized (lock) {
+ if(surface == nil
+ || surface->size.Width != size.Width
+ || surface->size.Height != size.Height
+ || surface->scale != scale)
+ surface = [[IOSurfaceHolder alloc] initWithSize:size withScale:scale withOpenGlContext:_glContext.getRaw()];
+ }
+}
+
+- (void)updateLayer {
+ if ([NSThread isMainThread])
+ {
+ @synchronized (lock) {
+ if(_layer == nil)
+ return;
+ [_layer setContents: nil];
+ if(surface != nil)
+ {
+ [_layer setContentsScale: surface->scale];
+ [_layer setContents: (__bridge IOSurface*) surface->surface];
+ }
+ }
+ }
+ else
+ dispatch_async(dispatch_get_main_queue(), ^{
+ [self updateLayer];
+ });
+}
+
+- (void) setNewLayer:(CALayer *)layer {
+ _layer = layer;
+ [self updateLayer];
+}
+
+- (HRESULT)setSwFrame:(AvnFramebuffer *)fb {
+ @synchronized (lock) {
+ if(fb->PixelFormat == AvnPixelFormat::kAvnRgb565)
+ return E_INVALIDARG;
+ if(surface == nil)
+ return E_FAIL;
+ IOSurfaceRef surf = surface->surface;
+ if(IOSurfaceLock(surf, 0, nil))
+ return E_FAIL;
+ size_t w = MIN(fb->Width, IOSurfaceGetWidth(surf));
+ size_t h = MIN(fb->Height, IOSurfaceGetHeight(surf));
+ size_t wbytes = w*4;
+ size_t sstride = IOSurfaceGetBytesPerRow(surf);
+ size_t fstride = fb->Stride;
+ char*pSurface = (char*)IOSurfaceGetBaseAddress(surf);
+ char*pFb = (char*)fb->Data;
+ for(size_t y = 0; y < h; y++)
+ {
+ memcpy(pSurface + y*sstride, pFb + y*fstride, wbytes);
+ }
+ IOSurfaceUnlock(surf, 0, nil);
+ [self updateLayer];
+ return S_OK;
+ }
+}
+
+-(IAvnGlSurfaceRenderTarget*) createSurfaceRenderTarget
+{
+ return CreateGlRenderTarget(self);
+}
+
+@end
+
+class AvnGlRenderingSession : public ComSingleObject
+{
+ ComPtr _releaseContext;
+ IOSurfaceRenderTarget* _target;
+ IOSurfaceHolder* _surface;
+public:
+ FORWARD_IUNKNOWN()
+ AvnGlRenderingSession(IOSurfaceRenderTarget* target, ComPtr releaseContext)
+ {
+ _target = target;
+ // This happens in a synchronized block set up by AvnRenderTarget, so we take the current surface for this
+ // particular render session
+ _surface = _target->surface;
+ _releaseContext = releaseContext;
+ }
+
+ virtual HRESULT GetPixelSize(AvnPixelSize* ret) override
+ {
+ if(!_surface)
+ return E_FAIL;
+ *ret = _surface->size;
+ return S_OK;
+ }
+
+ virtual HRESULT GetScaling(double* ret) override
+ {
+ if(!_surface)
+ return E_FAIL;
+ *ret = _surface->scale;
+ return S_OK;
+ }
+
+ virtual ~AvnGlRenderingSession()
+ {
+ [_surface finishDraw];
+ [_target updateLayer];
+ _releaseContext = nil;
+ }
+};
+
+class AvnGlRenderTarget : public ComSingleObject
+{
+ IOSurfaceRenderTarget* _target;
+public:
+ FORWARD_IUNKNOWN()
+ AvnGlRenderTarget(IOSurfaceRenderTarget* target)
+ {
+ _target = target;
+ }
+
+ virtual HRESULT BeginDrawing(IAvnGlSurfaceRenderingSession** ret) override
+ {
+ ComPtr releaseContext;
+ @synchronized (_target->lock) {
+ if(_target->surface == nil)
+ return E_FAIL;
+ _target->_glContext->MakeCurrent(releaseContext.getPPV());
+ HRESULT res = [_target->surface prepareForGlRender];
+ if(res)
+ return res;
+ *ret = new AvnGlRenderingSession(_target, releaseContext);
+ return S_OK;
+ }
+ }
+};
+
+
+static IAvnGlSurfaceRenderTarget* CreateGlRenderTarget(IOSurfaceRenderTarget* target)
+{
+ return new AvnGlRenderTarget(target);
+}
diff --git a/native/Avalonia.Native/src/OSX/window.mm b/native/Avalonia.Native/src/OSX/window.mm
index e39a061e99..b6ce172ffa 100644
--- a/native/Avalonia.Native/src/OSX/window.mm
+++ b/native/Avalonia.Native/src/OSX/window.mm
@@ -7,44 +7,9 @@
#include "cursor.h"
#include "menu.h"
#include
+#include "rendertarget.h"
+
-class SoftwareDrawingOperation
-{
-public:
- void* Data = 0;
- AvnFramebuffer Desc;
- void Alloc(NSView* view)
- {
- auto logicalSize = [view frame].size;
- auto pixelSize = [view convertSizeToBacking:logicalSize];
- int w = pixelSize.width;
- int h = pixelSize.height;
- int stride = w * 4;
- Data = malloc(h * stride);
- Desc = {
- .Data = Data,
- .Stride = stride,
- .Width = w,
- .Height = h,
- .PixelFormat = kAvnRgba8888,
- .Dpi = AvnVector { .X = w / logicalSize.width * 96, .Y = h / logicalSize.height * 96}
- };
- }
-
- void Dealloc()
- {
- if(Data != NULL)
- {
- free(Data);
- Data = NULL;
- }
- }
-
- ~SoftwareDrawingOperation()
- {
- Dealloc();
- }
-};
class WindowBaseImpl : public virtual ComSingleObject, public INSWindowHolder
{
@@ -61,15 +26,18 @@ public:
AvnView* View;
AvnWindow* Window;
ComPtr BaseEvents;
- SoftwareDrawingOperation CurrentSwDrawingOperation;
+ ComPtr _glContext;
+ NSObject* renderTarget;
AvnPoint lastPositionSet;
NSString* _lastTitle;
IAvnAppMenu* _mainMenu;
- WindowBaseImpl(IAvnWindowBaseEvents* events)
+ WindowBaseImpl(IAvnWindowBaseEvents* events, IAvnGlContext* gl)
{
_mainMenu = nullptr;
BaseEvents = events;
+ _glContext = gl;
+ renderTarget = [[IOSurfaceRenderTarget alloc] initWithOpenGlContext: gl];
View = [[AvnView alloc] initWithParent:this];
Window = [[AvnWindow alloc] initWithParent:this];
@@ -291,29 +259,6 @@ public:
return S_OK;
}
- virtual bool TryLock() override
- {
- @autoreleasepool
- {
- @try
- {
- return [View lockFocusIfCanDraw] == YES;
- }
- @catch (NSException*)
- {
- return NO;
- }
- }
- }
-
- virtual void Unlock() override
- {
- @autoreleasepool
- {
- [View unlockFocus];
- }
- }
-
virtual HRESULT BeginMoveDrag () override
{
@autoreleasepool
@@ -408,16 +353,6 @@ public:
return S_OK;
}
- virtual HRESULT GetSoftwareFramebuffer(AvnFramebuffer*ret) override
- {
- if(![[NSThread currentThread] isMainThread])
- return E_FAIL;
- if(CurrentSwDrawingOperation.Data == NULL)
- CurrentSwDrawingOperation.Alloc(View);
- *ret = CurrentSwDrawingOperation.Desc;
- return S_OK;
- }
-
virtual HRESULT SetCursor(IAvnCursor* cursor) override
{
@autoreleasepool
@@ -451,8 +386,8 @@ public:
{
if(View == NULL)
return E_FAIL;
- *ppv = ::CreateGlRenderTarget(Window, View);
- return S_OK;
+ *ppv = [renderTarget createSurfaceRenderTarget];
+ return *ppv == nil ? E_FAIL : S_OK;
}
protected:
@@ -490,7 +425,7 @@ private:
}
ComPtr WindowEvents;
- WindowImpl(IAvnWindowEvents* events) : WindowBaseImpl(events)
+ WindowImpl(IAvnWindowEvents* events, IAvnGlContext* gl) : WindowBaseImpl(events, gl)
{
WindowEvents = events;
[Window setCanBecomeKeyAndMain];
@@ -731,6 +666,7 @@ NSArray* AllLoopModes = [NSArray arrayWithObjects: NSDefaultRunLoopMode, NSEvent
NSEvent* _lastMouseDownEvent;
bool _lastKeyHandled;
AvnPixelSize _lastPixelSize;
+ NSObject* _renderTarget;
}
- (void)onClosed
@@ -741,19 +677,6 @@ NSArray* AllLoopModes = [NSArray arrayWithObjects: NSDefaultRunLoopMode, NSEvent
}
}
-- (BOOL)lockFocusIfCanDraw
-{
- @synchronized (self)
- {
- if(_parent == nullptr)
- {
- return NO;
- }
- }
-
- return [super lockFocusIfCanDraw];
-}
-
-(AvnPixelSize) getPixelSize
{
return _lastPixelSize;
@@ -764,18 +687,43 @@ NSArray* AllLoopModes = [NSArray arrayWithObjects: NSDefaultRunLoopMode, NSEvent
return _lastMouseDownEvent;
}
+- (void) updateRenderTarget
+{
+ [_renderTarget resize:_lastPixelSize withScale: [[self window] backingScaleFactor]];
+ [self setNeedsDisplayInRect:[self frame]];
+}
+
-(AvnView*) initWithParent: (WindowBaseImpl*) parent
{
self = [super init];
- [self setWantsBestResolutionOpenGLSurface:true];
+ _renderTarget = parent->renderTarget;
[self setWantsLayer:YES];
+ [self setLayerContentsRedrawPolicy: NSViewLayerContentsRedrawDuringViewResize];
+
_parent = parent;
_area = nullptr;
_lastPixelSize.Height = 100;
_lastPixelSize.Width = 100;
+
return self;
}
+- (BOOL)isFlipped
+{
+ return YES;
+}
+
+- (BOOL)wantsUpdateLayer
+{
+ return YES;
+}
+
+- (void)setLayer:(CALayer *)layer
+{
+ [_renderTarget setNewLayer: layer];
+ [super setLayer: layer];
+}
+
- (BOOL)isOpaque
{
return YES;
@@ -823,87 +771,32 @@ NSArray* AllLoopModes = [NSArray arrayWithObjects: NSDefaultRunLoopMode, NSEvent
auto fsize = [self convertSizeToBacking: [self frame].size];
_lastPixelSize.Width = (int)fsize.width;
_lastPixelSize.Height = (int)fsize.height;
-
+ [self updateRenderTarget];
_parent->BaseEvents->Resized(AvnSize{newSize.width, newSize.height});
}
-- (void) drawFb: (AvnFramebuffer*) fb
-{
- auto colorSpace = CGColorSpaceCreateDeviceRGB();
- auto dataProvider = CGDataProviderCreateWithData(NULL, fb->Data, fb->Height*fb->Stride, NULL);
-
- auto image = CGImageCreate(fb->Width, fb->Height, 8, 32, fb->Stride, colorSpace, kCGBitmapByteOrderDefault | kCGImageAlphaPremultipliedLast,
- dataProvider, nullptr, false, kCGRenderingIntentDefault);
-
- auto ctx = [NSGraphicsContext currentContext];
-
- [ctx saveGraphicsState];
- auto cgc = [ctx CGContext];
-
- CGContextDrawImage(cgc, CGRect{0,0, fb->Width/(fb->Dpi.X/96), fb->Height/(fb->Dpi.Y/96)}, image);
- CGImageRelease(image);
- CGColorSpaceRelease(colorSpace);
- CGDataProviderRelease(dataProvider);
-
- [ctx restoreGraphicsState];
-
-}
-
-- (void)drawRect:(NSRect)dirtyRect
+- (void)updateLayer
{
if (_parent == nullptr)
{
return;
}
-
- _parent->BaseEvents->RunRenderPriorityJobs();
- @synchronized (self) {
- if(_swRenderedFrame != NULL)
- {
- [self drawFb: &_swRenderedFrameBuffer];
- return;
- }
- }
-
- auto swOp = &_parent->CurrentSwDrawingOperation;
+ _parent->BaseEvents->RunRenderPriorityJobs();
_parent->BaseEvents->Paint();
- if(swOp->Data != NULL)
- [self drawFb: &swOp->Desc];
-
- swOp->Dealloc();
- return;
}
--(void) redrawSelf
+- (void)drawRect:(NSRect)dirtyRect
{
- @autoreleasepool
- {
- @synchronized(self)
- {
- if(!_queuedDisplayFromThread)
- return;
- _queuedDisplayFromThread = false;
- }
- [self setNeedsDisplayInRect:[self frame]];
- [self display];
-
- }
+ return;
}
-(void) setSwRenderedFrame: (AvnFramebuffer*) fb dispose: (IUnknown*) dispose
{
@autoreleasepool {
- @synchronized (self) {
- _swRenderedFrame = dispose;
- _swRenderedFrameBuffer = *fb;
- if(!_queuedDisplayFromThread)
- {
- _queuedDisplayFromThread = true;
- [self performSelector:@selector(redrawSelf) onThread:[NSThread mainThread] withObject:NULL waitUntilDone:false modes: AllLoopModes];
- }
- }
+ [_renderTarget setSwFrame:fb];
+ dispose->Release();
}
}
@@ -928,7 +821,7 @@ NSArray* AllLoopModes = [NSArray arrayWithObjects: NSDefaultRunLoopMode, NSEvent
auto fsize = [self convertSizeToBacking: [self frame].size];
_lastPixelSize.Width = (int)fsize.width;
_lastPixelSize.Height = (int)fsize.height;
-
+ [self updateRenderTarget];
_parent->BaseEvents->ScalingChanged([_parent->Window backingScaleFactor]);
[super viewDidChangeBackingProperties];
@@ -1478,7 +1371,7 @@ private:
END_INTERFACE_MAP()
virtual ~PopupImpl(){}
ComPtr WindowEvents;
- PopupImpl(IAvnWindowEvents* events) : WindowBaseImpl(events)
+ PopupImpl(IAvnWindowEvents* events, IAvnGlContext* gl) : WindowBaseImpl(events, gl)
{
WindowEvents = events;
[Window setLevel:NSPopUpMenuWindowLevel];
@@ -1502,20 +1395,20 @@ protected:
}
};
-extern IAvnPopup* CreateAvnPopup(IAvnWindowEvents*events)
+extern IAvnPopup* CreateAvnPopup(IAvnWindowEvents*events, IAvnGlContext* gl)
{
@autoreleasepool
{
- IAvnPopup* ptr = dynamic_cast(new PopupImpl(events));
+ IAvnPopup* ptr = dynamic_cast(new PopupImpl(events, gl));
return ptr;
}
}
-extern IAvnWindow* CreateAvnWindow(IAvnWindowEvents*events)
+extern IAvnWindow* CreateAvnWindow(IAvnWindowEvents*events, IAvnGlContext* gl)
{
@autoreleasepool
{
- IAvnWindow* ptr = (IAvnWindow*)new WindowImpl(events);
+ IAvnWindow* ptr = (IAvnWindow*)new WindowImpl(events, gl);
return ptr;
}
}
diff --git a/readme.md b/readme.md
index 783857a096..42b1e52205 100644
--- a/readme.md
+++ b/readme.md
@@ -10,7 +10,7 @@
**Avalonia** is a WPF/UWP-inspired cross-platform XAML-based UI framework providing a flexible styling system and supporting a wide range of Operating Systems such as Windows (.NET Framework, .NET Core), Linux (via Xorg), macOS and with experimental support for Android and iOS.
-**Avalonia** is ready for **General-Purpose Desktop App Development**. However, there may be some bugs and breaking changes as we continue along into this project's development. To see the status of some of our features, please see our [Roadmap here](https://github.com/AvaloniaUI/Avalonia/issues/2239).
+**Avalonia** is ready for **General-Purpose Desktop App Development**. However, there may be some bugs and [breaking changes](https://github.com/AvaloniaUI/Avalonia/wiki/Breaking-Changes) as we continue along into this project's development. To see the status of some of our features, please see our [Roadmap here](https://github.com/AvaloniaUI/Avalonia/issues/2239).
| Control catalog | Desktop platforms | Mobile platforms |
|---|---|---|
@@ -24,6 +24,8 @@ 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).
+If you need to develop Avalonia app with JetBrains Rider, go and *vote* on [this issue](https://youtrack.jetbrains.com/issue/RIDER-39247) in their tracker. JetBrains won't do things without their users telling them that they want the feature, so only **YOU** can make it happen.
+
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/BindingDemo/BindingDemo.csproj b/samples/BindingDemo/BindingDemo.csproj
index 3005fdbc51..a3f16dc222 100644
--- a/samples/BindingDemo/BindingDemo.csproj
+++ b/samples/BindingDemo/BindingDemo.csproj
@@ -4,6 +4,7 @@
netcoreapp2.0;net461
+
diff --git a/samples/ControlCatalog/ControlCatalog.csproj b/samples/ControlCatalog/ControlCatalog.csproj
index 4c2769bcc4..11c0a4fa68 100644
--- a/samples/ControlCatalog/ControlCatalog.csproj
+++ b/samples/ControlCatalog/ControlCatalog.csproj
@@ -21,6 +21,7 @@
+
diff --git a/samples/ControlCatalog/Pages/ProgressBarPage.xaml b/samples/ControlCatalog/Pages/ProgressBarPage.xaml
index 39bbf391bb..13bae59805 100644
--- a/samples/ControlCatalog/Pages/ProgressBarPage.xaml
+++ b/samples/ControlCatalog/Pages/ProgressBarPage.xaml
@@ -6,15 +6,19 @@
A progress bar control
+
-
+
-
+
diff --git a/samples/RenderDemo/RenderDemo.csproj b/samples/RenderDemo/RenderDemo.csproj
index 3005fdbc51..a3f16dc222 100644
--- a/samples/RenderDemo/RenderDemo.csproj
+++ b/samples/RenderDemo/RenderDemo.csproj
@@ -4,6 +4,7 @@
netcoreapp2.0;net461
+
diff --git a/samples/VirtualizationDemo/VirtualizationDemo.csproj b/samples/VirtualizationDemo/VirtualizationDemo.csproj
index 3005fdbc51..a3f16dc222 100644
--- a/samples/VirtualizationDemo/VirtualizationDemo.csproj
+++ b/samples/VirtualizationDemo/VirtualizationDemo.csproj
@@ -4,6 +4,7 @@
netcoreapp2.0;net461
+
diff --git a/src/Avalonia.Base/AvaloniaObject.cs b/src/Avalonia.Base/AvaloniaObject.cs
index 6a00feaf79..b0ff591682 100644
--- a/src/Avalonia.Base/AvaloniaObject.cs
+++ b/src/Avalonia.Base/AvaloniaObject.cs
@@ -34,7 +34,6 @@ namespace Avalonia
public AvaloniaObject()
{
VerifyAccess();
- AvaloniaPropertyRegistry.Instance.NotifyInitialized(this);
}
///
@@ -479,7 +478,13 @@ namespace Avalonia
}
}
- void IValueSink.Completed(AvaloniaProperty property, IPriorityValueEntry entry) { }
+ void IValueSink.Completed(
+ StyledPropertyBase property,
+ IPriorityValueEntry entry,
+ Optional oldValue)
+ {
+ ((IValueSink)this).ValueChanged(property, BindingPriority.Unset, oldValue, default);
+ }
///
/// Called for each inherited property when the changes.
diff --git a/src/Avalonia.Base/AvaloniaProperty.cs b/src/Avalonia.Base/AvaloniaProperty.cs
index b305a9aaa2..aa7a675764 100644
--- a/src/Avalonia.Base/AvaloniaProperty.cs
+++ b/src/Avalonia.Base/AvaloniaProperty.cs
@@ -20,7 +20,6 @@ namespace Avalonia
public static readonly object UnsetValue = new UnsetValueType();
private static int s_nextId;
- private readonly Subject _initialized;
private readonly Subject _changed;
private readonly PropertyMetadata _defaultMetadata;
private readonly Dictionary _metadata;
@@ -53,7 +52,6 @@ namespace Avalonia
throw new ArgumentException("'name' may not contain periods.");
}
- _initialized = new Subject();
_changed = new Subject();
_metadata = new Dictionary();
@@ -81,7 +79,6 @@ namespace Avalonia
Contract.Requires(source != null);
Contract.Requires(ownerType != null);
- _initialized = source._initialized;
_changed = source._changed;
_metadata = new Dictionary();
@@ -136,22 +133,6 @@ namespace Avalonia
///
public virtual bool IsReadOnly => false;
- ///
- /// Gets an observable that is fired when this property is initialized on a
- /// new instance.
- ///
- ///
- /// This observable is fired each time a new is constructed
- /// for all properties registered on the object's type. The default value of the property
- /// for the object is passed in the args' NewValue (OldValue will always be
- /// .
- ///
- ///
- /// An observable that is fired when this property is initialized on a new
- /// instance.
- ///
- public IObservable Initialized => _initialized;
-
///
/// Gets an observable that is fired when this property changes on any
/// instance.
@@ -488,26 +469,6 @@ namespace Avalonia
return Name;
}
- ///
- /// True if has any observers.
- ///
- internal bool HasNotifyInitializedObservers => _initialized.HasObservers;
-
- ///
- /// Notifies the observable.
- ///
- /// The object being initialized.
- internal abstract void NotifyInitialized(IAvaloniaObject o);
-
- ///
- /// Notifies the observable.
- ///
- /// The observable arguments.
- internal void NotifyInitialized(AvaloniaPropertyChangedEventArgs e)
- {
- _initialized.OnNext(e);
- }
-
///
/// Notifies the observable.
///
diff --git a/src/Avalonia.Base/AvaloniaPropertyRegistry.cs b/src/Avalonia.Base/AvaloniaPropertyRegistry.cs
index 14c8630599..29ab10278b 100644
--- a/src/Avalonia.Base/AvaloniaPropertyRegistry.cs
+++ b/src/Avalonia.Base/AvaloniaPropertyRegistry.cs
@@ -415,51 +415,6 @@ namespace Avalonia
_inheritedCache.Clear();
}
- internal void NotifyInitialized(AvaloniaObject o)
- {
- Contract.Requires(o != null);
-
- var type = o.GetType();
-
- if (!_initializedCache.TryGetValue(type, out var initializationData))
- {
- var visited = new HashSet();
-
- initializationData = new List();
-
- foreach (AvaloniaProperty property in GetRegistered(type))
- {
- if (property.IsDirect)
- {
- initializationData.Add(new PropertyInitializationData(property, (IDirectPropertyAccessor)property));
- }
- else
- {
- initializationData.Add(new PropertyInitializationData(property, (IStyledPropertyAccessor)property, type));
- }
-
- visited.Add(property);
- }
-
- foreach (AvaloniaProperty property in GetRegisteredAttached(type))
- {
- if (!visited.Contains(property))
- {
- initializationData.Add(new PropertyInitializationData(property, (IStyledPropertyAccessor)property, type));
-
- visited.Add(property);
- }
- }
-
- _initializedCache.Add(type, initializationData);
- }
-
- foreach (PropertyInitializationData data in initializationData)
- {
- data.Property.NotifyInitialized(o);
- }
- }
-
private readonly struct PropertyInitializationData
{
public AvaloniaProperty Property { get; }
diff --git a/src/Avalonia.Base/DirectPropertyBase.cs b/src/Avalonia.Base/DirectPropertyBase.cs
index 7a0be065eb..39ed3b084f 100644
--- a/src/Avalonia.Base/DirectPropertyBase.cs
+++ b/src/Avalonia.Base/DirectPropertyBase.cs
@@ -101,21 +101,6 @@ namespace Avalonia
return (DirectPropertyMetadata)base.GetMetadata(type);
}
- ///
- internal override void NotifyInitialized(IAvaloniaObject o)
- {
- if (HasNotifyInitializedObservers)
- {
- var e = new AvaloniaPropertyChangedEventArgs(
- o,
- this,
- default,
- InvokeGetter(o),
- BindingPriority.Unset);
- NotifyInitialized(e);
- }
- }
-
///
internal override void RouteClearValue(IAvaloniaObject o)
{
diff --git a/src/Avalonia.Base/PropertyStore/BindingEntry.cs b/src/Avalonia.Base/PropertyStore/BindingEntry.cs
index 09a0f169df..3249b31d66 100644
--- a/src/Avalonia.Base/PropertyStore/BindingEntry.cs
+++ b/src/Avalonia.Base/PropertyStore/BindingEntry.cs
@@ -48,10 +48,10 @@ namespace Avalonia.PropertyStore
{
_subscription?.Dispose();
_subscription = null;
- _sink.Completed(Property, this);
+ _sink.Completed(Property, this, Value);
}
- public void OnCompleted() => _sink.Completed(Property, this);
+ public void OnCompleted() => _sink.Completed(Property, this, Value);
public void OnError(Exception error)
{
diff --git a/src/Avalonia.Base/PropertyStore/IValueSink.cs b/src/Avalonia.Base/PropertyStore/IValueSink.cs
index 223b0058c1..9012a985ac 100644
--- a/src/Avalonia.Base/PropertyStore/IValueSink.cs
+++ b/src/Avalonia.Base/PropertyStore/IValueSink.cs
@@ -15,6 +15,9 @@ namespace Avalonia.PropertyStore
Optional oldValue,
BindingValue newValue);
- void Completed(AvaloniaProperty property, IPriorityValueEntry entry);
+ void Completed(
+ StyledPropertyBase property,
+ IPriorityValueEntry entry,
+ Optional oldValue);
}
}
diff --git a/src/Avalonia.Base/PropertyStore/PriorityValue.cs b/src/Avalonia.Base/PropertyStore/PriorityValue.cs
index 2785dc6840..4ef8f650fa 100644
--- a/src/Avalonia.Base/PropertyStore/PriorityValue.cs
+++ b/src/Avalonia.Base/PropertyStore/PriorityValue.cs
@@ -117,7 +117,10 @@ namespace Avalonia.PropertyStore
UpdateEffectiveValue();
}
- void IValueSink.Completed(AvaloniaProperty property, IPriorityValueEntry entry)
+ void IValueSink.Completed(
+ StyledPropertyBase property,
+ IPriorityValueEntry entry,
+ Optional oldValue)
{
_entries.Remove((IPriorityValueEntry)entry);
UpdateEffectiveValue();
diff --git a/src/Avalonia.Base/StyledPropertyBase.cs b/src/Avalonia.Base/StyledPropertyBase.cs
index 8c4d683ae0..d1f961a567 100644
--- a/src/Avalonia.Base/StyledPropertyBase.cs
+++ b/src/Avalonia.Base/StyledPropertyBase.cs
@@ -181,21 +181,6 @@ namespace Avalonia
///
object IStyledPropertyAccessor.GetDefaultValue(Type type) => GetDefaultBoxedValue(type);
- ///
- internal override void NotifyInitialized(IAvaloniaObject o)
- {
- if (HasNotifyInitializedObservers)
- {
- var e = new AvaloniaPropertyChangedEventArgs(
- o,
- this,
- default,
- o.GetValue(this),
- BindingPriority.Unset);
- NotifyInitialized(e);
- }
- }
-
///
internal override void RouteClearValue(IAvaloniaObject o)
{
diff --git a/src/Avalonia.Base/Utilities/TypeUtilities.cs b/src/Avalonia.Base/Utilities/TypeUtilities.cs
index d1393a9c0d..8a25d29c3f 100644
--- a/src/Avalonia.Base/Utilities/TypeUtilities.cs
+++ b/src/Avalonia.Base/Utilities/TypeUtilities.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.ComponentModel;
using System.Globalization;
using System.Linq;
using System.Reflection;
@@ -185,6 +186,14 @@ namespace Avalonia.Utilities
}
}
+ var typeConverter = TypeDescriptor.GetConverter(to);
+
+ if (typeConverter.CanConvertFrom(from) == true)
+ {
+ result = typeConverter.ConvertFrom(null, culture, value);
+ return true;
+ }
+
var cast = FindTypeConversionOperatorMethod(from, to, OperatorType.Implicit | OperatorType.Explicit);
if (cast != null)
diff --git a/src/Avalonia.Base/Utilities/WeakEventHandlerManager.cs b/src/Avalonia.Base/Utilities/WeakEventHandlerManager.cs
index 37e25d0fac..aca08f5259 100644
--- a/src/Avalonia.Base/Utilities/WeakEventHandlerManager.cs
+++ b/src/Avalonia.Base/Utilities/WeakEventHandlerManager.cs
@@ -183,11 +183,16 @@ namespace Avalonia.Utilities
for (int c = 0; c < _count; c++)
{
var r = _data[c];
+
+ TSubscriber target = null;
+
+ r.Subscriber?.TryGetTarget(out target);
+
//Mark current index as first empty
- if (r.Subscriber == null && empty == -1)
+ if (target == null && empty == -1)
empty = c;
//If current element isn't null and we have an empty one
- if (r.Subscriber != null && empty != -1)
+ if (target != null && empty != -1)
{
_data[c] = default;
_data[empty] = r;
diff --git a/src/Avalonia.Base/ValueStore.cs b/src/Avalonia.Base/ValueStore.cs
index 58ebc48652..e310be0f0a 100644
--- a/src/Avalonia.Base/ValueStore.cs
+++ b/src/Avalonia.Base/ValueStore.cs
@@ -148,7 +148,7 @@ namespace Avalonia
_values.Remove(property);
_sink.ValueChanged(
property,
- BindingPriority.LocalValue,
+ BindingPriority.Unset,
old,
BindingValue.Unset);
}
@@ -173,7 +173,7 @@ namespace Avalonia
{
return new Diagnostics.AvaloniaPropertyValue(
property,
- slot.Value.HasValue ? (object)slot.Value : AvaloniaProperty.UnsetValue,
+ slot.Value.HasValue ? slot.Value.Value : AvaloniaProperty.UnsetValue,
slot.ValuePriority,
null);
}
@@ -190,13 +190,17 @@ namespace Avalonia
_sink.ValueChanged(property, priority, oldValue, newValue);
}
- void IValueSink.Completed(AvaloniaProperty property, IPriorityValueEntry entry)
+ void IValueSink.Completed(
+ StyledPropertyBase property,
+ IPriorityValueEntry entry,
+ Optional oldValue)
{
if (_values.TryGetValue(property, out var slot))
{
if (slot == entry)
{
_values.Remove(property);
+ _sink.Completed(property, entry, oldValue);
}
}
}
@@ -228,6 +232,7 @@ namespace Avalonia
else
{
var priorityValue = new PriorityValue(_owner, property, this, l);
+ priorityValue.SetValue(value, priority);
_values.SetValue(property, priorityValue);
}
}
diff --git a/src/Avalonia.Controls.DataGrid/DataGrid.cs b/src/Avalonia.Controls.DataGrid/DataGrid.cs
index e16e11cdd6..844316741a 100644
--- a/src/Avalonia.Controls.DataGrid/DataGrid.cs
+++ b/src/Avalonia.Controls.DataGrid/DataGrid.cs
@@ -65,7 +65,7 @@ namespace Avalonia.Controls
private const double DATAGRID_minimumColumnHeaderHeight = 4;
internal const double DATAGRID_maximumStarColumnWidth = 10000;
internal const double DATAGRID_minimumStarColumnWidth = 0.001;
- private const double DATAGRID_mouseWheelDelta = 48.0;
+ private const double DATAGRID_mouseWheelDelta = 72.0;
private const double DATAGRID_maxHeadersThickness = 32768;
private const double DATAGRID_defaultRowHeight = 22;
@@ -149,6 +149,9 @@ namespace Avalonia.Controls
private IEnumerable _items;
+ public event EventHandler HorizontalScroll;
+ public event EventHandler VerticalScroll;
+
///
/// Identifies the CanUserReorderColumns dependency property.
///
@@ -373,7 +376,11 @@ namespace Avalonia.Controls
public bool IsValid
{
get { return _isValid; }
- internal set { SetAndRaise(IsValidProperty, ref _isValid, value); }
+ internal set
+ {
+ SetAndRaise(IsValidProperty, ref _isValid, value);
+ PseudoClasses.Set(":invalid", !value);
+ }
}
public static readonly StyledProperty MaxColumnWidthProperty =
@@ -656,8 +663,6 @@ namespace Avalonia.Controls
HorizontalScrollBarVisibilityProperty,
VerticalScrollBarVisibilityProperty);
- PseudoClass(IsValidProperty, x => !x, ":invalid");
-
ItemsProperty.Changed.AddClassHandler((x, e) => x.OnItemsPropertyChanged(e));
CanUserResizeColumnsProperty.Changed.AddClassHandler((x, e) => x.OnCanUserResizeColumnsChanged(e));
ColumnWidthProperty.Changed.AddClassHandler((x, e) => x.OnColumnWidthChanged(e));
@@ -4223,6 +4228,7 @@ namespace Avalonia.Controls
private void HorizontalScrollBar_Scroll(object sender, ScrollEventArgs e)
{
ProcessHorizontalScroll(e.ScrollEventType);
+ HorizontalScroll?.Invoke(sender, e);
}
private bool IsColumnOutOfBounds(int columnIndex)
@@ -5555,6 +5561,7 @@ namespace Avalonia.Controls
private void VerticalScrollBar_Scroll(object sender, ScrollEventArgs e)
{
ProcessVerticalScroll(e.ScrollEventType);
+ VerticalScroll?.Invoke(sender, e);
}
//TODO: Ensure left button is checked for
diff --git a/src/Avalonia.Controls.DataGrid/DataGridColumnHeader.cs b/src/Avalonia.Controls.DataGrid/DataGridColumnHeader.cs
index 062fdeb4d3..31c77c595d 100644
--- a/src/Avalonia.Controls.DataGrid/DataGridColumnHeader.cs
+++ b/src/Avalonia.Controls.DataGrid/DataGridColumnHeader.cs
@@ -67,7 +67,7 @@ namespace Avalonia.Controls
static DataGridColumnHeader()
{
- AreSeparatorsVisibleProperty.Changed.AddClassHandler((x,e) => x.OnAreSeparatorsVisibleChanged(e));
+ AreSeparatorsVisibleProperty.Changed.AddClassHandler((x, e) => x.OnAreSeparatorsVisibleChanged(e));
}
///
@@ -103,7 +103,7 @@ namespace Avalonia.Controls
{
get;
set;
- }
+ }
internal DataGrid OwningGrid => OwningColumn?.OwningGrid;
internal int ColumnIndex
@@ -116,19 +116,19 @@ namespace Avalonia.Controls
}
return OwningColumn.Index;
}
- }
+ }
internal ListSortDirection? CurrentSortingState
{
get;
private set;
- }
+ }
private bool IsMouseOver
{
get;
set;
- }
+ }
private bool IsPressed
{
@@ -158,14 +158,14 @@ namespace Avalonia.Controls
&& OwningGrid.DataConnection.AllowSort)
{
var sort = OwningColumn.GetSortDescription();
- if(sort != null)
+ if (sort != null)
{
CurrentSortingState = sort.Descending ? ListSortDirection.Descending : ListSortDirection.Ascending;
}
}
- PseudoClasses.Set(":sortascending",
+ PseudoClasses.Set(":sortascending",
CurrentSortingState.HasValue && CurrentSortingState.Value == ListSortDirection.Ascending);
- PseudoClasses.Set(":sortdescending",
+ PseudoClasses.Set(":sortdescending",
CurrentSortingState.HasValue && CurrentSortingState.Value == ListSortDirection.Descending);
}
@@ -195,7 +195,7 @@ namespace Avalonia.Controls
// completed a click without dragging, so we're sorting
InvokeProcessSort(keyModifiers);
handled = true;
- }
+ }
internal void InvokeProcessSort(KeyModifiers keyModifiers)
{
@@ -208,7 +208,7 @@ namespace Avalonia.Controls
{
Avalonia.Threading.Dispatcher.UIThread.Post(() => ProcessSort(keyModifiers));
}
- }
+ }
//TODO GroupSorting
internal void ProcessSort(KeyModifiers keyModifiers)
@@ -246,45 +246,49 @@ namespace Avalonia.Controls
owningGrid.DataConnection.SortDescriptions.Clear();
}
- if (sort != null)
+ // if ctrl is held down, we only clear the sort directions
+ if (!ctrl)
{
- newSort = sort.SwitchSortDirection();
-
- // changing direction should not affect sort order, so we replace this column's
- // sort description instead of just adding it to the end of the collection
- int oldIndex = owningGrid.DataConnection.SortDescriptions.IndexOf(sort);
- if (oldIndex >= 0)
+ if (sort != null)
{
- owningGrid.DataConnection.SortDescriptions.Remove(sort);
- owningGrid.DataConnection.SortDescriptions.Insert(oldIndex, newSort);
+ newSort = sort.SwitchSortDirection();
+
+ // changing direction should not affect sort order, so we replace this column's
+ // sort description instead of just adding it to the end of the collection
+ int oldIndex = owningGrid.DataConnection.SortDescriptions.IndexOf(sort);
+ if (oldIndex >= 0)
+ {
+ owningGrid.DataConnection.SortDescriptions.Remove(sort);
+ owningGrid.DataConnection.SortDescriptions.Insert(oldIndex, newSort);
+ }
+ else
+ {
+ owningGrid.DataConnection.SortDescriptions.Add(newSort);
+ }
}
else
{
+ string propertyName = OwningColumn.GetSortPropertyName();
+ // no-opt if we couldn't find a property to sort on
+ if (string.IsNullOrEmpty(propertyName))
+ {
+ return;
+ }
+
+ newSort = DataGridSortDescription.FromPath(propertyName, culture: collectionView.Culture);
owningGrid.DataConnection.SortDescriptions.Add(newSort);
}
}
- else
- {
- string propertyName = OwningColumn.GetSortPropertyName();
- // no-opt if we couldn't find a property to sort on
- if (string.IsNullOrEmpty(propertyName))
- {
- return;
- }
-
- newSort = DataGridSortDescription.FromPath(propertyName, culture: collectionView.Culture);
- owningGrid.DataConnection.SortDescriptions.Add(newSort);
- }
}
}
- }
+ }
private bool CanReorderColumn(DataGridColumn column)
{
- return OwningGrid.CanUserReorderColumns
+ return OwningGrid.CanUserReorderColumns
&& !(column is DataGridFillerColumn)
&& (column.CanUserReorderInternal.HasValue && column.CanUserReorderInternal.Value || !column.CanUserReorderInternal.HasValue);
- }
+ }
///
/// Determines whether a column can be resized by dragging the border of its header. If star sizing
@@ -302,7 +306,7 @@ namespace Avalonia.Controls
return false;
}
return column.ActualCanUserResize;
- }
+ }
private static bool TrySetResizeColumn(DataGridColumn column)
{
@@ -316,7 +320,7 @@ namespace Avalonia.Controls
return true;
}
return false;
- }
+ }
//TODO DragDrop
@@ -326,7 +330,7 @@ namespace Avalonia.Controls
if (OwningGrid != null && OwningGrid.ColumnHeaders != null)
{
- args.Device.Capture(this);
+ args.Pointer.Capture(this);
_dragMode = DragMode.MouseDown;
_frozenColumnsWidth = OwningGrid.ColumnsInternal.GetVisibleFrozenEdgedColumnsWidth();
@@ -371,7 +375,7 @@ namespace Avalonia.Controls
{
if (_dragMode == DragMode.MouseDown)
{
- OnMouseLeftButtonUp_Click(args.KeyModifiers, ref handled);
+ OnMouseLeftButtonUp_Click(args.KeyModifiers, ref handled);
}
else if (_dragMode == DragMode.Reorder)
{
@@ -391,7 +395,7 @@ namespace Avalonia.Controls
SetDragCursor(mousePosition);
// Variables that track drag mode states get reset in DataGridColumnHeader_LostMouseCapture
- args.Device.Capture(null);
+ args.Pointer.Capture(null);
OnLostMouseCapture();
_dragMode = DragMode.None;
handled = true;
@@ -449,7 +453,7 @@ namespace Avalonia.Controls
OnMouseLeave();
ApplyState();
- }
+ }
private void DataGridColumnHeader_PointerPressed(object sender, PointerPressedEventArgs e)
{
@@ -577,7 +581,7 @@ namespace Avalonia.Controls
{
return OwningGrid.Columns.Count - 1;
}
- }
+ }
///
/// Returns true if the mouse is
@@ -723,7 +727,7 @@ namespace Avalonia.Controls
Point targetPosition = new Point(0, 0);
if (targetColumn == null || targetColumn == OwningGrid.ColumnsInternal.FillerColumn || targetColumn.IsFrozen != OwningColumn.IsFrozen)
{
- targetColumn =
+ targetColumn =
OwningGrid.ColumnsInternal.GetLastColumn(
isVisible: true,
isFrozen: OwningColumn.IsFrozen,
@@ -741,7 +745,7 @@ namespace Avalonia.Controls
handled = true;
}
- }
+ }
private void OnMouseMove_Resize(ref bool handled, Point mousePositionHeaders)
{
@@ -764,7 +768,7 @@ namespace Avalonia.Controls
handled = true;
}
- }
+ }
private void SetDragCursor(Point mousePosition)
{
diff --git a/src/Avalonia.Controls.DataGrid/Themes/Default.xaml b/src/Avalonia.Controls.DataGrid/Themes/Default.xaml
index 7a399349f6..17e7ecba43 100644
--- a/src/Avalonia.Controls.DataGrid/Themes/Default.xaml
+++ b/src/Avalonia.Controls.DataGrid/Themes/Default.xaml
@@ -45,7 +45,7 @@
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+ >
+
+
+
+
+
+
+
+
+
diff --git a/src/Avalonia.Diagnostics/Diagnostics/Views/ConsoleView.xaml.cs b/src/Avalonia.Diagnostics/Diagnostics/Views/ConsoleView.xaml.cs
new file mode 100644
index 0000000000..ae70b59fde
--- /dev/null
+++ b/src/Avalonia.Diagnostics/Diagnostics/Views/ConsoleView.xaml.cs
@@ -0,0 +1,64 @@
+using System;
+using System.Collections.Specialized;
+using Avalonia.Controls;
+using Avalonia.Diagnostics.ViewModels;
+using Avalonia.Input;
+using Avalonia.LogicalTree;
+using Avalonia.Markup.Xaml;
+using Avalonia.Threading;
+
+namespace Avalonia.Diagnostics.Views
+{
+ internal class ConsoleView : UserControl
+ {
+ private readonly ListBox _historyList;
+ private readonly TextBox _input;
+
+ public ConsoleView()
+ {
+ this.InitializeComponent();
+ _historyList = this.FindControl("historyList");
+ ((ILogical)_historyList).LogicalChildren.CollectionChanged += HistoryChanged;
+ _input = this.FindControl("input");
+ _input.KeyDown += InputKeyDown;
+ }
+
+ public void FocusInput() => _input.Focus();
+
+ private void InitializeComponent()
+ {
+ AvaloniaXamlLoader.Load(this);
+ }
+
+ private void HistoryChanged(object sender, NotifyCollectionChangedEventArgs e)
+ {
+ if (e.Action == NotifyCollectionChangedAction.Add && e.NewItems[0] is IControl control)
+ {
+ DispatcherTimer.RunOnce(control.BringIntoView, TimeSpan.Zero);
+ }
+ }
+
+ private void InputKeyDown(object sender, KeyEventArgs e)
+ {
+ var vm = (ConsoleViewModel)DataContext;
+
+ switch (e.Key)
+ {
+ case Key.Enter:
+ vm.Execute();
+ e.Handled = true;
+ break;
+ case Key.Up:
+ vm.HistoryUp();
+ _input.CaretIndex = _input.Text.Length;
+ e.Handled = true;
+ break;
+ case Key.Down:
+ vm.HistoryDown();
+ _input.CaretIndex = _input.Text.Length;
+ e.Handled = true;
+ break;
+ }
+ }
+ }
+}
diff --git a/src/Avalonia.Diagnostics/Diagnostics/Views/ControlDetailsView.xaml b/src/Avalonia.Diagnostics/Diagnostics/Views/ControlDetailsView.xaml
new file mode 100644
index 0000000000..29ce26fcdf
--- /dev/null
+++ b/src/Avalonia.Diagnostics/Diagnostics/Views/ControlDetailsView.xaml
@@ -0,0 +1,25 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/Avalonia.Diagnostics/Diagnostics/Views/ControlDetailsView.xaml.cs b/src/Avalonia.Diagnostics/Diagnostics/Views/ControlDetailsView.xaml.cs
new file mode 100644
index 0000000000..c6bd5a18aa
--- /dev/null
+++ b/src/Avalonia.Diagnostics/Diagnostics/Views/ControlDetailsView.xaml.cs
@@ -0,0 +1,18 @@
+using Avalonia.Controls;
+using Avalonia.Markup.Xaml;
+
+namespace Avalonia.Diagnostics.Views
+{
+ internal class ControlDetailsView : UserControl
+ {
+ public ControlDetailsView()
+ {
+ InitializeComponent();
+ }
+
+ private void InitializeComponent()
+ {
+ AvaloniaXamlLoader.Load(this);
+ }
+ }
+}
diff --git a/src/Avalonia.Diagnostics/Views/EventsView.xaml b/src/Avalonia.Diagnostics/Diagnostics/Views/EventsPageView.xaml
similarity index 92%
rename from src/Avalonia.Diagnostics/Views/EventsView.xaml
rename to src/Avalonia.Diagnostics/Diagnostics/Views/EventsPageView.xaml
index 406dd433a2..b7f0860e70 100644
--- a/src/Avalonia.Diagnostics/Views/EventsView.xaml
+++ b/src/Avalonia.Diagnostics/Diagnostics/Views/EventsPageView.xaml
@@ -1,9 +1,10 @@
+ xmlns:conv="clr-namespace:Avalonia.Diagnostics.Converters"
+ x:Class="Avalonia.Diagnostics.Views.EventsPageView">
-
+
("events");
diff --git a/src/Avalonia.Diagnostics/Diagnostics/Views/MainView.xaml b/src/Avalonia.Diagnostics/Diagnostics/Views/MainView.xaml
new file mode 100644
index 0000000000..663722acba
--- /dev/null
+++ b/src/Avalonia.Diagnostics/Diagnostics/Views/MainView.xaml
@@ -0,0 +1,55 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Hold Ctrl+Shift over a control to inspect.
+
+ Focused:
+
+
+ Pointer Over:
+
+
+
+
+
diff --git a/src/Avalonia.Diagnostics/Diagnostics/Views/MainView.xaml.cs b/src/Avalonia.Diagnostics/Diagnostics/Views/MainView.xaml.cs
new file mode 100644
index 0000000000..783709e54b
--- /dev/null
+++ b/src/Avalonia.Diagnostics/Diagnostics/Views/MainView.xaml.cs
@@ -0,0 +1,66 @@
+using Avalonia.Controls;
+using Avalonia.Diagnostics.ViewModels;
+using Avalonia.Input;
+using Avalonia.Interactivity;
+using Avalonia.Markup.Xaml;
+using Avalonia.Threading;
+
+namespace Avalonia.Diagnostics.Views
+{
+ internal class MainView : UserControl
+ {
+ private readonly ConsoleView _console;
+ private readonly GridSplitter _consoleSplitter;
+ private readonly Grid _rootGrid;
+ private readonly int _consoleRow;
+ private double _consoleHeight = -1;
+
+ public MainView()
+ {
+ InitializeComponent();
+ AddHandler(KeyDownEvent, PreviewKeyDown, RoutingStrategies.Tunnel);
+ _console = this.FindControl("console");
+ _consoleSplitter = this.FindControl("consoleSplitter");
+ _rootGrid = this.FindControl("rootGrid");
+ _consoleRow = Grid.GetRow(_console);
+ }
+
+ public void ToggleConsole()
+ {
+ var vm = (MainViewModel)DataContext;
+
+ if (_consoleHeight == -1)
+ {
+ _consoleHeight = Bounds.Height / 3;
+ }
+
+ vm.Console.ToggleVisibility();
+ _consoleSplitter.IsVisible = vm.Console.IsVisible;
+
+ if (vm.Console.IsVisible)
+ {
+ _rootGrid.RowDefinitions[_consoleRow].Height = new GridLength(_consoleHeight, GridUnitType.Pixel);
+ Dispatcher.UIThread.Post(() => _console.FocusInput(), DispatcherPriority.Background);
+ }
+ else
+ {
+ _consoleHeight = _rootGrid.RowDefinitions[_consoleRow].Height.Value;
+ _rootGrid.RowDefinitions[_consoleRow].Height = new GridLength(0, GridUnitType.Pixel);
+ }
+ }
+
+ private void InitializeComponent()
+ {
+ AvaloniaXamlLoader.Load(this);
+ }
+
+ private void PreviewKeyDown(object sender, KeyEventArgs e)
+ {
+ if (e.Key == Key.Escape)
+ {
+ ToggleConsole();
+ e.Handled = true;
+ }
+ }
+ }
+}
diff --git a/src/Avalonia.Diagnostics/Diagnostics/Views/MainWindow.xaml b/src/Avalonia.Diagnostics/Diagnostics/Views/MainWindow.xaml
new file mode 100644
index 0000000000..3623e95597
--- /dev/null
+++ b/src/Avalonia.Diagnostics/Diagnostics/Views/MainWindow.xaml
@@ -0,0 +1,18 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/Avalonia.Diagnostics/Diagnostics/Views/MainWindow.xaml.cs b/src/Avalonia.Diagnostics/Diagnostics/Views/MainWindow.xaml.cs
new file mode 100644
index 0000000000..3abdb5034a
--- /dev/null
+++ b/src/Avalonia.Diagnostics/Diagnostics/Views/MainWindow.xaml.cs
@@ -0,0 +1,74 @@
+using System;
+using System.Linq;
+using System.Reactive.Linq;
+using Avalonia.Controls;
+using Avalonia.Controls.Primitives;
+using Avalonia.Diagnostics.ViewModels;
+using Avalonia.Input;
+using Avalonia.Input.Raw;
+using Avalonia.Markup.Xaml;
+using Avalonia.Styling;
+using Avalonia.VisualTree;
+
+namespace Avalonia.Diagnostics.Views
+{
+ internal class MainWindow : Window, IStyleHost
+ {
+ private TopLevel _root;
+ private IDisposable _keySubscription;
+
+ public MainWindow()
+ {
+ InitializeComponent();
+
+ _keySubscription = InputManager.Instance.Process
+ .OfType()
+ .Subscribe(RawKeyDown);
+ }
+
+ public TopLevel Root
+ {
+ get => _root;
+ set
+ {
+ if (_root != value)
+ {
+ _root = value;
+ DataContext = new MainViewModel(value);
+ }
+ }
+ }
+
+ IStyleHost IStyleHost.StylingParent => null;
+
+ protected override void OnClosed(EventArgs e)
+ {
+ base.OnClosed(e);
+ _keySubscription.Dispose();
+ ((MainViewModel)DataContext)?.Dispose();
+ }
+
+ private void InitializeComponent()
+ {
+ AvaloniaXamlLoader.Load(this);
+ }
+
+ private void RawKeyDown(RawKeyEventArgs e)
+ {
+ const RawInputModifiers modifiers = RawInputModifiers.Control | RawInputModifiers.Shift;
+
+ if (e.Modifiers == modifiers)
+ {
+ var point = (Root as IInputRoot)?.MouseDevice?.GetPosition(Root) ?? default;
+ var control = Root.GetVisualsAt(point, x => (!(x is AdornerLayer) && x.IsVisible))
+ .FirstOrDefault();
+
+ if (control != null)
+ {
+ var vm = (MainViewModel)DataContext;
+ vm.SelectControl((IControl)control);
+ }
+ }
+ }
+ }
+}
diff --git a/src/Avalonia.Diagnostics/Views/TreePageView.xaml b/src/Avalonia.Diagnostics/Diagnostics/Views/TreePageView.xaml
similarity index 60%
rename from src/Avalonia.Diagnostics/Views/TreePageView.xaml
rename to src/Avalonia.Diagnostics/Diagnostics/Views/TreePageView.xaml
index 2619fd744a..a1e6ca7d37 100644
--- a/src/Avalonia.Diagnostics/Views/TreePageView.xaml
+++ b/src/Avalonia.Diagnostics/Diagnostics/Views/TreePageView.xaml
@@ -1,26 +1,29 @@
-
-
+
+
-
-
+
+
-
-
+
+
diff --git a/src/Avalonia.Diagnostics/Views/TreePageView.xaml.cs b/src/Avalonia.Diagnostics/Diagnostics/Views/TreePageView.xaml.cs
similarity index 90%
rename from src/Avalonia.Diagnostics/Views/TreePageView.xaml.cs
rename to src/Avalonia.Diagnostics/Diagnostics/Views/TreePageView.xaml.cs
index 1326f718de..633d18ddd8 100644
--- a/src/Avalonia.Diagnostics/Views/TreePageView.xaml.cs
+++ b/src/Avalonia.Diagnostics/Diagnostics/Views/TreePageView.xaml.cs
@@ -1,6 +1,3 @@
-// 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 Avalonia.Controls;
using Avalonia.Controls.Generators;
using Avalonia.Controls.Primitives;
@@ -12,14 +9,14 @@ using Avalonia.Media;
namespace Avalonia.Diagnostics.Views
{
- public class TreePageView : UserControl
+ internal class TreePageView : UserControl
{
private Control _adorner;
private TreeView _tree;
public TreePageView()
{
- InitializeComponent();
+ this.InitializeComponent();
_tree.ItemContainerGenerator.Index.Materialized += TreeViewItemMaterialized;
}
@@ -39,7 +36,7 @@ namespace Avalonia.Diagnostics.Views
_adorner = new Rectangle
{
Fill = new SolidColorBrush(0x80a0c5e8),
- [AdornerLayer.AdornedElementProperty] = node.Visual
+ [AdornerLayer.AdornedElementProperty] = node.Visual,
};
layer.Children.Add(_adorner);
diff --git a/src/Avalonia.Diagnostics/VisualTreeDebug.cs b/src/Avalonia.Diagnostics/Diagnostics/VisualTreeDebug.cs
similarity index 100%
rename from src/Avalonia.Diagnostics/VisualTreeDebug.cs
rename to src/Avalonia.Diagnostics/Diagnostics/VisualTreeDebug.cs
diff --git a/src/Avalonia.Diagnostics/ViewModels/ControlDetailsViewModel.cs b/src/Avalonia.Diagnostics/ViewModels/ControlDetailsViewModel.cs
deleted file mode 100644
index 4b832f7ce6..0000000000
--- a/src/Avalonia.Diagnostics/ViewModels/ControlDetailsViewModel.cs
+++ /dev/null
@@ -1,25 +0,0 @@
-// Copyright (c) The Avalonia Project. All rights reserved.
-// Licensed under the MIT license. See licence.md file in the project root for full license information.
-
-using System.Collections.Generic;
-using System.Linq;
-using Avalonia.VisualTree;
-
-namespace Avalonia.Diagnostics.ViewModels
-{
- internal class ControlDetailsViewModel : ViewModelBase
- {
- public ControlDetailsViewModel(IVisual control)
- {
- if (control is AvaloniaObject avaloniaObject)
- {
- Properties = AvaloniaPropertyRegistry.Instance.GetRegistered(avaloniaObject)
- .Select(x => new PropertyDetails(avaloniaObject, x))
- .OrderBy(x => x.IsAttached)
- .ThenBy(x => x.Name);
- }
- }
-
- public IEnumerable Properties { get; }
- }
-}
diff --git a/src/Avalonia.Diagnostics/ViewModels/DevToolsViewModel.cs b/src/Avalonia.Diagnostics/ViewModels/DevToolsViewModel.cs
deleted file mode 100644
index 9f524a21eb..0000000000
--- a/src/Avalonia.Diagnostics/ViewModels/DevToolsViewModel.cs
+++ /dev/null
@@ -1,76 +0,0 @@
-// Copyright (c) The Avalonia Project. All rights reserved.
-// Licensed under the MIT license. See licence.md file in the project root for full license information.
-
-using System;
-using System.Collections.ObjectModel;
-using System.Linq;
-using Avalonia.Controls;
-using Avalonia.Input;
-
-namespace Avalonia.Diagnostics.ViewModels
-{
- internal class DevToolsViewModel : ViewModelBase
- {
- private IDevToolViewModel _selectedTool;
- private string _focusedControl;
- private string _pointerOverElement;
-
- public DevToolsViewModel(IControl root)
- {
- Tools = new ObservableCollection
- {
- new TreePageViewModel(LogicalTreeNode.Create(root), "Logical Tree"),
- new TreePageViewModel(VisualTreeNode.Create(root), "Visual Tree"),
- new EventsViewModel(root)
- };
-
- SelectedTool = Tools.First();
-
- UpdateFocusedControl();
-
- KeyboardDevice.Instance.PropertyChanged += (s, e) =>
- {
- if (e.PropertyName == nameof(KeyboardDevice.Instance.FocusedElement))
- {
- UpdateFocusedControl();
- }
- };
-
- root.GetObservable(TopLevel.PointerOverElementProperty)
- .Subscribe(x => PointerOverElement = x?.GetType().Name);
- }
-
- public IDevToolViewModel SelectedTool
- {
- get => _selectedTool;
- set => RaiseAndSetIfChanged(ref _selectedTool, value);
- }
-
- public ObservableCollection Tools { get; }
-
- public string FocusedControl
- {
- get => _focusedControl;
- private set => RaiseAndSetIfChanged(ref _focusedControl, value);
- }
-
- public string PointerOverElement
- {
- get => _pointerOverElement;
- private set => RaiseAndSetIfChanged(ref _pointerOverElement, value);
- }
-
- public void SelectControl(IControl control)
- {
- if (SelectedTool is TreePageViewModel tree)
- {
- tree.SelectControl(control);
- }
- }
-
- private void UpdateFocusedControl()
- {
- FocusedControl = KeyboardDevice.Instance.FocusedElement?.GetType().Name;
- }
- }
-}
diff --git a/src/Avalonia.Diagnostics/ViewModels/IDevToolViewModel.cs b/src/Avalonia.Diagnostics/ViewModels/IDevToolViewModel.cs
deleted file mode 100644
index 0434230a63..0000000000
--- a/src/Avalonia.Diagnostics/ViewModels/IDevToolViewModel.cs
+++ /dev/null
@@ -1,16 +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.Diagnostics.ViewModels
-{
- ///
- /// View model interface for tool showing up in DevTools
- ///
- public interface IDevToolViewModel
- {
- ///
- /// Name of a tool.
- ///
- string Name { get; }
- }
-}
diff --git a/src/Avalonia.Diagnostics/ViewModels/PropertyDetails.cs b/src/Avalonia.Diagnostics/ViewModels/PropertyDetails.cs
deleted file mode 100644
index 523be406c8..0000000000
--- a/src/Avalonia.Diagnostics/ViewModels/PropertyDetails.cs
+++ /dev/null
@@ -1,58 +0,0 @@
-// Copyright (c) The Avalonia Project. All rights reserved.
-// Licensed under the MIT license. See licence.md file in the project root for full license information.
-
-using System;
-using Avalonia.Data;
-
-namespace Avalonia.Diagnostics.ViewModels
-{
- internal class PropertyDetails : ViewModelBase
- {
- private object _value;
- private string _priority;
- private string _diagnostic;
-
- public PropertyDetails(AvaloniaObject o, AvaloniaProperty property)
- {
- Name = property.IsAttached ?
- $"[{property.OwnerType.Name}.{property.Name}]" :
- property.Name;
- IsAttached = property.IsAttached;
-
- // TODO: Unsubscribe when view model is deactivated.
- o.GetObservable(property).Subscribe(x =>
- {
- var diagnostic = o.GetDiagnostic(property);
- Value = diagnostic.Value ?? "(null)";
- Priority = (diagnostic.Priority != BindingPriority.Unset) ?
- diagnostic.Priority.ToString() :
- diagnostic.Property.Inherits ?
- "Inherited" :
- "Unset";
- Diagnostic = diagnostic.Diagnostic;
- });
- }
-
- public string Name { get; }
-
- public bool IsAttached { get; }
-
- public string Priority
- {
- get => _priority;
- private set => RaiseAndSetIfChanged(ref _priority, value);
- }
-
- public string Diagnostic
- {
- get => _diagnostic;
- private set => RaiseAndSetIfChanged(ref _diagnostic, value);
- }
-
- public object Value
- {
- get => _value;
- private set => RaiseAndSetIfChanged(ref _value, value);
- }
- }
-}
diff --git a/src/Avalonia.Diagnostics/Views/ControlDetailsView.cs b/src/Avalonia.Diagnostics/Views/ControlDetailsView.cs
deleted file mode 100644
index fb867ab55e..0000000000
--- a/src/Avalonia.Diagnostics/Views/ControlDetailsView.cs
+++ /dev/null
@@ -1,75 +0,0 @@
-// Copyright (c) The Avalonia Project. All rights reserved.
-// Licensed under the MIT license. See licence.md file in the project root for full license information.
-
-using System;
-using System.Collections.Generic;
-using System.Reactive.Linq;
-using Avalonia.Controls;
-using Avalonia.Diagnostics.ViewModels;
-using Avalonia.Media;
-
-namespace Avalonia.Diagnostics.Views
-{
- internal class ControlDetailsView : UserControl
- {
- private static readonly StyledProperty ViewModelProperty =
- AvaloniaProperty.Register(nameof(ViewModel));
-
- private SimpleGrid _grid;
-
- public ControlDetailsView()
- {
- InitializeComponent();
- this.GetObservable(DataContextProperty)
- .Subscribe(x => ViewModel = (ControlDetailsViewModel)x);
- }
-
- public ControlDetailsViewModel ViewModel
- {
- get => GetValue(ViewModelProperty);
- private set
- {
- SetValue(ViewModelProperty, value);
- _grid[GridRepeater.ItemsProperty] = value?.Properties;
- }
- }
-
- private void InitializeComponent()
- {
- Func
-
+
\ No newline at end of file
diff --git a/src/Avalonia.Dialogs/Avalonia.Dialogs.csproj b/src/Avalonia.Dialogs/Avalonia.Dialogs.csproj
index cb86e271a6..f54ddf32be 100644
--- a/src/Avalonia.Dialogs/Avalonia.Dialogs.csproj
+++ b/src/Avalonia.Dialogs/Avalonia.Dialogs.csproj
@@ -13,7 +13,6 @@
-
diff --git a/src/Avalonia.Input/GestureRecognizers/GestureRecognizerCollection.cs b/src/Avalonia.Input/GestureRecognizers/GestureRecognizerCollection.cs
index 91b224e65a..112abb1a4e 100644
--- a/src/Avalonia.Input/GestureRecognizers/GestureRecognizerCollection.cs
+++ b/src/Avalonia.Input/GestureRecognizers/GestureRecognizerCollection.cs
@@ -111,9 +111,7 @@ namespace Avalonia.Input.GestureRecognizers
_pointerGrabs.Remove(e.Pointer);
foreach (var r in _recognizers)
{
- if(e.Handled)
- break;
- r.PointerCaptureLost(e);
+ r.PointerCaptureLost(e.Pointer);
}
}
@@ -121,6 +119,11 @@ namespace Avalonia.Input.GestureRecognizers
{
pointer.Capture(_inputElement);
_pointerGrabs[pointer] = recognizer;
+ foreach (var r in _recognizers)
+ {
+ if (r != recognizer)
+ r.PointerCaptureLost(pointer);
+ }
}
}
diff --git a/src/Avalonia.Input/GestureRecognizers/IGestureRecognizer.cs b/src/Avalonia.Input/GestureRecognizers/IGestureRecognizer.cs
index b8ba9e529c..c1d9ae5304 100644
--- a/src/Avalonia.Input/GestureRecognizers/IGestureRecognizer.cs
+++ b/src/Avalonia.Input/GestureRecognizers/IGestureRecognizer.cs
@@ -6,7 +6,7 @@ namespace Avalonia.Input.GestureRecognizers
void PointerPressed(PointerPressedEventArgs e);
void PointerReleased(PointerReleasedEventArgs e);
void PointerMoved(PointerEventArgs e);
- void PointerCaptureLost(PointerCaptureLostEventArgs e);
+ void PointerCaptureLost(IPointer pointer);
}
public interface IGestureRecognizerActionsDispatcher
diff --git a/src/Avalonia.Input/GestureRecognizers/ScrollGestureRecognizer.cs b/src/Avalonia.Input/GestureRecognizers/ScrollGestureRecognizer.cs
index 4f3c7c0bba..e022401c8e 100644
--- a/src/Avalonia.Input/GestureRecognizers/ScrollGestureRecognizer.cs
+++ b/src/Avalonia.Input/GestureRecognizers/ScrollGestureRecognizer.cs
@@ -116,9 +116,9 @@ namespace Avalonia.Input.GestureRecognizers
}
}
- public void PointerCaptureLost(PointerCaptureLostEventArgs e)
+ public void PointerCaptureLost(IPointer pointer)
{
- if (e.Pointer == _tracking) EndGesture();
+ if (pointer == _tracking) EndGesture();
}
void EndGesture()
@@ -148,6 +148,7 @@ namespace Avalonia.Input.GestureRecognizers
EndGesture();
else
{
+ _tracking = null;
var savedGestureId = _gestureId;
var st = Stopwatch.StartNew();
var lastTime = TimeSpan.Zero;
diff --git a/src/Avalonia.Input/IInputElement.cs b/src/Avalonia.Input/IInputElement.cs
index 9247fb48a9..aa5e0dd8ec 100644
--- a/src/Avalonia.Input/IInputElement.cs
+++ b/src/Avalonia.Input/IInputElement.cs
@@ -63,7 +63,7 @@ namespace Avalonia.Input
event EventHandler PointerReleased;
///
- /// Occurs when the mouse wheen is scrolled over the control.
+ /// Occurs when the mouse wheel is scrolled over the control.
///
event EventHandler PointerWheelChanged;
diff --git a/src/Avalonia.Input/InputElement.cs b/src/Avalonia.Input/InputElement.cs
index 1e2a621bd1..121412f2e4 100644
--- a/src/Avalonia.Input/InputElement.cs
+++ b/src/Avalonia.Input/InputElement.cs
@@ -4,6 +4,8 @@
using System;
using System.Collections.Generic;
using System.Linq;
+using Avalonia.Controls;
+using Avalonia.Data;
using Avalonia.Input.GestureRecognizers;
using Avalonia.Interactivity;
using Avalonia.VisualTree;
@@ -181,10 +183,11 @@ namespace Avalonia.Input
PointerReleasedEvent.AddClassHandler((x, e) => x.OnPointerReleased(e));
PointerCaptureLostEvent.AddClassHandler((x, e) => x.OnPointerCaptureLost(e));
PointerWheelChangedEvent.AddClassHandler((x, e) => x.OnPointerWheelChanged(e));
+ }
- PseudoClass(IsEffectivelyEnabledProperty, x => !x, ":disabled");
- PseudoClass(IsFocusedProperty, ":focus");
- PseudoClass(IsPointerOverProperty, ":pointerover");
+ public InputElement()
+ {
+ UpdatePseudoClasses(IsFocused, IsPointerOver);
}
///
@@ -372,7 +375,11 @@ namespace Avalonia.Input
public bool IsEffectivelyEnabled
{
get => _isEffectivelyEnabled;
- private set => SetAndRaise(IsEffectivelyEnabledProperty, ref _isEffectivelyEnabled, value);
+ private set
+ {
+ SetAndRaise(IsEffectivelyEnabledProperty, ref _isEffectivelyEnabled, value);
+ PseudoClasses.Set(":disabled", !value);
+ }
}
public List KeyBindings { get; } = new List();
@@ -522,6 +529,20 @@ namespace Avalonia.Input
{
}
+ protected override void OnPropertyChanged(AvaloniaProperty property, Optional oldValue, BindingValue newValue, BindingPriority priority)
+ {
+ base.OnPropertyChanged(property, oldValue, newValue, priority);
+
+ if (property == IsFocusedProperty)
+ {
+ UpdatePseudoClasses(newValue.GetValueOrDefault(), null);
+ }
+ else if (property == IsPointerOverProperty)
+ {
+ UpdatePseudoClasses(null, newValue.GetValueOrDefault());
+ }
+ }
+
///
/// Updates the property value according to the parent
/// control's enabled state and .
@@ -578,5 +599,18 @@ namespace Avalonia.Input
child?.UpdateIsEffectivelyEnabled(this);
}
}
+
+ private void UpdatePseudoClasses(bool? isFocused, bool? isPointerOver)
+ {
+ if (isFocused.HasValue)
+ {
+ PseudoClasses.Set(":focus", isFocused.Value);
+ }
+
+ if (isPointerOver.HasValue)
+ {
+ PseudoClasses.Set(":pointerover", isPointerOver.Value);
+ }
+ }
}
}
diff --git a/src/Avalonia.Input/NavigationDirection.cs b/src/Avalonia.Input/NavigationDirection.cs
index 406890b767..0e7e991260 100644
--- a/src/Avalonia.Input/NavigationDirection.cs
+++ b/src/Avalonia.Input/NavigationDirection.cs
@@ -100,12 +100,12 @@ namespace Avalonia.Input
///
public static NavigationDirection? ToNavigationDirection(
this Key key,
- InputModifiers modifiers = InputModifiers.None)
+ KeyModifiers modifiers = KeyModifiers.None)
{
switch (key)
{
case Key.Tab:
- return (modifiers & InputModifiers.Shift) != 0 ?
+ return (modifiers & KeyModifiers.Shift) != 0 ?
NavigationDirection.Next : NavigationDirection.Previous;
case Key.Up:
return NavigationDirection.Up;
diff --git a/src/Avalonia.Layout/LayoutManager.cs b/src/Avalonia.Layout/LayoutManager.cs
index 1792655a13..69a4406828 100644
--- a/src/Avalonia.Layout/LayoutManager.cs
+++ b/src/Avalonia.Layout/LayoutManager.cs
@@ -132,8 +132,16 @@ namespace Avalonia.Layout
///
public void ExecuteInitialLayoutPass(ILayoutRoot root)
{
- Measure(root);
- Arrange(root);
+ try
+ {
+ _running = true;
+ Measure(root);
+ Arrange(root);
+ }
+ finally
+ {
+ _running = false;
+ }
// Running the initial layout pass may have caused some control to be invalidated
// so run a full layout pass now (this usually due to scrollbars; its not known
diff --git a/src/Avalonia.Native/AvaloniaNativeDeferredRendererLock.cs b/src/Avalonia.Native/AvaloniaNativeDeferredRendererLock.cs
deleted file mode 100644
index ce2b03e355..0000000000
--- a/src/Avalonia.Native/AvaloniaNativeDeferredRendererLock.cs
+++ /dev/null
@@ -1,42 +0,0 @@
-// Copyright (c) The Avalonia Project. All rights reserved.
-// Licensed under the MIT license. See licence.md file in the project root for full license information.
-
-using System;
-using System.Threading;
-using Avalonia.Native.Interop;
-using Avalonia.Rendering;
-
-namespace Avalonia.Native
-{
- public class AvaloniaNativeDeferredRendererLock : IDeferredRendererLock
- {
- private readonly IAvnWindowBase _window;
-
- public AvaloniaNativeDeferredRendererLock(IAvnWindowBase window)
- {
- _window = window;
- }
-
- public IDisposable TryLock()
- {
- if (_window.TryLock())
- return new UnlockDisposable(_window);
- return null;
- }
-
- private sealed class UnlockDisposable : IDisposable
- {
- private IAvnWindowBase _window;
-
- public UnlockDisposable(IAvnWindowBase window)
- {
- _window = window;
- }
-
- public void Dispose()
- {
- Interlocked.Exchange(ref _window, null)?.Unlock();
- }
- }
- }
-}
diff --git a/src/Avalonia.Native/AvaloniaNativePlatform.cs b/src/Avalonia.Native/AvaloniaNativePlatform.cs
index 756619fa9f..7fc067ee4e 100644
--- a/src/Avalonia.Native/AvaloniaNativePlatform.cs
+++ b/src/Avalonia.Native/AvaloniaNativePlatform.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.InteropServices;
+using System.Security.Cryptography;
using Avalonia.Controls.Platform;
using Avalonia.Input;
using Avalonia.Input.Platform;
@@ -17,6 +18,7 @@ namespace Avalonia.Native
{
private readonly IAvaloniaNativeFactory _factory;
private AvaloniaNativePlatformOptions _options;
+ private GlPlatformFeature _glFeature;
[DllImport("libAvaloniaNative")]
static extern IntPtr CreateAvaloniaNative();
@@ -87,7 +89,7 @@ namespace Avalonia.Native
_factory.MacOptions.ShowInDock = macOpts?.ShowInDock != false ? 1 : 0;
}
-
+
AvaloniaLocator.CurrentMutable
.Bind()
.ToConstant(new PlatformThreadingInterface(_factory.CreatePlatformThreadingInterface()))
@@ -100,14 +102,16 @@ namespace Avalonia.Native
.Bind().ToConstant(new RenderLoop())
.Bind().ToConstant(new DefaultRenderTimer(60))
.Bind().ToConstant(new SystemDialogs(_factory.CreateSystemDialogs()))
- .Bind().ToConstant(new GlPlatformFeature(_factory.ObtainGlFeature()))
.Bind().ToConstant(new PlatformHotkeyConfiguration(KeyModifiers.Meta))
.Bind().ToConstant(new MacOSMountedVolumeInfoProvider());
+ if (_options.UseGpu)
+ AvaloniaLocator.CurrentMutable.Bind()
+ .ToConstant(_glFeature = new GlPlatformFeature(_factory.ObtainGlDisplay()));
}
public IWindowImpl CreateWindow()
{
- return new WindowImpl(_factory, _options);
+ return new WindowImpl(_factory, _options, _glFeature);
}
public IEmbeddableWindowImpl CreateEmbeddableWindow()
diff --git a/src/Avalonia.Native/DeferredFramebuffer.cs b/src/Avalonia.Native/DeferredFramebuffer.cs
index adc721de42..0c32bce145 100644
--- a/src/Avalonia.Native/DeferredFramebuffer.cs
+++ b/src/Avalonia.Native/DeferredFramebuffer.cs
@@ -21,7 +21,7 @@ namespace Avalonia.Native
Size = new PixelSize(width, height);
RowBytes = width * 4;
Dpi = dpi;
- Format = PixelFormat.Rgba8888;
+ Format = PixelFormat.Bgra8888;
}
public IntPtr Address { get; set; }
diff --git a/src/Avalonia.Native/GlPlatformFeature.cs b/src/Avalonia.Native/GlPlatformFeature.cs
index e39680bdee..8e3448795c 100644
--- a/src/Avalonia.Native/GlPlatformFeature.cs
+++ b/src/Avalonia.Native/GlPlatformFeature.cs
@@ -8,24 +8,31 @@ namespace Avalonia.Native
{
class GlPlatformFeature : IWindowingPlatformGlFeature
{
-
- public GlPlatformFeature(IAvnGlFeature feature)
+ public GlPlatformFeature(IAvnGlDisplay display)
{
- Display = new GlDisplay(feature.ObtainDisplay());
- ImmediateContext = new GlContext(Display, feature.ObtainImmediateContext());
+ var immediate = display.CreateContext(null);
+ var deferred = display.CreateContext(immediate);
+ GlDisplay = new GlDisplay(display, immediate.SampleCount, immediate.StencilSize);
+
+ ImmediateContext = new GlContext(Display, immediate);
+ DeferredContext = new GlContext(Display, deferred);
}
public IGlContext ImmediateContext { get; }
- public GlDisplay Display { get; }
+ internal GlContext DeferredContext { get; }
+ internal GlDisplay GlDisplay;
+ public GlDisplay Display => GlDisplay;
}
class GlDisplay : IGlDisplay
{
private readonly IAvnGlDisplay _display;
- public GlDisplay(IAvnGlDisplay display)
+ public GlDisplay(IAvnGlDisplay display, int sampleCount, int stencilSize)
{
_display = display;
+ SampleCount = sampleCount;
+ StencilSize = stencilSize;
GlInterface = new GlInterface((name, optional) =>
{
var rv = _display.GetProcAddress(name);
@@ -39,11 +46,11 @@ namespace Avalonia.Native
public GlInterface GlInterface { get; }
- public int SampleCount => _display.GetSampleCount();
+ public int SampleCount { get; }
- public int StencilSize => _display.GetStencilSize();
+ public int StencilSize { get; }
- public void ClearContext() => _display.ClearContext();
+ public void ClearContext() => _display.LegacyClearCurrentContext();
}
class GlContext : IGlContext
@@ -60,7 +67,7 @@ namespace Avalonia.Native
public void MakeCurrent()
{
- Context.MakeCurrent();
+ Context.LegacyMakeCurrent();
}
}
@@ -109,6 +116,9 @@ namespace Avalonia.Native
public double Scaling => _session.GetScaling();
+
+ public bool IsYFlipped => true;
+
public void Dispose()
{
_session?.Dispose();
@@ -128,5 +138,6 @@ namespace Avalonia.Native
{
return new GlPlatformSurfaceRenderTarget(_window.CreateGlRenderTarget());
}
+
}
}
diff --git a/src/Avalonia.Native/PopupImpl.cs b/src/Avalonia.Native/PopupImpl.cs
index d7fa1052ff..9423b2aa2a 100644
--- a/src/Avalonia.Native/PopupImpl.cs
+++ b/src/Avalonia.Native/PopupImpl.cs
@@ -8,19 +8,23 @@ using Avalonia.Platform;
namespace Avalonia.Native
{
- public class PopupImpl : WindowBaseImpl, IPopupImpl
+ class PopupImpl : WindowBaseImpl, IPopupImpl
{
private readonly IAvaloniaNativeFactory _factory;
private readonly AvaloniaNativePlatformOptions _opts;
+ private readonly GlPlatformFeature _glFeature;
+
public PopupImpl(IAvaloniaNativeFactory factory,
AvaloniaNativePlatformOptions opts,
- IWindowBaseImpl parent) : base(opts)
+ GlPlatformFeature glFeature,
+ IWindowBaseImpl parent) : base(opts, glFeature)
{
_factory = factory;
_opts = opts;
+ _glFeature = glFeature;
using (var e = new PopupEvents(this))
{
- Init(factory.CreatePopup(e), factory.CreateScreens());
+ Init(factory.CreatePopup(e, _opts.UseGpu ? glFeature?.DeferredContext.Context : null), factory.CreateScreens());
}
PopupPositioner = new ManagedPopupPositioner(new OsxManagedPopupPositionerPopupImplHelper(parent, MoveResize));
}
@@ -51,7 +55,7 @@ namespace Avalonia.Native
}
}
- public override IPopupImpl CreatePopup() => new PopupImpl(_factory, _opts, this);
+ public override IPopupImpl CreatePopup() => new PopupImpl(_factory, _opts, _glFeature, this);
public IPopupPositioner PopupPositioner { get; }
}
}
diff --git a/src/Avalonia.Native/WindowImpl.cs b/src/Avalonia.Native/WindowImpl.cs
index a7828bedaf..c757576017 100644
--- a/src/Avalonia.Native/WindowImpl.cs
+++ b/src/Avalonia.Native/WindowImpl.cs
@@ -14,14 +14,18 @@ namespace Avalonia.Native
{
private readonly IAvaloniaNativeFactory _factory;
private readonly AvaloniaNativePlatformOptions _opts;
+ private readonly GlPlatformFeature _glFeature;
IAvnWindow _native;
- public WindowImpl(IAvaloniaNativeFactory factory, AvaloniaNativePlatformOptions opts) : base(opts)
+ internal WindowImpl(IAvaloniaNativeFactory factory, AvaloniaNativePlatformOptions opts,
+ GlPlatformFeature glFeature) : base(opts, glFeature)
{
_factory = factory;
_opts = opts;
+ _glFeature = glFeature;
using (var e = new WindowEvents(this))
{
- Init(_native = factory.CreateWindow(e), factory.CreateScreens());
+ Init(_native = factory.CreateWindow(e,
+ _opts.UseGpu ? glFeature?.DeferredContext.Context : null), factory.CreateScreens());
}
NativeMenuExporter = new AvaloniaNativeMenuExporter(_native, factory);
@@ -113,6 +117,6 @@ namespace Avalonia.Native
public void Move(PixelPoint point) => Position = point;
public override IPopupImpl CreatePopup() =>
- _opts.OverlayPopups ? null : new PopupImpl(_factory, _opts, this);
+ _opts.OverlayPopups ? null : new PopupImpl(_factory, _opts, _glFeature, this);
}
}
diff --git a/src/Avalonia.Native/WindowImplBase.cs b/src/Avalonia.Native/WindowImplBase.cs
index 5d701dc8df..331f083e8b 100644
--- a/src/Avalonia.Native/WindowImplBase.cs
+++ b/src/Avalonia.Native/WindowImplBase.cs
@@ -60,9 +60,9 @@ namespace Avalonia.Native
private double _savedScaling;
private GlPlatformSurface _glSurface;
- public WindowBaseImpl(AvaloniaNativePlatformOptions opts)
+ internal WindowBaseImpl(AvaloniaNativePlatformOptions opts, GlPlatformFeature glFeature)
{
- _gpu = opts.UseGpu;
+ _gpu = opts.UseGpu && glFeature != null;
_deferredRendering = opts.UseDeferredRendering;
_keyboard = AvaloniaLocator.Current.GetService();
@@ -75,8 +75,8 @@ namespace Avalonia.Native
_native = window;
Handle = new MacOSTopLevelWindowHandle(window);
-
- _glSurface = new GlPlatformSurface(window);
+ if (_gpu)
+ _glSurface = new GlPlatformSurface(window);
Screen = new ScreenImpl(screens);
_savedLogicalSize = ClientSize;
_savedScaling = Scaling;
@@ -103,25 +103,20 @@ namespace Avalonia.Native
public ILockedFramebuffer Lock()
{
- if(_deferredRendering)
+ var w = _savedLogicalSize.Width * _savedScaling;
+ var h = _savedLogicalSize.Height * _savedScaling;
+ var dpi = _savedScaling * 96;
+ return new DeferredFramebuffer(cb =>
{
- var w = _savedLogicalSize.Width * _savedScaling;
- var h = _savedLogicalSize.Height * _savedScaling;
- var dpi = _savedScaling * 96;
- return new DeferredFramebuffer(cb =>
+ lock (_syncRoot)
{
- lock (_syncRoot)
- {
- if (_native == null)
- return false;
- cb(_native);
- _lastRenderedLogicalSize = _savedLogicalSize;
- return true;
- }
- }, (int)w, (int)h, new Vector(dpi, dpi));
- }
-
- return new FramebufferWrapper(_native.GetSoftwareFramebuffer());
+ if (_native == null)
+ return false;
+ cb(_native);
+ _lastRenderedLogicalSize = _savedLogicalSize;
+ return true;
+ }
+ }, (int)w, (int)h, new Vector(dpi, dpi));
}
public Action Paint { get; set; }
@@ -130,28 +125,6 @@ namespace Avalonia.Native
public IMouseDevice MouseDevice => _mouse;
public abstract IPopupImpl CreatePopup();
-
- class FramebufferWrapper : ILockedFramebuffer
- {
- public FramebufferWrapper(AvnFramebuffer fb)
- {
- Address = fb.Data;
- Size = new PixelSize(fb.Width, fb.Height);
- RowBytes = fb.Stride;
- Dpi = new Vector(fb.Dpi.X, fb.Dpi.Y);
- Format = (PixelFormat)fb.PixelFormat;
- }
- public IntPtr Address { get; set; }
- public PixelSize Size { get; set; }
- public int RowBytes {get;set;}
- public Vector Dpi { get; set; }
- public PixelFormat Format { get; }
- public void Dispose()
- {
- // Do nothing
- }
- }
-
protected class WindowBaseEvents : CallbackBase, IAvnWindowBaseEvents
{
private readonly WindowBaseImpl _parent;
@@ -189,9 +162,12 @@ namespace Avalonia.Native
void IAvnWindowBaseEvents.Resized(AvnSize size)
{
- var s = new Size(size.Width, size.Height);
- _parent._savedLogicalSize = s;
- _parent.Resized?.Invoke(s);
+ if (_parent._native != null)
+ {
+ var s = new Size(size.Width, size.Height);
+ _parent._savedLogicalSize = s;
+ _parent.Resized?.Invoke(s);
+ }
}
void IAvnWindowBaseEvents.PositionChanged(AvnPoint position)
@@ -278,9 +254,7 @@ namespace Avalonia.Native
public IRenderer CreateRenderer(IRenderRoot root)
{
if (_deferredRendering)
- return new DeferredRenderer(root, AvaloniaLocator.Current.GetService(),
- rendererLock:
- _gpu ? new AvaloniaNativeDeferredRendererLock(_native) : null);
+ return new DeferredRenderer(root, AvaloniaLocator.Current.GetService());
return new ImmediateRenderer(root);
}
@@ -346,7 +320,7 @@ namespace Avalonia.Native
_native.SetTopMost(value);
}
- public double Scaling => _native.GetScaling();
+ public double Scaling => _native?.GetScaling() ?? 1;
public Action Deactivated { get; set; }
public Action Activated { get; set; }
diff --git a/src/Avalonia.OpenGL/EglGlPlatformSurface.cs b/src/Avalonia.OpenGL/EglGlPlatformSurface.cs
index d2e4543af3..a4666bbcbf 100644
--- a/src/Avalonia.OpenGL/EglGlPlatformSurface.cs
+++ b/src/Avalonia.OpenGL/EglGlPlatformSurface.cs
@@ -107,6 +107,7 @@ namespace Avalonia.OpenGL
public IGlDisplay Display => _context.Display;
public PixelSize Size => _info.Size;
public double Scaling => _info.Scaling;
+ public bool IsYFlipped { get; }
}
}
}
diff --git a/src/Avalonia.OpenGL/IGlPlatformSurfaceRenderingSession.cs b/src/Avalonia.OpenGL/IGlPlatformSurfaceRenderingSession.cs
index 4b0de05b77..f56f47095e 100644
--- a/src/Avalonia.OpenGL/IGlPlatformSurfaceRenderingSession.cs
+++ b/src/Avalonia.OpenGL/IGlPlatformSurfaceRenderingSession.cs
@@ -7,5 +7,6 @@ namespace Avalonia.OpenGL
IGlDisplay Display { get; }
PixelSize Size { get; }
double Scaling { get; }
+ bool IsYFlipped { get; }
}
}
diff --git a/src/Avalonia.Styling/Controls/PseudoClassesExtensions.cs b/src/Avalonia.Styling/Controls/PseudoClassesExtensions.cs
new file mode 100644
index 0000000000..7b28d1a911
--- /dev/null
+++ b/src/Avalonia.Styling/Controls/PseudoClassesExtensions.cs
@@ -0,0 +1,28 @@
+using System;
+
+namespace Avalonia.Controls
+{
+ public static class PseudolassesExtensions
+ {
+ ///
+ /// Adds or removes a pseudoclass depending on a boolean value.
+ ///
+ /// The pseudoclasses collection.
+ /// The name of the pseudoclass to set.
+ /// True to add the pseudoclass or false to remove.
+ public static void Set(this IPseudoClasses classes, string name, bool value)
+ {
+ Contract.Requires(classes != null);
+
+ if (value)
+ {
+ classes.Add(name);
+ }
+ else
+ {
+ classes.Remove(name);
+ }
+ }
+
+ }
+}
diff --git a/src/Avalonia.Styling/StyledElement.cs b/src/Avalonia.Styling/StyledElement.cs
index 13cfa905d4..120a53c664 100644
--- a/src/Avalonia.Styling/StyledElement.cs
+++ b/src/Avalonia.Styling/StyledElement.cs
@@ -488,83 +488,6 @@ namespace Avalonia
InheritanceParent = parent;
}
- ///
- /// Adds a pseudo-class to be set when a property is true.
- ///
- /// The property.
- /// The pseudo-class.
- [Obsolete("Use PseudoClass and specify the control type.")]
- protected static void PseudoClass(AvaloniaProperty property, string className)
- {
- PseudoClass(property, className);
- }
-
- ///
- /// Adds a pseudo-class to be set when a property is true.
- ///
- /// The type to apply the pseudo-class to.
- /// The property.
- /// The pseudo-class.
- protected static void PseudoClass(AvaloniaProperty property, string className)
- where TOwner : class, IStyledElement
- {
- PseudoClass(property, x => x, className);
- }
-
- ///
- /// Adds a pseudo-class to be set when a property equals a certain value.
- ///
- /// The type of the property.
- /// The property.
- /// Returns a boolean value based on the property value.
- /// The pseudo-class.
- [Obsolete("Use PseudoClass and specify the control type.")]
- protected static void PseudoClass(
- AvaloniaProperty property,
- Func selector,
- string className)
- {
- PseudoClass(property, selector, className);
- }
-
- ///
- /// Adds a pseudo-class to be set when a property equals a certain value.
- ///
- /// The type of the property.
- /// The type to apply the pseudo-class to.
- /// The property.
- /// Returns a boolean value based on the property value.
- /// The pseudo-class.
- protected static void PseudoClass(
- AvaloniaProperty property,
- Func selector,
- string className)
- where TOwner : class, IStyledElement
- {
- Contract.Requires(property != null);
- Contract.Requires(selector != null);
- Contract.Requires(className != null);
-
- if (string.IsNullOrWhiteSpace(className))
- {
- throw new ArgumentException("Cannot supply an empty className.");
- }
-
- property.Changed.Merge(property.Initialized)
- .Where(e => e.Sender is TOwner)
- .Subscribe(e =>
- {
- if (selector((TProperty)e.NewValue))
- {
- ((StyledElement)e.Sender).PseudoClasses.Add(className);
- }
- else
- {
- ((StyledElement)e.Sender).PseudoClasses.Remove(className);
- }
- });
- }
-
protected virtual void LogicalChildrenCollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
{
switch (e.Action)
@@ -650,9 +573,12 @@ namespace Avalonia
element._dataContextUpdating = true;
element.OnDataContextBeginUpdate();
- foreach (var child in element.LogicalChildren)
+ var logicalChildren = element.LogicalChildren;
+ var logicalChildrenCount = logicalChildren.Count;
+
+ for (var i = 0; i < logicalChildrenCount; i++)
{
- if (child is StyledElement s &&
+ if (element.LogicalChildren[i] is StyledElement s &&
s.InheritanceParent == element &&
!s.IsSet(DataContextProperty))
{
@@ -723,9 +649,15 @@ namespace Avalonia
AttachedToLogicalTree?.Invoke(this, e);
}
- foreach (var child in LogicalChildren.OfType())
+ var logicalChildren = LogicalChildren;
+ var logicalChildrenCount = logicalChildren.Count;
+
+ for (var i = 0; i < logicalChildrenCount; i++)
{
- child.OnAttachedToLogicalTreeCore(e);
+ if (logicalChildren[i] is StyledElement child)
+ {
+ child.OnAttachedToLogicalTreeCore(e);
+ }
}
}
@@ -738,9 +670,15 @@ namespace Avalonia
OnDetachedFromLogicalTree(e);
DetachedFromLogicalTree?.Invoke(this, e);
- foreach (var child in LogicalChildren.OfType())
+ var logicalChildren = LogicalChildren;
+ var logicalChildrenCount = logicalChildren.Count;
+
+ for (var i = 0; i < logicalChildrenCount; i++)
{
- child.OnDetachedFromLogicalTreeCore(e);
+ if (logicalChildren[i] is StyledElement child)
+ {
+ child.OnDetachedFromLogicalTreeCore(e);
+ }
}
#if DEBUG
diff --git a/src/Avalonia.Styling/Styling/Setter.cs b/src/Avalonia.Styling/Styling/Setter.cs
index 9244232ff5..b880ecb01c 100644
--- a/src/Avalonia.Styling/Styling/Setter.cs
+++ b/src/Avalonia.Styling/Styling/Setter.cs
@@ -78,8 +78,6 @@ namespace Avalonia.Styling
{
Contract.Requires(control != null);
- var description = style?.ToString();
-
if (Property == null)
{
throw new InvalidOperationException("Setter.Property must be set.");
@@ -107,6 +105,8 @@ namespace Avalonia.Styling
}
else
{
+ var description = style?.ToString();
+
var activated = new ActivatedValue(activator, value, description);
return control.Bind(Property, activated, BindingPriority.StyleTrigger);
}
diff --git a/src/Avalonia.Styling/Styling/Style.cs b/src/Avalonia.Styling/Styling/Style.cs
index 99ee8d8563..22db7adfe4 100644
--- a/src/Avalonia.Styling/Styling/Style.cs
+++ b/src/Avalonia.Styling/Styling/Style.cs
@@ -116,13 +116,21 @@ namespace Avalonia.Styling
if (match.IsMatch)
{
var controlSubscriptions = GetSubscriptions(control);
-
- var subs = new CompositeDisposable(Setters.Count + Animations.Count);
- if (control is Animatable animatable)
+ var animatable = control as Animatable;
+
+ var setters = Setters;
+ var settersCount = setters.Count;
+ var animations = Animations;
+ var animationsCount = animations.Count;
+
+ var subs = new CompositeDisposable(settersCount + (animatable != null ? animationsCount : 0) + 1);
+
+ if (animatable != null)
{
- foreach (var animation in Animations)
+ for (var i = 0; i < animationsCount; i++)
{
+ var animation = animations[i];
var obsMatch = match.Activator;
if (match.Result == SelectorMatchResult.AlwaysThisType ||
@@ -133,17 +141,19 @@ namespace Avalonia.Styling
var sub = animation.Apply(animatable, null, obsMatch);
subs.Add(sub);
- }
+ }
}
- foreach (var setter in Setters)
+ for (var i = 0; i < settersCount; i++)
{
+ var setter = setters[i];
var sub = setter.Apply(this, control, match.Activator);
subs.Add(sub);
}
+ subs.Add(Disposable.Create((subs, Subscriptions) , state => state.Subscriptions.Remove(state.subs)));
+
controlSubscriptions.Add(subs);
- controlSubscriptions.Add(Disposable.Create(() => Subscriptions.Remove(subs)));
Subscriptions.Add(subs);
}
@@ -151,18 +161,23 @@ namespace Avalonia.Styling
}
else if (control == container)
{
+ var setters = Setters;
+ var settersCount = setters.Count;
+
var controlSubscriptions = GetSubscriptions(control);
- var subs = new CompositeDisposable(Setters.Count);
+ var subs = new CompositeDisposable(settersCount + 1);
- foreach (var setter in Setters)
+ for (var i = 0; i < settersCount; i++)
{
+ var setter = setters[i];
var sub = setter.Apply(this, control, null);
subs.Add(sub);
}
+ subs.Add(Disposable.Create((subs, Subscriptions), state => state.Subscriptions.Remove(state.subs)));
+
controlSubscriptions.Add(subs);
- controlSubscriptions.Add(Disposable.Create(() => Subscriptions.Remove(subs)));
Subscriptions.Add(subs);
return true;
}
diff --git a/src/Avalonia.Styling/Styling/Styles.cs b/src/Avalonia.Styling/Styling/Styles.cs
index 0226288998..fd38c39650 100644
--- a/src/Avalonia.Styling/Styling/Styles.cs
+++ b/src/Avalonia.Styling/Styling/Styles.cs
@@ -239,8 +239,10 @@ namespace Avalonia.Styling
///
public bool Remove(IStyle item) => _styles.Remove(item);
+ public AvaloniaList.Enumerator GetEnumerator() => _styles.GetEnumerator();
+
///
- public IEnumerator GetEnumerator() => _styles.GetEnumerator();
+ IEnumerator IEnumerable.GetEnumerator() => _styles.GetEnumerator();
///
IEnumerator IEnumerable.GetEnumerator() => _styles.GetEnumerator();
diff --git a/src/Avalonia.Themes.Default/ComboBox.xaml b/src/Avalonia.Themes.Default/ComboBox.xaml
index 1c5c8106d3..95bd9550a5 100644
--- a/src/Avalonia.Themes.Default/ComboBox.xaml
+++ b/src/Avalonia.Themes.Default/ComboBox.xaml
@@ -4,6 +4,7 @@
+
-
-
+
-
+
@@ -58,7 +58,7 @@
-
+
diff --git a/src/Avalonia.Themes.Default/MenuItem.xaml b/src/Avalonia.Themes.Default/MenuItem.xaml
index 93989d3782..769ab893bf 100644
--- a/src/Avalonia.Themes.Default/MenuItem.xaml
+++ b/src/Avalonia.Themes.Default/MenuItem.xaml
@@ -45,8 +45,7 @@
+ IsOpen="{TemplateBinding IsSubMenuOpen, Mode=TwoWay}">
@@ -95,8 +94,7 @@
+ StaysOpen="True">
diff --git a/src/Avalonia.Themes.Default/ProgressBar.xaml b/src/Avalonia.Themes.Default/ProgressBar.xaml
index 72271e785a..d3c2f0c784 100644
--- a/src/Avalonia.Themes.Default/ProgressBar.xaml
+++ b/src/Avalonia.Themes.Default/ProgressBar.xaml
@@ -1,14 +1,25 @@
@@ -22,42 +33,49 @@
+
diff --git a/src/Avalonia.Themes.Default/TextBox.xaml b/src/Avalonia.Themes.Default/TextBox.xaml
index 423d19da14..2c4cafde26 100644
--- a/src/Avalonia.Themes.Default/TextBox.xaml
+++ b/src/Avalonia.Themes.Default/TextBox.xaml
@@ -12,7 +12,9 @@
Background="{TemplateBinding Background}"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}">
-
+
$"A {Size} {RotationAngle.ToString(CultureInfo.InvariantCulture)} {(IsLargeArc ? 1 : 0)} {(int)SweepDirection} {Point}";
}
}
\ No newline at end of file
diff --git a/src/Avalonia.Visuals/Media/BezierSegment .cs b/src/Avalonia.Visuals/Media/BezierSegment .cs
index 8bea03bc23..ddb818c3ac 100644
--- a/src/Avalonia.Visuals/Media/BezierSegment .cs
+++ b/src/Avalonia.Visuals/Media/BezierSegment .cs
@@ -61,5 +61,8 @@ namespace Avalonia.Media
{
ctx.CubicBezierTo(Point1, Point2, Point3);
}
+
+ public override string ToString()
+ => $"C {Point1} {Point2} {Point3}";
}
}
\ No newline at end of file
diff --git a/src/Avalonia.Visuals/Media/LineSegment.cs b/src/Avalonia.Visuals/Media/LineSegment.cs
index d81f67d99f..81e53ff7c6 100644
--- a/src/Avalonia.Visuals/Media/LineSegment.cs
+++ b/src/Avalonia.Visuals/Media/LineSegment.cs
@@ -27,5 +27,8 @@ namespace Avalonia.Media
{
ctx.LineTo(Point);
}
+
+ public override string ToString()
+ => $"L {Point}";
}
}
\ No newline at end of file
diff --git a/src/Avalonia.Visuals/Media/PathFigure.cs b/src/Avalonia.Visuals/Media/PathFigure.cs
index 6a438a85ee..720fca8337 100644
--- a/src/Avalonia.Visuals/Media/PathFigure.cs
+++ b/src/Avalonia.Visuals/Media/PathFigure.cs
@@ -98,5 +98,8 @@ namespace Avalonia.Media
}
private PathSegments _segments;
+
+ public override string ToString()
+ => $"M {StartPoint} {string.Join(" ", _segments)}{(IsClosed ? "Z" : "")}";
}
}
\ No newline at end of file
diff --git a/src/Avalonia.Visuals/Media/PathGeometry.cs b/src/Avalonia.Visuals/Media/PathGeometry.cs
index 3a91c924d2..be7ffe57a7 100644
--- a/src/Avalonia.Visuals/Media/PathGeometry.cs
+++ b/src/Avalonia.Visuals/Media/PathGeometry.cs
@@ -112,5 +112,9 @@ namespace Avalonia.Media
() => InvalidateGeometry());
_figuresPropertiesObserver = figures?.TrackItemPropertyChanged(_ => InvalidateGeometry());
}
+
+
+ public override string ToString()
+ => $"{(FillRule != FillRule.EvenOdd ? "F1 " : "")}{(string.Join(" ", Figures))}";
}
}
diff --git a/src/Avalonia.Visuals/Media/QuadraticBezierSegment .cs b/src/Avalonia.Visuals/Media/QuadraticBezierSegment .cs
index 31c364edf4..f1a0ccaaa0 100644
--- a/src/Avalonia.Visuals/Media/QuadraticBezierSegment .cs
+++ b/src/Avalonia.Visuals/Media/QuadraticBezierSegment .cs
@@ -42,5 +42,8 @@ namespace Avalonia.Media
{
ctx.QuadraticBezierTo(Point1, Point2);
}
+
+ public override string ToString()
+ => $"Q {Point1} {Point2}";
}
}
\ No newline at end of file
diff --git a/src/Avalonia.Visuals/Visual.cs b/src/Avalonia.Visuals/Visual.cs
index ef7a254f86..bd2ab6e614 100644
--- a/src/Avalonia.Visuals/Visual.cs
+++ b/src/Avalonia.Visuals/Visual.cs
@@ -386,11 +386,18 @@ namespace Avalonia
AttachedToVisualTree?.Invoke(this, e);
InvalidateVisual();
- if (VisualChildren != null)
+ var visualChildren = VisualChildren;
+
+ if (visualChildren != null)
{
- foreach (Visual child in VisualChildren.OfType())
+ var visualChildrenCount = visualChildren.Count;
+
+ for (var i = 0; i < visualChildrenCount; i++)
{
- child.OnAttachedToVisualTreeCore(e);
+ if (visualChildren[i] is Visual child)
+ {
+ child.OnAttachedToVisualTreeCore(e);
+ }
}
}
}
@@ -415,11 +422,18 @@ namespace Avalonia
DetachedFromVisualTree?.Invoke(this, e);
e.Root?.Renderer?.AddDirty(this);
- if (VisualChildren != null)
+ var visualChildren = VisualChildren;
+
+ if (visualChildren != null)
{
- foreach (Visual child in VisualChildren.OfType())
+ var visualChildrenCount = visualChildren.Count;
+
+ for (var i = 0; i < visualChildrenCount; i++)
{
- child.OnDetachedFromVisualTreeCore(e);
+ if (visualChildren[i] is Visual child)
+ {
+ child.OnDetachedFromVisualTreeCore(e);
+ }
}
}
}
diff --git a/src/Avalonia.X11/Glx/GlxGlPlatformSurface.cs b/src/Avalonia.X11/Glx/GlxGlPlatformSurface.cs
index 45a46bd6f5..c49903cc16 100644
--- a/src/Avalonia.X11/Glx/GlxGlPlatformSurface.cs
+++ b/src/Avalonia.X11/Glx/GlxGlPlatformSurface.cs
@@ -80,6 +80,7 @@ namespace Avalonia.X11.Glx
public IGlDisplay Display => _context.Display;
public PixelSize Size => _info.Size;
public double Scaling => _info.Scaling;
+ public bool IsYFlipped { get; }
}
}
}
diff --git a/src/Avalonia.X11/X11Window.cs b/src/Avalonia.X11/X11Window.cs
index 919abae243..94f1dbc8d1 100644
--- a/src/Avalonia.X11/X11Window.cs
+++ b/src/Avalonia.X11/X11Window.cs
@@ -995,11 +995,18 @@ namespace Avalonia.X11
public void SetIcon(IWindowIconImpl icon)
{
- var data = ((X11IconData)icon).Data;
- fixed (void* pdata = data)
- XChangeProperty(_x11.Display, _handle, _x11.Atoms._NET_WM_ICON,
- new IntPtr((int)Atom.XA_CARDINAL), 32, PropertyMode.Replace,
- pdata, data.Length);
+ if (icon != null)
+ {
+ var data = ((X11IconData)icon).Data;
+ fixed (void* pdata = data)
+ XChangeProperty(_x11.Display, _handle, _x11.Atoms._NET_WM_ICON,
+ new IntPtr((int)Atom.XA_CARDINAL), 32, PropertyMode.Replace,
+ pdata, data.Length);
+ }
+ else
+ {
+ XDeleteProperty(_x11.Display, _handle, _x11.Atoms._NET_WM_ICON);
+ }
}
public void ShowTaskbarIcon(bool value)
diff --git a/src/Linux/Avalonia.LinuxFramebuffer/Output/DrmOutput.cs b/src/Linux/Avalonia.LinuxFramebuffer/Output/DrmOutput.cs
index 273265a6dc..1ebfd1a47f 100644
--- a/src/Linux/Avalonia.LinuxFramebuffer/Output/DrmOutput.cs
+++ b/src/Linux/Avalonia.LinuxFramebuffer/Output/DrmOutput.cs
@@ -234,6 +234,8 @@ namespace Avalonia.LinuxFramebuffer.Output
public PixelSize Size => _parent._mode.Resolution;
public double Scaling => _parent.Scaling;
+
+ public bool IsYFlipped { get; }
}
public IGlPlatformSurfaceRenderingSession BeginDraw()
diff --git a/src/Skia/Avalonia.Skia/GlRenderTarget.cs b/src/Skia/Avalonia.Skia/GlRenderTarget.cs
index 61ccf09e52..10d8ff3234 100644
--- a/src/Skia/Avalonia.Skia/GlRenderTarget.cs
+++ b/src/Skia/Avalonia.Skia/GlRenderTarget.cs
@@ -54,7 +54,7 @@ namespace Avalonia.Skia
new GRBackendRenderTarget(size.Width, size.Height, disp.SampleCount, disp.StencilSize,
new GRGlFramebufferInfo((uint)fb, GRPixelConfig.Rgba8888.ToGlSizedFormat()));
var surface = SKSurface.Create(_grContext, renderTarget,
- GRSurfaceOrigin.BottomLeft,
+ session.IsYFlipped ? GRSurfaceOrigin.TopLeft : GRSurfaceOrigin.BottomLeft,
GRPixelConfig.Rgba8888.ToColorType());
var nfo = new DrawingContextImpl.CreateInfo
diff --git a/src/Skia/Avalonia.Skia/PlatformRenderInterface.cs b/src/Skia/Avalonia.Skia/PlatformRenderInterface.cs
index 05c3bbdaa0..65ed1f506e 100644
--- a/src/Skia/Avalonia.Skia/PlatformRenderInterface.cs
+++ b/src/Skia/Avalonia.Skia/PlatformRenderInterface.cs
@@ -43,6 +43,7 @@ namespace Avalonia.Skia
{
GrContext = GRContext.Create(GRBackend.OpenGL, iface);
}
+ display.ClearContext();
}
}
diff --git a/src/Windows/Avalonia.Win32/WindowImpl.cs b/src/Windows/Avalonia.Win32/WindowImpl.cs
index c16b76b539..cea9f88799 100644
--- a/src/Windows/Avalonia.Win32/WindowImpl.cs
+++ b/src/Windows/Avalonia.Win32/WindowImpl.cs
@@ -942,7 +942,7 @@ namespace Avalonia.Win32
public void SetIcon(IWindowIconImpl icon)
{
var impl = (IconImpl)icon;
- var hIcon = impl.HIcon;
+ var hIcon = impl?.HIcon ?? IntPtr.Zero;
UnmanagedMethods.PostMessage(_hwnd, (int)UnmanagedMethods.WindowsMessage.WM_SETICON,
new IntPtr((int)UnmanagedMethods.Icons.ICON_BIG), hIcon);
}
diff --git a/tests/Avalonia.Base.UnitTests/AvaloniaObjectTests_Attached.cs b/tests/Avalonia.Base.UnitTests/AvaloniaObjectTests_Attached.cs
index 44e2976e03..474ef9abec 100644
--- a/tests/Avalonia.Base.UnitTests/AvaloniaObjectTests_Attached.cs
+++ b/tests/Avalonia.Base.UnitTests/AvaloniaObjectTests_Attached.cs
@@ -16,19 +16,6 @@ namespace Avalonia.Base.UnitTests
Assert.Equal("foodefault", target.GetValue(Class2.FooProperty));
}
- [Fact]
- public void AvaloniaProperty_Initialized_Is_Called_For_Attached_Property()
- {
- bool raised = false;
-
- using (Class1.FooProperty.Initialized.Subscribe(x => raised = true))
- {
- new Class3();
- }
-
- Assert.True(raised);
- }
-
private class Base : AvaloniaObject
{
}
@@ -46,9 +33,5 @@ namespace Avalonia.Base.UnitTests
public static readonly AttachedProperty FooProperty =
Class1.FooProperty.AddOwner();
}
-
- private class Class3 : Base
- {
- }
}
}
diff --git a/tests/Avalonia.Base.UnitTests/AvaloniaObjectTests_Binding.cs b/tests/Avalonia.Base.UnitTests/AvaloniaObjectTests_Binding.cs
index 4c00d2a1ea..2839fde320 100644
--- a/tests/Avalonia.Base.UnitTests/AvaloniaObjectTests_Binding.cs
+++ b/tests/Avalonia.Base.UnitTests/AvaloniaObjectTests_Binding.cs
@@ -132,6 +132,111 @@ namespace Avalonia.Base.UnitTests
Assert.Equal("foo", target.GetValue(property));
}
+ [Fact]
+ public void Completing_LocalValue_Binding_Raises_PropertyChanged()
+ {
+ var target = new Class1();
+ var source = new BehaviorSubject>("foo");
+ var property = Class1.FooProperty;
+ var raised = 0;
+
+ target.Bind(property, source);
+ Assert.Equal("foo", target.GetValue(property));
+
+ target.PropertyChanged += (s, e) =>
+ {
+ Assert.Equal(BindingPriority.Unset, e.Priority);
+ Assert.Equal(property, e.Property);
+ Assert.Equal("foo", e.OldValue as string);
+ Assert.Equal("foodefault", e.NewValue as string);
+ ++raised;
+ };
+
+ source.OnCompleted();
+
+ Assert.Equal("foodefault", target.GetValue(property));
+ Assert.Equal(1, raised);
+ }
+
+ [Fact]
+ public void Completing_Style_Binding_Raises_PropertyChanged()
+ {
+ var target = new Class1();
+ var source = new BehaviorSubject>("foo");
+ var property = Class1.FooProperty;
+ var raised = 0;
+
+ target.Bind(property, source, BindingPriority.Style);
+ Assert.Equal("foo", target.GetValue(property));
+
+ target.PropertyChanged += (s, e) =>
+ {
+ Assert.Equal(BindingPriority.Unset, e.Priority);
+ Assert.Equal(property, e.Property);
+ Assert.Equal("foo", e.OldValue as string);
+ Assert.Equal("foodefault", e.NewValue as string);
+ ++raised;
+ };
+
+ source.OnCompleted();
+
+ Assert.Equal("foodefault", target.GetValue(property));
+ Assert.Equal(1, raised);
+ }
+
+ [Fact]
+ public void Completing_LocalValue_Binding_With_Style_Binding_Raises_PropertyChanged()
+ {
+ var target = new Class1();
+ var source = new BehaviorSubject>("foo");
+ var property = Class1.FooProperty;
+ var raised = 0;
+
+ target.Bind(property, new BehaviorSubject("bar"), BindingPriority.Style);
+ target.Bind(property, source);
+ Assert.Equal("foo", target.GetValue(property));
+
+ target.PropertyChanged += (s, e) =>
+ {
+ Assert.Equal(BindingPriority.Style, e.Priority);
+ Assert.Equal(property, e.Property);
+ Assert.Equal("foo", e.OldValue as string);
+ Assert.Equal("bar", e.NewValue as string);
+ ++raised;
+ };
+
+ source.OnCompleted();
+
+ Assert.Equal("bar", target.GetValue(property));
+ Assert.Equal(1, raised);
+ }
+
+ [Fact]
+ public void Disposing_LocalValue_Binding_Raises_PropertyChanged()
+ {
+ var target = new Class1();
+ var source = new BehaviorSubject>("foo");
+ var property = Class1.FooProperty;
+ var raised = 0;
+
+ var sub = target.Bind(property, source);
+ Assert.Equal("foo", target.GetValue(property));
+
+ target.PropertyChanged += (s, e) =>
+ {
+ Assert.Equal(BindingPriority.Unset, e.Priority);
+ Assert.Equal(property, e.Property);
+ Assert.Equal("foo", e.OldValue as string);
+ Assert.Equal("foodefault", e.NewValue as string);
+ ++raised;
+ };
+
+ sub.Dispose();
+
+ Assert.Equal("foodefault", target.GetValue(property));
+ Assert.Equal(1, raised);
+ }
+
[Fact]
public void Setting_Style_Value_Overrides_Binding_Permanently()
{
diff --git a/tests/Avalonia.Base.UnitTests/AvaloniaObjectTests_Direct.cs b/tests/Avalonia.Base.UnitTests/AvaloniaObjectTests_Direct.cs
index 4110c3771f..ca17afb94f 100644
--- a/tests/Avalonia.Base.UnitTests/AvaloniaObjectTests_Direct.cs
+++ b/tests/Avalonia.Base.UnitTests/AvaloniaObjectTests_Direct.cs
@@ -188,6 +188,7 @@ namespace Avalonia.Base.UnitTests
target.PropertyChanged += (s, e) =>
{
Assert.Same(target, s);
+ Assert.Equal(BindingPriority.LocalValue, e.Priority);
Assert.Equal(Class1.FooProperty, e.Property);
Assert.Equal("newvalue", (string)e.OldValue);
Assert.Equal("unset", (string)e.NewValue);
@@ -424,22 +425,6 @@ namespace Avalonia.Base.UnitTests
Assert.Equal("second", target.Foo);
}
- [Fact]
- public void Property_Notifies_Initialized()
- {
- bool raised = false;
-
- Class1.FooProperty.Initialized.Subscribe(e =>
- raised = e.Property == Class1.FooProperty &&
- e.OldValue == AvaloniaProperty.UnsetValue &&
- (string)e.NewValue == "initial" &&
- e.Priority == BindingPriority.Unset);
-
- var target = new Class1();
-
- Assert.True(raised);
- }
-
[Fact]
public void Binding_Error_Reverts_To_Default_Value()
{
diff --git a/tests/Avalonia.Base.UnitTests/AvaloniaObjectTests_SetValue.cs b/tests/Avalonia.Base.UnitTests/AvaloniaObjectTests_SetValue.cs
index 40631d04cf..4b477287e8 100644
--- a/tests/Avalonia.Base.UnitTests/AvaloniaObjectTests_SetValue.cs
+++ b/tests/Avalonia.Base.UnitTests/AvaloniaObjectTests_SetValue.cs
@@ -30,6 +30,7 @@ namespace Avalonia.Base.UnitTests
target.PropertyChanged += (s, e) =>
{
Assert.Same(target, s);
+ Assert.Equal(BindingPriority.Unset, e.Priority);
Assert.Equal(Class1.FooProperty, e.Property);
Assert.Equal("newvalue", (string)e.OldValue);
Assert.Equal("foodefault", (string)e.NewValue);
@@ -239,6 +240,17 @@ namespace Avalonia.Base.UnitTests
Assert.Equal("two", target.GetValue(Class1.FooProperty));
}
+ [Fact]
+ public void SetValue_Animation_Overrides_LocalValue()
+ {
+ Class1 target = new Class1();
+
+ target.SetValue(Class1.FooProperty, "one", BindingPriority.LocalValue);
+ Assert.Equal("one", target.GetValue(Class1.FooProperty));
+ target.SetValue(Class1.FooProperty, "two", BindingPriority.Animation);
+ Assert.Equal("two", target.GetValue(Class1.FooProperty));
+ }
+
[Fact]
public void Setting_UnsetValue_Reverts_To_Default_Value()
{
diff --git a/tests/Avalonia.Base.UnitTests/AvaloniaPropertyTests.cs b/tests/Avalonia.Base.UnitTests/AvaloniaPropertyTests.cs
index 90b8bcff63..6bb8dfe1f5 100644
--- a/tests/Avalonia.Base.UnitTests/AvaloniaPropertyTests.cs
+++ b/tests/Avalonia.Base.UnitTests/AvaloniaPropertyTests.cs
@@ -78,24 +78,6 @@ namespace Avalonia.Base.UnitTests
Assert.Equal(BindingMode.TwoWay, result.DefaultBindingMode);
}
- [Fact]
- public void Initialized_Observable_Fired()
- {
- bool invoked = false;
-
- Class1.FooProperty.Initialized.Subscribe(x =>
- {
- Assert.Equal(AvaloniaProperty.UnsetValue, x.OldValue);
- Assert.Equal("default", x.NewValue);
- Assert.Equal(BindingPriority.Unset, x.Priority);
- invoked = true;
- });
-
- var target = new Class1();
-
- Assert.True(invoked);
- }
-
[Fact]
public void Changed_Observable_Fired()
{
@@ -141,11 +123,6 @@ namespace Avalonia.Base.UnitTests
OverrideMetadata(typeof(T), metadata);
}
- internal override void NotifyInitialized(IAvaloniaObject o)
- {
- throw new NotImplementedException();
- }
-
internal override IDisposable RouteBind(
IAvaloniaObject o,
IObservable> source,
diff --git a/tests/Avalonia.Base.UnitTests/Data/DefaultValueConverterTests.cs b/tests/Avalonia.Base.UnitTests/Data/DefaultValueConverterTests.cs
index ecf559951a..f29dabb6bf 100644
--- a/tests/Avalonia.Base.UnitTests/Data/DefaultValueConverterTests.cs
+++ b/tests/Avalonia.Base.UnitTests/Data/DefaultValueConverterTests.cs
@@ -50,6 +50,18 @@ namespace Avalonia.Base.UnitTests.Data.Converters
Assert.Equal(TestEnum.Bar, result);
}
+ [Fact]
+ public void Can_Convert_String_To_TimeSpan()
+ {
+ var result = DefaultValueConverter.Instance.Convert(
+ "00:00:10",
+ typeof(TimeSpan),
+ null,
+ CultureInfo.InvariantCulture);
+
+ Assert.Equal(TimeSpan.FromSeconds(10), result);
+ }
+
[Fact]
public void Can_Convert_Int_To_Enum()
{
diff --git a/tests/Avalonia.Base.UnitTests/DirectPropertyTests.cs b/tests/Avalonia.Base.UnitTests/DirectPropertyTests.cs
index c2a8b03f15..8f62dc9136 100644
--- a/tests/Avalonia.Base.UnitTests/DirectPropertyTests.cs
+++ b/tests/Avalonia.Base.UnitTests/DirectPropertyTests.cs
@@ -1,33 +1,12 @@
// Copyright (c) The Avalonia Project. All rights reserved.
// Licensed under the MIT license. See licence.md file in the project root for full license information.
-using System;
-using System.Reactive.Subjects;
-using Avalonia.Data;
using Xunit;
namespace Avalonia.Base.UnitTests
{
public class DirectPropertyTests
{
- [Fact]
- public void Initialized_Observable_Fired()
- {
- bool invoked = false;
-
- Class1.FooProperty.Initialized.Subscribe(x =>
- {
- Assert.Equal(AvaloniaProperty.UnsetValue, x.OldValue);
- Assert.Equal("foo", x.NewValue);
- Assert.Equal(BindingPriority.Unset, x.Priority);
- invoked = true;
- });
-
- var target = new Class1();
-
- Assert.True(invoked);
- }
-
[Fact]
public void IsDirect_Property_Returns_True()
{
@@ -69,7 +48,6 @@ namespace Avalonia.Base.UnitTests
var p2 = p1.AddOwner(o => null, (o, v) => { });
Assert.Same(p1.Changed, p2.Changed);
- Assert.Same(p1.Initialized, p2.Initialized);
}
private class Class1 : AvaloniaObject
diff --git a/tests/Avalonia.Benchmarks/Base/AvaloniaObjectInitializationBenchmark.cs b/tests/Avalonia.Benchmarks/Base/AvaloniaObjectInitializationBenchmark.cs
index 06716f7102..d2e38850f2 100644
--- a/tests/Avalonia.Benchmarks/Base/AvaloniaObjectInitializationBenchmark.cs
+++ b/tests/Avalonia.Benchmarks/Base/AvaloniaObjectInitializationBenchmark.cs
@@ -6,7 +6,7 @@ namespace Avalonia.Benchmarks.Base
[MemoryDiagnoser]
public class AvaloniaObjectInitializationBenchmark
{
- [Benchmark(OperationsPerInvoke = 1000)]
+ [Benchmark]
public Button InitializeButton()
{
return new Button();
diff --git a/tests/Avalonia.Benchmarks/Layout/CalendarBenchmark.cs b/tests/Avalonia.Benchmarks/Layout/CalendarBenchmark.cs
new file mode 100644
index 0000000000..c55912d94d
--- /dev/null
+++ b/tests/Avalonia.Benchmarks/Layout/CalendarBenchmark.cs
@@ -0,0 +1,46 @@
+using System;
+using System.Runtime.CompilerServices;
+using Avalonia.Controls;
+using Avalonia.UnitTests;
+using BenchmarkDotNet.Attributes;
+
+namespace Avalonia.Benchmarks.Layout
+{
+ [MemoryDiagnoser]
+ public class CalendarBenchmark : IDisposable
+ {
+ private readonly IDisposable _app;
+ private readonly TestRoot _root;
+
+ public CalendarBenchmark()
+ {
+ _app = UnitTestApplication.Start(
+ TestServices.StyledWindow.With(
+ renderInterface: new NullRenderingPlatform(),
+ threadingInterface: new NullThreadingPlatform()));
+
+ _root = new TestRoot(true, null)
+ {
+ Renderer = new NullRenderer()
+ };
+
+ _root.LayoutManager.ExecuteInitialLayoutPass(_root);
+ }
+
+ [Benchmark]
+ [MethodImpl(MethodImplOptions.NoInlining)]
+ public void CreateCalendar()
+ {
+ var calendar = new Calendar();
+
+ _root.Child = calendar;
+
+ _root.LayoutManager.ExecuteLayoutPass();
+ }
+
+ public void Dispose()
+ {
+ _app.Dispose();
+ }
+ }
+}
diff --git a/tests/Avalonia.Benchmarks/NullFormattedTextImpl.cs b/tests/Avalonia.Benchmarks/NullFormattedTextImpl.cs
new file mode 100644
index 0000000000..f886d077cc
--- /dev/null
+++ b/tests/Avalonia.Benchmarks/NullFormattedTextImpl.cs
@@ -0,0 +1,36 @@
+using System;
+using System.Collections.Generic;
+using Avalonia.Media;
+using Avalonia.Platform;
+
+namespace Avalonia.Benchmarks
+{
+ internal class NullFormattedTextImpl : IFormattedTextImpl
+ {
+ public Size Constraint { get; }
+
+ public Rect Bounds { get; }
+
+ public string Text { get; }
+
+ public IEnumerable GetLines()
+ {
+ throw new NotImplementedException();
+ }
+
+ public TextHitTestResult HitTestPoint(Point point)
+ {
+ throw new NotImplementedException();
+ }
+
+ public Rect HitTestTextPosition(int index)
+ {
+ throw new NotImplementedException();
+ }
+
+ public IEnumerable HitTestTextRange(int index, int length)
+ {
+ throw new NotImplementedException();
+ }
+ }
+}
diff --git a/tests/Avalonia.Benchmarks/NullRenderingPlatform.cs b/tests/Avalonia.Benchmarks/NullRenderingPlatform.cs
new file mode 100644
index 0000000000..101d40f00a
--- /dev/null
+++ b/tests/Avalonia.Benchmarks/NullRenderingPlatform.cs
@@ -0,0 +1,78 @@
+using System;
+using System.Collections.Generic;
+using System.IO;
+using Avalonia.Media;
+using Avalonia.Platform;
+using Avalonia.UnitTests;
+
+namespace Avalonia.Benchmarks
+{
+ internal class NullRenderingPlatform : IPlatformRenderInterface
+ {
+ public IFormattedTextImpl CreateFormattedText(string text, Typeface typeface, double fontSize, TextAlignment textAlignment,
+ TextWrapping wrapping, Size constraint, IReadOnlyList spans)
+ {
+ return new NullFormattedTextImpl();
+ }
+
+ public IGeometryImpl CreateEllipseGeometry(Rect rect)
+ {
+ throw new NotImplementedException();
+ }
+
+ public IGeometryImpl CreateLineGeometry(Point p1, Point p2)
+ {
+ throw new NotImplementedException();
+ }
+
+ public IGeometryImpl CreateRectangleGeometry(Rect rect)
+ {
+ throw new NotImplementedException();
+ }
+
+ public IStreamGeometryImpl CreateStreamGeometry()
+ {
+ return new MockStreamGeometryImpl();
+ }
+
+ public IRenderTarget CreateRenderTarget(IEnumerable surfaces)
+ {
+ throw new NotImplementedException();
+ }
+
+ public IRenderTargetBitmapImpl CreateRenderTargetBitmap(PixelSize size, Vector dpi)
+ {
+ throw new NotImplementedException();
+ }
+
+ public IWriteableBitmapImpl CreateWriteableBitmap(PixelSize size, Vector dpi, PixelFormat? format = null)
+ {
+ throw new NotImplementedException();
+ }
+
+ public IBitmapImpl LoadBitmap(string fileName)
+ {
+ throw new NotImplementedException();
+ }
+
+ public IBitmapImpl LoadBitmap(Stream stream)
+ {
+ throw new NotImplementedException();
+ }
+
+ public IBitmapImpl LoadBitmap(PixelFormat format, IntPtr data, PixelSize size, Vector dpi, int stride)
+ {
+ throw new NotImplementedException();
+ }
+
+ public IFontManagerImpl CreateFontManager()
+ {
+ return new MockFontManagerImpl();
+ }
+
+ public IGlyphRunImpl CreateGlyphRun(GlyphRun glyphRun, out double width)
+ {
+ throw new NotImplementedException();
+ }
+ }
+}
diff --git a/tests/Avalonia.Benchmarks/NullThreadingPlatform.cs b/tests/Avalonia.Benchmarks/NullThreadingPlatform.cs
new file mode 100644
index 0000000000..ba84b5afcc
--- /dev/null
+++ b/tests/Avalonia.Benchmarks/NullThreadingPlatform.cs
@@ -0,0 +1,28 @@
+using System;
+using System.Reactive.Disposables;
+using System.Threading;
+using Avalonia.Platform;
+using Avalonia.Threading;
+
+namespace Avalonia.Benchmarks
+{
+ internal class NullThreadingPlatform : IPlatformThreadingInterface
+ {
+ public void RunLoop(CancellationToken cancellationToken)
+ {
+ }
+
+ public IDisposable StartTimer(DispatcherPriority priority, TimeSpan interval, Action tick)
+ {
+ return Disposable.Empty;
+ }
+
+ public void Signal(DispatcherPriority priority)
+ {
+ }
+
+ public bool CurrentThreadIsLoopThread => true;
+
+ public event Action Signaled;
+ }
+}
diff --git a/tests/Avalonia.Benchmarks/Styling/StyleAttachBenchmark.cs b/tests/Avalonia.Benchmarks/Styling/StyleAttachBenchmark.cs
new file mode 100644
index 0000000000..7bccd65c81
--- /dev/null
+++ b/tests/Avalonia.Benchmarks/Styling/StyleAttachBenchmark.cs
@@ -0,0 +1,47 @@
+using System;
+using System.Runtime.CompilerServices;
+using Avalonia.Controls;
+using Avalonia.UnitTests;
+using BenchmarkDotNet.Attributes;
+
+namespace Avalonia.Benchmarks.Styling
+{
+ [MemoryDiagnoser]
+ public class StyleAttachBenchmark : IDisposable
+ {
+ private readonly IDisposable _app;
+ private readonly TestRoot _root;
+ private readonly TextBox _control;
+
+ public StyleAttachBenchmark()
+ {
+ _app = UnitTestApplication.Start(
+ TestServices.StyledWindow.With(
+ renderInterface: new NullRenderingPlatform(),
+ threadingInterface: new NullThreadingPlatform()));
+
+ _root = new TestRoot(true, null)
+ {
+ Renderer = new NullRenderer(),
+ };
+
+ _control = new TextBox();
+ }
+
+ [Benchmark]
+ [MethodImpl(MethodImplOptions.NoInlining)]
+ public void AttachTextBoxStyles()
+ {
+ var styles = UnitTestApplication.Current.Styles;
+
+ styles.Attach(_control, UnitTestApplication.Current);
+
+ styles.Detach();
+ }
+
+ public void Dispose()
+ {
+ _app.Dispose();
+ }
+ }
+}
diff --git a/tests/Avalonia.Controls.UnitTests/CarouselTests.cs b/tests/Avalonia.Controls.UnitTests/CarouselTests.cs
index b16ac6bb8e..1bcae8e736 100644
--- a/tests/Avalonia.Controls.UnitTests/CarouselTests.cs
+++ b/tests/Avalonia.Controls.UnitTests/CarouselTests.cs
@@ -51,10 +51,9 @@ namespace Avalonia.Controls.UnitTests
Assert.Single(target.GetLogicalChildren());
- var child = target.GetLogicalChildren().Single();
+ var child = GetContainerTextBlock(target.GetLogicalChildren().Single());
- Assert.IsType(child);
- Assert.Equal("Foo", ((TextBlock)child).Text);
+ Assert.Equal("Foo", child.Text);
}
[Fact]
@@ -98,20 +97,18 @@ namespace Avalonia.Controls.UnitTests
Assert.Equal(3, target.GetLogicalChildren().Count());
- var child = target.GetLogicalChildren().First();
+ var child = GetContainerTextBlock(target.GetLogicalChildren().First());
- Assert.IsType(child);
- Assert.Equal("Foo", ((TextBlock)child).Text);
+ Assert.Equal("Foo", child.Text);
var newItems = items.ToList();
newItems.RemoveAt(0);
target.Items = newItems;
- child = target.GetLogicalChildren().First();
+ child = GetContainerTextBlock(target.GetLogicalChildren().First());
- Assert.IsType(child);
- Assert.Equal("Bar", ((TextBlock)child).Text);
+ Assert.Equal("Bar", child.Text);
}
[Fact]
@@ -136,20 +133,18 @@ namespace Avalonia.Controls.UnitTests
Assert.Single(target.GetLogicalChildren());
- var child = target.GetLogicalChildren().Single();
+ var child = GetContainerTextBlock(target.GetLogicalChildren().Single());
- Assert.IsType(child);
- Assert.Equal("Foo", ((TextBlock)child).Text);
+ Assert.Equal("Foo", child.Text);
var newItems = items.ToList();
newItems.RemoveAt(0);
target.Items = newItems;
- child = target.GetLogicalChildren().Single();
+ child = GetContainerTextBlock(target.GetLogicalChildren().Single());
- Assert.IsType(child);
- Assert.Equal("Bar", ((TextBlock)child).Text);
+ Assert.Equal("Bar", child.Text);
}
[Fact]
@@ -197,10 +192,9 @@ namespace Avalonia.Controls.UnitTests
Assert.Equal(3, target.GetLogicalChildren().Count());
- var child = target.GetLogicalChildren().First();
+ var child = GetContainerTextBlock(target.GetLogicalChildren().First());
- Assert.IsType(child);
- Assert.Equal("Foo", ((TextBlock)child).Text);
+ Assert.Equal("Foo", child.Text);
target.Items = null;
@@ -233,7 +227,7 @@ namespace Avalonia.Controls.UnitTests
Assert.Equal("FooBar", target.SelectedItem);
- var child = target.GetVisualDescendants().LastOrDefault();
+ var child = GetContainerTextBlock(target.GetVisualDescendants().LastOrDefault());
Assert.IsType(child);
Assert.Equal("FooBar", ((TextBlock)child).Text);
@@ -261,14 +255,13 @@ namespace Avalonia.Controls.UnitTests
Assert.Equal(3, target.GetLogicalChildren().Count());
- var child = target.GetLogicalChildren().First();
+ var child = GetContainerTextBlock(target.GetLogicalChildren().First());
- Assert.IsType(child);
- Assert.Equal("Foo", ((TextBlock)child).Text);
+ Assert.Equal("Foo", child.Text);
items.RemoveAt(0);
- child = target.GetLogicalChildren().First();
+ child = GetContainerTextBlock(target.GetLogicalChildren().First());
Assert.IsType(child);
Assert.Equal("Bar", ((TextBlock)child).Text);
@@ -314,5 +307,12 @@ namespace Avalonia.Controls.UnitTests
[~CarouselPresenter.PageTransitionProperty] = control[~Carousel.PageTransitionProperty],
}.RegisterInNameScope(scope);
}
+
+ private static TextBlock GetContainerTextBlock(object control)
+ {
+ var contentPresenter = Assert.IsType(control);
+ contentPresenter.UpdateChild();
+ return Assert.IsType(contentPresenter.Child);
+ }
}
}
diff --git a/tests/Avalonia.Controls.UnitTests/ComboBoxTests.cs b/tests/Avalonia.Controls.UnitTests/ComboBoxTests.cs
index 599e214b31..81908b5147 100644
--- a/tests/Avalonia.Controls.UnitTests/ComboBoxTests.cs
+++ b/tests/Avalonia.Controls.UnitTests/ComboBoxTests.cs
@@ -108,5 +108,36 @@ namespace Avalonia.Controls.UnitTests
};
});
}
+
+ [Fact]
+ public void Detaching_Closed_ComboBox_Keeps_Current_Focus()
+ {
+ using (UnitTestApplication.Start(TestServices.RealFocus))
+ {
+ var target = new ComboBox
+ {
+ Items = new[] { new Canvas() },
+ SelectedIndex = 0,
+ Template = GetTemplate(),
+ };
+
+ var other = new Control { Focusable = true };
+
+ StackPanel panel;
+
+ var root = new TestRoot { Child = panel = new StackPanel { Children = { target, other } } };
+
+ target.ApplyTemplate();
+ target.Presenter.ApplyTemplate();
+
+ other.Focus();
+
+ Assert.True(other.IsFocused);
+
+ panel.Children.Remove(target);
+
+ Assert.True(other.IsFocused);
+ }
+ }
}
}
diff --git a/tests/Avalonia.Controls.UnitTests/ItemsControlTests.cs b/tests/Avalonia.Controls.UnitTests/ItemsControlTests.cs
index 227d783874..a728500316 100644
--- a/tests/Avalonia.Controls.UnitTests/ItemsControlTests.cs
+++ b/tests/Avalonia.Controls.UnitTests/ItemsControlTests.cs
@@ -66,7 +66,7 @@ namespace Avalonia.Controls.UnitTests
}
[Fact]
- public void Container_Child_Should_Have_LogicalParent_Set_To_Container()
+ public void Container_Should_Have_LogicalParent_Set_To_ItemsControl()
{
using (UnitTestApplication.Start(TestServices.StyledWindow))
{
@@ -87,7 +87,7 @@ namespace Avalonia.Controls.UnitTests
var container = (ContentPresenter)target.Presenter.Panel.Children[0];
- Assert.Equal(container, container.Child.Parent);
+ Assert.Equal(target, container.Parent);
}
}
@@ -190,7 +190,7 @@ namespace Avalonia.Controls.UnitTests
}
[Fact]
- public void Adding_String_Item_Should_Make_TextBlock_Appear_In_LogicalChildren()
+ public void Adding_String_Item_Should_Make_ContentPresenter_Appear_In_LogicalChildren()
{
var target = new ItemsControl();
var child = new Control();
@@ -202,7 +202,7 @@ namespace Avalonia.Controls.UnitTests
var logical = (ILogical)target;
Assert.Equal(1, logical.LogicalChildren.Count);
- Assert.IsType(logical.LogicalChildren[0]);
+ Assert.IsType(logical.LogicalChildren[0]);
}
[Fact]
@@ -575,6 +575,30 @@ namespace Avalonia.Controls.UnitTests
};
}
+ [Fact]
+ public void Detaching_Then_Reattaching_To_Logical_Tree_Twice_Does_Not_Throw()
+ {
+ // # Issue 3487
+ var target = new ItemsControl
+ {
+ Template = GetTemplate(),
+ Items = new[] { "foo", "bar" },
+ ItemTemplate = new FuncDataTemplate((_, __) => new Canvas()),
+ };
+
+ var root = new TestRoot(target);
+ root.Measure(Size.Infinity);
+ root.Arrange(new Rect(root.DesiredSize));
+
+ root.Child = null;
+ root.Child = target;
+
+ target.Measure(Size.Infinity);
+
+ root.Child = null;
+ root.Child = target;
+ }
+
private class Item
{
public Item(string value)
diff --git a/tests/Avalonia.Controls.UnitTests/Primitives/PopupTests.cs b/tests/Avalonia.Controls.UnitTests/Primitives/PopupTests.cs
index 7cb9fccee8..1f07482617 100644
--- a/tests/Avalonia.Controls.UnitTests/Primitives/PopupTests.cs
+++ b/tests/Avalonia.Controls.UnitTests/Primitives/PopupTests.cs
@@ -223,7 +223,33 @@ namespace Avalonia.Controls.UnitTests.Primitives
}
}
-
+ [Fact]
+ public void Popup_Close_On_Closed_Popup_Should_Not_Raise_Closed_Event()
+ {
+ using (CreateServices())
+ {
+ var window = PreparedWindow();
+ var target = new Popup() { PlacementMode = PlacementMode.Pointer };
+
+ window.Content = target;
+ window.ApplyTemplate();
+
+ int closedCount = 0;
+
+ target.Closed += (sender, args) =>
+ {
+ closedCount++;
+ };
+
+ target.Close();
+ target.Close();
+ target.Close();
+ target.Close();
+
+ Assert.Equal(0, closedCount);
+ }
+ }
+
[Fact]
public void Templated_Control_With_Popup_In_Template_Should_Set_TemplatedParent()
{
diff --git a/tests/Avalonia.Controls.UnitTests/Primitives/SelectingItemsControlTests.cs b/tests/Avalonia.Controls.UnitTests/Primitives/SelectingItemsControlTests.cs
index 696c0dbf46..004cf009fc 100644
--- a/tests/Avalonia.Controls.UnitTests/Primitives/SelectingItemsControlTests.cs
+++ b/tests/Avalonia.Controls.UnitTests/Primitives/SelectingItemsControlTests.cs
@@ -1202,6 +1202,28 @@ namespace Avalonia.Controls.UnitTests.Primitives
target.MoveSelection(NavigationDirection.Next, true);
}
+ [Fact]
+ public void MoveSelection_Does_Select_Disabled_Controls()
+ {
+ // Issue #3426.
+ var target = new TestSelector
+ {
+ Template = Template(),
+ Items = new[]
+ {
+ new ListBoxItem(),
+ new ListBoxItem { IsEnabled = false },
+ },
+ SelectedIndex = 0,
+ };
+
+ target.Measure(new Size(100, 100));
+ target.Arrange(new Rect(0, 0, 100, 100));
+ target.MoveSelection(NavigationDirection.Next, true);
+
+ Assert.Equal(0, target.SelectedIndex);
+ }
+
[Fact]
public void Pre_Selecting_Item_Should_Set_Selection_After_It_Was_Added_When_AlwaysSelected()
{
diff --git a/tests/Avalonia.Controls.UnitTests/ScrollViewerTests.cs b/tests/Avalonia.Controls.UnitTests/ScrollViewerTests.cs
index 2238175a4a..f281c83bb3 100644
--- a/tests/Avalonia.Controls.UnitTests/ScrollViewerTests.cs
+++ b/tests/Avalonia.Controls.UnitTests/ScrollViewerTests.cs
@@ -65,6 +65,30 @@ namespace Avalonia.Controls.UnitTests
Assert.Equal(new Vector(10, 10), target.Offset);
}
+ [Fact]
+ public void Test_ScrollToHome()
+ {
+ var target = new ScrollViewer();
+ target.SetValue(ScrollViewer.ExtentProperty, new Size(50, 50));
+ target.SetValue(ScrollViewer.ViewportProperty, new Size(10, 10));
+ target.Offset = new Vector(25, 25);
+ target.ScrollToHome();
+
+ Assert.Equal(new Vector(0, 0), target.Offset);
+ }
+
+ [Fact]
+ public void Test_ScrollToEnd()
+ {
+ var target = new ScrollViewer();
+ target.SetValue(ScrollViewer.ExtentProperty, new Size(50, 50));
+ target.SetValue(ScrollViewer.ViewportProperty, new Size(10, 10));
+ target.Offset = new Vector(25, 25);
+ target.ScrollToEnd();
+
+ Assert.Equal(new Vector(0, 40), target.Offset);
+ }
+
private Control CreateTemplate(ScrollViewer control, INameScope scope)
{
return new Grid
diff --git a/tests/Avalonia.Controls.UnitTests/WindowTests.cs b/tests/Avalonia.Controls.UnitTests/WindowTests.cs
index 0508edd92f..81699b075c 100644
--- a/tests/Avalonia.Controls.UnitTests/WindowTests.cs
+++ b/tests/Avalonia.Controls.UnitTests/WindowTests.cs
@@ -341,11 +341,62 @@ namespace Avalonia.Controls.UnitTests
}
}
+ [Fact]
+ public void Child_Should_Be_Measured_With_Width_And_Height_If_SizeToContent_Is_Manual()
+ {
+ using (UnitTestApplication.Start(TestServices.StyledWindow))
+ {
+ var child = new ChildControl();
+ var target = new Window
+ {
+ Width = 100,
+ Height = 50,
+ SizeToContent = SizeToContent.Manual,
+ Content = child
+ };
+
+ target.Show();
+
+ Assert.Equal(new Size(100, 50), child.MeasureSize);
+ }
+ }
+
+ [Fact]
+ public void Child_Should_Be_Measured_With_Infinity_If_SizeToContent_Is_WidthAndHeight()
+ {
+ using (UnitTestApplication.Start(TestServices.StyledWindow))
+ {
+ var child = new ChildControl();
+ var target = new Window
+ {
+ Width = 100,
+ Height = 50,
+ SizeToContent = SizeToContent.WidthAndHeight,
+ Content = child
+ };
+
+ target.Show();
+
+ Assert.Equal(Size.Infinity, child.MeasureSize);
+ }
+ }
+
private IWindowImpl CreateImpl(Mock renderer)
{
return Mock.Of(x =>
x.Scaling == 1 &&
x.CreateRenderer(It.IsAny()) == renderer.Object);
}
+
+ private class ChildControl : Control
+ {
+ public Size MeasureSize { get; private set; }
+
+ protected override Size MeasureOverride(Size availableSize)
+ {
+ MeasureSize = availableSize;
+ return base.MeasureOverride(availableSize);
+ }
+ }
}
}
diff --git a/tests/Avalonia.Layout.UnitTests/LayoutManagerTests.cs b/tests/Avalonia.Layout.UnitTests/LayoutManagerTests.cs
index 3f20c9a76a..0165b91844 100644
--- a/tests/Avalonia.Layout.UnitTests/LayoutManagerTests.cs
+++ b/tests/Avalonia.Layout.UnitTests/LayoutManagerTests.cs
@@ -374,5 +374,38 @@ namespace Avalonia.Layout.UnitTests
Assert.True(control.Measured);
Assert.True(control.IsMeasureValid);
}
+
+ [Fact]
+ public void Calling_ExecuteLayoutPass_From_ExecuteInitialLayoutPass_Does_Not_Break_Measure()
+ {
+ // Test for issue #3550.
+ var control = new LayoutTestControl();
+ var root = new LayoutTestRoot { Child = control };
+ var count = 0;
+
+ root.LayoutManager.ExecuteInitialLayoutPass(root);
+ control.Measured = false;
+
+ control.DoMeasureOverride = (l, s) =>
+ {
+ if (count++ == 0)
+ {
+ control.InvalidateMeasure();
+ root.LayoutManager.ExecuteLayoutPass();
+ return new Size(100, 100);
+ }
+ else
+ {
+ return new Size(200, 200);
+ }
+ };
+
+ root.InvalidateMeasure();
+ control.InvalidateMeasure();
+ root.LayoutManager.ExecuteInitialLayoutPass(root);
+
+ Assert.Equal(new Size(200, 200), control.Bounds.Size);
+ Assert.Equal(new Size(200, 200), control.DesiredSize);
+ }
}
}
diff --git a/tests/Avalonia.LeakTests/ControlTests.cs b/tests/Avalonia.LeakTests/ControlTests.cs
index 389b3c8df8..3b288dbf66 100644
--- a/tests/Avalonia.LeakTests/ControlTests.cs
+++ b/tests/Avalonia.LeakTests/ControlTests.cs
@@ -8,8 +8,10 @@ using Avalonia.Controls;
using Avalonia.Controls.Templates;
using Avalonia.Diagnostics;
using Avalonia.Layout;
+using Avalonia.Media;
using Avalonia.Platform;
using Avalonia.Rendering;
+using Avalonia.Styling;
using Avalonia.UnitTests;
using Avalonia.VisualTree;
using JetBrains.dotMemoryUnit;
@@ -370,6 +372,56 @@ namespace Avalonia.LeakTests
}
}
+ [Fact]
+ public void Control_With_Style_RenderTransform_Is_Freed()
+ {
+ // # Issue #3545
+ using (Start())
+ {
+ Func run = () =>
+ {
+ var window = new Window
+ {
+ Styles =
+ {
+ new Style(x => x.OfType