Browse Source

Merge remote-tracking branch 'origin/master' into fixes/6007-child-window-activation

# Conflicts:
#	native/Avalonia.Native/src/OSX/window.mm
#	src/Avalonia.Controls/ApiCompatBaseline.txt
pull/6188/head
Dan Walmsley 5 years ago
parent
commit
7b631e9cb6
  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. 6
      native/Avalonia.Native/src/OSX/app.mm
  6. 8
      native/Avalonia.Native/src/OSX/cgl.mm
  7. 82
      native/Avalonia.Native/src/OSX/clipboard.mm
  8. 63
      native/Avalonia.Native/src/OSX/controlhost.mm
  9. 52
      native/Avalonia.Native/src/OSX/cursor.mm
  10. 174
      native/Avalonia.Native/src/OSX/main.mm
  11. 22
      native/Avalonia.Native/src/OSX/menu.mm
  12. 2
      native/Avalonia.Native/src/OSX/platformthreading.mm
  13. 6
      native/Avalonia.Native/src/OSX/rendertarget.mm
  14. 284
      native/Avalonia.Native/src/OSX/window.mm
  15. 102
      samples/ControlCatalog/Pages/ContextFlyoutPage.axaml
  16. 45
      samples/ControlCatalog/Pages/ContextFlyoutPage.axaml.cs
  17. 157
      samples/ControlCatalog/Pages/ContextFlyoutPage.xaml
  18. 91
      samples/ControlCatalog/Pages/ContextFlyoutPage.xaml.cs
  19. 140
      samples/ControlCatalog/Pages/ContextMenuPage.xaml
  20. 42
      samples/ControlCatalog/Pages/ContextMenuPage.xaml.cs
  21. 25
      samples/ControlCatalog/Pages/DataGridPage.xaml
  22. 43
      samples/ControlCatalog/Pages/NumericUpDownPage.xaml
  23. 15
      samples/ControlCatalog/Pages/NumericUpDownPage.xaml.cs
  24. 78
      samples/ControlCatalog/ViewModels/ContextFlyoutPageViewModel.cs
  25. 4
      samples/ControlCatalog/ViewModels/ContextPageViewModel.cs
  26. 149
      samples/RenderDemo/Pages/AnimationsPage.xaml
  27. 84
      samples/RenderDemo/Pages/TransitionsPage.xaml
  28. 10
      samples/Sandbox/Program.cs
  29. 2
      src/Android/Avalonia.Android/Platform/SkiaPlatform/TopLevelImpl.cs
  30. 40
      src/Avalonia.Base/Collections/AvaloniaList.cs
  31. 32
      src/Avalonia.Base/Utilities/MathUtilities.cs
  32. 42
      src/Avalonia.Controls.DataGrid/DataGridColumn.cs
  33. 4
      src/Avalonia.Controls.DataGrid/Themes/Fluent.xaml
  34. 24
      src/Avalonia.Controls.DataGrid/Utils/ReflectionHelper.cs
  35. 31
      src/Avalonia.Controls/ApiCompatBaseline.txt
  36. 29
      src/Avalonia.Controls/ApplicationLifetimes/ClassicDesktopStyleApplicationLifetime.cs
  37. 12
      src/Avalonia.Controls/ApplicationLifetimes/IClassicDesktopStyleApplicationLifetime.cs
  38. 72
      src/Avalonia.Controls/ComboBox.cs
  39. 65
      src/Avalonia.Controls/ContextMenu.cs
  40. 58
      src/Avalonia.Controls/ContextRequestedEventArgs.cs
  41. 62
      src/Avalonia.Controls/Control.cs
  42. 2
      src/Avalonia.Controls/Embedding/Offscreen/OffscreenTopLevelImpl.cs
  43. 167
      src/Avalonia.Controls/Flyouts/FlyoutBase.cs
  44. 2
      src/Avalonia.Controls/Flyouts/FlyoutShowMode.cs
  45. 10
      src/Avalonia.Controls/Flyouts/MenuFlyoutPresenter.cs
  46. 6
      src/Avalonia.Controls/GridLength.cs
  47. 1
      src/Avalonia.Controls/ItemsControl.cs
  48. 148
      src/Avalonia.Controls/NumericUpDown/NumericUpDown.cs
  49. 6
      src/Avalonia.Controls/NumericUpDown/NumericUpDownValueChangedEventArgs.cs
  50. 16
      src/Avalonia.Controls/Platform/IPlatformLifetimeEventsImpl.cs
  51. 5
      src/Avalonia.Controls/Platform/ITopLevelImpl.cs
  52. 67
      src/Avalonia.Controls/Primitives/AdornerLayer.cs
  53. 10
      src/Avalonia.Controls/Primitives/Popup.cs
  54. 74
      src/Avalonia.Controls/Primitives/SelectingItemsControl.cs
  55. 20
      src/Avalonia.Controls/Primitives/Thumb.cs
  56. 8
      src/Avalonia.Controls/RowDefinitions.cs
  57. 103
      src/Avalonia.Controls/Shapes/Arc.cs
  58. 23
      src/Avalonia.Controls/TextBox.cs
  59. 18
      src/Avalonia.Controls/TopLevel.cs
  60. 6
      src/Avalonia.Controls/TreeView.cs
  61. 39
      src/Avalonia.Controls/Window.cs
  62. 1
      src/Avalonia.Controls/WindowBase.cs
  63. 1
      src/Avalonia.DesignerSupport/Remote/Stubs.cs
  64. 1
      src/Avalonia.Headless/HeadlessWindowImpl.cs
  65. 15
      src/Avalonia.Input/Platform/PlatformHotkeyConfiguration.cs
  66. 5
      src/Avalonia.Layout/ElementManager.cs
  67. 13
      src/Avalonia.Native/AvaloniaNativeApplicationPlatform.cs
  68. 3
      src/Avalonia.Native/AvaloniaNativePlatform.cs
  69. 2
      src/Avalonia.Native/WindowImpl.cs
  70. 14
      src/Avalonia.Native/WindowImplBase.cs
  71. 2
      src/Avalonia.Native/avn.idl
  72. 2
      src/Avalonia.Themes.Default/TextBox.xaml
  73. 4
      src/Avalonia.Themes.Fluent/Controls/TextBox.xaml
  74. 138
      src/Avalonia.Visuals/Animation/Animators/BaseBrushAnimator.cs
  75. 123
      src/Avalonia.Visuals/Animation/Animators/GradientBrushAnimator.cs
  76. 20
      src/Avalonia.Visuals/Animation/Animators/RelativePointAnimator.cs
  77. 20
      src/Avalonia.Visuals/Animation/Animators/SolidColorBrushAnimator.cs
  78. 44
      src/Avalonia.Visuals/Animation/Transitions/BrushTransition.cs
  79. 11
      src/Avalonia.Visuals/Animation/Transitions/RelativePointTransition.cs
  80. 3
      src/Avalonia.Visuals/ApiCompatBaseline.txt
  81. 41
      src/Avalonia.Visuals/Media/BoxShadow.cs
  82. 20
      src/Avalonia.Visuals/Media/BoxShadows.cs
  83. 3
      src/Avalonia.Visuals/Media/Brush.cs
  84. 20
      src/Avalonia.Visuals/Media/DrawingGroup.cs
  85. 2
      src/Avalonia.Visuals/Media/GlyphRun.cs
  86. 2
      src/Avalonia.Visuals/Media/GradientBrush.cs
  87. 11
      src/Avalonia.Visuals/Media/Immutable/ImmutableSolidColorBrush.cs
  88. 1
      src/Avalonia.Visuals/Media/SolidColorBrush.cs
  89. 87
      src/Avalonia.Visuals/Media/TextFormatting/ShapedTextCharacters.cs
  90. 9
      src/Avalonia.Visuals/Media/TextFormatting/TextCharacters.cs
  91. 86
      src/Avalonia.Visuals/Media/TextFormatting/TextFormatterImpl.cs
  92. 7
      src/Avalonia.Visuals/RelativePoint.cs
  93. 19
      src/Avalonia.Visuals/Rendering/SceneGraph/SceneBuilder.cs
  94. 31
      src/Avalonia.X11/X11Window.cs
  95. 1
      src/Linux/Avalonia.LinuxFramebuffer/FramebufferToplevelImpl.cs
  96. 14
      src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/AvaloniaXamlIlLanguageParseIntrinsics.cs
  97. 28
      src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlBindingPathParser.cs
  98. 9
      src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlWellKnownTypes.cs
  99. 1
      src/Windows/Avalonia.Win32.Interop/Wpf/WpfTopLevelImpl.cs
  100. 23
      src/Windows/Avalonia.Win32/Interop/UnmanagedMethods.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)

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

@ -50,6 +50,12 @@ ComPtr<IAvnApplicationEvents> _events;
_events->FilesOpened(array); _events->FilesOpened(array);
} }
- (NSApplicationTerminateReply)applicationShouldTerminate:(NSApplication *)sender
{
return _events->TryShutdown() ? NSTerminateNow : NSTerminateCancel;
}
@end @end
@interface AvnApplication : NSApplication @interface AvnApplication : NSApplication

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)

284
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;
@ -50,10 +52,13 @@ public:
[Window setBackingType:NSBackingStoreBuffered]; [Window setBackingType:NSBackingStoreBuffered];
[Window setOpaque:false]; [Window setOpaque:false];
[Window setContentView: StandardContainer];
} }
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 +71,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 +85,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 +99,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 +118,26 @@ public:
virtual HRESULT Show(bool activate, bool isDialog) override virtual HRESULT Show(bool activate, bool isDialog) override
{ {
START_COM_CALL;
@autoreleasepool @autoreleasepool
{ {
SetPosition(lastPositionSet); SetPosition(lastPositionSet);
UpdateStyle(); UpdateStyle();
[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,20 +313,28 @@ public:
y = maxSize.height; y = maxSize.height;
} }
if(!_shown) @try
{
if(!_shown)
{
BaseEvents->Resized(AvnSize{x,y});
}
[Window setContentSize:NSSize{x, y}];
}
@finally
{ {
BaseEvents->Resized(AvnSize{x,y}); _inResize = false;
} }
[StandardContainer setFrameSize:NSSize{x,y}];
[Window setContentSize:NSSize{x, y}];
return S_OK; return S_OK;
} }
} }
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)
@ -555,6 +644,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];
@ -581,6 +675,8 @@ private:
virtual HRESULT Show (bool activate, bool isDialog) override virtual HRESULT Show (bool activate, bool isDialog) override
{ {
START_COM_CALL;
@autoreleasepool @autoreleasepool
{ {
_isDialog = isDialog; _isDialog = isDialog;
@ -594,6 +690,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];
@ -603,6 +701,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)
@ -679,6 +779,7 @@ private:
} }
_lastWindowState = state; _lastWindowState = state;
_actualWindowState = state;
WindowEvents->WindowStateChanged(state); WindowEvents->WindowStateChanged(state);
} }
} }
@ -714,6 +815,8 @@ private:
virtual HRESULT SetCanResize(bool value) override virtual HRESULT SetCanResize(bool value) override
{ {
START_COM_CALL;
@autoreleasepool @autoreleasepool
{ {
_canResize = value; _canResize = value;
@ -724,6 +827,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;
@ -789,6 +894,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];
@ -800,6 +907,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;
@ -829,6 +938,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)
@ -862,86 +973,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]; if(enable)
auto wantsTitleBar = (_extendClientHints & AvnSystemChrome) || (_extendClientHints & AvnPreferSystemChrome);
if (wantsTitleBar)
{
[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 ()
@ -970,8 +1106,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;
@ -1161,6 +1304,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];
@ -1196,9 +1342,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
@ -1214,6 +1357,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;
} }
@ -1904,18 +2048,7 @@ NSArray* AllLoopModes = [NSArray arrayWithObjects: NSDefaultRunLoopMode, NSEvent
+(void)closeAll +(void)closeAll
{ {
NSArray<NSWindow*>* windows = [NSArray arrayWithArray:[NSApp windows]]; [[NSApplication sharedApplication] terminate:self];
auto numWindows = [windows count];
for(int i = 0; i < numWindows; i++)
{
auto window = (AvnWindow*)[windows objectAtIndex:i];
if([window parentWindow] == nullptr) // Avalonia will handle the child windows.
{
[window performClose:nil];
}
}
} }
- (void)performClose:(id)sender - (void)performClose:(id)sender
@ -1928,7 +2061,7 @@ NSArray* AllLoopModes = [NSArray arrayWithObjects: NSDefaultRunLoopMode, NSEvent
{ {
if(![self windowShouldClose:self]) return; if(![self windowShouldClose:self]) return;
} }
[self close]; [self close];
} }
@ -2251,11 +2384,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))];

102
samples/ControlCatalog/Pages/ContextFlyoutPage.axaml

@ -1,102 +0,0 @@
<UserControl xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
x:Class="ControlCatalog.Pages.ContextFlyoutPage">
<UserControl.Styles>
<Style Selector="FlyoutPresenter.NoPadding">
<Setter Property="Padding" Value="0" />
</Style>
</UserControl.Styles>
<StackPanel Orientation="Vertical" Spacing="4">
<TextBlock Classes="h1">Context Flyout</TextBlock>
<TextBlock Classes="h2">A right click Flyout that can be applied to any control.</TextBlock>
<StackPanel Orientation="Horizontal"
Margin="0,16,0,0"
HorizontalAlignment="Center"
Spacing="16">
<Border Background="{DynamicResource SystemAccentColor}"
Margin="16"
Padding="48,48,48,48">
<Border.ContextFlyout>
<MenuFlyout>
<MenuItem Header="Standard _Menu Item" InputGesture="Ctrl+A" />
<MenuItem Header="_Disabled Menu Item" IsEnabled="False" InputGesture="Ctrl+D" />
<Separator/>
<MenuItem Header="Menu with _Submenu">
<MenuItem Header="Submenu _1"/>
<MenuItem Header="Submenu _2"/>
</MenuItem>
<MenuItem Header="Menu Item with _Icon" InputGesture="Ctrl+Shift+B">
<MenuItem.Icon>
<Image Source="/Assets/github_icon.png"/>
</MenuItem.Icon>
</MenuItem>
<MenuItem Header="Menu Item with _Checkbox">
<MenuItem.Icon>
<CheckBox BorderThickness="0" IsHitTestVisible="False" IsChecked="True"/>
</MenuItem.Icon>
</MenuItem>
</MenuFlyout>
</Border.ContextFlyout>
<TextBlock Text="Defined in XAML"/>
</Border>
<Border Background="{DynamicResource SystemAccentColor}"
Margin="16"
Padding="48,48,48,48">
<Border.ContextMenu>
<ContextMenu Items="{Binding MenuItems}">
<ContextMenu.Styles>
<Style Selector="MenuItem">
<Setter Property="Header" Value="{Binding Header}"/>
<Setter Property="Items" Value="{Binding Items}"/>
<Setter Property="Command" Value="{Binding Command}"/>
<Setter Property="CommandParameter" Value="{Binding CommandParameter}"/>
</Style>
</ContextMenu.Styles>
</ContextMenu>
</Border.ContextMenu>
<TextBlock Text="Dynamically Generated"/>
</Border>
</StackPanel>
<TextBlock Text="Custom ContextFlyout for TextBox" />
<TextBox Name="TextBox" Width="150" HorizontalAlignment="Center" ContextMenu="{x:Null}">
<TextBox.ContextFlyout>
<Flyout FlyoutPresenterClasses="NoPadding">
<StackPanel Orientation="Horizontal">
<StackPanel.Styles>
<Style Selector="Button">
<Setter Property="Background" Value="Transparent" />
<Setter Property="Height" Value="40" />
<Setter Property="Width" Value="40" />
<Setter Property="VerticalContentAlignment" Value="Center" />
</Style>
<Style Selector="Button:disabled /template/ ContentPresenter#PART_ContentPresenter">
<Setter Property="Background" Value="Transparent" />
<Setter Property="Opacity" Value="0.5" />
</Style>
</StackPanel.Styles>
<Button Name="CutButton" Command="{Binding $parent[TextBox].Cut}" IsEnabled="{Binding $parent[TextBox].CanCut}">
<PathIcon Width="14" Height="14" Data="M5.22774,2.08072 C5.43359778,1.94704 5.7011484,1.98419259 5.86368634,2.15675215 L5.91939,2.22774 L12.5191,12.3904 C12.956,12.1419 13.4614,12.0000019 14,12.0000019 C15.6569,12.0000019 17,13.3431 17,15.0000019 C17,16.6569 15.6569,18.0000019 14,18.0000019 C12.3431,18.0000019 11,16.6569 11,15.0000019 C11,14.3201402 11.226152,13.693011 11.6073785,13.1899092 L11.7401,13.0269 L10,10.3474 L8.25991,13.0269 C8.72078,13.5543 9,14.2446 9,15.0000019 C9,16.6569 7.65685,18.0000019 6,18.0000019 C4.34315,18.0000019 3,16.6569 3,15.0000019 C3,13.3431 4.34315,12.0000019 6,12.0000019 C6.46163143,12.0000019 6.89890041,12.1042536 7.28955831,12.2905296 L7.4809,12.3904 L9.40382,9.42936 L5.08072,2.77238 C4.93033,2.54079 4.99615,2.23112 5.22774,2.08072 Z M14,13 C12.8954,13 12,13.8954 12,15 C12,16.1046 12.8954,17 14,17 C15.1046,17 16,16.1046 16,15 C16,13.8954 15.1046,13 14,13 Z M6,13 C4.89543,13 4,13.8954 4,15 C4,16.1046 4.89543,17 6,17 C7.10457,17 8,16.1046 8,15 C8,13.8954 7.10457,13 6,13 Z M14.7723,2.08072 C15.0039,2.23112 15.0697,2.54079 14.9193,2.77238 L11.1924,8.51133 L10.5962,7.59329 L14.0806,2.22774 C14.231,1.99615 14.5407,1.93033 14.7723,2.08072 Z" />
</Button>
<Button Name="CopyButton" Command="{Binding $parent[TextBox].Copy}" IsEnabled="{Binding $parent[TextBox].CanCopy}">
<PathIcon Width="14" Height="14" Data="M5.50280381,4.62704038 L5.5,6.75 L5.5,17.2542087 C5.5,19.0491342 6.95507456,20.5042087 8.75,20.5042087 L17.3662868,20.5044622 C17.057338,21.3782241 16.2239751,22.0042087 15.2444057,22.0042087 L8.75,22.0042087 C6.12664744,22.0042087 4,19.8775613 4,17.2542087 L4,6.75 C4,5.76928848 4.62744523,4.93512464 5.50280381,4.62704038 Z M17.75,2 C18.9926407,2 20,3.00735931 20,4.25 L20,17.25 C20,18.4926407 18.9926407,19.5 17.75,19.5 L8.75,19.5 C7.50735931,19.5 6.5,18.4926407 6.5,17.25 L6.5,4.25 C6.5,3.00735931 7.50735931,2 8.75,2 L17.75,2 Z M17.75,3.5 L8.75,3.5 C8.33578644,3.5 8,3.83578644 8,4.25 L8,17.25 C8,17.6642136 8.33578644,18 8.75,18 L17.75,18 C18.1642136,18 18.5,17.6642136 18.5,17.25 L18.5,4.25 C18.5,3.83578644 18.1642136,3.5 17.75,3.5 Z" />
</Button>
<Button Name="PasteButton" Command="{Binding $parent[TextBox].Paste}" IsEnabled="{Binding $parent[TextBox].CanPaste}">
<PathIcon Width="14" Height="14" Data="M13.75,2 C14.940864,2 15.9156449,2.92516159 15.9948092,4.09595119 L16,4.25 L16,4.25 C16,4.16530567 15.9953205,4.0817043 15.9862059,3.99944035 L17.75,4 C18.9926407,4 20,5.00735931 20,6.25 L20,19.75 C20,20.9926407 18.9926407,22 17.75,22 L6.25,22 C5.00735931,22 4,20.9926407 4,19.75 L4,6.25 C4,5.00735931 5.00735931,4 6.25,4 L8.01379413,3.99944035 C8.00733496,4.05773764 8.00310309,4.11670658 8.00118552,4.17626017 L8,4.25 C8,3.00735931 9.00735931,2 10.25,2 L13.75,2 Z M13.75,6.5 L10.25,6.5 C9.45594921,6.5 8.75796956,6.08867052 8.357512,5.4674625 L8.37902077,5.50019943 L8.37902077,5.50019943 L6.25,5.5 C5.83578644,5.5 5.5,5.83578644 5.5,6.25 L5.5,19.75 C5.5,20.1642136 5.83578644,20.5 6.25,20.5 L17.75,20.5 C18.1642136,20.5 18.5,20.1642136 18.5,19.75 L18.5,6.25 C18.5,5.83578644 18.1642136,5.5 17.75,5.5 L15.6209792,5.50019943 L15.642488,5.4674625 C15.2420304,6.08867052 14.5440508,6.5 13.75,6.5 Z M13.75,3.5 L10.25,3.5 C9.83578644,3.5 9.5,3.83578644 9.5,4.25 C9.5,4.66421356 9.83578644,5 10.25,5 L13.75,5 C14.1642136,5 14.5,4.66421356 14.5,4.25 C14.5,3.83578644 14.1642136,3.5 13.75,3.5 Z" />
</Button>
<Button Name="ClearButton" Command="{Binding $parent[TextBox].Clear}">
<PathIcon Width="14" Height="14" Data="M3.52499419,3.71761187 L3.61611652,3.61611652 C4.0717282,3.16050485 4.79154862,3.13013074 5.28238813,3.52499419 L5.38388348,3.61611652 L14,12.233 L22.6161165,3.61611652 C23.1042719,3.12796116 23.8957281,3.12796116 24.3838835,3.61611652 C24.8720388,4.10427189 24.8720388,4.89572811 24.3838835,5.38388348 L15.767,14 L24.3838835,22.6161165 C24.8394952,23.0717282 24.8698693,23.7915486 24.4750058,24.2823881 L24.3838835,24.3838835 C23.9282718,24.8394952 23.2084514,24.8698693 22.7176119,24.4750058 L22.6161165,24.3838835 L14,15.767 L5.38388348,24.3838835 C4.89572811,24.8720388 4.10427189,24.8720388 3.61611652,24.3838835 C3.12796116,23.8957281 3.12796116,23.1042719 3.61611652,22.6161165 L12.233,14 L3.61611652,5.38388348 C3.16050485,4.9282718 3.13013074,4.20845138 3.52499419,3.71761187 L3.61611652,3.61611652 L3.52499419,3.71761187 Z" />
</Button>
</StackPanel>
</Flyout>
</TextBox.ContextFlyout>
</TextBox>
</StackPanel>
</UserControl>

45
samples/ControlCatalog/Pages/ContextFlyoutPage.axaml.cs

@ -1,45 +0,0 @@
using Avalonia;
using Avalonia.Controls;
using Avalonia.Markup.Xaml;
using ControlCatalog.ViewModels;
using Avalonia.Interactivity;
namespace ControlCatalog.Pages
{
public class ContextFlyoutPage : UserControl
{
private TextBox _textBox;
public ContextFlyoutPage()
{
InitializeComponent();
var vm = new ContextFlyoutPageViewModel();
vm.View = this;
DataContext = vm;
_textBox = this.FindControl<TextBox>("TextBox");
var cutButton = this.FindControl<Button>("CutButton");
cutButton.Click += CloseFlyout;
var copyButton = this.FindControl<Button>("CopyButton");
copyButton.Click += CloseFlyout;
var pasteButton = this.FindControl<Button>("PasteButton");
pasteButton.Click += CloseFlyout;
var clearButton = this.FindControl<Button>("ClearButton");
clearButton.Click += CloseFlyout;
}
private void CloseFlyout(object sender, RoutedEventArgs e)
{
_textBox.ContextFlyout.Hide();
}
private void InitializeComponent()
{
AvaloniaXamlLoader.Load(this);
}
}
}

157
samples/ControlCatalog/Pages/ContextFlyoutPage.xaml

@ -0,0 +1,157 @@
<UserControl x:Class="ControlCatalog.Pages.ContextFlyoutPage"
xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
d:DesignHeight="450"
d:DesignWidth="800"
mc:Ignorable="d">
<UserControl.Styles>
<Style Selector="FlyoutPresenter.NoPadding">
<Setter Property="Padding" Value="0" />
</Style>
</UserControl.Styles>
<StackPanel Orientation="Vertical" Spacing="4">
<StackPanel.Styles>
<Style Selector="Border.context-target">
<Setter Property="Padding" Value="48,20" />
<Setter Property="Margin" Value="8" />
<Setter Property="Focusable" Value="True" />
<Setter Property="Background" Value="{DynamicResource SystemAccentColor}" />
</Style>
<Style Selector="Border.context-target > :is(Control)">
<Setter Property="VerticalAlignment" Value="Center" />
</Style>
</StackPanel.Styles>
<TextBlock Classes="h1">Context Flyout</TextBlock>
<TextBlock Classes="h2">A right click Flyout that can be applied to any control.</TextBlock>
<UniformGrid HorizontalAlignment="Center" Rows="2">
<Border Classes="context-target">
<Border.ContextFlyout>
<MenuFlyout>
<MenuItem Header="Standard _Menu Item" InputGesture="Ctrl+A" />
<MenuItem Header="_Disabled Menu Item"
InputGesture="Ctrl+D"
IsEnabled="False" />
<Separator />
<MenuItem Header="Menu with _Submenu">
<MenuItem Header="Submenu _1" />
<MenuItem Header="Submenu _2" />
</MenuItem>
<MenuItem Header="Menu Item with _Icon" InputGesture="Ctrl+Shift+B">
<MenuItem.Icon>
<Image Source="/Assets/github_icon.png" />
</MenuItem.Icon>
</MenuItem>
<MenuItem Header="Menu Item with _Checkbox">
<MenuItem.Icon>
<CheckBox BorderThickness="0"
IsChecked="True"
IsHitTestVisible="False" />
</MenuItem.Icon>
</MenuItem>
</MenuFlyout>
</Border.ContextFlyout>
<TextBlock Text="Defined in XAML" />
</Border>
<Border Classes="context-target">
<Border.Styles>
<Style Selector="MenuFlyoutPresenter MenuItem">
<Setter Property="Header" Value="{Binding Header}"/>
<Setter Property="Items" Value="{Binding Items}"/>
<Setter Property="Command" Value="{Binding Command}"/>
<Setter Property="CommandParameter" Value="{Binding CommandParameter}"/>
</Style>
</Border.Styles>
<Border.ContextFlyout>
<MenuFlyout Items="{Binding MenuItems}" />
</Border.ContextFlyout>
<TextBlock Text="Dynamically Generated"/>
</Border>
<Border x:Name="CustomContextRequestedBorder"
Classes="context-target">
<Border.ContextFlyout>
<Flyout Content="Should never be visible" />
</Border.ContextFlyout>
<TextBlock Text="Custom ContextRequested handler" TextWrapping="Wrap" />
</Border>
<Border x:Name="CancellableContextBorder"
Classes="context-target">
<Border.ContextFlyout>
<Flyout>
<CheckBox x:Name="CancelCloseCheckBox" Content="Cancel close" />
</Flyout>
</Border.ContextFlyout>
<StackPanel>
<TextBlock Text="Cancellable" />
<CheckBox x:Name="CancelOpenCheckBox" Content="Cancel open" />
</StackPanel>
</Border>
</UniformGrid>
<TextBlock Text="Custom ContextFlyout for TextBox" />
<TextBox Name="TextBox"
Width="150"
HorizontalAlignment="Center"
ContextMenu="{x:Null}">
<TextBox.ContextFlyout>
<Flyout FlyoutPresenterClasses="NoPadding">
<StackPanel>
<StackPanel Orientation="Horizontal">
<StackPanel.Styles>
<Style Selector="Button">
<Setter Property="Background" Value="Transparent" />
<Setter Property="Height" Value="40" />
<Setter Property="Width" Value="40" />
<Setter Property="VerticalContentAlignment" Value="Center" />
</Style>
<Style Selector="Button:disabled /template/ ContentPresenter#PART_ContentPresenter">
<Setter Property="Background" Value="Transparent" />
<Setter Property="Opacity" Value="0.5" />
</Style>
</StackPanel.Styles>
<Button Name="CutButton"
Command="{Binding $parent[TextBox].Cut}"
IsEnabled="{Binding $parent[TextBox].CanCut}">
<PathIcon Width="14"
Height="14"
Data="M5.22774,2.08072 C5.43359778,1.94704 5.7011484,1.98419259 5.86368634,2.15675215 L5.91939,2.22774 L12.5191,12.3904 C12.956,12.1419 13.4614,12.0000019 14,12.0000019 C15.6569,12.0000019 17,13.3431 17,15.0000019 C17,16.6569 15.6569,18.0000019 14,18.0000019 C12.3431,18.0000019 11,16.6569 11,15.0000019 C11,14.3201402 11.226152,13.693011 11.6073785,13.1899092 L11.7401,13.0269 L10,10.3474 L8.25991,13.0269 C8.72078,13.5543 9,14.2446 9,15.0000019 C9,16.6569 7.65685,18.0000019 6,18.0000019 C4.34315,18.0000019 3,16.6569 3,15.0000019 C3,13.3431 4.34315,12.0000019 6,12.0000019 C6.46163143,12.0000019 6.89890041,12.1042536 7.28955831,12.2905296 L7.4809,12.3904 L9.40382,9.42936 L5.08072,2.77238 C4.93033,2.54079 4.99615,2.23112 5.22774,2.08072 Z M14,13 C12.8954,13 12,13.8954 12,15 C12,16.1046 12.8954,17 14,17 C15.1046,17 16,16.1046 16,15 C16,13.8954 15.1046,13 14,13 Z M6,13 C4.89543,13 4,13.8954 4,15 C4,16.1046 4.89543,17 6,17 C7.10457,17 8,16.1046 8,15 C8,13.8954 7.10457,13 6,13 Z M14.7723,2.08072 C15.0039,2.23112 15.0697,2.54079 14.9193,2.77238 L11.1924,8.51133 L10.5962,7.59329 L14.0806,2.22774 C14.231,1.99615 14.5407,1.93033 14.7723,2.08072 Z" />
</Button>
<Button Name="CopyButton"
Command="{Binding $parent[TextBox].Copy}"
IsEnabled="{Binding $parent[TextBox].CanCopy}">
<PathIcon Width="14"
Height="14"
Data="M5.50280381,4.62704038 L5.5,6.75 L5.5,17.2542087 C5.5,19.0491342 6.95507456,20.5042087 8.75,20.5042087 L17.3662868,20.5044622 C17.057338,21.3782241 16.2239751,22.0042087 15.2444057,22.0042087 L8.75,22.0042087 C6.12664744,22.0042087 4,19.8775613 4,17.2542087 L4,6.75 C4,5.76928848 4.62744523,4.93512464 5.50280381,4.62704038 Z M17.75,2 C18.9926407,2 20,3.00735931 20,4.25 L20,17.25 C20,18.4926407 18.9926407,19.5 17.75,19.5 L8.75,19.5 C7.50735931,19.5 6.5,18.4926407 6.5,17.25 L6.5,4.25 C6.5,3.00735931 7.50735931,2 8.75,2 L17.75,2 Z M17.75,3.5 L8.75,3.5 C8.33578644,3.5 8,3.83578644 8,4.25 L8,17.25 C8,17.6642136 8.33578644,18 8.75,18 L17.75,18 C18.1642136,18 18.5,17.6642136 18.5,17.25 L18.5,4.25 C18.5,3.83578644 18.1642136,3.5 17.75,3.5 Z" />
</Button>
<Button Name="PasteButton"
Command="{Binding $parent[TextBox].Paste}"
IsEnabled="{Binding $parent[TextBox].CanPaste}">
<PathIcon Width="14"
Height="14"
Data="M13.75,2 C14.940864,2 15.9156449,2.92516159 15.9948092,4.09595119 L16,4.25 L16,4.25 C16,4.16530567 15.9953205,4.0817043 15.9862059,3.99944035 L17.75,4 C18.9926407,4 20,5.00735931 20,6.25 L20,19.75 C20,20.9926407 18.9926407,22 17.75,22 L6.25,22 C5.00735931,22 4,20.9926407 4,19.75 L4,6.25 C4,5.00735931 5.00735931,4 6.25,4 L8.01379413,3.99944035 C8.00733496,4.05773764 8.00310309,4.11670658 8.00118552,4.17626017 L8,4.25 C8,3.00735931 9.00735931,2 10.25,2 L13.75,2 Z M13.75,6.5 L10.25,6.5 C9.45594921,6.5 8.75796956,6.08867052 8.357512,5.4674625 L8.37902077,5.50019943 L8.37902077,5.50019943 L6.25,5.5 C5.83578644,5.5 5.5,5.83578644 5.5,6.25 L5.5,19.75 C5.5,20.1642136 5.83578644,20.5 6.25,20.5 L17.75,20.5 C18.1642136,20.5 18.5,20.1642136 18.5,19.75 L18.5,6.25 C18.5,5.83578644 18.1642136,5.5 17.75,5.5 L15.6209792,5.50019943 L15.642488,5.4674625 C15.2420304,6.08867052 14.5440508,6.5 13.75,6.5 Z M13.75,3.5 L10.25,3.5 C9.83578644,3.5 9.5,3.83578644 9.5,4.25 C9.5,4.66421356 9.83578644,5 10.25,5 L13.75,5 C14.1642136,5 14.5,4.66421356 14.5,4.25 C14.5,3.83578644 14.1642136,3.5 13.75,3.5 Z" />
</Button>
<Button Name="ClearButton" Command="{Binding $parent[TextBox].Clear}">
<PathIcon Width="14"
Height="14"
Data="M3.52499419,3.71761187 L3.61611652,3.61611652 C4.0717282,3.16050485 4.79154862,3.13013074 5.28238813,3.52499419 L5.38388348,3.61611652 L14,12.233 L22.6161165,3.61611652 C23.1042719,3.12796116 23.8957281,3.12796116 24.3838835,3.61611652 C24.8720388,4.10427189 24.8720388,4.89572811 24.3838835,5.38388348 L15.767,14 L24.3838835,22.6161165 C24.8394952,23.0717282 24.8698693,23.7915486 24.4750058,24.2823881 L24.3838835,24.3838835 C23.9282718,24.8394952 23.2084514,24.8698693 22.7176119,24.4750058 L22.6161165,24.3838835 L14,15.767 L5.38388348,24.3838835 C4.89572811,24.8720388 4.10427189,24.8720388 3.61611652,24.3838835 C3.12796116,23.8957281 3.12796116,23.1042719 3.61611652,22.6161165 L12.233,14 L3.61611652,5.38388348 C3.16050485,4.9282718 3.13013074,4.20845138 3.52499419,3.71761187 L3.61611652,3.61611652 L3.52499419,3.71761187 Z" />
</Button>
</StackPanel>
<Border Classes="context-target"
Padding="4, 20">
<Border.ContextFlyout>
<Flyout>
<TextBlock>Hello world</TextBlock>
</Flyout>
</Border.ContextFlyout>
<TextBlock>Inner context flyout</TextBlock>
</Border>
</StackPanel>
</Flyout>
</TextBox.ContextFlyout>
</TextBox>
</StackPanel>
</UserControl>

91
samples/ControlCatalog/Pages/ContextFlyoutPage.xaml.cs

@ -0,0 +1,91 @@
using Avalonia;
using Avalonia.Controls;
using Avalonia.Markup.Xaml;
using ControlCatalog.ViewModels;
using Avalonia.Interactivity;
using System;
using System.ComponentModel;
namespace ControlCatalog.Pages
{
public class ContextFlyoutPage : UserControl
{
private TextBox _textBox;
public ContextFlyoutPage()
{
InitializeComponent();
DataContext = new ContextPageViewModel();
_textBox = this.FindControl<TextBox>("TextBox");
var cutButton = this.FindControl<Button>("CutButton");
cutButton.Click += CloseFlyout;
var copyButton = this.FindControl<Button>("CopyButton");
copyButton.Click += CloseFlyout;
var pasteButton = this.FindControl<Button>("PasteButton");
pasteButton.Click += CloseFlyout;
var clearButton = this.FindControl<Button>("ClearButton");
clearButton.Click += CloseFlyout;
var customContextRequestedBorder = this.FindControl<Border>("CustomContextRequestedBorder");
customContextRequestedBorder.AddHandler(ContextRequestedEvent, CustomContextRequested, RoutingStrategies.Tunnel);
var cancellableContextBorder = this.FindControl<Border>("CancellableContextBorder");
cancellableContextBorder.ContextFlyout!.Closing += ContextFlyoutPage_Closing;
cancellableContextBorder.ContextFlyout!.Opening += ContextFlyoutPage_Opening;
}
private ContextPageViewModel _model;
protected override void OnDataContextChanged(EventArgs e)
{
if (_model != null)
_model.View = null;
_model = DataContext as ContextPageViewModel;
if (_model != null)
_model.View = this;
base.OnDataContextChanged(e);
}
private void ContextFlyoutPage_Closing(object sender, CancelEventArgs e)
{
var cancelCloseCheckBox = this.FindControl<CheckBox>("CancelCloseCheckBox");
e.Cancel = cancelCloseCheckBox.IsChecked ?? false;
}
private void ContextFlyoutPage_Opening(object sender, EventArgs e)
{
if (e is CancelEventArgs cancelArgs)
{
var cancelCloseCheckBox = this.FindControl<CheckBox>("CancelOpenCheckBox");
cancelArgs.Cancel = cancelCloseCheckBox.IsChecked ?? false;
}
}
private void CloseFlyout(object sender, RoutedEventArgs e)
{
_textBox.ContextFlyout.Hide();
}
public void CustomContextRequested(object sender, ContextRequestedEventArgs e)
{
var border = (Border)sender;
var textBlock = (TextBlock)border.Child;
textBlock.Text = e.TryGetPosition(border, out var point)
? $"Context was requested with pointer at: {point.X:N0}, {point.Y:N0}"
: "Context was requested without pointer";
e.Handled = true;
}
private void InitializeComponent()
{
AvaloniaXamlLoader.Load(this);
}
}
}

140
samples/ControlCatalog/Pages/ContextMenuPage.xaml

@ -1,58 +1,88 @@
<UserControl xmlns="https://github.com/avaloniaui" <UserControl x:Class="ControlCatalog.Pages.ContextMenuPage"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns="https://github.com/avaloniaui"
x:Class="ControlCatalog.Pages.ContextMenuPage"> xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<StackPanel Orientation="Vertical" Spacing="4"> <StackPanel Orientation="Vertical" Spacing="4">
<TextBlock Classes="h1">Context Menu</TextBlock> <TextBlock Classes="h1">Context Menu</TextBlock>
<TextBlock Classes="h2">A right click menu that can be applied to any control.</TextBlock> <TextBlock Classes="h2">A right click menu that can be applied to any control.</TextBlock>
<StackPanel Orientation="Horizontal" <UniformGrid HorizontalAlignment="Center" Rows="2">
Margin="0,16,0,0" <UniformGrid.Styles>
HorizontalAlignment="Center" <Style Selector="UniformGrid > Border">
Spacing="16"> <Setter Property="Padding" Value="48,20" />
<Border Background="{DynamicResource SystemAccentColor}" <Setter Property="Margin" Value="8" />
Margin="16" <Setter Property="Focusable" Value="True" />
Padding="48,48,48,48"> <Setter Property="Background" Value="{DynamicResource SystemAccentColor}" />
<Border.ContextMenu> </Style>
<ContextMenu> <Style Selector="UniformGrid > Border > :is(Control)">
<MenuItem Header="Standard _Menu Item" InputGesture="Ctrl+A" /> <Setter Property="VerticalAlignment" Value="Center" />
<MenuItem Header="_Disabled Menu Item" IsEnabled="False" InputGesture="Ctrl+D" /> </Style>
<Separator/> </UniformGrid.Styles>
<MenuItem Header="Menu with _Submenu"> <Border>
<MenuItem Header="Submenu _1"/> <Border.ContextMenu>
<MenuItem Header="Submenu _2"/> <ContextMenu>
</MenuItem> <MenuItem Header="Standard _Menu Item" InputGesture="Ctrl+A" />
<MenuItem Header="Menu Item with _Icon" InputGesture="Ctrl+Shift+B"> <MenuItem Header="_Disabled Menu Item"
<MenuItem.Icon> InputGesture="Ctrl+D"
<Image Source="/Assets/github_icon.png"/> IsEnabled="False" />
</MenuItem.Icon> <Separator />
</MenuItem> <MenuItem Header="Menu with _Submenu">
<MenuItem Header="Menu Item with _Checkbox"> <MenuItem Header="Submenu _1" />
<MenuItem.Icon> <MenuItem Header="Submenu _2" />
<CheckBox BorderThickness="0" IsHitTestVisible="False" IsChecked="True"/> </MenuItem>
</MenuItem.Icon> <MenuItem Header="Menu Item with _Icon" InputGesture="Ctrl+Shift+B">
</MenuItem> <MenuItem.Icon>
<MenuItem Header="Menu Item that won't close on click" StaysOpenOnClick="True" /> <Image Source="/Assets/github_icon.png" />
</ContextMenu> </MenuItem.Icon>
</Border.ContextMenu> </MenuItem>
<TextBlock Text="Defined in XAML"/> <MenuItem Header="Menu Item with _Checkbox">
</Border> <MenuItem.Icon>
<Border Background="{DynamicResource SystemAccentColor}" <CheckBox BorderThickness="0"
Margin="16" IsChecked="True"
Padding="48,48,48,48"> IsHitTestVisible="False" />
<Border.ContextMenu> </MenuItem.Icon>
<ContextMenu Items="{Binding MenuItems}"> </MenuItem>
<ContextMenu.Styles> <MenuItem Header="Menu Item that won't close on click" StaysOpenOnClick="True" />
<Style Selector="MenuItem"> </ContextMenu>
<Setter Property="Header" Value="{Binding Header}"/> </Border.ContextMenu>
<Setter Property="Items" Value="{Binding Items}"/> <TextBlock Text="Defined in XAML" />
<Setter Property="Command" Value="{Binding Command}"/> </Border>
<Setter Property="CommandParameter" Value="{Binding CommandParameter}"/> <Border>
</Style> <Border.Styles>
</ContextMenu.Styles> <Style Selector="ContextMenu MenuItem">
</ContextMenu> <Setter Property="Header" Value="{Binding Header}"/>
</Border.ContextMenu> <Setter Property="Items" Value="{Binding Items}"/>
<TextBlock Text="Dynamically Generated"/> <Setter Property="Command" Value="{Binding Command}"/>
</Border> <Setter Property="CommandParameter" Value="{Binding CommandParameter}"/>
</Style>
</Border.Styles>
<Border.ContextMenu>
<ContextMenu Items="{Binding MenuItems}" />
</Border.ContextMenu>
<TextBlock Text="Dynamically Generated"/>
</Border>
<Border x:Name="CustomContextRequestedBorder">
<Border.ContextMenu>
<ContextMenu>
<MenuItem Header="Should never be visible" />
</ContextMenu>
</Border.ContextMenu>
<TextBlock Text="Custom ContextRequested handler" TextWrapping="Wrap" />
</Border>
<Border x:Name="CancellableContextBorder">
<Border.ContextMenu>
<ContextMenu>
<MenuItem>
<MenuItem.Header>
<CheckBox x:Name="CancelCloseCheckBox" Content="Cancel close" />
</MenuItem.Header>
</MenuItem>
</ContextMenu>
</Border.ContextMenu>
<StackPanel>
<TextBlock Text="Cancellable" />
<CheckBox x:Name="CancelOpenCheckBox" Content="Cancel open" />
</StackPanel> </StackPanel>
</StackPanel> </Border>
</UniformGrid>
</StackPanel>
</UserControl> </UserControl>

42
samples/ControlCatalog/Pages/ContextMenuPage.xaml.cs

@ -1,5 +1,8 @@
using System; using System;
using System.ComponentModel;
using Avalonia.Controls; using Avalonia.Controls;
using Avalonia.Interactivity;
using Avalonia.Markup.Xaml; using Avalonia.Markup.Xaml;
using ControlCatalog.ViewModels; using ControlCatalog.ViewModels;
@ -10,21 +13,54 @@ namespace ControlCatalog.Pages
public ContextMenuPage() public ContextMenuPage()
{ {
this.InitializeComponent(); this.InitializeComponent();
DataContext = new ContextMenuPageViewModel(); DataContext = new ContextPageViewModel();
var customContextRequestedBorder = this.FindControl<Border>("CustomContextRequestedBorder");
customContextRequestedBorder.AddHandler(ContextRequestedEvent, CustomContextRequested, RoutingStrategies.Tunnel);
var cancellableContextBorder = this.FindControl<Border>("CancellableContextBorder");
cancellableContextBorder.ContextMenu!.ContextMenuClosing += ContextFlyoutPage_Closing;
cancellableContextBorder.ContextMenu!.ContextMenuOpening += ContextFlyoutPage_Opening;
} }
private ContextMenuPageViewModel _model; private ContextPageViewModel _model;
protected override void OnDataContextChanged(EventArgs e) protected override void OnDataContextChanged(EventArgs e)
{ {
if (_model != null) if (_model != null)
_model.View = null; _model.View = null;
_model = DataContext as ContextMenuPageViewModel; _model = DataContext as ContextPageViewModel;
if (_model != null) if (_model != null)
_model.View = this; _model.View = this;
base.OnDataContextChanged(e); base.OnDataContextChanged(e);
} }
private void ContextFlyoutPage_Closing(object sender, CancelEventArgs e)
{
var cancelCloseCheckBox = this.FindControl<CheckBox>("CancelCloseCheckBox");
e.Cancel = cancelCloseCheckBox.IsChecked ?? false;
}
private void ContextFlyoutPage_Opening(object sender, EventArgs e)
{
if (e is CancelEventArgs cancelArgs)
{
var cancelCloseCheckBox = this.FindControl<CheckBox>("CancelOpenCheckBox");
cancelArgs.Cancel = cancelCloseCheckBox.IsChecked ?? false;
}
}
public void CustomContextRequested(object sender, ContextRequestedEventArgs e)
{
var border = (Border)sender;
var textBlock = (TextBlock)border.Child;
textBlock.Text = e.TryGetPosition(border, out var point)
? $"Context was requested with pointer at: {point.X:N0}, {point.Y:N0}"
: "Context was requested without pointer";
e.Handled = true;
}
private void InitializeComponent() private void InitializeComponent()
{ {
AvaloniaXamlLoader.Load(this); AvaloniaXamlLoader.Load(this);

25
samples/ControlCatalog/Pages/DataGridPage.xaml

@ -23,16 +23,21 @@
</StackPanel> </StackPanel>
<TabControl Grid.Row="2"> <TabControl Grid.Row="2">
<TabItem Header="DataGrid"> <TabItem Header="DataGrid">
<DataGrid Name="dataGrid1" Margin="12" CanUserResizeColumns="True" CanUserReorderColumns="True" CanUserSortColumns="True" HeadersVisibility="All"> <DockPanel>
<DataGrid.Columns> <CheckBox x:Name="ShowGDP" IsChecked="True" Content="Toggle GDP Column Visibility"
<DataGridTextColumn Header="Country" Binding="{Binding Name}" Width="6*" /> DockPanel.Dock="Top"/>
<!-- CompiledBinding example of usage. --> <DataGrid Name="dataGrid1" Margin="12" CanUserResizeColumns="True" CanUserReorderColumns="True" CanUserSortColumns="True" HeadersVisibility="All">
<DataGridTextColumn Header="Region" Binding="{CompiledBinding Region}" Width="4*" x:DataType="local:Country" /> <DataGrid.Columns>
<DataGridTextColumn Header="Population" Binding="{Binding Population}" Width="3*" /> <DataGridTextColumn Header="Country" Binding="{Binding Name}" Width="6*" />
<DataGridTextColumn Header="Area" Binding="{Binding Area}" Width="3*" /> <!-- CompiledBinding example of usage. -->
<DataGridTextColumn Header="GDP" Binding="{Binding GDP}" Width="3*" CellStyleClasses="gdp" /> <DataGridTextColumn Header="Region" Binding="{CompiledBinding Region}" Width="4*" x:DataType="local:Country" />
</DataGrid.Columns> <DataGridTextColumn Header="Population" Binding="{Binding Population}" Width="3*" />
</DataGrid> <DataGridTextColumn Header="Area" Binding="{Binding Area}" Width="3*" />
<DataGridTextColumn Header="GDP" Binding="{Binding GDP}" Width="3*" CellStyleClasses="gdp"
IsVisible="{Binding #ShowGDP.IsChecked}"/>
</DataGrid.Columns>
</DataGrid>
</DockPanel>
</TabItem> </TabItem>
<TabItem Header="Grouping"> <TabItem Header="Grouping">
<DataGrid Name="dataGridGrouping" Margin="12"> <DataGrid Name="dataGridGrouping" Margin="12">

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

78
samples/ControlCatalog/ViewModels/ContextFlyoutPageViewModel.cs

@ -1,78 +0,0 @@
using System.Collections.Generic;
using System.Reactive;
using System.Threading.Tasks;
using Avalonia.Controls;
using Avalonia.VisualTree;
using MiniMvvm;
namespace ControlCatalog.ViewModels
{
public class ContextFlyoutPageViewModel
{
public Control View { get; set; }
public ContextFlyoutPageViewModel()
{
OpenCommand = MiniCommand.CreateFromTask(Open);
SaveCommand = MiniCommand.Create(Save);
OpenRecentCommand = MiniCommand.Create<string>(OpenRecent);
MenuItems = new[]
{
new MenuItemViewModel { Header = "_Open...", Command = OpenCommand },
new MenuItemViewModel { Header = "Save", Command = SaveCommand },
new MenuItemViewModel { Header = "-" },
new MenuItemViewModel
{
Header = "Recent",
Items = new[]
{
new MenuItemViewModel
{
Header = "File1.txt",
Command = OpenRecentCommand,
CommandParameter = @"c:\foo\File1.txt"
},
new MenuItemViewModel
{
Header = "File2.txt",
Command = OpenRecentCommand,
CommandParameter = @"c:\foo\File2.txt"
},
}
},
};
}
public IReadOnlyList<MenuItemViewModel> MenuItems { get; set; }
public MiniCommand OpenCommand { get; }
public MiniCommand SaveCommand { get; }
public MiniCommand OpenRecentCommand { get; }
public async Task Open()
{
var window = View?.GetVisualRoot() as Window;
if (window == null)
return;
var dialog = new OpenFileDialog();
var result = await dialog.ShowAsync(window);
if (result != null)
{
foreach (var path in result)
{
System.Diagnostics.Debug.WriteLine($"Opened: {path}");
}
}
}
public void Save()
{
System.Diagnostics.Debug.WriteLine("Save");
}
public void OpenRecent(string path)
{
System.Diagnostics.Debug.WriteLine($"Open recent: {path}");
}
}
}

4
samples/ControlCatalog/ViewModels/ContextMenuPageViewModel.cs → samples/ControlCatalog/ViewModels/ContextPageViewModel.cs

@ -7,10 +7,10 @@ using MiniMvvm;
namespace ControlCatalog.ViewModels namespace ControlCatalog.ViewModels
{ {
public class ContextMenuPageViewModel public class ContextPageViewModel
{ {
public Control View { get; set; } public Control View { get; set; }
public ContextMenuPageViewModel() public ContextPageViewModel()
{ {
OpenCommand = MiniCommand.CreateFromTask(Open); OpenCommand = MiniCommand.CreateFromTask(Open);
SaveCommand = MiniCommand.Create(Save); SaveCommand = MiniCommand.Create(Save);

149
samples/RenderDemo/Pages/AnimationsPage.xaml

@ -161,6 +161,151 @@
</Animation> </Animation>
</Style.Animations> </Style.Animations>
</Style> </Style>
<Style Selector="Border.Rect7">
<Style.Animations>
<Animation Duration="0:0:3"
IterationCount="Infinite"
PlaybackDirection="Alternate">
<KeyFrame Cue="0%">
<Setter Property="Background" Value="Red" />
</KeyFrame>
<KeyFrame Cue="30%">
<Setter Property="Background">
<LinearGradientBrush StartPoint="0%,0%" EndPoint="0%,100%">
<GradientStop Offset="0" Color="Red"/>
<GradientStop Offset="1" Color="Blue"/>
</LinearGradientBrush>
</Setter>
</KeyFrame>
<KeyFrame Cue="60%">
<Setter Property="Background" Value="Blue" />
</KeyFrame>
<KeyFrame Cue="100%">
<Setter Property="Background">
<LinearGradientBrush StartPoint="0%,0%" EndPoint="0%,100%">
<GradientStop Offset="0" Color="Green"/>
<GradientStop Offset="1" Color="Yellow"/>
</LinearGradientBrush>
</Setter>
</KeyFrame>
</Animation>
</Style.Animations>
</Style>
<Style Selector="Border.Rect8">
<Style.Animations>
<Animation Duration="0:0:3"
IterationCount="Infinite"
PlaybackDirection="Alternate">
<KeyFrame Cue="0%">
<Setter Property="Background">
<LinearGradientBrush StartPoint="0%,0%" EndPoint="0%,100%">
<GradientStop Offset="0" Color="Red"/>
<GradientStop Offset="1" Color="Blue"/>
</LinearGradientBrush>
</Setter>
</KeyFrame>
<KeyFrame Cue="50%">
<Setter Property="Background">
<LinearGradientBrush StartPoint="100%,0%" EndPoint="0%,100%">
<GradientStop Offset="0" Color="Red"/>
<GradientStop Offset="0.25" Color="Blue"/>
<GradientStop Offset="0.5" Color="Blue"/>
<GradientStop Offset="0.75" Color="Green"/>
<GradientStop Offset="1" Color="Yellow"/>
</LinearGradientBrush>
</Setter>
</KeyFrame>
<KeyFrame Cue="100%">
<Setter Property="Background">
<LinearGradientBrush StartPoint="0%,0%" EndPoint="0%,100%">
<GradientStop Offset="0" Color="Red"/>
<GradientStop Offset="1" Color="Blue"/>
</LinearGradientBrush>
</Setter>
</KeyFrame>
</Animation>
</Style.Animations>
</Style>
<Style Selector="Border.Rect9">
<Style.Animations>
<Animation Duration="0:0:3"
IterationCount="Infinite"
PlaybackDirection="Alternate">
<KeyFrame Cue="0%">
<Setter Property="Background">
<ConicGradientBrush Center="50%,50%" Angle="0">
<GradientStop Offset="0" Color="Blue"/>
<GradientStop Offset="0.5" Color="Red"/>
<GradientStop Offset="1" Color="Blue"/>
</ConicGradientBrush>
</Setter>
</KeyFrame>
<KeyFrame Cue="100%">
<Setter Property="Background">
<ConicGradientBrush Center="50%,70%" Angle="90">
<GradientStop Offset="0" Color="Green"/>
<GradientStop Offset="0.25" Color="Yellow"/>
<GradientStop Offset="0.5" Color="Red"/>
<GradientStop Offset="0.75" Color="Blue"/>
<GradientStop Offset="1" Color="Green"/>
</ConicGradientBrush>
</Setter>
</KeyFrame>
</Animation>
</Style.Animations>
</Style>
<Style Selector="Border.Rect10">
<Style.Animations>
<Animation Duration="0:0:3"
IterationCount="Infinite"
PlaybackDirection="Normal">
<KeyFrame Cue="0%">
<Setter Property="Background">
<RadialGradientBrush Center="0%,100%" Radius="0.8">
<GradientStop Offset="0" Color="Red"/>
<GradientStop Offset="1" Color="Blue"/>
</RadialGradientBrush>
</Setter>
</KeyFrame>
<KeyFrame Cue="25%">
<Setter Property="Background">
<RadialGradientBrush Center="0%,0%" Radius="1">
<GradientStop Offset="0" Color="Red"/>
<GradientStop Offset="1" Color="Blue"/>
</RadialGradientBrush>
</Setter>
</KeyFrame>
<KeyFrame Cue="50%">
<Setter Property="Background">
<RadialGradientBrush Center="100%,0%" Radius="0.8">
<GradientStop Offset="0" Color="Red"/>
<GradientStop Offset="1" Color="Blue"/>
</RadialGradientBrush>
</Setter>
</KeyFrame>
<KeyFrame Cue="75%">
<Setter Property="Background">
<RadialGradientBrush Center="100%,100%" Radius="1">
<GradientStop Offset="0" Color="Red"/>
<GradientStop Offset="1" Color="Blue"/>
</RadialGradientBrush>
</Setter>
</KeyFrame>
<KeyFrame Cue="100%">
<Setter Property="Background">
<RadialGradientBrush Center="0%,100%" Radius="0.8">
<GradientStop Offset="0" Color="Red"/>
<GradientStop Offset="1" Color="Blue"/>
</RadialGradientBrush>
</Setter>
</KeyFrame>
</Animation>
</Style.Animations>
</Style>
</Styles> </Styles>
</UserControl.Styles> </UserControl.Styles>
<Grid> <Grid>
@ -181,6 +326,10 @@
<Border Classes="Test Rect6" Background="Red"/> <Border Classes="Test Rect6" Background="Red"/>
<Border Classes="Test Shadow" CornerRadius="10" Child="{x:Null}" /> <Border Classes="Test Shadow" CornerRadius="10" Child="{x:Null}" />
<Border Classes="Test Shadow" CornerRadius="0 30 60 0" Child="{x:Null}" /> <Border Classes="Test Shadow" CornerRadius="0 30 60 0" Child="{x:Null}" />
<Border Classes="Test Rect7" Child="{x:Null}" />
<Border Classes="Test Rect8" Child="{x:Null}" />
<Border Classes="Test Rect9" Child="{x:Null}" />
<Border Classes="Test Rect10" Child="{x:Null}" />
</WrapPanel> </WrapPanel>
</StackPanel> </StackPanel>
</Grid> </Grid>

84
samples/RenderDemo/Pages/TransitionsPage.xaml

@ -167,13 +167,80 @@
<Style Selector="Border.Rect11:pointerover"> <Style Selector="Border.Rect11:pointerover">
<Setter Property="Background" > <Setter Property="Background" >
<LinearGradientBrush StartPoint="0%,0%" EndPoint="100%,100%"> <LinearGradientBrush StartPoint="0%,0%" EndPoint="100%,100%">
<LinearGradientBrush.GradientStops> <GradientStop Offset="0" Color="Red"/>
<GradientStop Offset="0" Color="Red"/> <GradientStop Offset="1" Color="Blue"/>
<GradientStop Offset="1" Color="Blue"/>
</LinearGradientBrush.GradientStops>
</LinearGradientBrush> </LinearGradientBrush>
</Setter> </Setter>
</Style> </Style>
<Style Selector="Border.Rect12">
<Setter Property="Transitions">
<Transitions>
<BrushTransition Property="Background" Duration="0:0:0.5" />
</Transitions>
</Setter>
<Setter Property="Background" >
<LinearGradientBrush StartPoint="0%,0%" EndPoint="100%,100%">
<GradientStop Offset="0" Color="Red"/>
<GradientStop Offset="1" Color="Blue"/>
</LinearGradientBrush>
</Setter>
</Style>
<Style Selector="Border.Rect12:pointerover">
<Setter Property="Background" >
<LinearGradientBrush StartPoint="100%,0%" EndPoint="0%,100%">
<GradientStop Offset="0" Color="Green"/>
<GradientStop Offset="1" Color="Yellow"/>
</LinearGradientBrush>
</Setter>
</Style>
<Style Selector="Border.Rect13">
<Setter Property="Transitions">
<Transitions>
<BrushTransition Property="Background" Duration="0:0:0.5" />
</Transitions>
</Setter>
<Setter Property="Background" >
<ConicGradientBrush Center="50%,50%" Angle="0">
<GradientStop Offset="0" Color="Red"/>
<GradientStop Offset="1" Color="Blue"/>
</ConicGradientBrush>
</Setter>
</Style>
<Style Selector="Border.Rect13:pointerover">
<Setter Property="Background" >
<ConicGradientBrush Center="70%,70%" Angle="90">
<GradientStop Offset="0" Color="Green"/>
<GradientStop Offset="1" Color="Yellow"/>
</ConicGradientBrush>
</Setter>
</Style>
<Style Selector="Border.Rect14">
<Setter Property="Transitions">
<Transitions>
<BrushTransition Property="Background" Duration="0:0:0.5" />
</Transitions>
</Setter>
<Setter Property="Background" >
<RadialGradientBrush Center="50%,50%" Radius="0.5">
<GradientStop Offset="0" Color="Red"/>
<GradientStop Offset="1" Color="Blue"/>
</RadialGradientBrush>
</Setter>
</Style>
<Style Selector="Border.Rect14:pointerover">
<Setter Property="Background" >
<RadialGradientBrush Center="30%,30%" Radius="0.2">
<GradientStop Offset="0" Color="Green"/>
<GradientStop Offset="1" Color="Yellow"/>
</RadialGradientBrush>
</Setter>
</Style>
</Styles> </Styles>
</UserControl.Styles> </UserControl.Styles>
@ -202,6 +269,15 @@
<Border Classes="Test Rect10" /> <Border Classes="Test Rect10" />
<Border Classes="Test Rect11" /> <Border Classes="Test Rect11" />
<Border Classes="Test Rect12" Child="{x:Null}"/>
<Border Classes="Test Rect13" Child="{x:Null}"/>
<Border Classes="Test Rect14" Child="{x:Null}"/>
<Border Classes="Test Rect14" />
<Border Classes="Test Rect14" />
<Border Classes="Test Rect14" />
<Border Classes="Test Rect14" />
</WrapPanel> </WrapPanel>
</StackPanel> </StackPanel>
</Grid> </Grid>

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

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}.");
} }

42
src/Avalonia.Controls.DataGrid/DataGridColumn.cs

@ -27,7 +27,6 @@ namespace Avalonia.Controls
private double? _minWidth; private double? _minWidth;
private bool _settingWidthInternally; private bool _settingWidthInternally;
private int _displayIndexWithFiller; private int _displayIndexWithFiller;
private bool _isVisible;
private object _header; private object _header;
private DataGridColumnHeader _headerCell; private DataGridColumnHeader _headerCell;
private IControl _editingElement; private IControl _editingElement;
@ -40,7 +39,6 @@ namespace Avalonia.Controls
/// </summary> /// </summary>
protected internal DataGridColumn() protected internal DataGridColumn()
{ {
_isVisible = true;
_displayIndexWithFiller = -1; _displayIndexWithFiller = -1;
IsInitialDesiredWidthDetermined = false; IsInitialDesiredWidthDetermined = false;
InheritsWidth = true; InheritsWidth = true;
@ -174,32 +172,42 @@ namespace Avalonia.Controls
get => _editBinding; get => _editBinding;
} }
/// <summary>
/// Defines the <see cref="IsVisible"/> property.
/// </summary>
public static StyledProperty<bool> IsVisibleProperty =
Control.IsVisibleProperty.AddOwner<DataGridColumn>();
/// <summary> /// <summary>
/// Determines whether or not this column is visible. /// Determines whether or not this column is visible.
/// </summary> /// </summary>
public bool IsVisible public bool IsVisible
{ {
get get => GetValue(IsVisibleProperty);
{ set => SetValue(IsVisibleProperty, value);
return _isVisible; }
}
set
{
if (value != IsVisible)
{
OwningGrid?.OnColumnVisibleStateChanging(this);
_isVisible = value;
if (_headerCell != null) protected override void OnPropertyChanged<T>(AvaloniaPropertyChangedEventArgs<T> change)
{ {
_headerCell.IsVisible = value; base.OnPropertyChanged(change);
}
OwningGrid?.OnColumnVisibleStateChanged(this); if (change.Property == IsVisibleProperty)
{
OwningGrid?.OnColumnVisibleStateChanging(this);
var isVisible = (change as AvaloniaPropertyChangedEventArgs<bool>).NewValue.Value;
if (_headerCell != null)
{
_headerCell.IsVisible = isVisible;
} }
OwningGrid?.OnColumnVisibleStateChanged(this);
NotifyPropertyChanged(change.Property.Name);
} }
} }
/// <summary> /// <summary>
/// Actual visible width after Width, MinWidth, and MaxWidth setting at the Column level and DataGrid level /// Actual visible width after Width, MinWidth, and MaxWidth setting at the Column level and DataGrid level
/// have been taken into account /// have been taken into account

4
src/Avalonia.Controls.DataGrid/Themes/Fluent.xaml

@ -6,8 +6,8 @@
<x:Double x:Key="ListAccentLowOpacity">0.6</x:Double> <x:Double x:Key="ListAccentLowOpacity">0.6</x:Double>
<x:Double x:Key="ListAccentMediumOpacity">0.8</x:Double> <x:Double x:Key="ListAccentMediumOpacity">0.8</x:Double>
<StreamGeometry x:Key="DataGridSortIconAscendingPath">M1875 1011l-787 787v-1798h-128v1798l-787 -787l-90 90l941 941l941 -941z</StreamGeometry> <StreamGeometry x:Key="DataGridSortIconDescendingPath">M1875 1011l-787 787v-1798h-128v1798l-787 -787l-90 90l941 941l941 -941z</StreamGeometry>
<StreamGeometry x:Key="DataGridSortIconDescendingPath">M1965 947l-941 -941l-941 941l90 90l787 -787v1798h128v-1798l787 787z</StreamGeometry> <StreamGeometry x:Key="DataGridSortIconAscendingPath">M1965 947l-941 -941l-941 941l90 90l787 -787v1798h128v-1798l787 787z</StreamGeometry>
<StreamGeometry x:Key="DataGridRowGroupHeaderIconClosedPath">M515 93l930 931l-930 931l90 90l1022 -1021l-1022 -1021z</StreamGeometry> <StreamGeometry x:Key="DataGridRowGroupHeaderIconClosedPath">M515 93l930 931l-930 931l90 90l1022 -1021l-1022 -1021z</StreamGeometry>
<StreamGeometry x:Key="DataGridRowGroupHeaderIconOpenedPath">M1939 1581l90 -90l-1005 -1005l-1005 1005l90 90l915 -915z</StreamGeometry> <StreamGeometry x:Key="DataGridRowGroupHeaderIconOpenedPath">M1939 1581l90 -90l-1005 -1005l-1005 1005l90 90l915 -915z</StreamGeometry>

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)

31
src/Avalonia.Controls/ApiCompatBaseline.txt

@ -4,15 +4,44 @@ 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.
InterfacesShouldHaveSameMembers : Interface member 'public System.EventHandler<System.ComponentModel.CancelEventArgs> Avalonia.Controls.ApplicationLifetimes.IClassicDesktopStyleApplicationLifetime.ShutdownRequested' is present in the implementation but not in the contract.
InterfacesShouldHaveSameMembers : Interface member 'public void Avalonia.Controls.ApplicationLifetimes.IClassicDesktopStyleApplicationLifetime.add_ShutdownRequested(System.EventHandler<System.ComponentModel.CancelEventArgs>)' is present in the implementation but not in the contract.
InterfacesShouldHaveSameMembers : Interface member 'public void Avalonia.Controls.ApplicationLifetimes.IClassicDesktopStyleApplicationLifetime.remove_ShutdownRequested(System.EventHandler<System.ComponentModel.CancelEventArgs>)' is present in the implementation but not 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.
InterfacesShouldHaveSameMembers : Interface member 'public void Avalonia.Platform.IWindowBaseImpl.Show(System.Boolean)' is present in the contract but not in the implementation. InterfacesShouldHaveSameMembers : Interface member 'public void Avalonia.Platform.IWindowBaseImpl.Show(System.Boolean)' is present in the contract but not in the implementation.
MembersMustExist : Member 'public void Avalonia.Platform.IWindowBaseImpl.Show(System.Boolean)' does not exist in the implementation but it does exist in the contract. MembersMustExist : Member 'public void Avalonia.Platform.IWindowBaseImpl.Show(System.Boolean)' does not exist in the implementation but it does exist in the contract.
InterfacesShouldHaveSameMembers : Interface member 'public void Avalonia.Platform.IWindowBaseImpl.Show(System.Boolean, System.Boolean)' is present in the implementation but not in the contract. InterfacesShouldHaveSameMembers : Interface member 'public void Avalonia.Platform.IWindowBaseImpl.Show(System.Boolean, System.Boolean)' is present in the implementation but not in the contract.
Total Issues: 16 Total Issues: 45

29
src/Avalonia.Controls/ApplicationLifetimes/ClassicDesktopStyleApplicationLifetime.cs

@ -1,5 +1,6 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.ComponentModel;
using System.Linq; using System.Linq;
using System.Threading; using System.Threading;
using Avalonia.Controls; using Avalonia.Controls;
@ -42,9 +43,13 @@ namespace Avalonia.Controls.ApplicationLifetimes
"Can not have multiple active ClassicDesktopStyleApplicationLifetime instances and the previously created one was not disposed"); "Can not have multiple active ClassicDesktopStyleApplicationLifetime instances and the previously created one was not disposed");
_activeLifetime = this; _activeLifetime = this;
} }
/// <inheritdoc/> /// <inheritdoc/>
public event EventHandler<ControlledApplicationLifetimeStartupEventArgs> Startup; public event EventHandler<ControlledApplicationLifetimeStartupEventArgs> Startup;
/// <inheritdoc/>
public event EventHandler<CancelEventArgs> ShutdownRequested;
/// <inheritdoc/> /// <inheritdoc/>
public event EventHandler<ControlledApplicationLifetimeExitEventArgs> Exit; public event EventHandler<ControlledApplicationLifetimeExitEventArgs> Exit;
@ -111,6 +116,11 @@ namespace Avalonia.Controls.ApplicationLifetimes
((IApplicationPlatformEvents)Application.Current).RaiseUrlsOpened(args); ((IApplicationPlatformEvents)Application.Current).RaiseUrlsOpened(args);
} }
var lifetimeEvents = AvaloniaLocator.Current.GetService<IPlatformLifetimeEventsImpl>();
if (lifetimeEvents != null)
lifetimeEvents.ShutdownRequested += OnShutdownRequested;
_cts = new CancellationTokenSource(); _cts = new CancellationTokenSource();
MainWindow?.Show(); MainWindow?.Show();
Dispatcher.UIThread.MainLoop(_cts.Token); Dispatcher.UIThread.MainLoop(_cts.Token);
@ -123,6 +133,23 @@ namespace Avalonia.Controls.ApplicationLifetimes
if (_activeLifetime == this) if (_activeLifetime == this)
_activeLifetime = null; _activeLifetime = null;
} }
private void OnShutdownRequested(object sender, CancelEventArgs e)
{
ShutdownRequested?.Invoke(this, e);
if (e.Cancel)
return;
// When an OS shutdown request is received, try to close all non-owned windows. Windows can cancel
// shutdown by setting e.Cancel = true in the Closing event. Owned windows will be shutdown by their
// owners.
foreach (var w in Windows)
if (w.Owner is null)
w.Close();
if (Windows.Count > 0)
e.Cancel = true;
}
} }
public class ClassicDesktopStyleApplicationLifetimeOptions public class ClassicDesktopStyleApplicationLifetimeOptions

12
src/Avalonia.Controls/ApplicationLifetimes/IClassicDesktopStyleApplicationLifetime.cs

@ -1,5 +1,6 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.ComponentModel;
namespace Avalonia.Controls.ApplicationLifetimes namespace Avalonia.Controls.ApplicationLifetimes
{ {
@ -34,5 +35,16 @@ namespace Avalonia.Controls.ApplicationLifetimes
Window MainWindow { get; set; } Window MainWindow { get; set; }
IReadOnlyList<Window> Windows { get; } IReadOnlyList<Window> Windows { get; }
/// <summary>
/// Raised by the platform when a shutdown is requested.
/// </summary>
/// <remarks>
/// Raised on on OSX via the Quit menu or right-clicking on the application icon and selecting Quit. This event
/// provides a first-chance to cancel application shutdown; if shutdown is not canceled at this point the application
/// will try to close each non-owned open window, invoking the <see cref="Window.Closing"/> event on each and allowing
/// each window to cancel the shutdown.
/// </remarks>
event EventHandler<CancelEventArgs> ShutdownRequested;
} }
} }

72
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;
@ -99,6 +91,7 @@ namespace Avalonia.Controls
FocusableProperty.OverrideDefaultValue<ComboBox>(true); FocusableProperty.OverrideDefaultValue<ComboBox>(true);
SelectedItemProperty.Changed.AddClassHandler<ComboBox>((x,e) => x.SelectedItemChanged(e)); SelectedItemProperty.Changed.AddClassHandler<ComboBox>((x,e) => x.SelectedItemChanged(e));
KeyDownEvent.AddClassHandler<ComboBox>((x, e) => x.OnKeyDown(e), Interactivity.RoutingStrategies.Tunnel); KeyDownEvent.AddClassHandler<ComboBox>((x, e) => x.OnKeyDown(e), Interactivity.RoutingStrategies.Tunnel);
IsTextSearchEnabledProperty.OverrideDefaultValue<ComboBox>(true);
} }
/// <summary> /// <summary>
@ -173,15 +166,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 +189,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 +231,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 +428,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();
}
} }
} }

65
src/Avalonia.Controls/ContextMenu.cs

@ -1,12 +1,15 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.ComponentModel; using System.ComponentModel;
using System.Linq;
using Avalonia.Controls.Generators; using Avalonia.Controls.Generators;
using Avalonia.Controls.Platform; using Avalonia.Controls.Platform;
using Avalonia.Controls.Primitives; using Avalonia.Controls.Primitives;
using Avalonia.Controls.Primitives.PopupPositioning; using Avalonia.Controls.Primitives.PopupPositioning;
using Avalonia.Controls.Templates; using Avalonia.Controls.Templates;
using Avalonia.Input; using Avalonia.Input;
using Avalonia.Input.Platform;
using Avalonia.Interactivity; using Avalonia.Interactivity;
using Avalonia.Layout; using Avalonia.Layout;
using Avalonia.Styling; using Avalonia.Styling;
@ -220,7 +223,8 @@ namespace Avalonia.Controls
if (e.OldValue is ContextMenu oldMenu) if (e.OldValue is ContextMenu oldMenu)
{ {
control.PointerReleased -= ControlPointerReleased; control.ContextRequested -= ControlContextRequested;
control.DetachedFromVisualTree -= ControlDetachedFromVisualTree;
oldMenu._attachedControls?.Remove(control); oldMenu._attachedControls?.Remove(control);
((ISetLogicalParent?)oldMenu._popup)?.SetParent(null); ((ISetLogicalParent?)oldMenu._popup)?.SetParent(null);
} }
@ -229,7 +233,8 @@ namespace Avalonia.Controls
{ {
newMenu._attachedControls ??= new List<Control>(); newMenu._attachedControls ??= new List<Control>();
newMenu._attachedControls.Add(control); newMenu._attachedControls.Add(control);
control.PointerReleased += ControlPointerReleased; control.ContextRequested += ControlContextRequested;
control.DetachedFromVisualTree += ControlDetachedFromVisualTree;
} }
} }
@ -269,7 +274,7 @@ namespace Avalonia.Controls
} }
control ??= _attachedControls![0]; control ??= _attachedControls![0];
Open(control, PlacementTarget ?? control); Open(control, PlacementTarget ?? control, false);
} }
/// <summary> /// <summary>
@ -304,7 +309,7 @@ namespace Avalonia.Controls
return new MenuItemContainerGenerator(this); return new MenuItemContainerGenerator(this);
} }
private void Open(Control control, Control placementTarget) private void Open(Control control, Control placementTarget, bool requestedByPointer)
{ {
if (IsOpen) if (IsOpen)
{ {
@ -329,6 +334,8 @@ namespace Avalonia.Controls
_popup.Opened += PopupOpened; _popup.Opened += PopupOpened;
_popup.Closed += PopupClosed; _popup.Closed += PopupClosed;
_popup.Closing += PopupClosing;
_popup.KeyUp += PopupKeyUp;
} }
if (_popup.Parent != control) if (_popup.Parent != control)
@ -337,6 +344,10 @@ namespace Avalonia.Controls
((ISetLogicalParent)_popup).SetParent(control); ((ISetLogicalParent)_popup).SetParent(control);
} }
_popup.PlacementMode = !requestedByPointer && PlacementMode == PlacementMode.Pointer
? PlacementMode.Bottom
: PlacementMode;
_popup.PlacementTarget = placementTarget; _popup.PlacementTarget = placementTarget;
_popup.Child = this; _popup.Child = this;
IsOpen = true; IsOpen = true;
@ -355,6 +366,11 @@ namespace Avalonia.Controls
Focus(); Focus();
} }
private void PopupClosing(object sender, CancelEventArgs e)
{
e.Cancel = CancelClosing();
}
private void PopupClosed(object sender, EventArgs e) private void PopupClosed(object sender, EventArgs e)
{ {
foreach (var i in LogicalChildren) foreach (var i in LogicalChildren)
@ -383,30 +399,43 @@ namespace Avalonia.Controls
}); });
} }
private static void ControlPointerReleased(object sender, PointerReleasedEventArgs e) private void PopupKeyUp(object sender, KeyEventArgs e)
{ {
var control = (Control)sender; if (IsOpen)
var contextMenu = control.ContextMenu;
if (control.ContextMenu.IsOpen)
{ {
if (contextMenu.CancelClosing()) var keymap = AvaloniaLocator.Current.GetService<PlatformHotkeyConfiguration>();
return;
control.ContextMenu.Close(); if (keymap.OpenContextMenu.Any(k => k.Matches(e))
e.Handled = true; && !CancelClosing())
{
Close();
e.Handled = true;
}
} }
}
if (e.InitialPressMouseButton == MouseButton.Right) private static void ControlContextRequested(object sender, ContextRequestedEventArgs e)
{
if (sender is Control control
&& control.ContextMenu is ContextMenu contextMenu
&& !e.Handled
&& !contextMenu.CancelOpening())
{ {
if (contextMenu.CancelOpening()) var requestedByPointer = e.TryGetPosition(null, out _);
return; contextMenu.Open(control, e.Source as Control ?? control, requestedByPointer);
contextMenu.Open(control, e.Source as Control ?? control);
e.Handled = true; e.Handled = true;
} }
} }
private static void ControlDetachedFromVisualTree(object sender, VisualTreeAttachmentEventArgs e)
{
if (sender is Control control
&& control.ContextMenu is ContextMenu contextMenu)
{
contextMenu.Close();
}
}
private bool CancelClosing() private bool CancelClosing()
{ {
var eventArgs = new CancelEventArgs(); var eventArgs = new CancelEventArgs();

58
src/Avalonia.Controls/ContextRequestedEventArgs.cs

@ -0,0 +1,58 @@
using Avalonia.Input;
using Avalonia.Interactivity;
#nullable enable
namespace Avalonia.Controls
{
/// <summary>
/// Provides event data for the ContextRequested event.
/// </summary>
public class ContextRequestedEventArgs : RoutedEventArgs
{
private readonly PointerEventArgs? _pointerEventArgs;
/// <summary>
/// Initializes a new instance of the ContextRequestedEventArgs class.
/// </summary>
public ContextRequestedEventArgs()
: base(Control.ContextRequestedEvent)
{
}
/// <inheritdoc cref="ContextRequestedEventArgs()" />
public ContextRequestedEventArgs(PointerEventArgs pointerEventArgs)
: this()
{
_pointerEventArgs = pointerEventArgs;
}
/// <summary>
/// Gets the x- and y-coordinates of the pointer position, optionally evaluated against a coordinate origin of a supplied <see cref="Control"/>.
/// </summary>
/// <param name="relativeTo">
/// Any <see cref="Control"/>-derived object that is connected to the same object tree.
/// To specify the object relative to the overall coordinate system, use a relativeTo value of null.
/// </param>
/// <param name="point">
/// A <see cref="Point"/> that represents the current x- and y-coordinates of the mouse pointer position.
/// If null was passed as relativeTo, this coordinate is for the overall window.
/// If a relativeTo value other than null was passed, this coordinate is relative to the object referenced by relativeTo.
/// </param>
/// <returns>
/// true if the context request was initiated by a pointer device; otherwise, false.
/// </returns>
public bool TryGetPosition(Control? relativeTo, out Point point)
{
if (_pointerEventArgs is null)
{
point = default;
return false;
}
point = _pointerEventArgs.GetPosition(relativeTo);
return true;
}
}
}

62
src/Avalonia.Controls/Control.cs

@ -3,6 +3,7 @@ using System.ComponentModel;
using Avalonia.Controls.Primitives; using Avalonia.Controls.Primitives;
using Avalonia.Controls.Templates; using Avalonia.Controls.Templates;
using Avalonia.Input; using Avalonia.Input;
using Avalonia.Input.Platform;
using Avalonia.Interactivity; using Avalonia.Interactivity;
using Avalonia.Rendering; using Avalonia.Rendering;
using Avalonia.Styling; using Avalonia.Styling;
@ -19,6 +20,7 @@ namespace Avalonia.Controls
/// The control class extends <see cref="InputElement"/> and adds the following features: /// The control class extends <see cref="InputElement"/> and adds the following features:
/// ///
/// - A <see cref="Tag"/> property to allow user-defined data to be attached to the control. /// - A <see cref="Tag"/> property to allow user-defined data to be attached to the control.
/// - <see cref="ContextRequestedEvent"/> and other context menu related members.
/// </remarks> /// </remarks>
public class Control : InputElement, IControl, INamed, IVisualBrushInitialize, ISetterValue public class Control : InputElement, IControl, INamed, IVisualBrushInitialize, ISetterValue
{ {
@ -52,6 +54,13 @@ namespace Avalonia.Controls
public static readonly RoutedEvent<RequestBringIntoViewEventArgs> RequestBringIntoViewEvent = public static readonly RoutedEvent<RequestBringIntoViewEventArgs> RequestBringIntoViewEvent =
RoutedEvent.Register<Control, RequestBringIntoViewEventArgs>("RequestBringIntoView", RoutingStrategies.Bubble); RoutedEvent.Register<Control, RequestBringIntoViewEventArgs>("RequestBringIntoView", RoutingStrategies.Bubble);
/// <summary>
/// Provides event data for the <see cref="ContextRequested"/> event.
/// </summary>
public static readonly RoutedEvent<ContextRequestedEventArgs> ContextRequestedEvent =
RoutedEvent.Register<Control, ContextRequestedEventArgs>(nameof(ContextRequested),
RoutingStrategies.Tunnel | RoutingStrategies.Bubble);
private DataTemplates? _dataTemplates; private DataTemplates? _dataTemplates;
private IControl? _focusAdorner; private IControl? _focusAdorner;
@ -100,6 +109,15 @@ namespace Avalonia.Controls
set => SetValue(TagProperty, value); set => SetValue(TagProperty, value);
} }
/// <summary>
/// Occurs when the user has completed a context input gesture, such as a right-click.
/// </summary>
public event EventHandler<ContextRequestedEventArgs> ContextRequested
{
add => AddHandler(ContextRequestedEvent, value);
remove => RemoveHandler(ContextRequestedEvent, value);
}
public new IControl? Parent => (IControl?)base.Parent; public new IControl? Parent => (IControl?)base.Parent;
/// <inheritdoc/> /// <inheritdoc/>
@ -208,5 +226,49 @@ namespace Avalonia.Controls
_focusAdorner = null; _focusAdorner = null;
} }
} }
protected override void OnPointerReleased(PointerReleasedEventArgs e)
{
base.OnPointerReleased(e);
if (e.Source == this
&& !e.Handled
&& e.InitialPressMouseButton == MouseButton.Right)
{
var args = new ContextRequestedEventArgs(e);
RaiseEvent(args);
e.Handled = args.Handled;
}
}
protected override void OnKeyUp(KeyEventArgs e)
{
base.OnKeyUp(e);
if (e.Source == this
&& !e.Handled)
{
var keymap = AvaloniaLocator.Current.GetService<PlatformHotkeyConfiguration>().OpenContextMenu;
var matches = false;
for (var index = 0; index < keymap.Count; index++)
{
var key = keymap[index];
matches |= key.Matches(e);
if (matches)
{
break;
}
}
if (matches)
{
var args = new ContextRequestedEventArgs();
RaiseEvent(args);
e.Handled = args.Handled;
}
}
}
} }
} }

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

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

@ -1,6 +1,9 @@
using System; using System;
using System.ComponentModel; using System.ComponentModel;
using System.Linq;
using Avalonia.Input; using Avalonia.Input;
using Avalonia.Input.Platform;
using Avalonia.Input.Raw; using Avalonia.Input.Raw;
using Avalonia.Layout; using Avalonia.Layout;
using Avalonia.Logging; using Avalonia.Logging;
@ -49,6 +52,7 @@ namespace Avalonia.Controls.Primitives
public static readonly AttachedProperty<FlyoutBase?> AttachedFlyoutProperty = public static readonly AttachedProperty<FlyoutBase?> AttachedFlyoutProperty =
AvaloniaProperty.RegisterAttached<FlyoutBase, Control, FlyoutBase?>("AttachedFlyout", null); AvaloniaProperty.RegisterAttached<FlyoutBase, Control, FlyoutBase?>("AttachedFlyout", null);
private readonly Lazy<Popup> _popupLazy;
private bool _isOpen; private bool _isOpen;
private Control? _target; private Control? _target;
private FlyoutShowMode _showMode = FlyoutShowMode.Standard; private FlyoutShowMode _showMode = FlyoutShowMode.Standard;
@ -56,7 +60,12 @@ namespace Avalonia.Controls.Primitives
private PixelRect? _enlargePopupRectScreenPixelRect; private PixelRect? _enlargePopupRectScreenPixelRect;
private IDisposable? _transientDisposable; private IDisposable? _transientDisposable;
protected Popup? Popup { get; private set; } public FlyoutBase()
{
_popupLazy = new Lazy<Popup>(() => CreatePopup());
}
protected Popup Popup => _popupLazy.Value;
/// <summary> /// <summary>
/// Gets whether this Flyout is currently Open /// Gets whether this Flyout is currently Open
@ -142,22 +151,19 @@ namespace Avalonia.Controls.Primitives
HideCore(); HideCore();
} }
protected virtual void HideCore(bool canCancel = true) /// <returns>True, if action was handled</returns>
protected virtual bool HideCore(bool canCancel = true)
{ {
if (!IsOpen) if (!IsOpen)
{ {
return; return false;
} }
if (canCancel) if (canCancel)
{ {
bool cancel = false; if (CancelClosing())
var closing = new CancelEventArgs();
Closing?.Invoke(this, closing);
if (cancel || closing.Cancel)
{ {
return; return false;
} }
} }
@ -170,31 +176,42 @@ namespace Avalonia.Controls.Primitives
_enlargedPopupRect = null; _enlargedPopupRect = null;
_enlargePopupRectScreenPixelRect = null; _enlargePopupRectScreenPixelRect = null;
if (Target != null)
{
Target.DetachedFromVisualTree -= PlacementTarget_DetachedFromVisualTree;
Target.KeyUp -= OnPlacementTargetOrPopupKeyUp;
}
OnClosed(); OnClosed();
return true;
} }
protected virtual void ShowAtCore(Control placementTarget, bool showAtPointer = false) /// <returns>True, if action was handled</returns>
protected virtual bool ShowAtCore(Control placementTarget, bool showAtPointer = false)
{ {
if (placementTarget == null) if (placementTarget == null)
throw new ArgumentNullException("placementTarget cannot be null");
if (Popup == null)
{ {
InitPopup(); throw new ArgumentNullException(nameof(placementTarget));
} }
if (IsOpen) if (IsOpen)
{ {
if (placementTarget == Target) if (placementTarget == Target)
{ {
return; return false;
} }
else // Close before opening a new one else // Close before opening a new one
{ {
HideCore(false); _ = HideCore(false);
} }
} }
if (CancelOpening())
{
return false;
}
if (Popup.Parent != null && Popup.Parent != placementTarget) if (Popup.Parent != null && Popup.Parent != placementTarget)
{ {
((ISetLogicalParent)Popup).SetParent(null); ((ISetLogicalParent)Popup).SetParent(null);
@ -211,11 +228,13 @@ namespace Avalonia.Controls.Primitives
Popup.Child = CreatePresenter(); Popup.Child = CreatePresenter();
} }
OnOpening();
PositionPopup(showAtPointer); PositionPopup(showAtPointer);
IsOpen = Popup.IsOpen = true; IsOpen = Popup.IsOpen = true;
OnOpened(); OnOpened();
placementTarget.DetachedFromVisualTree += PlacementTarget_DetachedFromVisualTree;
placementTarget.KeyUp += OnPlacementTargetOrPopupKeyUp;
if (ShowMode == FlyoutShowMode.Standard) if (ShowMode == FlyoutShowMode.Standard)
{ {
// Try and focus content inside Flyout // Try and focus content inside Flyout
@ -236,6 +255,13 @@ namespace Avalonia.Controls.Primitives
{ {
_transientDisposable = InputManager.Instance?.Process.Subscribe(HandleTransientDismiss); _transientDisposable = InputManager.Instance?.Process.Subscribe(HandleTransientDismiss);
} }
return true;
}
private void PlacementTarget_DetachedFromVisualTree(object sender, VisualTreeAttachmentEventArgs e)
{
_ = HideCore(false);
} }
private void HandleTransientDismiss(RawInputEventArgs args) private void HandleTransientDismiss(RawInputEventArgs args)
@ -254,7 +280,7 @@ namespace Avalonia.Controls.Primitives
{ {
// Only do this once when the Flyout opens & cache the result // Only do this once when the Flyout opens & cache the result
if (Popup?.Host is PopupRoot root) if (Popup?.Host is PopupRoot root)
{ {
// Get the popup root bounds and convert to screen coordinates // Get the popup root bounds and convert to screen coordinates
var tmp = root.Bounds.Inflate(100); var tmp = root.Bounds.Inflate(100);
@ -294,9 +320,9 @@ namespace Avalonia.Controls.Primitives
} }
} }
protected virtual void OnOpening() protected virtual void OnOpening(CancelEventArgs args)
{ {
Opening?.Invoke(this, null); Opening?.Invoke(this, args);
} }
protected virtual void OnOpened() protected virtual void OnOpened()
@ -320,14 +346,18 @@ namespace Avalonia.Controls.Primitives
/// <returns></returns> /// <returns></returns>
protected abstract Control CreatePresenter(); protected abstract Control CreatePresenter();
private void InitPopup() private Popup CreatePopup()
{ {
Popup = new Popup(); var popup = new Popup();
Popup.WindowManagerAddShadowHint = false; popup.WindowManagerAddShadowHint = false;
Popup.IsLightDismissEnabled = true; popup.IsLightDismissEnabled = true;
popup.OverlayDismissEventPassThrough = true;
Popup.Opened += OnPopupOpened;
Popup.Closed += OnPopupClosed; popup.Opened += OnPopupOpened;
popup.Closed += OnPopupClosed;
popup.Closing += OnPopupClosing;
popup.KeyUp += OnPlacementTargetOrPopupKeyUp;
return popup;
} }
private void OnPopupOpened(object sender, EventArgs e) private void OnPopupOpened(object sender, EventArgs e)
@ -335,15 +365,40 @@ namespace Avalonia.Controls.Primitives
IsOpen = true; IsOpen = true;
} }
private void OnPopupClosing(object sender, CancelEventArgs e)
{
if (IsOpen)
{
e.Cancel = CancelClosing();
}
}
private void OnPopupClosed(object sender, EventArgs e) private void OnPopupClosed(object sender, EventArgs e)
{ {
HideCore(); HideCore(false);
}
// This method is handling both popup logical tree and target logical tree.
private void OnPlacementTargetOrPopupKeyUp(object sender, KeyEventArgs e)
{
if (!e.Handled
&& IsOpen
&& Target?.ContextFlyout == this)
{
var keymap = AvaloniaLocator.Current.GetService<PlatformHotkeyConfiguration>();
if (keymap.OpenContextMenu.Any(k => k.Matches(e)))
{
e.Handled = HideCore();
}
}
} }
private void PositionPopup(bool showAtPointer) private void PositionPopup(bool showAtPointer)
{ {
Size sz; Size sz;
if(Popup.Child.DesiredSize == Size.Empty) // Popup.Child can't be null here, it was set in ShowAtCore.
if (Popup.Child!.DesiredSize == Size.Empty)
{ {
// Popup may not have been shown yet. Measure content // Popup may not have been shown yet. Measure content
sz = LayoutHelper.MeasureChild(Popup.Child, Size.Infinity, new Thickness()); sz = LayoutHelper.MeasureChild(Popup.Child, Size.Infinity, new Thickness());
@ -370,19 +425,19 @@ namespace Avalonia.Controls.Primitives
switch (Placement) switch (Placement)
{ {
case FlyoutPlacementMode.Top: //Above & centered case FlyoutPlacementMode.Top: //Above & centered
Popup.PlacementRect = new Rect(0, 0, trgtBnds.Width-1, 1); Popup.PlacementRect = new Rect(0, 0, trgtBnds.Width - 1, 1);
Popup.PlacementGravity = PopupPositioning.PopupGravity.Top; Popup.PlacementGravity = PopupPositioning.PopupGravity.Top;
Popup.PlacementAnchor = PopupPositioning.PopupAnchor.Top; Popup.PlacementAnchor = PopupPositioning.PopupAnchor.Top;
break; break;
case FlyoutPlacementMode.TopEdgeAlignedLeft: case FlyoutPlacementMode.TopEdgeAlignedLeft:
Popup.PlacementRect = new Rect(0, 0, 0, 0); Popup.PlacementRect = new Rect(0, 0, 0, 0);
Popup.PlacementGravity = PopupPositioning.PopupGravity.TopRight; Popup.PlacementGravity = PopupPositioning.PopupGravity.TopRight;
break; break;
case FlyoutPlacementMode.TopEdgeAlignedRight: case FlyoutPlacementMode.TopEdgeAlignedRight:
Popup.PlacementRect = new Rect(trgtBnds.Width - 1, 0, 10, 1); Popup.PlacementRect = new Rect(trgtBnds.Width - 1, 0, 10, 1);
Popup.PlacementGravity = PopupPositioning.PopupGravity.TopLeft; Popup.PlacementGravity = PopupPositioning.PopupGravity.TopLeft;
break; break;
case FlyoutPlacementMode.RightEdgeAlignedTop: case FlyoutPlacementMode.RightEdgeAlignedTop:
@ -454,33 +509,45 @@ namespace Avalonia.Controls.Primitives
{ {
if (args.OldValue is FlyoutBase) if (args.OldValue is FlyoutBase)
{ {
c.PointerReleased -= OnControlWithContextFlyoutPointerReleased; c.ContextRequested -= OnControlContextRequested;
} }
if (args.NewValue is FlyoutBase) if (args.NewValue is FlyoutBase)
{ {
c.PointerReleased += OnControlWithContextFlyoutPointerReleased; c.ContextRequested += OnControlContextRequested;
} }
} }
} }
private static void OnControlWithContextFlyoutPointerReleased(object sender, PointerReleasedEventArgs e) private static void OnControlContextRequested(object sender, ContextRequestedEventArgs e)
{ {
if (sender is Control c) var control = (Control)sender;
if (!e.Handled
&& control.ContextFlyout is FlyoutBase flyout)
{ {
if (e.InitialPressMouseButton == MouseButton.Right && if (control.ContextMenu != null)
e.GetCurrentPoint(c).Properties.PointerUpdateKind == PointerUpdateKind.RightButtonReleased)
{ {
if (c.ContextFlyout != null) Logger.TryGet(LogEventLevel.Verbose, "FlyoutBase")?.Log(control, "ContextMenu and ContextFlyout are both set, defaulting to ContextMenu");
{ return;
if (c.ContextMenu != null)
{
Logger.TryGet(LogEventLevel.Verbose, "FlyoutBase")?.Log(c, "ContextMenu and ContextFlyout are both set, defaulting to ContextMenu");
return;
}
c.ContextFlyout.ShowAt(c, true);
}
} }
}
// We do not support absolute popup positioning yet, so we ignore "point" at this moment.
var triggeredByPointerInput = e.TryGetPosition(null, out _);
e.Handled = flyout.ShowAtCore(control, triggeredByPointerInput);
}
}
private bool CancelClosing()
{
var eventArgs = new CancelEventArgs();
OnClosing(eventArgs);
return eventArgs.Cancel;
}
private bool CancelOpening()
{
var eventArgs = new CancelEventArgs();
OnOpening(eventArgs);
return eventArgs.Cancel;
} }
internal static void SetPresenterClasses(IControl presenter, Classes classes) internal static void SetPresenterClasses(IControl presenter, Classes classes)

2
src/Avalonia.Controls/Flyouts/FlyoutShowMode.cs

@ -12,7 +12,7 @@
Standard, Standard,
/// <summary> /// <summary>
/// Behavior is typical of a flyout shown proactively. The open flyout does not take focus. For a CommandBarFlyout, it opens in it's collapsed state. /// Behavior is typical of a flyout shown proactively. The open flyout does not take focus.
/// </summary> /// </summary>
Transient, Transient,

10
src/Avalonia.Controls/Flyouts/MenuFlyoutPresenter.cs

@ -29,16 +29,8 @@ namespace Avalonia.Controls
var host = this.FindLogicalAncestorOfType<Popup>(); var host = this.FindLogicalAncestorOfType<Popup>();
if (host != null) if (host != null)
{ {
for (int i = 0; i < LogicalChildren.Count; i++)
{
if (LogicalChildren[i] is MenuItem item)
{
item.IsSubMenuOpen = false;
}
}
SelectedIndex = -1; SelectedIndex = -1;
host.IsOpen = false; host.IsOpen = false;
} }
} }

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;

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

16
src/Avalonia.Controls/Platform/IPlatformLifetimeEventsImpl.cs

@ -0,0 +1,16 @@
using System;
using System.ComponentModel;
namespace Avalonia.Platform
{
public interface IPlatformLifetimeEventsImpl
{
/// <summary>
/// Raised by the platform when a shutdown is requested.
/// </summary>
/// <remarks>
/// Raised on on OSX via the Quit menu or right-clicking on the application icon and selecting Quit.
/// </remarks>
event EventHandler<CancelEventArgs> ShutdownRequested;
}
}

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>

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

10
src/Avalonia.Controls/Primitives/Popup.cs

@ -1,4 +1,5 @@
using System; using System;
using System.ComponentModel;
using System.Linq; using System.Linq;
using System.Reactive.Disposables; using System.Reactive.Disposables;
using Avalonia.Controls.Presenters; using Avalonia.Controls.Presenters;
@ -154,6 +155,8 @@ namespace Avalonia.Controls.Primitives
/// </summary> /// </summary>
public event EventHandler? Opened; public event EventHandler? Opened;
internal event EventHandler<CancelEventArgs>? Closing;
public IPopupHost? Host => _openState?.PopupHost; public IPopupHost? Host => _openState?.PopupHost;
public bool WindowManagerAddShadowHint public bool WindowManagerAddShadowHint
@ -567,6 +570,13 @@ namespace Avalonia.Controls.Primitives
private void CloseCore() private void CloseCore()
{ {
var closingArgs = new CancelEventArgs();
Closing?.Invoke(this, closingArgs);
if (closingArgs.Cancel)
{
return;
}
_isOpenRequested = false; _isOpenRequested = false;
if (_openState is null) if (_openState is null)
{ {

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), false);
/// <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)

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

23
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; }
@ -1098,6 +1098,11 @@ namespace Avalonia.Controls
private bool MoveVertical(int count) private bool MoveVertical(int count)
{ {
if (_presenter is null)
{
return false;
}
var formattedText = _presenter.FormattedText; var formattedText = _presenter.FormattedText;
var lines = formattedText.GetLines().ToList(); var lines = formattedText.GetLines().ToList();
var caretIndex = CaretIndex; var caretIndex = CaretIndex;
@ -1113,14 +1118,17 @@ namespace Avalonia.Controls
CaretIndex = hit.TextPosition + (hit.IsTrailing ? 1 : 0); CaretIndex = hit.TextPosition + (hit.IsTrailing ? 1 : 0);
return true; return true;
} }
else
{ return false;
return false;
}
} }
private void MoveHome(bool document) private void MoveHome(bool document)
{ {
if (_presenter is null)
{
return;
}
var text = Text ?? string.Empty; var text = Text ?? string.Empty;
var caretIndex = CaretIndex; var caretIndex = CaretIndex;
@ -1151,6 +1159,11 @@ namespace Avalonia.Controls
private void MoveEnd(bool document) private void MoveEnd(bool document)
{ {
if (_presenter is null)
{
return;
}
var text = Text ?? string.Empty; var text = Text ?? string.Empty;
var caretIndex = CaretIndex; var caretIndex = CaretIndex;

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

15
src/Avalonia.Input/Platform/PlatformHotkeyConfiguration.cs

@ -1,14 +1,16 @@
using System.Collections.Generic; using System.Collections.Generic;
#nullable enable
namespace Avalonia.Input.Platform namespace Avalonia.Input.Platform
{ {
public class PlatformHotkeyConfiguration public class PlatformHotkeyConfiguration
{ {
public PlatformHotkeyConfiguration() : this(KeyModifiers.Control) public PlatformHotkeyConfiguration() : this(KeyModifiers.Control)
{ {
} }
public PlatformHotkeyConfiguration(KeyModifiers commandModifiers, public PlatformHotkeyConfiguration(KeyModifiers commandModifiers,
KeyModifiers selectionModifiers = KeyModifiers.Shift, KeyModifiers selectionModifiers = KeyModifiers.Shift,
KeyModifiers wholeWordTextActionModifiers = KeyModifiers.Control) KeyModifiers wholeWordTextActionModifiers = KeyModifiers.Control)
@ -73,8 +75,12 @@ namespace Avalonia.Input.Platform
{ {
new KeyGesture(Key.End, commandModifiers | selectionModifiers) new KeyGesture(Key.End, commandModifiers | selectionModifiers)
}; };
OpenContextMenu = new List<KeyGesture>
{
new KeyGesture(Key.Apps)
};
} }
public KeyModifiers CommandModifiers { get; set; } public KeyModifiers CommandModifiers { get; set; }
public KeyModifiers WholeWordTextActionModifiers { get; set; } public KeyModifiers WholeWordTextActionModifiers { get; set; }
public KeyModifiers SelectionModifiers { get; set; } public KeyModifiers SelectionModifiers { get; set; }
@ -92,7 +98,6 @@ namespace Avalonia.Input.Platform
public List<KeyGesture> MoveCursorToTheEndOfLineWithSelection { get; set; } public List<KeyGesture> MoveCursorToTheEndOfLineWithSelection { get; set; }
public List<KeyGesture> MoveCursorToTheStartOfDocumentWithSelection { get; set; } public List<KeyGesture> MoveCursorToTheStartOfDocumentWithSelection { get; set; }
public List<KeyGesture> MoveCursorToTheEndOfDocumentWithSelection { get; set; } public List<KeyGesture> MoveCursorToTheEndOfDocumentWithSelection { get; set; }
public List<KeyGesture> OpenContextMenu { get; set; }
} }
} }

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

13
src/Avalonia.Native/AvaloniaNativeApplicationPlatform.cs

@ -1,14 +1,25 @@
using System; using System;
using System.ComponentModel;
using Avalonia.Native.Interop; using Avalonia.Native.Interop;
using Avalonia.Platform; using Avalonia.Platform;
namespace Avalonia.Native namespace Avalonia.Native
{ {
internal class AvaloniaNativeApplicationPlatform : CallbackBase, IAvnApplicationEvents internal class AvaloniaNativeApplicationPlatform : CallbackBase, IAvnApplicationEvents, IPlatformLifetimeEventsImpl
{ {
public event EventHandler<CancelEventArgs> ShutdownRequested;
void IAvnApplicationEvents.FilesOpened(IAvnStringArray urls) void IAvnApplicationEvents.FilesOpened(IAvnStringArray urls)
{ {
((IApplicationPlatformEvents)Application.Current).RaiseUrlsOpened(urls.ToStringArray()); ((IApplicationPlatformEvents)Application.Current).RaiseUrlsOpened(urls.ToStringArray());
} }
public int TryShutdown()
{
if (ShutdownRequested is null) return 1;
var e = new CancelEventArgs();
ShutdownRequested(this, e);
return (!e.Cancel).AsComBool();
}
} }
} }

3
src/Avalonia.Native/AvaloniaNativePlatform.cs

@ -111,7 +111,8 @@ namespace Avalonia.Native
.Bind<ISystemDialogImpl>().ToConstant(new SystemDialogs(_factory.CreateSystemDialogs())) .Bind<ISystemDialogImpl>().ToConstant(new SystemDialogs(_factory.CreateSystemDialogs()))
.Bind<PlatformHotkeyConfiguration>().ToConstant(new PlatformHotkeyConfiguration(KeyModifiers.Meta)) .Bind<PlatformHotkeyConfiguration>().ToConstant(new PlatformHotkeyConfiguration(KeyModifiers.Meta))
.Bind<IMountedVolumeInfoProvider>().ToConstant(new MacOSMountedVolumeInfoProvider()) .Bind<IMountedVolumeInfoProvider>().ToConstant(new MacOSMountedVolumeInfoProvider())
.Bind<IPlatformDragSource>().ToConstant(new AvaloniaNativeDragSource(_factory)); .Bind<IPlatformDragSource>().ToConstant(new AvaloniaNativeDragSource(_factory))
.Bind<IPlatformLifetimeEventsImpl>().ToConstant(applicationPlatform);
if (_options.UseGpu) if (_options.UseGpu)
{ {

2
src/Avalonia.Native/WindowImpl.cs

@ -114,7 +114,7 @@ namespace Avalonia.Native
{ {
var visual = (_inputRoot as Window).Renderer.HitTestFirst(e.Position, _inputRoot as Window, x => var visual = (_inputRoot as Window).Renderer.HitTestFirst(e.Position, _inputRoot as Window, x =>
{ {
if (x is IInputElement ie && !ie.IsHitTestVisible) if (x is IInputElement ie && (!ie.IsHitTestVisible || !ie.IsVisible))
{ {
return false; return false;
} }

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

2
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);
@ -732,4 +733,5 @@ interface IAvnNativeControlHostTopLevelAttachment : IUnknown
interface IAvnApplicationEvents : IUnknown interface IAvnApplicationEvents : IUnknown
{ {
void FilesOpened (IAvnStringArray* urls); void FilesOpened (IAvnStringArray* urls);
bool TryShutdown();
} }

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

@ -4,7 +4,7 @@
<StreamGeometry x:Key="PasswordBoxRevealButtonData">m10.051 7.0032c2.215 0 4.0105 1.7901 4.0105 3.9984s-1.7956 3.9984-4.0105 3.9984c-2.215 0-4.0105-1.7901-4.0105-3.9984s1.7956-3.9984 4.0105-3.9984zm0 1.4994c-1.3844 0-2.5066 1.1188-2.5066 2.499s1.1222 2.499 2.5066 2.499 2.5066-1.1188 2.5066-2.499-1.1222-2.499-2.5066-2.499zm0-5.0026c4.6257 0 8.6188 3.1487 9.7267 7.5613 0.10085 0.40165-0.14399 0.80877-0.54686 0.90931-0.40288 0.10054-0.81122-0.14355-0.91208-0.54521-0.94136-3.7492-4.3361-6.4261-8.2678-6.4261-3.9334 0-7.3292 2.6792-8.2689 6.4306-0.10063 0.40171-0.50884 0.64603-0.91177 0.54571s-0.648-0.5073-0.54737-0.90901c1.106-4.4152 5.1003-7.5667 9.728-7.5667z</StreamGeometry> <StreamGeometry x:Key="PasswordBoxRevealButtonData">m10.051 7.0032c2.215 0 4.0105 1.7901 4.0105 3.9984s-1.7956 3.9984-4.0105 3.9984c-2.215 0-4.0105-1.7901-4.0105-3.9984s1.7956-3.9984 4.0105-3.9984zm0 1.4994c-1.3844 0-2.5066 1.1188-2.5066 2.499s1.1222 2.499 2.5066 2.499 2.5066-1.1188 2.5066-2.499-1.1222-2.499-2.5066-2.499zm0-5.0026c4.6257 0 8.6188 3.1487 9.7267 7.5613 0.10085 0.40165-0.14399 0.80877-0.54686 0.90931-0.40288 0.10054-0.81122-0.14355-0.91208-0.54521-0.94136-3.7492-4.3361-6.4261-8.2678-6.4261-3.9334 0-7.3292 2.6792-8.2689 6.4306-0.10063 0.40171-0.50884 0.64603-0.91177 0.54571s-0.648-0.5073-0.54737-0.90901c1.106-4.4152 5.1003-7.5667 9.728-7.5667z</StreamGeometry>
<StreamGeometry x:Key="PasswordBoxHideButtonData">m0.21967 0.21965c-0.26627 0.26627-0.29047 0.68293-0.07262 0.97654l0.07262 0.08412 4.0346 4.0346c-1.922 1.3495-3.3585 3.365-3.9554 5.7495-0.10058 0.4018 0.14362 0.8091 0.54543 0.9097 0.40182 0.1005 0.80909-0.1436 0.90968-0.5455 0.52947-2.1151 1.8371-3.8891 3.5802-5.0341l1.8096 1.8098c-0.70751 0.7215-1.1438 1.71-1.1438 2.8003 0 2.2092 1.7909 4 4 4 1.0904 0 2.0788-0.4363 2.8004-1.1438l5.9193 5.9195c0.2929 0.2929 0.7677 0.2929 1.0606 0 0.2663-0.2662 0.2905-0.6829 0.0726-0.9765l-0.0726-0.0841-6.1135-6.1142 0.0012-0.0015-1.2001-1.1979-2.8699-2.8693 2e-3 -8e-4 -2.8812-2.8782 0.0012-0.0018-1.1333-1.1305-4.3064-4.3058c-0.29289-0.29289-0.76777-0.29289-1.0607 0zm7.9844 9.0458 3.5351 3.5351c-0.45 0.4358-1.0633 0.704-1.7392 0.704-1.3807 0-2.5-1.1193-2.5-2.5 0-0.6759 0.26824-1.2892 0.7041-1.7391zm1.7959-5.7655c-1.0003 0-1.9709 0.14807-2.8889 0.425l1.237 1.2362c0.5358-0.10587 1.0883-0.16119 1.6519-0.16119 3.9231 0 7.3099 2.6803 8.2471 6.4332 0.1004 0.4018 0.5075 0.6462 0.9094 0.5459 0.4019-0.1004 0.6463-0.5075 0.5459-0.9094-1.103-4.417-5.0869-7.5697-9.7024-7.5697zm0.1947 3.5093 3.8013 3.8007c-0.1018-2.0569-1.7488-3.7024-3.8013-3.8007z</StreamGeometry> <StreamGeometry x:Key="PasswordBoxHideButtonData">m0.21967 0.21965c-0.26627 0.26627-0.29047 0.68293-0.07262 0.97654l0.07262 0.08412 4.0346 4.0346c-1.922 1.3495-3.3585 3.365-3.9554 5.7495-0.10058 0.4018 0.14362 0.8091 0.54543 0.9097 0.40182 0.1005 0.80909-0.1436 0.90968-0.5455 0.52947-2.1151 1.8371-3.8891 3.5802-5.0341l1.8096 1.8098c-0.70751 0.7215-1.1438 1.71-1.1438 2.8003 0 2.2092 1.7909 4 4 4 1.0904 0 2.0788-0.4363 2.8004-1.1438l5.9193 5.9195c0.2929 0.2929 0.7677 0.2929 1.0606 0 0.2663-0.2662 0.2905-0.6829 0.0726-0.9765l-0.0726-0.0841-6.1135-6.1142 0.0012-0.0015-1.2001-1.1979-2.8699-2.8693 2e-3 -8e-4 -2.8812-2.8782 0.0012-0.0018-1.1333-1.1305-4.3064-4.3058c-0.29289-0.29289-0.76777-0.29289-1.0607 0zm7.9844 9.0458 3.5351 3.5351c-0.45 0.4358-1.0633 0.704-1.7392 0.704-1.3807 0-2.5-1.1193-2.5-2.5 0-0.6759 0.26824-1.2892 0.7041-1.7391zm1.7959-5.7655c-1.0003 0-1.9709 0.14807-2.8889 0.425l1.237 1.2362c0.5358-0.10587 1.0883-0.16119 1.6519-0.16119 3.9231 0 7.3099 2.6803 8.2471 6.4332 0.1004 0.4018 0.5075 0.6462 0.9094 0.5459 0.4019-0.1004 0.6463-0.5075 0.5459-0.9094-1.103-4.417-5.0869-7.5697-9.7024-7.5697zm0.1947 3.5093 3.8013 3.8007c-0.1018-2.0569-1.7488-3.7024-3.8013-3.8007z</StreamGeometry>
<MenuFlyout x:Key="DefaultTextBoxContextFlyout"> <MenuFlyout x:Key="DefaultTextBoxContextFlyout" Placement="Bottom">
<MenuItem x:Name="TextBoxContextFlyoutCutItem" Header="Cut" Command="{Binding $parent[TextBox].Cut}" IsEnabled="{Binding $parent[TextBox].CanCut}" InputGesture="{x:Static TextBox.CutGesture}" /> <MenuItem x:Name="TextBoxContextFlyoutCutItem" Header="Cut" Command="{Binding $parent[TextBox].Cut}" IsEnabled="{Binding $parent[TextBox].CanCut}" InputGesture="{x:Static TextBox.CutGesture}" />
<MenuItem x:Name="TextBoxContextFlyoutCopyItem" Header="Copy" Command="{Binding $parent[TextBox].Copy}" IsEnabled="{Binding $parent[TextBox].CanCopy}" InputGesture="{x:Static TextBox.CopyGesture}"/> <MenuItem x:Name="TextBoxContextFlyoutCopyItem" Header="Copy" Command="{Binding $parent[TextBox].Copy}" IsEnabled="{Binding $parent[TextBox].CanCopy}" InputGesture="{x:Static TextBox.CopyGesture}"/>
<MenuItem x:Name="TextBoxContextFlyoutPasteItem" Header="Paste" Command="{Binding $parent[TextBox].Paste}" IsEnabled="{Binding $parent[TextBox].CanPaste}" InputGesture="{x:Static TextBox.PasteGesture}"/> <MenuItem x:Name="TextBoxContextFlyoutPasteItem" Header="Paste" Command="{Binding $parent[TextBox].Paste}" IsEnabled="{Binding $parent[TextBox].CanPaste}" InputGesture="{x:Static TextBox.PasteGesture}"/>

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

@ -15,7 +15,7 @@
<StreamGeometry x:Key="PasswordBoxRevealButtonData">m10.051 7.0032c2.215 0 4.0105 1.7901 4.0105 3.9984s-1.7956 3.9984-4.0105 3.9984c-2.215 0-4.0105-1.7901-4.0105-3.9984s1.7956-3.9984 4.0105-3.9984zm0 1.4994c-1.3844 0-2.5066 1.1188-2.5066 2.499s1.1222 2.499 2.5066 2.499 2.5066-1.1188 2.5066-2.499-1.1222-2.499-2.5066-2.499zm0-5.0026c4.6257 0 8.6188 3.1487 9.7267 7.5613 0.10085 0.40165-0.14399 0.80877-0.54686 0.90931-0.40288 0.10054-0.81122-0.14355-0.91208-0.54521-0.94136-3.7492-4.3361-6.4261-8.2678-6.4261-3.9334 0-7.3292 2.6792-8.2689 6.4306-0.10063 0.40171-0.50884 0.64603-0.91177 0.54571s-0.648-0.5073-0.54737-0.90901c1.106-4.4152 5.1003-7.5667 9.728-7.5667z</StreamGeometry> <StreamGeometry x:Key="PasswordBoxRevealButtonData">m10.051 7.0032c2.215 0 4.0105 1.7901 4.0105 3.9984s-1.7956 3.9984-4.0105 3.9984c-2.215 0-4.0105-1.7901-4.0105-3.9984s1.7956-3.9984 4.0105-3.9984zm0 1.4994c-1.3844 0-2.5066 1.1188-2.5066 2.499s1.1222 2.499 2.5066 2.499 2.5066-1.1188 2.5066-2.499-1.1222-2.499-2.5066-2.499zm0-5.0026c4.6257 0 8.6188 3.1487 9.7267 7.5613 0.10085 0.40165-0.14399 0.80877-0.54686 0.90931-0.40288 0.10054-0.81122-0.14355-0.91208-0.54521-0.94136-3.7492-4.3361-6.4261-8.2678-6.4261-3.9334 0-7.3292 2.6792-8.2689 6.4306-0.10063 0.40171-0.50884 0.64603-0.91177 0.54571s-0.648-0.5073-0.54737-0.90901c1.106-4.4152 5.1003-7.5667 9.728-7.5667z</StreamGeometry>
<StreamGeometry x:Key="PasswordBoxHideButtonData">m0.21967 0.21965c-0.26627 0.26627-0.29047 0.68293-0.07262 0.97654l0.07262 0.08412 4.0346 4.0346c-1.922 1.3495-3.3585 3.365-3.9554 5.7495-0.10058 0.4018 0.14362 0.8091 0.54543 0.9097 0.40182 0.1005 0.80909-0.1436 0.90968-0.5455 0.52947-2.1151 1.8371-3.8891 3.5802-5.0341l1.8096 1.8098c-0.70751 0.7215-1.1438 1.71-1.1438 2.8003 0 2.2092 1.7909 4 4 4 1.0904 0 2.0788-0.4363 2.8004-1.1438l5.9193 5.9195c0.2929 0.2929 0.7677 0.2929 1.0606 0 0.2663-0.2662 0.2905-0.6829 0.0726-0.9765l-0.0726-0.0841-6.1135-6.1142 0.0012-0.0015-1.2001-1.1979-2.8699-2.8693 2e-3 -8e-4 -2.8812-2.8782 0.0012-0.0018-1.1333-1.1305-4.3064-4.3058c-0.29289-0.29289-0.76777-0.29289-1.0607 0zm7.9844 9.0458 3.5351 3.5351c-0.45 0.4358-1.0633 0.704-1.7392 0.704-1.3807 0-2.5-1.1193-2.5-2.5 0-0.6759 0.26824-1.2892 0.7041-1.7391zm1.7959-5.7655c-1.0003 0-1.9709 0.14807-2.8889 0.425l1.237 1.2362c0.5358-0.10587 1.0883-0.16119 1.6519-0.16119 3.9231 0 7.3099 2.6803 8.2471 6.4332 0.1004 0.4018 0.5075 0.6462 0.9094 0.5459 0.4019-0.1004 0.6463-0.5075 0.5459-0.9094-1.103-4.417-5.0869-7.5697-9.7024-7.5697zm0.1947 3.5093 3.8013 3.8007c-0.1018-2.0569-1.7488-3.7024-3.8013-3.8007z</StreamGeometry> <StreamGeometry x:Key="PasswordBoxHideButtonData">m0.21967 0.21965c-0.26627 0.26627-0.29047 0.68293-0.07262 0.97654l0.07262 0.08412 4.0346 4.0346c-1.922 1.3495-3.3585 3.365-3.9554 5.7495-0.10058 0.4018 0.14362 0.8091 0.54543 0.9097 0.40182 0.1005 0.80909-0.1436 0.90968-0.5455 0.52947-2.1151 1.8371-3.8891 3.5802-5.0341l1.8096 1.8098c-0.70751 0.7215-1.1438 1.71-1.1438 2.8003 0 2.2092 1.7909 4 4 4 1.0904 0 2.0788-0.4363 2.8004-1.1438l5.9193 5.9195c0.2929 0.2929 0.7677 0.2929 1.0606 0 0.2663-0.2662 0.2905-0.6829 0.0726-0.9765l-0.0726-0.0841-6.1135-6.1142 0.0012-0.0015-1.2001-1.1979-2.8699-2.8693 2e-3 -8e-4 -2.8812-2.8782 0.0012-0.0018-1.1333-1.1305-4.3064-4.3058c-0.29289-0.29289-0.76777-0.29289-1.0607 0zm7.9844 9.0458 3.5351 3.5351c-0.45 0.4358-1.0633 0.704-1.7392 0.704-1.3807 0-2.5-1.1193-2.5-2.5 0-0.6759 0.26824-1.2892 0.7041-1.7391zm1.7959-5.7655c-1.0003 0-1.9709 0.14807-2.8889 0.425l1.237 1.2362c0.5358-0.10587 1.0883-0.16119 1.6519-0.16119 3.9231 0 7.3099 2.6803 8.2471 6.4332 0.1004 0.4018 0.5075 0.6462 0.9094 0.5459 0.4019-0.1004 0.6463-0.5075 0.5459-0.9094-1.103-4.417-5.0869-7.5697-9.7024-7.5697zm0.1947 3.5093 3.8013 3.8007c-0.1018-2.0569-1.7488-3.7024-3.8013-3.8007z</StreamGeometry>
<MenuFlyout x:Key="DefaultTextBoxContextFlyout"> <MenuFlyout x:Key="DefaultTextBoxContextFlyout" Placement="Bottom">
<MenuItem x:Name="TextBoxContextFlyoutCutItem" Header="Cut" Command="{Binding $parent[TextBox].Cut}" IsEnabled="{Binding $parent[TextBox].CanCut}" InputGesture="{x:Static TextBox.CutGesture}" /> <MenuItem x:Name="TextBoxContextFlyoutCutItem" Header="Cut" Command="{Binding $parent[TextBox].Cut}" IsEnabled="{Binding $parent[TextBox].CanCut}" InputGesture="{x:Static TextBox.CutGesture}" />
<MenuItem x:Name="TextBoxContextFlyoutCopyItem" Header="Copy" Command="{Binding $parent[TextBox].Copy}" IsEnabled="{Binding $parent[TextBox].CanCopy}" InputGesture="{x:Static TextBox.CopyGesture}"/> <MenuItem x:Name="TextBoxContextFlyoutCopyItem" Header="Copy" Command="{Binding $parent[TextBox].Copy}" IsEnabled="{Binding $parent[TextBox].CanCopy}" InputGesture="{x:Static TextBox.CopyGesture}"/>
<MenuItem x:Name="TextBoxContextFlyoutPasteItem" Header="Paste" Command="{Binding $parent[TextBox].Paste}" IsEnabled="{Binding $parent[TextBox].CanPaste}" InputGesture="{x:Static TextBox.PasteGesture}"/> <MenuItem x:Name="TextBoxContextFlyoutPasteItem" Header="Paste" Command="{Binding $parent[TextBox].Paste}" IsEnabled="{Binding $parent[TextBox].CanPaste}" InputGesture="{x:Static TextBox.PasteGesture}"/>
@ -76,9 +76,7 @@
IsVisible="{TemplateBinding Text, Converter={x:Static StringConverters.IsNullOrEmpty}}" IsVisible="{TemplateBinding Text, Converter={x:Static StringConverters.IsNullOrEmpty}}"
HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}" HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}"
VerticalAlignment="{TemplateBinding VerticalContentAlignment}"/> VerticalAlignment="{TemplateBinding VerticalContentAlignment}"/>
<!-- TODO eliminate this margin... text layout issue? -->
<TextPresenter Name="PART_TextPresenter" <TextPresenter Name="PART_TextPresenter"
Margin="0 1 0 0"
Text="{TemplateBinding Text, Mode=TwoWay}" Text="{TemplateBinding Text, Mode=TwoWay}"
CaretIndex="{TemplateBinding CaretIndex}" CaretIndex="{TemplateBinding CaretIndex}"
SelectionStart="{TemplateBinding SelectionStart}" SelectionStart="{TemplateBinding SelectionStart}"

138
src/Avalonia.Visuals/Animation/Animators/BaseBrushAnimator.cs

@ -1,9 +1,12 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Reactive.Disposables; using System.Diagnostics.CodeAnalysis;
using Avalonia.Logging; using Avalonia.Logging;
using Avalonia.Media; using Avalonia.Media;
#nullable enable
namespace Avalonia.Animation.Animators namespace Avalonia.Animation.Animators
{ {
/// <summary> /// <summary>
@ -12,10 +15,8 @@ namespace Avalonia.Animation.Animators
/// redirect them to the properly registered /// redirect them to the properly registered
/// animators in this class. /// animators in this class.
/// </summary> /// </summary>
public class BaseBrushAnimator : Animator<IBrush> public class BaseBrushAnimator : Animator<IBrush?>
{ {
private IAnimator _targetAnimator;
private static readonly List<(Func<Type, bool> Match, Type AnimatorType)> _brushAnimators = private static readonly List<(Func<Type, bool> Match, Type AnimatorType)> _brushAnimators =
new List<(Func<Type, bool> Match, Type AnimatorType)>(); new List<(Func<Type, bool> Match, Type AnimatorType)>();
@ -31,7 +32,7 @@ namespace Avalonia.Animation.Animators
/// The type of the animator to instantiate. /// The type of the animator to instantiate.
/// </typeparam> /// </typeparam>
public static void RegisterBrushAnimator<TAnimator>(Func<Type, bool> condition) public static void RegisterBrushAnimator<TAnimator>(Func<Type, bool> condition)
where TAnimator : IAnimator where TAnimator : IAnimator, new()
{ {
_brushAnimators.Insert(0, (condition, typeof(TAnimator))); _brushAnimators.Insert(0, (condition, typeof(TAnimator)));
} }
@ -40,30 +41,127 @@ namespace Avalonia.Animation.Animators
public override IDisposable Apply(Animation animation, Animatable control, IClock clock, public override IDisposable Apply(Animation animation, Animatable control, IClock clock,
IObservable<bool> match, Action onComplete) IObservable<bool> match, Action onComplete)
{ {
foreach (var valueType in _brushAnimators) if (TryCreateCustomRegisteredAnimator(out var animator)
|| TryCreateGradientAnimator(out animator)
|| TryCreateSolidColorBrushAnimator(out animator))
{ {
if (!valueType.Match(this[0].Value.GetType())) continue; return animator.Apply(animation, control, clock, match, onComplete);
}
Logger.TryGet(LogEventLevel.Error, LogArea.Animations)?.Log(
this,
"The animation's keyframe value types set is not supported.");
return base.Apply(animation, control, clock, match, onComplete);
}
_targetAnimator = (IAnimator)Activator.CreateInstance(valueType.AnimatorType); /// <summary>
/// Fallback implementation of <see cref="IBrush"/> animation.
/// </summary>
public override IBrush? Interpolate(double progress, IBrush? oldValue, IBrush? newValue) => progress >= 0.5 ? newValue : oldValue;
foreach (var keyframe in this) private bool TryCreateGradientAnimator([NotNullWhen(true)] out IAnimator? animator)
{
IGradientBrush? firstGradient = null;
foreach (var keyframe in this)
{
if (keyframe.Value is IGradientBrush gradientBrush)
{ {
_targetAnimator.Add(keyframe); firstGradient = gradientBrush;
break;
} }
}
_targetAnimator.Property = this.Property; if (firstGradient is null)
{
return _targetAnimator.Apply(animation, control, clock, match, onComplete); animator = null;
return false;
} }
Logger.TryGet(LogEventLevel.Error, LogArea.Animations)?.Log( var gradientAnimator = new GradientBrushAnimator();
this, gradientAnimator.Property = Property;
"The animation's keyframe values didn't match any brush animators registered in BaseBrushAnimator.");
foreach (var keyframe in this)
return Disposable.Empty; {
if (keyframe.Value is ISolidColorBrush solidColorBrush)
{
gradientAnimator.Add(new AnimatorKeyFrame(typeof(GradientBrushAnimator), keyframe.Cue, keyframe.KeySpline)
{
Value = GradientBrushAnimator.ConvertSolidColorBrushToGradient(firstGradient, solidColorBrush)
});
}
else if (keyframe.Value is IGradientBrush)
{
gradientAnimator.Add(new AnimatorKeyFrame(typeof(GradientBrushAnimator), keyframe.Cue, keyframe.KeySpline)
{
Value = keyframe.Value
});
}
else
{
animator = null;
return false;
}
}
animator = gradientAnimator;
return true;
} }
/// <inheritdoc/> private bool TryCreateSolidColorBrushAnimator([NotNullWhen(true)] out IAnimator? animator)
public override IBrush Interpolate(double progress, IBrush oldValue, IBrush newValue) => null; {
var solidColorBrushAnimator = new ISolidColorBrushAnimator();
solidColorBrushAnimator.Property = Property;
foreach (var keyframe in this)
{
if (keyframe.Value is ISolidColorBrush)
{
solidColorBrushAnimator.Add(new AnimatorKeyFrame(typeof(ISolidColorBrushAnimator), keyframe.Cue, keyframe.KeySpline)
{
Value = keyframe.Value
});
}
else
{
animator = null;
return false;
}
}
animator = solidColorBrushAnimator;
return true;
}
private bool TryCreateCustomRegisteredAnimator([NotNullWhen(true)] out IAnimator? animator)
{
if (_brushAnimators.Count > 0)
{
var firstKeyType = this[0].Value.GetType();
foreach (var (match, animatorType) in _brushAnimators)
{
if (!match(firstKeyType))
continue;
animator = (IAnimator)Activator.CreateInstance(animatorType);
if (animator != null)
{
animator.Property = Property;
foreach (var keyframe in this)
{
animator.Add(new AnimatorKeyFrame(animatorType, keyframe.Cue, keyframe.KeySpline)
{
Value = keyframe.Value
});
}
return true;
}
}
}
animator = null;
return false;
}
} }
} }

123
src/Avalonia.Visuals/Animation/Animators/GradientBrushAnimator.cs

@ -0,0 +1,123 @@
using System;
using System.Collections.Generic;
using Avalonia.Data;
using Avalonia.Media;
using Avalonia.Media.Immutable;
#nullable enable
namespace Avalonia.Animation.Animators
{
/// <summary>
/// Animator that handles <see cref="SolidColorBrush"/> values.
/// </summary>
public class GradientBrushAnimator : Animator<IGradientBrush?>
{
private static readonly RelativePointAnimator s_relativePointAnimator = new RelativePointAnimator();
private static readonly DoubleAnimator s_doubleAnimator = new DoubleAnimator();
public override IGradientBrush? Interpolate(double progress, IGradientBrush? oldValue, IGradientBrush? newValue)
{
if (oldValue is null || newValue is null)
{
return progress >= 0.5 ? newValue : oldValue;
}
switch (oldValue)
{
case IRadialGradientBrush oldRadial when newValue is IRadialGradientBrush newRadial:
return new ImmutableRadialGradientBrush(
InterpolateStops(progress, oldValue.GradientStops, newValue.GradientStops),
s_doubleAnimator.Interpolate(progress, oldValue.Opacity, newValue.Opacity),
oldValue.SpreadMethod,
s_relativePointAnimator.Interpolate(progress, oldRadial.Center, newRadial.Center),
s_relativePointAnimator.Interpolate(progress, oldRadial.GradientOrigin, newRadial.GradientOrigin),
s_doubleAnimator.Interpolate(progress, oldRadial.Radius, newRadial.Radius));
case IConicGradientBrush oldConic when newValue is IConicGradientBrush newConic:
return new ImmutableConicGradientBrush(
InterpolateStops(progress, oldValue.GradientStops, newValue.GradientStops),
s_doubleAnimator.Interpolate(progress, oldValue.Opacity, newValue.Opacity),
oldValue.SpreadMethod,
s_relativePointAnimator.Interpolate(progress, oldConic.Center, newConic.Center),
s_doubleAnimator.Interpolate(progress, oldConic.Angle, newConic.Angle));
case ILinearGradientBrush oldLinear when newValue is ILinearGradientBrush newLinear:
return new ImmutableLinearGradientBrush(
InterpolateStops(progress, oldValue.GradientStops, newValue.GradientStops),
s_doubleAnimator.Interpolate(progress, oldValue.Opacity, newValue.Opacity),
oldValue.SpreadMethod,
s_relativePointAnimator.Interpolate(progress, oldLinear.StartPoint, newLinear.StartPoint),
s_relativePointAnimator.Interpolate(progress, oldLinear.EndPoint, newLinear.EndPoint));
default:
return progress >= 0.5 ? newValue : oldValue;
}
}
public override IDisposable BindAnimation(Animatable control, IObservable<IGradientBrush?> instance)
{
return control.Bind((AvaloniaProperty<IBrush?>)Property, instance, BindingPriority.Animation);
}
private IReadOnlyList<ImmutableGradientStop> InterpolateStops(double progress, IReadOnlyList<IGradientStop> oldValue, IReadOnlyList<IGradientStop> newValue)
{
var resultCount = Math.Max(oldValue.Count, newValue.Count);
var stops = new ImmutableGradientStop[resultCount];
for (int index = 0, oldIndex = 0, newIndex = 0; index < resultCount; index++)
{
stops[index] = new ImmutableGradientStop(
s_doubleAnimator.Interpolate(progress, oldValue[oldIndex].Offset, newValue[newIndex].Offset),
ColorAnimator.InterpolateCore(progress, oldValue[oldIndex].Color, newValue[newIndex].Color));
if (oldIndex < oldValue.Count - 1)
{
oldIndex++;
}
if (newIndex < newValue.Count - 1)
{
newIndex++;
}
}
return stops;
}
internal static IGradientBrush ConvertSolidColorBrushToGradient(IGradientBrush gradientBrush, ISolidColorBrush solidColorBrush)
{
switch (gradientBrush)
{
case IRadialGradientBrush oldRadial:
return new ImmutableRadialGradientBrush(
CreateStopsFromSolidColorBrush(solidColorBrush, oldRadial.GradientStops), solidColorBrush.Opacity,
oldRadial.SpreadMethod, oldRadial.Center, oldRadial.GradientOrigin, oldRadial.Radius);
case IConicGradientBrush oldConic:
return new ImmutableConicGradientBrush(
CreateStopsFromSolidColorBrush(solidColorBrush, oldConic.GradientStops), solidColorBrush.Opacity,
oldConic.SpreadMethod, oldConic.Center, oldConic.Angle);
case ILinearGradientBrush oldLinear:
return new ImmutableLinearGradientBrush(
CreateStopsFromSolidColorBrush(solidColorBrush, oldLinear.GradientStops), solidColorBrush.Opacity,
oldLinear.SpreadMethod, oldLinear.StartPoint, oldLinear.EndPoint);
default:
throw new NotSupportedException($"Gradient of type {gradientBrush?.GetType()} is not supported");
}
static IReadOnlyList<ImmutableGradientStop> CreateStopsFromSolidColorBrush(ISolidColorBrush solidColorBrush, IReadOnlyList<IGradientStop> baseStops)
{
var stops = new ImmutableGradientStop[baseStops.Count];
for (int index = 0; index < baseStops.Count; index++)
{
stops[index] = new ImmutableGradientStop(baseStops[index].Offset, solidColorBrush.Color);
}
return stops;
}
}
}
}

20
src/Avalonia.Visuals/Animation/Animators/RelativePointAnimator.cs

@ -0,0 +1,20 @@
namespace Avalonia.Animation.Animators
{
/// <summary>
/// Animator that handles <see cref="RelativePoint"/> properties.
/// </summary>
public class RelativePointAnimator : Animator<RelativePoint>
{
private static readonly PointAnimator s_pointAnimator = new PointAnimator();
public override RelativePoint Interpolate(double progress, RelativePoint oldValue, RelativePoint newValue)
{
if (oldValue.Unit != newValue.Unit)
{
return progress >= 0.5 ? newValue : oldValue;
}
return new RelativePoint(s_pointAnimator.Interpolate(progress, oldValue.Point, newValue.Point), oldValue.Unit);
}
}
}

20
src/Avalonia.Visuals/Animation/Animators/SolidColorBrushAnimator.cs

@ -3,37 +3,39 @@ using Avalonia.Data;
using Avalonia.Media; using Avalonia.Media;
using Avalonia.Media.Immutable; using Avalonia.Media.Immutable;
#nullable enable
namespace Avalonia.Animation.Animators namespace Avalonia.Animation.Animators
{ {
/// <summary> /// <summary>
/// Animator that handles <see cref="SolidColorBrush"/> values. /// Animator that handles <see cref="SolidColorBrush"/> values.
/// </summary> /// </summary>
public class ISolidColorBrushAnimator : Animator<ISolidColorBrush> public class ISolidColorBrushAnimator : Animator<ISolidColorBrush?>
{ {
public override ISolidColorBrush Interpolate(double progress, ISolidColorBrush oldValue, ISolidColorBrush newValue) public override ISolidColorBrush? Interpolate(double progress, ISolidColorBrush? oldValue, ISolidColorBrush? newValue)
{ {
if (oldValue is null || newValue is null) if (oldValue is null || newValue is null)
{ {
return oldValue; return progress >= 0.5 ? newValue : oldValue;
} }
return new ImmutableSolidColorBrush(ColorAnimator.InterpolateCore(progress, oldValue.Color, newValue.Color)); return new ImmutableSolidColorBrush(ColorAnimator.InterpolateCore(progress, oldValue.Color, newValue.Color));
} }
public override IDisposable BindAnimation(Animatable control, IObservable<ISolidColorBrush> instance) public override IDisposable BindAnimation(Animatable control, IObservable<ISolidColorBrush?> instance)
{ {
return control.Bind((AvaloniaProperty<IBrush>)Property, instance, BindingPriority.Animation); return control.Bind((AvaloniaProperty<IBrush?>)Property, instance, BindingPriority.Animation);
} }
} }
[Obsolete] [Obsolete("Use ISolidColorBrushAnimator instead")]
public class SolidColorBrushAnimator : Animator<SolidColorBrush> public class SolidColorBrushAnimator : Animator<SolidColorBrush?>
{ {
public override SolidColorBrush Interpolate(double progress, SolidColorBrush oldValue, SolidColorBrush newValue) public override SolidColorBrush? Interpolate(double progress, SolidColorBrush? oldValue, SolidColorBrush? newValue)
{ {
if (oldValue is null || newValue is null) if (oldValue is null || newValue is null)
{ {
return oldValue; return progress >= 0.5 ? newValue : oldValue;
} }
return new SolidColorBrush(ColorAnimator.InterpolateCore(progress, oldValue.Color, newValue.Color)); return new SolidColorBrush(ColorAnimator.InterpolateCore(progress, oldValue.Color, newValue.Color));

44
src/Avalonia.Visuals/Animation/Transitions/BrushTransition.cs

@ -1,4 +1,5 @@
using System; using System;
using Avalonia.Animation.Animators; using Avalonia.Animation.Animators;
using Avalonia.Animation.Easings; using Avalonia.Animation.Easings;
using Avalonia.Media; using Avalonia.Media;
@ -9,37 +10,46 @@ namespace Avalonia.Animation
{ {
/// <summary> /// <summary>
/// Transition class that handles <see cref="AvaloniaProperty"/> with <see cref="IBrush"/> type. /// Transition class that handles <see cref="AvaloniaProperty"/> with <see cref="IBrush"/> type.
/// Only values of <see cref="ISolidColorBrush"/> will transition correctly at the moment.
/// </summary> /// </summary>
public class BrushTransition : Transition<IBrush?> public class BrushTransition : Transition<IBrush?>
{ {
private static readonly ISolidColorBrushAnimator s_animator = new ISolidColorBrushAnimator(); private static readonly GradientBrushAnimator s_gradientAnimator = new GradientBrushAnimator();
private static readonly ISolidColorBrushAnimator s_solidColorBrushAnimator = new ISolidColorBrushAnimator();
public override IObservable<IBrush?> DoTransition(IObservable<double> progress, IBrush? oldValue, IBrush? newValue) public override IObservable<IBrush?> DoTransition(IObservable<double> progress, IBrush? oldValue, IBrush? newValue)
{ {
var oldSolidColorBrush = TryGetSolidColorBrush(oldValue); if (oldValue is null || newValue is null)
var newSolidColorBrush = TryGetSolidColorBrush(newValue);
if (oldSolidColorBrush != null && newSolidColorBrush != null)
{ {
return new AnimatorTransitionObservable<ISolidColorBrush, ISolidColorBrushAnimator>( return new IncompatibleTransitionObservable(progress, Easing, oldValue, newValue);
s_animator, progress, Easing, oldSolidColorBrush, newSolidColorBrush);
} }
return new IncompatibleTransitionObservable(progress, Easing, oldValue, newValue); if (oldValue is IGradientBrush oldGradient)
} {
if (newValue is IGradientBrush newGradient)
{
return new AnimatorTransitionObservable<IGradientBrush?, GradientBrushAnimator>(s_gradientAnimator, progress, Easing, oldGradient, newGradient);
}
else if (newValue is ISolidColorBrush newSolidColorBrushToConvert)
{
var convertedSolidColorBrush = GradientBrushAnimator.ConvertSolidColorBrushToGradient(oldGradient, newSolidColorBrushToConvert);
return new AnimatorTransitionObservable<IGradientBrush?, GradientBrushAnimator>(s_gradientAnimator, progress, Easing, oldGradient, convertedSolidColorBrush);
}
}
else if (newValue is IGradientBrush newGradient && oldValue is ISolidColorBrush oldSolidColorBrushToConvert)
{
var convertedSolidColorBrush = GradientBrushAnimator.ConvertSolidColorBrushToGradient(newGradient, oldSolidColorBrushToConvert);
return new AnimatorTransitionObservable<IGradientBrush?, GradientBrushAnimator>(s_gradientAnimator, progress, Easing, convertedSolidColorBrush, newGradient);
}
private static ISolidColorBrush? TryGetSolidColorBrush(IBrush? brush) if (oldValue is ISolidColorBrush oldSolidColorBrush && newValue is ISolidColorBrush newSolidColorBrush)
{
if (brush is null)
{ {
return Brushes.Transparent; return new AnimatorTransitionObservable<ISolidColorBrush?, ISolidColorBrushAnimator>(s_solidColorBrushAnimator, progress, Easing, oldSolidColorBrush, newSolidColorBrush);
} }
return brush as ISolidColorBrush; return new IncompatibleTransitionObservable(progress, Easing, oldValue, newValue);
} }
private class IncompatibleTransitionObservable : TransitionObservableBase<IBrush?> private sealed class IncompatibleTransitionObservable : TransitionObservableBase<IBrush?>
{ {
private readonly IBrush? _from; private readonly IBrush? _from;
private readonly IBrush? _to; private readonly IBrush? _to;
@ -52,7 +62,7 @@ namespace Avalonia.Animation
protected override IBrush? ProduceValue(double progress) protected override IBrush? ProduceValue(double progress)
{ {
return progress < 0.5 ? _from : _to; return progress >= 0.5 ? _to : _from;
} }
} }
} }

11
src/Avalonia.Visuals/Animation/Transitions/RelativePointTransition.cs

@ -0,0 +1,11 @@
using Avalonia.Animation.Animators;
namespace Avalonia.Animation
{
/// <summary>
/// Transition class that handles <see cref="AvaloniaProperty"/> with <see cref="RelativePoint"/> type.
/// </summary>
public class RelativePointTransition : AnimatorDrivenTransition<RelativePoint, RelativePointAnimator>
{
}
}

3
src/Avalonia.Visuals/ApiCompatBaseline.txt

@ -5,6 +5,7 @@ InterfacesShouldHaveSameMembers : Interface member 'public System.Threading.Task
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. 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. InterfacesShouldHaveSameMembers : Interface member 'public System.Threading.Tasks.Task Avalonia.Animation.IPageTransition.Start(Avalonia.Visual, Avalonia.Visual, System.Boolean, System.Threading.CancellationToken)' is present in the implementation but not in the contract.
MembersMustExist : Member 'public System.Threading.Tasks.Task Avalonia.Animation.PageSlide.Start(Avalonia.Visual, Avalonia.Visual, System.Boolean)' does not exist in the implementation but it does exist in the contract. MembersMustExist : Member 'public 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.
@ -73,4 +74,4 @@ InterfacesShouldHaveSameMembers : Interface member 'public Avalonia.Platform.IWr
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: 74 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);

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

@ -12,6 +12,12 @@ namespace Avalonia.Media
public static readonly StyledProperty<Transform> TransformProperty = public static readonly StyledProperty<Transform> TransformProperty =
AvaloniaProperty.Register<DrawingGroup, Transform>(nameof(Transform)); AvaloniaProperty.Register<DrawingGroup, Transform>(nameof(Transform));
public static readonly StyledProperty<Geometry> ClipGeometryProperty =
AvaloniaProperty.Register<DrawingGroup, Geometry>(nameof(ClipGeometry));
public static readonly StyledProperty<IBrush> OpacityMaskProperty =
AvaloniaProperty.Register<DrawingGroup, IBrush>(nameof(OpacityMask));
public double Opacity public double Opacity
{ {
get => GetValue(OpacityProperty); get => GetValue(OpacityProperty);
@ -24,6 +30,18 @@ namespace Avalonia.Media
set => SetValue(TransformProperty, value); set => SetValue(TransformProperty, value);
} }
public Geometry ClipGeometry
{
get => GetValue(ClipGeometryProperty);
set => SetValue(ClipGeometryProperty, value);
}
public IBrush OpacityMask
{
get => GetValue(OpacityMaskProperty);
set => SetValue(OpacityMaskProperty, value);
}
[Content] [Content]
public AvaloniaList<Drawing> Children { get; } = new AvaloniaList<Drawing>(); public AvaloniaList<Drawing> Children { get; } = new AvaloniaList<Drawing>();
@ -31,6 +49,8 @@ namespace Avalonia.Media
{ {
using (context.PushPreTransform(Transform?.Value ?? Matrix.Identity)) using (context.PushPreTransform(Transform?.Value ?? Matrix.Identity))
using (context.PushOpacity(Opacity)) using (context.PushOpacity(Opacity))
using (ClipGeometry != null ? context.PushGeometryClip(ClipGeometry) : default(DrawingContext.PushedState))
using (OpacityMask != null ? context.PushOpacityMask(OpacityMask, GetBounds()) : default(DrawingContext.PushedState))
{ {
foreach (var drawing in Children) foreach (var drawing in Children)
{ {

2
src/Avalonia.Visuals/Media/GlyphRun.cs

@ -582,7 +582,7 @@ namespace Avalonia.Media
{ {
var cluster = _glyphClusters[i]; var cluster = _glyphClusters[i];
var codepointIndex = cluster - _characters.Start; var codepointIndex = IsLeftToRight ? cluster - _characters.Start : _characters.End - cluster;
var codepoint = Codepoint.ReadAt(_characters, codepointIndex, out _); var codepoint = Codepoint.ReadAt(_characters, codepointIndex, out _);

2
src/Avalonia.Visuals/Media/GradientBrush.cs

@ -2,6 +2,8 @@ using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Collections.Specialized; using System.Collections.Specialized;
using System.ComponentModel; using System.ComponentModel;
using Avalonia.Animation.Animators;
using Avalonia.Collections; using Avalonia.Collections;
using Avalonia.Metadata; using Avalonia.Metadata;

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>

1
src/Avalonia.Visuals/Media/SolidColorBrush.cs

@ -16,7 +16,6 @@ namespace Avalonia.Media
static SolidColorBrush() static SolidColorBrush()
{ {
BaseBrushAnimator.RegisterBrushAnimator<ISolidColorBrushAnimator>(match => typeof(ISolidColorBrush).IsAssignableFrom(match));
AffectsRender<SolidColorBrush>(ColorProperty); AffectsRender<SolidColorBrush>(ColorProperty);
} }

87
src/Avalonia.Visuals/Media/TextFormatting/ShapedTextCharacters.cs

@ -90,7 +90,9 @@ namespace Avalonia.Media.TextFormatting
/// <returns>The split result.</returns> /// <returns>The split result.</returns>
public SplitTextCharactersResult Split(int length) public SplitTextCharactersResult Split(int length)
{ {
var glyphCount = GlyphRun.FindGlyphIndex(GlyphRun.Characters.Start + length); var glyphCount = GlyphRun.IsLeftToRight ?
GlyphRun.FindGlyphIndex(GlyphRun.Characters.Start + length) :
GlyphRun.FindGlyphIndex(GlyphRun.Characters.End - length);
if (GlyphRun.Characters.Length == length) if (GlyphRun.Characters.Length == length)
{ {
@ -102,31 +104,64 @@ namespace Avalonia.Media.TextFormatting
return new SplitTextCharactersResult(this, null); return new SplitTextCharactersResult(this, null);
} }
var firstGlyphRun = new GlyphRun( if (GlyphRun.IsLeftToRight)
Properties.Typeface.GlyphTypeface, {
Properties.FontRenderingEmSize, var firstGlyphRun = new GlyphRun(
GlyphRun.GlyphIndices.Take(glyphCount), Properties.Typeface.GlyphTypeface,
GlyphRun.GlyphAdvances.Take(glyphCount), Properties.FontRenderingEmSize,
GlyphRun.GlyphOffsets.Take(glyphCount), GlyphRun.GlyphIndices.Take(glyphCount),
GlyphRun.Characters.Take(length), GlyphRun.GlyphAdvances.Take(glyphCount),
GlyphRun.GlyphClusters.Take(glyphCount), GlyphRun.GlyphOffsets.Take(glyphCount),
GlyphRun.BiDiLevel); GlyphRun.Characters.Take(length),
GlyphRun.GlyphClusters.Take(glyphCount),
var firstTextRun = new ShapedTextCharacters(firstGlyphRun, Properties); GlyphRun.BiDiLevel);
var secondGlyphRun = new GlyphRun( var firstTextRun = new ShapedTextCharacters(firstGlyphRun, Properties);
Properties.Typeface.GlyphTypeface,
Properties.FontRenderingEmSize, var secondGlyphRun = new GlyphRun(
GlyphRun.GlyphIndices.Skip(glyphCount), Properties.Typeface.GlyphTypeface,
GlyphRun.GlyphAdvances.Skip(glyphCount), Properties.FontRenderingEmSize,
GlyphRun.GlyphOffsets.Skip(glyphCount), GlyphRun.GlyphIndices.Skip(glyphCount),
GlyphRun.Characters.Skip(length), GlyphRun.GlyphAdvances.Skip(glyphCount),
GlyphRun.GlyphClusters.Skip(glyphCount), GlyphRun.GlyphOffsets.Skip(glyphCount),
GlyphRun.BiDiLevel); GlyphRun.Characters.Skip(length),
GlyphRun.GlyphClusters.Skip(glyphCount),
var secondTextRun = new ShapedTextCharacters(secondGlyphRun, Properties); GlyphRun.BiDiLevel);
return new SplitTextCharactersResult(firstTextRun, secondTextRun); var secondTextRun = new ShapedTextCharacters(secondGlyphRun, Properties);
return new SplitTextCharactersResult(firstTextRun, secondTextRun);
}
else
{
var take = GlyphRun.GlyphIndices.Length - glyphCount;
var firstGlyphRun = new GlyphRun(
Properties.Typeface.GlyphTypeface,
Properties.FontRenderingEmSize,
GlyphRun.GlyphIndices.Take(take),
GlyphRun.GlyphAdvances.Take(take),
GlyphRun.GlyphOffsets.Take(take),
GlyphRun.Characters.Skip(length),
GlyphRun.GlyphClusters.Take(take),
GlyphRun.BiDiLevel);
var firstTextRun = new ShapedTextCharacters(firstGlyphRun, Properties);
var secondGlyphRun = new GlyphRun(
Properties.Typeface.GlyphTypeface,
Properties.FontRenderingEmSize,
GlyphRun.GlyphIndices.Skip(take),
GlyphRun.GlyphAdvances.Skip(take),
GlyphRun.GlyphOffsets.Skip(take),
GlyphRun.Characters.Take(length),
GlyphRun.GlyphClusters.Skip(take),
GlyphRun.BiDiLevel);
var secondTextRun = new ShapedTextCharacters(secondGlyphRun, Properties);
return new SplitTextCharactersResult(secondTextRun,firstTextRun);
}
} }
public readonly struct SplitTextCharactersResult public readonly struct SplitTextCharactersResult

9
src/Avalonia.Visuals/Media/TextFormatting/TextCharacters.cs

@ -134,7 +134,7 @@ namespace Avalonia.Media.TextFormatting
var isFallback = typeface != defaultTypeface; var isFallback = typeface != defaultTypeface;
count = 0; count = 0;
var script = Script.Common; var script = Script.Unknown;
var direction = BiDiClass.LeftToRight; var direction = BiDiClass.LeftToRight;
var font = typeface.GlyphTypeface; var font = typeface.GlyphTypeface;
@ -161,7 +161,7 @@ namespace Avalonia.Media.TextFormatting
if (currentScript != script) if (currentScript != script)
{ {
if (script == Script.Inherited || script == Script.Common) if (script is Script.Unknown)
{ {
script = currentScript; script = currentScript;
} }
@ -174,13 +174,16 @@ namespace Avalonia.Media.TextFormatting
} }
} }
if (currentScript != Script.Common && currentScript != Script.Inherited) //Only handle non whitespace here
if (!currentGrapheme.FirstCodepoint.IsWhiteSpace)
{ {
//Stop at the first glyph that is present in the default typeface.
if (isFallback && defaultFont.TryGetGlyph(currentGrapheme.FirstCodepoint, out _)) if (isFallback && defaultFont.TryGetGlyph(currentGrapheme.FirstCodepoint, out _))
{ {
break; break;
} }
//Stop at the first missing glyph
if (!font.TryGetGlyph(currentGrapheme.FirstCodepoint, out _)) if (!font.TryGetGlyph(currentGrapheme.FirstCodepoint, out _))
{ {
break; break;

86
src/Avalonia.Visuals/Media/TextFormatting/TextFormatterImpl.cs

@ -70,34 +70,74 @@ namespace Avalonia.Media.TextFormatting
{ {
var glyphTypeface = glyphRun.GlyphTypeface; var glyphTypeface = glyphRun.GlyphTypeface;
for (var i = 0; i < glyphRun.GlyphClusters.Length; i++) if (glyphRun.IsLeftToRight)
{ {
var glyph = glyphRun.GlyphIndices[i]; foreach (var glyph in glyphRun.GlyphIndices)
{
var advance = glyphTypeface.GetGlyphAdvance(glyph) * glyphRun.Scale;
var advance = glyphTypeface.GetGlyphAdvance(glyph) * glyphRun.Scale; if (currentWidth + advance > availableWidth)
{
break;
}
if (currentWidth + advance > availableWidth) currentWidth += advance;
{
break; glyphCount++;
} }
}
else
{
for (var index = glyphRun.GlyphClusters.Length - 1; index > 0; index--)
{
var glyph = glyphRun.GlyphIndices[index];
var advance = glyphTypeface.GetGlyphAdvance(glyph) * glyphRun.Scale;
currentWidth += advance; if (currentWidth + advance > availableWidth)
{
break;
}
glyphCount++; currentWidth += advance;
glyphCount++;
}
} }
} }
else else
{ {
foreach (var advance in glyphRun.GlyphAdvances) if (glyphRun.IsLeftToRight)
{ {
if (currentWidth + advance > availableWidth) for (var index = 0; index < glyphRun.GlyphAdvances.Length; index++)
{ {
break; var advance = glyphRun.GlyphAdvances[index];
if (currentWidth + advance > availableWidth)
{
break;
}
currentWidth += advance;
glyphCount++;
} }
}
else
{
for (var index = glyphRun.GlyphAdvances.Length - 1; index > 0; index--)
{
var advance = glyphRun.GlyphAdvances[index];
if (currentWidth + advance > availableWidth)
{
break;
}
currentWidth += advance; currentWidth += advance;
glyphCount++; glyphCount++;
}
} }
} }
@ -475,24 +515,14 @@ namespace Avalonia.Media.TextFormatting
var remainingCharacters = splitResult.Second; var remainingCharacters = splitResult.Second;
if (currentLineBreak?.RemainingCharacters != null) var lineBreak = remainingCharacters?.Count > 0 ? new TextLineBreak(remainingCharacters) : null;
if (lineBreak is null && currentLineBreak.TextEndOfLine != null)
{ {
if (remainingCharacters != null) lineBreak = new TextLineBreak(currentLineBreak.TextEndOfLine);
{
remainingCharacters.AddRange(currentLineBreak.RemainingCharacters);
}
else
{
remainingCharacters = new List<ShapedTextCharacters>(currentLineBreak.RemainingCharacters);
}
} }
var lineBreak = remainingCharacters != null && remainingCharacters.Count > 0 ? return new TextLineImpl(splitResult.First, textRange, paragraphWidth, paragraphProperties, lineBreak);
new TextLineBreak(remainingCharacters) :
null;
return new TextLineImpl(splitResult.First, textRange, paragraphWidth, paragraphProperties,
lineBreak);
} }
/// <summary> /// <summary>

7
src/Avalonia.Visuals/RelativePoint.cs

@ -1,5 +1,7 @@
using System; using System;
using System.Globalization; using System.Globalization;
using Avalonia.Animation.Animators;
using Avalonia.Utilities; using Avalonia.Utilities;
namespace Avalonia namespace Avalonia
@ -45,6 +47,11 @@ namespace Avalonia
private readonly RelativeUnit _unit; private readonly RelativeUnit _unit;
static RelativePoint()
{
Animation.Animation.RegisterAnimator<RelativePointAnimator>(prop => typeof(RelativePoint).IsAssignableFrom(prop.PropertyType));
}
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="RelativePoint"/> struct. /// Initializes a new instance of the <see cref="RelativePoint"/> struct.
/// </summary> /// </summary>

19
src/Avalonia.Visuals/Rendering/SceneGraph/SceneBuilder.cs

@ -258,14 +258,25 @@ namespace Avalonia.Rendering.SceneGraph
} }
else if (visualChildren.Count > 1) else if (visualChildren.Count > 1)
{ {
var sortedChildren = new IVisual[visualChildren.Count]; var count = visualChildren.Count;
visualChildren.CopyTo(sortedChildren, 0); var sortedChildren = new (IVisual visual, int index)[count];
Array.Sort(sortedChildren, ZIndexComparer.ComparisonInstance); for (var i = 0; i < count; i++)
{
sortedChildren[i] = (visualChildren[i], i);
}
// Regular Array.Sort is unstable, we need to provide indices as well to avoid reshuffling elements.
Array.Sort(sortedChildren, (lhs, rhs) =>
{
var result = ZIndexComparer.Instance.Compare(lhs.visual, rhs.visual);
return result == 0 ? lhs.index.CompareTo(rhs.index) : result;
});
foreach (var child in sortedChildren) foreach (var child in sortedChildren)
{ {
var childNode = GetOrCreateChildNode(scene, child, node); var childNode = GetOrCreateChildNode(scene, child.Item1, node);
Update(context, scene, (VisualNode)childNode, clip, forceRecurse); Update(context, scene, (VisualNode)childNode, clip, forceRecurse);
} }
} }

31
src/Avalonia.X11/X11Window.cs

@ -296,6 +296,30 @@ namespace Avalonia.X11
public Size ClientSize => new Size(_realSize.Width / RenderScaling, _realSize.Height / RenderScaling); public Size 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
@ -588,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;

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

@ -61,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;

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

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

Some files were not shown because too many files changed in this diff

Loading…
Cancel
Save