Browse Source

Merge branch 'master' into refactor/use-selectionmodel

fixes/tree-selectionmodel
Steven Kirk 6 years ago
parent
commit
885363d7a9
  1. 27
      Avalonia.sln
  2. 2
      build/SharedVersion.props
  3. 28
      native/Avalonia.Native/inc/avalonia-native.h
  4. 13
      native/Avalonia.Native/inc/comimpl.h
  5. 12
      native/Avalonia.Native/inc/rendertarget.h
  6. 20
      native/Avalonia.Native/src/OSX/Avalonia.Native.OSX.xcodeproj/project.pbxproj
  7. 166
      native/Avalonia.Native/src/OSX/cgl.mm
  8. 7
      native/Avalonia.Native/src/OSX/common.h
  9. 261
      native/Avalonia.Native/src/OSX/gl.mm
  10. 12
      native/Avalonia.Native/src/OSX/main.mm
  11. 284
      native/Avalonia.Native/src/OSX/rendertarget.mm
  12. 214
      native/Avalonia.Native/src/OSX/window.mm
  13. 1
      nukebuild/Build.cs
  14. 6
      readme.md
  15. 8
      samples/ControlCatalog/Pages/ProgressBarPage.xaml
  16. 9
      src/Avalonia.Base/AvaloniaObject.cs
  17. 43
      src/Avalonia.Base/AvaloniaProperty.cs
  18. 45
      src/Avalonia.Base/AvaloniaPropertyRegistry.cs
  19. 20
      src/Avalonia.Base/Collections/AvaloniaList.cs
  20. 15
      src/Avalonia.Base/DirectPropertyBase.cs
  21. 4
      src/Avalonia.Base/PropertyStore/BindingEntry.cs
  22. 5
      src/Avalonia.Base/PropertyStore/IValueSink.cs
  23. 5
      src/Avalonia.Base/PropertyStore/PriorityValue.cs
  24. 15
      src/Avalonia.Base/StyledPropertyBase.cs
  25. 32
      src/Avalonia.Base/Utilities/TypeUtilities.cs
  26. 94
      src/Avalonia.Base/Utilities/ValueSingleOrList.cs
  27. 9
      src/Avalonia.Base/Utilities/WeakEventHandlerManager.cs
  28. 9
      src/Avalonia.Base/ValueStore.cs
  29. 2
      src/Avalonia.Controls.DataGrid/Collections/DataGridSortDescription.cs
  30. 45
      src/Avalonia.Controls.DataGrid/DataGrid.cs
  31. 20
      src/Avalonia.Controls.DataGrid/DataGridColumnHeader.cs
  32. 2
      src/Avalonia.Controls.DataGrid/Properties/AssemblyInfo.cs
  33. 25
      src/Avalonia.Controls.DataGrid/Themes/Default.xaml
  34. 15
      src/Avalonia.Controls.DataGrid/Utils/KeyboardHelper.cs
  35. 2
      src/Avalonia.Controls/Application.cs
  36. 25
      src/Avalonia.Controls/Button.cs
  37. 28
      src/Avalonia.Controls/ButtonSpinner.cs
  38. 4
      src/Avalonia.Controls/Calendar/Calendar.cs
  39. 6
      src/Avalonia.Controls/Calendar/CalendarExtensions.cs
  40. 14
      src/Avalonia.Controls/Calendar/CalendarItem.cs
  41. 2
      src/Avalonia.Controls/Calendar/DatePicker.cs
  42. 2
      src/Avalonia.Controls/ComboBox.cs
  43. 20
      src/Avalonia.Controls/ControlExtensions.cs
  44. 41
      src/Avalonia.Controls/Expander.cs
  45. 44
      src/Avalonia.Controls/ItemsControl.cs
  46. 2
      src/Avalonia.Controls/ListBox.cs
  47. 30
      src/Avalonia.Controls/Notifications/WindowNotificationManager.cs
  48. 30
      src/Avalonia.Controls/Platform/DefaultMenuInteractionHandler.cs
  49. 283
      src/Avalonia.Controls/Primitives/Popup.cs
  50. 24
      src/Avalonia.Controls/Primitives/ScrollBar.cs
  51. 24
      src/Avalonia.Controls/Primitives/ToggleButton.cs
  52. 28
      src/Avalonia.Controls/Primitives/Track.cs
  53. 9
      src/Avalonia.Controls/Primitives/VisualLayerManager.cs
  54. 53
      src/Avalonia.Controls/ProgressBar.cs
  55. 16
      src/Avalonia.Controls/ScrollViewer.cs
  56. 24
      src/Avalonia.Controls/Slider.cs
  57. 3
      src/Avalonia.Controls/Templates/FuncDataTemplate.cs
  58. 3
      src/Avalonia.Controls/Templates/FuncTreeDataTemplate.cs
  59. 42
      src/Avalonia.Controls/TextBox.cs
  60. 3
      src/Avalonia.Controls/TopLevel.cs
  61. 10
      src/Avalonia.Controls/Utils/AncestorFinder.cs
  62. 2
      src/Avalonia.Controls/Utils/SelectingItemsControlSelectionAdapter.cs
  63. 4
      src/Avalonia.Controls/Window.cs
  64. 2
      src/Avalonia.Dialogs/AboutAvaloniaDialog.xaml
  65. 2
      src/Avalonia.Input/Gestures.cs
  66. 13
      src/Avalonia.Input/GotFocusEventArgs.cs
  67. 2
      src/Avalonia.Input/IInputElement.cs
  68. 42
      src/Avalonia.Input/InputElement.cs
  69. 2
      src/Avalonia.Input/KeyboardNavigationHandler.cs
  70. 3
      src/Avalonia.Interactivity/RoutedEvent.cs
  71. 12
      src/Avalonia.Layout/LayoutManager.cs
  72. 42
      src/Avalonia.Native/AvaloniaNativeDeferredRendererLock.cs
  73. 10
      src/Avalonia.Native/AvaloniaNativePlatform.cs
  74. 2
      src/Avalonia.Native/DeferredFramebuffer.cs
  75. 31
      src/Avalonia.Native/GlPlatformFeature.cs
  76. 1
      src/Avalonia.Native/PlatformThreadingInterface.cs
  77. 12
      src/Avalonia.Native/PopupImpl.cs
  78. 10
      src/Avalonia.Native/WindowImpl.cs
  79. 74
      src/Avalonia.Native/WindowImplBase.cs
  80. 1
      src/Avalonia.OpenGL/EglGlPlatformSurface.cs
  81. 1
      src/Avalonia.OpenGL/IGlPlatformSurfaceRenderingSession.cs
  82. 4
      src/Avalonia.ReactiveUI/AvaloniaActivationForViewFetcher.cs
  83. 28
      src/Avalonia.Styling/Controls/PseudoClassesExtensions.cs
  84. 7
      src/Avalonia.Styling/LogicalTree/ControlLocator.cs
  85. 9
      src/Avalonia.Styling/LogicalTree/ILogicalRoot.cs
  86. 5
      src/Avalonia.Styling/LogicalTree/LogicalTreeAttachmentEventArgs.cs
  87. 134
      src/Avalonia.Styling/StyledElement.cs
  88. 2
      src/Avalonia.Styling/Styling/IGlobalStyles.cs
  89. 12
      src/Avalonia.Styling/Styling/IStyleRoot.cs
  90. 17
      src/Avalonia.Styling/Styling/Selector.cs
  91. 21
      src/Avalonia.Styling/Styling/Setter.cs
  92. 35
      src/Avalonia.Styling/Styling/Style.cs
  93. 4
      src/Avalonia.Styling/Styling/Styles.cs
  94. 2
      src/Avalonia.Styling/Styling/TypeNameAndClassSelector.cs
  95. 18
      src/Avalonia.Themes.Default/ComboBox.xaml
  96. 13
      src/Avalonia.Themes.Default/ItemsControl.xaml
  97. 11
      src/Avalonia.Themes.Default/ListBox.xaml
  98. 86
      src/Avalonia.Themes.Default/ProgressBar.xaml
  99. 9
      src/Avalonia.Themes.Default/Slider.xaml
  100. 7
      src/Avalonia.Themes.Default/TextBox.xaml

27
Avalonia.sln

@ -204,6 +204,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Avalonia.Dialogs", "src\Ava
EndProject EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Avalonia.FreeDesktop", "src\Avalonia.FreeDesktop\Avalonia.FreeDesktop.csproj", "{4D36CEC8-53F2-40A5-9A37-79AAE356E2DA}" Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Avalonia.FreeDesktop", "src\Avalonia.FreeDesktop\Avalonia.FreeDesktop.csproj", "{4D36CEC8-53F2-40A5-9A37-79AAE356E2DA}"
EndProject EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Avalonia.Controls.DataGrid.UnitTests", "tests\Avalonia.Controls.DataGrid.UnitTests\Avalonia.Controls.DataGrid.UnitTests.csproj", "{351337F5-D66F-461B-A957-4EF60BDB4BA6}"
EndProject
Global Global
GlobalSection(SharedMSBuildProjectFiles) = preSolution GlobalSection(SharedMSBuildProjectFiles) = preSolution
src\Shared\RenderHelpers\RenderHelpers.projitems*{3c4c0cb4-0c0f-4450-a37b-148c84ff905f}*SharedItemsImports = 13 src\Shared\RenderHelpers\RenderHelpers.projitems*{3c4c0cb4-0c0f-4450-a37b-148c84ff905f}*SharedItemsImports = 13
@ -1895,6 +1897,30 @@ Global
{4D36CEC8-53F2-40A5-9A37-79AAE356E2DA}.Release|iPhone.Build.0 = Release|Any CPU {4D36CEC8-53F2-40A5-9A37-79AAE356E2DA}.Release|iPhone.Build.0 = Release|Any CPU
{4D36CEC8-53F2-40A5-9A37-79AAE356E2DA}.Release|iPhoneSimulator.ActiveCfg = Release|Any CPU {4D36CEC8-53F2-40A5-9A37-79AAE356E2DA}.Release|iPhoneSimulator.ActiveCfg = Release|Any CPU
{4D36CEC8-53F2-40A5-9A37-79AAE356E2DA}.Release|iPhoneSimulator.Build.0 = Release|Any CPU {4D36CEC8-53F2-40A5-9A37-79AAE356E2DA}.Release|iPhoneSimulator.Build.0 = Release|Any CPU
{351337F5-D66F-461B-A957-4EF60BDB4BA6}.Ad-Hoc|Any CPU.ActiveCfg = Debug|Any CPU
{351337F5-D66F-461B-A957-4EF60BDB4BA6}.Ad-Hoc|Any CPU.Build.0 = Debug|Any CPU
{351337F5-D66F-461B-A957-4EF60BDB4BA6}.Ad-Hoc|iPhone.ActiveCfg = Debug|Any CPU
{351337F5-D66F-461B-A957-4EF60BDB4BA6}.Ad-Hoc|iPhone.Build.0 = Debug|Any CPU
{351337F5-D66F-461B-A957-4EF60BDB4BA6}.Ad-Hoc|iPhoneSimulator.ActiveCfg = Debug|Any CPU
{351337F5-D66F-461B-A957-4EF60BDB4BA6}.Ad-Hoc|iPhoneSimulator.Build.0 = Debug|Any CPU
{351337F5-D66F-461B-A957-4EF60BDB4BA6}.AppStore|Any CPU.ActiveCfg = Debug|Any CPU
{351337F5-D66F-461B-A957-4EF60BDB4BA6}.AppStore|Any CPU.Build.0 = Debug|Any CPU
{351337F5-D66F-461B-A957-4EF60BDB4BA6}.AppStore|iPhone.ActiveCfg = Debug|Any CPU
{351337F5-D66F-461B-A957-4EF60BDB4BA6}.AppStore|iPhone.Build.0 = Debug|Any CPU
{351337F5-D66F-461B-A957-4EF60BDB4BA6}.AppStore|iPhoneSimulator.ActiveCfg = Debug|Any CPU
{351337F5-D66F-461B-A957-4EF60BDB4BA6}.AppStore|iPhoneSimulator.Build.0 = Debug|Any CPU
{351337F5-D66F-461B-A957-4EF60BDB4BA6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{351337F5-D66F-461B-A957-4EF60BDB4BA6}.Debug|Any CPU.Build.0 = Debug|Any CPU
{351337F5-D66F-461B-A957-4EF60BDB4BA6}.Debug|iPhone.ActiveCfg = Debug|Any CPU
{351337F5-D66F-461B-A957-4EF60BDB4BA6}.Debug|iPhone.Build.0 = Debug|Any CPU
{351337F5-D66F-461B-A957-4EF60BDB4BA6}.Debug|iPhoneSimulator.ActiveCfg = Debug|Any CPU
{351337F5-D66F-461B-A957-4EF60BDB4BA6}.Debug|iPhoneSimulator.Build.0 = Debug|Any CPU
{351337F5-D66F-461B-A957-4EF60BDB4BA6}.Release|Any CPU.ActiveCfg = Release|Any CPU
{351337F5-D66F-461B-A957-4EF60BDB4BA6}.Release|Any CPU.Build.0 = Release|Any CPU
{351337F5-D66F-461B-A957-4EF60BDB4BA6}.Release|iPhone.ActiveCfg = Release|Any CPU
{351337F5-D66F-461B-A957-4EF60BDB4BA6}.Release|iPhone.Build.0 = Release|Any CPU
{351337F5-D66F-461B-A957-4EF60BDB4BA6}.Release|iPhoneSimulator.ActiveCfg = Release|Any CPU
{351337F5-D66F-461B-A957-4EF60BDB4BA6}.Release|iPhoneSimulator.Build.0 = Release|Any CPU
EndGlobalSection EndGlobalSection
GlobalSection(SolutionProperties) = preSolution GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE HideSolutionNode = FALSE
@ -1951,6 +1977,7 @@ Global
{41B02319-965D-4945-8005-C1A3D1224165} = {86C53C40-57AA-45B8-AD42-FAE0EFDF0F2B} {41B02319-965D-4945-8005-C1A3D1224165} = {86C53C40-57AA-45B8-AD42-FAE0EFDF0F2B}
{D775DECB-4E00-4ED5-A75A-5FCE58ADFF0B} = {9B9E3891-2366-4253-A952-D08BCEB71098} {D775DECB-4E00-4ED5-A75A-5FCE58ADFF0B} = {9B9E3891-2366-4253-A952-D08BCEB71098}
{AF915D5C-AB00-4EA0-B5E6-001F4AE84E68} = {C5A00AC3-B34C-4564-9BDD-2DA473EF4D8B} {AF915D5C-AB00-4EA0-B5E6-001F4AE84E68} = {C5A00AC3-B34C-4564-9BDD-2DA473EF4D8B}
{351337F5-D66F-461B-A957-4EF60BDB4BA6} = {C5A00AC3-B34C-4564-9BDD-2DA473EF4D8B}
EndGlobalSection EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {87366D66-1391-4D90-8999-95A620AD786A} SolutionGuid = {87366D66-1391-4D90-8999-95A620AD786A}

2
build/SharedVersion.props

@ -3,7 +3,7 @@
<PropertyGroup> <PropertyGroup>
<Product>Avalonia</Product> <Product>Avalonia</Product>
<Version>0.9.999</Version> <Version>0.9.999</Version>
<Copyright>Copyright 2019 &#169; The AvaloniaUI Project</Copyright> <Copyright>Copyright 2020 &#169; The AvaloniaUI Project</Copyright>
<PackageProjectUrl>https://avaloniaui.net</PackageProjectUrl> <PackageProjectUrl>https://avaloniaui.net</PackageProjectUrl>
<RepositoryUrl>https://github.com/AvaloniaUI/Avalonia/</RepositoryUrl> <RepositoryUrl>https://github.com/AvaloniaUI/Avalonia/</RepositoryUrl>
<GenerateDocumentationFile>true</GenerateDocumentationFile> <GenerateDocumentationFile>true</GenerateDocumentationFile>

28
native/Avalonia.Native/inc/avalonia-native.h

@ -177,14 +177,14 @@ AVNCOM(IAvaloniaNativeFactory, 01) : IUnknown
public: public:
virtual HRESULT Initialize() = 0; virtual HRESULT Initialize() = 0;
virtual IAvnMacOptions* GetMacOptions() = 0; virtual IAvnMacOptions* GetMacOptions() = 0;
virtual HRESULT CreateWindow(IAvnWindowEvents* cb, IAvnWindow** ppv) = 0; virtual HRESULT CreateWindow(IAvnWindowEvents* cb, IAvnGlContext* gl, IAvnWindow** ppv) = 0;
virtual HRESULT CreatePopup (IAvnWindowEvents* cb, IAvnPopup** ppv) = 0; virtual HRESULT CreatePopup (IAvnWindowEvents* cb, IAvnGlContext* gl, IAvnPopup** ppv) = 0;
virtual HRESULT CreatePlatformThreadingInterface(IAvnPlatformThreadingInterface** ppv) = 0; virtual HRESULT CreatePlatformThreadingInterface(IAvnPlatformThreadingInterface** ppv) = 0;
virtual HRESULT CreateSystemDialogs (IAvnSystemDialogs** ppv) = 0; virtual HRESULT CreateSystemDialogs (IAvnSystemDialogs** ppv) = 0;
virtual HRESULT CreateScreens (IAvnScreens** ppv) = 0; virtual HRESULT CreateScreens (IAvnScreens** ppv) = 0;
virtual HRESULT CreateClipboard(IAvnClipboard** ppv) = 0; virtual HRESULT CreateClipboard(IAvnClipboard** ppv) = 0;
virtual HRESULT CreateCursorFactory(IAvnCursorFactory** ppv) = 0; virtual HRESULT CreateCursorFactory(IAvnCursorFactory** ppv) = 0;
virtual HRESULT ObtainGlFeature(IAvnGlFeature** ppv) = 0; virtual HRESULT ObtainGlDisplay(IAvnGlDisplay** ppv) = 0;
virtual HRESULT ObtainAppMenu(IAvnAppMenu** retOut) = 0; virtual HRESULT ObtainAppMenu(IAvnAppMenu** retOut) = 0;
virtual HRESULT SetAppMenu(IAvnAppMenu* menu) = 0; virtual HRESULT SetAppMenu(IAvnAppMenu* menu) = 0;
virtual HRESULT CreateMenu (IAvnAppMenu** ppv) = 0; virtual HRESULT CreateMenu (IAvnAppMenu** ppv) = 0;
@ -219,15 +219,12 @@ AVNCOM(IAvnWindowBase, 02) : IUnknown
virtual HRESULT SetTopMost (bool value) = 0; virtual HRESULT SetTopMost (bool value) = 0;
virtual HRESULT SetCursor(IAvnCursor* cursor) = 0; virtual HRESULT SetCursor(IAvnCursor* cursor) = 0;
virtual HRESULT CreateGlRenderTarget(IAvnGlSurfaceRenderTarget** ret) = 0; virtual HRESULT CreateGlRenderTarget(IAvnGlSurfaceRenderTarget** ret) = 0;
virtual HRESULT GetSoftwareFramebuffer(AvnFramebuffer*ret) = 0;
virtual HRESULT SetMainMenu(IAvnAppMenu* menu) = 0; virtual HRESULT SetMainMenu(IAvnAppMenu* menu) = 0;
virtual HRESULT ObtainMainMenu(IAvnAppMenu** retOut) = 0; virtual HRESULT ObtainMainMenu(IAvnAppMenu** retOut) = 0;
virtual HRESULT ObtainNSWindowHandle(void** retOut) = 0; virtual HRESULT ObtainNSWindowHandle(void** retOut) = 0;
virtual HRESULT ObtainNSWindowHandleRetained(void** retOut) = 0; virtual HRESULT ObtainNSWindowHandleRetained(void** retOut) = 0;
virtual HRESULT ObtainNSViewHandle(void** retOut) = 0; virtual HRESULT ObtainNSViewHandle(void** retOut) = 0;
virtual HRESULT ObtainNSViewHandleRetained(void** retOut) = 0; virtual HRESULT ObtainNSViewHandleRetained(void** retOut) = 0;
virtual bool TryLock() = 0;
virtual void Unlock() = 0;
}; };
AVNCOM(IAvnPopup, 03) : virtual IAvnWindowBase AVNCOM(IAvnPopup, 03) : virtual IAvnWindowBase
@ -360,24 +357,21 @@ AVNCOM(IAvnCursorFactory, 11) : IUnknown
virtual HRESULT GetCursor (AvnStandardCursorType cursorType, IAvnCursor** retOut) = 0; virtual HRESULT GetCursor (AvnStandardCursorType cursorType, IAvnCursor** retOut) = 0;
}; };
AVNCOM(IAvnGlFeature, 12) : IUnknown
{
virtual HRESULT ObtainDisplay(IAvnGlDisplay**retOut) = 0;
virtual HRESULT ObtainImmediateContext(IAvnGlContext**retOut) = 0;
};
AVNCOM(IAvnGlDisplay, 13) : IUnknown AVNCOM(IAvnGlDisplay, 13) : IUnknown
{ {
virtual HRESULT GetSampleCount(int* ret) = 0; virtual HRESULT CreateContext(IAvnGlContext* share, IAvnGlContext**ppv) = 0;
virtual HRESULT GetStencilSize(int* ret) = 0; virtual void LegacyClearCurrentContext() = 0;
virtual HRESULT ClearContext() = 0; virtual HRESULT WrapContext(void* native, IAvnGlContext**ppv) = 0;
virtual void* GetProcAddress(char* proc) = 0; virtual void* GetProcAddress(char* proc) = 0;
}; };
AVNCOM(IAvnGlContext, 14) : IUnknown AVNCOM(IAvnGlContext, 14) : IUnknown
{ {
virtual HRESULT MakeCurrent() = 0; virtual HRESULT MakeCurrent(IUnknown** ppv) = 0;
virtual HRESULT LegacyMakeCurrent() = 0;
virtual int GetSampleCount() = 0;
virtual int GetStencilSize() = 0;
virtual void* GetNativeHandle() = 0;
}; };
AVNCOM(IAvnGlSurfaceRenderTarget, 15) : IUnknown AVNCOM(IAvnGlSurfaceRenderTarget, 15) : IUnknown

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

@ -162,6 +162,19 @@ public:
return _obj; return _obj;
} }
TInterface* getRetainedReference()
{
if(_obj == NULL)
return NULL;
_obj->AddRef();
return _obj;
}
TInterface** getPPV()
{
return &_obj;
}
operator TInterface*() const operator TInterface*() const
{ {
return _obj; return _obj;

12
native/Avalonia.Native/inc/rendertarget.h

@ -0,0 +1,12 @@
@protocol IRenderTarget
-(void) setNewLayer: (CALayer*) layer;
-(HRESULT) setSwFrame: (AvnFramebuffer*) fb;
-(void) resize: (AvnPixelSize) size withScale: (float) scale;
-(AvnPixelSize) pixelSize;
-(IAvnGlSurfaceRenderTarget*) createSurfaceRenderTarget;
@end
@interface IOSurfaceRenderTarget : NSObject<IRenderTarget>
-(IOSurfaceRenderTarget*) initWithOpenGlContext: (IAvnGlContext*) context;
@end

20
native/Avalonia.Native/src/OSX/Avalonia.Native.OSX.xcodeproj/project.pbxproj

@ -8,6 +8,10 @@
/* Begin PBXBuildFile section */ /* Begin PBXBuildFile section */
1A002B9E232135EE00021753 /* app.mm in Sources */ = {isa = PBXBuildFile; fileRef = 1A002B9D232135EE00021753 /* app.mm */; }; 1A002B9E232135EE00021753 /* app.mm in Sources */ = {isa = PBXBuildFile; fileRef = 1A002B9D232135EE00021753 /* app.mm */; };
1A3E5EA823E9E83B00EDE661 /* rendertarget.mm in Sources */ = {isa = PBXBuildFile; fileRef = 1A3E5EA723E9E83B00EDE661 /* rendertarget.mm */; };
1A3E5EAA23E9F26C00EDE661 /* IOSurface.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 1A3E5EA923E9F26C00EDE661 /* IOSurface.framework */; };
1A3E5EAE23E9FB1300EDE661 /* cgl.mm in Sources */ = {isa = PBXBuildFile; fileRef = 1A3E5EAD23E9FB1300EDE661 /* cgl.mm */; };
1A3E5EB023E9FE8300EDE661 /* QuartzCore.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 1A3E5EAF23E9FE8300EDE661 /* QuartzCore.framework */; };
37155CE4233C00EB0034DCE9 /* menu.h in Headers */ = {isa = PBXBuildFile; fileRef = 37155CE3233C00EB0034DCE9 /* menu.h */; }; 37155CE4233C00EB0034DCE9 /* menu.h in Headers */ = {isa = PBXBuildFile; fileRef = 37155CE3233C00EB0034DCE9 /* menu.h */; };
37A517B32159597E00FBA241 /* Screens.mm in Sources */ = {isa = PBXBuildFile; fileRef = 37A517B22159597E00FBA241 /* Screens.mm */; }; 37A517B32159597E00FBA241 /* Screens.mm in Sources */ = {isa = PBXBuildFile; fileRef = 37A517B22159597E00FBA241 /* Screens.mm */; };
37C09D8821580FE4006A6758 /* SystemDialogs.mm in Sources */ = {isa = PBXBuildFile; fileRef = 37C09D8721580FE4006A6758 /* SystemDialogs.mm */; }; 37C09D8821580FE4006A6758 /* SystemDialogs.mm in Sources */ = {isa = PBXBuildFile; fileRef = 37C09D8721580FE4006A6758 /* SystemDialogs.mm */; };
@ -18,7 +22,6 @@
5B8BD94F215BFEA6005ED2A7 /* clipboard.mm in Sources */ = {isa = PBXBuildFile; fileRef = 5B8BD94E215BFEA6005ED2A7 /* clipboard.mm */; }; 5B8BD94F215BFEA6005ED2A7 /* clipboard.mm in Sources */ = {isa = PBXBuildFile; fileRef = 5B8BD94E215BFEA6005ED2A7 /* clipboard.mm */; };
AB00E4F72147CA920032A60A /* main.mm in Sources */ = {isa = PBXBuildFile; fileRef = AB00E4F62147CA920032A60A /* main.mm */; }; AB00E4F72147CA920032A60A /* main.mm in Sources */ = {isa = PBXBuildFile; fileRef = AB00E4F62147CA920032A60A /* main.mm */; };
AB1E522C217613570091CD71 /* OpenGL.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = AB1E522B217613570091CD71 /* OpenGL.framework */; }; AB1E522C217613570091CD71 /* OpenGL.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = AB1E522B217613570091CD71 /* OpenGL.framework */; };
AB573DC4217605E400D389A2 /* gl.mm in Sources */ = {isa = PBXBuildFile; fileRef = AB573DC3217605E400D389A2 /* gl.mm */; };
AB661C1E2148230F00291242 /* AppKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = AB661C1D2148230F00291242 /* AppKit.framework */; }; AB661C1E2148230F00291242 /* AppKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = AB661C1D2148230F00291242 /* AppKit.framework */; };
AB661C202148286E00291242 /* window.mm in Sources */ = {isa = PBXBuildFile; fileRef = AB661C1F2148286E00291242 /* window.mm */; }; AB661C202148286E00291242 /* window.mm in Sources */ = {isa = PBXBuildFile; fileRef = AB661C1F2148286E00291242 /* window.mm */; };
AB8F7D6B21482D7F0057DBA5 /* platformthreading.mm in Sources */ = {isa = PBXBuildFile; fileRef = AB8F7D6A21482D7F0057DBA5 /* platformthreading.mm */; }; AB8F7D6B21482D7F0057DBA5 /* platformthreading.mm in Sources */ = {isa = PBXBuildFile; fileRef = AB8F7D6A21482D7F0057DBA5 /* platformthreading.mm */; };
@ -26,6 +29,10 @@
/* Begin PBXFileReference section */ /* Begin PBXFileReference section */
1A002B9D232135EE00021753 /* app.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = app.mm; sourceTree = "<group>"; }; 1A002B9D232135EE00021753 /* app.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = app.mm; sourceTree = "<group>"; };
1A3E5EA723E9E83B00EDE661 /* rendertarget.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = rendertarget.mm; sourceTree = "<group>"; };
1A3E5EA923E9F26C00EDE661 /* IOSurface.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = IOSurface.framework; path = System/Library/Frameworks/IOSurface.framework; sourceTree = SDKROOT; };
1A3E5EAD23E9FB1300EDE661 /* cgl.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = cgl.mm; sourceTree = "<group>"; };
1A3E5EAF23E9FE8300EDE661 /* QuartzCore.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = QuartzCore.framework; path = System/Library/Frameworks/QuartzCore.framework; sourceTree = SDKROOT; };
37155CE3233C00EB0034DCE9 /* menu.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = menu.h; sourceTree = "<group>"; }; 37155CE3233C00EB0034DCE9 /* menu.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = menu.h; sourceTree = "<group>"; };
379860FE214DA0C000CD0246 /* KeyTransform.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = KeyTransform.h; sourceTree = "<group>"; }; 379860FE214DA0C000CD0246 /* KeyTransform.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = KeyTransform.h; sourceTree = "<group>"; };
37A4E71A2178846A00EACBCD /* headers */ = {isa = PBXFileReference; lastKnownFileType = folder; name = headers; path = ../../inc; sourceTree = "<group>"; }; 37A4E71A2178846A00EACBCD /* headers */ = {isa = PBXFileReference; lastKnownFileType = folder; name = headers; path = ../../inc; sourceTree = "<group>"; };
@ -41,7 +48,6 @@
5BF943652167AD1D009CAE35 /* cursor.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = cursor.h; sourceTree = "<group>"; }; 5BF943652167AD1D009CAE35 /* cursor.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = cursor.h; sourceTree = "<group>"; };
AB00E4F62147CA920032A60A /* main.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = main.mm; sourceTree = "<group>"; }; AB00E4F62147CA920032A60A /* main.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = main.mm; sourceTree = "<group>"; };
AB1E522B217613570091CD71 /* OpenGL.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = OpenGL.framework; path = System/Library/Frameworks/OpenGL.framework; sourceTree = SDKROOT; }; AB1E522B217613570091CD71 /* OpenGL.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = OpenGL.framework; path = System/Library/Frameworks/OpenGL.framework; sourceTree = SDKROOT; };
AB573DC3217605E400D389A2 /* gl.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = gl.mm; sourceTree = "<group>"; };
AB661C1D2148230F00291242 /* AppKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = AppKit.framework; path = System/Library/Frameworks/AppKit.framework; sourceTree = SDKROOT; }; AB661C1D2148230F00291242 /* AppKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = AppKit.framework; path = System/Library/Frameworks/AppKit.framework; sourceTree = SDKROOT; };
AB661C1F2148286E00291242 /* window.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = window.mm; sourceTree = "<group>"; }; AB661C1F2148286E00291242 /* window.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = window.mm; sourceTree = "<group>"; };
AB661C212148288600291242 /* common.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = common.h; sourceTree = "<group>"; }; AB661C212148288600291242 /* common.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = common.h; sourceTree = "<group>"; };
@ -54,6 +60,8 @@
isa = PBXFrameworksBuildPhase; isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647; buildActionMask = 2147483647;
files = ( files = (
1A3E5EB023E9FE8300EDE661 /* QuartzCore.framework in Frameworks */,
1A3E5EAA23E9F26C00EDE661 /* IOSurface.framework in Frameworks */,
AB1E522C217613570091CD71 /* OpenGL.framework in Frameworks */, AB1E522C217613570091CD71 /* OpenGL.framework in Frameworks */,
AB661C1E2148230F00291242 /* AppKit.framework in Frameworks */, AB661C1E2148230F00291242 /* AppKit.framework in Frameworks */,
); );
@ -65,6 +73,8 @@
AB661C1C2148230E00291242 /* Frameworks */ = { AB661C1C2148230E00291242 /* Frameworks */ = {
isa = PBXGroup; isa = PBXGroup;
children = ( children = (
1A3E5EAF23E9FE8300EDE661 /* QuartzCore.framework */,
1A3E5EA923E9F26C00EDE661 /* IOSurface.framework */,
AB1E522B217613570091CD71 /* OpenGL.framework */, AB1E522B217613570091CD71 /* OpenGL.framework */,
AB661C1D2148230F00291242 /* AppKit.framework */, AB661C1D2148230F00291242 /* AppKit.framework */,
); );
@ -78,7 +88,7 @@
37DDA9B121933371002E132B /* AvnString.h */, 37DDA9B121933371002E132B /* AvnString.h */,
37DDA9AF219330F8002E132B /* AvnString.mm */, 37DDA9AF219330F8002E132B /* AvnString.mm */,
37A4E71A2178846A00EACBCD /* headers */, 37A4E71A2178846A00EACBCD /* headers */,
AB573DC3217605E400D389A2 /* gl.mm */, 1A3E5EAD23E9FB1300EDE661 /* cgl.mm */,
5BF943652167AD1D009CAE35 /* cursor.h */, 5BF943652167AD1D009CAE35 /* cursor.h */,
5B21A981216530F500CEE36E /* cursor.mm */, 5B21A981216530F500CEE36E /* cursor.mm */,
5B8BD94E215BFEA6005ED2A7 /* clipboard.mm */, 5B8BD94E215BFEA6005ED2A7 /* clipboard.mm */,
@ -91,6 +101,7 @@
AB00E4F62147CA920032A60A /* main.mm */, AB00E4F62147CA920032A60A /* main.mm */,
37155CE3233C00EB0034DCE9 /* menu.h */, 37155CE3233C00EB0034DCE9 /* menu.h */,
520624B222973F4100C4DCEF /* menu.mm */, 520624B222973F4100C4DCEF /* menu.mm */,
1A3E5EA723E9E83B00EDE661 /* rendertarget.mm */,
37A517B22159597E00FBA241 /* Screens.mm */, 37A517B22159597E00FBA241 /* Screens.mm */,
37C09D8721580FE4006A6758 /* SystemDialogs.mm */, 37C09D8721580FE4006A6758 /* SystemDialogs.mm */,
AB7A61F02147C815003C5833 /* Products */, AB7A61F02147C815003C5833 /* Products */,
@ -180,12 +191,13 @@
5B21A982216530F500CEE36E /* cursor.mm in Sources */, 5B21A982216530F500CEE36E /* cursor.mm in Sources */,
37DDA9B0219330F8002E132B /* AvnString.mm in Sources */, 37DDA9B0219330F8002E132B /* AvnString.mm in Sources */,
AB8F7D6B21482D7F0057DBA5 /* platformthreading.mm in Sources */, AB8F7D6B21482D7F0057DBA5 /* platformthreading.mm in Sources */,
1A3E5EA823E9E83B00EDE661 /* rendertarget.mm in Sources */,
1A3E5EAE23E9FB1300EDE661 /* cgl.mm in Sources */,
37E2330F21583241000CB7E2 /* KeyTransform.mm in Sources */, 37E2330F21583241000CB7E2 /* KeyTransform.mm in Sources */,
520624B322973F4100C4DCEF /* menu.mm in Sources */, 520624B322973F4100C4DCEF /* menu.mm in Sources */,
37A517B32159597E00FBA241 /* Screens.mm in Sources */, 37A517B32159597E00FBA241 /* Screens.mm in Sources */,
AB00E4F72147CA920032A60A /* main.mm in Sources */, AB00E4F72147CA920032A60A /* main.mm in Sources */,
37C09D8821580FE4006A6758 /* SystemDialogs.mm in Sources */, 37C09D8821580FE4006A6758 /* SystemDialogs.mm in Sources */,
AB573DC4217605E400D389A2 /* gl.mm in Sources */,
AB661C202148286E00291242 /* window.mm in Sources */, AB661C202148286E00291242 /* window.mm in Sources */,
); );
runOnlyForDeploymentPostprocessing = 0; runOnlyForDeploymentPostprocessing = 0;

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

@ -0,0 +1,166 @@
#include "common.h"
#include <dlfcn.h>
static CGLContextObj CreateCglContext(CGLContextObj share)
{
int attributes[] = {
kCGLPFAAccelerated,
kCGLPFAOpenGLProfile, (CGLPixelFormatAttribute)kCGLOGLPVersion_3_2_Core,
kCGLPFADepthSize, 8,
kCGLPFAStencilSize, 8,
kCGLPFAColorSize, 32,
0
};
CGLPixelFormatObj pix;
CGLError errorCode;
GLint num; // stores the number of possible pixel formats
errorCode = CGLChoosePixelFormat( (CGLPixelFormatAttribute*)attributes, &pix, &num );
if(errorCode != 0)
return nil;
CGLContextObj ctx = nil;
errorCode = CGLCreateContext(pix, share, &ctx );
CGLDestroyPixelFormat( pix );
if(errorCode != 0)
return nil;
return ctx;
};
class AvnGlContext : public virtual ComSingleObject<IAvnGlContext, &IID_IAvnGlContext>
{
// Debug
int _usageCount = 0;
public:
CGLContextObj Context;
int SampleCount = 0, StencilBits = 0;
FORWARD_IUNKNOWN()
class SavedGlContext : public virtual ComUnknownObject
{
CGLContextObj _savedContext;
ComPtr<AvnGlContext> _parent;
public:
SavedGlContext(CGLContextObj saved, AvnGlContext* parent)
{
_savedContext = saved;
_parent = parent;
_parent->_usageCount++;
}
~SavedGlContext()
{
if(_parent->Context == CGLGetCurrentContext())
CGLSetCurrentContext(_savedContext);
_parent->_usageCount--;
CGLUnlockContext(_parent->Context);
}
};
AvnGlContext(CGLContextObj context)
{
Context = context;
CGLPixelFormatObj fmt = CGLGetPixelFormat(context);
CGLDescribePixelFormat(fmt, 0, kCGLPFASamples, &SampleCount);
CGLDescribePixelFormat(fmt, 0, kCGLPFAStencilSize, &StencilBits);
}
virtual HRESULT LegacyMakeCurrent() override
{
if(CGLSetCurrentContext(Context) != 0)
return E_FAIL;
return S_OK;
}
virtual HRESULT MakeCurrent(IUnknown** ppv) override
{
CGLContextObj saved = CGLGetCurrentContext();
CGLLockContext(Context);
if(CGLSetCurrentContext(Context) != 0)
{
CGLUnlockContext(Context);
return E_FAIL;
}
*ppv = new SavedGlContext(saved, this);
return S_OK;
}
virtual int GetSampleCount() override
{
return SampleCount;
}
virtual int GetStencilSize() override
{
return StencilBits;
}
virtual void* GetNativeHandle() override
{
return Context;
}
~AvnGlContext()
{
CGLReleaseContext(Context);
}
};
class AvnGlDisplay : public virtual ComSingleObject<IAvnGlDisplay, &IID_IAvnGlDisplay>
{
void* _libgl;
public:
FORWARD_IUNKNOWN()
AvnGlDisplay()
{
_libgl = dlopen("/System/Library/Frameworks/OpenGL.framework/Versions/A/Libraries/libGL.dylib", RTLD_LAZY);
}
virtual void* GetProcAddress(char* proc) override
{
return dlsym(_libgl, proc);
}
virtual HRESULT CreateContext(IAvnGlContext* share, IAvnGlContext**ppv) override
{
CGLContextObj shareContext = nil;
if(share != nil)
{
AvnGlContext* shareCtx = dynamic_cast<AvnGlContext*>(share);
if(shareCtx != nil)
shareContext = shareCtx->Context;
}
CGLContextObj ctx = ::CreateCglContext(shareContext);
if(ctx == nil)
return E_FAIL;
*ppv = new AvnGlContext(ctx);
return S_OK;
}
virtual HRESULT WrapContext(void* native, IAvnGlContext**ppv) override
{
if(native == nil)
return E_INVALIDARG;
*ppv = new AvnGlContext((CGLContextObj) native);
return S_OK;
}
virtual void LegacyClearCurrentContext() override
{
CGLSetCurrentContext(nil);
}
};
static IAvnGlDisplay* GlDisplay = new AvnGlDisplay();
extern IAvnGlDisplay* GetGlDisplay()
{
return GlDisplay;
};

7
native/Avalonia.Native/src/OSX/common.h

@ -11,14 +11,13 @@
#include <pthread.h> #include <pthread.h>
extern IAvnPlatformThreadingInterface* CreatePlatformThreading(); extern IAvnPlatformThreadingInterface* CreatePlatformThreading();
extern IAvnWindow* CreateAvnWindow(IAvnWindowEvents*events); extern IAvnWindow* CreateAvnWindow(IAvnWindowEvents*events, IAvnGlContext* gl);
extern IAvnPopup* CreateAvnPopup(IAvnWindowEvents*events); extern IAvnPopup* CreateAvnPopup(IAvnWindowEvents*events, IAvnGlContext* gl);
extern IAvnSystemDialogs* CreateSystemDialogs(); extern IAvnSystemDialogs* CreateSystemDialogs();
extern IAvnScreens* CreateScreens(); extern IAvnScreens* CreateScreens();
extern IAvnClipboard* CreateClipboard(); extern IAvnClipboard* CreateClipboard();
extern IAvnCursorFactory* CreateCursorFactory(); extern IAvnCursorFactory* CreateCursorFactory();
extern IAvnGlFeature* GetGlFeature(); extern IAvnGlDisplay* GetGlDisplay();
extern IAvnGlSurfaceRenderTarget* CreateGlRenderTarget(NSWindow* window, NSView* view);
extern IAvnAppMenu* CreateAppMenu(); extern IAvnAppMenu* CreateAppMenu();
extern IAvnAppMenuItem* CreateAppMenuItem(); extern IAvnAppMenuItem* CreateAppMenuItem();
extern IAvnAppMenuItem* CreateAppMenuItemSeperator(); extern IAvnAppMenuItem* CreateAppMenuItemSeperator();

261
native/Avalonia.Native/src/OSX/gl.mm

@ -1,261 +0,0 @@
#include "common.h"
#include <OpenGL/gl.h>
#include <dlfcn.h>
#include "window.h"
template <typename T, size_t N> char (&ArrayCounter(T (&a)[N]))[N];
#define ARRAY_COUNT(a) (sizeof(ArrayCounter(a)))
NSOpenGLPixelFormat* CreateFormat()
{
NSOpenGLPixelFormatAttribute attribs[] =
{
NSOpenGLPFADoubleBuffer,
NSOpenGLPFAColorSize, 32,
NSOpenGLPFAStencilSize, 8,
NSOpenGLPFADepthSize, 8,
0
};
return [[NSOpenGLPixelFormat alloc] initWithAttributes:attribs];
}
class AvnGlContext : public virtual ComSingleObject<IAvnGlContext, &IID_IAvnGlContext>
{
public:
FORWARD_IUNKNOWN()
NSOpenGLContext* GlContext;
GLuint Framebuffer, RenderBuffer, StencilBuffer;
AvnGlContext(NSOpenGLContext* gl, bool offscreen)
{
Framebuffer = 0;
RenderBuffer = 0;
StencilBuffer = 0;
GlContext = gl;
if(offscreen)
{
[GlContext makeCurrentContext];
glGenFramebuffersEXT(1, &Framebuffer);
glBindFramebufferEXT(GL_FRAMEBUFFER, Framebuffer);
glGenRenderbuffersEXT(1, &RenderBuffer);
glGenRenderbuffersEXT(1, &StencilBuffer);
glBindRenderbufferEXT(GL_RENDERBUFFER, StencilBuffer);
glFramebufferRenderbufferEXT(GL_FRAMEBUFFER, GL_STENCIL_ATTACHMENT, GL_RENDERBUFFER, StencilBuffer);
glBindRenderbufferEXT(GL_RENDERBUFFER, RenderBuffer);
glFramebufferRenderbufferEXT(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, RenderBuffer);
}
}
virtual HRESULT MakeCurrent() override
{
[GlContext makeCurrentContext];/*
glBindFramebufferEXT(GL_FRAMEBUFFER, Framebuffer);
glBindRenderbufferEXT(GL_RENDERBUFFER, RenderBuffer);
glFramebufferRenderbufferEXT(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, RenderBuffer);
glBindRenderbufferEXT(GL_RENDERBUFFER, StencilBuffer);
glFramebufferRenderbufferEXT(GL_FRAMEBUFFER, GL_STENCIL_ATTACHMENT, GL_RENDERBUFFER, StencilBuffer);*/
return S_OK;
}
};
class AvnGlDisplay : public virtual ComSingleObject<IAvnGlDisplay, &IID_IAvnGlDisplay>
{
int _sampleCount, _stencilSize;
void* _libgl;
public:
FORWARD_IUNKNOWN()
AvnGlDisplay(int sampleCount, int stencilSize)
{
_sampleCount = sampleCount;
_stencilSize = stencilSize;
_libgl = dlopen("/System/Library/Frameworks/OpenGL.framework/Versions/A/Libraries/libGL.dylib", RTLD_LAZY);
}
virtual HRESULT GetSampleCount(int* ret) override
{
*ret = _sampleCount;
return S_OK;
}
virtual HRESULT GetStencilSize(int* ret) override
{
*ret = _stencilSize;
return S_OK;
}
virtual HRESULT ClearContext() override
{
[NSOpenGLContext clearCurrentContext];
return S_OK;
}
virtual void* GetProcAddress(char* proc) override
{
return dlsym(_libgl, proc);
}
};
class GlFeature : public virtual ComSingleObject<IAvnGlFeature, &IID_IAvnGlFeature>
{
IAvnGlDisplay* _display;
AvnGlContext *_immediate;
NSOpenGLContext* _shared;
public:
FORWARD_IUNKNOWN()
NSOpenGLPixelFormat* _format;
GlFeature(IAvnGlDisplay* display, AvnGlContext* immediate, NSOpenGLPixelFormat* format)
{
_display = display;
_immediate = immediate;
_format = format;
_shared = [[NSOpenGLContext alloc] initWithFormat:_format shareContext:_immediate->GlContext];
}
NSOpenGLContext* CreateContext()
{
return _shared;
//return [[NSOpenGLContext alloc] initWithFormat:_format shareContext:nil];
}
virtual HRESULT ObtainDisplay(IAvnGlDisplay**retOut) override
{
*retOut = _display;
_display->AddRef();
return S_OK;
}
virtual HRESULT ObtainImmediateContext(IAvnGlContext**retOut) override
{
*retOut = _immediate;
_immediate->AddRef();
return S_OK;
}
};
static GlFeature* Feature;
GlFeature* CreateGlFeature()
{
auto format = CreateFormat();
if(format == nil)
{
NSLog(@"Unable to choose pixel format");
return NULL;
}
auto immediateContext = [[NSOpenGLContext alloc] initWithFormat:format shareContext:nil];
if(immediateContext == nil)
{
NSLog(@"Unable to create NSOpenGLContext");
return NULL;
}
int stencilBits = 0, sampleCount = 0;
auto fmt = CGLGetPixelFormat([immediateContext CGLContextObj]);
CGLDescribePixelFormat(fmt, 0, kCGLPFASamples, &sampleCount);
CGLDescribePixelFormat(fmt, 0, kCGLPFAStencilSize, &stencilBits);
auto offscreen = new AvnGlContext(immediateContext, true);
auto display = new AvnGlDisplay(sampleCount, stencilBits);
return new GlFeature(display, offscreen, format);
}
static GlFeature* GetFeature()
{
if(Feature == nil)
Feature = CreateGlFeature();
return Feature;
}
extern IAvnGlFeature* GetGlFeature()
{
return GetFeature();
}
class AvnGlRenderingSession : public ComSingleObject<IAvnGlSurfaceRenderingSession, &IID_IAvnGlSurfaceRenderingSession>
{
AvnView* _view;
AvnWindow* _window;
NSOpenGLContext* _context;
public:
FORWARD_IUNKNOWN()
AvnGlRenderingSession(AvnWindow*window, AvnView* view, NSOpenGLContext* context)
{
_context = context;
_window = window;
_view = view;
}
virtual HRESULT GetPixelSize(AvnPixelSize* ret) override
{
*ret = [_view getPixelSize];
return S_OK;
}
virtual HRESULT GetScaling(double* ret) override
{
*ret = [_window getScaling];
return S_OK;
}
virtual ~AvnGlRenderingSession()
{
[_context flushBuffer];
[NSOpenGLContext clearCurrentContext];
CGLUnlockContext([_context CGLContextObj]);
[_view unlockFocus];
}
};
class AvnGlRenderTarget : public ComSingleObject<IAvnGlSurfaceRenderTarget, &IID_IAvnGlSurfaceRenderTarget>
{
NSView* _view;
NSWindow* _window;
NSOpenGLContext* _context;
public:
FORWARD_IUNKNOWN()
AvnGlRenderTarget(NSWindow* window, NSView*view)
{
_window = window;
_view = view;
_context = GetFeature()->CreateContext();
}
virtual HRESULT BeginDrawing(IAvnGlSurfaceRenderingSession** ret) override
{
auto f = GetFeature();
if(f == NULL)
return E_FAIL;
@try
{
if(![_view lockFocusIfCanDraw])
return E_ABORT;
}
@catch(NSException* exception)
{
return E_ABORT;
}
auto gl = _context;
CGLLockContext([_context CGLContextObj]);
[gl setView: _view];
[gl update];
[gl makeCurrentContext];
*ret = new AvnGlRenderingSession(_window, _view, gl);
return S_OK;
}
};
extern IAvnGlSurfaceRenderTarget* CreateGlRenderTarget(NSWindow* window, NSView* view)
{
return new AvnGlRenderTarget(window, view);
}

12
native/Avalonia.Native/src/OSX/main.mm

@ -174,20 +174,20 @@ public:
return (IAvnMacOptions*)new MacOptions(); return (IAvnMacOptions*)new MacOptions();
} }
virtual HRESULT CreateWindow(IAvnWindowEvents* cb, IAvnWindow** ppv) override virtual HRESULT CreateWindow(IAvnWindowEvents* cb, IAvnGlContext* gl, IAvnWindow** ppv) override
{ {
if(cb == nullptr || ppv == nullptr) if(cb == nullptr || ppv == nullptr)
return E_POINTER; return E_POINTER;
*ppv = CreateAvnWindow(cb); *ppv = CreateAvnWindow(cb, gl);
return S_OK; return S_OK;
}; };
virtual HRESULT CreatePopup(IAvnWindowEvents* cb, IAvnPopup** ppv) override virtual HRESULT CreatePopup(IAvnWindowEvents* cb, IAvnGlContext* gl, IAvnPopup** ppv) override
{ {
if(cb == nullptr || ppv == nullptr) if(cb == nullptr || ppv == nullptr)
return E_POINTER; return E_POINTER;
*ppv = CreateAvnPopup(cb); *ppv = CreateAvnPopup(cb, gl);
return S_OK; return S_OK;
} }
@ -221,9 +221,9 @@ public:
return S_OK; return S_OK;
} }
virtual HRESULT ObtainGlFeature(IAvnGlFeature** ppv) override virtual HRESULT ObtainGlDisplay(IAvnGlDisplay** ppv) override
{ {
auto rv = ::GetGlFeature(); auto rv = ::GetGlDisplay();
if(rv == NULL) if(rv == NULL)
return E_FAIL; return E_FAIL;
rv->AddRef(); rv->AddRef();

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

@ -0,0 +1,284 @@
#include "common.h"
#include "rendertarget.h"
#import <IOSurface/IOSurface.h>
#import <IOSurface/IOSurfaceObjC.h>
#include <OpenGL/CGLIOSurface.h>
#include <OpenGL/OpenGL.h>
#include <OpenGL/glext.h>
#include <OpenGL/gl3.h>
#include <OpenGL/gl3ext.h>
@interface IOSurfaceHolder : NSObject
@end
@implementation IOSurfaceHolder
{
@public IOSurfaceRef surface;
@public AvnPixelSize size;
@public float scale;
ComPtr<IAvnGlContext> _context;
GLuint _framebuffer, _texture, _renderbuffer;
}
- (IOSurfaceHolder*) initWithSize: (AvnPixelSize) size
withScale: (float)scale
withOpenGlContext: (IAvnGlContext*) context
{
long bytesPerRow = IOSurfaceAlignProperty(kIOSurfaceBytesPerRow, size.Width * 4);
long allocSize = IOSurfaceAlignProperty(kIOSurfaceAllocSize, size.Height * bytesPerRow);
NSDictionary* options = @{
(id)kIOSurfaceWidth: @(size.Width),
(id)kIOSurfaceHeight: @(size.Height),
(id)kIOSurfacePixelFormat: @((uint)'BGRA'),
(id)kIOSurfaceBytesPerElement: @(4),
(id)kIOSurfaceBytesPerRow: @(bytesPerRow),
(id)kIOSurfaceAllocSize: @(allocSize),
//(id)kIOSurfaceCacheMode: @(kIOMapWriteCombineCache),
(id)kIOSurfaceElementWidth: @(1),
(id)kIOSurfaceElementHeight: @(1)
};
surface = IOSurfaceCreate((CFDictionaryRef)options);
self->scale = scale;
self->size = size;
self->_context = context;
return self;
}
-(HRESULT) prepareForGlRender
{
if(_context == nil)
return E_FAIL;
if(CGLGetCurrentContext() != _context->GetNativeHandle())
return E_FAIL;
if(_framebuffer == 0)
glGenFramebuffersEXT(1, &_framebuffer);
glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, _framebuffer);
if(_texture == 0)
{
glGenTextures(1, &_texture);
glBindTexture(GL_TEXTURE_RECTANGLE_EXT, _texture);
CGLError res = CGLTexImageIOSurface2D((CGLContextObj)_context->GetNativeHandle(),
GL_TEXTURE_RECTANGLE_EXT, GL_RGBA8,
size.Width, size.Height, GL_BGRA, GL_UNSIGNED_INT_8_8_8_8_REV, surface, 0);
glBindTexture(GL_TEXTURE_RECTANGLE_EXT, 0);
if(res != 0)
{
glDeleteTextures(1, &_texture);
_texture = 0;
glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, 0);
return E_FAIL;
}
glFramebufferTexture2DEXT(GL_FRAMEBUFFER_EXT, GL_COLOR_ATTACHMENT0_EXT, GL_TEXTURE_RECTANGLE_EXT, _texture, 0);
}
if(_renderbuffer == 0)
{
glGenRenderbuffers(1, &_renderbuffer);
glBindRenderbuffer(GL_RENDERBUFFER, _renderbuffer);
glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH_COMPONENT, size.Width, size.Height);
glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_RENDERBUFFER, _renderbuffer);
}
return S_OK;
}
-(void) finishDraw
{
ComPtr<IUnknown> release;
_context->MakeCurrent(release.getPPV());
glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, 0);
glFlush();
}
-(void) dealloc
{
if(_framebuffer != 0)
{
ComPtr<IUnknown> release;
_context->MakeCurrent(release.getPPV());
glDeleteFramebuffers(1, &_framebuffer);
if(_texture != 0)
glDeleteTextures(1, &_texture);
if(_renderbuffer != 0)
glDeleteRenderbuffers(1, &_renderbuffer);
}
IOSurfaceDecrementUseCount(surface);
}
@end
static IAvnGlSurfaceRenderTarget* CreateGlRenderTarget(IOSurfaceRenderTarget* target);
@implementation IOSurfaceRenderTarget
{
CALayer* _layer;
@public IOSurfaceHolder* surface;
@public NSObject* lock;
ComPtr<IAvnGlContext> _glContext;
}
- (IOSurfaceRenderTarget*) initWithOpenGlContext: (IAvnGlContext*) context;
{
self = [super init];
_glContext = context;
lock = [NSObject new];
surface = nil;
[self resize:{1,1} withScale: 1];
return self;
}
- (AvnPixelSize) pixelSize {
return {1, 1};
}
- (CALayer *)layer {
return _layer;
}
- (void)resize:(AvnPixelSize)size withScale: (float) scale;{
@synchronized (lock) {
if(surface == nil
|| surface->size.Width != size.Width
|| surface->size.Height != size.Height
|| surface->scale != scale)
surface = [[IOSurfaceHolder alloc] initWithSize:size withScale:scale withOpenGlContext:_glContext.getRaw()];
}
}
- (void)updateLayer {
if ([NSThread isMainThread])
{
@synchronized (lock) {
if(_layer == nil)
return;
[_layer setContents: nil];
if(surface != nil)
{
[_layer setContentsScale: surface->scale];
[_layer setContents: (__bridge IOSurface*) surface->surface];
}
}
}
else
dispatch_async(dispatch_get_main_queue(), ^{
[self updateLayer];
});
}
- (void) setNewLayer:(CALayer *)layer {
_layer = layer;
[self updateLayer];
}
- (HRESULT)setSwFrame:(AvnFramebuffer *)fb {
@synchronized (lock) {
if(fb->PixelFormat == AvnPixelFormat::kAvnRgb565)
return E_INVALIDARG;
if(surface == nil)
return E_FAIL;
IOSurfaceRef surf = surface->surface;
if(IOSurfaceLock(surf, 0, nil))
return E_FAIL;
size_t w = MIN(fb->Width, IOSurfaceGetWidth(surf));
size_t h = MIN(fb->Height, IOSurfaceGetHeight(surf));
size_t wbytes = w*4;
size_t sstride = IOSurfaceGetBytesPerRow(surf);
size_t fstride = fb->Stride;
char*pSurface = (char*)IOSurfaceGetBaseAddress(surf);
char*pFb = (char*)fb->Data;
for(size_t y = 0; y < h; y++)
{
memcpy(pSurface + y*sstride, pFb + y*fstride, wbytes);
}
IOSurfaceUnlock(surf, 0, nil);
[self updateLayer];
return S_OK;
}
}
-(IAvnGlSurfaceRenderTarget*) createSurfaceRenderTarget
{
return CreateGlRenderTarget(self);
}
@end
class AvnGlRenderingSession : public ComSingleObject<IAvnGlSurfaceRenderingSession, &IID_IAvnGlSurfaceRenderingSession>
{
ComPtr<IUnknown> _releaseContext;
IOSurfaceRenderTarget* _target;
IOSurfaceHolder* _surface;
public:
FORWARD_IUNKNOWN()
AvnGlRenderingSession(IOSurfaceRenderTarget* target, ComPtr<IUnknown> releaseContext)
{
_target = target;
// This happens in a synchronized block set up by AvnRenderTarget, so we take the current surface for this
// particular render session
_surface = _target->surface;
_releaseContext = releaseContext;
}
virtual HRESULT GetPixelSize(AvnPixelSize* ret) override
{
if(!_surface)
return E_FAIL;
*ret = _surface->size;
return S_OK;
}
virtual HRESULT GetScaling(double* ret) override
{
if(!_surface)
return E_FAIL;
*ret = _surface->scale;
return S_OK;
}
virtual ~AvnGlRenderingSession()
{
[_surface finishDraw];
[_target updateLayer];
_releaseContext = nil;
}
};
class AvnGlRenderTarget : public ComSingleObject<IAvnGlSurfaceRenderTarget, &IID_IAvnGlSurfaceRenderTarget>
{
IOSurfaceRenderTarget* _target;
public:
FORWARD_IUNKNOWN()
AvnGlRenderTarget(IOSurfaceRenderTarget* target)
{
_target = target;
}
virtual HRESULT BeginDrawing(IAvnGlSurfaceRenderingSession** ret) override
{
ComPtr<IUnknown> releaseContext;
@synchronized (_target->lock) {
if(_target->surface == nil)
return E_FAIL;
_target->_glContext->MakeCurrent(releaseContext.getPPV());
HRESULT res = [_target->surface prepareForGlRender];
if(res)
return res;
*ret = new AvnGlRenderingSession(_target, releaseContext);
return S_OK;
}
}
};
static IAvnGlSurfaceRenderTarget* CreateGlRenderTarget(IOSurfaceRenderTarget* target)
{
return new AvnGlRenderTarget(target);
}

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

@ -7,44 +7,9 @@
#include "cursor.h" #include "cursor.h"
#include "menu.h" #include "menu.h"
#include <OpenGL/gl.h> #include <OpenGL/gl.h>
#include "rendertarget.h"
class SoftwareDrawingOperation
{
public:
void* Data = 0;
AvnFramebuffer Desc;
void Alloc(NSView* view)
{
auto logicalSize = [view frame].size;
auto pixelSize = [view convertSizeToBacking:logicalSize];
int w = pixelSize.width;
int h = pixelSize.height;
int stride = w * 4;
Data = malloc(h * stride);
Desc = {
.Data = Data,
.Stride = stride,
.Width = w,
.Height = h,
.PixelFormat = kAvnRgba8888,
.Dpi = AvnVector { .X = w / logicalSize.width * 96, .Y = h / logicalSize.height * 96}
};
}
void Dealloc()
{
if(Data != NULL)
{
free(Data);
Data = NULL;
}
}
~SoftwareDrawingOperation()
{
Dealloc();
}
};
class WindowBaseImpl : public virtual ComSingleObject<IAvnWindowBase, &IID_IAvnWindowBase>, public INSWindowHolder class WindowBaseImpl : public virtual ComSingleObject<IAvnWindowBase, &IID_IAvnWindowBase>, public INSWindowHolder
{ {
@ -61,15 +26,18 @@ public:
AvnView* View; AvnView* View;
AvnWindow* Window; AvnWindow* Window;
ComPtr<IAvnWindowBaseEvents> BaseEvents; ComPtr<IAvnWindowBaseEvents> BaseEvents;
SoftwareDrawingOperation CurrentSwDrawingOperation; ComPtr<IAvnGlContext> _glContext;
NSObject<IRenderTarget>* renderTarget;
AvnPoint lastPositionSet; AvnPoint lastPositionSet;
NSString* _lastTitle; NSString* _lastTitle;
IAvnAppMenu* _mainMenu; IAvnAppMenu* _mainMenu;
WindowBaseImpl(IAvnWindowBaseEvents* events) WindowBaseImpl(IAvnWindowBaseEvents* events, IAvnGlContext* gl)
{ {
_mainMenu = nullptr; _mainMenu = nullptr;
BaseEvents = events; BaseEvents = events;
_glContext = gl;
renderTarget = [[IOSurfaceRenderTarget alloc] initWithOpenGlContext: gl];
View = [[AvnView alloc] initWithParent:this]; View = [[AvnView alloc] initWithParent:this];
Window = [[AvnWindow alloc] initWithParent:this]; Window = [[AvnWindow alloc] initWithParent:this];
@ -291,29 +259,6 @@ public:
return S_OK; return S_OK;
} }
virtual bool TryLock() override
{
@autoreleasepool
{
@try
{
return [View lockFocusIfCanDraw] == YES;
}
@catch (NSException*)
{
return NO;
}
}
}
virtual void Unlock() override
{
@autoreleasepool
{
[View unlockFocus];
}
}
virtual HRESULT BeginMoveDrag () override virtual HRESULT BeginMoveDrag () override
{ {
@autoreleasepool @autoreleasepool
@ -408,16 +353,6 @@ public:
return S_OK; return S_OK;
} }
virtual HRESULT GetSoftwareFramebuffer(AvnFramebuffer*ret) override
{
if(![[NSThread currentThread] isMainThread])
return E_FAIL;
if(CurrentSwDrawingOperation.Data == NULL)
CurrentSwDrawingOperation.Alloc(View);
*ret = CurrentSwDrawingOperation.Desc;
return S_OK;
}
virtual HRESULT SetCursor(IAvnCursor* cursor) override virtual HRESULT SetCursor(IAvnCursor* cursor) override
{ {
@autoreleasepool @autoreleasepool
@ -451,8 +386,8 @@ public:
{ {
if(View == NULL) if(View == NULL)
return E_FAIL; return E_FAIL;
*ppv = ::CreateGlRenderTarget(Window, View); *ppv = [renderTarget createSurfaceRenderTarget];
return S_OK; return *ppv == nil ? E_FAIL : S_OK;
} }
protected: protected:
@ -490,7 +425,7 @@ private:
} }
ComPtr<IAvnWindowEvents> WindowEvents; ComPtr<IAvnWindowEvents> WindowEvents;
WindowImpl(IAvnWindowEvents* events) : WindowBaseImpl(events) WindowImpl(IAvnWindowEvents* events, IAvnGlContext* gl) : WindowBaseImpl(events, gl)
{ {
WindowEvents = events; WindowEvents = events;
[Window setCanBecomeKeyAndMain]; [Window setCanBecomeKeyAndMain];
@ -731,6 +666,7 @@ NSArray* AllLoopModes = [NSArray arrayWithObjects: NSDefaultRunLoopMode, NSEvent
NSEvent* _lastMouseDownEvent; NSEvent* _lastMouseDownEvent;
bool _lastKeyHandled; bool _lastKeyHandled;
AvnPixelSize _lastPixelSize; AvnPixelSize _lastPixelSize;
NSObject<IRenderTarget>* _renderTarget;
} }
- (void)onClosed - (void)onClosed
@ -741,19 +677,6 @@ NSArray* AllLoopModes = [NSArray arrayWithObjects: NSDefaultRunLoopMode, NSEvent
} }
} }
- (BOOL)lockFocusIfCanDraw
{
@synchronized (self)
{
if(_parent == nullptr)
{
return NO;
}
}
return [super lockFocusIfCanDraw];
}
-(AvnPixelSize) getPixelSize -(AvnPixelSize) getPixelSize
{ {
return _lastPixelSize; return _lastPixelSize;
@ -764,18 +687,43 @@ NSArray* AllLoopModes = [NSArray arrayWithObjects: NSDefaultRunLoopMode, NSEvent
return _lastMouseDownEvent; return _lastMouseDownEvent;
} }
- (void) updateRenderTarget
{
[_renderTarget resize:_lastPixelSize withScale: [[self window] backingScaleFactor]];
[self setNeedsDisplayInRect:[self frame]];
}
-(AvnView*) initWithParent: (WindowBaseImpl*) parent -(AvnView*) initWithParent: (WindowBaseImpl*) parent
{ {
self = [super init]; self = [super init];
[self setWantsBestResolutionOpenGLSurface:true]; _renderTarget = parent->renderTarget;
[self setWantsLayer:YES]; [self setWantsLayer:YES];
[self setLayerContentsRedrawPolicy: NSViewLayerContentsRedrawDuringViewResize];
_parent = parent; _parent = parent;
_area = nullptr; _area = nullptr;
_lastPixelSize.Height = 100; _lastPixelSize.Height = 100;
_lastPixelSize.Width = 100; _lastPixelSize.Width = 100;
return self; return self;
} }
- (BOOL)isFlipped
{
return YES;
}
- (BOOL)wantsUpdateLayer
{
return YES;
}
- (void)setLayer:(CALayer *)layer
{
[_renderTarget setNewLayer: layer];
[super setLayer: layer];
}
- (BOOL)isOpaque - (BOOL)isOpaque
{ {
return YES; return YES;
@ -805,7 +753,12 @@ NSArray* AllLoopModes = [NSArray arrayWithObjects: NSDefaultRunLoopMode, NSEvent
[self removeTrackingArea:_area]; [self removeTrackingArea:_area];
_area = nullptr; _area = nullptr;
} }
if (_parent == nullptr)
{
return;
}
NSRect rect = NSZeroRect; NSRect rect = NSZeroRect;
rect.size = newSize; rect.size = newSize;
@ -818,87 +771,32 @@ NSArray* AllLoopModes = [NSArray arrayWithObjects: NSDefaultRunLoopMode, NSEvent
auto fsize = [self convertSizeToBacking: [self frame].size]; auto fsize = [self convertSizeToBacking: [self frame].size];
_lastPixelSize.Width = (int)fsize.width; _lastPixelSize.Width = (int)fsize.width;
_lastPixelSize.Height = (int)fsize.height; _lastPixelSize.Height = (int)fsize.height;
[self updateRenderTarget];
_parent->BaseEvents->Resized(AvnSize{newSize.width, newSize.height}); _parent->BaseEvents->Resized(AvnSize{newSize.width, newSize.height});
} }
- (void) drawFb: (AvnFramebuffer*) fb
{
auto colorSpace = CGColorSpaceCreateDeviceRGB();
auto dataProvider = CGDataProviderCreateWithData(NULL, fb->Data, fb->Height*fb->Stride, NULL);
auto image = CGImageCreate(fb->Width, fb->Height, 8, 32, fb->Stride, colorSpace, kCGBitmapByteOrderDefault | kCGImageAlphaPremultipliedLast,
dataProvider, nullptr, false, kCGRenderingIntentDefault);
auto ctx = [NSGraphicsContext currentContext];
[ctx saveGraphicsState];
auto cgc = [ctx CGContext];
CGContextDrawImage(cgc, CGRect{0,0, fb->Width/(fb->Dpi.X/96), fb->Height/(fb->Dpi.Y/96)}, image);
CGImageRelease(image);
CGColorSpaceRelease(colorSpace);
CGDataProviderRelease(dataProvider);
[ctx restoreGraphicsState];
}
- (void)drawRect:(NSRect)dirtyRect - (void)updateLayer
{ {
if (_parent == nullptr) if (_parent == nullptr)
{ {
return; return;
} }
_parent->BaseEvents->RunRenderPriorityJobs();
@synchronized (self) {
if(_swRenderedFrame != NULL)
{
[self drawFb: &_swRenderedFrameBuffer];
return;
}
}
auto swOp = &_parent->CurrentSwDrawingOperation; _parent->BaseEvents->RunRenderPriorityJobs();
_parent->BaseEvents->Paint(); _parent->BaseEvents->Paint();
if(swOp->Data != NULL)
[self drawFb: &swOp->Desc];
swOp->Dealloc();
return;
} }
-(void) redrawSelf - (void)drawRect:(NSRect)dirtyRect
{ {
@autoreleasepool return;
{
@synchronized(self)
{
if(!_queuedDisplayFromThread)
return;
_queuedDisplayFromThread = false;
}
[self setNeedsDisplayInRect:[self frame]];
[self display];
}
} }
-(void) setSwRenderedFrame: (AvnFramebuffer*) fb dispose: (IUnknown*) dispose -(void) setSwRenderedFrame: (AvnFramebuffer*) fb dispose: (IUnknown*) dispose
{ {
@autoreleasepool { @autoreleasepool {
@synchronized (self) { [_renderTarget setSwFrame:fb];
_swRenderedFrame = dispose; dispose->Release();
_swRenderedFrameBuffer = *fb;
if(!_queuedDisplayFromThread)
{
_queuedDisplayFromThread = true;
[self performSelector:@selector(redrawSelf) onThread:[NSThread mainThread] withObject:NULL waitUntilDone:false modes: AllLoopModes];
}
}
} }
} }
@ -923,7 +821,7 @@ NSArray* AllLoopModes = [NSArray arrayWithObjects: NSDefaultRunLoopMode, NSEvent
auto fsize = [self convertSizeToBacking: [self frame].size]; auto fsize = [self convertSizeToBacking: [self frame].size];
_lastPixelSize.Width = (int)fsize.width; _lastPixelSize.Width = (int)fsize.width;
_lastPixelSize.Height = (int)fsize.height; _lastPixelSize.Height = (int)fsize.height;
[self updateRenderTarget];
_parent->BaseEvents->ScalingChanged([_parent->Window backingScaleFactor]); _parent->BaseEvents->ScalingChanged([_parent->Window backingScaleFactor]);
[super viewDidChangeBackingProperties]; [super viewDidChangeBackingProperties];
@ -1473,7 +1371,7 @@ private:
END_INTERFACE_MAP() END_INTERFACE_MAP()
virtual ~PopupImpl(){} virtual ~PopupImpl(){}
ComPtr<IAvnWindowEvents> WindowEvents; ComPtr<IAvnWindowEvents> WindowEvents;
PopupImpl(IAvnWindowEvents* events) : WindowBaseImpl(events) PopupImpl(IAvnWindowEvents* events, IAvnGlContext* gl) : WindowBaseImpl(events, gl)
{ {
WindowEvents = events; WindowEvents = events;
[Window setLevel:NSPopUpMenuWindowLevel]; [Window setLevel:NSPopUpMenuWindowLevel];
@ -1497,20 +1395,20 @@ protected:
} }
}; };
extern IAvnPopup* CreateAvnPopup(IAvnWindowEvents*events) extern IAvnPopup* CreateAvnPopup(IAvnWindowEvents*events, IAvnGlContext* gl)
{ {
@autoreleasepool @autoreleasepool
{ {
IAvnPopup* ptr = dynamic_cast<IAvnPopup*>(new PopupImpl(events)); IAvnPopup* ptr = dynamic_cast<IAvnPopup*>(new PopupImpl(events, gl));
return ptr; return ptr;
} }
} }
extern IAvnWindow* CreateAvnWindow(IAvnWindowEvents*events) extern IAvnWindow* CreateAvnWindow(IAvnWindowEvents*events, IAvnGlContext* gl)
{ {
@autoreleasepool @autoreleasepool
{ {
IAvnWindow* ptr = (IAvnWindow*)new WindowImpl(events); IAvnWindow* ptr = (IAvnWindow*)new WindowImpl(events, gl);
return ptr; return ptr;
} }
} }

1
nukebuild/Build.cs

@ -191,6 +191,7 @@ partial class Build : NukeBuild
RunCoreTest("./tests/Avalonia.Animation.UnitTests"); RunCoreTest("./tests/Avalonia.Animation.UnitTests");
RunCoreTest("./tests/Avalonia.Base.UnitTests"); RunCoreTest("./tests/Avalonia.Base.UnitTests");
RunCoreTest("./tests/Avalonia.Controls.UnitTests"); RunCoreTest("./tests/Avalonia.Controls.UnitTests");
RunCoreTest("./tests/Avalonia.Controls.DataGrid.UnitTests");
RunCoreTest("./tests/Avalonia.Input.UnitTests"); RunCoreTest("./tests/Avalonia.Input.UnitTests");
RunCoreTest("./tests/Avalonia.Interactivity.UnitTests"); RunCoreTest("./tests/Avalonia.Interactivity.UnitTests");
RunCoreTest("./tests/Avalonia.Layout.UnitTests"); RunCoreTest("./tests/Avalonia.Layout.UnitTests");

6
readme.md

@ -10,18 +10,22 @@
**Avalonia** is a WPF/UWP-inspired cross-platform XAML-based UI framework providing a flexible styling system and supporting a wide range of Operating Systems such as Windows (.NET Framework, .NET Core), Linux (via Xorg), macOS and with experimental support for Android and iOS. **Avalonia** is a WPF/UWP-inspired cross-platform XAML-based UI framework providing a flexible styling system and supporting a wide range of Operating Systems such as Windows (.NET Framework, .NET Core), Linux (via Xorg), macOS and with experimental support for Android and iOS.
**Avalonia** is ready for **General-Purpose Desktop App Development**. However, there may be some bugs and breaking changes as we continue along into this project's development. To see the status of some of our features, please see our [Roadmap here](https://github.com/AvaloniaUI/Avalonia/issues/2239). **Avalonia** is ready for **General-Purpose Desktop App Development**. However, there may be some bugs and [breaking changes](https://github.com/AvaloniaUI/Avalonia/wiki/Breaking-Changes) as we continue along into this project's development. To see the status of some of our features, please see our [Roadmap here](https://github.com/AvaloniaUI/Avalonia/issues/2239).
| Control catalog | Desktop platforms | Mobile platforms | | Control catalog | Desktop platforms | Mobile platforms |
|---|---|---| |---|---|---|
| <a href='https://youtu.be/wHcB3sGLVYg'><img width='300' src='http://avaloniaui.net/images/screen.png'></a> | <a href='https://www.youtube.com/watch?t=28&v=c_AB_XSILp0' target='_blank'><img width='300' src='http://avaloniaui.net/images/avalonia-video.png'></a> | <a href='https://www.youtube.com/watch?v=NJ9-hnmUbBM' target='_blank'><img width='300' src='https://i.ytimg.com/vi/NJ9-hnmUbBM/hqdefault.jpg'></a> | | <a href='https://youtu.be/wHcB3sGLVYg'><img width='300' src='http://avaloniaui.net/images/screen.png'></a> | <a href='https://www.youtube.com/watch?t=28&v=c_AB_XSILp0' target='_blank'><img width='300' src='http://avaloniaui.net/images/avalonia-video.png'></a> | <a href='https://www.youtube.com/watch?v=NJ9-hnmUbBM' target='_blank'><img width='300' src='https://i.ytimg.com/vi/NJ9-hnmUbBM/hqdefault.jpg'></a> |
[Awesome Avalonia](https://github.com/AvaloniaCommunity/awesome-avalonia) is curated list of awesome Avalonia UI tools, libraries, projects and resources.
## Getting Started ## Getting Started
Avalonia [Visual Studio Extension](https://marketplace.visualstudio.com/items?itemName=AvaloniaTeam.AvaloniaforVisualStudio) contains project and control templates that will help you get started. After installing it, open "New Project" dialog in Visual Studio, choose "Avalonia" in "Visual C#" section, select "Avalonia .NET Core Application" and press OK (<a href="http://avaloniaui.net/docs/quickstart/images/new-project-dialog.png">screenshot</a>). Now you can write code and markup that will work on multiple platforms! Avalonia [Visual Studio Extension](https://marketplace.visualstudio.com/items?itemName=AvaloniaTeam.AvaloniaforVisualStudio) contains project and control templates that will help you get started. After installing it, open "New Project" dialog in Visual Studio, choose "Avalonia" in "Visual C#" section, select "Avalonia .NET Core Application" and press OK (<a href="http://avaloniaui.net/docs/quickstart/images/new-project-dialog.png">screenshot</a>). Now you can write code and markup that will work on multiple platforms!
For those without Visual Studio, a starter guide for .NET Core CLI can be found [here](http://avaloniaui.net/docs/quickstart/create-new-project#net-core). For those without Visual Studio, a starter guide for .NET Core CLI can be found [here](http://avaloniaui.net/docs/quickstart/create-new-project#net-core).
If you need to develop Avalonia app with JetBrains Rider, go and *vote* on [this issue](https://youtrack.jetbrains.com/issue/RIDER-39247) in their tracker. JetBrains won't do things without their users telling them that they want the feature, so only **YOU** can make it happen.
Avalonia is delivered via <b>NuGet</b> package manager. You can find the packages here: [stable(ish)](https://www.nuget.org/packages/Avalonia/) Avalonia is delivered via <b>NuGet</b> package manager. You can find the packages here: [stable(ish)](https://www.nuget.org/packages/Avalonia/)
Use these commands in the Package Manager console to install Avalonia manually: Use these commands in the Package Manager console to install Avalonia manually:

8
samples/ControlCatalog/Pages/ProgressBarPage.xaml

@ -6,15 +6,19 @@
<TextBlock Classes="h2">A progress bar control</TextBlock> <TextBlock Classes="h2">A progress bar control</TextBlock>
<StackPanel> <StackPanel>
<CheckBox
x:Name="showProgress"
Margin="10,16,0,0"
Content="Show Progress Text" />
<StackPanel Orientation="Horizontal" <StackPanel Orientation="Horizontal"
Margin="0,16,0,0" Margin="0,16,0,0"
HorizontalAlignment="Center" HorizontalAlignment="Center"
Spacing="16"> Spacing="16">
<StackPanel Spacing="16"> <StackPanel Spacing="16">
<ProgressBar Value="{Binding #hprogress.Value}" /> <ProgressBar ShowProgressText="{Binding #showProgress.IsChecked}" Value="{Binding #hprogress.Value}" />
<ProgressBar IsIndeterminate="True"/> <ProgressBar IsIndeterminate="True"/>
</StackPanel> </StackPanel>
<ProgressBar Value="{Binding #vprogress.Value}" Orientation="Vertical" /> <ProgressBar ShowProgressText="{Binding #showProgress.IsChecked}" Value="{Binding #vprogress.Value}" Orientation="Vertical" />
<ProgressBar Orientation="Vertical" IsIndeterminate="True" /> <ProgressBar Orientation="Vertical" IsIndeterminate="True" />
</StackPanel> </StackPanel>
<StackPanel Margin="16"> <StackPanel Margin="16">

9
src/Avalonia.Base/AvaloniaObject.cs

@ -34,7 +34,6 @@ namespace Avalonia
public AvaloniaObject() public AvaloniaObject()
{ {
VerifyAccess(); VerifyAccess();
AvaloniaPropertyRegistry.Instance.NotifyInitialized(this);
} }
/// <summary> /// <summary>
@ -479,7 +478,13 @@ namespace Avalonia
} }
} }
void IValueSink.Completed(AvaloniaProperty property, IPriorityValueEntry entry) { } void IValueSink.Completed<T>(
StyledPropertyBase<T> property,
IPriorityValueEntry entry,
Optional<T> oldValue)
{
((IValueSink)this).ValueChanged(property, BindingPriority.Unset, oldValue, default);
}
/// <summary> /// <summary>
/// Called for each inherited property when the <see cref="InheritanceParent"/> changes. /// Called for each inherited property when the <see cref="InheritanceParent"/> changes.

43
src/Avalonia.Base/AvaloniaProperty.cs

@ -3,9 +3,7 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Diagnostics;
using System.Reactive.Subjects; using System.Reactive.Subjects;
using System.Reflection;
using Avalonia.Data; using Avalonia.Data;
using Avalonia.Utilities; using Avalonia.Utilities;
@ -22,7 +20,6 @@ namespace Avalonia
public static readonly object UnsetValue = new UnsetValueType(); public static readonly object UnsetValue = new UnsetValueType();
private static int s_nextId; private static int s_nextId;
private readonly Subject<AvaloniaPropertyChangedEventArgs> _initialized;
private readonly Subject<AvaloniaPropertyChangedEventArgs> _changed; private readonly Subject<AvaloniaPropertyChangedEventArgs> _changed;
private readonly PropertyMetadata _defaultMetadata; private readonly PropertyMetadata _defaultMetadata;
private readonly Dictionary<Type, PropertyMetadata> _metadata; private readonly Dictionary<Type, PropertyMetadata> _metadata;
@ -55,7 +52,6 @@ namespace Avalonia
throw new ArgumentException("'name' may not contain periods."); throw new ArgumentException("'name' may not contain periods.");
} }
_initialized = new Subject<AvaloniaPropertyChangedEventArgs>();
_changed = new Subject<AvaloniaPropertyChangedEventArgs>(); _changed = new Subject<AvaloniaPropertyChangedEventArgs>();
_metadata = new Dictionary<Type, PropertyMetadata>(); _metadata = new Dictionary<Type, PropertyMetadata>();
@ -83,7 +79,6 @@ namespace Avalonia
Contract.Requires<ArgumentNullException>(source != null); Contract.Requires<ArgumentNullException>(source != null);
Contract.Requires<ArgumentNullException>(ownerType != null); Contract.Requires<ArgumentNullException>(ownerType != null);
_initialized = source._initialized;
_changed = source._changed; _changed = source._changed;
_metadata = new Dictionary<Type, PropertyMetadata>(); _metadata = new Dictionary<Type, PropertyMetadata>();
@ -138,22 +133,6 @@ namespace Avalonia
/// </summary> /// </summary>
public virtual bool IsReadOnly => false; public virtual bool IsReadOnly => false;
/// <summary>
/// Gets an observable that is fired when this property is initialized on a
/// new <see cref="AvaloniaObject"/> instance.
/// </summary>
/// <remarks>
/// This observable is fired each time a new <see cref="AvaloniaObject"/> is constructed
/// for all properties registered on the object's type. The default value of the property
/// for the object is passed in the args' NewValue (OldValue will always be
/// <see cref="UnsetValue"/>.
/// </remarks>
/// <value>
/// An observable that is fired when this property is initialized on a new
/// <see cref="AvaloniaObject"/> instance.
/// </value>
public IObservable<AvaloniaPropertyChangedEventArgs> Initialized => _initialized;
/// <summary> /// <summary>
/// Gets an observable that is fired when this property changes on any /// Gets an observable that is fired when this property changes on any
/// <see cref="AvaloniaObject"/> instance. /// <see cref="AvaloniaObject"/> instance.
@ -490,26 +469,6 @@ namespace Avalonia
return Name; return Name;
} }
/// <summary>
/// True if <see cref="Initialized"/> has any observers.
/// </summary>
internal bool HasNotifyInitializedObservers => _initialized.HasObservers;
/// <summary>
/// Notifies the <see cref="Initialized"/> observable.
/// </summary>
/// <param name="o">The object being initialized.</param>
internal abstract void NotifyInitialized(IAvaloniaObject o);
/// <summary>
/// Notifies the <see cref="Initialized"/> observable.
/// </summary>
/// <param name="e">The observable arguments.</param>
internal void NotifyInitialized(AvaloniaPropertyChangedEventArgs e)
{
_initialized.OnNext(e);
}
/// <summary> /// <summary>
/// Notifies the <see cref="Changed"/> observable. /// Notifies the <see cref="Changed"/> observable.
/// </summary> /// </summary>
@ -602,7 +561,7 @@ namespace Avalonia
return result; return result;
} }
currentType = currentType.GetTypeInfo().BaseType; currentType = currentType.BaseType;
} }
_metadataCache[type] = _defaultMetadata; _metadataCache[type] = _defaultMetadata;

45
src/Avalonia.Base/AvaloniaPropertyRegistry.cs

@ -415,51 +415,6 @@ namespace Avalonia
_inheritedCache.Clear(); _inheritedCache.Clear();
} }
internal void NotifyInitialized(AvaloniaObject o)
{
Contract.Requires<ArgumentNullException>(o != null);
var type = o.GetType();
if (!_initializedCache.TryGetValue(type, out var initializationData))
{
var visited = new HashSet<AvaloniaProperty>();
initializationData = new List<PropertyInitializationData>();
foreach (AvaloniaProperty property in GetRegistered(type))
{
if (property.IsDirect)
{
initializationData.Add(new PropertyInitializationData(property, (IDirectPropertyAccessor)property));
}
else
{
initializationData.Add(new PropertyInitializationData(property, (IStyledPropertyAccessor)property, type));
}
visited.Add(property);
}
foreach (AvaloniaProperty property in GetRegisteredAttached(type))
{
if (!visited.Contains(property))
{
initializationData.Add(new PropertyInitializationData(property, (IStyledPropertyAccessor)property, type));
visited.Add(property);
}
}
_initializedCache.Add(type, initializationData);
}
foreach (PropertyInitializationData data in initializationData)
{
data.Property.NotifyInitialized(o);
}
}
private readonly struct PropertyInitializationData private readonly struct PropertyInitializationData
{ {
public AvaloniaProperty Property { get; } public AvaloniaProperty Property { get; }

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

@ -327,6 +327,8 @@ namespace Avalonia.Collections
} }
else else
{ {
EnsureCapacity(_inner.Count + list.Count);
using (IEnumerator<T> en = items.GetEnumerator()) using (IEnumerator<T> en = items.GetEnumerator())
{ {
int insertIndex = index; int insertIndex = index;
@ -550,6 +552,24 @@ 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>

15
src/Avalonia.Base/DirectPropertyBase.cs

@ -101,21 +101,6 @@ namespace Avalonia
return (DirectPropertyMetadata<TValue>)base.GetMetadata(type); return (DirectPropertyMetadata<TValue>)base.GetMetadata(type);
} }
/// <inheritdoc/>
internal override void NotifyInitialized(IAvaloniaObject o)
{
if (HasNotifyInitializedObservers)
{
var e = new AvaloniaPropertyChangedEventArgs<TValue>(
o,
this,
default,
InvokeGetter(o),
BindingPriority.Unset);
NotifyInitialized(e);
}
}
/// <inheritdoc/> /// <inheritdoc/>
internal override void RouteClearValue(IAvaloniaObject o) internal override void RouteClearValue(IAvaloniaObject o)
{ {

4
src/Avalonia.Base/PropertyStore/BindingEntry.cs

@ -48,10 +48,10 @@ namespace Avalonia.PropertyStore
{ {
_subscription?.Dispose(); _subscription?.Dispose();
_subscription = null; _subscription = null;
_sink.Completed(Property, this); _sink.Completed(Property, this, Value);
} }
public void OnCompleted() => _sink.Completed(Property, this); public void OnCompleted() => _sink.Completed(Property, this, Value);
public void OnError(Exception error) public void OnError(Exception error)
{ {

5
src/Avalonia.Base/PropertyStore/IValueSink.cs

@ -15,6 +15,9 @@ namespace Avalonia.PropertyStore
Optional<T> oldValue, Optional<T> oldValue,
BindingValue<T> newValue); BindingValue<T> newValue);
void Completed(AvaloniaProperty property, IPriorityValueEntry entry); void Completed<T>(
StyledPropertyBase<T> property,
IPriorityValueEntry entry,
Optional<T> oldValue);
} }
} }

5
src/Avalonia.Base/PropertyStore/PriorityValue.cs

@ -117,7 +117,10 @@ namespace Avalonia.PropertyStore
UpdateEffectiveValue(); UpdateEffectiveValue();
} }
void IValueSink.Completed(AvaloniaProperty property, IPriorityValueEntry entry) void IValueSink.Completed<TValue>(
StyledPropertyBase<TValue> property,
IPriorityValueEntry entry,
Optional<TValue> oldValue)
{ {
_entries.Remove((IPriorityValueEntry<T>)entry); _entries.Remove((IPriorityValueEntry<T>)entry);
UpdateEffectiveValue(); UpdateEffectiveValue();

15
src/Avalonia.Base/StyledPropertyBase.cs

@ -181,21 +181,6 @@ namespace Avalonia
/// <inheritdoc/> /// <inheritdoc/>
object IStyledPropertyAccessor.GetDefaultValue(Type type) => GetDefaultBoxedValue(type); object IStyledPropertyAccessor.GetDefaultValue(Type type) => GetDefaultBoxedValue(type);
/// <inheritdoc/>
internal override void NotifyInitialized(IAvaloniaObject o)
{
if (HasNotifyInitializedObservers)
{
var e = new AvaloniaPropertyChangedEventArgs<TValue>(
o,
this,
default,
o.GetValue(this),
BindingPriority.Unset);
NotifyInitialized(e);
}
}
/// <inheritdoc/> /// <inheritdoc/>
internal override void RouteClearValue(IAvaloniaObject o) internal override void RouteClearValue(IAvaloniaObject o)
{ {

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

@ -92,8 +92,7 @@ namespace Avalonia.Utilities
/// <returns>True if the type accepts null values; otherwise false.</returns> /// <returns>True if the type accepts null values; otherwise false.</returns>
public static bool AcceptsNull(Type type) public static bool AcceptsNull(Type type)
{ {
var t = type.GetTypeInfo(); return !type.IsValueType || IsNullableType(type);
return !t.IsValueType || (t.IsGenericType && (t.GetGenericTypeDefinition() == typeof(Nullable<>)));
} }
/// <summary> /// <summary>
@ -119,10 +118,8 @@ namespace Avalonia.Utilities
} }
var from = value.GetType(); var from = value.GetType();
var fromTypeInfo = from.GetTypeInfo();
var toTypeInfo = to.GetTypeInfo();
if (toTypeInfo.IsAssignableFrom(fromTypeInfo)) if (to.IsAssignableFrom(from))
{ {
result = value; result = value;
return true; return true;
@ -134,7 +131,7 @@ namespace Avalonia.Utilities
return true; return true;
} }
if (toTypeInfo.IsEnum && from == typeof(string)) if (to.IsEnum && from == typeof(string))
{ {
if (Enum.IsDefined(to, (string)value)) if (Enum.IsDefined(to, (string)value))
{ {
@ -143,7 +140,7 @@ namespace Avalonia.Utilities
} }
} }
if (!fromTypeInfo.IsEnum && toTypeInfo.IsEnum) if (!from.IsEnum && to.IsEnum)
{ {
result = null; result = null;
@ -154,7 +151,7 @@ namespace Avalonia.Utilities
} }
} }
if (fromTypeInfo.IsEnum && IsNumeric(to)) if (from.IsEnum && IsNumeric(to))
{ {
try try
{ {
@ -223,10 +220,8 @@ namespace Avalonia.Utilities
} }
var from = value.GetType(); var from = value.GetType();
var fromTypeInfo = from.GetTypeInfo();
var toTypeInfo = to.GetTypeInfo();
if (toTypeInfo.IsAssignableFrom(fromTypeInfo)) if (to.IsAssignableFrom(from))
{ {
result = value; result = value;
return true; return true;
@ -307,9 +302,7 @@ namespace Avalonia.Utilities
/// <returns>The default value.</returns> /// <returns>The default value.</returns>
public static object Default(Type type) public static object Default(Type type)
{ {
var typeInfo = type.GetTypeInfo(); if (type.IsValueType)
if (typeInfo.IsValueType)
{ {
return Activator.CreateInstance(type); return Activator.CreateInstance(type);
} }
@ -335,9 +328,11 @@ namespace Avalonia.Utilities
return false; return false;
} }
if (type.GetTypeInfo().IsGenericType && type.GetGenericTypeDefinition() == typeof(Nullable<>)) Type underlyingType = Nullable.GetUnderlyingType(type);
if (underlyingType != null)
{ {
return IsNumeric(Nullable.GetUnderlyingType(type)); return IsNumeric(underlyingType);
} }
else else
{ {
@ -352,6 +347,11 @@ namespace Avalonia.Utilities
Explicit = 2 Explicit = 2
} }
private static bool IsNullableType(Type type)
{
return type.IsGenericType && type.GetGenericTypeDefinition() == typeof(Nullable<>);
}
private static MethodInfo FindTypeConversionOperatorMethod(Type fromType, Type toType, OperatorType operatorType) private static MethodInfo FindTypeConversionOperatorMethod(Type fromType, Type toType, OperatorType operatorType)
{ {
const string implicitName = "op_Implicit"; const string implicitName = "op_Implicit";

94
src/Avalonia.Base/Utilities/ValueSingleOrList.cs

@ -0,0 +1,94 @@
using System.Collections.Generic;
namespace Avalonia.Utilities
{
/// <summary>
/// A list like struct optimized for holding zero or one items.
/// </summary>
/// <typeparam name="T">The type of items held in the list.</typeparam>
/// <remarks>
/// Once more than value has been added to this storage it will switch to using <see cref="List"/> internally.
/// </remarks>
public ref struct ValueSingleOrList<T>
{
private bool _isSingleSet;
/// <summary>
/// Single contained value. Only valid if <see cref="IsSingle"/> is set.
/// </summary>
public T Single { get; private set; }
/// <summary>
/// List of values.
/// </summary>
public List<T> List { get; private set; }
/// <summary>
/// If this struct is backed by a list.
/// </summary>
public bool HasList => List != null;
/// <summary>
/// If this struct contains only single value and storage was not promoted to a list.
/// </summary>
public bool IsSingle => List == null && _isSingleSet;
/// <summary>
/// Adds a value.
/// </summary>
/// <param name="value">Value to add.</param>
public void Add(T value)
{
if (List != null)
{
List.Add(value);
}
else
{
if (!_isSingleSet)
{
Single = value;
_isSingleSet = true;
}
else
{
List = new List<T>();
List.Add(Single);
List.Add(value);
Single = default;
}
}
}
/// <summary>
/// Removes a value.
/// </summary>
/// <param name="value">Value to remove.</param>
public bool Remove(T value)
{
if (List != null)
{
return List.Remove(value);
}
if (!_isSingleSet)
{
return false;
}
if (EqualityComparer<T>.Default.Equals(Single, value))
{
Single = default;
_isSingleSet = false;
return true;
}
return false;
}
}
}

9
src/Avalonia.Base/Utilities/WeakEventHandlerManager.cs

@ -183,11 +183,16 @@ namespace Avalonia.Utilities
for (int c = 0; c < _count; c++) for (int c = 0; c < _count; c++)
{ {
var r = _data[c]; var r = _data[c];
TSubscriber target = null;
r.Subscriber?.TryGetTarget(out target);
//Mark current index as first empty //Mark current index as first empty
if (r.Subscriber == null && empty == -1) if (target == null && empty == -1)
empty = c; empty = c;
//If current element isn't null and we have an empty one //If current element isn't null and we have an empty one
if (r.Subscriber != null && empty != -1) if (target != null && empty != -1)
{ {
_data[c] = default; _data[c] = default;
_data[empty] = r; _data[empty] = r;

9
src/Avalonia.Base/ValueStore.cs

@ -148,7 +148,7 @@ namespace Avalonia
_values.Remove(property); _values.Remove(property);
_sink.ValueChanged( _sink.ValueChanged(
property, property,
BindingPriority.LocalValue, BindingPriority.Unset,
old, old,
BindingValue<T>.Unset); BindingValue<T>.Unset);
} }
@ -190,13 +190,17 @@ namespace Avalonia
_sink.ValueChanged(property, priority, oldValue, newValue); _sink.ValueChanged(property, priority, oldValue, newValue);
} }
void IValueSink.Completed(AvaloniaProperty property, IPriorityValueEntry entry) void IValueSink.Completed<T>(
StyledPropertyBase<T> property,
IPriorityValueEntry entry,
Optional<T> oldValue)
{ {
if (_values.TryGetValue(property, out var slot)) if (_values.TryGetValue(property, out var slot))
{ {
if (slot == entry) if (slot == entry)
{ {
_values.Remove(property); _values.Remove(property);
_sink.Completed(property, entry, oldValue);
} }
} }
} }
@ -228,6 +232,7 @@ namespace Avalonia
else else
{ {
var priorityValue = new PriorityValue<T>(_owner, property, this, l); var priorityValue = new PriorityValue<T>(_owner, property, this, l);
priorityValue.SetValue(value, priority);
_values.SetValue(property, priorityValue); _values.SetValue(property, priorityValue);
} }
} }

2
src/Avalonia.Controls.DataGrid/Collections/DataGridSortDescription.cs

@ -238,7 +238,7 @@ namespace Avalonia.Collections
} }
else else
{ {
return seq.ThenByDescending(o => GetValue(o), InternalComparer); return seq.ThenBy(o => GetValue(o), InternalComparer);
} }
} }

45
src/Avalonia.Controls.DataGrid/DataGrid.cs

@ -149,6 +149,9 @@ namespace Avalonia.Controls
private IEnumerable _items; private IEnumerable _items;
public event EventHandler<ScrollEventArgs> HorizontalScroll;
public event EventHandler<ScrollEventArgs> VerticalScroll;
/// <summary> /// <summary>
/// Identifies the CanUserReorderColumns dependency property. /// Identifies the CanUserReorderColumns dependency property.
/// </summary> /// </summary>
@ -373,7 +376,11 @@ namespace Avalonia.Controls
public bool IsValid public bool IsValid
{ {
get { return _isValid; } get { return _isValid; }
internal set { SetAndRaise(IsValidProperty, ref _isValid, value); } internal set
{
SetAndRaise(IsValidProperty, ref _isValid, value);
PseudoClasses.Set(":invalid", !value);
}
} }
public static readonly StyledProperty<double> MaxColumnWidthProperty = public static readonly StyledProperty<double> MaxColumnWidthProperty =
@ -656,8 +663,6 @@ namespace Avalonia.Controls
HorizontalScrollBarVisibilityProperty, HorizontalScrollBarVisibilityProperty,
VerticalScrollBarVisibilityProperty); VerticalScrollBarVisibilityProperty);
PseudoClass<DataGrid, bool>(IsValidProperty, x => !x, ":invalid");
ItemsProperty.Changed.AddClassHandler<DataGrid>((x, e) => x.OnItemsPropertyChanged(e)); ItemsProperty.Changed.AddClassHandler<DataGrid>((x, e) => x.OnItemsPropertyChanged(e));
CanUserResizeColumnsProperty.Changed.AddClassHandler<DataGrid>((x, e) => x.OnCanUserResizeColumnsChanged(e)); CanUserResizeColumnsProperty.Changed.AddClassHandler<DataGrid>((x, e) => x.OnCanUserResizeColumnsChanged(e));
ColumnWidthProperty.Changed.AddClassHandler<DataGrid>((x, e) => x.OnColumnWidthChanged(e)); ColumnWidthProperty.Changed.AddClassHandler<DataGrid>((x, e) => x.OnColumnWidthChanged(e));
@ -2472,25 +2477,25 @@ namespace Avalonia.Controls
internal bool ProcessDownKey(KeyEventArgs e) internal bool ProcessDownKey(KeyEventArgs e)
{ {
KeyboardHelper.GetMetaKeyState(e.Modifiers, out bool ctrl, out bool shift); KeyboardHelper.GetMetaKeyState(e.KeyModifiers, out bool ctrl, out bool shift);
return ProcessDownKeyInternal(shift, ctrl); return ProcessDownKeyInternal(shift, ctrl);
} }
internal bool ProcessEndKey(KeyEventArgs e) internal bool ProcessEndKey(KeyEventArgs e)
{ {
KeyboardHelper.GetMetaKeyState(e.Modifiers, out bool ctrl, out bool shift); KeyboardHelper.GetMetaKeyState(e.KeyModifiers, out bool ctrl, out bool shift);
return ProcessEndKey(shift, ctrl); return ProcessEndKey(shift, ctrl);
} }
internal bool ProcessEnterKey(KeyEventArgs e) internal bool ProcessEnterKey(KeyEventArgs e)
{ {
KeyboardHelper.GetMetaKeyState(e.Modifiers, out bool ctrl, out bool shift); KeyboardHelper.GetMetaKeyState(e.KeyModifiers, out bool ctrl, out bool shift);
return ProcessEnterKey(shift, ctrl); return ProcessEnterKey(shift, ctrl);
} }
internal bool ProcessHomeKey(KeyEventArgs e) internal bool ProcessHomeKey(KeyEventArgs e)
{ {
KeyboardHelper.GetMetaKeyState(e.Modifiers, out bool ctrl, out bool shift); KeyboardHelper.GetMetaKeyState(e.KeyModifiers, out bool ctrl, out bool shift);
return ProcessHomeKey(shift, ctrl); return ProcessHomeKey(shift, ctrl);
} }
@ -2530,25 +2535,25 @@ namespace Avalonia.Controls
internal bool ProcessLeftKey(KeyEventArgs e) internal bool ProcessLeftKey(KeyEventArgs e)
{ {
KeyboardHelper.GetMetaKeyState(e.Modifiers, out bool ctrl, out bool shift); KeyboardHelper.GetMetaKeyState(e.KeyModifiers, out bool ctrl, out bool shift);
return ProcessLeftKey(shift, ctrl); return ProcessLeftKey(shift, ctrl);
} }
internal bool ProcessNextKey(KeyEventArgs e) internal bool ProcessNextKey(KeyEventArgs e)
{ {
KeyboardHelper.GetMetaKeyState(e.Modifiers, out bool ctrl, out bool shift); KeyboardHelper.GetMetaKeyState(e.KeyModifiers, out bool ctrl, out bool shift);
return ProcessNextKey(shift, ctrl); return ProcessNextKey(shift, ctrl);
} }
internal bool ProcessPriorKey(KeyEventArgs e) internal bool ProcessPriorKey(KeyEventArgs e)
{ {
KeyboardHelper.GetMetaKeyState(e.Modifiers, out bool ctrl, out bool shift); KeyboardHelper.GetMetaKeyState(e.KeyModifiers, out bool ctrl, out bool shift);
return ProcessPriorKey(shift, ctrl); return ProcessPriorKey(shift, ctrl);
} }
internal bool ProcessRightKey(KeyEventArgs e) internal bool ProcessRightKey(KeyEventArgs e)
{ {
KeyboardHelper.GetMetaKeyState(e.Modifiers, out bool ctrl, out bool shift); KeyboardHelper.GetMetaKeyState(e.KeyModifiers, out bool ctrl, out bool shift);
return ProcessRightKey(shift, ctrl); return ProcessRightKey(shift, ctrl);
} }
@ -2666,7 +2671,7 @@ namespace Avalonia.Controls
internal bool ProcessUpKey(KeyEventArgs e) internal bool ProcessUpKey(KeyEventArgs e)
{ {
KeyboardHelper.GetMetaKeyState(e.Modifiers, out bool ctrl, out bool shift); KeyboardHelper.GetMetaKeyState(e.KeyModifiers, out bool ctrl, out bool shift);
return ProcessUpKey(shift, ctrl); return ProcessUpKey(shift, ctrl);
} }
@ -2934,7 +2939,7 @@ namespace Avalonia.Controls
//TODO: Ensure left button is checked for //TODO: Ensure left button is checked for
internal bool UpdateStateOnMouseLeftButtonDown(PointerPressedEventArgs pointerPressedEventArgs, int columnIndex, int slot, bool allowEdit) internal bool UpdateStateOnMouseLeftButtonDown(PointerPressedEventArgs pointerPressedEventArgs, int columnIndex, int slot, bool allowEdit)
{ {
KeyboardHelper.GetMetaKeyState(pointerPressedEventArgs.InputModifiers, out bool ctrl, out bool shift); KeyboardHelper.GetMetaKeyState(pointerPressedEventArgs.KeyModifiers, out bool ctrl, out bool shift);
return UpdateStateOnMouseLeftButtonDown(pointerPressedEventArgs, columnIndex, slot, allowEdit, shift, ctrl); return UpdateStateOnMouseLeftButtonDown(pointerPressedEventArgs, columnIndex, slot, allowEdit, shift, ctrl);
} }
@ -4223,6 +4228,7 @@ namespace Avalonia.Controls
private void HorizontalScrollBar_Scroll(object sender, ScrollEventArgs e) private void HorizontalScrollBar_Scroll(object sender, ScrollEventArgs e)
{ {
ProcessHorizontalScroll(e.ScrollEventType); ProcessHorizontalScroll(e.ScrollEventType);
HorizontalScroll?.Invoke(sender, e);
} }
private bool IsColumnOutOfBounds(int columnIndex) private bool IsColumnOutOfBounds(int columnIndex)
@ -4376,7 +4382,7 @@ namespace Avalonia.Controls
private bool ProcessAKey(KeyEventArgs e) private bool ProcessAKey(KeyEventArgs e)
{ {
KeyboardHelper.GetMetaKeyState(e.Modifiers, out bool ctrl, out bool shift, out bool alt); KeyboardHelper.GetMetaKeyState(e.KeyModifiers, out bool ctrl, out bool shift, out bool alt);
if (ctrl && !shift && !alt && SelectionMode == DataGridSelectionMode.Extended) if (ctrl && !shift && !alt && SelectionMode == DataGridSelectionMode.Extended)
{ {
@ -4442,10 +4448,10 @@ namespace Avalonia.Controls
return ProcessAKey(e); return ProcessAKey(e);
case Key.C: case Key.C:
return ProcessCopyKey(e.Modifiers); return ProcessCopyKey(e.KeyModifiers);
case Key.Insert: case Key.Insert:
return ProcessCopyKey(e.Modifiers); return ProcessCopyKey(e.KeyModifiers);
} }
if (focusDataGrid) if (focusDataGrid)
{ {
@ -4644,7 +4650,7 @@ namespace Avalonia.Controls
private bool ProcessF2Key(KeyEventArgs e) private bool ProcessF2Key(KeyEventArgs e)
{ {
KeyboardHelper.GetMetaKeyState(e.Modifiers, out bool ctrl, out bool shift); KeyboardHelper.GetMetaKeyState(e.KeyModifiers, out bool ctrl, out bool shift);
if (!shift && !ctrl && if (!shift && !ctrl &&
_editingColumnIndex == -1 && CurrentColumnIndex != -1 && GetRowSelection(CurrentSlot) && _editingColumnIndex == -1 && CurrentColumnIndex != -1 && GetRowSelection(CurrentSlot) &&
@ -5001,7 +5007,7 @@ namespace Avalonia.Controls
private bool ProcessTabKey(KeyEventArgs e) private bool ProcessTabKey(KeyEventArgs e)
{ {
KeyboardHelper.GetMetaKeyState(e.Modifiers, out bool ctrl, out bool shift); KeyboardHelper.GetMetaKeyState(e.KeyModifiers, out bool ctrl, out bool shift);
return ProcessTabKey(e, shift, ctrl); return ProcessTabKey(e, shift, ctrl);
} }
@ -5555,6 +5561,7 @@ namespace Avalonia.Controls
private void VerticalScrollBar_Scroll(object sender, ScrollEventArgs e) private void VerticalScrollBar_Scroll(object sender, ScrollEventArgs e)
{ {
ProcessVerticalScroll(e.ScrollEventType); ProcessVerticalScroll(e.ScrollEventType);
VerticalScroll?.Invoke(sender, e);
} }
//TODO: Ensure left button is checked for //TODO: Ensure left button is checked for
@ -5787,7 +5794,7 @@ namespace Avalonia.Controls
/// to the Clipboard as text. /// to the Clipboard as text.
/// </summary> /// </summary>
/// <returns>Whether or not the DataGrid handled the key press.</returns> /// <returns>Whether or not the DataGrid handled the key press.</returns>
private bool ProcessCopyKey(InputModifiers modifiers) private bool ProcessCopyKey(KeyModifiers modifiers)
{ {
KeyboardHelper.GetMetaKeyState(modifiers, out bool ctrl, out bool shift, out bool alt); KeyboardHelper.GetMetaKeyState(modifiers, out bool ctrl, out bool shift, out bool alt);

20
src/Avalonia.Controls.DataGrid/DataGridColumnHeader.cs

@ -190,28 +190,28 @@ namespace Avalonia.Controls
} }
} }
internal void OnMouseLeftButtonUp_Click(InputModifiers inputModifiers, ref bool handled) internal void OnMouseLeftButtonUp_Click(KeyModifiers keyModifiers, ref bool handled)
{ {
// completed a click without dragging, so we're sorting // completed a click without dragging, so we're sorting
InvokeProcessSort(inputModifiers); InvokeProcessSort(keyModifiers);
handled = true; handled = true;
} }
internal void InvokeProcessSort(InputModifiers inputModifiers) internal void InvokeProcessSort(KeyModifiers keyModifiers)
{ {
Debug.Assert(OwningGrid != null); Debug.Assert(OwningGrid != null);
if (OwningGrid.WaitForLostFocus(() => InvokeProcessSort(inputModifiers))) if (OwningGrid.WaitForLostFocus(() => InvokeProcessSort(keyModifiers)))
{ {
return; return;
} }
if (OwningGrid.CommitEdit(DataGridEditingUnit.Row, exitEditingMode: true)) if (OwningGrid.CommitEdit(DataGridEditingUnit.Row, exitEditingMode: true))
{ {
Avalonia.Threading.Dispatcher.UIThread.Post(() => ProcessSort(inputModifiers)); Avalonia.Threading.Dispatcher.UIThread.Post(() => ProcessSort(keyModifiers));
} }
} }
//TODO GroupSorting //TODO GroupSorting
internal void ProcessSort(InputModifiers inputModifiers) internal void ProcessSort(KeyModifiers keyModifiers)
{ {
// if we can sort: // if we can sort:
// - DataConnection.AllowSort is true, and // - DataConnection.AllowSort is true, and
@ -233,7 +233,7 @@ namespace Avalonia.Controls
DataGridSortDescription newSort; DataGridSortDescription newSort;
KeyboardHelper.GetMetaKeyState(inputModifiers, out bool ctrl, out bool shift); KeyboardHelper.GetMetaKeyState(keyModifiers, out bool ctrl, out bool shift);
DataGridSortDescription sort = OwningColumn.GetSortDescription(); DataGridSortDescription sort = OwningColumn.GetSortDescription();
IDataGridCollectionView collectionView = owningGrid.DataConnection.CollectionView; IDataGridCollectionView collectionView = owningGrid.DataConnection.CollectionView;
@ -326,7 +326,7 @@ namespace Avalonia.Controls
if (OwningGrid != null && OwningGrid.ColumnHeaders != null) if (OwningGrid != null && OwningGrid.ColumnHeaders != null)
{ {
args.Device.Capture(this); args.Pointer.Capture(this);
_dragMode = DragMode.MouseDown; _dragMode = DragMode.MouseDown;
_frozenColumnsWidth = OwningGrid.ColumnsInternal.GetVisibleFrozenEdgedColumnsWidth(); _frozenColumnsWidth = OwningGrid.ColumnsInternal.GetVisibleFrozenEdgedColumnsWidth();
@ -371,7 +371,7 @@ namespace Avalonia.Controls
{ {
if (_dragMode == DragMode.MouseDown) if (_dragMode == DragMode.MouseDown)
{ {
OnMouseLeftButtonUp_Click(args.InputModifiers, ref handled); OnMouseLeftButtonUp_Click(args.KeyModifiers, ref handled);
} }
else if (_dragMode == DragMode.Reorder) else if (_dragMode == DragMode.Reorder)
{ {
@ -391,7 +391,7 @@ namespace Avalonia.Controls
SetDragCursor(mousePosition); SetDragCursor(mousePosition);
// Variables that track drag mode states get reset in DataGridColumnHeader_LostMouseCapture // Variables that track drag mode states get reset in DataGridColumnHeader_LostMouseCapture
args.Device.Capture(null); args.Pointer.Capture(null);
OnLostMouseCapture(); OnLostMouseCapture();
_dragMode = DragMode.None; _dragMode = DragMode.None;
handled = true; handled = true;

2
src/Avalonia.Controls.DataGrid/Properties/AssemblyInfo.cs

@ -5,7 +5,7 @@ using System.Reflection;
using System.Runtime.CompilerServices; using System.Runtime.CompilerServices;
using Avalonia.Metadata; using Avalonia.Metadata;
[assembly: InternalsVisibleTo("Avalonia.Controls.UnitTests")] [assembly: InternalsVisibleTo("Avalonia.Controls.DataGrid.UnitTests")]
[assembly: InternalsVisibleTo("Avalonia.DesignerSupport")] [assembly: InternalsVisibleTo("Avalonia.DesignerSupport")]
[assembly: XmlnsDefinition("https://github.com/avaloniaui", "Avalonia.Controls")] [assembly: XmlnsDefinition("https://github.com/avaloniaui", "Avalonia.Controls")]

25
src/Avalonia.Controls.DataGrid/Themes/Default.xaml

@ -45,7 +45,7 @@
<Path Name="SortIcon" <Path Name="SortIcon"
Grid.Column="1" Grid.Column="1"
Fill="#FF444444" Fill="{TemplateBinding Foreground}"
HorizontalAlignment="Left" HorizontalAlignment="Left"
VerticalAlignment="Center" VerticalAlignment="Center"
Stretch="Uniform" Stretch="Uniform"
@ -113,7 +113,7 @@
<Style Selector="DataGridRow /template/ Rectangle#BackgroundRectangle"> <Style Selector="DataGridRow /template/ Rectangle#BackgroundRectangle">
<Setter Property="IsVisible" Value="False"/> <Setter Property="IsVisible" Value="False"/>
<Setter Property="Fill" Value="#FFBADDE9" /> <Setter Property="Fill" Value="{DynamicResource HighlightBrush}" />
</Style> </Style>
<Style Selector="DataGridRow:pointerover /template/ Rectangle#BackgroundRectangle"> <Style Selector="DataGridRow:pointerover /template/ Rectangle#BackgroundRectangle">
@ -126,6 +126,10 @@
<Setter Property="Opacity" Value="1"/> <Setter Property="Opacity" Value="1"/>
</Style> </Style>
<Style Selector="DataGridRow:selected">
<Setter Property="Foreground" Value="{DynamicResource HighlightForegroundBrush}" />
</Style>
<Style Selector="DataGridRowHeader"> <Style Selector="DataGridRowHeader">
<Setter Property="Template"> <Setter Property="Template">
<ControlTemplate> <ControlTemplate>
@ -139,7 +143,7 @@
</Style> </Style>
<Style Selector="DataGridRowGroupHeader"> <Style Selector="DataGridRowGroupHeader">
<Setter Property="Background" Value="#FFE4E8EA" /> <Setter Property="Background" Value="{DynamicResource ThemeControlMidHighBrush}" />
<Setter Property="Height" Value="20"/> <Setter Property="Height" Value="20"/>
<Setter Property="Template"> <Setter Property="Template">
<ControlTemplate> <ControlTemplate>
@ -148,7 +152,6 @@
ColumnDefinitions="Auto,Auto,Auto,Auto" ColumnDefinitions="Auto,Auto,Auto,Auto"
RowDefinitions="Auto,*,Auto"> RowDefinitions="Auto,*,Auto">
<Rectangle Grid.Column="1" Grid.ColumnSpan="5" Fill="#FFFFFFFF" Height="1"/>
<Rectangle Grid.Column="1" Grid.Row="1" Name="IndentSpacer" /> <Rectangle Grid.Column="1" Grid.Row="1" Name="IndentSpacer" />
<ToggleButton Grid.Column="2" Grid.Row="1" Name="ExpanderButton" Margin="2,0,0,0"/> <ToggleButton Grid.Column="2" Grid.Row="1" Name="ExpanderButton" Margin="2,0,0,0"/>
@ -169,7 +172,7 @@
<Setter Property="Template"> <Setter Property="Template">
<ControlTemplate> <ControlTemplate>
<Border Grid.Column="0" Width="20" Height="20" Background="Transparent" HorizontalAlignment="Center" VerticalAlignment="Center"> <Border Grid.Column="0" Width="20" Height="20" Background="Transparent" HorizontalAlignment="Center" VerticalAlignment="Center">
<Path Fill="Black" <Path Fill="{TemplateBinding Foreground}"
HorizontalAlignment="Center" HorizontalAlignment="Center"
VerticalAlignment="Center" VerticalAlignment="Center"
Data="M 0 2 L 4 6 L 0 10 Z" /> Data="M 0 2 L 4 6 L 0 10 Z" />
@ -204,10 +207,10 @@
</Setter> </Setter>
<Setter Property="Template"> <Setter Property="Template">
<ControlTemplate> <ControlTemplate>
<Border BorderThickness="{TemplateBinding BorderThickness}" BorderBrush="{TemplateBinding BorderBrush}"> <Border Background="{TemplateBinding Background}"
<Grid BorderThickness="{TemplateBinding BorderThickness}"
RowDefinitions="Auto,*,Auto,Auto" BorderBrush="{TemplateBinding BorderBrush}">
ColumnDefinitions="Auto,*,Auto"> <Grid RowDefinitions="Auto,*,Auto,Auto" ColumnDefinitions="Auto,*,Auto">
<DataGridColumnHeader Name="PART_TopLeftCornerHeader" Width="22" /> <DataGridColumnHeader Name="PART_TopLeftCornerHeader" Width="22" />
<DataGridColumnHeadersPresenter Name="PART_ColumnHeadersPresenter" Grid.Column="1"/> <DataGridColumnHeadersPresenter Name="PART_ColumnHeadersPresenter" Grid.Column="1"/>
@ -217,12 +220,12 @@
<DataGridRowsPresenter Name="PART_RowsPresenter" Grid.ColumnSpan="2" Grid.Row="1" /> <DataGridRowsPresenter Name="PART_RowsPresenter" Grid.ColumnSpan="2" Grid.Row="1" />
<Rectangle Name="BottomRightCorner" Fill="#FFE9EEF4" Grid.Column="2" Grid.Row="2" /> <Rectangle Name="BottomRightCorner" Fill="#FFE9EEF4" Grid.Column="2" Grid.Row="2" />
<Rectangle Name="BottomLeftCorner" Fill="#FFE9EEF4" Grid.Row="2" Grid.ColumnSpan="2" /> <Rectangle Name="BottomLeftCorner" Fill="#FFE9EEF4" Grid.Row="2" Grid.ColumnSpan="2" />
<ScrollBar Name="PART_VerticalScrollbar" Orientation="Vertical" Grid.Column="2" Grid.Row="1" Width="{DynamicResource ScrollBarThickness}" Margin="0,-1,-1,-1"/> <ScrollBar Name="PART_VerticalScrollbar" Orientation="Vertical" Grid.Column="2" Grid.Row="1" Width="{DynamicResource ScrollBarThickness}"/>
<Grid Grid.Column="1" Grid.Row="2" <Grid Grid.Column="1" Grid.Row="2"
ColumnDefinitions="Auto,*"> ColumnDefinitions="Auto,*">
<Rectangle Name="PART_FrozenColumnScrollBarSpacer" /> <Rectangle Name="PART_FrozenColumnScrollBarSpacer" />
<ScrollBar Name="PART_HorizontalScrollbar" Grid.Column="1" Orientation="Horizontal" Height="{DynamicResource ScrollBarThickness}" Margin="-1,0,-1,-1"/> <ScrollBar Name="PART_HorizontalScrollbar" Grid.Column="1" Orientation="Horizontal" Height="{DynamicResource ScrollBarThickness}"/>
</Grid> </Grid>
</Grid> </Grid>
</Border> </Border>

15
src/Avalonia.Controls.DataGrid/Utils/KeyboardHelper.cs

@ -9,16 +9,17 @@ namespace Avalonia.Controls.Utils
{ {
internal static class KeyboardHelper internal static class KeyboardHelper
{ {
public static void GetMetaKeyState(InputModifiers modifiers, out bool ctrl, out bool shift) public static void GetMetaKeyState(KeyModifiers modifiers, out bool ctrl, out bool shift)
{ {
ctrl = (modifiers & InputModifiers.Control) == InputModifiers.Control; ctrl = (modifiers & KeyModifiers.Control) == KeyModifiers.Control;
shift = (modifiers & InputModifiers.Shift) == InputModifiers.Shift; shift = (modifiers & KeyModifiers.Shift) == KeyModifiers.Shift;
} }
public static void GetMetaKeyState(InputModifiers modifiers, out bool ctrl, out bool shift, out bool alt)
public static void GetMetaKeyState(KeyModifiers modifiers, out bool ctrl, out bool shift, out bool alt)
{ {
ctrl = (modifiers & InputModifiers.Control) == InputModifiers.Control; ctrl = (modifiers & KeyModifiers.Control) == KeyModifiers.Control;
shift = (modifiers & InputModifiers.Shift) == InputModifiers.Shift; shift = (modifiers & KeyModifiers.Shift) == KeyModifiers.Shift;
alt = (modifiers & InputModifiers.Alt) == InputModifiers.Alt; alt = (modifiers & KeyModifiers.Alt) == KeyModifiers.Alt;
} }
} }
} }

2
src/Avalonia.Controls/Application.cs

@ -32,7 +32,7 @@ namespace Avalonia
/// method. /// method.
/// - Tracks the lifetime of the application. /// - Tracks the lifetime of the application.
/// </remarks> /// </remarks>
public class Application : AvaloniaObject, IDataContextProvider, IGlobalDataTemplates, IGlobalStyles, IStyleRoot, IResourceNode public class Application : AvaloniaObject, IDataContextProvider, IGlobalDataTemplates, IGlobalStyles, IResourceNode
{ {
/// <summary> /// <summary>
/// The application-global data templates. /// The application-global data templates.

25
src/Avalonia.Controls/Button.cs

@ -91,7 +91,11 @@ namespace Avalonia.Controls
CommandProperty.Changed.Subscribe(CommandChanged); CommandProperty.Changed.Subscribe(CommandChanged);
IsDefaultProperty.Changed.Subscribe(IsDefaultChanged); IsDefaultProperty.Changed.Subscribe(IsDefaultChanged);
IsCancelProperty.Changed.Subscribe(IsCancelChanged); IsCancelProperty.Changed.Subscribe(IsCancelChanged);
PseudoClass<Button>(IsPressedProperty, ":pressed"); }
public Button()
{
UpdatePseudoClasses(IsPressed);
} }
/// <summary> /// <summary>
@ -312,6 +316,20 @@ namespace Avalonia.Controls
IsPressed = false; IsPressed = false;
} }
protected override void OnPropertyChanged<T>(
AvaloniaProperty<T> property,
Optional<T> oldValue,
BindingValue<T> newValue,
BindingPriority priority)
{
base.OnPropertyChanged(property, oldValue, newValue, priority);
if (property == IsPressedProperty)
{
UpdatePseudoClasses(newValue.GetValueOrDefault<bool>());
}
}
protected override void UpdateDataValidation<T>(AvaloniaProperty<T> property, BindingValue<T> value) protected override void UpdateDataValidation<T>(AvaloniaProperty<T> property, BindingValue<T> value)
{ {
base.UpdateDataValidation(property, value); base.UpdateDataValidation(property, value);
@ -474,5 +492,10 @@ namespace Avalonia.Controls
OnClick(); OnClick();
} }
} }
private void UpdatePseudoClasses(bool isPressed)
{
PseudoClasses.Set(":pressed", isPressed);
}
} }
} }

28
src/Avalonia.Controls/ButtonSpinner.cs

@ -1,5 +1,6 @@
using System; using System;
using Avalonia.Controls.Primitives; using Avalonia.Controls.Primitives;
using Avalonia.Data;
using Avalonia.Input; using Avalonia.Input;
using Avalonia.Interactivity; using Avalonia.Interactivity;
@ -34,6 +35,11 @@ namespace Avalonia.Controls
public static readonly StyledProperty<Location> ButtonSpinnerLocationProperty = public static readonly StyledProperty<Location> ButtonSpinnerLocationProperty =
AvaloniaProperty.Register<ButtonSpinner, Location>(nameof(ButtonSpinnerLocation), Location.Right); AvaloniaProperty.Register<ButtonSpinner, Location>(nameof(ButtonSpinnerLocation), Location.Right);
public ButtonSpinner()
{
UpdatePseudoClasses(ButtonSpinnerLocation);
}
private Button _decreaseButton; private Button _decreaseButton;
/// <summary> /// <summary>
/// Gets or sets the DecreaseButton template part. /// Gets or sets the DecreaseButton template part.
@ -85,8 +91,6 @@ namespace Avalonia.Controls
static ButtonSpinner() static ButtonSpinner()
{ {
AllowSpinProperty.Changed.Subscribe(AllowSpinChanged); AllowSpinProperty.Changed.Subscribe(AllowSpinChanged);
PseudoClass<ButtonSpinner, Location>(ButtonSpinnerLocationProperty, location => location == Location.Left, ":left");
PseudoClass<ButtonSpinner, Location>(ButtonSpinnerLocationProperty, location => location == Location.Right, ":right");
} }
/// <summary> /// <summary>
@ -201,6 +205,20 @@ namespace Avalonia.Controls
} }
} }
protected override void OnPropertyChanged<T>(
AvaloniaProperty<T> property,
Optional<T> oldValue,
BindingValue<T> newValue,
BindingPriority priority)
{
base.OnPropertyChanged(property, oldValue, newValue, priority);
if (property == ButtonSpinnerLocationProperty)
{
UpdatePseudoClasses(newValue.GetValueOrDefault<Location>());
}
}
protected override void OnValidSpinDirectionChanged(ValidSpinDirections oldValue, ValidSpinDirections newValue) protected override void OnValidSpinDirectionChanged(ValidSpinDirections oldValue, ValidSpinDirections newValue)
{ {
SetButtonUsage(); SetButtonUsage();
@ -259,5 +277,11 @@ namespace Avalonia.Controls
OnSpin(new SpinEventArgs(SpinEvent, direction)); OnSpin(new SpinEventArgs(SpinEvent, direction));
} }
} }
private void UpdatePseudoClasses(Location location)
{
PseudoClasses.Set(":left", location == Location.Left);
PseudoClasses.Set(":right", location == Location.Right);
}
} }
} }

4
src/Avalonia.Controls/Calendar/Calendar.cs

@ -1575,7 +1575,7 @@ namespace Avalonia.Controls
base.OnPointerWheelChanged(e); base.OnPointerWheelChanged(e);
if (!e.Handled) if (!e.Handled)
{ {
CalendarExtensions.GetMetaKeyState(e.InputModifiers, out bool ctrl, out bool shift); CalendarExtensions.GetMetaKeyState(e.KeyModifiers, out bool ctrl, out bool shift);
if (!ctrl) if (!ctrl)
{ {
@ -1631,7 +1631,7 @@ namespace Avalonia.Controls
// Some keys (e.g. Left/Right) need to be translated in RightToLeft mode // Some keys (e.g. Left/Right) need to be translated in RightToLeft mode
Key invariantKey = e.Key; //InteractionHelper.GetLogicalKey(FlowDirection, e.Key); Key invariantKey = e.Key; //InteractionHelper.GetLogicalKey(FlowDirection, e.Key);
CalendarExtensions.GetMetaKeyState(e.Modifiers, out bool ctrl, out bool shift); CalendarExtensions.GetMetaKeyState(e.KeyModifiers, out bool ctrl, out bool shift);
switch (invariantKey) switch (invariantKey)
{ {

6
src/Avalonia.Controls/Calendar/CalendarExtensions.cs

@ -9,10 +9,10 @@ namespace Avalonia.Controls.Primitives
{ {
internal static class CalendarExtensions internal static class CalendarExtensions
{ {
public static void GetMetaKeyState(InputModifiers modifiers, out bool ctrl, out bool shift) public static void GetMetaKeyState(KeyModifiers modifiers, out bool ctrl, out bool shift)
{ {
ctrl = (modifiers & InputModifiers.Control) == InputModifiers.Control; ctrl = (modifiers & KeyModifiers.Control) == KeyModifiers.Control;
shift = (modifiers & InputModifiers.Shift) == InputModifiers.Shift; shift = (modifiers & KeyModifiers.Shift) == KeyModifiers.Shift;
} }
} }
} }

14
src/Avalonia.Controls/Calendar/CalendarItem.cs

@ -943,8 +943,8 @@ namespace Avalonia.Controls.Primitives
{ {
CalendarDayButton b = (CalendarDayButton)sender; CalendarDayButton b = (CalendarDayButton)sender;
// The button is in Pressed state. Change the state to normal. // The button is in Pressed state. Change the state to normal.
if (e.Device.Captured == b) if (e.Pointer.Captured == b)
e.Device.Capture(null); e.Pointer.Capture(null);
_lastCalendarDayButton = b; _lastCalendarDayButton = b;
} }
} }
@ -958,7 +958,7 @@ namespace Avalonia.Controls.Primitives
} }
bool ctrl, shift; bool ctrl, shift;
CalendarExtensions.GetMetaKeyState(e.InputModifiers, out ctrl, out shift); CalendarExtensions.GetMetaKeyState(e.KeyModifiers, out ctrl, out shift);
CalendarDayButton b = sender as CalendarDayButton; CalendarDayButton b = sender as CalendarDayButton;
if (b != null) if (b != null)
@ -1213,8 +1213,8 @@ namespace Avalonia.Controls.Primitives
{ {
CalendarButton b = (CalendarButton)sender; CalendarButton b = (CalendarButton)sender;
// The button is in Pressed state. Change the state to normal. // The button is in Pressed state. Change the state to normal.
if (e.Device.Captured == b) if (e.Pointer.Captured == b)
e.Device.Capture(null); e.Pointer.Capture(null);
//b.ReleaseMouseCapture(); //b.ReleaseMouseCapture();
_lastCalendarButton = b; _lastCalendarButton = b;
@ -1224,7 +1224,7 @@ namespace Avalonia.Controls.Primitives
{ {
if (_lastCalendarDayButton != null) if (_lastCalendarDayButton != null)
{ {
e.Device.Capture(_lastCalendarDayButton); e.Pointer.Capture(_lastCalendarDayButton);
} }
} }
@ -1232,7 +1232,7 @@ namespace Avalonia.Controls.Primitives
{ {
if (_lastCalendarButton != null) if (_lastCalendarButton != null)
{ {
e.Device.Capture(_lastCalendarButton); e.Pointer.Capture(_lastCalendarButton);
} }
} }

2
src/Avalonia.Controls/Calendar/DatePicker.cs

@ -1008,7 +1008,7 @@ namespace Avalonia.Controls
} }
case Key.Down: case Key.Down:
{ {
if ((e.Modifiers & InputModifiers.Control) == InputModifiers.Control) if ((e.KeyModifiers & KeyModifiers.Control) == KeyModifiers.Control)
{ {
HandlePopUp(); HandlePopUp();
return true; return true;

2
src/Avalonia.Controls/ComboBox.cs

@ -130,7 +130,7 @@ namespace Avalonia.Controls
return; return;
if (e.Key == Key.F4 || if (e.Key == Key.F4 ||
((e.Key == Key.Down || e.Key == Key.Up) && ((e.Modifiers & InputModifiers.Alt) != 0))) ((e.Key == Key.Down || e.Key == Key.Up) && ((e.KeyModifiers & KeyModifiers.Alt) != 0)))
{ {
IsDropDownOpen = !IsDropDownOpen; IsDropDownOpen = !IsDropDownOpen;
e.Handled = true; e.Handled = true;

20
src/Avalonia.Controls/ControlExtensions.cs

@ -69,26 +69,6 @@ namespace Avalonia.Controls
return nameScope.Find<T>(name); return nameScope.Find<T>(name);
} }
/// <summary>
/// Adds or removes a pseudoclass depending on a boolean value.
/// </summary>
/// <param name="classes">The pseudoclasses collection.</param>
/// <param name="name">The name of the pseudoclass to set.</param>
/// <param name="value">True to add the pseudoclass or false to remove.</param>
public static void Set(this IPseudoClasses classes, string name, bool value)
{
Contract.Requires<ArgumentNullException>(classes != null);
if (value)
{
classes.Add(name);
}
else
{
classes.Remove(name);
}
}
/// <summary> /// <summary>
/// Sets a pseudoclass depending on an observable trigger. /// Sets a pseudoclass depending on an observable trigger.
/// </summary> /// </summary>

41
src/Avalonia.Controls/Expander.cs

@ -1,5 +1,6 @@
using Avalonia.Animation; using Avalonia.Animation;
using Avalonia.Controls.Primitives; using Avalonia.Controls.Primitives;
using Avalonia.Data;
namespace Avalonia.Controls namespace Avalonia.Controls
{ {
@ -30,16 +31,14 @@ namespace Avalonia.Controls
static Expander() static Expander()
{ {
PseudoClass<Expander, ExpandDirection>(ExpandDirectionProperty, d => d == ExpandDirection.Down, ":down");
PseudoClass<Expander, ExpandDirection>(ExpandDirectionProperty, d => d == ExpandDirection.Up, ":up");
PseudoClass<Expander, ExpandDirection>(ExpandDirectionProperty, d => d == ExpandDirection.Left, ":left");
PseudoClass<Expander, ExpandDirection>(ExpandDirectionProperty, d => d == ExpandDirection.Right, ":right");
PseudoClass<Expander>(IsExpandedProperty, ":expanded");
IsExpandedProperty.Changed.AddClassHandler<Expander>((x, e) => x.OnIsExpandedChanged(e)); IsExpandedProperty.Changed.AddClassHandler<Expander>((x, e) => x.OnIsExpandedChanged(e));
} }
public Expander()
{
UpdatePseudoClasses(ExpandDirection);
}
public IPageTransition ContentTransition public IPageTransition ContentTransition
{ {
get => GetValue(ContentTransitionProperty); get => GetValue(ContentTransitionProperty);
@ -55,7 +54,11 @@ namespace Avalonia.Controls
public bool IsExpanded public bool IsExpanded
{ {
get { return _isExpanded; } get { return _isExpanded; }
set { SetAndRaise(IsExpandedProperty, ref _isExpanded, value); } set
{
SetAndRaise(IsExpandedProperty, ref _isExpanded, value);
PseudoClasses.Set(":expanded", value);
}
} }
protected virtual void OnIsExpandedChanged(AvaloniaPropertyChangedEventArgs e) protected virtual void OnIsExpandedChanged(AvaloniaPropertyChangedEventArgs e)
@ -74,5 +77,27 @@ namespace Avalonia.Controls
} }
} }
} }
protected override void OnPropertyChanged<T>(
AvaloniaProperty<T> property,
Optional<T> oldValue,
BindingValue<T> newValue,
BindingPriority priority)
{
base.OnPropertyChanged(property, oldValue, newValue, priority);
if (property == ExpandDirectionProperty)
{
UpdatePseudoClasses(newValue.GetValueOrDefault<ExpandDirection>());
}
}
private void UpdatePseudoClasses(ExpandDirection d)
{
PseudoClasses.Set(":up", d == ExpandDirection.Up);
PseudoClasses.Set(":down", d == ExpandDirection.Down);
PseudoClasses.Set(":left", d == ExpandDirection.Left);
PseudoClasses.Set(":right", d == ExpandDirection.Right);
}
} }
} }

44
src/Avalonia.Controls/ItemsControl.cs

@ -236,25 +236,7 @@ namespace Avalonia.Controls
// it was added to the Items collection. // it was added to the Items collection.
if (container.ContainerControl != null && container.ContainerControl != container.Item) if (container.ContainerControl != null && container.ContainerControl != container.Item)
{ {
if (ItemContainerGenerator.ContainerType == null) LogicalChildren.Add(container.ContainerControl);
{
var containerControl = container.ContainerControl as ContentPresenter;
if (containerControl != null)
{
((ISetLogicalParent)containerControl).SetParent(this);
containerControl.UpdateChild();
if (containerControl.Child != null)
{
LogicalChildren.Add(containerControl.Child);
}
}
}
else
{
LogicalChildren.Add(container.ContainerControl);
}
} }
} }
} }
@ -272,24 +254,7 @@ namespace Avalonia.Controls
// when it is removed from the Items collection. // when it is removed from the Items collection.
if (container?.ContainerControl != container?.Item) if (container?.ContainerControl != container?.Item)
{ {
if (ItemContainerGenerator.ContainerType == null) LogicalChildren.Remove(container.ContainerControl);
{
var containerControl = container.ContainerControl as ContentPresenter;
if (containerControl != null)
{
((ISetLogicalParent)containerControl).SetParent(null);
if (containerControl.Child != null)
{
LogicalChildren.Remove(containerControl.Child);
}
}
}
else
{
LogicalChildren.Remove(container.ContainerControl);
}
} }
} }
} }
@ -507,7 +472,10 @@ namespace Avalonia.Controls
result = container.GetControl(direction, c, wrap); result = container.GetControl(direction, c, wrap);
from = from ?? result; from = from ?? result;
if (result?.Focusable == true) if (result != null &&
result.Focusable &&
result.IsEffectivelyEnabled &&
result.IsEffectivelyVisible)
{ {
return result; return result;
} }

2
src/Avalonia.Controls/ListBox.cs

@ -139,7 +139,7 @@ namespace Avalonia.Controls
e.Handled = UpdateSelectionFromEventSource( e.Handled = UpdateSelectionFromEventSource(
e.Source, e.Source,
true, true,
(e.InputModifiers & InputModifiers.Shift) != 0); (e.KeyModifiers & KeyModifiers.Shift) != 0);
} }
} }

30
src/Avalonia.Controls/Notifications/WindowNotificationManager.cs

@ -8,6 +8,7 @@ using System.Reactive.Linq;
using System.Threading.Tasks; using System.Threading.Tasks;
using Avalonia.Controls.Primitives; using Avalonia.Controls.Primitives;
using Avalonia.Rendering; using Avalonia.Rendering;
using Avalonia.Data;
using Avalonia.VisualTree; using Avalonia.VisualTree;
namespace Avalonia.Controls.Notifications namespace Avalonia.Controls.Notifications
@ -68,15 +69,12 @@ namespace Avalonia.Controls.Notifications
Install(host); Install(host);
}); });
} }
UpdatePseudoClasses(Position);
} }
static WindowNotificationManager() static WindowNotificationManager()
{ {
PseudoClass<WindowNotificationManager, NotificationPosition>(PositionProperty, x => x == NotificationPosition.TopLeft, ":topleft");
PseudoClass<WindowNotificationManager, NotificationPosition>(PositionProperty, x => x == NotificationPosition.TopRight, ":topright");
PseudoClass<WindowNotificationManager, NotificationPosition>(PositionProperty, x => x == NotificationPosition.BottomLeft, ":bottomleft");
PseudoClass<WindowNotificationManager, NotificationPosition>(PositionProperty, x => x == NotificationPosition.BottomRight, ":bottomright");
HorizontalAlignmentProperty.OverrideDefaultValue<WindowNotificationManager>(Layout.HorizontalAlignment.Stretch); HorizontalAlignmentProperty.OverrideDefaultValue<WindowNotificationManager>(Layout.HorizontalAlignment.Stretch);
VerticalAlignmentProperty.OverrideDefaultValue<WindowNotificationManager>(Layout.VerticalAlignment.Stretch); VerticalAlignmentProperty.OverrideDefaultValue<WindowNotificationManager>(Layout.VerticalAlignment.Stretch);
} }
@ -143,6 +141,20 @@ namespace Avalonia.Controls.Notifications
notificationControl.Close(); notificationControl.Close();
} }
protected override void OnPropertyChanged<T>(
AvaloniaProperty<T> property,
Optional<T> oldValue,
BindingValue<T> newValue,
BindingPriority priority)
{
base.OnPropertyChanged(property, oldValue, newValue, priority);
if (property == PositionProperty)
{
UpdatePseudoClasses(newValue.GetValueOrDefault<NotificationPosition>());
}
}
/// <summary> /// <summary>
/// Installs the <see cref="WindowNotificationManager"/> within the <see cref="AdornerLayer"/> /// Installs the <see cref="WindowNotificationManager"/> within the <see cref="AdornerLayer"/>
/// of the host <see cref="Window"/>. /// of the host <see cref="Window"/>.
@ -155,6 +167,14 @@ namespace Avalonia.Controls.Notifications
adornerLayer?.Children.Add(this); adornerLayer?.Children.Add(this);
} }
private void UpdatePseudoClasses(NotificationPosition position)
{
PseudoClasses.Set(":topleft", position == NotificationPosition.TopLeft);
PseudoClasses.Set(":topright", position == NotificationPosition.TopRight);
PseudoClasses.Set(":bottomleft", position == NotificationPosition.BottomLeft);
PseudoClasses.Set(":bottomright", position == NotificationPosition.BottomRight);
}
public bool HitTest(Point point) => VisualChildren.HitTestCustom(point); public bool HitTest(Point point) => VisualChildren.HitTestCustom(point);
} }
} }

30
src/Avalonia.Controls/Platform/DefaultMenuInteractionHandler.cs

@ -6,6 +6,8 @@ using Avalonia.LogicalTree;
using Avalonia.Rendering; using Avalonia.Rendering;
using Avalonia.Threading; using Avalonia.Threading;
#nullable enable
namespace Avalonia.Controls.Platform namespace Avalonia.Controls.Platform
{ {
/// <summary> /// <summary>
@ -14,8 +16,8 @@ namespace Avalonia.Controls.Platform
public class DefaultMenuInteractionHandler : IMenuInteractionHandler public class DefaultMenuInteractionHandler : IMenuInteractionHandler
{ {
private readonly bool _isContextMenu; private readonly bool _isContextMenu;
private IDisposable _inputManagerSubscription; private IDisposable? _inputManagerSubscription;
private IRenderRoot _root; private IRenderRoot? _root;
public DefaultMenuInteractionHandler(bool isContextMenu) public DefaultMenuInteractionHandler(bool isContextMenu)
: this(isContextMenu, Input.InputManager.Instance, DefaultDelayRun) : this(isContextMenu, Input.InputManager.Instance, DefaultDelayRun)
@ -24,9 +26,11 @@ namespace Avalonia.Controls.Platform
public DefaultMenuInteractionHandler( public DefaultMenuInteractionHandler(
bool isContextMenu, bool isContextMenu,
IInputManager inputManager, IInputManager? inputManager,
Action<Action, TimeSpan> delayRun) Action<Action, TimeSpan> delayRun)
{ {
delayRun = delayRun ?? throw new ArgumentNullException(nameof(delayRun));
_isContextMenu = isContextMenu; _isContextMenu = isContextMenu;
InputManager = inputManager; InputManager = inputManager;
DelayRun = delayRun; DelayRun = delayRun;
@ -92,7 +96,7 @@ namespace Avalonia.Controls.Platform
root.Deactivated -= WindowDeactivated; root.Deactivated -= WindowDeactivated;
} }
_inputManagerSubscription.Dispose(); _inputManagerSubscription!.Dispose();
Menu = null; Menu = null;
_root = null; _root = null;
@ -100,9 +104,9 @@ namespace Avalonia.Controls.Platform
protected Action<Action, TimeSpan> DelayRun { get; } protected Action<Action, TimeSpan> DelayRun { get; }
protected IInputManager InputManager { get; } protected IInputManager? InputManager { get; }
protected IMenu Menu { get; private set; } protected IMenu? Menu { get; private set; }
protected static TimeSpan MenuShowDelay { get; } = TimeSpan.FromMilliseconds(400); protected static TimeSpan MenuShowDelay { get; } = TimeSpan.FromMilliseconds(400);
@ -131,7 +135,7 @@ namespace Avalonia.Controls.Platform
KeyDown(GetMenuItem(e.Source as IControl), e); KeyDown(GetMenuItem(e.Source as IControl), e);
} }
protected internal virtual void KeyDown(IMenuItem item, KeyEventArgs e) protected internal virtual void KeyDown(IMenuItem? item, KeyEventArgs e)
{ {
switch (e.Key) switch (e.Key)
{ {
@ -200,7 +204,7 @@ namespace Avalonia.Controls.Platform
} }
else else
{ {
Menu.Close(); Menu!.Close();
} }
e.Handled = true; e.Handled = true;
@ -213,12 +217,12 @@ namespace Avalonia.Controls.Platform
{ {
if (item == null && _isContextMenu) if (item == null && _isContextMenu)
{ {
if (Menu.MoveSelection(direction.Value, true) == true) if (Menu!.MoveSelection(direction.Value, true) == true)
{ {
e.Handled = true; e.Handled = true;
} }
} }
else if (item.Parent?.MoveSelection(direction.Value, true) == true) else if (item?.Parent?.MoveSelection(direction.Value, true) == true)
{ {
// If the the parent is an IMenu which successfully moved its selection, // If the the parent is an IMenu which successfully moved its selection,
// and the current menu is open then close the current menu and open the // and the current menu is open then close the current menu and open the
@ -408,7 +412,7 @@ namespace Avalonia.Controls.Platform
protected void CloseMenu(IMenuItem item) protected void CloseMenu(IMenuItem item)
{ {
var current = (IMenuElement)item; var current = (IMenuElement?)item;
while (current != null && !(current is IMenu)) while (current != null && !(current is IMenu))
{ {
@ -456,7 +460,7 @@ namespace Avalonia.Controls.Platform
protected void SelectItemAndAncestors(IMenuItem item) protected void SelectItemAndAncestors(IMenuItem item)
{ {
var current = item; var current = (IMenuItem?)item;
while (current?.Parent != null) while (current?.Parent != null)
{ {
@ -465,7 +469,7 @@ namespace Avalonia.Controls.Platform
} }
} }
protected static IMenuItem GetMenuItem(IControl item) protected static IMenuItem? GetMenuItem(IControl? item)
{ {
while (true) while (true)
{ {

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

@ -2,12 +2,10 @@
// Licensed under the MIT license. See licence.md file in the project root for full license information. // Licensed under the MIT license. See licence.md file in the project root for full license information.
using System; using System;
using System.Collections.Generic;
using System.Diagnostics; using System.Diagnostics;
using System.Linq; using System.Linq;
using System.Reactive.Disposables; using System.Reactive.Disposables;
using Avalonia.Controls.Presenters; using Avalonia.Controls.Presenters;
using Avalonia.Data;
using Avalonia.Input; using Avalonia.Input;
using Avalonia.Input.Raw; using Avalonia.Input.Raw;
using Avalonia.Interactivity; using Avalonia.Interactivity;
@ -15,6 +13,8 @@ using Avalonia.LogicalTree;
using Avalonia.Metadata; using Avalonia.Metadata;
using Avalonia.VisualTree; using Avalonia.VisualTree;
#nullable enable
namespace Avalonia.Controls.Primitives namespace Avalonia.Controls.Primitives
{ {
/// <summary> /// <summary>
@ -25,8 +25,8 @@ namespace Avalonia.Controls.Primitives
/// <summary> /// <summary>
/// Defines the <see cref="Child"/> property. /// Defines the <see cref="Child"/> property.
/// </summary> /// </summary>
public static readonly StyledProperty<Control> ChildProperty = public static readonly StyledProperty<Control?> ChildProperty =
AvaloniaProperty.Register<Popup, Control>(nameof(Child)); AvaloniaProperty.Register<Popup, Control?>(nameof(Child));
/// <summary> /// <summary>
/// Defines the <see cref="IsOpen"/> property. /// Defines the <see cref="IsOpen"/> property.
@ -43,11 +43,13 @@ namespace Avalonia.Controls.Primitives
public static readonly StyledProperty<PlacementMode> PlacementModeProperty = public static readonly StyledProperty<PlacementMode> PlacementModeProperty =
AvaloniaProperty.Register<Popup, PlacementMode>(nameof(PlacementMode), defaultValue: PlacementMode.Bottom); AvaloniaProperty.Register<Popup, PlacementMode>(nameof(PlacementMode), defaultValue: PlacementMode.Bottom);
#pragma warning disable 618
/// <summary> /// <summary>
/// Defines the <see cref="ObeyScreenEdges"/> property. /// Defines the <see cref="ObeyScreenEdges"/> property.
/// </summary> /// </summary>
public static readonly StyledProperty<bool> ObeyScreenEdgesProperty = public static readonly StyledProperty<bool> ObeyScreenEdgesProperty =
AvaloniaProperty.Register<Popup, bool>(nameof(ObeyScreenEdges), true); AvaloniaProperty.Register<Popup, bool>(nameof(ObeyScreenEdges), true);
#pragma warning restore 618
/// <summary> /// <summary>
/// Defines the <see cref="HorizontalOffset"/> property. /// Defines the <see cref="HorizontalOffset"/> property.
@ -64,8 +66,8 @@ namespace Avalonia.Controls.Primitives
/// <summary> /// <summary>
/// Defines the <see cref="PlacementTarget"/> property. /// Defines the <see cref="PlacementTarget"/> property.
/// </summary> /// </summary>
public static readonly StyledProperty<Control> PlacementTargetProperty = public static readonly StyledProperty<Control?> PlacementTargetProperty =
AvaloniaProperty.Register<Popup, Control>(nameof(PlacementTarget)); AvaloniaProperty.Register<Popup, Control?>(nameof(PlacementTarget));
/// <summary> /// <summary>
/// Defines the <see cref="StaysOpen"/> property. /// Defines the <see cref="StaysOpen"/> property.
@ -80,12 +82,8 @@ namespace Avalonia.Controls.Primitives
AvaloniaProperty.Register<Popup, bool>(nameof(Topmost)); AvaloniaProperty.Register<Popup, bool>(nameof(Topmost));
private bool _isOpen; private bool _isOpen;
private IPopupHost _popupHost; private bool _ignoreIsOpenChanged;
private TopLevel _topLevel; private PopupOpenState? _openState;
private IDisposable _nonClientListener;
private IDisposable _presenterSubscription;
bool _ignoreIsOpenChanged = false;
private List<IDisposable> _bindings = new List<IDisposable>();
/// <summary> /// <summary>
/// Initializes static members of the <see cref="Popup"/> class. /// Initializes static members of the <see cref="Popup"/> class.
@ -94,31 +92,26 @@ namespace Avalonia.Controls.Primitives
{ {
IsHitTestVisibleProperty.OverrideDefaultValue<Popup>(false); IsHitTestVisibleProperty.OverrideDefaultValue<Popup>(false);
ChildProperty.Changed.AddClassHandler<Popup>((x, e) => x.ChildChanged(e)); ChildProperty.Changed.AddClassHandler<Popup>((x, e) => x.ChildChanged(e));
IsOpenProperty.Changed.AddClassHandler<Popup>((x, e) => x.IsOpenChanged(e)); IsOpenProperty.Changed.AddClassHandler<Popup>((x, e) => x.IsOpenChanged((AvaloniaPropertyChangedEventArgs<bool>)e));
}
public Popup()
{
} }
/// <summary> /// <summary>
/// Raised when the popup closes. /// Raised when the popup closes.
/// </summary> /// </summary>
public event EventHandler Closed; public event EventHandler? Closed;
/// <summary> /// <summary>
/// Raised when the popup opens. /// Raised when the popup opens.
/// </summary> /// </summary>
public event EventHandler Opened; public event EventHandler? Opened;
public IPopupHost Host => _popupHost; public IPopupHost? Host => _openState?.PopupHost;
/// <summary> /// <summary>
/// Gets or sets the control to display in the popup. /// Gets or sets the control to display in the popup.
/// </summary> /// </summary>
[Content] [Content]
public Control Child public Control? Child
{ {
get { return GetValue(ChildProperty); } get { return GetValue(ChildProperty); }
set { SetValue(ChildProperty, value); } set { SetValue(ChildProperty, value); }
@ -131,7 +124,7 @@ namespace Avalonia.Controls.Primitives
/// This property allows a client to customize the behaviour of the popup by injecting /// This property allows a client to customize the behaviour of the popup by injecting
/// a specialized dependency resolver into the <see cref="PopupRoot"/>'s constructor. /// a specialized dependency resolver into the <see cref="PopupRoot"/>'s constructor.
/// </remarks> /// </remarks>
public IAvaloniaDependencyResolver DependencyResolver public IAvaloniaDependencyResolver? DependencyResolver
{ {
get; get;
set; set;
@ -183,7 +176,7 @@ namespace Avalonia.Controls.Primitives
/// <summary> /// <summary>
/// Gets or sets the control that is used to determine the popup's position. /// Gets or sets the control that is used to determine the popup's position.
/// </summary> /// </summary>
public Control PlacementTarget public Control? PlacementTarget
{ {
get { return GetValue(PlacementTargetProperty); } get { return GetValue(PlacementTargetProperty); }
set { SetValue(PlacementTargetProperty, value); } set { SetValue(PlacementTargetProperty, value); }
@ -211,7 +204,7 @@ namespace Avalonia.Controls.Primitives
/// <summary> /// <summary>
/// Gets the root of the popup window. /// Gets the root of the popup window.
/// </summary> /// </summary>
IVisual IVisualTreeHost.Root => _popupHost?.HostedVisualTreeRoot; IVisual? IVisualTreeHost.Root => _openState?.PopupHost.HostedVisualTreeRoot;
/// <summary> /// <summary>
/// Opens the popup. /// Opens the popup.
@ -219,50 +212,91 @@ namespace Avalonia.Controls.Primitives
public void Open() public void Open()
{ {
// Popup is currently open // Popup is currently open
if (_topLevel != null) if (_openState != null)
{
return; return;
CloseCurrent(); }
var placementTarget = PlacementTarget ?? this.GetLogicalAncestors().OfType<IVisual>().FirstOrDefault(); var placementTarget = PlacementTarget ?? this.GetLogicalAncestors().OfType<IVisual>().FirstOrDefault();
if (placementTarget == null) if (placementTarget == null)
{
throw new InvalidOperationException("Popup has no logical parent and PlacementTarget is null"); throw new InvalidOperationException("Popup has no logical parent and PlacementTarget is null");
}
_topLevel = placementTarget.GetVisualRoot() as TopLevel; var topLevel = placementTarget.VisualRoot as TopLevel;
if (_topLevel == null) if (topLevel == null)
{ {
throw new InvalidOperationException( throw new InvalidOperationException(
"Attempted to open a popup not attached to a TopLevel"); "Attempted to open a popup not attached to a TopLevel");
} }
_popupHost = OverlayPopupHost.CreatePopupHost(placementTarget, DependencyResolver); var popupHost = OverlayPopupHost.CreatePopupHost(placementTarget, DependencyResolver);
var handlerCleanup = new CompositeDisposable(5);
_bindings.Add(_popupHost.BindConstraints(this, WidthProperty, MinWidthProperty, MaxWidthProperty, void DeferCleanup(IDisposable? disposable)
{
if (disposable is null)
{
return;
}
handlerCleanup.Add(disposable);
}
DeferCleanup(popupHost.BindConstraints(this, WidthProperty, MinWidthProperty, MaxWidthProperty,
HeightProperty, MinHeightProperty, MaxHeightProperty, TopmostProperty)); HeightProperty, MinHeightProperty, MaxHeightProperty, TopmostProperty));
_popupHost.SetChild(Child); popupHost.SetChild(Child);
((ISetLogicalParent)_popupHost).SetParent(this); ((ISetLogicalParent)popupHost).SetParent(this);
_popupHost.ConfigurePosition(placementTarget,
PlacementMode, new Point(HorizontalOffset, VerticalOffset)); popupHost.ConfigurePosition(
_popupHost.TemplateApplied += RootTemplateApplied; placementTarget,
PlacementMode,
var window = _topLevel as Window; new Point(HorizontalOffset, VerticalOffset));
if (window != null)
DeferCleanup(SubscribeToEventHandler<IPopupHost, EventHandler<TemplateAppliedEventArgs>>(popupHost, RootTemplateApplied,
(x, handler) => x.TemplateApplied += handler,
(x, handler) => x.TemplateApplied -= handler));
if (topLevel is Window window)
{ {
window.Deactivated += WindowDeactivated; DeferCleanup(SubscribeToEventHandler<Window, EventHandler>(window, WindowDeactivated,
(x, handler) => x.Deactivated += handler,
(x, handler) => x.Deactivated -= handler));
} }
else else
{ {
var parentPopuproot = _topLevel as PopupRoot; var parentPopupRoot = topLevel as PopupRoot;
if (parentPopuproot?.Parent is Popup popup)
if (parentPopupRoot?.Parent is Popup popup)
{ {
popup.Closed += ParentClosed; DeferCleanup(SubscribeToEventHandler<Popup, EventHandler>(popup, ParentClosed,
(x, handler) => x.Closed += handler,
(x, handler) => x.Closed -= handler));
} }
} }
_topLevel.AddHandler(PointerPressedEvent, PointerPressedOutside, RoutingStrategies.Tunnel);
_nonClientListener = InputManager.Instance?.Process.Subscribe(ListenForNonClientClick);
_popupHost.Show(); DeferCleanup(topLevel.AddHandler(PointerPressedEvent, PointerPressedOutside, RoutingStrategies.Tunnel));
DeferCleanup(InputManager.Instance?.Process.Subscribe(ListenForNonClientClick));
var cleanupPopup = Disposable.Create((popupHost, handlerCleanup), state =>
{
state.handlerCleanup.Dispose();
state.popupHost.SetChild(null);
state.popupHost.Hide();
((ISetLogicalParent)state.popupHost).SetParent(null);
state.popupHost.Dispose();
});
_openState = new PopupOpenState(topLevel, popupHost, cleanupPopup);
popupHost.Show();
using (BeginIgnoringIsOpen()) using (BeginIgnoringIsOpen())
{ {
@ -277,14 +311,19 @@ namespace Avalonia.Controls.Primitives
/// </summary> /// </summary>
public void Close() public void Close()
{ {
if (_popupHost != null) if (_openState is null)
{ {
_popupHost.TemplateApplied -= RootTemplateApplied; using (BeginIgnoringIsOpen())
{
IsOpen = false;
}
return;
} }
_presenterSubscription?.Dispose(); _openState.Dispose();
_openState = null;
CloseCurrent();
using (BeginIgnoringIsOpen()) using (BeginIgnoringIsOpen())
{ {
IsOpen = false; IsOpen = false;
@ -293,41 +332,6 @@ namespace Avalonia.Controls.Primitives
Closed?.Invoke(this, EventArgs.Empty); Closed?.Invoke(this, EventArgs.Empty);
} }
void CloseCurrent()
{
if (_topLevel != null)
{
_topLevel.RemoveHandler(PointerPressedEvent, PointerPressedOutside);
var window = _topLevel as Window;
if (window != null)
window.Deactivated -= WindowDeactivated;
else
{
var parentPopuproot = _topLevel as PopupRoot;
if (parentPopuproot?.Parent is Popup popup)
{
popup.Closed -= ParentClosed;
}
}
_nonClientListener?.Dispose();
_nonClientListener = null;
_topLevel = null;
}
if (_popupHost != null)
{
foreach(var b in _bindings)
b.Dispose();
_bindings.Clear();
_popupHost.SetChild(null);
_popupHost.Hide();
((ISetLogicalParent)_popupHost).SetParent(null);
_popupHost.Dispose();
_popupHost = null;
}
}
/// <summary> /// <summary>
/// Measures the control. /// Measures the control.
/// </summary> /// </summary>
@ -345,16 +349,22 @@ namespace Avalonia.Controls.Primitives
Close(); Close();
} }
private static IDisposable SubscribeToEventHandler<T, TEventHandler>(T target, TEventHandler handler, Action<T, TEventHandler> subscribe, Action<T, TEventHandler> unsubscribe)
{
subscribe(target, handler);
return Disposable.Create((unsubscribe, target, handler), state => state.unsubscribe(state.target, state.handler));
}
/// <summary> /// <summary>
/// Called when the <see cref="IsOpen"/> property changes. /// Called when the <see cref="IsOpen"/> property changes.
/// </summary> /// </summary>
/// <param name="e">The event args.</param> /// <param name="e">The event args.</param>
private void IsOpenChanged(AvaloniaPropertyChangedEventArgs e) private void IsOpenChanged(AvaloniaPropertyChangedEventArgs<bool> e)
{ {
if (!_ignoreIsOpenChanged) if (!_ignoreIsOpenChanged)
{ {
if ((bool)e.NewValue) if (e.NewValue.Value)
{ {
Open(); Open();
} }
@ -373,7 +383,7 @@ namespace Avalonia.Controls.Primitives
{ {
LogicalChildren.Clear(); LogicalChildren.Clear();
((ISetLogicalParent)e.OldValue)?.SetParent(null); ((ISetLogicalParent?)e.OldValue)?.SetParent(null);
if (e.NewValue != null) if (e.NewValue != null)
{ {
@ -394,34 +404,37 @@ namespace Avalonia.Controls.Primitives
private void PointerPressedOutside(object sender, PointerPressedEventArgs e) private void PointerPressedOutside(object sender, PointerPressedEventArgs e)
{ {
if (!StaysOpen) if (!StaysOpen && !IsChildOrThis((IVisual)e.Source))
{ {
if (!IsChildOrThis((IVisual)e.Source)) Close();
{ e.Handled = true;
Close();
e.Handled = true;
}
} }
} }
private void RootTemplateApplied(object sender, TemplateAppliedEventArgs e) private void RootTemplateApplied(object sender, TemplateAppliedEventArgs e)
{ {
_popupHost.TemplateApplied -= RootTemplateApplied; if (_openState is null)
if (_presenterSubscription != null)
{ {
_presenterSubscription.Dispose(); return;
_presenterSubscription = null;
} }
var popupHost = _openState.PopupHost;
popupHost.TemplateApplied -= RootTemplateApplied;
_openState.SetPresenterSubscription(null);
// If the Popup appears in a control template, then the child controls // If the Popup appears in a control template, then the child controls
// that appear in the popup host need to have their TemplatedParent // that appear in the popup host need to have their TemplatedParent
// properties set. // properties set.
if (TemplatedParent != null) if (TemplatedParent != null && popupHost.Presenter != null)
{ {
_popupHost.Presenter?.ApplyTemplate(); popupHost.Presenter.ApplyTemplate();
_popupHost.Presenter?.GetObservable(ContentPresenter.ChildProperty)
var presenterSubscription = popupHost.Presenter.GetObservable(ContentPresenter.ChildProperty)
.Subscribe(SetTemplatedParentAndApplyChildTemplates); .Subscribe(SetTemplatedParentAndApplyChildTemplates);
_openState.SetPresenterSubscription(presenterSubscription);
} }
} }
@ -440,7 +453,7 @@ namespace Avalonia.Controls.Primitives
if (!(control is IPresenter) && control.TemplatedParent == templatedParent) if (!(control is IPresenter) && control.TemplatedParent == templatedParent)
{ {
foreach (IControl child in control.GetVisualChildren()) foreach (IControl child in control.VisualChildren)
{ {
SetTemplatedParentAndApplyChildTemplates(child); SetTemplatedParentAndApplyChildTemplates(child);
} }
@ -450,22 +463,41 @@ namespace Avalonia.Controls.Primitives
private bool IsChildOrThis(IVisual child) private bool IsChildOrThis(IVisual child)
{ {
IVisual root = child.GetVisualRoot(); if (_openState is null)
while (root is IHostedVisualTreeRoot hostedRoot )
{ {
if (root == this._popupHost) return false;
}
var popupHost = _openState.PopupHost;
IVisual? root = child.VisualRoot;
while (root is IHostedVisualTreeRoot hostedRoot)
{
if (root == popupHost)
{
return true; return true;
root = hostedRoot.Host?.GetVisualRoot(); }
root = hostedRoot.Host?.VisualRoot;
} }
return false; return false;
} }
public bool IsInsidePopup(IVisual visual) public bool IsInsidePopup(IVisual visual)
{ {
return _popupHost != null && ((IVisual)_popupHost)?.IsVisualAncestorOf(visual) == true; if (_openState is null)
{
return false;
}
var popupHost = _openState.PopupHost;
return popupHost != null && ((IVisual)popupHost).IsVisualAncestorOf(visual);
} }
public bool IsPointerOverPopup => ((IInputElement)_popupHost).IsPointerOver; public bool IsPointerOverPopup => ((IInputElement?)_openState?.PopupHost)?.IsPointerOver ?? false;
private void WindowDeactivated(object sender, EventArgs e) private void WindowDeactivated(object sender, EventArgs e)
{ {
@ -503,5 +535,36 @@ namespace Avalonia.Controls.Primitives
_owner._ignoreIsOpenChanged = false; _owner._ignoreIsOpenChanged = false;
} }
} }
private class PopupOpenState : IDisposable
{
private readonly IDisposable _cleanup;
private IDisposable? _presenterCleanup;
public PopupOpenState(TopLevel topLevel, IPopupHost popupHost, IDisposable cleanup)
{
TopLevel = topLevel;
PopupHost = popupHost;
_cleanup = cleanup;
}
public TopLevel TopLevel { get; }
public IPopupHost PopupHost { get; }
public void SetPresenterSubscription(IDisposable? presenterCleanup)
{
_presenterCleanup?.Dispose();
_presenterCleanup = presenterCleanup;
}
public void Dispose()
{
_presenterCleanup?.Dispose();
_cleanup.Dispose();
}
}
} }
} }

24
src/Avalonia.Controls/Primitives/ScrollBar.cs

@ -55,9 +55,6 @@ namespace Avalonia.Controls.Primitives
/// </summary> /// </summary>
static ScrollBar() static ScrollBar()
{ {
PseudoClass<ScrollBar, Orientation>(OrientationProperty, o => o == Orientation.Vertical, ":vertical");
PseudoClass<ScrollBar, Orientation>(OrientationProperty, o => o == Orientation.Horizontal, ":horizontal");
Thumb.DragDeltaEvent.AddClassHandler<ScrollBar>((x, e) => x.OnThumbDragDelta(e), RoutingStrategies.Bubble); Thumb.DragDeltaEvent.AddClassHandler<ScrollBar>((x, e) => x.OnThumbDragDelta(e), RoutingStrategies.Bubble);
Thumb.DragCompletedEvent.AddClassHandler<ScrollBar>((x, e) => x.OnThumbDragComplete(e), RoutingStrategies.Bubble); Thumb.DragCompletedEvent.AddClassHandler<ScrollBar>((x, e) => x.OnThumbDragComplete(e), RoutingStrategies.Bubble);
} }
@ -74,6 +71,7 @@ namespace Avalonia.Controls.Primitives
this.GetObservable(VisibilityProperty).Select(_ => Unit.Default)) this.GetObservable(VisibilityProperty).Select(_ => Unit.Default))
.Select(_ => CalculateIsVisible()); .Select(_ => CalculateIsVisible());
this.Bind(IsVisibleProperty, isVisible, BindingPriority.Style); this.Bind(IsVisibleProperty, isVisible, BindingPriority.Style);
UpdatePseudoClasses(Orientation);
} }
/// <summary> /// <summary>
@ -143,6 +141,20 @@ namespace Avalonia.Controls.Primitives
} }
} }
protected override void OnPropertyChanged<T>(
AvaloniaProperty<T> property,
Optional<T> oldValue,
BindingValue<T> newValue,
BindingPriority priority)
{
base.OnPropertyChanged(property, oldValue, newValue, priority);
if (property == OrientationProperty)
{
UpdatePseudoClasses(newValue.GetValueOrDefault<Orientation>());
}
}
protected override void OnTemplateApplied(TemplateAppliedEventArgs e) protected override void OnTemplateApplied(TemplateAppliedEventArgs e)
{ {
base.OnTemplateApplied(e); base.OnTemplateApplied(e);
@ -252,5 +264,11 @@ namespace Avalonia.Controls.Primitives
{ {
Scroll?.Invoke(this, new ScrollEventArgs(scrollEventType, Value)); Scroll?.Invoke(this, new ScrollEventArgs(scrollEventType, Value));
} }
private void UpdatePseudoClasses(Orientation o)
{
PseudoClasses.Set(":vertical", o == Orientation.Vertical);
PseudoClasses.Set(":horizontal", o == Orientation.Horizontal);
}
} }
} }

24
src/Avalonia.Controls/Primitives/ToggleButton.cs

@ -2,8 +2,8 @@
// Licensed under the MIT license. See licence.md file in the project root for full license information. // Licensed under the MIT license. See licence.md file in the project root for full license information.
using System; using System;
using Avalonia.Interactivity;
using Avalonia.Data; using Avalonia.Data;
using Avalonia.Interactivity;
namespace Avalonia.Controls.Primitives namespace Avalonia.Controls.Primitives
{ {
@ -51,13 +51,14 @@ namespace Avalonia.Controls.Primitives
static ToggleButton() static ToggleButton()
{ {
PseudoClass<ToggleButton, bool?>(IsCheckedProperty, c => c == true, ":checked");
PseudoClass<ToggleButton, bool?>(IsCheckedProperty, c => c == false, ":unchecked");
PseudoClass<ToggleButton, bool?>(IsCheckedProperty, c => c == null, ":indeterminate");
IsCheckedProperty.Changed.AddClassHandler<ToggleButton>((x, e) => x.OnIsCheckedChanged(e)); IsCheckedProperty.Changed.AddClassHandler<ToggleButton>((x, e) => x.OnIsCheckedChanged(e));
} }
public ToggleButton()
{
UpdatePseudoClasses(IsChecked);
}
/// <summary> /// <summary>
/// Raised when a <see cref="ToggleButton"/> is checked. /// Raised when a <see cref="ToggleButton"/> is checked.
/// </summary> /// </summary>
@ -91,7 +92,11 @@ namespace Avalonia.Controls.Primitives
public bool? IsChecked public bool? IsChecked
{ {
get => _isChecked; get => _isChecked;
set => SetAndRaise(IsCheckedProperty, ref _isChecked, value); set
{
SetAndRaise(IsCheckedProperty, ref _isChecked, value);
UpdatePseudoClasses(value);
}
} }
/// <summary> /// <summary>
@ -182,5 +187,12 @@ namespace Avalonia.Controls.Primitives
break; break;
} }
} }
private void UpdatePseudoClasses(bool? isChecked)
{
PseudoClasses.Set(":checked", isChecked == true);
PseudoClasses.Set(":unchecked", isChecked == false);
PseudoClasses.Set(":indeterminate", isChecked == null);
}
} }
} }

28
src/Avalonia.Controls/Primitives/Track.cs

@ -4,6 +4,7 @@
// Licensed to The Avalonia Project under MIT License, courtesy of The .NET Foundation. // Licensed to The Avalonia Project under MIT License, courtesy of The .NET Foundation.
using System; using System;
using Avalonia.Data;
using Avalonia.Input; using Avalonia.Input;
using Avalonia.Layout; using Avalonia.Layout;
using Avalonia.Metadata; using Avalonia.Metadata;
@ -46,14 +47,17 @@ namespace Avalonia.Controls.Primitives
static Track() static Track()
{ {
PseudoClass<Track, Orientation>(OrientationProperty, o => o == Orientation.Vertical, ":vertical");
PseudoClass<Track, Orientation>(OrientationProperty, o => o == Orientation.Horizontal, ":horizontal");
ThumbProperty.Changed.AddClassHandler<Track>((x,e) => x.ThumbChanged(e)); ThumbProperty.Changed.AddClassHandler<Track>((x,e) => x.ThumbChanged(e));
IncreaseButtonProperty.Changed.AddClassHandler<Track>((x, e) => x.ButtonChanged(e)); IncreaseButtonProperty.Changed.AddClassHandler<Track>((x, e) => x.ButtonChanged(e));
DecreaseButtonProperty.Changed.AddClassHandler<Track>((x, e) => x.ButtonChanged(e)); DecreaseButtonProperty.Changed.AddClassHandler<Track>((x, e) => x.ButtonChanged(e));
AffectsArrange<Track>(MinimumProperty, MaximumProperty, ValueProperty, OrientationProperty); AffectsArrange<Track>(MinimumProperty, MaximumProperty, ValueProperty, OrientationProperty);
} }
public Track()
{
UpdatePseudoClasses(Orientation);
}
public double Minimum public double Minimum
{ {
get { return _minimum; } get { return _minimum; }
@ -276,6 +280,20 @@ namespace Avalonia.Controls.Primitives
return arrangeSize; return arrangeSize;
} }
protected override void OnPropertyChanged<T>(
AvaloniaProperty<T> property,
Optional<T> oldValue,
BindingValue<T> newValue,
BindingPriority priority)
{
base.OnPropertyChanged(property, oldValue, newValue, priority);
if (property == OrientationProperty)
{
UpdatePseudoClasses(newValue.GetValueOrDefault<Orientation>());
}
}
private static void CoerceLength(ref double componentLength, double trackLength) private static void CoerceLength(ref double componentLength, double trackLength)
{ {
if (componentLength < 0) if (componentLength < 0)
@ -433,5 +451,11 @@ namespace Avalonia.Controls.Primitives
DecreaseButton.IsVisible = visible; DecreaseButton.IsVisible = visible;
} }
} }
private void UpdatePseudoClasses(Orientation o)
{
PseudoClasses.Set(":vertical", o == Orientation.Vertical);
PseudoClasses.Set(":horizontal", o == Orientation.Horizontal);
}
} }
} }

9
src/Avalonia.Controls/Primitives/VisualLayerManager.cs

@ -1,6 +1,5 @@
using System.Collections.Generic; using System.Collections.Generic;
using Avalonia.LogicalTree; using Avalonia.LogicalTree;
using Avalonia.Styling;
namespace Avalonia.Controls.Primitives namespace Avalonia.Controls.Primitives
{ {
@ -8,7 +7,7 @@ namespace Avalonia.Controls.Primitives
{ {
private const int AdornerZIndex = int.MaxValue - 100; private const int AdornerZIndex = int.MaxValue - 100;
private const int OverlayZIndex = int.MaxValue - 99; private const int OverlayZIndex = int.MaxValue - 99;
private IStyleRoot _styleRoot; private ILogicalRoot _logicalRoot;
private readonly List<Control> _layers = new List<Control>(); private readonly List<Control> _layers = new List<Control>();
@ -53,7 +52,7 @@ namespace Avalonia.Controls.Primitives
layer.ZIndex = zindex; layer.ZIndex = zindex;
VisualChildren.Add(layer); VisualChildren.Add(layer);
if (((ILogical)this).IsAttachedToLogicalTree) if (((ILogical)this).IsAttachedToLogicalTree)
((ILogical)layer).NotifyAttachedToLogicalTree(new LogicalTreeAttachmentEventArgs(_styleRoot, layer, this)); ((ILogical)layer).NotifyAttachedToLogicalTree(new LogicalTreeAttachmentEventArgs(_logicalRoot, layer, this));
InvalidateArrange(); InvalidateArrange();
} }
@ -61,7 +60,7 @@ namespace Avalonia.Controls.Primitives
protected override void OnAttachedToLogicalTree(LogicalTreeAttachmentEventArgs e) protected override void OnAttachedToLogicalTree(LogicalTreeAttachmentEventArgs e)
{ {
base.OnAttachedToLogicalTree(e); base.OnAttachedToLogicalTree(e);
_styleRoot = e.Root; _logicalRoot = e.Root;
foreach (var l in _layers) foreach (var l in _layers)
((ILogical)l).NotifyAttachedToLogicalTree(e); ((ILogical)l).NotifyAttachedToLogicalTree(e);
@ -69,7 +68,7 @@ namespace Avalonia.Controls.Primitives
protected override void OnDetachedFromLogicalTree(LogicalTreeAttachmentEventArgs e) protected override void OnDetachedFromLogicalTree(LogicalTreeAttachmentEventArgs e)
{ {
_styleRoot = null; _logicalRoot = null;
base.OnDetachedFromLogicalTree(e); base.OnDetachedFromLogicalTree(e);
foreach (var l in _layers) foreach (var l in _layers)
((ILogical)l).NotifyDetachedFromLogicalTree(e); ((ILogical)l).NotifyDetachedFromLogicalTree(e);

53
src/Avalonia.Controls/ProgressBar.cs

@ -4,6 +4,7 @@
using System; using System;
using Avalonia.Controls.Primitives; using Avalonia.Controls.Primitives;
using Avalonia.Data;
using Avalonia.Layout; using Avalonia.Layout;
namespace Avalonia.Controls namespace Avalonia.Controls
@ -16,6 +17,9 @@ namespace Avalonia.Controls
public static readonly StyledProperty<bool> IsIndeterminateProperty = public static readonly StyledProperty<bool> IsIndeterminateProperty =
AvaloniaProperty.Register<ProgressBar, bool>(nameof(IsIndeterminate)); AvaloniaProperty.Register<ProgressBar, bool>(nameof(IsIndeterminate));
public static readonly StyledProperty<bool> ShowProgressTextProperty =
AvaloniaProperty.Register<ProgressBar, bool>(nameof(ShowProgressText));
public static readonly StyledProperty<Orientation> OrientationProperty = public static readonly StyledProperty<Orientation> OrientationProperty =
AvaloniaProperty.Register<ProgressBar, Orientation>(nameof(Orientation), Orientation.Horizontal); AvaloniaProperty.Register<ProgressBar, Orientation>(nameof(Orientation), Orientation.Horizontal);
@ -35,20 +39,27 @@ namespace Avalonia.Controls
static ProgressBar() static ProgressBar()
{ {
PseudoClass<ProgressBar, Orientation>(OrientationProperty, o => o == Orientation.Vertical, ":vertical");
PseudoClass<ProgressBar, Orientation>(OrientationProperty, o => o == Orientation.Horizontal, ":horizontal");
PseudoClass<ProgressBar>(IsIndeterminateProperty, ":indeterminate");
ValueProperty.Changed.AddClassHandler<ProgressBar>((x, e) => x.UpdateIndicatorWhenPropChanged(e)); ValueProperty.Changed.AddClassHandler<ProgressBar>((x, e) => x.UpdateIndicatorWhenPropChanged(e));
IsIndeterminateProperty.Changed.AddClassHandler<ProgressBar>((x, e) => x.UpdateIndicatorWhenPropChanged(e)); IsIndeterminateProperty.Changed.AddClassHandler<ProgressBar>((x, e) => x.UpdateIndicatorWhenPropChanged(e));
} }
public ProgressBar()
{
UpdatePseudoClasses(IsIndeterminate, Orientation);
}
public bool IsIndeterminate public bool IsIndeterminate
{ {
get => GetValue(IsIndeterminateProperty); get => GetValue(IsIndeterminateProperty);
set => SetValue(IsIndeterminateProperty, value); set => SetValue(IsIndeterminateProperty, value);
} }
public bool ShowProgressText
{
get => GetValue(ShowProgressTextProperty);
set => SetValue(ShowProgressTextProperty, value);
}
public Orientation Orientation public Orientation Orientation
{ {
get => GetValue(OrientationProperty); get => GetValue(OrientationProperty);
@ -75,6 +86,24 @@ namespace Avalonia.Controls
return base.ArrangeOverride(finalSize); return base.ArrangeOverride(finalSize);
} }
protected override void OnPropertyChanged<T>(
AvaloniaProperty<T> property,
Optional<T> oldValue,
BindingValue<T> newValue,
BindingPriority priority)
{
base.OnPropertyChanged(property, oldValue, newValue, priority);
if (property == IsIndeterminateProperty)
{
UpdatePseudoClasses(newValue.GetValueOrDefault<bool>(), null);
}
else if (property == OrientationProperty)
{
UpdatePseudoClasses(null, newValue.GetValueOrDefault<Orientation>());
}
}
/// <inheritdoc/> /// <inheritdoc/>
protected override void OnTemplateApplied(TemplateAppliedEventArgs e) protected override void OnTemplateApplied(TemplateAppliedEventArgs e)
{ {
@ -121,5 +150,21 @@ namespace Avalonia.Controls
{ {
UpdateIndicator(Bounds.Size); UpdateIndicator(Bounds.Size);
} }
private void UpdatePseudoClasses(
bool? isIndeterminate,
Orientation? o)
{
if (isIndeterminate.HasValue)
{
PseudoClasses.Set(":indeterminate", isIndeterminate.Value);
}
if (o.HasValue)
{
PseudoClasses.Set(":vertical", o == Orientation.Vertical);
PseudoClasses.Set(":horizontal", o == Orientation.Horizontal);
}
}
} }
} }

16
src/Avalonia.Controls/ScrollViewer.cs

@ -249,6 +249,22 @@ namespace Avalonia.Controls
set { SetValue(VerticalScrollBarVisibilityProperty, value); } set { SetValue(VerticalScrollBarVisibilityProperty, value); }
} }
/// <summary>
/// Scrolls to the top-left corner of the content.
/// </summary>
public void ScrollToHome()
{
Offset = new Vector(double.NegativeInfinity, double.NegativeInfinity);
}
/// <summary>
/// Scrolls to the bottom-left corner of the content.
/// </summary>
public void ScrollToEnd()
{
Offset = new Vector(double.NegativeInfinity, double.PositiveInfinity);
}
/// <summary> /// <summary>
/// Gets a value indicating whether the viewer can scroll horizontally. /// Gets a value indicating whether the viewer can scroll horizontally.
/// </summary> /// </summary>

24
src/Avalonia.Controls/Slider.cs

@ -3,6 +3,7 @@
using System; using System;
using Avalonia.Controls.Primitives; using Avalonia.Controls.Primitives;
using Avalonia.Data;
using Avalonia.Input; using Avalonia.Input;
using Avalonia.Interactivity; using Avalonia.Interactivity;
using Avalonia.Layout; using Avalonia.Layout;
@ -43,8 +44,6 @@ namespace Avalonia.Controls
static Slider() static Slider()
{ {
OrientationProperty.OverrideDefaultValue(typeof(Slider), Orientation.Horizontal); OrientationProperty.OverrideDefaultValue(typeof(Slider), Orientation.Horizontal);
PseudoClass<Slider, Orientation>(OrientationProperty, o => o == Orientation.Vertical, ":vertical");
PseudoClass<Slider, Orientation>(OrientationProperty, o => o == Orientation.Horizontal, ":horizontal");
Thumb.DragStartedEvent.AddClassHandler<Slider>((x, e) => x.OnThumbDragStarted(e), RoutingStrategies.Bubble); Thumb.DragStartedEvent.AddClassHandler<Slider>((x, e) => x.OnThumbDragStarted(e), RoutingStrategies.Bubble);
Thumb.DragDeltaEvent.AddClassHandler<Slider>((x, e) => x.OnThumbDragDelta(e), RoutingStrategies.Bubble); Thumb.DragDeltaEvent.AddClassHandler<Slider>((x, e) => x.OnThumbDragDelta(e), RoutingStrategies.Bubble);
Thumb.DragCompletedEvent.AddClassHandler<Slider>((x, e) => x.OnThumbDragCompleted(e), RoutingStrategies.Bubble); Thumb.DragCompletedEvent.AddClassHandler<Slider>((x, e) => x.OnThumbDragCompleted(e), RoutingStrategies.Bubble);
@ -55,6 +54,7 @@ namespace Avalonia.Controls
/// </summary> /// </summary>
public Slider() public Slider()
{ {
UpdatePseudoClasses(Orientation);
} }
/// <summary> /// <summary>
@ -137,6 +137,20 @@ namespace Avalonia.Controls
} }
} }
protected override void OnPropertyChanged<T>(
AvaloniaProperty<T> property,
Optional<T> oldValue,
BindingValue<T> newValue,
BindingPriority priority)
{
base.OnPropertyChanged(property, oldValue, newValue, priority);
if (property == OrientationProperty)
{
UpdatePseudoClasses(newValue.GetValueOrDefault<Orientation>());
}
}
/// <summary> /// <summary>
/// Called when user start dragging the <see cref="Thumb"/>. /// Called when user start dragging the <see cref="Thumb"/>.
/// </summary> /// </summary>
@ -190,5 +204,11 @@ namespace Avalonia.Controls
return value; return value;
} }
private void UpdatePseudoClasses(Orientation o)
{
PseudoClasses.Set(":vertical", o == Orientation.Vertical);
PseudoClasses.Set(":horizontal", o == Orientation.Horizontal);
}
} }
} }

3
src/Avalonia.Controls/Templates/FuncDataTemplate.cs

@ -3,7 +3,6 @@
using System; using System;
using System.Reactive.Linq; using System.Reactive.Linq;
using System.Reflection;
namespace Avalonia.Controls.Templates namespace Avalonia.Controls.Templates
{ {
@ -102,7 +101,7 @@ namespace Avalonia.Controls.Templates
/// </returns> /// </returns>
private static bool IsInstance(object o, Type t) private static bool IsInstance(object o, Type t)
{ {
return (o != null) && t.GetTypeInfo().IsAssignableFrom(o.GetType().GetTypeInfo()); return t.IsInstanceOfType(o);
} }
} }
} }

3
src/Avalonia.Controls/Templates/FuncTreeDataTemplate.cs

@ -3,7 +3,6 @@
using System; using System;
using System.Collections; using System.Collections;
using System.Reflection;
using Avalonia.Data; using Avalonia.Data;
namespace Avalonia.Controls.Templates namespace Avalonia.Controls.Templates
@ -75,7 +74,7 @@ namespace Avalonia.Controls.Templates
/// </returns> /// </returns>
private static bool IsInstance(object o, Type t) private static bool IsInstance(object o, Type t)
{ {
return (o != null) && t.GetTypeInfo().IsAssignableFrom(o.GetType().GetTypeInfo()); return t.IsInstanceOfType(o);
} }
} }
} }

42
src/Avalonia.Controls/TextBox.cs

@ -14,6 +14,7 @@ using Avalonia.Interactivity;
using Avalonia.Media; using Avalonia.Media;
using Avalonia.Metadata; using Avalonia.Metadata;
using Avalonia.Data; using Avalonia.Data;
using Avalonia.Layout;
using Avalonia.Utilities; using Avalonia.Utilities;
namespace Avalonia.Controls namespace Avalonia.Controls
@ -72,6 +73,18 @@ namespace Avalonia.Controls
public static readonly StyledProperty<TextAlignment> TextAlignmentProperty = public static readonly StyledProperty<TextAlignment> TextAlignmentProperty =
TextBlock.TextAlignmentProperty.AddOwner<TextBox>(); TextBlock.TextAlignmentProperty.AddOwner<TextBox>();
/// <summary>
/// Defines the <see cref="HorizontalAlignment"/> property.
/// </summary>
public static readonly StyledProperty<HorizontalAlignment> HorizontalContentAlignmentProperty =
ContentControl.HorizontalContentAlignmentProperty.AddOwner<TextBox>();
/// <summary>
/// Defines the <see cref="VerticalAlignment"/> property.
/// </summary>
public static readonly StyledProperty<VerticalAlignment> VerticalContentAlignmentProperty =
ContentControl.VerticalContentAlignmentProperty.AddOwner<TextBox>();
public static readonly StyledProperty<TextWrapping> TextWrappingProperty = public static readonly StyledProperty<TextWrapping> TextWrappingProperty =
TextBlock.TextWrappingProperty.AddOwner<TextBox>(); TextBlock.TextWrappingProperty.AddOwner<TextBox>();
@ -262,6 +275,24 @@ namespace Avalonia.Controls
} }
} }
/// <summary>
/// Gets or sets the horizontal alignment of the content within the control.
/// </summary>
public HorizontalAlignment HorizontalContentAlignment
{
get { return GetValue(HorizontalContentAlignmentProperty); }
set { SetValue(HorizontalContentAlignmentProperty, value); }
}
/// <summary>
/// Gets or sets the vertical alignment of the content within the control.
/// </summary>
public VerticalAlignment VerticalContentAlignment
{
get { return GetValue(VerticalContentAlignmentProperty); }
set { SetValue(VerticalContentAlignmentProperty, value); }
}
public TextAlignment TextAlignment public TextAlignment TextAlignment
{ {
get { return GetValue(TextAlignmentProperty); } get { return GetValue(TextAlignmentProperty); }
@ -316,8 +347,7 @@ namespace Avalonia.Controls
!AcceptsReturn && !AcceptsReturn &&
Text?.Length > 0) Text?.Length > 0)
{ {
SelectionStart = 0; SelectAll();
SelectionEnd = Text.Length;
} }
else else
{ {
@ -673,8 +703,7 @@ namespace Avalonia.Controls
SelectionEnd = StringUtils.NextWord(text, index); SelectionEnd = StringUtils.NextWord(text, index);
break; break;
case 3: case 3:
SelectionStart = 0; SelectAll();
SelectionEnd = text.Length;
break; break;
} }
} }
@ -896,7 +925,10 @@ namespace Avalonia.Controls
CaretIndex = caretIndex; CaretIndex = caretIndex;
} }
private void SelectAll() /// <summary>
/// Select all text in the TextBox
/// </summary>
public void SelectAll()
{ {
SelectionStart = 0; SelectionStart = 0;
SelectionEnd = Text?.Length ?? 0; SelectionEnd = Text?.Length ?? 0;

3
src/Avalonia.Controls/TopLevel.cs

@ -30,7 +30,8 @@ namespace Avalonia.Controls
ILayoutRoot, ILayoutRoot,
IRenderRoot, IRenderRoot,
ICloseable, ICloseable,
IStyleRoot, IStyleHost,
ILogicalRoot,
IWeakSubscriber<ResourcesChangedEventArgs> IWeakSubscriber<ResourcesChangedEventArgs>
{ {
/// <summary> /// <summary>

10
src/Avalonia.Controls/Utils/AncestorFinder.cs

@ -1,10 +1,8 @@
using System; using System;
using System.Linq;
using System.Reactive; using System.Reactive;
using System.Reactive.Disposables; using System.Reactive.Disposables;
using System.Reactive.Linq; using System.Reactive.Linq;
using System.Reactive.Subjects; using System.Reactive.Subjects;
using System.Reflection;
namespace Avalonia.Controls.Utils namespace Avalonia.Controls.Utils
{ {
@ -13,14 +11,14 @@ namespace Avalonia.Controls.Utils
class FinderNode : IDisposable class FinderNode : IDisposable
{ {
private readonly IStyledElement _control; private readonly IStyledElement _control;
private readonly TypeInfo _ancestorType; private readonly Type _ancestorType;
public IObservable<IStyledElement> Observable => _subject; public IObservable<IStyledElement> Observable => _subject;
private readonly Subject<IStyledElement> _subject = new Subject<IStyledElement>(); private readonly Subject<IStyledElement> _subject = new Subject<IStyledElement>();
private FinderNode _child; private FinderNode _child;
private IDisposable _disposable; private IDisposable _disposable;
public FinderNode(IStyledElement control, TypeInfo ancestorType) public FinderNode(IStyledElement control, Type ancestorType)
{ {
_control = control; _control = control;
_ancestorType = ancestorType; _ancestorType = ancestorType;
@ -33,7 +31,7 @@ namespace Avalonia.Controls.Utils
private void OnValueChanged(IStyledElement next) private void OnValueChanged(IStyledElement next)
{ {
if (next == null || _ancestorType.IsAssignableFrom(next.GetType().GetTypeInfo())) if (next == null || _ancestorType.IsAssignableFrom(next.GetType()))
_subject.OnNext(next); _subject.OnNext(next);
else else
{ {
@ -63,7 +61,7 @@ namespace Avalonia.Controls.Utils
{ {
return new AnonymousObservable<IStyledElement>(observer => return new AnonymousObservable<IStyledElement>(observer =>
{ {
var finder = new FinderNode(control, ancestorType.GetTypeInfo()); var finder = new FinderNode(control, ancestorType);
var subscription = finder.Observable.Subscribe(observer); var subscription = finder.Observable.Subscribe(observer);
finder.Init(); finder.Init();

2
src/Avalonia.Controls/Utils/SelectingItemsControlSelectionAdapter.cs

@ -261,7 +261,7 @@ namespace Avalonia.Controls.Utils
break; break;
case Key.Down: case Key.Down:
if ((e.Modifiers & InputModifiers.Alt) == InputModifiers.None) if ((e.KeyModifiers & KeyModifiers.Alt) == KeyModifiers.None)
{ {
SelectedIndexIncrement(); SelectedIndexIncrement();
e.Handled = true; e.Handled = true;

4
src/Avalonia.Controls/Window.cs

@ -129,7 +129,7 @@ namespace Avalonia.Controls
ShowInTaskbarProperty.Changed.AddClassHandler<Window>((w, e) => w.PlatformImpl?.ShowTaskbarIcon((bool)e.NewValue)); ShowInTaskbarProperty.Changed.AddClassHandler<Window>((w, e) => w.PlatformImpl?.ShowTaskbarIcon((bool)e.NewValue));
IconProperty.Changed.AddClassHandler<Window>((s, e) => s.PlatformImpl?.SetIcon(((WindowIcon)e.NewValue).PlatformImpl)); IconProperty.Changed.AddClassHandler<Window>((s, e) => s.PlatformImpl?.SetIcon(((WindowIcon)e.NewValue)?.PlatformImpl));
CanResizeProperty.Changed.AddClassHandler<Window>((w, e) => w.PlatformImpl?.CanResize((bool)e.NewValue)); CanResizeProperty.Changed.AddClassHandler<Window>((w, e) => w.PlatformImpl?.CanResize((bool)e.NewValue));
@ -529,7 +529,7 @@ namespace Avalonia.Controls
{ {
var sizeToContent = SizeToContent; var sizeToContent = SizeToContent;
var clientSize = ClientSize; var clientSize = ClientSize;
Size constraint = clientSize; var constraint = availableSize;
if ((sizeToContent & SizeToContent.Width) != 0) if ((sizeToContent & SizeToContent.Width) != 0)
{ {

2
src/Avalonia.Dialogs/AboutAvaloniaDialog.xaml

@ -99,7 +99,7 @@
</StackPanel> </StackPanel>
</StackPanel> </StackPanel>
<StackPanel VerticalAlignment="Bottom" Margin="10"> <StackPanel VerticalAlignment="Bottom" Margin="10">
<TextBlock Text="© 2019 The Avalonia Project" TextWrapping="Wrap" HorizontalAlignment="Center" /> <TextBlock Text="© 2020 The Avalonia Project" TextWrapping="Wrap" HorizontalAlignment="Center" />
</StackPanel> </StackPanel>
</Grid> </Grid>
</Window> </Window>

2
src/Avalonia.Input/Gestures.cs

@ -79,7 +79,7 @@ namespace Avalonia.Input
{ {
s_lastPress = new WeakReference<IInteractive>(e.Source); s_lastPress = new WeakReference<IInteractive>(e.Source);
} }
else if (s_lastPress != null && e.ClickCount == 2 && e.MouseButton != MouseButton.Right) else if (s_lastPress != null && e.ClickCount == 2 && e.MouseButton == MouseButton.Left)
{ {
if (s_lastPress.TryGetTarget(out var target) && target == e.Source) if (s_lastPress.TryGetTarget(out var target) && target == e.Source)
{ {

13
src/Avalonia.Input/GotFocusEventArgs.cs

@ -1,6 +1,7 @@
// Copyright (c) The Avalonia Project. All rights reserved. // Copyright (c) The Avalonia Project. All rights reserved.
// Licensed under the MIT license. See licence.md file in the project root for full license information. // Licensed under the MIT license. See licence.md file in the project root for full license information.
using System;
using Avalonia.Interactivity; using Avalonia.Interactivity;
namespace Avalonia.Input namespace Avalonia.Input
@ -18,6 +19,16 @@ namespace Avalonia.Input
/// <summary> /// <summary>
/// Gets or sets any input modifiers active at the time of focus. /// Gets or sets any input modifiers active at the time of focus.
/// </summary> /// </summary>
public InputModifiers InputModifiers { get; set; } [Obsolete("Use KeyModifiers")]
public InputModifiers InputModifiers
{
get => (InputModifiers)KeyModifiers;
set => KeyModifiers = (KeyModifiers)((int)value & 0xF);
}
/// <summary>
/// Gets or sets any key modifiers active at the time of focus.
/// </summary>
public KeyModifiers KeyModifiers { get; set; }
} }
} }

2
src/Avalonia.Input/IInputElement.cs

@ -63,7 +63,7 @@ namespace Avalonia.Input
event EventHandler<PointerReleasedEventArgs> PointerReleased; event EventHandler<PointerReleasedEventArgs> PointerReleased;
/// <summary> /// <summary>
/// Occurs when the mouse wheen is scrolled over the control. /// Occurs when the mouse wheel is scrolled over the control.
/// </summary> /// </summary>
event EventHandler<PointerWheelEventArgs> PointerWheelChanged; event EventHandler<PointerWheelEventArgs> PointerWheelChanged;

42
src/Avalonia.Input/InputElement.cs

@ -4,6 +4,8 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using Avalonia.Controls;
using Avalonia.Data;
using Avalonia.Input.GestureRecognizers; using Avalonia.Input.GestureRecognizers;
using Avalonia.Interactivity; using Avalonia.Interactivity;
using Avalonia.VisualTree; using Avalonia.VisualTree;
@ -181,10 +183,11 @@ namespace Avalonia.Input
PointerReleasedEvent.AddClassHandler<InputElement>((x, e) => x.OnPointerReleased(e)); PointerReleasedEvent.AddClassHandler<InputElement>((x, e) => x.OnPointerReleased(e));
PointerCaptureLostEvent.AddClassHandler<InputElement>((x, e) => x.OnPointerCaptureLost(e)); PointerCaptureLostEvent.AddClassHandler<InputElement>((x, e) => x.OnPointerCaptureLost(e));
PointerWheelChangedEvent.AddClassHandler<InputElement>((x, e) => x.OnPointerWheelChanged(e)); PointerWheelChangedEvent.AddClassHandler<InputElement>((x, e) => x.OnPointerWheelChanged(e));
}
PseudoClass<InputElement, bool>(IsEffectivelyEnabledProperty, x => !x, ":disabled"); public InputElement()
PseudoClass<InputElement>(IsFocusedProperty, ":focus"); {
PseudoClass<InputElement>(IsPointerOverProperty, ":pointerover"); UpdatePseudoClasses(IsFocused, IsPointerOver);
} }
/// <summary> /// <summary>
@ -372,7 +375,11 @@ namespace Avalonia.Input
public bool IsEffectivelyEnabled public bool IsEffectivelyEnabled
{ {
get => _isEffectivelyEnabled; get => _isEffectivelyEnabled;
private set => SetAndRaise(IsEffectivelyEnabledProperty, ref _isEffectivelyEnabled, value); private set
{
SetAndRaise(IsEffectivelyEnabledProperty, ref _isEffectivelyEnabled, value);
PseudoClasses.Set(":disabled", !value);
}
} }
public List<KeyBinding> KeyBindings { get; } = new List<KeyBinding>(); public List<KeyBinding> KeyBindings { get; } = new List<KeyBinding>();
@ -522,6 +529,20 @@ namespace Avalonia.Input
{ {
} }
protected override void OnPropertyChanged<T>(AvaloniaProperty<T> property, Optional<T> oldValue, BindingValue<T> newValue, BindingPriority priority)
{
base.OnPropertyChanged(property, oldValue, newValue, priority);
if (property == IsFocusedProperty)
{
UpdatePseudoClasses(newValue.GetValueOrDefault<bool>(), null);
}
else if (property == IsPointerOverProperty)
{
UpdatePseudoClasses(null, newValue.GetValueOrDefault<bool>());
}
}
/// <summary> /// <summary>
/// Updates the <see cref="IsEffectivelyEnabled"/> property value according to the parent /// Updates the <see cref="IsEffectivelyEnabled"/> property value according to the parent
/// control's enabled state and <see cref="IsEnabledCore"/>. /// control's enabled state and <see cref="IsEnabledCore"/>.
@ -578,5 +599,18 @@ namespace Avalonia.Input
child?.UpdateIsEffectivelyEnabled(this); child?.UpdateIsEffectivelyEnabled(this);
} }
} }
private void UpdatePseudoClasses(bool? isFocused, bool? isPointerOver)
{
if (isFocused.HasValue)
{
PseudoClasses.Set(":focus", isFocused.Value);
}
if (isPointerOver.HasValue)
{
PseudoClasses.Set(":pointerover", isPointerOver.Value);
}
}
} }
} }

2
src/Avalonia.Input/KeyboardNavigationHandler.cs

@ -124,7 +124,7 @@ namespace Avalonia.Input
if (current != null && e.Key == Key.Tab) if (current != null && e.Key == Key.Tab)
{ {
var direction = (e.Modifiers & InputModifiers.Shift) == 0 ? var direction = (e.KeyModifiers & KeyModifiers.Shift) == 0 ?
NavigationDirection.Next : NavigationDirection.Previous; NavigationDirection.Next : NavigationDirection.Previous;
Move(current, direction, e.Modifiers); Move(current, direction, e.Modifiers);
e.Handled = true; e.Handled = true;

3
src/Avalonia.Interactivity/RoutedEvent.cs

@ -3,7 +3,6 @@
using System; using System;
using System.Reactive.Subjects; using System.Reactive.Subjects;
using System.Reflection;
namespace Avalonia.Interactivity namespace Avalonia.Interactivity
{ {
@ -29,7 +28,7 @@ namespace Avalonia.Interactivity
Contract.Requires<ArgumentNullException>(name != null); Contract.Requires<ArgumentNullException>(name != null);
Contract.Requires<ArgumentNullException>(eventArgsType != null); Contract.Requires<ArgumentNullException>(eventArgsType != null);
Contract.Requires<ArgumentNullException>(ownerType != null); Contract.Requires<ArgumentNullException>(ownerType != null);
Contract.Requires<InvalidCastException>(typeof(RoutedEventArgs).GetTypeInfo().IsAssignableFrom(eventArgsType.GetTypeInfo())); Contract.Requires<InvalidCastException>(typeof(RoutedEventArgs).IsAssignableFrom(eventArgsType));
EventArgsType = eventArgsType; EventArgsType = eventArgsType;
Name = name; Name = name;

12
src/Avalonia.Layout/LayoutManager.cs

@ -132,8 +132,16 @@ namespace Avalonia.Layout
/// <inheritdoc/> /// <inheritdoc/>
public void ExecuteInitialLayoutPass(ILayoutRoot root) public void ExecuteInitialLayoutPass(ILayoutRoot root)
{ {
Measure(root); try
Arrange(root); {
_running = true;
Measure(root);
Arrange(root);
}
finally
{
_running = false;
}
// Running the initial layout pass may have caused some control to be invalidated // Running the initial layout pass may have caused some control to be invalidated
// so run a full layout pass now (this usually due to scrollbars; its not known // so run a full layout pass now (this usually due to scrollbars; its not known

42
src/Avalonia.Native/AvaloniaNativeDeferredRendererLock.cs

@ -1,42 +0,0 @@
// Copyright (c) The Avalonia Project. All rights reserved.
// Licensed under the MIT license. See licence.md file in the project root for full license information.
using System;
using System.Threading;
using Avalonia.Native.Interop;
using Avalonia.Rendering;
namespace Avalonia.Native
{
public class AvaloniaNativeDeferredRendererLock : IDeferredRendererLock
{
private readonly IAvnWindowBase _window;
public AvaloniaNativeDeferredRendererLock(IAvnWindowBase window)
{
_window = window;
}
public IDisposable TryLock()
{
if (_window.TryLock())
return new UnlockDisposable(_window);
return null;
}
private sealed class UnlockDisposable : IDisposable
{
private IAvnWindowBase _window;
public UnlockDisposable(IAvnWindowBase window)
{
_window = window;
}
public void Dispose()
{
Interlocked.Exchange(ref _window, null)?.Unlock();
}
}
}
}

10
src/Avalonia.Native/AvaloniaNativePlatform.cs

@ -2,6 +2,7 @@
// Licensed under the MIT license. See licence.md file in the project root for full license information. // Licensed under the MIT license. See licence.md file in the project root for full license information.
using System; using System;
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
using System.Security.Cryptography;
using Avalonia.Controls.Platform; using Avalonia.Controls.Platform;
using Avalonia.Input; using Avalonia.Input;
using Avalonia.Input.Platform; using Avalonia.Input.Platform;
@ -17,6 +18,7 @@ namespace Avalonia.Native
{ {
private readonly IAvaloniaNativeFactory _factory; private readonly IAvaloniaNativeFactory _factory;
private AvaloniaNativePlatformOptions _options; private AvaloniaNativePlatformOptions _options;
private GlPlatformFeature _glFeature;
[DllImport("libAvaloniaNative")] [DllImport("libAvaloniaNative")]
static extern IntPtr CreateAvaloniaNative(); static extern IntPtr CreateAvaloniaNative();
@ -87,7 +89,7 @@ namespace Avalonia.Native
_factory.MacOptions.ShowInDock = macOpts?.ShowInDock != false ? 1 : 0; _factory.MacOptions.ShowInDock = macOpts?.ShowInDock != false ? 1 : 0;
} }
AvaloniaLocator.CurrentMutable AvaloniaLocator.CurrentMutable
.Bind<IPlatformThreadingInterface>() .Bind<IPlatformThreadingInterface>()
.ToConstant(new PlatformThreadingInterface(_factory.CreatePlatformThreadingInterface())) .ToConstant(new PlatformThreadingInterface(_factory.CreatePlatformThreadingInterface()))
@ -100,14 +102,16 @@ namespace Avalonia.Native
.Bind<IRenderLoop>().ToConstant(new RenderLoop()) .Bind<IRenderLoop>().ToConstant(new RenderLoop())
.Bind<IRenderTimer>().ToConstant(new DefaultRenderTimer(60)) .Bind<IRenderTimer>().ToConstant(new DefaultRenderTimer(60))
.Bind<ISystemDialogImpl>().ToConstant(new SystemDialogs(_factory.CreateSystemDialogs())) .Bind<ISystemDialogImpl>().ToConstant(new SystemDialogs(_factory.CreateSystemDialogs()))
.Bind<IWindowingPlatformGlFeature>().ToConstant(new GlPlatformFeature(_factory.ObtainGlFeature()))
.Bind<PlatformHotkeyConfiguration>().ToConstant(new PlatformHotkeyConfiguration(KeyModifiers.Meta)) .Bind<PlatformHotkeyConfiguration>().ToConstant(new PlatformHotkeyConfiguration(KeyModifiers.Meta))
.Bind<IMountedVolumeInfoProvider>().ToConstant(new MacOSMountedVolumeInfoProvider()); .Bind<IMountedVolumeInfoProvider>().ToConstant(new MacOSMountedVolumeInfoProvider());
if (_options.UseGpu)
AvaloniaLocator.CurrentMutable.Bind<IWindowingPlatformGlFeature>()
.ToConstant(_glFeature = new GlPlatformFeature(_factory.ObtainGlDisplay()));
} }
public IWindowImpl CreateWindow() public IWindowImpl CreateWindow()
{ {
return new WindowImpl(_factory, _options); return new WindowImpl(_factory, _options, _glFeature);
} }
public IEmbeddableWindowImpl CreateEmbeddableWindow() public IEmbeddableWindowImpl CreateEmbeddableWindow()

2
src/Avalonia.Native/DeferredFramebuffer.cs

@ -21,7 +21,7 @@ namespace Avalonia.Native
Size = new PixelSize(width, height); Size = new PixelSize(width, height);
RowBytes = width * 4; RowBytes = width * 4;
Dpi = dpi; Dpi = dpi;
Format = PixelFormat.Rgba8888; Format = PixelFormat.Bgra8888;
} }
public IntPtr Address { get; set; } public IntPtr Address { get; set; }

31
src/Avalonia.Native/GlPlatformFeature.cs

@ -8,24 +8,31 @@ namespace Avalonia.Native
{ {
class GlPlatformFeature : IWindowingPlatformGlFeature class GlPlatformFeature : IWindowingPlatformGlFeature
{ {
public GlPlatformFeature(IAvnGlDisplay display)
public GlPlatformFeature(IAvnGlFeature feature)
{ {
Display = new GlDisplay(feature.ObtainDisplay()); var immediate = display.CreateContext(null);
ImmediateContext = new GlContext(Display, feature.ObtainImmediateContext()); var deferred = display.CreateContext(immediate);
GlDisplay = new GlDisplay(display, immediate.SampleCount, immediate.StencilSize);
ImmediateContext = new GlContext(Display, immediate);
DeferredContext = new GlContext(Display, deferred);
} }
public IGlContext ImmediateContext { get; } public IGlContext ImmediateContext { get; }
public GlDisplay Display { get; } internal GlContext DeferredContext { get; }
internal GlDisplay GlDisplay;
public GlDisplay Display => GlDisplay;
} }
class GlDisplay : IGlDisplay class GlDisplay : IGlDisplay
{ {
private readonly IAvnGlDisplay _display; private readonly IAvnGlDisplay _display;
public GlDisplay(IAvnGlDisplay display) public GlDisplay(IAvnGlDisplay display, int sampleCount, int stencilSize)
{ {
_display = display; _display = display;
SampleCount = sampleCount;
StencilSize = stencilSize;
GlInterface = new GlInterface((name, optional) => GlInterface = new GlInterface((name, optional) =>
{ {
var rv = _display.GetProcAddress(name); var rv = _display.GetProcAddress(name);
@ -39,11 +46,11 @@ namespace Avalonia.Native
public GlInterface GlInterface { get; } public GlInterface GlInterface { get; }
public int SampleCount => _display.GetSampleCount(); public int SampleCount { get; }
public int StencilSize => _display.GetStencilSize(); public int StencilSize { get; }
public void ClearContext() => _display.ClearContext(); public void ClearContext() => _display.LegacyClearCurrentContext();
} }
class GlContext : IGlContext class GlContext : IGlContext
@ -60,7 +67,7 @@ namespace Avalonia.Native
public void MakeCurrent() public void MakeCurrent()
{ {
Context.MakeCurrent(); Context.LegacyMakeCurrent();
} }
} }
@ -109,6 +116,9 @@ namespace Avalonia.Native
public double Scaling => _session.GetScaling(); public double Scaling => _session.GetScaling();
public bool IsYFlipped => true;
public void Dispose() public void Dispose()
{ {
_session?.Dispose(); _session?.Dispose();
@ -128,5 +138,6 @@ namespace Avalonia.Native
{ {
return new GlPlatformSurfaceRenderTarget(_window.CreateGlRenderTarget()); return new GlPlatformSurfaceRenderTarget(_window.CreateGlRenderTarget());
} }
} }
} }

1
src/Avalonia.Native/PlatformThreadingInterface.cs

@ -60,6 +60,7 @@ namespace Avalonia.Native
public void RunLoop(CancellationToken cancellationToken) public void RunLoop(CancellationToken cancellationToken)
{ {
_exceptionDispatchInfo?.Throw();
var l = new object(); var l = new object();
_exceptionCancellationSource = new CancellationTokenSource(); _exceptionCancellationSource = new CancellationTokenSource();

12
src/Avalonia.Native/PopupImpl.cs

@ -8,19 +8,23 @@ using Avalonia.Platform;
namespace Avalonia.Native namespace Avalonia.Native
{ {
public class PopupImpl : WindowBaseImpl, IPopupImpl class PopupImpl : WindowBaseImpl, IPopupImpl
{ {
private readonly IAvaloniaNativeFactory _factory; private readonly IAvaloniaNativeFactory _factory;
private readonly AvaloniaNativePlatformOptions _opts; private readonly AvaloniaNativePlatformOptions _opts;
private readonly GlPlatformFeature _glFeature;
public PopupImpl(IAvaloniaNativeFactory factory, public PopupImpl(IAvaloniaNativeFactory factory,
AvaloniaNativePlatformOptions opts, AvaloniaNativePlatformOptions opts,
IWindowBaseImpl parent) : base(opts) GlPlatformFeature glFeature,
IWindowBaseImpl parent) : base(opts, glFeature)
{ {
_factory = factory; _factory = factory;
_opts = opts; _opts = opts;
_glFeature = glFeature;
using (var e = new PopupEvents(this)) using (var e = new PopupEvents(this))
{ {
Init(factory.CreatePopup(e), factory.CreateScreens()); Init(factory.CreatePopup(e, _opts.UseGpu ? glFeature?.DeferredContext.Context : null), factory.CreateScreens());
} }
PopupPositioner = new ManagedPopupPositioner(new OsxManagedPopupPositionerPopupImplHelper(parent, MoveResize)); PopupPositioner = new ManagedPopupPositioner(new OsxManagedPopupPositionerPopupImplHelper(parent, MoveResize));
} }
@ -51,7 +55,7 @@ namespace Avalonia.Native
} }
} }
public override IPopupImpl CreatePopup() => new PopupImpl(_factory, _opts, this); public override IPopupImpl CreatePopup() => new PopupImpl(_factory, _opts, _glFeature, this);
public IPopupPositioner PopupPositioner { get; } public IPopupPositioner PopupPositioner { get; }
} }
} }

10
src/Avalonia.Native/WindowImpl.cs

@ -14,14 +14,18 @@ namespace Avalonia.Native
{ {
private readonly IAvaloniaNativeFactory _factory; private readonly IAvaloniaNativeFactory _factory;
private readonly AvaloniaNativePlatformOptions _opts; private readonly AvaloniaNativePlatformOptions _opts;
private readonly GlPlatformFeature _glFeature;
IAvnWindow _native; IAvnWindow _native;
public WindowImpl(IAvaloniaNativeFactory factory, AvaloniaNativePlatformOptions opts) : base(opts) internal WindowImpl(IAvaloniaNativeFactory factory, AvaloniaNativePlatformOptions opts,
GlPlatformFeature glFeature) : base(opts, glFeature)
{ {
_factory = factory; _factory = factory;
_opts = opts; _opts = opts;
_glFeature = glFeature;
using (var e = new WindowEvents(this)) using (var e = new WindowEvents(this))
{ {
Init(_native = factory.CreateWindow(e), factory.CreateScreens()); Init(_native = factory.CreateWindow(e,
_opts.UseGpu ? glFeature?.DeferredContext.Context : null), factory.CreateScreens());
} }
NativeMenuExporter = new AvaloniaNativeMenuExporter(_native, factory); NativeMenuExporter = new AvaloniaNativeMenuExporter(_native, factory);
@ -113,6 +117,6 @@ namespace Avalonia.Native
public void Move(PixelPoint point) => Position = point; public void Move(PixelPoint point) => Position = point;
public override IPopupImpl CreatePopup() => public override IPopupImpl CreatePopup() =>
_opts.OverlayPopups ? null : new PopupImpl(_factory, _opts, this); _opts.OverlayPopups ? null : new PopupImpl(_factory, _opts, _glFeature, this);
} }
} }

74
src/Avalonia.Native/WindowImplBase.cs

@ -60,9 +60,9 @@ namespace Avalonia.Native
private double _savedScaling; private double _savedScaling;
private GlPlatformSurface _glSurface; private GlPlatformSurface _glSurface;
public WindowBaseImpl(AvaloniaNativePlatformOptions opts) internal WindowBaseImpl(AvaloniaNativePlatformOptions opts, GlPlatformFeature glFeature)
{ {
_gpu = opts.UseGpu; _gpu = opts.UseGpu && glFeature != null;
_deferredRendering = opts.UseDeferredRendering; _deferredRendering = opts.UseDeferredRendering;
_keyboard = AvaloniaLocator.Current.GetService<IKeyboardDevice>(); _keyboard = AvaloniaLocator.Current.GetService<IKeyboardDevice>();
@ -75,8 +75,8 @@ namespace Avalonia.Native
_native = window; _native = window;
Handle = new MacOSTopLevelWindowHandle(window); Handle = new MacOSTopLevelWindowHandle(window);
if (_gpu)
_glSurface = new GlPlatformSurface(window); _glSurface = new GlPlatformSurface(window);
Screen = new ScreenImpl(screens); Screen = new ScreenImpl(screens);
_savedLogicalSize = ClientSize; _savedLogicalSize = ClientSize;
_savedScaling = Scaling; _savedScaling = Scaling;
@ -103,25 +103,20 @@ namespace Avalonia.Native
public ILockedFramebuffer Lock() public ILockedFramebuffer Lock()
{ {
if(_deferredRendering) var w = _savedLogicalSize.Width * _savedScaling;
var h = _savedLogicalSize.Height * _savedScaling;
var dpi = _savedScaling * 96;
return new DeferredFramebuffer(cb =>
{ {
var w = _savedLogicalSize.Width * _savedScaling; lock (_syncRoot)
var h = _savedLogicalSize.Height * _savedScaling;
var dpi = _savedScaling * 96;
return new DeferredFramebuffer(cb =>
{ {
lock (_syncRoot) if (_native == null)
{ return false;
if (_native == null) cb(_native);
return false; _lastRenderedLogicalSize = _savedLogicalSize;
cb(_native); return true;
_lastRenderedLogicalSize = _savedLogicalSize; }
return true; }, (int)w, (int)h, new Vector(dpi, dpi));
}
}, (int)w, (int)h, new Vector(dpi, dpi));
}
return new FramebufferWrapper(_native.GetSoftwareFramebuffer());
} }
public Action<Rect> Paint { get; set; } public Action<Rect> Paint { get; set; }
@ -130,28 +125,6 @@ namespace Avalonia.Native
public IMouseDevice MouseDevice => _mouse; public IMouseDevice MouseDevice => _mouse;
public abstract IPopupImpl CreatePopup(); public abstract IPopupImpl CreatePopup();
class FramebufferWrapper : ILockedFramebuffer
{
public FramebufferWrapper(AvnFramebuffer fb)
{
Address = fb.Data;
Size = new PixelSize(fb.Width, fb.Height);
RowBytes = fb.Stride;
Dpi = new Vector(fb.Dpi.X, fb.Dpi.Y);
Format = (PixelFormat)fb.PixelFormat;
}
public IntPtr Address { get; set; }
public PixelSize Size { get; set; }
public int RowBytes {get;set;}
public Vector Dpi { get; set; }
public PixelFormat Format { get; }
public void Dispose()
{
// Do nothing
}
}
protected class WindowBaseEvents : CallbackBase, IAvnWindowBaseEvents protected class WindowBaseEvents : CallbackBase, IAvnWindowBaseEvents
{ {
private readonly WindowBaseImpl _parent; private readonly WindowBaseImpl _parent;
@ -189,9 +162,12 @@ namespace Avalonia.Native
void IAvnWindowBaseEvents.Resized(AvnSize size) void IAvnWindowBaseEvents.Resized(AvnSize size)
{ {
var s = new Size(size.Width, size.Height); if (_parent._native != null)
_parent._savedLogicalSize = s; {
_parent.Resized?.Invoke(s); var s = new Size(size.Width, size.Height);
_parent._savedLogicalSize = s;
_parent.Resized?.Invoke(s);
}
} }
void IAvnWindowBaseEvents.PositionChanged(AvnPoint position) void IAvnWindowBaseEvents.PositionChanged(AvnPoint position)
@ -278,9 +254,7 @@ namespace Avalonia.Native
public IRenderer CreateRenderer(IRenderRoot root) public IRenderer CreateRenderer(IRenderRoot root)
{ {
if (_deferredRendering) if (_deferredRendering)
return new DeferredRenderer(root, AvaloniaLocator.Current.GetService<IRenderLoop>(), return new DeferredRenderer(root, AvaloniaLocator.Current.GetService<IRenderLoop>());
rendererLock:
_gpu ? new AvaloniaNativeDeferredRendererLock(_native) : null);
return new ImmediateRenderer(root); return new ImmediateRenderer(root);
} }
@ -346,7 +320,7 @@ namespace Avalonia.Native
_native.SetTopMost(value); _native.SetTopMost(value);
} }
public double Scaling => _native.GetScaling(); public double Scaling => _native?.GetScaling() ?? 1;
public Action Deactivated { get; set; } public Action Deactivated { get; set; }
public Action Activated { get; set; } public Action Activated { get; set; }

1
src/Avalonia.OpenGL/EglGlPlatformSurface.cs

@ -107,6 +107,7 @@ namespace Avalonia.OpenGL
public IGlDisplay Display => _context.Display; public IGlDisplay Display => _context.Display;
public PixelSize Size => _info.Size; public PixelSize Size => _info.Size;
public double Scaling => _info.Scaling; public double Scaling => _info.Scaling;
public bool IsYFlipped { get; }
} }
} }
} }

1
src/Avalonia.OpenGL/IGlPlatformSurfaceRenderingSession.cs

@ -7,5 +7,6 @@ namespace Avalonia.OpenGL
IGlDisplay Display { get; } IGlDisplay Display { get; }
PixelSize Size { get; } PixelSize Size { get; }
double Scaling { get; } double Scaling { get; }
bool IsYFlipped { get; }
} }
} }

4
src/Avalonia.ReactiveUI/AvaloniaActivationForViewFetcher.cs

@ -2,9 +2,7 @@
// Licensed under the MIT license. See licence.md file in the project root for full license information. // Licensed under the MIT license. See licence.md file in the project root for full license information.
using System; using System;
using System.Reflection;
using System.Reactive.Linq; using System.Reactive.Linq;
using Avalonia;
using Avalonia.VisualTree; using Avalonia.VisualTree;
using Avalonia.Controls; using Avalonia.Controls;
using ReactiveUI; using ReactiveUI;
@ -21,7 +19,7 @@ namespace Avalonia.ReactiveUI
/// </summary> /// </summary>
public int GetAffinityForView(Type view) public int GetAffinityForView(Type view)
{ {
return typeof(IVisual).GetTypeInfo().IsAssignableFrom(view.GetTypeInfo()) ? 10 : 0; return typeof(IVisual).IsAssignableFrom(view) ? 10 : 0;
} }
/// <summary> /// <summary>

28
src/Avalonia.Styling/Controls/PseudoClassesExtensions.cs

@ -0,0 +1,28 @@
using System;
namespace Avalonia.Controls
{
public static class PseudolassesExtensions
{
/// <summary>
/// Adds or removes a pseudoclass depending on a boolean value.
/// </summary>
/// <param name="classes">The pseudoclasses collection.</param>
/// <param name="name">The name of the pseudoclass to set.</param>
/// <param name="value">True to add the pseudoclass or false to remove.</param>
public static void Set(this IPseudoClasses classes, string name, bool value)
{
Contract.Requires<ArgumentNullException>(classes != null);
if (value)
{
classes.Add(name);
}
else
{
classes.Remove(name);
}
}
}
}

7
src/Avalonia.Styling/LogicalTree/ControlLocator.cs

@ -3,9 +3,6 @@
using System; using System;
using System.Linq; using System.Linq;
using System.Reactive.Linq;
using System.Reflection;
using Avalonia.Controls;
using Avalonia.Reactive; using Avalonia.Reactive;
namespace Avalonia.LogicalTree namespace Avalonia.LogicalTree
@ -25,7 +22,7 @@ namespace Avalonia.LogicalTree
private readonly ILogical _relativeTo; private readonly ILogical _relativeTo;
private readonly int _ancestorLevel; private readonly int _ancestorLevel;
private readonly Type _ancestorType; private readonly Type _ancestorType;
ILogical _value; private ILogical _value;
public ControlTracker(ILogical relativeTo, int ancestorLevel, Type ancestorType) public ControlTracker(ILogical relativeTo, int ancestorLevel, Type ancestorType)
{ {
@ -69,7 +66,7 @@ namespace Avalonia.LogicalTree
private void Update() private void Update()
{ {
_value = _relativeTo.GetLogicalAncestors() _value = _relativeTo.GetLogicalAncestors()
.Where(x => _ancestorType?.GetTypeInfo().IsAssignableFrom(x.GetType().GetTypeInfo()) ?? true) .Where(x => _ancestorType?.IsAssignableFrom(x.GetType()) ?? true)
.ElementAtOrDefault(_ancestorLevel); .ElementAtOrDefault(_ancestorLevel);
} }
} }

9
src/Avalonia.Styling/LogicalTree/ILogicalRoot.cs

@ -0,0 +1,9 @@
namespace Avalonia.LogicalTree
{
/// <summary>
/// Represents a root of a logical tree.
/// </summary>
public interface ILogicalRoot : ILogical
{
}
}

5
src/Avalonia.Styling/LogicalTree/LogicalTreeAttachmentEventArgs.cs

@ -2,7 +2,6 @@
// Licensed under the MIT license. See licence.md file in the project root for full license information. // Licensed under the MIT license. See licence.md file in the project root for full license information.
using System; using System;
using Avalonia.Styling;
namespace Avalonia.LogicalTree namespace Avalonia.LogicalTree
{ {
@ -19,7 +18,7 @@ namespace Avalonia.LogicalTree
/// <param name="source">The control being attached/detached.</param> /// <param name="source">The control being attached/detached.</param>
/// <param name="parent">The <see cref="Parent"/>.</param> /// <param name="parent">The <see cref="Parent"/>.</param>
public LogicalTreeAttachmentEventArgs( public LogicalTreeAttachmentEventArgs(
IStyleRoot root, ILogicalRoot root,
ILogical source, ILogical source,
ILogical parent) ILogical parent)
{ {
@ -34,7 +33,7 @@ namespace Avalonia.LogicalTree
/// <summary> /// <summary>
/// Gets the root of the logical tree that the control is being attached to or detached from. /// Gets the root of the logical tree that the control is being attached to or detached from.
/// </summary> /// </summary>
public IStyleRoot Root { get; } public ILogicalRoot Root { get; }
/// <summary> /// <summary>
/// Gets the control that was attached or detached from the logical tree. /// Gets the control that was attached or detached from the logical tree.

134
src/Avalonia.Styling/StyledElement.cs

@ -59,7 +59,7 @@ namespace Avalonia
private int _initCount; private int _initCount;
private string _name; private string _name;
private readonly Classes _classes = new Classes(); private readonly Classes _classes = new Classes();
private IStyleRoot _styleRoot; private ILogicalRoot _logicalRoot;
private IAvaloniaList<ILogical> _logicalChildren; private IAvaloniaList<ILogical> _logicalChildren;
private IResourceDictionary _resources; private IResourceDictionary _resources;
private Styles _styles; private Styles _styles;
@ -81,7 +81,7 @@ namespace Avalonia
/// </summary> /// </summary>
public StyledElement() public StyledElement()
{ {
_styleRoot = this as IStyleRoot; _logicalRoot = this as ILogicalRoot;
} }
/// <summary> /// <summary>
@ -307,7 +307,7 @@ namespace Avalonia
/// <summary> /// <summary>
/// Gets a value indicating whether the element is attached to a rooted logical tree. /// Gets a value indicating whether the element is attached to a rooted logical tree.
/// </summary> /// </summary>
bool ILogical.IsAttachedToLogicalTree => _styleRoot != null; bool ILogical.IsAttachedToLogicalTree => _logicalRoot != null;
/// <summary> /// <summary>
/// Gets the styled element's logical parent. /// Gets the styled element's logical parent.
@ -367,7 +367,7 @@ namespace Avalonia
throw new InvalidOperationException("BeginInit was not called."); throw new InvalidOperationException("BeginInit was not called.");
} }
if (--_initCount == 0 && _styleRoot != null) if (--_initCount == 0 && _logicalRoot != null)
{ {
InitializeStylesIfNeeded(); InitializeStylesIfNeeded();
@ -442,9 +442,9 @@ namespace Avalonia
Parent = (IStyledElement)parent; Parent = (IStyledElement)parent;
if (_styleRoot != null) if (_logicalRoot != null)
{ {
var e = new LogicalTreeAttachmentEventArgs(_styleRoot, this, old); var e = new LogicalTreeAttachmentEventArgs(_logicalRoot, this, old);
OnDetachedFromLogicalTreeCore(e); OnDetachedFromLogicalTreeCore(e);
} }
@ -458,9 +458,9 @@ namespace Avalonia
} }
((ILogical)this).NotifyResourcesChanged(new ResourcesChangedEventArgs()); ((ILogical)this).NotifyResourcesChanged(new ResourcesChangedEventArgs());
if (Parent is IStyleRoot || Parent?.IsAttachedToLogicalTree == true || this is IStyleRoot) if (Parent is ILogicalRoot || Parent?.IsAttachedToLogicalTree == true || this is ILogicalRoot)
{ {
var newRoot = FindStyleRoot(this); var newRoot = FindLogicalRoot(this);
if (newRoot == null) if (newRoot == null)
{ {
@ -488,83 +488,6 @@ namespace Avalonia
InheritanceParent = parent; InheritanceParent = parent;
} }
/// <summary>
/// Adds a pseudo-class to be set when a property is true.
/// </summary>
/// <param name="property">The property.</param>
/// <param name="className">The pseudo-class.</param>
[Obsolete("Use PseudoClass<TOwner> and specify the control type.")]
protected static void PseudoClass(AvaloniaProperty<bool> property, string className)
{
PseudoClass<StyledElement>(property, className);
}
/// <summary>
/// Adds a pseudo-class to be set when a property is true.
/// </summary>
/// <typeparam name="TOwner">The type to apply the pseudo-class to.</typeparam>
/// <param name="property">The property.</param>
/// <param name="className">The pseudo-class.</param>
protected static void PseudoClass<TOwner>(AvaloniaProperty<bool> property, string className)
where TOwner : class, IStyledElement
{
PseudoClass<TOwner, bool>(property, x => x, className);
}
/// <summary>
/// Adds a pseudo-class to be set when a property equals a certain value.
/// </summary>
/// <typeparam name="TProperty">The type of the property.</typeparam>
/// <param name="property">The property.</param>
/// <param name="selector">Returns a boolean value based on the property value.</param>
/// <param name="className">The pseudo-class.</param>
[Obsolete("Use PseudoClass<TOwner, TProperty> and specify the control type.")]
protected static void PseudoClass<TProperty>(
AvaloniaProperty<TProperty> property,
Func<TProperty, bool> selector,
string className)
{
PseudoClass<StyledElement, TProperty>(property, selector, className);
}
/// <summary>
/// Adds a pseudo-class to be set when a property equals a certain value.
/// </summary>
/// <typeparam name="TProperty">The type of the property.</typeparam>
/// <typeparam name="TOwner">The type to apply the pseudo-class to.</typeparam>
/// <param name="property">The property.</param>
/// <param name="selector">Returns a boolean value based on the property value.</param>
/// <param name="className">The pseudo-class.</param>
protected static void PseudoClass<TOwner, TProperty>(
AvaloniaProperty<TProperty> property,
Func<TProperty, bool> selector,
string className)
where TOwner : class, IStyledElement
{
Contract.Requires<ArgumentNullException>(property != null);
Contract.Requires<ArgumentNullException>(selector != null);
Contract.Requires<ArgumentNullException>(className != null);
if (string.IsNullOrWhiteSpace(className))
{
throw new ArgumentException("Cannot supply an empty className.");
}
property.Changed.Merge(property.Initialized)
.Where(e => e.Sender is TOwner)
.Subscribe(e =>
{
if (selector((TProperty)e.NewValue))
{
((StyledElement)e.Sender).PseudoClasses.Add(className);
}
else
{
((StyledElement)e.Sender).PseudoClasses.Remove(className);
}
});
}
protected virtual void LogicalChildrenCollectionChanged(object sender, NotifyCollectionChangedEventArgs e) protected virtual void LogicalChildrenCollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
{ {
switch (e.Action) switch (e.Action)
@ -650,9 +573,12 @@ namespace Avalonia
element._dataContextUpdating = true; element._dataContextUpdating = true;
element.OnDataContextBeginUpdate(); element.OnDataContextBeginUpdate();
foreach (var child in element.LogicalChildren) var logicalChildren = element.LogicalChildren;
var logicalChildrenCount = logicalChildren.Count;
for (var i = 0; i < logicalChildrenCount; i++)
{ {
if (child is StyledElement s && if (element.LogicalChildren[i] is StyledElement s &&
s.InheritanceParent == element && s.InheritanceParent == element &&
!s.IsSet(DataContextProperty)) !s.IsSet(DataContextProperty))
{ {
@ -671,11 +597,11 @@ namespace Avalonia
} }
} }
private static IStyleRoot FindStyleRoot(IStyleHost e) private static ILogicalRoot FindLogicalRoot(IStyleHost e)
{ {
while (e != null) while (e != null)
{ {
if (e is IStyleRoot root) if (e is ILogicalRoot root)
{ {
return root; return root;
} }
@ -701,7 +627,7 @@ namespace Avalonia
private void OnAttachedToLogicalTreeCore(LogicalTreeAttachmentEventArgs e) private void OnAttachedToLogicalTreeCore(LogicalTreeAttachmentEventArgs e)
{ {
if (this.GetLogicalParent() == null && !(this is IStyleRoot)) if (this.GetLogicalParent() == null && !(this is ILogicalRoot))
{ {
throw new InvalidOperationException( throw new InvalidOperationException(
$"AttachedToLogicalTreeCore called for '{GetType().Name}' but control has no logical parent."); $"AttachedToLogicalTreeCore called for '{GetType().Name}' but control has no logical parent.");
@ -713,9 +639,9 @@ namespace Avalonia
// - ListBox makes ListBoxItem a logical child // - ListBox makes ListBoxItem a logical child
// - ListBox template gets applied; making its Panel get attached to logical tree // - ListBox template gets applied; making its Panel get attached to logical tree
// - That AttachedToLogicalTree signal travels down to the ListBoxItem // - That AttachedToLogicalTree signal travels down to the ListBoxItem
if (_styleRoot == null) if (_logicalRoot == null)
{ {
_styleRoot = e.Root; _logicalRoot = e.Root;
InitializeStylesIfNeeded(true); InitializeStylesIfNeeded(true);
@ -723,24 +649,36 @@ namespace Avalonia
AttachedToLogicalTree?.Invoke(this, e); AttachedToLogicalTree?.Invoke(this, e);
} }
foreach (var child in LogicalChildren.OfType<StyledElement>()) var logicalChildren = LogicalChildren;
var logicalChildrenCount = logicalChildren.Count;
for (var i = 0; i < logicalChildrenCount; i++)
{ {
child.OnAttachedToLogicalTreeCore(e); if (logicalChildren[i] is StyledElement child)
{
child.OnAttachedToLogicalTreeCore(e);
}
} }
} }
private void OnDetachedFromLogicalTreeCore(LogicalTreeAttachmentEventArgs e) private void OnDetachedFromLogicalTreeCore(LogicalTreeAttachmentEventArgs e)
{ {
if (_styleRoot != null) if (_logicalRoot != null)
{ {
_styleRoot = null; _logicalRoot = null;
_styleDetach.OnNext(this); _styleDetach.OnNext(this);
OnDetachedFromLogicalTree(e); OnDetachedFromLogicalTree(e);
DetachedFromLogicalTree?.Invoke(this, e); DetachedFromLogicalTree?.Invoke(this, e);
foreach (var child in LogicalChildren.OfType<StyledElement>()) var logicalChildren = LogicalChildren;
var logicalChildrenCount = logicalChildren.Count;
for (var i = 0; i < logicalChildrenCount; i++)
{ {
child.OnDetachedFromLogicalTreeCore(e); if (logicalChildren[i] is StyledElement child)
{
child.OnDetachedFromLogicalTreeCore(e);
}
} }
#if DEBUG #if DEBUG

2
src/Avalonia.Styling/Styling/IGlobalStyles.cs

@ -6,7 +6,7 @@ namespace Avalonia.Styling
/// <summary> /// <summary>
/// Defines the style host that provides styles global to the application. /// Defines the style host that provides styles global to the application.
/// </summary> /// </summary>
public interface IGlobalStyles : IStyleRoot public interface IGlobalStyles : IStyleHost
{ {
} }
} }

12
src/Avalonia.Styling/Styling/IStyleRoot.cs

@ -1,12 +0,0 @@
// Copyright (c) The Avalonia Project. All rights reserved.
// Licensed under the MIT license. See licence.md file in the project root for full license information.
namespace Avalonia.Styling
{
/// <summary>
/// Denotes the root <see cref="IStyleHost"/> in a tree.
/// </summary>
public interface IStyleRoot : IStyleHost
{
}
}

17
src/Avalonia.Styling/Styling/Selector.cs

@ -3,6 +3,8 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Diagnostics;
using Avalonia.Utilities;
namespace Avalonia.Styling namespace Avalonia.Styling
{ {
@ -41,7 +43,8 @@ namespace Avalonia.Styling
/// <returns>A <see cref="SelectorMatch"/>.</returns> /// <returns>A <see cref="SelectorMatch"/>.</returns>
public SelectorMatch Match(IStyleable control, bool subscribe = true) public SelectorMatch Match(IStyleable control, bool subscribe = true)
{ {
var inputs = new List<IObservable<bool>>(); ValueSingleOrList<IObservable<bool>> inputs = default;
var selector = this; var selector = this;
var alwaysThisType = true; var alwaysThisType = true;
var hitCombinator = false; var hitCombinator = false;
@ -66,19 +69,25 @@ namespace Avalonia.Styling
} }
else if (match.Result == SelectorMatchResult.Sometimes) else if (match.Result == SelectorMatchResult.Sometimes)
{ {
Debug.Assert(match.Activator != null);
inputs.Add(match.Activator); inputs.Add(match.Activator);
} }
selector = selector.MovePrevious(); selector = selector.MovePrevious();
} }
if (inputs.Count > 0) if (inputs.HasList)
{
return new SelectorMatch(StyleActivator.And(inputs.List));
}
else if (inputs.IsSingle)
{ {
return new SelectorMatch(StyleActivator.And(inputs)); return new SelectorMatch(inputs.Single);
} }
else else
{ {
return alwaysThisType && !hitCombinator ? return alwaysThisType && !hitCombinator ?
SelectorMatch.AlwaysThisType : SelectorMatch.AlwaysThisType :
SelectorMatch.AlwaysThisInstance; SelectorMatch.AlwaysThisInstance;
} }

21
src/Avalonia.Styling/Styling/Setter.cs

@ -3,9 +3,7 @@
using System; using System;
using System.Reactive.Disposables; using System.Reactive.Disposables;
using System.Reflection;
using Avalonia.Animation; using Avalonia.Animation;
using Avalonia.Controls;
using Avalonia.Data; using Avalonia.Data;
using Avalonia.Metadata; using Avalonia.Metadata;
using Avalonia.Reactive; using Avalonia.Reactive;
@ -80,8 +78,6 @@ namespace Avalonia.Styling
{ {
Contract.Requires<ArgumentNullException>(control != null); Contract.Requires<ArgumentNullException>(control != null);
var description = style?.ToString();
if (Property == null) if (Property == null)
{ {
throw new InvalidOperationException("Setter.Property must be set."); throw new InvalidOperationException("Setter.Property must be set.");
@ -92,14 +88,15 @@ namespace Avalonia.Styling
if (binding == null) if (binding == null)
{ {
var template = value as ITemplate; if (value is ITemplate template)
bool isPropertyOfTypeITemplate = typeof(ITemplate).GetTypeInfo()
.IsAssignableFrom(Property.PropertyType.GetTypeInfo());
if (template != null && !isPropertyOfTypeITemplate)
{ {
var materialized = template.Build(); bool isPropertyOfTypeITemplate = typeof(ITemplate).IsAssignableFrom(Property.PropertyType);
value = materialized;
if (!isPropertyOfTypeITemplate)
{
var materialized = template.Build();
value = materialized;
}
} }
if (activator == null) if (activator == null)
@ -108,6 +105,8 @@ namespace Avalonia.Styling
} }
else else
{ {
var description = style?.ToString();
var activated = new ActivatedValue(activator, value, description); var activated = new ActivatedValue(activator, value, description);
return control.Bind(Property, activated, BindingPriority.StyleTrigger); return control.Bind(Property, activated, BindingPriority.StyleTrigger);
} }

35
src/Avalonia.Styling/Styling/Style.cs

@ -116,13 +116,21 @@ namespace Avalonia.Styling
if (match.IsMatch) if (match.IsMatch)
{ {
var controlSubscriptions = GetSubscriptions(control); var controlSubscriptions = GetSubscriptions(control);
var subs = new CompositeDisposable(Setters.Count + Animations.Count);
if (control is Animatable animatable) var animatable = control as Animatable;
var setters = Setters;
var settersCount = setters.Count;
var animations = Animations;
var animationsCount = animations.Count;
var subs = new CompositeDisposable(settersCount + (animatable != null ? animationsCount : 0) + 1);
if (animatable != null)
{ {
foreach (var animation in Animations) for (var i = 0; i < animationsCount; i++)
{ {
var animation = animations[i];
var obsMatch = match.Activator; var obsMatch = match.Activator;
if (match.Result == SelectorMatchResult.AlwaysThisType || if (match.Result == SelectorMatchResult.AlwaysThisType ||
@ -133,17 +141,19 @@ namespace Avalonia.Styling
var sub = animation.Apply(animatable, null, obsMatch); var sub = animation.Apply(animatable, null, obsMatch);
subs.Add(sub); subs.Add(sub);
} }
} }
foreach (var setter in Setters) for (var i = 0; i < settersCount; i++)
{ {
var setter = setters[i];
var sub = setter.Apply(this, control, match.Activator); var sub = setter.Apply(this, control, match.Activator);
subs.Add(sub); subs.Add(sub);
} }
subs.Add(Disposable.Create((subs, Subscriptions) , state => state.Subscriptions.Remove(state.subs)));
controlSubscriptions.Add(subs); controlSubscriptions.Add(subs);
controlSubscriptions.Add(Disposable.Create(() => Subscriptions.Remove(subs)));
Subscriptions.Add(subs); Subscriptions.Add(subs);
} }
@ -151,18 +161,23 @@ namespace Avalonia.Styling
} }
else if (control == container) else if (control == container)
{ {
var setters = Setters;
var settersCount = setters.Count;
var controlSubscriptions = GetSubscriptions(control); var controlSubscriptions = GetSubscriptions(control);
var subs = new CompositeDisposable(Setters.Count); var subs = new CompositeDisposable(settersCount + 1);
foreach (var setter in Setters) for (var i = 0; i < settersCount; i++)
{ {
var setter = setters[i];
var sub = setter.Apply(this, control, null); var sub = setter.Apply(this, control, null);
subs.Add(sub); subs.Add(sub);
} }
subs.Add(Disposable.Create((subs, Subscriptions), state => state.Subscriptions.Remove(state.subs)));
controlSubscriptions.Add(subs); controlSubscriptions.Add(subs);
controlSubscriptions.Add(Disposable.Create(() => Subscriptions.Remove(subs)));
Subscriptions.Add(subs); Subscriptions.Add(subs);
return true; return true;
} }

4
src/Avalonia.Styling/Styling/Styles.cs

@ -239,8 +239,10 @@ namespace Avalonia.Styling
/// <inheritdoc/> /// <inheritdoc/>
public bool Remove(IStyle item) => _styles.Remove(item); public bool Remove(IStyle item) => _styles.Remove(item);
public AvaloniaList<IStyle>.Enumerator GetEnumerator() => _styles.GetEnumerator();
/// <inheritdoc/> /// <inheritdoc/>
public IEnumerator<IStyle> GetEnumerator() => _styles.GetEnumerator(); IEnumerator<IStyle> IEnumerable<IStyle>.GetEnumerator() => _styles.GetEnumerator();
/// <inheritdoc/> /// <inheritdoc/>
IEnumerator IEnumerable.GetEnumerator() => _styles.GetEnumerator(); IEnumerator IEnumerable.GetEnumerator() => _styles.GetEnumerator();

2
src/Avalonia.Styling/Styling/TypeNameAndClassSelector.cs

@ -114,7 +114,7 @@ namespace Avalonia.Styling
} }
else else
{ {
if (!TargetType.GetTypeInfo().IsAssignableFrom(controlType.GetTypeInfo())) if (!TargetType.IsAssignableFrom(controlType))
{ {
return SelectorMatch.NeverThisType; return SelectorMatch.NeverThisType;
} }

18
src/Avalonia.Themes.Default/ComboBox.xaml

@ -4,6 +4,7 @@
<Setter Property="BorderBrush" Value="{DynamicResource ThemeBorderMidBrush}"/> <Setter Property="BorderBrush" Value="{DynamicResource ThemeBorderMidBrush}"/>
<Setter Property="BorderThickness" Value="{DynamicResource ThemeBorderThickness}"/> <Setter Property="BorderThickness" Value="{DynamicResource ThemeBorderThickness}"/>
<Setter Property="Padding" Value="4"/> <Setter Property="Padding" Value="4"/>
<Setter Property="MinHeight" Value="20"/>
<Setter Property="Template"> <Setter Property="Template">
<ControlTemplate> <ControlTemplate>
<Border Name="border" <Border Name="border"
@ -40,14 +41,14 @@
StaysOpen="False"> StaysOpen="False">
<Border BorderBrush="{DynamicResource ThemeBorderMidBrush}" <Border BorderBrush="{DynamicResource ThemeBorderMidBrush}"
BorderThickness="1"> BorderThickness="1">
<ScrollViewer> <ScrollViewer>
<ItemsPresenter Name="PART_ItemsPresenter" <ItemsPresenter Name="PART_ItemsPresenter"
Items="{TemplateBinding Items}" Items="{TemplateBinding Items}"
ItemsPanel="{TemplateBinding ItemsPanel}" ItemsPanel="{TemplateBinding ItemsPanel}"
ItemTemplate="{TemplateBinding ItemTemplate}" ItemTemplate="{TemplateBinding ItemTemplate}"
VirtualizationMode="{TemplateBinding VirtualizationMode}" VirtualizationMode="{TemplateBinding VirtualizationMode}"
/> />
</ScrollViewer> </ScrollViewer>
</Border> </Border>
</Popup> </Popup>
</Grid> </Grid>
@ -58,4 +59,7 @@
<Style Selector="ComboBox:pointerover /template/ Border#border"> <Style Selector="ComboBox:pointerover /template/ Border#border">
<Setter Property="BorderBrush" Value="{DynamicResource ThemeBorderHighBrush}"/> <Setter Property="BorderBrush" Value="{DynamicResource ThemeBorderHighBrush}"/>
</Style> </Style>
<Style Selector="ComboBox:disabled /template/ Border#border">
<Setter Property="Opacity" Value="{DynamicResource ThemeDisabledOpacity}" />
</Style>
</Styles> </Styles>

13
src/Avalonia.Themes.Default/ItemsControl.xaml

@ -1,10 +1,15 @@
<Style xmlns="https://github.com/avaloniaui" Selector="ItemsControl"> <Style xmlns="https://github.com/avaloniaui" Selector="ItemsControl">
<Setter Property="Template"> <Setter Property="Template">
<ControlTemplate> <ControlTemplate>
<ItemsPresenter Name="PART_ItemsPresenter" <Border Background="{TemplateBinding Background}"
Items="{TemplateBinding Items}" BorderBrush="{TemplateBinding BorderBrush}"
ItemsPanel="{TemplateBinding ItemsPanel}" BorderThickness="{TemplateBinding BorderThickness}"
ItemTemplate="{TemplateBinding ItemTemplate}"/> Padding="{TemplateBinding Padding}">
<ItemsPresenter Name="PART_ItemsPresenter"
Items="{TemplateBinding Items}"
ItemsPanel="{TemplateBinding ItemsPanel}"
ItemTemplate="{TemplateBinding ItemTemplate}"/>
</Border>
</ControlTemplate> </ControlTemplate>
</Setter> </Setter>
</Style> </Style>

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

@ -1,4 +1,5 @@
<Style xmlns="https://github.com/avaloniaui" Selector="ListBox"> <Styles xmlns="https://github.com/avaloniaui" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<Style Selector="ListBox">
<Setter Property="Background" Value="{DynamicResource ThemeBackgroundBrush}"/> <Setter Property="Background" Value="{DynamicResource ThemeBackgroundBrush}"/>
<Setter Property="BorderBrush" Value="{DynamicResource ThemeBorderMidBrush}"/> <Setter Property="BorderBrush" Value="{DynamicResource ThemeBorderMidBrush}"/>
<Setter Property="BorderThickness" Value="{DynamicResource ThemeBorderThickness}"/> <Setter Property="BorderThickness" Value="{DynamicResource ThemeBorderThickness}"/>
@ -7,7 +8,7 @@
<Setter Property="ScrollViewer.VerticalScrollBarVisibility" Value="Auto"/> <Setter Property="ScrollViewer.VerticalScrollBarVisibility" Value="Auto"/>
<Setter Property="Template"> <Setter Property="Template">
<ControlTemplate> <ControlTemplate>
<Border BorderBrush="{TemplateBinding BorderBrush}" <Border Name="border" BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}"> BorderThickness="{TemplateBinding BorderThickness}">
<ScrollViewer Name="PART_ScrollViewer" <ScrollViewer Name="PART_ScrollViewer"
Background="{TemplateBinding Background}" Background="{TemplateBinding Background}"
@ -23,4 +24,8 @@
</Border> </Border>
</ControlTemplate> </ControlTemplate>
</Setter> </Setter>
</Style> </Style>
<Style Selector="ListBox:disabled /template/ Border#border">
<Setter Property="Opacity" Value="{DynamicResource ThemeDisabledOpacity}" />
</Style>
</Styles>

86
src/Avalonia.Themes.Default/ProgressBar.xaml

@ -1,14 +1,25 @@
<Styles xmlns="https://github.com/avaloniaui"> <Styles xmlns="https://github.com/avaloniaui">
<Style Selector="ProgressBar"> <Style Selector="ProgressBar">
<Setter Property="Background" Value="{DynamicResource ThemeAccentBrush4}"/> <Setter Property="Background" Value="{DynamicResource ThemeAccentBrush4}"/>
<Setter Property="Foreground" Value="{DynamicResource ThemeAccentBrush}"/> <Setter Property="Foreground" Value="{DynamicResource ThemeAccentBrush}"/>
<Setter Property="Template"> <Setter Property="Template">
<ControlTemplate> <ControlTemplate>
<Border Background="{TemplateBinding Background}" <Grid>
BorderBrush="{TemplateBinding BorderBrush}" <Border Background="{TemplateBinding Background}"
BorderThickness="{TemplateBinding BorderThickness}"> BorderBrush="{TemplateBinding BorderBrush}"
<Border Name="PART_Indicator" Background="{TemplateBinding Foreground}"/> BorderThickness="{TemplateBinding BorderThickness}">
</Border> <Border Name="PART_Indicator" Background="{TemplateBinding Foreground}"/>
</Border>
<LayoutTransformControl
HorizontalAlignment="Center"
VerticalAlignment="Center"
IsVisible="{Binding ShowProgressText, RelativeSource={RelativeSource TemplatedParent}}"
Name="PART_LayoutTransformControl">
<TextBlock
Foreground="{DynamicResource ThemeForegroundBrush}"
Text="{Binding Value, RelativeSource={RelativeSource TemplatedParent}, StringFormat={}{0:0}%}" />
</LayoutTransformControl>
</Grid>
</ControlTemplate> </ControlTemplate>
</Setter> </Setter>
</Style> </Style>
@ -22,42 +33,49 @@
</Style> </Style>
<Style Selector="ProgressBar:horizontal"> <Style Selector="ProgressBar:horizontal">
<Setter Property="MinWidth" Value="200"/> <Setter Property="MinWidth" Value="200"/>
<Setter Property="MinHeight" Value="14"/> <Setter Property="MinHeight" Value="16"/>
</Style> </Style>
<Style Selector="ProgressBar:vertical"> <Style Selector="ProgressBar:vertical">
<Setter Property="MinWidth" Value="14"/> <Setter Property="MinWidth" Value="16"/>
<Setter Property="MinHeight" Value="200"/> <Setter Property="MinHeight" Value="200"/>
</Style> </Style>
<Style Selector="ProgressBar:vertical /template/ LayoutTransformControl#PART_LayoutTransformControl">
<Setter Property="LayoutTransform">
<Setter.Value>
<RotateTransform Angle="90"/>
</Setter.Value>
</Setter>
</Style>
<Style Selector="ProgressBar:horizontal:indeterminate /template/ Border#PART_Indicator"> <Style Selector="ProgressBar:horizontal:indeterminate /template/ Border#PART_Indicator">
<Style.Animations> <Style.Animations>
<Animation Duration="0:0:3" <Animation Duration="0:0:3"
IterationCount="Infinite" IterationCount="Infinite"
Easing="LinearEasing"> Easing="LinearEasing">
<KeyFrame Cue="0%"> <KeyFrame Cue="0%">
<Setter Property="TranslateTransform.X" <Setter Property="TranslateTransform.X"
Value="{Binding IndeterminateStartingOffset, RelativeSource={RelativeSource TemplatedParent}}" /> Value="{Binding IndeterminateStartingOffset, RelativeSource={RelativeSource TemplatedParent}}" />
</KeyFrame> </KeyFrame>
<KeyFrame Cue="100%"> <KeyFrame Cue="100%">
<Setter Property="TranslateTransform.X" <Setter Property="TranslateTransform.X"
Value="{Binding IndeterminateEndingOffset, RelativeSource={RelativeSource TemplatedParent}}" /> Value="{Binding IndeterminateEndingOffset, RelativeSource={RelativeSource TemplatedParent}}" />
</KeyFrame> </KeyFrame>
</Animation> </Animation>
</Style.Animations> </Style.Animations>
</Style> </Style>
<Style Selector="ProgressBar:vertical:indeterminate /template/ Border#PART_Indicator"> <Style Selector="ProgressBar:vertical:indeterminate /template/ Border#PART_Indicator">
<Style.Animations> <Style.Animations>
<Animation Duration="0:0:3" <Animation Duration="0:0:3"
IterationCount="Infinite" IterationCount="Infinite"
Easing="LinearEasing"> Easing="LinearEasing">
<KeyFrame Cue="0%"> <KeyFrame Cue="0%">
<Setter Property="TranslateTransform.Y" <Setter Property="TranslateTransform.Y"
Value="{Binding IndeterminateStartingOffset, RelativeSource={RelativeSource TemplatedParent}}" /> Value="{Binding IndeterminateStartingOffset, RelativeSource={RelativeSource TemplatedParent}}" />
</KeyFrame> </KeyFrame>
<KeyFrame Cue="100%"> <KeyFrame Cue="100%">
<Setter Property="TranslateTransform.Y" <Setter Property="TranslateTransform.Y"
Value="{Binding IndeterminateEndingOffset, RelativeSource={RelativeSource TemplatedParent}}" /> Value="{Binding IndeterminateEndingOffset, RelativeSource={RelativeSource TemplatedParent}}" />
</KeyFrame> </KeyFrame>
</Animation> </Animation>
</Style.Animations> </Style.Animations>
</Style> </Style>
</Styles> </Styles>

9
src/Avalonia.Themes.Default/Slider.xaml

@ -4,7 +4,7 @@
<Setter Property="MinHeight" Value="20"/> <Setter Property="MinHeight" Value="20"/>
<Setter Property="Template"> <Setter Property="Template">
<ControlTemplate> <ControlTemplate>
<Grid> <Grid Name="grid">
<Grid.RowDefinitions> <Grid.RowDefinitions>
<RowDefinition Height="Auto"/> <RowDefinition Height="Auto"/>
<RowDefinition Height="Auto" MinHeight="20"/> <RowDefinition Height="Auto" MinHeight="20"/>
@ -20,7 +20,7 @@
<RepeatButton Name="PART_IncreaseButton" <RepeatButton Name="PART_IncreaseButton"
Classes="repeattrack" /> Classes="repeattrack" />
</Track.IncreaseButton> </Track.IncreaseButton>
<Thumb MinWidth="20" MinHeight="20"> <Thumb Name="thumb" MinWidth="20" MinHeight="20">
<Thumb.Template> <Thumb.Template>
<ControlTemplate> <ControlTemplate>
<Grid> <Grid>
@ -55,7 +55,7 @@
<RepeatButton Name="PART_IncreaseButton" <RepeatButton Name="PART_IncreaseButton"
Classes="repeattrack" /> Classes="repeattrack" />
</Track.IncreaseButton> </Track.IncreaseButton>
<Thumb MinWidth="20" MinHeight="20"> <Thumb Name="thumb" MinWidth="20" MinHeight="20">
<Thumb.Template> <Thumb.Template>
<ControlTemplate> <ControlTemplate>
<Grid> <Grid>
@ -87,4 +87,7 @@
</ControlTemplate> </ControlTemplate>
</Setter> </Setter>
</Style> </Style>
<Style Selector="Slider:disabled /template/ Grid#grid">
<Setter Property="Opacity" Value="{DynamicResource ThemeDisabledOpacity}" />
</Style>
</Styles> </Styles>

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

@ -12,7 +12,9 @@
Background="{TemplateBinding Background}" Background="{TemplateBinding Background}"
BorderBrush="{TemplateBinding BorderBrush}" BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}"> BorderThickness="{TemplateBinding BorderThickness}">
<DockPanel Margin="{TemplateBinding Padding}"> <DockPanel Margin="{TemplateBinding Padding}"
HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}"
VerticalAlignment="{TemplateBinding VerticalContentAlignment}">
<TextBlock Name="floatingWatermark" <TextBlock Name="floatingWatermark"
Foreground="{DynamicResource ThemeAccentBrush}" Foreground="{DynamicResource ThemeAccentBrush}"
@ -70,4 +72,7 @@
<Style Selector="TextBox"> <Style Selector="TextBox">
<Setter Property="Cursor" Value="IBeam" /> <Setter Property="Cursor" Value="IBeam" />
</Style> </Style>
<Style Selector="TextBox:disabled /template/ Border#border">
<Setter Property="Opacity" Value="{DynamicResource ThemeDisabledOpacity}" />
</Style>
</Styles> </Styles>

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

Loading…
Cancel
Save