Browse Source

Merge branch 'master' into context-request-keyboard

pull/6059/head
Jumar Macato 5 years ago
committed by GitHub
parent
commit
bb44c69490
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 3
      .gitignore
  2. 196
      native/Avalonia.Native/inc/comimpl.h
  3. 32
      native/Avalonia.Native/src/OSX/AvnString.mm
  4. 4
      native/Avalonia.Native/src/OSX/Screens.mm
  5. 8
      native/Avalonia.Native/src/OSX/cgl.mm
  6. 82
      native/Avalonia.Native/src/OSX/clipboard.mm
  7. 63
      native/Avalonia.Native/src/OSX/controlhost.mm
  8. 52
      native/Avalonia.Native/src/OSX/cursor.mm
  9. 174
      native/Avalonia.Native/src/OSX/main.mm
  10. 22
      native/Avalonia.Native/src/OSX/menu.mm
  11. 2
      native/Avalonia.Native/src/OSX/platformthreading.mm
  12. 6
      native/Avalonia.Native/src/OSX/rendertarget.mm
  13. 271
      native/Avalonia.Native/src/OSX/window.mm
  14. 43
      samples/ControlCatalog/Pages/NumericUpDownPage.xaml
  15. 15
      samples/ControlCatalog/Pages/NumericUpDownPage.xaml.cs
  16. 10
      samples/Sandbox/Program.cs
  17. 2
      src/Android/Avalonia.Android/Platform/SkiaPlatform/TopLevelImpl.cs
  18. 30
      src/Avalonia.Animation/Animation.cs
  19. 6
      src/Avalonia.Animation/ApiCompatBaseline.txt
  20. 3
      src/Avalonia.Animation/IAnimation.cs
  21. 40
      src/Avalonia.Base/Collections/AvaloniaList.cs
  22. 32
      src/Avalonia.Base/Utilities/MathUtilities.cs
  23. 3
      src/Avalonia.Controls.DataGrid/DataGrid.cs
  24. 2
      src/Avalonia.Controls.DataGrid/DataGridRows.cs
  25. 24
      src/Avalonia.Controls.DataGrid/Utils/ReflectionHelper.cs
  26. 28
      src/Avalonia.Controls/ApiCompatBaseline.txt
  27. 31
      src/Avalonia.Controls/Application.cs
  28. 71
      src/Avalonia.Controls/ComboBox.cs
  29. 2
      src/Avalonia.Controls/Embedding/Offscreen/OffscreenTopLevelImpl.cs
  30. 21
      src/Avalonia.Controls/Expander.cs
  31. 1
      src/Avalonia.Controls/Grid.cs
  32. 6
      src/Avalonia.Controls/GridLength.cs
  33. 1
      src/Avalonia.Controls/ItemsControl.cs
  34. 1
      src/Avalonia.Controls/Menu.cs
  35. 6
      src/Avalonia.Controls/MenuItem.cs
  36. 148
      src/Avalonia.Controls/NumericUpDown/NumericUpDown.cs
  37. 6
      src/Avalonia.Controls/NumericUpDown/NumericUpDownValueChangedEventArgs.cs
  38. 5
      src/Avalonia.Controls/Platform/ITopLevelImpl.cs
  39. 2
      src/Avalonia.Controls/Presenters/CarouselPresenter.cs
  40. 2
      src/Avalonia.Controls/Presenters/ScrollContentPresenter.cs
  41. 67
      src/Avalonia.Controls/Primitives/AdornerLayer.cs
  42. 74
      src/Avalonia.Controls/Primitives/SelectingItemsControl.cs
  43. 20
      src/Avalonia.Controls/Primitives/Thumb.cs
  44. 1
      src/Avalonia.Controls/Repeater/ViewportManager.cs
  45. 1
      src/Avalonia.Controls/Repeater/VirtualizationInfo.cs
  46. 8
      src/Avalonia.Controls/RowDefinitions.cs
  47. 103
      src/Avalonia.Controls/Shapes/Arc.cs
  48. 2
      src/Avalonia.Controls/SplitView.cs
  49. 4
      src/Avalonia.Controls/TextBox.cs
  50. 18
      src/Avalonia.Controls/TopLevel.cs
  51. 6
      src/Avalonia.Controls/TreeView.cs
  52. 39
      src/Avalonia.Controls/Window.cs
  53. 1
      src/Avalonia.Controls/WindowBase.cs
  54. 1
      src/Avalonia.DesignerSupport/Remote/Stubs.cs
  55. 1
      src/Avalonia.Headless/HeadlessWindowImpl.cs
  56. 5
      src/Avalonia.Layout/ElementManager.cs
  57. 14
      src/Avalonia.Native/WindowImplBase.cs
  58. 1
      src/Avalonia.Native/avn.idl
  59. 13
      src/Avalonia.ReactiveUI/TransitioningContentControl.cs
  60. 6
      src/Avalonia.Themes.Fluent/Controls/ComboBox.xaml
  61. 21
      src/Avalonia.Visuals/Animation/CompositePageTransition.cs
  62. 65
      src/Avalonia.Visuals/Animation/CrossFade.cs
  63. 6
      src/Avalonia.Visuals/Animation/IPageTransition.cs
  64. 30
      src/Avalonia.Visuals/Animation/PageSlide.cs
  65. 10
      src/Avalonia.Visuals/ApiCompatBaseline.txt
  66. 41
      src/Avalonia.Visuals/Media/BoxShadow.cs
  67. 20
      src/Avalonia.Visuals/Media/BoxShadows.cs
  68. 3
      src/Avalonia.Visuals/Media/Brush.cs
  69. 11
      src/Avalonia.Visuals/Media/Immutable/ImmutableSolidColorBrush.cs
  70. 4
      src/Avalonia.Visuals/Rendering/SceneGraph/BitmapBlendModeNode.cs
  71. 1
      src/Avalonia.X11/TransparencyHelper.cs
  72. 32
      src/Avalonia.X11/X11Window.cs
  73. 2
      src/Linux/Avalonia.LinuxFramebuffer/FramebufferToplevelImpl.cs
  74. 1
      src/Linux/Avalonia.LinuxFramebuffer/Input/EvDev/EvDevTouchScreen.cs
  75. 5
      src/Linux/Avalonia.LinuxFramebuffer/LinuxFramebufferPlatform.cs
  76. 2
      src/Linux/Avalonia.LinuxFramebuffer/LockedFramebuffer.cs
  77. 1
      src/Linux/Avalonia.LinuxFramebuffer/Output/DrmOutput.cs
  78. 88
      src/Linux/Avalonia.LinuxFramebuffer/Output/FbdevOutput.cs
  79. 14
      src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/AvaloniaXamlIlLanguageParseIntrinsics.cs
  80. 28
      src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlBindingPathParser.cs
  81. 9
      src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlWellKnownTypes.cs
  82. 2
      src/Skia/Avalonia.Skia/DrawingContextImpl.cs
  83. 3
      src/Skia/Avalonia.Skia/Gpu/ISkiaGpu.cs
  84. 1
      src/Windows/Avalonia.Win32.Interop/Wpf/WpfTopLevelImpl.cs
  85. 23
      src/Windows/Avalonia.Win32/Interop/UnmanagedMethods.cs
  86. 1
      src/Windows/Avalonia.Win32/WinRT/Composition/WinUICompositorConnection.cs
  87. 15
      src/Windows/Avalonia.Win32/WindowImpl.cs
  88. 1
      src/iOS/Avalonia.iOS/AvaloniaView.cs
  89. 268
      tests/Avalonia.Animation.UnitTests/AnimationIterationTests.cs
  90. 61
      tests/Avalonia.Controls.UnitTests/ComboBoxTests.cs
  91. 2
      tests/Avalonia.Controls.UnitTests/TreeViewTests.cs
  92. 136
      tests/Avalonia.Controls.UnitTests/WindowTests.cs
  93. 104
      tests/Avalonia.Markup.Xaml.UnitTests/MarkupExtensions/CompiledBindingExtensionTests.cs
  94. 20
      tests/Avalonia.Markup.Xaml.UnitTests/MarkupExtensions/DynamicResourceExtensionTests.cs
  95. 2
      tests/Avalonia.Markup.Xaml.UnitTests/MarkupExtensions/ResourceIncludeTests.cs
  96. 20
      tests/Avalonia.Markup.Xaml.UnitTests/MarkupExtensions/StaticResourceExtensionTests.cs
  97. 2
      tests/Avalonia.Markup.Xaml.UnitTests/Xaml/BasicTests.cs
  98. 2
      tests/Avalonia.Markup.Xaml.UnitTests/Xaml/StyleTests.cs

3
.gitignore

@ -106,6 +106,9 @@ _NCrunch_*/
*.ncrunchsolution.user *.ncrunchsolution.user
nCrunchTemp_* nCrunchTemp_*
# CodeRush
.cr/
# Others # Others
sql/ sql/
*.Cache *.Cache

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

@ -8,8 +8,109 @@
#include <cstring> #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); __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 class ComObject : public virtual IUnknown
{ {
private: private:
@ -58,6 +159,12 @@ public:
_refCount++; _refCount++;
return S_OK; return S_OK;
} }
protected:
ComPtr<IUnknown> UnknownSelf()
{
return this;
}
}; };
@ -104,94 +211,5 @@ public:
virtual ~ComSingleObject(){} 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 #endif // COMIMPL_H_INCLUDED
#pragma clang diagnostic pop #pragma clang diagnostic pop

32
native/Avalonia.Native/src/OSX/AvnString.mm

@ -43,6 +43,8 @@ public:
virtual HRESULT Pointer(void**retOut) override virtual HRESULT Pointer(void**retOut) override
{ {
START_COM_CALL;
@autoreleasepool @autoreleasepool
{ {
if(retOut == nullptr) if(retOut == nullptr)
@ -58,14 +60,19 @@ public:
virtual HRESULT Length(int*retOut) override 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 virtual HRESULT Get(unsigned int index, IAvnString**ppv) override
{ {
if(_list.size() <= index) START_COM_CALL;
return E_INVALIDARG;
*ppv = _list[index].getRetainedReference(); @autoreleasepool
return S_OK; {
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: public:
virtual HRESULT GetScreenCount (int* ret) override virtual HRESULT GetScreenCount (int* ret) override
{ {
START_COM_CALL;
@autoreleasepool @autoreleasepool
{ {
*ret = (int)[NSScreen screens].count; *ret = (int)[NSScreen screens].count;
@ -18,6 +20,8 @@ public:
virtual HRESULT GetScreen (int index, AvnScreen* ret) override virtual HRESULT GetScreen (int index, AvnScreen* ret) override
{ {
START_COM_CALL;
@autoreleasepool @autoreleasepool
{ {
if(index < 0 || index >= [NSScreen screens].count) if(index < 0 || index >= [NSScreen screens].count)

8
native/Avalonia.Native/src/OSX/cgl.mm

@ -69,6 +69,8 @@ public:
virtual HRESULT LegacyMakeCurrent() override virtual HRESULT LegacyMakeCurrent() override
{ {
START_COM_CALL;
if(CGLSetCurrentContext(Context) != 0) if(CGLSetCurrentContext(Context) != 0)
return E_FAIL; return E_FAIL;
return S_OK; return S_OK;
@ -76,6 +78,8 @@ public:
virtual HRESULT MakeCurrent(IUnknown** ppv) override virtual HRESULT MakeCurrent(IUnknown** ppv) override
{ {
START_COM_CALL;
CGLContextObj saved = CGLGetCurrentContext(); CGLContextObj saved = CGLGetCurrentContext();
CGLLockContext(Context); CGLLockContext(Context);
if(CGLSetCurrentContext(Context) != 0) if(CGLSetCurrentContext(Context) != 0)
@ -128,6 +132,8 @@ public:
virtual HRESULT CreateContext(IAvnGlContext* share, IAvnGlContext**ppv) override virtual HRESULT CreateContext(IAvnGlContext* share, IAvnGlContext**ppv) override
{ {
START_COM_CALL;
CGLContextObj shareContext = nil; CGLContextObj shareContext = nil;
if(share != nil) if(share != nil)
{ {
@ -144,6 +150,8 @@ public:
virtual HRESULT WrapContext(void* native, IAvnGlContext**ppv) override virtual HRESULT WrapContext(void* native, IAvnGlContext**ppv) override
{ {
START_COM_CALL;
if(native == nil) if(native == nil)
return E_INVALIDARG; return E_INVALIDARG;
*ppv = new AvnGlContext((CGLContextObj) native); *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 virtual HRESULT GetText (char* type, IAvnString**ppv) override
{ {
START_COM_CALL;
@autoreleasepool @autoreleasepool
{ {
if(ppv == nullptr) if(ppv == nullptr)
@ -42,6 +44,8 @@ public:
virtual HRESULT GetStrings(char* type, IAvnStringArray**ppv) override virtual HRESULT GetStrings(char* type, IAvnStringArray**ppv) override
{ {
START_COM_CALL;
@autoreleasepool @autoreleasepool
{ {
*ppv= nil; *ppv= nil;
@ -69,56 +73,71 @@ public:
virtual HRESULT SetText (char* type, char* utf8String) override virtual HRESULT SetText (char* type, char* utf8String) override
{ {
Clear(); START_COM_CALL;
@autoreleasepool @autoreleasepool
{ {
Clear();
auto string = [NSString stringWithUTF8String:(const char*)utf8String]; auto string = [NSString stringWithUTF8String:(const char*)utf8String];
auto typeString = [NSString stringWithUTF8String:(const char*)type]; auto typeString = [NSString stringWithUTF8String:(const char*)type];
if(_item == nil) if(_item == nil)
[_pb setString: string forType: typeString]; [_pb setString: string forType: typeString];
else else
[_item setString: string forType:typeString]; [_item setString: string forType:typeString];
}
return S_OK; return S_OK;
}
} }
virtual HRESULT SetBytes(char* type, void* bytes, int len) override virtual HRESULT SetBytes(char* type, void* bytes, int len) override
{ {
auto typeString = [NSString stringWithUTF8String:(const char*)type]; START_COM_CALL;
auto data = [NSData dataWithBytes:bytes length:len];
if(_item == nil) @autoreleasepool
[_pb setData:data forType:typeString]; {
else auto typeString = [NSString stringWithUTF8String:(const char*)type];
[_item setData:data forType:typeString]; auto data = [NSData dataWithBytes:bytes length:len];
return S_OK; 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 virtual HRESULT GetBytes(char* type, IAvnString**ppv) override
{ {
*ppv = nil; START_COM_CALL;
auto typeString = [NSString stringWithUTF8String:(const char*)type];
NSData*data; @autoreleasepool
@try
{ {
if(_item) *ppv = nil;
data = [_item dataForType:typeString]; auto typeString = [NSString stringWithUTF8String:(const char*)type];
else NSData*data;
data = [_pb dataForType:typeString]; @try
if(data == nil) {
if(_item)
data = [_item dataForType:typeString];
else
data = [_pb dataForType:typeString];
if(data == nil)
return E_FAIL;
}
@catch(NSException* e)
{
return E_FAIL; 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 virtual HRESULT Clear() override
{ {
START_COM_CALL;
@autoreleasepool @autoreleasepool
{ {
if(_item != nil) if(_item != nil)
@ -128,15 +147,20 @@ public:
[_pb clearContents]; [_pb clearContents];
[_pb setString:@"" forType:NSPasteboardTypeString]; [_pb setString:@"" forType:NSPasteboardTypeString];
} }
}
return S_OK; return S_OK;
}
} }
virtual HRESULT ObtainFormats(IAvnStringArray** ppv) override virtual HRESULT ObtainFormats(IAvnStringArray** ppv) override
{ {
*ppv = CreateAvnStringArray(_item == nil ? [_pb types] : [_item types]); START_COM_CALL;
return S_OK;
@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 virtual HRESULT CreateDefaultChild(void* parent, void** retOut) override
{ {
NSView* view = [NSView new]; START_COM_CALL;
[view setWantsLayer: true];
*retOut = (__bridge_retained void*)view; @autoreleasepool
return S_OK; {
NSView* view = [NSView new];
[view setWantsLayer: true];
*retOut = (__bridge_retained void*)view;
return S_OK;
}
}; };
virtual IAvnNativeControlHostTopLevelAttachment* CreateAttachment() override virtual IAvnNativeControlHostTopLevelAttachment* CreateAttachment() override
@ -69,32 +74,42 @@ public:
virtual HRESULT InitializeWithChildHandle(void* child) override virtual HRESULT InitializeWithChildHandle(void* child) override
{ {
if(_child != nil) START_COM_CALL;
return E_FAIL;
_child = (__bridge NSView*)child; @autoreleasepool
if(_child == nil) {
return E_FAIL; if(_child != nil)
[_holder addSubview:_child]; return E_FAIL;
[_child setHidden: false]; _child = (__bridge NSView*)child;
return S_OK; if(_child == nil)
return E_FAIL;
[_holder addSubview:_child];
[_child setHidden: false];
return S_OK;
}
}; };
virtual HRESULT AttachTo(IAvnNativeControlHost* host) override virtual HRESULT AttachTo(IAvnNativeControlHost* host) override
{ {
if(host == nil) START_COM_CALL;
{
[_holder removeFromSuperview]; @autoreleasepool
[_holder setHidden: true];
}
else
{ {
AvnNativeControlHost* chost = dynamic_cast<AvnNativeControlHost*>(host); if(host == nil)
if(chost == nil || chost->View == nil) {
return E_FAIL; [_holder removeFromSuperview];
[_holder setHidden:true]; [_holder setHidden: true];
[chost->View addSubview:_holder]; }
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 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 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 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 virtual HRESULT SetApplicationTitle(char* utf8String) override
{ {
auto appTitle = [NSString stringWithUTF8String: utf8String]; START_COM_CALL;
[[NSProcessInfo processInfo] setProcessName:appTitle]; @autoreleasepool
{
auto appTitle = [NSString stringWithUTF8String: utf8String];
SetProcessName(appTitle);
[[NSProcessInfo processInfo] setProcessName:appTitle];
return S_OK;
SetProcessName(appTitle);
return S_OK;
}
} }
virtual HRESULT SetShowInDock(int show) override virtual HRESULT SetShowInDock(int show) override
{ {
AvnDesiredActivationPolicy = show START_COM_CALL;
? NSApplicationActivationPolicyRegular : NSApplicationActivationPolicyAccessory;
return S_OK; @autoreleasepool
{
AvnDesiredActivationPolicy = show
? NSApplicationActivationPolicyRegular : NSApplicationActivationPolicyAccessory;
return S_OK;
}
} }
virtual HRESULT SetDisableDefaultApplicationMenuItems (bool enabled) override virtual HRESULT SetDisableDefaultApplicationMenuItems (bool enabled) override
{ {
SetAutoGenerateDefaultAppMenuItems(!enabled); START_COM_CALL;
return S_OK;
@autoreleasepool
{
SetAutoGenerateDefaultAppMenuItems(!enabled);
return S_OK;
}
} }
}; };
@ -165,6 +180,8 @@ public:
FORWARD_IUNKNOWN() FORWARD_IUNKNOWN()
virtual HRESULT Initialize(IAvnGCHandleDeallocatorCallback* deallocator, IAvnApplicationEvents* events) override virtual HRESULT Initialize(IAvnGCHandleDeallocatorCallback* deallocator, IAvnApplicationEvents* events) override
{ {
START_COM_CALL;
_deallocator = deallocator; _deallocator = deallocator;
@autoreleasepool{ @autoreleasepool{
[[ThreadingInitializer new] do]; [[ThreadingInitializer new] do];
@ -180,89 +197,154 @@ public:
virtual HRESULT CreateWindow(IAvnWindowEvents* cb, IAvnGlContext* gl, IAvnWindow** ppv) override virtual HRESULT CreateWindow(IAvnWindowEvents* cb, IAvnGlContext* gl, IAvnWindow** ppv) override
{ {
if(cb == nullptr || ppv == nullptr) START_COM_CALL;
return E_POINTER;
*ppv = CreateAvnWindow(cb, gl); @autoreleasepool
return S_OK; {
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 virtual HRESULT CreatePopup(IAvnWindowEvents* cb, IAvnGlContext* gl, IAvnPopup** ppv) override
{ {
if(cb == nullptr || ppv == nullptr) START_COM_CALL;
return E_POINTER;
*ppv = CreateAvnPopup(cb, gl); @autoreleasepool
return S_OK; {
if(cb == nullptr || ppv == nullptr)
return E_POINTER;
*ppv = CreateAvnPopup(cb, gl);
return S_OK;
}
} }
virtual HRESULT CreatePlatformThreadingInterface(IAvnPlatformThreadingInterface** ppv) override virtual HRESULT CreatePlatformThreadingInterface(IAvnPlatformThreadingInterface** ppv) override
{ {
*ppv = CreatePlatformThreading(); START_COM_CALL;
return S_OK;
@autoreleasepool
{
*ppv = CreatePlatformThreading();
return S_OK;
}
} }
virtual HRESULT CreateSystemDialogs(IAvnSystemDialogs** ppv) override virtual HRESULT CreateSystemDialogs(IAvnSystemDialogs** ppv) override
{ {
*ppv = ::CreateSystemDialogs(); START_COM_CALL;
return S_OK;
@autoreleasepool
{
*ppv = ::CreateSystemDialogs();
return S_OK;
}
} }
virtual HRESULT CreateScreens (IAvnScreens** ppv) override virtual HRESULT CreateScreens (IAvnScreens** ppv) override
{ {
*ppv = ::CreateScreens (); START_COM_CALL;
return S_OK;
@autoreleasepool
{
*ppv = ::CreateScreens ();
return S_OK;
}
} }
virtual HRESULT CreateClipboard(IAvnClipboard** ppv) override virtual HRESULT CreateClipboard(IAvnClipboard** ppv) override
{ {
*ppv = ::CreateClipboard (nil, nil); START_COM_CALL;
return S_OK;
@autoreleasepool
{
*ppv = ::CreateClipboard (nil, nil);
return S_OK;
}
} }
virtual HRESULT CreateDndClipboard(IAvnClipboard** ppv) override virtual HRESULT CreateDndClipboard(IAvnClipboard** ppv) override
{ {
*ppv = ::CreateClipboard (nil, [NSPasteboardItem new]); START_COM_CALL;
return S_OK;
@autoreleasepool
{
*ppv = ::CreateClipboard (nil, [NSPasteboardItem new]);
return S_OK;
}
} }
virtual HRESULT CreateCursorFactory(IAvnCursorFactory** ppv) override virtual HRESULT CreateCursorFactory(IAvnCursorFactory** ppv) override
{ {
*ppv = ::CreateCursorFactory(); START_COM_CALL;
return S_OK;
@autoreleasepool
{
*ppv = ::CreateCursorFactory();
return S_OK;
}
} }
virtual HRESULT ObtainGlDisplay(IAvnGlDisplay** ppv) override virtual HRESULT ObtainGlDisplay(IAvnGlDisplay** ppv) override
{ {
auto rv = ::GetGlDisplay(); START_COM_CALL;
if(rv == NULL)
return E_FAIL; @autoreleasepool
rv->AddRef(); {
*ppv = rv; auto rv = ::GetGlDisplay();
return S_OK; if(rv == NULL)
return E_FAIL;
rv->AddRef();
*ppv = rv;
return S_OK;
}
} }
virtual HRESULT CreateMenu (IAvnMenuEvents* cb, IAvnMenu** ppv) override virtual HRESULT CreateMenu (IAvnMenuEvents* cb, IAvnMenu** ppv) override
{ {
*ppv = ::CreateAppMenu(cb); START_COM_CALL;
return S_OK;
@autoreleasepool
{
*ppv = ::CreateAppMenu(cb);
return S_OK;
}
} }
virtual HRESULT CreateMenuItem (IAvnMenuItem** ppv) override virtual HRESULT CreateMenuItem (IAvnMenuItem** ppv) override
{ {
*ppv = ::CreateAppMenuItem(); START_COM_CALL;
return S_OK;
@autoreleasepool
{
*ppv = ::CreateAppMenuItem();
return S_OK;
}
} }
virtual HRESULT CreateMenuItemSeparator (IAvnMenuItem** ppv) override virtual HRESULT CreateMenuItemSeparator (IAvnMenuItem** ppv) override
{ {
*ppv = ::CreateAppMenuItemSeparator(); START_COM_CALL;
return S_OK;
@autoreleasepool
{
*ppv = ::CreateAppMenuItemSeparator();
return S_OK;
}
} }
virtual HRESULT SetAppMenu (IAvnMenu* appMenu) override virtual HRESULT SetAppMenu (IAvnMenu* appMenu) override
{ {
::SetAppMenu(s_appTitle, appMenu); START_COM_CALL;
return S_OK;
@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) HRESULT AvnAppMenuItem::SetSubMenu (IAvnMenu* menu)
{ {
START_COM_CALL;
@autoreleasepool @autoreleasepool
{ {
if(menu != nullptr) if(menu != nullptr)
@ -114,6 +116,8 @@ HRESULT AvnAppMenuItem::SetSubMenu (IAvnMenu* menu)
HRESULT AvnAppMenuItem::SetTitle (char* utf8String) HRESULT AvnAppMenuItem::SetTitle (char* utf8String)
{ {
START_COM_CALL;
@autoreleasepool @autoreleasepool
{ {
if (utf8String != nullptr) if (utf8String != nullptr)
@ -128,6 +132,8 @@ HRESULT AvnAppMenuItem::SetTitle (char* utf8String)
HRESULT AvnAppMenuItem::SetGesture (AvnKey key, AvnInputModifiers modifiers) HRESULT AvnAppMenuItem::SetGesture (AvnKey key, AvnInputModifiers modifiers)
{ {
START_COM_CALL;
@autoreleasepool @autoreleasepool
{ {
if(key != AvnKeyNone) if(key != AvnKeyNone)
@ -183,6 +189,8 @@ HRESULT AvnAppMenuItem::SetGesture (AvnKey key, AvnInputModifiers modifiers)
HRESULT AvnAppMenuItem::SetAction (IAvnPredicateCallback* predicate, IAvnActionCallback* callback) HRESULT AvnAppMenuItem::SetAction (IAvnPredicateCallback* predicate, IAvnActionCallback* callback)
{ {
START_COM_CALL;
@autoreleasepool @autoreleasepool
{ {
_predicate = predicate; _predicate = predicate;
@ -193,6 +201,8 @@ HRESULT AvnAppMenuItem::SetAction (IAvnPredicateCallback* predicate, IAvnActionC
HRESULT AvnAppMenuItem::SetIsChecked (bool isChecked) HRESULT AvnAppMenuItem::SetIsChecked (bool isChecked)
{ {
START_COM_CALL;
@autoreleasepool @autoreleasepool
{ {
[_native setState:(isChecked && _isCheckable ? NSOnState : NSOffState)]; [_native setState:(isChecked && _isCheckable ? NSOnState : NSOffState)];
@ -202,6 +212,8 @@ HRESULT AvnAppMenuItem::SetIsChecked (bool isChecked)
HRESULT AvnAppMenuItem::SetToggleType(AvnMenuItemToggleType toggleType) HRESULT AvnAppMenuItem::SetToggleType(AvnMenuItemToggleType toggleType)
{ {
START_COM_CALL;
@autoreleasepool @autoreleasepool
{ {
switch(toggleType) switch(toggleType)
@ -231,6 +243,8 @@ HRESULT AvnAppMenuItem::SetToggleType(AvnMenuItemToggleType toggleType)
HRESULT AvnAppMenuItem::SetIcon(void *data, size_t length) HRESULT AvnAppMenuItem::SetIcon(void *data, size_t length)
{ {
START_COM_CALL;
@autoreleasepool @autoreleasepool
{ {
if(data != nullptr) if(data != nullptr)
@ -317,6 +331,8 @@ void AvnAppMenu::RaiseClosed()
HRESULT AvnAppMenu::InsertItem(int index, IAvnMenuItem *item) HRESULT AvnAppMenu::InsertItem(int index, IAvnMenuItem *item)
{ {
START_COM_CALL;
@autoreleasepool @autoreleasepool
{ {
if([_native hasGlobalMenuItem]) if([_native hasGlobalMenuItem])
@ -337,6 +353,8 @@ HRESULT AvnAppMenu::InsertItem(int index, IAvnMenuItem *item)
HRESULT AvnAppMenu::RemoveItem (IAvnMenuItem* item) HRESULT AvnAppMenu::RemoveItem (IAvnMenuItem* item)
{ {
START_COM_CALL;
@autoreleasepool @autoreleasepool
{ {
auto avnMenuItem = dynamic_cast<AvnAppMenuItem*>(item); auto avnMenuItem = dynamic_cast<AvnAppMenuItem*>(item);
@ -352,6 +370,8 @@ HRESULT AvnAppMenu::RemoveItem (IAvnMenuItem* item)
HRESULT AvnAppMenu::SetTitle (char* utf8String) HRESULT AvnAppMenu::SetTitle (char* utf8String)
{ {
START_COM_CALL;
@autoreleasepool @autoreleasepool
{ {
if (utf8String != nullptr) if (utf8String != nullptr)
@ -365,6 +385,8 @@ HRESULT AvnAppMenu::SetTitle (char* utf8String)
HRESULT AvnAppMenu::Clear() HRESULT AvnAppMenu::Clear()
{ {
START_COM_CALL;
@autoreleasepool @autoreleasepool
{ {
[_native removeAllItems]; [_native removeAllItems];

2
native/Avalonia.Native/src/OSX/platformthreading.mm

@ -114,6 +114,8 @@ public:
virtual HRESULT RunLoop(IAvnLoopCancellation* cancel) override virtual HRESULT RunLoop(IAvnLoopCancellation* cancel) override
{ {
START_COM_CALL;
auto can = dynamic_cast<LoopCancellation*>(cancel); auto can = dynamic_cast<LoopCancellation*>(cancel);
if(can->Cancelled) if(can->Cancelled)
return S_OK; return S_OK;

6
native/Avalonia.Native/src/OSX/rendertarget.mm

@ -247,6 +247,8 @@ public:
virtual HRESULT GetPixelSize(AvnPixelSize* ret) override virtual HRESULT GetPixelSize(AvnPixelSize* ret) override
{ {
START_COM_CALL;
if(!_surface) if(!_surface)
return E_FAIL; return E_FAIL;
*ret = _surface->size; *ret = _surface->size;
@ -255,6 +257,8 @@ public:
virtual HRESULT GetScaling(double* ret) override virtual HRESULT GetScaling(double* ret) override
{ {
START_COM_CALL;
if(!_surface) if(!_surface)
return E_FAIL; return E_FAIL;
*ret = _surface->scale; *ret = _surface->scale;
@ -281,6 +285,8 @@ public:
virtual HRESULT BeginDrawing(IAvnGlSurfaceRenderingSession** ret) override virtual HRESULT BeginDrawing(IAvnGlSurfaceRenderingSession** ret) override
{ {
START_COM_CALL;
ComPtr<IUnknown> releaseContext; ComPtr<IUnknown> releaseContext;
@synchronized (_target->lock) { @synchronized (_target->lock) {
if(_target->surface == nil) if(_target->surface == nil)

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

@ -29,10 +29,12 @@ public:
IAvnMenu* _mainMenu; IAvnMenu* _mainMenu;
bool _shown; bool _shown;
bool _inResize;
WindowBaseImpl(IAvnWindowBaseEvents* events, IAvnGlContext* gl) WindowBaseImpl(IAvnWindowBaseEvents* events, IAvnGlContext* gl)
{ {
_shown = false; _shown = false;
_inResize = false;
_mainMenu = nullptr; _mainMenu = nullptr;
BaseEvents = events; BaseEvents = events;
_glContext = gl; _glContext = gl;
@ -54,6 +56,8 @@ public:
virtual HRESULT ObtainNSWindowHandle(void** ret) override virtual HRESULT ObtainNSWindowHandle(void** ret) override
{ {
START_COM_CALL;
if (ret == nullptr) if (ret == nullptr)
{ {
return E_POINTER; return E_POINTER;
@ -66,6 +70,8 @@ public:
virtual HRESULT ObtainNSWindowHandleRetained(void** ret) override virtual HRESULT ObtainNSWindowHandleRetained(void** ret) override
{ {
START_COM_CALL;
if (ret == nullptr) if (ret == nullptr)
{ {
return E_POINTER; return E_POINTER;
@ -78,6 +84,8 @@ public:
virtual HRESULT ObtainNSViewHandle(void** ret) override virtual HRESULT ObtainNSViewHandle(void** ret) override
{ {
START_COM_CALL;
if (ret == nullptr) if (ret == nullptr)
{ {
return E_POINTER; return E_POINTER;
@ -90,6 +98,8 @@ public:
virtual HRESULT ObtainNSViewHandleRetained(void** ret) override virtual HRESULT ObtainNSViewHandleRetained(void** ret) override
{ {
START_COM_CALL;
if (ret == nullptr) if (ret == nullptr)
{ {
return E_POINTER; return E_POINTER;
@ -107,23 +117,27 @@ public:
virtual HRESULT Show(bool activate) override virtual HRESULT Show(bool activate) override
{ {
START_COM_CALL;
@autoreleasepool @autoreleasepool
{ {
SetPosition(lastPositionSet); SetPosition(lastPositionSet);
UpdateStyle(); UpdateStyle();
[Window setContentView: StandardContainer]; [Window setContentView: StandardContainer];
[Window setTitle:_lastTitle];
if(ShouldTakeFocusOnShow() && activate) if(ShouldTakeFocusOnShow() && activate)
{ {
[Window orderFront: Window];
[Window makeKeyAndOrderFront:Window]; [Window makeKeyAndOrderFront:Window];
[Window makeFirstResponder:View];
[NSApp activateIgnoringOtherApps:YES]; [NSApp activateIgnoringOtherApps:YES];
} }
else else
{ {
[Window orderFront: Window]; [Window orderFront: Window];
} }
[Window setTitle:_lastTitle];
_shown = true; _shown = true;
@ -138,6 +152,8 @@ public:
virtual HRESULT Hide () override virtual HRESULT Hide () override
{ {
START_COM_CALL;
@autoreleasepool @autoreleasepool
{ {
if(Window != nullptr) if(Window != nullptr)
@ -152,6 +168,8 @@ public:
virtual HRESULT Activate () override virtual HRESULT Activate () override
{ {
START_COM_CALL;
@autoreleasepool @autoreleasepool
{ {
if(Window != nullptr) if(Window != nullptr)
@ -166,6 +184,8 @@ public:
virtual HRESULT SetTopMost (bool value) override virtual HRESULT SetTopMost (bool value) override
{ {
START_COM_CALL;
@autoreleasepool @autoreleasepool
{ {
[Window setLevel: value ? NSFloatingWindowLevel : NSNormalWindowLevel]; [Window setLevel: value ? NSFloatingWindowLevel : NSNormalWindowLevel];
@ -176,11 +196,16 @@ public:
virtual HRESULT Close() override virtual HRESULT Close() override
{ {
START_COM_CALL;
@autoreleasepool @autoreleasepool
{ {
if (Window != nullptr) if (Window != nullptr)
{ {
[Window close]; auto window = Window;
Window = nullptr;
[window close];
} }
return S_OK; return S_OK;
@ -189,6 +214,8 @@ public:
virtual HRESULT GetClientSize(AvnSize* ret) override virtual HRESULT GetClientSize(AvnSize* ret) override
{ {
START_COM_CALL;
@autoreleasepool @autoreleasepool
{ {
if(ret == nullptr) if(ret == nullptr)
@ -202,8 +229,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 virtual HRESULT GetScaling (double* ret) override
{ {
START_COM_CALL;
@autoreleasepool @autoreleasepool
{ {
if(ret == nullptr) if(ret == nullptr)
@ -222,6 +266,8 @@ public:
virtual HRESULT SetMinMaxSize (AvnSize minSize, AvnSize maxSize) override virtual HRESULT SetMinMaxSize (AvnSize minSize, AvnSize maxSize) override
{ {
START_COM_CALL;
@autoreleasepool @autoreleasepool
{ {
[Window setMinSize: ToNSSize(minSize)]; [Window setMinSize: ToNSSize(minSize)];
@ -233,6 +279,15 @@ public:
virtual HRESULT Resize(double x, double y) override virtual HRESULT Resize(double x, double y) override
{ {
if(_inResize)
{
return S_OK;
}
_inResize = true;
START_COM_CALL;
@autoreleasepool @autoreleasepool
{ {
auto maxSize = [Window maxSize]; auto maxSize = [Window maxSize];
@ -258,13 +313,19 @@ public:
y = maxSize.height; y = maxSize.height;
} }
if(!_shown) @try
{ {
BaseEvents->Resized(AvnSize{x,y}); if(!_shown)
{
BaseEvents->Resized(AvnSize{x,y});
}
[Window setContentSize:NSSize{x, y}];
}
@finally
{
_inResize = false;
} }
[StandardContainer setFrameSize:NSSize{x,y}];
[Window setContentSize:NSSize{x, y}];
return S_OK; return S_OK;
} }
@ -272,6 +333,8 @@ public:
virtual HRESULT Invalidate (AvnRect rect) override virtual HRESULT Invalidate (AvnRect rect) override
{ {
START_COM_CALL;
@autoreleasepool @autoreleasepool
{ {
[View setNeedsDisplayInRect:[View frame]]; [View setNeedsDisplayInRect:[View frame]];
@ -282,6 +345,8 @@ public:
virtual HRESULT SetMainMenu(IAvnMenu* menu) override virtual HRESULT SetMainMenu(IAvnMenu* menu) override
{ {
START_COM_CALL;
_mainMenu = menu; _mainMenu = menu;
auto nativeMenu = dynamic_cast<AvnAppMenu*>(menu); auto nativeMenu = dynamic_cast<AvnAppMenu*>(menu);
@ -300,6 +365,8 @@ public:
virtual HRESULT BeginMoveDrag () override virtual HRESULT BeginMoveDrag () override
{ {
START_COM_CALL;
@autoreleasepool @autoreleasepool
{ {
auto lastEvent = [View lastMouseDownEvent]; auto lastEvent = [View lastMouseDownEvent];
@ -317,11 +384,15 @@ public:
virtual HRESULT BeginResizeDrag (AvnWindowEdge edge) override virtual HRESULT BeginResizeDrag (AvnWindowEdge edge) override
{ {
START_COM_CALL;
return S_OK; return S_OK;
} }
virtual HRESULT GetPosition (AvnPoint* ret) override virtual HRESULT GetPosition (AvnPoint* ret) override
{ {
START_COM_CALL;
@autoreleasepool @autoreleasepool
{ {
if(ret == nullptr) if(ret == nullptr)
@ -342,6 +413,8 @@ public:
virtual HRESULT SetPosition (AvnPoint point) override virtual HRESULT SetPosition (AvnPoint point) override
{ {
START_COM_CALL;
@autoreleasepool @autoreleasepool
{ {
lastPositionSet = point; lastPositionSet = point;
@ -353,6 +426,8 @@ public:
virtual HRESULT PointToClient (AvnPoint point, AvnPoint* ret) override virtual HRESULT PointToClient (AvnPoint point, AvnPoint* ret) override
{ {
START_COM_CALL;
@autoreleasepool @autoreleasepool
{ {
if(ret == nullptr) if(ret == nullptr)
@ -371,6 +446,8 @@ public:
virtual HRESULT PointToScreen (AvnPoint point, AvnPoint* ret) override virtual HRESULT PointToScreen (AvnPoint point, AvnPoint* ret) override
{ {
START_COM_CALL;
@autoreleasepool @autoreleasepool
{ {
if(ret == nullptr) if(ret == nullptr)
@ -388,12 +465,16 @@ public:
virtual HRESULT ThreadSafeSetSwRenderedFrame(AvnFramebuffer* fb, IUnknown* dispose) override virtual HRESULT ThreadSafeSetSwRenderedFrame(AvnFramebuffer* fb, IUnknown* dispose) override
{ {
START_COM_CALL;
[View setSwRenderedFrame: fb dispose: dispose]; [View setSwRenderedFrame: fb dispose: dispose];
return S_OK; return S_OK;
} }
virtual HRESULT SetCursor(IAvnCursor* cursor) override virtual HRESULT SetCursor(IAvnCursor* cursor) override
{ {
START_COM_CALL;
@autoreleasepool @autoreleasepool
{ {
Cursor* avnCursor = dynamic_cast<Cursor*>(cursor); Cursor* avnCursor = dynamic_cast<Cursor*>(cursor);
@ -423,6 +504,8 @@ public:
virtual HRESULT CreateGlRenderTarget(IAvnGlSurfaceRenderTarget** ppv) override virtual HRESULT CreateGlRenderTarget(IAvnGlSurfaceRenderTarget** ppv) override
{ {
START_COM_CALL;
if(View == NULL) if(View == NULL)
return E_FAIL; return E_FAIL;
*ppv = [renderTarget createSurfaceRenderTarget]; *ppv = [renderTarget createSurfaceRenderTarget];
@ -431,6 +514,8 @@ public:
virtual HRESULT CreateNativeControlHost(IAvnNativeControlHost** retOut) override virtual HRESULT CreateNativeControlHost(IAvnNativeControlHost** retOut) override
{ {
START_COM_CALL;
if(View == NULL) if(View == NULL)
return E_FAIL; return E_FAIL;
*retOut = ::CreateNativeControlHost(View); *retOut = ::CreateNativeControlHost(View);
@ -439,6 +524,8 @@ public:
virtual HRESULT SetBlurEnabled (bool enable) override virtual HRESULT SetBlurEnabled (bool enable) override
{ {
START_COM_CALL;
[StandardContainer ShowBlur:enable]; [StandardContainer ShowBlur:enable];
return S_OK; return S_OK;
@ -448,6 +535,8 @@ public:
IAvnClipboard* clipboard, IAvnDndResultCallback* cb, IAvnClipboard* clipboard, IAvnDndResultCallback* cb,
void* sourceHandle) override void* sourceHandle) override
{ {
START_COM_CALL;
auto item = TryGetPasteboardItem(clipboard); auto item = TryGetPasteboardItem(clipboard);
[item setString:@"" forType:GetAvnCustomDataType()]; [item setString:@"" forType:GetAvnCustomDataType()];
if(item == nil) if(item == nil)
@ -549,6 +638,11 @@ private:
void HideOrShowTrafficLights () void HideOrShowTrafficLights ()
{ {
if (Window == nil)
{
return;
}
for (id subview in Window.contentView.superview.subviews) { for (id subview in Window.contentView.superview.subviews) {
if ([subview isKindOfClass:NSClassFromString(@"NSTitlebarContainerView")]) { if ([subview isKindOfClass:NSClassFromString(@"NSTitlebarContainerView")]) {
NSView *titlebarView = [subview subviews][0]; NSView *titlebarView = [subview subviews][0];
@ -575,8 +669,10 @@ private:
virtual HRESULT Show (bool activate) override virtual HRESULT Show (bool activate) override
{ {
START_COM_CALL;
@autoreleasepool @autoreleasepool
{ {
WindowBaseImpl::Show(activate); WindowBaseImpl::Show(activate);
HideOrShowTrafficLights(); HideOrShowTrafficLights();
@ -587,6 +683,8 @@ private:
virtual HRESULT SetEnabled (bool enable) override virtual HRESULT SetEnabled (bool enable) override
{ {
START_COM_CALL;
@autoreleasepool @autoreleasepool
{ {
[Window setEnabled:enable]; [Window setEnabled:enable];
@ -596,6 +694,8 @@ private:
virtual HRESULT SetParent (IAvnWindow* parent) override virtual HRESULT SetParent (IAvnWindow* parent) override
{ {
START_COM_CALL;
@autoreleasepool @autoreleasepool
{ {
if(parent == nullptr) if(parent == nullptr)
@ -672,6 +772,7 @@ private:
} }
_lastWindowState = state; _lastWindowState = state;
_actualWindowState = state;
WindowEvents->WindowStateChanged(state); WindowEvents->WindowStateChanged(state);
} }
} }
@ -707,6 +808,8 @@ private:
virtual HRESULT SetCanResize(bool value) override virtual HRESULT SetCanResize(bool value) override
{ {
START_COM_CALL;
@autoreleasepool @autoreleasepool
{ {
_canResize = value; _canResize = value;
@ -717,6 +820,8 @@ private:
virtual HRESULT SetDecorations(SystemDecorations value) override virtual HRESULT SetDecorations(SystemDecorations value) override
{ {
START_COM_CALL;
@autoreleasepool @autoreleasepool
{ {
auto currentWindowState = _lastWindowState; auto currentWindowState = _lastWindowState;
@ -782,6 +887,8 @@ private:
virtual HRESULT SetTitle (char* utf8title) override virtual HRESULT SetTitle (char* utf8title) override
{ {
START_COM_CALL;
@autoreleasepool @autoreleasepool
{ {
_lastTitle = [NSString stringWithUTF8String:(const char*)utf8title]; _lastTitle = [NSString stringWithUTF8String:(const char*)utf8title];
@ -793,6 +900,8 @@ private:
virtual HRESULT SetTitleBarColor(AvnColor color) override virtual HRESULT SetTitleBarColor(AvnColor color) override
{ {
START_COM_CALL;
@autoreleasepool @autoreleasepool
{ {
float a = (float)color.Alpha / 255.0f; float a = (float)color.Alpha / 255.0f;
@ -822,6 +931,8 @@ private:
virtual HRESULT GetWindowState (AvnWindowState*ret) override virtual HRESULT GetWindowState (AvnWindowState*ret) override
{ {
START_COM_CALL;
@autoreleasepool @autoreleasepool
{ {
if(ret == nullptr) if(ret == nullptr)
@ -855,86 +966,111 @@ private:
virtual HRESULT TakeFocusFromChildren () override virtual HRESULT TakeFocusFromChildren () override
{ {
if(Window == nil) START_COM_CALL;
return S_OK;
if([Window isKeyWindow])
[Window makeFirstResponder: View];
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 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) if(enable)
{
[StandardContainer ShowTitleBar:true];
}
else
{ {
[StandardContainer ShowTitleBar:false]; Window.titleVisibility = NSWindowTitleHidden;
}
[Window setTitlebarAppearsTransparent:true];
if(_extendClientHints & AvnOSXThickTitleBar)
{ auto wantsTitleBar = (_extendClientHints & AvnSystemChrome) || (_extendClientHints & AvnPreferSystemChrome);
Window.toolbar = [NSToolbar new];
Window.toolbar.showsBaselineSeparator = false; 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 else
{ {
Window.titleVisibility = NSWindowTitleVisible;
Window.toolbar = nullptr; 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 virtual HRESULT SetExtendClientAreaHints (AvnExtendClientAreaChromeHints hints) override
{ {
_extendClientHints = hints; START_COM_CALL;
SetExtendClientArea(_isClientAreaExtended); @autoreleasepool
return S_OK; {
_extendClientHints = hints;
SetExtendClientArea(_isClientAreaExtended);
return S_OK;
}
} }
virtual HRESULT GetExtendTitleBarHeight (double*ret) override 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 virtual HRESULT SetExtendTitleBarHeight (double value) override
{ {
[StandardContainer SetTitleBarHeightHint:value]; START_COM_CALL;
return S_OK;
@autoreleasepool
{
[StandardContainer SetTitleBarHeightHint:value];
return S_OK;
}
} }
void EnterFullScreenMode () void EnterFullScreenMode ()
@ -963,8 +1099,15 @@ private:
virtual HRESULT SetWindowState (AvnWindowState state) override virtual HRESULT SetWindowState (AvnWindowState state) override
{ {
START_COM_CALL;
@autoreleasepool @autoreleasepool
{ {
if(Window == nullptr)
{
return S_OK;
}
if(_actualWindowState == state) if(_actualWindowState == state)
{ {
return S_OK; return S_OK;
@ -1149,6 +1292,9 @@ NSArray* AllLoopModes = [NSArray arrayWithObjects: NSDefaultRunLoopMode, NSEvent
[_blurBehind setWantsLayer:true]; [_blurBehind setWantsLayer:true];
_blurBehind.hidden = true; _blurBehind.hidden = true;
[_blurBehind setAutoresizingMask:NSViewWidthSizable | NSViewHeightSizable];
[_content setAutoresizingMask:NSViewWidthSizable | NSViewHeightSizable];
[self addSubview:_blurBehind]; [self addSubview:_blurBehind];
[self addSubview:_content]; [self addSubview:_content];
@ -1184,9 +1330,6 @@ NSArray* AllLoopModes = [NSArray arrayWithObjects: NSDefaultRunLoopMode, NSEvent
_settingSize = true; _settingSize = true;
[super setFrameSize:newSize]; [super setFrameSize:newSize];
[_blurBehind setFrameSize:newSize];
[_content setFrameSize:newSize];
auto window = objc_cast<AvnWindow>([self window]); auto window = objc_cast<AvnWindow>([self window]);
// TODO get actual titlebar size // TODO get actual titlebar size
@ -1202,6 +1345,7 @@ NSArray* AllLoopModes = [NSArray arrayWithObjects: NSDefaultRunLoopMode, NSEvent
[_titleBarMaterial setFrame:tbar]; [_titleBarMaterial setFrame:tbar];
tbar.size.height = height < 1 ? 0 : 1; tbar.size.height = height < 1 ? 0 : 1;
[_titleBarUnderline setFrame:tbar]; [_titleBarUnderline setFrame:tbar];
_settingSize = false; _settingSize = false;
} }
@ -1911,7 +2055,7 @@ NSArray* AllLoopModes = [NSArray arrayWithObjects: NSDefaultRunLoopMode, NSEvent
{ {
if(![self windowShouldClose:self]) return; if(![self windowShouldClose:self]) return;
} }
[self close]; [self close];
} }
@ -2247,11 +2391,12 @@ protected:
virtual HRESULT Resize(double x, double y) override virtual HRESULT Resize(double x, double y) override
{ {
START_COM_CALL;
@autoreleasepool @autoreleasepool
{ {
if (Window != nullptr) if (Window != nullptr)
{ {
[StandardContainer setFrameSize:NSSize{x,y}];
[Window setContentSize:NSSize{x, y}]; [Window setContentSize:NSSize{x, y}];
[Window setFrameTopLeftPoint:ToNSPoint(ConvertPointY(lastPositionSet))]; [Window setFrameTopLeftPoint:ToNSPoint(ConvertPointY(lastPositionSet))];

43
samples/ControlCatalog/Pages/NumericUpDownPage.xaml

@ -2,7 +2,8 @@
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:sys="clr-namespace:System;assembly=netstandard" xmlns:sys="clr-namespace:System;assembly=netstandard"
x:Class="ControlCatalog.Pages.NumericUpDownPage"> x:Class="ControlCatalog.Pages.NumericUpDownPage">
<StackPanel Orientation="Vertical" Spacing="4"> <StackPanel Orientation="Vertical" Spacing="4"
MaxWidth="800">
<TextBlock Margin="2" Classes="h1">Numeric up-down control</TextBlock> <TextBlock Margin="2" Classes="h1">Numeric up-down control</TextBlock>
<TextBlock Margin="2" Classes="h2" TextWrapping="Wrap">Numeric up-down control provides a TextBox with button spinners that allow incrementing and decrementing numeric values by using the spinner buttons, keyboard up/down arrows, or mouse wheel.</TextBlock> <TextBlock Margin="2" Classes="h2" TextWrapping="Wrap">Numeric up-down control provides a TextBox with button spinners that allow incrementing and decrementing numeric values by using the spinner buttons, keyboard up/down arrows, or mouse wheel.</TextBlock>
@ -68,24 +69,36 @@
<NumericUpDown Grid.Row="3" Grid.Column="1" Value="{Binding #upDown.Value}" VerticalAlignment="Center" <NumericUpDown Grid.Row="3" Grid.Column="1" Value="{Binding #upDown.Value}" VerticalAlignment="Center"
Margin="2" HorizontalAlignment="Center"/> Margin="2" HorizontalAlignment="Center"/>
</Grid> </Grid>
</Grid> </Grid>
<StackPanel Margin="2,10,2,2" Orientation="Horizontal" Spacing="10"> <WrapPanel Margin="2,10,2,2">
<TextBlock FontSize="14" FontWeight="Bold" VerticalAlignment="Center">Usage of NumericUpDown:</TextBlock> <StackPanel Orientation="Vertical" Margin="10">
<NumericUpDown Name="upDown" Minimum="0" Maximum="10" Increment="0.5" <Label Target="upDown" FontSize="14" FontWeight="Bold" VerticalAlignment="Center">Usage of decimal NumericUpDown:</Label>
CultureInfo="en-US" VerticalAlignment="Center" <NumericUpDown Name="upDown" Minimum="0" Maximum="10" Increment="0.5"
Watermark="Enter text" FormatString="{Binding SelectedFormat.Value}"/> CultureInfo="en-US" VerticalAlignment="Center" Value="{Binding DecimalValue}"
Watermark="Enter text" FormatString="{Binding SelectedFormat.Value}"/>
</StackPanel>
<StackPanel Orientation="Vertical" Margin="10">
<Label Target="DoubleUpDown" FontSize="14" FontWeight="Bold" VerticalAlignment="Center">Usage of double NumericUpDown:</Label>
<NumericUpDown Name="DoubleUpDown" Minimum="0" Maximum="10" Increment="0.5"
CultureInfo="en-US" VerticalAlignment="Center" Value="{Binding DoubleValue}"
Watermark="Enter text" FormatString="{Binding SelectedFormat.Value}"/>
</StackPanel>
<TextBlock FontSize="14" FontWeight="Bold" VerticalAlignment="Center">NumericUpDown with Validation Errors:</TextBlock> <StackPanel Orientation="Vertical" Margin="10">
<NumericUpDown Minimum="0" Maximum="10" Increment="0.5" <Label Target="ValidationUpDown" FontSize="14" FontWeight="Bold" VerticalAlignment="Center">NumericUpDown with Validation Errors:</Label>
CultureInfo="en-US" VerticalAlignment="Center" <NumericUpDown x:Name="ValidationUpDown" Minimum="0" Maximum="10" Increment="0.5"
Watermark="Enter text" FormatString="{Binding SelectedFormat.Value}"> CultureInfo="en-US" VerticalAlignment="Center"
<DataValidationErrors.Error> Watermark="Enter text" FormatString="{Binding SelectedFormat.Value}">
<sys:Exception /> <DataValidationErrors.Error>
</DataValidationErrors.Error> <sys:Exception />
</NumericUpDown> </DataValidationErrors.Error>
</StackPanel> </NumericUpDown>
</StackPanel>
</WrapPanel>
</StackPanel> </StackPanel>
</UserControl> </UserControl>

15
samples/ControlCatalog/Pages/NumericUpDownPage.xaml.cs

@ -32,11 +32,26 @@ namespace ControlCatalog.Pages
private FormatObject _selectedFormat; private FormatObject _selectedFormat;
private IList<Location> _spinnerLocations; private IList<Location> _spinnerLocations;
private double _doubleValue;
private decimal _decimalValue;
public NumbersPageViewModel() public NumbersPageViewModel()
{ {
SelectedFormat = Formats.FirstOrDefault(); SelectedFormat = Formats.FirstOrDefault();
} }
public double DoubleValue
{
get { return _doubleValue; }
set { this.RaiseAndSetIfChanged(ref _doubleValue, value); }
}
public decimal DecimalValue
{
get { return _decimalValue; }
set { this.RaiseAndSetIfChanged(ref _decimalValue, value); }
}
public IList<FormatObject> Formats public IList<FormatObject> Formats
{ {
get get

10
samples/Sandbox/Program.cs

@ -4,12 +4,12 @@ namespace Sandbox
{ {
public class Program public class Program
{ {
static void Main(string[] args) static void Main(string[] args) => BuildAvaloniaApp()
{ .StartWithClassicDesktopLifetime(args);
public static AppBuilder BuildAvaloniaApp() =>
AppBuilder.Configure<App>() AppBuilder.Configure<App>()
.UsePlatformDetect() .UsePlatformDetect()
.LogToTrace() .LogToTrace();
.StartWithClassicDesktopLifetime(args);
}
} }
} }

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 virtual Size ClientSize => Size.ToSize(RenderScaling);
public Size? FrameSize => null;
public IMouseDevice MouseDevice { get; } = new MouseDevice(); public IMouseDevice MouseDevice { get; } = new MouseDevice();
public Action Closed { get; set; } public Action Closed { get; set; }

30
src/Avalonia.Animation/Animation.cs

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

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

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

@ -454,6 +454,28 @@ namespace Avalonia.Collections
} }
} }
/// <summary>
/// Ensures that the capacity of the list is at least <see cref="capacity"/>.
/// </summary>
/// <param name="capacity">The capacity.</param>
public void EnsureCapacity(int capacity)
{
// Adapted from List<T> implementation.
var currentCapacity = _inner.Capacity;
if (currentCapacity < capacity)
{
var newCapacity = currentCapacity == 0 ? 4 : currentCapacity * 2;
if (newCapacity < capacity)
{
newCapacity = capacity;
}
_inner.Capacity = newCapacity;
}
}
/// <summary> /// <summary>
/// Removes an item from the collection. /// Removes an item from the collection.
/// </summary> /// </summary>
@ -633,24 +655,6 @@ namespace Avalonia.Collections
/// <inheritdoc/> /// <inheritdoc/>
Delegate[] INotifyCollectionChangedDebug.GetCollectionChangedSubscribers() => _collectionChanged?.GetInvocationList(); Delegate[] INotifyCollectionChangedDebug.GetCollectionChangedSubscribers() => _collectionChanged?.GetInvocationList();
private void EnsureCapacity(int capacity)
{
// Adapted from List<T> implementation.
var currentCapacity = _inner.Capacity;
if (currentCapacity < capacity)
{
var newCapacity = currentCapacity == 0 ? 4 : currentCapacity * 2;
if (newCapacity < capacity)
{
newCapacity = capacity;
}
_inner.Capacity = newCapacity;
}
}
/// <summary> /// <summary>
/// Raises the <see cref="CollectionChanged"/> event with an add action. /// Raises the <see cref="CollectionChanged"/> event with an add action.
/// </summary> /// </summary>

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

@ -224,6 +224,34 @@ namespace Avalonia.Utilities
} }
} }
/// <summary>
/// Clamps a value between a minimum and maximum value.
/// </summary>
/// <param name="val">The value.</param>
/// <param name="min">The minimum value.</param>
/// <param name="max">The maximum value.</param>
/// <returns>The clamped value.</returns>
public static decimal Clamp(decimal val, decimal min, decimal max)
{
if (min > max)
{
ThrowCannotBeGreaterThanException(min, max);
}
if (val < min)
{
return min;
}
else if (val > max)
{
return max;
}
else
{
return val;
}
}
/// <summary> /// <summary>
/// Clamps a value between a minimum and maximum value. /// Clamps a value between a minimum and maximum value.
/// </summary> /// </summary>
@ -281,8 +309,8 @@ namespace Avalonia.Utilities
{ {
return angle * 2 * Math.PI; return angle * 2 * Math.PI;
} }
private static void ThrowCannotBeGreaterThanException(double min, double max) private static void ThrowCannotBeGreaterThanException<T>(T min, T max)
{ {
throw new ArgumentException($"{min} cannot be greater than {max}."); throw new ArgumentException($"{min} cannot be greater than {max}.");
} }

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

@ -75,7 +75,6 @@ namespace Avalonia.Controls
private const double DATAGRID_defaultMinColumnWidth = 20; private const double DATAGRID_defaultMinColumnWidth = 20;
private const double DATAGRID_defaultMaxColumnWidth = double.PositiveInfinity; private const double DATAGRID_defaultMaxColumnWidth = double.PositiveInfinity;
private List<Exception> _validationErrors;
private List<Exception> _bindingValidationErrors; private List<Exception> _bindingValidationErrors;
private IDisposable _validationSubscription; private IDisposable _validationSubscription;
@ -102,7 +101,6 @@ namespace Avalonia.Controls
private bool _areHandlersSuspended; private bool _areHandlersSuspended;
private bool _autoSizingColumns; private bool _autoSizingColumns;
private IndexToValueTable<bool> _collapsedSlotsTable; private IndexToValueTable<bool> _collapsedSlotsTable;
private DataGridCellCoordinates _currentCellCoordinates;
private Control _clickedElement; private Control _clickedElement;
// used to store the current column during a Reset // used to store the current column during a Reset
@ -141,7 +139,6 @@ namespace Avalonia.Controls
private DataGridSelectedItemsCollection _selectedItems; private DataGridSelectedItemsCollection _selectedItems;
private bool _temporarilyResetCurrentCell; private bool _temporarilyResetCurrentCell;
private object _uneditedValue; // Represents the original current cell value at the time it enters editing mode. 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 // 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 // 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); 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 // The row is already displayed in its entirety
return true; return true;

24
src/Avalonia.Controls.DataGrid/Utils/ReflectionHelper.cs

@ -340,10 +340,30 @@ namespace Avalonia.Controls.Utils
internal static PropertyInfo GetPropertyOrIndexer(this Type type, string propertyPath, out object[] index) internal static PropertyInfo GetPropertyOrIndexer(this Type type, string propertyPath, out object[] index)
{ {
index = null; index = null;
// Return the default value of GetProperty if the first character is not an indexer token.
if (string.IsNullOrEmpty(propertyPath) || propertyPath[0] != LeftIndexerToken) if (string.IsNullOrEmpty(propertyPath) || propertyPath[0] != LeftIndexerToken)
{ {
// Return the default value of GetProperty if the first character is not an indexer token. var property = type.GetProperty(propertyPath);
return type.GetProperty(propertyPath); if (property != null)
{
return property;
}
// GetProperty does not return inherited interface properties,
// so we need to enumerate them manually.
if (type.IsInterface)
{
foreach (var typeInterface in type.GetInterfaces())
{
property = type.GetProperty(propertyPath);
if (property != null)
{
return property;
}
}
}
return null;
} }
if (propertyPath.Length < 2 || propertyPath[propertyPath.Length - 1] != RightIndexerToken) if (propertyPath.Length < 2 || propertyPath[propertyPath.Length - 1] != RightIndexerToken)

28
src/Avalonia.Controls/ApiCompatBaseline.txt

@ -4,12 +4,38 @@ 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.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.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. InterfacesShouldHaveSameMembers : Interface member 'public void Avalonia.Controls.INativeMenuExporterEventsImplBridge.RaiseOpening()' is present in the implementation but not in the contract.
MembersMustExist : Member 'public Avalonia.DirectProperty<Avalonia.Controls.NumericUpDown, System.Double> Avalonia.DirectProperty<Avalonia.Controls.NumericUpDown, System.Double> Avalonia.Controls.NumericUpDown.ValueProperty' does not exist in the implementation but it does exist in the contract.
MembersMustExist : Member 'public Avalonia.StyledProperty<System.Double> Avalonia.StyledProperty<System.Double> Avalonia.Controls.NumericUpDown.IncrementProperty' does not exist in the implementation but it does exist in the contract.
MembersMustExist : Member 'public Avalonia.StyledProperty<System.Double> Avalonia.StyledProperty<System.Double> Avalonia.Controls.NumericUpDown.MaximumProperty' does not exist in the implementation but it does exist in the contract.
MembersMustExist : Member 'public Avalonia.StyledProperty<System.Double> Avalonia.StyledProperty<System.Double> Avalonia.Controls.NumericUpDown.MinimumProperty' does not exist in the implementation but it does exist in the contract.
MembersMustExist : Member 'public System.Double Avalonia.Controls.NumericUpDown.Increment.get()' does not exist in the implementation but it does exist in the contract.
MembersMustExist : Member 'public void Avalonia.Controls.NumericUpDown.Increment.set(System.Double)' does not exist in the implementation but it does exist in the contract.
MembersMustExist : Member 'public System.Double Avalonia.Controls.NumericUpDown.Maximum.get()' does not exist in the implementation but it does exist in the contract.
MembersMustExist : Member 'public void Avalonia.Controls.NumericUpDown.Maximum.set(System.Double)' does not exist in the implementation but it does exist in the contract.
MembersMustExist : Member 'public System.Double Avalonia.Controls.NumericUpDown.Minimum.get()' does not exist in the implementation but it does exist in the contract.
MembersMustExist : Member 'public void Avalonia.Controls.NumericUpDown.Minimum.set(System.Double)' does not exist in the implementation but it does exist in the contract.
MembersMustExist : Member 'protected System.Double Avalonia.Controls.NumericUpDown.OnCoerceIncrement(System.Double)' does not exist in the implementation but it does exist in the contract.
MembersMustExist : Member 'protected System.Double Avalonia.Controls.NumericUpDown.OnCoerceMaximum(System.Double)' does not exist in the implementation but it does exist in the contract.
MembersMustExist : Member 'protected System.Double Avalonia.Controls.NumericUpDown.OnCoerceMinimum(System.Double)' does not exist in the implementation but it does exist in the contract.
MembersMustExist : Member 'protected System.Double Avalonia.Controls.NumericUpDown.OnCoerceValue(System.Double)' does not exist in the implementation but it does exist in the contract.
MembersMustExist : Member 'protected void Avalonia.Controls.NumericUpDown.OnIncrementChanged(System.Double, System.Double)' does not exist in the implementation but it does exist in the contract.
MembersMustExist : Member 'protected void Avalonia.Controls.NumericUpDown.OnMaximumChanged(System.Double, System.Double)' does not exist in the implementation but it does exist in the contract.
MembersMustExist : Member 'protected void Avalonia.Controls.NumericUpDown.OnMinimumChanged(System.Double, System.Double)' does not exist in the implementation but it does exist in the contract.
MembersMustExist : Member 'protected void Avalonia.Controls.NumericUpDown.OnValueChanged(System.Double, System.Double)' does not exist in the implementation but it does exist in the contract.
MembersMustExist : Member 'protected void Avalonia.Controls.NumericUpDown.RaiseValueChangedEvent(System.Double, System.Double)' does not exist in the implementation but it does exist in the contract.
MembersMustExist : Member 'public System.Double Avalonia.Controls.NumericUpDown.Value.get()' does not exist in the implementation but it does exist in the contract.
MembersMustExist : Member 'public void Avalonia.Controls.NumericUpDown.Value.set(System.Double)' does not exist in the implementation but it does exist in the contract.
MembersMustExist : Member 'public void Avalonia.Controls.NumericUpDownValueChangedEventArgs..ctor(Avalonia.Interactivity.RoutedEvent, System.Double, System.Double)' does not exist in the implementation but it does exist in the contract.
MembersMustExist : Member 'public System.Double Avalonia.Controls.NumericUpDownValueChangedEventArgs.NewValue.get()' does not exist in the implementation but it does exist in the contract.
MembersMustExist : Member 'public System.Double Avalonia.Controls.NumericUpDownValueChangedEventArgs.OldValue.get()' does not exist in the implementation but it does exist 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.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 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 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. 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.
EnumValuesMustMatch : Enum value 'Avalonia.Platform.ExtendClientAreaChromeHints Avalonia.Platform.ExtendClientAreaChromeHints.Default' is (System.Int32)2 in the implementation but (System.Int32)1 in the contract. EnumValuesMustMatch : Enum value 'Avalonia.Platform.ExtendClientAreaChromeHints Avalonia.Platform.ExtendClientAreaChromeHints.Default' is (System.Int32)2 in the implementation but (System.Int32)1 in the contract.
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.
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.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. 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. MembersMustExist : Member 'public void Avalonia.Platform.ITopLevelImpl.SetCursor(Avalonia.Platform.IPlatformHandle)' does not exist in the implementation but it does exist in the contract.
Total Issues: 13 Total Issues: 39

31
src/Avalonia.Controls/Application.cs

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

71
src/Avalonia.Controls/ComboBox.cs

@ -77,14 +77,6 @@ namespace Avalonia.Controls
public static readonly StyledProperty<VerticalAlignment> VerticalContentAlignmentProperty = public static readonly StyledProperty<VerticalAlignment> VerticalContentAlignmentProperty =
ContentControl.VerticalContentAlignmentProperty.AddOwner<ComboBox>(); ContentControl.VerticalContentAlignmentProperty.AddOwner<ComboBox>();
/// <summary>
/// Defines the <see cref="IsTextSearchEnabled"/> property.
/// </summary>
public static readonly StyledProperty<bool> IsTextSearchEnabledProperty =
AvaloniaProperty.Register<ComboBox, bool>(nameof(IsTextSearchEnabled), true);
private string _textSearchTerm = string.Empty;
private DispatcherTimer _textSearchTimer;
private bool _isDropDownOpen; private bool _isDropDownOpen;
private Popup _popup; private Popup _popup;
private object _selectionBoxItem; private object _selectionBoxItem;
@ -173,15 +165,6 @@ namespace Avalonia.Controls
set { SetValue(VerticalContentAlignmentProperty, value); } set { SetValue(VerticalContentAlignmentProperty, value); }
} }
/// <summary>
/// Gets or sets a value that specifies whether a user can jump to a value by typing.
/// </summary>
public bool IsTextSearchEnabled
{
get { return GetValue(IsTextSearchEnabledProperty); }
set { SetValue(IsTextSearchEnabledProperty, value); }
}
/// <inheritdoc/> /// <inheritdoc/>
protected override IItemContainerGenerator CreateItemContainerGenerator() protected override IItemContainerGenerator CreateItemContainerGenerator()
{ {
@ -205,7 +188,7 @@ namespace Avalonia.Controls
if (e.Handled) if (e.Handled)
return; 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))) ((e.Key == Key.Down || e.Key == Key.Up) && e.KeyModifiers.HasAllFlags(KeyModifiers.Alt)))
{ {
IsDropDownOpen = !IsDropDownOpen; IsDropDownOpen = !IsDropDownOpen;
@ -247,32 +230,6 @@ namespace Avalonia.Controls
} }
} }
/// <inheritdoc />
protected override void OnTextInput(TextInputEventArgs e)
{
if (!IsTextSearchEnabled || e.Handled)
return;
StopTextSearchTimer();
_textSearchTerm += e.Text;
bool match(ItemContainerInfo info) =>
info.ContainerControl is IContentControl control &&
control.Content?.ToString()?.StartsWith(_textSearchTerm, StringComparison.OrdinalIgnoreCase) == true;
var info = ItemContainerGenerator.Containers.FirstOrDefault(match);
if (info != null)
{
SelectedIndex = info.Index;
}
StartTextSearchTimer();
e.Handled = true;
}
/// <inheritdoc/> /// <inheritdoc/>
protected override void OnPointerWheelChanged(PointerWheelEventArgs e) protected override void OnPointerWheelChanged(PointerWheelEventArgs e)
{ {
@ -470,31 +427,5 @@ namespace Avalonia.Controls
SelectedIndex = prev; SelectedIndex = prev;
} }
private void StartTextSearchTimer()
{
_textSearchTimer = new DispatcherTimer { Interval = TimeSpan.FromSeconds(1) };
_textSearchTimer.Tick += TextSearchTimer_Tick;
_textSearchTimer.Start();
}
private void StopTextSearchTimer()
{
if (_textSearchTimer == null)
{
return;
}
_textSearchTimer.Stop();
_textSearchTimer.Tick -= TextSearchTimer_Tick;
_textSearchTimer = null;
}
private void TextSearchTimer_Tick(object sender, EventArgs e)
{
_textSearchTerm = string.Empty;
StopTextSearchTimer();
}
} }
} }

2
src/Avalonia.Controls/Embedding/Offscreen/OffscreenTopLevelImpl.cs

@ -35,6 +35,8 @@ namespace Avalonia.Controls.Embedding.Offscreen
} }
} }
public Size? FrameSize => null;
public double RenderScaling public double RenderScaling
{ {
get { return _scaling; } get { return _scaling; }

21
src/Avalonia.Controls/Expander.cs

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

1
src/Avalonia.Controls/Grid.cs

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

6
src/Avalonia.Controls/GridLength.cs

@ -77,6 +77,12 @@ namespace Avalonia.Controls
/// </summary> /// </summary>
public static GridLength Auto => new GridLength(0, GridUnitType.Auto); 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> /// <summary>
/// Gets the unit of the <see cref="GridLength"/>. /// Gets the unit of the <see cref="GridLength"/>.
/// </summary> /// </summary>

1
src/Avalonia.Controls/ItemsControl.cs

@ -2,6 +2,7 @@ using System;
using System.Collections; using System.Collections;
using System.Collections.Generic; using System.Collections.Generic;
using System.Collections.Specialized; using System.Collections.Specialized;
using System.Linq;
using Avalonia.Collections; using Avalonia.Collections;
using Avalonia.Controls.Generators; using Avalonia.Controls.Generators;
using Avalonia.Controls.Metadata; using Avalonia.Controls.Metadata;

1
src/Avalonia.Controls/Menu.cs

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

6
src/Avalonia.Controls/MenuItem.cs

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

148
src/Avalonia.Controls/NumericUpDown/NumericUpDown.cs

@ -45,10 +45,18 @@ namespace Avalonia.Controls
/// <summary> /// <summary>
/// Defines the <see cref="CultureInfo"/> property. /// Defines the <see cref="CultureInfo"/> property.
/// </summary> /// </summary>
[Obsolete]
public static readonly DirectProperty<NumericUpDown, CultureInfo> CultureInfoProperty = public static readonly DirectProperty<NumericUpDown, CultureInfo> CultureInfoProperty =
AvaloniaProperty.RegisterDirect<NumericUpDown, CultureInfo>(nameof(CultureInfo), o => o.CultureInfo, AvaloniaProperty.RegisterDirect<NumericUpDown, CultureInfo>(nameof(CultureInfo), o => o.CultureInfo,
(o, v) => o.CultureInfo = v, CultureInfo.CurrentCulture); (o, v) => o.CultureInfo = v, CultureInfo.CurrentCulture);
/// <summary>
/// Defines the <see cref="NumberFormat"/> property.
/// </summary>
public static readonly DirectProperty<NumericUpDown, NumberFormatInfo> NumberFormatProperty =
AvaloniaProperty.RegisterDirect<NumericUpDown, NumberFormatInfo>(nameof(NumberFormat), o => o.NumberFormat,
(o, v) => o.NumberFormat = v, NumberFormatInfo.CurrentInfo);
/// <summary> /// <summary>
/// Defines the <see cref="FormatString"/> property. /// Defines the <see cref="FormatString"/> property.
/// </summary> /// </summary>
@ -58,8 +66,8 @@ namespace Avalonia.Controls
/// <summary> /// <summary>
/// Defines the <see cref="Increment"/> property. /// Defines the <see cref="Increment"/> property.
/// </summary> /// </summary>
public static readonly StyledProperty<double> IncrementProperty = public static readonly StyledProperty<decimal> IncrementProperty =
AvaloniaProperty.Register<NumericUpDown, double>(nameof(Increment), 1.0d, coerce: OnCoerceIncrement); AvaloniaProperty.Register<NumericUpDown, decimal>(nameof(Increment), 1.0m, coerce: OnCoerceIncrement);
/// <summary> /// <summary>
/// Defines the <see cref="IsReadOnly"/> property. /// Defines the <see cref="IsReadOnly"/> property.
@ -70,14 +78,14 @@ namespace Avalonia.Controls
/// <summary> /// <summary>
/// Defines the <see cref="Maximum"/> property. /// Defines the <see cref="Maximum"/> property.
/// </summary> /// </summary>
public static readonly StyledProperty<double> MaximumProperty = public static readonly StyledProperty<decimal> MaximumProperty =
AvaloniaProperty.Register<NumericUpDown, double>(nameof(Maximum), double.MaxValue, coerce: OnCoerceMaximum); AvaloniaProperty.Register<NumericUpDown, decimal>(nameof(Maximum), decimal.MaxValue, coerce: OnCoerceMaximum);
/// <summary> /// <summary>
/// Defines the <see cref="Minimum"/> property. /// Defines the <see cref="Minimum"/> property.
/// </summary> /// </summary>
public static readonly StyledProperty<double> MinimumProperty = public static readonly StyledProperty<decimal> MinimumProperty =
AvaloniaProperty.Register<NumericUpDown, double>(nameof(Minimum), double.MinValue, coerce: OnCoerceMinimum); AvaloniaProperty.Register<NumericUpDown, decimal>(nameof(Minimum), decimal.MinValue, coerce: OnCoerceMinimum);
/// <summary> /// <summary>
/// Defines the <see cref="ParsingNumberStyle"/> property. /// Defines the <see cref="ParsingNumberStyle"/> property.
@ -96,8 +104,8 @@ namespace Avalonia.Controls
/// <summary> /// <summary>
/// Defines the <see cref="Value"/> property. /// Defines the <see cref="Value"/> property.
/// </summary> /// </summary>
public static readonly DirectProperty<NumericUpDown, double> ValueProperty = public static readonly DirectProperty<NumericUpDown, decimal> ValueProperty =
AvaloniaProperty.RegisterDirect<NumericUpDown, double>(nameof(Value), updown => updown.Value, AvaloniaProperty.RegisterDirect<NumericUpDown, decimal>(nameof(Value), updown => updown.Value,
(updown, v) => updown.Value = v, defaultBindingMode: BindingMode.TwoWay, enableDataValidation: true); (updown, v) => updown.Value = v, defaultBindingMode: BindingMode.TwoWay, enableDataValidation: true);
/// <summary> /// <summary>
@ -106,7 +114,6 @@ namespace Avalonia.Controls
public static readonly StyledProperty<string> WatermarkProperty = public static readonly StyledProperty<string> WatermarkProperty =
AvaloniaProperty.Register<NumericUpDown, string>(nameof(Watermark)); AvaloniaProperty.Register<NumericUpDown, string>(nameof(Watermark));
/// <summary> /// <summary>
/// Defines the <see cref="HorizontalContentAlignment"/> property. /// Defines the <see cref="HorizontalContentAlignment"/> property.
/// </summary> /// </summary>
@ -121,7 +128,7 @@ namespace Avalonia.Controls
private IDisposable _textBoxTextChangedSubscription; private IDisposable _textBoxTextChangedSubscription;
private double _value; private decimal _value;
private string _text; private string _text;
private bool _internalValueSet; private bool _internalValueSet;
private bool _clipValueToMinMax; private bool _clipValueToMinMax;
@ -129,7 +136,8 @@ namespace Avalonia.Controls
private bool _isTextChangedFromUI; private bool _isTextChangedFromUI;
private CultureInfo _cultureInfo; private CultureInfo _cultureInfo;
private NumberStyles _parsingNumberStyle = NumberStyles.Any; private NumberStyles _parsingNumberStyle = NumberStyles.Any;
private NumberFormatInfo _numberFormat;
/// <summary> /// <summary>
/// Gets the Spinner template part. /// Gets the Spinner template part.
/// </summary> /// </summary>
@ -179,10 +187,25 @@ namespace Avalonia.Controls
/// <summary> /// <summary>
/// Gets or sets the current CultureInfo. /// Gets or sets the current CultureInfo.
/// </summary> /// </summary>
[Obsolete("CultureInfo is obsolete, please use NumberFormat instead.")]
public CultureInfo CultureInfo public CultureInfo CultureInfo
{ {
get { return _cultureInfo; } get { return _cultureInfo; }
set { SetAndRaise(CultureInfoProperty, ref _cultureInfo, value); } set
{
SetAndRaise(CultureInfoProperty, ref _cultureInfo, value);
//Set and Raise the NumberFormatProperty when CultureInfo is changed.
SetAndRaise(NumberFormatProperty, ref _numberFormat, value?.NumberFormat);
}
}
/// <summary>
/// Gets or sets the current NumberFormatInfo
/// </summary>
public NumberFormatInfo NumberFormat
{
get { return _numberFormat; }
set { SetAndRaise(NumberFormatProperty, ref _numberFormat, value); }
} }
/// <summary> /// <summary>
@ -197,7 +220,7 @@ namespace Avalonia.Controls
/// <summary> /// <summary>
/// Gets or sets the amount in which to increment the <see cref="Value"/>. /// Gets or sets the amount in which to increment the <see cref="Value"/>.
/// </summary> /// </summary>
public double Increment public decimal Increment
{ {
get { return GetValue(IncrementProperty); } get { return GetValue(IncrementProperty); }
set { SetValue(IncrementProperty, value); } set { SetValue(IncrementProperty, value); }
@ -215,7 +238,7 @@ namespace Avalonia.Controls
/// <summary> /// <summary>
/// Gets or sets the maximum allowed value. /// Gets or sets the maximum allowed value.
/// </summary> /// </summary>
public double Maximum public decimal Maximum
{ {
get { return GetValue(MaximumProperty); } get { return GetValue(MaximumProperty); }
set { SetValue(MaximumProperty, value); } set { SetValue(MaximumProperty, value); }
@ -224,7 +247,7 @@ namespace Avalonia.Controls
/// <summary> /// <summary>
/// Gets or sets the minimum allowed value. /// Gets or sets the minimum allowed value.
/// </summary> /// </summary>
public double Minimum public decimal Minimum
{ {
get { return GetValue(MinimumProperty); } get { return GetValue(MinimumProperty); }
set { SetValue(MinimumProperty, value); } set { SetValue(MinimumProperty, value); }
@ -251,7 +274,7 @@ namespace Avalonia.Controls
/// <summary> /// <summary>
/// Gets or sets the value. /// Gets or sets the value.
/// </summary> /// </summary>
public double Value public decimal Value
{ {
get { return _value; } get { return _value; }
set set
@ -270,7 +293,6 @@ namespace Avalonia.Controls
set { SetValue(WatermarkProperty, value); } set { SetValue(WatermarkProperty, value); }
} }
/// <summary> /// <summary>
/// Gets or sets the horizontal alignment of the content within the control. /// Gets or sets the horizontal alignment of the content within the control.
/// </summary> /// </summary>
@ -311,6 +333,7 @@ namespace Avalonia.Controls
static NumericUpDown() static NumericUpDown()
{ {
CultureInfoProperty.Changed.Subscribe(OnCultureInfoChanged); CultureInfoProperty.Changed.Subscribe(OnCultureInfoChanged);
NumberFormatProperty.Changed.Subscribe(OnNumberFormatChanged);
FormatStringProperty.Changed.Subscribe(FormatStringChanged); FormatStringProperty.Changed.Subscribe(FormatStringChanged);
IncrementProperty.Changed.Subscribe(IncrementChanged); IncrementProperty.Changed.Subscribe(IncrementChanged);
IsReadOnlyProperty.Changed.Subscribe(OnIsReadOnlyChanged); IsReadOnlyProperty.Changed.Subscribe(OnIsReadOnlyChanged);
@ -397,6 +420,19 @@ namespace Avalonia.Controls
} }
} }
/// <summary>
/// Called when the <see cref="NumberFormat"/> property value changed.
/// </summary>
/// <param name="oldValue">The old value.</param>
/// <param name="newValue">The new value.</param>
protected virtual void OnNumberFormatChanged(NumberFormatInfo oldValue, NumberFormatInfo newValue)
{
if (IsInitialized)
{
SyncTextAndValueProperties(false, null);
}
}
/// <summary> /// <summary>
/// Called when the <see cref="FormatString"/> property value changed. /// Called when the <see cref="FormatString"/> property value changed.
/// </summary> /// </summary>
@ -415,7 +451,7 @@ namespace Avalonia.Controls
/// </summary> /// </summary>
/// <param name="oldValue">The old value.</param> /// <param name="oldValue">The old value.</param>
/// <param name="newValue">The new value.</param> /// <param name="newValue">The new value.</param>
protected virtual void OnIncrementChanged(double oldValue, double newValue) protected virtual void OnIncrementChanged(decimal oldValue, decimal newValue)
{ {
if (IsInitialized) if (IsInitialized)
{ {
@ -438,7 +474,7 @@ namespace Avalonia.Controls
/// </summary> /// </summary>
/// <param name="oldValue">The old value.</param> /// <param name="oldValue">The old value.</param>
/// <param name="newValue">The new value.</param> /// <param name="newValue">The new value.</param>
protected virtual void OnMaximumChanged(double oldValue, double newValue) protected virtual void OnMaximumChanged(decimal oldValue, decimal newValue)
{ {
if (IsInitialized) if (IsInitialized)
{ {
@ -455,7 +491,7 @@ namespace Avalonia.Controls
/// </summary> /// </summary>
/// <param name="oldValue">The old value.</param> /// <param name="oldValue">The old value.</param>
/// <param name="newValue">The new value.</param> /// <param name="newValue">The new value.</param>
protected virtual void OnMinimumChanged(double oldValue, double newValue) protected virtual void OnMinimumChanged(decimal oldValue, decimal newValue)
{ {
if (IsInitialized) if (IsInitialized)
{ {
@ -485,7 +521,7 @@ namespace Avalonia.Controls
/// </summary> /// </summary>
/// <param name="oldValue">The old value.</param> /// <param name="oldValue">The old value.</param>
/// <param name="newValue">The new value.</param> /// <param name="newValue">The new value.</param>
protected virtual void OnValueChanged(double oldValue, double newValue) protected virtual void OnValueChanged(decimal oldValue, decimal newValue)
{ {
if (!_internalValueSet && IsInitialized) if (!_internalValueSet && IsInitialized)
{ {
@ -501,7 +537,7 @@ namespace Avalonia.Controls
/// Called when the <see cref="Increment"/> property has to be coerced. /// Called when the <see cref="Increment"/> property has to be coerced.
/// </summary> /// </summary>
/// <param name="baseValue">The value.</param> /// <param name="baseValue">The value.</param>
protected virtual double OnCoerceIncrement(double baseValue) protected virtual decimal OnCoerceIncrement(decimal baseValue)
{ {
return baseValue; return baseValue;
} }
@ -510,7 +546,7 @@ namespace Avalonia.Controls
/// Called when the <see cref="Maximum"/> property has to be coerced. /// Called when the <see cref="Maximum"/> property has to be coerced.
/// </summary> /// </summary>
/// <param name="baseValue">The value.</param> /// <param name="baseValue">The value.</param>
protected virtual double OnCoerceMaximum(double baseValue) protected virtual decimal OnCoerceMaximum(decimal baseValue)
{ {
return Math.Max(baseValue, Minimum); return Math.Max(baseValue, Minimum);
} }
@ -519,7 +555,7 @@ namespace Avalonia.Controls
/// Called when the <see cref="Minimum"/> property has to be coerced. /// Called when the <see cref="Minimum"/> property has to be coerced.
/// </summary> /// </summary>
/// <param name="baseValue">The value.</param> /// <param name="baseValue">The value.</param>
protected virtual double OnCoerceMinimum(double baseValue) protected virtual decimal OnCoerceMinimum(decimal baseValue)
{ {
return Math.Min(baseValue, Maximum); return Math.Min(baseValue, Maximum);
} }
@ -528,7 +564,7 @@ namespace Avalonia.Controls
/// Called when the <see cref="Value"/> property has to be coerced. /// Called when the <see cref="Value"/> property has to be coerced.
/// </summary> /// </summary>
/// <param name="baseValue">The value.</param> /// <param name="baseValue">The value.</param>
protected virtual double OnCoerceValue(double baseValue) protected virtual decimal OnCoerceValue(decimal baseValue)
{ {
return baseValue; return baseValue;
} }
@ -562,7 +598,7 @@ namespace Avalonia.Controls
/// </summary> /// </summary>
/// <param name="oldValue">The old value.</param> /// <param name="oldValue">The old value.</param>
/// <param name="newValue">The new value.</param> /// <param name="newValue">The new value.</param>
protected virtual void RaiseValueChangedEvent(double oldValue, double newValue) protected virtual void RaiseValueChangedEvent(decimal oldValue, decimal newValue)
{ {
var e = new NumericUpDownValueChangedEventArgs(ValueChangedEvent, oldValue, newValue); var e = new NumericUpDownValueChangedEventArgs(ValueChangedEvent, oldValue, newValue);
RaiseEvent(e); RaiseEvent(e);
@ -571,9 +607,9 @@ namespace Avalonia.Controls
/// <summary> /// <summary>
/// Converts the formatted text to a value. /// Converts the formatted text to a value.
/// </summary> /// </summary>
private double ConvertTextToValue(string text) private decimal ConvertTextToValue(string text)
{ {
double result = 0; decimal result = 0;
if (string.IsNullOrEmpty(text)) if (string.IsNullOrEmpty(text))
{ {
@ -609,10 +645,10 @@ namespace Avalonia.Controls
//Manage FormatString of type "{}{0:N2} °" (in xaml) or "{0:N2} °" in code-behind. //Manage FormatString of type "{}{0:N2} °" (in xaml) or "{0:N2} °" in code-behind.
if (FormatString.Contains("{0")) if (FormatString.Contains("{0"))
{ {
return string.Format(CultureInfo, FormatString, Value); return string.Format(NumberFormat, FormatString, Value);
} }
return Value.ToString(FormatString, CultureInfo); return Value.ToString(FormatString, NumberFormat);
} }
/// <summary> /// <summary>
@ -674,6 +710,20 @@ namespace Avalonia.Controls
} }
} }
/// <summary>
/// Called when the <see cref="NumberFormat"/> property value changed.
/// </summary>
/// <param name="e">The event args.</param>
private static void OnNumberFormatChanged(AvaloniaPropertyChangedEventArgs e)
{
if (e.Sender is NumericUpDown upDown)
{
var oldValue = (NumberFormatInfo)e.OldValue;
var newValue = (NumberFormatInfo)e.NewValue;
upDown.OnNumberFormatChanged(oldValue, newValue);
}
}
/// <summary> /// <summary>
/// Called when the <see cref="Increment"/> property value changed. /// Called when the <see cref="Increment"/> property value changed.
/// </summary> /// </summary>
@ -682,8 +732,8 @@ namespace Avalonia.Controls
{ {
if (e.Sender is NumericUpDown upDown) if (e.Sender is NumericUpDown upDown)
{ {
var oldValue = (double)e.OldValue; var oldValue = (decimal)e.OldValue;
var newValue = (double)e.NewValue; var newValue = (decimal)e.NewValue;
upDown.OnIncrementChanged(oldValue, newValue); upDown.OnIncrementChanged(oldValue, newValue);
} }
} }
@ -724,8 +774,8 @@ namespace Avalonia.Controls
{ {
if (e.Sender is NumericUpDown upDown) if (e.Sender is NumericUpDown upDown)
{ {
var oldValue = (double)e.OldValue; var oldValue = (decimal)e.OldValue;
var newValue = (double)e.NewValue; var newValue = (decimal)e.NewValue;
upDown.OnMaximumChanged(oldValue, newValue); upDown.OnMaximumChanged(oldValue, newValue);
} }
} }
@ -738,8 +788,8 @@ namespace Avalonia.Controls
{ {
if (e.Sender is NumericUpDown upDown) if (e.Sender is NumericUpDown upDown)
{ {
var oldValue = (double)e.OldValue; var oldValue = (decimal)e.OldValue;
var newValue = (double)e.NewValue; var newValue = (decimal)e.NewValue;
upDown.OnMinimumChanged(oldValue, newValue); upDown.OnMinimumChanged(oldValue, newValue);
} }
} }
@ -766,13 +816,13 @@ namespace Avalonia.Controls
{ {
if (e.Sender is NumericUpDown upDown) if (e.Sender is NumericUpDown upDown)
{ {
var oldValue = (double)e.OldValue; var oldValue = (decimal)e.OldValue;
var newValue = (double)e.NewValue; var newValue = (decimal)e.NewValue;
upDown.OnValueChanged(oldValue, newValue); upDown.OnValueChanged(oldValue, newValue);
} }
} }
private void SetValueInternal(double value) private void SetValueInternal(decimal value)
{ {
_internalValueSet = true; _internalValueSet = true;
try try
@ -785,7 +835,7 @@ namespace Avalonia.Controls
} }
} }
private static double OnCoerceMaximum(IAvaloniaObject instance, double value) private static decimal OnCoerceMaximum(IAvaloniaObject instance, decimal value)
{ {
if (instance is NumericUpDown upDown) if (instance is NumericUpDown upDown)
{ {
@ -795,7 +845,7 @@ namespace Avalonia.Controls
return value; return value;
} }
private static double OnCoerceMinimum(IAvaloniaObject instance, double value) private static decimal OnCoerceMinimum(IAvaloniaObject instance, decimal value)
{ {
if (instance is NumericUpDown upDown) if (instance is NumericUpDown upDown)
{ {
@ -805,7 +855,7 @@ namespace Avalonia.Controls
return value; return value;
} }
private static double OnCoerceIncrement(IAvaloniaObject instance, double value) private static decimal OnCoerceIncrement(IAvaloniaObject instance, decimal value)
{ {
if (instance is NumericUpDown upDown) if (instance is NumericUpDown upDown)
{ {
@ -977,23 +1027,23 @@ namespace Avalonia.Controls
return parsedTextIsValid; return parsedTextIsValid;
} }
private double ConvertTextToValueCore(string currentValueText, string text) private decimal ConvertTextToValueCore(string currentValueText, string text)
{ {
double result; decimal result;
if (IsPercent(FormatString)) if (IsPercent(FormatString))
{ {
result = decimal.ToDouble(ParsePercent(text, CultureInfo)); result = ParsePercent(text, NumberFormat);
} }
else else
{ {
// Problem while converting new text // Problem while converting new text
if (!double.TryParse(text, ParsingNumberStyle, CultureInfo, out var outputValue)) if (!decimal.TryParse(text, ParsingNumberStyle, NumberFormat, out var outputValue))
{ {
var shouldThrow = true; var shouldThrow = true;
// Check if CurrentValueText is also failing => it also contains special characters. ex : 90° // Check if CurrentValueText is also failing => it also contains special characters. ex : 90°
if (!double.TryParse(currentValueText, ParsingNumberStyle, CultureInfo, out var _)) if (!decimal.TryParse(currentValueText, ParsingNumberStyle, NumberFormat, out var _))
{ {
// extract non-digit characters // extract non-digit characters
var currentValueTextSpecialCharacters = currentValueText.Where(c => !char.IsDigit(c)); var currentValueTextSpecialCharacters = currentValueText.Where(c => !char.IsDigit(c));
@ -1006,7 +1056,7 @@ namespace Avalonia.Controls
text = text.Replace(character.ToString(), string.Empty); text = text.Replace(character.ToString(), string.Empty);
} }
// if without the special characters, parsing is good, do not throw // if without the special characters, parsing is good, do not throw
if (double.TryParse(text, ParsingNumberStyle, CultureInfo, out outputValue)) if (decimal.TryParse(text, ParsingNumberStyle, NumberFormat, out outputValue))
{ {
shouldThrow = false; shouldThrow = false;
} }
@ -1023,7 +1073,7 @@ namespace Avalonia.Controls
return result; return result;
} }
private void ValidateMinMax(double value) private void ValidateMinMax(decimal value)
{ {
if (value < Minimum) if (value < Minimum)
{ {

6
src/Avalonia.Controls/NumericUpDown/NumericUpDownValueChangedEventArgs.cs

@ -4,13 +4,13 @@ namespace Avalonia.Controls
{ {
public class NumericUpDownValueChangedEventArgs : RoutedEventArgs public class NumericUpDownValueChangedEventArgs : RoutedEventArgs
{ {
public NumericUpDownValueChangedEventArgs(RoutedEvent routedEvent, double oldValue, double newValue) : base(routedEvent) public NumericUpDownValueChangedEventArgs(RoutedEvent routedEvent, decimal oldValue, decimal newValue) : base(routedEvent)
{ {
OldValue = oldValue; OldValue = oldValue;
NewValue = newValue; NewValue = newValue;
} }
public double OldValue { get; } public decimal OldValue { get; }
public double NewValue { get; } public decimal NewValue { get; }
} }
} }

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

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

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

@ -186,7 +186,7 @@ namespace Avalonia.Controls.Presenters
if (PageTransition != null && (from != null || to != null)) 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) else if (to != null)
{ {

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

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

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

@ -1,19 +1,33 @@
using System; using System;
using System.Collections.Specialized; using System.Collections.Specialized;
using System.Linq;
using System.Resources;
using Avalonia.Media; using Avalonia.Media;
using Avalonia.Rendering; using Avalonia.Rendering;
using Avalonia.Utilities;
using Avalonia.VisualTree; using Avalonia.VisualTree;
#nullable enable
namespace Avalonia.Controls.Primitives namespace Avalonia.Controls.Primitives
{ {
// TODO: Need to track position of adorned elements and move the adorner if they move. /// <summary>
/// Represents a surface for showing adorners.
/// Adorners are always on top of the adorned element and are positioned to stay relative to the adorned element.
/// </summary>
/// <remarks>
/// TODO: Need to track position of adorned elements and move the adorner if they move.
/// </remarks>
public class AdornerLayer : Canvas, ICustomSimpleHitTest public class AdornerLayer : Canvas, ICustomSimpleHitTest
{ {
public static readonly AttachedProperty<Visual> AdornedElementProperty = /// <summary>
AvaloniaProperty.RegisterAttached<AdornerLayer, Visual, Visual>("AdornedElement"); /// Allows for getting and setting of the adorned element.
/// </summary>
public static readonly AttachedProperty<Visual?> AdornedElementProperty =
AvaloniaProperty.RegisterAttached<AdornerLayer, Visual, Visual?>("AdornedElement");
/// <summary>
/// Allows for controlling clipping of the adorner.
/// </summary>
public static readonly AttachedProperty<bool> IsClipEnabledProperty =
AvaloniaProperty.RegisterAttached<AdornerLayer, Visual, bool>("IsClipEnabled", true);
private static readonly AttachedProperty<AdornedElementInfo> s_adornedElementInfoProperty = private static readonly AttachedProperty<AdornedElementInfo> s_adornedElementInfoProperty =
AvaloniaProperty.RegisterAttached<AdornerLayer, Visual, AdornedElementInfo>("AdornedElementInfo"); AvaloniaProperty.RegisterAttached<AdornerLayer, Visual, AdornedElementInfo>("AdornedElementInfo");
@ -28,7 +42,7 @@ namespace Avalonia.Controls.Primitives
Children.CollectionChanged += ChildrenCollectionChanged; Children.CollectionChanged += ChildrenCollectionChanged;
} }
public static Visual GetAdornedElement(Visual adorner) public static Visual? GetAdornedElement(Visual adorner)
{ {
return adorner.GetValue(AdornedElementProperty); return adorner.GetValue(AdornedElementProperty);
} }
@ -38,12 +52,19 @@ namespace Avalonia.Controls.Primitives
adorner.SetValue(AdornedElementProperty, adorned); adorner.SetValue(AdornedElementProperty, adorned);
} }
public static AdornerLayer GetAdornerLayer(IVisual visual) public static AdornerLayer? GetAdornerLayer(IVisual visual)
{
return visual.FindAncestorOfType<VisualLayerManager>()?.AdornerLayer;
}
public static bool GetIsClipEnabled(Visual adorner)
{ {
return visual.GetVisualAncestors() return adorner.GetValue(IsClipEnabledProperty);
.OfType<VisualLayerManager>() }
.FirstOrDefault()
?.AdornerLayer; public static void SetIsClipEnabled(Visual adorner, bool isClipEnabled)
{
adorner.SetValue(IsClipEnabledProperty, isClipEnabled);
} }
protected override Size MeasureOverride(Size availableSize) protected override Size MeasureOverride(Size availableSize)
@ -70,12 +91,13 @@ namespace Avalonia.Controls.Primitives
foreach (var child in Children) foreach (var child in Children)
{ {
var info = child.GetValue(s_adornedElementInfoProperty); var info = child.GetValue(s_adornedElementInfoProperty);
var isClipEnabled = child.GetValue(IsClipEnabledProperty);
if (info != null && info.Bounds.HasValue) if (info != null && info.Bounds.HasValue)
{ {
child.RenderTransform = new MatrixTransform(info.Bounds.Value.Transform); child.RenderTransform = new MatrixTransform(info.Bounds.Value.Transform);
child.RenderTransformOrigin = new RelativePoint(new Point(0,0), RelativeUnit.Absolute); child.RenderTransformOrigin = new RelativePoint(new Point(0,0), RelativeUnit.Absolute);
UpdateClip(child, info.Bounds.Value); UpdateClip(child, info.Bounds.Value, isClipEnabled);
child.Arrange(info.Bounds.Value.Bounds); child.Arrange(info.Bounds.Value.Bounds);
} }
else else
@ -87,16 +109,23 @@ namespace Avalonia.Controls.Primitives
return finalSize; return finalSize;
} }
private static void AdornedElementChanged(AvaloniaPropertyChangedEventArgs e) private static void AdornedElementChanged(AvaloniaPropertyChangedEventArgs<Visual?> e)
{ {
var adorner = (Visual)e.Sender; var adorner = (Visual)e.Sender;
var adorned = (Visual)e.NewValue; var adorned = e.NewValue.GetValueOrDefault();
var layer = adorner.GetVisualParent<AdornerLayer>(); var layer = adorner.GetVisualParent<AdornerLayer>();
layer?.UpdateAdornedElement(adorner, adorned); layer?.UpdateAdornedElement(adorner, adorned);
} }
private void UpdateClip(IControl control, TransformedBounds bounds) private void UpdateClip(IControl control, TransformedBounds bounds, bool isEnabled)
{ {
if (!isEnabled)
{
control.Clip = null;
return;
}
if (!(control.Clip is RectangleGeometry clip)) if (!(control.Clip is RectangleGeometry clip))
{ {
clip = new RectangleGeometry(); clip = new RectangleGeometry();
@ -129,13 +158,13 @@ namespace Avalonia.Controls.Primitives
InvalidateArrange(); InvalidateArrange();
} }
private void UpdateAdornedElement(Visual adorner, Visual adorned) private void UpdateAdornedElement(Visual adorner, Visual? adorned)
{ {
var info = adorner.GetValue(s_adornedElementInfoProperty); var info = adorner.GetValue(s_adornedElementInfoProperty);
if (info != null) if (info != null)
{ {
info.Subscription.Dispose(); info.Subscription!.Dispose();
if (adorned == null) if (adorned == null)
{ {
@ -163,7 +192,7 @@ namespace Avalonia.Controls.Primitives
private class AdornedElementInfo private class AdornedElementInfo
{ {
public IDisposable Subscription { get; set; } public IDisposable? Subscription { get; set; }
public TransformedBounds? Bounds { get; set; } public TransformedBounds? Bounds { get; set; }
} }

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

@ -10,6 +10,7 @@ using Avalonia.Data;
using Avalonia.Input; using Avalonia.Input;
using Avalonia.Input.Platform; using Avalonia.Input.Platform;
using Avalonia.Interactivity; using Avalonia.Interactivity;
using Avalonia.Threading;
using Avalonia.VisualTree; using Avalonia.VisualTree;
#nullable enable #nullable enable
@ -91,6 +92,12 @@ namespace Avalonia.Controls.Primitives
AvaloniaProperty.Register<SelectingItemsControl, SelectionMode>( AvaloniaProperty.Register<SelectingItemsControl, SelectionMode>(
nameof(SelectionMode)); nameof(SelectionMode));
/// <summary>
/// Defines the <see cref="IsTextSearchEnabled"/> property.
/// </summary>
public static readonly StyledProperty<bool> IsTextSearchEnabledProperty =
AvaloniaProperty.Register<ItemsControl, bool>(nameof(IsTextSearchEnabled), true);
/// <summary> /// <summary>
/// Event that should be raised by items that implement <see cref="ISelectable"/> to /// Event that should be raised by items that implement <see cref="ISelectable"/> to
/// notify the parent <see cref="SelectingItemsControl"/> that their selection state /// notify the parent <see cref="SelectingItemsControl"/> that their selection state
@ -110,6 +117,8 @@ namespace Avalonia.Controls.Primitives
RoutingStrategies.Bubble); RoutingStrategies.Bubble);
private static readonly IList Empty = Array.Empty<object>(); private static readonly IList Empty = Array.Empty<object>();
private string _textSearchTerm = string.Empty;
private DispatcherTimer? _textSearchTimer;
private ISelectionModel? _selection; private ISelectionModel? _selection;
private int _oldSelectedIndex; private int _oldSelectedIndex;
private object? _oldSelectedItem; private object? _oldSelectedItem;
@ -305,6 +314,15 @@ namespace Avalonia.Controls.Primitives
} }
} }
/// <summary>
/// Gets or sets a value that specifies whether a user can jump to a value by typing.
/// </summary>
public bool IsTextSearchEnabled
{
get { return GetValue(IsTextSearchEnabledProperty); }
set { SetValue(IsTextSearchEnabledProperty, value); }
}
/// <summary> /// <summary>
/// Gets or sets the selection mode. /// Gets or sets the selection mode.
/// </summary> /// </summary>
@ -490,6 +508,36 @@ namespace Avalonia.Controls.Primitives
} }
} }
protected override void OnTextInput(TextInputEventArgs e)
{
if (!e.Handled)
{
if (!IsTextSearchEnabled)
return;
StopTextSearchTimer();
_textSearchTerm += e.Text;
bool match(ItemContainerInfo info) =>
info.ContainerControl is IContentControl control &&
control.Content?.ToString()?.StartsWith(_textSearchTerm, StringComparison.OrdinalIgnoreCase) == true;
var info = ItemContainerGenerator.Containers.FirstOrDefault(match);
if (info != null)
{
SelectedIndex = info.Index;
}
StartTextSearchTimer();
e.Handled = true;
}
base.OnTextInput(e);
}
protected override void OnKeyDown(KeyEventArgs e) protected override void OnKeyDown(KeyEventArgs e)
{ {
base.OnKeyDown(e); base.OnKeyDown(e);
@ -962,6 +1010,32 @@ namespace Avalonia.Controls.Primitives
} }
} }
private void StartTextSearchTimer()
{
_textSearchTimer = new DispatcherTimer { Interval = TimeSpan.FromSeconds(1) };
_textSearchTimer.Tick += TextSearchTimer_Tick;
_textSearchTimer.Start();
}
private void StopTextSearchTimer()
{
if (_textSearchTimer == null)
{
return;
}
_textSearchTimer.Tick -= TextSearchTimer_Tick;
_textSearchTimer.Stop();
_textSearchTimer = null;
}
private void TextSearchTimer_Tick(object sender, EventArgs e)
{
_textSearchTerm = string.Empty;
StopTextSearchTimer();
}
// When in a BeginInit..EndInit block, or when the DataContext is updating, we need to // When in a BeginInit..EndInit block, or when the DataContext is updating, we need to
// defer changes to the selection model because we have no idea in which order properties // defer changes to the selection model because we have no idea in which order properties
// will be set. Consider: // will be set. Consider:

20
src/Avalonia.Controls/Primitives/Thumb.cs

@ -56,6 +56,26 @@ namespace Avalonia.Controls.Primitives
{ {
} }
protected override void OnPointerCaptureLost(PointerCaptureLostEventArgs e)
{
if (_lastPoint.HasValue)
{
var ev = new VectorEventArgs
{
RoutedEvent = DragCompletedEvent,
Vector = _lastPoint.Value,
};
_lastPoint = null;
RaiseEvent(ev);
}
PseudoClasses.Remove(":pressed");
base.OnPointerCaptureLost(e);
}
protected override void OnPointerMoved(PointerEventArgs e) protected override void OnPointerMoved(PointerEventArgs e)
{ {
if (_lastPoint.HasValue) if (_lastPoint.HasValue)

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

@ -27,7 +27,6 @@ namespace Avalonia.Controls
private IScrollAnchorProvider _scroller; private IScrollAnchorProvider _scroller;
private IControl _makeAnchorElement; private IControl _makeAnchorElement;
private bool _isAnchorOutsideRealizedRange; private bool _isAnchorOutsideRealizedRange;
private Task _cacheBuildAction;
private Rect _visibleWindow; private Rect _visibleWindow;
private Rect _layoutExtent; private Rect _layoutExtent;
// This is the expected shift by the layout. // 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 internal class VirtualizationInfo
{ {
private int _pinCounter; private int _pinCounter;
private object _data;
public Rect ArrangeBounds { get; set; } public Rect ArrangeBounds { get; set; }
public bool AutoRecycleCandidate { get; set; } public bool AutoRecycleCandidate { get; set; }

8
src/Avalonia.Controls/RowDefinitions.cs

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

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)); AvaloniaProperty.Register<SplitView, object?>(nameof(Pane));
/// <summary> /// <summary>
/// Defines the <see cref="HeaderTemplate"/> property. /// Defines the <see cref="PaneTemplate"/> property.
/// </summary> /// </summary>
public static readonly StyledProperty<IDataTemplate?> PaneTemplateProperty = public static readonly StyledProperty<IDataTemplate?> PaneTemplateProperty =
AvaloniaProperty.Register<HeaderedContentControl, IDataTemplate?>(nameof(PaneTemplate)); AvaloniaProperty.Register<HeaderedContentControl, IDataTemplate?>(nameof(PaneTemplate));

4
src/Avalonia.Controls/TextBox.cs

@ -145,7 +145,7 @@ namespace Avalonia.Controls
(o, v) => o.UndoLimit = v, (o, v) => o.UndoLimit = v,
unsetValue: -1); unsetValue: -1);
struct UndoRedoState : IEquatable<UndoRedoState> readonly struct UndoRedoState : IEquatable<UndoRedoState>
{ {
public string Text { get; } public string Text { get; }
public int CaretPosition { get; } public int CaretPosition { get; }
@ -1290,7 +1290,7 @@ namespace Avalonia.Controls
private void UpdatePseudoclasses() private void UpdatePseudoclasses()
{ {
PseudoClasses.Set(":empty", string.IsNullOrWhiteSpace(Text)); PseudoClasses.Set(":empty", string.IsNullOrEmpty(Text));
} }
private bool IsPasswordBox => PasswordChar != default(char); 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 = public static readonly DirectProperty<TopLevel, Size> ClientSizeProperty =
AvaloniaProperty.RegisterDirect<TopLevel, Size>(nameof(ClientSize), o => o.ClientSize); 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> /// <summary>
/// Defines the <see cref="IInputRoot.PointerOverElement"/> property. /// Defines the <see cref="IInputRoot.PointerOverElement"/> property.
/// </summary> /// </summary>
@ -74,6 +80,7 @@ namespace Avalonia.Controls
private readonly IPlatformRenderInterface _renderInterface; private readonly IPlatformRenderInterface _renderInterface;
private readonly IGlobalStyles _globalStyles; private readonly IGlobalStyles _globalStyles;
private Size _clientSize; private Size _clientSize;
private Size? _frameSize;
private WindowTransparencyLevel _actualTransparencyLevel; private WindowTransparencyLevel _actualTransparencyLevel;
private ILayoutManager _layoutManager; private ILayoutManager _layoutManager;
private Border _transparencyFallbackBorder; private Border _transparencyFallbackBorder;
@ -161,6 +168,7 @@ namespace Avalonia.Controls
styler?.ApplyStyles(this); styler?.ApplyStyles(this);
ClientSize = impl.ClientSize; ClientSize = impl.ClientSize;
FrameSize = impl.FrameSize;
this.GetObservable(PointerOverElementProperty) this.GetObservable(PointerOverElementProperty)
.Select( .Select(
@ -197,6 +205,15 @@ namespace Avalonia.Controls
protected set { SetAndRaise(ClientSizeProperty, ref _clientSize, value); } 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> /// <summary>
/// Gets or sets the <see cref="WindowTransparencyLevel"/> that the TopLevel should use when possible. /// Gets or sets the <see cref="WindowTransparencyLevel"/> that the TopLevel should use when possible.
/// </summary> /// </summary>
@ -366,6 +383,7 @@ namespace Avalonia.Controls
protected virtual void HandleResized(Size clientSize) protected virtual void HandleResized(Size clientSize)
{ {
ClientSize = clientSize; ClientSize = clientSize;
FrameSize = PlatformImpl.FrameSize;
Width = clientSize.Width; Width = clientSize.Width;
Height = clientSize.Height; Height = clientSize.Height;
LayoutManager.ExecuteLayoutPass(); LayoutManager.ExecuteLayoutPass();

6
src/Avalonia.Controls/TreeView.cs

@ -166,11 +166,9 @@ namespace Avalonia.Controls
{ {
item.IsExpanded = true; item.IsExpanded = true;
var panel = item.Presenter.Panel; if (item.Presenter?.Panel != null)
if (panel != null)
{ {
foreach (var child in panel.Children) foreach (var child in item.Presenter.Panel.Children)
{ {
if (child is TreeViewItem treeViewItem) if (child is TreeViewItem treeViewItem)
{ {

39
src/Avalonia.Controls/Window.cs

@ -592,6 +592,14 @@ namespace Avalonia.Controls
owner.RemoveChild(this); owner.RemoveChild(this);
} }
if (_children.Count > 0)
{
foreach (var child in _children.ToArray())
{
child.child.Hide();
}
}
Owner = null; Owner = null;
PlatformImpl?.Hide(); PlatformImpl?.Hide();
@ -635,6 +643,22 @@ namespace Avalonia.Controls
throw new InvalidOperationException("Cannot re-show a closed window."); 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) if (IsVisible)
{ {
return; return;
@ -708,11 +732,22 @@ namespace Avalonia.Controls
{ {
throw new ArgumentNullException(nameof(owner)); throw new ArgumentNullException(nameof(owner));
} }
else if (owner.PlatformImpl == null)
if (IsVisible) {
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."); 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)); RaiseEvent(new RoutedEventArgs(WindowOpenedEvent));

1
src/Avalonia.Controls/WindowBase.cs

@ -222,6 +222,7 @@ namespace Avalonia.Controls
protected override void HandleResized(Size clientSize) protected override void HandleResized(Size clientSize)
{ {
ClientSize = clientSize; ClientSize = clientSize;
FrameSize = PlatformImpl.FrameSize;
LayoutManager.ExecuteLayoutPass(); LayoutManager.ExecuteLayoutPass();
Renderer?.Resized(clientSize); Renderer?.Resized(clientSize);
} }

1
src/Avalonia.DesignerSupport/Remote/Stubs.cs

@ -21,6 +21,7 @@ namespace Avalonia.DesignerSupport.Remote
public IPlatformHandle Handle { get; } public IPlatformHandle Handle { get; }
public Size MaxAutoSizeHint { get; } public Size MaxAutoSizeHint { get; }
public Size ClientSize { get; } public Size ClientSize { get; }
public Size? FrameSize => null;
public double RenderScaling { get; } = 1.0; public double RenderScaling { get; } = 1.0;
public double DesktopScaling => 1.0; public double DesktopScaling => 1.0;
public IEnumerable<object> Surfaces { get; } 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 ClientSize { get; set; }
public Size? FrameSize => null;
public double RenderScaling { get; } = 1; public double RenderScaling { get; } = 1;
public double DesktopScaling => RenderScaling; public double DesktopScaling => RenderScaling;
public IEnumerable<object> Surfaces { get; } public IEnumerable<object> Surfaces { get; }

5
src/Avalonia.Layout/ElementManager.cs

@ -325,7 +325,10 @@ namespace Avalonia.Layout
break; break;
case NotifyCollectionChangedAction.Move: 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[] { public IEnumerable<object> Surfaces => new[] {
(_gpu ? _glSurface : (object)null), (_gpu ? _glSurface : (object)null),
this this

1
src/Avalonia.Native/avn.idl

@ -435,6 +435,7 @@ interface IAvnWindowBase : IUnknown
HRESULT Close(); HRESULT Close();
HRESULT Activate(); HRESULT Activate();
HRESULT GetClientSize(AvnSize*ret); HRESULT GetClientSize(AvnSize*ret);
HRESULT GetFrameSize(AvnSize*ret);
HRESULT GetScaling(double*ret); HRESULT GetScaling(double*ret);
HRESULT SetMinMaxSize(AvnSize minSize, AvnSize maxSize); HRESULT SetMinMaxSize(AvnSize minSize, AvnSize maxSize);
HRESULT Resize(double width, double height); HRESULT Resize(double width, double height);

13
src/Avalonia.ReactiveUI/TransitioningContentControl.cs

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

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

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

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

@ -1,5 +1,6 @@
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using Avalonia.Metadata; using Avalonia.Metadata;
@ -35,25 +36,11 @@ namespace Avalonia.Animation
[Content] [Content]
public List<IPageTransition> PageTransitions { get; set; } = new List<IPageTransition>(); public List<IPageTransition> PageTransitions { get; set; } = new List<IPageTransition>();
/// <summary> /// <inheritdoc />
/// Starts the animation. public Task Start(Visual from, Visual to, bool forward, CancellationToken cancellationToken)
/// </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)
{ {
var transitionTasks = PageTransitions var transitionTasks = PageTransitions
.Select(transition => transition.Start(from, to, forward)) .Select(transition => transition.Start(from, to, forward, cancellationToken))
.ToList(); .ToList();
return Task.WhenAll(transitionTasks); return Task.WhenAll(transitionTasks);
} }

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

@ -1,5 +1,7 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Reactive.Disposables;
using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using Avalonia.Animation.Easings; using Avalonia.Animation.Easings;
using Avalonia.Styling; using Avalonia.Styling;
@ -97,49 +99,39 @@ namespace Avalonia.Animation
set => _fadeOutAnimation.Easing = value; set => _fadeOutAnimation.Easing = value;
} }
/// <summary> /// <inheritdoc cref="Start(Visual, Visual, CancellationToken)" />
/// Starts the animation. public async Task Start(Visual from, Visual to, CancellationToken cancellationToken)
/// </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)
{ {
var tasks = new List<Task>(); if (cancellationToken.IsCancellationRequested)
if (to != null)
{
to.Opacity = 0;
}
if (from != null)
{ {
tasks.Add(_fadeOutAnimation.RunAsync(from)); return;
} }
if (to != null) var tasks = new List<Task>();
using (var disposables = new CompositeDisposable())
{ {
to.IsVisible = true; if (to != null)
tasks.Add(_fadeInAnimation.RunAsync(to)); {
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) await Task.WhenAll(tasks);
{
from.IsVisible = false;
}
if (to != null) if (from != null && !cancellationToken.IsCancellationRequested)
{ {
to.Opacity = 1; from.IsVisible = false;
}
} }
} }
@ -155,12 +147,13 @@ namespace Avalonia.Animation
/// <param name="forward"> /// <param name="forward">
/// Unused for cross-fades. /// Unused for cross-fades.
/// </param> /// </param>
/// <param name="cancellationToken">allowed cancel transition</param>
/// <returns> /// <returns>
/// A <see cref="Task"/> that tracks the progress of the animation. /// A <see cref="Task"/> that tracks the progress of the animation.
/// </returns> /// </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; using System.Threading.Tasks;
namespace Avalonia.Animation namespace Avalonia.Animation
@ -19,9 +20,12 @@ namespace Avalonia.Animation
/// <param name="forward"> /// <param name="forward">
/// If the animation is bidirectional, controls the direction of the animation. /// If the animation is bidirectional, controls the direction of the animation.
/// </param> /// </param>
/// <param name="cancellationToken">
/// Animation cancellation.
/// </param>
/// <returns> /// <returns>
/// A <see cref="Task"/> that tracks the progress of the animation. /// A <see cref="Task"/> that tracks the progress of the animation.
/// </returns> /// </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;
using System.Collections.Generic; using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using Avalonia.Animation.Easings; using Avalonia.Animation.Easings;
using Avalonia.Media; using Avalonia.Media;
@ -60,23 +61,14 @@ namespace Avalonia.Animation
/// </summary> /// </summary>
public Easing SlideOutEasing { get; set; } = new LinearEasing(); public Easing SlideOutEasing { get; set; } = new LinearEasing();
/// <summary> /// <inheritdoc />
/// Starts the animation. public async Task Start(Visual from, Visual to, bool forward, CancellationToken cancellationToken)
/// </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)
{ {
if (cancellationToken.IsCancellationRequested)
{
return;
}
var tasks = new List<Task>(); var tasks = new List<Task>();
var parent = GetVisualParent(from, to); var parent = GetVisualParent(from, to);
var distance = Orientation == SlideAxis.Horizontal ? parent.Bounds.Width : parent.Bounds.Height; var distance = Orientation == SlideAxis.Horizontal ? parent.Bounds.Width : parent.Bounds.Height;
@ -109,7 +101,7 @@ namespace Avalonia.Animation
}, },
Duration = Duration Duration = Duration
}; };
tasks.Add(animation.RunAsync(from)); tasks.Add(animation.RunAsync(from, null, cancellationToken));
} }
if (to != null) if (to != null)
@ -140,12 +132,12 @@ namespace Avalonia.Animation
}, },
Duration = Duration Duration = Duration
}; };
tasks.Add(animation.RunAsync(to)); tasks.Add(animation.RunAsync(to, null, cancellationToken));
} }
await Task.WhenAll(tasks); await Task.WhenAll(tasks);
if (from != null) if (from != null && !cancellationToken.IsCancellationRequested)
{ {
from.IsVisible = false; from.IsVisible = false;
} }

10
src/Avalonia.Visuals/ApiCompatBaseline.txt

@ -1,4 +1,11 @@
Compat issues with assembly Avalonia.Visuals: 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.
TypeCannotChangeClassification : Type 'Avalonia.Media.Immutable.ImmutableSolidColorBrush' is a 'class' in the implementation but is a 'struct' 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. 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. 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. CannotSealType : Type 'Avalonia.Media.TextFormatting.GenericTextParagraphProperties' is actually (has the sealed modifier) sealed in the implementation but not sealed in the contract.
@ -63,9 +70,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)' 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. 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. 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.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.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.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. 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: 75

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

@ -1,5 +1,6 @@
using System; using System;
using System.Globalization; using System.Globalization;
using System.Text;
using Avalonia.Animation.Animators; using Avalonia.Animation.Animators;
using Avalonia.Utilities; using Avalonia.Utilities;
@ -75,6 +76,46 @@ namespace Avalonia.Media
return rv; 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) public static unsafe BoxShadow Parse(string s)
{ {
if(s == null) if(s == null)

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

@ -1,6 +1,6 @@
using System; using System;
using System.Collections.Generic;
using System.ComponentModel; using System.ComponentModel;
using System.Text;
using Avalonia.Animation.Animators; using Avalonia.Animation.Animators;
namespace Avalonia.Media 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)] [EditorBrowsable(EditorBrowsableState.Never)]
public struct BoxShadowsEnumerator public struct BoxShadowsEnumerator
{ {

3
src/Avalonia.Visuals/Media/Brush.cs

@ -2,6 +2,7 @@ using System;
using System.ComponentModel; using System.ComponentModel;
using Avalonia.Animation; using Avalonia.Animation;
using Avalonia.Animation.Animators; using Avalonia.Animation.Animators;
using Avalonia.Media.Immutable;
namespace Avalonia.Media namespace Avalonia.Media
{ {
@ -47,7 +48,7 @@ namespace Avalonia.Media
if (s[0] == '#') if (s[0] == '#')
{ {
return new SolidColorBrush(Color.Parse(s)); return new ImmutableSolidColorBrush(Color.Parse(s));
} }
var brush = KnownColors.GetKnownBrush(s); var brush = KnownColors.GetKnownBrush(s);

11
src/Avalonia.Visuals/Media/Immutable/ImmutableSolidColorBrush.cs

@ -5,7 +5,7 @@ namespace Avalonia.Media.Immutable
/// <summary> /// <summary>
/// Fills an area with a solid color. /// Fills an area with a solid color.
/// </summary> /// </summary>
public readonly struct ImmutableSolidColorBrush : ISolidColorBrush, IEquatable<ImmutableSolidColorBrush> public class ImmutableSolidColorBrush : ISolidColorBrush, IEquatable<ImmutableSolidColorBrush>
{ {
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="ImmutableSolidColorBrush"/> class. /// Initializes a new instance of the <see cref="ImmutableSolidColorBrush"/> class.
@ -48,8 +48,9 @@ namespace Avalonia.Media.Immutable
public bool Equals(ImmutableSolidColorBrush other) public bool Equals(ImmutableSolidColorBrush other)
{ {
// ReSharper disable once CompareOfFloatsByEqualityOperator if (ReferenceEquals(null, other)) return false;
return Color == other.Color && Opacity == other.Opacity; if (ReferenceEquals(this, other)) return true;
return Color.Equals(other.Color) && Opacity.Equals(other.Opacity);
} }
public override bool Equals(object obj) public override bool Equals(object obj)
@ -67,12 +68,12 @@ namespace Avalonia.Media.Immutable
public static bool operator ==(ImmutableSolidColorBrush left, ImmutableSolidColorBrush right) public static bool operator ==(ImmutableSolidColorBrush left, ImmutableSolidColorBrush right)
{ {
return left.Equals(right); return Equals(left, right);
} }
public static bool operator !=(ImmutableSolidColorBrush left, ImmutableSolidColorBrush right) public static bool operator !=(ImmutableSolidColorBrush left, ImmutableSolidColorBrush right)
{ {
return !left.Equals(right); return !Equals(left, right);
} }
/// <summary> /// <summary>

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

@ -19,7 +19,7 @@ namespace Avalonia.Rendering.SceneGraph
} }
/// <summary> /// <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. /// <see cref="BitmapBlendingMode"/> pop.
/// </summary> /// </summary>
public BitmapBlendModeNode() public BitmapBlendModeNode()
@ -40,7 +40,7 @@ namespace Avalonia.Rendering.SceneGraph
/// <summary> /// <summary>
/// Determines if this draw operation equals another. /// Determines if this draw operation equals another.
/// </summary> /// </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> /// <returns>True if the draw operations are the same, otherwise false.</returns>
/// <remarks> /// <remarks>
/// The properties of the other draw operation are passed in as arguments to prevent /// 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 readonly X11Globals _globals;
private WindowTransparencyLevel _currentLevel; private WindowTransparencyLevel _currentLevel;
private WindowTransparencyLevel _requestedLevel; private WindowTransparencyLevel _requestedLevel;
private bool _isCompositing;
private bool _blurAtomsAreSet; private bool _blurAtomsAreSet;
public Action<WindowTransparencyLevel> TransparencyLevelChanged { get; set; } public Action<WindowTransparencyLevel> TransparencyLevelChanged { get; set; }

32
src/Avalonia.X11/X11Window.cs

@ -30,7 +30,6 @@ namespace Avalonia.X11
ITopLevelImplWithTextInputMethod ITopLevelImplWithTextInputMethod
{ {
private readonly AvaloniaX11Platform _platform; private readonly AvaloniaX11Platform _platform;
private readonly IWindowImpl _popupParent;
private readonly bool _popup; private readonly bool _popup;
private readonly X11Info _x11; private readonly X11Info _x11;
private XConfigureEvent? _configure; private XConfigureEvent? _configure;
@ -297,6 +296,30 @@ namespace Avalonia.X11
public Size ClientSize => new Size(_realSize.Width / RenderScaling, _realSize.Height / RenderScaling); 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 public double RenderScaling
{ {
get get
@ -589,6 +612,13 @@ namespace Avalonia.X11
private void OnPropertyChange(IntPtr atom, bool hasValue) 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) if (atom == _x11.Atoms._NET_WM_STATE)
{ {
WindowState state = WindowState.Normal; WindowState state = WindowState.Normal;

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

@ -15,7 +15,6 @@ namespace Avalonia.LinuxFramebuffer
private readonly IOutputBackend _outputBackend; private readonly IOutputBackend _outputBackend;
private readonly IInputBackend _inputBackend; private readonly IInputBackend _inputBackend;
private bool _renderQueued;
public IInputRoot InputRoot { get; private set; } public IInputRoot InputRoot { get; private set; }
public FramebufferToplevelImpl(IOutputBackend outputBackend, IInputBackend inputBackend) public FramebufferToplevelImpl(IOutputBackend outputBackend, IInputBackend inputBackend)
@ -62,6 +61,7 @@ namespace Avalonia.LinuxFramebuffer
} }
public Size ClientSize => ScaledSize; public Size ClientSize => ScaledSize;
public Size? FrameSize => null;
public IMouseDevice MouseDevice => new MouseDevice(); public IMouseDevice MouseDevice => new MouseDevice();
public IPopupImpl CreatePopup() => null; 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 internal class EvDevSingleTouchScreen : EvDevDeviceHandler
{ {
private readonly IScreenInfoProvider _screenInfo; private readonly IScreenInfoProvider _screenInfo;
private readonly int _width, _height;
private readonly Matrix _calibration; private readonly Matrix _calibration;
private input_absinfo _axisX; private input_absinfo _axisX;
private input_absinfo _axisY; 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) public static int StartLinuxFbDev<T>(this T builder, string[] args, string fbdev = null, double scaling = 1)
where T : AppBuilderBase<T>, new() => 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) 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}); 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 PixelSize Size => new PixelSize((int)_varInfo.xres, (int) _varInfo.yres);
public int RowBytes => (int) _fixedInfo.line_length; public int RowBytes => (int) _fixedInfo.line_length;
public Vector Dpi { get; } 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 public unsafe class DrmOutput : IGlOutputBackend, IGlPlatformSurface
{ {
private DrmCard _card; private DrmCard _card;
private readonly EglGlPlatformSurface _eglPlatformSurface;
public PixelSize PixelSize => _mode.Resolution; public PixelSize PixelSize => _mode.Resolution;
public double Scaling { get; set; } public double Scaling { get; set; }
public IGlContext PrimaryContext => _deferredContext; public IGlContext PrimaryContext => _deferredContext;

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

@ -16,16 +16,33 @@ namespace Avalonia.LinuxFramebuffer
private IntPtr _mappedAddress; private IntPtr _mappedAddress;
public double Scaling { get; set; } 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); _fd = NativeUnsafeMethods.open(fileName, 2, 0);
if (_fd <= 0) if (_fd <= 0)
throw new Exception("Error: " + Marshal.GetLastWin32Error()); throw new Exception("Error: " + Marshal.GetLastWin32Error());
try try
{ {
Init(); Init(format);
} }
catch catch
{ {
@ -34,25 +51,28 @@ namespace Avalonia.LinuxFramebuffer
} }
} }
void Init() void Init(PixelFormat? format)
{ {
fixed (void* pnfo = &_varInfo) fixed (void* pnfo = &_varInfo)
{ {
if (-1 == NativeUnsafeMethods.ioctl(_fd, FbIoCtl.FBIOGET_VSCREENINFO, pnfo)) if (-1 == NativeUnsafeMethods.ioctl(_fd, FbIoCtl.FBIOGET_VSCREENINFO, pnfo))
throw new Exception("FBIOGET_VSCREENINFO error: " + Marshal.GetLastWin32Error()); throw new Exception("FBIOGET_VSCREENINFO error: " + Marshal.GetLastWin32Error());
SetBpp(); if (format.HasValue)
{
SetBpp(format.Value);
if (-1 == NativeUnsafeMethods.ioctl(_fd, FbIoCtl.FBIOPUT_VSCREENINFO, pnfo)) if (-1 == NativeUnsafeMethods.ioctl(_fd, FbIoCtl.FBIOPUT_VSCREENINFO, pnfo))
_varInfo.transp = new fb_bitfield(); _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)) if (-1 == NativeUnsafeMethods.ioctl(_fd, FbIoCtl.FBIOGET_VSCREENINFO, pnfo))
throw new Exception("FBIOGET_VSCREENINFO error: " + Marshal.GetLastWin32Error()); throw new Exception("FBIOGET_VSCREENINFO error: " + Marshal.GetLastWin32Error());
if (_varInfo.bits_per_pixel != 32) if (_varInfo.bits_per_pixel != 32)
throw new Exception("Unable to set 32-bit display mode"); throw new Exception("Unable to set 32-bit display mode");
}
} }
fixed(void*pnfo = &_fixedInfo) fixed(void*pnfo = &_fixedInfo)
if (-1 == NativeUnsafeMethods.ioctl(_fd, FbIoCtl.FBIOGET_FSCREENINFO, pnfo)) 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; switch (format)
_varInfo.grayscale = 0;
_varInfo.red = _varInfo.blue = _varInfo.green = _varInfo.transp = new fb_bitfield
{ {
length = 8 case PixelFormat.Rgba8888:
}; _varInfo.bits_per_pixel = 32;
_varInfo.green.offset = 8; _varInfo.grayscale = 0;
_varInfo.blue.offset = 16; _varInfo.red = _varInfo.blue = _varInfo.green = _varInfo.transp = new fb_bitfield
_varInfo.transp.offset = 24; {
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; } public string Id { get; private set; }

14
src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/AvaloniaXamlIlLanguageParseIntrinsics.cs

@ -207,6 +207,20 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions
return true; return true;
} }
if (types.IBrush.IsAssignableFrom(type))
{
if (Color.TryParse(text, out Color color))
{
var brushTypeRef = new XamlAstClrTypeReference(node, types.ImmutableSolidColorBrush, false);
result = new XamlAstNewClrObjectNode(node, brushTypeRef,
types.ImmutableSolidColorBrushConstructorColor,
new List<IXamlAstValueNode> { new XamlConstantNode(node, types.UInt, color.ToUint32()) });
return true;
}
}
result = null; result = null;
return false; return false;
} }

28
src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlBindingPathParser.cs

@ -9,7 +9,6 @@ using XamlX.Ast;
using XamlX.Transform; using XamlX.Transform;
using XamlX.Transform.Transformers; using XamlX.Transform.Transformers;
using XamlX.TypeSystem; using XamlX.TypeSystem;
using XamlParseException = XamlX.XamlParseException; using XamlParseException = XamlX.XamlParseException;
namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers
@ -21,6 +20,7 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers
if (node is XamlAstObjectNode binding && binding.Type.GetClrType().Equals(context.GetAvaloniaTypes().CompiledBindingExtension)) if (node is XamlAstObjectNode binding && binding.Type.GetClrType().Equals(context.GetAvaloniaTypes().CompiledBindingExtension))
{ {
var convertedNode = ConvertLongFormPropertiesToBindingExpressionNode(context, binding); var convertedNode = ConvertLongFormPropertiesToBindingExpressionNode(context, binding);
var foundPath = false;
if (binding.Arguments.Count > 0 && binding.Arguments[0] is XamlAstTextNode bindingPathText) if (binding.Arguments.Count > 0 && binding.Arguments[0] is XamlAstTextNode bindingPathText)
{ {
@ -32,9 +32,18 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers
nodes.Insert(nodes.TakeWhile(x => x is BindingExpressionGrammar.ITransformNode).Count(), convertedNode); nodes.Insert(nodes.TakeWhile(x => x is BindingExpressionGrammar.ITransformNode).Count(), convertedNode);
} }
binding.Arguments[0] = new ParsedBindingPathNode(bindingPathText, context.GetAvaloniaTypes().CompiledBindingPath, nodes); if (nodes.Count == 1 && nodes[0] is BindingExpressionGrammar.EmptyExpressionNode)
{
binding.Arguments.RemoveAt(0);
}
else
{
binding.Arguments[0] = new ParsedBindingPathNode(bindingPathText, context.GetAvaloniaTypes().CompiledBindingPath, nodes);
foundPath = true;
}
} }
else
if (!foundPath)
{ {
var bindingPathAssignment = binding.Children.OfType<XamlAstXamlPropertyValueNode>() var bindingPathAssignment = binding.Children.OfType<XamlAstXamlPropertyValueNode>()
.FirstOrDefault(v => v.Property.GetClrProperty().Name == "Path"); .FirstOrDefault(v => v.Property.GetClrProperty().Name == "Path");
@ -44,12 +53,19 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers
var reader = new CharacterReader(pathValue.Text.AsSpan()); var reader = new CharacterReader(pathValue.Text.AsSpan());
var (nodes, _) = BindingExpressionGrammar.Parse(ref reader); var (nodes, _) = BindingExpressionGrammar.Parse(ref reader);
if (convertedNode != null) if (nodes.Count == 1 && nodes[0] is BindingExpressionGrammar.EmptyExpressionNode)
{ {
nodes.Insert(nodes.TakeWhile(x => x is BindingExpressionGrammar.ITransformNode).Count(), convertedNode); bindingPathAssignment.Values.RemoveAt(0);
} }
else
{
if (convertedNode != null)
{
nodes.Insert(nodes.TakeWhile(x => x is BindingExpressionGrammar.ITransformNode).Count(), convertedNode);
}
bindingPathAssignment.Values[0] = new ParsedBindingPathNode(pathValue, context.GetAvaloniaTypes().CompiledBindingPath, nodes); bindingPathAssignment.Values[0] = new ParsedBindingPathNode(pathValue, context.GetAvaloniaTypes().CompiledBindingPath, nodes);
}
} }
} }
} }

9
src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlWellKnownTypes.cs

@ -80,7 +80,10 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers
public IXamlType ColumnDefinitions { get; } public IXamlType ColumnDefinitions { get; }
public IXamlType Classes { get; } public IXamlType Classes { get; }
public IXamlMethod ClassesBindMethod { get; } public IXamlMethod ClassesBindMethod { get; }
public IXamlProperty StyledElementClassesProperty { get; set; } public IXamlProperty StyledElementClassesProperty { get; }
public IXamlType IBrush { get; }
public IXamlType ImmutableSolidColorBrush { get; }
public IXamlConstructor ImmutableSolidColorBrushConstructorColor { get; }
public AvaloniaXamlIlWellKnownTypes(TransformerConfiguration cfg) public AvaloniaXamlIlWellKnownTypes(TransformerConfiguration cfg)
{ {
@ -178,6 +181,10 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers
.FindMethod( "BindClass", IDisposable, false, IStyledElement, .FindMethod( "BindClass", IDisposable, false, IStyledElement,
cfg.WellKnownTypes.String, cfg.WellKnownTypes.String,
IBinding, cfg.WellKnownTypes.Object); IBinding, cfg.WellKnownTypes.Object);
IBrush = cfg.TypeSystem.GetType("Avalonia.Media.IBrush");
ImmutableSolidColorBrush = cfg.TypeSystem.GetType("Avalonia.Media.Immutable.ImmutableSolidColorBrush");
ImmutableSolidColorBrushConstructorColor = ImmutableSolidColorBrush.GetConstructor(new List<IXamlType> { UInt });
} }
} }

2
src/Skia/Avalonia.Skia/DrawingContextImpl.cs

@ -591,7 +591,7 @@ namespace Avalonia.Skia
/// Configure paint wrapper for using gradient brush. /// Configure paint wrapper for using gradient brush.
/// </summary> /// </summary>
/// <param name="paintWrapper">Paint wrapper.</param> /// <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> /// <param name="gradientBrush">Gradient brush.</param>
private void ConfigureGradientBrush(ref PaintWrapper paintWrapper, Rect targetRect, IGradientBrush gradientBrush) private void ConfigureGradientBrush(ref PaintWrapper paintWrapper, Rect targetRect, IGradientBrush gradientBrush)
{ {

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

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

1
src/Windows/Avalonia.Win32.Interop/Wpf/WpfTopLevelImpl.cs

@ -100,6 +100,7 @@ namespace Avalonia.Win32.Interop.Wpf
} }
Size ITopLevelImpl.ClientSize => _finalSize; Size ITopLevelImpl.ClientSize => _finalSize;
Size? ITopLevelImpl.FrameSize => null;
IMouseDevice ITopLevelImpl.MouseDevice => _mouse; IMouseDevice ITopLevelImpl.MouseDevice => _mouse;
double ITopLevelImpl.RenderScaling => PresentationSource.FromVisual(this)?.CompositionTarget?.TransformToDevice.M11 ?? 1; 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, 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 public enum MapVirtualKeyMapTypes : uint
{ {
MAPVK_VK_TO_VSC = 0x00, MAPVK_VK_TO_VSC = 0x00,
@ -1388,6 +1408,9 @@ namespace Avalonia.Win32.Interop
[DllImport("dwmapi.dll")] [DllImport("dwmapi.dll")]
public static extern int DwmExtendFrameIntoClientArea(IntPtr hwnd, ref MARGINS margins); 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")] [DllImport("dwmapi.dll")]
public static extern int DwmIsCompositionEnabled(out bool enabled); 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 class WinUICompositorConnection : IRenderTimer
{ {
private readonly EglContext _syncContext; private readonly EglContext _syncContext;
private IntPtr _queue;
private ICompositor _compositor; private ICompositor _compositor;
private ICompositor2 _compositor2; private ICompositor2 _compositor2;
private ICompositor5 _compositor5; 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 IScreenImpl Screen { get; }
public IPlatformHandle Handle { get; private set; } 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 ClientSize => new Size(_view.Bounds.Width, _view.Bounds.Height);
public Size? FrameSize => null;
public double RenderScaling => _view.ContentScaleFactor; public double RenderScaling => _view.ContentScaleFactor;
public IEnumerable<object> Surfaces { get; set; } public IEnumerable<object> Surfaces { get; set; }
public Action<RawInputEventArgs> Input { 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 Avalonia.Data;
using Xunit; using Xunit;
using Avalonia.Animation.Easings; using Avalonia.Animation.Easings;
using System.Threading;
using System.Reactive.Linq;
namespace Avalonia.Animation.UnitTests namespace Avalonia.Animation.UnitTests
{ {
@ -176,5 +178,271 @@ namespace Avalonia.Animation.UnitTests
clock.Step(TimeSpan.FromSeconds(0.100d)); clock.Step(TimeSpan.FromSeconds(0.100d));
Assert.Equal(border.Width, 300d); 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] [Fact]
public void Auto_Expanding_In_Style_Should_Not_Break_Range_Selection() public void Auto_Expanding_In_Style_Should_Not_Break_Range_Selection()
{ {
/// Issue #2980. // Issue #2980.
using (Application()) using (Application())
{ {
var target = new DerivedTreeView var target = new DerivedTreeView

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

@ -279,10 +279,11 @@ namespace Avalonia.Controls.UnitTests
{ {
using (UnitTestApplication.Start(TestServices.StyledWindow)) using (UnitTestApplication.Start(TestServices.StyledWindow))
{ {
var parent = Mock.Of<Window>(); var parent = new Window();
var renderer = new Mock<IRenderer>(); var renderer = new Mock<IRenderer>();
var target = new Window(CreateImpl(renderer)); var target = new Window(CreateImpl(renderer));
parent.Show();
target.ShowDialog<object>(parent); target.ShowDialog<object>(parent);
renderer.Verify(x => x.Start(), Times.Once); renderer.Verify(x => x.Start(), Times.Once);
@ -294,10 +295,11 @@ namespace Avalonia.Controls.UnitTests
{ {
using (UnitTestApplication.Start(TestServices.StyledWindow)) using (UnitTestApplication.Start(TestServices.StyledWindow))
{ {
var parent = Mock.Of<Window>(); var parent = new Window();
var target = new Window(); var target = new Window();
var raised = false; var raised = false;
parent.Show();
target.Opened += (s, e) => raised = true; target.Opened += (s, e) => raised = true;
target.ShowDialog<object>(parent); target.ShowDialog<object>(parent);
@ -326,14 +328,15 @@ namespace Avalonia.Controls.UnitTests
{ {
using (UnitTestApplication.Start(TestServices.StyledWindow)) using (UnitTestApplication.Start(TestServices.StyledWindow))
{ {
var parent = new Mock<Window>(); var parent = new Window();
var windowImpl = new Mock<IWindowImpl>(); var windowImpl = new Mock<IWindowImpl>();
windowImpl.SetupProperty(x => x.Closed); windowImpl.SetupProperty(x => x.Closed);
windowImpl.Setup(x => x.DesktopScaling).Returns(1); windowImpl.Setup(x => x.DesktopScaling).Returns(1);
windowImpl.Setup(x => x.RenderScaling).Returns(1); windowImpl.Setup(x => x.RenderScaling).Returns(1);
parent.Show();
var target = new Window(windowImpl.Object); var target = new Window(windowImpl.Object);
var task = target.ShowDialog<bool>(parent.Object); var task = target.ShowDialog<bool>(parent);
windowImpl.Object.Closed(); windowImpl.Object.Closed();
@ -366,14 +369,16 @@ namespace Avalonia.Controls.UnitTests
{ {
using (UnitTestApplication.Start(TestServices.StyledWindow)) using (UnitTestApplication.Start(TestServices.StyledWindow))
{ {
var parent = new Mock<Window>(); var parent = new Window();
var windowImpl = new Mock<IWindowImpl>(); var windowImpl = new Mock<IWindowImpl>();
windowImpl.SetupProperty(x => x.Closed); windowImpl.SetupProperty(x => x.Closed);
windowImpl.Setup(x => x.DesktopScaling).Returns(1); windowImpl.Setup(x => x.DesktopScaling).Returns(1);
windowImpl.Setup(x => x.RenderScaling).Returns(1); windowImpl.Setup(x => x.RenderScaling).Returns(1);
parent.Show();
var target = new Window(windowImpl.Object); var target = new Window(windowImpl.Object);
var task = target.ShowDialog<bool>(parent.Object); var task = target.ShowDialog<bool>(parent);
windowImpl.Object.Closed(); windowImpl.Object.Closed();
await task; await task;
@ -381,12 +386,128 @@ namespace Avalonia.Controls.UnitTests
var openedRaised = false; var openedRaised = false;
target.Opened += (s, e) => openedRaised = true; 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.Equal("Cannot re-show a closed window.", ex.Message);
Assert.False(openedRaised); 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] [Fact]
public void Window_Should_Be_Centered_When_WindowStartupLocation_Is_CenterScreen() public void Window_Should_Be_Centered_When_WindowStartupLocation_Is_CenterScreen()
{ {
@ -686,6 +807,7 @@ namespace Avalonia.Controls.UnitTests
protected override void Show(Window window) protected override void Show(Window window)
{ {
var owner = new Window(); var owner = new Window();
owner.Show();
window.ShowDialog(owner); window.ShowDialog(owner);
} }
} }

104
tests/Avalonia.Markup.Xaml.UnitTests/MarkupExtensions/CompiledBindingExtensionTests.cs

@ -938,6 +938,110 @@ namespace Avalonia.Markup.Xaml.UnitTests.MarkupExtensions
} }
} }
[Fact]
public void SupportsEmptyPath()
{
using (UnitTestApplication.Start(TestServices.StyledWindow))
{
var xaml = @"
<Window xmlns='https://github.com/avaloniaui'
xmlns:x='http://schemas.microsoft.com/winfx/2006/xaml'
xmlns:local='clr-namespace:Avalonia.Markup.Xaml.UnitTests.MarkupExtensions;assembly=Avalonia.Markup.Xaml.UnitTests'
x:DataType='local:TestDataContext'>
<TextBlock Text='{CompiledBinding}' Name='textBlock' />
</Window>";
var window = (Window)AvaloniaRuntimeXamlLoader.Load(xaml);
var textBlock = window.FindControl<TextBlock>("textBlock");
var dataContext = new TestDataContext
{
StringProperty = "foobar"
};
window.DataContext = dataContext;
Assert.Equal(typeof(TestDataContext).FullName, textBlock.Text);
}
}
[Fact]
public void SupportsEmptyPathWithStringFormat()
{
using (UnitTestApplication.Start(TestServices.StyledWindow))
{
var xaml = @"
<Window xmlns='https://github.com/avaloniaui'
xmlns:x='http://schemas.microsoft.com/winfx/2006/xaml'
xmlns:local='clr-namespace:Avalonia.Markup.Xaml.UnitTests.MarkupExtensions;assembly=Avalonia.Markup.Xaml.UnitTests'
x:DataType='local:TestDataContext'>
<TextBlock Text='{CompiledBinding StringFormat=bar-\{0\}}' Name='textBlock' />
</Window>";
var window = (Window)AvaloniaRuntimeXamlLoader.Load(xaml);
var textBlock = window.FindControl<TextBlock>("textBlock");
var dataContext = new TestDataContext
{
StringProperty = "foobar"
};
window.DataContext = dataContext;
Assert.Equal("bar-" + typeof(TestDataContext).FullName, textBlock.Text);
}
}
[Fact]
public void SupportsDotPath()
{
using (UnitTestApplication.Start(TestServices.StyledWindow))
{
var xaml = @"
<Window xmlns='https://github.com/avaloniaui'
xmlns:x='http://schemas.microsoft.com/winfx/2006/xaml'
xmlns:local='clr-namespace:Avalonia.Markup.Xaml.UnitTests.MarkupExtensions;assembly=Avalonia.Markup.Xaml.UnitTests'
x:DataType='local:TestDataContext'>
<TextBlock Text='{CompiledBinding .}' Name='textBlock' />
</Window>";
var window = (Window)AvaloniaRuntimeXamlLoader.Load(xaml);
var textBlock = window.FindControl<TextBlock>("textBlock");
var dataContext = new TestDataContext
{
StringProperty = "foobar"
};
window.DataContext = dataContext;
Assert.Equal(typeof(TestDataContext).FullName, textBlock.Text);
}
}
[Fact]
public void SupportsExplicitDotPathWithStringFormat()
{
using (UnitTestApplication.Start(TestServices.StyledWindow))
{
var xaml = @"
<Window xmlns='https://github.com/avaloniaui'
xmlns:x='http://schemas.microsoft.com/winfx/2006/xaml'
xmlns:local='clr-namespace:Avalonia.Markup.Xaml.UnitTests.MarkupExtensions;assembly=Avalonia.Markup.Xaml.UnitTests'
x:DataType='local:TestDataContext'>
<TextBlock Text='{CompiledBinding Path=., StringFormat=bar-\{0\}}' Name='textBlock' />
</Window>";
var window = (Window)AvaloniaRuntimeXamlLoader.Load(xaml);
var textBlock = window.FindControl<TextBlock>("textBlock");
var dataContext = new TestDataContext
{
StringProperty = "foobar"
};
window.DataContext = dataContext;
Assert.Equal("bar-" + typeof(TestDataContext).FullName, textBlock.Text);
}
}
void Throws(string type, Action cb) void Throws(string type, Action cb)
{ {
try try

20
tests/Avalonia.Markup.Xaml.UnitTests/MarkupExtensions/DynamicResourceExtensionTests.cs

@ -33,7 +33,7 @@ namespace Avalonia.Markup.Xaml.UnitTests.MarkupExtensions
DelayedBinding.ApplyBindings(border); DelayedBinding.ApplyBindings(border);
var brush = (SolidColorBrush)border.Background; var brush = (ISolidColorBrush)border.Background;
Assert.Equal(0xff506070, brush.Color.ToUint32()); Assert.Equal(0xff506070, brush.Color.ToUint32());
} }
@ -80,7 +80,7 @@ namespace Avalonia.Markup.Xaml.UnitTests.MarkupExtensions
DelayedBinding.ApplyBindings(border); DelayedBinding.ApplyBindings(border);
var brush = (SolidColorBrush)border.Background; var brush = (ISolidColorBrush)border.Background;
Assert.Equal(0xff506070, brush.Color.ToUint32()); Assert.Equal(0xff506070, brush.Color.ToUint32());
} }
@ -108,7 +108,7 @@ namespace Avalonia.Markup.Xaml.UnitTests.MarkupExtensions
DelayedBinding.ApplyBindings(border); DelayedBinding.ApplyBindings(border);
var brush = (SolidColorBrush)border.Background; var brush = (ISolidColorBrush)border.Background;
Assert.Equal(0xff506070, brush.Color.ToUint32()); Assert.Equal(0xff506070, brush.Color.ToUint32());
} }
@ -140,7 +140,7 @@ namespace Avalonia.Markup.Xaml.UnitTests.MarkupExtensions
DelayedBinding.ApplyBindings(border); DelayedBinding.ApplyBindings(border);
var brush = (SolidColorBrush)border.Background; var brush = (ISolidColorBrush)border.Background;
Assert.Equal(0xff506070, brush.Color.ToUint32()); Assert.Equal(0xff506070, brush.Color.ToUint32());
} }
@ -212,7 +212,7 @@ namespace Avalonia.Markup.Xaml.UnitTests.MarkupExtensions
var window = (Window)AvaloniaRuntimeXamlLoader.Load(xaml); var window = (Window)AvaloniaRuntimeXamlLoader.Load(xaml);
var button = window.FindControl<Button>("button"); var button = window.FindControl<Button>("button");
var brush = (SolidColorBrush)button.Background; var brush = (ISolidColorBrush)button.Background;
Assert.Equal(0xff506070, brush.Color.ToUint32()); Assert.Equal(0xff506070, brush.Color.ToUint32());
} }
@ -241,7 +241,7 @@ namespace Avalonia.Markup.Xaml.UnitTests.MarkupExtensions
var window = (Window)AvaloniaRuntimeXamlLoader.Load(xaml); var window = (Window)AvaloniaRuntimeXamlLoader.Load(xaml);
var button = window.FindControl<Button>("button"); var button = window.FindControl<Button>("button");
var brush = (SolidColorBrush)button.Background; var brush = (ISolidColorBrush)button.Background;
Assert.Equal(0xff506070, brush.Color.ToUint32()); Assert.Equal(0xff506070, brush.Color.ToUint32());
} }
@ -275,7 +275,7 @@ namespace Avalonia.Markup.Xaml.UnitTests.MarkupExtensions
var window = (Window)AvaloniaRuntimeXamlLoader.Load(xaml); var window = (Window)AvaloniaRuntimeXamlLoader.Load(xaml);
var border = window.FindControl<Border>("border"); var border = window.FindControl<Border>("border");
var brush = (SolidColorBrush)border.Background; var brush = (ISolidColorBrush)border.Background;
Assert.Equal(0xff506070, brush.Color.ToUint32()); Assert.Equal(0xff506070, brush.Color.ToUint32());
} }
@ -317,7 +317,7 @@ namespace Avalonia.Markup.Xaml.UnitTests.MarkupExtensions
window.Show(); window.Show();
var border = (Border)button.GetVisualChildren().Single(); var border = (Border)button.GetVisualChildren().Single();
var brush = (SolidColorBrush)border.Background; var brush = (ISolidColorBrush)border.Background;
Assert.Equal(0xff506070, brush.Color.ToUint32()); Assert.Equal(0xff506070, brush.Color.ToUint32());
} }
@ -648,7 +648,7 @@ namespace Avalonia.Markup.Xaml.UnitTests.MarkupExtensions
DelayedBinding.ApplyBindings(border); DelayedBinding.ApplyBindings(border);
var brush = (SolidColorBrush)border.Background; var brush = (ISolidColorBrush)border.Background;
Assert.Equal(0xff506070, brush.Color.ToUint32()); Assert.Equal(0xff506070, brush.Color.ToUint32());
window.Content = null; window.Content = null;
@ -657,7 +657,7 @@ namespace Avalonia.Markup.Xaml.UnitTests.MarkupExtensions
window.Content = border; window.Content = border;
brush = (SolidColorBrush)border.Background; brush = (ISolidColorBrush)border.Background;
Assert.Equal(0xff506070, brush.Color.ToUint32()); Assert.Equal(0xff506070, brush.Color.ToUint32());
} }
} }

2
tests/Avalonia.Markup.Xaml.UnitTests/MarkupExtensions/ResourceIncludeTests.cs

@ -39,7 +39,7 @@ namespace Avalonia.Markup.Xaml.UnitTests.MarkupExtensions
var userControl = (UserControl)AvaloniaRuntimeXamlLoader.Load(xaml); var userControl = (UserControl)AvaloniaRuntimeXamlLoader.Load(xaml);
var border = userControl.FindControl<Border>("border"); var border = userControl.FindControl<Border>("border");
var brush = (SolidColorBrush)border.Background; var brush = (ISolidColorBrush)border.Background;
Assert.Equal(0xff506070, brush.Color.ToUint32()); Assert.Equal(0xff506070, brush.Color.ToUint32());
} }
} }

20
tests/Avalonia.Markup.Xaml.UnitTests/MarkupExtensions/StaticResourceExtensionTests.cs

@ -29,7 +29,7 @@ namespace Avalonia.Markup.Xaml.UnitTests.MarkupExtensions
var userControl = (UserControl)AvaloniaRuntimeXamlLoader.Load(xaml); var userControl = (UserControl)AvaloniaRuntimeXamlLoader.Load(xaml);
var border = userControl.FindControl<Border>("border"); var border = userControl.FindControl<Border>("border");
var brush = (SolidColorBrush)border.Background; var brush = (ISolidColorBrush)border.Background;
Assert.Equal(0xff506070, brush.Color.ToUint32()); Assert.Equal(0xff506070, brush.Color.ToUint32());
} }
@ -72,7 +72,7 @@ namespace Avalonia.Markup.Xaml.UnitTests.MarkupExtensions
var userControl = (UserControl)AvaloniaRuntimeXamlLoader.Load(xaml); var userControl = (UserControl)AvaloniaRuntimeXamlLoader.Load(xaml);
var border = userControl.FindControl<Border>("border"); var border = userControl.FindControl<Border>("border");
var brush = (SolidColorBrush)border.Background; var brush = (ISolidColorBrush)border.Background;
Assert.Equal(0xff506070, brush.Color.ToUint32()); Assert.Equal(0xff506070, brush.Color.ToUint32());
} }
@ -119,7 +119,7 @@ namespace Avalonia.Markup.Xaml.UnitTests.MarkupExtensions
var userControl = (UserControl)AvaloniaRuntimeXamlLoader.Load(xaml); var userControl = (UserControl)AvaloniaRuntimeXamlLoader.Load(xaml);
var border = userControl.FindControl<Border>("border"); var border = userControl.FindControl<Border>("border");
var brush = (SolidColorBrush)border.Background; var brush = (ISolidColorBrush)border.Background;
Assert.Equal(0xff506070, brush.Color.ToUint32()); Assert.Equal(0xff506070, brush.Color.ToUint32());
} }
@ -149,7 +149,7 @@ namespace Avalonia.Markup.Xaml.UnitTests.MarkupExtensions
var userControl = (UserControl)AvaloniaRuntimeXamlLoader.Load(xaml); var userControl = (UserControl)AvaloniaRuntimeXamlLoader.Load(xaml);
var border = userControl.FindControl<Border>("border"); var border = userControl.FindControl<Border>("border");
var brush = (SolidColorBrush)border.Background; var brush = (ISolidColorBrush)border.Background;
Assert.Equal(0xff506070, brush.Color.ToUint32()); Assert.Equal(0xff506070, brush.Color.ToUint32());
} }
@ -200,7 +200,7 @@ namespace Avalonia.Markup.Xaml.UnitTests.MarkupExtensions
var window = (Window)AvaloniaRuntimeXamlLoader.Load(xaml); var window = (Window)AvaloniaRuntimeXamlLoader.Load(xaml);
var button = window.FindControl<Button>("button"); var button = window.FindControl<Button>("button");
var brush = (SolidColorBrush)button.Background; var brush = (ISolidColorBrush)button.Background;
Assert.Equal(0xff506070, brush.Color.ToUint32()); Assert.Equal(0xff506070, brush.Color.ToUint32());
} }
@ -229,7 +229,7 @@ namespace Avalonia.Markup.Xaml.UnitTests.MarkupExtensions
var window = (Window)AvaloniaRuntimeXamlLoader.Load(xaml); var window = (Window)AvaloniaRuntimeXamlLoader.Load(xaml);
var button = window.FindControl<Button>("button"); var button = window.FindControl<Button>("button");
var brush = (SolidColorBrush)button.Background; var brush = (ISolidColorBrush)button.Background;
Assert.Equal(0xff506070, brush.Color.ToUint32()); Assert.Equal(0xff506070, brush.Color.ToUint32());
} }
@ -263,7 +263,7 @@ namespace Avalonia.Markup.Xaml.UnitTests.MarkupExtensions
var window = (Window)AvaloniaRuntimeXamlLoader.Load(xaml); var window = (Window)AvaloniaRuntimeXamlLoader.Load(xaml);
var border = window.FindControl<Border>("border"); var border = window.FindControl<Border>("border");
var brush = (SolidColorBrush)border.Background; var brush = (ISolidColorBrush)border.Background;
Assert.Equal(0xff506070, brush.Color.ToUint32()); Assert.Equal(0xff506070, brush.Color.ToUint32());
} }
@ -344,7 +344,7 @@ namespace Avalonia.Markup.Xaml.UnitTests.MarkupExtensions
window.Show(); window.Show();
var border = (Border)button.GetVisualChildren().Single(); var border = (Border)button.GetVisualChildren().Single();
var brush = (SolidColorBrush)border.Background; var brush = (ISolidColorBrush)border.Background;
Assert.Equal(0xff506070, brush.Color.ToUint32()); Assert.Equal(0xff506070, brush.Color.ToUint32());
} }
@ -484,12 +484,12 @@ namespace Avalonia.Markup.Xaml.UnitTests.MarkupExtensions
var userControl = (UserControl)AvaloniaRuntimeXamlLoader.Load(xaml); var userControl = (UserControl)AvaloniaRuntimeXamlLoader.Load(xaml);
var border = userControl.FindControl<Border>("border"); var border = userControl.FindControl<Border>("border");
var brush = (SolidColorBrush)border.Background; var brush = (ISolidColorBrush)border.Background;
Assert.Equal(0xff506070, brush.Color.ToUint32()); Assert.Equal(0xff506070, brush.Color.ToUint32());
userControl.Content = null; userControl.Content = null;
brush = (SolidColorBrush)border.Background; brush = (ISolidColorBrush)border.Background;
Assert.Equal(0xff506070, brush.Color.ToUint32()); Assert.Equal(0xff506070, brush.Color.ToUint32());
} }

2
tests/Avalonia.Markup.Xaml.UnitTests/Xaml/BasicTests.cs

@ -512,7 +512,7 @@ namespace Avalonia.Markup.Xaml.UnitTests.Xaml
style.TryGetResource("Brush", out var brush); style.TryGetResource("Brush", out var brush);
Assert.NotNull(brush); Assert.NotNull(brush);
Assert.IsType<SolidColorBrush>(brush); Assert.IsAssignableFrom<ISolidColorBrush>(brush);
Assert.Equal(Colors.White, ((ISolidColorBrush)brush).Color); Assert.Equal(Colors.White, ((ISolidColorBrush)brush).Color);
style.TryGetResource("Double", out var d); style.TryGetResource("Double", out var d);

2
tests/Avalonia.Markup.Xaml.UnitTests/Xaml/StyleTests.cs

@ -101,7 +101,7 @@ namespace Avalonia.Markup.Xaml.UnitTests.Xaml
</UserControl.Styles> </UserControl.Styles>
</UserControl>"; </UserControl>";
var userControl = (UserControl)AvaloniaRuntimeXamlLoader.Load(xaml); var userControl = (UserControl)AvaloniaRuntimeXamlLoader.Load(xaml);
var brush = (SolidColorBrush)((Style)userControl.Styles[0]).Resources["brush"]; var brush = (ISolidColorBrush)((Style)userControl.Styles[0]).Resources["brush"];
Assert.Equal(0xff506070, brush.Color.ToUint32()); Assert.Equal(0xff506070, brush.Color.ToUint32());
} }

Loading…
Cancel
Save