diff --git a/Avalonia.sln.DotSettings b/Avalonia.sln.DotSettings index 1361172fff..7060f4a62a 100644 --- a/Avalonia.sln.DotSettings +++ b/Avalonia.sln.DotSettings @@ -3,6 +3,7 @@ ExplicitlyExcluded ExplicitlyExcluded ExplicitlyExcluded + DO_NOT_SHOW HINT <Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /> <Policy Inspect="True" Prefix="" Suffix="" Style="aa_bb" /> diff --git a/build/Microsoft.Reactive.Testing.props b/build/Microsoft.Reactive.Testing.props index 777bc4bb53..a0ba2163bb 100644 --- a/build/Microsoft.Reactive.Testing.props +++ b/build/Microsoft.Reactive.Testing.props @@ -1,5 +1,5 @@  - + diff --git a/build/ReactiveUI.props b/build/ReactiveUI.props index 1208be34b8..f827cb9a32 100644 --- a/build/ReactiveUI.props +++ b/build/ReactiveUI.props @@ -1,5 +1,5 @@ - + diff --git a/build/Rx.props b/build/Rx.props index 359ce53a92..edff0af160 100644 --- a/build/Rx.props +++ b/build/Rx.props @@ -1,5 +1,5 @@  - + diff --git a/build/System.Memory.props b/build/System.Memory.props index b328f7fd97..b36998a780 100644 --- a/build/System.Memory.props +++ b/build/System.Memory.props @@ -1,5 +1,5 @@ - + diff --git a/dirs.proj b/dirs.proj index e56320e73f..4b3b1183f0 100644 --- a/dirs.proj +++ b/dirs.proj @@ -8,7 +8,8 @@ - + + diff --git a/global.json b/global.json index 6d12c28846..1e599211d4 100644 --- a/global.json +++ b/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" } } diff --git a/native/Avalonia.Native/inc/avalonia-native.h b/native/Avalonia.Native/inc/avalonia-native.h index 36e16c24d1..f1c7664c3e 100644 --- a/native/Avalonia.Native/inc/avalonia-native.h +++ b/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 { @@ -52,6 +54,7 @@ struct AvnScreen { AvnRect Bounds; AvnRect WorkingArea; + float PixelDensity; bool Primary; }; @@ -172,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 @@ -187,7 +195,6 @@ AVNCOM(IAvnWindowBase, 02) : IUnknown virtual HRESULT Close() = 0; virtual HRESULT Activate () = 0; virtual HRESULT GetClientSize(AvnSize*ret) = 0; - virtual HRESULT GetMaxClientSize(AvnSize* ret) = 0; virtual HRESULT GetScaling(double*ret)=0; virtual HRESULT SetMinMaxSize(AvnSize minSize, AvnSize maxSize) = 0; virtual HRESULT Resize(double width, double height) = 0; @@ -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(); diff --git a/native/Avalonia.Native/src/OSX/Avalonia.Native.OSX.xcodeproj/project.pbxproj b/native/Avalonia.Native/src/OSX/Avalonia.Native.OSX.xcodeproj/project.pbxproj index 1870ef7ab3..c0a49382a7 100644 --- a/native/Avalonia.Native/src/OSX/Avalonia.Native.OSX.xcodeproj/project.pbxproj +++ b/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 = ""; }; + 37155CE3233C00EB0034DCE9 /* menu.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = menu.h; sourceTree = ""; }; 379860FE214DA0C000CD0246 /* KeyTransform.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = KeyTransform.h; sourceTree = ""; }; 37A4E71A2178846A00EACBCD /* headers */ = {isa = PBXFileReference; lastKnownFileType = folder; name = headers; path = ../../inc; sourceTree = ""; }; 37A517B22159597E00FBA241 /* Screens.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = Screens.mm; sourceTree = ""; }; @@ -32,6 +35,7 @@ 37DDA9AF219330F8002E132B /* AvnString.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = AvnString.mm; sourceTree = ""; }; 37DDA9B121933371002E132B /* AvnString.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = AvnString.h; sourceTree = ""; }; 37E2330E21583241000CB7E2 /* KeyTransform.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = KeyTransform.mm; sourceTree = ""; }; + 520624B222973F4100C4DCEF /* menu.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = menu.mm; sourceTree = ""; }; 5B21A981216530F500CEE36E /* cursor.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = cursor.mm; sourceTree = ""; }; 5B8BD94E215BFEA6005ED2A7 /* clipboard.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = clipboard.mm; sourceTree = ""; }; 5BF943652167AD1D009CAE35 /* cursor.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = cursor.h; sourceTree = ""; }; @@ -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 */, diff --git a/native/Avalonia.Native/src/OSX/Avalonia.Native.OSX.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/native/Avalonia.Native/src/OSX/Avalonia.Native.OSX.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 0000000000..18d981003d --- /dev/null +++ b/native/Avalonia.Native/src/OSX/Avalonia.Native.OSX.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/native/Avalonia.Native/src/OSX/Screens.mm b/native/Avalonia.Native/src/OSX/Screens.mm index 9d436b98c5..e7f009787a 100644 --- a/native/Avalonia.Native/src/OSX/Screens.mm +++ b/native/Avalonia.Native/src/OSX/Screens.mm @@ -38,6 +38,8 @@ class Screens : public ComSingleObject ret->WorkingArea.Height = [screen visibleFrame].size.height; ret->WorkingArea.Width = [screen visibleFrame].size.width; + ret->PixelDensity = [screen backingScaleFactor]; + ret->Primary = index == 0; return S_OK; diff --git a/native/Avalonia.Native/src/OSX/common.h b/native/Avalonia.Native/src/OSX/common.h index 45ec40c361..10534dea26 100644 --- a/native/Avalonia.Native/src/OSX/common.h +++ b/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 inline T* objc_cast(id from) { return nil; } +@interface ActionCallback : NSObject +- (ActionCallback*) initWithCallback: (IAvnActionCallback*) callback; +- (void) action; +@end + #endif diff --git a/native/Avalonia.Native/src/OSX/main.mm b/native/Avalonia.Native/src/OSX/main.mm index 70bd1e67f6..9418782fd1 100644 --- a/native/Avalonia.Native/src/OSX/main.mm +++ b/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( + 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( + CFBundleGetFunctionPointerForName( + launch_services_bundle, + CFSTR("_LSSetApplicationInformationItem"))); + if (!ls_set_application_information_item_func){} + //LOG(ERROR) << "Could not find _LSSetApplicationInformationItem"; + + CFStringRef* key_pointer = reinterpret_cast( + 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 { 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() diff --git a/native/Avalonia.Native/src/OSX/menu.h b/native/Avalonia.Native/src/OSX/menu.h new file mode 100644 index 0000000000..befbe6a7e0 --- /dev/null +++ b/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 +{ +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 +{ +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 + diff --git a/native/Avalonia.Native/src/OSX/menu.mm b/native/Avalonia.Native/src/OSX/menu.mm new file mode 100644 index 0000000000..d9dfe36444 --- /dev/null +++ b/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(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(item); + + if(avnMenuItem != nullptr) + { + [_native addItem: avnMenuItem->GetNative()]; + } + + return S_OK; +} + +HRESULT AvnAppMenu::RemoveItem (IAvnAppMenuItem* item) +{ + auto avnMenuItem = dynamic_cast(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(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; +} + + diff --git a/native/Avalonia.Native/src/OSX/platformthreading.mm b/native/Avalonia.Native/src/OSX/platformthreading.mm index 297097584a..e7abedae51 100644 --- a/native/Avalonia.Native/src/OSX/platformthreading.mm +++ b/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 _callback; diff --git a/native/Avalonia.Native/src/OSX/window.h b/native/Avalonia.Native/src/OSX/window.h index e2221217f3..557e19e7a8 100644 --- a/native/Avalonia.Native/src/OSX/window.h +++ b/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 diff --git a/native/Avalonia.Native/src/OSX/window.mm b/native/Avalonia.Native/src/OSX/window.mm index bfa33eb259..dbb437243a 100644 --- a/native/Avalonia.Native/src/OSX/window.mm +++ b/native/Avalonia.Native/src/OSX/window.mm @@ -5,6 +5,7 @@ #include "window.h" #include "KeyTransform.h" #include "cursor.h" +#include "menu.h" #include class SoftwareDrawingOperation @@ -54,7 +55,6 @@ public: FORWARD_IUNKNOWN() virtual ~WindowBaseImpl() { - NSDebugLog(@"~WindowBaseImpl()"); View = NULL; Window = NULL; } @@ -64,9 +64,11 @@ public: SoftwareDrawingOperation CurrentSwDrawingOperation; AvnPoint lastPositionSet; NSString* _lastTitle; + IAvnAppMenu* _mainMenu; WindowBaseImpl(IAvnWindowBaseEvents* events) { + _mainMenu = nullptr; BaseEvents = events; View = [[AvnView alloc] initWithParent:this]; @@ -94,6 +96,7 @@ public: UpdateStyle(); [Window makeKeyAndOrderFront:Window]; + [NSApp activateIgnoringOtherApps:YES]; [Window setTitle:_lastTitle]; [Window setTitleVisibility:NSWindowTitleVisible]; @@ -123,6 +126,7 @@ public: if(Window != nullptr) { [Window makeKeyWindow]; + [NSApp activateIgnoringOtherApps:YES]; } } @@ -161,22 +165,6 @@ public: } } - virtual HRESULT GetMaxClientSize(AvnSize* ret) override - { - @autoreleasepool - { - if(ret == nullptr) - return E_POINTER; - - auto size = [NSScreen.screens objectAtIndex:0].frame.size; - - ret->Height = size.height; - ret->Width = size.width; - - return S_OK; - } - } - virtual HRESULT GetScaling (double* ret) override { @autoreleasepool @@ -226,6 +214,31 @@ public: } } + virtual HRESULT SetMainMenu(IAvnAppMenu* menu) override + { + _mainMenu = menu; + + auto nativeMenu = dynamic_cast(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 @@ -369,12 +382,9 @@ public: virtual void UpdateCursor() { - [View resetCursorRects]; if (cursor != nil) { - auto rect = [Window frame]; - [View addCursorRect:rect cursor:cursor]; - [cursor set]; + [cursor set]; } } @@ -416,8 +426,8 @@ private: INHERIT_INTERFACE_MAP(WindowBaseImpl) INTERFACE_MAP_ENTRY(IAvnWindow, IID_IAvnWindow) END_INTERFACE_MAP() - virtual ~WindowImpl(){ - NSDebugLog(@"~WindowImpl"); + virtual ~WindowImpl() + { } ComPtr WindowEvents; @@ -425,6 +435,7 @@ private: { WindowEvents = events; [Window setCanBecomeKeyAndMain]; + [Window disableCursorRects]; } virtual HRESULT Show () override @@ -664,10 +675,8 @@ NSArray* AllLoopModes = [NSArray arrayWithObjects: NSDefaultRunLoopMode, NSEvent - (void)dealloc { - NSDebugLog(@"AvnView dealloc"); } - - (void)onClosed { _parent = NULL; @@ -1063,11 +1072,12 @@ NSArray* AllLoopModes = [NSArray arrayWithObjects: NSDefaultRunLoopMode, NSEvent ComPtr _parent; bool _canBecomeKeyAndMain; bool _closed; + NSMenu* _menu; + bool _isAppMenuApplied; } - (void)dealloc { - NSDebugLog(@"AvnWindow dealloc"); } - (void)pollModalSession:(nonnull NSModalSession)session @@ -1087,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; @@ -1179,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]; } @@ -1223,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(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]; } diff --git a/samples/BindingDemo/MainWindow.xaml b/samples/BindingDemo/MainWindow.xaml index aa598cf82d..735e2d7102 100644 --- a/samples/BindingDemo/MainWindow.xaml +++ b/samples/BindingDemo/MainWindow.xaml @@ -25,6 +25,9 @@ + diff --git a/samples/BindingDemo/ViewModels/MainWindowViewModel.cs b/samples/BindingDemo/ViewModels/MainWindowViewModel.cs index 881799a4c4..300c696c34 100644 --- a/samples/BindingDemo/ViewModels/MainWindowViewModel.cs +++ b/samples/BindingDemo/ViewModels/MainWindowViewModel.cs @@ -1,10 +1,11 @@ using System; using System.Collections.ObjectModel; using System.Linq; -using ReactiveUI; +using System.Reactive; using System.Reactive.Linq; using System.Threading.Tasks; using System.Threading; +using ReactiveUI; namespace BindingDemo.ViewModels { @@ -56,7 +57,7 @@ namespace BindingDemo.ViewModels public ObservableCollection Items { get; } public ObservableCollection SelectedItems { get; } - public ReactiveCommand ShuffleItems { get; } + public ReactiveCommand ShuffleItems { get; } public string BooleanString { @@ -89,7 +90,7 @@ namespace BindingDemo.ViewModels } public IObservable CurrentTimeObservable { get; } - public ReactiveCommand StringValueCommand { get; } + public ReactiveCommand StringValueCommand { get; } public DataAnnotationsErrorViewModel DataAnnotationsValidation { get; } = new DataAnnotationsErrorViewModel(); public ExceptionErrorViewModel ExceptionDataValidation { get; } = new ExceptionErrorViewModel(); diff --git a/samples/ControlCatalog.Android/ControlCatalog.Android.csproj b/samples/ControlCatalog.Android/ControlCatalog.Android.csproj index bc76a39f08..5b82e2caee 100644 --- a/samples/ControlCatalog.Android/ControlCatalog.Android.csproj +++ b/samples/ControlCatalog.Android/ControlCatalog.Android.csproj @@ -16,7 +16,7 @@ Resources\Resource.Designer.cs Off False - v8.0 + v9.0 Properties\AndroidManifest.xml diff --git a/samples/ControlCatalog.Android/MainActivity.cs b/samples/ControlCatalog.Android/MainActivity.cs index 157609088f..40d001a195 100644 --- a/samples/ControlCatalog.Android/MainActivity.cs +++ b/samples/ControlCatalog.Android/MainActivity.cs @@ -20,7 +20,7 @@ namespace ControlCatalog.Android { if (Avalonia.Application.Current == null) { - AppBuilder.Configure(new App()) + AppBuilder.Configure() .UseAndroid() .SetupWithoutStarting(); Content = new MainView(); diff --git a/samples/ControlCatalog.NetCore/Program.cs b/samples/ControlCatalog.NetCore/Program.cs index d683092edf..854cae484c 100644 --- a/samples/ControlCatalog.NetCore/Program.cs +++ b/samples/ControlCatalog.NetCore/Program.cs @@ -55,7 +55,11 @@ namespace ControlCatalog.NetCore public static AppBuilder BuildAvaloniaApp() => AppBuilder.Configure() .UsePlatformDetect() - .With(new X11PlatformOptions { EnableMultiTouch = true }) + .With(new X11PlatformOptions + { + EnableMultiTouch = true, + UseDBusMenu = true + }) .With(new Win32PlatformOptions { EnableMultitouch = true, diff --git a/samples/ControlCatalog/App.xaml b/samples/ControlCatalog/App.xaml index 2f6d25c089..335c460b40 100644 --- a/samples/ControlCatalog/App.xaml +++ b/samples/ControlCatalog/App.xaml @@ -2,9 +2,9 @@ xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" x:Class="ControlCatalog.App"> - - - + + + + + + + + + + + + + + + diff --git a/samples/ControlCatalog/App.xaml.cs b/samples/ControlCatalog/App.xaml.cs index 07c42c60c4..4fc63ea054 100644 --- a/samples/ControlCatalog/App.xaml.cs +++ b/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() diff --git a/samples/ControlCatalog/DecoratedWindow.xaml b/samples/ControlCatalog/DecoratedWindow.xaml index cb6016b324..8e4c97b7f0 100644 --- a/samples/ControlCatalog/DecoratedWindow.xaml +++ b/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"> + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/samples/ControlCatalog/MainView.xaml b/samples/ControlCatalog/MainView.xaml index c99a6b117b..874560a294 100644 --- a/samples/ControlCatalog/MainView.xaml +++ b/samples/ControlCatalog/MainView.xaml @@ -34,7 +34,8 @@ + ScrollViewer.VerticalScrollBarVisibility="Disabled" + ScrollViewer.HorizontalScrollBarVisibility="Disabled"> diff --git a/samples/ControlCatalog/MainWindow.xaml b/samples/ControlCatalog/MainWindow.xaml index 9527ac3b4e..6088f2ec57 100644 --- a/samples/ControlCatalog/MainWindow.xaml +++ b/samples/ControlCatalog/MainWindow.xaml @@ -8,7 +8,36 @@ xmlns:vm="clr-namespace:ControlCatalog.ViewModels" xmlns:v="clr-namespace:ControlCatalog.Views" x:Class="ControlCatalog.MainWindow"> - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/samples/ControlCatalog/MainWindow.xaml.cs b/samples/ControlCatalog/MainWindow.xaml.cs index 95c65ed92f..7b0ee897c4 100644 --- a/samples/ControlCatalog/MainWindow.xaml.cs +++ b/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 diff --git a/samples/ControlCatalog/Pages/ItemsRepeaterPage.xaml b/samples/ControlCatalog/Pages/ItemsRepeaterPage.xaml index d0631d2cbd..103c016298 100644 --- a/samples/ControlCatalog/Pages/ItemsRepeaterPage.xaml +++ b/samples/ControlCatalog/Pages/ItemsRepeaterPage.xaml @@ -14,12 +14,19 @@ UniformGrid - Horizontal + - + + + + + + + diff --git a/samples/ControlCatalog/Pages/ItemsRepeaterPage.xaml.cs b/samples/ControlCatalog/Pages/ItemsRepeaterPage.xaml.cs index 1a607342f3..decd849adc 100644 --- a/samples/ControlCatalog/Pages/ItemsRepeaterPage.xaml.cs +++ b/samples/ControlCatalog/Pages/ItemsRepeaterPage.xaml.cs @@ -74,7 +74,7 @@ namespace ControlCatalog.Pages private void RepeaterClick(object sender, PointerPressedEventArgs e) { - var item = (e.Source as TextBlock)?.DataContext as string; + var item = (e.Source as TextBlock)?.DataContext as ItemsRepeaterPageViewModel.Item; ((ItemsRepeaterPageViewModel)DataContext).SelectedItem = item; } } diff --git a/samples/ControlCatalog/Pages/ListBoxPage.xaml.cs b/samples/ControlCatalog/Pages/ListBoxPage.xaml.cs index cdbf8fd2b6..0f2d4461bf 100644 --- a/samples/ControlCatalog/Pages/ListBoxPage.xaml.cs +++ b/samples/ControlCatalog/Pages/ListBoxPage.xaml.cs @@ -78,7 +78,7 @@ namespace ControlCatalog.Pages } } - private string GenerateItem() => $"Item {_counter++}"; + private string GenerateItem() => $"Item {_counter++.ToString()}"; } } } diff --git a/samples/ControlCatalog/Pages/MenuPage.xaml b/samples/ControlCatalog/Pages/MenuPage.xaml index e1a5cf2c5a..868f0df6ad 100644 --- a/samples/ControlCatalog/Pages/MenuPage.xaml +++ b/samples/ControlCatalog/Pages/MenuPage.xaml @@ -3,8 +3,11 @@ x:Class="ControlCatalog.Pages.MenuPage"> Menu + Exported menu fallback + (Should be only visible on platforms without desktop-global menu bar) + A window menu - + ( - Enumerable.Range(1, 100000).Select(i => $"Item {i}")); + Items = new ObservableCollection( + Enumerable.Range(1, 100000).Select(i => new Item + { + Text = $"Item {i.ToString()}", + })); } - public ObservableCollection Items { get; } + public ObservableCollection Items { get; } - public string SelectedItem { get; set; } + public Item SelectedItem { get; set; } public void AddItem() { var index = SelectedItem != null ? Items.IndexOf(SelectedItem) : -1; - Items.Insert(index + 1, $"New Item {newItemIndex++}"); + Items.Insert(index + 1, new Item { Text = $"New Item {newItemIndex++}" }); + } + + public void RandomizeHeights() + { + var random = new Random(); + + foreach (var i in Items) + { + i.Height = random.Next(240) + 10; + } + } + + public class Item : ReactiveObject + { + private double _height = double.NaN; + + public string Text { get; set; } + + public double Height + { + get => _height; + set => this.RaiseAndSetIfChanged(ref _height, value); + } } } } diff --git a/samples/RenderDemo/ViewModels/MainWindowViewModel.cs b/samples/RenderDemo/ViewModels/MainWindowViewModel.cs index 0cb5e1b87b..d2d789a687 100644 --- a/samples/RenderDemo/ViewModels/MainWindowViewModel.cs +++ b/samples/RenderDemo/ViewModels/MainWindowViewModel.cs @@ -1,4 +1,5 @@ using System; +using System.Reactive; using ReactiveUI; namespace RenderDemo.ViewModels @@ -26,7 +27,7 @@ namespace RenderDemo.ViewModels set { this.RaiseAndSetIfChanged(ref drawFps, value); } } - public ReactiveCommand ToggleDrawDirtyRects { get; } - public ReactiveCommand ToggleDrawFps { get; } + public ReactiveCommand ToggleDrawDirtyRects { get; } + public ReactiveCommand ToggleDrawFps { get; } } } diff --git a/samples/VirtualizationDemo/ViewModels/MainWindowViewModel.cs b/samples/VirtualizationDemo/ViewModels/MainWindowViewModel.cs index 93fe09a156..649c64ab1d 100644 --- a/samples/VirtualizationDemo/ViewModels/MainWindowViewModel.cs +++ b/samples/VirtualizationDemo/ViewModels/MainWindowViewModel.cs @@ -4,10 +4,10 @@ using System; using System.Collections.Generic; using System.Linq; +using System.Reactive; using Avalonia.Collections; using Avalonia.Controls; using Avalonia.Controls.Primitives; -using ReactiveUI.Legacy; using ReactiveUI; using Avalonia.Layout; @@ -18,7 +18,7 @@ namespace VirtualizationDemo.ViewModels private int _itemCount = 200; private string _newItemString = "New Item"; private int _newItemIndex; - private IReactiveList _items; + private AvaloniaList _items; private string _prefix = "Item"; private ScrollBarVisibility _horizontalScrollBarVisibility = ScrollBarVisibility.Auto; private ScrollBarVisibility _verticalScrollBarVisibility = ScrollBarVisibility.Auto; @@ -54,7 +54,7 @@ namespace VirtualizationDemo.ViewModels public AvaloniaList SelectedItems { get; } = new AvaloniaList(); - public IReactiveList Items + public AvaloniaList Items { get { return _items; } private set { this.RaiseAndSetIfChanged(ref _items, value); } @@ -93,11 +93,11 @@ namespace VirtualizationDemo.ViewModels public IEnumerable VirtualizationModes => Enum.GetValues(typeof(ItemVirtualizationMode)).Cast(); - public ReactiveCommand AddItemCommand { get; private set; } - public ReactiveCommand RecreateCommand { get; private set; } - public ReactiveCommand RemoveItemCommand { get; private set; } - public ReactiveCommand SelectFirstCommand { get; private set; } - public ReactiveCommand SelectLastCommand { get; private set; } + public ReactiveCommand AddItemCommand { get; private set; } + public ReactiveCommand RecreateCommand { get; private set; } + public ReactiveCommand RemoveItemCommand { get; private set; } + public ReactiveCommand SelectFirstCommand { get; private set; } + public ReactiveCommand SelectLastCommand { get; private set; } public void RandomizeSize() { @@ -123,7 +123,7 @@ namespace VirtualizationDemo.ViewModels { var items = Enumerable.Range(0, count) .Select(x => new ItemViewModel(x)); - Items = new ReactiveList(items); + Items = new AvaloniaList(items); } else if (count > Items.Count) { @@ -162,7 +162,7 @@ namespace VirtualizationDemo.ViewModels _prefix = _prefix == "Item" ? "Recreated" : "Item"; var items = Enumerable.Range(0, _itemCount) .Select(x => new ItemViewModel(x, _prefix)); - Items = new ReactiveList(items); + Items = new AvaloniaList(items); } private void SelectItem(int index) diff --git a/src/Android/Avalonia.Android/AndroidPlatform.cs b/src/Android/Avalonia.Android/AndroidPlatform.cs index c91b58311b..c11cadfbac 100644 --- a/src/Android/Avalonia.Android/AndroidPlatform.cs +++ b/src/Android/Avalonia.Android/AndroidPlatform.cs @@ -17,7 +17,7 @@ namespace Avalonia { public static T UseAndroid(this T builder) where T : AppBuilderBase, 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().ToTransient() @@ -55,7 +55,7 @@ namespace Avalonia.Android .Bind().ToConstant(new DefaultRenderTimer(60)) .Bind().ToConstant(new RenderLoop()) .Bind().ToSingleton() - .Bind().ToConstant(new AssetLoader(app.GetType().Assembly)); + .Bind().ToConstant(new AssetLoader(appType.Assembly)); SkiaPlatform.Initialize(); ((global::Android.App.Application) global::Android.App.Application.Context.ApplicationContext) diff --git a/src/Android/Avalonia.Android/Avalonia.Android.csproj b/src/Android/Avalonia.Android/Avalonia.Android.csproj index 0089ea3b8d..c170e8449c 100644 --- a/src/Android/Avalonia.Android/Avalonia.Android.csproj +++ b/src/Android/Avalonia.Android/Avalonia.Android.csproj @@ -1,6 +1,6 @@  - monoandroid80 + monoandroid90 true diff --git a/src/Android/Avalonia.Android/Platform/SkiaPlatform/TopLevelImpl.cs b/src/Android/Avalonia.Android/Platform/SkiaPlatform/TopLevelImpl.cs index 0d0d9db252..71706676d6 100644 --- a/src/Android/Avalonia.Android/Platform/SkiaPlatform/TopLevelImpl.cs +++ b/src/Android/Avalonia.Android/Platform/SkiaPlatform/TopLevelImpl.cs @@ -15,10 +15,10 @@ using Avalonia.Rendering; namespace Avalonia.Android.Platform.SkiaPlatform { class TopLevelImpl : IAndroidView, ITopLevelImpl, IFramebufferPlatformSurface - { private readonly AndroidKeyboardEventsHelper _keyboardHelper; private readonly AndroidTouchEventsHelper _touchHelper; + private ViewImpl _view; public TopLevelImpl(Context context, bool placeOnTop = false) @@ -28,6 +28,8 @@ namespace Avalonia.Android.Platform.SkiaPlatform _touchHelper = new AndroidTouchEventsHelper(this, () => InputRoot, p => GetAvaloniaPointFromEvent(p)); + Surfaces = new object[] { this }; + MaxClientSize = new Size(_view.Resources.DisplayMetrics.WidthPixels, _view.Resources.DisplayMetrics.HeightPixels); } @@ -82,7 +84,7 @@ namespace Avalonia.Android.Platform.SkiaPlatform public IPlatformHandle Handle => _view; - public IEnumerable Surfaces => new object[] {this}; + public IEnumerable Surfaces { get; } public IRenderer CreateRenderer(IRenderRoot root) { diff --git a/src/Android/Avalonia.AndroidTestApplication/Avalonia.AndroidTestApplication.csproj b/src/Android/Avalonia.AndroidTestApplication/Avalonia.AndroidTestApplication.csproj index 1b2b205d45..2f95a6e4bd 100644 --- a/src/Android/Avalonia.AndroidTestApplication/Avalonia.AndroidTestApplication.csproj +++ b/src/Android/Avalonia.AndroidTestApplication/Avalonia.AndroidTestApplication.csproj @@ -16,7 +16,7 @@ Resources\Resource.Designer.cs Off False - v8.0 + v9.0 Properties\AndroidManifest.xml diff --git a/src/Avalonia.Animation/Animatable.cs b/src/Avalonia.Animation/Animatable.cs index 2c321b8b28..ca45fb8c4d 100644 --- a/src/Avalonia.Animation/Animatable.cs +++ b/src/Avalonia.Animation/Animatable.cs @@ -55,6 +55,11 @@ namespace Avalonia.Animation } set { + if (value is null) + return; + + if (_previousTransitions is null) + _previousTransitions = new Dictionary(); SetAndRaise(TransitionsProperty, ref _transitions, value); } @@ -70,7 +75,7 @@ namespace Avalonia.Animation if (_transitions is null || _previousTransitions is null || e.Priority == BindingPriority.Animation) return; // PERF-SENSITIVE: Called on every property change. Don't use LINQ here (too many allocations). - foreach (var transition in Transitions) + foreach (var transition in _transitions) { if (transition.Property == e.Property) { diff --git a/src/Avalonia.Animation/DisposeAnimationInstanceSubject.cs b/src/Avalonia.Animation/DisposeAnimationInstanceSubject.cs index b44f392ce3..0c15524362 100644 --- a/src/Avalonia.Animation/DisposeAnimationInstanceSubject.cs +++ b/src/Avalonia.Animation/DisposeAnimationInstanceSubject.cs @@ -2,14 +2,7 @@ // Licensed under the MIT license. See licence.md file in the project root for full license information. using System; -using System.Collections.Generic; -using System.Linq; -using System.Reactive.Linq; using Avalonia.Animation.Animators; -using Avalonia.Animation.Utils; -using Avalonia.Collections; -using Avalonia.Data; -using Avalonia.Reactive; namespace Avalonia.Animation { @@ -46,6 +39,7 @@ namespace Avalonia.Animation public void OnError(Exception error) { _lastInstance?.Dispose(); + _lastInstance = null; } void IObserver.OnNext(bool matchVal) @@ -53,12 +47,18 @@ namespace Avalonia.Animation if (matchVal != _lastMatch) { _lastInstance?.Dispose(); + if (matchVal) { _lastInstance = _animator.Run(_animation, _control, _clock, _onComplete); } + else + { + _lastInstance = null; + } + _lastMatch = matchVal; } } } -} \ No newline at end of file +} diff --git a/src/Avalonia.Base/AvaloniaObject.cs b/src/Avalonia.Base/AvaloniaObject.cs index 8cc512d132..2450f1a3a1 100644 --- a/src/Avalonia.Base/AvaloniaObject.cs +++ b/src/Avalonia.Base/AvaloniaObject.cs @@ -326,8 +326,6 @@ namespace Avalonia VerifyAccess(); - var description = GetDescription(source); - if (property.IsDirect) { if (property.IsReadOnly) @@ -335,12 +333,12 @@ namespace Avalonia throw new ArgumentException($"The property {property.Name} is readonly."); } - Logger.Verbose( - LogArea.Property, + Logger.TryGet(LogEventLevel.Verbose)?.Log( + LogArea.Property, this, - "Bound {Property} to {Binding} with priority LocalValue", - property, - description); + "Bound {Property} to {Binding} with priority LocalValue", + property, + GetDescription(source)); if (_directBindings == null) { @@ -351,12 +349,12 @@ namespace Avalonia } else { - Logger.Verbose( + Logger.TryGet(LogEventLevel.Verbose)?.Log( LogArea.Property, this, "Bound {Property} to {Binding} with priority {Priority}", property, - description, + GetDescription(source), priority); return Values.AddBinding(property, source, priority); @@ -406,7 +404,7 @@ namespace Avalonia { RaisePropertyChanged(property, oldValue, newValue, (BindingPriority)priority); - Logger.Verbose( + Logger.TryGet(LogEventLevel.Verbose)?.Log( LogArea.Property, this, "{Property} changed from {$Old} to {$Value} with priority {Priority}", @@ -458,8 +456,7 @@ namespace Avalonia /// The binding error. protected internal virtual void LogBindingError(AvaloniaProperty property, Exception e) { - Logger.Log( - LogEventLevel.Warning, + Logger.TryGet(LogEventLevel.Warning)?.Log( LogArea.Binding, this, "Error in binding to {Target}.{Property}: {Message}", @@ -812,7 +809,7 @@ namespace Avalonia /// The priority. private void LogPropertySet(AvaloniaProperty property, object value, BindingPriority priority) { - Logger.Verbose( + Logger.TryGet(LogEventLevel.Verbose)?.Log( LogArea.Property, this, "Set {Property} to {$Value} with priority {Priority}", diff --git a/src/Avalonia.Base/AvaloniaObjectExtensions.cs b/src/Avalonia.Base/AvaloniaObjectExtensions.cs index 393482cccf..ad1cefd4ea 100644 --- a/src/Avalonia.Base/AvaloniaObjectExtensions.cs +++ b/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 /// The property changed observable. /// Given a TTarget, returns the handler. /// A disposable that can be used to terminate the subscription. + [Obsolete("Use overload taking Action.")] public static IDisposable AddClassHandler( this IObservable observable, Func> handler) @@ -238,9 +239,7 @@ namespace Avalonia Func> handler) where TTarget : class { - var target = e.Sender as TTarget; - - if (target != null) + if (e.Sender is TTarget target) { handler(target)(e); } diff --git a/src/Avalonia.Base/Data/Core/BindingExpression.cs b/src/Avalonia.Base/Data/Core/BindingExpression.cs index 7f8396cdfa..986e2cf012 100644 --- a/src/Avalonia.Base/Data/Core/BindingExpression.cs +++ b/src/Avalonia.Base/Data/Core/BindingExpression.cs @@ -165,7 +165,7 @@ namespace Avalonia.Data.Core } else { - Logger.Error( + Logger.TryGet(LogEventLevel.Error)?.Log( LogArea.Binding, this, "Could not convert FallbackValue {FallbackValue} to {Type}", diff --git a/src/Avalonia.Base/Data/Core/ExpressionNode.cs b/src/Avalonia.Base/Data/Core/ExpressionNode.cs index ce40b3e517..c2e5c8e4f3 100644 --- a/src/Avalonia.Base/Data/Core/ExpressionNode.cs +++ b/src/Avalonia.Base/Data/Core/ExpressionNode.cs @@ -8,9 +8,13 @@ namespace Avalonia.Data.Core public abstract class ExpressionNode { private static readonly object CacheInvalid = new object(); + protected static readonly WeakReference UnsetReference = new WeakReference(AvaloniaProperty.UnsetValue); + protected static readonly WeakReference NullReference = + new WeakReference(null); + private WeakReference _target = UnsetReference; private Action _subscriber; private bool _listening; @@ -98,7 +102,7 @@ namespace Avalonia.Data.Core if (notification == null) { - LastValue = new WeakReference(value); + LastValue = value != null ? new WeakReference(value) : NullReference; if (Next != null) { @@ -111,7 +115,7 @@ namespace Avalonia.Data.Core } else { - LastValue = new WeakReference(notification.Value); + LastValue = notification.Value != null ? new WeakReference(notification.Value) : NullReference; if (Next != null) { @@ -136,8 +140,8 @@ namespace Avalonia.Data.Core } else if (target != AvaloniaProperty.UnsetValue) { - StartListeningCore(_target); _listening = true; + StartListeningCore(_target); } else { diff --git a/src/Avalonia.Base/Data/Core/Plugins/InpcPropertyAccessorPlugin.cs b/src/Avalonia.Base/Data/Core/Plugins/InpcPropertyAccessorPlugin.cs index 4716b45340..cbceb58204 100644 --- a/src/Avalonia.Base/Data/Core/Plugins/InpcPropertyAccessorPlugin.cs +++ b/src/Avalonia.Base/Data/Core/Plugins/InpcPropertyAccessorPlugin.cs @@ -103,8 +103,8 @@ namespace Avalonia.Data.Core.Plugins protected override void SubscribeCore() { - SendCurrentValue(); SubscribeToChanges(); + SendCurrentValue(); } protected override void UnsubscribeCore() diff --git a/src/Avalonia.Base/Data/Core/SettableNode.cs b/src/Avalonia.Base/Data/Core/SettableNode.cs index eb98b9e8d6..d0a918dc88 100644 --- a/src/Avalonia.Base/Data/Core/SettableNode.cs +++ b/src/Avalonia.Base/Data/Core/SettableNode.cs @@ -29,6 +29,11 @@ namespace Avalonia.Data.Core if (!isLastValueAlive) { + if (value == null && LastValue == NullReference) + { + return true; + } + return false; } diff --git a/src/Avalonia.Base/Logging/ILogSink.cs b/src/Avalonia.Base/Logging/ILogSink.cs index 0ed4eede8f..8b5751b0af 100644 --- a/src/Avalonia.Base/Logging/ILogSink.cs +++ b/src/Avalonia.Base/Logging/ILogSink.cs @@ -8,6 +8,77 @@ namespace Avalonia.Logging /// public interface ILogSink { + /// + /// Checks if given log level is enabled. + /// + /// The log event level. + /// if given log level is enabled. + bool IsEnabled(LogEventLevel level); + + /// + /// Logs an event. + /// + /// The log event level. + /// The area that the event originates. + /// The object from which the event originates. + /// The message template. + void Log( + LogEventLevel level, + string area, + object source, + string messageTemplate); + + /// + /// Logs an event. + /// + /// The log event level. + /// The area that the event originates. + /// The object from which the event originates. + /// The message template. + /// Message property value. + void Log( + LogEventLevel level, + string area, + object source, + string messageTemplate, + T0 propertyValue0); + + /// + /// Logs an event. + /// + /// The log event level. + /// The area that the event originates. + /// The object from which the event originates. + /// The message template. + /// Message property value. + /// Message property value. + void Log( + LogEventLevel level, + string area, + object source, + string messageTemplate, + T0 propertyValue0, + T1 propertyValue1); + + /// + /// Logs an event. + /// + /// The log event level. + /// The area that the event originates. + /// The object from which the event originates. + /// The message template. + /// Message property value. + /// Message property value. + /// Message property value. + void Log( + LogEventLevel level, + string area, + object source, + string messageTemplate, + T0 propertyValue0, + T1 propertyValue1, + T2 propertyValue2); + /// /// Logs a new event. /// diff --git a/src/Avalonia.Base/Logging/Logger.cs b/src/Avalonia.Base/Logging/Logger.cs index b1132ff4a9..c895c70094 100644 --- a/src/Avalonia.Base/Logging/Logger.cs +++ b/src/Avalonia.Base/Logging/Logger.cs @@ -1,8 +1,6 @@ // 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.Runtime.CompilerServices; - namespace Avalonia.Logging { /// @@ -16,124 +14,43 @@ namespace Avalonia.Logging public static ILogSink Sink { get; set; } /// - /// Logs an event. + /// Checks if given log level is enabled. /// /// The log event level. - /// The area that the event originates. - /// The object from which the event originates. - /// The message template. - /// The message property values. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void Log( - LogEventLevel level, - string area, - object source, - string messageTemplate, - params object[] propertyValues) - { - Sink?.Log(level, area, source, messageTemplate, propertyValues); - } - - /// - /// Logs an event with the level. - /// - /// The area that the event originates. - /// The object from which the event originates. - /// The message template. - /// The message property values. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void Verbose( - string area, - object source, - string messageTemplate, - params object[] propertyValues) + /// if given log level is enabled. + public static bool IsEnabled(LogEventLevel level) { - Log(LogEventLevel.Verbose, area, source, messageTemplate, propertyValues); + return Sink?.IsEnabled(level) == true; } /// - /// Logs an event with the level. + /// Returns parametrized logging sink if given log level is enabled. /// - /// The area that the event originates. - /// The object from which the event originates. - /// The message template. - /// The message property values. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void Debug( - string area, - object source, - string messageTemplate, - params object[] propertyValues) + /// The log event level. + /// Log sink or if log level is not enabled. + public static ParametrizedLogger? TryGet(LogEventLevel level) { - Log(LogEventLevel.Debug, area, source, messageTemplate, propertyValues); - } + if (!IsEnabled(level)) + { + return null; + } - /// - /// Logs an event with the level. - /// - /// The area that the event originates. - /// The object from which the event originates. - /// The message template. - /// The message property values. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void Information( - string area, - object source, - string messageTemplate, - params object[] propertyValues) - { - Log(LogEventLevel.Information, area, source, messageTemplate, propertyValues); + return new ParametrizedLogger(Sink, level); } /// - /// Logs an event with the level. + /// Returns parametrized logging sink if given log level is enabled. /// - /// The area that the event originates. - /// The object from which the event originates. - /// The message template. - /// The message property values. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void Warning( - string area, - object source, - string messageTemplate, - params object[] propertyValues) + /// The log event level. + /// Log sink that is valid only if method returns . + /// if logger was obtained successfully. + public static bool TryGet(LogEventLevel level, out ParametrizedLogger outLogger) { - Log(LogEventLevel.Warning, area, source, messageTemplate, propertyValues); - } + ParametrizedLogger? logger = TryGet(level); - /// - /// Logs an event with the level. - /// - /// The area that the event originates. - /// The object from which the event originates. - /// The message template. - /// The message property values. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void Error( - string area, - object source, - string messageTemplate, - params object[] propertyValues) - { - Log(LogEventLevel.Error, area, source, messageTemplate, propertyValues); - } + outLogger = logger.GetValueOrDefault(); - /// - /// Logs an event with the level. - /// - /// The area that the event originates. - /// The object from which the event originates. - /// The message template. - /// The message property values. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void Fatal( - string area, - object source, - string messageTemplate, - params object[] propertyValues) - { - Log(LogEventLevel.Fatal, area, source, messageTemplate, propertyValues); + return logger.HasValue; } } } diff --git a/src/Avalonia.Base/Logging/ParametrizedLogger.cs b/src/Avalonia.Base/Logging/ParametrizedLogger.cs new file mode 100644 index 0000000000..1550cc1b40 --- /dev/null +++ b/src/Avalonia.Base/Logging/ParametrizedLogger.cs @@ -0,0 +1,174 @@ +// 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.Runtime.CompilerServices; + +namespace Avalonia.Logging +{ + /// + /// Logger sink parametrized for given logging level. + /// + public readonly struct ParametrizedLogger + { + private readonly ILogSink _sink; + private readonly LogEventLevel _level; + + public ParametrizedLogger(ILogSink sink, LogEventLevel level) + { + _sink = sink; + _level = level; + } + + /// + /// Checks if this logger can be used. + /// + public bool IsValid => _sink != null; + + /// + /// Logs an event. + /// + /// The area that the event originates. + /// The object from which the event originates. + /// The message template. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void Log( + string area, + object source, + string messageTemplate) + { + _sink.Log(_level, area, source, messageTemplate); + } + + /// + /// Logs an event. + /// + /// The area that the event originates. + /// The object from which the event originates. + /// The message template. + /// Message property value. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void Log( + string area, + object source, + string messageTemplate, + T0 propertyValue0) + { + _sink.Log(_level, area, source, messageTemplate, propertyValue0); + } + + /// + /// Logs an event. + /// + /// The area that the event originates. + /// The object from which the event originates. + /// The message template. + /// Message property value. + /// Message property value. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void Log( + string area, + object source, + string messageTemplate, + T0 propertyValue0, + T1 propertyValue1) + { + _sink.Log(_level, area, source, messageTemplate, propertyValue0, propertyValue1); + } + + /// + /// Logs an event. + /// + /// The area that the event originates. + /// The object from which the event originates. + /// The message template. + /// Message property value. + /// Message property value. + /// Message property value. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void Log( + string area, + object source, + string messageTemplate, + T0 propertyValue0, + T1 propertyValue1, + T2 propertyValue2) + { + _sink.Log(_level, area, source, messageTemplate, propertyValue0, propertyValue1, propertyValue2); + } + + /// + /// Logs an event. + /// + /// The area that the event originates. + /// The object from which the event originates. + /// The message template. + /// Message property value. + /// Message property value. + /// Message property value. + /// Message property value. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void Log( + string area, + object source, + string messageTemplate, + T0 propertyValue0, + T1 propertyValue1, + T2 propertyValue2, + T3 propertyValue3) + { + _sink.Log(_level, area, source, messageTemplate, propertyValue0, propertyValue1, propertyValue2, propertyValue3); + } + + /// + /// Logs an event. + /// + /// The area that the event originates. + /// The object from which the event originates. + /// The message template. + /// Message property value. + /// Message property value. + /// Message property value. + /// Message property value. + /// Message property value. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void Log( + string area, + object source, + string messageTemplate, + T0 propertyValue0, + T1 propertyValue1, + T2 propertyValue2, + T3 propertyValue3, + T4 propertyValue4) + { + _sink.Log(_level, area, source, messageTemplate, propertyValue0, propertyValue1, propertyValue2, propertyValue3, propertyValue4); + } + + /// + /// Logs an event. + /// + /// The area that the event originates. + /// The object from which the event originates. + /// The message template. + /// Message property value. + /// Message property value. + /// Message property value. + /// Message property value. + /// Message property value. + /// Message property value. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void Log( + string area, + object source, + string messageTemplate, + T0 propertyValue0, + T1 propertyValue1, + T2 propertyValue2, + T3 propertyValue3, + T4 propertyValue4, + T5 propertyValue5) + { + _sink.Log(_level, area, source, messageTemplate, propertyValue0, propertyValue1, propertyValue2, propertyValue3, propertyValue4, propertyValue5); + } + } +} diff --git a/src/Avalonia.Base/Platform/Interop/Utf8Buffer.cs b/src/Avalonia.Base/Platform/Interop/Utf8Buffer.cs index dc7a3e5634..7117576b3b 100644 --- a/src/Avalonia.Base/Platform/Interop/Utf8Buffer.cs +++ b/src/Avalonia.Base/Platform/Interop/Utf8Buffer.cs @@ -1,4 +1,5 @@ using System; +using System.Buffers; using System.Runtime.InteropServices; using System.Text; @@ -6,7 +7,7 @@ namespace Avalonia.Platform.Interop { public class Utf8Buffer : SafeHandle { - private GCHandle _gchandle; + private GCHandle _gcHandle; private byte[] _data; public Utf8Buffer(string s) : base(IntPtr.Zero, true) @@ -14,8 +15,8 @@ namespace Avalonia.Platform.Interop if (s == null) return; _data = Encoding.UTF8.GetBytes(s); - _gchandle = GCHandle.Alloc(_data, GCHandleType.Pinned); - handle = _gchandle.AddrOfPinnedObject(); + _gcHandle = GCHandle.Alloc(_data, GCHandleType.Pinned); + handle = _gcHandle.AddrOfPinnedObject(); } public int ByteLen => _data.Length; @@ -26,7 +27,7 @@ namespace Avalonia.Platform.Interop { handle = IntPtr.Zero; _data = null; - _gchandle.Free(); + _gcHandle.Free(); } return true; } @@ -40,10 +41,18 @@ namespace Avalonia.Platform.Interop return null; int len; for (len = 0; pstr[len] != 0; len++) ; - var bytes = new byte[len]; - Marshal.Copy(s, bytes, 0, len); - return Encoding.UTF8.GetString(bytes, 0, len); + var bytes = ArrayPool.Shared.Rent(len); + + try + { + Marshal.Copy(s, bytes, 0, len); + return Encoding.UTF8.GetString(bytes, 0, len); + } + finally + { + ArrayPool.Shared.Return(bytes); + } } } } diff --git a/src/Avalonia.Base/PriorityBindingEntry.cs b/src/Avalonia.Base/PriorityBindingEntry.cs index 95add0dfac..7f5415c2d8 100644 --- a/src/Avalonia.Base/PriorityBindingEntry.cs +++ b/src/Avalonia.Base/PriorityBindingEntry.cs @@ -99,37 +99,42 @@ namespace Avalonia void IObserver.OnNext(object value) { - void Signal() + void Signal(PriorityBindingEntry instance, object newValue) { - var notification = value as BindingNotification; + var notification = newValue as BindingNotification; if (notification != null) { if (notification.HasValue || notification.ErrorType == BindingErrorType.Error) { - Value = notification.Value; - _owner.Changed(this); + instance.Value = notification.Value; + instance._owner.Changed(instance); } if (notification.ErrorType != BindingErrorType.None) { - _owner.Error(this, notification); + instance._owner.Error(instance, notification); } } else { - Value = value; - _owner.Changed(this); + instance.Value = newValue; + instance._owner.Changed(instance); } } if (Dispatcher.UIThread.CheckAccess()) { - Signal(); + Signal(this, value); } else { - Dispatcher.UIThread.Post(Signal); + // To avoid allocating closure in the outer scope we need to capture variables + // locally. This allows us to skip most of the allocations when on UI thread. + var instance = this; + var newValue = value; + + Dispatcher.UIThread.Post(() => Signal(instance, newValue)); } } diff --git a/src/Avalonia.Base/PriorityLevel.cs b/src/Avalonia.Base/PriorityLevel.cs index 909558b0ce..a2364083ea 100644 --- a/src/Avalonia.Base/PriorityLevel.cs +++ b/src/Avalonia.Base/PriorityLevel.cs @@ -3,7 +3,8 @@ using System; using System.Collections.Generic; -using System.Reactive.Disposables; +using System.Diagnostics; +using System.Threading; using Avalonia.Data; namespace Avalonia @@ -110,20 +111,7 @@ namespace Avalonia entry.Start(binding); - return Disposable.Create(() => - { - if (!entry.HasCompleted) - { - Bindings.Remove(node); - - entry.Dispose(); - - if (entry.Index >= ActiveBindingIndex) - { - ActivateFirstBinding(); - } - } - }); + return new RemoveBindingDisposable(node, Bindings, this); } /// @@ -191,5 +179,49 @@ namespace Avalonia ActiveBindingIndex = -1; Owner.LevelValueChanged(this); } + + private sealed class RemoveBindingDisposable : IDisposable + { + private readonly LinkedList _bindings; + private readonly PriorityLevel _priorityLevel; + private LinkedListNode _binding; + + public RemoveBindingDisposable( + LinkedListNode binding, + LinkedList bindings, + PriorityLevel priorityLevel) + { + _binding = binding; + _bindings = bindings; + _priorityLevel = priorityLevel; + } + + public void Dispose() + { + LinkedListNode binding = Interlocked.Exchange(ref _binding, null); + + if (binding == null) + { + // Some system is trying to remove binding twice. + Debug.Assert(false); + + return; + } + + PriorityBindingEntry entry = binding.Value; + + if (!entry.HasCompleted) + { + _bindings.Remove(binding); + + entry.Dispose(); + + if (entry.Index >= _priorityLevel.ActiveBindingIndex) + { + _priorityLevel.ActivateFirstBinding(); + } + } + } + } } } diff --git a/src/Avalonia.Base/PriorityValue.cs b/src/Avalonia.Base/PriorityValue.cs index 2871271062..61184ef7b1 100644 --- a/src/Avalonia.Base/PriorityValue.cs +++ b/src/Avalonia.Base/PriorityValue.cs @@ -301,7 +301,7 @@ namespace Avalonia } else { - Logger.Error( + Logger.TryGet(LogEventLevel.Error)?.Log( LogArea.Binding, Owner, "Binding produced invalid value for {$Property} ({$PropertyType}): {$Value} ({$ValueType})", diff --git a/src/Avalonia.Base/Utilities/SingleOrDictionary.cs b/src/Avalonia.Base/Utilities/SingleOrDictionary.cs index bda85f90e2..140da6750d 100644 --- a/src/Avalonia.Base/Utilities/SingleOrDictionary.cs +++ b/src/Avalonia.Base/Utilities/SingleOrDictionary.cs @@ -38,7 +38,7 @@ namespace Avalonia.Utilities { if (dictionary == null) { - if (!_singleValue.HasValue || !_singleValue.Value.Key.Equals(key)) + if (!_singleValue.HasValue || !EqualityComparer.Default.Equals(_singleValue.Value.Key, key)) { value = default(TValue); return false; diff --git a/src/Avalonia.Base/Utilities/WeakEventHandlerManager.cs b/src/Avalonia.Base/Utilities/WeakEventHandlerManager.cs index f4cec98628..37e25d0fac 100644 --- a/src/Avalonia.Base/Utilities/WeakEventHandlerManager.cs +++ b/src/Avalonia.Base/Utilities/WeakEventHandlerManager.cs @@ -142,10 +142,12 @@ namespace Avalonia.Utilities _data = ndata; } + MethodInfo method = s.Method; + var subscriber = (TSubscriber)s.Target; - if (!s_Callers.TryGetValue(s.Method, out var caller)) - s_Callers[s.Method] = caller = - (CallerDelegate)Delegate.CreateDelegate(typeof(CallerDelegate), null, s.Method); + if (!s_Callers.TryGetValue(method, out var caller)) + s_Callers[method] = caller = + (CallerDelegate)Delegate.CreateDelegate(typeof(CallerDelegate), null, method); _data[_count] = new Descriptor { Caller = caller, diff --git a/src/Avalonia.Controls.DataGrid/DataGrid.cs b/src/Avalonia.Controls.DataGrid/DataGrid.cs index 490a724eda..b65fd2a8b7 100644 --- a/src/Avalonia.Controls.DataGrid/DataGrid.cs +++ b/src/Avalonia.Controls.DataGrid/DataGrid.cs @@ -723,29 +723,29 @@ namespace Avalonia.Controls PseudoClass(IsValidProperty, x => !x, ":invalid"); - ItemsProperty.Changed.AddClassHandler(x => x.OnItemsPropertyChanged); - CanUserResizeColumnsProperty.Changed.AddClassHandler(x => x.OnCanUserResizeColumnsChanged); - ColumnWidthProperty.Changed.AddClassHandler(x => x.OnColumnWidthChanged); - RowBackgroundProperty.Changed.AddClassHandler(x => x.OnRowBackgroundChanged); - AlternatingRowBackgroundProperty.Changed.AddClassHandler(x => x.OnRowBackgroundChanged); - FrozenColumnCountProperty.Changed.AddClassHandler(x => x.OnFrozenColumnCountChanged); - GridLinesVisibilityProperty.Changed.AddClassHandler(x => x.OnGridLinesVisibilityChanged); - HeadersVisibilityProperty.Changed.AddClassHandler(x => x.OnHeadersVisibilityChanged); - HorizontalGridLinesBrushProperty.Changed.AddClassHandler(x => x.OnHorizontalGridLinesBrushChanged); - IsReadOnlyProperty.Changed.AddClassHandler(x => x.OnIsReadOnlyChanged); - MaxColumnWidthProperty.Changed.AddClassHandler(x => x.OnMaxColumnWidthChanged); - MinColumnWidthProperty.Changed.AddClassHandler(x => x.OnMinColumnWidthChanged); - RowHeightProperty.Changed.AddClassHandler(x => x.OnRowHeightChanged); - RowHeaderWidthProperty.Changed.AddClassHandler(x => x.OnRowHeaderWidthChanged); - SelectionModeProperty.Changed.AddClassHandler(x => x.OnSelectionModeChanged); - VerticalGridLinesBrushProperty.Changed.AddClassHandler(x => x.OnVerticalGridLinesBrushChanged); - SelectedIndexProperty.Changed.AddClassHandler(x => x.OnSelectedIndexChanged); - SelectedItemProperty.Changed.AddClassHandler(x => x.OnSelectedItemChanged); - IsEnabledProperty.Changed.AddClassHandler(x => x.DataGrid_IsEnabledChanged); - AreRowGroupHeadersFrozenProperty.Changed.AddClassHandler(x => x.OnAreRowGroupHeadersFrozenChanged); - RowDetailsTemplateProperty.Changed.AddClassHandler(x => x.OnRowDetailsTemplateChanged); - RowDetailsVisibilityModeProperty.Changed.AddClassHandler(x => x.OnRowDetailsVisibilityModeChanged); - AutoGenerateColumnsProperty.Changed.AddClassHandler(x => x.OnAutoGenerateColumnsChanged); + ItemsProperty.Changed.AddClassHandler((x, e) => x.OnItemsPropertyChanged(e)); + CanUserResizeColumnsProperty.Changed.AddClassHandler((x, e) => x.OnCanUserResizeColumnsChanged(e)); + ColumnWidthProperty.Changed.AddClassHandler((x, e) => x.OnColumnWidthChanged(e)); + RowBackgroundProperty.Changed.AddClassHandler((x, e) => x.OnRowBackgroundChanged(e)); + AlternatingRowBackgroundProperty.Changed.AddClassHandler((x, e) => x.OnRowBackgroundChanged(e)); + FrozenColumnCountProperty.Changed.AddClassHandler((x, e) => x.OnFrozenColumnCountChanged(e)); + GridLinesVisibilityProperty.Changed.AddClassHandler((x, e) => x.OnGridLinesVisibilityChanged(e)); + HeadersVisibilityProperty.Changed.AddClassHandler((x, e) => x.OnHeadersVisibilityChanged(e)); + HorizontalGridLinesBrushProperty.Changed.AddClassHandler((x, e) => x.OnHorizontalGridLinesBrushChanged(e)); + IsReadOnlyProperty.Changed.AddClassHandler((x, e) => x.OnIsReadOnlyChanged(e)); + MaxColumnWidthProperty.Changed.AddClassHandler((x, e) => x.OnMaxColumnWidthChanged(e)); + MinColumnWidthProperty.Changed.AddClassHandler((x, e) => x.OnMinColumnWidthChanged(e)); + RowHeightProperty.Changed.AddClassHandler((x, e) => x.OnRowHeightChanged(e)); + RowHeaderWidthProperty.Changed.AddClassHandler((x, e) => x.OnRowHeaderWidthChanged(e)); + SelectionModeProperty.Changed.AddClassHandler((x, e) => x.OnSelectionModeChanged(e)); + VerticalGridLinesBrushProperty.Changed.AddClassHandler((x, e) => x.OnVerticalGridLinesBrushChanged(e)); + SelectedIndexProperty.Changed.AddClassHandler((x, e) => x.OnSelectedIndexChanged(e)); + SelectedItemProperty.Changed.AddClassHandler((x, e) => x.OnSelectedItemChanged(e)); + IsEnabledProperty.Changed.AddClassHandler((x, e) => x.DataGrid_IsEnabledChanged(e)); + AreRowGroupHeadersFrozenProperty.Changed.AddClassHandler((x, e) => x.OnAreRowGroupHeadersFrozenChanged(e)); + RowDetailsTemplateProperty.Changed.AddClassHandler((x, e) => x.OnRowDetailsTemplateChanged(e)); + RowDetailsVisibilityModeProperty.Changed.AddClassHandler((x, e) => x.OnRowDetailsVisibilityModeChanged(e)); + AutoGenerateColumnsProperty.Changed.AddClassHandler((x, e) => x.OnAutoGenerateColumnsChanged(e)); } /// @@ -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 diff --git a/src/Avalonia.Controls.DataGrid/DataGridCell.cs b/src/Avalonia.Controls.DataGrid/DataGridCell.cs index a21583b38e..e56c534f50 100644 --- a/src/Avalonia.Controls.DataGrid/DataGridCell.cs +++ b/src/Avalonia.Controls.DataGrid/DataGridCell.cs @@ -29,7 +29,7 @@ namespace Avalonia.Controls static DataGridCell() { PointerPressedEvent.AddClassHandler( - x => x.DataGridCell_PointerPressed, handledEventsToo: true); + (x,e) => x.DataGridCell_PointerPressed(e), handledEventsToo: true); } public DataGridCell() { } @@ -219,4 +219,4 @@ namespace Avalonia.Controls } } } -} \ No newline at end of file +} diff --git a/src/Avalonia.Controls.DataGrid/DataGridColumnHeader.cs b/src/Avalonia.Controls.DataGrid/DataGridColumnHeader.cs index 6cb0807e29..d1651b2d09 100644 --- a/src/Avalonia.Controls.DataGrid/DataGridColumnHeader.cs +++ b/src/Avalonia.Controls.DataGrid/DataGridColumnHeader.cs @@ -67,7 +67,7 @@ namespace Avalonia.Controls static DataGridColumnHeader() { - AreSeparatorsVisibleProperty.Changed.AddClassHandler(x => x.OnAreSeparatorsVisibleChanged); + AreSeparatorsVisibleProperty.Changed.AddClassHandler((x,e) => x.OnAreSeparatorsVisibleChanged(e)); } /// @@ -468,7 +468,7 @@ namespace Avalonia.Controls private void DataGridColumnHeader_PointerReleased(object sender, PointerReleasedEventArgs e) { - if (OwningColumn == null || e.Handled || !IsEnabled || e.MouseButton != MouseButton.Left) + if (OwningColumn == null || e.Handled || !IsEnabled || e.InitialPressMouseButton != MouseButton.Left) { return; } diff --git a/src/Avalonia.Controls.DataGrid/DataGridRow.cs b/src/Avalonia.Controls.DataGrid/DataGridRow.cs index 04a1575486..c9924660be 100644 --- a/src/Avalonia.Controls.DataGrid/DataGridRow.cs +++ b/src/Avalonia.Controls.DataGrid/DataGridRow.cs @@ -116,10 +116,10 @@ namespace Avalonia.Controls static DataGridRow() { - HeaderProperty.Changed.AddClassHandler(x => x.OnHeaderChanged); - DetailsTemplateProperty.Changed.AddClassHandler(x => x.OnDetailsTemplateChanged); - AreDetailsVisibleProperty.Changed.AddClassHandler(x => x.OnAreDetailsVisibleChanged); - PointerPressedEvent.AddClassHandler(x => x.DataGridRow_PointerPressed, handledEventsToo: true); + HeaderProperty.Changed.AddClassHandler((x, e) => x.OnHeaderChanged(e)); + DetailsTemplateProperty.Changed.AddClassHandler((x, e) => x.OnDetailsTemplateChanged(e)); + AreDetailsVisibleProperty.Changed.AddClassHandler((x, e) => x.OnAreDetailsVisibleChanged(e)); + PointerPressedEvent.AddClassHandler((x, e) => x.DataGridRow_PointerPressed(e), handledEventsToo: true); } /// diff --git a/src/Avalonia.Controls.DataGrid/DataGridRowGroupHeader.cs b/src/Avalonia.Controls.DataGrid/DataGridRowGroupHeader.cs index 716997f62c..7dafef9d8b 100644 --- a/src/Avalonia.Controls.DataGrid/DataGridRowGroupHeader.cs +++ b/src/Avalonia.Controls.DataGrid/DataGridRowGroupHeader.cs @@ -109,7 +109,7 @@ namespace Avalonia.Controls static DataGridRowGroupHeader() { - SublevelIndentProperty.Changed.AddClassHandler(x => x.OnSublevelIndentChanged); + SublevelIndentProperty.Changed.AddClassHandler((x,e) => x.OnSublevelIndentChanged(e)); } /// @@ -446,4 +446,4 @@ namespace Avalonia.Controls } } -} \ No newline at end of file +} diff --git a/src/Avalonia.Controls/AppBuilderBase.cs b/src/Avalonia.Controls/AppBuilderBase.cs index 307ddd284c..d9be9171ed 100644 --- a/src/Avalonia.Controls/AppBuilderBase.cs +++ b/src/Avalonia.Controls/AppBuilderBase.cs @@ -18,7 +18,9 @@ namespace Avalonia.Controls { private static bool s_setupWasAlreadyCalled; private Action _optionsInitializers; - + private Func _appFactory; + private IApplicationLifetime _lifetime; + /// /// Gets or sets the instance. /// @@ -30,10 +32,15 @@ namespace Avalonia.Controls public Action RuntimePlatformServicesInitializer { get; private set; } /// - /// Gets or sets the instance being initialized. + /// Gets the instance being initialized. /// - public Application Instance { get; protected set; } - + public Application Instance { get; private set; } + + /// + /// Gets the type of the Instance (even if it's not created yet) + /// + public Type ApplicationType { get; private set; } + /// /// Gets or sets a method to call the initialize the windowing subsystem. /// @@ -76,20 +83,11 @@ namespace Avalonia.Controls public static TAppBuilder Configure() where TApp : Application, new() { - return Configure(new TApp()); - } - - /// - /// Begin configuring an . - /// - /// An instance. - 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; } + /// + /// Sets up the platform-specific services for the application and initialized it with a particular lifetime, but does not run it. + /// + /// + /// + public TAppBuilder SetupWithLifetime(IApplicationLifetime lifetime) + { + _lifetime = lifetime; + Setup(); + return Self; + } + /// /// Specifies a windowing subsystem to use. /// @@ -254,11 +264,6 @@ namespace Avalonia.Controls /// 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); diff --git a/src/Avalonia.Controls/Application.cs b/src/Avalonia.Controls/Application.cs index 382106de65..ce60a0f0b9 100644 --- a/src/Avalonia.Controls/Application.cs +++ b/src/Avalonia.Controls/Application.cs @@ -32,7 +32,7 @@ namespace Avalonia /// method. /// - Tracks the lifetime of the application. /// - public class Application : IGlobalDataTemplates, IGlobalStyles, IStyleRoot, IResourceNode + public class Application : AvaloniaObject, IGlobalDataTemplates, IGlobalStyles, IStyleRoot, IResourceNode { /// /// The application-global data templates. @@ -210,5 +210,22 @@ namespace Avalonia { ResourcesChanged?.Invoke(this, e); } + + private string _name; + /// + /// Defines Name property + /// + public static readonly DirectProperty NameProperty = + AvaloniaProperty.RegisterDirect("Name", o => o.Name, (o, v) => o.Name = v); + + /// + /// Application name to be used for various platform-specific purposes + /// + public string Name + { + get => _name; + set => SetAndRaise(NameProperty, ref _name, value); + } + } } diff --git a/src/Avalonia.Controls/ApplicationLifetimes/ClassicDesktopStyleApplicationLifetime.cs b/src/Avalonia.Controls/ApplicationLifetimes/ClassicDesktopStyleApplicationLifetime.cs index abca7a64ee..2533191ae4 100644 --- a/src/Avalonia.Controls/ApplicationLifetimes/ClassicDesktopStyleApplicationLifetime.cs +++ b/src/Avalonia.Controls/ApplicationLifetimes/ClassicDesktopStyleApplicationLifetime.cs @@ -125,8 +125,7 @@ namespace Avalonia where T : AppBuilderBase, new() { var lifetime = new ClassicDesktopStyleApplicationLifetime(builder.Instance) {ShutdownMode = shutdownMode}; - builder.Instance.ApplicationLifetime = lifetime; - builder.SetupWithoutStarting(); + builder.SetupWithLifetime(lifetime); return lifetime.Start(args); } } diff --git a/src/Avalonia.Controls/AutoCompleteBox.cs b/src/Avalonia.Controls/AutoCompleteBox.cs index 1e2fc9f9d0..ce4358648b 100644 --- a/src/Avalonia.Controls/AutoCompleteBox.cs +++ b/src/Avalonia.Controls/AutoCompleteBox.cs @@ -805,15 +805,15 @@ namespace Avalonia.Controls { FocusableProperty.OverrideDefaultValue(true); - MinimumPopulateDelayProperty.Changed.AddClassHandler(x => x.OnMinimumPopulateDelayChanged); - IsDropDownOpenProperty.Changed.AddClassHandler(x => x.OnIsDropDownOpenChanged); - SelectedItemProperty.Changed.AddClassHandler(x => x.OnSelectedItemPropertyChanged); - TextProperty.Changed.AddClassHandler(x => x.OnTextPropertyChanged); - SearchTextProperty.Changed.AddClassHandler(x => x.OnSearchTextPropertyChanged); - FilterModeProperty.Changed.AddClassHandler(x => x.OnFilterModePropertyChanged); - ItemFilterProperty.Changed.AddClassHandler(x => x.OnItemFilterPropertyChanged); - ItemsProperty.Changed.AddClassHandler(x => x.OnItemsPropertyChanged); - IsEnabledProperty.Changed.AddClassHandler(x => x.OnControlIsEnabledChanged); + MinimumPopulateDelayProperty.Changed.AddClassHandler((x,e) => x.OnMinimumPopulateDelayChanged(e)); + IsDropDownOpenProperty.Changed.AddClassHandler((x,e) => x.OnIsDropDownOpenChanged(e)); + SelectedItemProperty.Changed.AddClassHandler((x,e) => x.OnSelectedItemPropertyChanged(e)); + TextProperty.Changed.AddClassHandler((x,e) => x.OnTextPropertyChanged(e)); + SearchTextProperty.Changed.AddClassHandler((x,e) => x.OnSearchTextPropertyChanged(e)); + FilterModeProperty.Changed.AddClassHandler((x,e) => x.OnFilterModePropertyChanged(e)); + ItemFilterProperty.Changed.AddClassHandler((x,e) => x.OnItemFilterPropertyChanged(e)); + ItemsProperty.Changed.AddClassHandler((x,e) => x.OnItemsPropertyChanged(e)); + IsEnabledProperty.Changed.AddClassHandler((x,e) => x.OnControlIsEnabledChanged(e)); } /// diff --git a/src/Avalonia.Controls/Button.cs b/src/Avalonia.Controls/Button.cs index b09d3bddff..78d02e200f 100644 --- a/src/Avalonia.Controls/Button.cs +++ b/src/Avalonia.Controls/Button.cs @@ -64,6 +64,12 @@ namespace Avalonia.Controls public static readonly StyledProperty IsDefaultProperty = AvaloniaProperty.Register(nameof(IsDefault)); + /// + /// Defines the property. + /// + public static readonly StyledProperty IsCancelProperty = + AvaloniaProperty.Register(nameof(IsCancel)); + /// /// Defines the event. /// @@ -84,6 +90,7 @@ namespace Avalonia.Controls FocusableProperty.OverrideDefaultValue(typeof(Button), true); CommandProperty.Changed.Subscribe(CommandChanged); IsDefaultProperty.Changed.Subscribe(IsDefaultChanged); + IsCancelProperty.Changed.Subscribe(IsCancelChanged); PseudoClass