Browse Source

Merge remote-tracking branch 'origin/master' into xdatatype-to-datatype

# Conflicts:
#	tests/Avalonia.Markup.Xaml.UnitTests/Xaml/DataTemplateTests.cs
pull/8220/head
Max Katz 4 years ago
parent
commit
d15d1e75f4
  1. 1519
      Avalonia.sln
  2. 1
      build/CoreLibraries.props
  3. 6
      build/SkiaSharp.props
  4. 2
      native/Avalonia.Native/src/OSX/AvnPanelWindow.mm
  5. 4
      native/Avalonia.Native/src/OSX/AvnView.mm
  6. 96
      native/Avalonia.Native/src/OSX/AvnWindow.mm
  7. 2
      native/Avalonia.Native/src/OSX/INSWindowHolder.h
  8. 6
      native/Avalonia.Native/src/OSX/PopupImpl.mm
  9. 9
      native/Avalonia.Native/src/OSX/WindowBaseImpl.h
  10. 82
      native/Avalonia.Native/src/OSX/WindowBaseImpl.mm
  11. 9
      native/Avalonia.Native/src/OSX/WindowImpl.h
  12. 115
      native/Avalonia.Native/src/OSX/WindowImpl.mm
  13. 1
      native/Avalonia.Native/src/OSX/WindowProtocol.h
  14. 11
      native/Avalonia.Native/src/OSX/app.mm
  15. 1
      nukebuild/Build.cs
  16. 15
      readme.md
  17. 7
      samples/ControlCatalog.Android/ControlCatalog.Android.csproj
  18. 35
      samples/ControlCatalog.Android/EmbedSample.Android.cs
  19. 6
      samples/ControlCatalog.Android/MainActivity.cs
  20. 14
      samples/ControlCatalog.NetCore/ControlCatalog.NetCore.csproj
  21. 35
      samples/ControlCatalog.NetCore/NativeControls/Gtk/EmbedSample.Gtk.cs
  22. 58
      samples/ControlCatalog.NetCore/NativeControls/Gtk/GtkHelper.cs
  23. 0
      samples/ControlCatalog.NetCore/NativeControls/Gtk/nodes-license.md
  24. 0
      samples/ControlCatalog.NetCore/NativeControls/Gtk/nodes.mp4
  25. 29
      samples/ControlCatalog.NetCore/NativeControls/Mac/EmbedSample.Mac.cs
  26. 38
      samples/ControlCatalog.NetCore/NativeControls/Mac/MacHelper.cs
  27. 45
      samples/ControlCatalog.NetCore/NativeControls/Win/EmbedSample.Win.cs
  28. 73
      samples/ControlCatalog.NetCore/NativeControls/Win/WinApi.cs
  29. 8
      samples/ControlCatalog.NetCore/Program.cs
  30. 28
      samples/ControlCatalog.NetCore/app.manifest
  31. 4
      samples/ControlCatalog.Web/App.razor.cs
  32. 34
      samples/ControlCatalog.Web/EmbedSample.Browser.cs
  33. 70
      samples/ControlCatalog.Web/Shared/MainLayout.razor.css
  34. 44
      samples/ControlCatalog.Web/wwwroot/css/app.css
  35. 11
      samples/ControlCatalog.Web/wwwroot/js/app.js
  36. 9
      samples/ControlCatalog.iOS/AppDelegate.cs
  37. 38
      samples/ControlCatalog.iOS/EmbedSample.iOS.cs
  38. 15
      samples/ControlCatalog/ControlCatalog.csproj
  39. 10
      samples/ControlCatalog/Converter/MathSubtractConverter.cs
  40. 10
      samples/ControlCatalog/DecoratedWindow.xaml.cs
  41. 7
      samples/ControlCatalog/MainView.xaml
  42. 16
      samples/ControlCatalog/MainView.xaml.cs
  43. 8
      samples/ControlCatalog/MainWindow.xaml.cs
  44. 4
      samples/ControlCatalog/Models/Countries.cs
  45. 4
      samples/ControlCatalog/Models/GDPValueConverter.cs
  46. 16
      samples/ControlCatalog/Models/Person.cs
  47. 33
      samples/ControlCatalog/Pages/AutoCompleteBoxPage.xaml.cs
  48. 2
      samples/ControlCatalog/Pages/ButtonSpinnerPage.xaml.cs
  49. 4
      samples/ControlCatalog/Pages/ButtonsPage.xaml.cs
  50. 12
      samples/ControlCatalog/Pages/CalendarDatePickerPage.xaml.cs
  51. 4
      samples/ControlCatalog/Pages/CalendarPage.xaml.cs
  52. 11
      samples/ControlCatalog/Pages/CarouselPage.xaml.cs
  53. 22
      samples/ControlCatalog/Pages/ContextFlyoutPage.xaml.cs
  54. 6
      samples/ControlCatalog/Pages/ContextMenuPage.xaml.cs
  55. 8
      samples/ControlCatalog/Pages/DataGridPage.xaml.cs
  56. 4
      samples/ControlCatalog/Pages/DateTimePickerPage.xaml.cs
  57. 2
      samples/ControlCatalog/Pages/DialogsPage.xaml
  58. 45
      samples/ControlCatalog/Pages/DialogsPage.xaml.cs
  59. 6
      samples/ControlCatalog/Pages/DragAndDropPage.xaml.cs
  60. 8
      samples/ControlCatalog/Pages/FlyoutsPage.axaml.cs
  61. 13
      samples/ControlCatalog/Pages/ImagePage.xaml.cs
  62. 26
      samples/ControlCatalog/Pages/ItemsRepeaterPage.xaml.cs
  63. 2
      samples/ControlCatalog/Pages/LabelsPage.axaml.cs
  64. 2
      samples/ControlCatalog/Pages/MenuPage.xaml.cs
  65. 68
      samples/ControlCatalog/Pages/NativeEmbedPage.xaml
  66. 86
      samples/ControlCatalog/Pages/NativeEmbedPage.xaml.cs
  67. 2
      samples/ControlCatalog/Pages/NumericUpDownPage.xaml
  68. 10
      samples/ControlCatalog/Pages/NumericUpDownPage.xaml.cs
  69. 2
      samples/ControlCatalog/Pages/ScreenPage.cs
  70. 12
      samples/ControlCatalog/Pages/TabControlPage.xaml.cs
  71. 2
      samples/ControlCatalog/Pages/TabStripPage.xaml.cs
  72. 2
      samples/ControlCatalog/ViewModels/ApplicationViewModel.cs
  73. 2
      samples/ControlCatalog/ViewModels/ContextPageViewModel.cs
  74. 2
      samples/ControlCatalog/ViewModels/CursorPageViewModel.cs
  75. 19
      samples/ControlCatalog/ViewModels/ItemsRepeaterPageViewModel.cs
  76. 13
      samples/ControlCatalog/ViewModels/MainWindowViewModel.cs
  77. 8
      samples/ControlCatalog/ViewModels/MenuItemViewModel.cs
  78. 2
      samples/ControlCatalog/ViewModels/MenuPageViewModel.cs
  79. 4
      samples/ControlCatalog/ViewModels/NotificationViewModel.cs
  80. 16
      samples/ControlCatalog/ViewModels/TransitioningContentControlPageViewModel.cs
  81. 4
      samples/ControlCatalog/ViewModels/TreeViewPageViewModel.cs
  82. 12
      samples/RenderDemo/Pages/GlyphRunPage.xaml
  83. 113
      samples/RenderDemo/Pages/GlyphRunPage.xaml.cs
  84. 8
      samples/interop/NativeEmbedSample/App.xaml
  85. 22
      samples/interop/NativeEmbedSample/App.xaml.cs
  86. 121
      samples/interop/NativeEmbedSample/EmbedSample.cs
  87. 58
      samples/interop/NativeEmbedSample/GtkHelper.cs
  88. 39
      samples/interop/NativeEmbedSample/MacHelper.cs
  89. 52
      samples/interop/NativeEmbedSample/MainWindow.xaml
  90. 36
      samples/interop/NativeEmbedSample/MainWindow.xaml.cs
  91. 31
      samples/interop/NativeEmbedSample/NativeEmbedSample.csproj
  92. 17
      samples/interop/NativeEmbedSample/Program.cs
  93. 74
      samples/interop/NativeEmbedSample/WinApi.cs
  94. 2
      src/Android/Avalonia.Android/AndroidPlatform.cs
  95. 32
      src/Android/Avalonia.Android/AndroidViewControlHandle.cs
  96. 7
      src/Android/Avalonia.Android/AvaloniaActivity.cs
  97. 9
      src/Android/Avalonia.Android/AvaloniaView.cs
  98. 139
      src/Android/Avalonia.Android/Platform/AndroidNativeControlHostImpl.cs
  99. 10
      src/Android/Avalonia.Android/Platform/SkiaPlatform/TopLevelImpl.cs
  100. 2
      src/Android/Avalonia.Android/Platform/Specific/Helpers/AndroidKeyboardEventsHelper.cs

1519
Avalonia.sln

File diff suppressed because it is too large

1
build/CoreLibraries.props

@ -8,6 +8,5 @@
<ProjectReference Include="$(MSBuildThisFileDirectory)/../src/Markup/Avalonia.Markup/Avalonia.Markup.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/Markup/Avalonia.Markup.Xaml/Avalonia.Markup.Xaml.csproj" />
<ProjectReference Include="$(MSBuildThisFileDirectory)/../src/Avalonia.MicroCom/Avalonia.MicroCom.csproj" /> <ProjectReference Include="$(MSBuildThisFileDirectory)/../src/Avalonia.MicroCom/Avalonia.MicroCom.csproj" />
<ProjectReference Include="$(MSBuildThisFileDirectory)/../src/Avalonia.PlatformSupport/Avalonia.PlatformSupport.csproj" />
</ItemGroup> </ItemGroup>
</Project> </Project>

6
build/SkiaSharp.props

@ -1,7 +1,7 @@
<Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> <Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<ItemGroup> <ItemGroup>
<PackageReference Include="SkiaSharp" Version="2.88.0" /> <PackageReference Include="SkiaSharp" Version="2.88.1-preview.1" />
<PackageReference Condition="'$(IncludeLinuxSkia)' == 'true'" Include="SkiaSharp.NativeAssets.Linux" Version="2.88.0" /> <PackageReference Condition="'$(IncludeLinuxSkia)' == 'true'" Include="SkiaSharp.NativeAssets.Linux" Version="2.88.1-preview.1" />
<PackageReference Condition="'$(IncludeWasmSkia)' == 'true'" Include="SkiaSharp.NativeAssets.WebAssembly" Version="2.88.0" /> <PackageReference Condition="'$(IncludeWasmSkia)' == 'true'" Include="SkiaSharp.NativeAssets.WebAssembly" Version="2.88.1-preview.1" />
</ItemGroup> </ItemGroup>
</Project> </Project>

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

@ -3,8 +3,6 @@
// Copyright (c) 2022 Avalonia. All rights reserved. // Copyright (c) 2022 Avalonia. All rights reserved.
// //
#pragma once
#define IS_NSPANEL #define IS_NSPANEL
#include "AvnWindow.mm" #include "AvnWindow.mm"

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

@ -222,7 +222,7 @@
- (void)mouseEvent:(NSEvent *)event withType:(AvnRawMouseEventType) type - (void)mouseEvent:(NSEvent *)event withType:(AvnRawMouseEventType) type
{ {
bool triggerInputWhenDisabled = type != Move; bool triggerInputWhenDisabled = type != Move && type != LeaveWindow;
if([self ignoreUserInput: triggerInputWhenDisabled]) if([self ignoreUserInput: triggerInputWhenDisabled])
{ {
@ -709,4 +709,4 @@
return [[self accessibilityChild] accessibilityFocusedUIElement]; return [[self accessibilityChild] accessibilityFocusedUIElement];
} }
@end @end

96
native/Avalonia.Native/src/OSX/AvnWindow.mm

@ -33,6 +33,7 @@
bool _isEnabled; bool _isEnabled;
bool _canBecomeKeyWindow; bool _canBecomeKeyWindow;
bool _isExtended; bool _isExtended;
bool _isTransitioningToFullScreen;
AvnMenu* _menu; AvnMenu* _menu;
} }
@ -68,7 +69,7 @@
} }
} }
- (void)performClose:(id)sender - (void)performClose:(id _Nullable )sender
{ {
if([[self delegate] respondsToSelector:@selector(windowShouldClose:)]) if([[self delegate] respondsToSelector:@selector(windowShouldClose:)])
{ {
@ -147,7 +148,7 @@
} }
} }
-(void) applyMenu:(AvnMenu *)menu -(void) applyMenu:(AvnMenu *_Nullable)menu
{ {
if(menu == nullptr) if(menu == nullptr)
{ {
@ -157,7 +158,7 @@
_menu = menu; _menu = menu;
} }
-(CLASS_NAME*) initWithParent: (WindowBaseImpl*) parent contentRect: (NSRect)contentRect styleMask: (NSWindowStyleMask)styleMask; -(CLASS_NAME*_Nonnull) initWithParent: (WindowBaseImpl*_Nonnull) parent contentRect: (NSRect)contentRect styleMask: (NSWindowStyleMask)styleMask;
{ {
// https://jameshfisher.com/2020/07/10/why-is-the-contentrect-of-my-nswindow-ignored/ // https://jameshfisher.com/2020/07/10/why-is-the-contentrect-of-my-nswindow-ignored/
// create nswindow with specific contentRect, otherwise we wont be able to resize the window // create nswindow with specific contentRect, otherwise we wont be able to resize the window
@ -175,15 +176,17 @@
[self setBackgroundColor: [NSColor clearColor]]; [self setBackgroundColor: [NSColor clearColor]];
_isExtended = false; _isExtended = false;
_isTransitioningToFullScreen = false;
#ifdef IS_NSPANEL if(self.isDialog)
[self setCollectionBehavior:NSWindowCollectionBehaviorCanJoinAllSpaces|NSWindowCollectionBehaviorFullScreenAuxiliary]; {
#endif [self setCollectionBehavior:NSWindowCollectionBehaviorCanJoinAllSpaces|NSWindowCollectionBehaviorFullScreenAuxiliary];
}
return self; return self;
} }
- (BOOL)windowShouldClose:(NSWindow *)sender - (BOOL)windowShouldClose:(NSWindow *_Nonnull)sender
{ {
auto window = dynamic_cast<WindowImpl*>(_parent.getRaw()); auto window = dynamic_cast<WindowImpl*>(_parent.getRaw());
@ -195,21 +198,28 @@
return true; return true;
} }
- (void)windowDidChangeBackingProperties:(NSNotification *)notification - (void)windowDidChangeBackingProperties:(NSNotification *_Nonnull)notification
{ {
[self backingScaleFactor]; [self backingScaleFactor];
} }
- (void)windowWillClose:(NSNotification *)notification - (void)windowWillClose:(NSNotification *_Nonnull)notification
{ {
_closed = true; _closed = true;
if(_parent) if(_parent)
{ {
ComPtr<WindowBaseImpl> parent = _parent; ComPtr<WindowBaseImpl> parent = _parent;
_parent = NULL; _parent = NULL;
[self restoreParentWindow];
auto window = dynamic_cast<WindowImpl*>(parent.getRaw());
if(window != nullptr)
{
window->SetParent(nullptr);
}
parent->BaseEvents->Closed(); parent->BaseEvents->Closed();
[parent->View onClosed]; [parent->View onClosed];
} }
@ -220,17 +230,11 @@
if(_canBecomeKeyWindow) if(_canBecomeKeyWindow)
{ {
// If the window has a child window being shown as a dialog then don't allow it to become the key window. // If the window has a child window being shown as a dialog then don't allow it to become the key window.
for(NSWindow* uch in [self childWindows]) auto parent = dynamic_cast<WindowImpl*>(_parent.getRaw());
if(parent != nullptr)
{ {
if (![uch conformsToProtocol:@protocol(AvnWindowProtocol)]) return parent->CanBecomeKeyWindow();
{
continue;
}
id <AvnWindowProtocol> ch = (id <AvnWindowProtocol>) uch;
if(ch.isDialog)
return false;
} }
return true; return true;
@ -259,6 +263,10 @@
-(void) setEnabled:(bool)enable -(void) setEnabled:(bool)enable
{ {
_isEnabled = enable; _isEnabled = enable;
[[self standardWindowButton:NSWindowCloseButton] setEnabled:enable];
[[self standardWindowButton:NSWindowMiniaturizeButton] setEnabled:enable];
[[self standardWindowButton:NSWindowZoomButton] setEnabled:enable];
} }
-(void)becomeKeyWindow -(void)becomeKeyWindow
@ -273,17 +281,20 @@
[super becomeKeyWindow]; [super becomeKeyWindow];
} }
-(void) restoreParentWindow; - (void)windowDidBecomeKey:(NSNotification *_Nonnull)notification
{ {
auto parent = [self parentWindow]; _parent->BringToFront();
if(parent != nil) dispatch_async(dispatch_get_main_queue(), ^{
{ @try {
[parent removeChildWindow:self]; [self invalidateShadow];
} }
@finally{
}
});
} }
- (void)windowDidMiniaturize:(NSNotification *)notification - (void)windowDidMiniaturize:(NSNotification *_Nonnull)notification
{ {
auto parent = dynamic_cast<IWindowStateChanged*>(_parent.operator->()); auto parent = dynamic_cast<IWindowStateChanged*>(_parent.operator->());
@ -293,7 +304,7 @@
} }
} }
- (void)windowDidDeminiaturize:(NSNotification *)notification - (void)windowDidDeminiaturize:(NSNotification *_Nonnull)notification
{ {
auto parent = dynamic_cast<IWindowStateChanged*>(_parent.operator->()); auto parent = dynamic_cast<IWindowStateChanged*>(_parent.operator->());
@ -303,7 +314,7 @@
} }
} }
- (void)windowDidResize:(NSNotification *)notification - (void)windowDidResize:(NSNotification *_Nonnull)notification
{ {
auto parent = dynamic_cast<IWindowStateChanged*>(_parent.operator->()); auto parent = dynamic_cast<IWindowStateChanged*>(_parent.operator->());
@ -313,7 +324,7 @@
} }
} }
- (void)windowWillExitFullScreen:(NSNotification *)notification - (void)windowWillExitFullScreen:(NSNotification *_Nonnull)notification
{ {
auto parent = dynamic_cast<IWindowStateChanged*>(_parent.operator->()); auto parent = dynamic_cast<IWindowStateChanged*>(_parent.operator->());
@ -323,7 +334,7 @@
} }
} }
- (void)windowDidExitFullScreen:(NSNotification *)notification - (void)windowDidExitFullScreen:(NSNotification *_Nonnull)notification
{ {
auto parent = dynamic_cast<IWindowStateChanged*>(_parent.operator->()); auto parent = dynamic_cast<IWindowStateChanged*>(_parent.operator->());
@ -346,8 +357,9 @@
} }
} }
- (void)windowWillEnterFullScreen:(NSNotification *)notification - (void)windowWillEnterFullScreen:(NSNotification *_Nonnull)notification
{ {
_isTransitioningToFullScreen = true;
auto parent = dynamic_cast<IWindowStateChanged*>(_parent.operator->()); auto parent = dynamic_cast<IWindowStateChanged*>(_parent.operator->());
if(parent != nullptr) if(parent != nullptr)
@ -356,8 +368,9 @@
} }
} }
- (void)windowDidEnterFullScreen:(NSNotification *)notification - (void)windowDidEnterFullScreen:(NSNotification *_Nonnull)notification
{ {
_isTransitioningToFullScreen = false;
auto parent = dynamic_cast<IWindowStateChanged*>(_parent.operator->()); auto parent = dynamic_cast<IWindowStateChanged*>(_parent.operator->());
if(parent != nullptr) if(parent != nullptr)
@ -367,7 +380,7 @@
} }
} }
- (BOOL)windowShouldZoom:(NSWindow *)window toFrame:(NSRect)newFrame - (BOOL)windowShouldZoom:(NSWindow *_Nonnull)window toFrame:(NSRect)newFrame
{ {
return true; return true;
} }
@ -378,11 +391,13 @@
_parent->BaseEvents->Deactivated(); _parent->BaseEvents->Deactivated();
[self showAppMenuOnly]; [self showAppMenuOnly];
[self invalidateShadow];
[super resignKeyWindow]; [super resignKeyWindow];
} }
- (void)windowDidMove:(NSNotification *)notification - (void)windowDidMove:(NSNotification *_Nonnull)notification
{ {
AvnPoint position; AvnPoint position;
@ -414,7 +429,7 @@
return pt; return pt;
} }
- (void)sendEvent:(NSEvent *)event - (void)sendEvent:(NSEvent *_Nonnull)event
{ {
[super sendEvent:event]; [super sendEvent:event];
@ -437,8 +452,13 @@
_parent->BaseEvents->RawMouseEvent(NonClientLeftButtonDown, static_cast<uint32>([event timestamp] * 1000), AvnInputModifiersNone, point, delta); _parent->BaseEvents->RawMouseEvent(NonClientLeftButtonDown, static_cast<uint32>([event timestamp] * 1000), AvnInputModifiersNone, point, delta);
} }
if(!_isTransitioningToFullScreen)
{
_parent->BringToFront();
}
} }
break; break;
case NSEventTypeMouseEntered: case NSEventTypeMouseEntered:
{ {

2
native/Avalonia.Native/src/OSX/INSWindowHolder.h

@ -11,7 +11,7 @@
struct INSWindowHolder struct INSWindowHolder
{ {
virtual NSWindow* _Nonnull GetNSWindow () = 0; virtual NSWindow* _Nonnull GetNSWindow () = 0;
virtual NSView* _Nonnull GetNSView () = 0; virtual AvnView* _Nonnull GetNSView () = 0;
}; };
#endif //AVALONIA_NATIVE_OSX_INSWINDOWHOLDER_H #endif //AVALONIA_NATIVE_OSX_INSWINDOWHOLDER_H

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

@ -26,17 +26,13 @@ private:
PopupImpl(IAvnWindowEvents* events, IAvnGlContext* gl) : WindowBaseImpl(events, gl) PopupImpl(IAvnWindowEvents* events, IAvnGlContext* gl) : WindowBaseImpl(events, gl)
{ {
WindowEvents = events; WindowEvents = events;
[Window setLevel:NSPopUpMenuWindowLevel];
} }
protected: protected:
virtual NSWindowStyleMask GetStyle() override virtual NSWindowStyleMask GetStyle() override
{ {
return NSWindowStyleMaskBorderless; return NSWindowStyleMaskBorderless;
} }
virtual void OnInitialiseNSWindow () override
{
[Window setLevel:NSPopUpMenuWindowLevel];
}
public: public:
virtual bool ShouldTakeFocusOnShow() override virtual bool ShouldTakeFocusOnShow() override

9
native/Avalonia.Native/src/OSX/WindowBaseImpl.h

@ -26,7 +26,7 @@ BEGIN_INTERFACE_MAP()
virtual ~WindowBaseImpl(); virtual ~WindowBaseImpl();
WindowBaseImpl(IAvnWindowBaseEvents *events, IAvnGlContext *gl); WindowBaseImpl(IAvnWindowBaseEvents *events, IAvnGlContext *gl, bool usePanel = false);
virtual HRESULT ObtainNSWindowHandle(void **ret) override; virtual HRESULT ObtainNSWindowHandle(void **ret) override;
@ -38,7 +38,7 @@ BEGIN_INTERFACE_MAP()
virtual NSWindow *GetNSWindow() override; virtual NSWindow *GetNSWindow() override;
virtual NSView *GetNSView() override; virtual AvnView *GetNSView() override;
virtual HRESULT Show(bool activate, bool isDialog) override; virtual HRESULT Show(bool activate, bool isDialog) override;
@ -99,18 +99,17 @@ BEGIN_INTERFACE_MAP()
virtual bool IsDialog(); virtual bool IsDialog();
id<AvnWindowProtocol> GetWindowProtocol (); id<AvnWindowProtocol> GetWindowProtocol ();
virtual void BringToFront ();
protected: protected:
virtual NSWindowStyleMask GetStyle(); virtual NSWindowStyleMask GetStyle();
void UpdateStyle(); void UpdateStyle();
virtual void OnInitialiseNSWindow ();
private: private:
void CreateNSWindow (bool isDialog); void CreateNSWindow (bool isDialog);
void CleanNSWindow (); void CleanNSWindow ();
void InitialiseNSWindow ();
NSCursor *cursor; NSCursor *cursor;
ComPtr<IAvnGlContext> _glContext; ComPtr<IAvnGlContext> _glContext;

82
native/Avalonia.Native/src/OSX/WindowBaseImpl.mm

@ -21,7 +21,7 @@ WindowBaseImpl::~WindowBaseImpl() {
Window = nullptr; Window = nullptr;
} }
WindowBaseImpl::WindowBaseImpl(IAvnWindowBaseEvents *events, IAvnGlContext *gl) { WindowBaseImpl::WindowBaseImpl(IAvnWindowBaseEvents *events, IAvnGlContext *gl, bool usePanel) {
_shown = false; _shown = false;
_inResize = false; _inResize = false;
BaseEvents = events; BaseEvents = events;
@ -36,8 +36,19 @@ WindowBaseImpl::WindowBaseImpl(IAvnWindowBaseEvents *events, IAvnGlContext *gl)
lastMaxSize = NSSize { CGFLOAT_MAX, CGFLOAT_MAX}; lastMaxSize = NSSize { CGFLOAT_MAX, CGFLOAT_MAX};
lastMinSize = NSSize { 0, 0 }; lastMinSize = NSSize { 0, 0 };
Window = nullptr;
lastMenu = nullptr; lastMenu = nullptr;
CreateNSWindow(usePanel);
[Window setContentView:StandardContainer];
[Window setStyleMask:NSWindowStyleMaskBorderless];
[Window setBackingType:NSBackingStoreBuffered];
[Window setContentMinSize:lastMinSize];
[Window setContentMaxSize:lastMaxSize];
[Window setOpaque:false];
[Window setHasShadow:true];
} }
HRESULT WindowBaseImpl::ObtainNSViewHandle(void **ret) { HRESULT WindowBaseImpl::ObtainNSViewHandle(void **ret) {
@ -68,7 +79,7 @@ NSWindow *WindowBaseImpl::GetNSWindow() {
return Window; return Window;
} }
NSView *WindowBaseImpl::GetNSView() { AvnView *WindowBaseImpl::GetNSView() {
return View; return View;
} }
@ -88,9 +99,8 @@ HRESULT WindowBaseImpl::Show(bool activate, bool isDialog) {
START_COM_CALL; START_COM_CALL;
@autoreleasepool { @autoreleasepool {
CreateNSWindow(isDialog); [Window setContentSize:lastSize];
InitialiseNSWindow();
if(hasPosition) if(hasPosition)
{ {
SetPosition(lastPositionSet); SetPosition(lastPositionSet);
@ -100,6 +110,8 @@ HRESULT WindowBaseImpl::Show(bool activate, bool isDialog) {
} }
UpdateStyle(); UpdateStyle();
[Window invalidateShadow];
if (ShouldTakeFocusOnShow() && activate) { if (ShouldTakeFocusOnShow() && activate) {
[Window orderFront:Window]; [Window orderFront:Window];
@ -143,8 +155,6 @@ HRESULT WindowBaseImpl::Hide() {
@autoreleasepool { @autoreleasepool {
if (Window != nullptr) { if (Window != nullptr) {
[Window orderOut:Window]; [Window orderOut:Window];
[GetWindowProtocol() restoreParentWindow];
} }
return S_OK; return S_OK;
@ -288,15 +298,15 @@ HRESULT WindowBaseImpl::Resize(double x, double y, AvnPlatformResizeReason reaso
} }
@try { @try {
lastSize = NSSize {x, y}; if(x != lastSize.width || y != lastSize.height) {
lastSize = NSSize{x, y};
if (!_shown) {
BaseEvents->Resized(AvnSize{x, y}, reason); if (!_shown) {
} BaseEvents->Resized(AvnSize{x, y}, reason);
} else if (Window != nullptr) {
if(Window != nullptr) { [Window setContentSize:lastSize];
[Window setContentSize:lastSize]; [Window invalidateShadow];
[Window invalidateShadow]; }
} }
} }
@finally { @finally {
@ -558,6 +568,8 @@ void WindowBaseImpl::CreateNSWindow(bool isDialog) {
CleanNSWindow(); CleanNSWindow();
Window = [[AvnPanel alloc] initWithParent:this contentRect:NSRect{0, 0, lastSize} styleMask:GetStyle()]; Window = [[AvnPanel alloc] initWithParent:this contentRect:NSRect{0, 0, lastSize} styleMask:GetStyle()];
[Window setHidesOnDeactivate:false];
} }
} else { } else {
if (![Window isKindOfClass:[AvnWindow class]]) { if (![Window isKindOfClass:[AvnWindow class]]) {
@ -568,37 +580,6 @@ void WindowBaseImpl::CreateNSWindow(bool isDialog) {
} }
} }
void WindowBaseImpl::OnInitialiseNSWindow()
{
}
void WindowBaseImpl::InitialiseNSWindow() {
if(Window != nullptr) {
[Window setContentView:StandardContainer];
[Window setStyleMask:NSWindowStyleMaskBorderless];
[Window setBackingType:NSBackingStoreBuffered];
[Window setContentSize:lastSize];
[Window setContentMinSize:lastMinSize];
[Window setContentMaxSize:lastMaxSize];
[Window setOpaque:false];
[Window invalidateShadow];
if (lastMenu != nullptr) {
[GetWindowProtocol() applyMenu:lastMenu];
if ([Window isKeyWindow]) {
[GetWindowProtocol() showWindowMenuWithAppMenu];
}
}
OnInitialiseNSWindow();
}
}
id <AvnWindowProtocol> WindowBaseImpl::GetWindowProtocol() { id <AvnWindowProtocol> WindowBaseImpl::GetWindowProtocol() {
if(Window == nullptr) if(Window == nullptr)
{ {
@ -608,6 +589,11 @@ id <AvnWindowProtocol> WindowBaseImpl::GetWindowProtocol() {
return (id <AvnWindowProtocol>) Window; return (id <AvnWindowProtocol>) Window;
} }
void WindowBaseImpl::BringToFront()
{
// do nothing.
}
extern IAvnWindow* CreateAvnWindow(IAvnWindowEvents*events, IAvnGlContext* gl) extern IAvnWindow* CreateAvnWindow(IAvnWindowEvents*events, IAvnGlContext* gl)
{ {
@autoreleasepool @autoreleasepool

9
native/Avalonia.Native/src/OSX/WindowImpl.h

@ -8,10 +8,12 @@
#import "WindowBaseImpl.h" #import "WindowBaseImpl.h"
#include "IWindowStateChanged.h" #include "IWindowStateChanged.h"
#include <list>
class WindowImpl : public virtual WindowBaseImpl, public virtual IAvnWindow, public IWindowStateChanged class WindowImpl : public virtual WindowBaseImpl, public virtual IAvnWindow, public IWindowStateChanged
{ {
private: private:
bool _isEnabled;
bool _canResize; bool _canResize;
bool _fullScreenActive; bool _fullScreenActive;
SystemDecorations _decorations; SystemDecorations _decorations;
@ -22,6 +24,8 @@ private:
bool _transitioningWindowState; bool _transitioningWindowState;
bool _isClientAreaExtended; bool _isClientAreaExtended;
bool _isDialog; bool _isDialog;
WindowImpl* _parent;
std::list<WindowImpl*> _children;
AvnExtendClientAreaChromeHints _extendClientHints; AvnExtendClientAreaChromeHints _extendClientHints;
FORWARD_IUNKNOWN() FORWARD_IUNKNOWN()
@ -89,12 +93,15 @@ BEGIN_INTERFACE_MAP()
virtual bool IsDialog() override; virtual bool IsDialog() override;
virtual void OnInitialiseNSWindow() override; virtual void BringToFront () override;
bool CanBecomeKeyWindow ();
protected: protected:
virtual NSWindowStyleMask GetStyle() override; virtual NSWindowStyleMask GetStyle() override;
private: private:
void OnInitialiseNSWindow();
NSString *_lastTitle; NSString *_lastTitle;
}; };

115
native/Avalonia.Native/src/OSX/WindowImpl.mm

@ -10,6 +10,8 @@
#include "WindowProtocol.h" #include "WindowProtocol.h"
WindowImpl::WindowImpl(IAvnWindowEvents *events, IAvnGlContext *gl) : WindowBaseImpl(events, gl) { WindowImpl::WindowImpl(IAvnWindowEvents *events, IAvnGlContext *gl) : WindowBaseImpl(events, gl) {
_isEnabled = true;
_children = std::list<WindowImpl*>();
_isClientAreaExtended = false; _isClientAreaExtended = false;
_extendClientHints = AvnDefaultChrome; _extendClientHints = AvnDefaultChrome;
_fullScreenActive = false; _fullScreenActive = false;
@ -20,7 +22,10 @@ WindowImpl::WindowImpl(IAvnWindowEvents *events, IAvnGlContext *gl) : WindowBase
_lastWindowState = Normal; _lastWindowState = Normal;
_actualWindowState = Normal; _actualWindowState = Normal;
_lastTitle = @""; _lastTitle = @"";
_parent = nullptr;
WindowEvents = events; WindowEvents = events;
OnInitialiseNSWindow();
} }
void WindowImpl::HideOrShowTrafficLights() { void WindowImpl::HideOrShowTrafficLights() {
@ -28,28 +33,17 @@ void WindowImpl::HideOrShowTrafficLights() {
return; return;
} }
for (id subview in Window.contentView.superview.subviews) { bool wantsChrome = (_extendClientHints & AvnSystemChrome) || (_extendClientHints & AvnPreferSystemChrome);
if ([subview isKindOfClass:NSClassFromString(@"NSTitlebarContainerView")]) { bool hasTrafficLights = _isClientAreaExtended ? wantsChrome : _decorations == SystemDecorationsFull;
NSView *titlebarView = [subview subviews][0];
for (id button in titlebarView.subviews) { [[Window standardWindowButton:NSWindowCloseButton] setHidden:!hasTrafficLights];
if ([button isKindOfClass:[NSButton class]]) { [[Window standardWindowButton:NSWindowMiniaturizeButton] setHidden:!hasTrafficLights];
if (_isClientAreaExtended) { [[Window standardWindowButton:NSWindowZoomButton] setHidden:!hasTrafficLights];
auto wantsChrome = (_extendClientHints & AvnSystemChrome) || (_extendClientHints & AvnPreferSystemChrome);
[button setHidden:!wantsChrome];
} else {
[button setHidden:(_decorations != SystemDecorationsFull)];
}
[button setWantsLayer:true];
}
}
}
}
} }
void WindowImpl::OnInitialiseNSWindow(){ void WindowImpl::OnInitialiseNSWindow(){
[GetWindowProtocol() setCanBecomeKeyWindow:true]; [GetWindowProtocol() setCanBecomeKeyWindow:true];
[Window disableCursorRects]; [Window disableCursorRects];
[Window setTabbingMode:NSWindowTabbingModeDisallowed]; [Window setTabbingMode:NSWindowTabbingModeDisallowed];
[Window setCollectionBehavior:NSWindowCollectionBehaviorFullScreenPrimary]; [Window setCollectionBehavior:NSWindowCollectionBehaviorFullScreenPrimary];
@ -81,7 +75,9 @@ HRESULT WindowImpl::SetEnabled(bool enable) {
START_COM_CALL; START_COM_CALL;
@autoreleasepool { @autoreleasepool {
_isEnabled = enable;
[GetWindowProtocol() setEnabled:enable]; [GetWindowProtocol() setEnabled:enable];
UpdateStyle();
return S_OK; return S_OK;
} }
} }
@ -90,26 +86,68 @@ HRESULT WindowImpl::SetParent(IAvnWindow *parent) {
START_COM_CALL; START_COM_CALL;
@autoreleasepool { @autoreleasepool {
if (parent == nullptr) if(_parent != nullptr)
return E_POINTER; {
_parent->_children.remove(this);
_parent->BringToFront();
}
auto cparent = dynamic_cast<WindowImpl *>(parent); auto cparent = dynamic_cast<WindowImpl *>(parent);
if (cparent == nullptr)
return E_INVALIDARG; _parent = cparent;
// If one tries to show a child window with a minimized parent window, then the parent window will be if(_parent != nullptr && Window != nullptr){
// restored but macOS isn't kind enough to *tell* us that, so the window will be left in a non-interactive // If one tries to show a child window with a minimized parent window, then the parent window will be
// state. Detect this and explicitly restore the parent window ourselves to avoid this situation. // restored but macOS isn't kind enough to *tell* us that, so the window will be left in a non-interactive
if (cparent->WindowState() == Minimized) // state. Detect this and explicitly restore the parent window ourselves to avoid this situation.
cparent->SetWindowState(Normal); if (cparent->WindowState() == Minimized)
cparent->SetWindowState(Normal);
[Window setCollectionBehavior:NSWindowCollectionBehaviorFullScreenAuxiliary];
cparent->_children.push_back(this);
UpdateStyle();
}
[Window setCollectionBehavior:NSWindowCollectionBehaviorFullScreenAuxiliary]; return S_OK;
[cparent->Window addChildWindow:Window ordered:NSWindowAbove]; }
}
UpdateStyle(); void WindowImpl::BringToFront()
{
if(Window != nullptr)
{
if(IsDialog())
{
Activate();
}
else
{
[Window orderFront:nullptr];
}
[Window invalidateShadow];
for(auto iterator = _children.begin(); iterator != _children.end(); iterator++)
{
(*iterator)->BringToFront();
}
}
}
return S_OK; bool WindowImpl::CanBecomeKeyWindow()
{
for(auto iterator = _children.begin(); iterator != _children.end(); iterator++)
{
if((*iterator)->IsDialog())
{
return false;
}
} }
return true;
} }
void WindowImpl::StartStateTransition() { void WindowImpl::StartStateTransition() {
@ -523,7 +561,12 @@ bool WindowImpl::IsDialog() {
} }
NSWindowStyleMask WindowImpl::GetStyle() { NSWindowStyleMask WindowImpl::GetStyle() {
unsigned long s = this->_isDialog ? NSWindowStyleMaskDocModalWindow : NSWindowStyleMaskBorderless; unsigned long s = NSWindowStyleMaskBorderless;
if(_actualWindowState == FullScreen)
{
s |= NSWindowStyleMaskFullScreen;
}
switch (_decorations) { switch (_decorations) {
case SystemDecorationsNone: case SystemDecorationsNone:
@ -535,15 +578,15 @@ NSWindowStyleMask WindowImpl::GetStyle() {
break; break;
case SystemDecorationsFull: case SystemDecorationsFull:
s = s | NSWindowStyleMaskTitled | NSWindowStyleMaskClosable | NSWindowStyleMaskBorderless; s = s | NSWindowStyleMaskTitled | NSWindowStyleMaskClosable;
if (_canResize) { if (_canResize && _isEnabled) {
s = s | NSWindowStyleMaskResizable; s = s | NSWindowStyleMaskResizable;
} }
break; break;
} }
if ([Window parentWindow] == nullptr) { if (!IsDialog()) {
s |= NSWindowStyleMaskMiniaturizable; s |= NSWindowStyleMaskMiniaturizable;
} }

1
native/Avalonia.Native/src/OSX/WindowProtocol.h

@ -11,7 +11,6 @@
@protocol AvnWindowProtocol @protocol AvnWindowProtocol
-(void) pollModalSession: (NSModalSession _Nonnull) session; -(void) pollModalSession: (NSModalSession _Nonnull) session;
-(void) restoreParentWindow;
-(bool) shouldTryToHandleEvents; -(bool) shouldTryToHandleEvents;
-(void) setEnabled: (bool) enable; -(void) setEnabled: (bool) enable;
-(void) showAppMenuOnly; -(void) showAppMenuOnly;

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

@ -82,6 +82,17 @@ ComPtr<IAvnApplicationEvents> _events;
_isHandlingSendEvent = oldHandling; _isHandlingSendEvent = oldHandling;
} }
} }
// This is needed for certain embedded controls DO NOT REMOVE..
- (BOOL) isHandlingSendEvent
{
return _isHandlingSendEvent;
}
- (void)setHandlingSendEvent:(BOOL)handlingSendEvent
{
_isHandlingSendEvent = handlingSendEvent;
}
@end @end
extern void InitializeAvnApp(IAvnApplicationEvents* events) extern void InitializeAvnApp(IAvnApplicationEvents* events)

1
nukebuild/Build.cs

@ -221,7 +221,6 @@ partial class Build : NukeBuild
RunCoreTest("Avalonia.Markup.Xaml.UnitTests"); RunCoreTest("Avalonia.Markup.Xaml.UnitTests");
RunCoreTest("Avalonia.Skia.UnitTests"); RunCoreTest("Avalonia.Skia.UnitTests");
RunCoreTest("Avalonia.ReactiveUI.UnitTests"); RunCoreTest("Avalonia.ReactiveUI.UnitTests");
RunCoreTest("Avalonia.PlatformSupport.UnitTests");
}); });
Target RunRenderTests => _ => _ Target RunRenderTests => _ => _

15
readme.md

@ -70,11 +70,15 @@ For more information see the [.NET Foundation Code of Conduct](https://dotnetfou
Avalonia is licenced under the [MIT licence](licence.md). Avalonia is licenced under the [MIT licence](licence.md).
## Support Avalonia ## Donate
**BTC**: bc1q05wx78qemgy9x6ytl5ljk2xrt00yqargyjm8gx Donating to the project is a fantastic way to thank our valued contributors for their hard work. Your donations are shared among our community and awarded for significant contributions.
If you need support see Commercial Support section below.
Donate with BTC or use [Open Collective](https://opencollective.com/avalonia).
This will be shared with the community and awarded for significant contributions. **BTC**: bc1q05wx78qemgy9x6ytl5ljk2xrt00yqargyjm8gx
### Backers ### Backers
@ -98,6 +102,11 @@ Support this project by becoming a sponsor. Your logo will show up here with a l
<a href="https://opencollective.com/Avalonia/sponsor/9/website" target="_blank"><img src="https://opencollective.com/Avalonia/sponsor/9/avatar.svg"></a> <a href="https://opencollective.com/Avalonia/sponsor/9/website" target="_blank"><img src="https://opencollective.com/Avalonia/sponsor/9/avatar.svg"></a>
<a href="https://baseheadinc.com/" target="_blank"><img height="50" src="https://baseheadinc.com/wp-content/uploads/2020/09/BH-Logo-for-Site-Header-New.png"></a> <a href="https://baseheadinc.com/" target="_blank"><img height="50" src="https://baseheadinc.com/wp-content/uploads/2020/09/BH-Logo-for-Site-Header-New.png"></a>
## Commercial Support
We have a range of [support plans available](https://avaloniaui.net/support.html) for those looking to partner with the creators of Avalonia, enabling access to the best support at every step of the development process.
*Please note that donations are not considered payment for commercial support agreements. Please contact us to discuss your needs first. [team@avaloniaui.net](mailto://team@avaloniaui.net)*
## .NET Foundation ## .NET Foundation
This project is supported by the [.NET Foundation](https://dotnetfoundation.org). This project is supported by the [.NET Foundation](https://dotnetfoundation.org).

7
samples/ControlCatalog.Android/ControlCatalog.Android.csproj

@ -20,10 +20,13 @@
</ItemGroup> </ItemGroup>
<PropertyGroup Condition="'$(Configuration)'=='Release' and '$(TF_BUILD)' == ''"> <PropertyGroup Condition="'$(Configuration)'=='Release' and '$(TF_BUILD)' == ''">
<DebugSymbols>True</DebugSymbols> <DebugSymbols>False</DebugSymbols>
<UseInterpreter>False</UseInterpreter>
<RunAOTCompilation>True</RunAOTCompilation> <RunAOTCompilation>True</RunAOTCompilation>
<EnableLLVM>True</EnableLLVM> <EnableLLVM>True</EnableLLVM>
<AndroidEnableProfiledAot>True</AndroidEnableProfiledAot> <AndroidAotAdditionalArguments>no-write-symbols,nodebug</AndroidAotAdditionalArguments>
<AndroidAotMode>Hybrid</AndroidAotMode>
<AndroidGenerateJniMarshalMethods>True</AndroidGenerateJniMarshalMethods>
</PropertyGroup> </PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'"> <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'">

35
samples/ControlCatalog.Android/EmbedSample.Android.cs

@ -0,0 +1,35 @@
using System;
using Avalonia.Platform;
using Avalonia.Android;
using ControlCatalog.Pages;
namespace ControlCatalog.Android;
public class EmbedSampleAndroid : INativeDemoControl
{
public IPlatformHandle CreateControl(bool isSecond, IPlatformHandle parent, Func<IPlatformHandle> createDefault)
{
var parentContext = (parent as AndroidViewControlHandle)?.View.Context
?? global::Android.App.Application.Context;
if (isSecond)
{
var webView = new global::Android.Webkit.WebView(parentContext);
webView.LoadUrl("https://www.android.com/");
return new AndroidViewControlHandle(webView);
}
else
{
var button = new global::Android.Widget.Button(parentContext) { Text = "Hello world" };
var clickCount = 0;
button.Click += (sender, args) =>
{
clickCount++;
button.Text = $"Click count {clickCount}";
};
return new AndroidViewControlHandle(button);
}
}
}

6
samples/ControlCatalog.Android/MainActivity.cs

@ -10,7 +10,11 @@ namespace ControlCatalog.Android
{ {
protected override AppBuilder CustomizeAppBuilder(AppBuilder builder) protected override AppBuilder CustomizeAppBuilder(AppBuilder builder)
{ {
return base.CustomizeAppBuilder(builder); return base.CustomizeAppBuilder(builder)
.AfterSetup(_ =>
{
Pages.EmbedSample.Implementation = new EmbedSampleAndroid();
});
} }
} }
} }

14
samples/ControlCatalog.NetCore/ControlCatalog.NetCore.csproj

@ -4,6 +4,7 @@
<OutputType>WinExe</OutputType> <OutputType>WinExe</OutputType>
<TargetFramework>net6.0</TargetFramework> <TargetFramework>net6.0</TargetFramework>
<TargetLatestRuntimePatch>true</TargetLatestRuntimePatch> <TargetLatestRuntimePatch>true</TargetLatestRuntimePatch>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
</PropertyGroup> </PropertyGroup>
<PropertyGroup Condition="'$(RunNativeAotCompilation)' == 'true'"> <PropertyGroup Condition="'$(RunNativeAotCompilation)' == 'true'">
@ -12,6 +13,16 @@
<NativeAotCompilerVersion>7.0.0-*</NativeAotCompilerVersion> <NativeAotCompilerVersion>7.0.0-*</NativeAotCompilerVersion>
</PropertyGroup> </PropertyGroup>
<ItemGroup>
<Compile Include="..\..\src\Avalonia.X11\NativeDialogs\Gtk.cs" Link="NativeControls\Gtk\Gtk.cs" />
</ItemGroup>
<ItemGroup>
<Content Include="NativeControls\Gtk\nodes.mp4">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
</ItemGroup>
<ItemGroup> <ItemGroup>
<ProjectReference Include="..\..\src\Avalonia.Diagnostics\Avalonia.Diagnostics.csproj" /> <ProjectReference Include="..\..\src\Avalonia.Diagnostics\Avalonia.Diagnostics.csproj" />
<ProjectReference Include="..\..\src\Avalonia.Headless.Vnc\Avalonia.Headless.Vnc.csproj" /> <ProjectReference Include="..\..\src\Avalonia.Headless.Vnc\Avalonia.Headless.Vnc.csproj" />
@ -20,6 +31,8 @@
<ProjectReference Include="..\ControlCatalog\ControlCatalog.csproj" /> <ProjectReference Include="..\ControlCatalog\ControlCatalog.csproj" />
<ProjectReference Include="..\..\src\Avalonia.X11\Avalonia.X11.csproj" /> <ProjectReference Include="..\..\src\Avalonia.X11\Avalonia.X11.csproj" />
<PackageReference Include="Avalonia.Angle.Windows.Natives" Version="2.1.0.2020091801" /> <PackageReference Include="Avalonia.Angle.Windows.Natives" Version="2.1.0.2020091801" />
<!-- For native controls test -->
<PackageReference Include="MonoMac.NetStandard" Version="0.0.4" />
</ItemGroup> </ItemGroup>
<ItemGroup Condition="'$(RunNativeAotCompilation)' == 'true'"> <ItemGroup Condition="'$(RunNativeAotCompilation)' == 'true'">
@ -32,6 +45,7 @@
<PropertyGroup> <PropertyGroup>
<!-- For Microsoft.CodeAnalysis --> <!-- For Microsoft.CodeAnalysis -->
<SatelliteResourceLanguages>en</SatelliteResourceLanguages> <SatelliteResourceLanguages>en</SatelliteResourceLanguages>
<ApplicationManifest>app.manifest</ApplicationManifest>
</PropertyGroup> </PropertyGroup>
<Import Project="..\..\build\SampleApp.props" /> <Import Project="..\..\build\SampleApp.props" />

35
samples/ControlCatalog.NetCore/NativeControls/Gtk/EmbedSample.Gtk.cs

@ -0,0 +1,35 @@
using System.IO;
using System.Diagnostics;
using Avalonia.Platform;
using Avalonia.Controls.Platform;
using System;
using ControlCatalog.Pages;
namespace ControlCatalog.NetCore;
public class EmbedSampleGtk : INativeDemoControl
{
private Process _mplayer;
public IPlatformHandle CreateControl(bool isSecond, IPlatformHandle parent, Func<IPlatformHandle> createDefault)
{
if (isSecond)
{
var chooser = GtkHelper.CreateGtkFileChooser(parent.Handle);
if (chooser != null)
return chooser;
}
var control = createDefault();
var nodes = Path.GetFullPath(Path.Combine(typeof(EmbedSample).Assembly.GetModules()[0].FullyQualifiedName,
"..",
"nodes.mp4"));
_mplayer = Process.Start(new ProcessStartInfo("mplayer",
$"-vo x11 -zoom -loop 0 -wid {control.Handle.ToInt64()} \"{nodes}\"")
{
UseShellExecute = false,
});
return control;
}
}

58
samples/ControlCatalog.NetCore/NativeControls/Gtk/GtkHelper.cs

@ -0,0 +1,58 @@
using System;
using System.Threading.Tasks;
using Avalonia.Controls.Platform;
using Avalonia.Platform.Interop;
using Avalonia.X11.NativeDialogs;
using static Avalonia.X11.NativeDialogs.Gtk;
using static Avalonia.X11.NativeDialogs.Glib;
namespace ControlCatalog.NetCore;
internal class GtkHelper
{
private static Task<bool> s_gtkTask;
class FileChooser : INativeControlHostDestroyableControlHandle
{
private readonly IntPtr _widget;
public FileChooser(IntPtr widget, IntPtr xid)
{
_widget = widget;
Handle = xid;
}
public IntPtr Handle { get; }
public string HandleDescriptor => "XID";
public void Destroy()
{
RunOnGlibThread(() =>
{
gtk_widget_destroy(_widget);
return 0;
}).Wait();
}
}
public static INativeControlHostDestroyableControlHandle CreateGtkFileChooser(IntPtr parentXid)
{
if (s_gtkTask == null)
s_gtkTask = StartGtk();
if (!s_gtkTask.Result)
return null;
return RunOnGlibThread(() =>
{
using (var title = new Utf8Buffer("Embedded"))
{
var widget = gtk_file_chooser_dialog_new(title, IntPtr.Zero, GtkFileChooserAction.SelectFolder,
IntPtr.Zero);
gtk_widget_realize(widget);
var xid = gdk_x11_window_get_xid(gtk_widget_get_window(widget));
gtk_window_present(widget);
return new FileChooser(widget, xid);
}
}).Result;
}
}

0
samples/interop/NativeEmbedSample/nodes-license.md → samples/ControlCatalog.NetCore/NativeControls/Gtk/nodes-license.md

0
samples/interop/NativeEmbedSample/nodes.mp4 → samples/ControlCatalog.NetCore/NativeControls/Gtk/nodes.mp4

29
samples/ControlCatalog.NetCore/NativeControls/Mac/EmbedSample.Mac.cs

@ -0,0 +1,29 @@
using System;
using Avalonia.Platform;
using Avalonia.Threading;
using ControlCatalog.Pages;
using MonoMac.Foundation;
using MonoMac.WebKit;
namespace ControlCatalog.NetCore;
public class EmbedSampleMac : INativeDemoControl
{
public IPlatformHandle CreateControl(bool isSecond, IPlatformHandle parent, Func<IPlatformHandle> createDefault)
{
// Note: We are using MonoMac for example purposes
// It shouldn't be used in production apps
MacHelper.EnsureInitialized();
var webView = new WebView();
Dispatcher.UIThread.Post(() =>
{
webView.MainFrame.LoadRequest(new NSUrlRequest(new NSUrl(
isSecond ? "https://bing.com" : "https://google.com/")));
});
return new MacOSViewHandle(webView);
}
}

38
samples/ControlCatalog.NetCore/NativeControls/Mac/MacHelper.cs

@ -0,0 +1,38 @@
using System;
using Avalonia.Controls.Platform;
using MonoMac.AppKit;
namespace ControlCatalog.NetCore;
internal class MacHelper
{
private static bool _isInitialized;
public static void EnsureInitialized()
{
if (_isInitialized)
return;
_isInitialized = true;
NSApplication.Init();
}
}
internal class MacOSViewHandle : INativeControlHostDestroyableControlHandle
{
private NSView _view;
public MacOSViewHandle(NSView view)
{
_view = view;
}
public IntPtr Handle => _view?.Handle ?? IntPtr.Zero;
public string HandleDescriptor => "NSView";
public void Destroy()
{
_view.Dispose();
_view = null;
}
}

45
samples/ControlCatalog.NetCore/NativeControls/Win/EmbedSample.Win.cs

@ -0,0 +1,45 @@
using System;
using System.Text;
using Avalonia.Controls.Platform;
using Avalonia.Platform;
using ControlCatalog.Pages;
namespace ControlCatalog.NetCore;
public class EmbedSampleWin : INativeDemoControl
{
private const string RichText =
@"{\rtf1\ansi\ansicpg1251\deff0\nouicompat\deflang1049{\fonttbl{\f0\fnil\fcharset0 Calibri;}}
{\colortbl ;\red255\green0\blue0;\red0\green77\blue187;\red0\green176\blue80;\red155\green0\blue211;\red247\green150\blue70;\red75\green172\blue198;}
{\*\generator Riched20 6.3.9600}\viewkind4\uc1
\pard\sa200\sl276\slmult1\f0\fs22\lang9 <PREFIX>I \i am\i0 a \cf1\b Rich Text \cf0\b0\fs24 control\cf2\fs28 !\cf3\fs32 !\cf4\fs36 !\cf1\fs40 !\cf5\fs44 !\cf6\fs48 !\cf0\fs44\par
}";
public IPlatformHandle CreateControl(bool isSecond, IPlatformHandle parent, Func<IPlatformHandle> createDefault)
{
WinApi.LoadLibrary("Msftedit.dll");
var handle = WinApi.CreateWindowEx(0, "RICHEDIT50W",
@"Rich Edit",
0x800000 | 0x10000000 | 0x40000000 | 0x800000 | 0x10000 | 0x0004, 0, 0, 1, 1, parent.Handle,
IntPtr.Zero, WinApi.GetModuleHandle(null), IntPtr.Zero);
var st = new WinApi.SETTEXTEX { Codepage = 65001, Flags = 0x00000008 };
var text = RichText.Replace("<PREFIX>", isSecond ? "\\qr " : "");
var bytes = Encoding.UTF8.GetBytes(text);
WinApi.SendMessage(handle, 0x0400 + 97, ref st, bytes);
return new Win32WindowControlHandle(handle, "HWND");
}
}
internal class Win32WindowControlHandle : PlatformHandle, INativeControlHostDestroyableControlHandle
{
public Win32WindowControlHandle(IntPtr handle, string descriptor) : base(handle, descriptor)
{
}
public void Destroy()
{
_ = WinApi.DestroyWindow(Handle);
}
}

73
samples/ControlCatalog.NetCore/NativeControls/Win/WinApi.cs

@ -0,0 +1,73 @@
using System;
using System.Runtime.InteropServices;
namespace ControlCatalog.NetCore;
internal unsafe class WinApi
{
public enum CommonControls : uint
{
ICC_LISTVIEW_CLASSES = 0x00000001, // listview, header
ICC_TREEVIEW_CLASSES = 0x00000002, // treeview, tooltips
ICC_BAR_CLASSES = 0x00000004, // toolbar, statusbar, trackbar, tooltips
ICC_TAB_CLASSES = 0x00000008, // tab, tooltips
ICC_UPDOWN_CLASS = 0x00000010, // updown
ICC_PROGRESS_CLASS = 0x00000020, // progress
ICC_HOTKEY_CLASS = 0x00000040, // hotkey
ICC_ANIMATE_CLASS = 0x00000080, // animate
ICC_WIN95_CLASSES = 0x000000FF,
ICC_DATE_CLASSES = 0x00000100, // month picker, date picker, time picker, updown
ICC_USEREX_CLASSES = 0x00000200, // comboex
ICC_COOL_CLASSES = 0x00000400, // rebar (coolbar) control
ICC_INTERNET_CLASSES = 0x00000800,
ICC_PAGESCROLLER_CLASS = 0x00001000, // page scroller
ICC_NATIVEFNTCTL_CLASS = 0x00002000, // native font control
ICC_STANDARD_CLASSES = 0x00004000,
ICC_LINK_CLASS = 0x00008000
}
[StructLayout(LayoutKind.Sequential)]
public struct INITCOMMONCONTROLSEX
{
public int dwSize;
public uint dwICC;
}
[DllImport("Comctl32.dll")]
public static extern void InitCommonControlsEx(ref INITCOMMONCONTROLSEX init);
[DllImport("user32.dll", SetLastError = true)]
public static extern bool DestroyWindow(IntPtr hwnd);
[DllImport("kernel32.dll")]
public static extern IntPtr LoadLibrary(string lib);
[DllImport("kernel32.dll")]
public static extern IntPtr GetModuleHandle(string lpModuleName);
[DllImport("user32.dll", SetLastError = true)]
public static extern IntPtr CreateWindowEx(
int dwExStyle,
string lpClassName,
string lpWindowName,
uint dwStyle,
int x,
int y,
int nWidth,
int nHeight,
IntPtr hWndParent,
IntPtr hMenu,
IntPtr hInstance,
IntPtr lpParam);
[StructLayout(LayoutKind.Sequential)]
public struct SETTEXTEX
{
public uint Flags;
public uint Codepage;
}
[DllImport("user32.dll", CharSet = CharSet.Unicode, EntryPoint = "SendMessageW")]
public static extern IntPtr SendMessage(IntPtr hWnd, int Msg, ref SETTEXTEX wParam, byte[] lParam);
}

8
samples/ControlCatalog.NetCore/Program.cs

@ -7,11 +7,12 @@ using System.Threading.Tasks;
using Avalonia; using Avalonia;
using Avalonia.Controls; using Avalonia.Controls;
using Avalonia.Controls.ApplicationLifetimes; using Avalonia.Controls.ApplicationLifetimes;
using Avalonia.Dialogs;
using Avalonia.Headless; using Avalonia.Headless;
using Avalonia.LogicalTree; using Avalonia.LogicalTree;
using Avalonia.Threading; using Avalonia.Threading;
using ControlCatalog.Pages;
namespace ControlCatalog.NetCore namespace ControlCatalog.NetCore
{ {
static class Program static class Program
@ -123,6 +124,11 @@ namespace ControlCatalog.NetCore
{ {
StartupScreenIndex = 1, StartupScreenIndex = 1,
}); });
EmbedSample.Implementation = OperatingSystem.IsWindows() ? (INativeDemoControl)new EmbedSampleWin()
: OperatingSystem.IsMacOS() ? new EmbedSampleMac()
: OperatingSystem.IsLinux() ? new EmbedSampleGtk()
: null;
}) })
.LogToTrace(); .LogToTrace();

28
samples/ControlCatalog.NetCore/app.manifest

@ -0,0 +1,28 @@
<?xml version="1.0" encoding="utf-8"?>
<assembly manifestVersion="1.0" xmlns="urn:schemas-microsoft-com:asm.v1">
<assemblyIdentity version="1.0.0.0" name="ControlCatalog.app"/>
<compatibility xmlns="urn:schemas-microsoft-com:compatibility.v1">
<application>
<!-- A list of the Windows versions that this application has been tested on
and is designed to work with. Uncomment the appropriate elements
and Windows will automatically select the most compatible environment. -->
<!-- Windows Vista -->
<!--<supportedOS Id="{e2011457-1546-43c5-a5fe-008deee3d3f0}" />-->
<!-- Windows 7 -->
<!--<supportedOS Id="{35138b9a-5d96-4fbd-8e2d-a2440225f93a}" />-->
<!-- Windows 8 -->
<!--<supportedOS Id="{4a2f28e3-53b9-4441-ba9c-d69d4a4a6e38}" />-->
<!-- Windows 8.1 -->
<!--<supportedOS Id="{1f676c76-80e1-4239-95bb-83d0f6d0da78}" />-->
<!-- Windows 10 -->
<supportedOS Id="{8e0f7a12-bfb3-4fe8-b9a5-48fd50a15a9a}" />
</application>
</compatibility>
</assembly>

4
samples/ControlCatalog.Web/App.razor.cs

@ -7,6 +7,10 @@ public partial class App
protected override void OnParametersSet() protected override void OnParametersSet()
{ {
WebAppBuilder.Configure<ControlCatalog.App>() WebAppBuilder.Configure<ControlCatalog.App>()
.AfterSetup(_ =>
{
ControlCatalog.Pages.EmbedSample.Implementation = new EmbedSampleWeb();
})
.SetupWithSingleViewLifetime(); .SetupWithSingleViewLifetime();
base.OnParametersSet(); base.OnParametersSet();

34
samples/ControlCatalog.Web/EmbedSample.Browser.cs

@ -0,0 +1,34 @@
using System;
using Avalonia;
using Avalonia.Platform;
using Avalonia.Web.Blazor;
using ControlCatalog.Pages;
using Microsoft.JSInterop;
namespace ControlCatalog.Web;
public class EmbedSampleWeb : INativeDemoControl
{
public IPlatformHandle CreateControl(bool isSecond, IPlatformHandle parent, Func<IPlatformHandle> createDefault)
{
var runtime = AvaloniaLocator.Current.GetRequiredService<IJSInProcessRuntime>();
if (isSecond)
{
var iframe = runtime.Invoke<IJSInProcessObjectReference>("document.createElement", "iframe");
iframe.InvokeVoid("setAttribute", "src", "https://www.youtube.com/embed/kZCIporjJ70");
return new JSObjectControlHandle(iframe);
}
else
{
// window.createAppButton source is defined in "app.js" file.
var button = runtime.Invoke<IJSInProcessObjectReference>("window.createAppButton");
return new JSObjectControlHandle(button);
}
}
}

70
samples/ControlCatalog.Web/Shared/MainLayout.razor.css

@ -1,70 +0,0 @@
.page {
position: relative;
display: flex;
flex-direction: column;
}
.main {
flex: 1;
}
.sidebar {
background-image: linear-gradient(180deg, rgb(5, 39, 103) 0%, #3a0647 70%);
}
.top-row {
background-color: #f7f7f7;
border-bottom: 1px solid #d6d5d5;
justify-content: flex-end;
height: 3.5rem;
display: flex;
align-items: center;
}
.top-row ::deep a, .top-row .btn-link {
white-space: nowrap;
margin-left: 1.5rem;
}
.top-row a:first-child {
overflow: hidden;
text-overflow: ellipsis;
}
@media (max-width: 640.98px) {
.top-row:not(.auth) {
display: none;
}
.top-row.auth {
justify-content: space-between;
}
.top-row a, .top-row .btn-link {
margin-left: 0;
}
}
@media (min-width: 641px) {
.page {
flex-direction: row;
}
.sidebar {
width: 250px;
height: 100vh;
position: sticky;
top: 0;
}
.top-row {
position: sticky;
top: 0;
z-index: 1;
}
.main > div {
padding-left: 2rem !important;
padding-right: 1.5rem !important;
}
}

44
samples/ControlCatalog.Web/wwwroot/css/app.css

@ -44,47 +44,13 @@ a, .btn-link {
z-index: 1000; z-index: 1000;
} }
#blazor-error-ui .dismiss { #blazor-error-ui .dismiss {
cursor: pointer; cursor: pointer;
position: absolute; position: absolute;
right: 0.75rem; right: 0.75rem;
top: 0.5rem; top: 0.5rem;
}
.canvas-container {
opacity:1;
background-color:#ccc;
position:fixed;
width:100%;
height:100%;
top:0px;
left:0px;
z-index:500;
}
canvas
{
opacity:1;
background-color:#ccc;
position:fixed;
width:100%;
height:100%;
top:0px;
left:0px;
z-index:500;
} }
#app, .page { #app, .page {
height: 100%; height: 100%;
} }
.overlay{
opacity:0.0;
background-color:#ccc;
position:fixed;
width:100vw;
height:100vh;
top:0px;
left:0px;
z-index:1000;
}

11
samples/ControlCatalog.Web/wwwroot/js/app.js

@ -1 +1,10 @@
 window.createAppButton = function () {
var button = document.createElement('button');
button.innerText = 'Hello world';
var clickCount = 0;
button.onclick = () => {
clickCount++;
button.innerText = 'Click count ' + clickCount;
};
return button;
}

9
samples/ControlCatalog.iOS/AppDelegate.cs

@ -13,6 +13,13 @@ namespace ControlCatalog
[Register("AppDelegate")] [Register("AppDelegate")]
public partial class AppDelegate : AvaloniaAppDelegate<App> public partial class AppDelegate : AvaloniaAppDelegate<App>
{ {
protected override AppBuilder CustomizeAppBuilder(AppBuilder builder)
{
return base.CustomizeAppBuilder(builder)
.AfterSetup(_ =>
{
Pages.EmbedSample.Implementation = new EmbedSampleIOS();
});
}
} }
} }

38
samples/ControlCatalog.iOS/EmbedSample.iOS.cs

@ -0,0 +1,38 @@
using System;
using Avalonia.Platform;
using CoreGraphics;
using Foundation;
using UIKit;
using WebKit;
using Avalonia.iOS;
using ControlCatalog.Pages;
namespace ControlCatalog;
public class EmbedSampleIOS : INativeDemoControl
{
public IPlatformHandle CreateControl(bool isSecond, IPlatformHandle parent, Func<IPlatformHandle> createDefault)
{
if (isSecond)
{
var webView = new WKWebView(CGRect.Empty, new WKWebViewConfiguration());
webView.LoadRequest(new NSUrlRequest(new NSUrl("https://www.apple.com/")));
return new UIViewControlHandle(webView);
}
else
{
var button = new UIButton();
var clickCount = 0;
button.SetTitle("Hello world", UIControlState.Normal);
button.BackgroundColor = UIColor.Blue;
button.AddTarget((_, _) =>
{
clickCount++;
button.SetTitle($"Click count {clickCount}", UIControlState.Normal);
}, UIControlEvent.TouchDown);
return new UIViewControlHandle(button);
}
}
}

15
samples/ControlCatalog/ControlCatalog.csproj

@ -14,6 +14,9 @@
<AvaloniaResource Include="Assets\*" /> <AvaloniaResource Include="Assets\*" />
<AvaloniaResource Include="Assets\Fonts\*" /> <AvaloniaResource Include="Assets\Fonts\*" />
</ItemGroup> </ItemGroup>
<ItemGroup>
<None Remove="Pages\NativeEmbedPage.xaml" />
</ItemGroup>
<ItemGroup> <ItemGroup>
<EmbeddedResource Include="Assets\Fonts\SourceSansPro-Bold.ttf" /> <EmbeddedResource Include="Assets\Fonts\SourceSansPro-Bold.ttf" />
<EmbeddedResource Include="Assets\Fonts\SourceSansPro-BoldItalic.ttf" /> <EmbeddedResource Include="Assets\Fonts\SourceSansPro-BoldItalic.ttf" />
@ -32,5 +35,17 @@
<ProjectReference Include="..\SampleControls\ControlSamples.csproj" /> <ProjectReference Include="..\SampleControls\ControlSamples.csproj" />
</ItemGroup> </ItemGroup>
<ItemGroup>
<AvaloniaResource Update="Pages\NativeEmbedPage.xaml">
<Generator>MSBuild:Compile</Generator>
</AvaloniaResource>
</ItemGroup>
<ItemGroup>
<Compile Update="Pages\NativeEmbedPage.xaml.cs">
<DependentUpon>%(Filename)</DependentUpon>
</Compile>
</ItemGroup>
<Import Project="..\..\build\BuildTargets.targets" /> <Import Project="..\..\build\BuildTargets.targets" />
</Project> </Project>

10
samples/ControlCatalog/Converter/MathSubtractConverter.cs

@ -6,12 +6,16 @@ namespace ControlCatalog.Converter;
public class MathSubtractConverter : IValueConverter public class MathSubtractConverter : IValueConverter
{ {
public object Convert(object value, Type targetType, object parameter, CultureInfo culture) public object Convert(object? value, Type targetType, object? parameter, CultureInfo culture)
{ {
return (double)value - (double)parameter; if (value is double dv && parameter is double dp)
{
return dv - dp;
}
return double.NaN;
} }
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) public object ConvertBack(object? value, Type targetType, object? parameter, CultureInfo culture)
{ {
throw new NotSupportedException(); throw new NotSupportedException();
} }

10
samples/ControlCatalog/DecoratedWindow.xaml.cs

@ -15,7 +15,7 @@ namespace ControlCatalog
void SetupSide(string name, StandardCursorType cursor, WindowEdge edge) void SetupSide(string name, StandardCursorType cursor, WindowEdge edge)
{ {
var ctl = this.FindControl<Control>(name); var ctl = this.Get<Control>(name);
ctl.Cursor = new Cursor(cursor); ctl.Cursor = new Cursor(cursor);
ctl.PointerPressed += (i, e) => ctl.PointerPressed += (i, e) =>
{ {
@ -26,7 +26,7 @@ namespace ControlCatalog
private void InitializeComponent() private void InitializeComponent()
{ {
AvaloniaXamlLoader.Load(this); AvaloniaXamlLoader.Load(this);
this.FindControl<Control>("TitleBar").PointerPressed += (i, e) => this.Get<Control>("TitleBar").PointerPressed += (i, e) =>
{ {
PlatformImpl?.BeginMoveDrag(e); PlatformImpl?.BeginMoveDrag(e);
}; };
@ -38,12 +38,12 @@ namespace ControlCatalog
SetupSide("TopRight", StandardCursorType.TopRightCorner, WindowEdge.NorthEast); SetupSide("TopRight", StandardCursorType.TopRightCorner, WindowEdge.NorthEast);
SetupSide("BottomLeft", StandardCursorType.BottomLeftCorner, WindowEdge.SouthWest); SetupSide("BottomLeft", StandardCursorType.BottomLeftCorner, WindowEdge.SouthWest);
SetupSide("BottomRight", StandardCursorType.BottomRightCorner, WindowEdge.SouthEast); SetupSide("BottomRight", StandardCursorType.BottomRightCorner, WindowEdge.SouthEast);
this.FindControl<Button>("MinimizeButton").Click += delegate { this.WindowState = WindowState.Minimized; }; this.Get<Button>("MinimizeButton").Click += delegate { this.WindowState = WindowState.Minimized; };
this.FindControl<Button>("MaximizeButton").Click += delegate this.Get<Button>("MaximizeButton").Click += delegate
{ {
WindowState = WindowState == WindowState.Maximized ? WindowState.Normal : WindowState.Maximized; WindowState = WindowState == WindowState.Maximized ? WindowState.Normal : WindowState.Maximized;
}; };
this.FindControl<Button>("CloseButton").Click += delegate this.Get<Button>("CloseButton").Click += delegate
{ {
Close(); Close();
}; };

7
samples/ControlCatalog/MainView.xaml

@ -2,8 +2,8 @@
xmlns="https://github.com/avaloniaui" xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:controls="clr-namespace:ControlSamples;assembly=ControlSamples" xmlns:controls="clr-namespace:ControlSamples;assembly=ControlSamples"
xmlns:pages="clr-namespace:ControlCatalog.Pages" xmlns:models="clr-namespace:ControlCatalog.Models"
xmlns:models="clr-namespace:ControlCatalog.Models"> xmlns:pages="clr-namespace:ControlCatalog.Pages">
<Grid> <Grid>
<Grid.Styles> <Grid.Styles>
<Style Selector="TextBlock.h2"> <Style Selector="TextBlock.h2">
@ -157,6 +157,9 @@
<TabItem Header="Viewbox"> <TabItem Header="Viewbox">
<pages:ViewboxPage /> <pages:ViewboxPage />
</TabItem> </TabItem>
<TabItem Header="Native Embed">
<pages:NativeEmbedPage />
</TabItem>
<TabItem Header="Window Customizations"> <TabItem Header="Window Customizations">
<pages:WindowCustomizationsPage /> <pages:WindowCustomizationsPage />
</TabItem> </TabItem>

16
samples/ControlCatalog/MainView.xaml.cs

@ -18,9 +18,9 @@ namespace ControlCatalog
{ {
AvaloniaXamlLoader.Load(this); AvaloniaXamlLoader.Load(this);
var sideBar = this.FindControl<TabControl>("Sidebar"); var sideBar = this.Get<TabControl>("Sidebar");
if (AvaloniaLocator.Current.GetService<IRuntimePlatform>().GetRuntimeInfo().IsDesktop) if (AvaloniaLocator.Current?.GetService<IRuntimePlatform>()?.GetRuntimeInfo().IsDesktop == true)
{ {
IList tabItems = ((IList)sideBar.Items); IList tabItems = ((IList)sideBar.Items);
tabItems.Add(new TabItem() tabItems.Add(new TabItem()
@ -36,7 +36,7 @@ namespace ControlCatalog
} }
var themes = this.Find<ComboBox>("Themes"); var themes = this.Get<ComboBox>("Themes");
themes.SelectionChanged += (sender, e) => themes.SelectionChanged += (sender, e) =>
{ {
if (themes.SelectedItem is CatalogTheme theme) if (themes.SelectedItem is CatalogTheme theme)
@ -80,7 +80,7 @@ namespace ControlCatalog
} }
}; };
var flowDirections = this.Find<ComboBox>("FlowDirection"); var flowDirections = this.Get<ComboBox>("FlowDirection");
flowDirections.SelectionChanged += (sender, e) => flowDirections.SelectionChanged += (sender, e) =>
{ {
if (flowDirections.SelectedItem is FlowDirection flowDirection) if (flowDirections.SelectedItem is FlowDirection flowDirection)
@ -89,7 +89,7 @@ namespace ControlCatalog
} }
}; };
var decorations = this.Find<ComboBox>("Decorations"); var decorations = this.Get<ComboBox>("Decorations");
decorations.SelectionChanged += (sender, e) => decorations.SelectionChanged += (sender, e) =>
{ {
if (VisualRoot is Window window if (VisualRoot is Window window
@ -99,8 +99,8 @@ namespace ControlCatalog
} }
}; };
var transparencyLevels = this.Find<ComboBox>("TransparencyLevels"); var transparencyLevels = this.Get<ComboBox>("TransparencyLevels");
IDisposable backgroundSetter = null, paneBackgroundSetter = null; IDisposable? backgroundSetter = null, paneBackgroundSetter = null;
transparencyLevels.SelectionChanged += (sender, e) => transparencyLevels.SelectionChanged += (sender, e) =>
{ {
backgroundSetter?.Dispose(); backgroundSetter?.Dispose();
@ -118,7 +118,7 @@ namespace ControlCatalog
protected override void OnAttachedToVisualTree(VisualTreeAttachmentEventArgs e) protected override void OnAttachedToVisualTree(VisualTreeAttachmentEventArgs e)
{ {
base.OnAttachedToVisualTree(e); base.OnAttachedToVisualTree(e);
var decorations = this.Find<ComboBox>("Decorations"); var decorations = this.Get<ComboBox>("Decorations");
if (VisualRoot is Window window) if (VisualRoot is Window window)
decorations.SelectedIndex = (int)window.SystemDecorations; decorations.SelectedIndex = (int)window.SystemDecorations;
} }

8
samples/ControlCatalog/MainWindow.xaml.cs

@ -12,7 +12,7 @@ namespace ControlCatalog
public class MainWindow : Window public class MainWindow : Window
{ {
private WindowNotificationManager _notificationArea; private WindowNotificationManager _notificationArea;
private NativeMenu _recentMenu; private NativeMenu? _recentMenu;
public MainWindow() public MainWindow()
{ {
@ -28,9 +28,7 @@ namespace ControlCatalog
}; };
DataContext = new MainWindowViewModel(_notificationArea); DataContext = new MainWindowViewModel(_notificationArea);
_recentMenu = ((NativeMenu.GetMenu(this).Items[0] as NativeMenuItem).Menu.Items[2] as NativeMenuItem).Menu; _recentMenu = ((NativeMenu.GetMenu(this)?.Items[0] as NativeMenuItem)?.Menu?.Items[2] as NativeMenuItem)?.Menu;
ExtendClientAreaChromeHints = Avalonia.Platform.ExtendClientAreaChromeHints.OSXThickTitleBar;
} }
public static string MenuQuitHeader => RuntimeInformation.IsOSPlatform(OSPlatform.OSX) ? "Quit Avalonia" : "E_xit"; public static string MenuQuitHeader => RuntimeInformation.IsOSPlatform(OSPlatform.OSX) ? "Quit Avalonia" : "E_xit";
@ -41,7 +39,7 @@ namespace ControlCatalog
public void OnOpenClicked(object sender, EventArgs args) public void OnOpenClicked(object sender, EventArgs args)
{ {
_recentMenu.Items.Insert(0, new NativeMenuItem("Item " + (_recentMenu.Items.Count + 1))); _recentMenu?.Items.Insert(0, new NativeMenuItem("Item " + (_recentMenu.Items.Count + 1)));
} }
public void OnCloseClicked(object sender, EventArgs args) public void OnCloseClicked(object sender, EventArgs args)

4
samples/ControlCatalog/Models/Countries.cs

@ -237,7 +237,7 @@ namespace ControlCatalog.Models
yield return new Country("Zimbabwe", "SUB-SAHARAN AFRICA", 12236805, 390580, 31.3, 0, 0, 67.69, 1900, 90.7, 26.8, 28.01, 21.84); yield return new Country("Zimbabwe", "SUB-SAHARAN AFRICA", 12236805, 390580, 31.3, 0, 0, 67.69, 1900, 90.7, 26.8, 28.01, 21.84);
} }
static IReadOnlyList<Country> _all; static IReadOnlyList<Country>? _all;
public static IReadOnlyList<Country> All public static IReadOnlyList<Country> All
{ {
@ -253,4 +253,4 @@ namespace ControlCatalog.Models
} }
} }
} }

4
samples/ControlCatalog/Models/GDPValueConverter.cs

@ -14,7 +14,7 @@ namespace ControlCatalog.Models
{ {
public class GDPValueConverter : IValueConverter public class GDPValueConverter : IValueConverter
{ {
public object Convert(object value, Type targetType, object parameter, CultureInfo culture) public object? Convert(object? value, Type targetType, object? parameter, CultureInfo culture)
{ {
if (value is int gdp) if (value is int gdp)
{ {
@ -29,7 +29,7 @@ namespace ControlCatalog.Models
return value; return value;
} }
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) public object? ConvertBack(object? value, Type targetType, object? parameter, CultureInfo culture)
{ {
throw new NotImplementedException(); throw new NotImplementedException();
} }

16
samples/ControlCatalog/Models/Person.cs

@ -13,8 +13,8 @@ namespace ControlCatalog.Models
{ {
public class Person : INotifyDataErrorInfo, INotifyPropertyChanged public class Person : INotifyDataErrorInfo, INotifyPropertyChanged
{ {
string _firstName; string _firstName = string.Empty;
string _lastName; string _lastName = string.Empty;
bool _isBanned; bool _isBanned;
private int _age; private int _age;
@ -76,7 +76,7 @@ namespace ControlCatalog.Models
Dictionary<string, List<string>> _errorLookup = new Dictionary<string, List<string>>(); Dictionary<string, List<string>> _errorLookup = new Dictionary<string, List<string>>();
void SetError(string propertyName, string error) void SetError(string propertyName, string? error)
{ {
if (string.IsNullOrEmpty(error)) if (string.IsNullOrEmpty(error))
{ {
@ -88,11 +88,11 @@ namespace ControlCatalog.Models
if (_errorLookup.TryGetValue(propertyName, out List<string> errorList)) if (_errorLookup.TryGetValue(propertyName, out List<string> errorList))
{ {
errorList.Clear(); errorList.Clear();
errorList.Add(error); errorList.Add(error!);
} }
else else
{ {
var errors = new List<string> { error }; var errors = new List<string> { error! };
_errorLookup.Add(propertyName, errors); _errorLookup.Add(propertyName, errors);
} }
@ -102,8 +102,8 @@ namespace ControlCatalog.Models
public bool HasErrors => _errorLookup.Count > 0; public bool HasErrors => _errorLookup.Count > 0;
public event EventHandler<DataErrorsChangedEventArgs> ErrorsChanged; public event EventHandler<DataErrorsChangedEventArgs>? ErrorsChanged;
public event PropertyChangedEventHandler PropertyChanged; public event PropertyChangedEventHandler? PropertyChanged;
void OnErrorsChanged(string propertyName) void OnErrorsChanged(string propertyName)
{ {
@ -114,7 +114,7 @@ namespace ControlCatalog.Models
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
} }
public IEnumerable GetErrors(string propertyName) public IEnumerable? GetErrors(string propertyName)
{ {
if (_errorLookup.TryGetValue(propertyName, out List<string> errorList)) if (_errorLookup.TryGetValue(propertyName, out List<string> errorList))
return errorList; return errorList;

33
samples/ControlCatalog/Pages/AutoCompleteBoxPage.xaml.cs

@ -126,13 +126,13 @@ namespace ControlCatalog.Pages
binding.Bindings.Add(new Binding("Name")); binding.Bindings.Add(new Binding("Name"));
binding.Bindings.Add(new Binding("Abbreviation")); binding.Bindings.Add(new Binding("Abbreviation"));
var multibindingBox = this.FindControl<AutoCompleteBox>("MultiBindingBox"); var multibindingBox = this.Get<AutoCompleteBox>("MultiBindingBox");
multibindingBox.ValueMemberBinding = binding; multibindingBox.ValueMemberBinding = binding;
var asyncBox = this.FindControl<AutoCompleteBox>("AsyncBox"); var asyncBox = this.Get<AutoCompleteBox>("AsyncBox");
asyncBox.AsyncPopulator = PopulateAsync; asyncBox.AsyncPopulator = PopulateAsync;
var customAutocompleteBox = this.FindControl<AutoCompleteBox>("CustomAutocompleteBox"); var customAutocompleteBox = this.Get<AutoCompleteBox>("CustomAutocompleteBox");
customAutocompleteBox.Items = Sentences.SelectMany(x => x); customAutocompleteBox.Items = Sentences.SelectMany(x => x);
customAutocompleteBox.TextFilter = LastWordContains; customAutocompleteBox.TextFilter = LastWordContains;
customAutocompleteBox.TextSelector = AppendWord; customAutocompleteBox.TextSelector = AppendWord;
@ -144,11 +144,12 @@ namespace ControlCatalog.Pages
.OfType<AutoCompleteBox>(); .OfType<AutoCompleteBox>();
} }
private bool StringContains(string str, string query) private bool StringContains(string str, string? query)
{ {
if (query == null) return false;
return str.IndexOf(query, StringComparison.OrdinalIgnoreCase) >= 0; return str.IndexOf(query, StringComparison.OrdinalIgnoreCase) >= 0;
} }
private async Task<IEnumerable<object>> PopulateAsync(string searchText, CancellationToken cancellationToken) private async Task<IEnumerable<object>> PopulateAsync(string? searchText, CancellationToken cancellationToken)
{ {
await Task.Delay(TimeSpan.FromSeconds(1.5), cancellationToken); await Task.Delay(TimeSpan.FromSeconds(1.5), cancellationToken);
@ -157,14 +158,14 @@ namespace ControlCatalog.Pages
.ToList(); .ToList();
} }
private bool LastWordContains(string searchText, string item) private bool LastWordContains(string? searchText, string? item)
{ {
var words = searchText.Split(' '); var words = searchText?.Split(' ') ?? Array.Empty<string>();
var options = Sentences.Select(x => x.First).ToArray(); var options = Sentences.Select(x => x.First).ToArray();
for (var i = 0; i < words.Length; ++i) for (var i = 0; i < words.Length; ++i)
{ {
var word = words[i]; var word = words[i];
for (var j = 0; j < options.Length; ++j) for (var j = 0; word is { } && j < options.Length; ++j)
{ {
var option = options[j]; var option = options[j];
if (option == null) if (option == null)
@ -183,14 +184,18 @@ namespace ControlCatalog.Pages
return options.Any(x => x != null && x.Value == item); return options.Any(x => x != null && x.Value == item);
} }
private string AppendWord(string text, string item) private string AppendWord(string? text, string? item)
{ {
string[] parts = text.Split(' '); if (item is { })
if (parts.Length == 0) {
return item; string[] parts = text?.Split(' ') ?? Array.Empty<string>();
if (parts.Length == 0)
return item;
parts[parts.Length - 1] = item; parts[parts.Length - 1] = item;
return string.Join(" ", parts); return string.Join(" ", parts);
}
return string.Empty;
} }
private void InitializeComponent() private void InitializeComponent()

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

@ -23,7 +23,7 @@ namespace ControlCatalog.Pages
var spinner = (ButtonSpinner)sender; var spinner = (ButtonSpinner)sender;
var txtBox = (TextBlock)spinner.Content; var txtBox = (TextBlock)spinner.Content;
int value = Array.IndexOf(_mountains, txtBox.Text); int value = Array.IndexOf(_mountains, txtBox?.Text);
if (e.Direction == SpinDirection.Increase) if (e.Direction == SpinDirection.Increase)
value++; value++;
else else

4
samples/ControlCatalog/Pages/ButtonsPage.xaml.cs

@ -11,7 +11,7 @@ namespace ControlCatalog.Pages
{ {
InitializeComponent(); InitializeComponent();
this.FindControl<RepeatButton>("RepeatButton").Click += OnRepeatButtonClick; this.Get<RepeatButton>("RepeatButton").Click += OnRepeatButtonClick;
} }
private void InitializeComponent() private void InitializeComponent()
@ -22,7 +22,7 @@ namespace ControlCatalog.Pages
public void OnRepeatButtonClick(object sender, object args) public void OnRepeatButtonClick(object sender, object args)
{ {
repeatButtonClickCount++; repeatButtonClickCount++;
var textBlock = this.FindControl<TextBlock>("RepeatButtonTextBlock"); var textBlock = this.Get<TextBlock>("RepeatButtonTextBlock");
textBlock.Text = $"Repeat Button: {repeatButtonClickCount}"; textBlock.Text = $"Repeat Button: {repeatButtonClickCount}";
} }
} }

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

@ -10,11 +10,11 @@ namespace ControlCatalog.Pages
{ {
InitializeComponent(); InitializeComponent();
var dp1 = this.FindControl<CalendarDatePicker>("DatePicker1"); var dp1 = this.Get<CalendarDatePicker>("DatePicker1");
var dp2 = this.FindControl<CalendarDatePicker>("DatePicker2"); var dp2 = this.Get<CalendarDatePicker>("DatePicker2");
var dp3 = this.FindControl<CalendarDatePicker>("DatePicker3"); var dp3 = this.Get<CalendarDatePicker>("DatePicker3");
var dp4 = this.FindControl<CalendarDatePicker>("DatePicker4"); var dp4 = this.Get<CalendarDatePicker>("DatePicker4");
var dp5 = this.FindControl<CalendarDatePicker>("DatePicker5"); var dp5 = this.Get<CalendarDatePicker>("DatePicker5");
dp1.SelectedDate = DateTime.Today; dp1.SelectedDate = DateTime.Today;
dp2.SelectedDate = DateTime.Today.AddDays(10); dp2.SelectedDate = DateTime.Today.AddDays(10);
@ -23,7 +23,7 @@ namespace ControlCatalog.Pages
dp4.TemplateApplied += (s, e) => dp4.TemplateApplied += (s, e) =>
{ {
dp4.BlackoutDates.AddDatesInPast(); dp4.BlackoutDates?.AddDatesInPast();
}; };
} }

4
samples/ControlCatalog/Pages/CalendarPage.xaml.cs

@ -11,11 +11,11 @@ namespace ControlCatalog.Pages
this.InitializeComponent(); this.InitializeComponent();
var today = DateTime.Today; var today = DateTime.Today;
var cal1 = this.FindControl<Calendar>("DisplayDatesCalendar"); var cal1 = this.Get<Calendar>("DisplayDatesCalendar");
cal1.DisplayDateStart = today.AddDays(-25); cal1.DisplayDateStart = today.AddDays(-25);
cal1.DisplayDateEnd = today.AddDays(25); cal1.DisplayDateEnd = today.AddDays(25);
var cal2 = this.FindControl<Calendar>("BlackoutDatesCalendar"); var cal2 = this.Get<Calendar>("BlackoutDatesCalendar");
cal2.BlackoutDates.AddDatesInPast(); cal2.BlackoutDates.AddDatesInPast();
cal2.BlackoutDates.Add(new CalendarDateRange(today.AddDays(6))); cal2.BlackoutDates.Add(new CalendarDateRange(today.AddDays(6)));
} }

11
samples/ControlCatalog/Pages/CarouselPage.xaml.cs

@ -16,6 +16,11 @@ namespace ControlCatalog.Pages
public CarouselPage() public CarouselPage()
{ {
this.InitializeComponent(); this.InitializeComponent();
_carousel = this.Get<Carousel>("carousel");
_left = this.Get<Button>("left");
_right = this.Get<Button>("right");
_transition = this.Get<ComboBox>("transition");
_orientation = this.Get<ComboBox>("orientation");
_left.Click += (s, e) => _carousel.Previous(); _left.Click += (s, e) => _carousel.Previous();
_right.Click += (s, e) => _carousel.Next(); _right.Click += (s, e) => _carousel.Next();
_transition.SelectionChanged += TransitionChanged; _transition.SelectionChanged += TransitionChanged;
@ -25,11 +30,7 @@ namespace ControlCatalog.Pages
private void InitializeComponent() private void InitializeComponent()
{ {
AvaloniaXamlLoader.Load(this); AvaloniaXamlLoader.Load(this);
_carousel = this.FindControl<Carousel>("carousel");
_left = this.FindControl<Button>("left");
_right = this.FindControl<Button>("right");
_transition = this.FindControl<ComboBox>("transition");
_orientation = this.FindControl<ComboBox>("orientation");
} }
private void TransitionChanged(object sender, SelectionChangedEventArgs e) private void TransitionChanged(object sender, SelectionChangedEventArgs e)

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

@ -18,29 +18,29 @@ namespace ControlCatalog.Pages
DataContext = new ContextPageViewModel(); DataContext = new ContextPageViewModel();
_textBox = this.FindControl<TextBox>("TextBox"); _textBox = this.Get<TextBox>("TextBox");
var cutButton = this.FindControl<Button>("CutButton"); var cutButton = this.Get<Button>("CutButton");
cutButton.Click += CloseFlyout; cutButton.Click += CloseFlyout;
var copyButton = this.FindControl<Button>("CopyButton"); var copyButton = this.Get<Button>("CopyButton");
copyButton.Click += CloseFlyout; copyButton.Click += CloseFlyout;
var pasteButton = this.FindControl<Button>("PasteButton"); var pasteButton = this.Get<Button>("PasteButton");
pasteButton.Click += CloseFlyout; pasteButton.Click += CloseFlyout;
var clearButton = this.FindControl<Button>("ClearButton"); var clearButton = this.Get<Button>("ClearButton");
clearButton.Click += CloseFlyout; clearButton.Click += CloseFlyout;
var customContextRequestedBorder = this.FindControl<Border>("CustomContextRequestedBorder"); var customContextRequestedBorder = this.Get<Border>("CustomContextRequestedBorder");
customContextRequestedBorder.AddHandler(ContextRequestedEvent, CustomContextRequested, RoutingStrategies.Tunnel); customContextRequestedBorder.AddHandler(ContextRequestedEvent, CustomContextRequested, RoutingStrategies.Tunnel);
var cancellableContextBorder = this.FindControl<Border>("CancellableContextBorder"); var cancellableContextBorder = this.Get<Border>("CancellableContextBorder");
cancellableContextBorder.ContextFlyout!.Closing += ContextFlyoutPage_Closing; cancellableContextBorder.ContextFlyout!.Closing += ContextFlyoutPage_Closing;
cancellableContextBorder.ContextFlyout!.Opening += ContextFlyoutPage_Opening; cancellableContextBorder.ContextFlyout!.Opening += ContextFlyoutPage_Opening;
} }
private ContextPageViewModel _model; private ContextPageViewModel? _model;
protected override void OnDataContextChanged(EventArgs e) protected override void OnDataContextChanged(EventArgs e)
{ {
if (_model != null) if (_model != null)
@ -55,7 +55,7 @@ namespace ControlCatalog.Pages
private void ContextFlyoutPage_Closing(object sender, CancelEventArgs e) private void ContextFlyoutPage_Closing(object sender, CancelEventArgs e)
{ {
var cancelCloseCheckBox = this.FindControl<CheckBox>("CancelCloseCheckBox"); var cancelCloseCheckBox = this.FindControl<CheckBox>("CancelCloseCheckBox");
e.Cancel = cancelCloseCheckBox.IsChecked ?? false; e.Cancel = cancelCloseCheckBox?.IsChecked ?? false;
} }
private void ContextFlyoutPage_Opening(object sender, EventArgs e) private void ContextFlyoutPage_Opening(object sender, EventArgs e)
@ -63,13 +63,13 @@ namespace ControlCatalog.Pages
if (e is CancelEventArgs cancelArgs) if (e is CancelEventArgs cancelArgs)
{ {
var cancelCloseCheckBox = this.FindControl<CheckBox>("CancelOpenCheckBox"); var cancelCloseCheckBox = this.FindControl<CheckBox>("CancelOpenCheckBox");
cancelArgs.Cancel = cancelCloseCheckBox.IsChecked ?? false; cancelArgs.Cancel = cancelCloseCheckBox?.IsChecked ?? false;
} }
} }
private void CloseFlyout(object sender, RoutedEventArgs e) private void CloseFlyout(object sender, RoutedEventArgs e)
{ {
_textBox.ContextFlyout.Hide(); _textBox.ContextFlyout?.Hide();
} }
public void CustomContextRequested(object sender, ContextRequestedEventArgs e) public void CustomContextRequested(object sender, ContextRequestedEventArgs e)

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

@ -15,15 +15,15 @@ namespace ControlCatalog.Pages
this.InitializeComponent(); this.InitializeComponent();
DataContext = new ContextPageViewModel(); DataContext = new ContextPageViewModel();
var customContextRequestedBorder = this.FindControl<Border>("CustomContextRequestedBorder"); var customContextRequestedBorder = this.Get<Border>("CustomContextRequestedBorder");
customContextRequestedBorder.AddHandler(ContextRequestedEvent, CustomContextRequested, RoutingStrategies.Tunnel); customContextRequestedBorder.AddHandler(ContextRequestedEvent, CustomContextRequested, RoutingStrategies.Tunnel);
var cancellableContextBorder = this.FindControl<Border>("CancellableContextBorder"); var cancellableContextBorder = this.Get<Border>("CancellableContextBorder");
cancellableContextBorder.ContextMenu!.ContextMenuClosing += ContextFlyoutPage_Closing; cancellableContextBorder.ContextMenu!.ContextMenuClosing += ContextFlyoutPage_Closing;
cancellableContextBorder.ContextMenu!.ContextMenuOpening += ContextFlyoutPage_Opening; cancellableContextBorder.ContextMenu!.ContextMenuOpening += ContextFlyoutPage_Opening;
} }
private ContextPageViewModel _model; private ContextPageViewModel? _model;
protected override void OnDataContextChanged(EventArgs e) protected override void OnDataContextChanged(EventArgs e)
{ {
if (_model != null) if (_model != null)

8
samples/ControlCatalog/Pages/DataGridPage.xaml.cs

@ -21,7 +21,7 @@ namespace ControlCatalog.Pages
var dataGridSortDescription = DataGridSortDescription.FromPath(nameof(Country.Region), ListSortDirection.Ascending, new ReversedStringComparer()); var dataGridSortDescription = DataGridSortDescription.FromPath(nameof(Country.Region), ListSortDirection.Ascending, new ReversedStringComparer());
var collectionView1 = new DataGridCollectionView(Countries.All); var collectionView1 = new DataGridCollectionView(Countries.All);
collectionView1.SortDescriptions.Add(dataGridSortDescription); collectionView1.SortDescriptions.Add(dataGridSortDescription);
var dg1 = this.FindControl<DataGrid>("dataGrid1"); var dg1 = this.Get<DataGrid>("dataGrid1");
dg1.IsReadOnly = true; dg1.IsReadOnly = true;
dg1.LoadingRow += Dg1_LoadingRow; dg1.LoadingRow += Dg1_LoadingRow;
dg1.Sorting += (s, a) => dg1.Sorting += (s, a) =>
@ -37,7 +37,7 @@ namespace ControlCatalog.Pages
}; };
dg1.Items = collectionView1; dg1.Items = collectionView1;
var dg2 = this.FindControl<DataGrid>("dataGridGrouping"); var dg2 = this.Get<DataGrid>("dataGridGrouping");
dg2.IsReadOnly = true; dg2.IsReadOnly = true;
var collectionView2 = new DataGridCollectionView(Countries.All); var collectionView2 = new DataGridCollectionView(Countries.All);
@ -45,7 +45,7 @@ namespace ControlCatalog.Pages
dg2.Items = collectionView2; dg2.Items = collectionView2;
var dg3 = this.FindControl<DataGrid>("dataGridEdit"); var dg3 = this.Get<DataGrid>("dataGridEdit");
dg3.IsReadOnly = false; dg3.IsReadOnly = false;
var items = new List<Person> var items = new List<Person>
@ -58,7 +58,7 @@ namespace ControlCatalog.Pages
dg3.Items = collectionView3; dg3.Items = collectionView3;
var addButton = this.FindControl<Button>("btnAdd"); var addButton = this.Get<Button>("btnAdd");
addButton.Click += (a, b) => collectionView3.AddNew(); addButton.Click += (a, b) => collectionView3.AddNew();
} }

4
samples/ControlCatalog/Pages/DateTimePickerPage.xaml.cs

@ -9,12 +9,12 @@ namespace ControlCatalog.Pages
public DateTimePickerPage() public DateTimePickerPage()
{ {
this.InitializeComponent(); this.InitializeComponent();
this.FindControl<TextBlock>("DatePickerDesc").Text = "Use a DatePicker to let users set a date in your app, " + this.Get<TextBlock>("DatePickerDesc").Text = "Use a DatePicker to let users set a date in your app, " +
"for example to schedule an appointment. The DatePicker displays three controls for month, day, and year. " + "for example to schedule an appointment. The DatePicker displays three controls for month, day, and year. " +
"These controls are easy to use with touch or mouse, and they can be styled and configured in several different ways. " + "These controls are easy to use with touch or mouse, and they can be styled and configured in several different ways. " +
"Order of month, day, and year is dynamically set based on user date settings"; "Order of month, day, and year is dynamically set based on user date settings";
this.FindControl<TextBlock>("TimePickerDesc").Text = "Use a TimePicker to let users set a time in your app, for example " + this.Get<TextBlock>("TimePickerDesc").Text = "Use a TimePicker to let users set a time in your app, for example " +
"to set a reminder. The TimePicker displays three controls for hour, minute, and AM / PM(if necessary).These controls " + "to set a reminder. The TimePicker displays three controls for hour, minute, and AM / PM(if necessary).These controls " +
"are easy to use with touch or mouse, and they can be styled and configured in several different ways. " + "are easy to use with touch or mouse, and they can be styled and configured in several different ways. " +
"12 - hour or 24 - hour clock and visiblility of AM / PM is dynamically set based on user time settings, or can be overridden."; "12 - hour or 24 - hour clock and visiblility of AM / PM is dynamically set based on user time settings, or can be overridden.";

2
samples/ControlCatalog/Pages/DialogsPage.xaml

@ -20,7 +20,7 @@
Text="Window dialogs" /> Text="Window dialogs" />
<Button Name="DecoratedWindow">Decorated _window</Button> <Button Name="DecoratedWindow">Decorated _window</Button>
<Button Name="DecoratedWindowDialog">Decorated w_indow (dialog)</Button> <Button Name="DecoratedWindowDialog">Decorated w_indow (dialog)</Button>
<Button Name="Dialog">_Dialog</Button> <Button Name="Dialog" ToolTip.Tip="Shows a dialog">_Dialog</Button>
<Button Name="DialogNoTaskbar">Dialog (_No taskbar icon)</Button> <Button Name="DialogNoTaskbar">Dialog (_No taskbar icon)</Button>
<Button Name="OwnedWindow">Own_ed window</Button> <Button Name="OwnedWindow">Own_ed window</Button>
<Button Name="OwnedWindowNoTaskbar">Owned window (No tas_kbar icon)</Button> <Button Name="OwnedWindowNoTaskbar">Owned window (No tas_kbar icon)</Button>

45
samples/ControlCatalog/Pages/DialogsPage.xaml.cs

@ -16,14 +16,14 @@ namespace ControlCatalog.Pages
{ {
this.InitializeComponent(); this.InitializeComponent();
var results = this.FindControl<ItemsPresenter>("PickerLastResults"); var results = this.Get<ItemsPresenter>("PickerLastResults");
var resultsVisible = this.FindControl<TextBlock>("PickerLastResultsVisible"); var resultsVisible = this.Get<TextBlock>("PickerLastResultsVisible");
string lastSelectedDirectory = null; string? lastSelectedDirectory = null;
List<FileDialogFilter>? GetFilters() List<FileDialogFilter>? GetFilters()
{ {
if (this.FindControl<CheckBox>("UseFilters").IsChecked != true) if (this.Get<CheckBox>("UseFilters").IsChecked != true)
return null; return null;
return new List<FileDialogFilter> return new List<FileDialogFilter>
{ {
@ -39,7 +39,7 @@ namespace ControlCatalog.Pages
}; };
} }
this.FindControl<Button>("OpenFile").Click += async delegate this.Get<Button>("OpenFile").Click += async delegate
{ {
// Almost guaranteed to exist // Almost guaranteed to exist
var fullPath = Assembly.GetEntryAssembly()?.GetModules().FirstOrDefault()?.FullyQualifiedName; var fullPath = Assembly.GetEntryAssembly()?.GetModules().FirstOrDefault()?.FullyQualifiedName;
@ -56,7 +56,7 @@ namespace ControlCatalog.Pages
results.Items = result; results.Items = result;
resultsVisible.IsVisible = result?.Any() == true; resultsVisible.IsVisible = result?.Any() == true;
}; };
this.FindControl<Button>("OpenMultipleFiles").Click += async delegate this.Get<Button>("OpenMultipleFiles").Click += async delegate
{ {
var result = await new OpenFileDialog() var result = await new OpenFileDialog()
{ {
@ -68,7 +68,7 @@ namespace ControlCatalog.Pages
results.Items = result; results.Items = result;
resultsVisible.IsVisible = result?.Any() == true; resultsVisible.IsVisible = result?.Any() == true;
}; };
this.FindControl<Button>("SaveFile").Click += async delegate this.Get<Button>("SaveFile").Click += async delegate
{ {
var result = await new SaveFileDialog() var result = await new SaveFileDialog()
{ {
@ -80,7 +80,7 @@ namespace ControlCatalog.Pages
results.Items = new[] { result }; results.Items = new[] { result };
resultsVisible.IsVisible = result != null; resultsVisible.IsVisible = result != null;
}; };
this.FindControl<Button>("SelectFolder").Click += async delegate this.Get<Button>("SelectFolder").Click += async delegate
{ {
var result = await new OpenFolderDialog() var result = await new OpenFolderDialog()
{ {
@ -96,7 +96,7 @@ namespace ControlCatalog.Pages
results.Items = new [] { result }; results.Items = new [] { result };
resultsVisible.IsVisible = result != null; resultsVisible.IsVisible = result != null;
}; };
this.FindControl<Button>("OpenBoth").Click += async delegate this.Get<Button>("OpenBoth").Click += async delegate
{ {
var result = await new OpenFileDialog() var result = await new OpenFileDialog()
{ {
@ -110,35 +110,35 @@ namespace ControlCatalog.Pages
results.Items = result; results.Items = result;
resultsVisible.IsVisible = result?.Any() == true; resultsVisible.IsVisible = result?.Any() == true;
}; };
this.FindControl<Button>("DecoratedWindow").Click += delegate this.Get<Button>("DecoratedWindow").Click += delegate
{ {
new DecoratedWindow().Show(); new DecoratedWindow().Show();
}; };
this.FindControl<Button>("DecoratedWindowDialog").Click += delegate this.Get<Button>("DecoratedWindowDialog").Click += delegate
{ {
new DecoratedWindow().ShowDialog(GetWindow()); new DecoratedWindow().ShowDialog(GetWindow());
}; };
this.FindControl<Button>("Dialog").Click += delegate this.Get<Button>("Dialog").Click += delegate
{ {
var window = CreateSampleWindow(); var window = CreateSampleWindow();
window.Height = 200; window.Height = 200;
window.ShowDialog(GetWindow()); window.ShowDialog(GetWindow());
}; };
this.FindControl<Button>("DialogNoTaskbar").Click += delegate this.Get<Button>("DialogNoTaskbar").Click += delegate
{ {
var window = CreateSampleWindow(); var window = CreateSampleWindow();
window.Height = 200; window.Height = 200;
window.ShowInTaskbar = false; window.ShowInTaskbar = false;
window.ShowDialog(GetWindow()); window.ShowDialog(GetWindow());
}; };
this.FindControl<Button>("OwnedWindow").Click += delegate this.Get<Button>("OwnedWindow").Click += delegate
{ {
var window = CreateSampleWindow(); var window = CreateSampleWindow();
window.Show(GetWindow()); window.Show(GetWindow());
}; };
this.FindControl<Button>("OwnedWindowNoTaskbar").Click += delegate this.Get<Button>("OwnedWindowNoTaskbar").Click += delegate
{ {
var window = CreateSampleWindow(); var window = CreateSampleWindow();
@ -151,6 +151,7 @@ namespace ControlCatalog.Pages
private Window CreateSampleWindow() private Window CreateSampleWindow()
{ {
Button button; Button button;
Button dialogButton;
var window = new Window var window = new Window
{ {
@ -167,6 +168,12 @@ namespace ControlCatalog.Pages
HorizontalAlignment = HorizontalAlignment.Center, HorizontalAlignment = HorizontalAlignment.Center,
Content = "Click to close", Content = "Click to close",
IsDefault = true IsDefault = true
}),
(dialogButton = new Button
{
HorizontalAlignment = HorizontalAlignment.Center,
Content = "Dialog",
IsDefault = false
}) })
} }
}, },
@ -174,11 +181,17 @@ namespace ControlCatalog.Pages
}; };
button.Click += (_, __) => window.Close(); button.Click += (_, __) => window.Close();
dialogButton.Click += (_, __) =>
{
var dialog = CreateSampleWindow();
dialog.Height = 200;
dialog.ShowDialog(window);
};
return window; return window;
} }
Window GetWindow() => (Window)this.VisualRoot; Window GetWindow() => this.VisualRoot as Window ?? throw new NullReferenceException("Invalid Owner");
private void InitializeComponent() private void InitializeComponent()
{ {

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

@ -14,7 +14,7 @@ namespace ControlCatalog.Pages
public DragAndDropPage() public DragAndDropPage()
{ {
this.InitializeComponent(); this.InitializeComponent();
_DropState = this.Find<TextBlock>("DropState"); _DropState = this.Get<TextBlock>("DropState");
int textCount = 0; int textCount = 0;
SetupDnd("Text", d => d.Set(DataFormats.Text, SetupDnd("Text", d => d.Set(DataFormats.Text,
@ -26,8 +26,8 @@ namespace ControlCatalog.Pages
void SetupDnd(string suffix, Action<DataObject> factory, DragDropEffects effects) void SetupDnd(string suffix, Action<DataObject> factory, DragDropEffects effects)
{ {
var dragMe = this.Find<Border>("DragMe" + suffix); var dragMe = this.Get<Border>("DragMe" + suffix);
var dragState = this.Find<TextBlock>("DragState"+suffix); var dragState = this.Get<TextBlock>("DragState"+suffix);
async void DoDrag(object sender, Avalonia.Input.PointerPressedEventArgs e) async void DoDrag(object sender, Avalonia.Input.PointerPressedEventArgs e)
{ {

8
samples/ControlCatalog/Pages/FlyoutsPage.axaml.cs

@ -35,7 +35,7 @@ namespace ControlCatalog.Pages
private void SetXamlTexts() private void SetXamlTexts()
{ {
var bfxt = this.FindControl<TextBlock>("ButtonFlyoutXamlText"); var bfxt = this.Get<TextBlock>("ButtonFlyoutXamlText");
bfxt.Text = "<Button Content=\"Click me!\">\n" + bfxt.Text = "<Button Content=\"Click me!\">\n" +
" <Button.Flyout>\n" + " <Button.Flyout>\n" +
" <Flyout>\n" + " <Flyout>\n" +
@ -45,7 +45,7 @@ namespace ControlCatalog.Pages
" </Flyout>\n" + " </Flyout>\n" +
" </Button.Flyout>\n</Button>"; " </Button.Flyout>\n</Button>";
var mfxt = this.FindControl<TextBlock>("MenuFlyoutXamlText"); var mfxt = this.Get<TextBlock>("MenuFlyoutXamlText");
mfxt.Text = "<Button Content=\"Click me!\">\n" + mfxt.Text = "<Button Content=\"Click me!\">\n" +
" <Button.Flyout>\n" + " <Button.Flyout>\n" +
" <MenuFlyout>\n" + " <MenuFlyout>\n" +
@ -54,7 +54,7 @@ namespace ControlCatalog.Pages
" </MenuFlyout>\n" + " </MenuFlyout>\n" +
" </Button.Flyout>\n</Button>"; " </Button.Flyout>\n</Button>";
var afxt = this.FindControl<TextBlock>("AttachedFlyoutXamlText"); var afxt = this.Get<TextBlock>("AttachedFlyoutXamlText");
afxt.Text = "<Panel Name=\"AttachedFlyoutPanel\">\n" + afxt.Text = "<Panel Name=\"AttachedFlyoutPanel\">\n" +
" <FlyoutBase.AttachedFlyout>\n" + " <FlyoutBase.AttachedFlyout>\n" +
" <Flyout>\n" + " <Flyout>\n" +
@ -66,7 +66,7 @@ namespace ControlCatalog.Pages
"\n\n In DoubleTapped handler:\n" + "\n\n In DoubleTapped handler:\n" +
"FlyoutBase.ShowAttachedFlyout(AttachedFlyoutPanel);"; "FlyoutBase.ShowAttachedFlyout(AttachedFlyoutPanel);";
var sfxt = this.FindControl<TextBlock>("SharedFlyoutXamlText"); var sfxt = this.Get<TextBlock>("SharedFlyoutXamlText");
sfxt.Text = "Declare a flyout in Resources:\n" + sfxt.Text = "Declare a flyout in Resources:\n" +
"<Window.Resources>\n" + "<Window.Resources>\n" +
" <Flyout x:Key=\"SharedFlyout\">\n" + " <Flyout x:Key=\"SharedFlyout\">\n" +

13
samples/ControlCatalog/Pages/ImagePage.xaml.cs

@ -17,9 +17,9 @@ namespace ControlCatalog.Pages
public ImagePage() public ImagePage()
{ {
InitializeComponent(); InitializeComponent();
_bitmapImage = this.FindControl<Image>("bitmapImage"); _bitmapImage = this.Get<Image>("bitmapImage");
_drawingImage = this.FindControl<Image>("drawingImage"); _drawingImage = this.Get<Image>("drawingImage");
_croppedImage = this.FindControl<Image>("croppedImage"); _croppedImage = this.Get<Image>("croppedImage");
} }
private void InitializeComponent() private void InitializeComponent()
@ -50,8 +50,11 @@ namespace ControlCatalog.Pages
if (_croppedImage != null) if (_croppedImage != null)
{ {
var comboxBox = (ComboBox)sender; var comboxBox = (ComboBox)sender;
var croppedBitmap = _croppedImage.Source as CroppedBitmap; if (_croppedImage.Source is CroppedBitmap croppedBitmap)
croppedBitmap.SourceRect = GetCropRect(comboxBox.SelectedIndex); {
croppedBitmap.SourceRect = GetCropRect(comboxBox.SelectedIndex);
}
} }
} }

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

@ -24,11 +24,11 @@ namespace ControlCatalog.Pages
public ItemsRepeaterPage() public ItemsRepeaterPage()
{ {
this.InitializeComponent(); this.InitializeComponent();
_repeater = this.FindControl<ItemsRepeater>("repeater"); _repeater = this.Get<ItemsRepeater>("repeater");
_scroller = this.FindControl<ScrollViewer>("scroller"); _scroller = this.Get<ScrollViewer>("scroller");
_scrollToLast = this.FindControl<Button>("scrollToLast"); _scrollToLast = this.Get<Button>("scrollToLast");
_scrollToRandom = this.FindControl<Button>("scrollToRandom"); _scrollToRandom = this.Get<Button>("scrollToRandom");
_scrollToSelected = this.FindControl<Button>("scrollToSelected"); _scrollToSelected = this.Get<Button>("scrollToSelected");
_repeater.PointerPressed += RepeaterClick; _repeater.PointerPressed += RepeaterClick;
_repeater.KeyDown += RepeaterOnKeyDown; _repeater.KeyDown += RepeaterOnKeyDown;
_scrollToLast.Click += scrollToLast_Click; _scrollToLast.Click += scrollToLast_Click;
@ -44,8 +44,10 @@ namespace ControlCatalog.Pages
public void OnSelectTemplateKey(object sender, SelectTemplateEventArgs e) public void OnSelectTemplateKey(object sender, SelectTemplateEventArgs e)
{ {
var item = (ItemsRepeaterPageViewModel.Item)e.DataContext; if (e.DataContext is ItemsRepeaterPageViewModel.Item item)
e.TemplateKey = (item.Index % 2 == 0) ? "even" : "odd"; {
e.TemplateKey = (item.Index % 2 == 0) ? "even" : "odd";
}
} }
private void LayoutChanged(object sender, SelectionChangedEventArgs e) private void LayoutChanged(object sender, SelectionChangedEventArgs e)
@ -115,7 +117,7 @@ namespace ControlCatalog.Pages
private void ScrollTo(int index) private void ScrollTo(int index)
{ {
System.Diagnostics.Debug.WriteLine("Scroll to " + index); System.Diagnostics.Debug.WriteLine("Scroll to " + index);
var layoutManager = ((TopLevel)VisualRoot).LayoutManager; var layoutManager = ((TopLevel)VisualRoot!).LayoutManager;
var element = _repeater.GetOrCreateElement(index); var element = _repeater.GetOrCreateElement(index);
layoutManager.ExecuteLayoutPass(); layoutManager.ExecuteLayoutPass();
element.BringIntoView(); element.BringIntoView();
@ -123,9 +125,11 @@ namespace ControlCatalog.Pages
private void RepeaterClick(object sender, PointerPressedEventArgs e) private void RepeaterClick(object sender, PointerPressedEventArgs e)
{ {
var item = (e.Source as TextBlock)?.DataContext as ItemsRepeaterPageViewModel.Item; if ((e.Source as TextBlock)?.DataContext is ItemsRepeaterPageViewModel.Item item)
_viewModel.SelectedItem = item; {
_selectedIndex = _viewModel.Items.IndexOf(item); _viewModel.SelectedItem = item;
_selectedIndex = _viewModel.Items.IndexOf(item);
}
} }
private void RepeaterOnKeyDown(object sender, KeyEventArgs e) private void RepeaterOnKeyDown(object sender, KeyEventArgs e)

2
samples/ControlCatalog/Pages/LabelsPage.axaml.cs

@ -7,7 +7,7 @@ namespace ControlCatalog.Pages
{ {
public class LabelsPage : UserControl public class LabelsPage : UserControl
{ {
private Person _person; private Person? _person;
public LabelsPage() public LabelsPage()
{ {

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

@ -22,7 +22,7 @@ namespace ControlCatalog.Pages
AvaloniaXamlLoader.Load(this); AvaloniaXamlLoader.Load(this);
} }
private MenuPageViewModel _model; private MenuPageViewModel? _model;
protected override void OnDataContextChanged(EventArgs e) protected override void OnDataContextChanged(EventArgs e)
{ {
if (_model != null) if (_model != null)

68
samples/ControlCatalog/Pages/NativeEmbedPage.xaml

@ -0,0 +1,68 @@
<UserControl xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="using:ControlCatalog.Pages"
d:DesignHeight="800"
d:DesignWidth="400"
x:Class="ControlCatalog.Pages.NativeEmbedPage">
<DockPanel>
<Menu DockPanel.Dock="Top">
<MenuItem Header="Test">
<MenuItem Header="SubMenu">
<MenuItem Header="Item 1"/>
<MenuItem Header="Item 2"/>
<MenuItem Header="Item 3"/>
</MenuItem>
<MenuItem Header="Item 1"/>
<MenuItem Header="Item 2"/>
<MenuItem Header="Item 3"/>
</MenuItem>
</Menu>
<DockPanel DockPanel.Dock="Top">
<Button DockPanel.Dock="Right" Click="ShowPopupDelay">Show popup (delay)</Button>
<Button DockPanel.Dock="Right" Click="ShowPopup">Show popup</Button>
<Border DockPanel.Dock="Right" Background="#c0c0c0">
<ToolTip.Tip>
<ToolTip>
<TextBlock>Text</TextBlock>
</ToolTip>
</ToolTip.Tip>
<TextBlock VerticalAlignment="Center">Tooltip</TextBlock>
</Border>
<TextBox Text="Lorem ipsum dolor sit amet"/>
</DockPanel>
<Grid ColumnDefinitions="*,5,*"
RowDefinitions="*,5,*">
<Grid.Styles>
<Style Selector="DockPanel#FirstPanel:not(.mobile), DockPanel#SecondPanel:not(.mobile)">
<Setter Property="Grid.RowSpan" Value="3" />
</Style>
<Style Selector="DockPanel#SecondPanel:not(.mobile)">
<Setter Property="Grid.Column" Value="2" />
</Style>
<Style Selector="DockPanel#FirstPanel.mobile, DockPanel#SecondPanel.mobile">
<Setter Property="Grid.ColumnSpan" Value="3" />
</Style>
<Style Selector="DockPanel#SecondPanel.mobile">
<Setter Property="Grid.Row" Value="2" />
</Style>
</Grid.Styles>
<DockPanel x:Name="FirstPanel">
<CheckBox x:Name="firstVisible" DockPanel.Dock="Top"
IsChecked="True" Content="Visible" />
<local:EmbedSample IsVisible="{Binding #firstVisible.IsChecked}"/>
</DockPanel>
<GridSplitter Grid.Row="0" Grid.RowSpan="3" Grid.Column="1" Width="5" HorizontalAlignment="Stretch" />
<GridSplitter Grid.Column="0" Grid.ColumnSpan="3" Grid.Row="1" Height="5" VerticalAlignment="Stretch" />
<DockPanel x:Name="SecondPanel">
<CheckBox x:Name="secondVisible" DockPanel.Dock="Top"
IsChecked="True" Content="Visible" />
<local:EmbedSample IsSecond="True" IsVisible="{Binding #secondVisible.IsChecked}"/>
</DockPanel>
</Grid>
</DockPanel>
</UserControl>

86
samples/ControlCatalog/Pages/NativeEmbedPage.xaml.cs

@ -0,0 +1,86 @@
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using Avalonia.Platform;
using Avalonia.Interactivity;
using Avalonia.Controls;
using Avalonia.Controls.Platform;
using Avalonia.Markup.Xaml;
using Avalonia;
namespace ControlCatalog.Pages
{
public class NativeEmbedPage : UserControl
{
public NativeEmbedPage()
{
this.InitializeComponent();
}
private void InitializeComponent()
{
AvaloniaXamlLoader.Load(this);
}
public async void ShowPopupDelay(object sender, RoutedEventArgs args)
{
await Task.Delay(3000);
ShowPopup(sender, args);
}
public void ShowPopup(object sender, RoutedEventArgs args)
{
new ContextMenu()
{
Items = new List<MenuItem>
{
new MenuItem() { Header = "Test" }, new MenuItem() { Header = "Test" }
}
}.Open((Control)sender);
}
protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs change)
{
base.OnPropertyChanged(change);
if (change.Property == BoundsProperty)
{
var isMobile = change.GetNewValue<Rect>().Width < 1200;
this.Find<DockPanel>("FirstPanel")!.Classes.Set("mobile", isMobile);
this.Find<DockPanel>("SecondPanel")!.Classes.Set("mobile", isMobile);
}
}
}
public class EmbedSample : NativeControlHost
{
public static INativeDemoControl? Implementation { get; set; }
static EmbedSample()
{
}
public bool IsSecond { get; set; }
protected override IPlatformHandle CreateNativeControlCore(IPlatformHandle parent)
{
return Implementation?.CreateControl(IsSecond, parent, () => base.CreateNativeControlCore(parent))
?? base.CreateNativeControlCore(parent);
}
protected override void DestroyNativeControlCore(IPlatformHandle control)
{
base.DestroyNativeControlCore(control);
}
}
public interface INativeDemoControl
{
/// <param name="isSecond">Used to specify which control should be displayed as a demo</param>
/// <param name="parent"></param>
/// <param name="createDefault"></param>
IPlatformHandle CreateControl(bool isSecond, IPlatformHandle parent, Func<IPlatformHandle> createDefault);
}
}

2
samples/ControlCatalog/Pages/NumericUpDownPage.xaml

@ -65,7 +65,7 @@
Margin="2" HorizontalAlignment="Center"/> Margin="2" HorizontalAlignment="Center"/>
<TextBlock Grid.Row="3" Grid.Column="0" VerticalAlignment="Center" Margin="10,2,2,2">Value:</TextBlock> <TextBlock Grid.Row="3" Grid.Column="0" VerticalAlignment="Center" Margin="10,2,2,2">Value:</TextBlock>
<NumericUpDown Grid.Row="3" Grid.Column="1" Value="{Binding #upDown.Value}" VerticalAlignment="Center" <NumericUpDown Grid.Row="3" Grid.Column="1" Value="{Binding DecimalValue}" VerticalAlignment="Center"
Margin="2" HorizontalAlignment="Center"/> Margin="2" HorizontalAlignment="Center"/>

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

@ -28,16 +28,16 @@ namespace ControlCatalog.Pages
public class NumbersPageViewModel : ViewModelBase public class NumbersPageViewModel : ViewModelBase
{ {
private IList<FormatObject> _formats; private IList<FormatObject>? _formats;
private FormatObject _selectedFormat; private FormatObject _selectedFormat;
private IList<Location> _spinnerLocations; private IList<Location>? _spinnerLocations;
private double _doubleValue; private double _doubleValue;
private decimal _decimalValue; private decimal _decimalValue;
public NumbersPageViewModel() public NumbersPageViewModel()
{ {
SelectedFormat = Formats.FirstOrDefault(); _selectedFormat = Formats.FirstOrDefault();
} }
public double DoubleValue public double DoubleValue
@ -103,7 +103,7 @@ namespace ControlCatalog.Pages
public class FormatObject public class FormatObject
{ {
public string Value { get; set; } public string? Value { get; set; }
public string Name { get; set; } public string? Name { get; set; }
} }
} }

2
samples/ControlCatalog/Pages/ScreenPage.cs

@ -18,7 +18,7 @@ namespace ControlCatalog.Pages
protected override void OnAttachedToVisualTree(VisualTreeAttachmentEventArgs e) protected override void OnAttachedToVisualTree(VisualTreeAttachmentEventArgs e)
{ {
base.OnAttachedToVisualTree(e); base.OnAttachedToVisualTree(e);
Window w = (Window)VisualRoot; Window w = (Window)VisualRoot!;
w.PositionChanged += (sender, args) => InvalidateVisual(); w.PositionChanged += (sender, args) => InvalidateVisual();
} }

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

@ -52,7 +52,7 @@ namespace ControlCatalog.Pages
private IBitmap LoadBitmap(string uri) private IBitmap LoadBitmap(string uri)
{ {
var assets = AvaloniaLocator.Current.GetService<IAssetLoader>(); var assets = AvaloniaLocator.Current!.GetService<IAssetLoader>()!;
return new Bitmap(assets.Open(new Uri(uri))); return new Bitmap(assets.Open(new Uri(uri)));
} }
@ -60,7 +60,7 @@ namespace ControlCatalog.Pages
{ {
private Dock _tabPlacement; private Dock _tabPlacement;
public TabItemViewModel[] Tabs { get; set; } public TabItemViewModel[]? Tabs { get; set; }
public Dock TabPlacement public Dock TabPlacement
{ {
@ -71,10 +71,10 @@ namespace ControlCatalog.Pages
private class TabItemViewModel private class TabItemViewModel
{ {
public string Header { get; set; } public string? Header { get; set; }
public string Text { get; set; } public string? Text { get; set; }
public IBitmap Image { get; set; } public IBitmap? Image { get; set; }
public bool IsEnabled { get; set; } = true; public bool IsEnabled { get; set; } = true;
} }
} }
} }

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

@ -38,7 +38,7 @@ namespace ControlCatalog.Pages
private class TabStripItemViewModel private class TabStripItemViewModel
{ {
public string Header { get; set; } public string? Header { get; set; }
public bool IsEnabled { get; set; } = true; public bool IsEnabled { get; set; } = true;
} }
} }

2
samples/ControlCatalog/ViewModels/ApplicationViewModel.cs

@ -10,7 +10,7 @@ namespace ControlCatalog.ViewModels
{ {
ExitCommand = MiniCommand.Create(() => ExitCommand = MiniCommand.Create(() =>
{ {
if (Application.Current.ApplicationLifetime is IClassicDesktopStyleApplicationLifetime lifetime) if (Application.Current?.ApplicationLifetime is IClassicDesktopStyleApplicationLifetime lifetime)
{ {
lifetime.Shutdown(); lifetime.Shutdown();
} }

2
samples/ControlCatalog/ViewModels/ContextPageViewModel.cs

@ -9,7 +9,7 @@ namespace ControlCatalog.ViewModels
{ {
public class ContextPageViewModel public class ContextPageViewModel
{ {
public Control View { get; set; } public Control? View { get; set; }
public ContextPageViewModel() public ContextPageViewModel()
{ {
OpenCommand = MiniCommand.CreateFromTask(Open); OpenCommand = MiniCommand.CreateFromTask(Open);

2
samples/ControlCatalog/ViewModels/CursorPageViewModel.cs

@ -18,7 +18,7 @@ namespace ControlCatalog.ViewModels
.Select(x => new StandardCursorModel(x)) .Select(x => new StandardCursorModel(x))
.ToList(); .ToList();
var loader = AvaloniaLocator.Current.GetService<IAssetLoader>(); var loader = AvaloniaLocator.Current!.GetService<IAssetLoader>()!;
var s = loader.Open(new Uri("avares://ControlCatalog/Assets/avalonia-32.png")); var s = loader.Open(new Uri("avares://ControlCatalog/Assets/avalonia-32.png"));
var bitmap = new Bitmap(s); var bitmap = new Bitmap(s);
CustomCursor = new Cursor(bitmap, new PixelPoint(16, 16)); CustomCursor = new Cursor(bitmap, new PixelPoint(16, 16));

19
samples/ControlCatalog/ViewModels/ItemsRepeaterPageViewModel.cs

@ -14,7 +14,7 @@ namespace ControlCatalog.ViewModels
public ItemsRepeaterPageViewModel() public ItemsRepeaterPageViewModel()
{ {
Items = CreateItems(); _items = CreateItems();
} }
public ObservableCollection<Item> Items public ObservableCollection<Item> Items
@ -23,12 +23,12 @@ namespace ControlCatalog.ViewModels
set => this.RaiseAndSetIfChanged(ref _items, value); set => this.RaiseAndSetIfChanged(ref _items, value);
} }
public Item SelectedItem { get; set; } public Item? SelectedItem { get; set; }
public void AddItem() public void AddItem()
{ {
var index = SelectedItem != null ? Items.IndexOf(SelectedItem) : -1; var index = SelectedItem != null ? Items.IndexOf(SelectedItem) : -1;
Items.Insert(index + 1, new Item(index + 1) { Text = $"New Item {_newItemIndex++}" }); Items.Insert(index + 1, new Item(index + 1, $"New Item {_newItemIndex++}"));
} }
public void RemoveItem() public void RemoveItem()
@ -66,19 +66,20 @@ namespace ControlCatalog.ViewModels
_newGenerationIndex++; _newGenerationIndex++;
return new ObservableCollection<Item>( return new ObservableCollection<Item>(
Enumerable.Range(1, 100000).Select(i => new Item(i) Enumerable.Range(1, 100000).Select(i => new Item(i, $"Item {i.ToString()} {suffix}")));
{
Text = $"Item {i.ToString()} {suffix}"
}));
} }
public class Item : ViewModelBase public class Item : ViewModelBase
{ {
private double _height = double.NaN; private double _height = double.NaN;
public Item(int index) => Index = index; public Item(int index, string text)
{
Index = index;
Text = text;
}
public int Index { get; } public int Index { get; }
public string Text { get; set; } public string Text { get; }
public double Height public double Height
{ {

13
samples/ControlCatalog/ViewModels/MainWindowViewModel.cs

@ -16,11 +16,11 @@ namespace ControlCatalog.ViewModels
private bool _isMenuItemChecked = true; private bool _isMenuItemChecked = true;
private WindowState _windowState; private WindowState _windowState;
private WindowState[] _windowStates; private WindowState[] _windowStates = Array.Empty<WindowState>();
private int _transparencyLevel; private int _transparencyLevel;
private ExtendClientAreaChromeHints _chromeHints = ExtendClientAreaChromeHints.PreferSystemChrome; private ExtendClientAreaChromeHints _chromeHints = ExtendClientAreaChromeHints.PreferSystemChrome;
private bool _extendClientAreaEnabled; private bool _extendClientAreaEnabled;
private bool _systemTitleBarEnabled; private bool _systemTitleBarEnabled;
private bool _preferSystemChromeEnabled; private bool _preferSystemChromeEnabled;
private double _titleBarHeight; private double _titleBarHeight;
@ -47,14 +47,15 @@ namespace ControlCatalog.ViewModels
{ {
var dialog = new AboutAvaloniaDialog(); var dialog = new AboutAvaloniaDialog();
var mainWindow = (App.Current.ApplicationLifetime as IClassicDesktopStyleApplicationLifetime)?.MainWindow; if ((App.Current?.ApplicationLifetime as IClassicDesktopStyleApplicationLifetime)?.MainWindow is { } mainWindow)
{
await dialog.ShowDialog(mainWindow); await dialog.ShowDialog(mainWindow);
}
}); });
ExitCommand = MiniCommand.Create(() => ExitCommand = MiniCommand.Create(() =>
{ {
(App.Current.ApplicationLifetime as IClassicDesktopStyleApplicationLifetime).Shutdown(); (App.Current?.ApplicationLifetime as IClassicDesktopStyleApplicationLifetime)?.Shutdown();
}); });
ToggleMenuItemCheckedCommand = MiniCommand.Create(() => ToggleMenuItemCheckedCommand = MiniCommand.Create(() =>

8
samples/ControlCatalog/ViewModels/MenuItemViewModel.cs

@ -5,9 +5,9 @@ namespace ControlCatalog.ViewModels
{ {
public class MenuItemViewModel public class MenuItemViewModel
{ {
public string Header { get; set; } public string? Header { get; set; }
public ICommand Command { get; set; } public ICommand? Command { get; set; }
public object CommandParameter { get; set; } public object? CommandParameter { get; set; }
public IList<MenuItemViewModel> Items { get; set; } public IList<MenuItemViewModel>? Items { get; set; }
} }
} }

2
samples/ControlCatalog/ViewModels/MenuPageViewModel.cs

@ -10,7 +10,7 @@ namespace ControlCatalog.ViewModels
{ {
public class MenuPageViewModel public class MenuPageViewModel
{ {
public Control View { get; set; } public Control? View { get; set; }
public MenuPageViewModel() public MenuPageViewModel()
{ {
OpenCommand = MiniCommand.CreateFromTask(Open); OpenCommand = MiniCommand.CreateFromTask(Open);

4
samples/ControlCatalog/ViewModels/NotificationViewModel.cs

@ -19,8 +19,8 @@ namespace ControlCatalog.ViewModels
}); });
} }
public string Title { get; set; } public string? Title { get; set; }
public string Message { get; set; } public string? Message { get; set; }
public MiniCommand YesCommand { get; } public MiniCommand YesCommand { get; }

16
samples/ControlCatalog/ViewModels/TransitioningContentControlPageViewModel.cs

@ -19,7 +19,7 @@ namespace ControlCatalog.ViewModels
{ {
public TransitioningContentControlPageViewModel() public TransitioningContentControlPageViewModel()
{ {
var assetLoader = AvaloniaLocator.Current.GetService<IAssetLoader>(); var assetLoader = AvaloniaLocator.Current?.GetService<IAssetLoader>()!;
var images = new string[] var images = new string[]
{ {
@ -36,8 +36,8 @@ namespace ControlCatalog.ViewModels
SetupTransitions(); SetupTransitions();
SelectedTransition = PageTransitions[1]; _SelectedTransition = PageTransitions[1];
SelectedImage = Images[0]; _SelectedImage = Images[0];
} }
public List<PageTransition> PageTransitions { get; } = new List<PageTransition>(); public List<PageTransition> PageTransitions { get; } = new List<PageTransition>();
@ -45,12 +45,12 @@ namespace ControlCatalog.ViewModels
public List<Bitmap> Images { get; } = new List<Bitmap>(); public List<Bitmap> Images { get; } = new List<Bitmap>();
private Bitmap? _SelectedImage; private Bitmap _SelectedImage;
/// <summary> /// <summary>
/// Gets or Sets the selected image /// Gets or Sets the selected image
/// </summary> /// </summary>
public Bitmap? SelectedImage public Bitmap SelectedImage
{ {
get { return _SelectedImage; } get { return _SelectedImage; }
set { this.RaiseAndSetIfChanged(ref _SelectedImage, value); } set { this.RaiseAndSetIfChanged(ref _SelectedImage, value); }
@ -160,12 +160,12 @@ namespace ControlCatalog.ViewModels
public string DisplayTitle { get; } public string DisplayTitle { get; }
private IPageTransition _Transition; private IPageTransition? _Transition;
/// <summary> /// <summary>
/// Gets or sets the transition /// Gets or sets the transition
/// </summary> /// </summary>
public IPageTransition Transition public IPageTransition? Transition
{ {
get { return _Transition; } get { return _Transition; }
set { this.RaiseAndSetIfChanged(ref _Transition, value); } set { this.RaiseAndSetIfChanged(ref _Transition, value); }
@ -201,7 +201,7 @@ namespace ControlCatalog.ViewModels
/// </summary> /// </summary>
public TimeSpan Duration { get; set; } public TimeSpan Duration { get; set; }
public async Task Start(Visual from, Visual to, bool forward, CancellationToken cancellationToken) public async Task Start(Visual? from, Visual? to, bool forward, CancellationToken cancellationToken)
{ {
if (cancellationToken.IsCancellationRequested) if (cancellationToken.IsCancellationRequested)
{ {

4
samples/ControlCatalog/ViewModels/TreeViewPageViewModel.cs

@ -92,7 +92,7 @@ namespace ControlCatalog.ViewModels
public class Node public class Node
{ {
private ObservableCollection<Node> _children; private ObservableCollection<Node>? _children;
private int _childIndex = 10; private int _childIndex = 10;
public Node() public Node()
@ -106,7 +106,7 @@ namespace ControlCatalog.ViewModels
Header = parent.Header + ' ' + index; Header = parent.Header + ' ' + index;
} }
public Node Parent { get; } public Node? Parent { get; }
public string Header { get; } public string Header { get; }
public bool AreChildrenInitialized => _children != null; public bool AreChildrenInitialized => _children != null;
public ObservableCollection<Node> Children => _children ??= CreateChildren(); public ObservableCollection<Node> Children => _children ??= CreateChildren();

12
samples/RenderDemo/Pages/GlyphRunPage.xaml

@ -2,13 +2,13 @@
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:RenderDemo.Pages"
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450" mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
x:Class="RenderDemo.Pages.GlyphRunPage"> x:Class="RenderDemo.Pages.GlyphRunPage">
<Border <Grid
ColumnDefinitions="*,*"
Background="White"> Background="White">
<Image <local:GlyphRunControl Grid.Column="0"/>
x:Name="imageControl" <local:GlyphRunGeometryControl Grid.Column="1"/>
Stretch="None"> </Grid>
</Image>
</Border>
</UserControl> </UserControl>

113
samples/RenderDemo/Pages/GlyphRunPage.xaml.cs

@ -9,14 +9,6 @@ namespace RenderDemo.Pages
{ {
public class GlyphRunPage : UserControl public class GlyphRunPage : UserControl
{ {
private Image _imageControl;
private GlyphTypeface _glyphTypeface = Typeface.Default.GlyphTypeface;
private readonly Random _rand = new Random();
private ushort[] _glyphIndices = new ushort[1];
private char[] _characters = new char[1];
private float _fontSize = 20;
private int _direction = 10;
public GlyphRunPage() public GlyphRunPage()
{ {
this.InitializeComponent(); this.InitializeComponent();
@ -25,19 +17,43 @@ namespace RenderDemo.Pages
private void InitializeComponent() private void InitializeComponent()
{ {
AvaloniaXamlLoader.Load(this); AvaloniaXamlLoader.Load(this);
}
}
public class GlyphRunControl : Control
{
private GlyphTypeface _glyphTypeface = Typeface.Default.GlyphTypeface;
private readonly Random _rand = new Random();
private ushort[] _glyphIndices = new ushort[1];
private char[] _characters = new char[1];
private float _fontSize = 20;
private int _direction = 10;
_imageControl = this.FindControl<Image>("imageControl"); private DispatcherTimer _timer;
_imageControl.Source = new DrawingImage();
DispatcherTimer.Run(() => protected override void OnAttachedToVisualTree(VisualTreeAttachmentEventArgs e)
{
_timer = new DispatcherTimer
{
Interval = TimeSpan.FromSeconds(1)
};
_timer.Tick += (s,e) =>
{ {
UpdateGlyphRun(); InvalidateVisual();
};
_timer.Start();
}
protected override void OnDetachedFromVisualTree(VisualTreeAttachmentEventArgs e)
{
_timer.Stop();
return true; _timer = null;
}, TimeSpan.FromSeconds(1));
} }
private void UpdateGlyphRun() public override void Render(DrawingContext context)
{ {
var c = (char)_rand.Next(65, 90); var c = (char)_rand.Next(65, 90);
@ -57,27 +73,70 @@ namespace RenderDemo.Pages
_characters[0] = c; _characters[0] = c;
var scale = (double)_fontSize / _glyphTypeface.DesignEmHeight; var glyphRun = new GlyphRun(_glyphTypeface, _fontSize, _characters, _glyphIndices);
var drawingGroup = new DrawingGroup(); context.DrawGlyphRun(Brushes.Black, glyphRun);
}
}
var glyphRunDrawing = new GlyphRunDrawing public class GlyphRunGeometryControl : Control
{
private GlyphTypeface _glyphTypeface = Typeface.Default.GlyphTypeface;
private readonly Random _rand = new Random();
private ushort[] _glyphIndices = new ushort[1];
private char[] _characters = new char[1];
private float _fontSize = 20;
private int _direction = 10;
private DispatcherTimer _timer;
protected override void OnAttachedToVisualTree(VisualTreeAttachmentEventArgs e)
{
_timer = new DispatcherTimer
{ {
Foreground = Brushes.Black, Interval = TimeSpan.FromSeconds(1)
GlyphRun = new GlyphRun(_glyphTypeface, _fontSize, _characters, _glyphIndices)
}; };
drawingGroup.Children.Add(glyphRunDrawing); _timer.Tick += (s, e) =>
var geometryDrawing = new GeometryDrawing
{ {
Pen = new Pen(Brushes.Black), InvalidateVisual();
Geometry = new RectangleGeometry { Rect = new Rect(glyphRunDrawing.GlyphRun.Size) }
}; };
drawingGroup.Children.Add(geometryDrawing); _timer.Start();
}
protected override void OnDetachedFromVisualTree(VisualTreeAttachmentEventArgs e)
{
_timer.Stop();
_timer = null;
}
public override void Render(DrawingContext context)
{
var c = (char)_rand.Next(65, 90);
if (_fontSize + _direction > 200)
{
_direction = -10;
}
if (_fontSize + _direction < 20)
{
_direction = 10;
}
_fontSize += _direction;
_glyphIndices[0] = _glyphTypeface.GetGlyph(c);
_characters[0] = c;
var glyphRun = new GlyphRun(_glyphTypeface, _fontSize, _characters, _glyphIndices);
var geometry = glyphRun.BuildGeometry();
(_imageControl.Source as DrawingImage).Drawing = drawingGroup; context.DrawGeometry(Brushes.Green, null, geometry);
} }
} }
} }

8
samples/interop/NativeEmbedSample/App.xaml

@ -1,8 +0,0 @@
<Application xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
x:Class="NativeEmbedSample.App">
<Application.Styles>
<StyleInclude Source="avares://Avalonia.Themes.Default/DefaultTheme.xaml"/>
<StyleInclude Source="avares://Avalonia.Themes.Default/Accents/BaseLight.xaml"/>
</Application.Styles>
</Application>

22
samples/interop/NativeEmbedSample/App.xaml.cs

@ -1,22 +0,0 @@
using Avalonia;
using Avalonia.Controls.ApplicationLifetimes;
using Avalonia.Markup.Xaml;
namespace NativeEmbedSample
{
public class App : Application
{
public override void Initialize()
{
AvaloniaXamlLoader.Load(this);
}
public override void OnFrameworkInitializationCompleted()
{
if (ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktopLifetime)
desktopLifetime.MainWindow = new MainWindow();
base.OnFrameworkInitializationCompleted();
}
}
}

121
samples/interop/NativeEmbedSample/EmbedSample.cs

@ -1,121 +0,0 @@
using System;
using System.Diagnostics;
using System.IO;
using System.Runtime.InteropServices;
using System.Text;
using Avalonia.Controls;
using Avalonia.Platform;
using Avalonia.Threading;
using MonoMac.AppKit;
using MonoMac.Foundation;
using MonoMac.WebKit;
using Encoding = SharpDX.Text.Encoding;
namespace NativeEmbedSample
{
public class EmbedSample : NativeControlHost
{
public bool IsSecond { get; set; }
private Process _mplayer;
IPlatformHandle CreateLinux(IPlatformHandle parent)
{
if (IsSecond)
{
var chooser = GtkHelper.CreateGtkFileChooser(parent.Handle);
if (chooser != null)
return chooser;
}
var control = base.CreateNativeControlCore(parent);
var nodes = Path.GetFullPath(Path.Combine(typeof(EmbedSample).Assembly.GetModules()[0].FullyQualifiedName,
"..",
"nodes.mp4"));
_mplayer = Process.Start(new ProcessStartInfo("mplayer",
$"-vo x11 -zoom -loop 0 -wid {control.Handle.ToInt64()} \"{nodes}\"")
{
UseShellExecute = false,
});
return control;
}
void DestroyLinux(IPlatformHandle handle)
{
_mplayer?.Kill();
_mplayer = null;
base.DestroyNativeControlCore(handle);
}
private const string RichText =
@"{\rtf1\ansi\ansicpg1251\deff0\nouicompat\deflang1049{\fonttbl{\f0\fnil\fcharset0 Calibri;}}
{\colortbl ;\red255\green0\blue0;\red0\green77\blue187;\red0\green176\blue80;\red155\green0\blue211;\red247\green150\blue70;\red75\green172\blue198;}
{\*\generator Riched20 6.3.9600}\viewkind4\uc1
\pard\sa200\sl276\slmult1\f0\fs22\lang9 <PREFIX>I \i am\i0 a \cf1\b Rich Text \cf0\b0\fs24 control\cf2\fs28 !\cf3\fs32 !\cf4\fs36 !\cf1\fs40 !\cf5\fs44 !\cf6\fs48 !\cf0\fs44\par
}";
IPlatformHandle CreateWin32(IPlatformHandle parent)
{
WinApi.LoadLibrary("Msftedit.dll");
var handle = WinApi.CreateWindowEx(0, "RICHEDIT50W",
@"Rich Edit",
0x800000 | 0x10000000 | 0x40000000 | 0x800000 | 0x10000 | 0x0004, 0, 0, 1, 1, parent.Handle,
IntPtr.Zero, WinApi.GetModuleHandle(null), IntPtr.Zero);
var st = new WinApi.SETTEXTEX { Codepage = 65001, Flags = 0x00000008 };
var text = RichText.Replace("<PREFIX>", IsSecond ? "\\qr " : "");
var bytes = Encoding.UTF8.GetBytes(text);
WinApi.SendMessage(handle, 0x0400 + 97, ref st, bytes);
return new PlatformHandle(handle, "HWND");
}
void DestroyWin32(IPlatformHandle handle)
{
WinApi.DestroyWindow(handle.Handle);
}
IPlatformHandle CreateOSX(IPlatformHandle parent)
{
// Note: We are using MonoMac for example purposes
// It shouldn't be used in production apps
MacHelper.EnsureInitialized();
var webView = new WebView();
Dispatcher.UIThread.Post(() =>
{
webView.MainFrame.LoadRequest(new NSUrlRequest(new NSUrl(
IsSecond ? "https://bing.com": "https://google.com/")));
});
return new MacOSViewHandle(webView);
}
void DestroyOSX(IPlatformHandle handle)
{
((MacOSViewHandle)handle).Dispose();
}
protected override IPlatformHandle CreateNativeControlCore(IPlatformHandle parent)
{
if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux))
return CreateLinux(parent);
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
return CreateWin32(parent);
if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX))
return CreateOSX(parent);
return base.CreateNativeControlCore(parent);
}
protected override void DestroyNativeControlCore(IPlatformHandle control)
{
if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux))
DestroyLinux(control);
else if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
DestroyWin32(control);
else if(RuntimeInformation.IsOSPlatform(OSPlatform.OSX))
DestroyOSX(control);
else
base.DestroyNativeControlCore(control);
}
}
}

58
samples/interop/NativeEmbedSample/GtkHelper.cs

@ -1,58 +0,0 @@
using System;
using System.Threading.Tasks;
using Avalonia.Controls.Platform;
using Avalonia.Platform;
using Avalonia.Platform.Interop;
using Avalonia.X11.NativeDialogs;
using static Avalonia.X11.NativeDialogs.Gtk;
using static Avalonia.X11.NativeDialogs.Glib;
namespace NativeEmbedSample
{
public class GtkHelper
{
private static Task<bool> s_gtkTask;
class FileChooser : INativeControlHostDestroyableControlHandle
{
private readonly IntPtr _widget;
public FileChooser(IntPtr widget, IntPtr xid)
{
_widget = widget;
Handle = xid;
}
public IntPtr Handle { get; }
public string HandleDescriptor => "XID";
public void Destroy()
{
RunOnGlibThread(() =>
{
gtk_widget_destroy(_widget);
return 0;
}).Wait();
}
}
public static IPlatformHandle CreateGtkFileChooser(IntPtr parentXid)
{
if (s_gtkTask == null)
s_gtkTask = StartGtk();
if (!s_gtkTask.Result)
return null;
return RunOnGlibThread(() =>
{
using (var title = new Utf8Buffer("Embedded"))
{
var widget = gtk_file_chooser_dialog_new(title, IntPtr.Zero, GtkFileChooserAction.SelectFolder,
IntPtr.Zero);
gtk_widget_realize(widget);
var xid = gdk_x11_window_get_xid(gtk_widget_get_window(widget));
gtk_window_present(widget);
return new FileChooser(widget, xid);
}
}).Result;
}
}
}

39
samples/interop/NativeEmbedSample/MacHelper.cs

@ -1,39 +0,0 @@
using System;
using Avalonia.Platform;
using MonoMac.AppKit;
namespace NativeEmbedSample
{
public class MacHelper
{
private static bool _isInitialized;
public static void EnsureInitialized()
{
if (_isInitialized)
return;
_isInitialized = true;
NSApplication.Init();
}
}
class MacOSViewHandle : IPlatformHandle, IDisposable
{
private NSView _view;
public MacOSViewHandle(NSView view)
{
_view = view;
}
public IntPtr Handle => _view?.Handle ?? IntPtr.Zero;
public string HandleDescriptor => "NSView";
public void Dispose()
{
_view.Dispose();
_view = null;
}
}
}

52
samples/interop/NativeEmbedSample/MainWindow.xaml

@ -1,52 +0,0 @@
<Window xmlns="https://github.com/avaloniaui" MinWidth="500" MinHeight="300"
Width="1024" Height="800"
Title="Native embedding sample"
xmlns:local="clr-namespace:NativeEmbedSample"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
x:Class="NativeEmbedSample.MainWindow">
<DockPanel>
<Menu DockPanel.Dock="Top">
<MenuItem Header="Test">
<MenuItem Header="SubMenu">
<MenuItem Header="Item 1"/>
<MenuItem Header="Item 2"/>
<MenuItem Header="Item 3"/>
</MenuItem>
<MenuItem Header="Item 1"/>
<MenuItem Header="Item 2"/>
<MenuItem Header="Item 3"/>
</MenuItem>
</Menu>
<DockPanel DockPanel.Dock="Top">
<Button DockPanel.Dock="Right" Click="ShowPopupDelay">Show popup (delay)</Button>
<Button DockPanel.Dock="Right" Click="ShowPopup">Show popup</Button>
<Border DockPanel.Dock="Right" Background="#c0c0c0">
<ToolTip.Tip>
<ToolTip>
<TextBlock>Text</TextBlock>
</ToolTip>
</ToolTip.Tip>
<TextBlock>Tooltip</TextBlock>
</Border>
<TextBox Text="Lorem ipsum dolor sit amet"/>
</DockPanel>
<Grid ColumnDefinitions="*,5,*">
<DockPanel>
<StackPanel Orientation="Horizontal" DockPanel.Dock="Top">
<CheckBox x:Name="firstVisible" IsChecked="True"/>
<TextBlock>Visible</TextBlock>
</StackPanel>
<local:EmbedSample IsVisible="{Binding #firstVisible.IsChecked}"/>
</DockPanel>
<GridSplitter Grid.Column="1" Width="5" HorizontalAlignment="Stretch" />
<DockPanel Grid.Column="2">
<StackPanel Orientation="Horizontal" DockPanel.Dock="Top">
<CheckBox x:Name="secondVisible" IsChecked="True"/>
<TextBlock>Visible</TextBlock>
</StackPanel>
<local:EmbedSample IsSecond="True" IsVisible="{Binding #secondVisible.IsChecked}"/>
</DockPanel>
</Grid>
</DockPanel>
</Window>

36
samples/interop/NativeEmbedSample/MainWindow.xaml.cs

@ -1,36 +0,0 @@
using System.Collections.Generic;
using System.Threading.Tasks;
using Avalonia;
using Avalonia.Controls;
using Avalonia.Interactivity;
using Avalonia.Markup.Xaml;
namespace NativeEmbedSample
{
public class MainWindow : Window
{
public MainWindow()
{
AvaloniaXamlLoader.Load(this);
this.AttachDevTools();
}
public async void ShowPopupDelay(object sender, RoutedEventArgs args)
{
await Task.Delay(3000);
ShowPopup(sender, args);
}
public void ShowPopup(object sender, RoutedEventArgs args)
{
new ContextMenu()
{
Items = new List<MenuItem>
{
new MenuItem() { Header = "Test" }, new MenuItem() { Header = "Test" }
}
}.Open((Control)sender);
}
}
}

31
samples/interop/NativeEmbedSample/NativeEmbedSample.csproj

@ -1,31 +0,0 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>netcoreapp2.0</TargetFramework>
<TargetLatestRuntimePatch>true</TargetLatestRuntimePatch>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="MonoMac.NetStandard" Version="0.0.4" />
<ProjectReference Include="..\..\..\src\Avalonia.Themes.Default\Avalonia.Themes.Default.csproj" />
<ProjectReference Include="..\..\..\src\Windows\Avalonia.Direct2D1\Avalonia.Direct2D1.csproj" />
<ProjectReference Include="..\..\..\src\Avalonia.Diagnostics\Avalonia.Diagnostics.csproj" />
<ProjectReference Include="..\..\..\src\Avalonia.X11\Avalonia.X11.csproj" />
<PackageReference Include="Avalonia.Angle.Windows.Natives" Version="2.1.0.2020091801" />
<AvaloniaResource Include="**\*.xaml">
<SubType>Designer</SubType>
</AvaloniaResource>
<None Remove="nodes.mp4" />
<Content Include="nodes.mp4">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Compile Include="..\..\..\src\Avalonia.X11\NativeDialogs\Gtk.cs" />
</ItemGroup>
<Import Project="..\..\..\build\SampleApp.props" />
<Import Project="..\..\..\build\BuildTargets.targets" />
<Import Project="..\..\..\build\ReferenceCoreLibraries.props" />
</Project>

17
samples/interop/NativeEmbedSample/Program.cs

@ -1,17 +0,0 @@
using Avalonia;
namespace NativeEmbedSample
{
class Program
{
static int Main(string[] args) => BuildAvaloniaApp().StartWithClassicDesktopLifetime(args);
public static AppBuilder BuildAvaloniaApp()
=> AppBuilder.Configure<App>()
.With(new AvaloniaNativePlatformOptions()
{
})
.UsePlatformDetect();
}
}

74
samples/interop/NativeEmbedSample/WinApi.cs

@ -1,74 +0,0 @@
using System;
using System.Runtime.InteropServices;
namespace NativeEmbedSample
{
public unsafe class WinApi
{
public enum CommonControls : uint
{
ICC_LISTVIEW_CLASSES = 0x00000001, // listview, header
ICC_TREEVIEW_CLASSES = 0x00000002, // treeview, tooltips
ICC_BAR_CLASSES = 0x00000004, // toolbar, statusbar, trackbar, tooltips
ICC_TAB_CLASSES = 0x00000008, // tab, tooltips
ICC_UPDOWN_CLASS = 0x00000010, // updown
ICC_PROGRESS_CLASS = 0x00000020, // progress
ICC_HOTKEY_CLASS = 0x00000040, // hotkey
ICC_ANIMATE_CLASS = 0x00000080, // animate
ICC_WIN95_CLASSES = 0x000000FF,
ICC_DATE_CLASSES = 0x00000100, // month picker, date picker, time picker, updown
ICC_USEREX_CLASSES = 0x00000200, // comboex
ICC_COOL_CLASSES = 0x00000400, // rebar (coolbar) control
ICC_INTERNET_CLASSES = 0x00000800,
ICC_PAGESCROLLER_CLASS = 0x00001000, // page scroller
ICC_NATIVEFNTCTL_CLASS = 0x00002000, // native font control
ICC_STANDARD_CLASSES = 0x00004000,
ICC_LINK_CLASS = 0x00008000
}
[StructLayout(LayoutKind.Sequential)]
public struct INITCOMMONCONTROLSEX
{
public int dwSize;
public uint dwICC;
}
[DllImport("Comctl32.dll")]
public static extern void InitCommonControlsEx(ref INITCOMMONCONTROLSEX init);
[DllImport("user32.dll", SetLastError = true)]
public static extern bool DestroyWindow(IntPtr hwnd);
[DllImport("kernel32.dll")]
public static extern IntPtr LoadLibrary(string lib);
[DllImport("kernel32.dll")]
public static extern IntPtr GetModuleHandle(string lpModuleName);
[DllImport("user32.dll", SetLastError = true)]
public static extern IntPtr CreateWindowEx(
int dwExStyle,
string lpClassName,
string lpWindowName,
uint dwStyle,
int x,
int y,
int nWidth,
int nHeight,
IntPtr hWndParent,
IntPtr hMenu,
IntPtr hInstance,
IntPtr lpParam);
[StructLayout(LayoutKind.Sequential)]
public struct SETTEXTEX
{
public uint Flags;
public uint Codepage;
}
[DllImport("user32.dll", CharSet = CharSet.Unicode, EntryPoint = "SendMessageW")]
public static extern IntPtr SendMessage(IntPtr hWnd, int Msg, ref SETTEXTEX wParam, byte[] lParam);
}
}

2
src/Android/Avalonia.Android/AndroidPlatform.cs

@ -61,8 +61,6 @@ namespace Avalonia.Android
.Bind<IRenderLoop>().ToConstant(new RenderLoop()) .Bind<IRenderLoop>().ToConstant(new RenderLoop())
.Bind<PlatformHotkeyConfiguration>().ToSingleton<PlatformHotkeyConfiguration>(); .Bind<PlatformHotkeyConfiguration>().ToSingleton<PlatformHotkeyConfiguration>();
SkiaPlatform.Initialize();
if (options.UseGpu) if (options.UseGpu)
{ {
EglPlatformOpenGlInterface.TryInitialize(); EglPlatformOpenGlInterface.TryInitialize();

32
src/Android/Avalonia.Android/AndroidViewControlHandle.cs

@ -0,0 +1,32 @@
#nullable enable
using System;
using Android.Views;
using Avalonia.Controls.Platform;
using Avalonia.Platform;
namespace Avalonia.Android
{
public class AndroidViewControlHandle : INativeControlHostDestroyableControlHandle
{
internal const string AndroidDescriptor = "JavaObjectHandle";
public AndroidViewControlHandle(View view)
{
View = view;
}
public View View { get; }
public string HandleDescriptor => AndroidDescriptor;
IntPtr IPlatformHandle.Handle => View.Handle;
public void Destroy()
{
View?.Dispose();
}
}
}

7
src/Android/Avalonia.Android/AvaloniaActivity.cs

@ -31,21 +31,22 @@ namespace Avalonia.Android
CustomizeAppBuilder(builder); CustomizeAppBuilder(builder);
View = new AvaloniaView(this);
SetContentView(View);
var lifetime = new SingleViewLifetime(); var lifetime = new SingleViewLifetime();
lifetime.View = View;
builder.AfterSetup(x => builder.AfterSetup(x =>
{ {
_viewModel = new ViewModelProvider(this).Get(Java.Lang.Class.FromType(typeof(AvaloniaViewModel))) as AvaloniaViewModel; _viewModel = new ViewModelProvider(this).Get(Java.Lang.Class.FromType(typeof(AvaloniaViewModel))) as AvaloniaViewModel;
View = new AvaloniaView(this);
if (_viewModel.Content != null) if (_viewModel.Content != null)
{ {
View.Content = _viewModel.Content; View.Content = _viewModel.Content;
} }
SetContentView(View);
lifetime.View = View;
View.Prepare(); View.Prepare();
}); });

9
src/Android/Avalonia.Android/AvaloniaView.cs

@ -15,13 +15,12 @@ namespace Avalonia.Android
private EmbeddableControlRoot _root; private EmbeddableControlRoot _root;
private readonly ViewImpl _view; private readonly ViewImpl _view;
private IDisposable? _timerSubscription; private IDisposable _timerSubscription;
public AvaloniaView(Context context) : base(context) public AvaloniaView(Context context) : base(context)
{ {
_view = new ViewImpl(context); _view = new ViewImpl(this);
AddView(_view.View); AddView(_view.View);
} }
internal void Prepare () internal void Prepare ()
@ -30,6 +29,8 @@ namespace Avalonia.Android
_root.Prepare(); _root.Prepare();
} }
internal TopLevelImpl TopLevelImpl => _view;
public object Content public object Content
{ {
get { return _root.Content; } get { return _root.Content; }
@ -73,7 +74,7 @@ namespace Avalonia.Android
class ViewImpl : TopLevelImpl class ViewImpl : TopLevelImpl
{ {
public ViewImpl(Context context) : base(context) public ViewImpl(AvaloniaView avaloniaView) : base(avaloniaView)
{ {
View.Focusable = true; View.Focusable = true;
View.FocusChange += ViewImpl_FocusChange; View.FocusChange += ViewImpl_FocusChange;

139
src/Android/Avalonia.Android/Platform/AndroidNativeControlHostImpl.cs

@ -0,0 +1,139 @@
#nullable enable
using System;
using System.Diagnostics.CodeAnalysis;
using Android.Views;
using Android.Widget;
using Avalonia.Controls.Platform;
using Avalonia.Platform;
namespace Avalonia.Android.Platform
{
internal class AndroidNativeControlHostImpl : INativeControlHostImpl
{
private readonly AvaloniaView _avaloniaView;
public AndroidNativeControlHostImpl(AvaloniaView avaloniaView)
{
_avaloniaView = avaloniaView;
}
public INativeControlHostDestroyableControlHandle CreateDefaultChild(IPlatformHandle parent)
{
return new AndroidViewControlHandle(new FrameLayout(_avaloniaView.Context!));
}
public INativeControlHostControlTopLevelAttachment CreateNewAttachment(Func<IPlatformHandle, IPlatformHandle> create)
{
var parent = new AndroidViewControlHandle(_avaloniaView);
AndroidNativeControlAttachment? attachment = null;
try
{
var child = create(parent);
// It has to be assigned to the variable before property setter is called so we dispose it on exception
#pragma warning disable IDE0017 // Simplify object initialization
attachment = new AndroidNativeControlAttachment(child);
#pragma warning restore IDE0017 // Simplify object initialization
attachment.AttachedTo = this;
return attachment;
}
catch
{
attachment?.Dispose();
throw;
}
}
public INativeControlHostControlTopLevelAttachment CreateNewAttachment(IPlatformHandle handle)
{
return new AndroidNativeControlAttachment(handle)
{
AttachedTo = this
};
}
public bool IsCompatibleWith(IPlatformHandle handle) => handle.HandleDescriptor == AndroidViewControlHandle.AndroidDescriptor;
private class AndroidNativeControlAttachment : INativeControlHostControlTopLevelAttachment
{
private View? _view;
private AndroidNativeControlHostImpl? _attachedTo;
public AndroidNativeControlAttachment(IPlatformHandle child)
{
_view = (child as AndroidViewControlHandle)?.View
?? Java.Lang.Object.GetObject<View>(child.Handle, global::Android.Runtime.JniHandleOwnership.DoNotTransfer);
}
[MemberNotNull(nameof(_view))]
private void CheckDisposed()
{
if (_view == null)
throw new ObjectDisposedException(nameof(AndroidNativeControlAttachment));
}
public void Dispose()
{
if (_view != null && _attachedTo?._avaloniaView is ViewGroup parent)
{
parent.RemoveView(_view);
}
_attachedTo = null;
_view?.Dispose();
_view = null;
}
public INativeControlHostImpl? AttachedTo
{
get => _attachedTo;
set
{
CheckDisposed();
var oldAttachedTo = _attachedTo;
_attachedTo = (AndroidNativeControlHostImpl?)value;
if (_attachedTo == null)
{
oldAttachedTo?._avaloniaView.RemoveView(_view);
}
else
{
_attachedTo._avaloniaView.AddView(_view);
}
}
}
public bool IsCompatibleWith(INativeControlHostImpl host) => host is AndroidNativeControlHostImpl;
public void HideWithSize(Size size)
{
CheckDisposed();
if (_attachedTo == null)
return;
size *= _attachedTo._avaloniaView.TopLevelImpl.RenderScaling;
_view.Visibility = ViewStates.Gone;
_view.LayoutParameters = new FrameLayout.LayoutParams(Math.Max(1, (int)size.Width), Math.Max(1, (int)size.Height));
_view.RequestLayout();
}
public void ShowInBounds(Rect bounds)
{
CheckDisposed();
if (_attachedTo == null)
throw new InvalidOperationException("The control isn't currently attached to a toplevel");
bounds *= _attachedTo._avaloniaView.TopLevelImpl.RenderScaling;
_view.Visibility = ViewStates.Visible;
_view.LayoutParameters = new FrameLayout.LayoutParams(Math.Max(1, (int)bounds.Width), Math.Max(1, (int)bounds.Height))
{
LeftMargin = (int)bounds.X,
TopMargin = (int)bounds.Y
};
_view.RequestLayout();
}
}
}
}

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

@ -20,7 +20,7 @@ using Avalonia.Rendering;
namespace Avalonia.Android.Platform.SkiaPlatform namespace Avalonia.Android.Platform.SkiaPlatform
{ {
class TopLevelImpl : IAndroidView, ITopLevelImpl, EglGlPlatformSurfaceBase.IEglWindowGlPlatformSurfaceInfo, ITopLevelImplWithTextInputMethod class TopLevelImpl : IAndroidView, ITopLevelImpl, EglGlPlatformSurfaceBase.IEglWindowGlPlatformSurfaceInfo, ITopLevelImplWithTextInputMethod, ITopLevelImplWithNativeControlHost
{ {
private readonly IGlPlatformSurface _gl; private readonly IGlPlatformSurface _gl;
private readonly IFramebufferPlatformSurface _framebuffer; private readonly IFramebufferPlatformSurface _framebuffer;
@ -30,9 +30,9 @@ namespace Avalonia.Android.Platform.SkiaPlatform
private readonly ITextInputMethodImpl _textInputMethod; private readonly ITextInputMethodImpl _textInputMethod;
private ViewImpl _view; private ViewImpl _view;
public TopLevelImpl(Context context, bool placeOnTop = false) public TopLevelImpl(AvaloniaView avaloniaView, bool placeOnTop = false)
{ {
_view = new ViewImpl(context, this, placeOnTop); _view = new ViewImpl(avaloniaView.Context, this, placeOnTop);
_textInputMethod = new AndroidInputMethod<ViewImpl>(_view); _textInputMethod = new AndroidInputMethod<ViewImpl>(_view);
_keyboardHelper = new AndroidKeyboardEventsHelper<TopLevelImpl>(this); _keyboardHelper = new AndroidKeyboardEventsHelper<TopLevelImpl>(this);
_touchHelper = new AndroidTouchEventsHelper<TopLevelImpl>(this, () => InputRoot, _touchHelper = new AndroidTouchEventsHelper<TopLevelImpl>(this, () => InputRoot,
@ -44,6 +44,8 @@ namespace Avalonia.Android.Platform.SkiaPlatform
MaxClientSize = new PixelSize(_view.Resources.DisplayMetrics.WidthPixels, MaxClientSize = new PixelSize(_view.Resources.DisplayMetrics.WidthPixels,
_view.Resources.DisplayMetrics.HeightPixels).ToSize(RenderScaling); _view.Resources.DisplayMetrics.HeightPixels).ToSize(RenderScaling);
NativeControlHost = new AndroidNativeControlHostImpl(avaloniaView);
} }
public virtual Point GetAvaloniaPointFromEvent(MotionEvent e, int pointerIndex) => public virtual Point GetAvaloniaPointFromEvent(MotionEvent e, int pointerIndex) =>
@ -222,6 +224,8 @@ namespace Avalonia.Android.Platform.SkiaPlatform
public ITextInputMethodImpl TextInputMethod => _textInputMethod; public ITextInputMethodImpl TextInputMethod => _textInputMethod;
public INativeControlHostImpl NativeControlHost { get; }
public void SetTransparencyLevelHint(WindowTransparencyLevel transparencyLevel) public void SetTransparencyLevelHint(WindowTransparencyLevel transparencyLevel)
{ {
throw new NotImplementedException(); throw new NotImplementedException();

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

@ -30,7 +30,7 @@ namespace Avalonia.Android.Platform.Specific.Helpers
return DispatchKeyEventInternal(e, out callBase); return DispatchKeyEventInternal(e, out callBase);
} }
string? UnicodeTextInput(KeyEvent keyEvent) string UnicodeTextInput(KeyEvent keyEvent)
{ {
return keyEvent.Action == KeyEventActions.Multiple return keyEvent.Action == KeyEventActions.Multiple
&& keyEvent.RepeatCount == 0 && keyEvent.RepeatCount == 0

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

Loading…
Cancel
Save