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

6
build/SkiaSharp.props

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

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

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

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

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

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

@ -33,6 +33,7 @@
bool _isEnabled;
bool _canBecomeKeyWindow;
bool _isExtended;
bool _isTransitioningToFullScreen;
AvnMenu* _menu;
}
@ -68,7 +69,7 @@
}
}
- (void)performClose:(id)sender
- (void)performClose:(id _Nullable )sender
{
if([[self delegate] respondsToSelector:@selector(windowShouldClose:)])
{
@ -147,7 +148,7 @@
}
}
-(void) applyMenu:(AvnMenu *)menu
-(void) applyMenu:(AvnMenu *_Nullable)menu
{
if(menu == nullptr)
{
@ -157,7 +158,7 @@
_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/
// create nswindow with specific contentRect, otherwise we wont be able to resize the window
@ -175,15 +176,17 @@
[self setBackgroundColor: [NSColor clearColor]];
_isExtended = false;
_isTransitioningToFullScreen = false;
#ifdef IS_NSPANEL
[self setCollectionBehavior:NSWindowCollectionBehaviorCanJoinAllSpaces|NSWindowCollectionBehaviorFullScreenAuxiliary];
#endif
if(self.isDialog)
{
[self setCollectionBehavior:NSWindowCollectionBehaviorCanJoinAllSpaces|NSWindowCollectionBehaviorFullScreenAuxiliary];
}
return self;
}
- (BOOL)windowShouldClose:(NSWindow *)sender
- (BOOL)windowShouldClose:(NSWindow *_Nonnull)sender
{
auto window = dynamic_cast<WindowImpl*>(_parent.getRaw());
@ -195,21 +198,28 @@
return true;
}
- (void)windowDidChangeBackingProperties:(NSNotification *)notification
- (void)windowDidChangeBackingProperties:(NSNotification *_Nonnull)notification
{
[self backingScaleFactor];
}
- (void)windowWillClose:(NSNotification *)notification
- (void)windowWillClose:(NSNotification *_Nonnull)notification
{
_closed = true;
if(_parent)
{
ComPtr<WindowBaseImpl> parent = _parent;
_parent = NULL;
[self restoreParentWindow];
auto window = dynamic_cast<WindowImpl*>(parent.getRaw());
if(window != nullptr)
{
window->SetParent(nullptr);
}
parent->BaseEvents->Closed();
[parent->View onClosed];
}
@ -220,17 +230,11 @@
if(_canBecomeKeyWindow)
{
// 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)])
{
continue;
}
id <AvnWindowProtocol> ch = (id <AvnWindowProtocol>) uch;
if(ch.isDialog)
return false;
return parent->CanBecomeKeyWindow();
}
return true;
@ -259,6 +263,10 @@
-(void) setEnabled:(bool)enable
{
_isEnabled = enable;
[[self standardWindowButton:NSWindowCloseButton] setEnabled:enable];
[[self standardWindowButton:NSWindowMiniaturizeButton] setEnabled:enable];
[[self standardWindowButton:NSWindowZoomButton] setEnabled:enable];
}
-(void)becomeKeyWindow
@ -273,17 +281,20 @@
[super becomeKeyWindow];
}
-(void) restoreParentWindow;
- (void)windowDidBecomeKey:(NSNotification *_Nonnull)notification
{
auto parent = [self parentWindow];
if(parent != nil)
{
[parent removeChildWindow:self];
}
_parent->BringToFront();
dispatch_async(dispatch_get_main_queue(), ^{
@try {
[self invalidateShadow];
}
@finally{
}
});
}
- (void)windowDidMiniaturize:(NSNotification *)notification
- (void)windowDidMiniaturize:(NSNotification *_Nonnull)notification
{
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->());
@ -303,7 +314,7 @@
}
}
- (void)windowDidResize:(NSNotification *)notification
- (void)windowDidResize:(NSNotification *_Nonnull)notification
{
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->());
@ -323,7 +334,7 @@
}
}
- (void)windowDidExitFullScreen:(NSNotification *)notification
- (void)windowDidExitFullScreen:(NSNotification *_Nonnull)notification
{
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->());
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->());
if(parent != nullptr)
@ -367,7 +380,7 @@
}
}
- (BOOL)windowShouldZoom:(NSWindow *)window toFrame:(NSRect)newFrame
- (BOOL)windowShouldZoom:(NSWindow *_Nonnull)window toFrame:(NSRect)newFrame
{
return true;
}
@ -378,11 +391,13 @@
_parent->BaseEvents->Deactivated();
[self showAppMenuOnly];
[self invalidateShadow];
[super resignKeyWindow];
}
- (void)windowDidMove:(NSNotification *)notification
- (void)windowDidMove:(NSNotification *_Nonnull)notification
{
AvnPoint position;
@ -414,7 +429,7 @@
return pt;
}
- (void)sendEvent:(NSEvent *)event
- (void)sendEvent:(NSEvent *_Nonnull)event
{
[super sendEvent:event];
@ -437,8 +452,13 @@
_parent->BaseEvents->RawMouseEvent(NonClientLeftButtonDown, static_cast<uint32>([event timestamp] * 1000), AvnInputModifiersNone, point, delta);
}
if(!_isTransitioningToFullScreen)
{
_parent->BringToFront();
}
}
break;
break;
case NSEventTypeMouseEntered:
{

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

@ -11,7 +11,7 @@
struct INSWindowHolder
{
virtual NSWindow* _Nonnull GetNSWindow () = 0;
virtual NSView* _Nonnull GetNSView () = 0;
virtual AvnView* _Nonnull GetNSView () = 0;
};
#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)
{
WindowEvents = events;
[Window setLevel:NSPopUpMenuWindowLevel];
}
protected:
virtual NSWindowStyleMask GetStyle() override
{
return NSWindowStyleMaskBorderless;
}
virtual void OnInitialiseNSWindow () override
{
[Window setLevel:NSPopUpMenuWindowLevel];
}
public:
virtual bool ShouldTakeFocusOnShow() override

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

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

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

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

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

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

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

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

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

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

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

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

1
nukebuild/Build.cs

@ -221,7 +221,6 @@ partial class Build : NukeBuild
RunCoreTest("Avalonia.Markup.Xaml.UnitTests");
RunCoreTest("Avalonia.Skia.UnitTests");
RunCoreTest("Avalonia.ReactiveUI.UnitTests");
RunCoreTest("Avalonia.PlatformSupport.UnitTests");
});
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).
## 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
@ -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://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
This project is supported by the [.NET Foundation](https://dotnetfoundation.org).

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

@ -20,10 +20,13 @@
</ItemGroup>
<PropertyGroup Condition="'$(Configuration)'=='Release' and '$(TF_BUILD)' == ''">
<DebugSymbols>True</DebugSymbols>
<DebugSymbols>False</DebugSymbols>
<UseInterpreter>False</UseInterpreter>
<RunAOTCompilation>True</RunAOTCompilation>
<EnableLLVM>True</EnableLLVM>
<AndroidEnableProfiledAot>True</AndroidEnableProfiledAot>
<AndroidAotAdditionalArguments>no-write-symbols,nodebug</AndroidAotAdditionalArguments>
<AndroidAotMode>Hybrid</AndroidAotMode>
<AndroidGenerateJniMarshalMethods>True</AndroidGenerateJniMarshalMethods>
</PropertyGroup>
<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)
{
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>
<TargetFramework>net6.0</TargetFramework>
<TargetLatestRuntimePatch>true</TargetLatestRuntimePatch>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
</PropertyGroup>
<PropertyGroup Condition="'$(RunNativeAotCompilation)' == 'true'">
@ -12,6 +13,16 @@
<NativeAotCompilerVersion>7.0.0-*</NativeAotCompilerVersion>
</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>
<ProjectReference Include="..\..\src\Avalonia.Diagnostics\Avalonia.Diagnostics.csproj" />
<ProjectReference Include="..\..\src\Avalonia.Headless.Vnc\Avalonia.Headless.Vnc.csproj" />
@ -20,6 +31,8 @@
<ProjectReference Include="..\ControlCatalog\ControlCatalog.csproj" />
<ProjectReference Include="..\..\src\Avalonia.X11\Avalonia.X11.csproj" />
<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 Condition="'$(RunNativeAotCompilation)' == 'true'">
@ -32,6 +45,7 @@
<PropertyGroup>
<!-- For Microsoft.CodeAnalysis -->
<SatelliteResourceLanguages>en</SatelliteResourceLanguages>
<ApplicationManifest>app.manifest</ApplicationManifest>
</PropertyGroup>
<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.Controls;
using Avalonia.Controls.ApplicationLifetimes;
using Avalonia.Dialogs;
using Avalonia.Headless;
using Avalonia.LogicalTree;
using Avalonia.Threading;
using ControlCatalog.Pages;
namespace ControlCatalog.NetCore
{
static class Program
@ -123,6 +124,11 @@ namespace ControlCatalog.NetCore
{
StartupScreenIndex = 1,
});
EmbedSample.Implementation = OperatingSystem.IsWindows() ? (INativeDemoControl)new EmbedSampleWin()
: OperatingSystem.IsMacOS() ? new EmbedSampleMac()
: OperatingSystem.IsLinux() ? new EmbedSampleGtk()
: null;
})
.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()
{
WebAppBuilder.Configure<ControlCatalog.App>()
.AfterSetup(_ =>
{
ControlCatalog.Pages.EmbedSample.Implementation = new EmbedSampleWeb();
})
.SetupWithSingleViewLifetime();
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;
}
#blazor-error-ui .dismiss {
cursor: pointer;
position: absolute;
right: 0.75rem;
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;
#blazor-error-ui .dismiss {
cursor: pointer;
position: absolute;
right: 0.75rem;
top: 0.5rem;
}
#app, .page {
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")]
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\Fonts\*" />
</ItemGroup>
<ItemGroup>
<None Remove="Pages\NativeEmbedPage.xaml" />
</ItemGroup>
<ItemGroup>
<EmbeddedResource Include="Assets\Fonts\SourceSansPro-Bold.ttf" />
<EmbeddedResource Include="Assets\Fonts\SourceSansPro-BoldItalic.ttf" />
@ -32,5 +35,17 @@
<ProjectReference Include="..\SampleControls\ControlSamples.csproj" />
</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" />
</Project>

10
samples/ControlCatalog/Converter/MathSubtractConverter.cs

@ -6,12 +6,16 @@ namespace ControlCatalog.Converter;
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();
}

10
samples/ControlCatalog/DecoratedWindow.xaml.cs

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

7
samples/ControlCatalog/MainView.xaml

@ -2,8 +2,8 @@
xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
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.Styles>
<Style Selector="TextBlock.h2">
@ -157,6 +157,9 @@
<TabItem Header="Viewbox">
<pages:ViewboxPage />
</TabItem>
<TabItem Header="Native Embed">
<pages:NativeEmbedPage />
</TabItem>
<TabItem Header="Window Customizations">
<pages:WindowCustomizationsPage />
</TabItem>

16
samples/ControlCatalog/MainView.xaml.cs

@ -18,9 +18,9 @@ namespace ControlCatalog
{
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);
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) =>
{
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) =>
{
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) =>
{
if (VisualRoot is Window window
@ -99,8 +99,8 @@ namespace ControlCatalog
}
};
var transparencyLevels = this.Find<ComboBox>("TransparencyLevels");
IDisposable backgroundSetter = null, paneBackgroundSetter = null;
var transparencyLevels = this.Get<ComboBox>("TransparencyLevels");
IDisposable? backgroundSetter = null, paneBackgroundSetter = null;
transparencyLevels.SelectionChanged += (sender, e) =>
{
backgroundSetter?.Dispose();
@ -118,7 +118,7 @@ namespace ControlCatalog
protected override void OnAttachedToVisualTree(VisualTreeAttachmentEventArgs e)
{
base.OnAttachedToVisualTree(e);
var decorations = this.Find<ComboBox>("Decorations");
var decorations = this.Get<ComboBox>("Decorations");
if (VisualRoot is Window window)
decorations.SelectedIndex = (int)window.SystemDecorations;
}

8
samples/ControlCatalog/MainWindow.xaml.cs

@ -12,7 +12,7 @@ namespace ControlCatalog
public class MainWindow : Window
{
private WindowNotificationManager _notificationArea;
private NativeMenu _recentMenu;
private NativeMenu? _recentMenu;
public MainWindow()
{
@ -28,9 +28,7 @@ namespace ControlCatalog
};
DataContext = new MainWindowViewModel(_notificationArea);
_recentMenu = ((NativeMenu.GetMenu(this).Items[0] as NativeMenuItem).Menu.Items[2] as NativeMenuItem).Menu;
ExtendClientAreaChromeHints = Avalonia.Platform.ExtendClientAreaChromeHints.OSXThickTitleBar;
_recentMenu = ((NativeMenu.GetMenu(this)?.Items[0] as NativeMenuItem)?.Menu?.Items[2] as NativeMenuItem)?.Menu;
}
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)
{
_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)

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);
}
static IReadOnlyList<Country> _all;
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 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)
{
@ -29,7 +29,7 @@ namespace ControlCatalog.Models
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();
}

16
samples/ControlCatalog/Models/Person.cs

@ -13,8 +13,8 @@ namespace ControlCatalog.Models
{
public class Person : INotifyDataErrorInfo, INotifyPropertyChanged
{
string _firstName;
string _lastName;
string _firstName = string.Empty;
string _lastName = string.Empty;
bool _isBanned;
private int _age;
@ -76,7 +76,7 @@ namespace ControlCatalog.Models
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))
{
@ -88,11 +88,11 @@ namespace ControlCatalog.Models
if (_errorLookup.TryGetValue(propertyName, out List<string> errorList))
{
errorList.Clear();
errorList.Add(error);
errorList.Add(error!);
}
else
{
var errors = new List<string> { error };
var errors = new List<string> { error! };
_errorLookup.Add(propertyName, errors);
}
@ -102,8 +102,8 @@ namespace ControlCatalog.Models
public bool HasErrors => _errorLookup.Count > 0;
public event EventHandler<DataErrorsChangedEventArgs> ErrorsChanged;
public event PropertyChangedEventHandler PropertyChanged;
public event EventHandler<DataErrorsChangedEventArgs>? ErrorsChanged;
public event PropertyChangedEventHandler? PropertyChanged;
void OnErrorsChanged(string propertyName)
{
@ -114,7 +114,7 @@ namespace ControlCatalog.Models
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
public IEnumerable GetErrors(string propertyName)
public IEnumerable? GetErrors(string propertyName)
{
if (_errorLookup.TryGetValue(propertyName, out List<string> 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("Abbreviation"));
var multibindingBox = this.FindControl<AutoCompleteBox>("MultiBindingBox");
var multibindingBox = this.Get<AutoCompleteBox>("MultiBindingBox");
multibindingBox.ValueMemberBinding = binding;
var asyncBox = this.FindControl<AutoCompleteBox>("AsyncBox");
var asyncBox = this.Get<AutoCompleteBox>("AsyncBox");
asyncBox.AsyncPopulator = PopulateAsync;
var customAutocompleteBox = this.FindControl<AutoCompleteBox>("CustomAutocompleteBox");
var customAutocompleteBox = this.Get<AutoCompleteBox>("CustomAutocompleteBox");
customAutocompleteBox.Items = Sentences.SelectMany(x => x);
customAutocompleteBox.TextFilter = LastWordContains;
customAutocompleteBox.TextSelector = AppendWord;
@ -144,11 +144,12 @@ namespace ControlCatalog.Pages
.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;
}
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);
@ -157,14 +158,14 @@ namespace ControlCatalog.Pages
.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();
for (var i = 0; i < words.Length; ++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];
if (option == null)
@ -183,14 +184,18 @@ namespace ControlCatalog.Pages
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 (parts.Length == 0)
return item;
if (item is { })
{
string[] parts = text?.Split(' ') ?? Array.Empty<string>();
if (parts.Length == 0)
return item;
parts[parts.Length - 1] = item;
return string.Join(" ", parts);
parts[parts.Length - 1] = item;
return string.Join(" ", parts);
}
return string.Empty;
}
private void InitializeComponent()

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

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

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

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

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

@ -10,11 +10,11 @@ namespace ControlCatalog.Pages
{
InitializeComponent();
var dp1 = this.FindControl<CalendarDatePicker>("DatePicker1");
var dp2 = this.FindControl<CalendarDatePicker>("DatePicker2");
var dp3 = this.FindControl<CalendarDatePicker>("DatePicker3");
var dp4 = this.FindControl<CalendarDatePicker>("DatePicker4");
var dp5 = this.FindControl<CalendarDatePicker>("DatePicker5");
var dp1 = this.Get<CalendarDatePicker>("DatePicker1");
var dp2 = this.Get<CalendarDatePicker>("DatePicker2");
var dp3 = this.Get<CalendarDatePicker>("DatePicker3");
var dp4 = this.Get<CalendarDatePicker>("DatePicker4");
var dp5 = this.Get<CalendarDatePicker>("DatePicker5");
dp1.SelectedDate = DateTime.Today;
dp2.SelectedDate = DateTime.Today.AddDays(10);
@ -23,7 +23,7 @@ namespace ControlCatalog.Pages
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();
var today = DateTime.Today;
var cal1 = this.FindControl<Calendar>("DisplayDatesCalendar");
var cal1 = this.Get<Calendar>("DisplayDatesCalendar");
cal1.DisplayDateStart = 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.Add(new CalendarDateRange(today.AddDays(6)));
}

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

@ -16,6 +16,11 @@ namespace ControlCatalog.Pages
public CarouselPage()
{
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();
_right.Click += (s, e) => _carousel.Next();
_transition.SelectionChanged += TransitionChanged;
@ -25,11 +30,7 @@ namespace ControlCatalog.Pages
private void InitializeComponent()
{
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)

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

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

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

@ -15,15 +15,15 @@ namespace ControlCatalog.Pages
this.InitializeComponent();
DataContext = new ContextPageViewModel();
var customContextRequestedBorder = this.FindControl<Border>("CustomContextRequestedBorder");
var customContextRequestedBorder = this.Get<Border>("CustomContextRequestedBorder");
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!.ContextMenuOpening += ContextFlyoutPage_Opening;
}
private ContextPageViewModel _model;
private ContextPageViewModel? _model;
protected override void OnDataContextChanged(EventArgs e)
{
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 collectionView1 = new DataGridCollectionView(Countries.All);
collectionView1.SortDescriptions.Add(dataGridSortDescription);
var dg1 = this.FindControl<DataGrid>("dataGrid1");
var dg1 = this.Get<DataGrid>("dataGrid1");
dg1.IsReadOnly = true;
dg1.LoadingRow += Dg1_LoadingRow;
dg1.Sorting += (s, a) =>
@ -37,7 +37,7 @@ namespace ControlCatalog.Pages
};
dg1.Items = collectionView1;
var dg2 = this.FindControl<DataGrid>("dataGridGrouping");
var dg2 = this.Get<DataGrid>("dataGridGrouping");
dg2.IsReadOnly = true;
var collectionView2 = new DataGridCollectionView(Countries.All);
@ -45,7 +45,7 @@ namespace ControlCatalog.Pages
dg2.Items = collectionView2;
var dg3 = this.FindControl<DataGrid>("dataGridEdit");
var dg3 = this.Get<DataGrid>("dataGridEdit");
dg3.IsReadOnly = false;
var items = new List<Person>
@ -58,7 +58,7 @@ namespace ControlCatalog.Pages
dg3.Items = collectionView3;
var addButton = this.FindControl<Button>("btnAdd");
var addButton = this.Get<Button>("btnAdd");
addButton.Click += (a, b) => collectionView3.AddNew();
}

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

@ -9,12 +9,12 @@ namespace ControlCatalog.Pages
public DateTimePickerPage()
{
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. " +
"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";
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 " +
"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.";

2
samples/ControlCatalog/Pages/DialogsPage.xaml

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

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

@ -14,7 +14,7 @@ namespace ControlCatalog.Pages
public DragAndDropPage()
{
this.InitializeComponent();
_DropState = this.Find<TextBlock>("DropState");
_DropState = this.Get<TextBlock>("DropState");
int textCount = 0;
SetupDnd("Text", d => d.Set(DataFormats.Text,
@ -26,8 +26,8 @@ namespace ControlCatalog.Pages
void SetupDnd(string suffix, Action<DataObject> factory, DragDropEffects effects)
{
var dragMe = this.Find<Border>("DragMe" + suffix);
var dragState = this.Find<TextBlock>("DragState"+suffix);
var dragMe = this.Get<Border>("DragMe" + suffix);
var dragState = this.Get<TextBlock>("DragState"+suffix);
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()
{
var bfxt = this.FindControl<TextBlock>("ButtonFlyoutXamlText");
var bfxt = this.Get<TextBlock>("ButtonFlyoutXamlText");
bfxt.Text = "<Button Content=\"Click me!\">\n" +
" <Button.Flyout>\n" +
" <Flyout>\n" +
@ -45,7 +45,7 @@ namespace ControlCatalog.Pages
" </Flyout>\n" +
" </Button.Flyout>\n</Button>";
var mfxt = this.FindControl<TextBlock>("MenuFlyoutXamlText");
var mfxt = this.Get<TextBlock>("MenuFlyoutXamlText");
mfxt.Text = "<Button Content=\"Click me!\">\n" +
" <Button.Flyout>\n" +
" <MenuFlyout>\n" +
@ -54,7 +54,7 @@ namespace ControlCatalog.Pages
" </MenuFlyout>\n" +
" </Button.Flyout>\n</Button>";
var afxt = this.FindControl<TextBlock>("AttachedFlyoutXamlText");
var afxt = this.Get<TextBlock>("AttachedFlyoutXamlText");
afxt.Text = "<Panel Name=\"AttachedFlyoutPanel\">\n" +
" <FlyoutBase.AttachedFlyout>\n" +
" <Flyout>\n" +
@ -66,7 +66,7 @@ namespace ControlCatalog.Pages
"\n\n In DoubleTapped handler:\n" +
"FlyoutBase.ShowAttachedFlyout(AttachedFlyoutPanel);";
var sfxt = this.FindControl<TextBlock>("SharedFlyoutXamlText");
var sfxt = this.Get<TextBlock>("SharedFlyoutXamlText");
sfxt.Text = "Declare a flyout in Resources:\n" +
"<Window.Resources>\n" +
" <Flyout x:Key=\"SharedFlyout\">\n" +

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

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

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

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

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

@ -22,7 +22,7 @@ namespace ControlCatalog.Pages
AvaloniaXamlLoader.Load(this);
}
private MenuPageViewModel _model;
private MenuPageViewModel? _model;
protected override void OnDataContextChanged(EventArgs e)
{
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"/>
<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"/>

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

@ -28,16 +28,16 @@ namespace ControlCatalog.Pages
public class NumbersPageViewModel : ViewModelBase
{
private IList<FormatObject> _formats;
private IList<FormatObject>? _formats;
private FormatObject _selectedFormat;
private IList<Location> _spinnerLocations;
private IList<Location>? _spinnerLocations;
private double _doubleValue;
private decimal _decimalValue;
public NumbersPageViewModel()
{
SelectedFormat = Formats.FirstOrDefault();
_selectedFormat = Formats.FirstOrDefault();
}
public double DoubleValue
@ -103,7 +103,7 @@ namespace ControlCatalog.Pages
public class FormatObject
{
public string Value { get; set; }
public string Name { get; set; }
public string? Value { 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)
{
base.OnAttachedToVisualTree(e);
Window w = (Window)VisualRoot;
Window w = (Window)VisualRoot!;
w.PositionChanged += (sender, args) => InvalidateVisual();
}

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

@ -52,7 +52,7 @@ namespace ControlCatalog.Pages
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)));
}
@ -60,7 +60,7 @@ namespace ControlCatalog.Pages
{
private Dock _tabPlacement;
public TabItemViewModel[] Tabs { get; set; }
public TabItemViewModel[]? Tabs { get; set; }
public Dock TabPlacement
{
@ -71,10 +71,10 @@ namespace ControlCatalog.Pages
private class TabItemViewModel
{
public string Header { get; set; }
public string Text { get; set; }
public IBitmap Image { get; set; }
public bool IsEnabled { get; set; } = true;
public string? Header { get; set; }
public string? Text { get; set; }
public IBitmap? Image { get; set; }
public bool IsEnabled { get; set; } = true;
}
}
}

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

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

2
samples/ControlCatalog/ViewModels/ApplicationViewModel.cs

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

2
samples/ControlCatalog/ViewModels/ContextPageViewModel.cs

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

2
samples/ControlCatalog/ViewModels/CursorPageViewModel.cs

@ -18,7 +18,7 @@ namespace ControlCatalog.ViewModels
.Select(x => new StandardCursorModel(x))
.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 bitmap = new Bitmap(s);
CustomCursor = new Cursor(bitmap, new PixelPoint(16, 16));

19
samples/ControlCatalog/ViewModels/ItemsRepeaterPageViewModel.cs

@ -14,7 +14,7 @@ namespace ControlCatalog.ViewModels
public ItemsRepeaterPageViewModel()
{
Items = CreateItems();
_items = CreateItems();
}
public ObservableCollection<Item> Items
@ -23,12 +23,12 @@ namespace ControlCatalog.ViewModels
set => this.RaiseAndSetIfChanged(ref _items, value);
}
public Item SelectedItem { get; set; }
public Item? SelectedItem { get; set; }
public void AddItem()
{
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()
@ -66,19 +66,20 @@ namespace ControlCatalog.ViewModels
_newGenerationIndex++;
return new ObservableCollection<Item>(
Enumerable.Range(1, 100000).Select(i => new Item(i)
{
Text = $"Item {i.ToString()} {suffix}"
}));
Enumerable.Range(1, 100000).Select(i => new Item(i, $"Item {i.ToString()} {suffix}")));
}
public class Item : ViewModelBase
{
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 string Text { get; set; }
public string Text { get; }
public double Height
{

13
samples/ControlCatalog/ViewModels/MainWindowViewModel.cs

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

8
samples/ControlCatalog/ViewModels/MenuItemViewModel.cs

@ -5,9 +5,9 @@ namespace ControlCatalog.ViewModels
{
public class MenuItemViewModel
{
public string Header { get; set; }
public ICommand Command { get; set; }
public object CommandParameter { get; set; }
public IList<MenuItemViewModel> Items { get; set; }
public string? Header { get; set; }
public ICommand? Command { get; set; }
public object? CommandParameter { 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 Control View { get; set; }
public Control? View { get; set; }
public MenuPageViewModel()
{
OpenCommand = MiniCommand.CreateFromTask(Open);

4
samples/ControlCatalog/ViewModels/NotificationViewModel.cs

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

16
samples/ControlCatalog/ViewModels/TransitioningContentControlPageViewModel.cs

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

4
samples/ControlCatalog/ViewModels/TreeViewPageViewModel.cs

@ -92,7 +92,7 @@ namespace ControlCatalog.ViewModels
public class Node
{
private ObservableCollection<Node> _children;
private ObservableCollection<Node>? _children;
private int _childIndex = 10;
public Node()
@ -106,7 +106,7 @@ namespace ControlCatalog.ViewModels
Header = parent.Header + ' ' + index;
}
public Node Parent { get; }
public Node? Parent { get; }
public string Header { get; }
public bool AreChildrenInitialized => _children != null;
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:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:RenderDemo.Pages"
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
x:Class="RenderDemo.Pages.GlyphRunPage">
<Border
<Grid
ColumnDefinitions="*,*"
Background="White">
<Image
x:Name="imageControl"
Stretch="None">
</Image>
</Border>
<local:GlyphRunControl Grid.Column="0"/>
<local:GlyphRunGeometryControl Grid.Column="1"/>
</Grid>
</UserControl>

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

@ -9,14 +9,6 @@ namespace RenderDemo.Pages
{
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()
{
this.InitializeComponent();
@ -25,19 +17,43 @@ namespace RenderDemo.Pages
private void InitializeComponent()
{
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");
_imageControl.Source = new DrawingImage();
private DispatcherTimer _timer;
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;
}, TimeSpan.FromSeconds(1));
_timer = null;
}
private void UpdateGlyphRun()
public override void Render(DrawingContext context)
{
var c = (char)_rand.Next(65, 90);
@ -57,27 +73,70 @@ namespace RenderDemo.Pages
_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,
GlyphRun = new GlyphRun(_glyphTypeface, _fontSize, _characters, _glyphIndices)
Interval = TimeSpan.FromSeconds(1)
};
drawingGroup.Children.Add(glyphRunDrawing);
var geometryDrawing = new GeometryDrawing
_timer.Tick += (s, e) =>
{
Pen = new Pen(Brushes.Black),
Geometry = new RectangleGeometry { Rect = new Rect(glyphRunDrawing.GlyphRun.Size) }
InvalidateVisual();
};
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<PlatformHotkeyConfiguration>().ToSingleton<PlatformHotkeyConfiguration>();
SkiaPlatform.Initialize();
if (options.UseGpu)
{
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);
View = new AvaloniaView(this);
SetContentView(View);
var lifetime = new SingleViewLifetime();
lifetime.View = View;
builder.AfterSetup(x =>
{
_viewModel = new ViewModelProvider(this).Get(Java.Lang.Class.FromType(typeof(AvaloniaViewModel))) as AvaloniaViewModel;
View = new AvaloniaView(this);
if (_viewModel.Content != null)
{
View.Content = _viewModel.Content;
}
SetContentView(View);
lifetime.View = View;
View.Prepare();
});

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

@ -15,13 +15,12 @@ namespace Avalonia.Android
private EmbeddableControlRoot _root;
private readonly ViewImpl _view;
private IDisposable? _timerSubscription;
private IDisposable _timerSubscription;
public AvaloniaView(Context context) : base(context)
{
_view = new ViewImpl(context);
_view = new ViewImpl(this);
AddView(_view.View);
}
internal void Prepare ()
@ -30,6 +29,8 @@ namespace Avalonia.Android
_root.Prepare();
}
internal TopLevelImpl TopLevelImpl => _view;
public object Content
{
get { return _root.Content; }
@ -73,7 +74,7 @@ namespace Avalonia.Android
class ViewImpl : TopLevelImpl
{
public ViewImpl(Context context) : base(context)
public ViewImpl(AvaloniaView avaloniaView) : base(avaloniaView)
{
View.Focusable = true;
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
{
class TopLevelImpl : IAndroidView, ITopLevelImpl, EglGlPlatformSurfaceBase.IEglWindowGlPlatformSurfaceInfo, ITopLevelImplWithTextInputMethod
class TopLevelImpl : IAndroidView, ITopLevelImpl, EglGlPlatformSurfaceBase.IEglWindowGlPlatformSurfaceInfo, ITopLevelImplWithTextInputMethod, ITopLevelImplWithNativeControlHost
{
private readonly IGlPlatformSurface _gl;
private readonly IFramebufferPlatformSurface _framebuffer;
@ -30,9 +30,9 @@ namespace Avalonia.Android.Platform.SkiaPlatform
private readonly ITextInputMethodImpl _textInputMethod;
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);
_keyboardHelper = new AndroidKeyboardEventsHelper<TopLevelImpl>(this);
_touchHelper = new AndroidTouchEventsHelper<TopLevelImpl>(this, () => InputRoot,
@ -44,6 +44,8 @@ namespace Avalonia.Android.Platform.SkiaPlatform
MaxClientSize = new PixelSize(_view.Resources.DisplayMetrics.WidthPixels,
_view.Resources.DisplayMetrics.HeightPixels).ToSize(RenderScaling);
NativeControlHost = new AndroidNativeControlHostImpl(avaloniaView);
}
public virtual Point GetAvaloniaPointFromEvent(MotionEvent e, int pointerIndex) =>
@ -222,6 +224,8 @@ namespace Avalonia.Android.Platform.SkiaPlatform
public ITextInputMethodImpl TextInputMethod => _textInputMethod;
public INativeControlHostImpl NativeControlHost { get; }
public void SetTransparencyLevelHint(WindowTransparencyLevel transparencyLevel)
{
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);
}
string? UnicodeTextInput(KeyEvent keyEvent)
string UnicodeTextInput(KeyEvent keyEvent)
{
return keyEvent.Action == KeyEventActions.Multiple
&& keyEvent.RepeatCount == 0

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

Loading…
Cancel
Save