Browse Source

Merge branch 'master' into compile-time-brush-parsing

pull/6031/head
Jumar Macato 5 years ago
committed by GitHub
parent
commit
ad5007cf42
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 196
      native/Avalonia.Native/inc/comimpl.h
  2. 32
      native/Avalonia.Native/src/OSX/AvnString.mm
  3. 4
      native/Avalonia.Native/src/OSX/Screens.mm
  4. 8
      native/Avalonia.Native/src/OSX/cgl.mm
  5. 82
      native/Avalonia.Native/src/OSX/clipboard.mm
  6. 63
      native/Avalonia.Native/src/OSX/controlhost.mm
  7. 52
      native/Avalonia.Native/src/OSX/cursor.mm
  8. 174
      native/Avalonia.Native/src/OSX/main.mm
  9. 22
      native/Avalonia.Native/src/OSX/menu.mm
  10. 2
      native/Avalonia.Native/src/OSX/platformthreading.mm
  11. 6
      native/Avalonia.Native/src/OSX/rendertarget.mm
  12. 235
      native/Avalonia.Native/src/OSX/window.mm
  13. 10
      samples/Sandbox/Program.cs
  14. 2
      src/Android/Avalonia.Android/Platform/SkiaPlatform/TopLevelImpl.cs
  15. 30
      src/Avalonia.Animation/Animation.cs
  16. 8
      src/Avalonia.Animation/AnimationInstance`1.cs
  17. 6
      src/Avalonia.Animation/ApiCompatBaseline.txt
  18. 3
      src/Avalonia.Animation/IAnimation.cs
  19. 24
      src/Avalonia.Base/AvaloniaObjectExtensions.cs
  20. 3
      src/Avalonia.Controls.DataGrid/DataGrid.cs
  21. 2
      src/Avalonia.Controls.DataGrid/DataGridRows.cs
  22. 5
      src/Avalonia.Controls/ApiCompatBaseline.txt
  23. 31
      src/Avalonia.Controls/Application.cs
  24. 2
      src/Avalonia.Controls/ComboBox.cs
  25. 2
      src/Avalonia.Controls/Embedding/Offscreen/OffscreenTopLevelImpl.cs
  26. 21
      src/Avalonia.Controls/Expander.cs
  27. 32
      src/Avalonia.Controls/Flyouts/FlyoutBase.cs
  28. 1
      src/Avalonia.Controls/Grid.cs
  29. 6
      src/Avalonia.Controls/GridLength.cs
  30. 1
      src/Avalonia.Controls/Menu.cs
  31. 6
      src/Avalonia.Controls/MenuItem.cs
  32. 5
      src/Avalonia.Controls/Platform/ITopLevelImpl.cs
  33. 2
      src/Avalonia.Controls/Presenters/CarouselPresenter.cs
  34. 2
      src/Avalonia.Controls/Presenters/ScrollContentPresenter.cs
  35. 22
      src/Avalonia.Controls/Primitives/AdornerLayer.cs
  36. 1
      src/Avalonia.Controls/Repeater/ViewportManager.cs
  37. 1
      src/Avalonia.Controls/Repeater/VirtualizationInfo.cs
  38. 8
      src/Avalonia.Controls/RowDefinitions.cs
  39. 36
      src/Avalonia.Controls/ScrollViewer.cs
  40. 103
      src/Avalonia.Controls/Shapes/Arc.cs
  41. 2
      src/Avalonia.Controls/SplitView.cs
  42. 2
      src/Avalonia.Controls/TextBox.cs
  43. 18
      src/Avalonia.Controls/TopLevel.cs
  44. 6
      src/Avalonia.Controls/TreeView.cs
  45. 39
      src/Avalonia.Controls/Window.cs
  46. 1
      src/Avalonia.Controls/WindowBase.cs
  47. 1
      src/Avalonia.DesignerSupport/Remote/Stubs.cs
  48. 1
      src/Avalonia.Headless/HeadlessWindowImpl.cs
  49. 5
      src/Avalonia.Layout/ElementManager.cs
  50. 14
      src/Avalonia.Native/WindowImplBase.cs
  51. 1
      src/Avalonia.Native/avn.idl
  52. 13
      src/Avalonia.ReactiveUI/TransitioningContentControl.cs
  53. 3
      src/Avalonia.Themes.Default/ListBox.xaml
  54. 3
      src/Avalonia.Themes.Default/TextBox.xaml
  55. 3
      src/Avalonia.Themes.Default/TreeView.xaml
  56. 6
      src/Avalonia.Themes.Fluent/Controls/ComboBox.xaml
  57. 3
      src/Avalonia.Themes.Fluent/Controls/ListBox.xaml
  58. 3
      src/Avalonia.Themes.Fluent/Controls/TextBox.xaml
  59. 3
      src/Avalonia.Themes.Fluent/Controls/TreeView.xaml
  60. 21
      src/Avalonia.Visuals/Animation/CompositePageTransition.cs
  61. 65
      src/Avalonia.Visuals/Animation/CrossFade.cs
  62. 6
      src/Avalonia.Visuals/Animation/IPageTransition.cs
  63. 30
      src/Avalonia.Visuals/Animation/PageSlide.cs
  64. 9
      src/Avalonia.Visuals/ApiCompatBaseline.txt
  65. 41
      src/Avalonia.Visuals/Media/BoxShadow.cs
  66. 20
      src/Avalonia.Visuals/Media/BoxShadows.cs
  67. 4
      src/Avalonia.Visuals/Rendering/SceneGraph/BitmapBlendModeNode.cs
  68. 1
      src/Avalonia.X11/TransparencyHelper.cs
  69. 32
      src/Avalonia.X11/X11Window.cs
  70. 2
      src/Linux/Avalonia.LinuxFramebuffer/FramebufferToplevelImpl.cs
  71. 1
      src/Linux/Avalonia.LinuxFramebuffer/Input/EvDev/EvDevTouchScreen.cs
  72. 5
      src/Linux/Avalonia.LinuxFramebuffer/LinuxFramebufferPlatform.cs
  73. 2
      src/Linux/Avalonia.LinuxFramebuffer/LockedFramebuffer.cs
  74. 1
      src/Linux/Avalonia.LinuxFramebuffer/Output/DrmOutput.cs
  75. 88
      src/Linux/Avalonia.LinuxFramebuffer/Output/FbdevOutput.cs
  76. 49
      src/Skia/Avalonia.Skia/DrawingContextImpl.cs
  77. 4
      src/Skia/Avalonia.Skia/FormattedTextImpl.cs
  78. 3
      src/Skia/Avalonia.Skia/Gpu/ISkiaGpu.cs
  79. 4
      src/Windows/Avalonia.Direct2D1/Media/AvaloniaTextRenderer.cs
  80. 32
      src/Windows/Avalonia.Direct2D1/Media/DrawingContextImpl.cs
  81. 7
      src/Windows/Avalonia.Direct2D1/Media/LinearGradientBrushImpl.cs
  82. 11
      src/Windows/Avalonia.Direct2D1/Media/RadialGradientBrushImpl.cs
  83. 1
      src/Windows/Avalonia.Win32.Interop/Wpf/WpfTopLevelImpl.cs
  84. 23
      src/Windows/Avalonia.Win32/Interop/UnmanagedMethods.cs
  85. 1
      src/Windows/Avalonia.Win32/WinRT/Composition/WinUICompositorConnection.cs
  86. 15
      src/Windows/Avalonia.Win32/WindowImpl.cs
  87. 1
      src/iOS/Avalonia.iOS/AvaloniaView.cs
  88. 268
      tests/Avalonia.Animation.UnitTests/AnimationIterationTests.cs
  89. 61
      tests/Avalonia.Controls.UnitTests/ComboBoxTests.cs
  90. 2
      tests/Avalonia.Controls.UnitTests/TreeViewTests.cs
  91. 136
      tests/Avalonia.Controls.UnitTests/WindowTests.cs
  92. 40
      tests/Avalonia.RenderTests/Media/ConicGradientBrushTests.cs
  93. 36
      tests/Avalonia.RenderTests/Media/LinearGradientBrushTests.cs
  94. 34
      tests/Avalonia.RenderTests/Media/RadialGradientBrushTests.cs
  95. BIN
      tests/TestFiles/Direct2D1/Media/ConicGradientBrush/ConicGradientBrush_DrawingContext.expected.png
  96. BIN
      tests/TestFiles/Direct2D1/Media/LinearGradientBrush/LinearGradientBrush_DrawingContext.expected.png
  97. BIN
      tests/TestFiles/Direct2D1/Media/RadialGradientBrush/RadialGradientBrush_DrawingContext.expected.png
  98. BIN
      tests/TestFiles/Skia/Media/ConicGradientBrush/ConicGradientBrush_DrawingContext.expected.png
  99. BIN
      tests/TestFiles/Skia/Media/LinearGradientBrush/LinearGradientBrush_DrawingContext.expected.png
  100. BIN
      tests/TestFiles/Skia/Media/RadialGradientBrush/RadialGradientBrush_DrawingContext.expected.png

196
native/Avalonia.Native/inc/comimpl.h

@ -8,8 +8,109 @@
#include <cstring>
/**
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 TInterface>
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<IUnknown> UnknownSelf()
{
return this;
}
};
@ -104,94 +211,5 @@ public:
virtual ~ComSingleObject(){}
};
template<class TInterface>
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

32
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;
}
}
};

4
native/Avalonia.Native/src/OSX/Screens.mm

@ -8,6 +8,8 @@ class Screens : public ComSingleObject<IAvnScreens, &IID_IAvnScreens>
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)

8
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);

82
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;
}
}
};

63
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<AvnNativeControlHost*>(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<AvnNativeControlHost*>(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

52
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;
}
};

174
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;
}
}
};

22
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<AvnAppMenuItem*>(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];

2
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<LoopCancellation*>(cancel);
if(can->Cancelled)
return S_OK;

6
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<IUnknown> releaseContext;
@synchronized (_target->lock) {
if(_target->surface == nil)

235
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<AvnAppMenu*>(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*>(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];
}

10
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<App>()
.UsePlatformDetect()
.LogToTrace()
.StartWithClassicDesktopLifetime(args);
}
.LogToTrace();
}
}

2
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; }

30
src/Avalonia.Animation/Animation.cs

@ -3,10 +3,11 @@ using System.Collections.Generic;
using System.Linq;
using System.Reactive.Disposables;
using System.Reactive.Linq;
using System.Threading;
using System.Threading.Tasks;
using Avalonia.Animation.Animators;
using Avalonia.Animation.Easings;
using Avalonia.Collections;
using Avalonia.Data;
using Avalonia.Metadata;
@ -319,7 +320,7 @@ namespace Avalonia.Animation
return (newAnimatorInstances, subscriptions);
}
/// <inheritdocs/>
/// <inheritdoc/>
public IDisposable Apply(Animatable control, IClock clock, IObservable<bool> match, Action onComplete)
{
var (animators, subscriptions) = InterpretKeyframes(control);
@ -344,25 +345,40 @@ namespace Avalonia.Animation
if (onComplete != null)
{
Task.WhenAll(completionTasks).ContinueWith(_ => onComplete());
Task.WhenAll(completionTasks).ContinueWith(
(_, state) => ((Action)state).Invoke(),
onComplete);
}
}
return new CompositeDisposable(subscriptions);
}
/// <inheritdocs/>
public Task RunAsync(Animatable control, IClock clock = null)
/// <inheritdoc/>
public Task RunAsync(Animatable control, IClock clock = null, CancellationToken cancellationToken = default)
{
if (cancellationToken.IsCancellationRequested)
{
return Task.CompletedTask;
}
var run = new TaskCompletionSource<object>();
if (this.IterationCount == IterationCount.Infinite)
run.SetException(new InvalidOperationException("Looping animations must not use the Run method."));
IDisposable subscriptions = null;
IDisposable subscriptions = null, cancellation = null;
subscriptions = this.Apply(control, clock, Observable.Return(true), () =>
{
run.SetResult(null);
run.TrySetResult(null);
subscriptions?.Dispose();
cancellation?.Dispose();
});
cancellation = cancellationToken.Register(() =>
{
run.TrySetResult(null);
subscriptions?.Dispose();
cancellation?.Dispose();
});
return run.Task;

8
src/Avalonia.Animation/AnimationInstance`1.cs

@ -37,6 +37,7 @@ namespace Avalonia.Animation
private IDisposable _timerSub;
private readonly IClock _baseClock;
private IClock _clock;
private EventHandler<AvaloniaPropertyChangedEventArgs> _propertyChangedDelegate;
public AnimationInstance(Animation animation, Animatable control, Animator<T> animator, IClock baseClock, Action OnComplete, Func<double, T, T> Interpolator)
{
@ -46,9 +47,6 @@ namespace Avalonia.Animation
_onCompleteAction = OnComplete;
_interpolator = Interpolator;
_baseClock = baseClock;
control.PropertyChanged += ControlPropertyChanged;
UpdateNeutralValue();
FetchProperties();
}
@ -82,6 +80,7 @@ namespace Avalonia.Animation
// Animation may have been stopped before it has finished.
ApplyFinalFill();
_targetControl.PropertyChanged -= _propertyChangedDelegate;
_timerSub?.Dispose();
_clock.PlayState = PlayState.Stop;
}
@ -90,6 +89,9 @@ namespace Avalonia.Animation
{
_clock = new Clock(_baseClock);
_timerSub = _clock.Subscribe(Step);
_propertyChangedDelegate ??= ControlPropertyChanged;
_targetControl.PropertyChanged += _propertyChangedDelegate;
UpdateNeutralValue();
}
public void Step(TimeSpan frameTick)

6
src/Avalonia.Animation/ApiCompatBaseline.txt

@ -0,0 +1,6 @@
Compat issues with assembly Avalonia.Animation:
MembersMustExist : Member 'public System.Threading.Tasks.Task Avalonia.Animation.Animation.RunAsync(Avalonia.Animation.Animatable, Avalonia.Animation.IClock)' does not exist in the implementation but it does exist in the contract.
InterfacesShouldHaveSameMembers : Interface member 'public System.Threading.Tasks.Task Avalonia.Animation.IAnimation.RunAsync(Avalonia.Animation.Animatable, Avalonia.Animation.IClock)' is present in the contract but not in the implementation.
MembersMustExist : Member 'public System.Threading.Tasks.Task Avalonia.Animation.IAnimation.RunAsync(Avalonia.Animation.Animatable, Avalonia.Animation.IClock)' does not exist in the implementation but it does exist in the contract.
InterfacesShouldHaveSameMembers : Interface member 'public System.Threading.Tasks.Task Avalonia.Animation.IAnimation.RunAsync(Avalonia.Animation.Animatable, Avalonia.Animation.IClock, System.Threading.CancellationToken)' is present in the implementation but not in the contract.
Total Issues: 4

3
src/Avalonia.Animation/IAnimation.cs

@ -1,4 +1,5 @@
using System;
using System.Threading;
using System.Threading.Tasks;
namespace Avalonia.Animation
@ -16,6 +17,6 @@ namespace Avalonia.Animation
/// <summary>
/// Run the animation on the specified control.
/// </summary>
Task RunAsync(Animatable control, IClock clock);
Task RunAsync(Animatable control, IClock clock, CancellationToken cancellationToken = default);
}
}

24
src/Avalonia.Base/AvaloniaObjectExtensions.cs

@ -585,6 +585,30 @@ namespace Avalonia
});
}
/// <summary>
/// Subscribes to a property changed notifications for changes that originate from a
/// <typeparamref name="TTarget"/>.
/// </summary>
/// <typeparam name="TTarget">The type of the property change sender.</typeparam>
/// /// <typeparam name="TValue">The type of the property..</typeparam>
/// <param name="observable">The property changed observable.</param>
/// <param name="action">
/// The method to call. The parameters are the sender and the event args.
/// </param>
/// <returns>A disposable that can be used to terminate the subscription.</returns>
public static IDisposable AddClassHandler<TTarget, TValue>(
this IObservable<AvaloniaPropertyChangedEventArgs<TValue>> observable,
Action<TTarget, AvaloniaPropertyChangedEventArgs<TValue>> action) where TTarget : AvaloniaObject
{
return observable.Subscribe(e =>
{
if (e.Sender is TTarget target)
{
action(target, e);
}
});
}
/// <summary>
/// Subscribes to a property changed notifications for changes that originate from a
/// <typeparamref name="TTarget"/>.

3
src/Avalonia.Controls.DataGrid/DataGrid.cs

@ -75,7 +75,6 @@ namespace Avalonia.Controls
private const double DATAGRID_defaultMinColumnWidth = 20;
private const double DATAGRID_defaultMaxColumnWidth = double.PositiveInfinity;
private List<Exception> _validationErrors;
private List<Exception> _bindingValidationErrors;
private IDisposable _validationSubscription;
@ -102,7 +101,6 @@ namespace Avalonia.Controls
private bool _areHandlersSuspended;
private bool _autoSizingColumns;
private IndexToValueTable<bool> _collapsedSlotsTable;
private DataGridCellCoordinates _currentCellCoordinates;
private Control _clickedElement;
// used to store the current column during a Reset
@ -141,7 +139,6 @@ namespace Avalonia.Controls
private DataGridSelectedItemsCollection _selectedItems;
private bool _temporarilyResetCurrentCell;
private object _uneditedValue; // Represents the original current cell value at the time it enters editing mode.
private ICellEditBinding _currentCellEditBinding;
// An approximation of the sum of the heights in pixels of the scrolling rows preceding
// the first displayed scrolling row. Since the scrolled off rows are discarded, the grid

2
src/Avalonia.Controls.DataGrid/DataGridRows.cs

@ -425,7 +425,7 @@ namespace Avalonia.Controls
UpdateDisplayedRows(DisplayData.FirstScrollingSlot, CellsHeight);
}
if (DisplayData.FirstScrollingSlot < slot && DisplayData.LastScrollingSlot > slot)
if (DisplayData.FirstScrollingSlot < slot && (DisplayData.LastScrollingSlot > slot || DisplayData.LastScrollingSlot == -1))
{
// The row is already displayed in its entirety
return true;

5
src/Avalonia.Controls/ApiCompatBaseline.txt

@ -4,6 +4,7 @@ InterfacesShouldHaveSameMembers : Interface member 'public System.Boolean Avalon
InterfacesShouldHaveSameMembers : Interface member 'public void Avalonia.Controls.IMenuItem.StaysOpenOnClick.set(System.Boolean)' is present in the implementation but not in the contract.
InterfacesShouldHaveSameMembers : Interface member 'public void Avalonia.Controls.INativeMenuExporterEventsImplBridge.RaiseClosed()' is present in the implementation but not in the contract.
InterfacesShouldHaveSameMembers : Interface member 'public void Avalonia.Controls.INativeMenuExporterEventsImplBridge.RaiseOpening()' is present in the implementation but not in the contract.
MembersMustExist : Member 'public Avalonia.StyledProperty<System.Boolean> Avalonia.StyledProperty<System.Boolean> Avalonia.Controls.ScrollViewer.AllowAutoHideProperty' does not exist in the implementation but it does exist in the contract.
MembersMustExist : Member 'public Avalonia.AvaloniaProperty<Avalonia.Media.Stretch> Avalonia.AvaloniaProperty<Avalonia.Media.Stretch> Avalonia.Controls.Viewbox.StretchProperty' does not exist in the implementation but it does exist in the contract.
MembersMustExist : Member 'public void Avalonia.Controls.Embedding.Offscreen.OffscreenTopLevelImplBase.SetCursor(Avalonia.Platform.IPlatformHandle)' does not exist in the implementation but it does exist in the contract.
MembersMustExist : Member 'public Avalonia.AvaloniaProperty Avalonia.AvaloniaProperty Avalonia.Controls.Notifications.NotificationCard.CloseOnClickProperty' does not exist in the implementation but it does exist in the contract.
@ -11,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: 12
InterfacesShouldHaveSameMembers : Interface member 'public System.Nullable<Avalonia.Size> Avalonia.Platform.ITopLevelImpl.FrameSize' is present in the implementation but not in the contract.
InterfacesShouldHaveSameMembers : Interface member 'public System.Nullable<Avalonia.Size> Avalonia.Platform.ITopLevelImpl.FrameSize.get()' is present in the implementation but not in the contract.
Total Issues: 15

31
src/Avalonia.Controls/Application.cs

@ -13,6 +13,7 @@ using Avalonia.Platform;
using Avalonia.Rendering;
using Avalonia.Styling;
using Avalonia.Threading;
#nullable enable
namespace Avalonia
{
@ -35,27 +36,27 @@ namespace Avalonia
/// <summary>
/// The application-global data templates.
/// </summary>
private DataTemplates _dataTemplates;
private DataTemplates? _dataTemplates;
private readonly Lazy<IClipboard> _clipboard =
new Lazy<IClipboard>(() => (IClipboard)AvaloniaLocator.Current.GetService(typeof(IClipboard)));
private readonly Styler _styler = new Styler();
private Styles _styles;
private IResourceDictionary _resources;
private Styles? _styles;
private IResourceDictionary? _resources;
private bool _notifyingResourcesChanged;
private Action<IReadOnlyList<IStyle>> _stylesAdded;
private Action<IReadOnlyList<IStyle>> _stylesRemoved;
private Action<IReadOnlyList<IStyle>>? _stylesAdded;
private Action<IReadOnlyList<IStyle>>? _stylesRemoved;
/// <summary>
/// Defines the <see cref="DataContext"/> property.
/// </summary>
public static readonly StyledProperty<object> DataContextProperty =
public static readonly StyledProperty<object?> DataContextProperty =
StyledElement.DataContextProperty.AddOwner<Application>();
/// <inheritdoc/>
public event EventHandler<ResourcesChangedEventArgs> ResourcesChanged;
public event EventHandler<ResourcesChangedEventArgs>? ResourcesChanged;
public event EventHandler<UrlOpenedEventArgs> UrlsOpened;
public event EventHandler<UrlOpenedEventArgs>? UrlsOpened;
/// <summary>
/// Creates an instance of the <see cref="Application"/> class.
@ -72,7 +73,7 @@ namespace Avalonia
/// The data context property specifies the default object that will
/// be used for data binding.
/// </remarks>
public object DataContext
public object? DataContext
{
get { return GetValue(DataContextProperty); }
set { SetValue(DataContextProperty, value); }
@ -162,7 +163,7 @@ namespace Avalonia
/// <summary>
/// Gets the styling parent of the application, which is null.
/// </summary>
IStyleHost IStyleHost.StylingParent => null;
IStyleHost? IStyleHost.StylingParent => null;
/// <inheritdoc/>
bool IStyleHost.IsStylesInitialized => _styles != null;
@ -194,7 +195,7 @@ namespace Avalonia
public virtual void Initialize() { }
/// <inheritdoc/>
bool IResourceNode.TryGetResource(object key, out object value)
bool IResourceNode.TryGetResource(object key, out object? value)
{
value = null;
return (_resources?.TryGetResource(key, out value) ?? false) ||
@ -279,17 +280,17 @@ namespace Avalonia
NotifyResourcesChanged(e);
}
private string _name;
private string? _name;
/// <summary>
/// Defines Name property
/// </summary>
public static readonly DirectProperty<Application, string> NameProperty =
AvaloniaProperty.RegisterDirect<Application, string>("Name", o => o.Name, (o, v) => o.Name = v);
public static readonly DirectProperty<Application, string?> NameProperty =
AvaloniaProperty.RegisterDirect<Application, string?>("Name", o => o.Name, (o, v) => o.Name = v);
/// <summary>
/// Application name to be used for various platform-specific purposes
/// </summary>
public string Name
public string? Name
{
get => _name;
set => SetAndRaise(NameProperty, ref _name, value);

2
src/Avalonia.Controls/ComboBox.cs

@ -205,7 +205,7 @@ namespace Avalonia.Controls
if (e.Handled)
return;
if (e.Key == Key.F4 ||
if ((e.Key == Key.F4 && e.KeyModifiers.HasAllFlags(KeyModifiers.Alt) == false) ||
((e.Key == Key.Down || e.Key == Key.Up) && e.KeyModifiers.HasAllFlags(KeyModifiers.Alt)))
{
IsDropDownOpen = !IsDropDownOpen;

2
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; }

21
src/Avalonia.Controls/Expander.cs

@ -1,7 +1,11 @@
using System.Threading;
using Avalonia.Animation;
using Avalonia.Controls.Metadata;
using Avalonia.Controls.Primitives;
#nullable enable
namespace Avalonia.Controls
{
/// <summary>
@ -36,8 +40,8 @@ namespace Avalonia.Controls
[PseudoClasses(":expanded", ":up", ":down", ":left", ":right")]
public class Expander : HeaderedContentControl
{
public static readonly StyledProperty<IPageTransition> ContentTransitionProperty =
AvaloniaProperty.Register<Expander, IPageTransition>(nameof(ContentTransition));
public static readonly StyledProperty<IPageTransition?> ContentTransitionProperty =
AvaloniaProperty.Register<Expander, IPageTransition?>(nameof(ContentTransition));
public static readonly StyledProperty<ExpandDirection> ExpandDirectionProperty =
AvaloniaProperty.Register<Expander, ExpandDirection>(nameof(ExpandDirection), ExpandDirection.Down);
@ -50,6 +54,7 @@ namespace Avalonia.Controls
defaultBindingMode: Data.BindingMode.TwoWay);
private bool _isExpanded;
private CancellationTokenSource? _lastTransitionCts;
static Expander()
{
@ -61,7 +66,7 @@ namespace Avalonia.Controls
UpdatePseudoClasses(ExpandDirection);
}
public IPageTransition ContentTransition
public IPageTransition? ContentTransition
{
get => GetValue(ContentTransitionProperty);
set => SetValue(ContentTransitionProperty, value);
@ -83,19 +88,23 @@ namespace Avalonia.Controls
}
}
protected virtual void OnIsExpandedChanged(AvaloniaPropertyChangedEventArgs e)
protected virtual async void OnIsExpandedChanged(AvaloniaPropertyChangedEventArgs e)
{
if (Content != null && ContentTransition != null && Presenter is Visual visualContent)
{
bool forward = ExpandDirection == ExpandDirection.Left ||
ExpandDirection == ExpandDirection.Up;
_lastTransitionCts?.Cancel();
_lastTransitionCts = new CancellationTokenSource();
if (IsExpanded)
{
ContentTransition.Start(null, visualContent, forward);
await ContentTransition.Start(null, visualContent, forward, _lastTransitionCts.Token);
}
else
{
ContentTransition.Start(visualContent, null, !forward);
await ContentTransition.Start(visualContent, null, forward, _lastTransitionCts.Token);
}
}
}

32
src/Avalonia.Controls/Flyouts/FlyoutBase.cs

@ -4,6 +4,7 @@ using Avalonia.Input;
using Avalonia.Input.Raw;
using Avalonia.Layout;
using Avalonia.Logging;
using Avalonia.Rendering;
#nullable enable
@ -51,8 +52,9 @@ namespace Avalonia.Controls.Primitives
private bool _isOpen;
private Control? _target;
private FlyoutShowMode _showMode = FlyoutShowMode.Standard;
private Rect? enlargedPopupRect;
private IDisposable? transientDisposable;
private Rect? _enlargedPopupRect;
private PixelRect? _enlargePopupRectScreenPixelRect;
private IDisposable? _transientDisposable;
protected Popup? Popup { get; private set; }
@ -163,8 +165,10 @@ namespace Avalonia.Controls.Primitives
Popup.IsOpen = false;
// Ensure this isn't active
transientDisposable?.Dispose();
transientDisposable = null;
_transientDisposable?.Dispose();
_transientDisposable = null;
_enlargedPopupRect = null;
_enlargePopupRectScreenPixelRect = null;
OnClosed();
}
@ -230,7 +234,7 @@ namespace Avalonia.Controls.Primitives
}
else if (ShowMode == FlyoutShowMode.TransientWithDismissOnPointerMoveAway)
{
transientDisposable = InputManager.Instance?.Process.Subscribe(HandleTransientDismiss);
_transientDisposable = InputManager.Instance?.Process.Subscribe(HandleTransientDismiss);
}
}
@ -246,20 +250,20 @@ namespace Avalonia.Controls.Primitives
// For windowed popups, enlargedPopupRect is in screen coordinates,
// for overlay popups, its in OverlayLayer coordinates
if (enlargedPopupRect == null)
if (_enlargedPopupRect == null && _enlargePopupRectScreenPixelRect == null)
{
// Only do this once when the Flyout opens & cache the result
if (Popup?.Host is PopupRoot root)
{
// Get the popup root bounds and convert to screen coordinates
var tmp = root.Bounds.Inflate(100);
var scPt = root.PointToScreen(tmp.TopLeft);
enlargedPopupRect = new Rect(scPt.X, scPt.Y, tmp.Width, tmp.Height);
_enlargePopupRectScreenPixelRect = new PixelRect(root.PointToScreen(tmp.TopLeft), root.PointToScreen(tmp.BottomRight));
}
else if (Popup?.Host is OverlayPopupHost host)
{
// Overlay popups are in OverlayLayer coordinates, just use that
enlargedPopupRect = host.Bounds.Inflate(100);
_enlargedPopupRect = host.Bounds.Inflate(100);
}
return;
@ -273,24 +277,18 @@ namespace Avalonia.Controls.Primitives
// window will not close this (as pointer events stop), which
// does match UWP
var pt = pArgs.Root.PointToScreen(pArgs.Position);
if (!enlargedPopupRect?.Contains(new Point(pt.X, pt.Y)) ?? false)
if (!_enlargePopupRectScreenPixelRect?.Contains(pt) ?? false)
{
HideCore(false);
enlargedPopupRect = null;
transientDisposable?.Dispose();
transientDisposable = null;
}
}
else if (Popup?.Host is OverlayPopupHost)
{
// Same as above here, but just different coordinate space
// so we don't need to translate
if (!enlargedPopupRect?.Contains(pArgs.Position) ?? false)
if (!_enlargedPopupRect?.Contains(pArgs.Position) ?? false)
{
HideCore(false);
enlargedPopupRect = null;
transientDisposable?.Dispose();
transientDisposable = null;
}
}
}

1
src/Avalonia.Controls/Grid.cs

@ -978,6 +978,7 @@ namespace Avalonia.Controls
/// width is not registered in columns.</param>
/// <param name="forceInfinityV">Passed through to MeasureCell.
/// When "true" cells' desired height is not registered in rows.</param>
/// <param name="hasDesiredSizeUChanged">return true when desired size has changed</param>
private void MeasureCellsGroup(
int cellsHead,
Size referenceSize,

6
src/Avalonia.Controls/GridLength.cs

@ -77,6 +77,12 @@ namespace Avalonia.Controls
/// </summary>
public static GridLength Auto => new GridLength(0, GridUnitType.Auto);
/// <summary>
/// Gets an instance of <see cref="GridLength"/> that indicates that a row or column should
/// fill its content.
/// </summary>
public static GridLength Star => new GridLength(1, GridUnitType.Star);
/// <summary>
/// Gets the unit of the <see cref="GridLength"/>.
/// </summary>

1
src/Avalonia.Controls/Menu.cs

@ -17,7 +17,6 @@ namespace Avalonia.Controls
private static readonly ITemplate<IPanel> DefaultPanel =
new FuncTemplate<IPanel>(() => new StackPanel { Orientation = Orientation.Horizontal });
private LightDismissOverlayLayer? _overlay;
/// <summary>
/// Initializes a new instance of the <see cref="Menu"/> class.

6
src/Avalonia.Controls/MenuItem.cs

@ -36,7 +36,7 @@ namespace Avalonia.Controls
/// <summary>
/// Defines the <see cref="HotKey"/> property.
/// </summary>
public static readonly StyledProperty<KeyGesture> HotKeyProperty =
public static readonly StyledProperty<KeyGesture?> HotKeyProperty =
HotKeyManager.HotKeyProperty.AddOwner<MenuItem>();
/// <summary>
@ -108,7 +108,7 @@ namespace Avalonia.Controls
private ICommand? _command;
private bool _commandCanExecute = true;
private Popup? _popup;
private KeyGesture _hotkey;
private KeyGesture? _hotkey;
private bool _isEmbeddedInMenu;
/// <summary>
@ -214,7 +214,7 @@ namespace Avalonia.Controls
/// <summary>
/// Gets or sets an <see cref="KeyGesture"/> associated with this control
/// </summary>
public KeyGesture HotKey
public KeyGesture? HotKey
{
get { return GetValue(HotKeyProperty); }
set { SetValue(HotKeyProperty, value); }

5
src/Avalonia.Controls/Platform/ITopLevelImpl.cs

@ -22,6 +22,11 @@ namespace Avalonia.Platform
/// </summary>
Size ClientSize { get; }
/// <summary>
/// Gets the total size of the toplevel, excluding shadows.
/// </summary>
Size? FrameSize { get; }
/// <summary>
/// Gets the scaling factor for the toplevel. This is used for rendering.
/// </summary>

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

@ -186,7 +186,7 @@ namespace Avalonia.Controls.Presenters
if (PageTransition != null && (from != null || to != null))
{
await PageTransition.Start((Visual)from, (Visual)to, fromIndex < toIndex);
await PageTransition.Start((Visual)from, (Visual)to, fromIndex < toIndex, default);
}
else if (to != null)
{

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

@ -511,8 +511,8 @@ namespace Avalonia.Controls.Presenters
else if (scrollable.IsLogicalScrollEnabled)
{
Viewport = scrollable.Viewport;
Extent = scrollable.Extent;
Offset = scrollable.Offset;
Extent = scrollable.Extent;
}
}

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

@ -1,6 +1,7 @@
using System;
using System.Collections.Specialized;
using System.Linq;
using System.Resources;
using Avalonia.Media;
using Avalonia.Rendering;
using Avalonia.Utilities;
@ -45,10 +46,27 @@ namespace Avalonia.Controls.Primitives
?.AdornerLayer;
}
protected override Size ArrangeOverride(Size finalSize)
protected override Size MeasureOverride(Size availableSize)
{
var parent = Parent;
foreach (var child in Children)
{
var info = child.GetValue(s_adornedElementInfoProperty);
if (info != null && info.Bounds.HasValue)
{
child.Measure(info.Bounds.Value.Bounds.Size);
}
else
{
child.Measure(availableSize);
}
}
return default;
}
protected override Size ArrangeOverride(Size finalSize)
{
foreach (var child in Children)
{
var info = child.GetValue(s_adornedElementInfoProperty);

1
src/Avalonia.Controls/Repeater/ViewportManager.cs

@ -27,7 +27,6 @@ namespace Avalonia.Controls
private IScrollAnchorProvider _scroller;
private IControl _makeAnchorElement;
private bool _isAnchorOutsideRealizedRange;
private Task _cacheBuildAction;
private Rect _visibleWindow;
private Rect _layoutExtent;
// This is the expected shift by the layout.

1
src/Avalonia.Controls/Repeater/VirtualizationInfo.cs

@ -27,7 +27,6 @@ namespace Avalonia.Controls
internal class VirtualizationInfo
{
private int _pinCounter;
private object _data;
public Rect ArrangeBounds { get; set; }
public bool AutoRecycleCandidate { get; set; }

8
src/Avalonia.Controls/RowDefinitions.cs

@ -1,5 +1,4 @@
using System.Linq;
using Avalonia.Collections;
namespace Avalonia.Controls
{
@ -25,6 +24,11 @@ namespace Avalonia.Controls
AddRange(GridLength.ParseLengths(s).Select(x => new RowDefinition(x)));
}
public override string ToString()
{
return string.Join(",", this.Select(x => x.Height));
}
/// <summary>
/// Parses a string representation of row definitions collection.
/// </summary>
@ -32,4 +36,4 @@ namespace Avalonia.Controls
/// <returns>The <see cref="RowDefinitions"/>.</returns>
public static RowDefinitions Parse(string s) => new RowDefinitions(s);
}
}
}

36
src/Avalonia.Controls/ScrollViewer.cs

@ -176,8 +176,10 @@ namespace Avalonia.Controls
/// <summary>
/// Defines the <see cref="AllowAutoHide"/> property.
/// </summary>
public static readonly StyledProperty<bool> AllowAutoHideProperty =
ScrollBar.AllowAutoHideProperty.AddOwner<ScrollViewer>();
public static readonly AttachedProperty<bool> AllowAutoHideProperty =
AvaloniaProperty.RegisterAttached<ScrollViewer, Control, bool>(
nameof(AllowAutoHide),
true);
/// <summary>
/// Defines the <see cref="ScrollChanged"/> event.
@ -207,8 +209,8 @@ namespace Avalonia.Controls
/// </summary>
static ScrollViewer()
{
HorizontalScrollBarVisibilityProperty.Changed.AddClassHandler<ScrollViewer>((x, e) => x.ScrollBarVisibilityChanged(e));
VerticalScrollBarVisibilityProperty.Changed.AddClassHandler<ScrollViewer>((x, e) => x.ScrollBarVisibilityChanged(e));
HorizontalScrollBarVisibilityProperty.Changed.AddClassHandler<ScrollViewer, ScrollBarVisibility>((x, e) => x.ScrollBarVisibilityChanged(e));
VerticalScrollBarVisibilityProperty.Changed.AddClassHandler<ScrollViewer, ScrollBarVisibility>((x, e) => x.ScrollBarVisibilityChanged(e));
}
/// <summary>
@ -526,6 +528,26 @@ namespace Avalonia.Controls
return control.GetValue(VerticalScrollBarVisibilityProperty);
}
/// <summary>
/// Gets the value of the AllowAutoHideProperty attached property.
/// </summary>
/// <param name="control">The control to set the value on.</param>
/// <param name="value">The value of the property.</param>
public static void SetAllowAutoHide(Control control, bool value)
{
control.SetValue(AllowAutoHideProperty, value);
}
/// <summary>
/// Gets the value of the AllowAutoHideProperty attached property.
/// </summary>
/// <param name="control">The control to read the value from.</param>
/// <returns>The value of the property.</returns>
public static bool GetAllowAutoHide(Control control)
{
return control.GetValue(AllowAutoHideProperty);
}
/// <summary>
/// Gets the value of the VerticalScrollBarVisibility attached property.
/// </summary>
@ -604,10 +626,10 @@ namespace Avalonia.Controls
CalculatedPropertiesChanged();
}
private void ScrollBarVisibilityChanged(AvaloniaPropertyChangedEventArgs e)
private void ScrollBarVisibilityChanged(AvaloniaPropertyChangedEventArgs<ScrollBarVisibility> e)
{
var wasEnabled = !ScrollBarVisibility.Disabled.Equals(e.OldValue);
var isEnabled = !ScrollBarVisibility.Disabled.Equals(e.NewValue);
var wasEnabled = e.OldValue.GetValueOrDefault() != ScrollBarVisibility.Disabled;
var isEnabled = e.NewValue.GetValueOrDefault() != ScrollBarVisibility.Disabled;
if (wasEnabled != isEnabled)
{

103
src/Avalonia.Controls/Shapes/Arc.cs

@ -0,0 +1,103 @@
using System;
using Avalonia.Media;
namespace Avalonia.Controls.Shapes
{
public class Arc : Shape
{
/// <summary>
/// Defines the <see cref="StartAngle"/> property.
/// </summary>
public static readonly StyledProperty<double> StartAngleProperty =
AvaloniaProperty.Register<Arc, double>(nameof(StartAngle), 0.0);
/// <summary>
/// Defines the <see cref="SweepAngle"/> property.
/// </summary>
public static readonly StyledProperty<double> SweepAngleProperty =
AvaloniaProperty.Register<Arc, double>(nameof(SweepAngle), 0.0);
static Arc()
{
StrokeThicknessProperty.OverrideDefaultValue<Arc>(1);
AffectsGeometry<Arc>(BoundsProperty, StrokeThicknessProperty, StartAngleProperty, SweepAngleProperty);
}
/// <summary>
/// Gets or sets the angle at which the arc starts, in degrees.
/// </summary>
public double StartAngle
{
get => GetValue(StartAngleProperty);
set => SetValue(StartAngleProperty, value);
}
/// <summary>
/// Gets or sets the angle, in degrees, added to the <see cref="StartAngle"/> defining where the arc ends.
/// A positive value is clockwise, negative is counter-clockwise.
/// </summary>
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);
}
}

2
src/Avalonia.Controls/SplitView.cs

@ -133,7 +133,7 @@ namespace Avalonia.Controls
AvaloniaProperty.Register<SplitView, object?>(nameof(Pane));
/// <summary>
/// Defines the <see cref="HeaderTemplate"/> property.
/// Defines the <see cref="PaneTemplate"/> property.
/// </summary>
public static readonly StyledProperty<IDataTemplate?> PaneTemplateProperty =
AvaloniaProperty.Register<HeaderedContentControl, IDataTemplate?>(nameof(PaneTemplate));

2
src/Avalonia.Controls/TextBox.cs

@ -1290,7 +1290,7 @@ namespace Avalonia.Controls
private void UpdatePseudoclasses()
{
PseudoClasses.Set(":empty", string.IsNullOrWhiteSpace(Text));
PseudoClasses.Set(":empty", string.IsNullOrEmpty(Text));
}
private bool IsPasswordBox => PasswordChar != default(char);

18
src/Avalonia.Controls/TopLevel.cs

@ -42,6 +42,12 @@ namespace Avalonia.Controls
public static readonly DirectProperty<TopLevel, Size> ClientSizeProperty =
AvaloniaProperty.RegisterDirect<TopLevel, Size>(nameof(ClientSize), o => o.ClientSize);
/// <summary>
/// Defines the <see cref="FrameSize"/> property.
/// </summary>
public static readonly DirectProperty<TopLevel, Size?> FrameSizeProperty =
AvaloniaProperty.RegisterDirect<TopLevel, Size?>(nameof(FrameSize), o => o.FrameSize);
/// <summary>
/// Defines the <see cref="IInputRoot.PointerOverElement"/> property.
/// </summary>
@ -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); }
}
/// <summary>
/// Gets or sets the total size of the window.
/// </summary>
public Size? FrameSize
{
get { return _frameSize; }
protected set { SetAndRaise(FrameSizeProperty, ref _frameSize, value); }
}
/// <summary>
/// Gets or sets the <see cref="WindowTransparencyLevel"/> that the TopLevel should use when possible.
/// </summary>
@ -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();

6
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)
{

39
src/Avalonia.Controls/Window.cs

@ -592,6 +592,14 @@ namespace Avalonia.Controls
owner.RemoveChild(this);
}
if (_children.Count > 0)
{
foreach (var child in _children.ToArray())
{
child.child.Hide();
}
}
Owner = null;
PlatformImpl?.Hide();
@ -635,6 +643,22 @@ namespace Avalonia.Controls
throw new InvalidOperationException("Cannot re-show a closed window.");
}
if (parent != null)
{
if (parent.PlatformImpl == null)
{
throw new InvalidOperationException("Cannot show a window with a closed parent.");
}
else if (parent == this)
{
throw new InvalidOperationException("A Window cannot be its own parent.");
}
else if (!parent.IsVisible)
{
throw new InvalidOperationException("Cannot show window with non-visible parent.");
}
}
if (IsVisible)
{
return;
@ -708,11 +732,22 @@ namespace Avalonia.Controls
{
throw new ArgumentNullException(nameof(owner));
}
if (IsVisible)
else if (owner.PlatformImpl == null)
{
throw new InvalidOperationException("Cannot show a window with a closed owner.");
}
else if (owner == this)
{
throw new InvalidOperationException("A Window cannot be its own owner.");
}
else if (IsVisible)
{
throw new InvalidOperationException("The window is already being shown.");
}
else if (!owner.IsVisible)
{
throw new InvalidOperationException("Cannot show window with non-visible parent.");
}
RaiseEvent(new RoutedEventArgs(WindowOpenedEvent));

1
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);
}

1
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<object> Surfaces { get; }

1
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<object> Surfaces { get; }

5
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;
}
}
}

14
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<object> Surfaces => new[] {
(_gpu ? _glSurface : (object)null),
this

1
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);

13
src/Avalonia.ReactiveUI/TransitioningContentControl.cs

@ -1,4 +1,6 @@
using System;
using System.Threading;
using Avalonia.Animation;
using Avalonia.Controls;
using Avalonia.Styling;
@ -22,7 +24,9 @@ namespace Avalonia.ReactiveUI
/// </summary>
public static readonly StyledProperty<object?> DefaultContentProperty =
AvaloniaProperty.Register<TransitioningContentControl, object?>(nameof(DefaultContent));
private CancellationTokenSource? _lastTransitionCts;
/// <summary>
/// Gets or sets the animation played when content appears and disappears.
/// </summary>
@ -62,11 +66,14 @@ namespace Avalonia.ReactiveUI
/// <param name="content">New content to set.</param>
private async void UpdateContentWithTransition(object? content)
{
_lastTransitionCts?.Cancel();
_lastTransitionCts = new CancellationTokenSource();
if (PageTransition != null)
await PageTransition.Start(this, null, true);
await PageTransition.Start(this, null, true, _lastTransitionCts.Token);
base.Content = content;
if (PageTransition != null)
await PageTransition.Start(null, this, true);
await PageTransition.Start(null, this, true, _lastTransitionCts.Token);
}
}
}

3
src/Avalonia.Themes.Default/ListBox.xaml

@ -13,7 +13,8 @@
<ScrollViewer Name="PART_ScrollViewer"
Background="{TemplateBinding Background}"
HorizontalScrollBarVisibility="{TemplateBinding (ScrollViewer.HorizontalScrollBarVisibility)}"
VerticalScrollBarVisibility="{TemplateBinding (ScrollViewer.VerticalScrollBarVisibility)}">
VerticalScrollBarVisibility="{TemplateBinding (ScrollViewer.VerticalScrollBarVisibility)}"
AllowAutoHide="{TemplateBinding (ScrollViewer.AllowAutoHide)}">
<ItemsPresenter Name="PART_ItemsPresenter"
Items="{TemplateBinding Items}"
ItemsPanel="{TemplateBinding ItemsPanel}"

3
src/Avalonia.Themes.Default/TextBox.xaml

@ -57,7 +57,8 @@
<ContentPresenter Grid.Column="0" Grid.ColumnSpan="1" Content="{TemplateBinding InnerLeftContent}"/>
<ScrollViewer Grid.Column="1" Grid.ColumnSpan="1"
HorizontalScrollBarVisibility="{TemplateBinding (ScrollViewer.HorizontalScrollBarVisibility)}"
VerticalScrollBarVisibility="{TemplateBinding (ScrollViewer.VerticalScrollBarVisibility)}">
VerticalScrollBarVisibility="{TemplateBinding (ScrollViewer.VerticalScrollBarVisibility)}"
AllowAutoHide="{TemplateBinding (ScrollViewer.AllowAutoHide)}">
<Panel>
<TextBlock Name="watermark"
Opacity="0.5"

3
src/Avalonia.Themes.Default/TreeView.xaml

@ -11,7 +11,8 @@
BorderThickness="{TemplateBinding BorderThickness}">
<ScrollViewer Background="{TemplateBinding Background}"
HorizontalScrollBarVisibility="{TemplateBinding (ScrollViewer.HorizontalScrollBarVisibility)}"
VerticalScrollBarVisibility="{TemplateBinding (ScrollViewer.VerticalScrollBarVisibility)}">
VerticalScrollBarVisibility="{TemplateBinding (ScrollViewer.VerticalScrollBarVisibility)}"
AllowAutoHide="{TemplateBinding (ScrollViewer.AllowAutoHide)}">
<ItemsPresenter Name="PART_ItemsPresenter"
Items="{TemplateBinding Items}"
ItemsPanel="{TemplateBinding ItemsPanel}"

6
src/Avalonia.Themes.Fluent/Controls/ComboBox.xaml

@ -112,9 +112,7 @@
<Panel Height="12"
Width="12" />
<Path x:Name="DropDownGlyph"
Stretch="Uniform"
VerticalAlignment="Center"
Data="M1939 486L2029 576L1024 1581L19 576L109 486L1024 1401L1939 486Z" />
VerticalAlignment="Center" />
</Panel>
</Viewbox>
<Popup Name="PART_Popup"
@ -160,6 +158,8 @@
<Style Selector="ComboBox /template/ Path#DropDownGlyph">
<Setter Property="Fill" Value="{DynamicResource ComboBoxDropDownGlyphForeground}" />
<Setter Property="Data" Value="M1939 486L2029 576L1024 1581L19 576L109 486L1024 1401L1939 486Z" />
<Setter Property="Stretch" Value="Uniform" />
</Style>
<!-- PointerOver State -->

3
src/Avalonia.Themes.Fluent/Controls/ListBox.xaml

@ -24,7 +24,8 @@
<ScrollViewer Name="PART_ScrollViewer"
Background="{TemplateBinding Background}"
HorizontalScrollBarVisibility="{TemplateBinding (ScrollViewer.HorizontalScrollBarVisibility)}"
VerticalScrollBarVisibility="{TemplateBinding (ScrollViewer.VerticalScrollBarVisibility)}">
VerticalScrollBarVisibility="{TemplateBinding (ScrollViewer.VerticalScrollBarVisibility)}"
AllowAutoHide="{TemplateBinding (ScrollViewer.AllowAutoHide)}">
<ItemsPresenter Name="PART_ItemsPresenter"
Items="{TemplateBinding Items}"
ItemsPanel="{TemplateBinding ItemsPanel}"

3
src/Avalonia.Themes.Fluent/Controls/TextBox.xaml

@ -65,7 +65,8 @@
Text="{TemplateBinding Watermark}"
DockPanel.Dock="Top" />
<ScrollViewer HorizontalScrollBarVisibility="{TemplateBinding (ScrollViewer.HorizontalScrollBarVisibility)}"
VerticalScrollBarVisibility="{TemplateBinding (ScrollViewer.VerticalScrollBarVisibility)}">
VerticalScrollBarVisibility="{TemplateBinding (ScrollViewer.VerticalScrollBarVisibility)}"
AllowAutoHide="{TemplateBinding (ScrollViewer.AllowAutoHide)}">
<Panel>
<TextBlock Name="PART_Watermark"
Opacity="0.5"

3
src/Avalonia.Themes.Fluent/Controls/TreeView.xaml

@ -12,7 +12,8 @@
BorderThickness="{TemplateBinding BorderThickness}">
<ScrollViewer Background="{TemplateBinding Background}"
HorizontalScrollBarVisibility="{TemplateBinding (ScrollViewer.HorizontalScrollBarVisibility)}"
VerticalScrollBarVisibility="{TemplateBinding (ScrollViewer.VerticalScrollBarVisibility)}">
VerticalScrollBarVisibility="{TemplateBinding (ScrollViewer.VerticalScrollBarVisibility)}"
AllowAutoHide="{TemplateBinding (ScrollViewer.AllowAutoHide)}">
<ItemsPresenter Name="PART_ItemsPresenter"
Items="{TemplateBinding Items}"
ItemsPanel="{TemplateBinding ItemsPanel}"

21
src/Avalonia.Visuals/Animation/CompositePageTransition.cs

@ -1,5 +1,6 @@
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Avalonia.Metadata;
@ -35,25 +36,11 @@ namespace Avalonia.Animation
[Content]
public List<IPageTransition> PageTransitions { get; set; } = new List<IPageTransition>();
/// <summary>
/// Starts the animation.
/// </summary>
/// <param name="from">
/// The control that is being transitioned away from. May be null.
/// </param>
/// <param name="to">
/// The control that is being transitioned to. May be null.
/// </param>
/// <param name="forward">
/// Defines the direction of the transition.
/// </param>
/// <returns>
/// A <see cref="Task"/> that tracks the progress of the animation.
/// </returns>
public Task Start(Visual from, Visual to, bool forward)
/// <inheritdoc />
public Task Start(Visual from, Visual to, bool forward, CancellationToken cancellationToken)
{
var transitionTasks = PageTransitions
.Select(transition => transition.Start(from, to, forward))
.Select(transition => transition.Start(from, to, forward, cancellationToken))
.ToList();
return Task.WhenAll(transitionTasks);
}

65
src/Avalonia.Visuals/Animation/CrossFade.cs

@ -1,5 +1,7 @@
using System;
using System.Collections.Generic;
using System.Reactive.Disposables;
using System.Threading;
using System.Threading.Tasks;
using Avalonia.Animation.Easings;
using Avalonia.Styling;
@ -97,49 +99,39 @@ namespace Avalonia.Animation
set => _fadeOutAnimation.Easing = value;
}
/// <summary>
/// Starts the animation.
/// </summary>
/// <param name="from">
/// The control that is being transitioned away from. May be null.
/// </param>
/// <param name="to">
/// The control that is being transitioned to. May be null.
/// </param>
/// <returns>
/// A <see cref="Task"/> that tracks the progress of the animation.
/// </returns>
public async Task Start(Visual from, Visual to)
/// <inheritdoc cref="Start(Visual, Visual, CancellationToken)" />
public async Task Start(Visual from, Visual to, CancellationToken cancellationToken)
{
var tasks = new List<Task>();
if (to != null)
{
to.Opacity = 0;
}
if (from != null)
if (cancellationToken.IsCancellationRequested)
{
tasks.Add(_fadeOutAnimation.RunAsync(from));
return;
}
if (to != null)
var tasks = new List<Task>();
using (var disposables = new CompositeDisposable())
{
to.IsVisible = true;
tasks.Add(_fadeInAnimation.RunAsync(to));
if (to != null)
{
disposables.Add(to.SetValue(Visual.OpacityProperty, 0, Data.BindingPriority.Animation));
}
}
if (from != null)
{
tasks.Add(_fadeOutAnimation.RunAsync(from, null, cancellationToken));
}
await Task.WhenAll(tasks);
if (to != null)
{
to.IsVisible = true;
tasks.Add(_fadeInAnimation.RunAsync(to, null, cancellationToken));
}
if (from != null)
{
from.IsVisible = false;
}
await Task.WhenAll(tasks);
if (to != null)
{
to.Opacity = 1;
if (from != null && !cancellationToken.IsCancellationRequested)
{
from.IsVisible = false;
}
}
}
@ -155,12 +147,13 @@ namespace Avalonia.Animation
/// <param name="forward">
/// Unused for cross-fades.
/// </param>
/// <param name="cancellationToken">allowed cancel transition</param>
/// <returns>
/// A <see cref="Task"/> that tracks the progress of the animation.
/// </returns>
Task IPageTransition.Start(Visual from, Visual to, bool forward)
Task IPageTransition.Start(Visual from, Visual to, bool forward, CancellationToken cancellationToken)
{
return Start(from, to);
return Start(from, to, cancellationToken);
}
}
}

6
src/Avalonia.Visuals/Animation/IPageTransition.cs

@ -1,3 +1,4 @@
using System.Threading;
using System.Threading.Tasks;
namespace Avalonia.Animation
@ -19,9 +20,12 @@ namespace Avalonia.Animation
/// <param name="forward">
/// If the animation is bidirectional, controls the direction of the animation.
/// </param>
/// <param name="cancellationToken">
/// Animation cancellation.
/// </param>
/// <returns>
/// A <see cref="Task"/> that tracks the progress of the animation.
/// </returns>
Task Start(Visual from, Visual to, bool forward);
Task Start(Visual from, Visual to, bool forward, CancellationToken cancellationToken);
}
}

30
src/Avalonia.Visuals/Animation/PageSlide.cs

@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
using Avalonia.Animation.Easings;
using Avalonia.Media;
@ -60,23 +61,14 @@ namespace Avalonia.Animation
/// </summary>
public Easing SlideOutEasing { get; set; } = new LinearEasing();
/// <summary>
/// Starts the animation.
/// </summary>
/// <param name="from">
/// The control that is being transitioned away from. May be null.
/// </param>
/// <param name="to">
/// The control that is being transitioned to. May be null.
/// </param>
/// <param name="forward">
/// If true, the new page is slid in from the right, or if false from the left.
/// </param>
/// <returns>
/// A <see cref="Task"/> that tracks the progress of the animation.
/// </returns>
public async Task Start(Visual from, Visual to, bool forward)
/// <inheritdoc />
public async Task Start(Visual from, Visual to, bool forward, CancellationToken cancellationToken)
{
if (cancellationToken.IsCancellationRequested)
{
return;
}
var tasks = new List<Task>();
var parent = GetVisualParent(from, to);
var distance = Orientation == SlideAxis.Horizontal ? parent.Bounds.Width : parent.Bounds.Height;
@ -109,7 +101,7 @@ namespace Avalonia.Animation
},
Duration = Duration
};
tasks.Add(animation.RunAsync(from));
tasks.Add(animation.RunAsync(from, null, cancellationToken));
}
if (to != null)
@ -140,12 +132,12 @@ namespace Avalonia.Animation
},
Duration = Duration
};
tasks.Add(animation.RunAsync(to));
tasks.Add(animation.RunAsync(to, null, cancellationToken));
}
await Task.WhenAll(tasks);
if (from != null)
if (from != null && !cancellationToken.IsCancellationRequested)
{
from.IsVisible = false;
}

9
src/Avalonia.Visuals/ApiCompatBaseline.txt

@ -1,4 +1,10 @@
Compat issues with assembly Avalonia.Visuals:
MembersMustExist : Member 'public System.Threading.Tasks.Task Avalonia.Animation.CompositePageTransition.Start(Avalonia.Visual, Avalonia.Visual, System.Boolean)' does not exist in the implementation but it does exist in the contract.
MembersMustExist : Member 'public System.Threading.Tasks.Task Avalonia.Animation.CrossFade.Start(Avalonia.Visual, Avalonia.Visual)' does not exist in the implementation but it does exist in the contract.
InterfacesShouldHaveSameMembers : Interface member 'public System.Threading.Tasks.Task Avalonia.Animation.IPageTransition.Start(Avalonia.Visual, Avalonia.Visual, System.Boolean)' is present in the contract but not in the implementation.
MembersMustExist : Member 'public System.Threading.Tasks.Task Avalonia.Animation.IPageTransition.Start(Avalonia.Visual, Avalonia.Visual, System.Boolean)' does not exist in the implementation but it does exist in the contract.
InterfacesShouldHaveSameMembers : Interface member 'public System.Threading.Tasks.Task Avalonia.Animation.IPageTransition.Start(Avalonia.Visual, Avalonia.Visual, System.Boolean, System.Threading.CancellationToken)' is present in the implementation but not in the contract.
MembersMustExist : Member 'public System.Threading.Tasks.Task Avalonia.Animation.PageSlide.Start(Avalonia.Visual, Avalonia.Visual, System.Boolean)' does not exist in the implementation but it does exist in the contract.
MembersMustExist : Member 'public void Avalonia.Media.TextFormatting.DrawableTextRun.Draw(Avalonia.Media.DrawingContext)' does not exist in the implementation but it does exist in the contract.
CannotAddAbstractMembers : Member 'public void Avalonia.Media.TextFormatting.DrawableTextRun.Draw(Avalonia.Media.DrawingContext, Avalonia.Point)' is abstract in the implementation but is missing in the contract.
CannotSealType : Type 'Avalonia.Media.TextFormatting.GenericTextParagraphProperties' is actually (has the sealed modifier) sealed in the implementation but not sealed in the contract.
@ -63,9 +69,8 @@ InterfacesShouldHaveSameMembers : Interface member 'public System.Boolean Avalon
InterfacesShouldHaveSameMembers : Interface member 'public Avalonia.Platform.IGlyphRunImpl Avalonia.Platform.IPlatformRenderInterface.CreateGlyphRun(Avalonia.Media.GlyphRun)' is present in the implementation but not in the contract.
InterfacesShouldHaveSameMembers : Interface member 'public Avalonia.Platform.IGlyphRunImpl Avalonia.Platform.IPlatformRenderInterface.CreateGlyphRun(Avalonia.Media.GlyphRun, System.Double)' is present in the contract but not in the implementation.
MembersMustExist : Member 'public Avalonia.Platform.IGlyphRunImpl Avalonia.Platform.IPlatformRenderInterface.CreateGlyphRun(Avalonia.Media.GlyphRun, System.Double)' does not exist in the implementation but it does exist in the contract.
Total Issues: 64
InterfacesShouldHaveSameMembers : Interface member 'public Avalonia.Platform.IWriteableBitmapImpl Avalonia.Platform.IPlatformRenderInterface.LoadWriteableBitmap(System.IO.Stream)' is present in the implementation but not in the contract.
InterfacesShouldHaveSameMembers : Interface member 'public Avalonia.Platform.IWriteableBitmapImpl Avalonia.Platform.IPlatformRenderInterface.LoadWriteableBitmap(System.String)' is present in the implementation but not in the contract.
InterfacesShouldHaveSameMembers : Interface member 'public Avalonia.Platform.IWriteableBitmapImpl Avalonia.Platform.IPlatformRenderInterface.LoadWriteableBitmapToHeight(System.IO.Stream, System.Int32, Avalonia.Visuals.Media.Imaging.BitmapInterpolationMode)' is present in the implementation but not in the contract.
InterfacesShouldHaveSameMembers : Interface member 'public Avalonia.Platform.IWriteableBitmapImpl Avalonia.Platform.IPlatformRenderInterface.LoadWriteableBitmapToWidth(System.IO.Stream, System.Int32, Avalonia.Visuals.Media.Imaging.BitmapInterpolationMode)' is present in the implementation but not in the contract.
Total Issues: 11
Total Issues: 74

41
src/Avalonia.Visuals/Media/BoxShadow.cs

@ -1,5 +1,6 @@
using System;
using System.Globalization;
using System.Text;
using Avalonia.Animation.Animators;
using Avalonia.Utilities;
@ -75,6 +76,46 @@ namespace Avalonia.Media
return rv;
}
}
public override string ToString()
{
var sb = new StringBuilder();
if (IsEmpty)
{
return "none";
}
if (IsInset)
{
sb.Append("inset");
}
if (OffsetX != 0.0)
{
sb.AppendFormat(" {0}", OffsetX.ToString());
}
if (OffsetY != 0.0)
{
sb.AppendFormat(" {0}", OffsetY.ToString());
}
if (Blur != 0.0)
{
sb.AppendFormat(" {0}", Blur.ToString());
}
if (Spread != 0.0)
{
sb.AppendFormat(" {0}", Spread.ToString());
}
sb.AppendFormat(" {0}", Color.ToString());
return sb.ToString();
}
public static unsafe BoxShadow Parse(string s)
{
if(s == null)

20
src/Avalonia.Visuals/Media/BoxShadows.cs

@ -1,6 +1,6 @@
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Text;
using Avalonia.Animation.Animators;
namespace Avalonia.Media
@ -43,6 +43,24 @@ namespace Avalonia.Media
}
}
public override string ToString()
{
var sb = new StringBuilder();
if (Count == 0)
{
return "none";
}
foreach (var boxShadow in this)
{
sb.AppendFormat("{0} ", boxShadow.ToString());
}
return sb.ToString();
}
[EditorBrowsable(EditorBrowsableState.Never)]
public struct BoxShadowsEnumerator
{

4
src/Avalonia.Visuals/Rendering/SceneGraph/BitmapBlendModeNode.cs

@ -19,7 +19,7 @@ namespace Avalonia.Rendering.SceneGraph
}
/// <summary>
/// Initializes a new instance of the <see cref="BitmapBlendNode"/> class that represents an
/// Initializes a new instance of the <see cref="BitmapBlendModeNode"/> class that represents an
/// <see cref="BitmapBlendingMode"/> pop.
/// </summary>
public BitmapBlendModeNode()
@ -40,7 +40,7 @@ namespace Avalonia.Rendering.SceneGraph
/// <summary>
/// Determines if this draw operation equals another.
/// </summary>
/// <param name="opacity">The opacity of the other draw operation.</param>
/// <param name="blendingMode">the <see cref="BitmapBlendModeNode"/> how to compare</param>
/// <returns>True if the draw operations are the same, otherwise false.</returns>
/// <remarks>
/// The properties of the other draw operation are passed in as arguments to prevent

1
src/Avalonia.X11/TransparencyHelper.cs

@ -10,7 +10,6 @@ namespace Avalonia.X11
private readonly X11Globals _globals;
private WindowTransparencyLevel _currentLevel;
private WindowTransparencyLevel _requestedLevel;
private bool _isCompositing;
private bool _blurAtomsAreSet;
public Action<WindowTransparencyLevel> TransparencyLevelChanged { get; set; }

32
src/Avalonia.X11/X11Window.cs

@ -30,7 +30,6 @@ namespace Avalonia.X11
ITopLevelImplWithTextInputMethod
{
private readonly AvaloniaX11Platform _platform;
private readonly IWindowImpl _popupParent;
private readonly bool _popup;
private readonly X11Info _x11;
private XConfigureEvent? _configure;
@ -297,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
@ -589,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;

2
src/Linux/Avalonia.LinuxFramebuffer/FramebufferToplevelImpl.cs

@ -15,7 +15,6 @@ namespace Avalonia.LinuxFramebuffer
private readonly IOutputBackend _outputBackend;
private readonly IInputBackend _inputBackend;
private bool _renderQueued;
public IInputRoot InputRoot { get; private set; }
public FramebufferToplevelImpl(IOutputBackend outputBackend, IInputBackend inputBackend)
@ -62,6 +61,7 @@ namespace Avalonia.LinuxFramebuffer
}
public Size ClientSize => ScaledSize;
public Size? FrameSize => null;
public IMouseDevice MouseDevice => new MouseDevice();
public IPopupImpl CreatePopup() => null;

1
src/Linux/Avalonia.LinuxFramebuffer/Input/EvDev/EvDevTouchScreen.cs

@ -7,7 +7,6 @@ namespace Avalonia.LinuxFramebuffer.Input.EvDev
internal class EvDevSingleTouchScreen : EvDevDeviceHandler
{
private readonly IScreenInfoProvider _screenInfo;
private readonly int _width, _height;
private readonly Matrix _calibration;
private input_absinfo _axisX;
private input_absinfo _axisY;

5
src/Linux/Avalonia.LinuxFramebuffer/LinuxFramebufferPlatform.cs

@ -132,7 +132,10 @@ public static class LinuxFramebufferPlatformExtensions
{
public static int StartLinuxFbDev<T>(this T builder, string[] args, string fbdev = null, double scaling = 1)
where T : AppBuilderBase<T>, new() =>
StartLinuxDirect(builder, args, new FbdevOutput(fbdev) {Scaling = scaling});
StartLinuxDirect(builder, args, new FbdevOutput(fileName: fbdev, format: null) { Scaling = scaling });
public static int StartLinuxFbDev<T>(this T builder, string[] args, string fbdev, PixelFormat? format, double scaling)
where T : AppBuilderBase<T>, new() =>
StartLinuxDirect(builder, args, new FbdevOutput(fileName: fbdev, format: format) { Scaling = scaling });
public static int StartLinuxDrm<T>(this T builder, string[] args, string card = null, double scaling = 1)
where T : AppBuilderBase<T>, new() => StartLinuxDirect(builder, args, new DrmOutput(card) {Scaling = scaling});

2
src/Linux/Avalonia.LinuxFramebuffer/LockedFramebuffer.cs

@ -41,6 +41,6 @@ namespace Avalonia.LinuxFramebuffer
public PixelSize Size => new PixelSize((int)_varInfo.xres, (int) _varInfo.yres);
public int RowBytes => (int) _fixedInfo.line_length;
public Vector Dpi { get; }
public PixelFormat Format => _varInfo.blue.offset == 16 ? PixelFormat.Rgba8888 : PixelFormat.Bgra8888;
public PixelFormat Format => _varInfo.bits_per_pixel == 16 ? PixelFormat.Rgb565 : _varInfo.blue.offset == 16 ? PixelFormat.Rgba8888 : PixelFormat.Bgra8888;
}
}

1
src/Linux/Avalonia.LinuxFramebuffer/Output/DrmOutput.cs

@ -16,7 +16,6 @@ namespace Avalonia.LinuxFramebuffer.Output
public unsafe class DrmOutput : IGlOutputBackend, IGlPlatformSurface
{
private DrmCard _card;
private readonly EglGlPlatformSurface _eglPlatformSurface;
public PixelSize PixelSize => _mode.Resolution;
public double Scaling { get; set; }
public IGlContext PrimaryContext => _deferredContext;

88
src/Linux/Avalonia.LinuxFramebuffer/Output/FbdevOutput.cs

@ -16,16 +16,33 @@ namespace Avalonia.LinuxFramebuffer
private IntPtr _mappedAddress;
public double Scaling { get; set; }
public FbdevOutput(string fileName = null)
/// <summary>
/// Create a Linux frame buffer device output
/// </summary>
/// <param name="fileName">The frame buffer device name.
/// Defaults to the value in environment variable FRAMEBUFFER or /dev/fb0 when FRAMEBUFFER is not set</param>
public FbdevOutput(string fileName = null) : this(fileName, null)
{
fileName = fileName ?? Environment.GetEnvironmentVariable("FRAMEBUFFER") ?? "/dev/fb0";
}
/// <summary>
/// Create a Linux frame buffer device output
/// </summary>
/// <param name="fileName">The frame buffer device name.
/// Defaults to the value in environment variable FRAMEBUFFER or /dev/fb0 when FRAMEBUFFER is not set</param>
/// <param name="format">The required pixel format for the frame buffer.
/// A null value will leave the frame buffer in the current pixel format.
/// Otherwise sets the frame buffer to the required format</param>
public FbdevOutput(string fileName, PixelFormat? format)
{
fileName ??= Environment.GetEnvironmentVariable("FRAMEBUFFER") ?? "/dev/fb0";
_fd = NativeUnsafeMethods.open(fileName, 2, 0);
if (_fd <= 0)
throw new Exception("Error: " + Marshal.GetLastWin32Error());
try
{
Init();
Init(format);
}
catch
{
@ -34,25 +51,28 @@ namespace Avalonia.LinuxFramebuffer
}
}
void Init()
void Init(PixelFormat? format)
{
fixed (void* pnfo = &_varInfo)
{
if (-1 == NativeUnsafeMethods.ioctl(_fd, FbIoCtl.FBIOGET_VSCREENINFO, pnfo))
throw new Exception("FBIOGET_VSCREENINFO error: " + Marshal.GetLastWin32Error());
SetBpp();
if (format.HasValue)
{
SetBpp(format.Value);
if (-1 == NativeUnsafeMethods.ioctl(_fd, FbIoCtl.FBIOPUT_VSCREENINFO, pnfo))
_varInfo.transp = new fb_bitfield();
if (-1 == NativeUnsafeMethods.ioctl(_fd, FbIoCtl.FBIOPUT_VSCREENINFO, pnfo))
_varInfo.transp = new fb_bitfield();
NativeUnsafeMethods.ioctl(_fd, FbIoCtl.FBIOPUT_VSCREENINFO, pnfo);
NativeUnsafeMethods.ioctl(_fd, FbIoCtl.FBIOPUT_VSCREENINFO, pnfo);
if (-1 == NativeUnsafeMethods.ioctl(_fd, FbIoCtl.FBIOGET_VSCREENINFO, pnfo))
throw new Exception("FBIOGET_VSCREENINFO error: " + Marshal.GetLastWin32Error());
if (-1 == NativeUnsafeMethods.ioctl(_fd, FbIoCtl.FBIOGET_VSCREENINFO, pnfo))
throw new Exception("FBIOGET_VSCREENINFO error: " + Marshal.GetLastWin32Error());
if (_varInfo.bits_per_pixel != 32)
throw new Exception("Unable to set 32-bit display mode");
if (_varInfo.bits_per_pixel != 32)
throw new Exception("Unable to set 32-bit display mode");
}
}
fixed(void*pnfo = &_fixedInfo)
if (-1 == NativeUnsafeMethods.ioctl(_fd, FbIoCtl.FBIOGET_FSCREENINFO, pnfo))
@ -70,17 +90,43 @@ namespace Avalonia.LinuxFramebuffer
}
}
void SetBpp()
void SetBpp(PixelFormat format)
{
_varInfo.bits_per_pixel = 32;
_varInfo.grayscale = 0;
_varInfo.red = _varInfo.blue = _varInfo.green = _varInfo.transp = new fb_bitfield
switch (format)
{
length = 8
};
_varInfo.green.offset = 8;
_varInfo.blue.offset = 16;
_varInfo.transp.offset = 24;
case PixelFormat.Rgba8888:
_varInfo.bits_per_pixel = 32;
_varInfo.grayscale = 0;
_varInfo.red = _varInfo.blue = _varInfo.green = _varInfo.transp = new fb_bitfield
{
length = 8
};
_varInfo.green.offset = 8;
_varInfo.blue.offset = 16;
_varInfo.transp.offset = 24;
break;
case PixelFormat.Bgra8888:
_varInfo.bits_per_pixel = 32;
_varInfo.grayscale = 0;
_varInfo.red = _varInfo.blue = _varInfo.green = _varInfo.transp = new fb_bitfield
{
length = 8
};
_varInfo.green.offset = 8;
_varInfo.red.offset = 16;
_varInfo.transp.offset = 24;
break;
case PixelFormat.Rgb565:
_varInfo.bits_per_pixel = 16;
_varInfo.grayscale = 0;
_varInfo.red = _varInfo.blue = _varInfo.green = _varInfo.transp = new fb_bitfield();
_varInfo.red.length = 5;
_varInfo.green.offset = 5;
_varInfo.green.length = 6;
_varInfo.blue.offset = 11;
_varInfo.blue.length = 5;
break;
}
}
public string Id { get; private set; }

49
src/Skia/Avalonia.Skia/DrawingContextImpl.cs

@ -164,7 +164,7 @@ namespace Avalonia.Skia
/// <inheritdoc />
public void DrawLine(IPen pen, Point p1, Point p2)
{
using (var paint = CreatePaint(_strokePaint, pen, new Size(Math.Abs(p2.X - p1.X), Math.Abs(p2.Y - p1.Y))))
using (var paint = CreatePaint(_strokePaint, pen, new Rect(p1, p2).Normalize()))
{
if (paint.Paint is object)
{
@ -177,10 +177,10 @@ namespace Avalonia.Skia
public void DrawGeometry(IBrush brush, IPen pen, IGeometryImpl geometry)
{
var impl = (GeometryImpl) geometry;
var size = geometry.Bounds.Size;
var rect = geometry.Bounds;
using (var fill = brush != null ? CreatePaint(_fillPaint, brush, size) : default(PaintWrapper))
using (var stroke = pen?.Brush != null ? CreatePaint(_strokePaint, pen, size) : default(PaintWrapper))
using (var fill = brush != null ? CreatePaint(_fillPaint, brush, rect) : default(PaintWrapper))
using (var stroke = pen?.Brush != null ? CreatePaint(_strokePaint, pen, rect) : default(PaintWrapper))
{
if (fill.Paint != null)
{
@ -354,7 +354,7 @@ namespace Avalonia.Skia
if (brush != null)
{
using (var paint = CreatePaint(_fillPaint, brush, rect.Rect.Size))
using (var paint = CreatePaint(_fillPaint, brush, rect.Rect))
{
if (isRounded)
{
@ -397,7 +397,7 @@ namespace Avalonia.Skia
if (pen?.Brush != null)
{
using (var paint = CreatePaint(_strokePaint, pen, rect.Rect.Size))
using (var paint = CreatePaint(_strokePaint, pen, rect.Rect))
{
if (paint.Paint is object)
{
@ -417,7 +417,7 @@ namespace Avalonia.Skia
/// <inheritdoc />
public void DrawText(IBrush foreground, Point origin, IFormattedTextImpl text)
{
using (var paint = CreatePaint(_fillPaint, foreground, text.Bounds.Size))
using (var paint = CreatePaint(_fillPaint, foreground, text.Bounds))
{
var textImpl = (FormattedTextImpl) text;
textImpl.Draw(this, Canvas, origin.ToSKPoint(), paint, _canTextUseLcdRendering);
@ -427,7 +427,7 @@ namespace Avalonia.Skia
/// <inheritdoc />
public void DrawGlyphRun(IBrush foreground, GlyphRun glyphRun)
{
using (var paintWrapper = CreatePaint(_fillPaint, foreground, glyphRun.Size))
using (var paintWrapper = CreatePaint(_fillPaint, foreground, new Rect(glyphRun.Size)))
{
var glyphRunImpl = (GlyphRunImpl)glyphRun.GlyphRunImpl;
@ -537,7 +537,7 @@ namespace Avalonia.Skia
var paint = new SKPaint();
Canvas.SaveLayer(paint);
_maskStack.Push(CreatePaint(paint, mask, bounds.Size, true));
_maskStack.Push(CreatePaint(paint, mask, bounds, true));
}
/// <inheritdoc />
@ -591,20 +591,21 @@ namespace Avalonia.Skia
/// Configure paint wrapper for using gradient brush.
/// </summary>
/// <param name="paintWrapper">Paint wrapper.</param>
/// <param name="targetSize">Target size.</param>
/// <param name="targetRect">Target bound rect.</param>
/// <param name="gradientBrush">Gradient brush.</param>
private void ConfigureGradientBrush(ref PaintWrapper paintWrapper, Size targetSize, IGradientBrush gradientBrush)
private void ConfigureGradientBrush(ref PaintWrapper paintWrapper, Rect targetRect, IGradientBrush gradientBrush)
{
var tileMode = gradientBrush.SpreadMethod.ToSKShaderTileMode();
var stopColors = gradientBrush.GradientStops.Select(s => s.Color.ToSKColor()).ToArray();
var stopOffsets = gradientBrush.GradientStops.Select(s => (float)s.Offset).ToArray();
var position = targetRect.Position.ToSKPoint();
switch (gradientBrush)
{
case ILinearGradientBrush linearGradient:
{
var start = linearGradient.StartPoint.ToPixels(targetSize).ToSKPoint();
var end = linearGradient.EndPoint.ToPixels(targetSize).ToSKPoint();
var start = position + linearGradient.StartPoint.ToPixels(targetRect.Size).ToSKPoint();
var end = position + linearGradient.EndPoint.ToPixels(targetRect.Size).ToSKPoint();
// would be nice to cache these shaders possibly?
using (var shader =
@ -617,10 +618,10 @@ namespace Avalonia.Skia
}
case IRadialGradientBrush radialGradient:
{
var center = radialGradient.Center.ToPixels(targetSize).ToSKPoint();
var radius = (float)(radialGradient.Radius * targetSize.Width);
var center = position + radialGradient.Center.ToPixels(targetRect.Size).ToSKPoint();
var radius = (float)(radialGradient.Radius * targetRect.Width);
var origin = radialGradient.GradientOrigin.ToPixels(targetSize).ToSKPoint();
var origin = position + radialGradient.GradientOrigin.ToPixels(targetRect.Size).ToSKPoint();
if (origin.Equals(center))
{
@ -665,7 +666,7 @@ namespace Avalonia.Skia
}
case IConicGradientBrush conicGradient:
{
var center = conicGradient.Center.ToPixels(targetSize).ToSKPoint();
var center = position + conicGradient.Center.ToPixels(targetRect.Size).ToSKPoint();
// Skia's default is that angle 0 is from the right hand side of the center point
// but we are matching CSS where the vertical point above the center is 0.
@ -867,10 +868,10 @@ namespace Avalonia.Skia
/// </summary>
/// <param name="paint">The paint to wrap.</param>
/// <param name="brush">Source brush.</param>
/// <param name="targetSize">Target size.</param>
/// <param name="targetRect">Target rect.</param>
/// <param name="disposePaint">Optional dispose of the supplied paint.</param>
/// <returns>Paint wrapper for given brush.</returns>
internal PaintWrapper CreatePaint(SKPaint paint, IBrush brush, Size targetSize, bool disposePaint = false)
internal PaintWrapper CreatePaint(SKPaint paint, IBrush brush, Rect targetRect, bool disposePaint = false)
{
var paintWrapper = new PaintWrapper(paint, disposePaint);
@ -889,7 +890,7 @@ namespace Avalonia.Skia
if (brush is IGradientBrush gradient)
{
ConfigureGradientBrush(ref paintWrapper, targetSize, gradient);
ConfigureGradientBrush(ref paintWrapper, targetRect, gradient);
return paintWrapper;
}
@ -909,7 +910,7 @@ namespace Avalonia.Skia
if (tileBrush != null && tileBrushImage != null)
{
ConfigureTileBrush(ref paintWrapper, targetSize, tileBrush, tileBrushImage);
ConfigureTileBrush(ref paintWrapper, targetRect.Size, tileBrush, tileBrushImage);
}
else
{
@ -924,10 +925,10 @@ namespace Avalonia.Skia
/// </summary>
/// <param name="paint">The paint to wrap.</param>
/// <param name="pen">Source pen.</param>
/// <param name="targetSize">Target size.</param>
/// <param name="targetRect">Target rect.</param>
/// <param name="disposePaint">Optional dispose of the supplied paint.</param>
/// <returns></returns>
private PaintWrapper CreatePaint(SKPaint paint, IPen pen, Size targetSize, bool disposePaint = false)
private PaintWrapper CreatePaint(SKPaint paint, IPen pen, Rect targetRect, bool disposePaint = false)
{
// In Skia 0 thickness means - use hairline rendering
// and for us it means - there is nothing rendered.
@ -936,7 +937,7 @@ namespace Avalonia.Skia
return default;
}
var rv = CreatePaint(paint, pen.Brush, targetSize, disposePaint);
var rv = CreatePaint(paint, pen.Brush, targetRect, disposePaint);
paint.IsStroke = true;
paint.StrokeWidth = (float) pen.Thickness;

4
src/Skia/Avalonia.Skia/FormattedTextImpl.cs

@ -278,9 +278,9 @@ namespace Avalonia.Skia
if (fb != null)
{
//TODO: figure out how to get the brush size
//TODO: figure out how to get the brush rect
currentWrapper = context.CreatePaint(new SKPaint { IsAntialias = true }, fb,
new Size());
default);
}
else
{

3
src/Skia/Avalonia.Skia/Gpu/ISkiaGpu.cs

@ -16,11 +16,12 @@ namespace Avalonia.Skia
/// <param name="surfaces">Surfaces.</param>
/// <returns>Created render target or <see langword="null"/> if it fails.</returns>
ISkiaGpuRenderTarget TryCreateRenderTarget(IEnumerable<object> surfaces);
/// <summary>
/// Creates an offscreen render target surface
/// </summary>
/// <param name="size">size in pixels</param>
/// <param name="session">current Skia render session</param>
ISkiaSurface TryCreateSurface(PixelSize size, ISkiaGpuRenderSession session);
}

4
src/Windows/Avalonia.Direct2D1/Media/AvaloniaTextRenderer.cs

@ -34,10 +34,10 @@ namespace Avalonia.Direct2D1.Media
{
var wrapper = clientDrawingEffect as BrushWrapper;
// TODO: Work out how to get the size below rather than passing new Size().
// TODO: Work out how to get the rect below rather than passing default.
var brush = (wrapper == null) ?
_foreground :
_context.CreateBrush(wrapper.Brush, new Size()).PlatformBrush;
_context.CreateBrush(wrapper.Brush, default).PlatformBrush;
_renderTarget.DrawGlyphRun(
new RawVector2 { X = baselineOriginX, Y = baselineOriginY },

32
src/Windows/Avalonia.Direct2D1/Media/DrawingContextImpl.cs

@ -192,7 +192,7 @@ namespace Avalonia.Direct2D1.Media
{
using (var d2dSource = ((BitmapImpl)source.Item).GetDirect2DBitmap(_deviceContext))
using (var sourceBrush = new BitmapBrush(_deviceContext, d2dSource.Value))
using (var d2dOpacityMask = CreateBrush(opacityMask, opacityMaskRect.Size))
using (var d2dOpacityMask = CreateBrush(opacityMask, opacityMaskRect))
using (var geometry = new SharpDX.Direct2D1.RectangleGeometry(Direct2D1Platform.Direct2D1Factory, destRect.ToDirect2D()))
{
if (d2dOpacityMask.PlatformBrush != null)
@ -217,9 +217,7 @@ namespace Avalonia.Direct2D1.Media
{
if (pen != null)
{
var size = new Rect(p1, p2).Size;
using (var d2dBrush = CreateBrush(pen.Brush, size))
using (var d2dBrush = CreateBrush(pen.Brush, new Rect(p1, p2).Normalize()))
using (var d2dStroke = pen.ToDirect2DStrokeStyle(_deviceContext))
{
if (d2dBrush.PlatformBrush != null)
@ -245,7 +243,7 @@ namespace Avalonia.Direct2D1.Media
{
if (brush != null)
{
using (var d2dBrush = CreateBrush(brush, geometry.Bounds.Size))
using (var d2dBrush = CreateBrush(brush, geometry.Bounds))
{
if (d2dBrush.PlatformBrush != null)
{
@ -257,7 +255,7 @@ namespace Avalonia.Direct2D1.Media
if (pen != null)
{
using (var d2dBrush = CreateBrush(pen.Brush, geometry.GetRenderBounds(pen).Size))
using (var d2dBrush = CreateBrush(pen.Brush, geometry.GetRenderBounds(pen)))
using (var d2dStroke = pen.ToDirect2DStrokeStyle(_deviceContext))
{
if (d2dBrush.PlatformBrush != null)
@ -282,7 +280,7 @@ namespace Avalonia.Direct2D1.Media
if (brush != null)
{
using (var b = CreateBrush(brush, rect.Size))
using (var b = CreateBrush(brush, rect))
{
if (b.PlatformBrush != null)
{
@ -311,7 +309,7 @@ namespace Avalonia.Direct2D1.Media
if (pen?.Brush != null)
{
using (var wrapper = CreateBrush(pen.Brush, rect.Size))
using (var wrapper = CreateBrush(pen.Brush, rect))
using (var d2dStroke = pen.ToDirect2DStrokeStyle(_deviceContext))
{
if (wrapper.PlatformBrush != null)
@ -349,7 +347,7 @@ namespace Avalonia.Direct2D1.Media
{
var impl = (FormattedTextImpl)text;
using (var brush = CreateBrush(foreground, impl.Bounds.Size))
using (var brush = CreateBrush(foreground, impl.Bounds))
using (var renderer = new AvaloniaTextRenderer(this, _deviceContext, brush.PlatformBrush))
{
if (brush.PlatformBrush != null)
@ -367,7 +365,7 @@ namespace Avalonia.Direct2D1.Media
/// <param name="glyphRun">The glyph run.</param>
public void DrawGlyphRun(IBrush foreground, GlyphRun glyphRun)
{
using (var brush = CreateBrush(foreground, glyphRun.Size))
using (var brush = CreateBrush(foreground, new Rect(glyphRun.Size)))
{
var glyphRunImpl = (GlyphRunImpl)glyphRun.GlyphRunImpl;
@ -458,9 +456,9 @@ namespace Avalonia.Direct2D1.Media
/// Creates a Direct2D brush wrapper for a Avalonia brush.
/// </summary>
/// <param name="brush">The avalonia brush.</param>
/// <param name="destinationSize">The size of the brush's target area.</param>
/// <param name="destinationRect">The brush's target area.</param>
/// <returns>The Direct2D brush wrapper.</returns>
public BrushImpl CreateBrush(IBrush brush, Size destinationSize)
public BrushImpl CreateBrush(IBrush brush, Rect destinationRect)
{
var solidColorBrush = brush as ISolidColorBrush;
var linearGradientBrush = brush as ILinearGradientBrush;
@ -475,11 +473,11 @@ namespace Avalonia.Direct2D1.Media
}
else if (linearGradientBrush != null)
{
return new LinearGradientBrushImpl(linearGradientBrush, _deviceContext, destinationSize);
return new LinearGradientBrushImpl(linearGradientBrush, _deviceContext, destinationRect);
}
else if (radialGradientBrush != null)
{
return new RadialGradientBrushImpl(radialGradientBrush, _deviceContext, destinationSize);
return new RadialGradientBrushImpl(radialGradientBrush, _deviceContext, destinationRect);
}
else if (conicGradientBrush != null)
{
@ -492,7 +490,7 @@ namespace Avalonia.Direct2D1.Media
imageBrush,
_deviceContext,
(BitmapImpl)imageBrush.Source.PlatformImpl.Item,
destinationSize);
destinationRect.Size);
}
else if (visualBrush != null)
{
@ -523,7 +521,7 @@ namespace Avalonia.Direct2D1.Media
visualBrush,
_deviceContext,
new D2DBitmapImpl(intermediate.Bitmap),
destinationSize);
destinationRect.Size);
}
}
}
@ -574,7 +572,7 @@ namespace Avalonia.Direct2D1.Media
ContentBounds = PrimitiveExtensions.RectangleInfinite,
MaskTransform = PrimitiveExtensions.Matrix3x2Identity,
Opacity = 1,
OpacityBrush = CreateBrush(mask, bounds.Size).PlatformBrush
OpacityBrush = CreateBrush(mask, bounds).PlatformBrush
};
var layer = _layerPool.Count != 0 ? _layerPool.Pop() : new Layer(_deviceContext);
_deviceContext.PushLayer(ref parameters, layer);

7
src/Windows/Avalonia.Direct2D1/Media/LinearGradientBrushImpl.cs

@ -8,7 +8,7 @@ namespace Avalonia.Direct2D1.Media
public LinearGradientBrushImpl(
ILinearGradientBrush brush,
SharpDX.Direct2D1.RenderTarget target,
Size destinationSize)
Rect destinationRect)
{
if (brush.GradientStops.Count == 0)
{
@ -21,8 +21,9 @@ namespace Avalonia.Direct2D1.Media
Position = (float)s.Offset
}).ToArray();
var startPoint = brush.StartPoint.ToPixels(destinationSize);
var endPoint = brush.EndPoint.ToPixels(destinationSize);
var position = destinationRect.Position;
var startPoint = position + brush.StartPoint.ToPixels(destinationRect.Size);
var endPoint = position + brush.EndPoint.ToPixels(destinationRect.Size);
using (var stops = new SharpDX.Direct2D1.GradientStopCollection(
target,

11
src/Windows/Avalonia.Direct2D1/Media/RadialGradientBrushImpl.cs

@ -8,7 +8,7 @@ namespace Avalonia.Direct2D1.Media
public RadialGradientBrushImpl(
IRadialGradientBrush brush,
SharpDX.Direct2D1.RenderTarget target,
Size destinationSize)
Rect destinationRect)
{
if (brush.GradientStops.Count == 0)
{
@ -21,12 +21,13 @@ namespace Avalonia.Direct2D1.Media
Position = (float)s.Offset
}).ToArray();
var centerPoint = brush.Center.ToPixels(destinationSize);
var gradientOrigin = brush.GradientOrigin.ToPixels(destinationSize) - centerPoint;
var position = destinationRect.Position;
var centerPoint = position + brush.Center.ToPixels(destinationRect.Size);
var gradientOrigin = position + brush.GradientOrigin.ToPixels(destinationRect.Size) - centerPoint;
// Note: Direct2D supports RadiusX and RadiusY but Cairo backend supports only Radius property
var radiusX = brush.Radius * destinationSize.Width;
var radiusY = brush.Radius * destinationSize.Height;
var radiusX = brush.Radius * destinationRect.Width;
var radiusY = brush.Radius * destinationRect.Height;
using (var stops = new SharpDX.Direct2D1.GradientStopCollection(
target,

1
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;

23
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);

1
src/Windows/Avalonia.Win32/WinRT/Composition/WinUICompositorConnection.cs

@ -17,7 +17,6 @@ namespace Avalonia.Win32.WinRT.Composition
class WinUICompositorConnection : IRenderTimer
{
private readonly EglContext _syncContext;
private IntPtr _queue;
private ICompositor _compositor;
private ICompositor2 _compositor2;
private ICompositor5 _compositor5;

15
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; }

1
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<object> Surfaces { get; set; }
public Action<RawInputEventArgs> Input { get; set; }

268
tests/Avalonia.Animation.UnitTests/AnimationIterationTests.cs

@ -9,6 +9,8 @@ using Avalonia.UnitTests;
using Avalonia.Data;
using Xunit;
using Avalonia.Animation.Easings;
using System.Threading;
using System.Reactive.Linq;
namespace Avalonia.Animation.UnitTests
{
@ -176,5 +178,271 @@ namespace Avalonia.Animation.UnitTests
clock.Step(TimeSpan.FromSeconds(0.100d));
Assert.Equal(border.Width, 300d);
}
[Fact(Skip = "See #6111")]
public void Dispose_Subscription_Should_Stop_Animation()
{
var keyframe1 = new KeyFrame()
{
Setters =
{
new Setter(Border.WidthProperty, 200d),
},
Cue = new Cue(1d)
};
var keyframe2 = new KeyFrame()
{
Setters =
{
new Setter(Border.WidthProperty, 100d),
},
Cue = new Cue(0d)
};
var animation = new Animation()
{
Duration = TimeSpan.FromSeconds(10),
Delay = TimeSpan.FromSeconds(0),
DelayBetweenIterations = TimeSpan.FromSeconds(0),
IterationCount = new IterationCount(1),
Children =
{
keyframe2,
keyframe1
}
};
var border = new Border()
{
Height = 100d,
Width = 50d
};
var propertyChangedCount = 0;
var animationCompletedCount = 0;
border.PropertyChanged += (sender, e) =>
{
if (e.Property == Control.WidthProperty)
{
propertyChangedCount++;
}
};
var clock = new TestClock();
var disposable = animation.Apply(border, clock, Observable.Return(true), () => animationCompletedCount++);
Assert.Equal(0, propertyChangedCount);
clock.Step(TimeSpan.FromSeconds(0));
Assert.Equal(0, animationCompletedCount);
Assert.Equal(1, propertyChangedCount);
disposable.Dispose();
// Clock ticks should be ignored after Dispose
clock.Step(TimeSpan.FromSeconds(5));
clock.Step(TimeSpan.FromSeconds(6));
clock.Step(TimeSpan.FromSeconds(7));
// On animation disposing (cancellation) on completed is not invoked (is it expected?)
Assert.Equal(0, animationCompletedCount);
// Initial property changed before cancellation + animation value removal.
Assert.Equal(2, propertyChangedCount);
}
[Fact]
public void Do_Not_Run_Cancelled_Animation()
{
var keyframe1 = new KeyFrame()
{
Setters =
{
new Setter(Border.WidthProperty, 200d),
},
Cue = new Cue(1d)
};
var keyframe2 = new KeyFrame()
{
Setters =
{
new Setter(Border.WidthProperty, 100d),
},
Cue = new Cue(0d)
};
var animation = new Animation()
{
Duration = TimeSpan.FromSeconds(10),
Delay = TimeSpan.FromSeconds(0),
DelayBetweenIterations = TimeSpan.FromSeconds(0),
IterationCount = new IterationCount(1),
Children =
{
keyframe2,
keyframe1
}
};
var border = new Border()
{
Height = 100d,
Width = 100d
};
var propertyChangedCount = 0;
border.PropertyChanged += (sender, e) =>
{
if (e.Property == Control.WidthProperty)
{
propertyChangedCount++;
}
};
var clock = new TestClock();
var cancellationTokenSource = new CancellationTokenSource();
cancellationTokenSource.Cancel();
var animationRun = animation.RunAsync(border, clock, cancellationTokenSource.Token);
clock.Step(TimeSpan.FromSeconds(10));
Assert.Equal(0, propertyChangedCount);
Assert.True(animationRun.IsCompleted);
}
[Fact(Skip = "See #6111")]
public void Cancellation_Should_Stop_Animation()
{
var keyframe1 = new KeyFrame()
{
Setters =
{
new Setter(Border.WidthProperty, 200d),
},
Cue = new Cue(1d)
};
var keyframe2 = new KeyFrame()
{
Setters =
{
new Setter(Border.WidthProperty, 100d),
},
Cue = new Cue(0d)
};
var animation = new Animation()
{
Duration = TimeSpan.FromSeconds(10),
Delay = TimeSpan.FromSeconds(0),
DelayBetweenIterations = TimeSpan.FromSeconds(0),
IterationCount = new IterationCount(1),
Children =
{
keyframe2,
keyframe1
}
};
var border = new Border()
{
Height = 100d,
Width = 50d
};
var propertyChangedCount = 0;
border.PropertyChanged += (sender, e) =>
{
if (e.Property == Control.WidthProperty)
{
propertyChangedCount++;
}
};
var clock = new TestClock();
var cancellationTokenSource = new CancellationTokenSource();
var animationRun = animation.RunAsync(border, clock, cancellationTokenSource.Token);
Assert.Equal(0, propertyChangedCount);
clock.Step(TimeSpan.FromSeconds(0));
Assert.False(animationRun.IsCompleted);
Assert.Equal(1, propertyChangedCount);
cancellationTokenSource.Cancel();
clock.Step(TimeSpan.FromSeconds(1));
clock.Step(TimeSpan.FromSeconds(2));
clock.Step(TimeSpan.FromSeconds(3));
//Assert.Equal(2, propertyChangedCount);
animationRun.Wait();
clock.Step(TimeSpan.FromSeconds(6));
Assert.True(animationRun.IsCompleted);
Assert.Equal(2, propertyChangedCount);
}
[Fact]
public void Cancellation_Of_Completed_Animation_Does_Not_Fail()
{
var keyframe1 = new KeyFrame()
{
Setters =
{
new Setter(Border.WidthProperty, 200d),
},
Cue = new Cue(1d)
};
var keyframe2 = new KeyFrame()
{
Setters =
{
new Setter(Border.WidthProperty, 100d),
},
Cue = new Cue(0d)
};
var animation = new Animation()
{
Duration = TimeSpan.FromSeconds(10),
Delay = TimeSpan.FromSeconds(0),
DelayBetweenIterations = TimeSpan.FromSeconds(0),
IterationCount = new IterationCount(1),
Children =
{
keyframe2,
keyframe1
}
};
var border = new Border()
{
Height = 100d,
Width = 50d
};
var propertyChangedCount = 0;
border.PropertyChanged += (sender, e) =>
{
if (e.Property == Control.WidthProperty)
{
propertyChangedCount++;
}
};
var clock = new TestClock();
var cancellationTokenSource = new CancellationTokenSource();
var animationRun = animation.RunAsync(border, clock, cancellationTokenSource.Token);
Assert.Equal(0, propertyChangedCount);
clock.Step(TimeSpan.FromSeconds(0));
Assert.False(animationRun.IsCompleted);
Assert.Equal(1, propertyChangedCount);
clock.Step(TimeSpan.FromSeconds(10));
Assert.True(animationRun.IsCompleted);
Assert.Equal(2, propertyChangedCount);
cancellationTokenSource.Cancel();
animationRun.Wait();
}
}
}

61
tests/Avalonia.Controls.UnitTests/ComboBoxTests.cs

@ -201,6 +201,65 @@ namespace Avalonia.Controls.UnitTests
}
}
}
[Fact]
public void Close_Window_On_Alt_F4_When_ComboBox_Is_Focus()
{
var inputManagerMock = new Moq.Mock<IInputManager>();
var services = TestServices.StyledWindow.With(inputManager: inputManagerMock.Object);
using (UnitTestApplication.Start(TestServices.StyledWindow))
{
var window = new Window();
window.KeyDown += (s, e) =>
{
if (e.Handled == false
&& e.KeyModifiers.HasAllFlags(KeyModifiers.Alt) == true
&& e.Key == Key.F4 )
{
e.Handled = true;
window.Close();
}
};
var count = 0;
var target = new ComboBox
{
Items = new[] { new Canvas() },
SelectedIndex = 0,
Template = GetTemplate(),
};
window.Content = target;
window.Closing +=
(sender, e) =>
{
count++;
};
window.Show();
target.Focus();
_helper.Down(target);
_helper.Up(target);
Assert.True(target.IsDropDownOpen);
target.RaiseEvent(new KeyEventArgs
{
RoutedEvent = InputElement.KeyDownEvent,
KeyModifiers = KeyModifiers.Alt,
Key = Key.F4
});
Assert.Equal(1, count);
}
}
}
}

2
tests/Avalonia.Controls.UnitTests/TreeViewTests.cs

@ -1066,7 +1066,7 @@ namespace Avalonia.Controls.UnitTests
[Fact]
public void Auto_Expanding_In_Style_Should_Not_Break_Range_Selection()
{
/// Issue #2980.
// Issue #2980.
using (Application())
{
var target = new DerivedTreeView

136
tests/Avalonia.Controls.UnitTests/WindowTests.cs

@ -279,10 +279,11 @@ namespace Avalonia.Controls.UnitTests
{
using (UnitTestApplication.Start(TestServices.StyledWindow))
{
var parent = Mock.Of<Window>();
var parent = new Window();
var renderer = new Mock<IRenderer>();
var target = new Window(CreateImpl(renderer));
parent.Show();
target.ShowDialog<object>(parent);
renderer.Verify(x => x.Start(), Times.Once);
@ -294,10 +295,11 @@ namespace Avalonia.Controls.UnitTests
{
using (UnitTestApplication.Start(TestServices.StyledWindow))
{
var parent = Mock.Of<Window>();
var parent = new Window();
var target = new Window();
var raised = false;
parent.Show();
target.Opened += (s, e) => raised = true;
target.ShowDialog<object>(parent);
@ -326,14 +328,15 @@ namespace Avalonia.Controls.UnitTests
{
using (UnitTestApplication.Start(TestServices.StyledWindow))
{
var parent = new Mock<Window>();
var parent = new Window();
var windowImpl = new Mock<IWindowImpl>();
windowImpl.SetupProperty(x => x.Closed);
windowImpl.Setup(x => x.DesktopScaling).Returns(1);
windowImpl.Setup(x => x.RenderScaling).Returns(1);
parent.Show();
var target = new Window(windowImpl.Object);
var task = target.ShowDialog<bool>(parent.Object);
var task = target.ShowDialog<bool>(parent);
windowImpl.Object.Closed();
@ -366,14 +369,16 @@ namespace Avalonia.Controls.UnitTests
{
using (UnitTestApplication.Start(TestServices.StyledWindow))
{
var parent = new Mock<Window>();
var parent = new Window();
var windowImpl = new Mock<IWindowImpl>();
windowImpl.SetupProperty(x => x.Closed);
windowImpl.Setup(x => x.DesktopScaling).Returns(1);
windowImpl.Setup(x => x.RenderScaling).Returns(1);
parent.Show();
var target = new Window(windowImpl.Object);
var task = target.ShowDialog<bool>(parent.Object);
var task = target.ShowDialog<bool>(parent);
windowImpl.Object.Closed();
await task;
@ -381,12 +386,128 @@ namespace Avalonia.Controls.UnitTests
var openedRaised = false;
target.Opened += (s, e) => openedRaised = true;
var ex = await Assert.ThrowsAsync<InvalidOperationException>(() => target.ShowDialog<bool>(parent.Object));
var ex = await Assert.ThrowsAsync<InvalidOperationException>(() => target.ShowDialog<bool>(parent));
Assert.Equal("Cannot re-show a closed window.", ex.Message);
Assert.False(openedRaised);
}
}
[Fact]
public void Calling_Show_With_Closed_Parent_Window_Should_Throw()
{
using (UnitTestApplication.Start(TestServices.StyledWindow))
{
var parent = new Window();
var target = new Window();
parent.Close();
var ex = Assert.Throws<InvalidOperationException>(() => target.Show(parent));
Assert.Equal("Cannot show a window with a closed parent.", ex.Message);
}
}
[Fact]
public async Task Calling_ShowDialog_With_Closed_Parent_Window_Should_Throw()
{
using (UnitTestApplication.Start(TestServices.StyledWindow))
{
var parent = new Window();
var target = new Window();
parent.Close();
var ex = await Assert.ThrowsAsync<InvalidOperationException>(() => target.ShowDialog(parent));
Assert.Equal("Cannot show a window with a closed owner.", ex.Message);
}
}
[Fact]
public void Calling_Show_With_Invisible_Parent_Window_Should_Throw()
{
using (UnitTestApplication.Start(TestServices.StyledWindow))
{
var parent = new Window();
var target = new Window();
var ex = Assert.Throws<InvalidOperationException>(() => target.Show(parent));
Assert.Equal("Cannot show window with non-visible parent.", ex.Message);
}
}
[Fact]
public async Task Calling_ShowDialog_With_Invisible_Parent_Window_Should_Throw()
{
using (UnitTestApplication.Start(TestServices.StyledWindow))
{
var parent = new Window();
var target = new Window();
var ex = await Assert.ThrowsAsync<InvalidOperationException>(() => target.ShowDialog(parent));
Assert.Equal("Cannot show window with non-visible parent.", ex.Message);
}
}
[Fact]
public void Calling_Show_With_Self_As_Parent_Window_Should_Throw()
{
using (UnitTestApplication.Start(TestServices.StyledWindow))
{
var target = new Window();
var ex = Assert.Throws<InvalidOperationException>(() => target.Show(target));
Assert.Equal("A Window cannot be its own parent.", ex.Message);
}
}
[Fact]
public async Task Calling_ShowDialog_With_Self_As_Parent_Window_Should_Throw()
{
using (UnitTestApplication.Start(TestServices.StyledWindow))
{
var target = new Window();
var ex = await Assert.ThrowsAsync<InvalidOperationException>(() => target.ShowDialog(target));
Assert.Equal("A Window cannot be its own owner.", ex.Message);
}
}
[Fact]
public void Hiding_Parent_Window_Should_Close_Children()
{
using (UnitTestApplication.Start(TestServices.MockWindowingPlatform))
{
var parent = new Window();
var child = new Window();
parent.Show();
child.Show(parent);
parent.Hide();
Assert.False(parent.IsVisible);
Assert.False(child.IsVisible);
}
}
[Fact]
public void Hiding_Parent_Window_Should_Close_Dialog_Children()
{
using (UnitTestApplication.Start(TestServices.MockWindowingPlatform))
{
var parent = new Window();
var child = new Window();
parent.Show();
child.ShowDialog(parent);
parent.Hide();
Assert.False(parent.IsVisible);
Assert.False(child.IsVisible);
}
}
[Fact]
public void Window_Should_Be_Centered_When_WindowStartupLocation_Is_CenterScreen()
{
@ -686,6 +807,7 @@ namespace Avalonia.Controls.UnitTests
protected override void Show(Window window)
{
var owner = new Window();
owner.Show();
window.ShowDialog(owner);
}
}

40
tests/Avalonia.RenderTests/Media/ConicGradientBrushTests.cs

@ -1,5 +1,6 @@
using Avalonia.Controls;
using Avalonia.Media;
using System;
using System.Threading.Tasks;
using Xunit;
@ -174,5 +175,44 @@ namespace Avalonia.Direct2D1.RenderTests.Media
await RenderToFile(target);
CompareImages();
}
[Fact]
public async Task ConicGradientBrush_DrawingContext()
{
var brush = new ConicGradientBrush
{
GradientStops =
{
new GradientStop { Color = Colors.Red, Offset = 0 },
new GradientStop { Color = Colors.Yellow, Offset = 0.1667 },
new GradientStop { Color = Colors.Lime, Offset = 0.3333 },
new GradientStop { Color = Colors.Aqua, Offset = 0.5000 },
new GradientStop { Color = Colors.Blue, Offset = 0.6667 },
new GradientStop { Color = Colors.Magenta, Offset = 0.8333 },
new GradientStop { Color = Colors.Red, Offset = 1 },
}
};
Decorator target = new Decorator
{
Width = 200,
Height = 200,
Child = new DrawnControl(c =>
{
c.DrawRectangle(brush, null, new Rect(0, 0, 100, 100));
c.DrawRectangle(brush, null, new Rect(100, 100, 100, 100));
}),
};
await RenderToFile(target);
CompareImages();
}
private class DrawnControl : Control
{
private readonly Action<DrawingContext> _render;
public DrawnControl(Action<DrawingContext> render) => _render = render;
public override void Render(DrawingContext context) => _render(context);
}
}
}

36
tests/Avalonia.RenderTests/Media/LinearGradientBrushTests.cs

@ -72,5 +72,41 @@ namespace Avalonia.Direct2D1.RenderTests.Media
await RenderToFile(target);
CompareImages();
}
[Fact]
public async Task LinearGradientBrush_DrawingContext()
{
var brush = new LinearGradientBrush
{
StartPoint = new RelativePoint(0, 0, RelativeUnit.Relative),
EndPoint = new RelativePoint(1, 1, RelativeUnit.Relative),
GradientStops =
{
new GradientStop { Color = Colors.Red, Offset = 0 },
new GradientStop { Color = Colors.Blue, Offset = 1 }
}
};
Decorator target = new Decorator
{
Width = 200,
Height = 200,
Child = new DrawnControl(c =>
{
c.DrawRectangle(brush, null, new Rect(0, 0, 100, 100));
c.DrawRectangle(brush, null, new Rect(100, 100, 100, 100));
}),
};
await RenderToFile(target);
CompareImages();
}
private class DrawnControl : Control
{
private readonly Action<DrawingContext> _render;
public DrawnControl(Action<DrawingContext> render) => _render = render;
public override void Render(DrawingContext context) => _render(context);
}
}
}

34
tests/Avalonia.RenderTests/Media/RadialGradientBrushTests.cs

@ -165,5 +165,39 @@ namespace Avalonia.Direct2D1.RenderTests.Media
await RenderToFile(target);
CompareImages();
}
[Fact]
public async Task RadialGradientBrush_DrawingContext()
{
var brush = new RadialGradientBrush
{
GradientStops =
{
new GradientStop { Color = Colors.Red, Offset = 0 },
new GradientStop { Color = Colors.Blue, Offset = 1 }
}
};
Decorator target = new Decorator
{
Width = 200,
Height = 200,
Child = new DrawnControl(c =>
{
c.DrawRectangle(brush, null, new Rect(0, 0, 100, 100));
c.DrawRectangle(brush, null, new Rect(100, 100, 100, 100));
}),
};
await RenderToFile(target);
CompareImages();
}
private class DrawnControl : Control
{
private readonly Action<DrawingContext> _render;
public DrawnControl(Action<DrawingContext> render) => _render = render;
public override void Render(DrawingContext context) => _render(context);
}
}
}

BIN
tests/TestFiles/Direct2D1/Media/ConicGradientBrush/ConicGradientBrush_DrawingContext.expected.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 631 B

BIN
tests/TestFiles/Direct2D1/Media/LinearGradientBrush/LinearGradientBrush_DrawingContext.expected.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.9 KiB

BIN
tests/TestFiles/Direct2D1/Media/RadialGradientBrush/RadialGradientBrush_DrawingContext.expected.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.9 KiB

BIN
tests/TestFiles/Skia/Media/ConicGradientBrush/ConicGradientBrush_DrawingContext.expected.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.8 KiB

BIN
tests/TestFiles/Skia/Media/LinearGradientBrush/LinearGradientBrush_DrawingContext.expected.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

BIN
tests/TestFiles/Skia/Media/RadialGradientBrush/RadialGradientBrush_DrawingContext.expected.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.8 KiB

Loading…
Cancel
Save