Browse Source

Merge branch 'master' of https://github.com/AvaloniaUI/Avalonia into compiled-bindings

pull/3001/head
Jeremy Koritzinsky 7 years ago
parent
commit
fd3d70430a
  1. 58
      Avalonia.sln
  2. 1
      azure-pipelines.yml
  3. 8
      build/AndroidWorkarounds.props
  4. 1
      build/CoreLibraries.props
  5. 2
      native/Avalonia.Native/inc/avalonia-native.h
  6. 4
      native/Avalonia.Native/src/OSX/Avalonia.Native.OSX.xcodeproj/project.pbxproj
  7. 20
      native/Avalonia.Native/src/OSX/AvnString.mm
  8. 24
      native/Avalonia.Native/src/OSX/app.mm
  9. 11
      native/Avalonia.Native/src/OSX/clipboard.mm
  10. 4
      native/Avalonia.Native/src/OSX/common.h
  11. 14
      native/Avalonia.Native/src/OSX/main.mm
  12. 59
      native/Avalonia.Native/src/OSX/platformthreading.mm
  13. 1
      samples/ControlCatalog.NetCore/ControlCatalog.NetCore.csproj
  14. 31
      samples/ControlCatalog.NetCore/Program.cs
  15. 2
      samples/ControlCatalog/DecoratedWindow.xaml.cs
  16. 24
      samples/ControlCatalog/MainView.xaml
  17. 1
      samples/ControlCatalog/MainWindow.xaml
  18. 1
      samples/ControlCatalog/MainWindow.xaml.cs
  19. 2
      samples/ControlCatalog/Pages/DragAndDropPage.xaml.cs
  20. 5
      samples/ControlCatalog/Pages/ItemsRepeaterPage.xaml
  21. 12
      samples/ControlCatalog/Pages/ItemsRepeaterPage.xaml.cs
  22. 15
      samples/ControlCatalog/Pages/ListBoxPage.xaml
  23. 52
      samples/ControlCatalog/Pages/ListBoxPage.xaml.cs
  24. 9
      samples/ControlCatalog/Pages/PointersPage.cs
  25. 5
      samples/ControlCatalog/Pages/ScreenPage.cs
  26. 33
      samples/ControlCatalog/Pages/TreeViewPage.xaml
  27. 93
      samples/ControlCatalog/Pages/TreeViewPage.xaml.cs
  28. 26
      samples/ControlCatalog/SideBar.xaml
  29. 27
      samples/ControlCatalog/ViewModels/ItemsRepeaterPageViewModel.cs
  30. 5
      src/Android/Avalonia.Android/AndroidPlatform.cs
  31. 112
      src/Android/Avalonia.Android/Platform/SkiaPlatform/PopupImpl.cs
  32. 2
      src/Android/Avalonia.Android/Platform/SkiaPlatform/TopLevelImpl.cs
  33. 8
      src/Android/Avalonia.Android/Platform/Specific/Helpers/AndroidKeyboardEventsHelper.cs
  34. 6
      src/Android/Avalonia.Android/Platform/Specific/Helpers/AndroidTouchEventsHelper.cs
  35. 5
      src/Avalonia.Base/AvaloniaProperty.cs
  36. 74
      src/Avalonia.Base/AvaloniaPropertyRegistry.cs
  37. 229
      src/Avalonia.Base/Collections/AvaloniaList.cs
  38. 6
      src/Avalonia.Base/Data/Core/AvaloniaPropertyAccessorNode.cs
  39. 26
      src/Avalonia.Base/Data/Core/ExpressionNode.cs
  40. 8
      src/Avalonia.Base/Data/Core/ExpressionObserver.cs
  41. 11
      src/Avalonia.Base/Data/Core/IndexerExpressionNode.cs
  42. 5
      src/Avalonia.Base/Data/Core/IndexerNodeBase.cs
  43. 4
      src/Avalonia.Base/Data/Core/Plugins/AvaloniaPropertyAccessorPlugin.cs
  44. 23
      src/Avalonia.Base/Data/Core/Plugins/DataAnnotationsValidationPlugin.cs
  45. 8
      src/Avalonia.Base/Data/Core/Plugins/ExceptionValidationPlugin.cs
  46. 5
      src/Avalonia.Base/Data/Core/Plugins/IDataValidationPlugin.cs
  47. 3
      src/Avalonia.Base/Data/Core/Plugins/IPropertyAccessorPlugin.cs
  48. 4
      src/Avalonia.Base/Data/Core/Plugins/IStreamPlugin.cs
  49. 39
      src/Avalonia.Base/Data/Core/Plugins/IndeiValidationPlugin.cs
  50. 23
      src/Avalonia.Base/Data/Core/Plugins/InpcPropertyAccessorPlugin.cs
  51. 21
      src/Avalonia.Base/Data/Core/Plugins/MethodAccessorPlugin.cs
  52. 12
      src/Avalonia.Base/Data/Core/Plugins/ObservableStreamPlugin.cs
  53. 13
      src/Avalonia.Base/Data/Core/Plugins/TaskStreamPlugin.cs
  54. 6
      src/Avalonia.Base/Data/Core/PropertyAccessorNode.cs
  55. 18
      src/Avalonia.Base/Data/Core/SettableNode.cs
  56. 4
      src/Avalonia.Base/Data/Core/StreamNode.cs
  57. 21
      src/Avalonia.Base/Data/DataValidationException.cs
  58. 11
      src/Avalonia.Controls/AppBuilderBase.cs
  59. 2
      src/Avalonia.Controls/ComboBox.cs
  60. 2
      src/Avalonia.Controls/ContextMenu.cs
  61. 15
      src/Avalonia.Controls/ControlExtensions.cs
  62. 24
      src/Avalonia.Controls/DataValidationErrors.cs
  63. 1
      src/Avalonia.Controls/Embedding/Offscreen/OffscreenTopLevelImpl.cs
  64. 6
      src/Avalonia.Controls/Generators/ItemContainerGenerator.cs
  65. 6
      src/Avalonia.Controls/MenuItem.cs
  66. 1
      src/Avalonia.Controls/Mixins/ContentControlMixin.cs
  67. 2
      src/Avalonia.Controls/Notifications/WindowNotificationManager.cs
  68. 19
      src/Avalonia.Controls/PlacementMode.cs
  69. 2
      src/Avalonia.Controls/Platform/DefaultMenuInteractionHandler.cs
  70. 23
      src/Avalonia.Controls/Platform/IMountedVolumeInfoProvider.cs
  71. 4
      src/Avalonia.Controls/Platform/IPopupImpl.cs
  72. 2
      src/Avalonia.Controls/Platform/ITopLevelImpl.cs
  73. 24
      src/Avalonia.Controls/Platform/IWindowBaseImpl.cs
  74. 27
      src/Avalonia.Controls/Platform/IWindowImpl.cs
  75. 1
      src/Avalonia.Controls/Platform/IWindowingPlatform.cs
  76. 26
      src/Avalonia.Controls/Platform/InProcessDragSource.cs
  77. 24
      src/Avalonia.Controls/Platform/MountedDriveInfo.cs
  78. 5
      src/Avalonia.Controls/Platform/PlatformManager.cs
  79. 7
      src/Avalonia.Controls/Presenters/ContentPresenter.cs
  80. 2
      src/Avalonia.Controls/Presenters/ScrollContentPresenter.cs
  81. 42
      src/Avalonia.Controls/Primitives/AdornerDecorator.cs
  82. 2
      src/Avalonia.Controls/Primitives/AdornerLayer.cs
  83. 26
      src/Avalonia.Controls/Primitives/IPopupHost.cs
  84. 38
      src/Avalonia.Controls/Primitives/OverlayLayer.cs
  85. 149
      src/Avalonia.Controls/Primitives/OverlayPopupHost.cs
  86. 284
      src/Avalonia.Controls/Primitives/Popup.cs
  87. 358
      src/Avalonia.Controls/Primitives/PopupPositioning/IPopupPositioner.cs
  88. 175
      src/Avalonia.Controls/Primitives/PopupPositioning/ManagedPopupPositioner.cs
  89. 50
      src/Avalonia.Controls/Primitives/PopupPositioning/ManagedPopupPositionerPopupImplHelper.cs
  90. 119
      src/Avalonia.Controls/Primitives/PopupRoot.cs
  91. 5
      src/Avalonia.Controls/Primitives/SelectingItemsControl.cs
  92. 93
      src/Avalonia.Controls/Primitives/VisualLayerManager.cs
  93. 24
      src/Avalonia.Controls/Remote/Server/RemoteServerTopLevelImpl.cs
  94. 4
      src/Avalonia.Controls/Repeater/ItemsRepeater.cs
  95. 8
      src/Avalonia.Controls/Repeater/ItemsSourceView.cs
  96. 16
      src/Avalonia.Controls/SystemDialog.cs
  97. 13
      src/Avalonia.Controls/TextBox.cs
  98. 14
      src/Avalonia.Controls/ToolTip.cs
  99. 2
      src/Avalonia.Controls/TopLevel.cs
  100. 4
      src/Avalonia.Controls/TreeView.cs

58
Avalonia.sln

@ -1,7 +1,7 @@

Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio 15
VisualStudioVersion = 15.0.27130.2027
# Visual Studio Version 16
VisualStudioVersion = 16.0.29102.190
MinimumVisualStudioVersion = 10.0.40219.1
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Avalonia.Base", "src\Avalonia.Base\Avalonia.Base.csproj", "{B09B78D8-9B26-48B0-9149-D64A2F120F3F}"
EndProject
@ -197,7 +197,11 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "PlatformSanityChecks", "sam
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Avalonia.ReactiveUI.UnitTests", "tests\Avalonia.ReactiveUI.UnitTests\Avalonia.ReactiveUI.UnitTests.csproj", "{AF915D5C-AB00-4EA0-B5E6-001F4AE84E68}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Avalonia.Controls.DataGrid", "src\Avalonia.Controls.DataGrid\Avalonia.Controls.DataGrid.csproj", "{3278F3A9-9509-4A3F-A15B-BDC8B5BFF632}"
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Avalonia.Controls.DataGrid", "src\Avalonia.Controls.DataGrid\Avalonia.Controls.DataGrid.csproj", "{3278F3A9-9509-4A3F-A15B-BDC8B5BFF632}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Avalonia.Dialogs", "src\Avalonia.Dialogs\Avalonia.Dialogs.csproj", "{4D55985A-1EE2-4F25-AD39-6EA8BC04F8FB}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Avalonia.FreeDesktop", "src\Avalonia.FreeDesktop\Avalonia.FreeDesktop.csproj", "{4D36CEC8-53F2-40A5-9A37-79AAE356E2DA}"
EndProject
Global
GlobalSection(SharedMSBuildProjectFiles) = preSolution
@ -1842,6 +1846,54 @@ Global
{3278F3A9-9509-4A3F-A15B-BDC8B5BFF632}.Release|iPhone.Build.0 = Release|Any CPU
{3278F3A9-9509-4A3F-A15B-BDC8B5BFF632}.Release|iPhoneSimulator.ActiveCfg = Release|Any CPU
{3278F3A9-9509-4A3F-A15B-BDC8B5BFF632}.Release|iPhoneSimulator.Build.0 = Release|Any CPU
{4D55985A-1EE2-4F25-AD39-6EA8BC04F8FB}.Ad-Hoc|Any CPU.ActiveCfg = Debug|Any CPU
{4D55985A-1EE2-4F25-AD39-6EA8BC04F8FB}.Ad-Hoc|Any CPU.Build.0 = Debug|Any CPU
{4D55985A-1EE2-4F25-AD39-6EA8BC04F8FB}.Ad-Hoc|iPhone.ActiveCfg = Debug|Any CPU
{4D55985A-1EE2-4F25-AD39-6EA8BC04F8FB}.Ad-Hoc|iPhone.Build.0 = Debug|Any CPU
{4D55985A-1EE2-4F25-AD39-6EA8BC04F8FB}.Ad-Hoc|iPhoneSimulator.ActiveCfg = Debug|Any CPU
{4D55985A-1EE2-4F25-AD39-6EA8BC04F8FB}.Ad-Hoc|iPhoneSimulator.Build.0 = Debug|Any CPU
{4D55985A-1EE2-4F25-AD39-6EA8BC04F8FB}.AppStore|Any CPU.ActiveCfg = Debug|Any CPU
{4D55985A-1EE2-4F25-AD39-6EA8BC04F8FB}.AppStore|Any CPU.Build.0 = Debug|Any CPU
{4D55985A-1EE2-4F25-AD39-6EA8BC04F8FB}.AppStore|iPhone.ActiveCfg = Debug|Any CPU
{4D55985A-1EE2-4F25-AD39-6EA8BC04F8FB}.AppStore|iPhone.Build.0 = Debug|Any CPU
{4D55985A-1EE2-4F25-AD39-6EA8BC04F8FB}.AppStore|iPhoneSimulator.ActiveCfg = Debug|Any CPU
{4D55985A-1EE2-4F25-AD39-6EA8BC04F8FB}.AppStore|iPhoneSimulator.Build.0 = Debug|Any CPU
{4D55985A-1EE2-4F25-AD39-6EA8BC04F8FB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{4D55985A-1EE2-4F25-AD39-6EA8BC04F8FB}.Debug|Any CPU.Build.0 = Debug|Any CPU
{4D55985A-1EE2-4F25-AD39-6EA8BC04F8FB}.Debug|iPhone.ActiveCfg = Debug|Any CPU
{4D55985A-1EE2-4F25-AD39-6EA8BC04F8FB}.Debug|iPhone.Build.0 = Debug|Any CPU
{4D55985A-1EE2-4F25-AD39-6EA8BC04F8FB}.Debug|iPhoneSimulator.ActiveCfg = Debug|Any CPU
{4D55985A-1EE2-4F25-AD39-6EA8BC04F8FB}.Debug|iPhoneSimulator.Build.0 = Debug|Any CPU
{4D55985A-1EE2-4F25-AD39-6EA8BC04F8FB}.Release|Any CPU.ActiveCfg = Release|Any CPU
{4D55985A-1EE2-4F25-AD39-6EA8BC04F8FB}.Release|Any CPU.Build.0 = Release|Any CPU
{4D55985A-1EE2-4F25-AD39-6EA8BC04F8FB}.Release|iPhone.ActiveCfg = Release|Any CPU
{4D55985A-1EE2-4F25-AD39-6EA8BC04F8FB}.Release|iPhone.Build.0 = Release|Any CPU
{4D55985A-1EE2-4F25-AD39-6EA8BC04F8FB}.Release|iPhoneSimulator.ActiveCfg = Release|Any CPU
{4D55985A-1EE2-4F25-AD39-6EA8BC04F8FB}.Release|iPhoneSimulator.Build.0 = Release|Any CPU
{4D36CEC8-53F2-40A5-9A37-79AAE356E2DA}.Ad-Hoc|Any CPU.ActiveCfg = Debug|Any CPU
{4D36CEC8-53F2-40A5-9A37-79AAE356E2DA}.Ad-Hoc|Any CPU.Build.0 = Debug|Any CPU
{4D36CEC8-53F2-40A5-9A37-79AAE356E2DA}.Ad-Hoc|iPhone.ActiveCfg = Debug|Any CPU
{4D36CEC8-53F2-40A5-9A37-79AAE356E2DA}.Ad-Hoc|iPhone.Build.0 = Debug|Any CPU
{4D36CEC8-53F2-40A5-9A37-79AAE356E2DA}.Ad-Hoc|iPhoneSimulator.ActiveCfg = Debug|Any CPU
{4D36CEC8-53F2-40A5-9A37-79AAE356E2DA}.Ad-Hoc|iPhoneSimulator.Build.0 = Debug|Any CPU
{4D36CEC8-53F2-40A5-9A37-79AAE356E2DA}.AppStore|Any CPU.ActiveCfg = Debug|Any CPU
{4D36CEC8-53F2-40A5-9A37-79AAE356E2DA}.AppStore|Any CPU.Build.0 = Debug|Any CPU
{4D36CEC8-53F2-40A5-9A37-79AAE356E2DA}.AppStore|iPhone.ActiveCfg = Debug|Any CPU
{4D36CEC8-53F2-40A5-9A37-79AAE356E2DA}.AppStore|iPhone.Build.0 = Debug|Any CPU
{4D36CEC8-53F2-40A5-9A37-79AAE356E2DA}.AppStore|iPhoneSimulator.ActiveCfg = Debug|Any CPU
{4D36CEC8-53F2-40A5-9A37-79AAE356E2DA}.AppStore|iPhoneSimulator.Build.0 = Debug|Any CPU
{4D36CEC8-53F2-40A5-9A37-79AAE356E2DA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{4D36CEC8-53F2-40A5-9A37-79AAE356E2DA}.Debug|Any CPU.Build.0 = Debug|Any CPU
{4D36CEC8-53F2-40A5-9A37-79AAE356E2DA}.Debug|iPhone.ActiveCfg = Debug|Any CPU
{4D36CEC8-53F2-40A5-9A37-79AAE356E2DA}.Debug|iPhone.Build.0 = Debug|Any CPU
{4D36CEC8-53F2-40A5-9A37-79AAE356E2DA}.Debug|iPhoneSimulator.ActiveCfg = Debug|Any CPU
{4D36CEC8-53F2-40A5-9A37-79AAE356E2DA}.Debug|iPhoneSimulator.Build.0 = Debug|Any CPU
{4D36CEC8-53F2-40A5-9A37-79AAE356E2DA}.Release|Any CPU.ActiveCfg = Release|Any CPU
{4D36CEC8-53F2-40A5-9A37-79AAE356E2DA}.Release|Any CPU.Build.0 = Release|Any CPU
{4D36CEC8-53F2-40A5-9A37-79AAE356E2DA}.Release|iPhone.ActiveCfg = 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.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE

1
azure-pipelines.yml

@ -134,3 +134,4 @@ jobs:
pathToPublish: '$(Build.SourcesDirectory)/artifacts/zip'
artifactName: 'Samples'
condition: succeeded()

8
build/AndroidWorkarounds.props

@ -5,4 +5,12 @@
<PackageReference Include="System.Runtime.CompilerServices.Unsafe" Version="4.5.1" />
<PackageReference Include="System.Buffers" Version="4.5.0" />
</ItemGroup>
<Target Name="_RemoveNonExistingResgenFile" BeforeTargets="CoreCompile" Condition="'$(_SdkSetAndroidResgenFile)' == 'true' And '$(AndroidResgenFile)' != '' And !Exists('$(AndroidResgenFile)')">
<ItemGroup>
<Compile Remove="$(AndroidResgenFile)"/>
</ItemGroup>
</Target>
<PropertyGroup>
<DesignTimeBuild>false</DesignTimeBuild>
</PropertyGroup>
</Project>

1
build/CoreLibraries.props

@ -13,6 +13,7 @@
<ProjectReference Include="$(MSBuildThisFileDirectory)/../src/Avalonia.Styling/Avalonia.Styling.csproj" />
<ProjectReference Include="$(MSBuildThisFileDirectory)/../src/Avalonia.Themes.Default/Avalonia.Themes.Default.csproj" />
<ProjectReference Include="$(MSBuildThisFileDirectory)/../src/Avalonia.OpenGL/Avalonia.OpenGL.csproj" />
<ProjectReference Include="$(MSBuildThisFileDirectory)/../src/Avalonia.Dialogs/Avalonia.Dialogs.csproj" />
<ProjectReference Include="$(MSBuildThisFileDirectory)/../src/Markup/Avalonia.Markup/Avalonia.Markup.csproj" />
<ProjectReference Include="$(MSBuildThisFileDirectory)/../src/Markup/Avalonia.Markup.Xaml/Avalonia.Markup.Xaml.csproj" />
<ProjectReference Include="$(MSBuildThisFileDirectory)/../src/Avalonia.DesktopRuntime/Avalonia.DesktopRuntime.csproj" Condition="'$(TargetFramework)' != 'netstandard2.0'" />

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

@ -280,7 +280,7 @@ AVNCOM(IAvnPlatformThreadingInterface, 0b) : IUnknown
virtual bool GetCurrentThreadIsLoopThread() = 0;
virtual void SetSignaledCallback(IAvnSignaledCallback* cb) = 0;
virtual IAvnLoopCancellation* CreateLoopCancellation() = 0;
virtual void RunLoop(IAvnLoopCancellation* cancel) = 0;
virtual HRESULT RunLoop(IAvnLoopCancellation* cancel) = 0;
// Can't pass int* to sharpgentools for some reason
virtual void Signal(int priority) = 0;
virtual IUnknown* StartTimer(int priority, int ms, IAvnActionCallback* callback) = 0;

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

@ -7,6 +7,7 @@
objects = {
/* Begin PBXBuildFile section */
1A002B9E232135EE00021753 /* app.mm in Sources */ = {isa = PBXBuildFile; fileRef = 1A002B9D232135EE00021753 /* app.mm */; };
37A517B32159597E00FBA241 /* Screens.mm in Sources */ = {isa = PBXBuildFile; fileRef = 37A517B22159597E00FBA241 /* Screens.mm */; };
37C09D8821580FE4006A6758 /* SystemDialogs.mm in Sources */ = {isa = PBXBuildFile; fileRef = 37C09D8721580FE4006A6758 /* SystemDialogs.mm */; };
37DDA9B0219330F8002E132B /* AvnString.mm in Sources */ = {isa = PBXBuildFile; fileRef = 37DDA9AF219330F8002E132B /* AvnString.mm */; };
@ -22,6 +23,7 @@
/* End PBXBuildFile section */
/* Begin PBXFileReference section */
1A002B9D232135EE00021753 /* app.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = app.mm; 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>"; };
37A517B22159597E00FBA241 /* Screens.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = Screens.mm; sourceTree = "<group>"; };
@ -68,6 +70,7 @@
AB7A61E62147C814003C5833 = {
isa = PBXGroup;
children = (
1A002B9D232135EE00021753 /* app.mm */,
37DDA9B121933371002E132B /* AvnString.h */,
37DDA9AF219330F8002E132B /* AvnString.mm */,
37A4E71A2178846A00EACBCD /* headers */,
@ -164,6 +167,7 @@
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
1A002B9E232135EE00021753 /* app.mm in Sources */,
5B8BD94F215BFEA6005ED2A7 /* clipboard.mm in Sources */,
5B21A982216530F500CEE36E /* cursor.mm in Sources */,
37DDA9B0219330F8002E132B /* AvnString.mm in Sources */,

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

@ -11,14 +11,26 @@
class AvnStringImpl : public virtual ComSingleObject<IAvnString, &IID_IAvnString>
{
private:
NSString* _string;
int _length;
const char* _cstring;
public:
FORWARD_IUNKNOWN()
AvnStringImpl(NSString* string)
{
auto cstring = [string cStringUsingEncoding:NSUTF8StringEncoding];
_length = (int)[string lengthOfBytesUsingEncoding:NSUTF8StringEncoding];
_cstring = (const char*)malloc(_length + 5);
memset((void*)_cstring, 0, _length + 5);
memcpy((void*)_cstring, (void*)cstring, _length);
}
virtual ~AvnStringImpl()
{
_string = string;
free((void*)_cstring);
}
virtual HRESULT Pointer(void**retOut) override
@ -30,7 +42,7 @@ public:
return E_POINTER;
}
*retOut = (void*)_string.UTF8String;
*retOut = (void*)_cstring;
return S_OK;
}
@ -43,7 +55,7 @@ public:
return E_POINTER;
}
*retOut = (int)_string.length;
*retOut = _length;
return S_OK;
}

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

@ -0,0 +1,24 @@
#include "common.h"
@interface AvnAppDelegate : NSObject<NSApplicationDelegate>
@end
extern NSApplicationActivationPolicy AvnDesiredActivationPolicy = NSApplicationActivationPolicyRegular;
@implementation AvnAppDelegate
- (void)applicationWillFinishLaunching:(NSNotification *)notification
{
[[NSApplication sharedApplication] setActivationPolicy: AvnDesiredActivationPolicy];
}
- (void)applicationDidFinishLaunching:(NSNotification *)notification
{
[NSApp activateIgnoringOtherApps:true];
}
@end
extern void InitializeAvnApp()
{
NSApplication* app = [NSApplication sharedApplication];
id delegate = [AvnAppDelegate new];
[app setDelegate:delegate];
}

11
native/Avalonia.Native/src/OSX/clipboard.mm

@ -8,6 +8,13 @@ class Clipboard : public ComSingleObject<IAvnClipboard, &IID_IAvnClipboard>
{
public:
FORWARD_IUNKNOWN()
Clipboard()
{
NSPasteboard *pasteBoard = [NSPasteboard generalPasteboard];
[pasteBoard stringForType:NSPasteboardTypeString];
}
virtual HRESULT GetText (IAvnString**ppv) override
{
@autoreleasepool
@ -39,7 +46,9 @@ public:
{
@autoreleasepool
{
[[NSPasteboard generalPasteboard] clearContents];
NSPasteboard *pasteBoard = [NSPasteboard generalPasteboard];
[pasteBoard clearContents];
[pasteBoard setString:@"" forType:NSPasteboardTypeString];
}
return S_OK;

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

@ -19,12 +19,12 @@ extern IAvnClipboard* CreateClipboard();
extern IAvnCursorFactory* CreateCursorFactory();
extern IAvnGlFeature* GetGlFeature();
extern IAvnGlSurfaceRenderTarget* CreateGlRenderTarget(NSWindow* window, NSView* view);
extern void InitializeAvnApp();
extern NSApplicationActivationPolicy AvnDesiredActivationPolicy;
extern NSPoint ToNSPoint (AvnPoint p);
extern AvnPoint ToAvnPoint (NSPoint p);
extern AvnPoint ConvertPointY (AvnPoint p);
extern NSSize ToNSSize (AvnSize s);
#ifdef DEBUG
#define NSDebugLog(...) NSLog(__VA_ARGS__)
#else

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

@ -5,21 +5,14 @@
#define COM_GUIDS_MATERIALIZE
#include "common.h"
static BOOL ShowInDock = 1;
static void SetActivationPolicy()
{
[[NSApplication sharedApplication] setActivationPolicy: (ShowInDock ? NSApplicationActivationPolicyRegular : NSApplicationActivationPolicyAccessory)];
}
class MacOptions : public ComSingleObject<IAvnMacOptions, &IID_IAvnMacOptions>
{
public:
FORWARD_IUNKNOWN()
virtual HRESULT SetShowInDock(int show) override
{
ShowInDock = show;
SetActivationPolicy();
AvnDesiredActivationPolicy = show
? NSApplicationActivationPolicyRegular : NSApplicationActivationPolicyAccessory;
return S_OK;
}
};
@ -64,8 +57,9 @@ public:
{
@autoreleasepool{
[[ThreadingInitializer new] do];
return S_OK;
}
InitializeAvnApp();
return S_OK;
};
virtual IAvnMacOptions* GetMacOptions() override

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

@ -57,16 +57,36 @@ class PlatformThreadingInterface : public ComSingleObject<IAvnPlatformThreadingI
{
private:
Signaler* _signaler;
bool _wasRunningAtLeastOnce = false;
class LoopCancellation : public ComSingleObject<IAvnLoopCancellation, &IID_IAvnLoopCancellation>
{
public:
FORWARD_IUNKNOWN()
bool Cancelled = 0;
virtual void Cancel() override
bool Running = false;
bool Cancelled = false;
virtual void Cancel()
{
Cancelled = 1;
Cancelled = true;
if(Running)
{
Running = false;
dispatch_async(dispatch_get_main_queue(), ^{
[[NSApplication sharedApplication] stop:nil];
NSEvent* event = [NSEvent otherEventWithType:NSEventTypeApplicationDefined
location:NSMakePoint(0, 0)
modifierFlags:0
timestamp:0
windowNumber:0
context:nil
subtype:0
data1:0
data2:0];
[NSApp postEvent:event atStart:YES];
});
}
}
};
public:
@ -99,30 +119,17 @@ public:
return new LoopCancellation();
}
virtual void RunLoop(IAvnLoopCancellation* cancel) override
virtual HRESULT RunLoop(IAvnLoopCancellation* cancel) override
{
@autoreleasepool {
auto can = dynamic_cast<LoopCancellation*>(cancel);
[[NSApplication sharedApplication] activateIgnoringOtherApps:true];
while(true)
{
@autoreleasepool
{
if(can != NULL && can->Cancelled)
return;
NSEvent* ev = [[NSApplication sharedApplication]
nextEventMatchingMask:NSEventMaskAny
untilDate: [NSDate dateWithTimeIntervalSinceNow:1]
inMode:NSDefaultRunLoopMode
dequeue:true];
if(can != NULL && can->Cancelled)
return;
if(ev != NULL)
[[NSApplication sharedApplication] sendEvent:ev];
}
}
NSDebugLog(@"RunLoop exited");
}
auto can = dynamic_cast<LoopCancellation*>(cancel);
if(can->Cancelled)
return S_OK;
if(_wasRunningAtLeastOnce)
return E_FAIL;
can->Running = true;
_wasRunningAtLeastOnce = true;
[NSApp run];
return S_OK;
}
virtual void Signal(int priority) override

1
samples/ControlCatalog.NetCore/ControlCatalog.NetCore.csproj

@ -7,6 +7,7 @@
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\..\src\Avalonia.Dialogs\Avalonia.Dialogs.csproj" />
<ProjectReference Include="..\..\src\Linux\Avalonia.LinuxFramebuffer\Avalonia.LinuxFramebuffer.csproj" />
<ProjectReference Include="..\ControlCatalog\ControlCatalog.csproj" />
<ProjectReference Include="..\..\src\Avalonia.Desktop\Avalonia.Desktop.csproj" />

31
samples/ControlCatalog.NetCore/Program.cs

@ -1,21 +1,19 @@
using System;
using System.Diagnostics;
using System.Globalization;
using System.Linq;
using System.Threading;
using Avalonia;
using Avalonia.Controls;
using Avalonia.LinuxFramebuffer.Output;
using Avalonia.Skia;
using Avalonia.ReactiveUI;
using Avalonia.Dialogs;
namespace ControlCatalog.NetCore
{
static class Program
{
[STAThread]
static int Main(string[] args)
{
Thread.CurrentThread.TrySetApartmentState(ApartmentState.STA);
if (args.Contains("--wait-for-attach"))
{
Console.WriteLine("Attach debugger and use 'Set next statement'");
@ -28,34 +26,44 @@ namespace ControlCatalog.NetCore
}
var builder = BuildAvaloniaApp();
double GetScaling()
{
var idx = Array.IndexOf(args, "--scaling");
if (idx != 0 && args.Length > idx + 1 &&
double.TryParse(args[idx + 1], NumberStyles.Any, CultureInfo.InvariantCulture, out var scaling))
return scaling;
return 1;
}
if (args.Contains("--fbdev"))
{
SilenceConsole();
return builder.StartLinuxFbDev(args);
return builder.StartLinuxFbDev(args, scaling: GetScaling());
}
else if (args.Contains("--drm"))
{
SilenceConsole();
return builder.StartLinuxDrm(args);
return builder.StartLinuxDrm(args, scaling: GetScaling());
}
else
return builder.StartWithClassicDesktopLifetime(args);
}
/// <summary>
/// This method is needed for IDE previewer infrastructure
/// </summary>
public static AppBuilder BuildAvaloniaApp()
=> AppBuilder.Configure<App>()
.UsePlatformDetect()
.With(new X11PlatformOptions {EnableMultiTouch = true})
.With(new X11PlatformOptions { EnableMultiTouch = true })
.With(new Win32PlatformOptions
{
EnableMultitouch = true,
AllowEglInitialization = true
})
.UseSkia()
.UseReactiveUI();
.UseReactiveUI()
.UseManagedSystemDialogs();
static void SilenceConsole()
{
@ -64,7 +72,8 @@ namespace ControlCatalog.NetCore
Console.CursorVisible = false;
while (true)
Console.ReadKey(true);
}) {IsBackground = true}.Start();
})
{ IsBackground = true }.Start();
}
}
}

2
samples/ControlCatalog/DecoratedWindow.xaml.cs

@ -34,7 +34,7 @@ namespace ControlCatalog
SetupSide("Left", StandardCursorType.LeftSide, WindowEdge.West);
SetupSide("Right", StandardCursorType.RightSide, WindowEdge.East);
SetupSide("Top", StandardCursorType.TopSide, WindowEdge.North);
SetupSide("Bottom", StandardCursorType.BottomSize, WindowEdge.South);
SetupSide("Bottom", StandardCursorType.BottomSide, WindowEdge.South);
SetupSide("TopLeft", StandardCursorType.TopLeftCorner, WindowEdge.NorthWest);
SetupSide("TopRight", StandardCursorType.TopRightCorner, WindowEdge.NorthEast);
SetupSide("BottomLeft", StandardCursorType.BottomLeftCorner, WindowEdge.SouthWest);

24
samples/ControlCatalog/MainView.xaml

@ -6,10 +6,13 @@
Foreground="{DynamicResource ThemeForegroundBrush}"
FontSize="{DynamicResource FontSizeNormal}">
<Grid>
<ComboBox x:Name="Themes" SelectedIndex="0" Width="100" Margin="8" HorizontalAlignment="Right" VerticalAlignment="Bottom">
<ComboBoxItem>Light</ComboBoxItem>
<ComboBoxItem>Dark</ComboBoxItem>
</ComboBox>
<Grid.Styles>
<Style Selector="TextBlock.h2">
<Setter Property="TextWrapping" Value="Wrap"/>
<Setter Property="MaxWidth" Value="400"/>
<Setter Property="HorizontalAlignment" Value="Left"/>
</Style>
</Grid.Styles>
<TabControl Classes="sidebar" Name="Sidebar">
<TabItem Header="AutoCompleteBox"><pages:AutoCompleteBoxPage/></TabItem>
<TabItem Header="Border"><pages:BorderPage/></TabItem>
@ -21,7 +24,12 @@
<TabItem Header="CheckBox"><pages:CheckBoxPage/></TabItem>
<TabItem Header="ComboBox"><pages:ComboBoxPage/></TabItem>
<TabItem Header="ContextMenu"><pages:ContextMenuPage/></TabItem>
<TabItem Header="DataGrid"><pages:DataGridPage/></TabItem>
<!-- DataGrid is our special snowflake -->
<TabItem Header="DataGrid"
ScrollViewer.VerticalScrollBarVisibility="Disabled"
ScrollViewer.HorizontalScrollBarVisibility="Disabled">
<pages:DataGridPage/>
</TabItem>
<TabItem Header="DatePicker"><pages:DatePickerPage/></TabItem>
<TabItem Header="Drag+Drop"><pages:DragAndDropPage/></TabItem>
<TabItem Header="Expander"><pages:ExpanderPage/></TabItem>
@ -42,6 +50,12 @@
<TabItem Header="ToolTip"><pages:ToolTipPage/></TabItem>
<TabItem Header="TreeView"><pages:TreeViewPage/></TabItem>
<TabItem Header="Viewbox"><pages:ViewboxPage/></TabItem>
<TabControl.Tag>
<ComboBox x:Name="Themes" SelectedIndex="0" Width="100" Margin="8" HorizontalAlignment="Right" VerticalAlignment="Bottom">
<ComboBoxItem>Light</ComboBoxItem>
<ComboBoxItem>Dark</ComboBoxItem>
</ComboBox>
</TabControl.Tag>
</TabControl>
</Grid>
</UserControl>

1
samples/ControlCatalog/MainWindow.xaml

@ -1,4 +1,5 @@
<Window xmlns="https://github.com/avaloniaui" MinWidth="500" MinHeight="300"
Width="1024" Height="800"
xmlns:pages="clr-namespace:ControlCatalog.Pages"
Title="Avalonia Control Gallery"
Icon="/Assets/test_icon.ico"

1
samples/ControlCatalog/MainWindow.xaml.cs

@ -6,6 +6,7 @@ using Avalonia.Markup.Xaml;
using Avalonia.Threading;
using ControlCatalog.ViewModels;
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
namespace ControlCatalog

2
samples/ControlCatalog/Pages/DragAndDropPage.xaml.cs

@ -29,7 +29,7 @@ namespace ControlCatalog.Pages
DataObject dragData = new DataObject();
dragData.Set(DataFormats.Text, $"You have dragged text {++DragCount} times");
var result = await DragDrop.DoDragDrop(dragData, DragDropEffects.Copy);
var result = await DragDrop.DoDragDrop(e, dragData, DragDropEffects.Copy);
switch(result)
{
case DragDropEffects.Copy:

5
samples/ControlCatalog/Pages/ItemsRepeaterPage.xaml

@ -6,19 +6,20 @@
<TextBlock Classes="h1">ItemsRepeater</TextBlock>
<TextBlock Classes="h2">A data-driven collection control that incorporates a flexible layout system, custom views, and virtualization.</TextBlock>
</StackPanel>
<StackPanel DockPanel.Dock="Right" Margin="8 0">
<StackPanel DockPanel.Dock="Right" Margin="8 0" Spacing="4">
<ComboBox SelectedIndex="0" SelectionChanged="LayoutChanged">
<ComboBoxItem>Stack - Vertical</ComboBoxItem>
<ComboBoxItem>Stack - Horizontal</ComboBoxItem>
<ComboBoxItem>UniformGrid - Vertical</ComboBoxItem>
<ComboBoxItem>UniformGrid - Horizontal</ComboBoxItem>
</ComboBox>
<Button Command="{Binding AddItem}">Add Item</Button>
</StackPanel>
<Border BorderThickness="1" BorderBrush="{DynamicResource ThemeBorderMidBrush}" Margin="0 0 0 16">
<ScrollViewer Name="scroller"
HorizontalScrollBarVisibility="Auto"
VerticalScrollBarVisibility="Auto">
<ItemsRepeater Name="repeater" Items="{Binding}"/>
<ItemsRepeater Name="repeater" Background="Transparent" Items="{Binding Items}"/>
</ScrollViewer>
</Border>
</DockPanel>

12
samples/ControlCatalog/Pages/ItemsRepeaterPage.xaml.cs

@ -1,8 +1,11 @@
using System;
using System.Linq;
using Avalonia.Controls;
using Avalonia.Controls.Primitives;
using Avalonia.Input;
using Avalonia.Layout;
using Avalonia.Markup.Xaml;
using ControlCatalog.ViewModels;
namespace ControlCatalog.Pages
{
@ -16,7 +19,8 @@ namespace ControlCatalog.Pages
this.InitializeComponent();
_repeater = this.FindControl<ItemsRepeater>("repeater");
_scroller = this.FindControl<ScrollViewer>("scroller");
DataContext = Enumerable.Range(1, 100000).Select(i => $"Item {i}" ).ToArray();
_repeater.PointerPressed += RepeaterClick;
DataContext = new ItemsRepeaterPageViewModel();
}
private void InitializeComponent()
@ -67,5 +71,11 @@ namespace ControlCatalog.Pages
break;
}
}
private void RepeaterClick(object sender, PointerPressedEventArgs e)
{
var item = (e.Source as TextBlock)?.DataContext as string;
((ItemsRepeaterPageViewModel)DataContext).SelectedItem = item;
}
}
}

15
samples/ControlCatalog/Pages/ListBoxPage.xaml

@ -9,7 +9,20 @@
Margin="0,16,0,0"
HorizontalAlignment="Center"
Spacing="16">
<ListBox Items="{Binding}" Width="250" Height="350"></ListBox>
<StackPanel Orientation="Vertical" Spacing="8">
<ListBox Items="{Binding Items}" SelectedItems="{Binding SelectedItems}" SelectionMode="{Binding SelectionMode}" Width="250" Height="350"></ListBox>
<Button Command="{Binding AddItemCommand}">Add</Button>
<Button Command="{Binding RemoveItemCommand}">Remove</Button>
<ComboBox SelectedIndex="{Binding SelectionMode, Mode=TwoWay}">
<ComboBoxItem>Single</ComboBoxItem>
<ComboBoxItem>Multiple</ComboBoxItem>
<ComboBoxItem>Toggle</ComboBoxItem>
<ComboBoxItem>AlwaysSelected</ComboBoxItem>
</ComboBox>
</StackPanel>
</StackPanel>
</StackPanel>
</UserControl>

52
samples/ControlCatalog/Pages/ListBoxPage.xaml.cs

@ -1,9 +1,9 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using System.Reactive;
using Avalonia.Controls;
using Avalonia.Markup.Xaml;
using ReactiveUI;
namespace ControlCatalog.Pages
{
@ -11,9 +11,8 @@ namespace ControlCatalog.Pages
{
public ListBoxPage()
{
this.InitializeComponent();
DataContext = Enumerable.Range(1, 10).Select(i => $"Item {i}" )
.ToArray();
InitializeComponent();
DataContext = new PageViewModel();
}
private void InitializeComponent()
@ -21,5 +20,46 @@ namespace ControlCatalog.Pages
AvaloniaXamlLoader.Load(this);
}
private class PageViewModel : ReactiveObject
{
private int _counter;
private SelectionMode _selectionMode;
public PageViewModel()
{
Items = new ObservableCollection<string>(Enumerable.Range(1, 10).Select(i => GenerateItem()));
SelectedItems = new ObservableCollection<string>();
AddItemCommand = ReactiveCommand.Create(() => Items.Add(GenerateItem()));
RemoveItemCommand = ReactiveCommand.Create(() =>
{
while (SelectedItems.Count > 0)
{
Items.Remove(SelectedItems[0]);
}
});
}
public ObservableCollection<string> Items { get; }
public ObservableCollection<string> SelectedItems { get; }
public ReactiveCommand<Unit, Unit> AddItemCommand { get; }
public ReactiveCommand<Unit, Unit> RemoveItemCommand { get; }
public SelectionMode SelectionMode
{
get => _selectionMode;
set
{
SelectedItems.Clear();
this.RaiseAndSetIfChanged(ref _selectionMode, value);
}
}
private string GenerateItem() => $"Item {_counter++}";
}
}
}

9
samples/ControlCatalog/Pages/PointersPage.cs

@ -69,16 +69,25 @@ namespace ControlCatalog.Pages
{
UpdatePointer(e);
e.Pointer.Capture(this);
e.Handled = true;
base.OnPointerPressed(e);
}
protected override void OnPointerMoved(PointerEventArgs e)
{
UpdatePointer(e);
e.Handled = true;
base.OnPointerMoved(e);
}
protected override void OnPointerReleased(PointerReleasedEventArgs e)
{
_pointers.Remove(e.Pointer);
e.Handled = true;
InvalidateVisual();
}
protected override void OnPointerCaptureLost(PointerCaptureLostEventArgs e)
{
_pointers.Remove(e.Pointer);
InvalidateVisual();

5
samples/ControlCatalog/Pages/ScreenPage.cs

@ -22,7 +22,10 @@ namespace ControlCatalog.Pages
public override void Render(DrawingContext context)
{
base.Render(context);
Window w = (Window)VisualRoot;
if (!(VisualRoot is Window w))
{
return;
}
var screens = w.Screens.All;
var scaling = ((IRenderRoot)w).RenderScaling;

33
samples/ControlCatalog/Pages/TreeViewPage.xaml

@ -6,16 +6,29 @@
<TextBlock Classes="h2">Displays a hierachical tree of data.</TextBlock>
<StackPanel Orientation="Horizontal"
Margin="0,16,0,0"
HorizontalAlignment="Center"
Spacing="16">
<TreeView SelectionMode="Multiple" Items="{Binding}" Width="250" Height="350">
<TreeView.ItemTemplate>
<TreeDataTemplate ItemsSource="{Binding Children}">
<TextBlock Text="{Binding Header}"/>
</TreeDataTemplate>
</TreeView.ItemTemplate>
</TreeView>
Margin="0,16,0,0"
HorizontalAlignment="Center"
Spacing="16">
<StackPanel Orientation="Vertical" Spacing="8">
<TreeView Items="{Binding Items}" SelectedItems="{Binding SelectedItems}" SelectionMode="{Binding SelectionMode}" Width="250" Height="350">
<TreeView.ItemTemplate>
<TreeDataTemplate ItemsSource="{Binding Children}">
<TextBlock Text="{Binding Header}"/>
</TreeDataTemplate>
</TreeView.ItemTemplate>
</TreeView>
<Button Command="{Binding AddItemCommand}">Add</Button>
<Button Command="{Binding RemoveItemCommand}">Remove</Button>
<ComboBox SelectedIndex="{Binding SelectionMode, Mode=TwoWay}">
<ComboBoxItem>Single</ComboBoxItem>
<ComboBoxItem>Multiple</ComboBoxItem>
<ComboBoxItem>Toggle</ComboBoxItem>
<ComboBoxItem>AlwaysSelected</ComboBoxItem>
</ComboBox>
</StackPanel>
</StackPanel>
</StackPanel>
</UserControl>

93
samples/ControlCatalog/Pages/TreeViewPage.xaml.cs

@ -1,8 +1,9 @@
using System.Collections;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using System.Reactive;
using Avalonia.Controls;
using Avalonia.Markup.Xaml;
using ReactiveUI;
namespace ControlCatalog.Pages
{
@ -10,8 +11,8 @@ namespace ControlCatalog.Pages
{
public TreeViewPage()
{
this.InitializeComponent();
DataContext = new Node().Children;
InitializeComponent();
DataContext = new PageViewModel();
}
private void InitializeComponent()
@ -19,22 +20,96 @@ namespace ControlCatalog.Pages
AvaloniaXamlLoader.Load(this);
}
public class Node
private class PageViewModel : ReactiveObject
{
private IList<Node> _children;
private SelectionMode _selectionMode;
public PageViewModel()
{
Node root = new Node();
Items = root.Children;
SelectedItems = new ObservableCollection<Node>();
AddItemCommand = ReactiveCommand.Create(() =>
{
Node parentItem = SelectedItems.Count > 0 ? SelectedItems[0] : root;
parentItem.AddNewItem();
});
RemoveItemCommand = ReactiveCommand.Create(() =>
{
while (SelectedItems.Count > 0)
{
Node lastItem = SelectedItems[0];
RecursiveRemove(Items, lastItem);
SelectedItems.Remove(lastItem);
}
bool RecursiveRemove(ObservableCollection<Node> items, Node selectedItem)
{
if (items.Remove(selectedItem))
{
return true;
}
foreach (Node item in items)
{
if (item.AreChildrenInitialized && RecursiveRemove(item.Children, selectedItem))
{
return true;
}
}
return false;
}
});
}
public ObservableCollection<Node> Items { get; }
public ObservableCollection<Node> SelectedItems { get; }
public ReactiveCommand<Unit, Unit> AddItemCommand { get; }
public ReactiveCommand<Unit, Unit> RemoveItemCommand { get; }
public SelectionMode SelectionMode
{
get => _selectionMode;
set
{
SelectedItems.Clear();
this.RaiseAndSetIfChanged(ref _selectionMode, value);
}
}
}
private class Node
{
private int _counter;
private ObservableCollection<Node> _children;
public string Header { get; private set; }
public IList<Node> Children
public bool AreChildrenInitialized => _children != null;
public ObservableCollection<Node> Children
{
get
{
if (_children == null)
{
_children = Enumerable.Range(1, 10).Select(i => new Node() {Header = $"Item {i}"})
.ToArray();
_children = new ObservableCollection<Node>(Enumerable.Range(1, 10).Select(i => CreateNewNode()));
}
return _children;
}
}
public void AddNewItem() => Children.Add(CreateNewNode());
public override string ToString() => Header;
private Node CreateNewNode() => new Node {Header = $"Item {_counter++}"};
}
}
}

26
samples/ControlCatalog/SideBar.xaml

@ -24,7 +24,8 @@
Name="PART_ScrollViewer"
HorizontalScrollBarVisibility="{TemplateBinding (ScrollViewer.HorizontalScrollBarVisibility)}"
VerticalScrollBarVisibility="{TemplateBinding (ScrollViewer.VerticalScrollBarVisibility)}"
Background="{TemplateBinding Background}">
Background="{TemplateBinding Background}"
DockPanel.Dock="Left">
<ItemsPresenter
Name="PART_ItemsPresenter"
Items="{TemplateBinding Items}"
@ -32,14 +33,19 @@
ItemTemplate="{TemplateBinding ItemTemplate}">
</ItemsPresenter>
</ScrollViewer>
<ContentPresenter
Name="PART_SelectedContentHost"
Margin="{TemplateBinding Padding}"
HorizontalContentAlignment="{TemplateBinding HorizontalContentAlignment}"
VerticalContentAlignment="{TemplateBinding VerticalContentAlignment}"
Content="{TemplateBinding SelectedContent}"
ContentTemplate="{TemplateBinding SelectedContentTemplate}">
</ContentPresenter>
<ContentControl Content="{TemplateBinding Tag}" HorizontalContentAlignment="Right" DockPanel.Dock="Bottom"/>
<ScrollViewer
HorizontalScrollBarVisibility="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=SelectedItem.(ScrollViewer.HorizontalScrollBarVisibility)}"
VerticalScrollBarVisibility="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=SelectedItem.(ScrollViewer.VerticalScrollBarVisibility)}">
<ContentPresenter
Name="PART_SelectedContentHost"
Margin="{TemplateBinding Padding}"
HorizontalContentAlignment="{TemplateBinding HorizontalContentAlignment}"
VerticalContentAlignment="{TemplateBinding VerticalContentAlignment}"
Content="{TemplateBinding SelectedContent}"
ContentTemplate="{TemplateBinding SelectedContentTemplate}">
</ContentPresenter>
</ScrollViewer>
</DockPanel>
</Border>
</ControlTemplate>
@ -58,6 +64,8 @@
<DoubleTransition Property="Opacity" Duration="0:0:0.150"/>
</Transitions>
</Setter>
<Setter Property="(ScrollViewer.HorizontalScrollBarVisibility)" Value="Auto"/>
<Setter Property="(ScrollViewer.VerticalScrollBarVisibility)" Value="Auto"/>
</Style>
<Style Selector="TabControl.sidebar > TabItem:pointerover">
<Setter Property="Opacity" Value="1"/>

27
samples/ControlCatalog/ViewModels/ItemsRepeaterPageViewModel.cs

@ -0,0 +1,27 @@
using System.Collections.ObjectModel;
using System.Linq;
using ReactiveUI;
namespace ControlCatalog.ViewModels
{
public class ItemsRepeaterPageViewModel : ReactiveObject
{
private int newItemIndex = 1;
public ItemsRepeaterPageViewModel()
{
Items = new ObservableCollection<string>(
Enumerable.Range(1, 100000).Select(i => $"Item {i}"));
}
public ObservableCollection<string> Items { get; }
public string SelectedItem { get; set; }
public void AddItem()
{
var index = SelectedItem != null ? Items.IndexOf(SelectedItem) : -1;
Items.Insert(index + 1, $"New Item {newItemIndex++}");
}
}
}

5
src/Android/Avalonia.Android/AndroidPlatform.cs

@ -71,10 +71,5 @@ namespace Avalonia.Android
{
throw new NotSupportedException();
}
public IPopupImpl CreatePopup()
{
return new PopupImpl();
}
}
}

112
src/Android/Avalonia.Android/Platform/SkiaPlatform/PopupImpl.cs

@ -1,112 +0,0 @@
using System;
using Android.Content;
using Android.Graphics;
using Android.Runtime;
using Android.Views;
using Avalonia.Controls;
using Avalonia.Platform;
namespace Avalonia.Android.Platform.SkiaPlatform
{
class PopupImpl : TopLevelImpl, IPopupImpl
{
private PixelPoint _position;
private bool _isAdded;
Action IWindowBaseImpl.Activated { get; set; }
public Action<PixelPoint> PositionChanged { get; set; }
public Action Deactivated { get; set; }
public PopupImpl() : base(ActivityTracker.Current, true)
{
}
private Size _clientSize = new Size(1, 1);
public void Resize(Size value)
{
if (View == null)
return;
_clientSize = value;
UpdateParams();
}
public void SetMinMaxSize(Size minSize, Size maxSize)
{
}
public IScreenImpl Screen { get; }
public PixelPoint Position
{
get { return _position; }
set
{
_position = value;
PositionChanged?.Invoke(_position);
UpdateParams();
}
}
WindowManagerLayoutParams CreateParams() => new WindowManagerLayoutParams(0,
WindowManagerFlags.NotTouchModal, Format.Translucent)
{
Gravity = GravityFlags.Left | GravityFlags.Top,
WindowAnimations = 0,
X = (int) _position.X,
Y = (int) _position.Y,
Width = Math.Max(1, (int) _clientSize.Width),
Height = Math.Max(1, (int) _clientSize.Height)
};
void UpdateParams()
{
if (_isAdded)
ActivityTracker.Current?.WindowManager?.UpdateViewLayout(View, CreateParams());
}
public override void Show()
{
if (_isAdded)
return;
ActivityTracker.Current.WindowManager.AddView(View, CreateParams());
_isAdded = true;
}
public override void Hide()
{
if (_isAdded)
{
var wm = View.Context.ApplicationContext.GetSystemService(Context.WindowService)
.JavaCast<IWindowManager>();
wm.RemoveView(View);
_isAdded = false;
}
}
public override void Dispose()
{
Hide();
base.Dispose();
}
public void Activate()
{
}
public void BeginMoveDrag()
{
//Not supported
}
public void BeginResizeDrag(WindowEdge edge)
{
//Not supported
}
public void SetTopmost(bool value)
{
//Not supported
}
}
}

2
src/Android/Avalonia.Android/Platform/SkiaPlatform/TopLevelImpl.cs

@ -191,6 +191,8 @@ namespace Avalonia.Android.Platform.SkiaPlatform
}
}
public IPopupImpl CreatePopup() => null;
ILockedFramebuffer IFramebufferPlatformSurface.Lock()=>new AndroidFramebuffer(_view.Holder.Surface);
}
}

8
src/Android/Avalonia.Android/Platform/Specific/Helpers/AndroidKeyboardEventsHelper.cs

@ -72,12 +72,12 @@ namespace Avalonia.Android.Platform.Specific.Helpers
return false;
}
private static InputModifiers GetModifierKeys(KeyEvent e)
private static RawInputModifiers GetModifierKeys(KeyEvent e)
{
var rv = InputModifiers.None;
var rv = RawInputModifiers.None;
if (e.IsCtrlPressed) rv |= InputModifiers.Control;
if (e.IsShiftPressed) rv |= InputModifiers.Shift;
if (e.IsCtrlPressed) rv |= RawInputModifiers.Control;
if (e.IsShiftPressed) rv |= RawInputModifiers.Shift;
return rv;
}

6
src/Android/Avalonia.Android/Platform/Specific/Helpers/AndroidTouchEventsHelper.cs

@ -78,12 +78,12 @@ namespace Avalonia.Android.Platform.Specific.Helpers
if (mouseEventType == RawPointerEventType.LeftButtonDown)
{
var me = new RawPointerEventArgs(mouseDevice, (uint)eventTime.Ticks, inputRoot,
RawPointerEventType.Move, _point, InputModifiers.None);
RawPointerEventType.Move, _point, RawInputModifiers.None);
_view.Input(me);
}
var mouseEvent = new RawPointerEventArgs(mouseDevice, (uint)eventTime.Ticks, inputRoot,
mouseEventType.Value, _point, InputModifiers.LeftMouseButton);
mouseEventType.Value, _point, RawInputModifiers.LeftMouseButton);
_view.Input(mouseEvent);
if (e.Action == MotionEventActions.Move && mouseDevice.Captured == null)
@ -102,7 +102,7 @@ namespace Avalonia.Android.Platform.Specific.Helpers
(uint)eventTime.Ticks,
inputRoot,
_point,
new Vector(vectorX * correction / ps, vectorY * correction / ps), InputModifiers.LeftMouseButton);
new Vector(vectorX * correction / ps, vectorY * correction / ps), RawInputModifiers.LeftMouseButton);
_view.Input(mouseWheelEvent);
}
_lastTouchMovePoint = _point;

5
src/Avalonia.Base/AvaloniaProperty.cs

@ -493,6 +493,11 @@ namespace Avalonia
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>

74
src/Avalonia.Base/AvaloniaPropertyRegistry.cs

@ -24,8 +24,8 @@ namespace Avalonia
new Dictionary<Type, List<AvaloniaProperty>>();
private readonly Dictionary<Type, List<AvaloniaProperty>> _attachedCache =
new Dictionary<Type, List<AvaloniaProperty>>();
private readonly Dictionary<Type, List<KeyValuePair<AvaloniaProperty, object>>> _initializedCache =
new Dictionary<Type, List<KeyValuePair<AvaloniaProperty, object>>>();
private readonly Dictionary<Type, List<PropertyInitializationData>> _initializedCache =
new Dictionary<Type, List<PropertyInitializationData>>();
/// <summary>
/// Gets the <see cref="AvaloniaPropertyRegistry"/> instance
@ -286,35 +286,73 @@ namespace Avalonia
property.NotifyInitialized(e);
}
if (!_initializedCache.TryGetValue(type, out var items))
if (!_initializedCache.TryGetValue(type, out var initializationData))
{
var build = new Dictionary<AvaloniaProperty, object>();
var visited = new HashSet<AvaloniaProperty>();
foreach (var property in GetRegistered(type))
initializationData = new List<PropertyInitializationData>();
foreach (AvaloniaProperty property in GetRegistered(type))
{
var value = !property.IsDirect ?
((IStyledPropertyAccessor)property).GetDefaultValue(type) :
null;
build.Add(property, value);
if (property.IsDirect)
{
initializationData.Add(new PropertyInitializationData(property, (IDirectPropertyAccessor)property));
}
else
{
initializationData.Add(new PropertyInitializationData(property, (IStyledPropertyAccessor)property, type));
}
visited.Add(property);
}
foreach (var property in GetRegisteredAttached(type))
foreach (AvaloniaProperty property in GetRegisteredAttached(type))
{
if (!build.ContainsKey(property))
if (!visited.Contains(property))
{
var value = ((IStyledPropertyAccessor)property).GetDefaultValue(type);
build.Add(property, value);
initializationData.Add(new PropertyInitializationData(property, (IStyledPropertyAccessor)property, type));
visited.Add(property);
}
}
items = build.ToList();
_initializedCache.Add(type, items);
_initializedCache.Add(type, initializationData);
}
foreach (PropertyInitializationData data in initializationData)
{
if (!data.Property.HasNotifyInitializedObservers)
{
continue;
}
object value = data.IsDirect ? data.DirectAccessor.GetValue(o) : data.Value;
Notify(data.Property, value);
}
}
private readonly struct PropertyInitializationData
{
public AvaloniaProperty Property { get; }
public object Value { get; }
public bool IsDirect { get; }
public IDirectPropertyAccessor DirectAccessor { get; }
public PropertyInitializationData(AvaloniaProperty property, IDirectPropertyAccessor directAccessor)
{
Property = property;
Value = null;
IsDirect = true;
DirectAccessor = directAccessor;
}
foreach (var i in items)
public PropertyInitializationData(AvaloniaProperty property, IStyledPropertyAccessor styledAccessor, Type type)
{
var value = i.Key.IsDirect ? o.GetValue(i.Key) : i.Value;
Notify(i.Key, value);
Property = property;
Value = styledAccessor.GetDefaultValue(type);
IsDirect = false;
DirectAccessor = null;
}
}
}

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

@ -55,15 +55,15 @@ namespace Avalonia.Collections
/// </remarks>
public class AvaloniaList<T> : IAvaloniaList<T>, IList, INotifyCollectionChangedDebug
{
private List<T> _inner;
private readonly List<T> _inner;
private NotifyCollectionChangedEventHandler _collectionChanged;
/// <summary>
/// Initializes a new instance of the <see cref="AvaloniaList{T}"/> class.
/// </summary>
public AvaloniaList()
: this(Enumerable.Empty<T>())
{
_inner = new List<T>();
}
/// <summary>
@ -89,8 +89,8 @@ namespace Avalonia.Collections
/// </summary>
public event NotifyCollectionChangedEventHandler CollectionChanged
{
add { _collectionChanged += value; }
remove { _collectionChanged -= value; }
add => _collectionChanged += value;
remove => _collectionChanged -= value;
}
/// <summary>
@ -150,7 +150,7 @@ namespace Avalonia.Collections
T old = _inner[index];
if (!object.Equals(old, value))
if (!EqualityComparer<T>.Default.Equals(old, value))
{
_inner[index] = value;
@ -187,45 +187,38 @@ namespace Avalonia.Collections
Validate?.Invoke(item);
int index = _inner.Count;
_inner.Add(item);
NotifyAdd(new[] { item }, index);
NotifyAdd(item, index);
}
/// <summary>
/// Adds multiple items to the collection.
/// </summary>
/// <param name="items">The items.</param>
public virtual void AddRange(IEnumerable<T> items)
{
Contract.Requires<ArgumentNullException>(items != null);
var list = (items as IList) ?? items.ToList();
if (list.Count > 0)
{
if (Validate != null)
{
foreach (var item in list)
{
Validate((T)item);
}
}
int index = _inner.Count;
_inner.AddRange(items);
NotifyAdd(list, index);
}
}
public virtual void AddRange(IEnumerable<T> items) => InsertRange(_inner.Count, items);
/// <summary>
/// Removes all items from the collection.
/// </summary>
public virtual void Clear()
{
if (this.Count > 0)
if (Count > 0)
{
var old = _inner;
_inner = new List<T>();
NotifyReset(old);
if (_collectionChanged != null)
{
var e = ResetBehavior == ResetBehavior.Reset ?
EventArgsCache.ResetCollectionChanged :
new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Remove, _inner.ToList(), 0);
_inner.Clear();
_collectionChanged(this, e);
}
else
{
_inner.Clear();
}
NotifyCountChanged();
}
}
@ -253,9 +246,20 @@ namespace Avalonia.Collections
/// Returns an enumerator that enumerates the items in the collection.
/// </summary>
/// <returns>An <see cref="IEnumerator{T}"/>.</returns>
public IEnumerator<T> GetEnumerator()
IEnumerator<T> IEnumerable<T>.GetEnumerator()
{
return _inner.GetEnumerator();
return new Enumerator(_inner);
}
/// <inheritdoc/>
IEnumerator IEnumerable.GetEnumerator()
{
return new Enumerator(_inner);
}
public Enumerator GetEnumerator()
{
return new Enumerator(_inner);
}
/// <summary>
@ -289,7 +293,7 @@ namespace Avalonia.Collections
{
Validate?.Invoke(item);
_inner.Insert(index, item);
NotifyAdd(new[] { item }, index);
NotifyAdd(item, index);
}
/// <summary>
@ -301,20 +305,83 @@ namespace Avalonia.Collections
{
Contract.Requires<ArgumentNullException>(items != null);
var list = (items as IList) ?? items.ToList();
bool willRaiseCollectionChanged = _collectionChanged != null;
bool hasValidation = Validate != null;
if (list.Count > 0)
if (items is IList list)
{
if (Validate != null)
if (list.Count > 0)
{
foreach (var item in list)
if (list is ICollection<T> collection)
{
Validate((T)item);
if (hasValidation)
{
foreach (T item in collection)
{
Validate(item);
}
}
_inner.InsertRange(index, collection);
NotifyAdd(list, index);
}
else
{
using (IEnumerator<T> en = items.GetEnumerator())
{
int insertIndex = index;
while (en.MoveNext())
{
T item = en.Current;
if (hasValidation)
{
Validate(item);
}
_inner.Insert(insertIndex++, item);
}
}
NotifyAdd(list, index);
}
}
}
else
{
using (IEnumerator<T> en = items.GetEnumerator())
{
if (en.MoveNext())
{
// Avoid allocating list for collection notification if there is no event subscriptions.
List<T> notificationItems = willRaiseCollectionChanged ?
new List<T>() :
null;
int insertIndex = index;
do
{
T item = en.Current;
if (hasValidation)
{
Validate(item);
}
_inner.InsertRange(index, items);
NotifyAdd((items as IList) ?? items.ToList(), index);
_inner.Insert(insertIndex++, item);
if (willRaiseCollectionChanged)
{
notificationItems.Add(item);
}
} while (en.MoveNext());
NotifyAdd(notificationItems, index);
}
}
}
}
@ -382,7 +449,7 @@ namespace Avalonia.Collections
if (index != -1)
{
_inner.RemoveAt(index);
NotifyRemove(new[] { item }, index);
NotifyRemove(item , index);
return true;
}
@ -412,7 +479,7 @@ namespace Avalonia.Collections
{
T item = _inner[index];
_inner.RemoveAt(index);
NotifyRemove(new[] { item }, index);
NotifyRemove(item , index);
}
/// <summary>
@ -480,12 +547,6 @@ namespace Avalonia.Collections
_inner.CopyTo((T[])array, index);
}
/// <inheritdoc/>
IEnumerator IEnumerable.GetEnumerator()
{
return _inner.GetEnumerator();
}
/// <inheritdoc/>
Delegate[] INotifyCollectionChangedDebug.GetCollectionChangedSubscribers() => _collectionChanged?.GetInvocationList();
@ -505,13 +566,29 @@ namespace Avalonia.Collections
NotifyCountChanged();
}
/// <summary>
/// Raises the <see cref="CollectionChanged"/> event with a add action.
/// </summary>
/// <param name="item">The item that was added.</param>
/// <param name="index">The starting index.</param>
private void NotifyAdd(T item, int index)
{
if (_collectionChanged != null)
{
var e = new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, new[] { item }, index);
_collectionChanged(this, e);
}
NotifyCountChanged();
}
/// <summary>
/// Raises the <see cref="PropertyChanged"/> event when the <see cref="Count"/> property
/// changes.
/// </summary>
private void NotifyCountChanged()
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(Count)));
PropertyChanged?.Invoke(this, EventArgsCache.CountPropertyChanged);
}
/// <summary>
@ -531,23 +608,57 @@ namespace Avalonia.Collections
}
/// <summary>
/// Raises the <see cref="CollectionChanged"/> event with a reset action.
/// Raises the <see cref="CollectionChanged"/> event with a remove action.
/// </summary>
/// <param name="t">The items that were removed.</param>
private void NotifyReset(IList t)
/// <param name="item">The item that was removed.</param>
/// <param name="index">The starting index.</param>
private void NotifyRemove(T item, int index)
{
if (_collectionChanged != null)
{
NotifyCollectionChangedEventArgs e;
e = ResetBehavior == ResetBehavior.Reset ?
new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset) :
new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Remove, t, 0);
var e = new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Remove, new[] { item }, index);
_collectionChanged(this, e);
}
NotifyCountChanged();
}
/// <summary>
/// Enumerates the elements of a <see cref="AvaloniaList{T}"/>.
/// </summary>
public struct Enumerator : IEnumerator<T>
{
private List<T>.Enumerator _innerEnumerator;
public Enumerator(List<T> inner)
{
_innerEnumerator = inner.GetEnumerator();
}
public bool MoveNext()
{
return _innerEnumerator.MoveNext();
}
void IEnumerator.Reset()
{
((IEnumerator)_innerEnumerator).Reset();
}
public T Current => _innerEnumerator.Current;
object IEnumerator.Current => Current;
public void Dispose()
{
_innerEnumerator.Dispose();
}
}
}
internal static class EventArgsCache
{
internal static readonly PropertyChangedEventArgs CountPropertyChanged = new PropertyChangedEventArgs(nameof(AvaloniaList<object>.Count));
internal static readonly NotifyCollectionChangedEventArgs ResetCollectionChanged = new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset);
}
}

6
src/Avalonia.Base/Data/Core/AvaloniaPropertyAccessorNode.cs

@ -24,7 +24,7 @@ namespace Avalonia.Data.Core
{
try
{
if (Target.IsAlive && Target.Target is IAvaloniaObject obj)
if (Target.TryGetTarget(out object target) && target is IAvaloniaObject obj)
{
obj.SetValue(_property, value, priority);
return true;
@ -37,9 +37,9 @@ namespace Avalonia.Data.Core
}
}
protected override void StartListeningCore(WeakReference reference)
protected override void StartListeningCore(WeakReference<object> reference)
{
if (reference.Target is IAvaloniaObject obj)
if (reference.TryGetTarget(out object target) && target is IAvaloniaObject obj)
{
_subscription = new AvaloniaPropertyObservable<object>(obj, _property).Subscribe(ValueChanged);
}

26
src/Avalonia.Base/Data/Core/ExpressionNode.cs

@ -8,27 +8,27 @@ namespace Avalonia.Data.Core
public abstract class ExpressionNode
{
private static readonly object CacheInvalid = new object();
protected static readonly WeakReference UnsetReference =
new WeakReference(AvaloniaProperty.UnsetValue);
protected static readonly WeakReference<object> UnsetReference =
new WeakReference<object>(AvaloniaProperty.UnsetValue);
private WeakReference _target = UnsetReference;
private WeakReference<object> _target = UnsetReference;
private Action<object> _subscriber;
private bool _listening;
protected WeakReference LastValue { get; private set; }
protected WeakReference<object> LastValue { get; private set; }
public abstract string Description { get; }
public ExpressionNode Next { get; set; }
public WeakReference Target
public WeakReference<object> Target
{
get { return _target; }
set
{
Contract.Requires<ArgumentNullException>(value != null);
var oldTarget = _target?.Target;
var newTarget = value.Target;
_target.TryGetTarget(out var oldTarget);
value.TryGetTarget(out object newTarget);
if (!ReferenceEquals(oldTarget, newTarget))
{
@ -72,9 +72,11 @@ namespace Avalonia.Data.Core
_subscriber = null;
}
protected virtual void StartListeningCore(WeakReference reference)
protected virtual void StartListeningCore(WeakReference<object> reference)
{
ValueChanged(reference.Target);
reference.TryGetTarget(out object target);
ValueChanged(target);
}
protected virtual void StopListeningCore()
@ -96,7 +98,7 @@ namespace Avalonia.Data.Core
if (notification == null)
{
LastValue = new WeakReference(value);
LastValue = new WeakReference<object>(value);
if (Next != null)
{
@ -109,7 +111,7 @@ namespace Avalonia.Data.Core
}
else
{
LastValue = new WeakReference(notification.Value);
LastValue = new WeakReference<object>(notification.Value);
if (Next != null)
{
@ -125,7 +127,7 @@ namespace Avalonia.Data.Core
private void StartListening()
{
var target = _target.Target;
_target.TryGetTarget(out object target);
if (target == null)
{

8
src/Avalonia.Base/Data/Core/ExpressionObserver.cs

@ -79,7 +79,7 @@ namespace Avalonia.Data.Core
_node = node;
Description = description;
_root = new WeakReference(root);
_root = new WeakReference<object>(root);
}
/// <summary>
@ -121,7 +121,7 @@ namespace Avalonia.Data.Core
Contract.Requires<ArgumentNullException>(update != null);
Description = description;
_node = node;
_node.Target = new WeakReference(rootGetter());
_node.Target = new WeakReference<object>(rootGetter());
_root = update.Select(x => rootGetter());
}
@ -299,13 +299,13 @@ namespace Avalonia.Data.Core
if (_root is IObservable<object> observable)
{
_rootSubscription = observable.Subscribe(
x => _node.Target = new WeakReference(x != AvaloniaProperty.UnsetValue ? x : null),
x => _node.Target = new WeakReference<object>(x != AvaloniaProperty.UnsetValue ? x : null),
x => PublishCompleted(),
() => PublishCompleted());
}
else
{
_node.Target = (WeakReference)_root;
_node.Target = (WeakReference<object>)_root;
}
}

11
src/Avalonia.Base/Data/Core/IndexerExpressionNode.cs

@ -36,7 +36,9 @@ namespace Avalonia.Data.Core
{
try
{
_setDelegate.DynamicInvoke(Target.Target, value);
Target.TryGetTarget(out object target);
_setDelegate.DynamicInvoke(target, value);
return true;
}
catch (Exception)
@ -64,6 +66,11 @@ namespace Avalonia.Data.Core
return _expression.Indexer == null || _expression.Indexer.Name == e.PropertyName;
}
protected override int? TryGetFirstArgumentAsInt() => _firstArgumentDelegate.DynamicInvoke(Target.Target) as int?;
protected override int? TryGetFirstArgumentAsInt()
{
Target.TryGetTarget(out object target);
return _firstArgumentDelegate.DynamicInvoke(target) as int?;
}
}
}

5
src/Avalonia.Base/Data/Core/IndexerNodeBase.cs

@ -13,9 +13,10 @@ namespace Avalonia.Data.Core
{
private IDisposable _subscription;
protected override void StartListeningCore(WeakReference reference)
protected override void StartListeningCore(WeakReference<object> reference)
{
var target = reference.Target;
reference.TryGetTarget(out object target);
var incc = target as INotifyCollectionChanged;
var inpc = target as INotifyPropertyChanged;
var inputs = new List<IObservable<object>>();

4
src/Avalonia.Base/Data/Core/Plugins/AvaloniaPropertyAccessorPlugin.cs

@ -31,12 +31,12 @@ namespace Avalonia.Data.Core.Plugins
/// An <see cref="IPropertyAccessor"/> interface through which future interactions with the
/// property will be made.
/// </returns>
public IPropertyAccessor Start(WeakReference reference, string propertyName)
public IPropertyAccessor Start(WeakReference<object> reference, string propertyName)
{
Contract.Requires<ArgumentNullException>(reference != null);
Contract.Requires<ArgumentNullException>(propertyName != null);
var instance = reference.Target;
reference.TryGetTarget(out object instance);
var o = (AvaloniaObject)instance;
var p = LookupProperty(o, propertyName);

23
src/Avalonia.Base/Data/Core/Plugins/DataAnnotationsValidationPlugin.cs

@ -15,9 +15,11 @@ namespace Avalonia.Data.Core.Plugins
public class DataAnnotationsValidationPlugin : IDataValidationPlugin
{
/// <inheritdoc/>
public bool Match(WeakReference reference, string memberName)
public bool Match(WeakReference<object> reference, string memberName)
{
return reference.Target?
reference.TryGetTarget(out object target);
return target?
.GetType()
.GetRuntimeProperty(memberName)?
.GetCustomAttributes<ValidationAttribute>()
@ -25,25 +27,22 @@ namespace Avalonia.Data.Core.Plugins
}
/// <inheritdoc/>
public IPropertyAccessor Start(WeakReference reference, string name, IPropertyAccessor inner)
public IPropertyAccessor Start(WeakReference<object> reference, string name, IPropertyAccessor inner)
{
return new Accessor(reference, name, inner);
}
private class Accessor : DataValidationBase
private sealed class Accessor : DataValidationBase
{
private ValidationContext _context;
private readonly ValidationContext _context;
public Accessor(WeakReference reference, string name, IPropertyAccessor inner)
public Accessor(WeakReference<object> reference, string name, IPropertyAccessor inner)
: base(inner)
{
_context = new ValidationContext(reference.Target);
_context.MemberName = name;
}
reference.TryGetTarget(out object target);
public override bool SetValue(object value, BindingPriority priority)
{
return base.SetValue(value, priority);
_context = new ValidationContext(target);
_context.MemberName = name;
}
protected override void InnerValueChanged(object value)

8
src/Avalonia.Base/Data/Core/Plugins/ExceptionValidationPlugin.cs

@ -12,17 +12,17 @@ namespace Avalonia.Data.Core.Plugins
public class ExceptionValidationPlugin : IDataValidationPlugin
{
/// <inheritdoc/>
public bool Match(WeakReference reference, string memberName) => true;
public bool Match(WeakReference<object> reference, string memberName) => true;
/// <inheritdoc/>
public IPropertyAccessor Start(WeakReference reference, string name, IPropertyAccessor inner)
public IPropertyAccessor Start(WeakReference<object> reference, string name, IPropertyAccessor inner)
{
return new Validator(reference, name, inner);
}
private class Validator : DataValidationBase
private sealed class Validator : DataValidationBase
{
public Validator(WeakReference reference, string name, IPropertyAccessor inner)
public Validator(WeakReference<object> reference, string name, IPropertyAccessor inner)
: base(inner)
{
}

5
src/Avalonia.Base/Data/Core/Plugins/IDataValidationPlugin.cs

@ -16,7 +16,7 @@ namespace Avalonia.Data.Core.Plugins
/// <param name="reference">A weak reference to the object.</param>
/// <param name="memberName">The name of the member to validate.</param>
/// <returns>True if the plugin can handle the object; otherwise false.</returns>
bool Match(WeakReference reference, string memberName);
bool Match(WeakReference<object> reference, string memberName);
/// <summary>
/// Starts monitoring the data validation state of a property on an object.
@ -28,8 +28,7 @@ namespace Avalonia.Data.Core.Plugins
/// An <see cref="IPropertyAccessor"/> interface through which future interactions with the
/// property will be made.
/// </returns>
IPropertyAccessor Start(
WeakReference reference,
IPropertyAccessor Start(WeakReference<object> reference,
string propertyName,
IPropertyAccessor inner);
}

3
src/Avalonia.Base/Data/Core/Plugins/IPropertyAccessorPlugin.cs

@ -28,8 +28,7 @@ namespace Avalonia.Data.Core.Plugins
/// An <see cref="IPropertyAccessor"/> interface through which future interactions with the
/// property will be made.
/// </returns>
IPropertyAccessor Start(
WeakReference reference,
IPropertyAccessor Start(WeakReference<object> reference,
string propertyName);
}
}

4
src/Avalonia.Base/Data/Core/Plugins/IStreamPlugin.cs

@ -15,7 +15,7 @@ namespace Avalonia.Data.Core.Plugins
/// </summary>
/// <param name="reference">A weak reference to the value.</param>
/// <returns>True if the plugin can handle the value; otherwise false.</returns>
bool Match(WeakReference reference);
bool Match(WeakReference<object> reference);
/// <summary>
/// Starts producing output based on the specified value.
@ -24,6 +24,6 @@ namespace Avalonia.Data.Core.Plugins
/// <returns>
/// An observable that produces the output for the value.
/// </returns>
IObservable<object> Start(WeakReference reference);
IObservable<object> Start(WeakReference<object> reference);
}
}

39
src/Avalonia.Base/Data/Core/Plugins/IndeiValidationPlugin.cs

@ -15,20 +15,25 @@ namespace Avalonia.Data.Core.Plugins
public class IndeiValidationPlugin : IDataValidationPlugin
{
/// <inheritdoc/>
public bool Match(WeakReference reference, string memberName) => reference.Target is INotifyDataErrorInfo;
public bool Match(WeakReference<object> reference, string memberName)
{
reference.TryGetTarget(out object target);
return target is INotifyDataErrorInfo;
}
/// <inheritdoc/>
public IPropertyAccessor Start(WeakReference reference, string name, IPropertyAccessor accessor)
public IPropertyAccessor Start(WeakReference<object> reference, string name, IPropertyAccessor accessor)
{
return new Validator(reference, name, accessor);
}
private class Validator : DataValidationBase, IWeakSubscriber<DataErrorsChangedEventArgs>
{
WeakReference _reference;
string _name;
private readonly WeakReference<object> _reference;
private readonly string _name;
public Validator(WeakReference reference, string name, IPropertyAccessor inner)
public Validator(WeakReference<object> reference, string name, IPropertyAccessor inner)
: base(inner)
{
_reference = reference;
@ -45,7 +50,7 @@ namespace Avalonia.Data.Core.Plugins
protected override void SubscribeCore()
{
var target = _reference.Target as INotifyDataErrorInfo;
var target = GetReferenceTarget() as INotifyDataErrorInfo;
if (target != null)
{
@ -60,7 +65,7 @@ namespace Avalonia.Data.Core.Plugins
protected override void UnsubscribeCore()
{
var target = _reference.Target as INotifyDataErrorInfo;
var target = GetReferenceTarget() as INotifyDataErrorInfo;
if (target != null)
{
@ -80,13 +85,14 @@ namespace Avalonia.Data.Core.Plugins
private BindingNotification CreateBindingNotification(object value)
{
var target = (INotifyDataErrorInfo)_reference.Target;
var target = (INotifyDataErrorInfo)GetReferenceTarget();
if (target != null)
{
var errors = target.GetErrors(_name)?
.Cast<String>()
.Where(x => x != null).ToList();
.Cast<object>()
.Where(x => x != null)
.ToList();
if (errors?.Count > 0)
{
@ -100,16 +106,23 @@ namespace Avalonia.Data.Core.Plugins
return new BindingNotification(value);
}
private Exception GenerateException(IList<string> errors)
private object GetReferenceTarget()
{
_reference.TryGetTarget(out object target);
return target;
}
private Exception GenerateException(IList<object> errors)
{
if (errors.Count == 1)
{
return new Exception(errors[0]);
return new DataValidationException(errors[0]);
}
else
{
return new AggregateException(
errors.Select(x => new Exception(x)));
errors.Select(x => new DataValidationException(x)));
}
}
}

23
src/Avalonia.Base/Data/Core/Plugins/InpcPropertyAccessorPlugin.cs

@ -28,12 +28,12 @@ namespace Avalonia.Data.Core.Plugins
/// An <see cref="IPropertyAccessor"/> interface through which future interactions with the
/// property will be made.
/// </returns>
public IPropertyAccessor Start(WeakReference reference, string propertyName)
public IPropertyAccessor Start(WeakReference<object> reference, string propertyName)
{
Contract.Requires<ArgumentNullException>(reference != null);
Contract.Requires<ArgumentNullException>(propertyName != null);
var instance = reference.Target;
reference.TryGetTarget(out object instance);
var p = instance.GetType().GetRuntimeProperties().FirstOrDefault(x => x.Name == propertyName);
if (p != null)
@ -50,11 +50,11 @@ namespace Avalonia.Data.Core.Plugins
private class Accessor : PropertyAccessorBase, IWeakSubscriber<PropertyChangedEventArgs>
{
private readonly WeakReference _reference;
private readonly WeakReference<object> _reference;
private readonly PropertyInfo _property;
private bool _eventRaised;
public Accessor(WeakReference reference, PropertyInfo property)
public Accessor(WeakReference<object> reference, PropertyInfo property)
{
Contract.Requires<ArgumentNullException>(reference != null);
Contract.Requires<ArgumentNullException>(property != null);
@ -69,7 +69,7 @@ namespace Avalonia.Data.Core.Plugins
{
get
{
var o = _reference.Target;
var o = GetReferenceTarget();
return (o != null) ? _property.GetValue(o) : null;
}
}
@ -79,7 +79,7 @@ namespace Avalonia.Data.Core.Plugins
if (_property.CanWrite)
{
_eventRaised = false;
_property.SetValue(_reference.Target, value);
_property.SetValue(GetReferenceTarget(), value);
if (!_eventRaised)
{
@ -109,7 +109,7 @@ namespace Avalonia.Data.Core.Plugins
protected override void UnsubscribeCore()
{
var inpc = _reference.Target as INotifyPropertyChanged;
var inpc = GetReferenceTarget() as INotifyPropertyChanged;
if (inpc != null)
{
@ -120,6 +120,13 @@ namespace Avalonia.Data.Core.Plugins
}
}
private object GetReferenceTarget()
{
_reference.TryGetTarget(out object target);
return target;
}
private void SendCurrentValue()
{
try
@ -132,7 +139,7 @@ namespace Avalonia.Data.Core.Plugins
private void SubscribeToChanges()
{
var inpc = _reference.Target as INotifyPropertyChanged;
var inpc = GetReferenceTarget() as INotifyPropertyChanged;
if (inpc != null)
{

21
src/Avalonia.Base/Data/Core/Plugins/MethodAccessorPlugin.cs

@ -9,12 +9,12 @@ namespace Avalonia.Data.Core.Plugins
public bool Match(object obj, string methodName)
=> obj.GetType().GetRuntimeMethods().Any(x => x.Name == methodName);
public IPropertyAccessor Start(WeakReference reference, string methodName)
public IPropertyAccessor Start(WeakReference<object> reference, string methodName)
{
Contract.Requires<ArgumentNullException>(reference != null);
Contract.Requires<ArgumentNullException>(methodName != null);
var instance = reference.Target;
reference.TryGetTarget(out object instance);
var method = instance.GetType().GetRuntimeMethods().FirstOrDefault(x => x.Name == methodName);
if (method != null)
@ -35,9 +35,9 @@ namespace Avalonia.Data.Core.Plugins
}
}
private class Accessor : PropertyAccessorBase
private sealed class Accessor : PropertyAccessorBase
{
public Accessor(WeakReference reference, MethodInfo method)
public Accessor(WeakReference<object> reference, MethodInfo method)
{
Contract.Requires<ArgumentNullException>(reference != null);
Contract.Requires<ArgumentNullException>(method != null);
@ -61,8 +61,17 @@ namespace Avalonia.Data.Core.Plugins
var genericTypeParameters = paramTypes.Concat(new[] { returnType }).ToArray();
PropertyType = Type.GetType($"System.Func`{genericTypeParameters.Length}").MakeGenericType(genericTypeParameters);
}
Value = method.IsStatic ? method.CreateDelegate(PropertyType) : method.CreateDelegate(PropertyType, reference.Target);
if (method.IsStatic)
{
Value = method.CreateDelegate(PropertyType);
}
else
{
reference.TryGetTarget(out object target);
Value = method.CreateDelegate(PropertyType, target);
}
}
public override Type PropertyType { get; }

12
src/Avalonia.Base/Data/Core/Plugins/ObservableStreamPlugin.cs

@ -20,9 +20,11 @@ namespace Avalonia.Data.Core.Plugins
/// </summary>
/// <param name="reference">A weak reference to the value.</param>
/// <returns>True if the plugin can handle the value; otherwise false.</returns>
public virtual bool Match(WeakReference reference)
public virtual bool Match(WeakReference<object> reference)
{
return reference.Target.GetType().GetInterfaces().Any(x =>
reference.TryGetTarget(out object target);
return target != null && target.GetType().GetInterfaces().Any(x =>
x.IsGenericType &&
x.GetGenericTypeDefinition() == typeof(IObservable<>));
}
@ -34,9 +36,9 @@ namespace Avalonia.Data.Core.Plugins
/// <returns>
/// An observable that produces the output for the value.
/// </returns>
public virtual IObservable<object> Start(WeakReference reference)
public virtual IObservable<object> Start(WeakReference<object> reference)
{
var target = reference.Target;
reference.TryGetTarget(out object target);
// If the observable returns a reference type then we can cast it.
if (target is IObservable<object> result)
@ -46,7 +48,7 @@ namespace Avalonia.Data.Core.Plugins
// If the observable returns a value type then we need to call Observable.Select on it.
// First get the type of T in `IObservable<T>`.
var sourceType = reference.Target.GetType().GetInterfaces().First(x =>
var sourceType = target.GetType().GetInterfaces().First(x =>
x.IsGenericType &&
x.GetGenericTypeDefinition() == typeof(IObservable<>)).GetGenericArguments()[0];

13
src/Avalonia.Base/Data/Core/Plugins/TaskStreamPlugin.cs

@ -19,7 +19,12 @@ namespace Avalonia.Data.Core.Plugins
/// </summary>
/// <param name="reference">A weak reference to the value.</param>
/// <returns>True if the plugin can handle the value; otherwise false.</returns>
public virtual bool Match(WeakReference reference) => reference.Target is Task;
public virtual bool Match(WeakReference<object> reference)
{
reference.TryGetTarget(out object target);
return target is Task;
}
/// <summary>
/// Starts producing output based on the specified value.
@ -28,11 +33,11 @@ namespace Avalonia.Data.Core.Plugins
/// <returns>
/// An observable that produces the output for the value.
/// </returns>
public virtual IObservable<object> Start(WeakReference reference)
public virtual IObservable<object> Start(WeakReference<object> reference)
{
var task = reference.Target as Task;
reference.TryGetTarget(out object target);
if (task != null)
if (target is Task task)
{
var resultProperty = task.GetType().GetRuntimeProperty("Result");

6
src/Avalonia.Base/Data/Core/PropertyAccessorNode.cs

@ -45,9 +45,11 @@ namespace Avalonia.Data.Core
return false;
}
protected override void StartListeningCore(WeakReference reference)
protected override void StartListeningCore(WeakReference<object> reference)
{
var plugin = _customPlugin ?? GetPropertyAccessorPluginForObject(reference.Target);
reference.TryGetTarget(out object target);
var plugin = _customPlugin ?? GetPropertyAccessorPluginForObject(target);
var accessor = plugin?.Start(reference, PropertyName);
if (_enableValidation && Next == null)

18
src/Avalonia.Base/Data/Core/SettableNode.cs

@ -19,11 +19,25 @@ namespace Avalonia.Data.Core
{
return false;
}
if (LastValue == null)
{
return false;
}
bool isLastValueAlive = LastValue.TryGetTarget(out object lastValue);
if (!isLastValueAlive)
{
return false;
}
if (PropertyType.IsValueType)
{
return LastValue?.Target != null && LastValue.Target.Equals(value);
return lastValue.Equals(value);
}
return LastValue != null && Object.ReferenceEquals(LastValue?.Target, value);
return ReferenceEquals(lastValue, value);
}
protected abstract bool SetTargetValueCore(object value, BindingPriority priority);

4
src/Avalonia.Base/Data/Core/StreamNode.cs

@ -21,7 +21,7 @@ namespace Avalonia.Data.Core
_customPlugin = customPlugin;
}
protected override void StartListeningCore(WeakReference reference)
protected override void StartListeningCore(WeakReference<object> reference)
{
GetPlugin(reference)?.Start(reference).Subscribe(ValueChanged);
}
@ -32,7 +32,7 @@ namespace Avalonia.Data.Core
_subscription = null;
}
private IStreamPlugin GetPlugin(WeakReference reference)
private IStreamPlugin GetPlugin(WeakReference<object> reference)
{
if (_customPlugin != null)
{

21
src/Avalonia.Base/Data/DataValidationException.cs

@ -0,0 +1,21 @@
using System;
namespace Avalonia.Data
{
/// <summary>
/// Exception, which wrap validation errors.
/// </summary>
public class DataValidationException : Exception
{
/// <summary>
/// Initializes a new instance of the <see cref="DataValidationException"/> class.
/// </summary>
/// <param name="errorData">Data of validation error.</param>
public DataValidationException(object errorData) : base(errorData?.ToString())
{
ErrorData = errorData;
}
public object ErrorData { get; }
}
}

11
src/Avalonia.Controls/AppBuilderBase.cs

@ -2,6 +2,7 @@
// Licensed under the MIT license. See licence.md file in the project root for full license information.
using System;
using System.Collections.Generic;
using System.Reflection;
using System.Linq;
using Avalonia.Controls.ApplicationLifetimes;
@ -59,6 +60,8 @@ namespace Avalonia.Controls
public Action<TAppBuilder> AfterSetupCallback { get; private set; } = builder => { };
public Action<TAppBuilder> AfterPlatformServicesSetupCallback { get; private set; } = builder => { };
protected AppBuilderBase(IRuntimePlatform platform, Action<TAppBuilder> platformServices)
{
RuntimePlatform = platform;
@ -97,6 +100,13 @@ namespace Avalonia.Controls
AfterSetupCallback = (Action<TAppBuilder>)Delegate.Combine(AfterSetupCallback, callback);
return Self;
}
public TAppBuilder AfterPlatformServicesSetup(Action<TAppBuilder> callback)
{
AfterPlatformServicesSetupCallback = (Action<TAppBuilder>)Delegate.Combine(AfterPlatformServicesSetupCallback, callback);
return Self;
}
/// <summary>
/// Starts the application with an instance of <typeparamref name="TMainWindow"/>.
@ -274,6 +284,7 @@ namespace Avalonia.Controls
RuntimePlatformServicesInitializer();
WindowingSubsystemInitializer();
RenderingSubsystemInitializer();
AfterPlatformServicesSetupCallback(Self);
Instance.RegisterServices();
Instance.Initialize();
AfterSetupCallback(Self);

2
src/Avalonia.Controls/ComboBox.cs

@ -202,7 +202,7 @@ namespace Avalonia.Controls
{
if (!e.Handled)
{
if (_popup?.PopupRoot != null && ((IVisual)e.Source).GetVisualRoot() == _popup?.PopupRoot)
if (_popup?.IsInsidePopup((IVisual)e.Source) == true)
{
if (UpdateSelectionFromEventSource(e.Source))
{

2
src/Avalonia.Controls/ContextMenu.cs

@ -91,6 +91,8 @@ namespace Avalonia.Controls
/// <param name="control">The control.</param>
public void Open(Control control)
{
if (control == null)
throw new ArgumentNullException(nameof(control));
if (IsOpen)
{
return;

15
src/Avalonia.Controls/ControlExtensions.cs

@ -34,14 +34,17 @@ namespace Avalonia.Controls
{
Contract.Requires<ArgumentNullException>(control != null);
var ev = new RequestBringIntoViewEventArgs
if (control.IsEffectivelyVisible)
{
RoutedEvent = Control.RequestBringIntoViewEvent,
TargetObject = control,
TargetRect = rect,
};
var ev = new RequestBringIntoViewEventArgs
{
RoutedEvent = Control.RequestBringIntoViewEvent,
TargetObject = control,
TargetRect = rect,
};
control.RaiseEvent(ev);
control.RaiseEvent(ev);
}
}
/// <summary>

24
src/Avalonia.Controls/DataValidationErrors.cs

@ -22,8 +22,8 @@ namespace Avalonia.Controls
/// <summary>
/// Defines the DataValidationErrors.Errors attached property.
/// </summary>
public static readonly AttachedProperty<IEnumerable<Exception>> ErrorsProperty =
AvaloniaProperty.RegisterAttached<DataValidationErrors, Control, IEnumerable<Exception>>("Errors");
public static readonly AttachedProperty<IEnumerable<object>> ErrorsProperty =
AvaloniaProperty.RegisterAttached<DataValidationErrors, Control, IEnumerable<object>>("Errors");
/// <summary>
/// Defines the DataValidationErrors.HasErrors attached property.
@ -76,7 +76,7 @@ namespace Avalonia.Controls
private static void ErrorsChanged(AvaloniaPropertyChangedEventArgs e)
{
var control = (Control)e.Sender;
var errors = (IEnumerable<Exception>)e.NewValue;
var errors = (IEnumerable<object>)e.NewValue;
var hasErrors = false;
if (errors != null && errors.Any())
@ -91,11 +91,11 @@ namespace Avalonia.Controls
classes.Set(":error", (bool)e.NewValue);
}
public static IEnumerable<Exception> GetErrors(Control control)
public static IEnumerable<object> GetErrors(Control control)
{
return control.GetValue(ErrorsProperty);
}
public static void SetErrors(Control control, IEnumerable<Exception> errors)
public static void SetErrors(Control control, IEnumerable<object> errors)
{
control.SetValue(ErrorsProperty, errors);
}
@ -112,14 +112,14 @@ namespace Avalonia.Controls
return control.GetValue(HasErrorsProperty);
}
private static IEnumerable<Exception> UnpackException(Exception exception)
private static IEnumerable<object> UnpackException(Exception exception)
{
if (exception != null)
{
var aggregate = exception as AggregateException;
var exceptions = aggregate == null ?
(IEnumerable<Exception>)new[] { exception } :
aggregate.InnerExceptions;
new[] { GetExceptionData(exception) } :
aggregate.InnerExceptions.Select(GetExceptionData).ToArray();
var filtered = exceptions.Where(x => !(x is BindingChainException)).ToList();
if (filtered.Count > 0)
@ -130,5 +130,13 @@ namespace Avalonia.Controls
return null;
}
private static object GetExceptionData(Exception exception)
{
if (exception is DataValidationException dataValidationException)
return dataValidationException.ErrorData;
return exception;
}
}
}

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

@ -61,5 +61,6 @@ namespace Avalonia.Controls.Embedding.Offscreen
public Action Closed { get; set; }
public abstract IMouseDevice MouseDevice { get; }
public IPopupImpl CreatePopup() => null;
}
}

6
src/Avalonia.Controls/Generators/ItemContainerGenerator.cs

@ -128,6 +128,12 @@ namespace Avalonia.Controls.Generators
}
Dematerialized?.Invoke(this, new ItemContainerEventArgs(startingIndex, result));
if (toMove.Count > 0)
{
var containers = toMove.Select(x => x.Value).ToList();
Recycled?.Invoke(this, new ItemContainerEventArgs(containers[0].Index, containers));
}
}
return result;

6
src/Avalonia.Controls/MenuItem.cs

@ -224,7 +224,7 @@ namespace Avalonia.Controls
public bool IsTopLevel => Parent is Menu;
/// <inheritdoc/>
bool IMenuItem.IsPointerOverSubMenu => _popup.PopupRoot?.IsPointerOver ?? false;
bool IMenuItem.IsPointerOverSubMenu => _popup?.IsPointerOverPopup ?? false;
/// <inheritdoc/>
IMenuElement IMenuItem.Parent => Parent as IMenuElement;
@ -339,7 +339,7 @@ namespace Avalonia.Controls
var point = e.GetPointerPoint(null);
RaiseEvent(new PointerEventArgs(PointerEnterItemEvent, this, e.Pointer, this.VisualRoot, point.Position,
e.Timestamp, point.Properties, e.InputModifiers));
e.Timestamp, point.Properties, e.KeyModifiers));
}
/// <inheritdoc/>
@ -349,7 +349,7 @@ namespace Avalonia.Controls
var point = e.GetPointerPoint(null);
RaiseEvent(new PointerEventArgs(PointerLeaveItemEvent, this, e.Pointer, this.VisualRoot, point.Position,
e.Timestamp, point.Properties, e.InputModifiers));
e.Timestamp, point.Properties, e.KeyModifiers));
}
/// <summary>

1
src/Avalonia.Controls/Mixins/ContentControlMixin.cs

@ -150,6 +150,7 @@ namespace Avalonia.Controls.Mixins
if (oldValue is IControl child)
{
logicalChildren.Remove(child);
((ISetInheritanceParent)child).SetParent(child.Parent);
}
child = newValue as IControl;

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

@ -150,7 +150,7 @@ namespace Avalonia.Controls.Notifications
private void Install(Window host)
{
var adornerLayer = host.GetVisualDescendants()
.OfType<AdornerDecorator>()
.OfType<VisualLayerManager>()
.FirstOrDefault()
?.AdornerLayer;

19
src/Avalonia.Controls/PlacementMode.cs

@ -23,6 +23,21 @@ namespace Avalonia.Controls
/// <summary>
/// The popup is placed at the top right of its target.
/// </summary>
Right
Right,
/// <summary>
/// The popup is placed at the top left of its target.
/// </summary>
Left,
/// <summary>
/// The popup is placed at the top left of its target.
/// </summary>
Top,
/// <summary>
/// The popup is placed according to anchor and gravity rules
/// </summary>
AnchorAndGravity
}
}
}

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

@ -396,7 +396,7 @@ namespace Avalonia.Controls.Platform
protected internal virtual void WindowDeactivated(object sender, EventArgs e)
{
Menu.Close();
Menu?.Close();
}
protected void Click(IMenuItem item)

23
src/Avalonia.Controls/Platform/IMountedVolumeInfoProvider.cs

@ -0,0 +1,23 @@
// 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.Collections.ObjectModel;
using System.Threading.Tasks;
using Avalonia.Platform;
namespace Avalonia.Controls.Platform
{
/// <summary>
/// Defines a platform-specific mount volumes info provider implementation.
/// </summary>
public interface IMountedVolumeInfoProvider
{
/// <summary>
/// Listens to any changes in volume mounts and
/// forwards updates to the referenced
/// <see cref="ObservableCollection{MountedDriveInfo}"/>.
/// </summary>
IDisposable Listen(ObservableCollection<MountedVolumeInfo> mountedDrives);
}
}

4
src/Avalonia.Controls/Platform/IPopupImpl.cs

@ -1,6 +1,8 @@
// 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 Avalonia.Controls.Primitives.PopupPositioning;
namespace Avalonia.Platform
{
/// <summary>
@ -8,6 +10,6 @@ namespace Avalonia.Platform
/// </summary>
public interface IPopupImpl : IWindowBaseImpl
{
IPopupPositioner PopupPositioner { get; }
}
}

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

@ -107,5 +107,7 @@ namespace Avalonia.Platform
/// </summary>
[CanBeNull]
IMouseDevice MouseDevice { get; }
IPopupImpl CreatePopup();
}
}

24
src/Avalonia.Controls/Platform/IWindowBaseImpl.cs

@ -15,21 +15,10 @@ namespace Avalonia.Platform
/// </summary>
void Hide();
/// <summary>
/// Starts moving a window with left button being held. Should be called from left mouse button press event handler.
/// </summary>
void BeginMoveDrag();
/// <summary>
/// Starts resizing a window. This function is used if an application has window resizing controls.
/// Should be called from left mouse button press event handler
/// </summary>
void BeginResizeDrag(WindowEdge edge);
/// <summary>
/// Gets the position of the window in device pixels.
/// </summary>
PixelPoint Position { get; set; }
PixelPoint Position { get; }
/// <summary>
/// Gets or sets a method called when the window's position changes.
@ -61,17 +50,6 @@ namespace Avalonia.Platform
/// </summary>
Size MaxClientSize { get; }
/// <summary>
/// Sets the client size of the top level.
/// </summary>
void Resize(Size clientSize);
/// <summary>
/// Minimum width of the window.
/// </summary>
///
void SetMinMaxSize(Size minSize, Size maxSize);
/// <summary>
/// Sets whether this window appears on top of all other windows
/// </summary>

27
src/Avalonia.Controls/Platform/IWindowImpl.cs

@ -57,5 +57,32 @@ namespace Avalonia.Platform
/// Return true to prevent the underlying implementation from closing.
/// </summary>
Func<bool> Closing { get; set; }
/// <summary>
/// Starts moving a window with left button being held. Should be called from left mouse button press event handler.
/// </summary>
void BeginMoveDrag();
/// <summary>
/// Starts resizing a window. This function is used if an application has window resizing controls.
/// Should be called from left mouse button press event handler
/// </summary>
void BeginResizeDrag(WindowEdge edge);
/// <summary>
/// Sets the client size of the top level.
/// </summary>
void Resize(Size clientSize);
/// <summary>
/// Sets the client size of the top level.
/// </summary>
void Move(PixelPoint point);
/// <summary>
/// Minimum width of the window.
/// </summary>
///
void SetMinMaxSize(Size minSize, Size maxSize);
}
}

1
src/Avalonia.Controls/Platform/IWindowingPlatform.cs

@ -4,6 +4,5 @@ namespace Avalonia.Platform
{
IWindowImpl CreateWindow();
IEmbeddableWindowImpl CreateEmbeddableWindow();
IPopupImpl CreatePopup();
}
}

26
src/Avalonia.Controls/Platform/InProcessDragSource.cs

@ -14,7 +14,7 @@ namespace Avalonia.Platform
{
class InProcessDragSource : IPlatformDragSource
{
private const InputModifiers MOUSE_INPUTMODIFIERS = InputModifiers.LeftMouseButton|InputModifiers.MiddleMouseButton|InputModifiers.RightMouseButton;
private const RawInputModifiers MOUSE_INPUTMODIFIERS = RawInputModifiers.LeftMouseButton|RawInputModifiers.MiddleMouseButton|RawInputModifiers.RightMouseButton;
private readonly IDragDropDevice _dragDrop;
private readonly IInputManager _inputManager;
private readonly Subject<DragDropEffects> _result = new Subject<DragDropEffects>();
@ -25,7 +25,7 @@ namespace Avalonia.Platform
private Point _lastPosition;
private StandardCursorType _lastCursorType;
private object _originalCursor;
private InputModifiers? _initialInputModifiers;
private RawInputModifiers? _initialInputModifiers;
public InProcessDragSource()
{
@ -33,9 +33,10 @@ namespace Avalonia.Platform
_dragDrop = AvaloniaLocator.Current.GetService<IDragDropDevice>();
}
public async Task<DragDropEffects> DoDragDrop(IDataObject data, DragDropEffects allowedEffects)
public async Task<DragDropEffects> DoDragDrop(PointerEventArgs triggerEvent, IDataObject data, DragDropEffects allowedEffects)
{
Dispatcher.UIThread.VerifyAccess();
triggerEvent.Pointer.Capture(null);
if (_draggedData == null)
{
_draggedData = data;
@ -55,8 +56,7 @@ namespace Avalonia.Platform
return DragDropEffects.None;
}
private DragDropEffects RaiseEventAndUpdateCursor(RawDragEventType type, IInputElement root, Point pt, InputModifiers modifiers)
private DragDropEffects RaiseEventAndUpdateCursor(RawDragEventType type, IInputElement root, Point pt, RawInputModifiers modifiers)
{
_lastPosition = pt;
@ -69,13 +69,13 @@ namespace Avalonia.Platform
return effect;
}
private DragDropEffects GetPreferredEffect(DragDropEffects effect, InputModifiers modifiers)
private DragDropEffects GetPreferredEffect(DragDropEffects effect, RawInputModifiers modifiers)
{
if (effect == DragDropEffects.Copy || effect == DragDropEffects.Move || effect == DragDropEffects.Link || effect == DragDropEffects.None)
return effect; // No need to check for the modifiers.
if (effect.HasFlag(DragDropEffects.Link) && modifiers.HasFlag(InputModifiers.Alt))
if (effect.HasFlag(DragDropEffects.Link) && modifiers.HasFlag(RawInputModifiers.Alt))
return DragDropEffects.Link;
if (effect.HasFlag(DragDropEffects.Copy) && modifiers.HasFlag(InputModifiers.Control))
if (effect.HasFlag(DragDropEffects.Copy) && modifiers.HasFlag(RawInputModifiers.Control))
return DragDropEffects.Copy;
return DragDropEffects.Move;
}
@ -131,7 +131,7 @@ namespace Avalonia.Platform
private void CancelDragging()
{
if (_lastRoot != null)
RaiseEventAndUpdateCursor(RawDragEventType.DragLeave, _lastRoot, _lastPosition, InputModifiers.None);
RaiseEventAndUpdateCursor(RawDragEventType.DragLeave, _lastRoot, _lastPosition, RawInputModifiers.None);
UpdateCursor(null, DragDropEffects.None);
_result.OnNext(DragDropEffects.None);
}
@ -159,7 +159,7 @@ namespace Avalonia.Platform
_initialInputModifiers = e.InputModifiers & MOUSE_INPUTMODIFIERS;
void CheckDraggingAccepted(InputModifiers changedMouseButton)
void CheckDraggingAccepted(RawInputModifiers changedMouseButton)
{
if (_initialInputModifiers.Value.HasFlag(changedMouseButton))
{
@ -184,11 +184,11 @@ namespace Avalonia.Platform
case RawPointerEventType.LeaveWindow:
RaiseEventAndUpdateCursor(RawDragEventType.DragLeave, e.Root, e.Position, e.InputModifiers); break;
case RawPointerEventType.LeftButtonUp:
CheckDraggingAccepted(InputModifiers.LeftMouseButton); break;
CheckDraggingAccepted(RawInputModifiers.LeftMouseButton); break;
case RawPointerEventType.MiddleButtonUp:
CheckDraggingAccepted(InputModifiers.MiddleMouseButton); break;
CheckDraggingAccepted(RawInputModifiers.MiddleMouseButton); break;
case RawPointerEventType.RightButtonUp:
CheckDraggingAccepted(InputModifiers.RightMouseButton); break;
CheckDraggingAccepted(RawInputModifiers.RightMouseButton); break;
case RawPointerEventType.Move:
var mods = e.InputModifiers & MOUSE_INPUTMODIFIERS;
if (_initialInputModifiers.Value != mods)

24
src/Avalonia.Controls/Platform/MountedDriveInfo.cs

@ -0,0 +1,24 @@
// 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;
namespace Avalonia.Controls.Platform
{
/// <summary>
/// Describes a Drive's properties.
/// </summary>
public class MountedVolumeInfo : IEquatable<MountedVolumeInfo>
{
public string VolumeLabel { get; set; }
public string VolumePath { get; set; }
public ulong VolumeSizeBytes { get; set; }
public bool Equals(MountedVolumeInfo other)
{
return this.VolumeSizeBytes.Equals(other.VolumeSizeBytes) &&
this.VolumePath.Equals(other.VolumePath) &&
(this.VolumeLabel ?? string.Empty).Equals(other.VolumeLabel ?? string.Empty);
}
}
}

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

@ -41,10 +41,5 @@ namespace Avalonia.Controls.Platform
throw new Exception("Could not CreateEmbeddableWindow(): IWindowingPlatform is not registered.");
return platform.CreateEmbeddableWindow();
}
public static IPopupImpl CreatePopup()
{
return AvaloniaLocator.Current.GetService<IWindowingPlatform>().CreatePopup();
}
}
}

7
src/Avalonia.Controls/Presenters/ContentPresenter.cs

@ -237,7 +237,7 @@ namespace Avalonia.Controls.Presenters
// template.
LogicalChildren.Remove(oldChild);
}
else
else if (TemplatedParent != null)
{
// If we're in a ContentControl's template then invoke ChildChanging to let
// ContentControlMixin handle removing the logical child.
@ -248,6 +248,10 @@ namespace Avalonia.Controls.Presenters
newChild,
BindingPriority.LocalValue));
}
else if (oldChild != null)
{
((ISetInheritanceParent)oldChild).SetParent(oldChild.Parent);
}
}
// Set the DataContext if the data isn't a control.
@ -433,6 +437,7 @@ namespace Avalonia.Controls.Presenters
{
VisualChildren.Remove(Child);
LogicalChildren.Remove(Child);
((ISetInheritanceParent)Child).SetParent(Child.Parent);
Child = null;
_dataTemplate = null;
}

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

@ -141,7 +141,7 @@ namespace Avalonia.Controls.Presenters
/// <returns>True if the scroll offset was changed; otherwise false.</returns>
public bool BringDescendantIntoView(IVisual target, Rect targetRect)
{
if (Child == null)
if (Child?.IsEffectivelyVisible != true)
{
return false;
}

42
src/Avalonia.Controls/Primitives/AdornerDecorator.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 Avalonia.LogicalTree;
namespace Avalonia.Controls.Primitives
{
public class AdornerDecorator : Decorator
{
public AdornerDecorator()
{
AdornerLayer = new AdornerLayer();
((ISetLogicalParent)AdornerLayer).SetParent(this);
AdornerLayer.ZIndex = int.MaxValue;
VisualChildren.Add(AdornerLayer);
}
protected override void OnAttachedToLogicalTree(LogicalTreeAttachmentEventArgs e)
{
base.OnAttachedToLogicalTree(e);
((ILogical)AdornerLayer).NotifyAttachedToLogicalTree(e);
}
public AdornerLayer AdornerLayer
{
get;
}
protected override Size MeasureOverride(Size availableSize)
{
AdornerLayer.Measure(availableSize);
return base.MeasureOverride(availableSize);
}
protected override Size ArrangeOverride(Size finalSize)
{
AdornerLayer.Arrange(new Rect(finalSize));
return base.ArrangeOverride(finalSize);
}
}
}

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

@ -42,7 +42,7 @@ namespace Avalonia.Controls.Primitives
public static AdornerLayer GetAdornerLayer(IVisual visual)
{
return visual.GetVisualAncestors()
.OfType<AdornerDecorator>()
.OfType<VisualLayerManager>()
.FirstOrDefault()
?.AdornerLayer;
}

26
src/Avalonia.Controls/Primitives/IPopupHost.cs

@ -0,0 +1,26 @@
using System;
using Avalonia.Controls.Presenters;
using Avalonia.Controls.Primitives.PopupPositioning;
using Avalonia.VisualTree;
namespace Avalonia.Controls.Primitives
{
public interface IPopupHost : IDisposable
{
void SetChild(IControl control);
IContentPresenter Presenter { get; }
IVisual HostedVisualTreeRoot { get; }
event EventHandler<TemplateAppliedEventArgs> TemplateApplied;
void ConfigurePosition(IVisual target, PlacementMode placement, Point offset,
PopupPositioningEdge anchor = PopupPositioningEdge.None,
PopupPositioningEdge gravity = PopupPositioningEdge.None);
void Show();
void Hide();
IDisposable BindConstraints(AvaloniaObject popup, StyledProperty<double> widthProperty,
StyledProperty<double> minWidthProperty, StyledProperty<double> maxWidthProperty,
StyledProperty<double> heightProperty, StyledProperty<double> minHeightProperty,
StyledProperty<double> maxHeightProperty, StyledProperty<bool> topmostProperty);
}
}

38
src/Avalonia.Controls/Primitives/OverlayLayer.cs

@ -0,0 +1,38 @@
using System.Linq;
using Avalonia.Rendering;
using Avalonia.VisualTree;
namespace Avalonia.Controls.Primitives
{
public class OverlayLayer : Canvas, ICustomSimpleHitTest
{
public Size AvailableSize { get; private set; }
public static OverlayLayer GetOverlayLayer(IVisual visual)
{
foreach(var v in visual.GetVisualAncestors())
if(v is VisualLayerManager vlm)
if (vlm.OverlayLayer != null)
return vlm.OverlayLayer;
if (visual is TopLevel tl)
{
var layers = tl.GetVisualDescendants().OfType<VisualLayerManager>().FirstOrDefault();
return layers?.OverlayLayer;
}
return null;
}
public bool HitTest(Point point)
{
return Children.Any(ctrl => ctrl.TransformedBounds?.Contains(point) == true);
}
protected override Size ArrangeOverride(Size finalSize)
{
// We are saving it here since child controls might need to know the entire size of the overlay
// and Bounds won't be updated in time
AvailableSize = finalSize;
return base.ArrangeOverride(finalSize);
}
}
}

149
src/Avalonia.Controls/Primitives/OverlayPopupHost.cs

@ -0,0 +1,149 @@
using System;
using System.Collections.Generic;
using System.Reactive.Disposables;
using Avalonia.Controls.Primitives.PopupPositioning;
using Avalonia.Interactivity;
using Avalonia.Media;
using Avalonia.Threading;
using Avalonia.VisualTree;
namespace Avalonia.Controls.Primitives
{
public class OverlayPopupHost : ContentControl, IPopupHost, IInteractive, IManagedPopupPositionerPopup
{
private readonly OverlayLayer _overlayLayer;
private PopupPositionerParameters _positionerParameters = new PopupPositionerParameters();
private ManagedPopupPositioner _positioner;
private Point _lastRequestedPosition;
private bool _shown;
public OverlayPopupHost(OverlayLayer overlayLayer)
{
_overlayLayer = overlayLayer;
_positioner = new ManagedPopupPositioner(this);
}
public void SetChild(IControl control)
{
Content = control;
}
public IVisual HostedVisualTreeRoot => null;
/// <inheritdoc/>
IInteractive IInteractive.InteractiveParent => Parent;
public void Dispose() => Hide();
public void Show()
{
_overlayLayer.Children.Add(this);
_shown = true;
}
public void Hide()
{
_overlayLayer.Children.Remove(this);
_shown = false;
}
public IDisposable BindConstraints(AvaloniaObject popup, StyledProperty<double> widthProperty, StyledProperty<double> minWidthProperty,
StyledProperty<double> maxWidthProperty, StyledProperty<double> heightProperty, StyledProperty<double> minHeightProperty,
StyledProperty<double> maxHeightProperty, StyledProperty<bool> topmostProperty)
{
// Topmost property is not supported
var bindings = new List<IDisposable>();
void Bind(AvaloniaProperty what, AvaloniaProperty to) => bindings.Add(this.Bind(what, popup[~to]));
Bind(WidthProperty, widthProperty);
Bind(MinWidthProperty, minWidthProperty);
Bind(MaxWidthProperty, maxWidthProperty);
Bind(HeightProperty, heightProperty);
Bind(MinHeightProperty, minHeightProperty);
Bind(MaxHeightProperty, maxHeightProperty);
return Disposable.Create(() =>
{
foreach (var x in bindings)
x.Dispose();
});
}
public void ConfigurePosition(IVisual target, PlacementMode placement, Point offset,
PopupPositioningEdge anchor = PopupPositioningEdge.None, PopupPositioningEdge gravity = PopupPositioningEdge.None)
{
_positionerParameters.ConfigurePosition((TopLevel)_overlayLayer.GetVisualRoot(), target, placement, offset, anchor,
gravity);
UpdatePosition();
}
protected override Size ArrangeOverride(Size finalSize)
{
if (_positionerParameters.Size != finalSize)
{
_positionerParameters.Size = finalSize;
UpdatePosition();
}
return base.ArrangeOverride(finalSize);
}
private void UpdatePosition()
{
// Don't bother the positioner with layout system artifacts
if (_positionerParameters.Size.Width == 0 || _positionerParameters.Size.Height == 0)
return;
if (_shown)
{
_positioner.Update(_positionerParameters);
}
}
IReadOnlyList<ManagedPopupPositionerScreenInfo> IManagedPopupPositionerPopup.Screens
{
get
{
var rc = new Rect(default, _overlayLayer.AvailableSize);
return new[] {new ManagedPopupPositionerScreenInfo(rc, rc)};
}
}
Rect IManagedPopupPositionerPopup.ParentClientAreaScreenGeometry =>
new Rect(default, _overlayLayer.Bounds.Size);
void IManagedPopupPositionerPopup.MoveAndResize(Point devicePoint, Size virtualSize)
{
_lastRequestedPosition = devicePoint;
Dispatcher.UIThread.Post(() =>
{
OverlayLayer.SetLeft(this, _lastRequestedPosition.X);
OverlayLayer.SetTop(this, _lastRequestedPosition.Y);
}, DispatcherPriority.Layout);
}
Point IManagedPopupPositionerPopup.TranslatePoint(Point pt) => pt;
Size IManagedPopupPositionerPopup.TranslateSize(Size size) => size;
public static IPopupHost CreatePopupHost(IVisual target, IAvaloniaDependencyResolver dependencyResolver)
{
var platform = (target.GetVisualRoot() as TopLevel)?.PlatformImpl?.CreatePopup();
if (platform != null)
return new PopupRoot((TopLevel)target.GetVisualRoot(), platform, dependencyResolver);
var overlayLayer = OverlayLayer.GetOverlayLayer(target);
if (overlayLayer == null)
throw new InvalidOperationException(
"Unable to create IPopupImpl and no overlay layer is found for the target control");
return new OverlayPopupHost(overlayLayer);
}
public override void Render(DrawingContext context)
{
context.FillRectangle(Brushes.White, new Rect(default, Bounds.Size));
}
}
}

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

@ -2,7 +2,12 @@
// Licensed under the MIT license. See licence.md file in the project root for full license information.
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Reactive.Disposables;
using Avalonia.Controls.Presenters;
using Avalonia.Data;
using Avalonia.Input;
using Avalonia.Input.Raw;
using Avalonia.Interactivity;
@ -42,7 +47,7 @@ namespace Avalonia.Controls.Primitives
/// Defines the <see cref="ObeyScreenEdges"/> property.
/// </summary>
public static readonly StyledProperty<bool> ObeyScreenEdgesProperty =
AvaloniaProperty.Register<Popup, bool>(nameof(ObeyScreenEdges));
AvaloniaProperty.Register<Popup, bool>(nameof(ObeyScreenEdges), true);
/// <summary>
/// Defines the <see cref="HorizontalOffset"/> property.
@ -75,10 +80,12 @@ namespace Avalonia.Controls.Primitives
AvaloniaProperty.Register<Popup, bool>(nameof(Topmost));
private bool _isOpen;
private PopupRoot _popupRoot;
private IPopupHost _popupHost;
private TopLevel _topLevel;
private IDisposable _nonClientListener;
private IDisposable _presenterSubscription;
bool _ignoreIsOpenChanged = false;
private List<IDisposable> _bindings = new List<IDisposable>();
/// <summary>
/// Initializes static members of the <see cref="Popup"/> class.
@ -88,7 +95,11 @@ namespace Avalonia.Controls.Primitives
IsHitTestVisibleProperty.OverrideDefaultValue<Popup>(false);
ChildProperty.Changed.AddClassHandler<Popup>(x => x.ChildChanged);
IsOpenProperty.Changed.AddClassHandler<Popup>(x => x.IsOpenChanged);
TopmostProperty.Changed.AddClassHandler<Popup>((p, e) => p.PopupRoot.Topmost = (bool)e.NewValue);
}
public Popup()
{
}
/// <summary>
@ -101,10 +112,7 @@ namespace Avalonia.Controls.Primitives
/// </summary>
public event EventHandler Opened;
/// <summary>
/// Raised when the popup root has been created, but before it has been shown.
/// </summary>
public event EventHandler PopupRootCreated;
public IPopupHost Host => _popupHost;
/// <summary>
/// Gets or sets the control to display in the popup.
@ -147,10 +155,7 @@ namespace Avalonia.Controls.Primitives
set { SetValue(PlacementModeProperty, value); }
}
/// <summary>
/// Gets or sets a value indicating whether the popup positions itself within the nearest screen boundary
/// when its opened at a position where it would otherwise overlap the screen edge.
/// </summary>
[Obsolete("This property has no effect")]
public bool ObeyScreenEdges
{
get => GetValue(ObeyScreenEdgesProperty);
@ -184,11 +189,6 @@ namespace Avalonia.Controls.Primitives
set { SetValue(PlacementTargetProperty, value); }
}
/// <summary>
/// Gets the root of the popup window.
/// </summary>
public PopupRoot PopupRoot => _popupRoot;
/// <summary>
/// Gets or sets a value indicating whether the popup should stay open when the popup is
/// pressed or loses focus.
@ -211,63 +211,58 @@ namespace Avalonia.Controls.Primitives
/// <summary>
/// Gets the root of the popup window.
/// </summary>
IVisual IVisualTreeHost.Root => _popupRoot;
IVisual IVisualTreeHost.Root => _popupHost?.HostedVisualTreeRoot;
/// <summary>
/// Opens the popup.
/// </summary>
public void Open()
{
if (_popupRoot == null)
// Popup is currently open
if (_topLevel != null)
return;
CloseCurrent();
var placementTarget = PlacementTarget ?? this.GetLogicalAncestors().OfType<IVisual>().FirstOrDefault();
if (placementTarget == null)
throw new InvalidOperationException("Popup has no logical parent and PlacementTarget is null");
_topLevel = placementTarget.GetVisualRoot() as TopLevel;
if (_topLevel == null)
{
_popupRoot = new PopupRoot(DependencyResolver)
{
[~ContentControl.ContentProperty] = this[~ChildProperty],
[~WidthProperty] = this[~WidthProperty],
[~HeightProperty] = this[~HeightProperty],
[~MinWidthProperty] = this[~MinWidthProperty],
[~MaxWidthProperty] = this[~MaxWidthProperty],
[~MinHeightProperty] = this[~MinHeightProperty],
[~MaxHeightProperty] = this[~MaxHeightProperty],
};
((ISetLogicalParent)_popupRoot).SetParent(this);
throw new InvalidOperationException(
"Attempted to open a popup not attached to a TopLevel");
}
_popupRoot.Position = GetPosition();
_popupHost = OverlayPopupHost.CreatePopupHost(placementTarget, DependencyResolver);
_bindings.Add(_popupHost.BindConstraints(this, WidthProperty, MinWidthProperty, MaxWidthProperty,
HeightProperty, MinHeightProperty, MaxHeightProperty, TopmostProperty));
if (_topLevel == null && PlacementTarget != null)
_popupHost.SetChild(Child);
((ISetLogicalParent)_popupHost).SetParent(this);
_popupHost.ConfigurePosition(placementTarget,
PlacementMode, new Point(HorizontalOffset, VerticalOffset));
_popupHost.TemplateApplied += RootTemplateApplied;
var window = _topLevel as Window;
if (window != null)
{
_topLevel = PlacementTarget.GetSelfAndLogicalAncestors().First(x => x is TopLevel) as TopLevel;
window.Deactivated += WindowDeactivated;
}
if (_topLevel != null)
else
{
var window = _topLevel as Window;
if (window != null)
var parentPopuproot = _topLevel as PopupRoot;
if (parentPopuproot?.Parent is Popup popup)
{
window.Deactivated += WindowDeactivated;
popup.Closed += ParentClosed;
}
else
{
var parentPopuproot = _topLevel as PopupRoot;
if (parentPopuproot?.Parent is Popup popup)
{
popup.Closed += ParentClosed;
}
}
_topLevel.AddHandler(PointerPressedEvent, PointerPressedOutside, RoutingStrategies.Tunnel);
_nonClientListener = InputManager.Instance.Process.Subscribe(ListenForNonClientClick);
}
_topLevel.AddHandler(PointerPressedEvent, PointerPressedOutside, RoutingStrategies.Tunnel);
_nonClientListener = InputManager.Instance?.Process.Subscribe(ListenForNonClientClick);
PopupRootCreated?.Invoke(this, EventArgs.Empty);
_popupRoot.Show();
if (ObeyScreenEdges)
{
_popupRoot.SnapInsideScreenEdges();
}
_popupHost.Show();
using (BeginIgnoringIsOpen())
{
@ -282,29 +277,14 @@ namespace Avalonia.Controls.Primitives
/// </summary>
public void Close()
{
if (_popupRoot != null)
if (_popupHost != null)
{
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;
}
_popupRoot.Hide();
_popupHost.TemplateApplied -= RootTemplateApplied;
}
_presenterSubscription?.Dispose();
CloseCurrent();
using (BeginIgnoringIsOpen())
{
IsOpen = false;
@ -313,6 +293,41 @@ namespace Avalonia.Controls.Primitives
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>
/// Measures the control.
/// </summary>
@ -323,27 +338,14 @@ namespace Avalonia.Controls.Primitives
return new Size();
}
/// <inheritdoc/>
protected override void OnAttachedToLogicalTree(LogicalTreeAttachmentEventArgs e)
{
base.OnAttachedToLogicalTree(e);
_topLevel = e.Root as TopLevel;
}
/// <inheritdoc/>
protected override void OnDetachedFromLogicalTree(LogicalTreeAttachmentEventArgs e)
{
base.OnDetachedFromLogicalTree(e);
_topLevel = null;
if (_popupRoot != null)
{
((ISetLogicalParent)_popupRoot).SetParent(null);
_popupRoot.Dispose();
_popupRoot = null;
}
Close();
}
/// <summary>
/// Called when the <see cref="IsOpen"/> property changes.
/// </summary>
@ -380,49 +382,6 @@ namespace Avalonia.Controls.Primitives
}
}
/// <summary>
/// Gets the position for the popup based on the placement properties.
/// </summary>
/// <returns>The popup's position in screen coordinates.</returns>
protected virtual PixelPoint GetPosition()
{
var result = GetPosition(PlacementTarget ?? this.GetVisualParent<Control>(), PlacementMode, PopupRoot,
HorizontalOffset, VerticalOffset);
return result;
}
internal static PixelPoint GetPosition(Control target, PlacementMode placement, PopupRoot popupRoot, double horizontalOffset, double verticalOffset)
{
var root = target?.GetVisualRoot();
var mode = root != null ? placement : PlacementMode.Pointer;
var scaling = root?.RenderScaling ?? 1;
switch (mode)
{
case PlacementMode.Pointer:
if (popupRoot != null)
{
var screenOffset = PixelPoint.FromPoint(new Point(horizontalOffset, verticalOffset), scaling);
var mouseOffset = ((IInputRoot)popupRoot)?.MouseDevice?.Position ?? default;
return new PixelPoint(
screenOffset.X + mouseOffset.X,
screenOffset.Y + mouseOffset.Y);
}
return default;
case PlacementMode.Bottom:
return target?.PointToScreen(new Point(0 + horizontalOffset, target.Bounds.Height + verticalOffset)) ?? default;
case PlacementMode.Right:
return target?.PointToScreen(new Point(target.Bounds.Width + horizontalOffset, 0 + verticalOffset)) ?? default;
default:
throw new InvalidOperationException("Invalid value for Popup.PlacementMode");
}
}
private void ListenForNonClientClick(RawInputEventArgs e)
{
var mouse = e as RawPointerEventArgs;
@ -445,17 +404,62 @@ namespace Avalonia.Controls.Primitives
}
}
private bool IsChildOrThis(IVisual child)
private void RootTemplateApplied(object sender, TemplateAppliedEventArgs e)
{
IVisual root = child.GetVisualRoot();
while (root is PopupRoot)
_popupHost.TemplateApplied -= RootTemplateApplied;
if (_presenterSubscription != null)
{
if (root == PopupRoot) return true;
root = ((PopupRoot)root).Parent.GetVisualRoot();
_presenterSubscription.Dispose();
_presenterSubscription = null;
}
// If the Popup appears in a control template, then the child controls
// that appear in the popup host need to have their TemplatedParent
// properties set.
if (TemplatedParent != null)
{
_popupHost.Presenter?.ApplyTemplate();
_popupHost.Presenter?.GetObservable(ContentPresenter.ChildProperty)
.Subscribe(SetTemplatedParentAndApplyChildTemplates);
}
}
private void SetTemplatedParentAndApplyChildTemplates(IControl control)
{
if (control != null)
{
var templatedParent = TemplatedParent;
if (control.TemplatedParent == null)
{
control.SetValue(TemplatedParentProperty, templatedParent);
}
control.ApplyTemplate();
if (!(control is IPresenter) && control.TemplatedParent == templatedParent)
{
foreach (IControl child in control.GetVisualChildren())
{
SetTemplatedParentAndApplyChildTemplates(child);
}
}
}
return false;
}
private bool IsChildOrThis(IVisual child)
{
return _popupHost != null && ((IVisual)_popupHost).FindCommonVisualAncestor(child) == _popupHost;
}
public bool IsInsidePopup(IVisual visual)
{
return _popupHost != null && ((IVisual)_popupHost)?.IsVisualAncestorOf(visual) == true;
}
public bool IsPointerOverPopup => ((IInputElement)_popupHost).IsPointerOver;
private void WindowDeactivated(object sender, EventArgs e)
{
if (!StaysOpen)

358
src/Avalonia.Controls/Primitives/PopupPositioning/IPopupPositioner.cs

@ -0,0 +1,358 @@
// The documentation and flag names in this file are initially taken from
// xdg_shell wayland protocol this API is designed after
// therefore, I'm including the license from wayland-protocols repo
/*
Copyright © 2008-2013 Kristian Høgsberg
Copyright © 2010-2013 Intel Corporation
Copyright © 2013 Rafael Antognolli
Copyright © 2013 Jasper St. Pierre
Copyright © 2014 Jonas Ådahl
Copyright © 2014 Jason Ekstrand
Copyright © 2014-2015 Collabora, Ltd.
Copyright © 2015 Red Hat Inc.
Permission is hereby granted, free of charge, to any person obtaining a
copy of this software and associated documentation files (the "Software"),
to deal in the Software without restriction, including without limitation
the rights to use, copy, modify, merge, publish, distribute, sublicense,
and/or sell copies of the Software, and to permit persons to whom the
Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice (including the next
paragraph) shall be included in all copies or substantial portions of the
Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
DEALINGS IN THE SOFTWARE.
---
The above is the version of the MIT "Expat" License used by X.org:
http://cgit.freedesktop.org/xorg/xserver/tree/COPYING
Adjustments for Avalonia needs:
Copyright © 2019 Nikita Tsukanov
*/
using System;
using Avalonia.VisualTree;
namespace Avalonia.Controls.Primitives.PopupPositioning
{
/// <summary>
///
/// The IPopupPositioner provides a collection of rules for the placement of a
/// a popup relative to its parent. Rules can be defined to ensure
/// the popup remains within the visible area's borders, and to
/// specify how the popup changes its position, such as sliding along
/// an axis, or flipping around a rectangle. These positioner-created rules are
/// constrained by the requirement that a popup must intersect with or
/// be at least partially adjacent to its parent surface.
/// </summary>
public struct PopupPositionerParameters
{
private PopupPositioningEdge _gravity;
private PopupPositioningEdge _anchor;
/// <summary>
/// Set the size of the popup that is to be positioned with the positioner
/// object. The size is in scaled coordinates.
/// </summary>
public Size Size { get; set; }
/// <summary>
/// Specify the anchor rectangle within the parent that the popup
/// will be placed relative to. The rectangle is relative to the
/// parent geometry
///
/// The anchor rectangle may not extend outside the window geometry of the
/// popup's parent. The anchor rectangle is in scaled coordinates
/// </summary>
public Rect AnchorRectangle { get; set; }
/// <summary>
/// Defines the anchor point for the anchor rectangle. The specified anchor
/// is used derive an anchor point that the popup will be
/// positioned relative to. If a corner anchor is set (e.g. 'TopLeft' or
/// 'BottomRight'), the anchor point will be at the specified corner;
/// otherwise, the derived anchor point will be centered on the specified
/// edge, or in the center of the anchor rectangle if no edge is specified.
/// </summary>
public PopupPositioningEdge Anchor
{
get => _anchor;
set
{
PopupPositioningEdgeHelper.ValidateEdge(value);
_anchor = value;
}
}
/// <summary>
/// Defines in what direction a popup should be positioned, relative to
/// the anchor point of the parent. If a corner gravity is
/// specified (e.g. 'BottomRight' or 'TopLeft'), then the popup
/// will be placed towards the specified gravity; otherwise, the popup
/// will be centered over the anchor point on any axis that had no
/// gravity specified.
/// </summary>
public PopupPositioningEdge Gravity
{
get => _gravity;
set
{
PopupPositioningEdgeHelper.ValidateEdge(value);
_gravity = value;
}
}
/// <summary>
/// Specify how the popup should be positioned if the originally intended
/// position caused the popup to be constrained, meaning at least
/// partially outside positioning boundaries set by the positioner. The
/// adjustment is set by constructing a bitmask describing the adjustment to
/// be made when the popup is constrained on that axis.
///
/// If no bit for one axis is set, the positioner will assume that the child
/// surface should not change its position on that axis when constrained.
///
/// If more than one bit for one axis is set, the order of how adjustments
/// are applied is specified in the corresponding adjustment descriptions.
///
/// The default adjustment is none.
/// </summary>
public PopupPositionerConstraintAdjustment ConstraintAdjustment { get; set; }
/// <summary>
/// Specify the popup position offset relative to the position of the
/// anchor on the anchor rectangle and the anchor on the popup. For
/// example if the anchor of the anchor rectangle is at (x, y), the popup
/// has the gravity bottom|right, and the offset is (ox, oy), the calculated
/// surface position will be (x + ox, y + oy). The offset position of the
/// surface is the one used for constraint testing. See
/// set_constraint_adjustment.
///
/// An example use case is placing a popup menu on top of a user interface
/// element, while aligning the user interface element of the parent surface
/// with some user interface element placed somewhere in the popup.
/// </summary>
public Point Offset { get; set; }
}
/// <summary>
/// The constraint adjustment value define ways how popup position will
/// be adjusted if the unadjusted position would result in the popup
/// being partly constrained.
///
/// Whether a popup is considered 'constrained' is left to the positioner
/// to determine. For example, the popup may be partly outside the
/// target platform defined 'work area', thus necessitating the popup's
/// position be adjusted until it is entirely inside the work area.
/// </summary>
[Flags]
public enum PopupPositionerConstraintAdjustment
{
/// <summary>
/// Don't alter the surface position even if it is constrained on some
/// axis, for example partially outside the edge of an output.
/// </summary>
None = 0,
/// <summary>
/// Slide the surface along the x axis until it is no longer constrained.
/// First try to slide towards the direction of the gravity on the x axis
/// until either the edge in the opposite direction of the gravity is
/// unconstrained or the edge in the direction of the gravity is
/// constrained.
///
/// Then try to slide towards the opposite direction of the gravity on the
/// x axis until either the edge in the direction of the gravity is
/// unconstrained or the edge in the opposite direction of the gravity is
/// constrained.
/// </summary>
SlideX = 1,
/// <summary>
/// Slide the surface along the y axis until it is no longer constrained.
///
/// First try to slide towards the direction of the gravity on the y axis
/// until either the edge in the opposite direction of the gravity is
/// unconstrained or the edge in the direction of the gravity is
/// constrained.
///
/// Then try to slide towards the opposite direction of the gravity on the
/// y axis until either the edge in the direction of the gravity is
/// unconstrained or the edge in the opposite direction of the gravity is
/// constrained.
/// */
/// </summary>
SlideY = 2,
/// <summary>
/// Invert the anchor and gravity on the x axis if the surface is
/// constrained on the x axis. For example, if the left edge of the
/// surface is constrained, the gravity is 'left' and the anchor is
/// 'left', change the gravity to 'right' and the anchor to 'right'.
///
/// If the adjusted position also ends up being constrained, the resulting
/// position of the flip_x adjustment will be the one before the
/// adjustment.
/// </summary>
FlipX = 4,
/// <summary>
/// Invert the anchor and gravity on the y axis if the surface is
/// constrained on the y axis. For example, if the bottom edge of the
/// surface is constrained, the gravity is 'bottom' and the anchor is
/// 'bottom', change the gravity to 'top' and the anchor to 'top'.
///
/// The adjusted position is calculated given the original anchor
/// rectangle and offset, but with the new flipped anchor and gravity
/// values.
///
/// If the adjusted position also ends up being constrained, the resulting
/// position of the flip_y adjustment will be the one before the
/// adjustment.
/// </summary>
FlipY = 8,
All = SlideX|SlideY|FlipX|FlipY
}
static class PopupPositioningEdgeHelper
{
public static void ValidateEdge(this PopupPositioningEdge edge)
{
if (((edge & PopupPositioningEdge.Left) != 0 && (edge & PopupPositioningEdge.Right) != 0)
||
((edge & PopupPositioningEdge.Top) != 0 && (edge & PopupPositioningEdge.Bottom) != 0))
throw new ArgumentException("Opposite edges specified");
}
public static PopupPositioningEdge Flip(this PopupPositioningEdge edge)
{
var hmask = PopupPositioningEdge.Left | PopupPositioningEdge.Right;
var vmask = PopupPositioningEdge.Top | PopupPositioningEdge.Bottom;
if ((edge & hmask) != 0)
edge ^= hmask;
if ((edge & vmask) != 0)
edge ^= vmask;
return edge;
}
public static PopupPositioningEdge FlipX(this PopupPositioningEdge edge)
{
if ((edge & PopupPositioningEdge.HorizontalMask) != 0)
edge ^= PopupPositioningEdge.HorizontalMask;
return edge;
}
public static PopupPositioningEdge FlipY(this PopupPositioningEdge edge)
{
if ((edge & PopupPositioningEdge.VerticalMask) != 0)
edge ^= PopupPositioningEdge.VerticalMask;
return edge;
}
}
[Flags]
public enum PopupPositioningEdge
{
None,
Top = 1,
Bottom = 2,
Left = 4,
Right = 8,
TopLeft = Top | Left,
TopRight = Top | Right,
BottomLeft = Bottom | Left,
BottomRight = Bottom | Right,
VerticalMask = Top | Bottom,
HorizontalMask = Left | Right,
AllMask = VerticalMask|HorizontalMask
}
public interface IPopupPositioner
{
void Update(PopupPositionerParameters parameters);
}
static class PopupPositionerExtensions
{
public static void ConfigurePosition(ref this PopupPositionerParameters positionerParameters,
TopLevel topLevel,
IVisual target, PlacementMode placement, Point offset,
PopupPositioningEdge anchor, PopupPositioningEdge gravity)
{
// We need a better way for tracking the last pointer position
var pointer = topLevel.PointToClient(topLevel.PlatformImpl.MouseDevice.Position);
positionerParameters.Offset = offset;
positionerParameters.ConstraintAdjustment = PopupPositionerConstraintAdjustment.All;
if (placement == PlacementMode.Pointer)
{
positionerParameters.AnchorRectangle = new Rect(pointer, new Size(1, 1));
positionerParameters.Anchor = PopupPositioningEdge.BottomRight;
positionerParameters.Gravity = PopupPositioningEdge.BottomRight;
}
else
{
if (target == null)
throw new InvalidOperationException("Placement mode is not Pointer and PlacementTarget is null");
var matrix = target.TransformToVisual(topLevel);
if (matrix == null)
{
if (target.GetVisualRoot() == null)
throw new InvalidCastException("Target control is not attached to the visual tree");
throw new InvalidCastException("Target control is not in the same tree as the popup parent");
}
positionerParameters.AnchorRectangle = new Rect(default, target.Bounds.Size)
.TransformToAABB(matrix.Value);
if (placement == PlacementMode.Right)
{
positionerParameters.Anchor = PopupPositioningEdge.TopRight;
positionerParameters.Gravity = PopupPositioningEdge.BottomRight;
}
else if (placement == PlacementMode.Bottom)
{
positionerParameters.Anchor = PopupPositioningEdge.BottomLeft;
positionerParameters.Gravity = PopupPositioningEdge.BottomRight;
}
else if (placement == PlacementMode.Left)
{
positionerParameters.Anchor = PopupPositioningEdge.TopLeft;
positionerParameters.Gravity = PopupPositioningEdge.BottomLeft;
}
else if (placement == PlacementMode.Top)
{
positionerParameters.Anchor = PopupPositioningEdge.TopLeft;
positionerParameters.Gravity = PopupPositioningEdge.TopRight;
}
else if (placement == PlacementMode.AnchorAndGravity)
{
positionerParameters.Anchor = anchor;
positionerParameters.Gravity = gravity;
}
else
throw new InvalidOperationException("Invalid value for Popup.PlacementMode");
}
}
}
}

175
src/Avalonia.Controls/Primitives/PopupPositioning/ManagedPopupPositioner.cs

@ -0,0 +1,175 @@
using System;
using System.Collections.Generic;
using System.Linq;
namespace Avalonia.Controls.Primitives.PopupPositioning
{
public interface IManagedPopupPositionerPopup
{
IReadOnlyList<ManagedPopupPositionerScreenInfo> Screens { get; }
Rect ParentClientAreaScreenGeometry { get; }
void MoveAndResize(Point devicePoint, Size virtualSize);
Point TranslatePoint(Point pt);
Size TranslateSize(Size size);
}
public class ManagedPopupPositionerScreenInfo
{
public Rect Bounds { get; }
public Rect WorkingArea { get; }
public ManagedPopupPositionerScreenInfo(Rect bounds, Rect workingArea)
{
Bounds = bounds;
WorkingArea = workingArea;
}
}
public class ManagedPopupPositioner : IPopupPositioner
{
private readonly IManagedPopupPositionerPopup _popup;
public ManagedPopupPositioner(IManagedPopupPositionerPopup popup)
{
_popup = popup;
}
private static Point GetAnchorPoint(Rect anchorRect, PopupPositioningEdge edge)
{
double x, y;
if ((edge & PopupPositioningEdge.Left) != 0)
x = anchorRect.X;
else if ((edge & PopupPositioningEdge.Right) != 0)
x = anchorRect.Right;
else
x = anchorRect.X + anchorRect.Width / 2;
if ((edge & PopupPositioningEdge.Top) != 0)
y = anchorRect.Y;
else if ((edge & PopupPositioningEdge.Bottom) != 0)
y = anchorRect.Bottom;
else
y = anchorRect.Y + anchorRect.Height / 2;
return new Point(x, y);
}
private static Point Gravitate(Point anchorPoint, Size size, PopupPositioningEdge gravity)
{
double x, y;
if ((gravity & PopupPositioningEdge.Left) != 0)
x = -size.Width;
else if ((gravity & PopupPositioningEdge.Right) != 0)
x = 0;
else
x = -size.Width / 2;
if ((gravity & PopupPositioningEdge.Top) != 0)
y = -size.Height;
else if ((gravity & PopupPositioningEdge.Bottom) != 0)
y = 0;
else
y = -size.Height / 2;
return anchorPoint + new Point(x, y);
}
public void Update(PopupPositionerParameters parameters)
{
Update(_popup.TranslateSize(parameters.Size), parameters.Size,
new Rect(_popup.TranslatePoint(parameters.AnchorRectangle.TopLeft),
_popup.TranslateSize(parameters.AnchorRectangle.Size)),
parameters.Anchor, parameters.Gravity, parameters.ConstraintAdjustment,
_popup.TranslatePoint(parameters.Offset));
}
private void Update(Size translatedSize, Size originalSize,
Rect anchorRect, PopupPositioningEdge anchor, PopupPositioningEdge gravity,
PopupPositionerConstraintAdjustment constraintAdjustment, Point offset)
{
var parentGeometry = _popup.ParentClientAreaScreenGeometry;
anchorRect = anchorRect.Translate(parentGeometry.TopLeft);
Rect GetBounds()
{
var screens = _popup.Screens;
var targetScreen = screens.FirstOrDefault(s => s.Bounds.Contains(anchorRect.TopLeft))
?? screens.FirstOrDefault(s => s.Bounds.Intersects(anchorRect))
?? screens.FirstOrDefault(s => s.Bounds.Contains(parentGeometry.TopLeft))
?? screens.FirstOrDefault(s => s.Bounds.Intersects(parentGeometry))
?? screens.FirstOrDefault();
return targetScreen?.WorkingArea
?? new Rect(0, 0, double.MaxValue, double.MaxValue);
}
var bounds = GetBounds();
bool FitsInBounds(Rect rc, PopupPositioningEdge edge = PopupPositioningEdge.AllMask)
{
if ((edge & PopupPositioningEdge.Left) != 0
&& rc.X < bounds.X)
return false;
if ((edge & PopupPositioningEdge.Top) != 0
&& rc.Y < bounds.Y)
return false;
if ((edge & PopupPositioningEdge.Right) != 0
&& rc.Right > bounds.Right)
return false;
if ((edge & PopupPositioningEdge.Bottom) != 0
&& rc.Bottom > bounds.Bottom)
return false;
return true;
}
Rect GetUnconstrained(PopupPositioningEdge a, PopupPositioningEdge g) =>
new Rect(Gravitate(GetAnchorPoint(anchorRect, a), translatedSize, g) + offset, translatedSize);
var geo = GetUnconstrained(anchor, gravity);
// If flipping geometry and anchor is allowed and helps, use the flipped one,
// otherwise leave it as is
if (!FitsInBounds(geo, PopupPositioningEdge.HorizontalMask)
&& (constraintAdjustment & PopupPositionerConstraintAdjustment.FlipX) != 0)
{
var flipped = GetUnconstrained(anchor.FlipX(), gravity.FlipX());
if (FitsInBounds(flipped, PopupPositioningEdge.HorizontalMask))
geo = geo.WithX(flipped.X);
}
// If sliding is allowed, try moving the rect into the bounds
if ((constraintAdjustment & PopupPositionerConstraintAdjustment.SlideX) != 0)
{
geo = geo.WithX(Math.Max(geo.X, bounds.X));
if (geo.Right > bounds.Right)
geo = geo.WithX(bounds.Right - geo.Width);
}
// If flipping geometry and anchor is allowed and helps, use the flipped one,
// otherwise leave it as is
if (!FitsInBounds(geo, PopupPositioningEdge.VerticalMask)
&& (constraintAdjustment & PopupPositionerConstraintAdjustment.FlipY) != 0)
{
var flipped = GetUnconstrained(anchor.FlipY(), gravity.FlipY());
if (FitsInBounds(flipped, PopupPositioningEdge.VerticalMask))
geo = geo.WithY(flipped.Y);
}
// If sliding is allowed, try moving the rect into the bounds
if ((constraintAdjustment & PopupPositionerConstraintAdjustment.SlideY) != 0)
{
geo = geo.WithY(Math.Max(geo.Y, bounds.Y));
if (geo.Bottom > bounds.Bottom)
geo = geo.WithY(bounds.Bottom - geo.Height);
}
_popup.MoveAndResize(geo.TopLeft, originalSize);
}
}
}

50
src/Avalonia.Controls/Primitives/PopupPositioning/ManagedPopupPositionerPopupImplHelper.cs

@ -0,0 +1,50 @@
using System;
using System.Collections.Generic;
using System.Linq;
using Avalonia.Platform;
namespace Avalonia.Controls.Primitives.PopupPositioning
{
/// <summary>
/// This class is used to simplify integration of IPopupImpl implementations with popup positioner
/// </summary>
public class ManagedPopupPositionerPopupImplHelper : IManagedPopupPositionerPopup
{
private readonly IWindowBaseImpl _parent;
public delegate void MoveResizeDelegate(PixelPoint position, Size size, double scaling);
private readonly MoveResizeDelegate _moveResize;
public ManagedPopupPositionerPopupImplHelper(IWindowBaseImpl parent, MoveResizeDelegate moveResize)
{
_parent = parent;
_moveResize = moveResize;
}
public IReadOnlyList<ManagedPopupPositionerScreenInfo> Screens =>
_parent.Screen.AllScreens.Select(s => new ManagedPopupPositionerScreenInfo(
s.Bounds.ToRect(1), s.WorkingArea.ToRect(1))).ToList();
public Rect ParentClientAreaScreenGeometry
{
get
{
// Popup positioner operates with abstract coordinates, but in our case they are pixel ones
var point = _parent.PointToScreen(default);
var size = PixelSize.FromSize(_parent.ClientSize, _parent.Scaling);
return new Rect(point.X, point.Y, size.Width, size.Height);
}
}
public void MoveAndResize(Point devicePoint, Size virtualSize)
{
_moveResize(new PixelPoint((int)devicePoint.X, (int)devicePoint.Y), virtualSize, _parent.Scaling);
}
public Point TranslatePoint(Point pt) => pt * _parent.Scaling;
public Size TranslateSize(Size size) => size * _parent.Scaling;
}
}

119
src/Avalonia.Controls/Primitives/PopupRoot.cs

@ -2,8 +2,9 @@
// Licensed under the MIT license. See licence.md file in the project root for full license information.
using System;
using Avalonia.Controls.Platform;
using Avalonia.Controls.Presenters;
using System.Collections.Generic;
using System.Reactive.Disposables;
using Avalonia.Controls.Primitives.PopupPositioning;
using Avalonia.Interactivity;
using Avalonia.Media;
using Avalonia.Platform;
@ -16,9 +17,10 @@ namespace Avalonia.Controls.Primitives
/// <summary>
/// The root window of a <see cref="Popup"/>.
/// </summary>
public class PopupRoot : WindowBase, IInteractive, IHostedVisualTreeRoot, IDisposable, IStyleHost
public class PopupRoot : WindowBase, IInteractive, IHostedVisualTreeRoot, IDisposable, IStyleHost, IPopupHost
{
private IDisposable _presenterSubscription;
private readonly TopLevel _parent;
private PopupPositionerParameters _positionerParameters;
/// <summary>
/// Initializes static members of the <see cref="PopupRoot"/> class.
@ -31,8 +33,8 @@ namespace Avalonia.Controls.Primitives
/// <summary>
/// Initializes a new instance of the <see cref="PopupRoot"/> class.
/// </summary>
public PopupRoot()
: this(null)
public PopupRoot(TopLevel parent, IPopupImpl impl)
: this(parent, impl,null)
{
}
@ -42,9 +44,10 @@ namespace Avalonia.Controls.Primitives
/// <param name="dependencyResolver">
/// The dependency resolver to use. If null the default dependency resolver will be used.
/// </param>
public PopupRoot(IAvaloniaDependencyResolver dependencyResolver)
: base(PlatformManager.CreatePopup(), dependencyResolver)
public PopupRoot(TopLevel parent, IPopupImpl impl, IAvaloniaDependencyResolver dependencyResolver)
: base(impl, dependencyResolver)
{
_parent = parent;
}
/// <summary>
@ -74,73 +77,61 @@ namespace Avalonia.Controls.Primitives
/// <inheritdoc/>
public void Dispose() => PlatformImpl?.Dispose();
/// <summary>
/// Moves the Popups position so that it doesnt overlap screen edges.
/// This method can be called immediately after Show has been called.
/// </summary>
public void SnapInsideScreenEdges()
private void UpdatePosition()
{
var screen = (VisualRoot as WindowBase)?.Screens?.ScreenFromPoint(Position);
if (screen != null)
{
var scaling = VisualRoot.RenderScaling;
var bounds = PixelRect.FromRect(Bounds, scaling);
var screenX = Position.X + bounds.Width - screen.Bounds.X;
var screenY = Position.Y + bounds.Height - screen.Bounds.Y;
if (screenX > screen.Bounds.Width)
{
Position = Position.WithX(Position.X - (screenX - screen.Bounds.Width));
}
if (screenY > screen.Bounds.Height)
{
Position = Position.WithY(Position.Y - (screenY - screen.Bounds.Height));
}
}
PlatformImpl?.PopupPositioner.Update(_positionerParameters);
}
/// <inheritdoc/>
protected override void OnTemplateApplied(TemplateAppliedEventArgs e)
public void ConfigurePosition(IVisual target, PlacementMode placement, Point offset,
PopupPositioningEdge anchor = PopupPositioningEdge.None,
PopupPositioningEdge gravity = PopupPositioningEdge.None)
{
base.OnTemplateApplied(e);
_positionerParameters.ConfigurePosition(_parent, target,
placement, offset, anchor, gravity);
if (_positionerParameters.Size != default)
UpdatePosition();
}
public void SetChild(IControl control) => Content = control;
if (Parent?.TemplatedParent != null)
IVisual IPopupHost.HostedVisualTreeRoot => this;
public IDisposable BindConstraints(AvaloniaObject popup, StyledProperty<double> widthProperty, StyledProperty<double> minWidthProperty,
StyledProperty<double> maxWidthProperty, StyledProperty<double> heightProperty, StyledProperty<double> minHeightProperty,
StyledProperty<double> maxHeightProperty, StyledProperty<bool> topmostProperty)
{
var bindings = new List<IDisposable>();
void Bind(AvaloniaProperty what, AvaloniaProperty to) => bindings.Add(this.Bind(what, popup[~to]));
Bind(WidthProperty, widthProperty);
Bind(MinWidthProperty, minWidthProperty);
Bind(MaxWidthProperty, maxWidthProperty);
Bind(HeightProperty, heightProperty);
Bind(MinHeightProperty, minHeightProperty);
Bind(MaxHeightProperty, maxHeightProperty);
Bind(TopmostProperty, topmostProperty);
return Disposable.Create(() =>
{
if (_presenterSubscription != null)
{
_presenterSubscription.Dispose();
_presenterSubscription = null;
}
Presenter?.ApplyTemplate();
Presenter?.GetObservable(ContentPresenter.ChildProperty)
.Subscribe(SetTemplatedParentAndApplyChildTemplates);
}
foreach (var x in bindings)
x.Dispose();
});
}
private void SetTemplatedParentAndApplyChildTemplates(IControl control)
/// <summary>
/// Carries out the arrange pass of the window.
/// </summary>
/// <param name="finalSize">The final window size.</param>
/// <returns>The <paramref name="finalSize"/> parameter unchanged.</returns>
protected override Size ArrangeOverride(Size finalSize)
{
if (control != null)
using (BeginAutoSizing())
{
var templatedParent = Parent.TemplatedParent;
if (control.TemplatedParent == null)
{
control.SetValue(TemplatedParentProperty, templatedParent);
}
control.ApplyTemplate();
if (!(control is IPresenter) && control.TemplatedParent == templatedParent)
{
foreach (IControl child in control.GetVisualChildren())
{
SetTemplatedParentAndApplyChildTemplates(child);
}
}
_positionerParameters.Size = finalSize;
UpdatePosition();
}
return base.ArrangeOverride(PlatformImpl?.ClientSize ?? default(Size));
}
}
}

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

@ -979,13 +979,14 @@ namespace Avalonia.Controls.Primitives
}
var item = ElementAt(Items, index);
var itemChanged = !Equals(item, oldItem);
var added = -1;
HashSet<int> removed = null;
_selectedIndex = index;
_selectedItem = item;
if (oldIndex != index || _selection.HasMultiple)
if (oldIndex != index || itemChanged || _selection.HasMultiple)
{
if (clear)
{
@ -1022,7 +1023,7 @@ namespace Avalonia.Controls.Primitives
index);
}
if (!Equals(item, oldItem))
if (itemChanged)
{
RaisePropertyChanged(
SelectedItemProperty,

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

@ -0,0 +1,93 @@
using System.Collections.Generic;
using Avalonia.LogicalTree;
using Avalonia.Styling;
namespace Avalonia.Controls.Primitives
{
public class VisualLayerManager : Decorator
{
private const int AdornerZIndex = int.MaxValue - 100;
private const int OverlayZIndex = int.MaxValue - 99;
private IStyleHost _styleRoot;
private readonly List<Control> _layers = new List<Control>();
public bool IsPopup { get; set; }
public AdornerLayer AdornerLayer
{
get
{
var rv = FindLayer<AdornerLayer>();
if (rv == null)
AddLayer(rv = new AdornerLayer(), AdornerZIndex);
return rv;
}
}
public OverlayLayer OverlayLayer
{
get
{
if (IsPopup)
return null;
var rv = FindLayer<OverlayLayer>();
if(rv == null)
AddLayer(rv = new OverlayLayer(), OverlayZIndex);
return rv;
}
}
T FindLayer<T>() where T : class
{
foreach (var layer in _layers)
if (layer is T match)
return match;
return null;
}
void AddLayer(Control layer, int zindex)
{
_layers.Add(layer);
((ISetLogicalParent)layer).SetParent(this);
layer.ZIndex = zindex;
VisualChildren.Add(layer);
if (((ILogical)this).IsAttachedToLogicalTree)
((ILogical)layer).NotifyAttachedToLogicalTree(new LogicalTreeAttachmentEventArgs(_styleRoot));
InvalidateArrange();
}
protected override void OnAttachedToLogicalTree(LogicalTreeAttachmentEventArgs e)
{
base.OnAttachedToLogicalTree(e);
_styleRoot = e.Root;
foreach (var l in _layers)
((ILogical)l).NotifyAttachedToLogicalTree(e);
}
protected override void OnDetachedFromLogicalTree(LogicalTreeAttachmentEventArgs e)
{
_styleRoot = null;
base.OnDetachedFromLogicalTree(e);
foreach (var l in _layers)
((ILogical)l).NotifyDetachedFromLogicalTree(e);
}
protected override Size MeasureOverride(Size availableSize)
{
foreach (var l in _layers)
l.Measure(availableSize);
return base.MeasureOverride(availableSize);
}
protected override Size ArrangeOverride(Size finalSize)
{
foreach (var l in _layers)
l.Arrange(new Rect(finalSize));
return base.ArrangeOverride(finalSize);
}
}
}

24
src/Avalonia.Controls/Remote/Server/RemoteServerTopLevelImpl.cs

@ -57,9 +57,13 @@ namespace Avalonia.Controls.Remote.Server
}
}
private static InputModifiers GetAvaloniaInputModifiers (Avalonia.Remote.Protocol.Input.InputModifiers[] modifiers)
private static RawInputModifiers GetAvaloniaRawInputModifiers(
Avalonia.Remote.Protocol.Input.InputModifiers[] modifiers)
=> (RawInputModifiers)GetAvaloniaInputModifiers(modifiers);
private static RawInputModifiers GetAvaloniaInputModifiers (Avalonia.Remote.Protocol.Input.InputModifiers[] modifiers)
{
var result = InputModifiers.None;
var result = RawInputModifiers.None;
if (modifiers == null)
{
@ -71,31 +75,31 @@ namespace Avalonia.Controls.Remote.Server
switch (modifier)
{
case Avalonia.Remote.Protocol.Input.InputModifiers.Control:
result |= InputModifiers.Control;
result |= RawInputModifiers.Control;
break;
case Avalonia.Remote.Protocol.Input.InputModifiers.Alt:
result |= InputModifiers.Alt;
result |= RawInputModifiers.Alt;
break;
case Avalonia.Remote.Protocol.Input.InputModifiers.Shift:
result |= InputModifiers.Shift;
result |= RawInputModifiers.Shift;
break;
case Avalonia.Remote.Protocol.Input.InputModifiers.Windows:
result |= InputModifiers.Windows;
result |= RawInputModifiers.Meta;
break;
case Avalonia.Remote.Protocol.Input.InputModifiers.LeftMouseButton:
result |= InputModifiers.LeftMouseButton;
result |= RawInputModifiers.LeftMouseButton;
break;
case Avalonia.Remote.Protocol.Input.InputModifiers.MiddleMouseButton:
result |= InputModifiers.MiddleMouseButton;
result |= RawInputModifiers.MiddleMouseButton;
break;
case Avalonia.Remote.Protocol.Input.InputModifiers.RightMouseButton:
result |= InputModifiers.RightMouseButton;
result |= RawInputModifiers.RightMouseButton;
break;
}
}
@ -225,7 +229,7 @@ namespace Avalonia.Controls.Remote.Server
0,
key.IsDown ? RawKeyEventType.KeyDown : RawKeyEventType.KeyUp,
(Key)key.Key,
GetAvaloniaInputModifiers(key.Modifiers)));
GetAvaloniaRawInputModifiers(key.Modifiers)));
}, DispatcherPriority.Input);
}
if(obj is TextInputEventMessage text)

4
src/Avalonia.Controls/Repeater/ItemsRepeater.cs

@ -707,9 +707,9 @@ namespace Avalonia.Controls
}
}
private void InvalidateArrangeForLayout(object sender, EventArgs e) => InvalidateMeasure();
private void InvalidateMeasureForLayout(object sender, EventArgs e) => InvalidateMeasure();
private void InvalidateMeasureForLayout(object sender, EventArgs e) => InvalidateArrange();
private void InvalidateArrangeForLayout(object sender, EventArgs e) => InvalidateArrange();
private VirtualizingLayoutContext GetLayoutContext()
{

8
src/Avalonia.Controls/Repeater/ItemsSourceView.cs

@ -35,9 +35,11 @@ namespace Avalonia.Controls
{
Contract.Requires<ArgumentNullException>(source != null);
_inner = source as IList;
if (_inner == null && source is IEnumerable<object> objectEnumerable)
if (source is IList list)
{
_inner = list;
}
else if (source is IEnumerable<object> objectEnumerable)
{
_inner = new List<object>(objectEnumerable);
}

16
src/Avalonia.Controls/SystemDialog.cs

@ -14,7 +14,13 @@ namespace Avalonia.Controls
public abstract class FileSystemDialog : SystemDialog
{
public string InitialDirectory { get; set; }
[Obsolete("Use Directory")]
public string InitialDirectory
{
get => Directory;
set => Directory = value;
}
public string Directory { get; set; }
}
public class SaveFileDialog : FileDialog
@ -45,8 +51,12 @@ namespace Avalonia.Controls
public class OpenFolderDialog : FileSystemDialog
{
public string DefaultDirectory { get; set; }
[Obsolete("Use Directory")]
public string DefaultDirectory
{
get => Directory;
set => Directory = value;
}
public Task<string> ShowAsync(Window parent)
{
if(parent == null)

13
src/Avalonia.Controls/TextBox.cs

@ -59,6 +59,9 @@ namespace Avalonia.Controls
o => o.SelectionEnd,
(o, v) => o.SelectionEnd = v);
public static readonly StyledProperty<int> MaxLengthProperty =
AvaloniaProperty.Register<TextBox, int>(nameof(MaxLength), defaultValue: 0);
public static readonly DirectProperty<TextBox, string> TextProperty =
TextBlock.TextProperty.AddOwner<TextBox>(
o => o.Text,
@ -232,6 +235,12 @@ namespace Avalonia.Controls
}
}
public int MaxLength
{
get { return GetValue(MaxLengthProperty); }
set { SetValue(MaxLengthProperty, value); }
}
[Content]
public string Text
{
@ -318,7 +327,7 @@ namespace Avalonia.Controls
private void DecideCaretVisibility()
{
_presenter.ShowCaret();
_presenter?.ShowCaret();
}
protected override void OnLostFocus(RoutedEventArgs e)
@ -345,7 +354,7 @@ namespace Avalonia.Controls
input = RemoveInvalidCharacters(input);
string text = Text ?? string.Empty;
int caretIndex = CaretIndex;
if (!string.IsNullOrEmpty(input))
if (!string.IsNullOrEmpty(input) && (MaxLength == 0 || input.Length + text.Length - (Math.Abs(SelectionStart - SelectionEnd)) <= MaxLength))
{
DeleteSelection();
caretIndex = CaretIndex;

14
src/Avalonia.Controls/ToolTip.cs

@ -4,6 +4,7 @@
using System;
using System.Reactive.Linq;
using Avalonia.Controls.Primitives;
using Avalonia.VisualTree;
namespace Avalonia.Controls
{
@ -60,7 +61,7 @@ namespace Avalonia.Controls
private static readonly AttachedProperty<ToolTip> ToolTipProperty =
AvaloniaProperty.RegisterAttached<ToolTip, Control, ToolTip>("ToolTip");
private PopupRoot _popup;
private IPopupHost _popup;
/// <summary>
/// Initializes static members of the <see cref="ToolTip"/> class.
@ -234,19 +235,20 @@ namespace Avalonia.Controls
{
Close();
_popup = new PopupRoot { Content = this, };
_popup = OverlayPopupHost.CreatePopupHost(control, null);
_popup.SetChild(this);
((ISetLogicalParent)_popup).SetParent(control);
_popup.Position = Popup.GetPosition(control, GetPlacement(control), _popup,
GetHorizontalOffset(control), GetVerticalOffset(control));
_popup.ConfigurePosition(control, GetPlacement(control),
new Point(GetHorizontalOffset(control), GetVerticalOffset(control)));
_popup.Show();
_popup.SnapInsideScreenEdges();
}
private void Close()
{
if (_popup != null)
{
_popup.Content = null;
_popup.SetChild(null);
_popup.Hide();
_popup = null;
}

2
src/Avalonia.Controls/TopLevel.cs

@ -269,8 +269,8 @@ namespace Avalonia.Controls
/// </summary>
protected virtual void HandleClosed()
{
(this as IInputRoot).MouseDevice?.TopLevelClosed(this);
PlatformImpl = null;
Closed?.Invoke(this, EventArgs.Empty);
Renderer?.Dispose();
Renderer = null;

4
src/Avalonia.Controls/TreeView.cs

@ -105,11 +105,13 @@ namespace Avalonia.Controls
get => _selectedItem;
set
{
var selectedItems = SelectedItems;
SetAndRaise(SelectedItemProperty, ref _selectedItem, value);
if (value != null)
{
if (SelectedItems.Count != 1 || SelectedItems[0] != value)
if (selectedItems.Count != 1 || selectedItems[0] != value)
{
_syncingSelectedItems = true;
SelectSingleItem(value);

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

Loading…
Cancel
Save