Browse Source

Merge branch 'master' into api/2965-window-onclosed

pull/3084/head
Jumar Macato 7 years ago
committed by GitHub
parent
commit
3eea1ecc13
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 1
      Avalonia.sln.DotSettings
  2. 2
      global.json
  3. 31
      native/Avalonia.Native/inc/avalonia-native.h
  4. 9
      native/Avalonia.Native/src/OSX/Avalonia.Native.OSX.xcodeproj/project.pbxproj
  5. 8
      native/Avalonia.Native/src/OSX/Avalonia.Native.OSX.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist
  6. 12
      native/Avalonia.Native/src/OSX/common.h
  7. 151
      native/Avalonia.Native/src/OSX/main.mm
  8. 80
      native/Avalonia.Native/src/OSX/menu.h
  9. 305
      native/Avalonia.Native/src/OSX/menu.mm
  10. 6
      native/Avalonia.Native/src/OSX/platformthreading.mm
  11. 1
      native/Avalonia.Native/src/OSX/window.h
  12. 98
      native/Avalonia.Native/src/OSX/window.mm
  13. 2
      samples/ControlCatalog.Android/ControlCatalog.Android.csproj
  14. 2
      samples/ControlCatalog.Android/MainActivity.cs
  15. 6
      samples/ControlCatalog.NetCore/Program.cs
  16. 18
      samples/ControlCatalog/App.xaml
  17. 13
      samples/ControlCatalog/App.xaml.cs
  18. 25
      samples/ControlCatalog/DecoratedWindow.xaml
  19. 31
      samples/ControlCatalog/MainWindow.xaml
  20. 14
      samples/ControlCatalog/MainWindow.xaml.cs
  21. 5
      samples/ControlCatalog/Pages/MenuPage.xaml
  22. 6
      src/Android/Avalonia.Android/AndroidPlatform.cs
  23. 2
      src/Android/Avalonia.Android/Avalonia.Android.csproj
  24. 2
      src/Android/Avalonia.AndroidTestApplication/Avalonia.AndroidTestApplication.csproj
  25. 9
      src/Avalonia.Base/AvaloniaObjectExtensions.cs
  26. 50
      src/Avalonia.Controls.DataGrid/DataGrid.cs
  27. 4
      src/Avalonia.Controls.DataGrid/DataGridCell.cs
  28. 2
      src/Avalonia.Controls.DataGrid/DataGridColumnHeader.cs
  29. 8
      src/Avalonia.Controls.DataGrid/DataGridRow.cs
  30. 4
      src/Avalonia.Controls.DataGrid/DataGridRowGroupHeader.cs
  31. 50
      src/Avalonia.Controls/AppBuilderBase.cs
  32. 19
      src/Avalonia.Controls/Application.cs
  33. 3
      src/Avalonia.Controls/ApplicationLifetimes/ClassicDesktopStyleApplicationLifetime.cs
  34. 18
      src/Avalonia.Controls/AutoCompleteBox.cs
  35. 23
      src/Avalonia.Controls/Calendar/Calendar.cs
  36. 16
      src/Avalonia.Controls/Calendar/DatePicker.cs
  37. 4
      src/Avalonia.Controls/ComboBox.cs
  38. 2
      src/Avalonia.Controls/ContentControl.cs
  39. 2
      src/Avalonia.Controls/DataValidationErrors.cs
  40. 2
      src/Avalonia.Controls/Decorator.cs
  41. 2
      src/Avalonia.Controls/Expander.cs
  42. 4
      src/Avalonia.Controls/ItemsControl.cs
  43. 8
      src/Avalonia.Controls/LayoutTransformControl.cs
  44. 2
      src/Avalonia.Controls/MenuBase.cs
  45. 12
      src/Avalonia.Controls/MenuItem.cs
  46. 85
      src/Avalonia.Controls/NativeMenu.Export.cs
  47. 60
      src/Avalonia.Controls/NativeMenu.cs
  48. 36
      src/Avalonia.Controls/NativeMenuBar.cs
  49. 162
      src/Avalonia.Controls/NativeMenuItem.cs
  50. 23
      src/Avalonia.Controls/NativeMenuItemBase.cs
  51. 10
      src/Avalonia.Controls/NativeMenuItemSeperator.cs
  52. 18
      src/Avalonia.Controls/Platform/ITopLevelNativeMenuExporter.cs
  53. 4
      src/Avalonia.Controls/Presenters/CarouselPresenter.cs
  54. 6
      src/Avalonia.Controls/Presenters/ContentPresenter.cs
  55. 2
      src/Avalonia.Controls/Presenters/ItemsPresenter.cs
  56. 2
      src/Avalonia.Controls/Presenters/ItemsPresenterBase.cs
  57. 6
      src/Avalonia.Controls/Presenters/ScrollContentPresenter.cs
  58. 2
      src/Avalonia.Controls/Primitives/HeaderedContentControl.cs
  59. 2
      src/Avalonia.Controls/Primitives/HeaderedItemsControl.cs
  60. 2
      src/Avalonia.Controls/Primitives/HeaderedSelectingItemsControl.cs
  61. 4
      src/Avalonia.Controls/Primitives/Popup.cs
  62. 4
      src/Avalonia.Controls/Primitives/ScrollBar.cs
  63. 6
      src/Avalonia.Controls/Primitives/SelectingItemsControl.cs
  64. 2
      src/Avalonia.Controls/Primitives/TemplatedControl.cs
  65. 6
      src/Avalonia.Controls/Primitives/Thumb.cs
  66. 6
      src/Avalonia.Controls/Primitives/Track.cs
  67. 5
      src/Avalonia.Controls/Primitives/UniformGrid.cs
  68. 5
      src/Avalonia.Controls/ProgressBar.cs
  69. 4
      src/Avalonia.Controls/ScrollViewer.cs
  70. 6
      src/Avalonia.Controls/Slider.cs
  71. 4
      src/Avalonia.Controls/TabItem.cs
  72. 2
      src/Avalonia.Controls/TreeViewItem.cs
  73. 2
      src/Avalonia.Controls/WindowBase.cs
  74. 11
      src/Avalonia.DesktopRuntime/AppBuilder.cs
  75. 4
      src/Avalonia.FreeDesktop/Avalonia.FreeDesktop.csproj
  76. 82
      src/Avalonia.FreeDesktop/DBusHelper.cs
  77. 56
      src/Avalonia.FreeDesktop/DBusMenu.cs
  78. 387
      src/Avalonia.FreeDesktop/DBusMenuExporter.cs
  79. 24
      src/Avalonia.Input/InputElement.cs
  80. 7
      src/Avalonia.Input/KeyGesture.cs
  81. 30
      src/Avalonia.Interactivity/RoutedEvent.cs
  82. 305
      src/Avalonia.Native/AvaloniaNativeMenuExporter.cs
  83. 32
      src/Avalonia.Native/AvaloniaNativePlatform.cs
  84. 13
      src/Avalonia.Native/AvaloniaNativePlatformExtensions.cs
  85. 8
      src/Avalonia.Native/WindowImpl.cs
  86. 2
      src/Avalonia.Styling/StyledElement.cs
  87. 1
      src/Avalonia.Themes.Default/DefaultTheme.xaml
  88. 20
      src/Avalonia.Themes.Default/InverseBooleanValueConverter.cs
  89. 25
      src/Avalonia.Themes.Default/NativeMenuBar.xaml
  90. 71
      src/Avalonia.Visuals/CornerRadius.cs
  91. 16
      src/Avalonia.Visuals/Matrix.cs
  92. 2
      src/Avalonia.Visuals/Media/Geometry.cs
  93. 28
      src/Avalonia.Visuals/Media/PixelPoint.cs
  94. 26
      src/Avalonia.Visuals/Media/PixelRect.cs
  95. 26
      src/Avalonia.Visuals/Media/PixelSize.cs
  96. 31
      src/Avalonia.Visuals/Point.cs
  97. 42
      src/Avalonia.Visuals/Rect.cs
  98. 12
      src/Avalonia.Visuals/RelativePoint.cs
  99. 14
      src/Avalonia.Visuals/RelativeRect.cs
  100. 30
      src/Avalonia.Visuals/Size.cs

1
Avalonia.sln.DotSettings

@ -3,6 +3,7 @@
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=3E53A01A_002DB331_002D47F3_002DB828_002D4A5717E77A24_002Fd_003Aglass/@EntryIndexedValue">ExplicitlyExcluded</s:String>
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=6417B24E_002D49C2_002D4985_002D8DB2_002D3AB9D898EC91/@EntryIndexedValue">ExplicitlyExcluded</s:String>
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=E3A1060B_002D50D0_002D44E8_002D88B6_002DF44EF2E5BD72_002Ff_003Ahtml_002Ehtm/@EntryIndexedValue">ExplicitlyExcluded</s:String>
<s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=MemberCanBePrivate_002EGlobal/@EntryIndexedValue">DO_NOT_SHOW</s:String>
<s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=RedundantUsingDirective/@EntryIndexedValue">HINT</s:String>
<s:String x:Key="/Default/CodeStyle/Naming/CppNaming/UserRules/=DECLSPEC_005FPROPERTY/@EntryIndexedValue">&lt;Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /&gt;</s:String>
<s:String x:Key="/Default/CodeStyle/Naming/CppNaming/UserRules/=ENUM/@EntryIndexedValue">&lt;Policy Inspect="True" Prefix="" Suffix="" Style="aa_bb" /&gt;</s:String>

2
global.json

@ -1,7 +1,7 @@
{
"msbuild-sdks": {
"Microsoft.Build.Traversal": "1.0.43",
"MSBuild.Sdk.Extras": "1.6.65",
"MSBuild.Sdk.Extras": "2.0.46",
"AggregatePackage.NuGet.Sdk" : "0.1.12"
}
}

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

@ -22,6 +22,8 @@ struct IAvnGlContext;
struct IAvnGlDisplay;
struct IAvnGlSurfaceRenderTarget;
struct IAvnGlSurfaceRenderingSession;
struct IAvnAppMenu;
struct IAvnAppMenuItem;
struct AvnSize
{
@ -173,6 +175,11 @@ public:
virtual HRESULT CreateClipboard(IAvnClipboard** ppv) = 0;
virtual HRESULT CreateCursorFactory(IAvnCursorFactory** ppv) = 0;
virtual HRESULT ObtainGlFeature(IAvnGlFeature** ppv) = 0;
virtual HRESULT ObtainAppMenu(IAvnAppMenu** retOut) = 0;
virtual HRESULT SetAppMenu(IAvnAppMenu* menu) = 0;
virtual HRESULT CreateMenu (IAvnAppMenu** ppv) = 0;
virtual HRESULT CreateMenuItem (IAvnAppMenuItem** ppv) = 0;
virtual HRESULT CreateMenuItemSeperator (IAvnAppMenuItem** ppv) = 0;
};
AVNCOM(IAvnString, 17) : IUnknown
@ -203,6 +210,8 @@ AVNCOM(IAvnWindowBase, 02) : IUnknown
virtual HRESULT SetCursor(IAvnCursor* cursor) = 0;
virtual HRESULT CreateGlRenderTarget(IAvnGlSurfaceRenderTarget** ret) = 0;
virtual HRESULT GetSoftwareFramebuffer(AvnFramebuffer*ret) = 0;
virtual HRESULT SetMainMenu(IAvnAppMenu* menu) = 0;
virtual HRESULT ObtainMainMenu(IAvnAppMenu** retOut) = 0;
virtual bool TryLock() = 0;
virtual void Unlock() = 0;
};
@ -258,6 +267,7 @@ AVNCOM(IAvnWindowEvents, 06) : IAvnWindowBaseEvents
AVNCOM(IAvnMacOptions, 07) : IUnknown
{
virtual HRESULT SetShowInDock(int show) = 0;
virtual HRESULT SetApplicationTitle (void* utf8string) = 0;
};
AVNCOM(IAvnActionCallback, 08) : IUnknown
@ -367,4 +377,25 @@ AVNCOM(IAvnGlSurfaceRenderingSession, 16) : IUnknown
virtual HRESULT GetScaling(double* ret) = 0;
};
AVNCOM(IAvnAppMenu, 17) : IUnknown
{
virtual HRESULT AddItem (IAvnAppMenuItem* item) = 0;
virtual HRESULT RemoveItem (IAvnAppMenuItem* item) = 0;
virtual HRESULT SetTitle (void* utf8String) = 0;
virtual HRESULT Clear () = 0;
};
AVNCOM(IAvnPredicateCallback, 18) : IUnknown
{
virtual bool Evaluate() = 0;
};
AVNCOM(IAvnAppMenuItem, 19) : IUnknown
{
virtual HRESULT SetSubMenu (IAvnAppMenu* menu) = 0;
virtual HRESULT SetTitle (void* utf8String) = 0;
virtual HRESULT SetGesture (void* utf8String, AvnInputModifiers modifiers) = 0;
virtual HRESULT SetAction (IAvnPredicateCallback* predicate, IAvnActionCallback* callback) = 0;
};
extern "C" IAvaloniaNativeFactory* CreateAvaloniaNative();

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

@ -8,10 +8,12 @@
/* Begin PBXBuildFile section */
1A002B9E232135EE00021753 /* app.mm in Sources */ = {isa = PBXBuildFile; fileRef = 1A002B9D232135EE00021753 /* app.mm */; };
37155CE4233C00EB0034DCE9 /* menu.h in Headers */ = {isa = PBXBuildFile; fileRef = 37155CE3233C00EB0034DCE9 /* menu.h */; };
37A517B32159597E00FBA241 /* Screens.mm in Sources */ = {isa = PBXBuildFile; fileRef = 37A517B22159597E00FBA241 /* Screens.mm */; };
37C09D8821580FE4006A6758 /* SystemDialogs.mm in Sources */ = {isa = PBXBuildFile; fileRef = 37C09D8721580FE4006A6758 /* SystemDialogs.mm */; };
37DDA9B0219330F8002E132B /* AvnString.mm in Sources */ = {isa = PBXBuildFile; fileRef = 37DDA9AF219330F8002E132B /* AvnString.mm */; };
37E2330F21583241000CB7E2 /* KeyTransform.mm in Sources */ = {isa = PBXBuildFile; fileRef = 37E2330E21583241000CB7E2 /* KeyTransform.mm */; };
520624B322973F4100C4DCEF /* menu.mm in Sources */ = {isa = PBXBuildFile; fileRef = 520624B222973F4100C4DCEF /* menu.mm */; };
5B21A982216530F500CEE36E /* cursor.mm in Sources */ = {isa = PBXBuildFile; fileRef = 5B21A981216530F500CEE36E /* cursor.mm */; };
5B8BD94F215BFEA6005ED2A7 /* clipboard.mm in Sources */ = {isa = PBXBuildFile; fileRef = 5B8BD94E215BFEA6005ED2A7 /* clipboard.mm */; };
AB00E4F72147CA920032A60A /* main.mm in Sources */ = {isa = PBXBuildFile; fileRef = AB00E4F62147CA920032A60A /* main.mm */; };
@ -24,6 +26,7 @@
/* Begin PBXFileReference section */
1A002B9D232135EE00021753 /* app.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = app.mm; sourceTree = "<group>"; };
37155CE3233C00EB0034DCE9 /* menu.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = menu.h; sourceTree = "<group>"; };
379860FE214DA0C000CD0246 /* KeyTransform.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = KeyTransform.h; sourceTree = "<group>"; };
37A4E71A2178846A00EACBCD /* headers */ = {isa = PBXFileReference; lastKnownFileType = folder; name = headers; path = ../../inc; sourceTree = "<group>"; };
37A517B22159597E00FBA241 /* Screens.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = Screens.mm; sourceTree = "<group>"; };
@ -32,6 +35,7 @@
37DDA9AF219330F8002E132B /* AvnString.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = AvnString.mm; sourceTree = "<group>"; };
37DDA9B121933371002E132B /* AvnString.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = AvnString.h; sourceTree = "<group>"; };
37E2330E21583241000CB7E2 /* KeyTransform.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = KeyTransform.mm; sourceTree = "<group>"; };
520624B222973F4100C4DCEF /* menu.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = menu.mm; sourceTree = "<group>"; };
5B21A981216530F500CEE36E /* cursor.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = cursor.mm; sourceTree = "<group>"; };
5B8BD94E215BFEA6005ED2A7 /* clipboard.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = clipboard.mm; sourceTree = "<group>"; };
5BF943652167AD1D009CAE35 /* cursor.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = cursor.h; sourceTree = "<group>"; };
@ -85,6 +89,8 @@
AB661C1F2148286E00291242 /* window.mm */,
37C09D8A21581EF2006A6758 /* window.h */,
AB00E4F62147CA920032A60A /* main.mm */,
37155CE3233C00EB0034DCE9 /* menu.h */,
520624B222973F4100C4DCEF /* menu.mm */,
37A517B22159597E00FBA241 /* Screens.mm */,
37C09D8721580FE4006A6758 /* SystemDialogs.mm */,
AB7A61F02147C815003C5833 /* Products */,
@ -107,6 +113,7 @@
isa = PBXHeadersBuildPhase;
buildActionMask = 2147483647;
files = (
37155CE4233C00EB0034DCE9 /* menu.h in Headers */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@ -150,6 +157,7 @@
developmentRegion = English;
hasScannedForEncodings = 0;
knownRegions = (
English,
en,
);
mainGroup = AB7A61E62147C814003C5833;
@ -173,6 +181,7 @@
37DDA9B0219330F8002E132B /* AvnString.mm in Sources */,
AB8F7D6B21482D7F0057DBA5 /* platformthreading.mm in Sources */,
37E2330F21583241000CB7E2 /* KeyTransform.mm in Sources */,
520624B322973F4100C4DCEF /* menu.mm in Sources */,
37A517B32159597E00FBA241 /* Screens.mm in Sources */,
AB00E4F72147CA920032A60A /* main.mm in Sources */,
37C09D8821580FE4006A6758 /* SystemDialogs.mm in Sources */,

8
native/Avalonia.Native/src/OSX/Avalonia.Native.OSX.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist

@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>IDEDidComputeMac32BitWarning</key>
<true/>
</dict>
</plist>

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

@ -19,6 +19,13 @@ extern IAvnClipboard* CreateClipboard();
extern IAvnCursorFactory* CreateCursorFactory();
extern IAvnGlFeature* GetGlFeature();
extern IAvnGlSurfaceRenderTarget* CreateGlRenderTarget(NSWindow* window, NSView* view);
extern IAvnAppMenu* CreateAppMenu();
extern IAvnAppMenuItem* CreateAppMenuItem();
extern IAvnAppMenuItem* CreateAppMenuItemSeperator();
extern void SetAppMenu (NSString* appName, IAvnAppMenu* appMenu);
extern IAvnAppMenu* GetAppMenu ();
extern NSMenuItem* GetAppMenuItem ();
extern void InitializeAvnApp();
extern NSApplicationActivationPolicy AvnDesiredActivationPolicy;
extern NSPoint ToNSPoint (AvnPoint p);
@ -40,4 +47,9 @@ template<typename T> inline T* objc_cast(id from) {
return nil;
}
@interface ActionCallback : NSObject
- (ActionCallback*) initWithCallback: (IAvnActionCallback*) callback;
- (void) action;
@end
#endif

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

@ -5,10 +5,121 @@
#define COM_GUIDS_MATERIALIZE
#include "common.h"
static NSString* s_appTitle = @"Avalonia";
// Copyright (c) 2011 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
void SetProcessName(NSString* appTitle) {
s_appTitle = appTitle;
CFStringRef process_name = (__bridge CFStringRef)appTitle;
if (!process_name || CFStringGetLength(process_name) == 0) {
//NOTREACHED() << "SetProcessName given bad name.";
return;
}
if (![NSThread isMainThread]) {
//NOTREACHED() << "Should only set process name from main thread.";
return;
}
// Warning: here be dragons! This is SPI reverse-engineered from WebKit's
// plugin host, and could break at any time (although realistically it's only
// likely to break in a new major release).
// When 10.7 is available, check that this still works, and update this
// comment for 10.8.
// Private CFType used in these LaunchServices calls.
typedef CFTypeRef PrivateLSASN;
typedef PrivateLSASN (*LSGetCurrentApplicationASNType)();
typedef OSStatus (*LSSetApplicationInformationItemType)(int, PrivateLSASN,
CFStringRef,
CFStringRef,
CFDictionaryRef*);
static LSGetCurrentApplicationASNType ls_get_current_application_asn_func =
NULL;
static LSSetApplicationInformationItemType
ls_set_application_information_item_func = NULL;
static CFStringRef ls_display_name_key = NULL;
static bool did_symbol_lookup = false;
if (!did_symbol_lookup) {
did_symbol_lookup = true;
CFBundleRef launch_services_bundle =
CFBundleGetBundleWithIdentifier(CFSTR("com.apple.LaunchServices"));
if (!launch_services_bundle) {
//LOG(ERROR) << "Failed to look up LaunchServices bundle";
return;
}
ls_get_current_application_asn_func =
reinterpret_cast<LSGetCurrentApplicationASNType>(
CFBundleGetFunctionPointerForName(
launch_services_bundle, CFSTR("_LSGetCurrentApplicationASN")));
if (!ls_get_current_application_asn_func){}
//LOG(ERROR) << "Could not find _LSGetCurrentApplicationASN";
ls_set_application_information_item_func =
reinterpret_cast<LSSetApplicationInformationItemType>(
CFBundleGetFunctionPointerForName(
launch_services_bundle,
CFSTR("_LSSetApplicationInformationItem")));
if (!ls_set_application_information_item_func){}
//LOG(ERROR) << "Could not find _LSSetApplicationInformationItem";
CFStringRef* key_pointer = reinterpret_cast<CFStringRef*>(
CFBundleGetDataPointerForName(launch_services_bundle,
CFSTR("_kLSDisplayNameKey")));
ls_display_name_key = key_pointer ? *key_pointer : NULL;
if (!ls_display_name_key){}
//LOG(ERROR) << "Could not find _kLSDisplayNameKey";
// Internally, this call relies on the Mach ports that are started up by the
// Carbon Process Manager. In debug builds this usually happens due to how
// the logging layers are started up; but in release, it isn't started in as
// much of a defined order. So if the symbols had to be loaded, go ahead
// and force a call to make sure the manager has been initialized and hence
// the ports are opened.
ProcessSerialNumber psn;
GetCurrentProcess(&psn);
}
if (!ls_get_current_application_asn_func ||
!ls_set_application_information_item_func ||
!ls_display_name_key) {
return;
}
PrivateLSASN asn = ls_get_current_application_asn_func();
// Constant used by WebKit; what exactly it means is unknown.
const int magic_session_constant = -2;
OSErr err =
ls_set_application_information_item_func(magic_session_constant, asn,
ls_display_name_key,
process_name,
NULL /* optional out param */);
//LOG_IF(ERROR, err) << "Call to set process name failed, err " << err;
}
class MacOptions : public ComSingleObject<IAvnMacOptions, &IID_IAvnMacOptions>
{
public:
FORWARD_IUNKNOWN()
virtual HRESULT SetApplicationTitle(void* utf8String) override
{
auto appTitle = [NSString stringWithUTF8String:(const char*)utf8String];
[[NSProcessInfo processInfo] setProcessName:appTitle];
SetProcessName(appTitle);
return S_OK;
}
virtual HRESULT SetShowInDock(int show) override
{
AvnDesiredActivationPolicy = show
@ -17,8 +128,6 @@ public:
}
};
/// See "Using POSIX Threads in a Cocoa Application" section here:
/// https://developer.apple.com/library/content/documentation/Cocoa/Conceptual/Multithreading/CreatingThreads/CreatingThreads.html#//apple_ref/doc/uid/20000738-125024
@interface ThreadingInitializer : NSObject
@ -43,8 +152,6 @@ public:
close(_fds[0]);
close(_fds[1]);
}
@end
@ -123,6 +230,42 @@ public:
*ppv = rv;
return S_OK;
}
virtual HRESULT CreateMenu (IAvnAppMenu** ppv) override
{
*ppv = ::CreateAppMenu();
return S_OK;
}
virtual HRESULT CreateMenuItem (IAvnAppMenuItem** ppv) override
{
*ppv = ::CreateAppMenuItem();
return S_OK;
}
virtual HRESULT CreateMenuItemSeperator (IAvnAppMenuItem** ppv) override
{
*ppv = ::CreateAppMenuItemSeperator();
return S_OK;
}
virtual HRESULT SetAppMenu (IAvnAppMenu* appMenu) override
{
::SetAppMenu(s_appTitle, appMenu);
return S_OK;
}
virtual HRESULT ObtainAppMenu(IAvnAppMenu** retOut) override
{
if(retOut == nullptr)
{
return E_POINTER;
}
*retOut = ::GetAppMenu();
return S_OK;
}
};
extern "C" IAvaloniaNativeFactory* CreateAvaloniaNative()

80
native/Avalonia.Native/src/OSX/menu.h

@ -0,0 +1,80 @@
//
// menu.h
// Avalonia.Native.OSX
//
// Created by Dan Walmsley on 01/08/2019.
// Copyright © 2019 Avalonia. All rights reserved.
//
#ifndef menu_h
#define menu_h
#include "common.h"
class AvnAppMenuItem;
class AvnAppMenu;
@interface AvnMenu : NSMenu // for some reason it doesnt detect nsmenu here but compiler doesnt complain
- (void)setMenu:(NSMenu*) menu;
@end
@interface AvnMenuItem : NSMenuItem
- (id) initWithAvnAppMenuItem: (AvnAppMenuItem*)menuItem;
- (void)didSelectItem:(id)sender;
@end
class AvnAppMenuItem : public ComSingleObject<IAvnAppMenuItem, &IID_IAvnAppMenuItem>
{
private:
NSMenuItem* _native; // here we hold a pointer to an AvnMenuItem
IAvnActionCallback* _callback;
IAvnPredicateCallback* _predicate;
bool _isSeperator;
public:
FORWARD_IUNKNOWN()
AvnAppMenuItem(bool isSeperator);
NSMenuItem* GetNative();
virtual HRESULT SetSubMenu (IAvnAppMenu* menu) override;
virtual HRESULT SetTitle (void* utf8String) override;
virtual HRESULT SetGesture (void* key, AvnInputModifiers modifiers) override;
virtual HRESULT SetAction (IAvnPredicateCallback* predicate, IAvnActionCallback* callback) override;
bool EvaluateItemEnabled();
void RaiseOnClicked();
};
class AvnAppMenu : public ComSingleObject<IAvnAppMenu, &IID_IAvnAppMenu>
{
private:
AvnMenu* _native;
public:
FORWARD_IUNKNOWN()
AvnAppMenu();
AvnAppMenu(AvnMenu* native);
AvnMenu* GetNative();
virtual HRESULT AddItem (IAvnAppMenuItem* item) override;
virtual HRESULT RemoveItem (IAvnAppMenuItem* item) override;
virtual HRESULT SetTitle (void* utf8String) override;
virtual HRESULT Clear () override;
};
#endif

305
native/Avalonia.Native/src/OSX/menu.mm

@ -0,0 +1,305 @@
#include "common.h"
#include "menu.h"
@implementation AvnMenu
@end
@implementation AvnMenuItem
{
AvnAppMenuItem* _item;
}
- (id) initWithAvnAppMenuItem: (AvnAppMenuItem*)menuItem
{
if(self != nil)
{
_item = menuItem;
self = [super initWithTitle:@""
action:@selector(didSelectItem:)
keyEquivalent:@""];
[self setEnabled:YES];
[self setTarget:self];
}
return self;
}
- (BOOL)validateMenuItem:(NSMenuItem *)menuItem
{
if([self submenu] != nil)
{
return YES;
}
return _item->EvaluateItemEnabled();
}
- (void)didSelectItem:(nullable id)sender
{
_item->RaiseOnClicked();
}
@end
AvnAppMenuItem::AvnAppMenuItem(bool isSeperator)
{
_isSeperator = isSeperator;
if(isSeperator)
{
_native = [NSMenuItem separatorItem];
}
else
{
_native = [[AvnMenuItem alloc] initWithAvnAppMenuItem: this];
}
_callback = nullptr;
}
NSMenuItem* AvnAppMenuItem::GetNative()
{
return _native;
}
HRESULT AvnAppMenuItem::SetSubMenu (IAvnAppMenu* menu)
{
auto nsMenu = dynamic_cast<AvnAppMenu*>(menu)->GetNative();
[_native setSubmenu: nsMenu];
return S_OK;
}
HRESULT AvnAppMenuItem::SetTitle (void* utf8String)
{
if (utf8String != nullptr)
{
[_native setTitle:[NSString stringWithUTF8String:(const char*)utf8String]];
}
return S_OK;
}
HRESULT AvnAppMenuItem::SetGesture (void* key, AvnInputModifiers modifiers)
{
NSEventModifierFlags flags = 0;
if (modifiers & Control)
flags |= NSEventModifierFlagControl;
if (modifiers & Shift)
flags |= NSEventModifierFlagShift;
if (modifiers & Alt)
flags |= NSEventModifierFlagOption;
if (modifiers & Windows)
flags |= NSEventModifierFlagCommand;
[_native setKeyEquivalent:[NSString stringWithUTF8String:(const char*)key]];
[_native setKeyEquivalentModifierMask:flags];
return S_OK;
}
HRESULT AvnAppMenuItem::SetAction (IAvnPredicateCallback* predicate, IAvnActionCallback* callback)
{
_predicate = predicate;
_callback = callback;
return S_OK;
}
bool AvnAppMenuItem::EvaluateItemEnabled()
{
if(_predicate != nullptr)
{
auto result = _predicate->Evaluate ();
return result;
}
return false;
}
void AvnAppMenuItem::RaiseOnClicked()
{
if(_callback != nullptr)
{
_callback->Run();
}
}
AvnAppMenu::AvnAppMenu()
{
_native = [AvnMenu new];
}
AvnAppMenu::AvnAppMenu(AvnMenu* native)
{
_native = native;
}
AvnMenu* AvnAppMenu::GetNative()
{
return _native;
}
HRESULT AvnAppMenu::AddItem (IAvnAppMenuItem* item)
{
auto avnMenuItem = dynamic_cast<AvnAppMenuItem*>(item);
if(avnMenuItem != nullptr)
{
[_native addItem: avnMenuItem->GetNative()];
}
return S_OK;
}
HRESULT AvnAppMenu::RemoveItem (IAvnAppMenuItem* item)
{
auto avnMenuItem = dynamic_cast<AvnAppMenuItem*>(item);
if(avnMenuItem != nullptr)
{
[_native removeItem:avnMenuItem->GetNative()];
}
return S_OK;
}
HRESULT AvnAppMenu::SetTitle (void* utf8String)
{
if (utf8String != nullptr)
{
[_native setTitle:[NSString stringWithUTF8String:(const char*)utf8String]];
}
return S_OK;
}
HRESULT AvnAppMenu::Clear()
{
[_native removeAllItems];
return S_OK;
}
extern IAvnAppMenu* CreateAppMenu()
{
@autoreleasepool
{
id menuBar = [NSMenu new];
return new AvnAppMenu(menuBar);
}
}
extern IAvnAppMenuItem* CreateAppMenuItem()
{
@autoreleasepool
{
return new AvnAppMenuItem(false);
}
}
extern IAvnAppMenuItem* CreateAppMenuItemSeperator()
{
@autoreleasepool
{
return new AvnAppMenuItem(true);
}
}
static IAvnAppMenu* s_appMenu = nullptr;
static NSMenuItem* s_appMenuItem = nullptr;
extern void SetAppMenu (NSString* appName, IAvnAppMenu* menu)
{
s_appMenu = menu;
if(s_appMenu != nullptr)
{
auto nativeMenu = dynamic_cast<AvnAppMenu*>(s_appMenu);
auto currentMenu = [s_appMenuItem menu];
if (currentMenu != nullptr)
{
[currentMenu removeItem:s_appMenuItem];
}
s_appMenuItem = [nativeMenu->GetNative() itemAtIndex:0];
if (currentMenu == nullptr)
{
currentMenu = [s_appMenuItem menu];
}
[[s_appMenuItem menu] removeItem:s_appMenuItem];
[currentMenu insertItem:s_appMenuItem atIndex:0];
if([s_appMenuItem submenu] == nullptr)
{
[s_appMenuItem setSubmenu:[NSMenu new]];
}
auto appMenu = [s_appMenuItem submenu];
[appMenu addItem:[NSMenuItem separatorItem]];
// Services item and menu
auto servicesItem = [[NSMenuItem alloc] init];
servicesItem.title = @"Services";
NSMenu *servicesMenu = [[NSMenu alloc] initWithTitle:@"Services"];
servicesItem.submenu = servicesMenu;
[NSApplication sharedApplication].servicesMenu = servicesMenu;
[appMenu addItem:servicesItem];
[appMenu addItem:[NSMenuItem separatorItem]];
// Hide Application
auto hideItem = [[NSMenuItem alloc] initWithTitle:[@"Hide " stringByAppendingString:appName] action:@selector(hide:) keyEquivalent:@"h"];
[appMenu addItem:hideItem];
// Hide Others
auto hideAllOthersItem = [[NSMenuItem alloc] initWithTitle:@"Hide Others"
action:@selector(hideOtherApplications:)
keyEquivalent:@"h"];
hideAllOthersItem.keyEquivalentModifierMask = NSEventModifierFlagCommand | NSEventModifierFlagOption;
[appMenu addItem:hideAllOthersItem];
// Show All
auto showAllItem = [[NSMenuItem alloc] initWithTitle:@"Show All"
action:@selector(unhideAllApplications:)
keyEquivalent:@""];
[appMenu addItem:showAllItem];
[appMenu addItem:[NSMenuItem separatorItem]];
// Quit Application
auto quitItem = [[NSMenuItem alloc] init];
quitItem.title = [@"Quit " stringByAppendingString:appName];
quitItem.keyEquivalent = @"q";
quitItem.action = @selector(terminate:);
[appMenu addItem:quitItem];
}
else
{
s_appMenuItem = nullptr;
}
}
extern IAvnAppMenu* GetAppMenu ()
{
return s_appMenu;
}
extern NSMenuItem* GetAppMenuItem ()
{
return s_appMenuItem;
}

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

@ -10,12 +10,6 @@ class PlatformThreadingInterface;
-(Signaler*) init;
@end
@interface ActionCallback : NSObject
- (ActionCallback*) initWithCallback: (IAvnActionCallback*) callback;
- (void) action;
@end
@implementation ActionCallback
{
ComPtr<IAvnActionCallback> _callback;

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

@ -20,6 +20,7 @@ class WindowBaseImpl;
-(void) pollModalSession: (NSModalSession _Nonnull) session;
-(void) restoreParentWindow;
-(bool) shouldTryToHandleEvents;
-(void) applyMenu:(NSMenu *)menu;
@end
struct INSWindowHolder

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

@ -5,6 +5,7 @@
#include "window.h"
#include "KeyTransform.h"
#include "cursor.h"
#include "menu.h"
#include <OpenGL/gl.h>
class SoftwareDrawingOperation
@ -63,9 +64,11 @@ public:
SoftwareDrawingOperation CurrentSwDrawingOperation;
AvnPoint lastPositionSet;
NSString* _lastTitle;
IAvnAppMenu* _mainMenu;
WindowBaseImpl(IAvnWindowBaseEvents* events)
{
_mainMenu = nullptr;
BaseEvents = events;
View = [[AvnView alloc] initWithParent:this];
@ -93,6 +96,7 @@ public:
UpdateStyle();
[Window makeKeyAndOrderFront:Window];
[NSApp activateIgnoringOtherApps:YES];
[Window setTitle:_lastTitle];
[Window setTitleVisibility:NSWindowTitleVisible];
@ -122,6 +126,7 @@ public:
if(Window != nullptr)
{
[Window makeKeyWindow];
[NSApp activateIgnoringOtherApps:YES];
}
}
@ -209,6 +214,31 @@ public:
}
}
virtual HRESULT SetMainMenu(IAvnAppMenu* menu) override
{
_mainMenu = menu;
auto nativeMenu = dynamic_cast<AvnAppMenu*>(menu);
auto nsmenu = nativeMenu->GetNative();
[Window applyMenu:nsmenu];
return S_OK;
}
virtual HRESULT ObtainMainMenu(IAvnAppMenu** ret) override
{
if(ret == nullptr)
{
return E_POINTER;
}
*ret = _mainMenu;
return S_OK;
}
virtual bool TryLock() override
{
@autoreleasepool
@ -1042,6 +1072,8 @@ NSArray* AllLoopModes = [NSArray arrayWithObjects: NSDefaultRunLoopMode, NSEvent
ComPtr<WindowBaseImpl> _parent;
bool _canBecomeKeyAndMain;
bool _closed;
NSMenu* _menu;
bool _isAppMenuApplied;
}
- (void)dealloc
@ -1065,6 +1097,32 @@ NSArray* AllLoopModes = [NSArray arrayWithObjects: NSDefaultRunLoopMode, NSEvent
}
}
-(void) applyMenu:(NSMenu *)menu
{
if(menu == nullptr)
{
menu = [NSMenu new];
}
_menu = menu;
if ([self isKeyWindow])
{
auto appMenu = ::GetAppMenuItem();
if(appMenu != nullptr)
{
[[appMenu menu] removeItem:appMenu];
[_menu insertItem:appMenu atIndex:0];
_isAppMenuApplied = true;
}
[NSApp setMenu:menu];
}
}
-(void) setCanBecomeKeyAndMain
{
_canBecomeKeyAndMain = true;
@ -1157,6 +1215,24 @@ NSArray* AllLoopModes = [NSArray arrayWithObjects: NSDefaultRunLoopMode, NSEvent
{
if([self activateAppropriateChild: true])
{
if(_menu == nullptr)
{
_menu = [NSMenu new];
}
auto appMenu = ::GetAppMenuItem();
if(appMenu != nullptr)
{
[[appMenu menu] removeItem:appMenu];
[_menu insertItem:appMenu atIndex:0];
_isAppMenuApplied = true;
}
[NSApp setMenu:_menu];
_parent->BaseEvents->Activated();
[super becomeKeyWindow];
}
@ -1201,6 +1277,28 @@ NSArray* AllLoopModes = [NSArray arrayWithObjects: NSDefaultRunLoopMode, NSEvent
{
if(_parent)
_parent->BaseEvents->Deactivated();
auto appMenuItem = ::GetAppMenuItem();
if(appMenuItem != nullptr)
{
auto appMenu = ::GetAppMenu();
auto nativeAppMenu = dynamic_cast<AvnAppMenu*>(appMenu);
[[appMenuItem menu] removeItem:appMenuItem];
[nativeAppMenu->GetNative() addItem:appMenuItem];
[NSApp setMenu:nativeAppMenu->GetNative()];
}
else
{
[NSApp setMenu:nullptr];
}
// remove window menu items from appmenu?
[super resignKeyWindow];
}

2
samples/ControlCatalog.Android/ControlCatalog.Android.csproj

@ -16,7 +16,7 @@
<AndroidResgenFile>Resources\Resource.Designer.cs</AndroidResgenFile>
<GenerateSerializationAssemblies>Off</GenerateSerializationAssemblies>
<AndroidUseLatestPlatformSdk>False</AndroidUseLatestPlatformSdk>
<TargetFrameworkVersion>v8.0</TargetFrameworkVersion>
<TargetFrameworkVersion>v9.0</TargetFrameworkVersion>
<AndroidManifest>Properties\AndroidManifest.xml</AndroidManifest>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">

2
samples/ControlCatalog.Android/MainActivity.cs

@ -20,7 +20,7 @@ namespace ControlCatalog.Android
{
if (Avalonia.Application.Current == null)
{
AppBuilder.Configure(new App())
AppBuilder.Configure<App>()
.UseAndroid()
.SetupWithoutStarting();
Content = new MainView();

6
samples/ControlCatalog.NetCore/Program.cs

@ -55,7 +55,11 @@ namespace ControlCatalog.NetCore
public static AppBuilder BuildAvaloniaApp()
=> AppBuilder.Configure<App>()
.UsePlatformDetect()
.With(new X11PlatformOptions { EnableMultiTouch = true })
.With(new X11PlatformOptions
{
EnableMultiTouch = true,
UseDBusMenu = true
})
.With(new Win32PlatformOptions
{
EnableMultitouch = true,

18
samples/ControlCatalog/App.xaml

@ -2,9 +2,9 @@
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
x:Class="ControlCatalog.App">
<Application.Styles>
<StyleInclude Source="avares://Avalonia.Themes.Default/DefaultTheme.xaml"/>
<StyleInclude Source="avares://Avalonia.Themes.Default/Accents/BaseLight.xaml"/>
<StyleInclude Source="avares://Avalonia.Controls.DataGrid/Themes/Default.xaml"/>
<StyleInclude Source="avares://Avalonia.Themes.Default/DefaultTheme.xaml"/>
<StyleInclude Source="avares://Avalonia.Themes.Default/Accents/BaseLight.xaml"/>
<StyleInclude Source="avares://Avalonia.Controls.DataGrid/Themes/Default.xaml"/>
<Style Selector="TextBlock.h1">
<Setter Property="FontSize" Value="{DynamicResource FontSizeLarge}"/>
<Setter Property="FontWeight" Value="Medium"/>
@ -17,4 +17,16 @@
</Style>
<StyleInclude Source="/SideBar.xaml"/>
</Application.Styles>
<NativeMenu.Menu>
<NativeMenu>
<NativeMenuItem Header="Open" Clicked="OnOpenClicked"/>
<NativeMenuItem Header="Recent">
<NativeMenuItem.Menu>
<NativeMenu/>
</NativeMenuItem.Menu>
</NativeMenuItem>
<NativeMenuItem Header="Quit Avalonia" Gesture="CMD+Q"/>
</NativeMenu>
</NativeMenu.Menu>
</Application>

13
samples/ControlCatalog/App.xaml.cs

@ -1,4 +1,6 @@
using System;
using Avalonia;
using Avalonia.Controls;
using Avalonia.Controls.ApplicationLifetimes;
using Avalonia.Markup.Xaml;
@ -6,9 +8,20 @@ namespace ControlCatalog
{
public class App : Application
{
private NativeMenu _recentMenu;
public override void Initialize()
{
AvaloniaXamlLoader.Load(this);
Name = "Avalonia";
_recentMenu = (NativeMenu.GetMenu(this).Items[1] as NativeMenuItem).Menu;
}
public void OnOpenClicked(object sender, EventArgs args)
{
_recentMenu.Items.Insert(0, new NativeMenuItem("Item " + (_recentMenu.Items.Count + 1)));
}
public override void OnFrameworkInitializationCompleted()

25
samples/ControlCatalog/DecoratedWindow.xaml

@ -3,6 +3,31 @@
x:Class="ControlCatalog.DecoratedWindow"
Title="Avalonia Control Gallery"
xmlns:local="clr-namespace:ControlCatalog" HasSystemDecorations="False" Name="Window">
<NativeMenu.Menu>
<NativeMenu>
<NativeMenuItem Header="Decorated">
<NativeMenuItem.Menu>
<NativeMenu>
<NativeMenuItem Header="Open"/>
<NativeMenuItem Header="Recent">
<NativeMenuItem.Menu>
<NativeMenu/>
</NativeMenuItem.Menu>
</NativeMenuItem>
<NativeMenuItem Header="Quit Avalonia" Gesture="CMD+Q"/>
</NativeMenu>
</NativeMenuItem.Menu>
</NativeMenuItem>
<NativeMenuItem Header="Edit">
<NativeMenuItem.Menu>
<NativeMenu>
<NativeMenuItem Header="Copy"/>
<NativeMenuItem Header="Paste"/>
</NativeMenu>
</NativeMenuItem.Menu>
</NativeMenuItem>
</NativeMenu>
</NativeMenu.Menu>
<Grid RowDefinitions="5,*,5" ColumnDefinitions="5,*,5">
<DockPanel Grid.Column="1" Grid.Row="1" >
<Grid Name="TitleBar" Background="LightBlue" DockPanel.Dock="Top" ColumnDefinitions="Auto,*,Auto">

31
samples/ControlCatalog/MainWindow.xaml

@ -8,7 +8,36 @@
xmlns:vm="clr-namespace:ControlCatalog.ViewModels"
xmlns:v="clr-namespace:ControlCatalog.Views"
x:Class="ControlCatalog.MainWindow">
<Window.DataTemplates>
<NativeMenu.Menu>
<NativeMenu>
<NativeMenuItem Header="File">
<NativeMenuItem.Menu>
<NativeMenu>
<NativeMenuItem Header="Open" Clicked="OnOpenClicked"/>
<NativeMenuItemSeperator/>
<NativeMenuItem Header="Recent">
<NativeMenuItem.Menu>
<NativeMenu/>
</NativeMenuItem.Menu>
</NativeMenuItem>
<NativeMenuItemSeperator/>
<NativeMenuItem Header="Quit Avalonia" Clicked="OnCloseClicked" Gesture="CMD+Q"/>
</NativeMenu>
</NativeMenuItem.Menu>
</NativeMenuItem>
<NativeMenuItem Header="Edit">
<NativeMenuItem.Menu>
<NativeMenu>
<NativeMenuItem Header="Copy"/>
<NativeMenuItem Header="Paste"/>
</NativeMenu>
</NativeMenuItem.Menu>
</NativeMenuItem>
</NativeMenu>
</NativeMenu.Menu>
<Window.DataTemplates>
<DataTemplate DataType="vm:NotificationViewModel">
<v:CustomNotificationView />
</DataTemplate>

14
samples/ControlCatalog/MainWindow.xaml.cs

@ -14,6 +14,7 @@ namespace ControlCatalog
public class MainWindow : Window
{
private WindowNotificationManager _notificationArea;
private NativeMenu _recentMenu;
public MainWindow()
{
@ -29,8 +30,21 @@ namespace ControlCatalog
};
DataContext = new MainWindowViewModel(_notificationArea);
_recentMenu = ((NativeMenu.GetMenu(this).Items[0] as NativeMenuItem).Menu.Items[2] as NativeMenuItem).Menu;
}
public void OnOpenClicked(object sender, EventArgs args)
{
_recentMenu.Items.Insert(0, new NativeMenuItem("Item " + (_recentMenu.Items.Count + 1)));
}
public void OnCloseClicked(object sender, EventArgs args)
{
Close();
}
private void InitializeComponent()
{
// TODO: iOS does not support dynamically loading assemblies

5
samples/ControlCatalog/Pages/MenuPage.xaml

@ -3,8 +3,11 @@
x:Class="ControlCatalog.Pages.MenuPage">
<StackPanel Orientation="Vertical" Spacing="4">
<TextBlock Classes="h1">Menu</TextBlock>
<TextBlock Classes="h2">Exported menu fallback</TextBlock>
<TextBlock>(Should be only visible on platforms without desktop-global menu bar)</TextBlock>
<NativeMenuBar/>
<TextBlock Classes="h2">A window menu</TextBlock>
<StackPanel Orientation="Horizontal"
Margin="0,16,0,0"
HorizontalAlignment="Center"

6
src/Android/Avalonia.Android/AndroidPlatform.cs

@ -17,7 +17,7 @@ namespace Avalonia
{
public static T UseAndroid<T>(this T builder) where T : AppBuilderBase<T>, new()
{
builder.UseWindowingSubsystem(() => Android.AndroidPlatform.Initialize(builder.Instance), "Android");
builder.UseWindowingSubsystem(() => Android.AndroidPlatform.Initialize(builder.ApplicationType), "Android");
builder.UseSkia();
return builder;
}
@ -41,7 +41,7 @@ namespace Avalonia.Android
_scalingFactor = global::Android.App.Application.Context.Resources.DisplayMetrics.ScaledDensity;
}
public static void Initialize(Avalonia.Application app)
public static void Initialize(Type appType)
{
AvaloniaLocator.CurrentMutable
.Bind<IClipboard>().ToTransient<ClipboardImpl>()
@ -55,7 +55,7 @@ namespace Avalonia.Android
.Bind<IRenderTimer>().ToConstant(new DefaultRenderTimer(60))
.Bind<IRenderLoop>().ToConstant(new RenderLoop())
.Bind<PlatformHotkeyConfiguration>().ToSingleton<PlatformHotkeyConfiguration>()
.Bind<IAssetLoader>().ToConstant(new AssetLoader(app.GetType().Assembly));
.Bind<IAssetLoader>().ToConstant(new AssetLoader(appType.Assembly));
SkiaPlatform.Initialize();
((global::Android.App.Application) global::Android.App.Application.Context.ApplicationContext)

2
src/Android/Avalonia.Android/Avalonia.Android.csproj

@ -1,6 +1,6 @@
<Project Sdk="MSBuild.Sdk.Extras">
<PropertyGroup>
<TargetFramework>monoandroid80</TargetFramework>
<TargetFramework>monoandroid90</TargetFramework>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
</PropertyGroup>
<ItemGroup>

2
src/Android/Avalonia.AndroidTestApplication/Avalonia.AndroidTestApplication.csproj

@ -16,7 +16,7 @@
<AndroidResgenFile>Resources\Resource.Designer.cs</AndroidResgenFile>
<GenerateSerializationAssemblies>Off</GenerateSerializationAssemblies>
<AndroidUseLatestPlatformSdk>False</AndroidUseLatestPlatformSdk>
<TargetFrameworkVersion>v8.0</TargetFrameworkVersion>
<TargetFrameworkVersion>v9.0</TargetFrameworkVersion>
<AndroidManifest>Properties\AndroidManifest.xml</AndroidManifest>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">

9
src/Avalonia.Base/AvaloniaObjectExtensions.cs

@ -192,9 +192,9 @@ namespace Avalonia
{
return observable.Subscribe(e =>
{
if (e.Sender is TTarget)
if (e.Sender is TTarget target)
{
action((TTarget)e.Sender, e);
action(target, e);
}
});
}
@ -207,6 +207,7 @@ namespace Avalonia
/// <param name="observable">The property changed observable.</param>
/// <param name="handler">Given a TTarget, returns the handler.</param>
/// <returns>A disposable that can be used to terminate the subscription.</returns>
[Obsolete("Use overload taking Action<TTarget, AvaloniaPropertyChangedEventArgs>.")]
public static IDisposable AddClassHandler<TTarget>(
this IObservable<AvaloniaPropertyChangedEventArgs> observable,
Func<TTarget, Action<AvaloniaPropertyChangedEventArgs>> handler)
@ -238,9 +239,7 @@ namespace Avalonia
Func<TTarget, Action<AvaloniaPropertyChangedEventArgs>> handler)
where TTarget : class
{
var target = e.Sender as TTarget;
if (target != null)
if (e.Sender is TTarget target)
{
handler(target)(e);
}

50
src/Avalonia.Controls.DataGrid/DataGrid.cs

@ -723,29 +723,29 @@ namespace Avalonia.Controls
PseudoClass<DataGrid, bool>(IsValidProperty, x => !x, ":invalid");
ItemsProperty.Changed.AddClassHandler<DataGrid>(x => x.OnItemsPropertyChanged);
CanUserResizeColumnsProperty.Changed.AddClassHandler<DataGrid>(x => x.OnCanUserResizeColumnsChanged);
ColumnWidthProperty.Changed.AddClassHandler<DataGrid>(x => x.OnColumnWidthChanged);
RowBackgroundProperty.Changed.AddClassHandler<DataGrid>(x => x.OnRowBackgroundChanged);
AlternatingRowBackgroundProperty.Changed.AddClassHandler<DataGrid>(x => x.OnRowBackgroundChanged);
FrozenColumnCountProperty.Changed.AddClassHandler<DataGrid>(x => x.OnFrozenColumnCountChanged);
GridLinesVisibilityProperty.Changed.AddClassHandler<DataGrid>(x => x.OnGridLinesVisibilityChanged);
HeadersVisibilityProperty.Changed.AddClassHandler<DataGrid>(x => x.OnHeadersVisibilityChanged);
HorizontalGridLinesBrushProperty.Changed.AddClassHandler<DataGrid>(x => x.OnHorizontalGridLinesBrushChanged);
IsReadOnlyProperty.Changed.AddClassHandler<DataGrid>(x => x.OnIsReadOnlyChanged);
MaxColumnWidthProperty.Changed.AddClassHandler<DataGrid>(x => x.OnMaxColumnWidthChanged);
MinColumnWidthProperty.Changed.AddClassHandler<DataGrid>(x => x.OnMinColumnWidthChanged);
RowHeightProperty.Changed.AddClassHandler<DataGrid>(x => x.OnRowHeightChanged);
RowHeaderWidthProperty.Changed.AddClassHandler<DataGrid>(x => x.OnRowHeaderWidthChanged);
SelectionModeProperty.Changed.AddClassHandler<DataGrid>(x => x.OnSelectionModeChanged);
VerticalGridLinesBrushProperty.Changed.AddClassHandler<DataGrid>(x => x.OnVerticalGridLinesBrushChanged);
SelectedIndexProperty.Changed.AddClassHandler<DataGrid>(x => x.OnSelectedIndexChanged);
SelectedItemProperty.Changed.AddClassHandler<DataGrid>(x => x.OnSelectedItemChanged);
IsEnabledProperty.Changed.AddClassHandler<DataGrid>(x => x.DataGrid_IsEnabledChanged);
AreRowGroupHeadersFrozenProperty.Changed.AddClassHandler<DataGrid>(x => x.OnAreRowGroupHeadersFrozenChanged);
RowDetailsTemplateProperty.Changed.AddClassHandler<DataGrid>(x => x.OnRowDetailsTemplateChanged);
RowDetailsVisibilityModeProperty.Changed.AddClassHandler<DataGrid>(x => x.OnRowDetailsVisibilityModeChanged);
AutoGenerateColumnsProperty.Changed.AddClassHandler<DataGrid>(x => x.OnAutoGenerateColumnsChanged);
ItemsProperty.Changed.AddClassHandler<DataGrid>((x, e) => x.OnItemsPropertyChanged(e));
CanUserResizeColumnsProperty.Changed.AddClassHandler<DataGrid>((x, e) => x.OnCanUserResizeColumnsChanged(e));
ColumnWidthProperty.Changed.AddClassHandler<DataGrid>((x, e) => x.OnColumnWidthChanged(e));
RowBackgroundProperty.Changed.AddClassHandler<DataGrid>((x, e) => x.OnRowBackgroundChanged(e));
AlternatingRowBackgroundProperty.Changed.AddClassHandler<DataGrid>((x, e) => x.OnRowBackgroundChanged(e));
FrozenColumnCountProperty.Changed.AddClassHandler<DataGrid>((x, e) => x.OnFrozenColumnCountChanged(e));
GridLinesVisibilityProperty.Changed.AddClassHandler<DataGrid>((x, e) => x.OnGridLinesVisibilityChanged(e));
HeadersVisibilityProperty.Changed.AddClassHandler<DataGrid>((x, e) => x.OnHeadersVisibilityChanged(e));
HorizontalGridLinesBrushProperty.Changed.AddClassHandler<DataGrid>((x, e) => x.OnHorizontalGridLinesBrushChanged(e));
IsReadOnlyProperty.Changed.AddClassHandler<DataGrid>((x, e) => x.OnIsReadOnlyChanged(e));
MaxColumnWidthProperty.Changed.AddClassHandler<DataGrid>((x, e) => x.OnMaxColumnWidthChanged(e));
MinColumnWidthProperty.Changed.AddClassHandler<DataGrid>((x, e) => x.OnMinColumnWidthChanged(e));
RowHeightProperty.Changed.AddClassHandler<DataGrid>((x, e) => x.OnRowHeightChanged(e));
RowHeaderWidthProperty.Changed.AddClassHandler<DataGrid>((x, e) => x.OnRowHeaderWidthChanged(e));
SelectionModeProperty.Changed.AddClassHandler<DataGrid>((x, e) => x.OnSelectionModeChanged(e));
VerticalGridLinesBrushProperty.Changed.AddClassHandler<DataGrid>((x, e) => x.OnVerticalGridLinesBrushChanged(e));
SelectedIndexProperty.Changed.AddClassHandler<DataGrid>((x, e) => x.OnSelectedIndexChanged(e));
SelectedItemProperty.Changed.AddClassHandler<DataGrid>((x, e) => x.OnSelectedItemChanged(e));
IsEnabledProperty.Changed.AddClassHandler<DataGrid>((x, e) => x.DataGrid_IsEnabledChanged(e));
AreRowGroupHeadersFrozenProperty.Changed.AddClassHandler<DataGrid>((x, e) => x.OnAreRowGroupHeadersFrozenChanged(e));
RowDetailsTemplateProperty.Changed.AddClassHandler<DataGrid>((x, e) => x.OnRowDetailsTemplateChanged(e));
RowDetailsVisibilityModeProperty.Changed.AddClassHandler<DataGrid>((x, e) => x.OnRowDetailsVisibilityModeChanged(e));
AutoGenerateColumnsProperty.Changed.AddClassHandler<DataGrid>((x, e) => x.OnAutoGenerateColumnsChanged(e));
}
/// <summary>
@ -3533,7 +3533,7 @@ namespace Avalonia.Controls
if (AreColumnHeadersVisible &&
_vScrollBar != null && _vScrollBar.IsVisible)
{
_topRightCornerHeader.IsVisible = true; ;
_topRightCornerHeader.IsVisible = true;
}
else
{
@ -5594,7 +5594,7 @@ namespace Avalonia.Controls
{
// This will trigger a call to this method via Cells_SizeChanged for
// which no processing is needed.
_vScrollBar.IsVisible = true; ;
_vScrollBar.IsVisible = true;
if (_vScrollBar.DesiredSize.Width == 0)
{
// We need to know the width for the rest of layout to work correctly so measure it now

4
src/Avalonia.Controls.DataGrid/DataGridCell.cs

@ -29,7 +29,7 @@ namespace Avalonia.Controls
static DataGridCell()
{
PointerPressedEvent.AddClassHandler<DataGridCell>(
x => x.DataGridCell_PointerPressed, handledEventsToo: true);
(x,e) => x.DataGridCell_PointerPressed(e), handledEventsToo: true);
}
public DataGridCell()
{ }
@ -219,4 +219,4 @@ namespace Avalonia.Controls
}
}
}
}
}

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

@ -67,7 +67,7 @@ namespace Avalonia.Controls
static DataGridColumnHeader()
{
AreSeparatorsVisibleProperty.Changed.AddClassHandler<DataGridColumnHeader>(x => x.OnAreSeparatorsVisibleChanged);
AreSeparatorsVisibleProperty.Changed.AddClassHandler<DataGridColumnHeader>((x,e) => x.OnAreSeparatorsVisibleChanged(e));
}
/// <summary>

8
src/Avalonia.Controls.DataGrid/DataGridRow.cs

@ -116,10 +116,10 @@ namespace Avalonia.Controls
static DataGridRow()
{
HeaderProperty.Changed.AddClassHandler<DataGridRow>(x => x.OnHeaderChanged);
DetailsTemplateProperty.Changed.AddClassHandler<DataGridRow>(x => x.OnDetailsTemplateChanged);
AreDetailsVisibleProperty.Changed.AddClassHandler<DataGridRow>(x => x.OnAreDetailsVisibleChanged);
PointerPressedEvent.AddClassHandler<DataGridRow>(x => x.DataGridRow_PointerPressed, handledEventsToo: true);
HeaderProperty.Changed.AddClassHandler<DataGridRow>((x, e) => x.OnHeaderChanged(e));
DetailsTemplateProperty.Changed.AddClassHandler<DataGridRow>((x, e) => x.OnDetailsTemplateChanged(e));
AreDetailsVisibleProperty.Changed.AddClassHandler<DataGridRow>((x, e) => x.OnAreDetailsVisibleChanged(e));
PointerPressedEvent.AddClassHandler<DataGridRow>((x, e) => x.DataGridRow_PointerPressed(e), handledEventsToo: true);
}
/// <summary>

4
src/Avalonia.Controls.DataGrid/DataGridRowGroupHeader.cs

@ -109,7 +109,7 @@ namespace Avalonia.Controls
static DataGridRowGroupHeader()
{
SublevelIndentProperty.Changed.AddClassHandler<DataGridRowGroupHeader>(x => x.OnSublevelIndentChanged);
SublevelIndentProperty.Changed.AddClassHandler<DataGridRowGroupHeader>((x,e) => x.OnSublevelIndentChanged(e));
}
/// <summary>
@ -446,4 +446,4 @@ namespace Avalonia.Controls
}
}
}
}

50
src/Avalonia.Controls/AppBuilderBase.cs

@ -18,7 +18,9 @@ namespace Avalonia.Controls
{
private static bool s_setupWasAlreadyCalled;
private Action _optionsInitializers;
private Func<Application> _appFactory;
private IApplicationLifetime _lifetime;
/// <summary>
/// Gets or sets the <see cref="IRuntimePlatform"/> instance.
/// </summary>
@ -30,10 +32,15 @@ namespace Avalonia.Controls
public Action RuntimePlatformServicesInitializer { get; private set; }
/// <summary>
/// Gets or sets the <see cref="Application"/> instance being initialized.
/// Gets the <see cref="Application"/> instance being initialized.
/// </summary>
public Application Instance { get; protected set; }
public Application Instance { get; private set; }
/// <summary>
/// Gets the type of the Instance (even if it's not created yet)
/// </summary>
public Type ApplicationType { get; private set; }
/// <summary>
/// Gets or sets a method to call the initialize the windowing subsystem.
/// </summary>
@ -76,20 +83,11 @@ namespace Avalonia.Controls
public static TAppBuilder Configure<TApp>()
where TApp : Application, new()
{
return Configure(new TApp());
}
/// <summary>
/// Begin configuring an <see cref="Application"/>.
/// </summary>
/// <returns>An <typeparamref name="TAppBuilder"/> instance.</returns>
public static TAppBuilder Configure(Application app)
{
AvaloniaLocator.CurrentMutable.BindToSelf(app);
return new TAppBuilder()
{
Instance = app,
ApplicationType = typeof(TApp),
// Needed for CoreRT compatibility
_appFactory = () => new TApp()
};
}
@ -157,6 +155,18 @@ namespace Avalonia.Controls
return Self;
}
/// <summary>
/// Sets up the platform-specific services for the application and initialized it with a particular lifetime, but does not run it.
/// </summary>
/// <param name="lifetime"></param>
/// <returns></returns>
public TAppBuilder SetupWithLifetime(IApplicationLifetime lifetime)
{
_lifetime = lifetime;
Setup();
return Self;
}
/// <summary>
/// Specifies a windowing subsystem to use.
/// </summary>
@ -254,11 +264,6 @@ namespace Avalonia.Controls
/// </summary>
private void Setup()
{
if (Instance == null)
{
throw new InvalidOperationException("No App instance configured.");
}
if (RuntimePlatformServicesInitializer == null)
{
throw new InvalidOperationException("No runtime platform services configured.");
@ -285,6 +290,9 @@ namespace Avalonia.Controls
WindowingSubsystemInitializer();
RenderingSubsystemInitializer();
AfterPlatformServicesSetupCallback(Self);
Instance = _appFactory();
Instance.ApplicationLifetime = _lifetime;
AvaloniaLocator.CurrentMutable.BindToSelf(Instance);
Instance.RegisterServices();
Instance.Initialize();
AfterSetupCallback(Self);

19
src/Avalonia.Controls/Application.cs

@ -32,7 +32,7 @@ namespace Avalonia
/// method.
/// - Tracks the lifetime of the application.
/// </remarks>
public class Application : IGlobalDataTemplates, IGlobalStyles, IStyleRoot, IResourceNode
public class Application : AvaloniaObject, IGlobalDataTemplates, IGlobalStyles, IStyleRoot, IResourceNode
{
/// <summary>
/// The application-global data templates.
@ -210,5 +210,22 @@ namespace Avalonia
{
ResourcesChanged?.Invoke(this, e);
}
private string _name;
/// <summary>
/// Defines Name property
/// </summary>
public static readonly DirectProperty<Application, string> NameProperty =
AvaloniaProperty.RegisterDirect<Application, string>("Name", o => o.Name, (o, v) => o.Name = v);
/// <summary>
/// Application name to be used for various platform-specific purposes
/// </summary>
public string Name
{
get => _name;
set => SetAndRaise(NameProperty, ref _name, value);
}
}
}

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

@ -125,8 +125,7 @@ namespace Avalonia
where T : AppBuilderBase<T>, new()
{
var lifetime = new ClassicDesktopStyleApplicationLifetime(builder.Instance) {ShutdownMode = shutdownMode};
builder.Instance.ApplicationLifetime = lifetime;
builder.SetupWithoutStarting();
builder.SetupWithLifetime(lifetime);
return lifetime.Start(args);
}
}

18
src/Avalonia.Controls/AutoCompleteBox.cs

@ -805,15 +805,15 @@ namespace Avalonia.Controls
{
FocusableProperty.OverrideDefaultValue<AutoCompleteBox>(true);
MinimumPopulateDelayProperty.Changed.AddClassHandler<AutoCompleteBox>(x => x.OnMinimumPopulateDelayChanged);
IsDropDownOpenProperty.Changed.AddClassHandler<AutoCompleteBox>(x => x.OnIsDropDownOpenChanged);
SelectedItemProperty.Changed.AddClassHandler<AutoCompleteBox>(x => x.OnSelectedItemPropertyChanged);
TextProperty.Changed.AddClassHandler<AutoCompleteBox>(x => x.OnTextPropertyChanged);
SearchTextProperty.Changed.AddClassHandler<AutoCompleteBox>(x => x.OnSearchTextPropertyChanged);
FilterModeProperty.Changed.AddClassHandler<AutoCompleteBox>(x => x.OnFilterModePropertyChanged);
ItemFilterProperty.Changed.AddClassHandler<AutoCompleteBox>(x => x.OnItemFilterPropertyChanged);
ItemsProperty.Changed.AddClassHandler<AutoCompleteBox>(x => x.OnItemsPropertyChanged);
IsEnabledProperty.Changed.AddClassHandler<AutoCompleteBox>(x => x.OnControlIsEnabledChanged);
MinimumPopulateDelayProperty.Changed.AddClassHandler<AutoCompleteBox>((x,e) => x.OnMinimumPopulateDelayChanged(e));
IsDropDownOpenProperty.Changed.AddClassHandler<AutoCompleteBox>((x,e) => x.OnIsDropDownOpenChanged(e));
SelectedItemProperty.Changed.AddClassHandler<AutoCompleteBox>((x,e) => x.OnSelectedItemPropertyChanged(e));
TextProperty.Changed.AddClassHandler<AutoCompleteBox>((x,e) => x.OnTextPropertyChanged(e));
SearchTextProperty.Changed.AddClassHandler<AutoCompleteBox>((x,e) => x.OnSearchTextPropertyChanged(e));
FilterModeProperty.Changed.AddClassHandler<AutoCompleteBox>((x,e) => x.OnFilterModePropertyChanged(e));
ItemFilterProperty.Changed.AddClassHandler<AutoCompleteBox>((x,e) => x.OnItemFilterPropertyChanged(e));
ItemsProperty.Changed.AddClassHandler<AutoCompleteBox>((x,e) => x.OnItemsPropertyChanged(e));
IsEnabledProperty.Changed.AddClassHandler<AutoCompleteBox>((x,e) => x.OnControlIsEnabledChanged(e));
}
/// <summary>

23
src/Avalonia.Controls/Calendar/Calendar.cs

@ -2057,18 +2057,17 @@ namespace Avalonia.Controls
static Calendar()
{
IsEnabledProperty.Changed.AddClassHandler<Calendar>(x => x.OnIsEnabledChanged);
FirstDayOfWeekProperty.Changed.AddClassHandler<Calendar>(x => x.OnFirstDayOfWeekChanged);
IsTodayHighlightedProperty.Changed.AddClassHandler<Calendar>(x => x.OnIsTodayHighlightedChanged);
DisplayModeProperty.Changed.AddClassHandler<Calendar>(x => x.OnDisplayModePropertyChanged);
SelectionModeProperty.Changed.AddClassHandler<Calendar>(x => x.OnSelectionModeChanged);
SelectedDateProperty.Changed.AddClassHandler<Calendar>(x => x.OnSelectedDateChanged);
DisplayDateProperty.Changed.AddClassHandler<Calendar>(x => x.OnDisplayDateChanged);
DisplayDateStartProperty.Changed.AddClassHandler<Calendar>(x => x.OnDisplayDateStartChanged);
DisplayDateEndProperty.Changed.AddClassHandler<Calendar>(x => x.OnDisplayDateEndChanged);
KeyDownEvent.AddClassHandler<Calendar>(x => x.Calendar_KeyDown);
KeyUpEvent.AddClassHandler<Calendar>(x => x.Calendar_KeyUp);
IsEnabledProperty.Changed.AddClassHandler<Calendar>((x,e) => x.OnIsEnabledChanged(e));
FirstDayOfWeekProperty.Changed.AddClassHandler<Calendar>((x,e) => x.OnFirstDayOfWeekChanged(e));
IsTodayHighlightedProperty.Changed.AddClassHandler<Calendar>((x,e) => x.OnIsTodayHighlightedChanged(e));
DisplayModeProperty.Changed.AddClassHandler<Calendar>((x,e) => x.OnDisplayModePropertyChanged(e));
SelectionModeProperty.Changed.AddClassHandler<Calendar>((x,e) => x.OnSelectionModeChanged(e));
SelectedDateProperty.Changed.AddClassHandler<Calendar>((x,e) => x.OnSelectedDateChanged(e));
DisplayDateProperty.Changed.AddClassHandler<Calendar>((x,e) => x.OnDisplayDateChanged(e));
DisplayDateStartProperty.Changed.AddClassHandler<Calendar>((x,e) => x.OnDisplayDateStartChanged(e));
DisplayDateEndProperty.Changed.AddClassHandler<Calendar>((x,e) => x.OnDisplayDateEndChanged(e));
KeyDownEvent.AddClassHandler<Calendar>((x,e) => x.Calendar_KeyDown(e));
KeyUpEvent.AddClassHandler<Calendar>((x,e) => x.Calendar_KeyUp(e));
}
/// <summary>

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

@ -393,14 +393,14 @@ namespace Avalonia.Controls
{
FocusableProperty.OverrideDefaultValue<DatePicker>(true);
DisplayDateProperty.Changed.AddClassHandler<DatePicker>(x => x.OnDisplayDateChanged);
DisplayDateStartProperty.Changed.AddClassHandler<DatePicker>(x => x.OnDisplayDateStartChanged);
DisplayDateEndProperty.Changed.AddClassHandler<DatePicker>(x => x.OnDisplayDateEndChanged);
IsDropDownOpenProperty.Changed.AddClassHandler<DatePicker>(x => x.OnIsDropDownOpenChanged);
SelectedDateProperty.Changed.AddClassHandler<DatePicker>(x => x.OnSelectedDateChanged);
SelectedDateFormatProperty.Changed.AddClassHandler<DatePicker>(x => x.OnSelectedDateFormatChanged);
CustomDateFormatStringProperty.Changed.AddClassHandler<DatePicker>(x => x.OnCustomDateFormatStringChanged);
TextProperty.Changed.AddClassHandler<DatePicker>(x => x.OnTextChanged);
DisplayDateProperty.Changed.AddClassHandler<DatePicker>((x,e) => x.OnDisplayDateChanged(e));
DisplayDateStartProperty.Changed.AddClassHandler<DatePicker>((x,e) => x.OnDisplayDateStartChanged(e));
DisplayDateEndProperty.Changed.AddClassHandler<DatePicker>((x,e) => x.OnDisplayDateEndChanged(e));
IsDropDownOpenProperty.Changed.AddClassHandler<DatePicker>((x,e) => x.OnIsDropDownOpenChanged(e));
SelectedDateProperty.Changed.AddClassHandler<DatePicker>((x,e) => x.OnSelectedDateChanged(e));
SelectedDateFormatProperty.Changed.AddClassHandler<DatePicker>((x,e) => x.OnSelectedDateFormatChanged(e));
CustomDateFormatStringProperty.Changed.AddClassHandler<DatePicker>((x,e) => x.OnCustomDateFormatStringChanged(e));
TextProperty.Changed.AddClassHandler<DatePicker>((x,e) => x.OnTextChanged(e));
}
/// <summary>
/// Initializes a new instance of the

4
src/Avalonia.Controls/ComboBox.cs

@ -65,8 +65,8 @@ namespace Avalonia.Controls
{
ItemsPanelProperty.OverrideDefaultValue<ComboBox>(DefaultPanel);
FocusableProperty.OverrideDefaultValue<ComboBox>(true);
SelectedItemProperty.Changed.AddClassHandler<ComboBox>(x => x.SelectedItemChanged);
KeyDownEvent.AddClassHandler<ComboBox>(x => x.OnKeyDown, Interactivity.RoutingStrategies.Tunnel);
SelectedItemProperty.Changed.AddClassHandler<ComboBox>((x,e) => x.SelectedItemChanged(e));
KeyDownEvent.AddClassHandler<ComboBox>((x, e) => x.OnKeyDown(e), Interactivity.RoutingStrategies.Tunnel);
}
/// <summary>

2
src/Avalonia.Controls/ContentControl.cs

@ -43,7 +43,7 @@ namespace Avalonia.Controls
static ContentControl()
{
ContentProperty.Changed.AddClassHandler<ContentControl>(x => x.ContentChanged);
ContentProperty.Changed.AddClassHandler<ContentControl>((x, e) => x.ContentChanged(e));
}
/// <summary>

2
src/Avalonia.Controls/DataValidationErrors.cs

@ -56,7 +56,7 @@ namespace Avalonia.Controls
{
ErrorsProperty.Changed.Subscribe(ErrorsChanged);
HasErrorsProperty.Changed.Subscribe(HasErrorsChanged);
TemplatedParentProperty.Changed.AddClassHandler<DataValidationErrors>(x => x.OnTemplatedParentChange);
TemplatedParentProperty.Changed.AddClassHandler<DataValidationErrors>((x, e) => x.OnTemplatedParentChange(e));
}
private void OnTemplatedParentChange(AvaloniaPropertyChangedEventArgs e)

2
src/Avalonia.Controls/Decorator.cs

@ -29,7 +29,7 @@ namespace Avalonia.Controls
static Decorator()
{
AffectsMeasure<Decorator>(ChildProperty, PaddingProperty);
ChildProperty.Changed.AddClassHandler<Decorator>(x => x.ChildChanged);
ChildProperty.Changed.AddClassHandler<Decorator>((x, e) => x.ChildChanged(e));
}
/// <summary>

2
src/Avalonia.Controls/Expander.cs

@ -37,7 +37,7 @@ namespace Avalonia.Controls
PseudoClass<Expander>(IsExpandedProperty, ":expanded");
IsExpandedProperty.Changed.AddClassHandler<Expander>(x => x.OnIsExpandedChanged);
IsExpandedProperty.Changed.AddClassHandler<Expander>((x, e) => x.OnIsExpandedChanged(e));
}
public IPageTransition ContentTransition

4
src/Avalonia.Controls/ItemsControl.cs

@ -64,8 +64,8 @@ namespace Avalonia.Controls
/// </summary>
static ItemsControl()
{
ItemsProperty.Changed.AddClassHandler<ItemsControl>(x => x.ItemsChanged);
ItemTemplateProperty.Changed.AddClassHandler<ItemsControl>(x => x.ItemTemplateChanged);
ItemsProperty.Changed.AddClassHandler<ItemsControl>((x, e) => x.ItemsChanged(e));
ItemTemplateProperty.Changed.AddClassHandler<ItemsControl>((x, e) => x.ItemTemplateChanged(e));
}
/// <summary>

8
src/Avalonia.Controls/LayoutTransformControl.cs

@ -28,11 +28,13 @@ namespace Avalonia.Controls
ClipToBoundsProperty.OverrideDefaultValue<LayoutTransformControl>(true);
LayoutTransformProperty.Changed
.AddClassHandler<LayoutTransformControl>(x => x.OnLayoutTransformChanged);
.AddClassHandler<LayoutTransformControl>((x, e) => x.OnLayoutTransformChanged(e));
ChildProperty.Changed
.AddClassHandler<LayoutTransformControl>(x => x.OnChildChanged);
UseRenderTransformProperty.Changed.AddClassHandler<LayoutTransformControl>(x => x.OnUseRenderTransformPropertyChanged);
.AddClassHandler<LayoutTransformControl>((x, e) => x.OnChildChanged(e));
UseRenderTransformProperty.Changed
.AddClassHandler<LayoutTransformControl>((x, e) => x.OnUseRenderTransformPropertyChanged(e));
}
/// <summary>

2
src/Avalonia.Controls/MenuBase.cs

@ -64,7 +64,7 @@ namespace Avalonia.Controls
/// </summary>
static MenuBase()
{
MenuItem.SubmenuOpenedEvent.AddClassHandler<MenuBase>(x => x.OnSubmenuOpened);
MenuItem.SubmenuOpenedEvent.AddClassHandler<MenuBase>((x, e) => x.OnSubmenuOpened(e));
}
/// <summary>

12
src/Avalonia.Controls/MenuItem.cs

@ -102,13 +102,13 @@ namespace Avalonia.Controls
SelectableMixin.Attach<MenuItem>(IsSelectedProperty);
CommandProperty.Changed.Subscribe(CommandChanged);
FocusableProperty.OverrideDefaultValue<MenuItem>(true);
HeaderProperty.Changed.AddClassHandler<MenuItem>(x => x.HeaderChanged);
IconProperty.Changed.AddClassHandler<MenuItem>(x => x.IconChanged);
IsSelectedProperty.Changed.AddClassHandler<MenuItem>(x => x.IsSelectedChanged);
HeaderProperty.Changed.AddClassHandler<MenuItem>((x, e) => x.HeaderChanged(e));
IconProperty.Changed.AddClassHandler<MenuItem>((x, e) => x.IconChanged(e));
IsSelectedProperty.Changed.AddClassHandler<MenuItem>((x, e) => x.IsSelectedChanged(e));
ItemsPanelProperty.OverrideDefaultValue<MenuItem>(DefaultPanel);
ClickEvent.AddClassHandler<MenuItem>(x => x.OnClick);
SubmenuOpenedEvent.AddClassHandler<MenuItem>(x => x.OnSubmenuOpened);
IsSubMenuOpenProperty.Changed.AddClassHandler<MenuItem>(x => x.SubMenuOpenChanged);
ClickEvent.AddClassHandler<MenuItem>((x, e) => x.OnClick(e));
SubmenuOpenedEvent.AddClassHandler<MenuItem>((x, e) => x.OnSubmenuOpened(e));
IsSubMenuOpenProperty.Changed.AddClassHandler<MenuItem>((x, e) => x.SubMenuOpenChanged(e));
}
public MenuItem()

85
src/Avalonia.Controls/NativeMenu.Export.cs

@ -0,0 +1,85 @@
using System;
using System.Collections.Generic;
using Avalonia.Controls.Platform;
using Avalonia.Data;
namespace Avalonia.Controls
{
public partial class NativeMenu
{
public static readonly AttachedProperty<bool> IsNativeMenuExportedProperty =
AvaloniaProperty.RegisterAttached<NativeMenu, TopLevel, bool>("IsNativeMenuExported");
public static bool GetIsNativeMenuExported(TopLevel tl) => tl.GetValue(IsNativeMenuExportedProperty);
private static readonly AttachedProperty<NativeMenuInfo> s_nativeMenuInfoProperty =
AvaloniaProperty.RegisterAttached<NativeMenu, TopLevel, NativeMenuInfo>("___NativeMenuInfo");
class NativeMenuInfo
{
public bool ChangingIsExported { get; set; }
public ITopLevelNativeMenuExporter Exporter { get; }
public NativeMenuInfo(TopLevel target)
{
Exporter = (target.PlatformImpl as ITopLevelImplWithNativeMenuExporter)?.NativeMenuExporter;
if (Exporter != null)
{
Exporter.OnIsNativeMenuExportedChanged += delegate
{
SetIsNativeMenuExported(target, Exporter.IsNativeMenuExported);
};
}
}
}
static NativeMenuInfo GetInfo(TopLevel target)
{
var rv = target.GetValue(s_nativeMenuInfoProperty);
if (rv == null)
{
target.SetValue(s_nativeMenuInfoProperty, rv = new NativeMenuInfo(target));
SetIsNativeMenuExported(target, rv.Exporter?.IsNativeMenuExported ?? false);
}
return rv;
}
static void SetIsNativeMenuExported(TopLevel tl, bool value)
{
GetInfo(tl).ChangingIsExported = true;
tl.SetValue(IsNativeMenuExportedProperty, value);
}
public static readonly AttachedProperty<NativeMenu> MenuProperty
= AvaloniaProperty.RegisterAttached<NativeMenu, AvaloniaObject, NativeMenu>("Menu", validate:
(o, v) =>
{
if(!(o is Application || o is TopLevel))
throw new InvalidOperationException("NativeMenu.Menu property isn't valid on "+o.GetType());
return v;
});
public static void SetMenu(AvaloniaObject o, NativeMenu menu) => o.SetValue(MenuProperty, menu);
public static NativeMenu GetMenu(AvaloniaObject o) => o.GetValue(MenuProperty);
static NativeMenu()
{
// This is needed because of the lack of attached direct properties
IsNativeMenuExportedProperty.Changed.Subscribe(args =>
{
var info = GetInfo((TopLevel)args.Sender);
if (!info.ChangingIsExported)
throw new InvalidOperationException("IsNativeMenuExported property is read-only");
info.ChangingIsExported = false;
});
MenuProperty.Changed.Subscribe(args =>
{
if (args.Sender is TopLevel tl)
{
GetInfo(tl).Exporter?.SetNativeMenu((NativeMenu)args.NewValue);
}
});
}
}
}

60
src/Avalonia.Controls/NativeMenu.cs

@ -0,0 +1,60 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.Collections.Specialized;
using Avalonia.Collections;
using Avalonia.Data;
using Avalonia.LogicalTree;
using Avalonia.Metadata;
namespace Avalonia.Controls
{
public partial class NativeMenu : AvaloniaObject, IEnumerable<NativeMenuItemBase>
{
private readonly AvaloniaList<NativeMenuItemBase> _items =
new AvaloniaList<NativeMenuItemBase> { ResetBehavior = ResetBehavior.Remove };
private NativeMenuItem _parent;
[Content]
public IList<NativeMenuItemBase> Items => _items;
public NativeMenu()
{
_items.Validate = Validator;
_items.CollectionChanged += ItemsChanged;
}
private void Validator(NativeMenuItemBase obj)
{
if (obj.Parent != null)
throw new InvalidOperationException("NativeMenuItem already has a parent");
}
private void ItemsChanged(object sender, NotifyCollectionChangedEventArgs e)
{
if(e.OldItems!=null)
foreach (NativeMenuItemBase i in e.OldItems)
i.Parent = null;
if(e.NewItems!=null)
foreach (NativeMenuItemBase i in e.NewItems)
i.Parent = this;
}
public static readonly DirectProperty<NativeMenu, NativeMenuItem> ParentProperty =
AvaloniaProperty.RegisterDirect<NativeMenu, NativeMenuItem>("Parent", o => o.Parent, (o, v) => o.Parent = v);
public NativeMenuItem Parent
{
get => _parent;
set => SetAndRaise(ParentProperty, ref _parent, value);
}
public void Add(NativeMenuItemBase item) => _items.Add(item);
public IEnumerator<NativeMenuItemBase> GetEnumerator() => _items.GetEnumerator();
IEnumerator IEnumerable.GetEnumerator()
{
return GetEnumerator();
}
}
}

36
src/Avalonia.Controls/NativeMenuBar.cs

@ -0,0 +1,36 @@
using System;
using Avalonia.Controls.Primitives;
using Avalonia.Interactivity;
using Avalonia.Styling;
namespace Avalonia.Controls
{
public class NativeMenuBar : TemplatedControl
{
public static readonly AttachedProperty<bool> EnableMenuItemClickForwardingProperty =
AvaloniaProperty.RegisterAttached<NativeMenuBar, MenuItem, Boolean>(
"EnableMenuItemClickForwarding");
static NativeMenuBar()
{
EnableMenuItemClickForwardingProperty.Changed.Subscribe(args =>
{
var item = (MenuItem)args.Sender;
if (args.NewValue.Equals(true))
item.Click += OnMenuItemClick;
else
item.Click -= OnMenuItemClick;
});
}
public static void SetEnableMenuItemClickForwarding(MenuItem menuItem, bool enable)
{
menuItem.SetValue(EnableMenuItemClickForwardingProperty, enable);
}
private static void OnMenuItemClick(object sender, RoutedEventArgs e)
{
(((MenuItem)sender).DataContext as NativeMenuItem)?.RaiseClick();
}
}
}

162
src/Avalonia.Controls/NativeMenuItem.cs

@ -0,0 +1,162 @@
using System;
using System.Windows.Input;
using Avalonia.Input;
using Avalonia.Utilities;
namespace Avalonia.Controls
{
public class NativeMenuItem : NativeMenuItemBase
{
private string _header;
private KeyGesture _gesture;
private bool _enabled = true;
private NativeMenu _menu;
static NativeMenuItem()
{
MenuProperty.Changed.Subscribe(args =>
{
var item = (NativeMenuItem)args.Sender;
var value = (NativeMenu)args.NewValue;
if (value.Parent != null && value.Parent != item)
throw new InvalidOperationException("NativeMenu already has a parent");
value.Parent = item;
});
}
class CanExecuteChangedSubscriber : IWeakSubscriber<EventArgs>
{
private readonly NativeMenuItem _parent;
public CanExecuteChangedSubscriber(NativeMenuItem parent)
{
_parent = parent;
}
public void OnEvent(object sender, EventArgs e)
{
_parent.CanExecuteChanged();
}
}
private readonly CanExecuteChangedSubscriber _canExecuteChangedSubscriber;
public NativeMenuItem()
{
_canExecuteChangedSubscriber = new CanExecuteChangedSubscriber(this);
}
public NativeMenuItem(string header) : this()
{
Header = header;
}
public static readonly DirectProperty<NativeMenuItem, NativeMenu> MenuProperty =
AvaloniaProperty.RegisterDirect<NativeMenuItem, NativeMenu>(nameof(Menu), o => o._menu,
(o, v) =>
{
if (v.Parent != null && v.Parent != o)
throw new InvalidOperationException("NativeMenu already has a parent");
o._menu = v;
});
public NativeMenu Menu
{
get => _menu;
set
{
if (value.Parent != null && value.Parent != this)
throw new InvalidOperationException("NativeMenu already has a parent");
SetAndRaise(MenuProperty, ref _menu, value);
}
}
public static readonly DirectProperty<NativeMenuItem, string> HeaderProperty =
AvaloniaProperty.RegisterDirect<NativeMenuItem, string>(nameof(Header), o => o._header, (o, v) => o._header = v);
public string Header
{
get => GetValue(HeaderProperty);
set => SetValue(HeaderProperty, value);
}
public static readonly DirectProperty<NativeMenuItem, KeyGesture> GestureProperty =
AvaloniaProperty.RegisterDirect<NativeMenuItem, KeyGesture>(nameof(Gesture), o => o._gesture, (o, v) => o._gesture = v);
public KeyGesture Gesture
{
get => GetValue(GestureProperty);
set => SetValue(GestureProperty, value);
}
private ICommand _command;
public static readonly DirectProperty<NativeMenuItem, ICommand> CommandProperty =
AvaloniaProperty.RegisterDirect<NativeMenuItem, ICommand>(nameof(Command),
o => o._command, (o, v) =>
{
if (o._command != null)
WeakSubscriptionManager.Unsubscribe(o._command,
nameof(ICommand.CanExecuteChanged), o._canExecuteChangedSubscriber);
o._command = v;
if (o._command != null)
WeakSubscriptionManager.Subscribe(o._command,
nameof(ICommand.CanExecuteChanged), o._canExecuteChangedSubscriber);
o.CanExecuteChanged();
});
/// <summary>
/// Defines the <see cref="CommandParameter"/> property.
/// </summary>
public static readonly StyledProperty<object> CommandParameterProperty =
Button.CommandParameterProperty.AddOwner<MenuItem>();
public static readonly DirectProperty<NativeMenuItem, bool> EnabledProperty =
AvaloniaProperty.RegisterDirect<NativeMenuItem, bool>(nameof(Enabled), o => o._enabled,
(o, v) => o._enabled = v, true);
public bool Enabled
{
get => GetValue(EnabledProperty);
set => SetValue(EnabledProperty, value);
}
void CanExecuteChanged()
{
Enabled = _command?.CanExecute(null) ?? true;
}
public bool HasClickHandlers => Clicked != null;
public ICommand Command
{
get => GetValue(CommandProperty);
set => SetValue(CommandProperty, value);
}
/// <summary>
/// Gets or sets the parameter to pass to the <see cref="Command"/> property of a
/// <see cref="NativeMenuItem"/>.
/// </summary>
public object CommandParameter
{
get { return GetValue(CommandParameterProperty); }
set { SetValue(CommandParameterProperty, value); }
}
public event EventHandler Clicked;
public void RaiseClick()
{
Clicked?.Invoke(this, new EventArgs());
if (Command?.CanExecute(CommandParameter) == true)
{
Command.Execute(CommandParameter);
}
}
}
}

23
src/Avalonia.Controls/NativeMenuItemBase.cs

@ -0,0 +1,23 @@
using System;
namespace Avalonia.Controls
{
public class NativeMenuItemBase : AvaloniaObject
{
private NativeMenu _parent;
internal NativeMenuItemBase()
{
}
public static readonly DirectProperty<NativeMenuItem, NativeMenu> ParentProperty =
AvaloniaProperty.RegisterDirect<NativeMenuItem, NativeMenu>("Parent", o => o.Parent, (o, v) => o.Parent = v);
public NativeMenu Parent
{
get => _parent;
set => SetAndRaise(ParentProperty, ref _parent, value);
}
}
}

10
src/Avalonia.Controls/NativeMenuItemSeperator.cs

@ -0,0 +1,10 @@
using System;
namespace Avalonia.Controls
{
public class NativeMenuItemSeperator : NativeMenuItemBase
{
[Obsolete("This is a temporary hack to make our MenuItem recognize this as a separator, don't use", true)]
public string Header => "-";
}
}

18
src/Avalonia.Controls/Platform/ITopLevelNativeMenuExporter.cs

@ -0,0 +1,18 @@
using System;
using System.Collections.Generic;
using Avalonia.Platform;
namespace Avalonia.Controls.Platform
{
public interface ITopLevelNativeMenuExporter
{
bool IsNativeMenuExported { get; }
event EventHandler OnIsNativeMenuExportedChanged;
void SetNativeMenu(NativeMenu menu);
}
public interface ITopLevelImplWithNativeMenuExporter : ITopLevelImpl
{
ITopLevelNativeMenuExporter NativeMenuExporter { get; }
}
}

4
src/Avalonia.Controls/Presenters/CarouselPresenter.cs

@ -46,8 +46,8 @@ namespace Avalonia.Controls.Presenters
/// </summary>
static CarouselPresenter()
{
IsVirtualizedProperty.Changed.AddClassHandler<CarouselPresenter>(x => x.IsVirtualizedChanged);
SelectedIndexProperty.Changed.AddClassHandler<CarouselPresenter>(x => x.SelectedIndexChanged);
IsVirtualizedProperty.Changed.AddClassHandler<CarouselPresenter>((x, e) => x.IsVirtualizedChanged(e));
SelectedIndexProperty.Changed.AddClassHandler<CarouselPresenter>((x, e) => x.SelectedIndexChanged(e));
}
/// <summary>

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

@ -94,9 +94,9 @@ namespace Avalonia.Controls.Presenters
{
AffectsRender<ContentPresenter>(BackgroundProperty, BorderBrushProperty, BorderThicknessProperty, CornerRadiusProperty);
AffectsMeasure<ContentPresenter>(BorderThicknessProperty, PaddingProperty);
ContentProperty.Changed.AddClassHandler<ContentPresenter>(x => x.ContentChanged);
ContentTemplateProperty.Changed.AddClassHandler<ContentPresenter>(x => x.ContentChanged);
TemplatedParentProperty.Changed.AddClassHandler<ContentPresenter>(x => x.TemplatedParentChanged);
ContentProperty.Changed.AddClassHandler<ContentPresenter>((x, e) => x.ContentChanged(e));
ContentTemplateProperty.Changed.AddClassHandler<ContentPresenter>((x, e) => x.ContentChanged(e));
TemplatedParentProperty.Changed.AddClassHandler<ContentPresenter>((x, e) => x.TemplatedParentChanged(e));
}
/// <summary>

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

@ -35,7 +35,7 @@ namespace Avalonia.Controls.Presenters
KeyboardNavigationMode.Once);
VirtualizationModeProperty.Changed
.AddClassHandler<ItemsPresenter>(x => x.VirtualizationModeChanged);
.AddClassHandler<ItemsPresenter>((x, e) => x.VirtualizationModeChanged(e));
}
/// <summary>

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

@ -45,7 +45,7 @@ namespace Avalonia.Controls.Presenters
/// </summary>
static ItemsPresenterBase()
{
TemplatedParentProperty.Changed.AddClassHandler<ItemsPresenterBase>(x => x.TemplatedParentChanged);
TemplatedParentProperty.Changed.AddClassHandler<ItemsPresenterBase>((x,e) => x.TemplatedParentChanged(e));
}
/// <summary>

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

@ -73,7 +73,7 @@ namespace Avalonia.Controls.Presenters
static ScrollContentPresenter()
{
ClipToBoundsProperty.OverrideDefaultValue(typeof(ScrollContentPresenter), true);
ChildProperty.Changed.AddClassHandler<ScrollContentPresenter>(x => x.ChildChanged);
ChildProperty.Changed.AddClassHandler<ScrollContentPresenter>((x, e) => x.ChildChanged(e));
AffectsArrange<ScrollContentPresenter>(OffsetProperty);
}
@ -246,7 +246,7 @@ namespace Avalonia.Controls.Presenters
if (isLogical)
_activeLogicalGestureScrolls?.TryGetValue(e.Id, out delta);
delta += e.Delta;
if (Extent.Height > Viewport.Height)
{
double dy;
@ -293,7 +293,7 @@ namespace Avalonia.Controls.Presenters
}
}
private void OnScrollGestureEnded(object sender, ScrollGestureEndedEventArgs e)
private void OnScrollGestureEnded(object sender, ScrollGestureEndedEventArgs e)
=> _activeLogicalGestureScrolls?.Remove(e.Id);
/// <inheritdoc/>

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

@ -30,7 +30,7 @@ namespace Avalonia.Controls.Primitives
/// </summary>
static HeaderedContentControl()
{
ContentProperty.Changed.AddClassHandler<HeaderedContentControl>(x => x.HeaderChanged);
ContentProperty.Changed.AddClassHandler<HeaderedContentControl>((x, e) => x.HeaderChanged(e));
}
/// <summary>

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

@ -24,7 +24,7 @@ namespace Avalonia.Controls.Primitives
/// </summary>
static HeaderedItemsControl()
{
HeaderProperty.Changed.AddClassHandler<HeaderedItemsControl>(x => x.HeaderChanged);
HeaderProperty.Changed.AddClassHandler<HeaderedItemsControl>((x, e) => x.HeaderChanged(e));
}
/// <summary>

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

@ -24,7 +24,7 @@ namespace Avalonia.Controls.Primitives
/// </summary>
static HeaderedSelectingItemsControl()
{
HeaderProperty.Changed.AddClassHandler<HeaderedSelectingItemsControl>(x => x.HeaderChanged);
HeaderProperty.Changed.AddClassHandler<HeaderedSelectingItemsControl>((x, e) => x.HeaderChanged(e));
}
/// <summary>

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

@ -93,8 +93,8 @@ namespace Avalonia.Controls.Primitives
static Popup()
{
IsHitTestVisibleProperty.OverrideDefaultValue<Popup>(false);
ChildProperty.Changed.AddClassHandler<Popup>(x => x.ChildChanged);
IsOpenProperty.Changed.AddClassHandler<Popup>(x => x.IsOpenChanged);
ChildProperty.Changed.AddClassHandler<Popup>((x, e) => x.ChildChanged(e));
IsOpenProperty.Changed.AddClassHandler<Popup>((x, e) => x.IsOpenChanged(e));
}
public Popup()

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

@ -58,8 +58,8 @@ namespace Avalonia.Controls.Primitives
PseudoClass<ScrollBar, Orientation>(OrientationProperty, o => o == Orientation.Vertical, ":vertical");
PseudoClass<ScrollBar, Orientation>(OrientationProperty, o => o == Orientation.Horizontal, ":horizontal");
Thumb.DragDeltaEvent.AddClassHandler<ScrollBar>(o => o.OnThumbDragDelta, RoutingStrategies.Bubble);
Thumb.DragCompletedEvent.AddClassHandler<ScrollBar>(o => o.OnThumbDragComplete, RoutingStrategies.Bubble);
Thumb.DragDeltaEvent.AddClassHandler<ScrollBar>((x, e) => x.OnThumbDragDelta(e), RoutingStrategies.Bubble);
Thumb.DragCompletedEvent.AddClassHandler<ScrollBar>((x, e) => x.OnThumbDragComplete(e), RoutingStrategies.Bubble);
}
/// <summary>

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

@ -119,7 +119,7 @@ namespace Avalonia.Controls.Primitives
/// </summary>
static SelectingItemsControl()
{
IsSelectedChangedEvent.AddClassHandler<SelectingItemsControl>(x => x.ContainerSelectionChanged);
IsSelectedChangedEvent.AddClassHandler<SelectingItemsControl>((x, e) => x.ContainerSelectionChanged(e));
}
/// <summary>
@ -1088,8 +1088,8 @@ namespace Avalonia.Controls.Primitives
}
else
{
SelectedIndex = _updateSelectedIndex != int.MinValue ?
_updateSelectedIndex :
SelectedIndex = _updateSelectedIndex != int.MinValue ?
_updateSelectedIndex :
AlwaysSelected ? 0 : -1;
}
}

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

@ -99,7 +99,7 @@ namespace Avalonia.Controls.Primitives
static TemplatedControl()
{
ClipToBoundsProperty.OverrideDefaultValue<TemplatedControl>(true);
TemplateProperty.Changed.AddClassHandler<TemplatedControl>(x => x.OnTemplateChanged);
TemplateProperty.Changed.AddClassHandler<TemplatedControl>((x, e) => x.OnTemplateChanged(e));
}
/// <summary>

6
src/Avalonia.Controls/Primitives/Thumb.cs

@ -22,9 +22,9 @@ namespace Avalonia.Controls.Primitives
static Thumb()
{
DragStartedEvent.AddClassHandler<Thumb>(x => x.OnDragStarted, RoutingStrategies.Bubble);
DragDeltaEvent.AddClassHandler<Thumb>(x => x.OnDragDelta, RoutingStrategies.Bubble);
DragCompletedEvent.AddClassHandler<Thumb>(x => x.OnDragCompleted, RoutingStrategies.Bubble);
DragStartedEvent.AddClassHandler<Thumb>((x,e) => x.OnDragStarted(e), RoutingStrategies.Bubble);
DragDeltaEvent.AddClassHandler<Thumb>((x, e) => x.OnDragDelta(e), RoutingStrategies.Bubble);
DragCompletedEvent.AddClassHandler<Thumb>((x, e) => x.OnDragCompleted(e), RoutingStrategies.Bubble);
}
public event EventHandler<VectorEventArgs> DragStarted

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

@ -48,9 +48,9 @@ namespace Avalonia.Controls.Primitives
{
PseudoClass<Track, Orientation>(OrientationProperty, o => o == Orientation.Vertical, ":vertical");
PseudoClass<Track, Orientation>(OrientationProperty, o => o == Orientation.Horizontal, ":horizontal");
ThumbProperty.Changed.AddClassHandler<Track>(x => x.ThumbChanged);
IncreaseButtonProperty.Changed.AddClassHandler<Track>(x => x.ButtonChanged);
DecreaseButtonProperty.Changed.AddClassHandler<Track>(x => x.ButtonChanged);
ThumbProperty.Changed.AddClassHandler<Track>((x,e) => x.ThumbChanged(e));
IncreaseButtonProperty.Changed.AddClassHandler<Track>((x, e) => x.ButtonChanged(e));
DecreaseButtonProperty.Changed.AddClassHandler<Track>((x, e) => x.ButtonChanged(e));
AffectsArrange<Track>(MinimumProperty, MaximumProperty, ValueProperty, OrientationProperty);
}

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

@ -28,6 +28,11 @@ namespace Avalonia.Controls.Primitives
private int _rows;
private int _columns;
static UniformGrid()
{
AffectsMeasure<UniformGrid>(RowsProperty, ColumnsProperty, FirstColumnProperty);
}
/// <summary>
/// Specifies the row count. If set to 0, row count will be calculated automatically.
/// </summary>

5
src/Avalonia.Controls/ProgressBar.cs

@ -2,6 +2,7 @@
// Licensed under the MIT license. See licence.md file in the project root for full license information.
using System;
using Avalonia.Controls.Primitives;
using Avalonia.Layout;
@ -38,8 +39,8 @@ namespace Avalonia.Controls
PseudoClass<ProgressBar, Orientation>(OrientationProperty, o => o == Orientation.Horizontal, ":horizontal");
PseudoClass<ProgressBar>(IsIndeterminateProperty, ":indeterminate");
ValueProperty.Changed.AddClassHandler<ProgressBar>(x => x.UpdateIndicatorWhenPropChanged);
IsIndeterminateProperty.Changed.AddClassHandler<ProgressBar>(x => x.UpdateIndicatorWhenPropChanged);
ValueProperty.Changed.AddClassHandler<ProgressBar>((x, e) => x.UpdateIndicatorWhenPropChanged(e));
IsIndeterminateProperty.Changed.AddClassHandler<ProgressBar>((x, e) => x.UpdateIndicatorWhenPropChanged(e));
}
public bool IsIndeterminate

4
src/Avalonia.Controls/ScrollViewer.cs

@ -163,8 +163,8 @@ namespace Avalonia.Controls
{
AffectsValidation(ExtentProperty, OffsetProperty);
AffectsValidation(ViewportProperty, OffsetProperty);
HorizontalScrollBarVisibilityProperty.Changed.AddClassHandler<ScrollViewer>(x => x.ScrollBarVisibilityChanged);
VerticalScrollBarVisibilityProperty.Changed.AddClassHandler<ScrollViewer>(x => x.ScrollBarVisibilityChanged);
HorizontalScrollBarVisibilityProperty.Changed.AddClassHandler<ScrollViewer>((x, e) => x.ScrollBarVisibilityChanged(e));
VerticalScrollBarVisibilityProperty.Changed.AddClassHandler<ScrollViewer>((x, e) => x.ScrollBarVisibilityChanged(e));
}
/// <summary>

6
src/Avalonia.Controls/Slider.cs

@ -45,9 +45,9 @@ namespace Avalonia.Controls
OrientationProperty.OverrideDefaultValue(typeof(Slider), Orientation.Horizontal);
PseudoClass<Slider, Orientation>(OrientationProperty, o => o == Orientation.Vertical, ":vertical");
PseudoClass<Slider, Orientation>(OrientationProperty, o => o == Orientation.Horizontal, ":horizontal");
Thumb.DragStartedEvent.AddClassHandler<Slider>(x => x.OnThumbDragStarted, RoutingStrategies.Bubble);
Thumb.DragDeltaEvent.AddClassHandler<Slider>(x => x.OnThumbDragDelta, RoutingStrategies.Bubble);
Thumb.DragCompletedEvent.AddClassHandler<Slider>(x => x.OnThumbDragCompleted, RoutingStrategies.Bubble);
Thumb.DragStartedEvent.AddClassHandler<Slider>((x, e) => x.OnThumbDragStarted(e), RoutingStrategies.Bubble);
Thumb.DragDeltaEvent.AddClassHandler<Slider>((x, e) => x.OnThumbDragDelta(e), RoutingStrategies.Bubble);
Thumb.DragCompletedEvent.AddClassHandler<Slider>((x, e) => x.OnThumbDragCompleted(e), RoutingStrategies.Bubble);
}
/// <summary>

4
src/Avalonia.Controls/TabItem.cs

@ -30,8 +30,8 @@ namespace Avalonia.Controls
{
SelectableMixin.Attach<TabItem>(IsSelectedProperty);
FocusableProperty.OverrideDefaultValue(typeof(TabItem), true);
IsSelectedProperty.Changed.AddClassHandler<TabItem>(x => x.UpdateSelectedContent);
DataContextProperty.Changed.AddClassHandler<TabItem>(x => x.UpdateHeader);
IsSelectedProperty.Changed.AddClassHandler<TabItem>((x, e) => x.UpdateSelectedContent(e));
DataContextProperty.Changed.AddClassHandler<TabItem>((x, e) => x.UpdateHeader(e));
}
/// <summary>

2
src/Avalonia.Controls/TreeViewItem.cs

@ -54,7 +54,7 @@ namespace Avalonia.Controls
SelectableMixin.Attach<TreeViewItem>(IsSelectedProperty);
FocusableProperty.OverrideDefaultValue<TreeViewItem>(true);
ItemsPanelProperty.OverrideDefaultValue<TreeViewItem>(DefaultPanel);
RequestBringIntoViewEvent.AddClassHandler<TreeViewItem>(x => x.OnRequestBringIntoView);
RequestBringIntoViewEvent.AddClassHandler<TreeViewItem>((x, e) => x.OnRequestBringIntoView(e));
}
/// <summary>

2
src/Avalonia.Controls/WindowBase.cs

@ -47,7 +47,7 @@ namespace Avalonia.Controls
static WindowBase()
{
IsVisibleProperty.OverrideDefaultValue<WindowBase>(false);
IsVisibleProperty.Changed.AddClassHandler<WindowBase>(x => x.IsVisibleChanged);
IsVisibleProperty.Changed.AddClassHandler<WindowBase>((x,e) => x.IsVisibleChanged(e));
TopmostProperty.Changed.AddClassHandler<WindowBase>((w, e) => w.PlatformImpl?.SetTopmost((bool)e.NewValue));

11
src/Avalonia.DesktopRuntime/AppBuilder.cs

@ -18,19 +18,10 @@ namespace Avalonia
/// </summary>
public AppBuilder()
: base(new StandardRuntimePlatform(),
builder => StandardRuntimePlatformServices.Register(builder.Instance?.GetType()?.Assembly))
builder => StandardRuntimePlatformServices.Register(builder.ApplicationType.Assembly))
{
}
/// <summary>
/// Initializes a new instance of the <see cref="AppBuilder"/> class.
/// </summary>
/// <param name="app">The <see cref="Application"/> instance.</param>
public AppBuilder(Application app) : this()
{
Instance = app;
}
bool CheckEnvironment(Type checkerType)
{
if (checkerType == null)

4
src/Avalonia.FreeDesktop/Avalonia.FreeDesktop.csproj

@ -8,4 +8,8 @@
<ProjectReference Include="..\Avalonia.Controls\Avalonia.Controls.csproj" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Tmds.DBus" Version="0.7.0" />
</ItemGroup>
</Project>

82
src/Avalonia.FreeDesktop/DBusHelper.cs

@ -0,0 +1,82 @@
using System;
using System.Threading;
using Avalonia.Threading;
using Tmds.DBus;
namespace Avalonia.FreeDesktop
{
public class DBusHelper
{
/// <summary>
/// This class uses synchronous execution at DBus connection establishment stage
/// then switches to using AvaloniaSynchronizationContext
/// </summary>
class DBusSyncContext : SynchronizationContext
{
private SynchronizationContext _ctx;
private object _lock = new object();
public override void Post(SendOrPostCallback d, object state)
{
lock (_lock)
{
if (_ctx != null)
_ctx?.Post(d, state);
else
lock (_lock)
d(state);
}
}
public override void Send(SendOrPostCallback d, object state)
{
lock (_lock)
{
if (_ctx != null)
_ctx?.Send(d, state);
else
d(state);
}
}
public void Initialized()
{
lock (_lock)
_ctx = new AvaloniaSynchronizationContext();
}
}
public static Connection Connection { get; private set; }
public static Exception TryInitialize(string dbusAddress = null)
{
var oldContext = SynchronizationContext.Current;
try
{
var dbusContext = new DBusSyncContext();
SynchronizationContext.SetSynchronizationContext(dbusContext);
var conn = new Connection(new ClientConnectionOptions(dbusAddress ?? Address.Session)
{
AutoConnect = false,
SynchronizationContext = dbusContext
});
// Connect synchronously
conn.ConnectAsync().Wait();
// Initialize a brand new sync-context
dbusContext.Initialized();
Connection = conn;
}
catch (Exception e)
{
return e;
}
finally
{
SynchronizationContext.SetSynchronizationContext(oldContext);
}
return null;
}
}
}

56
src/Avalonia.FreeDesktop/DBusMenu.cs

@ -0,0 +1,56 @@
using System;
using System.Collections.Generic;
using System.Runtime.CompilerServices;
using System.Threading.Tasks;
using Tmds.DBus;
[assembly: InternalsVisibleTo(Tmds.DBus.Connection.DynamicAssemblyName)]
namespace Avalonia.FreeDesktop.DBusMenu
{
[DBusInterface("org.freedesktop.DBus.Properties")]
interface IFreeDesktopDBusProperties : IDBusObject
{
Task<object> GetAsync(string prop);
Task<DBusMenuProperties> GetAllAsync();
Task SetAsync(string prop, object val);
Task<IDisposable> WatchPropertiesAsync(Action<PropertyChanges> handler);
}
[DBusInterface("com.canonical.dbusmenu")]
interface IDBusMenu : IFreeDesktopDBusProperties
{
Task<(uint revision, (int, KeyValuePair<string, object>[], object[]) layout)> GetLayoutAsync(int ParentId, int RecursionDepth, string[] PropertyNames);
Task<(int, KeyValuePair<string, object>[])[]> GetGroupPropertiesAsync(int[] Ids, string[] PropertyNames);
Task<object> GetPropertyAsync(int Id, string Name);
Task EventAsync(int Id, string EventId, object Data, uint Timestamp);
Task<int[]> EventGroupAsync((int id, string eventId, object data, uint timestamp)[] events);
Task<bool> AboutToShowAsync(int Id);
Task<(int[] updatesNeeded, int[] idErrors)> AboutToShowGroupAsync(int[] Ids);
Task<IDisposable> WatchItemsPropertiesUpdatedAsync(Action<((int, IDictionary<string, object>)[] updatedProps, (int, string[])[] removedProps)> handler, Action<Exception> onError = null);
Task<IDisposable> WatchLayoutUpdatedAsync(Action<(uint revision, int parent)> handler, Action<Exception> onError = null);
Task<IDisposable> WatchItemActivationRequestedAsync(Action<(int id, uint timestamp)> handler, Action<Exception> onError = null);
}
[Dictionary]
class DBusMenuProperties
{
public uint Version { get; set; } = default (uint);
public string TextDirection { get; set; } = default (string);
public string Status { get; set; } = default (string);
public string[] IconThemePath { get; set; } = default (string[]);
}
[DBusInterface("com.canonical.AppMenu.Registrar")]
interface IRegistrar : IDBusObject
{
Task RegisterWindowAsync(uint WindowId, ObjectPath MenuObjectPath);
Task UnregisterWindowAsync(uint WindowId);
Task<(string service, ObjectPath menuObjectPath)> GetMenuForWindowAsync(uint WindowId);
Task<(uint, string, ObjectPath)[]> GetMenusAsync();
Task<IDisposable> WatchWindowRegisteredAsync(Action<(uint windowId, string service, ObjectPath menuObjectPath)> handler, Action<Exception> onError = null);
Task<IDisposable> WatchWindowUnregisteredAsync(Action<uint> handler, Action<Exception> onError = null);
}
}

387
src/Avalonia.FreeDesktop/DBusMenuExporter.cs

@ -0,0 +1,387 @@
using System;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.Reactive.Disposables;
using System.Threading.Tasks;
using Avalonia.Controls;
using Avalonia.Controls.Platform;
using Avalonia.FreeDesktop.DBusMenu;
using Avalonia.Input;
using Avalonia.Threading;
using Tmds.DBus;
#pragma warning disable 1998
namespace Avalonia.FreeDesktop
{
public class DBusMenuExporter
{
public static ITopLevelNativeMenuExporter TryCreate(IntPtr xid)
{
if (DBusHelper.Connection == null)
return null;
return new DBusMenuExporterImpl(DBusHelper.Connection, xid);
}
class DBusMenuExporterImpl : ITopLevelNativeMenuExporter, IDBusMenu, IDisposable
{
private readonly Connection _dbus;
private readonly uint _xid;
private IRegistrar _registar;
private bool _disposed;
private uint _revision = 1;
private NativeMenu _menu;
private Dictionary<int, NativeMenuItemBase> _idsToItems = new Dictionary<int, NativeMenuItemBase>();
private Dictionary<NativeMenuItemBase, int> _itemsToIds = new Dictionary<NativeMenuItemBase, int>();
private readonly HashSet<NativeMenu> _menus = new HashSet<NativeMenu>();
private bool _resetQueued;
private int _nextId = 1;
public DBusMenuExporterImpl(Connection dbus, IntPtr xid)
{
_dbus = dbus;
_xid = (uint)xid.ToInt32();
ObjectPath = new ObjectPath("/net/avaloniaui/dbusmenu/"
+ Guid.NewGuid().ToString().Replace("-", ""));
SetNativeMenu(new NativeMenu());
Init();
}
async void Init()
{
try
{
await _dbus.RegisterObjectAsync(this);
_registar = DBusHelper.Connection.CreateProxy<IRegistrar>(
"com.canonical.AppMenu.Registrar",
"/com/canonical/AppMenu/Registrar");
if (!_disposed)
await _registar.RegisterWindowAsync(_xid, ObjectPath);
}
catch (Exception e)
{
Console.Error.WriteLine(e);
// It's not really important if this code succeeds,
// and it's not important to know if it succeeds
// since even if we register the window it's not guaranteed that
// menu will be actually exported
}
}
public void Dispose()
{
if (_disposed)
return;
_disposed = true;
_dbus.UnregisterObject(this);
// Fire and forget
_registar?.UnregisterWindowAsync(_xid);
}
public bool IsNativeMenuExported { get; private set; }
public event EventHandler OnIsNativeMenuExportedChanged;
public void SetNativeMenu(NativeMenu menu)
{
if (menu == null)
menu = new NativeMenu();
if (_menu != null)
((INotifyCollectionChanged)_menu.Items).CollectionChanged -= OnMenuItemsChanged;
_menu = menu;
((INotifyCollectionChanged)_menu.Items).CollectionChanged += OnMenuItemsChanged;
DoLayoutReset();
}
/*
This is basic initial implementation, so we don't actually track anything and
just reset the whole layout on *ANY* change
This is not how it should work and will prevent us from implementing various features,
but that's the fastest way to get things working, so...
*/
void DoLayoutReset()
{
_resetQueued = false;
foreach (var i in _idsToItems.Values)
i.PropertyChanged -= OnItemPropertyChanged;
foreach(var menu in _menus)
((INotifyCollectionChanged)menu.Items).CollectionChanged -= OnMenuItemsChanged;
_menus.Clear();
_idsToItems.Clear();
_itemsToIds.Clear();
_revision++;
LayoutUpdated?.Invoke((_revision, 0));
}
void QueueReset()
{
if(_resetQueued)
return;
_resetQueued = true;
Dispatcher.UIThread.Post(DoLayoutReset, DispatcherPriority.Background);
}
private (NativeMenuItemBase item, NativeMenu menu) GetMenu(int id)
{
if (id == 0)
return (null, _menu);
_idsToItems.TryGetValue(id, out var item);
return (item, (item as NativeMenuItem)?.Menu);
}
private void EnsureSubscribed(NativeMenu menu)
{
if(menu!=null && _menus.Add(menu))
((INotifyCollectionChanged)menu.Items).CollectionChanged += OnMenuItemsChanged;
}
private int GetId(NativeMenuItemBase item)
{
if (_itemsToIds.TryGetValue(item, out var id))
return id;
id = _nextId++;
_idsToItems[id] = item;
_itemsToIds[item] = id;
item.PropertyChanged += OnItemPropertyChanged;
if (item is NativeMenuItem nmi)
EnsureSubscribed(nmi.Menu);
return id;
}
private void OnMenuItemsChanged(object sender, NotifyCollectionChangedEventArgs e)
{
QueueReset();
}
private void OnItemPropertyChanged(object sender, AvaloniaPropertyChangedEventArgs e)
{
QueueReset();
}
public ObjectPath ObjectPath { get; }
async Task<object> IFreeDesktopDBusProperties.GetAsync(string prop)
{
if (prop == "Version")
return 2;
if (prop == "Status")
return "normal";
return 0;
}
async Task<DBusMenuProperties> IFreeDesktopDBusProperties.GetAllAsync()
{
return new DBusMenuProperties
{
Version = 2,
Status = "normal",
};
}
private static string[] AllProperties = new[]
{
"type", "label", "enabled", "visible", "shortcut", "toggle-type", "children-display"
};
object GetProperty((NativeMenuItemBase item, NativeMenu menu) i, string name)
{
var (it, menu) = i;
if (it is NativeMenuItemSeperator)
{
if (name == "type")
return "separator";
}
else if (it is NativeMenuItem item)
{
if (name == "type")
{
return null;
}
if (name == "label")
return item?.Header ?? "<null>";
if (name == "enabled")
{
if (item == null)
return null;
if (item.Menu != null && item.Menu.Items.Count == 0)
return false;
if (item.Enabled == false)
return false;
return null;
}
if (name == "shortcut")
{
if (item?.Gesture == null)
return null;
if (item.Gesture.KeyModifiers == 0)
return null;
var lst = new List<string>();
var mod = item.Gesture;
if ((mod.KeyModifiers & KeyModifiers.Control) != 0)
lst.Add("Control");
if ((mod.KeyModifiers & KeyModifiers.Alt) != 0)
lst.Add("Alt");
if ((mod.KeyModifiers & KeyModifiers.Shift) != 0)
lst.Add("Shift");
if ((mod.KeyModifiers & KeyModifiers.Meta) != 0)
lst.Add("Super");
lst.Add(item.Gesture.Key.ToString());
return new[] { lst.ToArray() };
}
if (name == "children-display")
return menu != null ? "submenu" : null;
}
return null;
}
private List<KeyValuePair<string, object>> _reusablePropertyList = new List<KeyValuePair<string, object>>();
KeyValuePair<string, object>[] GetProperties((NativeMenuItemBase item, NativeMenu menu) i, string[] names)
{
if (names?.Length > 0 != true)
names = AllProperties;
_reusablePropertyList.Clear();
foreach (var n in names)
{
var v = GetProperty(i, n);
if (v != null)
_reusablePropertyList.Add(new KeyValuePair<string, object>(n, v));
}
return _reusablePropertyList.ToArray();
}
public Task SetAsync(string prop, object val) => Task.CompletedTask;
public Task<(uint revision, (int, KeyValuePair<string, object>[], object[]) layout)> GetLayoutAsync(
int ParentId, int RecursionDepth, string[] PropertyNames)
{
var menu = GetMenu(ParentId);
var rv = (_revision, GetLayout(menu.item, menu.menu, RecursionDepth, PropertyNames));
if (!IsNativeMenuExported)
{
IsNativeMenuExported = true;
Dispatcher.UIThread.Post(() =>
{
OnIsNativeMenuExportedChanged?.Invoke(this, EventArgs.Empty);
});
}
return Task.FromResult(rv);
}
(int, KeyValuePair<string, object>[], object[]) GetLayout(NativeMenuItemBase item, NativeMenu menu, int depth, string[] propertyNames)
{
var id = item == null ? 0 : GetId(item);
var props = GetProperties((item, menu), propertyNames);
var children = (depth == 0 || menu == null) ? new object[0] : new object[menu.Items.Count];
if(menu != null)
for (var c = 0; c < children.Length; c++)
{
var ch = menu.Items[c];
children[c] = GetLayout(ch, (ch as NativeMenuItem)?.Menu, depth == -1 ? -1 : depth - 1, propertyNames);
}
return (id, props, children);
}
public Task<(int, KeyValuePair<string, object>[])[]> GetGroupPropertiesAsync(int[] Ids, string[] PropertyNames)
{
var arr = new (int, KeyValuePair<string, object>[])[Ids.Length];
for (var c = 0; c < Ids.Length; c++)
{
var id = Ids[c];
var item = GetMenu(id);
var props = GetProperties(item, PropertyNames);
arr[c] = (id, props);
}
return Task.FromResult(arr);
}
public async Task<object> GetPropertyAsync(int Id, string Name)
{
return GetProperty(GetMenu(Id), Name) ?? 0;
}
public void HandleEvent(int id, string eventId, object data, uint timestamp)
{
if (eventId == "clicked")
{
var item = GetMenu(id).item;
if (item is NativeMenuItem menuItem)
{
if (menuItem?.Enabled == true)
menuItem.RaiseClick();
}
}
}
public Task EventAsync(int Id, string EventId, object Data, uint Timestamp)
{
HandleEvent(Id, EventId, Data, Timestamp);
return Task.CompletedTask;
}
public Task<int[]> EventGroupAsync((int id, string eventId, object data, uint timestamp)[] Events)
{
foreach (var e in Events)
HandleEvent(e.id, e.eventId, e.data, e.timestamp);
return Task.FromResult(new int[0]);
}
public async Task<bool> AboutToShowAsync(int Id)
{
return false;
}
public async Task<(int[] updatesNeeded, int[] idErrors)> AboutToShowGroupAsync(int[] Ids)
{
return (new int[0], new int[0]);
}
#region Events
private event Action<((int, IDictionary<string, object>)[] updatedProps, (int, string[])[] removedProps)>
ItemsPropertiesUpdated;
private event Action<(uint revision, int parent)> LayoutUpdated;
private event Action<(int id, uint timestamp)> ItemActivationRequested;
private event Action<PropertyChanges> PropertiesChanged;
async Task<IDisposable> IDBusMenu.WatchItemsPropertiesUpdatedAsync(Action<((int, IDictionary<string, object>)[] updatedProps, (int, string[])[] removedProps)> handler, Action<Exception> onError)
{
ItemsPropertiesUpdated += handler;
return Disposable.Create(() => ItemsPropertiesUpdated -= handler);
}
async Task<IDisposable> IDBusMenu.WatchLayoutUpdatedAsync(Action<(uint revision, int parent)> handler, Action<Exception> onError)
{
LayoutUpdated += handler;
return Disposable.Create(() => LayoutUpdated -= handler);
}
async Task<IDisposable> IDBusMenu.WatchItemActivationRequestedAsync(Action<(int id, uint timestamp)> handler, Action<Exception> onError)
{
ItemActivationRequested+= handler;
return Disposable.Create(() => ItemActivationRequested -= handler);
}
async Task<IDisposable> IFreeDesktopDBusProperties.WatchPropertiesAsync(Action<PropertyChanges> handler)
{
PropertiesChanged += handler;
return Disposable.Create(() => PropertiesChanged -= handler);
}
#endregion
}
}
}

24
src/Avalonia.Input/InputElement.cs

@ -169,18 +169,18 @@ namespace Avalonia.Input
{
IsEnabledProperty.Changed.Subscribe(IsEnabledChanged);
GotFocusEvent.AddClassHandler<InputElement>(x => x.OnGotFocus);
LostFocusEvent.AddClassHandler<InputElement>(x => x.OnLostFocus);
KeyDownEvent.AddClassHandler<InputElement>(x => x.OnKeyDown);
KeyUpEvent.AddClassHandler<InputElement>(x => x.OnKeyUp);
TextInputEvent.AddClassHandler<InputElement>(x => x.OnTextInput);
PointerEnterEvent.AddClassHandler<InputElement>(x => x.OnPointerEnterCore);
PointerLeaveEvent.AddClassHandler<InputElement>(x => x.OnPointerLeaveCore);
PointerMovedEvent.AddClassHandler<InputElement>(x => x.OnPointerMoved);
PointerPressedEvent.AddClassHandler<InputElement>(x => x.OnPointerPressed);
PointerReleasedEvent.AddClassHandler<InputElement>(x => x.OnPointerReleased);
PointerCaptureLostEvent.AddClassHandler<InputElement>(x => x.OnPointerCaptureLost);
PointerWheelChangedEvent.AddClassHandler<InputElement>(x => x.OnPointerWheelChanged);
GotFocusEvent.AddClassHandler<InputElement>((x, e) => x.OnGotFocus(e));
LostFocusEvent.AddClassHandler<InputElement>((x, e) => x.OnLostFocus(e));
KeyDownEvent.AddClassHandler<InputElement>((x, e) => x.OnKeyDown(e));
KeyUpEvent.AddClassHandler<InputElement>((x, e) => x.OnKeyUp(e));
TextInputEvent.AddClassHandler<InputElement>((x, e) => x.OnTextInput(e));
PointerEnterEvent.AddClassHandler<InputElement>((x, e) => x.OnPointerEnterCore(e));
PointerLeaveEvent.AddClassHandler<InputElement>((x, e) => x.OnPointerLeaveCore(e));
PointerMovedEvent.AddClassHandler<InputElement>((x, e) => x.OnPointerMoved(e));
PointerPressedEvent.AddClassHandler<InputElement>((x, e) => x.OnPointerPressed(e));
PointerReleasedEvent.AddClassHandler<InputElement>((x, e) => x.OnPointerReleased(e));
PointerCaptureLostEvent.AddClassHandler<InputElement>((x, e) => x.OnPointerCaptureLost(e));
PointerWheelChangedEvent.AddClassHandler<InputElement>((x, e) => x.OnPointerWheelChanged(e));
PseudoClass<InputElement, bool>(IsEffectivelyEnabledProperty, x => !x, ":disabled");
PseudoClass<InputElement>(IsFocusedProperty, ":focus");

7
src/Avalonia.Input/KeyGesture.cs

@ -14,7 +14,7 @@ namespace Avalonia.Input
{
private static readonly Dictionary<string, Key> s_keySynonyms = new Dictionary<string, Key>
{
{ "+", Key.OemPlus }, { "-", Key.OemMinus }, { ".", Key.OemPeriod }
{ "+", Key.OemPlus }, { "-", Key.OemMinus }, { ".", Key.OemPeriod }, { ",", Key.OemComma }
};
[Obsolete("Use constructor taking KeyModifiers")]
@ -141,6 +141,11 @@ namespace Avalonia.Input
return KeyModifiers.Control;
}
if (modifier.Equals("cmd".AsSpan(), StringComparison.OrdinalIgnoreCase))
{
return KeyModifiers.Meta;
}
return (KeyModifiers)Enum.Parse(typeof(KeyModifiers), modifier.ToString(), true);
}

30
src/Avalonia.Interactivity/RoutedEvent.cs

@ -113,24 +113,38 @@ namespace Avalonia.Interactivity
Contract.Requires<ArgumentNullException>(ownerType != null);
}
[Obsolete("Use overload taking Action<TTarget, TEventArgs>.")]
public IDisposable AddClassHandler<TTarget>(
Func<TTarget, Action<TEventArgs>> handler,
RoutingStrategies routes = RoutingStrategies.Direct | RoutingStrategies.Bubble,
bool handledEventsToo = false)
where TTarget : class, IInteractive
where TTarget : class, IInteractive
{
EventHandler<RoutedEventArgs> adapter = (sender, e) =>
void Adapter(object sender, RoutedEventArgs e)
{
var target = sender as TTarget;
var args = e as TEventArgs;
if (target != null && args != null)
if (sender is TTarget target && e is TEventArgs args)
{
handler(target)(args);
}
};
}
return AddClassHandler(typeof(TTarget), Adapter, routes, handledEventsToo);
}
public IDisposable AddClassHandler<TTarget>(
Action<TTarget, TEventArgs> handler,
RoutingStrategies routes = RoutingStrategies.Direct | RoutingStrategies.Bubble,
bool handledEventsToo = false) where TTarget : class, IInteractive
{
void Adapter(object sender, RoutedEventArgs e)
{
if (sender is TTarget target && e is TEventArgs args)
{
handler(target, args);
}
}
return AddClassHandler(typeof(TTarget), adapter, routes, handledEventsToo);
return AddClassHandler(typeof(TTarget), Adapter, routes, handledEventsToo);
}
}
}

305
src/Avalonia.Native/AvaloniaNativeMenuExporter.cs

@ -0,0 +1,305 @@
using System;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.Linq;
using System.Text;
using Avalonia.Controls;
using Avalonia.Controls.Platform;
using Avalonia.Native.Interop;
using Avalonia.Platform.Interop;
using Avalonia.Threading;
namespace Avalonia.Native
{
public class MenuActionCallback : CallbackBase, IAvnActionCallback
{
private Action _action;
public MenuActionCallback(Action action)
{
_action = action;
}
void IAvnActionCallback.Run()
{
_action?.Invoke();
}
}
public class PredicateCallback : CallbackBase, IAvnPredicateCallback
{
private Func<bool> _predicate;
public PredicateCallback(Func<bool> predicate)
{
_predicate = predicate;
}
bool IAvnPredicateCallback.Evaluate()
{
return _predicate();
}
}
class AvaloniaNativeMenuExporter : ITopLevelNativeMenuExporter
{
private IAvaloniaNativeFactory _factory;
private NativeMenu _menu;
private bool _resetQueued;
private bool _exported = false;
private IAvnWindow _nativeWindow;
private List<NativeMenuItem> _menuItems = new List<NativeMenuItem>();
public AvaloniaNativeMenuExporter(IAvnWindow nativeWindow, IAvaloniaNativeFactory factory)
{
_factory = factory;
_nativeWindow = nativeWindow;
DoLayoutReset();
}
public AvaloniaNativeMenuExporter(IAvaloniaNativeFactory factory)
{
_factory = factory;
_menu = NativeMenu.GetMenu(Application.Current);
DoLayoutReset();
}
public bool IsNativeMenuExported => _exported;
public event EventHandler OnIsNativeMenuExportedChanged;
public void SetNativeMenu(NativeMenu menu)
{
if (menu == null)
menu = new NativeMenu();
if (_menu != null)
((INotifyCollectionChanged)_menu.Items).CollectionChanged -= OnMenuItemsChanged;
_menu = menu;
((INotifyCollectionChanged)_menu.Items).CollectionChanged += OnMenuItemsChanged;
DoLayoutReset();
}
private void OnItemPropertyChanged(object sender, AvaloniaPropertyChangedEventArgs e)
{
QueueReset();
}
private void OnMenuItemsChanged(object sender, NotifyCollectionChangedEventArgs e)
{
QueueReset();
}
void DoLayoutReset()
{
_resetQueued = false;
foreach (var i in _menuItems)
{
i.PropertyChanged -= OnItemPropertyChanged;
if (i.Menu != null)
((INotifyCollectionChanged)i.Menu.Items).CollectionChanged -= OnMenuItemsChanged;
}
_menuItems.Clear();
if(_nativeWindow is null)
{
_menu = NativeMenu.GetMenu(Application.Current);
if(_menu != null)
{
SetMenu(_menu);
}
}
else
{
SetMenu(_nativeWindow, _menu?.Items);
}
_exported = true;
}
private void QueueReset()
{
if (_resetQueued)
return;
_resetQueued = true;
Dispatcher.UIThread.Post(DoLayoutReset, DispatcherPriority.Background);
}
private IAvnAppMenu CreateSubmenu(ICollection<NativeMenuItemBase> children)
{
var menu = _factory.CreateMenu();
SetChildren(menu, children);
return menu;
}
private void AddMenuItem(NativeMenuItem item)
{
if (item.Menu?.Items != null)
{
((INotifyCollectionChanged)item.Menu.Items).CollectionChanged += OnMenuItemsChanged;
}
}
private void SetChildren(IAvnAppMenu menu, ICollection<NativeMenuItemBase> children)
{
foreach (var i in children)
{
if (i is NativeMenuItem item)
{
AddMenuItem(item);
var menuItem = _factory.CreateMenuItem();
using (var buffer = new Utf8Buffer(item.Header))
{
menuItem.Title = buffer.DangerousGetHandle();
}
if (item.Gesture != null)
{
using (var buffer = new Utf8Buffer(item.Gesture.Key.ToString().ToLower()))
{
menuItem.SetGesture(buffer.DangerousGetHandle(), (AvnInputModifiers)item.Gesture.KeyModifiers);
}
}
menuItem.SetAction(new PredicateCallback(() =>
{
if (item.Command != null || item.HasClickHandlers)
{
return item.Enabled;
}
return false;
}), new MenuActionCallback(() => { item.RaiseClick(); }));
menu.AddItem(menuItem);
if (item.Menu?.Items?.Count > 0)
{
var submenu = _factory.CreateMenu();
using (var buffer = new Utf8Buffer(item.Header))
{
submenu.Title = buffer.DangerousGetHandle();
}
menuItem.SetSubMenu(submenu);
AddItemsToMenu(submenu, item.Menu?.Items);
}
}
else if (i is NativeMenuItemSeperator seperator)
{
menu.AddItem(_factory.CreateMenuItemSeperator());
}
}
}
private void AddItemsToMenu(IAvnAppMenu menu, ICollection<NativeMenuItemBase> items, bool isMainMenu = false)
{
foreach (var i in items)
{
if (i is NativeMenuItem item)
{
var menuItem = _factory.CreateMenuItem();
AddMenuItem(item);
menuItem.SetAction(new PredicateCallback(() =>
{
if (item.Command != null || item.HasClickHandlers)
{
return item.Enabled;
}
return false;
}), new MenuActionCallback(() => { item.RaiseClick(); }));
if (item.Menu?.Items.Count > 0 || isMainMenu)
{
var subMenu = CreateSubmenu(item.Menu?.Items);
menuItem.SetSubMenu(subMenu);
using (var buffer = new Utf8Buffer(item.Header))
{
subMenu.Title = buffer.DangerousGetHandle();
}
}
else
{
using (var buffer = new Utf8Buffer(item.Header))
{
menuItem.Title = buffer.DangerousGetHandle();
}
if (item.Gesture != null)
{
using (var buffer = new Utf8Buffer(item.Gesture.Key.ToString().ToLower()))
{
menuItem.SetGesture(buffer.DangerousGetHandle(), (AvnInputModifiers)item.Gesture.KeyModifiers);
}
}
}
menu.AddItem(menuItem);
}
else if(i is NativeMenuItemSeperator seperator)
{
menu.AddItem(_factory.CreateMenuItemSeperator());
}
}
}
private void SetMenu(NativeMenu menu)
{
var appMenu = _factory.ObtainAppMenu();
if (appMenu is null)
{
appMenu = _factory.CreateMenu();
}
var menuItem = menu.Parent;
if(menu.Parent is null)
{
menuItem = new NativeMenuItem();
}
menuItem.Menu = menu;
appMenu.Clear();
AddItemsToMenu(appMenu, new List<NativeMenuItemBase> { menuItem });
_factory.SetAppMenu(appMenu);
}
private void SetMenu(IAvnWindow avnWindow, ICollection<NativeMenuItemBase> menuItems)
{
if (menuItems is null)
{
menuItems = new List<NativeMenuItemBase>();
}
var appMenu = avnWindow.ObtainMainMenu();
if (appMenu is null)
{
appMenu = _factory.CreateMenu();
}
appMenu.Clear();
AddItemsToMenu(appMenu, menuItems);
avnWindow.SetMainMenu(appMenu);
}
}
}

32
src/Avalonia.Native/AvaloniaNativePlatform.cs

@ -9,6 +9,7 @@ using Avalonia.Native.Interop;
using Avalonia.OpenGL;
using Avalonia.Platform;
using Avalonia.Rendering;
using Avalonia.Platform.Interop;
namespace Avalonia.Native
{
@ -27,15 +28,17 @@ namespace Avalonia.Native
public TimeSpan DoubleClickTime => TimeSpan.FromMilliseconds(500); //TODO
public static void Initialize(IntPtr factory, AvaloniaNativePlatformOptions options)
public static AvaloniaNativePlatform Initialize(IntPtr factory, AvaloniaNativePlatformOptions options)
{
new AvaloniaNativePlatform(new IAvaloniaNativeFactory(factory))
.DoInitialize(options);
var result = new AvaloniaNativePlatform(new IAvaloniaNativeFactory(factory));
result.DoInitialize(options);
return result;
}
delegate IntPtr CreateAvaloniaNativeDelegate();
public static void Initialize(AvaloniaNativePlatformOptions options)
public static AvaloniaNativePlatform Initialize(AvaloniaNativePlatformOptions options)
{
if (options.AvaloniaNativeLibraryPath != null)
{
@ -48,10 +51,26 @@ namespace Avalonia.Native
var d = Marshal.GetDelegateForFunctionPointer<CreateAvaloniaNativeDelegate>(proc);
Initialize(d(), options);
return Initialize(d(), options);
}
else
Initialize(CreateAvaloniaNative(), options);
return Initialize(CreateAvaloniaNative(), options);
}
public void SetupApplicationMenuExporter ()
{
var exporter = new AvaloniaNativeMenuExporter(_factory);
}
public void SetupApplicationName ()
{
if(!string.IsNullOrWhiteSpace(Application.Current.Name))
{
using (var buffer = new Utf8Buffer(Application.Current.Name))
{
_factory.MacOptions.SetApplicationTitle(buffer.DangerousGetHandle());
}
}
}
private AvaloniaNativePlatform(IAvaloniaNativeFactory factory)
@ -66,6 +85,7 @@ namespace Avalonia.Native
if (_factory.MacOptions != null)
{
var macOpts = AvaloniaLocator.Current.GetService<MacOSPlatformOptions>();
_factory.MacOptions.ShowInDock = macOpts?.ShowInDock != false ? 1 : 0;
}

13
src/Avalonia.Native/AvaloniaNativePlatformExtensions.cs

@ -13,9 +13,18 @@ namespace Avalonia
where T : AppBuilderBase<T>, new()
{
builder.UseWindowingSubsystem(() =>
AvaloniaNativePlatform.Initialize(
{
var platform = AvaloniaNativePlatform.Initialize(
AvaloniaLocator.Current.GetService<AvaloniaNativePlatformOptions>() ??
new AvaloniaNativePlatformOptions()));
new AvaloniaNativePlatformOptions());
builder.AfterSetup (x=>
{
platform.SetupApplicationName();
platform.SetupApplicationMenuExporter();
});
});
return builder;
}
}

8
src/Avalonia.Native/WindowImpl.cs

@ -3,13 +3,14 @@
using System;
using Avalonia.Controls;
using Avalonia.Controls.Platform;
using Avalonia.Native.Interop;
using Avalonia.Platform;
using Avalonia.Platform.Interop;
namespace Avalonia.Native
{
public class WindowImpl : WindowBaseImpl, IWindowImpl
public class WindowImpl : WindowBaseImpl, IWindowImpl, ITopLevelImplWithNativeMenuExporter
{
private readonly IAvaloniaNativeFactory _factory;
private readonly AvaloniaNativePlatformOptions _opts;
@ -22,6 +23,8 @@ namespace Avalonia.Native
{
Init(_native = factory.CreateWindow(e), factory.CreateScreens());
}
NativeMenuExporter = new AvaloniaNativeMenuExporter(_native, factory);
}
class WindowEvents : WindowBaseEvents, IAvnWindowEvents
@ -104,6 +107,9 @@ namespace Avalonia.Native
}
public Func<bool> Closing { get; set; }
public ITopLevelNativeMenuExporter NativeMenuExporter { get; }
public void Move(PixelPoint point) => Position = point;
public override IPopupImpl CreatePopup() =>

2
src/Avalonia.Styling/StyledElement.cs

@ -73,7 +73,7 @@ namespace Avalonia
/// </summary>
static StyledElement()
{
DataContextProperty.Changed.AddClassHandler<StyledElement>(x => x.OnDataContextChangedCore);
DataContextProperty.Changed.AddClassHandler<StyledElement>((x,e) => x.OnDataContextChangedCore(e));
}
/// <summary>

1
src/Avalonia.Themes.Default/DefaultTheme.xaml

@ -51,4 +51,5 @@
<StyleInclude Source="resm:Avalonia.Themes.Default.AutoCompleteBox.xaml?assembly=Avalonia.Themes.Default"/>
<StyleInclude Source="resm:Avalonia.Themes.Default.WindowNotificationManager.xaml?assembly=Avalonia.Themes.Default"/>
<StyleInclude Source="resm:Avalonia.Themes.Default.NotificationCard.xaml?assembly=Avalonia.Themes.Default"/>
<StyleInclude Source="resm:Avalonia.Themes.Default.NativeMenuBar.xaml?assembly=Avalonia.Themes.Default"/>
</Styles>

20
src/Avalonia.Themes.Default/InverseBooleanValueConverter.cs

@ -0,0 +1,20 @@
using System;
using System.Globalization;
using Avalonia.Data.Converters;
namespace Avalonia.Themes.Default
{
class InverseBooleanValueConverter : IValueConverter
{
public bool Default { get; set; }
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
return value is bool b ? !b : Default;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
return value is bool b ? !b : !Default;
}
}
}

25
src/Avalonia.Themes.Default/NativeMenuBar.xaml

@ -0,0 +1,25 @@
<Style xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:Avalonia.Themes.Default"
Selector="NativeMenuBar">
<Style.Resources>
<local:InverseBooleanValueConverter x:Key="AvaloniaThemesDefaultNativeMenuBarInverseBooleanValueConverter" Default="True"/>
</Style.Resources>
<Setter Property="Template">
<ControlTemplate>
<Menu
IsVisible="{Binding $parent[TopLevel].(NativeMenu.IsNativeMenuExported), Converter={StaticResource AvaloniaThemesDefaultNativeMenuBarInverseBooleanValueConverter}}"
Items="{Binding $parent[TopLevel].(NativeMenu.Menu).Items}">
<Menu.Styles>
<Style Selector="MenuItem">
<Setter Property="Header" Value="{Binding Header}"/>
<Setter Property="Items" Value="{Binding Menu.Items}"/>
<Setter Property="Command" Value="{Binding Command}"/>
<Setter Property="CommandParameter" Value="{Binding CommandParameter}"/>
<Setter Property="(NativeMenuBar.EnableMenuItemClickForwarding)" Value="True"/>
</Style>
</Menu.Styles>
</Menu>
</ControlTemplate>
</Setter>
</Style>

71
src/Avalonia.Visuals/CornerRadius.cs

@ -8,7 +8,10 @@ using Avalonia.Utilities;
namespace Avalonia
{
public struct CornerRadius
/// <summary>
/// Represents the radii of a rectangle's corners.
/// </summary>
public readonly struct CornerRadius : IEquatable<CornerRadius>
{
static CornerRadius()
{
@ -33,22 +36,59 @@ namespace Avalonia
BottomLeft = bottomLeft;
}
/// <summary>
/// Radius of the top left corner.
/// </summary>
public double TopLeft { get; }
/// <summary>
/// Radius of the top right corner.
/// </summary>
public double TopRight { get; }
/// <summary>
/// Radius of the bottom right corner.
/// </summary>
public double BottomRight { get; }
/// <summary>
/// Radius of the bottom left corner.
/// </summary>
public double BottomLeft { get; }
/// <summary>
/// Gets a value indicating whether all corner radii are set to 0.
/// </summary>
public bool IsEmpty => TopLeft.Equals(0) && IsUniform;
/// <summary>
/// Gets a value indicating whether all corner radii are equal.
/// </summary>
public bool IsUniform => TopLeft.Equals(TopRight) && BottomLeft.Equals(BottomRight) && TopRight.Equals(BottomRight);
public override bool Equals(object obj)
/// <summary>
/// Returns a boolean indicating whether the corner radius is equal to the other given corner radius.
/// </summary>
/// <param name="other">The other corner radius to test equality against.</param>
/// <returns>True if this corner radius is equal to other; False otherwise.</returns>
public bool Equals(CornerRadius other)
{
if (obj is CornerRadius)
{
return this == (CornerRadius)obj;
}
return false;
// ReSharper disable CompareOfFloatsByEqualityOperator
return TopLeft == other.TopLeft &&
TopRight == other.TopRight &&
BottomRight == other.BottomRight &&
BottomLeft == other.BottomLeft;
// ReSharper restore CompareOfFloatsByEqualityOperator
}
/// <summary>
/// Returns a boolean indicating whether the given Object is equal to this corner radius instance.
/// </summary>
/// <param name="obj">The Object to compare against.</param>
/// <returns>True if the Object is equal to this corner radius; False otherwise.</returns>
public override bool Equals(object obj) => obj is CornerRadius other && Equals(other);
public override int GetHashCode()
{
return TopLeft.GetHashCode() ^ TopRight.GetHashCode() ^ BottomLeft.GetHashCode() ^ BottomRight.GetHashCode();
@ -61,7 +101,9 @@ namespace Avalonia
public static CornerRadius Parse(string s)
{
using (var tokenizer = new StringTokenizer(s, CultureInfo.InvariantCulture, exceptionMessage: "Invalid Thickness"))
const string exceptionMessage = "Invalid CornerRadius.";
using (var tokenizer = new StringTokenizer(s, CultureInfo.InvariantCulture, exceptionMessage))
{
if (tokenizer.TryReadDouble(out var a))
{
@ -78,21 +120,18 @@ namespace Avalonia
return new CornerRadius(a);
}
throw new FormatException("Invalid CornerRadius.");
throw new FormatException(exceptionMessage);
}
}
public static bool operator ==(CornerRadius cr1, CornerRadius cr2)
public static bool operator ==(CornerRadius left, CornerRadius right)
{
return cr1.TopLeft.Equals(cr2.TopLeft)
&& cr1.TopRight.Equals(cr2.TopRight)
&& cr1.BottomRight.Equals(cr2.BottomRight)
&& cr1.BottomLeft.Equals(cr2.BottomLeft);
return left.Equals(right);
}
public static bool operator !=(CornerRadius cr1, CornerRadius cr2)
public static bool operator !=(CornerRadius left, CornerRadius right)
{
return !(cr1 == cr2);
return !(left == right);
}
}
}

16
src/Avalonia.Visuals/Matrix.cs

@ -10,7 +10,7 @@ namespace Avalonia
/// <summary>
/// A 2x3 matrix.
/// </summary>
public readonly struct Matrix
public readonly struct Matrix : IEquatable<Matrix>
{
private readonly double _m11;
private readonly double _m12;
@ -235,12 +235,14 @@ namespace Avalonia
/// <returns>True if this matrix is equal to other; False otherwise.</returns>
public bool Equals(Matrix other)
{
// ReSharper disable CompareOfFloatsByEqualityOperator
return _m11 == other.M11 &&
_m12 == other.M12 &&
_m21 == other.M21 &&
_m22 == other.M22 &&
_m31 == other.M31 &&
_m32 == other.M32;
// ReSharper restore CompareOfFloatsByEqualityOperator
}
/// <summary>
@ -248,15 +250,7 @@ namespace Avalonia
/// </summary>
/// <param name="obj">The Object to compare against.</param>
/// <returns>True if the Object is equal to this matrix; False otherwise.</returns>
public override bool Equals(object obj)
{
if (!(obj is Matrix))
{
return false;
}
return Equals((Matrix)obj);
}
public override bool Equals(object obj) => obj is Matrix other && Equals(other);
/// <summary>
/// Returns the hash code for this instance.
@ -316,7 +310,7 @@ namespace Avalonia
/// <returns>The <see cref="Matrix"/>.</returns>
public static Matrix Parse(string s)
{
using (var tokenizer = new StringTokenizer(s, CultureInfo.InvariantCulture, exceptionMessage: "Invalid Matrix"))
using (var tokenizer = new StringTokenizer(s, CultureInfo.InvariantCulture, exceptionMessage: "Invalid Matrix."))
{
return new Matrix(
tokenizer.ReadDouble(),

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

@ -22,7 +22,7 @@ namespace Avalonia.Media
static Geometry()
{
TransformProperty.Changed.AddClassHandler<Geometry>(x => x.TransformChanged);
TransformProperty.Changed.AddClassHandler<Geometry>((x,e) => x.TransformChanged(e));
}
/// <summary>

28
src/Avalonia.Visuals/Media/PixelPoint.cs

@ -10,7 +10,7 @@ namespace Avalonia
/// <summary>
/// Represents a point in device pixels.
/// </summary>
public readonly struct PixelPoint
public readonly struct PixelPoint : IEquatable<PixelPoint>
{
/// <summary>
/// A point representing 0,0.
@ -46,7 +46,7 @@ namespace Avalonia
/// <returns>True if the points are equal; otherwise false.</returns>
public static bool operator ==(PixelPoint left, PixelPoint right)
{
return left.X == right.X && left.Y == right.Y;
return left.Equals(right);
}
/// <summary>
@ -120,7 +120,7 @@ namespace Avalonia
/// <returns>The <see cref="PixelPoint"/>.</returns>
public static PixelPoint Parse(string s)
{
using (var tokenizer = new StringTokenizer(s, CultureInfo.InvariantCulture, exceptionMessage: "Invalid PixelPoint"))
using (var tokenizer = new StringTokenizer(s, CultureInfo.InvariantCulture, exceptionMessage: "Invalid PixelPoint."))
{
return new PixelPoint(
tokenizer.ReadInt32(),
@ -128,6 +128,18 @@ namespace Avalonia
}
}
/// <summary>
/// Returns a boolean indicating whether the point is equal to the other given point.
/// </summary>
/// <param name="other">The other point to test equality against.</param>
/// <returns>True if this point is equal to other; False otherwise.</returns>
public bool Equals(PixelPoint other)
{
// ReSharper disable CompareOfFloatsByEqualityOperator
return X == other.X && Y == other.Y;
// ReSharper restore CompareOfFloatsByEqualityOperator
}
/// <summary>
/// Checks for equality between a point and an object.
/// </summary>
@ -135,15 +147,7 @@ namespace Avalonia
/// <returns>
/// True if <paramref name="obj"/> is a point that equals the current point.
/// </returns>
public override bool Equals(object obj)
{
if (obj is PixelPoint other)
{
return this == other;
}
return false;
}
public override bool Equals(object obj) => obj is PixelPoint other && Equals(other);
/// <summary>
/// Returns a hash code for a <see cref="PixelPoint"/>.

26
src/Avalonia.Visuals/Media/PixelRect.cs

@ -10,7 +10,7 @@ namespace Avalonia
/// <summary>
/// Represents a rectangle in device pixels.
/// </summary>
public readonly struct PixelRect
public readonly struct PixelRect : IEquatable<PixelRect>
{
/// <summary>
/// An empty rectangle.
@ -148,7 +148,7 @@ namespace Avalonia
/// <returns>True if the rects are equal; otherwise false.</returns>
public static bool operator ==(PixelRect left, PixelRect right)
{
return left.Position == right.Position && left.Size == right.Size;
return left.Equals(right);
}
/// <summary>
@ -196,20 +196,22 @@ namespace Avalonia
rect.Height);
}
/// <summary>
/// Returns a boolean indicating whether the rect is equal to the other given rect.
/// </summary>
/// <param name="other">The other rect to test equality against.</param>
/// <returns>True if this rect is equal to other; False otherwise.</returns>
public bool Equals(PixelRect other)
{
return Position == other.Position && Size == other.Size;
}
/// <summary>
/// Returns a boolean indicating whether the given object is equal to this rectangle.
/// </summary>
/// <param name="obj">The object to compare against.</param>
/// <returns>True if the object is equal to this rectangle; false otherwise.</returns>
public override bool Equals(object obj)
{
if (obj is PixelRect other)
{
return this == other;
}
return false;
}
public override bool Equals(object obj) => obj is PixelRect other && Equals(other);
/// <summary>
/// Returns the hash code for this instance.
@ -432,7 +434,7 @@ namespace Avalonia
/// <returns>The parsed <see cref="PixelRect"/>.</returns>
public static PixelRect Parse(string s)
{
using (var tokenizer = new StringTokenizer(s, CultureInfo.InvariantCulture, exceptionMessage: "Invalid PixelRect"))
using (var tokenizer = new StringTokenizer(s, CultureInfo.InvariantCulture, exceptionMessage: "Invalid PixelRect."))
{
return new PixelRect(
tokenizer.ReadInt32(),

26
src/Avalonia.Visuals/Media/PixelSize.cs

@ -10,7 +10,7 @@ namespace Avalonia
/// <summary>
/// Represents a size in device pixels.
/// </summary>
public readonly struct PixelSize
public readonly struct PixelSize : IEquatable<PixelSize>
{
/// <summary>
/// A size representing zero
@ -51,7 +51,7 @@ namespace Avalonia
/// <returns>True if the sizes are equal; otherwise false.</returns>
public static bool operator ==(PixelSize left, PixelSize right)
{
return left.Width == right.Width && left.Height == right.Height;
return left.Equals(right);
}
/// <summary>
@ -72,7 +72,7 @@ namespace Avalonia
/// <returns>The <see cref="PixelSize"/>.</returns>
public static PixelSize Parse(string s)
{
using (var tokenizer = new StringTokenizer(s, CultureInfo.InvariantCulture, exceptionMessage: "Invalid PixelSize"))
using (var tokenizer = new StringTokenizer(s, CultureInfo.InvariantCulture, exceptionMessage: "Invalid PixelSize."))
{
return new PixelSize(
tokenizer.ReadInt32(),
@ -80,6 +80,16 @@ namespace Avalonia
}
}
/// <summary>
/// Returns a boolean indicating whether the size is equal to the other given size.
/// </summary>
/// <param name="other">The other size to test equality against.</param>
/// <returns>True if this size is equal to other; False otherwise.</returns>
public bool Equals(PixelSize other)
{
return Width == other.Width && Height == other.Height;
}
/// <summary>
/// Checks for equality between a size and an object.
/// </summary>
@ -87,15 +97,7 @@ namespace Avalonia
/// <returns>
/// True if <paramref name="obj"/> is a size that equals the current size.
/// </returns>
public override bool Equals(object obj)
{
if (obj is PixelSize other)
{
return this == other;
}
return false;
}
public override bool Equals(object obj) => obj is PixelSize other && Equals(other);
/// <summary>
/// Returns a hash code for a <see cref="PixelSize"/>.

31
src/Avalonia.Visuals/Point.cs

@ -1,6 +1,7 @@
// Copyright (c) The Avalonia Project. All rights reserved.
// Licensed under the MIT license. See licence.md file in the project root for full license information.
using System;
using System.Globalization;
using Avalonia.Animation.Animators;
using Avalonia.Utilities;
@ -10,7 +11,7 @@ namespace Avalonia
/// <summary>
/// Defines a point.
/// </summary>
public readonly struct Point
public readonly struct Point : IEquatable<Point>
{
static Point()
{
@ -75,7 +76,7 @@ namespace Avalonia
/// <returns>True if the points are equal; otherwise false.</returns>
public static bool operator ==(Point left, Point right)
{
return left.X == right.X && left.Y == right.Y;
return left.Equals(right);
}
/// <summary>
@ -177,7 +178,7 @@ namespace Avalonia
/// <returns>The <see cref="Thickness"/>.</returns>
public static Point Parse(string s)
{
using (var tokenizer = new StringTokenizer(s, CultureInfo.InvariantCulture, exceptionMessage: "Invalid Point"))
using (var tokenizer = new StringTokenizer(s, CultureInfo.InvariantCulture, exceptionMessage: "Invalid Point."))
{
return new Point(
tokenizer.ReadDouble(),
@ -186,6 +187,19 @@ namespace Avalonia
}
}
/// <summary>
/// Returns a boolean indicating whether the point is equal to the other given point.
/// </summary>
/// <param name="other">The other point to test equality against.</param>
/// <returns>True if this point is equal to other; False otherwise.</returns>
public bool Equals(Point other)
{
// ReSharper disable CompareOfFloatsByEqualityOperator
return _x == other._x &&
_y == other._y;
// ReSharper enable CompareOfFloatsByEqualityOperator
}
/// <summary>
/// Checks for equality between a point and an object.
/// </summary>
@ -193,16 +207,7 @@ namespace Avalonia
/// <returns>
/// True if <paramref name="obj"/> is a point that equals the current point.
/// </returns>
public override bool Equals(object obj)
{
if (obj is Point)
{
var other = (Point)obj;
return X == other.X && Y == other.Y;
}
return false;
}
public override bool Equals(object obj) => obj is Point other && Equals(other);
/// <summary>
/// Returns a hash code for a <see cref="Point"/>.

42
src/Avalonia.Visuals/Rect.cs

@ -11,7 +11,7 @@ namespace Avalonia
/// <summary>
/// Defines a rectangle.
/// </summary>
public readonly struct Rect
public readonly struct Rect : IEquatable<Rect>
{
static Rect()
{
@ -164,7 +164,9 @@ namespace Avalonia
/// <summary>
/// Gets a value that indicates whether the rectangle is empty.
/// </summary>
// ReSharper disable CompareOfFloatsByEqualityOperator
public bool IsEmpty => _width == 0 && _height == 0;
// ReSharper restore CompareOfFloatsByEqualityOperator
/// <summary>
/// Checks for equality between two <see cref="Rect"/>s.
@ -174,7 +176,7 @@ namespace Avalonia
/// <returns>True if the rects are equal; otherwise false.</returns>
public static bool operator ==(Rect left, Rect right)
{
return left.Position == right.Position && left.Size == right.Size;
return left.Equals(right);
}
/// <summary>
@ -297,21 +299,27 @@ namespace Avalonia
Size.Deflate(thickness));
}
/// <summary>
/// Returns a boolean indicating whether the rect is equal to the other given rect.
/// </summary>
/// <param name="other">The other rect to test equality against.</param>
/// <returns>True if this rect is equal to other; False otherwise.</returns>
public bool Equals(Rect other)
{
// ReSharper disable CompareOfFloatsByEqualityOperator
return _x == other._x &&
_y == other._y &&
_width == other._width &&
_height == other._height;
// ReSharper enable CompareOfFloatsByEqualityOperator
}
/// <summary>
/// Returns a boolean indicating whether the given object is equal to this rectangle.
/// </summary>
/// <param name="obj">The object to compare against.</param>
/// <returns>True if the object is equal to this rectangle; false otherwise.</returns>
public override bool Equals(object obj)
{
if (obj is Rect)
{
var other = (Rect)obj;
return Position == other.Position && Size == other.Size;
}
return false;
}
public override bool Equals(object obj) => obj is Rect other && Equals(other);
/// <summary>
/// Returns the hash code for this instance.
@ -422,10 +430,10 @@ namespace Avalonia
}
else
{
var x1 = Math.Min(this.X, rect.X);
var x2 = Math.Max(this.Right, rect.Right);
var y1 = Math.Min(this.Y, rect.Y);
var y2 = Math.Max(this.Bottom, rect.Bottom);
var x1 = Math.Min(X, rect.X);
var x2 = Math.Max(Right, rect.Right);
var y1 = Math.Min(Y, rect.Y);
var y2 = Math.Max(Bottom, rect.Bottom);
return new Rect(new Point(x1, y1), new Point(x2, y2));
}
@ -493,7 +501,7 @@ namespace Avalonia
/// <returns>The parsed <see cref="Rect"/>.</returns>
public static Rect Parse(string s)
{
using (var tokenizer = new StringTokenizer(s, CultureInfo.InvariantCulture, exceptionMessage: "Invalid Rect"))
using (var tokenizer = new StringTokenizer(s, CultureInfo.InvariantCulture, exceptionMessage: "Invalid Rect."))
{
return new Rect(
tokenizer.ReadDouble(),

12
src/Avalonia.Visuals/RelativePoint.cs

@ -107,10 +107,7 @@ namespace Avalonia
/// </summary>
/// <param name="obj">The other object.</param>
/// <returns>True if the objects are equal, otherwise false.</returns>
public override bool Equals(object obj)
{
return (obj is RelativePoint) && Equals((RelativePoint)obj);
}
public override bool Equals(object obj) => obj is RelativePoint other && Equals(other);
/// <summary>
/// Checks if the <see cref="RelativePoint"/> equals another point.
@ -130,10 +127,7 @@ namespace Avalonia
{
unchecked
{
int hash = 17;
hash = (hash * 23) + Unit.GetHashCode();
hash = (hash * 23) + Point.GetHashCode();
return hash;
return (_point.GetHashCode() * 397) ^ (int)_unit;
}
}
@ -156,7 +150,7 @@ namespace Avalonia
/// <returns>The parsed <see cref="RelativePoint"/>.</returns>
public static RelativePoint Parse(string s)
{
using (var tokenizer = new StringTokenizer(s, CultureInfo.InvariantCulture, exceptionMessage: "Invalid RelativePoint"))
using (var tokenizer = new StringTokenizer(s, CultureInfo.InvariantCulture, exceptionMessage: "Invalid RelativePoint."))
{
var x = tokenizer.ReadString();
var y = tokenizer.ReadString();

14
src/Avalonia.Visuals/RelativeRect.cs

@ -116,10 +116,7 @@ namespace Avalonia
/// </summary>
/// <param name="obj">The other object.</param>
/// <returns>True if the objects are equal, otherwise false.</returns>
public override bool Equals(object obj)
{
return (obj is RelativeRect) && Equals((RelativeRect)obj);
}
public override bool Equals(object obj) => obj is RelativeRect other && Equals(other);
/// <summary>
/// Checks if the <see cref="RelativeRect"/> equals another rectangle.
@ -139,10 +136,7 @@ namespace Avalonia
{
unchecked
{
int hash = 17;
hash = (hash * 23) + Unit.GetHashCode();
hash = (hash * 23) + Rect.GetHashCode();
return hash;
return ((int)Unit * 397) ^ Rect.GetHashCode();
}
}
@ -161,7 +155,7 @@ namespace Avalonia
Rect.Width * size.Width,
Rect.Height * size.Height);
}
/// <summary>
/// Parses a <see cref="RelativeRect"/> string.
/// </summary>
@ -169,7 +163,7 @@ namespace Avalonia
/// <returns>The parsed <see cref="RelativeRect"/>.</returns>
public static RelativeRect Parse(string s)
{
using (var tokenizer = new StringTokenizer(s, exceptionMessage: "Invalid RelativeRect"))
using (var tokenizer = new StringTokenizer(s, exceptionMessage: "Invalid RelativeRect."))
{
var x = tokenizer.ReadString();
var y = tokenizer.ReadString();

30
src/Avalonia.Visuals/Size.cs

@ -11,7 +11,7 @@ namespace Avalonia
/// <summary>
/// Defines a size.
/// </summary>
public readonly struct Size
public readonly struct Size : IEquatable<Size>
{
static Size()
{
@ -72,7 +72,7 @@ namespace Avalonia
/// <returns>True if the sizes are equal; otherwise false.</returns>
public static bool operator ==(Size left, Size right)
{
return left._width == right._width && left._height == right._height;
return left.Equals(right);
}
/// <summary>
@ -158,7 +158,7 @@ namespace Avalonia
/// <returns>The <see cref="Size"/>.</returns>
public static Size Parse(string s)
{
using (var tokenizer = new StringTokenizer(s, CultureInfo.InvariantCulture, exceptionMessage: "Invalid Size"))
using (var tokenizer = new StringTokenizer(s, CultureInfo.InvariantCulture, exceptionMessage: "Invalid Size."))
{
return new Size(
tokenizer.ReadDouble(),
@ -191,6 +191,19 @@ namespace Avalonia
Math.Max(0, _height - thickness.Top - thickness.Bottom));
}
/// <summary>
/// Returns a boolean indicating whether the size is equal to the other given size.
/// </summary>
/// <param name="other">The other size to test equality against.</param>
/// <returns>True if this size is equal to other; False otherwise.</returns>
public bool Equals(Size other)
{
// ReSharper disable CompareOfFloatsByEqualityOperator
return _width == other._width &&
_height == other._height;
// ReSharper enable CompareOfFloatsByEqualityOperator
}
/// <summary>
/// Checks for equality between a size and an object.
/// </summary>
@ -198,16 +211,7 @@ namespace Avalonia
/// <returns>
/// True if <paramref name="obj"/> is a size that equals the current size.
/// </returns>
public override bool Equals(object obj)
{
if (obj is Size)
{
var other = (Size)obj;
return Width == other.Width && Height == other.Height;
}
return false;
}
public override bool Equals(object obj) => obj is Size other && Equals(other);
/// <summary>
/// Returns a hash code for a <see cref="Size"/>.

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

Loading…
Cancel
Save