diff --git a/native/Avalonia.Native/inc/comimpl.h b/native/Avalonia.Native/inc/comimpl.h index 0ff64b7215..47b0a3c5f2 100644 --- a/native/Avalonia.Native/inc/comimpl.h +++ b/native/Avalonia.Native/inc/comimpl.h @@ -8,8 +8,109 @@ #include +/** + START_COM_CALL causes AddRef to be called at the beggining of a function. + When a function is exited, it causes ReleaseRef to be called. + This ensures that the object cannot be destroyed whilst the function is running. + For example: Window Show is called, which triggers an event, and user calls Close inside the event + causing the refcount to reach 0, and the object to be destroyed. Function then continues and this pointer + will now be invalid. + + START_COM_CALL protects against this scenario. + */ +#define START_COM_CALL auto r = this->UnknownSelf() + __IID_DEF(IUnknown, 0, 0, 0, C0, 00, 00, 00, 00, 00, 00, 46); +template +class ComPtr +{ +private: + TInterface* _obj; +public: + ComPtr() + { + _obj = 0; + } + + ComPtr(TInterface* pObj) + { + _obj = 0; + + if (pObj) + { + _obj = pObj; + _obj->AddRef(); + } + } + + ComPtr(const ComPtr& ptr) + { + _obj = 0; + + if (ptr._obj) + { + _obj = ptr._obj; + _obj->AddRef(); + } + + } + + ComPtr& operator=(ComPtr other) + { + if(_obj != NULL) + _obj->Release(); + _obj = other._obj; + if(_obj != NULL) + _obj->AddRef(); + return *this; + } + + ~ComPtr() + { + if (_obj) + { + _obj->Release(); + _obj = 0; + } + } + + TInterface* getRaw() + { + return _obj; + } + + TInterface* getRetainedReference() + { + if(_obj == NULL) + return NULL; + _obj->AddRef(); + return _obj; + } + + TInterface** getPPV() + { + return &_obj; + } + + operator TInterface*() const + { + return _obj; + } + TInterface& operator*() const + { + return *_obj; + } + TInterface** operator&() + { + return &_obj; + } + TInterface* operator->() const + { + return _obj; + } +}; + class ComObject : public virtual IUnknown { private: @@ -58,6 +159,12 @@ public: _refCount++; return S_OK; } + +protected: + ComPtr UnknownSelf() + { + return this; + } }; @@ -104,94 +211,5 @@ public: virtual ~ComSingleObject(){} }; -template -class ComPtr -{ -private: - TInterface* _obj; -public: - ComPtr() - { - _obj = 0; - } - - ComPtr(TInterface* pObj) - { - _obj = 0; - - if (pObj) - { - _obj = pObj; - _obj->AddRef(); - } - } - - ComPtr(const ComPtr& ptr) - { - _obj = 0; - - if (ptr._obj) - { - _obj = ptr._obj; - _obj->AddRef(); - } - - } - - ComPtr& operator=(ComPtr other) - { - if(_obj != NULL) - _obj->Release(); - _obj = other._obj; - if(_obj != NULL) - _obj->AddRef(); - return *this; - } - - ~ComPtr() - { - if (_obj) - { - _obj->Release(); - _obj = 0; - } - } - - TInterface* getRaw() - { - return _obj; - } - - TInterface* getRetainedReference() - { - if(_obj == NULL) - return NULL; - _obj->AddRef(); - return _obj; - } - - TInterface** getPPV() - { - return &_obj; - } - - operator TInterface*() const - { - return _obj; - } - TInterface& operator*() const - { - return *_obj; - } - TInterface** operator&() - { - return &_obj; - } - TInterface* operator->() const - { - return _obj; - } -}; - #endif // COMIMPL_H_INCLUDED #pragma clang diagnostic pop diff --git a/native/Avalonia.Native/src/OSX/AvnString.mm b/native/Avalonia.Native/src/OSX/AvnString.mm index 001cf151d8..cd0e2cdf94 100644 --- a/native/Avalonia.Native/src/OSX/AvnString.mm +++ b/native/Avalonia.Native/src/OSX/AvnString.mm @@ -43,6 +43,8 @@ public: virtual HRESULT Pointer(void**retOut) override { + START_COM_CALL; + @autoreleasepool { if(retOut == nullptr) @@ -58,14 +60,19 @@ public: virtual HRESULT Length(int*retOut) override { - if(retOut == nullptr) + START_COM_CALL; + + @autoreleasepool { - return E_POINTER; + if(retOut == nullptr) + { + return E_POINTER; + } + + *retOut = _length; + + return S_OK; } - - *retOut = _length; - - return S_OK; } }; @@ -109,10 +116,15 @@ public: virtual HRESULT Get(unsigned int index, IAvnString**ppv) override { - if(_list.size() <= index) - return E_INVALIDARG; - *ppv = _list[index].getRetainedReference(); - return S_OK; + START_COM_CALL; + + @autoreleasepool + { + if(_list.size() <= index) + return E_INVALIDARG; + *ppv = _list[index].getRetainedReference(); + return S_OK; + } } }; diff --git a/native/Avalonia.Native/src/OSX/Screens.mm b/native/Avalonia.Native/src/OSX/Screens.mm index 10f698ff45..b9c75ed742 100644 --- a/native/Avalonia.Native/src/OSX/Screens.mm +++ b/native/Avalonia.Native/src/OSX/Screens.mm @@ -8,6 +8,8 @@ class Screens : public ComSingleObject public: virtual HRESULT GetScreenCount (int* ret) override { + START_COM_CALL; + @autoreleasepool { *ret = (int)[NSScreen screens].count; @@ -18,6 +20,8 @@ public: virtual HRESULT GetScreen (int index, AvnScreen* ret) override { + START_COM_CALL; + @autoreleasepool { if(index < 0 || index >= [NSScreen screens].count) diff --git a/native/Avalonia.Native/src/OSX/cgl.mm b/native/Avalonia.Native/src/OSX/cgl.mm index a9d94cdf04..085037978e 100644 --- a/native/Avalonia.Native/src/OSX/cgl.mm +++ b/native/Avalonia.Native/src/OSX/cgl.mm @@ -69,6 +69,8 @@ public: virtual HRESULT LegacyMakeCurrent() override { + START_COM_CALL; + if(CGLSetCurrentContext(Context) != 0) return E_FAIL; return S_OK; @@ -76,6 +78,8 @@ public: virtual HRESULT MakeCurrent(IUnknown** ppv) override { + START_COM_CALL; + CGLContextObj saved = CGLGetCurrentContext(); CGLLockContext(Context); if(CGLSetCurrentContext(Context) != 0) @@ -128,6 +132,8 @@ public: virtual HRESULT CreateContext(IAvnGlContext* share, IAvnGlContext**ppv) override { + START_COM_CALL; + CGLContextObj shareContext = nil; if(share != nil) { @@ -144,6 +150,8 @@ public: virtual HRESULT WrapContext(void* native, IAvnGlContext**ppv) override { + START_COM_CALL; + if(native == nil) return E_INVALIDARG; *ppv = new AvnGlContext((CGLContextObj) native); diff --git a/native/Avalonia.Native/src/OSX/clipboard.mm b/native/Avalonia.Native/src/OSX/clipboard.mm index f148374759..9966971b73 100644 --- a/native/Avalonia.Native/src/OSX/clipboard.mm +++ b/native/Avalonia.Native/src/OSX/clipboard.mm @@ -25,6 +25,8 @@ public: virtual HRESULT GetText (char* type, IAvnString**ppv) override { + START_COM_CALL; + @autoreleasepool { if(ppv == nullptr) @@ -42,6 +44,8 @@ public: virtual HRESULT GetStrings(char* type, IAvnStringArray**ppv) override { + START_COM_CALL; + @autoreleasepool { *ppv= nil; @@ -69,56 +73,71 @@ public: virtual HRESULT SetText (char* type, char* utf8String) override { - Clear(); + START_COM_CALL; + @autoreleasepool { + Clear(); + auto string = [NSString stringWithUTF8String:(const char*)utf8String]; auto typeString = [NSString stringWithUTF8String:(const char*)type]; if(_item == nil) [_pb setString: string forType: typeString]; else [_item setString: string forType:typeString]; - } - return S_OK; + return S_OK; + } } virtual HRESULT SetBytes(char* type, void* bytes, int len) override { - auto typeString = [NSString stringWithUTF8String:(const char*)type]; - auto data = [NSData dataWithBytes:bytes length:len]; - if(_item == nil) - [_pb setData:data forType:typeString]; - else - [_item setData:data forType:typeString]; - return S_OK; + START_COM_CALL; + + @autoreleasepool + { + auto typeString = [NSString stringWithUTF8String:(const char*)type]; + auto data = [NSData dataWithBytes:bytes length:len]; + if(_item == nil) + [_pb setData:data forType:typeString]; + else + [_item setData:data forType:typeString]; + return S_OK; + } } virtual HRESULT GetBytes(char* type, IAvnString**ppv) override { - *ppv = nil; - auto typeString = [NSString stringWithUTF8String:(const char*)type]; - NSData*data; - @try + START_COM_CALL; + + @autoreleasepool { - if(_item) - data = [_item dataForType:typeString]; - else - data = [_pb dataForType:typeString]; - if(data == nil) + *ppv = nil; + auto typeString = [NSString stringWithUTF8String:(const char*)type]; + NSData*data; + @try + { + if(_item) + data = [_item dataForType:typeString]; + else + data = [_pb dataForType:typeString]; + if(data == nil) + return E_FAIL; + } + @catch(NSException* e) + { return E_FAIL; + } + *ppv = CreateByteArray((void*)data.bytes, (int)data.length); + return S_OK; } - @catch(NSException* e) - { - return E_FAIL; - } - *ppv = CreateByteArray((void*)data.bytes, (int)data.length); - return S_OK; } virtual HRESULT Clear() override { + START_COM_CALL; + @autoreleasepool { if(_item != nil) @@ -128,15 +147,20 @@ public: [_pb clearContents]; [_pb setString:@"" forType:NSPasteboardTypeString]; } - } - return S_OK; + return S_OK; + } } virtual HRESULT ObtainFormats(IAvnStringArray** ppv) override { - *ppv = CreateAvnStringArray(_item == nil ? [_pb types] : [_item types]); - return S_OK; + START_COM_CALL; + + @autoreleasepool + { + *ppv = CreateAvnStringArray(_item == nil ? [_pb types] : [_item types]); + return S_OK; + } } }; diff --git a/native/Avalonia.Native/src/OSX/controlhost.mm b/native/Avalonia.Native/src/OSX/controlhost.mm index 5ee2344ac7..f8e9a3b6d1 100644 --- a/native/Avalonia.Native/src/OSX/controlhost.mm +++ b/native/Avalonia.Native/src/OSX/controlhost.mm @@ -16,11 +16,16 @@ public: virtual HRESULT CreateDefaultChild(void* parent, void** retOut) override { - NSView* view = [NSView new]; - [view setWantsLayer: true]; + START_COM_CALL; - *retOut = (__bridge_retained void*)view; - return S_OK; + @autoreleasepool + { + NSView* view = [NSView new]; + [view setWantsLayer: true]; + + *retOut = (__bridge_retained void*)view; + return S_OK; + } }; virtual IAvnNativeControlHostTopLevelAttachment* CreateAttachment() override @@ -69,32 +74,42 @@ public: virtual HRESULT InitializeWithChildHandle(void* child) override { - if(_child != nil) - return E_FAIL; - _child = (__bridge NSView*)child; - if(_child == nil) - return E_FAIL; - [_holder addSubview:_child]; - [_child setHidden: false]; - return S_OK; + START_COM_CALL; + + @autoreleasepool + { + if(_child != nil) + return E_FAIL; + _child = (__bridge NSView*)child; + if(_child == nil) + return E_FAIL; + [_holder addSubview:_child]; + [_child setHidden: false]; + return S_OK; + } }; virtual HRESULT AttachTo(IAvnNativeControlHost* host) override { - if(host == nil) - { - [_holder removeFromSuperview]; - [_holder setHidden: true]; - } - else + START_COM_CALL; + + @autoreleasepool { - AvnNativeControlHost* chost = dynamic_cast(host); - if(chost == nil || chost->View == nil) - return E_FAIL; - [_holder setHidden:true]; - [chost->View addSubview:_holder]; + if(host == nil) + { + [_holder removeFromSuperview]; + [_holder setHidden: true]; + } + else + { + AvnNativeControlHost* chost = dynamic_cast(host); + if(chost == nil || chost->View == nil) + return E_FAIL; + [_holder setHidden:true]; + [chost->View addSubview:_holder]; + } + return S_OK; } - return S_OK; }; virtual void ShowInBounds(float x, float y, float width, float height) override diff --git a/native/Avalonia.Native/src/OSX/cursor.mm b/native/Avalonia.Native/src/OSX/cursor.mm index 1732d6e71f..dc38294a18 100644 --- a/native/Avalonia.Native/src/OSX/cursor.mm +++ b/native/Avalonia.Native/src/OSX/cursor.mm @@ -53,36 +53,46 @@ public: virtual HRESULT GetCursor (AvnStandardCursorType cursorType, IAvnCursor** retOut) override { - *retOut = s_cursorMap[cursorType]; + START_COM_CALL; - if(*retOut != nullptr) + @autoreleasepool { - (*retOut)->AddRef(); - } + *retOut = s_cursorMap[cursorType]; - return S_OK; + if(*retOut != nullptr) + { + (*retOut)->AddRef(); + } + + return S_OK; + } } virtual HRESULT CreateCustomCursor (void* bitmapData, size_t length, AvnPixelSize hotPixel, IAvnCursor** retOut) override { - if(bitmapData == nullptr || retOut == nullptr) + START_COM_CALL; + + @autoreleasepool { - return E_POINTER; + if(bitmapData == nullptr || retOut == nullptr) + { + return E_POINTER; + } + + NSData *imageData = [NSData dataWithBytes:bitmapData length:length]; + NSImage *image = [[NSImage alloc] initWithData:imageData]; + + + NSPoint hotSpot; + hotSpot.x = hotPixel.Width; + hotSpot.y = hotPixel.Height; + + *retOut = new Cursor([[NSCursor new] initWithImage: image hotSpot: hotSpot]); + + (*retOut)->AddRef(); + + return S_OK; } - - NSData *imageData = [NSData dataWithBytes:bitmapData length:length]; - NSImage *image = [[NSImage alloc] initWithData:imageData]; - - - NSPoint hotSpot; - hotSpot.x = hotPixel.Width; - hotSpot.y = hotPixel.Height; - - *retOut = new Cursor([[NSCursor new] initWithImage: image hotSpot: hotSpot]); - - (*retOut)->AddRef(); - - return S_OK; } }; diff --git a/native/Avalonia.Native/src/OSX/main.mm b/native/Avalonia.Native/src/OSX/main.mm index aaaf381b26..3e152a6125 100644 --- a/native/Avalonia.Native/src/OSX/main.mm +++ b/native/Avalonia.Native/src/OSX/main.mm @@ -107,27 +107,42 @@ public: virtual HRESULT SetApplicationTitle(char* utf8String) override { - auto appTitle = [NSString stringWithUTF8String: utf8String]; + START_COM_CALL; - [[NSProcessInfo processInfo] setProcessName:appTitle]; - - - SetProcessName(appTitle); - - return S_OK; + @autoreleasepool + { + auto appTitle = [NSString stringWithUTF8String: utf8String]; + + [[NSProcessInfo processInfo] setProcessName:appTitle]; + + + SetProcessName(appTitle); + + return S_OK; + } } virtual HRESULT SetShowInDock(int show) override { - AvnDesiredActivationPolicy = show - ? NSApplicationActivationPolicyRegular : NSApplicationActivationPolicyAccessory; - return S_OK; + START_COM_CALL; + + @autoreleasepool + { + AvnDesiredActivationPolicy = show + ? NSApplicationActivationPolicyRegular : NSApplicationActivationPolicyAccessory; + return S_OK; + } } virtual HRESULT SetDisableDefaultApplicationMenuItems (bool enabled) override { - SetAutoGenerateDefaultAppMenuItems(!enabled); - return S_OK; + START_COM_CALL; + + @autoreleasepool + { + SetAutoGenerateDefaultAppMenuItems(!enabled); + return S_OK; + } } }; @@ -165,6 +180,8 @@ public: FORWARD_IUNKNOWN() virtual HRESULT Initialize(IAvnGCHandleDeallocatorCallback* deallocator, IAvnApplicationEvents* events) override { + START_COM_CALL; + _deallocator = deallocator; @autoreleasepool{ [[ThreadingInitializer new] do]; @@ -180,89 +197,154 @@ public: virtual HRESULT CreateWindow(IAvnWindowEvents* cb, IAvnGlContext* gl, IAvnWindow** ppv) override { - if(cb == nullptr || ppv == nullptr) - return E_POINTER; - *ppv = CreateAvnWindow(cb, gl); - return S_OK; + START_COM_CALL; + + @autoreleasepool + { + if(cb == nullptr || ppv == nullptr) + return E_POINTER; + *ppv = CreateAvnWindow(cb, gl); + return S_OK; + } }; virtual HRESULT CreatePopup(IAvnWindowEvents* cb, IAvnGlContext* gl, IAvnPopup** ppv) override { - if(cb == nullptr || ppv == nullptr) - return E_POINTER; + START_COM_CALL; - *ppv = CreateAvnPopup(cb, gl); - return S_OK; + @autoreleasepool + { + if(cb == nullptr || ppv == nullptr) + return E_POINTER; + + *ppv = CreateAvnPopup(cb, gl); + return S_OK; + } } virtual HRESULT CreatePlatformThreadingInterface(IAvnPlatformThreadingInterface** ppv) override { - *ppv = CreatePlatformThreading(); - return S_OK; + START_COM_CALL; + + @autoreleasepool + { + *ppv = CreatePlatformThreading(); + return S_OK; + } } virtual HRESULT CreateSystemDialogs(IAvnSystemDialogs** ppv) override { - *ppv = ::CreateSystemDialogs(); - return S_OK; + START_COM_CALL; + + @autoreleasepool + { + *ppv = ::CreateSystemDialogs(); + return S_OK; + } } virtual HRESULT CreateScreens (IAvnScreens** ppv) override { - *ppv = ::CreateScreens (); - return S_OK; + START_COM_CALL; + + @autoreleasepool + { + *ppv = ::CreateScreens (); + return S_OK; + } } virtual HRESULT CreateClipboard(IAvnClipboard** ppv) override { - *ppv = ::CreateClipboard (nil, nil); - return S_OK; + START_COM_CALL; + + @autoreleasepool + { + *ppv = ::CreateClipboard (nil, nil); + return S_OK; + } } virtual HRESULT CreateDndClipboard(IAvnClipboard** ppv) override { - *ppv = ::CreateClipboard (nil, [NSPasteboardItem new]); - return S_OK; + START_COM_CALL; + + @autoreleasepool + { + *ppv = ::CreateClipboard (nil, [NSPasteboardItem new]); + return S_OK; + } } virtual HRESULT CreateCursorFactory(IAvnCursorFactory** ppv) override { - *ppv = ::CreateCursorFactory(); - return S_OK; + START_COM_CALL; + + @autoreleasepool + { + *ppv = ::CreateCursorFactory(); + return S_OK; + } } virtual HRESULT ObtainGlDisplay(IAvnGlDisplay** ppv) override { - auto rv = ::GetGlDisplay(); - if(rv == NULL) - return E_FAIL; - rv->AddRef(); - *ppv = rv; - return S_OK; + START_COM_CALL; + + @autoreleasepool + { + auto rv = ::GetGlDisplay(); + if(rv == NULL) + return E_FAIL; + rv->AddRef(); + *ppv = rv; + return S_OK; + } } virtual HRESULT CreateMenu (IAvnMenuEvents* cb, IAvnMenu** ppv) override { - *ppv = ::CreateAppMenu(cb); - return S_OK; + START_COM_CALL; + + @autoreleasepool + { + *ppv = ::CreateAppMenu(cb); + return S_OK; + } } virtual HRESULT CreateMenuItem (IAvnMenuItem** ppv) override { - *ppv = ::CreateAppMenuItem(); - return S_OK; + START_COM_CALL; + + @autoreleasepool + { + *ppv = ::CreateAppMenuItem(); + return S_OK; + } } virtual HRESULT CreateMenuItemSeparator (IAvnMenuItem** ppv) override { - *ppv = ::CreateAppMenuItemSeparator(); - return S_OK; + START_COM_CALL; + + @autoreleasepool + { + *ppv = ::CreateAppMenuItemSeparator(); + return S_OK; + } } virtual HRESULT SetAppMenu (IAvnMenu* appMenu) override { - ::SetAppMenu(s_appTitle, appMenu); - return S_OK; + START_COM_CALL; + + @autoreleasepool + { + ::SetAppMenu(s_appTitle, appMenu); + return S_OK; + } } }; diff --git a/native/Avalonia.Native/src/OSX/menu.mm b/native/Avalonia.Native/src/OSX/menu.mm index b9a95e7b3c..38f8c2a7cb 100644 --- a/native/Avalonia.Native/src/OSX/menu.mm +++ b/native/Avalonia.Native/src/OSX/menu.mm @@ -95,6 +95,8 @@ NSMenuItem* AvnAppMenuItem::GetNative() HRESULT AvnAppMenuItem::SetSubMenu (IAvnMenu* menu) { + START_COM_CALL; + @autoreleasepool { if(menu != nullptr) @@ -114,6 +116,8 @@ HRESULT AvnAppMenuItem::SetSubMenu (IAvnMenu* menu) HRESULT AvnAppMenuItem::SetTitle (char* utf8String) { + START_COM_CALL; + @autoreleasepool { if (utf8String != nullptr) @@ -128,6 +132,8 @@ HRESULT AvnAppMenuItem::SetTitle (char* utf8String) HRESULT AvnAppMenuItem::SetGesture (AvnKey key, AvnInputModifiers modifiers) { + START_COM_CALL; + @autoreleasepool { if(key != AvnKeyNone) @@ -183,6 +189,8 @@ HRESULT AvnAppMenuItem::SetGesture (AvnKey key, AvnInputModifiers modifiers) HRESULT AvnAppMenuItem::SetAction (IAvnPredicateCallback* predicate, IAvnActionCallback* callback) { + START_COM_CALL; + @autoreleasepool { _predicate = predicate; @@ -193,6 +201,8 @@ HRESULT AvnAppMenuItem::SetAction (IAvnPredicateCallback* predicate, IAvnActionC HRESULT AvnAppMenuItem::SetIsChecked (bool isChecked) { + START_COM_CALL; + @autoreleasepool { [_native setState:(isChecked && _isCheckable ? NSOnState : NSOffState)]; @@ -202,6 +212,8 @@ HRESULT AvnAppMenuItem::SetIsChecked (bool isChecked) HRESULT AvnAppMenuItem::SetToggleType(AvnMenuItemToggleType toggleType) { + START_COM_CALL; + @autoreleasepool { switch(toggleType) @@ -231,6 +243,8 @@ HRESULT AvnAppMenuItem::SetToggleType(AvnMenuItemToggleType toggleType) HRESULT AvnAppMenuItem::SetIcon(void *data, size_t length) { + START_COM_CALL; + @autoreleasepool { if(data != nullptr) @@ -317,6 +331,8 @@ void AvnAppMenu::RaiseClosed() HRESULT AvnAppMenu::InsertItem(int index, IAvnMenuItem *item) { + START_COM_CALL; + @autoreleasepool { if([_native hasGlobalMenuItem]) @@ -337,6 +353,8 @@ HRESULT AvnAppMenu::InsertItem(int index, IAvnMenuItem *item) HRESULT AvnAppMenu::RemoveItem (IAvnMenuItem* item) { + START_COM_CALL; + @autoreleasepool { auto avnMenuItem = dynamic_cast(item); @@ -352,6 +370,8 @@ HRESULT AvnAppMenu::RemoveItem (IAvnMenuItem* item) HRESULT AvnAppMenu::SetTitle (char* utf8String) { + START_COM_CALL; + @autoreleasepool { if (utf8String != nullptr) @@ -365,6 +385,8 @@ HRESULT AvnAppMenu::SetTitle (char* utf8String) HRESULT AvnAppMenu::Clear() { + START_COM_CALL; + @autoreleasepool { [_native removeAllItems]; diff --git a/native/Avalonia.Native/src/OSX/platformthreading.mm b/native/Avalonia.Native/src/OSX/platformthreading.mm index e83bf53331..6d5bd4aa02 100644 --- a/native/Avalonia.Native/src/OSX/platformthreading.mm +++ b/native/Avalonia.Native/src/OSX/platformthreading.mm @@ -114,6 +114,8 @@ public: virtual HRESULT RunLoop(IAvnLoopCancellation* cancel) override { + START_COM_CALL; + auto can = dynamic_cast(cancel); if(can->Cancelled) return S_OK; diff --git a/native/Avalonia.Native/src/OSX/rendertarget.mm b/native/Avalonia.Native/src/OSX/rendertarget.mm index b2d4341bb9..dc5c24e41e 100644 --- a/native/Avalonia.Native/src/OSX/rendertarget.mm +++ b/native/Avalonia.Native/src/OSX/rendertarget.mm @@ -247,6 +247,8 @@ public: virtual HRESULT GetPixelSize(AvnPixelSize* ret) override { + START_COM_CALL; + if(!_surface) return E_FAIL; *ret = _surface->size; @@ -255,6 +257,8 @@ public: virtual HRESULT GetScaling(double* ret) override { + START_COM_CALL; + if(!_surface) return E_FAIL; *ret = _surface->scale; @@ -281,6 +285,8 @@ public: virtual HRESULT BeginDrawing(IAvnGlSurfaceRenderingSession** ret) override { + START_COM_CALL; + ComPtr releaseContext; @synchronized (_target->lock) { if(_target->surface == nil) diff --git a/native/Avalonia.Native/src/OSX/window.mm b/native/Avalonia.Native/src/OSX/window.mm index c0936356d2..4be1419f78 100644 --- a/native/Avalonia.Native/src/OSX/window.mm +++ b/native/Avalonia.Native/src/OSX/window.mm @@ -54,6 +54,8 @@ public: virtual HRESULT ObtainNSWindowHandle(void** ret) override { + START_COM_CALL; + if (ret == nullptr) { return E_POINTER; @@ -66,6 +68,8 @@ public: virtual HRESULT ObtainNSWindowHandleRetained(void** ret) override { + START_COM_CALL; + if (ret == nullptr) { return E_POINTER; @@ -78,6 +82,8 @@ public: virtual HRESULT ObtainNSViewHandle(void** ret) override { + START_COM_CALL; + if (ret == nullptr) { return E_POINTER; @@ -90,6 +96,8 @@ public: virtual HRESULT ObtainNSViewHandleRetained(void** ret) override { + START_COM_CALL; + if (ret == nullptr) { return E_POINTER; @@ -107,23 +115,27 @@ public: virtual HRESULT Show(bool activate) override { + START_COM_CALL; + @autoreleasepool { SetPosition(lastPositionSet); UpdateStyle(); [Window setContentView: StandardContainer]; + [Window setTitle:_lastTitle]; if(ShouldTakeFocusOnShow() && activate) { + [Window orderFront: Window]; [Window makeKeyAndOrderFront:Window]; + [Window makeFirstResponder:View]; [NSApp activateIgnoringOtherApps:YES]; } else { [Window orderFront: Window]; } - [Window setTitle:_lastTitle]; _shown = true; @@ -138,6 +150,8 @@ public: virtual HRESULT Hide () override { + START_COM_CALL; + @autoreleasepool { if(Window != nullptr) @@ -152,6 +166,8 @@ public: virtual HRESULT Activate () override { + START_COM_CALL; + @autoreleasepool { if(Window != nullptr) @@ -166,6 +182,8 @@ public: virtual HRESULT SetTopMost (bool value) override { + START_COM_CALL; + @autoreleasepool { [Window setLevel: value ? NSFloatingWindowLevel : NSNormalWindowLevel]; @@ -176,11 +194,16 @@ public: virtual HRESULT Close() override { + START_COM_CALL; + @autoreleasepool { if (Window != nullptr) { - [Window close]; + auto window = Window; + Window = nullptr; + + [window close]; } return S_OK; @@ -189,6 +212,8 @@ public: virtual HRESULT GetClientSize(AvnSize* ret) override { + START_COM_CALL; + @autoreleasepool { if(ret == nullptr) @@ -202,8 +227,25 @@ public: } } + virtual HRESULT GetFrameSize(AvnSize* ret) override + { + @autoreleasepool + { + if(ret == nullptr) + return E_POINTER; + + auto frame = [Window frame]; + ret->Width = frame.size.width; + ret->Height = frame.size.height; + + return S_OK; + } + } + virtual HRESULT GetScaling (double* ret) override { + START_COM_CALL; + @autoreleasepool { if(ret == nullptr) @@ -222,6 +264,8 @@ public: virtual HRESULT SetMinMaxSize (AvnSize minSize, AvnSize maxSize) override { + START_COM_CALL; + @autoreleasepool { [Window setMinSize: ToNSSize(minSize)]; @@ -233,6 +277,8 @@ public: virtual HRESULT Resize(double x, double y) override { + START_COM_CALL; + @autoreleasepool { auto maxSize = [Window maxSize]; @@ -272,6 +318,8 @@ public: virtual HRESULT Invalidate (AvnRect rect) override { + START_COM_CALL; + @autoreleasepool { [View setNeedsDisplayInRect:[View frame]]; @@ -282,6 +330,8 @@ public: virtual HRESULT SetMainMenu(IAvnMenu* menu) override { + START_COM_CALL; + _mainMenu = menu; auto nativeMenu = dynamic_cast(menu); @@ -300,6 +350,8 @@ public: virtual HRESULT BeginMoveDrag () override { + START_COM_CALL; + @autoreleasepool { auto lastEvent = [View lastMouseDownEvent]; @@ -317,11 +369,15 @@ public: virtual HRESULT BeginResizeDrag (AvnWindowEdge edge) override { + START_COM_CALL; + return S_OK; } virtual HRESULT GetPosition (AvnPoint* ret) override { + START_COM_CALL; + @autoreleasepool { if(ret == nullptr) @@ -342,6 +398,8 @@ public: virtual HRESULT SetPosition (AvnPoint point) override { + START_COM_CALL; + @autoreleasepool { lastPositionSet = point; @@ -353,6 +411,8 @@ public: virtual HRESULT PointToClient (AvnPoint point, AvnPoint* ret) override { + START_COM_CALL; + @autoreleasepool { if(ret == nullptr) @@ -371,6 +431,8 @@ public: virtual HRESULT PointToScreen (AvnPoint point, AvnPoint* ret) override { + START_COM_CALL; + @autoreleasepool { if(ret == nullptr) @@ -388,12 +450,16 @@ public: virtual HRESULT ThreadSafeSetSwRenderedFrame(AvnFramebuffer* fb, IUnknown* dispose) override { + START_COM_CALL; + [View setSwRenderedFrame: fb dispose: dispose]; return S_OK; } virtual HRESULT SetCursor(IAvnCursor* cursor) override { + START_COM_CALL; + @autoreleasepool { Cursor* avnCursor = dynamic_cast(cursor); @@ -423,6 +489,8 @@ public: virtual HRESULT CreateGlRenderTarget(IAvnGlSurfaceRenderTarget** ppv) override { + START_COM_CALL; + if(View == NULL) return E_FAIL; *ppv = [renderTarget createSurfaceRenderTarget]; @@ -431,6 +499,8 @@ public: virtual HRESULT CreateNativeControlHost(IAvnNativeControlHost** retOut) override { + START_COM_CALL; + if(View == NULL) return E_FAIL; *retOut = ::CreateNativeControlHost(View); @@ -439,6 +509,8 @@ public: virtual HRESULT SetBlurEnabled (bool enable) override { + START_COM_CALL; + [StandardContainer ShowBlur:enable]; return S_OK; @@ -448,6 +520,8 @@ public: IAvnClipboard* clipboard, IAvnDndResultCallback* cb, void* sourceHandle) override { + START_COM_CALL; + auto item = TryGetPasteboardItem(clipboard); [item setString:@"" forType:GetAvnCustomDataType()]; if(item == nil) @@ -549,6 +623,11 @@ private: void HideOrShowTrafficLights () { + if (Window == nil) + { + return; + } + for (id subview in Window.contentView.superview.subviews) { if ([subview isKindOfClass:NSClassFromString(@"NSTitlebarContainerView")]) { NSView *titlebarView = [subview subviews][0]; @@ -575,8 +654,10 @@ private: virtual HRESULT Show (bool activate) override { + START_COM_CALL; + @autoreleasepool - { + { WindowBaseImpl::Show(activate); HideOrShowTrafficLights(); @@ -587,6 +668,8 @@ private: virtual HRESULT SetEnabled (bool enable) override { + START_COM_CALL; + @autoreleasepool { [Window setEnabled:enable]; @@ -596,6 +679,8 @@ private: virtual HRESULT SetParent (IAvnWindow* parent) override { + START_COM_CALL; + @autoreleasepool { if(parent == nullptr) @@ -707,6 +792,8 @@ private: virtual HRESULT SetCanResize(bool value) override { + START_COM_CALL; + @autoreleasepool { _canResize = value; @@ -717,6 +804,8 @@ private: virtual HRESULT SetDecorations(SystemDecorations value) override { + START_COM_CALL; + @autoreleasepool { auto currentWindowState = _lastWindowState; @@ -782,6 +871,8 @@ private: virtual HRESULT SetTitle (char* utf8title) override { + START_COM_CALL; + @autoreleasepool { _lastTitle = [NSString stringWithUTF8String:(const char*)utf8title]; @@ -793,6 +884,8 @@ private: virtual HRESULT SetTitleBarColor(AvnColor color) override { + START_COM_CALL; + @autoreleasepool { float a = (float)color.Alpha / 255.0f; @@ -822,6 +915,8 @@ private: virtual HRESULT GetWindowState (AvnWindowState*ret) override { + START_COM_CALL; + @autoreleasepool { if(ret == nullptr) @@ -855,86 +950,111 @@ private: virtual HRESULT TakeFocusFromChildren () override { - if(Window == nil) - return S_OK; - if([Window isKeyWindow]) - [Window makeFirstResponder: View]; + START_COM_CALL; - return S_OK; + @autoreleasepool + { + if(Window == nil) + return S_OK; + if([Window isKeyWindow]) + [Window makeFirstResponder: View]; + + return S_OK; + } } virtual HRESULT SetExtendClientArea (bool enable) override { - _isClientAreaExtended = enable; + START_COM_CALL; - if(enable) + @autoreleasepool { - Window.titleVisibility = NSWindowTitleHidden; + _isClientAreaExtended = enable; - [Window setTitlebarAppearsTransparent:true]; - - auto wantsTitleBar = (_extendClientHints & AvnSystemChrome) || (_extendClientHints & AvnPreferSystemChrome); - - if (wantsTitleBar) - { - [StandardContainer ShowTitleBar:true]; - } - else - { - [StandardContainer ShowTitleBar:false]; - } - - if(_extendClientHints & AvnOSXThickTitleBar) + if(enable) { - Window.toolbar = [NSToolbar new]; - Window.toolbar.showsBaselineSeparator = false; + Window.titleVisibility = NSWindowTitleHidden; + + [Window setTitlebarAppearsTransparent:true]; + + auto wantsTitleBar = (_extendClientHints & AvnSystemChrome) || (_extendClientHints & AvnPreferSystemChrome); + + if (wantsTitleBar) + { + [StandardContainer ShowTitleBar:true]; + } + else + { + [StandardContainer ShowTitleBar:false]; + } + + if(_extendClientHints & AvnOSXThickTitleBar) + { + Window.toolbar = [NSToolbar new]; + Window.toolbar.showsBaselineSeparator = false; + } + else + { + Window.toolbar = nullptr; + } } else { + Window.titleVisibility = NSWindowTitleVisible; Window.toolbar = nullptr; + [Window setTitlebarAppearsTransparent:false]; + View.layer.zPosition = 0; } + + [Window setIsExtended:enable]; + + HideOrShowTrafficLights(); + + UpdateStyle(); + + return S_OK; } - else - { - Window.titleVisibility = NSWindowTitleVisible; - Window.toolbar = nullptr; - [Window setTitlebarAppearsTransparent:false]; - View.layer.zPosition = 0; - } - - [Window setIsExtended:enable]; - - HideOrShowTrafficLights(); - - UpdateStyle(); - - return S_OK; } virtual HRESULT SetExtendClientAreaHints (AvnExtendClientAreaChromeHints hints) override { - _extendClientHints = hints; + START_COM_CALL; - SetExtendClientArea(_isClientAreaExtended); - return S_OK; + @autoreleasepool + { + _extendClientHints = hints; + + SetExtendClientArea(_isClientAreaExtended); + return S_OK; + } } virtual HRESULT GetExtendTitleBarHeight (double*ret) override { - if(ret == nullptr) + START_COM_CALL; + + @autoreleasepool { - return E_POINTER; + if(ret == nullptr) + { + return E_POINTER; + } + + *ret = [Window getExtendedTitleBarHeight]; + + return S_OK; } - - *ret = [Window getExtendedTitleBarHeight]; - - return S_OK; } virtual HRESULT SetExtendTitleBarHeight (double value) override { - [StandardContainer SetTitleBarHeightHint:value]; - return S_OK; + START_COM_CALL; + + @autoreleasepool + { + [StandardContainer SetTitleBarHeightHint:value]; + return S_OK; + } } void EnterFullScreenMode () @@ -963,8 +1083,15 @@ private: virtual HRESULT SetWindowState (AvnWindowState state) override { + START_COM_CALL; + @autoreleasepool { + if(Window == nullptr) + { + return S_OK; + } + if(_actualWindowState == state) { return S_OK; @@ -1911,7 +2038,7 @@ NSArray* AllLoopModes = [NSArray arrayWithObjects: NSDefaultRunLoopMode, NSEvent { if(![self windowShouldClose:self]) return; } - + [self close]; } diff --git a/samples/Sandbox/Program.cs b/samples/Sandbox/Program.cs index 1e74105196..52321b46a9 100644 --- a/samples/Sandbox/Program.cs +++ b/samples/Sandbox/Program.cs @@ -4,12 +4,12 @@ namespace Sandbox { public class Program { - static void Main(string[] args) - { + static void Main(string[] args) => BuildAvaloniaApp() + .StartWithClassicDesktopLifetime(args); + + public static AppBuilder BuildAvaloniaApp() => AppBuilder.Configure() .UsePlatformDetect() - .LogToTrace() - .StartWithClassicDesktopLifetime(args); - } + .LogToTrace(); } } diff --git a/src/Android/Avalonia.Android/Platform/SkiaPlatform/TopLevelImpl.cs b/src/Android/Avalonia.Android/Platform/SkiaPlatform/TopLevelImpl.cs index 60b772a183..a72742580c 100644 --- a/src/Android/Avalonia.Android/Platform/SkiaPlatform/TopLevelImpl.cs +++ b/src/Android/Avalonia.Android/Platform/SkiaPlatform/TopLevelImpl.cs @@ -55,6 +55,8 @@ namespace Avalonia.Android.Platform.SkiaPlatform public virtual Size ClientSize => Size.ToSize(RenderScaling); + public Size? FrameSize => null; + public IMouseDevice MouseDevice { get; } = new MouseDevice(); public Action Closed { get; set; } diff --git a/src/Avalonia.Controls/ApiCompatBaseline.txt b/src/Avalonia.Controls/ApiCompatBaseline.txt index 46c12ebd39..1abf4fd6ff 100644 --- a/src/Avalonia.Controls/ApiCompatBaseline.txt +++ b/src/Avalonia.Controls/ApiCompatBaseline.txt @@ -12,4 +12,6 @@ EnumValuesMustMatch : Enum value 'Avalonia.Platform.ExtendClientAreaChromeHints InterfacesShouldHaveSameMembers : Interface member 'public void Avalonia.Platform.ITopLevelImpl.SetCursor(Avalonia.Platform.ICursorImpl)' is present in the implementation but not in the contract. InterfacesShouldHaveSameMembers : Interface member 'public void Avalonia.Platform.ITopLevelImpl.SetCursor(Avalonia.Platform.IPlatformHandle)' is present in the contract but not in the implementation. MembersMustExist : Member 'public void Avalonia.Platform.ITopLevelImpl.SetCursor(Avalonia.Platform.IPlatformHandle)' does not exist in the implementation but it does exist in the contract. -Total Issues: 13 +InterfacesShouldHaveSameMembers : Interface member 'public System.Nullable Avalonia.Platform.ITopLevelImpl.FrameSize' is present in the implementation but not in the contract. +InterfacesShouldHaveSameMembers : Interface member 'public System.Nullable Avalonia.Platform.ITopLevelImpl.FrameSize.get()' is present in the implementation but not in the contract. +Total Issues: 15 diff --git a/src/Avalonia.Controls/Embedding/Offscreen/OffscreenTopLevelImpl.cs b/src/Avalonia.Controls/Embedding/Offscreen/OffscreenTopLevelImpl.cs index ca0e9d48b8..83470f161d 100644 --- a/src/Avalonia.Controls/Embedding/Offscreen/OffscreenTopLevelImpl.cs +++ b/src/Avalonia.Controls/Embedding/Offscreen/OffscreenTopLevelImpl.cs @@ -35,6 +35,8 @@ namespace Avalonia.Controls.Embedding.Offscreen } } + public Size? FrameSize => null; + public double RenderScaling { get { return _scaling; } diff --git a/src/Avalonia.Controls/Platform/ITopLevelImpl.cs b/src/Avalonia.Controls/Platform/ITopLevelImpl.cs index 09f38042a1..6e53233898 100644 --- a/src/Avalonia.Controls/Platform/ITopLevelImpl.cs +++ b/src/Avalonia.Controls/Platform/ITopLevelImpl.cs @@ -22,6 +22,11 @@ namespace Avalonia.Platform /// Size ClientSize { get; } + /// + /// Gets the total size of the toplevel, excluding shadows. + /// + Size? FrameSize { get; } + /// /// Gets the scaling factor for the toplevel. This is used for rendering. /// diff --git a/src/Avalonia.Controls/Shapes/Arc.cs b/src/Avalonia.Controls/Shapes/Arc.cs new file mode 100644 index 0000000000..5ebb321f9b --- /dev/null +++ b/src/Avalonia.Controls/Shapes/Arc.cs @@ -0,0 +1,103 @@ +using System; +using Avalonia.Media; + +namespace Avalonia.Controls.Shapes +{ + public class Arc : Shape + { + /// + /// Defines the property. + /// + public static readonly StyledProperty StartAngleProperty = + AvaloniaProperty.Register(nameof(StartAngle), 0.0); + + /// + /// Defines the property. + /// + public static readonly StyledProperty SweepAngleProperty = + AvaloniaProperty.Register(nameof(SweepAngle), 0.0); + + static Arc() + { + StrokeThicknessProperty.OverrideDefaultValue(1); + AffectsGeometry(BoundsProperty, StrokeThicknessProperty, StartAngleProperty, SweepAngleProperty); + } + + /// + /// Gets or sets the angle at which the arc starts, in degrees. + /// + public double StartAngle + { + get => GetValue(StartAngleProperty); + set => SetValue(StartAngleProperty, value); + } + + /// + /// Gets or sets the angle, in degrees, added to the defining where the arc ends. + /// A positive value is clockwise, negative is counter-clockwise. + /// + public double SweepAngle + { + get => GetValue(SweepAngleProperty); + set => SetValue(SweepAngleProperty, value); + } + + protected override Geometry CreateDefiningGeometry() + { + var angle1 = DegreesToRad(StartAngle); + var angle2 = angle1 + DegreesToRad(SweepAngle); + + var startAngle = Math.Min(angle1, angle2); + var sweepAngle = Math.Max(angle1, angle2); + + var normStart = RadToNormRad(startAngle); + var normEnd = RadToNormRad(sweepAngle); + + var rect = new Rect(Bounds.Size); + + if ((normStart == normEnd) && (startAngle != sweepAngle)) // Complete ring. + { + return new EllipseGeometry(rect.Deflate(StrokeThickness / 2)); + } + else if (SweepAngle == 0) + { + return new StreamGeometry(); + } + else // Partial arc. + { + var deflatedRect = rect.Deflate(StrokeThickness / 2); + + var centerX = rect.Center.X; + var centerY = rect.Center.Y; + + var radiusX = deflatedRect.Width / 2; + var radiusY = deflatedRect.Height / 2; + + var angleGap = RadToNormRad(sweepAngle - startAngle); + + var startPoint = GetRingPoint(radiusX, radiusY, centerX, centerY, startAngle); + var endPoint = GetRingPoint(radiusX, radiusY, centerX, centerY, sweepAngle); + + var arcGeometry = new StreamGeometry(); + + using (var ctx = arcGeometry.Open()) + { + ctx.BeginFigure(startPoint, false); + ctx.ArcTo(endPoint, new Size(radiusX, radiusY), angleGap, angleGap >= Math.PI, + SweepDirection.Clockwise); + ctx.EndFigure(false); + } + + return arcGeometry; + } + } + + static double DegreesToRad(double inAngle) => + inAngle * Math.PI / 180; + + static double RadToNormRad(double inAngle) => ((inAngle % (Math.PI * 2)) + (Math.PI * 2)) % (Math.PI * 2); + + static Point GetRingPoint(double radiusX, double radiusY, double centerX, double centerY, double angle) => + new Point((radiusX * Math.Cos(angle)) + centerX, (radiusY * Math.Sin(angle)) + centerY); + } +} diff --git a/src/Avalonia.Controls/TopLevel.cs b/src/Avalonia.Controls/TopLevel.cs index 7a92836ddf..7028dca769 100644 --- a/src/Avalonia.Controls/TopLevel.cs +++ b/src/Avalonia.Controls/TopLevel.cs @@ -42,6 +42,12 @@ namespace Avalonia.Controls public static readonly DirectProperty ClientSizeProperty = AvaloniaProperty.RegisterDirect(nameof(ClientSize), o => o.ClientSize); + /// + /// Defines the property. + /// + public static readonly DirectProperty FrameSizeProperty = + AvaloniaProperty.RegisterDirect(nameof(FrameSize), o => o.FrameSize); + /// /// Defines the property. /// @@ -74,6 +80,7 @@ namespace Avalonia.Controls private readonly IPlatformRenderInterface _renderInterface; private readonly IGlobalStyles _globalStyles; private Size _clientSize; + private Size? _frameSize; private WindowTransparencyLevel _actualTransparencyLevel; private ILayoutManager _layoutManager; private Border _transparencyFallbackBorder; @@ -161,6 +168,7 @@ namespace Avalonia.Controls styler?.ApplyStyles(this); ClientSize = impl.ClientSize; + FrameSize = impl.FrameSize; this.GetObservable(PointerOverElementProperty) .Select( @@ -197,6 +205,15 @@ namespace Avalonia.Controls protected set { SetAndRaise(ClientSizeProperty, ref _clientSize, value); } } + /// + /// Gets or sets the total size of the window. + /// + public Size? FrameSize + { + get { return _frameSize; } + protected set { SetAndRaise(FrameSizeProperty, ref _frameSize, value); } + } + /// /// Gets or sets the that the TopLevel should use when possible. /// @@ -366,6 +383,7 @@ namespace Avalonia.Controls protected virtual void HandleResized(Size clientSize) { ClientSize = clientSize; + FrameSize = PlatformImpl.FrameSize; Width = clientSize.Width; Height = clientSize.Height; LayoutManager.ExecuteLayoutPass(); diff --git a/src/Avalonia.Controls/TreeView.cs b/src/Avalonia.Controls/TreeView.cs index 3afbbd944c..78cd22ae32 100644 --- a/src/Avalonia.Controls/TreeView.cs +++ b/src/Avalonia.Controls/TreeView.cs @@ -166,11 +166,9 @@ namespace Avalonia.Controls { item.IsExpanded = true; - var panel = item.Presenter.Panel; - - if (panel != null) + if (item.Presenter?.Panel != null) { - foreach (var child in panel.Children) + foreach (var child in item.Presenter.Panel.Children) { if (child is TreeViewItem treeViewItem) { diff --git a/src/Avalonia.Controls/WindowBase.cs b/src/Avalonia.Controls/WindowBase.cs index cdcb499e98..2b31cef8bd 100644 --- a/src/Avalonia.Controls/WindowBase.cs +++ b/src/Avalonia.Controls/WindowBase.cs @@ -222,6 +222,7 @@ namespace Avalonia.Controls protected override void HandleResized(Size clientSize) { ClientSize = clientSize; + FrameSize = PlatformImpl.FrameSize; LayoutManager.ExecuteLayoutPass(); Renderer?.Resized(clientSize); } diff --git a/src/Avalonia.DesignerSupport/Remote/Stubs.cs b/src/Avalonia.DesignerSupport/Remote/Stubs.cs index eedfc52d9d..c8203686f9 100644 --- a/src/Avalonia.DesignerSupport/Remote/Stubs.cs +++ b/src/Avalonia.DesignerSupport/Remote/Stubs.cs @@ -21,6 +21,7 @@ namespace Avalonia.DesignerSupport.Remote public IPlatformHandle Handle { get; } public Size MaxAutoSizeHint { get; } public Size ClientSize { get; } + public Size? FrameSize => null; public double RenderScaling { get; } = 1.0; public double DesktopScaling => 1.0; public IEnumerable Surfaces { get; } diff --git a/src/Avalonia.Headless/HeadlessWindowImpl.cs b/src/Avalonia.Headless/HeadlessWindowImpl.cs index af522f3e36..7f4b9face4 100644 --- a/src/Avalonia.Headless/HeadlessWindowImpl.cs +++ b/src/Avalonia.Headless/HeadlessWindowImpl.cs @@ -41,6 +41,7 @@ namespace Avalonia.Headless } public Size ClientSize { get; set; } + public Size? FrameSize => null; public double RenderScaling { get; } = 1; public double DesktopScaling => RenderScaling; public IEnumerable Surfaces { get; } diff --git a/src/Avalonia.Layout/ElementManager.cs b/src/Avalonia.Layout/ElementManager.cs index cb13deb15f..3f106708e6 100644 --- a/src/Avalonia.Layout/ElementManager.cs +++ b/src/Avalonia.Layout/ElementManager.cs @@ -325,7 +325,10 @@ namespace Avalonia.Layout break; case NotifyCollectionChangedAction.Move: - throw new NotImplementedException(); + int size = args.OldItems != null ? args.OldItems.Count : 1; + OnItemsRemoved(args.OldStartingIndex, size); + OnItemsAdded(args.NewStartingIndex, size); + break; } } } diff --git a/src/Avalonia.Native/WindowImplBase.cs b/src/Avalonia.Native/WindowImplBase.cs index f716464d14..ced9cea3a8 100644 --- a/src/Avalonia.Native/WindowImplBase.cs +++ b/src/Avalonia.Native/WindowImplBase.cs @@ -104,6 +104,20 @@ namespace Avalonia.Native } } + public Size? FrameSize + { + get + { + if (_native != null) + { + var s = _native.FrameSize; + return new Size(s.Width, s.Height); + } + + return default; + } + } + public IEnumerable Surfaces => new[] { (_gpu ? _glSurface : (object)null), this diff --git a/src/Avalonia.Native/avn.idl b/src/Avalonia.Native/avn.idl index adcbeb2d3a..89e20463d8 100644 --- a/src/Avalonia.Native/avn.idl +++ b/src/Avalonia.Native/avn.idl @@ -435,6 +435,7 @@ interface IAvnWindowBase : IUnknown HRESULT Close(); HRESULT Activate(); HRESULT GetClientSize(AvnSize*ret); + HRESULT GetFrameSize(AvnSize*ret); HRESULT GetScaling(double*ret); HRESULT SetMinMaxSize(AvnSize minSize, AvnSize maxSize); HRESULT Resize(double width, double height); diff --git a/src/Avalonia.X11/X11Window.cs b/src/Avalonia.X11/X11Window.cs index 37260aa78b..bcb655245a 100644 --- a/src/Avalonia.X11/X11Window.cs +++ b/src/Avalonia.X11/X11Window.cs @@ -296,6 +296,30 @@ namespace Avalonia.X11 public Size ClientSize => new Size(_realSize.Width / RenderScaling, _realSize.Height / RenderScaling); + public Size? FrameSize + { + get + { + XGetWindowProperty(_x11.Display, _handle, _x11.Atoms._NET_FRAME_EXTENTS, IntPtr.Zero, + new IntPtr(4), false, (IntPtr)Atom.AnyPropertyType, out var _, + out var _, out var nitems, out var _, out var prop); + + if (nitems.ToInt64() != 4) + { + // Window hasn't been mapped by the WM yet, so can't get the extents. + return null; + } + + var data = (IntPtr*)prop.ToPointer(); + var extents = new Thickness(data[0].ToInt32(), data[2].ToInt32(), data[1].ToInt32(), data[3].ToInt32()); + XFree(prop); + + return new Size( + (_realSize.Width + extents.Left + extents.Right) / RenderScaling, + (_realSize.Height + extents.Top + extents.Bottom) / RenderScaling); + } + } + public double RenderScaling { get @@ -588,6 +612,13 @@ namespace Avalonia.X11 private void OnPropertyChange(IntPtr atom, bool hasValue) { + if (atom == _x11.Atoms._NET_FRAME_EXTENTS) + { + // Occurs once the window has been mapped, which is the earliest the extents + // can be retrieved, so invoke event to force update of TopLevel.FrameSize. + Resized.Invoke(ClientSize); + } + if (atom == _x11.Atoms._NET_WM_STATE) { WindowState state = WindowState.Normal; diff --git a/src/Linux/Avalonia.LinuxFramebuffer/FramebufferToplevelImpl.cs b/src/Linux/Avalonia.LinuxFramebuffer/FramebufferToplevelImpl.cs index ac2fd40c54..5a1da9058a 100644 --- a/src/Linux/Avalonia.LinuxFramebuffer/FramebufferToplevelImpl.cs +++ b/src/Linux/Avalonia.LinuxFramebuffer/FramebufferToplevelImpl.cs @@ -61,6 +61,7 @@ namespace Avalonia.LinuxFramebuffer } public Size ClientSize => ScaledSize; + public Size? FrameSize => null; public IMouseDevice MouseDevice => new MouseDevice(); public IPopupImpl CreatePopup() => null; diff --git a/src/Windows/Avalonia.Win32.Interop/Wpf/WpfTopLevelImpl.cs b/src/Windows/Avalonia.Win32.Interop/Wpf/WpfTopLevelImpl.cs index fe1d625efb..73e46b9e13 100644 --- a/src/Windows/Avalonia.Win32.Interop/Wpf/WpfTopLevelImpl.cs +++ b/src/Windows/Avalonia.Win32.Interop/Wpf/WpfTopLevelImpl.cs @@ -100,6 +100,7 @@ namespace Avalonia.Win32.Interop.Wpf } Size ITopLevelImpl.ClientSize => _finalSize; + Size? ITopLevelImpl.FrameSize => null; IMouseDevice ITopLevelImpl.MouseDevice => _mouse; double ITopLevelImpl.RenderScaling => PresentationSource.FromVisual(this)?.CompositionTarget?.TransformToDevice.M11 ?? 1; diff --git a/src/Windows/Avalonia.Win32/Interop/UnmanagedMethods.cs b/src/Windows/Avalonia.Win32/Interop/UnmanagedMethods.cs index c137926e4c..ad409810b8 100644 --- a/src/Windows/Avalonia.Win32/Interop/UnmanagedMethods.cs +++ b/src/Windows/Avalonia.Win32/Interop/UnmanagedMethods.cs @@ -745,6 +745,26 @@ namespace Avalonia.Win32.Interop WM_DISPATCH_WORK_ITEM = WM_USER, } + public enum DwmWindowAttribute : uint + { + DWMWA_NCRENDERING_ENABLED = 1, + DWMWA_NCRENDERING_POLICY, + DWMWA_TRANSITIONS_FORCEDISABLED, + DWMWA_ALLOW_NCPAINT, + DWMWA_CAPTION_BUTTON_BOUNDS, + DWMWA_NONCLIENT_RTL_LAYOUT, + DWMWA_FORCE_ICONIC_REPRESENTATION, + DWMWA_FLIP3D_POLICY, + DWMWA_EXTENDED_FRAME_BOUNDS, + DWMWA_HAS_ICONIC_BITMAP, + DWMWA_DISALLOW_PEEK, + DWMWA_EXCLUDED_FROM_PEEK, + DWMWA_CLOAK, + DWMWA_CLOAKED, + DWMWA_FREEZE_REPRESENTATION, + DWMWA_LAST + }; + public enum MapVirtualKeyMapTypes : uint { MAPVK_VK_TO_VSC = 0x00, @@ -1388,6 +1408,9 @@ namespace Avalonia.Win32.Interop [DllImport("dwmapi.dll")] public static extern int DwmExtendFrameIntoClientArea(IntPtr hwnd, ref MARGINS margins); + [DllImport("dwmapi.dll")] + public static extern int DwmGetWindowAttribute(IntPtr hwnd, int dwAttribute, out RECT pvAttribute, int cbAttribute); + [DllImport("dwmapi.dll")] public static extern int DwmIsCompositionEnabled(out bool enabled); diff --git a/src/Windows/Avalonia.Win32/WindowImpl.cs b/src/Windows/Avalonia.Win32/WindowImpl.cs index 3a3342fd14..646a6f5739 100644 --- a/src/Windows/Avalonia.Win32/WindowImpl.cs +++ b/src/Windows/Avalonia.Win32/WindowImpl.cs @@ -213,6 +213,21 @@ namespace Avalonia.Win32 } } + public Size? FrameSize + { + get + { + if (DwmIsCompositionEnabled(out var compositionEnabled) != 0 || !compositionEnabled) + { + GetWindowRect(_hwnd, out var rcWindow); + return new Size(rcWindow.Width, rcWindow.Height) / RenderScaling; + } + + DwmGetWindowAttribute(_hwnd, (int)DwmWindowAttribute.DWMWA_EXTENDED_FRAME_BOUNDS, out var rect, Marshal.SizeOf(typeof(RECT))); + return new Size(rect.Width, rect.Height) / RenderScaling; + } + } + public IScreenImpl Screen { get; } public IPlatformHandle Handle { get; private set; } diff --git a/src/iOS/Avalonia.iOS/AvaloniaView.cs b/src/iOS/Avalonia.iOS/AvaloniaView.cs index 36a70ea410..0371a7759a 100644 --- a/src/iOS/Avalonia.iOS/AvaloniaView.cs +++ b/src/iOS/Avalonia.iOS/AvaloniaView.cs @@ -91,6 +91,7 @@ namespace Avalonia.iOS } public Size ClientSize => new Size(_view.Bounds.Width, _view.Bounds.Height); + public Size? FrameSize => null; public double RenderScaling => _view.ContentScaleFactor; public IEnumerable Surfaces { get; set; } public Action Input { get; set; }