diff --git a/build/ApiCompatAttributeExcludeList.txt b/build/ApiCompatAttributeExcludeList.txt new file mode 100644 index 0000000000..1df5a30ec3 --- /dev/null +++ b/build/ApiCompatAttributeExcludeList.txt @@ -0,0 +1,2 @@ +T:Avalonia.Metadata.NotClientImplementableAttribute +T:Avalonia.Metadata.UnstableAttribute diff --git a/build/CoreLibraries.props b/build/CoreLibraries.props index 314d38190a..9448a31d73 100644 --- a/build/CoreLibraries.props +++ b/build/CoreLibraries.props @@ -3,8 +3,6 @@ - - diff --git a/native/Avalonia.Native/inc/rendertarget.h b/native/Avalonia.Native/inc/rendertarget.h index 2b0338d099..a59915777f 100644 --- a/native/Avalonia.Native/inc/rendertarget.h +++ b/native/Avalonia.Native/inc/rendertarget.h @@ -1,3 +1,8 @@ +#pragma once + +#include "com.h" +#include "comimpl.h" +#include "avalonia-native.h" @protocol IRenderTarget -(void) setNewLayer: (CALayer*) layer; diff --git a/native/Avalonia.Native/src/OSX/AutoFitContentView.h b/native/Avalonia.Native/src/OSX/AutoFitContentView.h index 68c9fb8dc8..7f1f764141 100644 --- a/native/Avalonia.Native/src/OSX/AutoFitContentView.h +++ b/native/Avalonia.Native/src/OSX/AutoFitContentView.h @@ -3,8 +3,10 @@ // Copyright (c) 2022 Avalonia. All rights reserved. // +#pragma once + #import -#import "avalonia-native.h" +#include "avalonia-native.h" @interface AutoFitContentView : NSView -(AutoFitContentView* _Nonnull) initWithContent: (NSView* _Nonnull) content; diff --git a/native/Avalonia.Native/src/OSX/AutoFitContentView.mm b/native/Avalonia.Native/src/OSX/AutoFitContentView.mm index 92d6f67a91..0fa4540726 100644 --- a/native/Avalonia.Native/src/OSX/AutoFitContentView.mm +++ b/native/Avalonia.Native/src/OSX/AutoFitContentView.mm @@ -4,7 +4,9 @@ // #include "AvnView.h" -#import "AutoFitContentView.h" +#include "AutoFitContentView.h" +#include "WindowInterfaces.h" +#include "WindowProtocol.h" @implementation AutoFitContentView { @@ -83,7 +85,7 @@ _settingSize = true; [super setFrameSize:newSize]; - auto window = objc_cast([self window]); + auto window = (id ) [self window]; // TODO get actual titlebar size 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 88e4c0c682..6fc3977d4e 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 @@ -9,16 +9,22 @@ /* Begin PBXBuildFile section */ 18391068E48EF96E3DB5FDAB /* ResizeScope.mm in Sources */ = {isa = PBXBuildFile; fileRef = 18391E45702740FE9DD69695 /* ResizeScope.mm */; }; 1839125F057B0A4EB1760058 /* WindowImpl.mm in Sources */ = {isa = PBXBuildFile; fileRef = 183919BF108EB72A029F7671 /* WindowImpl.mm */; }; + 183914E50CF6D2EFC1667F7C /* WindowInterfaces.h in Headers */ = {isa = PBXBuildFile; fileRef = 18391DB45C7D892E61BF388C /* WindowInterfaces.h */; }; + 1839151F32D1BB1AB51A7BB6 /* AvnPanelWindow.mm in Sources */ = {isa = PBXBuildFile; fileRef = 18391884C7476DA4E53A492D /* AvnPanelWindow.mm */; }; 183916173528EC2737DBE5E1 /* WindowBaseImpl.h in Headers */ = {isa = PBXBuildFile; fileRef = 183915BFF0E234CD3604A7CD /* WindowBaseImpl.h */; }; 1839171DCC651B0638603AC4 /* INSWindowHolder.h in Headers */ = {isa = PBXBuildFile; fileRef = 18391BBB7782C296D424071F /* INSWindowHolder.h */; }; 1839179A55FC1421BEE83330 /* WindowBaseImpl.mm in Sources */ = {isa = PBXBuildFile; fileRef = 18391676ECF0E983F4964357 /* WindowBaseImpl.mm */; }; 183919D91DB9AAB5D700C2EA /* WindowImpl.h in Headers */ = {isa = PBXBuildFile; fileRef = 18391CD090AA776E7E841AC9 /* WindowImpl.h */; }; 18391AA7E0BBA74D184C5734 /* AutoFitContentView.mm in Sources */ = {isa = PBXBuildFile; fileRef = 1839166350F32661F3ABD70F /* AutoFitContentView.mm */; }; + 18391AC16726CBC45856233B /* AvnWindow.mm in Sources */ = {isa = PBXBuildFile; fileRef = 1839155B28B20FFB672D29C6 /* AvnWindow.mm */; }; + 18391AC65ADD7DDD33FBE737 /* PopupImpl.h in Headers */ = {isa = PBXBuildFile; fileRef = 183910513F396141938832B5 /* PopupImpl.h */; }; 18391C28BF1823B5464FDD36 /* ResizeScope.h in Headers */ = {isa = PBXBuildFile; fileRef = 1839171D898F9BFC1373631A /* ResizeScope.h */; }; 18391CF07316F819E76B617C /* IWindowStateChanged.h in Headers */ = {isa = PBXBuildFile; fileRef = 183913C6BFD6856BD42D19FD /* IWindowStateChanged.h */; }; 18391D4EB311BC7EF8B8C0A6 /* AvnView.mm in Sources */ = {isa = PBXBuildFile; fileRef = 1839132D0E2454D911F1D1F9 /* AvnView.mm */; }; + 18391D8CD1756DC858DC1A09 /* PopupImpl.mm in Sources */ = {isa = PBXBuildFile; fileRef = 18391BB698579F40F1783F31 /* PopupImpl.mm */; }; 18391E1381E2D5BFD60265A9 /* AutoFitContentView.h in Headers */ = {isa = PBXBuildFile; fileRef = 18391654EF0E7AB3D3AB4071 /* AutoFitContentView.h */; }; 18391ED5F611FF62C45F196D /* AvnView.h in Headers */ = {isa = PBXBuildFile; fileRef = 18391D1669284AD2EC9E866A /* AvnView.h */; }; + 18391F1E2411C79405A9943A /* WindowProtocol.h in Headers */ = {isa = PBXBuildFile; fileRef = 1839122E037567BDD1D09DEB /* WindowProtocol.h */; }; 1A002B9E232135EE00021753 /* app.mm in Sources */ = {isa = PBXBuildFile; fileRef = 1A002B9D232135EE00021753 /* app.mm */; }; 1A1852DC23E05814008F0DED /* deadlock.mm in Sources */ = {isa = PBXBuildFile; fileRef = 1A1852DB23E05814008F0DED /* deadlock.mm */; }; 1A3E5EA823E9E83B00EDE661 /* rendertarget.mm in Sources */ = {isa = PBXBuildFile; fileRef = 1A3E5EA723E9E83B00EDE661 /* rendertarget.mm */; }; @@ -40,24 +46,29 @@ AB00E4F72147CA920032A60A /* main.mm in Sources */ = {isa = PBXBuildFile; fileRef = AB00E4F62147CA920032A60A /* main.mm */; }; AB1E522C217613570091CD71 /* OpenGL.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = AB1E522B217613570091CD71 /* OpenGL.framework */; }; AB661C1E2148230F00291242 /* AppKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = AB661C1D2148230F00291242 /* AppKit.framework */; }; - AB661C202148286E00291242 /* window.mm in Sources */ = {isa = PBXBuildFile; fileRef = AB661C1F2148286E00291242 /* window.mm */; }; AB8F7D6B21482D7F0057DBA5 /* platformthreading.mm in Sources */ = {isa = PBXBuildFile; fileRef = AB8F7D6A21482D7F0057DBA5 /* platformthreading.mm */; }; BC11A5BE2608D58F0017BAD0 /* automation.h in Headers */ = {isa = PBXBuildFile; fileRef = BC11A5BC2608D58F0017BAD0 /* automation.h */; }; BC11A5BF2608D58F0017BAD0 /* automation.mm in Sources */ = {isa = PBXBuildFile; fileRef = BC11A5BD2608D58F0017BAD0 /* automation.mm */; }; /* End PBXBuildFile section */ /* Begin PBXFileReference section */ + 183910513F396141938832B5 /* PopupImpl.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = PopupImpl.h; sourceTree = ""; }; + 1839122E037567BDD1D09DEB /* WindowProtocol.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = WindowProtocol.h; sourceTree = ""; }; 1839132D0E2454D911F1D1F9 /* AvnView.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = AvnView.mm; sourceTree = ""; }; 183913C6BFD6856BD42D19FD /* IWindowStateChanged.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = IWindowStateChanged.h; sourceTree = ""; }; + 1839155B28B20FFB672D29C6 /* AvnWindow.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = AvnWindow.mm; sourceTree = ""; }; 183915BFF0E234CD3604A7CD /* WindowBaseImpl.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = WindowBaseImpl.h; sourceTree = ""; }; 18391654EF0E7AB3D3AB4071 /* AutoFitContentView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = AutoFitContentView.h; sourceTree = ""; }; 1839166350F32661F3ABD70F /* AutoFitContentView.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = AutoFitContentView.mm; sourceTree = ""; }; 18391676ECF0E983F4964357 /* WindowBaseImpl.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = WindowBaseImpl.mm; sourceTree = ""; }; 1839171D898F9BFC1373631A /* ResizeScope.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ResizeScope.h; sourceTree = ""; }; + 18391884C7476DA4E53A492D /* AvnPanelWindow.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = AvnPanelWindow.mm; sourceTree = ""; }; 183919BF108EB72A029F7671 /* WindowImpl.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = WindowImpl.mm; sourceTree = ""; }; + 18391BB698579F40F1783F31 /* PopupImpl.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = PopupImpl.mm; sourceTree = ""; }; 18391BBB7782C296D424071F /* INSWindowHolder.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = INSWindowHolder.h; sourceTree = ""; }; 18391CD090AA776E7E841AC9 /* WindowImpl.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = WindowImpl.h; sourceTree = ""; }; 18391D1669284AD2EC9E866A /* AvnView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = AvnView.h; sourceTree = ""; }; + 18391DB45C7D892E61BF388C /* WindowInterfaces.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = WindowInterfaces.h; sourceTree = ""; }; 18391E45702740FE9DD69695 /* ResizeScope.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = ResizeScope.mm; sourceTree = ""; }; 1A002B9D232135EE00021753 /* app.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = app.mm; sourceTree = ""; }; 1A1852DB23E05814008F0DED /* deadlock.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = deadlock.mm; sourceTree = ""; }; @@ -72,7 +83,6 @@ 37A4E71A2178846A00EACBCD /* headers */ = {isa = PBXFileReference; lastKnownFileType = folder; name = headers; path = ../../inc; sourceTree = ""; }; 37A517B22159597E00FBA241 /* Screens.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = Screens.mm; sourceTree = ""; }; 37C09D8721580FE4006A6758 /* SystemDialogs.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = SystemDialogs.mm; sourceTree = ""; }; - 37C09D8A21581EF2006A6758 /* window.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = window.h; sourceTree = ""; }; 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 = ""; }; @@ -86,7 +96,6 @@ AB00E4F62147CA920032A60A /* main.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = main.mm; sourceTree = ""; }; AB1E522B217613570091CD71 /* OpenGL.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = OpenGL.framework; path = System/Library/Frameworks/OpenGL.framework; sourceTree = SDKROOT; }; AB661C1D2148230F00291242 /* AppKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = AppKit.framework; path = System/Library/Frameworks/AppKit.framework; sourceTree = SDKROOT; }; - AB661C1F2148286E00291242 /* window.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = window.mm; sourceTree = ""; }; AB661C212148288600291242 /* common.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = common.h; sourceTree = ""; }; AB7A61EF2147C815003C5833 /* libAvalonia.Native.OSX.dylib */ = {isa = PBXFileReference; explicitFileType = "compiled.mach-o.dylib"; includeInIndex = 0; path = libAvalonia.Native.OSX.dylib; sourceTree = BUILT_PRODUCTS_DIR; }; AB8F7D6A21482D7F0057DBA5 /* platformthreading.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = platformthreading.mm; sourceTree = ""; }; @@ -142,8 +151,6 @@ AB661C212148288600291242 /* common.h */, 379860FE214DA0C000CD0246 /* KeyTransform.h */, 37E2330E21583241000CB7E2 /* KeyTransform.mm */, - AB661C1F2148286E00291242 /* window.mm */, - 37C09D8A21581EF2006A6758 /* window.h */, AB00E4F62147CA920032A60A /* main.mm */, 37155CE3233C00EB0034DCE9 /* menu.h */, 520624B222973F4100C4DCEF /* menu.mm */, @@ -166,6 +173,12 @@ 18391D1669284AD2EC9E866A /* AvnView.h */, 1839166350F32661F3ABD70F /* AutoFitContentView.mm */, 18391654EF0E7AB3D3AB4071 /* AutoFitContentView.h */, + 18391884C7476DA4E53A492D /* AvnPanelWindow.mm */, + 1839122E037567BDD1D09DEB /* WindowProtocol.h */, + 1839155B28B20FFB672D29C6 /* AvnWindow.mm */, + 18391DB45C7D892E61BF388C /* WindowInterfaces.h */, + 18391BB698579F40F1783F31 /* PopupImpl.mm */, + 183910513F396141938832B5 /* PopupImpl.h */, ); sourceTree = ""; }; @@ -193,6 +206,9 @@ 18391C28BF1823B5464FDD36 /* ResizeScope.h in Headers */, 18391ED5F611FF62C45F196D /* AvnView.h in Headers */, 18391E1381E2D5BFD60265A9 /* AutoFitContentView.h in Headers */, + 18391F1E2411C79405A9943A /* WindowProtocol.h in Headers */, + 183914E50CF6D2EFC1667F7C /* WindowInterfaces.h in Headers */, + 18391AC65ADD7DDD33FBE737 /* PopupImpl.h in Headers */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -271,12 +287,14 @@ 1A465D10246AB61600C5858B /* dnd.mm in Sources */, AB00E4F72147CA920032A60A /* main.mm in Sources */, 37C09D8821580FE4006A6758 /* SystemDialogs.mm in Sources */, - AB661C202148286E00291242 /* window.mm in Sources */, 1839179A55FC1421BEE83330 /* WindowBaseImpl.mm in Sources */, 1839125F057B0A4EB1760058 /* WindowImpl.mm in Sources */, 18391068E48EF96E3DB5FDAB /* ResizeScope.mm in Sources */, 18391D4EB311BC7EF8B8C0A6 /* AvnView.mm in Sources */, 18391AA7E0BBA74D184C5734 /* AutoFitContentView.mm in Sources */, + 1839151F32D1BB1AB51A7BB6 /* AvnPanelWindow.mm in Sources */, + 18391AC16726CBC45856233B /* AvnWindow.mm in Sources */, + 18391D8CD1756DC858DC1A09 /* PopupImpl.mm in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; diff --git a/native/Avalonia.Native/src/OSX/AvnPanelWindow.mm b/native/Avalonia.Native/src/OSX/AvnPanelWindow.mm new file mode 100644 index 0000000000..2365189010 --- /dev/null +++ b/native/Avalonia.Native/src/OSX/AvnPanelWindow.mm @@ -0,0 +1,11 @@ +// +// Created by Dan Walmsley on 06/05/2022. +// Copyright (c) 2022 Avalonia. All rights reserved. +// + +#pragma once + +#define IS_NSPANEL + +#include "AvnWindow.mm" + diff --git a/native/Avalonia.Native/src/OSX/AvnView.h b/native/Avalonia.Native/src/OSX/AvnView.h index c6dd90150f..86a68d34c5 100644 --- a/native/Avalonia.Native/src/OSX/AvnView.h +++ b/native/Avalonia.Native/src/OSX/AvnView.h @@ -2,17 +2,15 @@ // Created by Dan Walmsley on 05/05/2022. // Copyright (c) 2022 Avalonia. All rights reserved. // - +#pragma once #import #import #import -#include "window.h" -#import "comimpl.h" -#import "common.h" -#import "WindowImpl.h" -#import "KeyTransform.h" +#include "common.h" +#include "WindowImpl.h" +#include "KeyTransform.h" @class AvnAccessibilityElement; diff --git a/native/Avalonia.Native/src/OSX/AvnView.mm b/native/Avalonia.Native/src/OSX/AvnView.mm index 24cbc25502..02526afbcb 100644 --- a/native/Avalonia.Native/src/OSX/AvnView.mm +++ b/native/Avalonia.Native/src/OSX/AvnView.mm @@ -4,8 +4,9 @@ // #import -#import "AvnView.h" +#include "AvnView.h" #include "automation.h" +#import "WindowInterfaces.h" @implementation AvnView { @@ -194,7 +195,12 @@ - (bool) ignoreUserInput:(bool)trigerInputWhenDisabled { - auto parentWindow = objc_cast([self window]); + if(_parent == nullptr) + { + return TRUE; + } + + auto parentWindow = _parent->GetWindowProtocol(); if(parentWindow == nil || ![parentWindow shouldTryToHandleEvents]) { diff --git a/native/Avalonia.Native/src/OSX/window.mm b/native/Avalonia.Native/src/OSX/AvnWindow.mm similarity index 78% rename from native/Avalonia.Native/src/OSX/window.mm rename to native/Avalonia.Native/src/OSX/AvnWindow.mm index 68a459d088..f51c693777 100644 --- a/native/Avalonia.Native/src/OSX/window.mm +++ b/native/Avalonia.Native/src/OSX/AvnWindow.mm @@ -1,18 +1,37 @@ +// +// Created by Dan Walmsley on 06/05/2022. +// Copyright (c) 2022 Avalonia. All rights reserved. +// + + +#import +#import "WindowProtocol.h" +#import "WindowBaseImpl.h" + +#ifdef IS_NSPANEL +#define BASE_CLASS NSPanel +#define CLASS_NAME AvnPanel +#else +#define BASE_CLASS NSWindow +#define CLASS_NAME AvnWindow +#endif + #import #include "common.h" -#import "window.h" #include "menu.h" #include "automation.h" -#import "WindowBaseImpl.h" +#include "WindowBaseImpl.h" #include "WindowImpl.h" #include "AvnView.h" +#include "WindowInterfaces.h" +#include "PopupImpl.h" -@implementation AvnWindow +@implementation CLASS_NAME { ComPtr _parent; - bool _canBecomeKeyAndMain; bool _closed; bool _isEnabled; + bool _canBecomeKeyWindow; bool _isExtended; AvnMenu* _menu; } @@ -66,7 +85,7 @@ - (void)pollModalSession:(nonnull NSModalSession)session { auto response = [NSApp runModalSession:session]; - + if(response == NSModalResponseContinue) { dispatch_async(dispatch_get_main_queue(), ^{ @@ -85,18 +104,18 @@ if(_menu != nullptr) { auto appMenuItem = ::GetAppMenuItem(); - + if(appMenuItem != nullptr) { auto appMenu = [appMenuItem menu]; - + [appMenu removeItem:appMenuItem]; - + [_menu insertItem:appMenuItem atIndex:0]; - + [_menu setHasGlobalMenuItem:true]; } - + [NSApp setMenu:_menu]; } else @@ -108,22 +127,22 @@ -(void) showAppMenuOnly { auto appMenuItem = ::GetAppMenuItem(); - + if(appMenuItem != nullptr) { auto appMenu = ::GetAppMenu(); - + auto nativeAppMenu = dynamic_cast(appMenu); - + [[appMenuItem menu] removeItem:appMenuItem]; - + if(_menu != nullptr) { [_menu setHasGlobalMenuItem:false]; } - + [nativeAppMenu->GetNative() addItem:appMenuItem]; - + [NSApp setMenu:nativeAppMenu->GetNative()]; } } @@ -134,40 +153,45 @@ { menu = [AvnMenu new]; } - + _menu = menu; } --(void) setCanBecomeKeyAndMain +-(CLASS_NAME*) initWithParent: (WindowBaseImpl*) parent contentRect: (NSRect)contentRect styleMask: (NSWindowStyleMask)styleMask; { - _canBecomeKeyAndMain = true; -} + // https://jameshfisher.com/2020/07/10/why-is-the-contentrect-of-my-nswindow-ignored/ + // create nswindow with specific contentRect, otherwise we wont be able to resize the window + // until several ms after the window is physically on the screen. + self = [super initWithContentRect:contentRect styleMask: styleMask backing:NSBackingStoreBuffered defer:false]; --(AvnWindow*) initWithParent: (WindowBaseImpl*) parent -{ - self = [super init]; [self setReleasedWhenClosed:false]; _parent = parent; [self setDelegate:self]; _closed = false; _isEnabled = true; - + [self backingScaleFactor]; [self setOpaque:NO]; [self setBackgroundColor: [NSColor clearColor]]; + _isExtended = false; + +#ifdef IS_NSPANEL + [self setCollectionBehavior:NSWindowCollectionBehaviorCanJoinAllSpaces|NSWindowCollectionBehaviorFullScreenAuxiliary]; +#endif + return self; } - (BOOL)windowShouldClose:(NSWindow *)sender { auto window = dynamic_cast(_parent.getRaw()); - + if(window != nullptr) { return !window->WindowEvents->Closing(); } - + return true; } @@ -176,6 +200,8 @@ [self backingScaleFactor]; } + + - (void)windowWillClose:(NSNotification *)notification { _closed = true; @@ -191,27 +217,38 @@ -(BOOL)canBecomeKeyWindow { - if (_canBecomeKeyAndMain) + if(_canBecomeKeyWindow) { // If the window has a child window being shown as a dialog then don't allow it to become the key window. for(NSWindow* uch in [self childWindows]) { - auto ch = objc_cast(uch); - if(ch == nil) + if (![uch conformsToProtocol:@protocol(AvnWindowProtocol)]) + { continue; - if (ch.isDialog) + } + + id ch = (id ) uch; + + if(ch.isDialog) return false; } - + return true; } return false; } +#ifndef IS_NSPANEL -(BOOL)canBecomeMainWindow { - return _canBecomeKeyAndMain; + return true; +} +#endif + +-(void)setCanBecomeKeyWindow:(bool)value +{ + _canBecomeKeyWindow = value; } -(bool)shouldTryToHandleEvents @@ -227,7 +264,7 @@ -(void)becomeKeyWindow { [self showWindowMenuWithAppMenu]; - + if(_parent != nullptr) { _parent->BaseEvents->Activated(); @@ -238,7 +275,8 @@ -(void) restoreParentWindow; { - auto parent = objc_cast([self parentWindow]); + auto parent = [self parentWindow]; + if(parent != nil) { [parent removeChildWindow:self]; @@ -248,7 +286,7 @@ - (void)windowDidMiniaturize:(NSNotification *)notification { auto parent = dynamic_cast(_parent.operator->()); - + if(parent != nullptr) { parent->WindowStateChanged(); @@ -258,7 +296,7 @@ - (void)windowDidDeminiaturize:(NSNotification *)notification { auto parent = dynamic_cast(_parent.operator->()); - + if(parent != nullptr) { parent->WindowStateChanged(); @@ -268,7 +306,7 @@ - (void)windowDidResize:(NSNotification *)notification { auto parent = dynamic_cast(_parent.operator->()); - + if(parent != nullptr) { parent->WindowStateChanged(); @@ -278,7 +316,7 @@ - (void)windowWillExitFullScreen:(NSNotification *)notification { auto parent = dynamic_cast(_parent.operator->()); - + if(parent != nullptr) { parent->StartStateTransition(); @@ -288,22 +326,22 @@ - (void)windowDidExitFullScreen:(NSNotification *)notification { auto parent = dynamic_cast(_parent.operator->()); - + if(parent != nullptr) { parent->EndStateTransition(); - + if(parent->Decorations() != SystemDecorationsFull && parent->WindowState() == Maximized) { NSRect screenRect = [[self screen] visibleFrame]; [self setFrame:screenRect display:YES]; } - + if(parent->WindowState() == Minimized) { [self miniaturize:nullptr]; } - + parent->WindowStateChanged(); } } @@ -311,7 +349,7 @@ - (void)windowWillEnterFullScreen:(NSNotification *)notification { auto parent = dynamic_cast(_parent.operator->()); - + if(parent != nullptr) { parent->StartStateTransition(); @@ -321,7 +359,7 @@ - (void)windowDidEnterFullScreen:(NSNotification *)notification { auto parent = dynamic_cast(_parent.operator->()); - + if(parent != nullptr) { parent->EndStateTransition(); @@ -338,28 +376,33 @@ { if(_parent) _parent->BaseEvents->Deactivated(); - + [self showAppMenuOnly]; - + [super resignKeyWindow]; } - (void)windowDidMove:(NSNotification *)notification { AvnPoint position; - + if(_parent != nullptr) { auto cparent = dynamic_cast(_parent.getRaw()); - + if(cparent != nullptr) { + if(!cparent->IsShown()) + { + return; + } + if(cparent->WindowState() == Maximized) { cparent->SetWindowState(Normal); } } - + _parent->GetPosition(&position); _parent->BaseEvents->PositionChanged(position); } @@ -374,7 +417,7 @@ - (void)sendEvent:(NSEvent *)event { [super sendEvent:event]; - + /// This is to detect non-client clicks. This can only be done on Windows... not popups, hence the dynamic_cast. if(_parent != nullptr && dynamic_cast(_parent.getRaw()) != nullptr) { @@ -385,95 +428,39 @@ AvnView* view = _parent->View; NSPoint windowPoint = [event locationInWindow]; NSPoint viewPoint = [view convertPoint:windowPoint fromView:nil]; - + if (!NSPointInRect(viewPoint, view.bounds)) { auto avnPoint = [AvnView toAvnPoint:windowPoint]; auto point = [self translateLocalPoint:avnPoint]; AvnVector delta = { 0, 0 }; - + _parent->BaseEvents->RawMouseEvent(NonClientLeftButtonDown, static_cast([event timestamp] * 1000), AvnInputModifiersNone, point, delta); } } - break; - + break; + case NSEventTypeMouseEntered: { _parent->UpdateCursor(); } - break; - + break; + case NSEventTypeMouseExited: { [[NSCursor arrowCursor] set]; } - break; - + break; + default: break; } } } -@end - -class PopupImpl : public virtual WindowBaseImpl, public IAvnPopup -{ -private: - BEGIN_INTERFACE_MAP() - INHERIT_INTERFACE_MAP(WindowBaseImpl) - INTERFACE_MAP_ENTRY(IAvnPopup, IID_IAvnPopup) - END_INTERFACE_MAP() - virtual ~PopupImpl(){} - ComPtr WindowEvents; - PopupImpl(IAvnWindowEvents* events, IAvnGlContext* gl) : WindowBaseImpl(events, gl) - { - WindowEvents = events; - [Window setLevel:NSPopUpMenuWindowLevel]; - } -protected: - virtual NSWindowStyleMask GetStyle() override - { - return NSWindowStyleMaskBorderless; - } - - virtual HRESULT Resize(double x, double y, AvnPlatformResizeReason reason) override - { - START_COM_CALL; - - @autoreleasepool - { - if (Window != nullptr) - { - [Window setContentSize:NSSize{x, y}]; - - [Window setFrameTopLeftPoint:ToNSPoint(ConvertPointY(lastPositionSet))]; - } - - return S_OK; - } - } -public: - virtual bool ShouldTakeFocusOnShow() override - { - return false; - } -}; - -extern IAvnPopup* CreateAvnPopup(IAvnWindowEvents*events, IAvnGlContext* gl) -{ - @autoreleasepool - { - IAvnPopup* ptr = dynamic_cast(new PopupImpl(events, gl)); - return ptr; - } +- (void)disconnectParent { + _parent = nullptr; } -extern IAvnWindow* CreateAvnWindow(IAvnWindowEvents*events, IAvnGlContext* gl) -{ - @autoreleasepool - { - IAvnWindow* ptr = (IAvnWindow*)new WindowImpl(events, gl); - return ptr; - } -} +@end + diff --git a/native/Avalonia.Native/src/OSX/INSWindowHolder.h b/native/Avalonia.Native/src/OSX/INSWindowHolder.h index adc0dd1990..ae64a53e7d 100644 --- a/native/Avalonia.Native/src/OSX/INSWindowHolder.h +++ b/native/Avalonia.Native/src/OSX/INSWindowHolder.h @@ -10,8 +10,8 @@ struct INSWindowHolder { - virtual AvnWindow* _Nonnull GetNSWindow () = 0; - virtual AvnView* _Nonnull GetNSView () = 0; + virtual NSWindow* _Nonnull GetNSWindow () = 0; + virtual NSView* _Nonnull GetNSView () = 0; }; #endif //AVALONIA_NATIVE_OSX_INSWINDOWHOLDER_H diff --git a/native/Avalonia.Native/src/OSX/PopupImpl.h b/native/Avalonia.Native/src/OSX/PopupImpl.h new file mode 100644 index 0000000000..451019a6a4 --- /dev/null +++ b/native/Avalonia.Native/src/OSX/PopupImpl.h @@ -0,0 +1,9 @@ +// +// Created by Dan Walmsley on 06/05/2022. +// Copyright (c) 2022 Avalonia. All rights reserved. +// + +#ifndef AVALONIA_NATIVE_OSX_POPUPIMPL_H +#define AVALONIA_NATIVE_OSX_POPUPIMPL_H + +#endif //AVALONIA_NATIVE_OSX_POPUPIMPL_H diff --git a/native/Avalonia.Native/src/OSX/PopupImpl.mm b/native/Avalonia.Native/src/OSX/PopupImpl.mm new file mode 100644 index 0000000000..3c5afd9424 --- /dev/null +++ b/native/Avalonia.Native/src/OSX/PopupImpl.mm @@ -0,0 +1,61 @@ +// +// Created by Dan Walmsley on 06/05/2022. +// Copyright (c) 2022 Avalonia. All rights reserved. +// + +#include "WindowInterfaces.h" +#include "AvnView.h" +#include "WindowImpl.h" +#include "automation.h" +#include "menu.h" +#include "common.h" +#import "WindowBaseImpl.h" +#import "WindowProtocol.h" +#import +#include "PopupImpl.h" + +class PopupImpl : public virtual WindowBaseImpl, public IAvnPopup +{ +private: + BEGIN_INTERFACE_MAP() + INHERIT_INTERFACE_MAP(WindowBaseImpl) + INTERFACE_MAP_ENTRY(IAvnPopup, IID_IAvnPopup) + END_INTERFACE_MAP() + virtual ~PopupImpl(){} + ComPtr WindowEvents; + PopupImpl(IAvnWindowEvents* events, IAvnGlContext* gl) : WindowBaseImpl(events, gl) + { + WindowEvents = events; + } +protected: + virtual NSWindowStyleMask GetStyle() override + { + return NSWindowStyleMaskBorderless; + } + + virtual void OnInitialiseNSWindow () override + { + [Window setLevel:NSPopUpMenuWindowLevel]; + } + +public: + virtual bool ShouldTakeFocusOnShow() override + { + return false; + } + + virtual HRESULT Show(bool activate, bool isDialog) override + { + return WindowBaseImpl::Show(activate, true); + } +}; + + +extern IAvnPopup* CreateAvnPopup(IAvnWindowEvents*events, IAvnGlContext* gl) +{ + @autoreleasepool + { + IAvnPopup* ptr = dynamic_cast(new PopupImpl(events, gl)); + return ptr; + } +} diff --git a/native/Avalonia.Native/src/OSX/ResizeScope.h b/native/Avalonia.Native/src/OSX/ResizeScope.h index 7509f93c01..9a43c158fe 100644 --- a/native/Avalonia.Native/src/OSX/ResizeScope.h +++ b/native/Avalonia.Native/src/OSX/ResizeScope.h @@ -6,7 +6,6 @@ #ifndef AVALONIA_NATIVE_OSX_RESIZESCOPE_H #define AVALONIA_NATIVE_OSX_RESIZESCOPE_H -#include "window.h" #include "avalonia-native.h" @class AvnView; diff --git a/native/Avalonia.Native/src/OSX/ResizeScope.mm b/native/Avalonia.Native/src/OSX/ResizeScope.mm index 90e7f5cf15..9f1177af8b 100644 --- a/native/Avalonia.Native/src/OSX/ResizeScope.mm +++ b/native/Avalonia.Native/src/OSX/ResizeScope.mm @@ -5,7 +5,7 @@ #import #include "ResizeScope.h" -#import "AvnView.h" +#include "AvnView.h" ResizeScope::ResizeScope(AvnView *view, AvnPlatformResizeReason reason) { _view = view; diff --git a/native/Avalonia.Native/src/OSX/SystemDialogs.mm b/native/Avalonia.Native/src/OSX/SystemDialogs.mm index 21ad9cfa7c..535b6c3b66 100644 --- a/native/Avalonia.Native/src/OSX/SystemDialogs.mm +++ b/native/Avalonia.Native/src/OSX/SystemDialogs.mm @@ -1,5 +1,4 @@ #include "common.h" -#include "window.h" #include "INSWindowHolder.h" class SystemDialogs : public ComSingleObject diff --git a/native/Avalonia.Native/src/OSX/WindowBaseImpl.h b/native/Avalonia.Native/src/OSX/WindowBaseImpl.h index d2690cee41..83850e780c 100644 --- a/native/Avalonia.Native/src/OSX/WindowBaseImpl.h +++ b/native/Avalonia.Native/src/OSX/WindowBaseImpl.h @@ -6,16 +6,16 @@ #ifndef AVALONIA_NATIVE_OSX_WINDOWBASEIMPL_H #define AVALONIA_NATIVE_OSX_WINDOWBASEIMPL_H -#import "rendertarget.h" +#include "rendertarget.h" #include "INSWindowHolder.h" @class AutoFitContentView; +@class AvnMenu; +@protocol AvnWindowProtocol; class WindowBaseImpl : public virtual ComObject, public virtual IAvnWindowBase, public INSWindowHolder { -private: - NSCursor *cursor; public: FORWARD_IUNKNOWN() @@ -24,22 +24,7 @@ BEGIN_INTERFACE_MAP() INTERFACE_MAP_ENTRY(IAvnWindowBase, IID_IAvnWindowBase) END_INTERFACE_MAP() - virtual ~WindowBaseImpl() { - View = NULL; - Window = NULL; - } - - AutoFitContentView *StandardContainer; - AvnView *View; - AvnWindow *Window; - ComPtr BaseEvents; - ComPtr _glContext; - NSObject *renderTarget; - AvnPoint lastPositionSet; - NSString *_lastTitle; - - bool _shown; - bool _inResize; + virtual ~WindowBaseImpl(); WindowBaseImpl(IAvnWindowBaseEvents *events, IAvnGlContext *gl); @@ -51,12 +36,14 @@ BEGIN_INTERFACE_MAP() virtual HRESULT ObtainNSViewHandleRetained(void **ret) override; - virtual AvnWindow *GetNSWindow() override; + virtual NSWindow *GetNSWindow() override; - virtual AvnView *GetNSView() override; + virtual NSView *GetNSView() override; virtual HRESULT Show(bool activate, bool isDialog) override; + virtual bool IsShown (); + virtual bool ShouldTakeFocusOnShow(); virtual HRESULT Hide() override; @@ -111,11 +98,39 @@ BEGIN_INTERFACE_MAP() virtual bool IsDialog(); + id GetWindowProtocol (); + protected: virtual NSWindowStyleMask GetStyle(); void UpdateStyle(); + + virtual void OnInitialiseNSWindow (); +private: + void CreateNSWindow (bool isDialog); + void CleanNSWindow (); + void InitialiseNSWindow (); + + NSCursor *cursor; + ComPtr _glContext; + bool hasPosition; + NSSize lastSize; + NSSize lastMinSize; + NSSize lastMaxSize; + AvnMenu* lastMenu; + bool _inResize; + +protected: + AvnPoint lastPositionSet; + AutoFitContentView *StandardContainer; + bool _shown; + +public: + NSObject *renderTarget; + NSWindow * Window; + ComPtr BaseEvents; + AvnView *View; }; #endif //AVALONIA_NATIVE_OSX_WINDOWBASEIMPL_H diff --git a/native/Avalonia.Native/src/OSX/WindowBaseImpl.mm b/native/Avalonia.Native/src/OSX/WindowBaseImpl.mm index 43a27a34b2..022769bad0 100644 --- a/native/Avalonia.Native/src/OSX/WindowBaseImpl.mm +++ b/native/Avalonia.Native/src/OSX/WindowBaseImpl.mm @@ -5,13 +5,21 @@ #import #include "common.h" -#import "window.h" -#import "AvnView.h" +#include "AvnView.h" #include "menu.h" #include "automation.h" -#import "cursor.h" +#include "cursor.h" #include "ResizeScope.h" -#import "AutoFitContentView.h" +#include "AutoFitContentView.h" +#import "WindowProtocol.h" +#import "WindowInterfaces.h" +#include "WindowBaseImpl.h" + + +WindowBaseImpl::~WindowBaseImpl() { + View = nullptr; + Window = nullptr; +} WindowBaseImpl::WindowBaseImpl(IAvnWindowBaseEvents *events, IAvnGlContext *gl) { _shown = false; @@ -22,17 +30,14 @@ WindowBaseImpl::WindowBaseImpl(IAvnWindowBaseEvents *events, IAvnGlContext *gl) View = [[AvnView alloc] initWithParent:this]; StandardContainer = [[AutoFitContentView new] initWithContent:View]; - Window = [[AvnWindow alloc] initWithParent:this]; - [Window setContentView:StandardContainer]; - - lastPositionSet.X = 100; - lastPositionSet.Y = 100; - _lastTitle = @""; + lastPositionSet = { 0, 0 }; + hasPosition = false; + lastSize = NSSize { 100, 100 }; + lastMaxSize = NSSize { CGFLOAT_MAX, CGFLOAT_MAX}; + lastMinSize = NSSize { 0, 0 }; - [Window setStyleMask:NSWindowStyleMaskBorderless]; - [Window setBackingType:NSBackingStoreBuffered]; - - [Window setOpaque:false]; + Window = nullptr; + lastMenu = nullptr; } HRESULT WindowBaseImpl::ObtainNSViewHandle(void **ret) { @@ -59,11 +64,11 @@ HRESULT WindowBaseImpl::ObtainNSViewHandleRetained(void **ret) { return S_OK; } -AvnWindow *WindowBaseImpl::GetNSWindow() { +NSWindow *WindowBaseImpl::GetNSWindow() { return Window; } -AvnView *WindowBaseImpl::GetNSView() { +NSView *WindowBaseImpl::GetNSView() { return View; } @@ -83,10 +88,18 @@ HRESULT WindowBaseImpl::Show(bool activate, bool isDialog) { START_COM_CALL; @autoreleasepool { - SetPosition(lastPositionSet); - UpdateStyle(); + CreateNSWindow(isDialog); + InitialiseNSWindow(); + + if(hasPosition) + { + SetPosition(lastPositionSet); + } else + { + [Window center]; + } - [Window setTitle:_lastTitle]; + UpdateStyle(); if (ShouldTakeFocusOnShow() && activate) { [Window orderFront:Window]; @@ -103,6 +116,11 @@ HRESULT WindowBaseImpl::Show(bool activate, bool isDialog) { } } +bool WindowBaseImpl::IsShown () +{ + return _shown; +} + bool WindowBaseImpl::ShouldTakeFocusOnShow() { return true; } @@ -125,7 +143,8 @@ HRESULT WindowBaseImpl::Hide() { @autoreleasepool { if (Window != nullptr) { [Window orderOut:Window]; - [Window restoreParentWindow]; + + [GetWindowProtocol() restoreParentWindow]; } return S_OK; @@ -181,9 +200,8 @@ HRESULT WindowBaseImpl::GetClientSize(AvnSize *ret) { if (ret == nullptr) return E_POINTER; - auto frame = [View frame]; - ret->Width = frame.size.width; - ret->Height = frame.size.height; + ret->Width = lastSize.width; + ret->Height = lastSize.height; return S_OK; } @@ -196,9 +214,11 @@ HRESULT WindowBaseImpl::GetFrameSize(AvnSize *ret) { if (ret == nullptr) return E_POINTER; - auto frame = [Window frame]; - ret->Width = frame.size.width; - ret->Height = frame.size.height; + if(Window != nullptr){ + auto frame = [Window frame]; + ret->Width = frame.size.width; + ret->Height = frame.size.height; + } return S_OK; } @@ -225,8 +245,13 @@ HRESULT WindowBaseImpl::SetMinMaxSize(AvnSize minSize, AvnSize maxSize) { START_COM_CALL; @autoreleasepool { - [Window setContentMinSize:ToNSSize(minSize)]; - [Window setContentMaxSize:ToNSSize(maxSize)]; + lastMinSize = ToNSSize(minSize); + lastMaxSize = ToNSSize(maxSize); + + if(Window != nullptr) { + [Window setContentMinSize:lastMinSize]; + [Window setContentMaxSize:lastMaxSize]; + } return S_OK; } @@ -243,8 +268,8 @@ HRESULT WindowBaseImpl::Resize(double x, double y, AvnPlatformResizeReason reaso auto resizeBlock = ResizeScope(View, reason); @autoreleasepool { - auto maxSize = [Window contentMaxSize]; - auto minSize = [Window contentMinSize]; + auto maxSize = lastMaxSize; + auto minSize = lastMinSize; if (x < minSize.width) { x = minSize.width; @@ -263,12 +288,15 @@ HRESULT WindowBaseImpl::Resize(double x, double y, AvnPlatformResizeReason reaso } @try { + lastSize = NSSize {x, y}; + if (!_shown) { BaseEvents->Resized(AvnSize{x, y}, reason); } - [Window setContentSize:NSSize{x, y}]; - [Window invalidateShadow]; + if(Window != nullptr) { + [Window setContentSize:lastSize]; + } } @finally { _inResize = false; @@ -293,12 +321,14 @@ HRESULT WindowBaseImpl::SetMainMenu(IAvnMenu *menu) { auto nativeMenu = dynamic_cast(menu); - auto nsmenu = nativeMenu->GetNative(); + lastMenu = nativeMenu->GetNative(); - [Window applyMenu:nsmenu]; + if(Window != nullptr) { + [GetWindowProtocol() applyMenu:lastMenu]; - if ([Window isKeyWindow]) { - [Window showWindowMenuWithAppMenu]; + if ([Window isKeyWindow]) { + [GetWindowProtocol() showWindowMenuWithAppMenu]; + } } return S_OK; @@ -334,12 +364,17 @@ HRESULT WindowBaseImpl::GetPosition(AvnPoint *ret) { return E_POINTER; } - auto frame = [Window frame]; + if(Window != nullptr) { + auto frame = [Window frame]; - ret->X = frame.origin.x; - ret->Y = frame.origin.y + frame.size.height; + ret->X = frame.origin.x; + ret->Y = frame.origin.y + frame.size.height; - *ret = ConvertPointY(*ret); + *ret = ConvertPointY(*ret); + } else + { + *ret = lastPositionSet; + } return S_OK; } @@ -350,7 +385,11 @@ HRESULT WindowBaseImpl::SetPosition(AvnPoint point) { @autoreleasepool { lastPositionSet = point; - [Window setFrameTopLeftPoint:ToNSPoint(ConvertPointY(point))]; + hasPosition = true; + + if(Window != nullptr) { + [Window setFrameTopLeftPoint:ToNSPoint(ConvertPointY(point))]; + } return S_OK; } @@ -502,4 +541,75 @@ NSWindowStyleMask WindowBaseImpl::GetStyle() { void WindowBaseImpl::UpdateStyle() { [Window setStyleMask:GetStyle()]; -} \ No newline at end of file +} + +void WindowBaseImpl::CleanNSWindow() { + if(Window != nullptr) { + [GetWindowProtocol() disconnectParent]; + [Window close]; + Window = nullptr; + } +} + +void WindowBaseImpl::CreateNSWindow(bool isDialog) { + if (isDialog) { + if (![Window isKindOfClass:[AvnPanel class]]) { + CleanNSWindow(); + + Window = [[AvnPanel alloc] initWithParent:this contentRect:NSRect{0, 0, lastSize} styleMask:GetStyle()]; + } + } else { + if (![Window isKindOfClass:[AvnWindow class]]) { + CleanNSWindow(); + + Window = [[AvnWindow alloc] initWithParent:this contentRect:NSRect{0, 0, lastSize} styleMask:GetStyle()]; + } + } +} + +void WindowBaseImpl::OnInitialiseNSWindow() +{ + +} + +void WindowBaseImpl::InitialiseNSWindow() { + if(Window != nullptr) { + [Window setContentView:StandardContainer]; + [Window setStyleMask:NSWindowStyleMaskBorderless]; + [Window setBackingType:NSBackingStoreBuffered]; + + [Window setContentSize:lastSize]; + [Window setContentMinSize:lastMinSize]; + [Window setContentMaxSize:lastMaxSize]; + + [Window setOpaque:false]; + + if (lastMenu != nullptr) { + [GetWindowProtocol() applyMenu:lastMenu]; + + if ([Window isKeyWindow]) { + [GetWindowProtocol() showWindowMenuWithAppMenu]; + } + } + + OnInitialiseNSWindow(); + } +} + +id WindowBaseImpl::GetWindowProtocol() { + if(Window == nullptr) + { + return nullptr; + } + + return (id ) Window; +} + +extern IAvnWindow* CreateAvnWindow(IAvnWindowEvents*events, IAvnGlContext* gl) +{ + @autoreleasepool + { + IAvnWindow* ptr = (IAvnWindow*)new WindowImpl(events, gl); + return ptr; + } +} diff --git a/native/Avalonia.Native/src/OSX/WindowImpl.h b/native/Avalonia.Native/src/OSX/WindowImpl.h index b229921baa..db19497b29 100644 --- a/native/Avalonia.Native/src/OSX/WindowImpl.h +++ b/native/Avalonia.Native/src/OSX/WindowImpl.h @@ -6,7 +6,6 @@ #ifndef AVALONIA_NATIVE_OSX_WINDOWIMPL_H #define AVALONIA_NATIVE_OSX_WINDOWIMPL_H - #import "WindowBaseImpl.h" #include "IWindowStateChanged.h" @@ -89,9 +88,14 @@ BEGIN_INTERFACE_MAP() virtual HRESULT SetWindowState (AvnWindowState state) override; virtual bool IsDialog() override; + + virtual void OnInitialiseNSWindow() override; protected: virtual NSWindowStyleMask GetStyle() override; + +private: + NSString *_lastTitle; }; #endif //AVALONIA_NATIVE_OSX_WINDOWIMPL_H diff --git a/native/Avalonia.Native/src/OSX/WindowImpl.mm b/native/Avalonia.Native/src/OSX/WindowImpl.mm index 0d325c4490..d43a8beee4 100644 --- a/native/Avalonia.Native/src/OSX/WindowImpl.mm +++ b/native/Avalonia.Native/src/OSX/WindowImpl.mm @@ -4,10 +4,10 @@ // #import -#import "window.h" -#import "AutoFitContentView.h" -#import "AvnView.h" +#include "AutoFitContentView.h" +#include "AvnView.h" #include "automation.h" +#include "WindowProtocol.h" WindowImpl::WindowImpl(IAvnWindowEvents *events, IAvnGlContext *gl) : WindowBaseImpl(events, gl) { _isClientAreaExtended = false; @@ -19,11 +19,8 @@ WindowImpl::WindowImpl(IAvnWindowEvents *events, IAvnGlContext *gl) : WindowBase _inSetWindowState = false; _lastWindowState = Normal; _actualWindowState = Normal; + _lastTitle = @""; WindowEvents = events; - [Window setCanBecomeKeyAndMain]; - [Window disableCursorRects]; - [Window setTabbingMode:NSWindowTabbingModeDisallowed]; - [Window setCollectionBehavior:NSWindowCollectionBehaviorFullScreenPrimary]; } void WindowImpl::HideOrShowTrafficLights() { @@ -51,11 +48,27 @@ void WindowImpl::HideOrShowTrafficLights() { } } +void WindowImpl::OnInitialiseNSWindow(){ + [GetWindowProtocol() setCanBecomeKeyWindow:true]; + [Window disableCursorRects]; + [Window setTabbingMode:NSWindowTabbingModeDisallowed]; + [Window setCollectionBehavior:NSWindowCollectionBehaviorFullScreenPrimary]; + + [Window setTitle:_lastTitle]; + + if(_isClientAreaExtended) + { + [GetWindowProtocol() setIsExtended:true]; + SetExtendClientArea(true); + } +} + HRESULT WindowImpl::Show(bool activate, bool isDialog) { START_COM_CALL; @autoreleasepool { _isDialog = isDialog; + WindowBaseImpl::Show(activate, isDialog); HideOrShowTrafficLights(); @@ -68,7 +81,7 @@ HRESULT WindowImpl::SetEnabled(bool enable) { START_COM_CALL; @autoreleasepool { - [Window setEnabled:enable]; + [GetWindowProtocol() setEnabled:enable]; return S_OK; } } @@ -328,37 +341,39 @@ HRESULT WindowImpl::SetExtendClientArea(bool enable) { @autoreleasepool { _isClientAreaExtended = enable; - if (enable) { - Window.titleVisibility = NSWindowTitleHidden; + if(Window != nullptr) { + if (enable) { + Window.titleVisibility = NSWindowTitleHidden; - [Window setTitlebarAppearsTransparent:true]; + [Window setTitlebarAppearsTransparent:true]; - auto wantsTitleBar = (_extendClientHints & AvnSystemChrome) || (_extendClientHints & AvnPreferSystemChrome); + auto wantsTitleBar = (_extendClientHints & AvnSystemChrome) || (_extendClientHints & AvnPreferSystemChrome); - if (wantsTitleBar) { - [StandardContainer ShowTitleBar:true]; - } else { - [StandardContainer ShowTitleBar:false]; - } + if (wantsTitleBar) { + [StandardContainer ShowTitleBar:true]; + } else { + [StandardContainer ShowTitleBar:false]; + } - if (_extendClientHints & AvnOSXThickTitleBar) { - Window.toolbar = [NSToolbar new]; - Window.toolbar.showsBaselineSeparator = false; + if (_extendClientHints & AvnOSXThickTitleBar) { + Window.toolbar = [NSToolbar new]; + Window.toolbar.showsBaselineSeparator = false; + } else { + Window.toolbar = nullptr; + } } else { + Window.titleVisibility = NSWindowTitleVisible; Window.toolbar = nullptr; + [Window setTitlebarAppearsTransparent:false]; + View.layer.zPosition = 0; } - } else { - Window.titleVisibility = NSWindowTitleVisible; - Window.toolbar = nullptr; - [Window setTitlebarAppearsTransparent:false]; - View.layer.zPosition = 0; - } - [Window setIsExtended:enable]; + [GetWindowProtocol() setIsExtended:enable]; - HideOrShowTrafficLights(); + HideOrShowTrafficLights(); - UpdateStyle(); + UpdateStyle(); + } return S_OK; } @@ -383,7 +398,7 @@ HRESULT WindowImpl::GetExtendTitleBarHeight(double *ret) { return E_POINTER; } - *ret = [Window getExtendedTitleBarHeight]; + *ret = [GetWindowProtocol() getExtendedTitleBarHeight]; return S_OK; } @@ -417,6 +432,9 @@ HRESULT WindowImpl::SetWindowState(AvnWindowState state) { START_COM_CALL; @autoreleasepool { + auto currentState = _actualWindowState; + _lastWindowState = state; + if (Window == nullptr) { return S_OK; } @@ -427,9 +445,6 @@ HRESULT WindowImpl::SetWindowState(AvnWindowState state) { _inSetWindowState = true; - auto currentState = _actualWindowState; - _lastWindowState = state; - if (currentState == Normal) { _preZoomSize = [Window frame]; } @@ -508,7 +523,7 @@ bool WindowImpl::IsDialog() { } NSWindowStyleMask WindowImpl::GetStyle() { - unsigned long s = NSWindowStyleMaskBorderless; + unsigned long s = this->_isDialog ? NSWindowStyleMaskUtilityWindow : NSWindowStyleMaskBorderless; switch (_decorations) { case SystemDecorationsNone: diff --git a/native/Avalonia.Native/src/OSX/WindowInterfaces.h b/native/Avalonia.Native/src/OSX/WindowInterfaces.h new file mode 100644 index 0000000000..6e6d62e85e --- /dev/null +++ b/native/Avalonia.Native/src/OSX/WindowInterfaces.h @@ -0,0 +1,17 @@ +// +// Created by Dan Walmsley on 06/05/2022. +// Copyright (c) 2022 Avalonia. All rights reserved. +// + +#import +#import +#include "WindowProtocol.h" +#include "WindowBaseImpl.h" + +@interface AvnWindow : NSWindow +-(AvnWindow* _Nonnull) initWithParent: (WindowBaseImpl* _Nonnull) parent contentRect: (NSRect)contentRect styleMask: (NSWindowStyleMask)styleMask; +@end + +@interface AvnPanel : NSPanel +-(AvnPanel* _Nonnull) initWithParent: (WindowBaseImpl* _Nonnull) parent contentRect: (NSRect)contentRect styleMask: (NSWindowStyleMask)styleMask; +@end \ No newline at end of file diff --git a/native/Avalonia.Native/src/OSX/window.h b/native/Avalonia.Native/src/OSX/WindowProtocol.h similarity index 59% rename from native/Avalonia.Native/src/OSX/window.h rename to native/Avalonia.Native/src/OSX/WindowProtocol.h index e9b98ce9ae..0e5c5869e7 100644 --- a/native/Avalonia.Native/src/OSX/window.h +++ b/native/Avalonia.Native/src/OSX/WindowProtocol.h @@ -1,15 +1,15 @@ -#ifndef window_h -#define window_h +// +// Created by Dan Walmsley on 06/05/2022. +// Copyright (c) 2022 Avalonia. All rights reserved. +// -#import "avalonia-native.h" +#pragma once -@class AvnMenu; +#import -class WindowBaseImpl; +@class AvnMenu; -@interface AvnWindow : NSWindow --(AvnWindow* _Nonnull) initWithParent: (WindowBaseImpl* _Nonnull) parent; --(void) setCanBecomeKeyAndMain; +@protocol AvnWindowProtocol -(void) pollModalSession: (NSModalSession _Nonnull) session; -(void) restoreParentWindow; -(bool) shouldTryToHandleEvents; @@ -20,7 +20,8 @@ class WindowBaseImpl; -(double) getExtendedTitleBarHeight; -(void) setIsExtended:(bool)value; +-(void) disconnectParent; -(bool) isDialog; -@end -#endif /* window_h */ +-(void) setCanBecomeKeyWindow:(bool)value; +@end diff --git a/native/Avalonia.Native/src/OSX/automation.h b/native/Avalonia.Native/src/OSX/automation.h index 1727d21f27..367df3619d 100644 --- a/native/Avalonia.Native/src/OSX/automation.h +++ b/native/Avalonia.Native/src/OSX/automation.h @@ -1,5 +1,6 @@ -#import +#pragma once +#import NS_ASSUME_NONNULL_BEGIN class IAvnAutomationPeer; diff --git a/native/Avalonia.Native/src/OSX/automation.mm b/native/Avalonia.Native/src/OSX/automation.mm index 7a0b3b8127..d0c8d7a9db 100644 --- a/native/Avalonia.Native/src/OSX/automation.mm +++ b/native/Avalonia.Native/src/OSX/automation.mm @@ -1,9 +1,8 @@ #include "common.h" -#import "automation.h" -#import "window.h" +#include "automation.h" #include "AvnString.h" -#import "INSWindowHolder.h" -#import "AvnView.h" +#include "INSWindowHolder.h" +#include "AvnView.h" @interface AvnAccessibilityElement (Events) - (void) raiseChildrenChanged; diff --git a/native/Avalonia.Native/src/OSX/main.mm b/native/Avalonia.Native/src/OSX/main.mm index 011f881e94..6ee86b21ae 100644 --- a/native/Avalonia.Native/src/OSX/main.mm +++ b/native/Avalonia.Native/src/OSX/main.mm @@ -1,7 +1,6 @@ //This file will contain actual IID structures #define COM_GUIDS_MATERIALIZE #include "common.h" -#include "window.h" static NSString* s_appTitle = @"Avalonia"; diff --git a/native/Avalonia.Native/src/OSX/menu.mm b/native/Avalonia.Native/src/OSX/menu.mm index fd74edd772..b05588a441 100644 --- a/native/Avalonia.Native/src/OSX/menu.mm +++ b/native/Avalonia.Native/src/OSX/menu.mm @@ -1,7 +1,6 @@ #include "common.h" #include "menu.h" -#import "window.h" #include "KeyTransform.h" #include #include /* For kVK_ constants, and TIS functions. */ diff --git a/samples/BindingDemo/BindingDemo.csproj b/samples/BindingDemo/BindingDemo.csproj index 2c6ff74e5e..bd6054327f 100644 --- a/samples/BindingDemo/BindingDemo.csproj +++ b/samples/BindingDemo/BindingDemo.csproj @@ -5,6 +5,7 @@ + diff --git a/samples/ControlCatalog/ControlCatalog.csproj b/samples/ControlCatalog/ControlCatalog.csproj index 7cbd8a3f9c..e5f07c90c3 100644 --- a/samples/ControlCatalog/ControlCatalog.csproj +++ b/samples/ControlCatalog/ControlCatalog.csproj @@ -25,6 +25,8 @@ + + diff --git a/samples/ControlCatalog/Pages/CalendarDatePickerPage.xaml b/samples/ControlCatalog/Pages/CalendarDatePickerPage.xaml index 2fe16ba8e3..aef96802f4 100644 --- a/samples/ControlCatalog/Pages/CalendarDatePickerPage.xaml +++ b/samples/ControlCatalog/Pages/CalendarDatePickerPage.xaml @@ -10,8 +10,7 @@ Margin="0,16,0,0" HorizontalAlignment="Center" Spacing="16"> - + + diff --git a/samples/ControlCatalog/ViewModels/MainWindowViewModel.cs b/samples/ControlCatalog/ViewModels/MainWindowViewModel.cs index 2b0c30f311..c44024d952 100644 --- a/samples/ControlCatalog/ViewModels/MainWindowViewModel.cs +++ b/samples/ControlCatalog/ViewModels/MainWindowViewModel.cs @@ -18,7 +18,7 @@ namespace ControlCatalog.ViewModels private WindowState _windowState; private WindowState[] _windowStates; private int _transparencyLevel; - private ExtendClientAreaChromeHints _chromeHints; + private ExtendClientAreaChromeHints _chromeHints = ExtendClientAreaChromeHints.PreferSystemChrome; private bool _extendClientAreaEnabled; private bool _systemTitleBarEnabled; private bool _preferSystemChromeEnabled; diff --git a/samples/IntegrationTestApp/IntegrationTestApp.csproj b/samples/IntegrationTestApp/IntegrationTestApp.csproj index e8338adae6..4284399357 100644 --- a/samples/IntegrationTestApp/IntegrationTestApp.csproj +++ b/samples/IntegrationTestApp/IntegrationTestApp.csproj @@ -1,4 +1,4 @@ - + WinExe net6.0 @@ -18,6 +18,7 @@ + diff --git a/samples/PlatformSanityChecks/PlatformSanityChecks.csproj b/samples/PlatformSanityChecks/PlatformSanityChecks.csproj index 9660d2a90d..4f7f06b529 100644 --- a/samples/PlatformSanityChecks/PlatformSanityChecks.csproj +++ b/samples/PlatformSanityChecks/PlatformSanityChecks.csproj @@ -7,6 +7,7 @@ + diff --git a/samples/Previewer/Previewer.csproj b/samples/Previewer/Previewer.csproj index c1d14cba26..98560e9ab0 100644 --- a/samples/Previewer/Previewer.csproj +++ b/samples/Previewer/Previewer.csproj @@ -9,6 +9,9 @@ + + + diff --git a/samples/RenderDemo/RenderDemo.csproj b/samples/RenderDemo/RenderDemo.csproj index 18a4ee5662..3c62af1eaf 100644 --- a/samples/RenderDemo/RenderDemo.csproj +++ b/samples/RenderDemo/RenderDemo.csproj @@ -12,6 +12,7 @@ + diff --git a/samples/Sandbox/Sandbox.csproj b/samples/Sandbox/Sandbox.csproj index f3c38cd96e..eab654acb6 100644 --- a/samples/Sandbox/Sandbox.csproj +++ b/samples/Sandbox/Sandbox.csproj @@ -10,6 +10,7 @@ + diff --git a/samples/VirtualizationDemo/VirtualizationDemo.csproj b/samples/VirtualizationDemo/VirtualizationDemo.csproj index 2c6ff74e5e..bd6054327f 100644 --- a/samples/VirtualizationDemo/VirtualizationDemo.csproj +++ b/samples/VirtualizationDemo/VirtualizationDemo.csproj @@ -5,6 +5,7 @@ + diff --git a/samples/interop/Direct3DInteropSample/Direct3DInteropSample.csproj b/samples/interop/Direct3DInteropSample/Direct3DInteropSample.csproj index cd9963a2e5..81b94b4d09 100644 --- a/samples/interop/Direct3DInteropSample/Direct3DInteropSample.csproj +++ b/samples/interop/Direct3DInteropSample/Direct3DInteropSample.csproj @@ -22,6 +22,7 @@ + diff --git a/samples/interop/NativeEmbedSample/NativeEmbedSample.csproj b/samples/interop/NativeEmbedSample/NativeEmbedSample.csproj index c25442b52c..2f3ea85e46 100644 --- a/samples/interop/NativeEmbedSample/NativeEmbedSample.csproj +++ b/samples/interop/NativeEmbedSample/NativeEmbedSample.csproj @@ -9,6 +9,7 @@ + diff --git a/src/Android/Avalonia.Android/Platform/SkiaPlatform/TopLevelImpl.cs b/src/Android/Avalonia.Android/Platform/SkiaPlatform/TopLevelImpl.cs index 8a475676a5..65a9adc937 100644 --- a/src/Android/Avalonia.Android/Platform/SkiaPlatform/TopLevelImpl.cs +++ b/src/Android/Avalonia.Android/Platform/SkiaPlatform/TopLevelImpl.cs @@ -40,7 +40,7 @@ namespace Avalonia.Android.Platform.SkiaPlatform _gl = GlPlatformSurface.TryCreate(this); _framebuffer = new FramebufferManager(this); - RenderScaling = (int)_view.Scaling; + RenderScaling = _view.Scaling; MaxClientSize = new PixelSize(_view.Resources.DisplayMetrics.WidthPixels, _view.Resources.DisplayMetrics.HeightPixels).ToSize(RenderScaling); diff --git a/src/Avalonia.Base/Animation/IAnimation.cs b/src/Avalonia.Base/Animation/IAnimation.cs index 436a765d27..eda86ed106 100644 --- a/src/Avalonia.Base/Animation/IAnimation.cs +++ b/src/Avalonia.Base/Animation/IAnimation.cs @@ -1,12 +1,14 @@ using System; using System.Threading; using System.Threading.Tasks; +using Avalonia.Metadata; namespace Avalonia.Animation { /// /// Interface for Animation objects /// + [NotClientImplementable] public interface IAnimation { /// diff --git a/src/Avalonia.Base/Animation/IAnimationSetter.cs b/src/Avalonia.Base/Animation/IAnimationSetter.cs index 6a1d3539e2..8c4ba95517 100644 --- a/src/Avalonia.Base/Animation/IAnimationSetter.cs +++ b/src/Avalonia.Base/Animation/IAnimationSetter.cs @@ -1,5 +1,8 @@ +using Avalonia.Metadata; + namespace Avalonia.Animation { + [NotClientImplementable] public interface IAnimationSetter { AvaloniaProperty? Property { get; set; } diff --git a/src/Avalonia.Base/Animation/IAnimator.cs b/src/Avalonia.Base/Animation/IAnimator.cs index f64ac9f913..06ba8dc329 100644 --- a/src/Avalonia.Base/Animation/IAnimator.cs +++ b/src/Avalonia.Base/Animation/IAnimator.cs @@ -1,11 +1,13 @@ using System; using System.Collections.Generic; +using Avalonia.Metadata; namespace Avalonia.Animation { /// /// Interface for Animator objects /// + [NotClientImplementable] public interface IAnimator : IList { /// diff --git a/src/Avalonia.Base/Animation/IClock.cs b/src/Avalonia.Base/Animation/IClock.cs index ae44102077..7b3b7b3924 100644 --- a/src/Avalonia.Base/Animation/IClock.cs +++ b/src/Avalonia.Base/Animation/IClock.cs @@ -1,9 +1,9 @@ using System; -using System.Collections.Generic; -using System.Text; +using Avalonia.Metadata; namespace Avalonia.Animation { + [NotClientImplementable] public interface IClock : IObservable { PlayState PlayState { get; set; } diff --git a/src/Avalonia.Base/Animation/IGlobalClock.cs b/src/Avalonia.Base/Animation/IGlobalClock.cs index b0455e2c80..138dc07539 100644 --- a/src/Avalonia.Base/Animation/IGlobalClock.cs +++ b/src/Avalonia.Base/Animation/IGlobalClock.cs @@ -1,9 +1,8 @@ -using System; -using System.Collections.Generic; -using System.Text; +using Avalonia.Metadata; namespace Avalonia.Animation { + [NotClientImplementable] public interface IGlobalClock : IClock { } diff --git a/src/Avalonia.Base/Animation/ITransition.cs b/src/Avalonia.Base/Animation/ITransition.cs index 241ca208d1..639b92ce35 100644 --- a/src/Avalonia.Base/Animation/ITransition.cs +++ b/src/Avalonia.Base/Animation/ITransition.cs @@ -1,10 +1,12 @@ using System; +using Avalonia.Metadata; namespace Avalonia.Animation { /// /// Interface for Transition objects. /// + [NotClientImplementable] public interface ITransition { /// diff --git a/src/Avalonia.Base/Collections/AvaloniaList.cs b/src/Avalonia.Base/Collections/AvaloniaList.cs index 9972e72dd4..a05c9c5da3 100644 --- a/src/Avalonia.Base/Collections/AvaloniaList.cs +++ b/src/Avalonia.Base/Collections/AvaloniaList.cs @@ -394,7 +394,13 @@ namespace Avalonia.Collections } while (en.MoveNext()); if (notificationItems is not null) + { NotifyAdd(notificationItems, index); + } + else + { + NotifyCountChanged(); + } } } } diff --git a/src/Avalonia.Base/Controls/INameScope.cs b/src/Avalonia.Base/Controls/INameScope.cs index 1ca7db2f37..a12e88c055 100644 --- a/src/Avalonia.Base/Controls/INameScope.cs +++ b/src/Avalonia.Base/Controls/INameScope.cs @@ -1,5 +1,4 @@ -using System; -using System.Threading.Tasks; +using Avalonia.Metadata; using Avalonia.Utilities; namespace Avalonia.Controls @@ -7,6 +6,7 @@ namespace Avalonia.Controls /// /// Defines a name scope. /// + [NotClientImplementable] public interface INameScope { /// diff --git a/src/Avalonia.Base/Controls/IPseudoClasses.cs b/src/Avalonia.Base/Controls/IPseudoClasses.cs index 27290716d0..eda521727f 100644 --- a/src/Avalonia.Base/Controls/IPseudoClasses.cs +++ b/src/Avalonia.Base/Controls/IPseudoClasses.cs @@ -1,9 +1,11 @@ +using Avalonia.Metadata; namespace Avalonia.Controls { /// /// Exposes an interface for setting pseudoclasses on a collection. /// + [NotClientImplementable] public interface IPseudoClasses { /// diff --git a/src/Avalonia.Base/Controls/IResourceHost.cs b/src/Avalonia.Base/Controls/IResourceHost.cs index ea34a8b39a..286f0e36ef 100644 --- a/src/Avalonia.Base/Controls/IResourceHost.cs +++ b/src/Avalonia.Base/Controls/IResourceHost.cs @@ -1,6 +1,5 @@ using System; - -#nullable enable +using Avalonia.Metadata; namespace Avalonia.Controls { @@ -10,6 +9,7 @@ namespace Avalonia.Controls /// /// This interface is implemented by and `Application`. /// + [NotClientImplementable] public interface IResourceHost : IResourceNode { /// diff --git a/src/Avalonia.Base/Controls/IResourceNode.cs b/src/Avalonia.Base/Controls/IResourceNode.cs index 73bfeaf161..d6c900f97f 100644 --- a/src/Avalonia.Base/Controls/IResourceNode.cs +++ b/src/Avalonia.Base/Controls/IResourceNode.cs @@ -1,6 +1,5 @@ using System; - -#nullable enable +using Avalonia.Metadata; namespace Avalonia.Controls { @@ -12,6 +11,7 @@ namespace Avalonia.Controls /// () and resource providers such as /// (see ). /// + [NotClientImplementable] public interface IResourceNode { /// diff --git a/src/Avalonia.Base/Controls/ISetInheritanceParent.cs b/src/Avalonia.Base/Controls/ISetInheritanceParent.cs index dbf8c68892..e85e025005 100644 --- a/src/Avalonia.Base/Controls/ISetInheritanceParent.cs +++ b/src/Avalonia.Base/Controls/ISetInheritanceParent.cs @@ -1,4 +1,4 @@ -#nullable enable +using Avalonia.Metadata; namespace Avalonia.Controls { @@ -10,6 +10,7 @@ namespace Avalonia.Controls /// Additionally, also sets the inheritance parent; this /// interface is only needed where the logical and inheritance parents differ. /// + [NotClientImplementable] public interface ISetInheritanceParent { /// diff --git a/src/Avalonia.Base/Controls/ISetLogicalParent.cs b/src/Avalonia.Base/Controls/ISetLogicalParent.cs index 85bda05961..7f1b4b5d87 100644 --- a/src/Avalonia.Base/Controls/ISetLogicalParent.cs +++ b/src/Avalonia.Base/Controls/ISetLogicalParent.cs @@ -1,6 +1,5 @@ using Avalonia.LogicalTree; - -#nullable enable +using Avalonia.Metadata; namespace Avalonia.Controls { @@ -10,6 +9,7 @@ namespace Avalonia.Controls /// /// You should not usually need to use this interface - it is for advanced scenarios only. /// + [NotClientImplementable] public interface ISetLogicalParent { /// diff --git a/src/Avalonia.Base/Data/Core/ExpressionNode.cs b/src/Avalonia.Base/Data/Core/ExpressionNode.cs index 54785f18e8..4f755ff140 100644 --- a/src/Avalonia.Base/Data/Core/ExpressionNode.cs +++ b/src/Avalonia.Base/Data/Core/ExpressionNode.cs @@ -98,36 +98,36 @@ namespace Avalonia.Data.Core private void ValueChanged(object? value, bool notify) { - if (_subscriber is null) - return; - - var notification = value as BindingNotification; - - if (notification == null) + if (_subscriber is { } subscriber) { - LastValue = value != null ? new WeakReference(value) : NullReference; + var notification = value as BindingNotification; + var next = Next; - if (Next != null) + if (notification == null) { - Next.Target = LastValue; + LastValue = value != null ? new WeakReference(value) : NullReference; + if (next != null) + { + next.Target = LastValue; + } + else if (notify) + { + subscriber(value); + } } - else if (notify) + else { - _subscriber(value); - } - } - else - { - LastValue = notification.Value != null ? new WeakReference(notification.Value) : NullReference; + LastValue = notification.Value != null ? new WeakReference(notification.Value) : NullReference; - if (Next != null) - { - Next.Target = LastValue; - } + if (next != null) + { + next.Target = LastValue; + } - if (Next == null || notification.Error != null) - { - _subscriber(value); + if (next == null || notification.Error != null) + { + subscriber(value); + } } } } diff --git a/src/Avalonia.Base/Data/Core/IPropertyInfo.cs b/src/Avalonia.Base/Data/Core/IPropertyInfo.cs index 4d80feb4ba..0cb8a937cc 100644 --- a/src/Avalonia.Base/Data/Core/IPropertyInfo.cs +++ b/src/Avalonia.Base/Data/Core/IPropertyInfo.cs @@ -1,7 +1,9 @@ using System; +using Avalonia.Metadata; namespace Avalonia.Data.Core { + [NotClientImplementable] public interface IPropertyInfo { string Name { get; } diff --git a/src/Avalonia.Base/Data/Core/Plugins/InpcPropertyAccessorPlugin.cs b/src/Avalonia.Base/Data/Core/Plugins/InpcPropertyAccessorPlugin.cs index 33cecd10a7..b93bf87fdf 100644 --- a/src/Avalonia.Base/Data/Core/Plugins/InpcPropertyAccessorPlugin.cs +++ b/src/Avalonia.Base/Data/Core/Plugins/InpcPropertyAccessorPlugin.cs @@ -17,7 +17,7 @@ namespace Avalonia.Data.Core.Plugins new Dictionary<(Type, string), PropertyInfo?>(); /// - public bool Match(object obj, string propertyName) => GetFirstPropertyWithName(obj.GetType(), propertyName) != null; + public bool Match(object obj, string propertyName) => GetFirstPropertyWithName(obj, propertyName) != null; /// /// Starts monitoring the value of a property on an object. @@ -36,7 +36,7 @@ namespace Avalonia.Data.Core.Plugins if (!reference.TryGetTarget(out var instance) || instance is null) return null; - var p = GetFirstPropertyWithName(instance.GetType(), propertyName); + var p = GetFirstPropertyWithName(instance, propertyName); if (p != null) { @@ -50,8 +50,16 @@ namespace Avalonia.Data.Core.Plugins } } - private PropertyInfo? GetFirstPropertyWithName(Type type, string propertyName) + private const BindingFlags PropertyBindingFlags = + BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Static | BindingFlags.Instance; + + private PropertyInfo? GetFirstPropertyWithName(object instance, string propertyName) { + if (instance is IReflectableType reflectableType) + return reflectableType.GetTypeInfo().GetProperty(propertyName, PropertyBindingFlags); + + var type = instance.GetType(); + var key = (type, propertyName); if (!_propertyLookup.TryGetValue(key, out var propertyInfo)) @@ -66,10 +74,7 @@ namespace Avalonia.Data.Core.Plugins { PropertyInfo? found = null; - const BindingFlags bindingFlags = - BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Static | BindingFlags.Instance; - - var properties = type.GetProperties(bindingFlags); + var properties = type.GetProperties(PropertyBindingFlags); foreach (PropertyInfo propertyInfo in properties) { diff --git a/src/Avalonia.Base/Data/IBinding.cs b/src/Avalonia.Base/Data/IBinding.cs index 9535cf608e..7d44bf09b5 100644 --- a/src/Avalonia.Base/Data/IBinding.cs +++ b/src/Avalonia.Base/Data/IBinding.cs @@ -1,8 +1,11 @@ +using Avalonia.Metadata; + namespace Avalonia.Data { /// /// Holds a binding that can be applied to a property on an object. /// + [NotClientImplementable] public interface IBinding { /// diff --git a/src/Avalonia.Base/DirectPropertyBase.cs b/src/Avalonia.Base/DirectPropertyBase.cs index 9c1ffce24c..efcb7dfecb 100644 --- a/src/Avalonia.Base/DirectPropertyBase.cs +++ b/src/Avalonia.Base/DirectPropertyBase.cs @@ -2,7 +2,6 @@ using Avalonia.Data; using Avalonia.Reactive; using Avalonia.Styling; -using Avalonia.Utilities; namespace Avalonia { @@ -188,10 +187,10 @@ namespace Avalonia } else if (value is ITemplate template && !typeof(ITemplate).IsAssignableFrom(PropertyType)) { - return new PropertySetterLazyInstance( + return new PropertySetterTemplateInstance( target, this, - () => (TValue)template.Build()); + template); } else { diff --git a/src/Avalonia.Base/IAvaloniaObject.cs b/src/Avalonia.Base/IAvaloniaObject.cs index 00f5062f9e..3b0016903b 100644 --- a/src/Avalonia.Base/IAvaloniaObject.cs +++ b/src/Avalonia.Base/IAvaloniaObject.cs @@ -1,11 +1,13 @@ using System; using Avalonia.Data; +using Avalonia.Metadata; namespace Avalonia { /// /// Interface for getting/setting values on an object. /// + [NotClientImplementable] public interface IAvaloniaObject { /// diff --git a/src/Avalonia.Base/IDataContextProvider.cs b/src/Avalonia.Base/IDataContextProvider.cs index 1172adcaa4..1d381cf4a5 100644 --- a/src/Avalonia.Base/IDataContextProvider.cs +++ b/src/Avalonia.Base/IDataContextProvider.cs @@ -1,10 +1,11 @@ -#nullable enable +using Avalonia.Metadata; namespace Avalonia { /// /// Defines an element with a data context that can be used for binding. /// + [NotClientImplementable] public interface IDataContextProvider : IAvaloniaObject { /// diff --git a/src/Avalonia.Base/IDirectPropertyAccessor.cs b/src/Avalonia.Base/IDirectPropertyAccessor.cs index e34483fa7b..476a36f372 100644 --- a/src/Avalonia.Base/IDirectPropertyAccessor.cs +++ b/src/Avalonia.Base/IDirectPropertyAccessor.cs @@ -1,4 +1,5 @@ using System; +using Avalonia.Metadata; namespace Avalonia { diff --git a/src/Avalonia.Base/IDirectPropertyMetadata.cs b/src/Avalonia.Base/IDirectPropertyMetadata.cs index 4fd4a3a6b7..7d74470a13 100644 --- a/src/Avalonia.Base/IDirectPropertyMetadata.cs +++ b/src/Avalonia.Base/IDirectPropertyMetadata.cs @@ -1,8 +1,11 @@ +using Avalonia.Metadata; + namespace Avalonia { /// /// Untyped interface to /// + [NotClientImplementable] public interface IDirectPropertyMetadata { /// diff --git a/src/Avalonia.Base/IStyledElement.cs b/src/Avalonia.Base/IStyledElement.cs index a068d4a5bf..4eed54de45 100644 --- a/src/Avalonia.Base/IStyledElement.cs +++ b/src/Avalonia.Base/IStyledElement.cs @@ -2,12 +2,12 @@ using System.ComponentModel; using Avalonia.Controls; using Avalonia.LogicalTree; +using Avalonia.Metadata; using Avalonia.Styling; -#nullable enable - namespace Avalonia { + [NotClientImplementable] public interface IStyledElement : IStyleable, IStyleHost, diff --git a/src/Avalonia.Base/IStyledPropertyMetadata.cs b/src/Avalonia.Base/IStyledPropertyMetadata.cs index 6b29b5f977..89990338e0 100644 --- a/src/Avalonia.Base/IStyledPropertyMetadata.cs +++ b/src/Avalonia.Base/IStyledPropertyMetadata.cs @@ -1,8 +1,11 @@ +using Avalonia.Metadata; + namespace Avalonia { /// /// Untyped interface to /// + [NotClientImplementable] public interface IStyledPropertyMetadata { /// diff --git a/src/Avalonia.Base/Input/IAccessKeyHandler.cs b/src/Avalonia.Base/Input/IAccessKeyHandler.cs index e484d003c7..93a50968e2 100644 --- a/src/Avalonia.Base/Input/IAccessKeyHandler.cs +++ b/src/Avalonia.Base/Input/IAccessKeyHandler.cs @@ -1,8 +1,11 @@ +using Avalonia.Metadata; + namespace Avalonia.Input { /// /// Defines the interface for classes that handle access keys for a window. /// + [Unstable] public interface IAccessKeyHandler { /// diff --git a/src/Avalonia.Base/Input/IFocusManager.cs b/src/Avalonia.Base/Input/IFocusManager.cs index 2510479a8e..0c85cad2f7 100644 --- a/src/Avalonia.Base/Input/IFocusManager.cs +++ b/src/Avalonia.Base/Input/IFocusManager.cs @@ -1,8 +1,11 @@ +using Avalonia.Metadata; + namespace Avalonia.Input { /// /// Manages focus for the application. /// + [NotClientImplementable] public interface IFocusManager { /// diff --git a/src/Avalonia.Base/Input/IInputDevice.cs b/src/Avalonia.Base/Input/IInputDevice.cs index ab0fae65df..fe4f584f18 100644 --- a/src/Avalonia.Base/Input/IInputDevice.cs +++ b/src/Avalonia.Base/Input/IInputDevice.cs @@ -1,7 +1,9 @@ using Avalonia.Input.Raw; +using Avalonia.Metadata; namespace Avalonia.Input { + [NotClientImplementable] public interface IInputDevice { /// diff --git a/src/Avalonia.Base/Input/IInputElement.cs b/src/Avalonia.Base/Input/IInputElement.cs index d1552a3a2a..78001143d7 100644 --- a/src/Avalonia.Base/Input/IInputElement.cs +++ b/src/Avalonia.Base/Input/IInputElement.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using Avalonia.Interactivity; +using Avalonia.Metadata; using Avalonia.VisualTree; #nullable enable @@ -10,6 +11,7 @@ namespace Avalonia.Input /// /// Defines input-related functionality for a control. /// + [NotClientImplementable] public interface IInputElement : IInteractive, IVisual { /// diff --git a/src/Avalonia.Base/Input/IInputManager.cs b/src/Avalonia.Base/Input/IInputManager.cs index 80b71d3e47..658226a519 100644 --- a/src/Avalonia.Base/Input/IInputManager.cs +++ b/src/Avalonia.Base/Input/IInputManager.cs @@ -1,5 +1,6 @@ using System; using Avalonia.Input.Raw; +using Avalonia.Metadata; namespace Avalonia.Input { @@ -7,6 +8,7 @@ namespace Avalonia.Input /// Receives input from the windowing subsystem and dispatches it to interested parties /// for processing. /// + [NotClientImplementable] public interface IInputManager { /// diff --git a/src/Avalonia.Base/Input/IInputRoot.cs b/src/Avalonia.Base/Input/IInputRoot.cs index 98e8699573..7edc69df52 100644 --- a/src/Avalonia.Base/Input/IInputRoot.cs +++ b/src/Avalonia.Base/Input/IInputRoot.cs @@ -1,8 +1,11 @@ +using Avalonia.Metadata; + namespace Avalonia.Input { /// /// Defines the interface for top-level input elements. /// + [NotClientImplementable] public interface IInputRoot : IInputElement { /// diff --git a/src/Avalonia.Base/Input/IKeyboardDevice.cs b/src/Avalonia.Base/Input/IKeyboardDevice.cs index d0e84e5ad0..c8db8bf16f 100644 --- a/src/Avalonia.Base/Input/IKeyboardDevice.cs +++ b/src/Avalonia.Base/Input/IKeyboardDevice.cs @@ -1,5 +1,6 @@ using System; using System.ComponentModel; +using Avalonia.Metadata; namespace Avalonia.Input { @@ -50,6 +51,7 @@ namespace Avalonia.Input KeyboardMask = Alt | Control | Shift | Meta } + [NotClientImplementable] public interface IKeyboardDevice : IInputDevice, INotifyPropertyChanged { IInputElement? FocusedElement { get; } diff --git a/src/Avalonia.Base/Input/IKeyboardNavigationHandler.cs b/src/Avalonia.Base/Input/IKeyboardNavigationHandler.cs index 88d00b3b50..3bcd6d6206 100644 --- a/src/Avalonia.Base/Input/IKeyboardNavigationHandler.cs +++ b/src/Avalonia.Base/Input/IKeyboardNavigationHandler.cs @@ -1,8 +1,11 @@ +using Avalonia.Metadata; + namespace Avalonia.Input { /// /// Defines the interface for classes that handle keyboard navigation for a window. /// + [Unstable] public interface IKeyboardNavigationHandler { /// diff --git a/src/Avalonia.Base/Input/IMainMenu.cs b/src/Avalonia.Base/Input/IMainMenu.cs index 67b58c0ffc..213a979c28 100644 --- a/src/Avalonia.Base/Input/IMainMenu.cs +++ b/src/Avalonia.Base/Input/IMainMenu.cs @@ -1,5 +1,6 @@ using System; using Avalonia.Interactivity; +using Avalonia.Metadata; using Avalonia.VisualTree; namespace Avalonia.Input @@ -7,6 +8,7 @@ namespace Avalonia.Input /// /// Defines the interface for a window's main menu. /// + [NotClientImplementable] public interface IMainMenu : IVisual { /// diff --git a/src/Avalonia.Base/Input/IMouseDevice.cs b/src/Avalonia.Base/Input/IMouseDevice.cs index 6b7f0e76e5..2d66397d63 100644 --- a/src/Avalonia.Base/Input/IMouseDevice.cs +++ b/src/Avalonia.Base/Input/IMouseDevice.cs @@ -1,10 +1,12 @@ using System; +using Avalonia.Metadata; namespace Avalonia.Input { /// /// Represents a mouse device. /// + [NotClientImplementable] public interface IMouseDevice : IPointerDevice { /// diff --git a/src/Avalonia.Base/Input/IPointer.cs b/src/Avalonia.Base/Input/IPointer.cs index 7af48cef82..66aeacadc9 100644 --- a/src/Avalonia.Base/Input/IPointer.cs +++ b/src/Avalonia.Base/Input/IPointer.cs @@ -1,5 +1,8 @@ +using Avalonia.Metadata; + namespace Avalonia.Input { + [NotClientImplementable] public interface IPointer { int Id { get; } diff --git a/src/Avalonia.Base/Input/IPointerDevice.cs b/src/Avalonia.Base/Input/IPointerDevice.cs index 0096bb77bf..0993835feb 100644 --- a/src/Avalonia.Base/Input/IPointerDevice.cs +++ b/src/Avalonia.Base/Input/IPointerDevice.cs @@ -1,9 +1,11 @@ using System; using Avalonia.VisualTree; using Avalonia.Input.Raw; +using Avalonia.Metadata; namespace Avalonia.Input { + [NotClientImplementable] public interface IPointerDevice : IInputDevice { /// diff --git a/src/Avalonia.Base/Input/InputElement.cs b/src/Avalonia.Base/Input/InputElement.cs index 9fe07f62dd..f4e25ebada 100644 --- a/src/Avalonia.Base/Input/InputElement.cs +++ b/src/Avalonia.Base/Input/InputElement.cs @@ -94,7 +94,7 @@ namespace Avalonia.Input /// public static readonly RoutedEvent KeyDownEvent = RoutedEvent.Register( - "KeyDown", + nameof(KeyDown), RoutingStrategies.Tunnel | RoutingStrategies.Bubble); /// @@ -102,7 +102,7 @@ namespace Avalonia.Input /// public static readonly RoutedEvent KeyUpEvent = RoutedEvent.Register( - "KeyUp", + nameof(KeyUp), RoutingStrategies.Tunnel | RoutingStrategies.Bubble); /// @@ -116,7 +116,7 @@ namespace Avalonia.Input /// public static readonly RoutedEvent TextInputEvent = RoutedEvent.Register( - "TextInput", + nameof(TextInput), RoutingStrategies.Tunnel | RoutingStrategies.Bubble); /// @@ -124,7 +124,7 @@ namespace Avalonia.Input /// public static readonly RoutedEvent TextInputMethodClientRequestedEvent = RoutedEvent.Register( - "TextInputMethodClientRequested", + nameof(TextInputMethodClientRequested), RoutingStrategies.Tunnel | RoutingStrategies.Bubble); /// @@ -144,7 +144,7 @@ namespace Avalonia.Input /// public static readonly RoutedEvent PointerMovedEvent = RoutedEvent.Register( - "PointerMove", + nameof(PointerMoved), RoutingStrategies.Tunnel | RoutingStrategies.Bubble); /// @@ -152,7 +152,7 @@ namespace Avalonia.Input /// public static readonly RoutedEvent PointerPressedEvent = RoutedEvent.Register( - "PointerPressed", + nameof(PointerPressed), RoutingStrategies.Tunnel | RoutingStrategies.Bubble); /// @@ -160,7 +160,7 @@ namespace Avalonia.Input /// public static readonly RoutedEvent PointerReleasedEvent = RoutedEvent.Register( - "PointerReleased", + nameof(PointerReleased), RoutingStrategies.Tunnel | RoutingStrategies.Bubble); /// @@ -168,7 +168,7 @@ namespace Avalonia.Input /// public static readonly RoutedEvent PointerCaptureLostEvent = RoutedEvent.Register( - "PointerCaptureLost", + nameof(PointerCaptureLost), RoutingStrategies.Direct); /// @@ -176,7 +176,7 @@ namespace Avalonia.Input /// public static readonly RoutedEvent PointerWheelChangedEvent = RoutedEvent.Register( - "PointerWheelChanged", + nameof(PointerWheelChanged), RoutingStrategies.Tunnel | RoutingStrategies.Bubble); /// diff --git a/src/Avalonia.Base/Input/Platform/IClipboard.cs b/src/Avalonia.Base/Input/Platform/IClipboard.cs index eb880904eb..bf2a5a8602 100644 --- a/src/Avalonia.Base/Input/Platform/IClipboard.cs +++ b/src/Avalonia.Base/Input/Platform/IClipboard.cs @@ -1,7 +1,9 @@ using System.Threading.Tasks; +using Avalonia.Metadata; namespace Avalonia.Input.Platform { + [NotClientImplementable] public interface IClipboard { Task GetTextAsync(); diff --git a/src/Avalonia.Base/Input/Platform/IPlatformDragSource.cs b/src/Avalonia.Base/Input/Platform/IPlatformDragSource.cs index 30d8ee5337..8d1b93087e 100644 --- a/src/Avalonia.Base/Input/Platform/IPlatformDragSource.cs +++ b/src/Avalonia.Base/Input/Platform/IPlatformDragSource.cs @@ -1,7 +1,9 @@ using System.Threading.Tasks; +using Avalonia.Metadata; namespace Avalonia.Input.Platform { + [Unstable] public interface IPlatformDragSource { Task DoDragDrop(PointerEventArgs triggerEvent, IDataObject data, DragDropEffects allowedEffects); diff --git a/src/Avalonia.Base/Input/Raw/IDragDropDevice.cs b/src/Avalonia.Base/Input/Raw/IDragDropDevice.cs index f7b7914bd1..3bcc9fadd3 100644 --- a/src/Avalonia.Base/Input/Raw/IDragDropDevice.cs +++ b/src/Avalonia.Base/Input/Raw/IDragDropDevice.cs @@ -1,5 +1,8 @@ -namespace Avalonia.Input.Raw +using Avalonia.Metadata; + +namespace Avalonia.Input.Raw { + [NotClientImplementable] public interface IDragDropDevice : IInputDevice { } diff --git a/src/Avalonia.Base/Input/TextInput/ITextInputMethodImpl.cs b/src/Avalonia.Base/Input/TextInput/ITextInputMethodImpl.cs index 4404c903b7..be7ad81f81 100644 --- a/src/Avalonia.Base/Input/TextInput/ITextInputMethodImpl.cs +++ b/src/Avalonia.Base/Input/TextInput/ITextInputMethodImpl.cs @@ -1,5 +1,8 @@ +using Avalonia.Metadata; + namespace Avalonia.Input.TextInput { + [Unstable] public interface ITextInputMethodImpl { void SetClient(ITextInputMethodClient? client); @@ -8,6 +11,7 @@ namespace Avalonia.Input.TextInput void Reset(); } + [NotClientImplementable] public interface ITextInputMethodRoot : IInputRoot { ITextInputMethodImpl? InputMethod { get; } diff --git a/src/Avalonia.Base/Interactivity/IInteractive.cs b/src/Avalonia.Base/Interactivity/IInteractive.cs index 6d7dcd64f4..980bf54f1f 100644 --- a/src/Avalonia.Base/Interactivity/IInteractive.cs +++ b/src/Avalonia.Base/Interactivity/IInteractive.cs @@ -1,4 +1,5 @@ using System; +using Avalonia.Metadata; #nullable enable @@ -7,6 +8,7 @@ namespace Avalonia.Interactivity /// /// Interface for objects that raise routed events. /// + [NotClientImplementable] public interface IInteractive { /// diff --git a/src/Avalonia.Base/Layout/ILayoutManager.cs b/src/Avalonia.Base/Layout/ILayoutManager.cs index 614670a53b..143ce13a1b 100644 --- a/src/Avalonia.Base/Layout/ILayoutManager.cs +++ b/src/Avalonia.Base/Layout/ILayoutManager.cs @@ -1,12 +1,12 @@ using System; - -#nullable enable +using Avalonia.Metadata; namespace Avalonia.Layout { /// /// Manages measuring and arranging of controls. /// + [NotClientImplementable] public interface ILayoutManager : IDisposable { /// diff --git a/src/Avalonia.Base/Layout/ILayoutRoot.cs b/src/Avalonia.Base/Layout/ILayoutRoot.cs index e2f16b338a..df15fc1a1d 100644 --- a/src/Avalonia.Base/Layout/ILayoutRoot.cs +++ b/src/Avalonia.Base/Layout/ILayoutRoot.cs @@ -1,8 +1,11 @@ +using Avalonia.Metadata; + namespace Avalonia.Layout { /// /// Defines the root of a layoutable tree. /// + [NotClientImplementable] public interface ILayoutRoot : ILayoutable { /// diff --git a/src/Avalonia.Base/Layout/ILayoutable.cs b/src/Avalonia.Base/Layout/ILayoutable.cs index 54d3ba6a11..d8b546b04a 100644 --- a/src/Avalonia.Base/Layout/ILayoutable.cs +++ b/src/Avalonia.Base/Layout/ILayoutable.cs @@ -1,12 +1,12 @@ +using Avalonia.Metadata; using Avalonia.VisualTree; -#nullable enable - namespace Avalonia.Layout { /// /// Defines layout-related functionality for a control. /// + [NotClientImplementable] public interface ILayoutable : IVisual { /// diff --git a/src/Avalonia.Base/Layout/Layoutable.cs b/src/Avalonia.Base/Layout/Layoutable.cs index df7aa937a0..f30925f489 100644 --- a/src/Avalonia.Base/Layout/Layoutable.cs +++ b/src/Avalonia.Base/Layout/Layoutable.cs @@ -141,7 +141,6 @@ namespace Avalonia.Layout static Layoutable() { AffectsMeasure( - IsVisibleProperty, WidthProperty, HeightProperty, MinWidthProperty, @@ -791,6 +790,25 @@ namespace Avalonia.Layout { } + protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs change) + { + base.OnPropertyChanged(change); + + if (change.Property == IsVisibleProperty) + { + DesiredSize = default; + + // All changes to visibility cause the parent element to be notified. + this.GetVisualParent()?.ChildDesiredSizeChanged(this); + + // We only invalidate outselves when visibility is changed to true. + if (change.GetNewValue()) + { + InvalidateMeasure(); + } + } + } + /// protected sealed override void OnVisualParentChanged(IVisual? oldParent, IVisual? newParent) { diff --git a/src/Avalonia.Base/Layout/UniformGridLayout.cs b/src/Avalonia.Base/Layout/UniformGridLayout.cs index 418cd55e41..47c994a350 100644 --- a/src/Avalonia.Base/Layout/UniformGridLayout.cs +++ b/src/Avalonia.Base/Layout/UniformGridLayout.cs @@ -116,7 +116,7 @@ namespace Avalonia.Layout /// Defines the property. /// public static readonly StyledProperty MaximumRowsOrColumnsProperty = - AvaloniaProperty.Register(nameof(MinItemWidth)); + AvaloniaProperty.Register(nameof(MaximumRowsOrColumns)); /// /// Defines the property. diff --git a/src/Avalonia.Base/LogicalTree/ILogical.cs b/src/Avalonia.Base/LogicalTree/ILogical.cs index caff3d8150..9cc2edff86 100644 --- a/src/Avalonia.Base/LogicalTree/ILogical.cs +++ b/src/Avalonia.Base/LogicalTree/ILogical.cs @@ -1,12 +1,14 @@ using System; using Avalonia.Collections; using Avalonia.Controls; +using Avalonia.Metadata; namespace Avalonia.LogicalTree { /// /// Represents a node in the logical tree. /// + [NotClientImplementable] public interface ILogical { /// diff --git a/src/Avalonia.Base/LogicalTree/ILogicalRoot.cs b/src/Avalonia.Base/LogicalTree/ILogicalRoot.cs index 4a61544a6f..ea0f554e96 100644 --- a/src/Avalonia.Base/LogicalTree/ILogicalRoot.cs +++ b/src/Avalonia.Base/LogicalTree/ILogicalRoot.cs @@ -1,8 +1,11 @@ -namespace Avalonia.LogicalTree +using Avalonia.Metadata; + +namespace Avalonia.LogicalTree { /// /// Represents a root of a logical tree. /// + [NotClientImplementable] public interface ILogicalRoot : ILogical { } diff --git a/src/Avalonia.Base/Media/ConicGradientBrush.cs b/src/Avalonia.Base/Media/ConicGradientBrush.cs index 7c1266fa17..4b50019ddc 100644 --- a/src/Avalonia.Base/Media/ConicGradientBrush.cs +++ b/src/Avalonia.Base/Media/ConicGradientBrush.cs @@ -11,7 +11,7 @@ namespace Avalonia.Media /// Defines the property. /// public static readonly StyledProperty CenterProperty = - AvaloniaProperty.Register( + AvaloniaProperty.Register( nameof(Center), RelativePoint.Center); @@ -19,7 +19,7 @@ namespace Avalonia.Media /// Defines the property. /// public static readonly StyledProperty AngleProperty = - AvaloniaProperty.Register( + AvaloniaProperty.Register( nameof(Angle), 0); diff --git a/src/Avalonia.Base/Media/IBrush.cs b/src/Avalonia.Base/Media/IBrush.cs index 830c066182..10700492d1 100644 --- a/src/Avalonia.Base/Media/IBrush.cs +++ b/src/Avalonia.Base/Media/IBrush.cs @@ -1,4 +1,5 @@ using System.ComponentModel; +using Avalonia.Metadata; namespace Avalonia.Media { @@ -6,6 +7,7 @@ namespace Avalonia.Media /// Describes how an area is painted. /// [TypeConverter(typeof(BrushConverter))] + [NotClientImplementable] public interface IBrush { /// diff --git a/src/Avalonia.Base/Media/IConicGradientBrush.cs b/src/Avalonia.Base/Media/IConicGradientBrush.cs index 5368dd1851..6a397b86d4 100644 --- a/src/Avalonia.Base/Media/IConicGradientBrush.cs +++ b/src/Avalonia.Base/Media/IConicGradientBrush.cs @@ -1,8 +1,11 @@ -namespace Avalonia.Media +using Avalonia.Metadata; + +namespace Avalonia.Media { /// /// Paints an area with a conic gradient. /// + [NotClientImplementable] public interface IConicGradientBrush : IGradientBrush { /// diff --git a/src/Avalonia.Base/Media/IDashStyle.cs b/src/Avalonia.Base/Media/IDashStyle.cs index 7835c7a1e9..7208216603 100644 --- a/src/Avalonia.Base/Media/IDashStyle.cs +++ b/src/Avalonia.Base/Media/IDashStyle.cs @@ -1,10 +1,12 @@ using System.Collections.Generic; +using Avalonia.Metadata; namespace Avalonia.Media { /// /// Represents the sequence of dashes and gaps that will be applied by a . /// + [NotClientImplementable] public interface IDashStyle { /// diff --git a/src/Avalonia.Base/Media/IExperimentalAcrylicMaterial.cs b/src/Avalonia.Base/Media/IExperimentalAcrylicMaterial.cs index e71584258a..38048fb255 100644 --- a/src/Avalonia.Base/Media/IExperimentalAcrylicMaterial.cs +++ b/src/Avalonia.Base/Media/IExperimentalAcrylicMaterial.cs @@ -1,8 +1,11 @@ -namespace Avalonia.Media +using Avalonia.Metadata; + +namespace Avalonia.Media { /// /// Experimental Interface for producing Acrylic-like materials. /// + [NotClientImplementable] public interface IExperimentalAcrylicMaterial { /// diff --git a/src/Avalonia.Base/Media/IGradientBrush.cs b/src/Avalonia.Base/Media/IGradientBrush.cs index 18db0af660..9b78a4af78 100644 --- a/src/Avalonia.Base/Media/IGradientBrush.cs +++ b/src/Avalonia.Base/Media/IGradientBrush.cs @@ -1,10 +1,12 @@ using System.Collections.Generic; +using Avalonia.Metadata; namespace Avalonia.Media { /// /// A brush that draws with a gradient. /// + [NotClientImplementable] public interface IGradientBrush : IBrush { /// diff --git a/src/Avalonia.Base/Media/IGradientStop.cs b/src/Avalonia.Base/Media/IGradientStop.cs index 22eb9df60d..64b952f3bc 100644 --- a/src/Avalonia.Base/Media/IGradientStop.cs +++ b/src/Avalonia.Base/Media/IGradientStop.cs @@ -1,8 +1,11 @@ -namespace Avalonia.Media +using Avalonia.Metadata; + +namespace Avalonia.Media { /// /// Describes the location and color of a transition point in a gradient. /// + [NotClientImplementable] public interface IGradientStop { /// diff --git a/src/Avalonia.Base/Media/IImageBrush.cs b/src/Avalonia.Base/Media/IImageBrush.cs index aaa481bd28..732f1957d0 100644 --- a/src/Avalonia.Base/Media/IImageBrush.cs +++ b/src/Avalonia.Base/Media/IImageBrush.cs @@ -1,10 +1,12 @@ using Avalonia.Media.Imaging; +using Avalonia.Metadata; namespace Avalonia.Media { /// /// Paints an area with an . /// + [NotClientImplementable] public interface IImageBrush : ITileBrush { /// @@ -12,4 +14,4 @@ namespace Avalonia.Media /// IBitmap Source { get; } } -} \ No newline at end of file +} diff --git a/src/Avalonia.Base/Media/ILinearGradientBrush.cs b/src/Avalonia.Base/Media/ILinearGradientBrush.cs index 3e2a5a0e22..4f4a55db36 100644 --- a/src/Avalonia.Base/Media/ILinearGradientBrush.cs +++ b/src/Avalonia.Base/Media/ILinearGradientBrush.cs @@ -1,8 +1,11 @@ -namespace Avalonia.Media +using Avalonia.Metadata; + +namespace Avalonia.Media { /// /// A brush that draws with a linear gradient. /// + [NotClientImplementable] public interface ILinearGradientBrush : IGradientBrush { /// @@ -15,4 +18,4 @@ /// RelativePoint EndPoint { get; } } -} \ No newline at end of file +} diff --git a/src/Avalonia.Base/Media/IMutableBrush.cs b/src/Avalonia.Base/Media/IMutableBrush.cs index 415db61d68..fef124ba36 100644 --- a/src/Avalonia.Base/Media/IMutableBrush.cs +++ b/src/Avalonia.Base/Media/IMutableBrush.cs @@ -1,10 +1,12 @@ using System; +using Avalonia.Metadata; namespace Avalonia.Media { /// /// Represents a mutable brush which can return an immutable clone of itself. /// + [NotClientImplementable] public interface IMutableBrush : IBrush, IAffectsRender { /// diff --git a/src/Avalonia.Base/Media/IMutableExperimentalAcrylicMaterial.cs b/src/Avalonia.Base/Media/IMutableExperimentalAcrylicMaterial.cs index fcfe4631a6..f954a8c52a 100644 --- a/src/Avalonia.Base/Media/IMutableExperimentalAcrylicMaterial.cs +++ b/src/Avalonia.Base/Media/IMutableExperimentalAcrylicMaterial.cs @@ -1,8 +1,11 @@ -namespace Avalonia.Media +using Avalonia.Metadata; + +namespace Avalonia.Media { /// /// Represents a mutable brush which can return an immutable clone of itself. /// + [NotClientImplementable] public interface IMutableExperimentalAcrylicMaterial : IExperimentalAcrylicMaterial, IAffectsRender { /// diff --git a/src/Avalonia.Base/Media/IMutableTransform.cs b/src/Avalonia.Base/Media/IMutableTransform.cs index 2033c434c0..22526eed17 100644 --- a/src/Avalonia.Base/Media/IMutableTransform.cs +++ b/src/Avalonia.Base/Media/IMutableTransform.cs @@ -1,4 +1,5 @@ using System; +using Avalonia.Metadata; namespace Avalonia.Media { diff --git a/src/Avalonia.Base/Media/IPen.cs b/src/Avalonia.Base/Media/IPen.cs index 1cad9948b4..eca1cb507a 100644 --- a/src/Avalonia.Base/Media/IPen.cs +++ b/src/Avalonia.Base/Media/IPen.cs @@ -1,8 +1,11 @@ -namespace Avalonia.Media +using Avalonia.Metadata; + +namespace Avalonia.Media { /// /// Describes how a stroke is drawn. /// + [NotClientImplementable] public interface IPen { /// diff --git a/src/Avalonia.Base/Media/IRadialGradientBrush.cs b/src/Avalonia.Base/Media/IRadialGradientBrush.cs index cadf53cc18..0f025e98bc 100644 --- a/src/Avalonia.Base/Media/IRadialGradientBrush.cs +++ b/src/Avalonia.Base/Media/IRadialGradientBrush.cs @@ -1,8 +1,11 @@ -namespace Avalonia.Media +using Avalonia.Metadata; + +namespace Avalonia.Media { /// /// Paints an area with a radial gradient. /// + [NotClientImplementable] public interface IRadialGradientBrush : IGradientBrush { /// @@ -21,4 +24,4 @@ /// double Radius { get; } } -} \ No newline at end of file +} diff --git a/src/Avalonia.Base/Media/ISolidColorBrush.cs b/src/Avalonia.Base/Media/ISolidColorBrush.cs index d0ab00599b..29e11210f1 100644 --- a/src/Avalonia.Base/Media/ISolidColorBrush.cs +++ b/src/Avalonia.Base/Media/ISolidColorBrush.cs @@ -1,8 +1,11 @@ +using Avalonia.Metadata; + namespace Avalonia.Media { /// /// Fills an area with a solid color. /// + [NotClientImplementable] public interface ISolidColorBrush : IBrush { /// @@ -10,4 +13,4 @@ namespace Avalonia.Media /// Color Color { get; } } -} \ No newline at end of file +} diff --git a/src/Avalonia.Base/Media/ITileBrush.cs b/src/Avalonia.Base/Media/ITileBrush.cs index 991857eec9..cb5a591003 100644 --- a/src/Avalonia.Base/Media/ITileBrush.cs +++ b/src/Avalonia.Base/Media/ITileBrush.cs @@ -1,10 +1,12 @@ using Avalonia.Media.Imaging; +using Avalonia.Metadata; namespace Avalonia.Media -{ +{ /// /// A brush which displays a repeating image. /// + [NotClientImplementable] public interface ITileBrush : IBrush { /// diff --git a/src/Avalonia.Base/Media/IVisualBrush.cs b/src/Avalonia.Base/Media/IVisualBrush.cs index e74892b218..b8900bbd73 100644 --- a/src/Avalonia.Base/Media/IVisualBrush.cs +++ b/src/Avalonia.Base/Media/IVisualBrush.cs @@ -1,10 +1,12 @@ -using Avalonia.VisualTree; +using Avalonia.Metadata; +using Avalonia.VisualTree; namespace Avalonia.Media { /// /// Paints an area with an . /// + [NotClientImplementable] public interface IVisualBrush : ITileBrush { /// @@ -12,4 +14,4 @@ namespace Avalonia.Media /// IVisual Visual { get; } } -} \ No newline at end of file +} diff --git a/src/Avalonia.Base/Media/Imaging/IBitmap.cs b/src/Avalonia.Base/Media/Imaging/IBitmap.cs index 134bebc002..bd04d5ce86 100644 --- a/src/Avalonia.Base/Media/Imaging/IBitmap.cs +++ b/src/Avalonia.Base/Media/Imaging/IBitmap.cs @@ -1,5 +1,6 @@ using System; using System.IO; +using Avalonia.Metadata; using Avalonia.Platform; using Avalonia.Utilities; @@ -8,6 +9,7 @@ namespace Avalonia.Media.Imaging /// /// Represents a bitmap image. /// + [NotClientImplementable] public interface IBitmap : IImage, IDisposable { /// diff --git a/src/Avalonia.Base/Media/TextFormatting/ITextSource.cs b/src/Avalonia.Base/Media/TextFormatting/ITextSource.cs index 32012ab8e9..26966b37bc 100644 --- a/src/Avalonia.Base/Media/TextFormatting/ITextSource.cs +++ b/src/Avalonia.Base/Media/TextFormatting/ITextSource.cs @@ -1,4 +1,6 @@ -namespace Avalonia.Media.TextFormatting +using Avalonia.Metadata; + +namespace Avalonia.Media.TextFormatting { /// /// Produces objects that are used by the . diff --git a/src/Avalonia.Base/Metadata/NotClientImplementableAttribute.cs b/src/Avalonia.Base/Metadata/NotClientImplementableAttribute.cs new file mode 100644 index 0000000000..348c983c03 --- /dev/null +++ b/src/Avalonia.Base/Metadata/NotClientImplementableAttribute.cs @@ -0,0 +1,17 @@ +using System; + +namespace Avalonia.Metadata +{ + /// + /// This interface is not intended to be implemented outside of the core Avalonia framework as + /// its API may change without warning. + /// + /// + /// This interface is stable for consumption by a client, but should not be implemented as members + /// may be added to its API. + /// + [AttributeUsage(AttributeTargets.Interface)] + public class NotClientImplementableAttribute : Attribute + { + } +} diff --git a/src/Avalonia.Base/Metadata/UnstableAttribute.cs b/src/Avalonia.Base/Metadata/UnstableAttribute.cs new file mode 100644 index 0000000000..3b6fa5168a --- /dev/null +++ b/src/Avalonia.Base/Metadata/UnstableAttribute.cs @@ -0,0 +1,12 @@ +using System; + +namespace Avalonia.Metadata +{ + /// + /// This API is unstable and is not covered by API compatibility guarantees between minor and + /// patch releases. + /// + public class UnstableAttribute : Attribute + { + } +} diff --git a/src/Avalonia.Base/Platform/IAssetLoader.cs b/src/Avalonia.Base/Platform/IAssetLoader.cs index e3899784ad..b65d61803f 100644 --- a/src/Avalonia.Base/Platform/IAssetLoader.cs +++ b/src/Avalonia.Base/Platform/IAssetLoader.cs @@ -2,12 +2,14 @@ using System; using System.Collections.Generic; using System.IO; using System.Reflection; +using Avalonia.Metadata; namespace Avalonia.Platform { /// /// Loads assets compiled into the application binary. /// + [Unstable] public interface IAssetLoader { /// diff --git a/src/Avalonia.Base/Platform/IBitmapImpl.cs b/src/Avalonia.Base/Platform/IBitmapImpl.cs index 1e68bc477d..8f11f68e7c 100644 --- a/src/Avalonia.Base/Platform/IBitmapImpl.cs +++ b/src/Avalonia.Base/Platform/IBitmapImpl.cs @@ -1,11 +1,13 @@ using System; using System.IO; +using Avalonia.Metadata; namespace Avalonia.Platform { /// /// Defines the platform-specific interface for a . /// + [Unstable] public interface IBitmapImpl : IDisposable { /// diff --git a/src/Avalonia.Base/Platform/ICursorImpl.cs b/src/Avalonia.Base/Platform/ICursorImpl.cs index 14235869f7..74e0ba2e5c 100644 --- a/src/Avalonia.Base/Platform/ICursorImpl.cs +++ b/src/Avalonia.Base/Platform/ICursorImpl.cs @@ -1,5 +1,6 @@ using System; using Avalonia.Input; +using Avalonia.Metadata; #nullable enable @@ -8,6 +9,7 @@ namespace Avalonia.Platform /// /// Represents a platform implementation of a . /// + [Unstable] public interface ICursorImpl : IDisposable { } diff --git a/src/Avalonia.Base/Platform/IDrawingContextImpl.cs b/src/Avalonia.Base/Platform/IDrawingContextImpl.cs index 4e6612e908..d84a509234 100644 --- a/src/Avalonia.Base/Platform/IDrawingContextImpl.cs +++ b/src/Avalonia.Base/Platform/IDrawingContextImpl.cs @@ -3,12 +3,14 @@ using Avalonia.Media; using Avalonia.Rendering.SceneGraph; using Avalonia.Utilities; using Avalonia.Media.Imaging; +using Avalonia.Metadata; namespace Avalonia.Platform { /// /// Defines the interface through which drawing occurs. /// + [Unstable] public interface IDrawingContextImpl : IDisposable { /// diff --git a/src/Avalonia.Base/Platform/IFontManagerImpl.cs b/src/Avalonia.Base/Platform/IFontManagerImpl.cs index 0110287afd..932249bd52 100644 --- a/src/Avalonia.Base/Platform/IFontManagerImpl.cs +++ b/src/Avalonia.Base/Platform/IFontManagerImpl.cs @@ -1,9 +1,11 @@ using System.Collections.Generic; using System.Globalization; using Avalonia.Media; +using Avalonia.Metadata; namespace Avalonia.Platform { + [Unstable] public interface IFontManagerImpl { /// diff --git a/src/Avalonia.Base/Platform/IGeometryImpl.cs b/src/Avalonia.Base/Platform/IGeometryImpl.cs index ed6de1b5c7..c80f8923ef 100644 --- a/src/Avalonia.Base/Platform/IGeometryImpl.cs +++ b/src/Avalonia.Base/Platform/IGeometryImpl.cs @@ -1,10 +1,12 @@ using Avalonia.Media; +using Avalonia.Metadata; namespace Avalonia.Platform { /// /// Defines the platform-specific interface for a . /// + [Unstable] public interface IGeometryImpl { /// diff --git a/src/Avalonia.Base/Platform/IGlyphRunImpl.cs b/src/Avalonia.Base/Platform/IGlyphRunImpl.cs index 08786d9689..7801bdd50f 100644 --- a/src/Avalonia.Base/Platform/IGlyphRunImpl.cs +++ b/src/Avalonia.Base/Platform/IGlyphRunImpl.cs @@ -1,9 +1,11 @@ using System; +using Avalonia.Metadata; namespace Avalonia.Platform { /// /// Actual implementation of a glyph run that stores platform dependent resources. /// + [Unstable] public interface IGlyphRunImpl : IDisposable { } } diff --git a/src/Avalonia.Base/Platform/IGlyphTypefaceImpl.cs b/src/Avalonia.Base/Platform/IGlyphTypefaceImpl.cs index 6afd79d29c..415f34fb29 100644 --- a/src/Avalonia.Base/Platform/IGlyphTypefaceImpl.cs +++ b/src/Avalonia.Base/Platform/IGlyphTypefaceImpl.cs @@ -1,7 +1,9 @@ using System; +using Avalonia.Metadata; namespace Avalonia.Platform { + [Unstable] public interface IGlyphTypefaceImpl : IDisposable { /// diff --git a/src/Avalonia.Base/Platform/IMacOSTopLevelPlatformHandle.cs b/src/Avalonia.Base/Platform/IMacOSTopLevelPlatformHandle.cs index e399976bbe..b087724079 100644 --- a/src/Avalonia.Base/Platform/IMacOSTopLevelPlatformHandle.cs +++ b/src/Avalonia.Base/Platform/IMacOSTopLevelPlatformHandle.cs @@ -1,7 +1,9 @@ using System; +using Avalonia.Metadata; namespace Avalonia.Platform { + [Unstable] public interface IMacOSTopLevelPlatformHandle { IntPtr NSView { get; } diff --git a/src/Avalonia.Base/Platform/IPlatformRenderInterface.cs b/src/Avalonia.Base/Platform/IPlatformRenderInterface.cs index c46efd46c3..0eeefddf0b 100644 --- a/src/Avalonia.Base/Platform/IPlatformRenderInterface.cs +++ b/src/Avalonia.Base/Platform/IPlatformRenderInterface.cs @@ -3,12 +3,14 @@ using System.Collections.Generic; using System.IO; using Avalonia.Media; using Avalonia.Media.Imaging; +using Avalonia.Metadata; namespace Avalonia.Platform { /// /// Defines the main platform-specific interface for the rendering subsystem. /// + [Unstable] public interface IPlatformRenderInterface { /// diff --git a/src/Avalonia.Base/Platform/IPlatformSettings.cs b/src/Avalonia.Base/Platform/IPlatformSettings.cs index e4b28e6575..78d1817312 100644 --- a/src/Avalonia.Base/Platform/IPlatformSettings.cs +++ b/src/Avalonia.Base/Platform/IPlatformSettings.cs @@ -1,7 +1,9 @@ using System; +using Avalonia.Metadata; namespace Avalonia.Platform { + [Unstable] public interface IPlatformSettings { Size DoubleClickSize { get; } diff --git a/src/Avalonia.Base/Platform/IPlatformThreadingInterface.cs b/src/Avalonia.Base/Platform/IPlatformThreadingInterface.cs index 2137f965cc..bf18a7da5b 100644 --- a/src/Avalonia.Base/Platform/IPlatformThreadingInterface.cs +++ b/src/Avalonia.Base/Platform/IPlatformThreadingInterface.cs @@ -1,5 +1,6 @@ using System; using System.Threading; +using Avalonia.Metadata; using Avalonia.Threading; namespace Avalonia.Platform @@ -7,6 +8,7 @@ namespace Avalonia.Platform /// /// Provides platform-specific services relating to threading. /// + [Unstable] public interface IPlatformThreadingInterface { void RunLoop(CancellationToken cancellationToken); diff --git a/src/Avalonia.Base/Platform/IRenderTargetBitmapImpl.cs b/src/Avalonia.Base/Platform/IRenderTargetBitmapImpl.cs index 9add07afe3..d33c503650 100644 --- a/src/Avalonia.Base/Platform/IRenderTargetBitmapImpl.cs +++ b/src/Avalonia.Base/Platform/IRenderTargetBitmapImpl.cs @@ -1,3 +1,4 @@ +using Avalonia.Metadata; namespace Avalonia.Platform { @@ -5,6 +6,7 @@ namespace Avalonia.Platform /// Defines the platform-specific interface for a /// . /// + [Unstable] public interface IRenderTargetBitmapImpl : IBitmapImpl, IRenderTarget { } diff --git a/src/Avalonia.Base/Platform/IRuntimePlatform.cs b/src/Avalonia.Base/Platform/IRuntimePlatform.cs index 850757a1ee..8ab04f5995 100644 --- a/src/Avalonia.Base/Platform/IRuntimePlatform.cs +++ b/src/Avalonia.Base/Platform/IRuntimePlatform.cs @@ -1,7 +1,9 @@ using System; +using Avalonia.Metadata; namespace Avalonia.Platform { + [Unstable] public interface IRuntimePlatform { IDisposable StartSystemTimer(TimeSpan interval, Action tick); @@ -9,6 +11,7 @@ namespace Avalonia.Platform IUnmanagedBlob AllocBlob(int size); } + [Unstable] public interface IUnmanagedBlob : IDisposable { IntPtr Address { get; } @@ -17,6 +20,7 @@ namespace Avalonia.Platform } + [Unstable] public struct RuntimePlatformInfo { public OperatingSystemType OperatingSystem { get; set; } @@ -29,6 +33,7 @@ namespace Avalonia.Platform public bool IsUnix { get; set; } } + [Unstable] public enum OperatingSystemType { Unknown, diff --git a/src/Avalonia.Base/Platform/IStreamGeometryContextImpl.cs b/src/Avalonia.Base/Platform/IStreamGeometryContextImpl.cs index 4587979308..3d5ee36608 100644 --- a/src/Avalonia.Base/Platform/IStreamGeometryContextImpl.cs +++ b/src/Avalonia.Base/Platform/IStreamGeometryContextImpl.cs @@ -1,8 +1,11 @@ +using Avalonia.Metadata; + namespace Avalonia.Platform { /// /// Describes a geometry using drawing commands. /// + [Unstable] public interface IStreamGeometryContextImpl : IGeometryContext { } diff --git a/src/Avalonia.Base/Platform/IStreamGeometryImpl.cs b/src/Avalonia.Base/Platform/IStreamGeometryImpl.cs index 5b070fde02..bd4411f3a4 100644 --- a/src/Avalonia.Base/Platform/IStreamGeometryImpl.cs +++ b/src/Avalonia.Base/Platform/IStreamGeometryImpl.cs @@ -1,8 +1,11 @@ +using Avalonia.Metadata; + namespace Avalonia.Platform { /// /// Defines the platform-specific interface for a . /// + [Unstable] public interface IStreamGeometryImpl : IGeometryImpl { /// diff --git a/src/Avalonia.Base/Platform/ITextShaperImpl.cs b/src/Avalonia.Base/Platform/ITextShaperImpl.cs index 11be9e3f09..10e58b7d0b 100644 --- a/src/Avalonia.Base/Platform/ITextShaperImpl.cs +++ b/src/Avalonia.Base/Platform/ITextShaperImpl.cs @@ -1,4 +1,5 @@ using Avalonia.Media.TextFormatting; +using Avalonia.Metadata; using Avalonia.Utilities; namespace Avalonia.Platform @@ -6,6 +7,7 @@ namespace Avalonia.Platform /// /// An abstraction that is used produce shaped text. /// + [Unstable] public interface ITextShaperImpl { /// diff --git a/src/Avalonia.Base/Platform/ITransformedGeometryImpl.cs b/src/Avalonia.Base/Platform/ITransformedGeometryImpl.cs index 1ed025b571..2754414cd1 100644 --- a/src/Avalonia.Base/Platform/ITransformedGeometryImpl.cs +++ b/src/Avalonia.Base/Platform/ITransformedGeometryImpl.cs @@ -1,4 +1,6 @@ -namespace Avalonia.Platform +using Avalonia.Metadata; + +namespace Avalonia.Platform { /// /// Represents a geometry with a transform applied. @@ -7,6 +9,7 @@ /// An transforms a geometry without transforming its /// stroke thickness. /// + [Unstable] public interface ITransformedGeometryImpl : IGeometryImpl { /// diff --git a/src/Avalonia.Base/Platform/IWriteableBitmapImpl.cs b/src/Avalonia.Base/Platform/IWriteableBitmapImpl.cs index c4e2e4915f..fa1e1862b7 100644 --- a/src/Avalonia.Base/Platform/IWriteableBitmapImpl.cs +++ b/src/Avalonia.Base/Platform/IWriteableBitmapImpl.cs @@ -1,8 +1,11 @@ -namespace Avalonia.Platform +using Avalonia.Metadata; + +namespace Avalonia.Platform { /// /// Defines the platform-specific interface for a . /// + [Unstable] public interface IWriteableBitmapImpl : IBitmapImpl { ILockedFramebuffer Lock(); diff --git a/src/Avalonia.Base/Platform/Interop/IDynamicLibraryLoader.cs b/src/Avalonia.Base/Platform/Interop/IDynamicLibraryLoader.cs index 8124ce6bc4..9389ebc703 100644 --- a/src/Avalonia.Base/Platform/Interop/IDynamicLibraryLoader.cs +++ b/src/Avalonia.Base/Platform/Interop/IDynamicLibraryLoader.cs @@ -1,7 +1,9 @@ using System; +using Avalonia.Metadata; namespace Avalonia.Platform.Interop { + [Unstable] public interface IDynamicLibraryLoader { IntPtr LoadLibrary(string dll); diff --git a/src/Avalonia.Base/Rendering/IDeferredRendererLock.cs b/src/Avalonia.Base/Rendering/IDeferredRendererLock.cs index eab3dca58e..1c6bd69158 100644 --- a/src/Avalonia.Base/Rendering/IDeferredRendererLock.cs +++ b/src/Avalonia.Base/Rendering/IDeferredRendererLock.cs @@ -1,7 +1,9 @@ using System; +using Avalonia.Metadata; namespace Avalonia.Rendering { + [Unstable] public interface IDeferredRendererLock { IDisposable? TryLock(); diff --git a/src/Avalonia.Base/Rendering/IRenderLoop.cs b/src/Avalonia.Base/Rendering/IRenderLoop.cs index dd7442e7f8..9838967261 100644 --- a/src/Avalonia.Base/Rendering/IRenderLoop.cs +++ b/src/Avalonia.Base/Rendering/IRenderLoop.cs @@ -1,4 +1,6 @@ -namespace Avalonia.Rendering +using Avalonia.Metadata; + +namespace Avalonia.Rendering { /// /// The application render loop. @@ -7,6 +9,7 @@ /// The render loop is responsible for advancing the animation timer and updating the scene /// graph for visible windows. /// + [NotClientImplementable] public interface IRenderLoop { /// diff --git a/src/Avalonia.Base/Rendering/IRenderRoot.cs b/src/Avalonia.Base/Rendering/IRenderRoot.cs index 54e58bf39c..1aa44158b2 100644 --- a/src/Avalonia.Base/Rendering/IRenderRoot.cs +++ b/src/Avalonia.Base/Rendering/IRenderRoot.cs @@ -1,3 +1,4 @@ +using Avalonia.Metadata; using Avalonia.Platform; using Avalonia.VisualTree; @@ -6,6 +7,7 @@ namespace Avalonia.Rendering /// /// Represents the root of a renderable tree. /// + [NotClientImplementable] public interface IRenderRoot : IVisual { /// diff --git a/src/Avalonia.Base/Rendering/IRenderTimer.cs b/src/Avalonia.Base/Rendering/IRenderTimer.cs index d333e928a0..ee74c345be 100644 --- a/src/Avalonia.Base/Rendering/IRenderTimer.cs +++ b/src/Avalonia.Base/Rendering/IRenderTimer.cs @@ -1,11 +1,13 @@ using System; using System.Threading.Tasks; +using Avalonia.Metadata; namespace Avalonia.Rendering { /// /// Defines the interface implemented by an application render timer. /// + [NotClientImplementable] public interface IRenderTimer { /// diff --git a/src/Avalonia.Base/Rendering/IVisualBrushInitialize.cs b/src/Avalonia.Base/Rendering/IVisualBrushInitialize.cs index 00449c5344..b5ab8ed0bd 100644 --- a/src/Avalonia.Base/Rendering/IVisualBrushInitialize.cs +++ b/src/Avalonia.Base/Rendering/IVisualBrushInitialize.cs @@ -1,4 +1,5 @@ using Avalonia.Media; +using Avalonia.Metadata; namespace Avalonia.Rendering { @@ -6,6 +7,7 @@ namespace Avalonia.Rendering /// Internal interface for initializing controls that are to be used as the visual in a /// . /// + [Unstable] public interface IVisualBrushInitialize { /// diff --git a/src/Avalonia.Base/Rendering/IVisualBrushRenderer.cs b/src/Avalonia.Base/Rendering/IVisualBrushRenderer.cs index 1cd6515635..f5312ad39b 100644 --- a/src/Avalonia.Base/Rendering/IVisualBrushRenderer.cs +++ b/src/Avalonia.Base/Rendering/IVisualBrushRenderer.cs @@ -1,4 +1,5 @@ using Avalonia.Media; +using Avalonia.Metadata; using Avalonia.Platform; namespace Avalonia.Rendering @@ -6,6 +7,7 @@ namespace Avalonia.Rendering /// /// Defines a renderer used to render a visual brush to a bitmap. /// + [Unstable] public interface IVisualBrushRenderer { /// diff --git a/src/Avalonia.Base/StyledPropertyBase.cs b/src/Avalonia.Base/StyledPropertyBase.cs index dd5eb703ea..da607720ff 100644 --- a/src/Avalonia.Base/StyledPropertyBase.cs +++ b/src/Avalonia.Base/StyledPropertyBase.cs @@ -1,9 +1,7 @@ using System; -using System.Diagnostics; using Avalonia.Data; using Avalonia.Reactive; using Avalonia.Styling; -using Avalonia.Utilities; namespace Avalonia { @@ -12,7 +10,7 @@ namespace Avalonia /// public abstract class StyledPropertyBase : AvaloniaProperty, IStyledPropertyAccessor { - private bool _inherits; + private readonly bool _inherits; /// /// Initializes a new instance of the class. @@ -243,10 +241,10 @@ namespace Avalonia } else if (value is ITemplate template && !typeof(ITemplate).IsAssignableFrom(PropertyType)) { - return new PropertySetterLazyInstance( + return new PropertySetterTemplateInstance( target, this, - () => (TValue)template.Build()); + template); } else { diff --git a/src/Avalonia.Base/Styling/Activators/IStyleActivator.cs b/src/Avalonia.Base/Styling/Activators/IStyleActivator.cs index 479100ed8a..ac7b8b3ef1 100644 --- a/src/Avalonia.Base/Styling/Activators/IStyleActivator.cs +++ b/src/Avalonia.Base/Styling/Activators/IStyleActivator.cs @@ -1,6 +1,5 @@ -#nullable enable - -using System; +using System; +using Avalonia.Metadata; namespace Avalonia.Styling.Activators { @@ -16,6 +15,7 @@ namespace Avalonia.Styling.Activators /// - The subscription can have a tag associated with it, allowing a subscriber to index /// into a list of subscriptions without having to allocate additional objects. /// + [Unstable] public interface IStyleActivator : IDisposable { /// diff --git a/src/Avalonia.Base/Styling/Activators/IStyleActivatorSink.cs b/src/Avalonia.Base/Styling/Activators/IStyleActivatorSink.cs index a1a6ef5c28..fbb18dc304 100644 --- a/src/Avalonia.Base/Styling/Activators/IStyleActivatorSink.cs +++ b/src/Avalonia.Base/Styling/Activators/IStyleActivatorSink.cs @@ -1,10 +1,11 @@ -#nullable enable +using Avalonia.Metadata; namespace Avalonia.Styling.Activators { /// /// Receives notifications from an . /// + [Unstable] public interface IStyleActivatorSink { /// diff --git a/src/Avalonia.Base/Styling/ChildSelector.cs b/src/Avalonia.Base/Styling/ChildSelector.cs index 5c92182b80..34f3a76b61 100644 --- a/src/Avalonia.Base/Styling/ChildSelector.cs +++ b/src/Avalonia.Base/Styling/ChildSelector.cs @@ -37,13 +37,13 @@ namespace Avalonia.Styling return _selectorString; } - protected override SelectorMatch Evaluate(IStyleable control, bool subscribe) + protected override SelectorMatch Evaluate(IStyleable control, IStyle? parent, bool subscribe) { var controlParent = ((ILogical)control).LogicalParent; if (controlParent != null) { - var parentMatch = _parent.Match((IStyleable)controlParent, subscribe); + var parentMatch = _parent.Match((IStyleable)controlParent, parent, subscribe); if (parentMatch.Result == SelectorMatchResult.Sometimes) { @@ -65,5 +65,6 @@ namespace Avalonia.Styling } protected override Selector? MovePrevious() => null; + internal override bool HasValidNestingSelector() => _parent.HasValidNestingSelector(); } } diff --git a/src/Avalonia.Base/Styling/DescendentSelector.cs b/src/Avalonia.Base/Styling/DescendentSelector.cs index dde88b3436..4ffaff6861 100644 --- a/src/Avalonia.Base/Styling/DescendentSelector.cs +++ b/src/Avalonia.Base/Styling/DescendentSelector.cs @@ -35,7 +35,7 @@ namespace Avalonia.Styling return _selectorString; } - protected override SelectorMatch Evaluate(IStyleable control, bool subscribe) + protected override SelectorMatch Evaluate(IStyleable control, IStyle? parent, bool subscribe) { var c = (ILogical)control; var descendantMatches = new OrActivatorBuilder(); @@ -46,7 +46,7 @@ namespace Avalonia.Styling if (c is IStyleable) { - var match = _parent.Match((IStyleable)c, subscribe); + var match = _parent.Match((IStyleable)c, parent, subscribe); if (match.Result == SelectorMatchResult.Sometimes) { @@ -70,5 +70,6 @@ namespace Avalonia.Styling } protected override Selector? MovePrevious() => null; + internal override bool HasValidNestingSelector() => _parent.HasValidNestingSelector(); } } diff --git a/src/Avalonia.Base/Styling/IGlobalStyles.cs b/src/Avalonia.Base/Styling/IGlobalStyles.cs index ab24e3138c..f9667a8895 100644 --- a/src/Avalonia.Base/Styling/IGlobalStyles.cs +++ b/src/Avalonia.Base/Styling/IGlobalStyles.cs @@ -1,13 +1,13 @@ using System; using System.Collections.Generic; - -#nullable enable +using Avalonia.Metadata; namespace Avalonia.Styling { /// /// Defines the style host that provides styles global to the application. /// + [NotClientImplementable] public interface IGlobalStyles : IStyleHost { /// diff --git a/src/Avalonia.Base/Styling/ISetter.cs b/src/Avalonia.Base/Styling/ISetter.cs index d588817be8..71ae5d84c0 100644 --- a/src/Avalonia.Base/Styling/ISetter.cs +++ b/src/Avalonia.Base/Styling/ISetter.cs @@ -1,12 +1,12 @@ using System; - -#nullable enable +using Avalonia.Metadata; namespace Avalonia.Styling { /// /// Represents a setter for a . /// + [NotClientImplementable] public interface ISetter { /// diff --git a/src/Avalonia.Base/Styling/ISetterInstance.cs b/src/Avalonia.Base/Styling/ISetterInstance.cs index a299a87b64..e0d3137619 100644 --- a/src/Avalonia.Base/Styling/ISetterInstance.cs +++ b/src/Avalonia.Base/Styling/ISetterInstance.cs @@ -1,12 +1,12 @@ -#nullable enable - -using System; +using System; +using Avalonia.Metadata; namespace Avalonia.Styling { /// /// Represents a setter that has been instanced on a control. /// + [Unstable] public interface ISetterInstance : IDisposable { /// diff --git a/src/Avalonia.Base/Styling/IStyle.cs b/src/Avalonia.Base/Styling/IStyle.cs index 738a69cb88..e9faf82c07 100644 --- a/src/Avalonia.Base/Styling/IStyle.cs +++ b/src/Avalonia.Base/Styling/IStyle.cs @@ -1,13 +1,13 @@ using System.Collections.Generic; using Avalonia.Controls; - -#nullable enable +using Avalonia.Metadata; namespace Avalonia.Styling { /// /// Defines the interface for styles. /// + [NotClientImplementable] public interface IStyle : IResourceNode { /// diff --git a/src/Avalonia.Base/Styling/IStyleHost.cs b/src/Avalonia.Base/Styling/IStyleHost.cs index 360b40d9a1..2ae488825c 100644 --- a/src/Avalonia.Base/Styling/IStyleHost.cs +++ b/src/Avalonia.Base/Styling/IStyleHost.cs @@ -1,5 +1,6 @@ using System.Collections.Generic; +using Avalonia.Metadata; #nullable enable @@ -8,6 +9,7 @@ namespace Avalonia.Styling /// /// Defines an element that has a collection. /// + [NotClientImplementable] public interface IStyleHost { /// diff --git a/src/Avalonia.Base/Styling/IStyleInstance.cs b/src/Avalonia.Base/Styling/IStyleInstance.cs index 8ddb989bc0..262f336e05 100644 --- a/src/Avalonia.Base/Styling/IStyleInstance.cs +++ b/src/Avalonia.Base/Styling/IStyleInstance.cs @@ -1,12 +1,12 @@ using System; - -#nullable enable +using Avalonia.Metadata; namespace Avalonia.Styling { /// /// Represents a style that has been instanced on a control. /// + [Unstable] public interface IStyleInstance : IDisposable { /// @@ -14,6 +14,11 @@ namespace Avalonia.Styling /// IStyle Source { get; } + /// + /// Gets a value indicating whether this style has an activator. + /// + bool HasActivator { get; } + /// /// Gets a value indicating whether this style is active. /// diff --git a/src/Avalonia.Base/Styling/IStyleable.cs b/src/Avalonia.Base/Styling/IStyleable.cs index a3df779057..5bc972e7ab 100644 --- a/src/Avalonia.Base/Styling/IStyleable.cs +++ b/src/Avalonia.Base/Styling/IStyleable.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using Avalonia.Collections; +using Avalonia.Metadata; #nullable enable @@ -9,6 +10,7 @@ namespace Avalonia.Styling /// /// Interface for styleable elements. /// + [NotClientImplementable] public interface IStyleable : IAvaloniaObject, INamed { /// diff --git a/src/Avalonia.Base/Styling/ITemplatedControl.cs b/src/Avalonia.Base/Styling/ITemplatedControl.cs index 5485babb62..68989c8a5e 100644 --- a/src/Avalonia.Base/Styling/ITemplatedControl.cs +++ b/src/Avalonia.Base/Styling/ITemplatedControl.cs @@ -1,6 +1,8 @@ +using Avalonia.Metadata; namespace Avalonia.Styling { + [NotClientImplementable] public interface ITemplatedControl : IAvaloniaObject { } diff --git a/src/Avalonia.Base/Styling/NestingSelector.cs b/src/Avalonia.Base/Styling/NestingSelector.cs new file mode 100644 index 0000000000..481a937867 --- /dev/null +++ b/src/Avalonia.Base/Styling/NestingSelector.cs @@ -0,0 +1,30 @@ +using System; + +namespace Avalonia.Styling +{ + /// + /// The `^` nesting style selector. + /// + internal class NestingSelector : Selector + { + public override bool InTemplate => false; + public override bool IsCombinator => false; + public override Type? TargetType => null; + + public override string ToString() => "^"; + + protected override SelectorMatch Evaluate(IStyleable control, IStyle? parent, bool subscribe) + { + if (parent is Style s && s.Selector is Selector selector) + { + return selector.Match(control, (parent as Style)?.Parent, subscribe); + } + + throw new InvalidOperationException( + "Nesting selector was specified but cannot determine parent selector."); + } + + protected override Selector? MovePrevious() => null; + internal override bool HasValidNestingSelector() => true; + } +} diff --git a/src/Avalonia.Base/Styling/NotSelector.cs b/src/Avalonia.Base/Styling/NotSelector.cs index ab4e9d5d7f..cdc3254d38 100644 --- a/src/Avalonia.Base/Styling/NotSelector.cs +++ b/src/Avalonia.Base/Styling/NotSelector.cs @@ -45,9 +45,9 @@ namespace Avalonia.Styling return _selectorString; } - protected override SelectorMatch Evaluate(IStyleable control, bool subscribe) + protected override SelectorMatch Evaluate(IStyleable control, IStyle? parent, bool subscribe) { - var innerResult = _argument.Match(control, subscribe); + var innerResult = _argument.Match(control, parent, subscribe); switch (innerResult.Result) { @@ -67,5 +67,6 @@ namespace Avalonia.Styling } protected override Selector? MovePrevious() => _previous; + internal override bool HasValidNestingSelector() => _argument.HasValidNestingSelector(); } } diff --git a/src/Avalonia.Base/Styling/NthChildSelector.cs b/src/Avalonia.Base/Styling/NthChildSelector.cs index aff34ea17c..047bf434da 100644 --- a/src/Avalonia.Base/Styling/NthChildSelector.cs +++ b/src/Avalonia.Base/Styling/NthChildSelector.cs @@ -48,7 +48,7 @@ namespace Avalonia.Styling public int Step { get; } public int Offset { get; } - protected override SelectorMatch Evaluate(IStyleable control, bool subscribe) + protected override SelectorMatch Evaluate(IStyleable control, IStyle? parent, bool subscribe) { if (!(control is ILogical logical)) { @@ -105,6 +105,7 @@ namespace Avalonia.Styling } protected override Selector? MovePrevious() => _previous; + internal override bool HasValidNestingSelector() => _previous?.HasValidNestingSelector() ?? false; public override string ToString() { diff --git a/src/Avalonia.Base/Styling/OrSelector.cs b/src/Avalonia.Base/Styling/OrSelector.cs index 3d6db9b01e..913c27bf0c 100644 --- a/src/Avalonia.Base/Styling/OrSelector.cs +++ b/src/Avalonia.Base/Styling/OrSelector.cs @@ -65,14 +65,14 @@ namespace Avalonia.Styling return _selectorString; } - protected override SelectorMatch Evaluate(IStyleable control, bool subscribe) + protected override SelectorMatch Evaluate(IStyleable control, IStyle? parent, bool subscribe) { var activators = new OrActivatorBuilder(); var neverThisInstance = false; foreach (var selector in _selectors) { - var match = selector.Match(control, subscribe); + var match = selector.Match(control, parent, subscribe); switch (match.Result) { @@ -104,6 +104,19 @@ namespace Avalonia.Styling protected override Selector? MovePrevious() => null; + internal override bool HasValidNestingSelector() + { + foreach (var selector in _selectors) + { + if (!selector.HasValidNestingSelector()) + { + return false; + } + } + + return true; + } + private Type? EvaluateTargetType() { Type? result = null; diff --git a/src/Avalonia.Base/Styling/PropertyEqualsSelector.cs b/src/Avalonia.Base/Styling/PropertyEqualsSelector.cs index 1cd1a650ef..7a37daf087 100644 --- a/src/Avalonia.Base/Styling/PropertyEqualsSelector.cs +++ b/src/Avalonia.Base/Styling/PropertyEqualsSelector.cs @@ -74,7 +74,7 @@ namespace Avalonia.Styling } /// - protected override SelectorMatch Evaluate(IStyleable control, bool subscribe) + protected override SelectorMatch Evaluate(IStyleable control, IStyle? parent, bool subscribe) { if (subscribe) { @@ -90,6 +90,7 @@ namespace Avalonia.Styling } protected override Selector? MovePrevious() => _previous; + internal override bool HasValidNestingSelector() => _previous?.HasValidNestingSelector() ?? false; internal static bool Compare(Type propertyType, object? propertyValue, object? value) { diff --git a/src/Avalonia.Base/Styling/PropertySetterInstance.cs b/src/Avalonia.Base/Styling/PropertySetterInstance.cs index 48f462d006..c4e8f47e67 100644 --- a/src/Avalonia.Base/Styling/PropertySetterInstance.cs +++ b/src/Avalonia.Base/Styling/PropertySetterInstance.cs @@ -44,7 +44,7 @@ namespace Avalonia.Styling { if (hasActivator) { - if (_styledProperty is object) + if (_styledProperty is not null) { _subscription = _target.Bind(_styledProperty, this, BindingPriority.StyleTrigger); } @@ -55,13 +55,15 @@ namespace Avalonia.Styling } else { - if (_styledProperty is object) + var target = (AvaloniaObject) _target; + + if (_styledProperty is not null) { - _subscription = _target.SetValue(_styledProperty!, _value, BindingPriority.Style); + _subscription = target.SetValue(_styledProperty!, _value, BindingPriority.Style); } else { - _target.SetValue(_directProperty!, _value); + target.SetValue(_directProperty!, _value); } } } diff --git a/src/Avalonia.Base/Styling/PropertySetterLazyInstance.cs b/src/Avalonia.Base/Styling/PropertySetterTemplateInstance.cs similarity index 79% rename from src/Avalonia.Base/Styling/PropertySetterLazyInstance.cs rename to src/Avalonia.Base/Styling/PropertySetterTemplateInstance.cs index 92653d0064..0f6efef1be 100644 --- a/src/Avalonia.Base/Styling/PropertySetterLazyInstance.cs +++ b/src/Avalonia.Base/Styling/PropertySetterTemplateInstance.cs @@ -11,42 +11,42 @@ namespace Avalonia.Styling /// evaluated. /// /// The target property type. - internal class PropertySetterLazyInstance : SingleSubscriberObservableBase>, + internal class PropertySetterTemplateInstance : SingleSubscriberObservableBase>, ISetterInstance { private readonly IStyleable _target; private readonly StyledPropertyBase? _styledProperty; private readonly DirectPropertyBase? _directProperty; - private readonly Func _valueFactory; + private readonly ITemplate _template; private BindingValue _value; private IDisposable? _subscription; private bool _isActive; - public PropertySetterLazyInstance( + public PropertySetterTemplateInstance( IStyleable target, StyledPropertyBase property, - Func valueFactory) + ITemplate template) { _target = target; _styledProperty = property; - _valueFactory = valueFactory; + _template = template; } - public PropertySetterLazyInstance( + public PropertySetterTemplateInstance( IStyleable target, DirectPropertyBase property, - Func valueFactory) + ITemplate template) { _target = target; _directProperty = property; - _valueFactory = valueFactory; + _template = template; } public void Start(bool hasActivator) { _isActive = !hasActivator; - if (_styledProperty is object) + if (_styledProperty is not null) { var priority = hasActivator ? BindingPriority.StyleTrigger : BindingPriority.Style; _subscription = _target.Bind(_styledProperty, this, priority); @@ -77,7 +77,7 @@ namespace Avalonia.Styling public override void Dispose() { - if (_subscription is object) + if (_subscription is not null) { var sub = _subscription; _subscription = null; @@ -85,7 +85,7 @@ namespace Avalonia.Styling } else if (_isActive) { - if (_styledProperty is object) + if (_styledProperty is not null) { _target.ClearValue(_styledProperty); } @@ -101,22 +101,21 @@ namespace Avalonia.Styling protected override void Subscribed() => PublishNext(); protected override void Unsubscribed() { } - private T GetValue() + private void EnsureTemplate() { if (_value.HasValue) { - return _value.Value; + return; } - _value = _valueFactory(); - return _value.Value; + _value = (T) _template.Build(); } private void PublishNext() { if (_isActive) { - GetValue(); + EnsureTemplate(); PublishNext(_value); } else diff --git a/src/Avalonia.Base/Styling/Selector.cs b/src/Avalonia.Base/Styling/Selector.cs index 0740e0f891..1e06f3d375 100644 --- a/src/Avalonia.Base/Styling/Selector.cs +++ b/src/Avalonia.Base/Styling/Selector.cs @@ -33,22 +33,25 @@ namespace Avalonia.Styling /// Tries to match the selector with a control. /// /// The control. + /// + /// The parent style, if the style containing the selector is a nested style. + /// /// /// Whether the match should subscribe to changes in order to track the match over time, /// or simply return an immediate result. /// /// A . - public SelectorMatch Match(IStyleable control, bool subscribe = true) + public SelectorMatch Match(IStyleable control, IStyle? parent = null, bool subscribe = true) { // First match the selector until a combinator is found. Selectors are stored from // right-to-left, so MatchUntilCombinator reverses this order because the type selector // will be on the left. - var match = MatchUntilCombinator(control, this, subscribe, out var combinator); + var match = MatchUntilCombinator(control, this, parent, subscribe, out var combinator); // If the pre-combinator selector matches, we can now match the combinator, if any. if (match.IsMatch && combinator is object) { - match = match.And(combinator.Match(control, subscribe)); + match = match.And(combinator.Match(control, parent, subscribe)); // If we have a combinator then we can never say that we always match a control of // this type, because by definition the combinator matches on things outside of the @@ -68,28 +71,34 @@ namespace Avalonia.Styling /// Evaluates the selector for a match. /// /// The control. + /// + /// The parent style, if the style containing the selector is a nested style. + /// /// /// Whether the match should subscribe to changes in order to track the match over time, /// or simply return an immediate result. /// /// A . - protected abstract SelectorMatch Evaluate(IStyleable control, bool subscribe); + protected abstract SelectorMatch Evaluate(IStyleable control, IStyle? parent, bool subscribe); /// /// Moves to the previous selector. /// protected abstract Selector? MovePrevious(); + internal abstract bool HasValidNestingSelector(); + private static SelectorMatch MatchUntilCombinator( IStyleable control, Selector start, + IStyle? parent, bool subscribe, out Selector? combinator) { combinator = null; var activators = new AndActivatorBuilder(); - var result = Match(control, start, subscribe, ref activators, ref combinator); + var result = Match(control, start, parent, subscribe, ref activators, ref combinator); return result == SelectorMatchResult.Sometimes ? new SelectorMatch(activators.Get()) : @@ -99,6 +108,7 @@ namespace Avalonia.Styling private static SelectorMatchResult Match( IStyleable control, Selector selector, + IStyle? parent, bool subscribe, ref AndActivatorBuilder activators, ref Selector? combinator) @@ -110,7 +120,7 @@ namespace Avalonia.Styling // opportunity to exit early. if (previous != null && !previous.IsCombinator) { - var previousMatch = Match(control, previous, subscribe, ref activators, ref combinator); + var previousMatch = Match(control, previous, parent, subscribe, ref activators, ref combinator); if (previousMatch < SelectorMatchResult.Sometimes) { @@ -119,7 +129,7 @@ namespace Avalonia.Styling } // Match this selector. - var match = selector.Evaluate(control, subscribe); + var match = selector.Evaluate(control, parent, subscribe); if (!match.IsMatch) { diff --git a/src/Avalonia.Base/Styling/Selectors.cs b/src/Avalonia.Base/Styling/Selectors.cs index 7c66469cf1..476d86cd11 100644 --- a/src/Avalonia.Base/Styling/Selectors.cs +++ b/src/Avalonia.Base/Styling/Selectors.cs @@ -109,6 +109,11 @@ namespace Avalonia.Styling } } + public static Selector Nesting(this Selector? previous) + { + return new NestingSelector(); + } + /// /// Returns a selector which inverts the results of selector argument. /// diff --git a/src/Avalonia.Base/Styling/Setter.cs b/src/Avalonia.Base/Styling/Setter.cs index b4b3399022..d989bb0706 100644 --- a/src/Avalonia.Base/Styling/Setter.cs +++ b/src/Avalonia.Base/Styling/Setter.cs @@ -1,9 +1,7 @@ using System; using Avalonia.Animation; using Avalonia.Data; -using Avalonia.Data.Core; using Avalonia.Metadata; -using Avalonia.Utilities; #nullable enable @@ -70,12 +68,5 @@ namespace Avalonia.Styling return Property.CreateSetterInstance(target, Value); } - - private struct SetterVisitorData - { - public IStyleable target; - public object? value; - public ISetterInstance? result; - } } } diff --git a/src/Avalonia.Base/Styling/Style.cs b/src/Avalonia.Base/Styling/Style.cs index 00819ef7be..8fcf5eec8a 100644 --- a/src/Avalonia.Base/Styling/Style.cs +++ b/src/Avalonia.Base/Styling/Style.cs @@ -4,8 +4,6 @@ using Avalonia.Animation; using Avalonia.Controls; using Avalonia.Metadata; -#nullable enable - namespace Avalonia.Styling { /// @@ -14,9 +12,11 @@ namespace Avalonia.Styling public class Style : AvaloniaObject, IStyle, IResourceProvider { private IResourceHost? _owner; + private StyleChildren? _children; private IResourceDictionary? _resources; private List? _setters; private List? _animations; + private StyleCache? _childCache; /// /// Initializes a new instance of the class. @@ -34,6 +34,14 @@ namespace Avalonia.Styling Selector = selector(null); } + /// + /// Gets the children of the style. + /// + public IList Children => _children ??= new(this); + + /// + /// Gets the or Application that hosts the style. + /// public IResourceHost? Owner { get => _owner; @@ -47,6 +55,11 @@ namespace Avalonia.Styling } } + /// + /// Gets the parent style if this style is hosted in a collection. + /// + public Style? Parent { get; private set; } + /// /// Gets or sets a dictionary of style resources. /// @@ -90,7 +103,7 @@ namespace Avalonia.Styling public IList Animations => _animations ??= new List(); bool IResourceNode.HasResources => _resources?.Count > 0; - IReadOnlyList IStyle.Children => Array.Empty(); + IReadOnlyList IStyle.Children => (IReadOnlyList?)_children ?? Array.Empty(); public event EventHandler? OwnerChanged; @@ -98,7 +111,7 @@ namespace Avalonia.Styling { target = target ?? throw new ArgumentNullException(nameof(target)); - var match = Selector is object ? Selector.Match(target) : + var match = Selector is object ? Selector.Match(target, Parent) : target == host ? SelectorMatch.AlwaysThisInstance : SelectorMatch.NeverThisInstance; if (match.IsMatch && (_setters is object || _animations is object)) @@ -108,7 +121,17 @@ namespace Avalonia.Styling instance.Start(); } - return match.Result; + var result = match.Result; + + if (_children is not null) + { + _childCache ??= new StyleCache(); + var childResult = _childCache.TryAttach(_children, target, host); + if (childResult > result) + result = childResult; + } + + return result; } public bool TryGetResource(object key, out object? result) @@ -156,5 +179,18 @@ namespace Avalonia.Styling _resources?.RemoveOwner(owner); } } + + internal void SetParent(Style? parent) + { + if (parent?.Selector is not null) + { + if (Selector is null) + throw new InvalidOperationException("Child styles must have a selector."); + if (!Selector.HasValidNestingSelector()) + throw new InvalidOperationException("Child styles must have a nesting selector."); + } + + Parent = parent; + } } } diff --git a/src/Avalonia.Base/Styling/StyleCache.cs b/src/Avalonia.Base/Styling/StyleCache.cs new file mode 100644 index 0000000000..3285476880 --- /dev/null +++ b/src/Avalonia.Base/Styling/StyleCache.cs @@ -0,0 +1,58 @@ +using System; +using System.Collections.Generic; + +namespace Avalonia.Styling +{ + /// + /// Simple cache for improving performance of applying styles. + /// + /// + /// Maps to a list of styles that are known be be possible + /// matches. + /// + internal class StyleCache : Dictionary?> + { + public SelectorMatchResult TryAttach(IList styles, IStyleable target, IStyleHost? host) + { + if (TryGetValue(target.StyleKey, out var cached)) + { + if (cached is object) + { + var result = SelectorMatchResult.NeverThisType; + + foreach (var style in cached) + { + var childResult = style.TryAttach(target, host); + if (childResult > result) + result = childResult; + } + + return result; + } + else + { + return SelectorMatchResult.NeverThisType; + } + } + else + { + List? matches = null; + + foreach (var child in styles) + { + if (child.TryAttach(target, host) != SelectorMatchResult.NeverThisType) + { + matches ??= new List(); + matches.Add(child); + } + } + + Add(target.StyleKey, matches); + + return matches is null ? + SelectorMatchResult.NeverThisType : + SelectorMatchResult.AlwaysThisType; + } + } + } +} diff --git a/src/Avalonia.Base/Styling/StyleChildren.cs b/src/Avalonia.Base/Styling/StyleChildren.cs new file mode 100644 index 0000000000..5f8635f155 --- /dev/null +++ b/src/Avalonia.Base/Styling/StyleChildren.cs @@ -0,0 +1,35 @@ +using System.Collections.ObjectModel; +using Avalonia.Controls; + +namespace Avalonia.Styling +{ + internal class StyleChildren : Collection + { + private readonly Style _owner; + + public StyleChildren(Style owner) => _owner = owner; + + protected override void InsertItem(int index, IStyle item) + { + (item as Style)?.SetParent(_owner); + base.InsertItem(index, item); + } + + protected override void RemoveItem(int index) + { + var item = Items[index]; + (item as Style)?.SetParent(null); + if (_owner.Owner is IResourceHost host) + (item as IResourceProvider)?.RemoveOwner(host); + base.RemoveItem(index); + } + + protected override void SetItem(int index, IStyle item) + { + (item as Style)?.SetParent(_owner); + base.SetItem(index, item); + if (_owner.Owner is IResourceHost host) + (item as IResourceProvider)?.AddOwner(host); + } + } +} diff --git a/src/Avalonia.Base/Styling/StyleInstance.cs b/src/Avalonia.Base/Styling/StyleInstance.cs index 830cf49a0d..db96da6821 100644 --- a/src/Avalonia.Base/Styling/StyleInstance.cs +++ b/src/Avalonia.Base/Styling/StyleInstance.cs @@ -11,10 +11,10 @@ namespace Avalonia.Styling /// /// A which has been instanced on a control. /// - internal class StyleInstance : IStyleInstance, IStyleActivatorSink + internal sealed class StyleInstance : IStyleInstance, IStyleActivatorSink { - private readonly List? _setters; - private readonly List? _animations; + private readonly ISetterInstance[]? _setters; + private readonly IDisposable[]? _animations; private readonly IStyleActivator? _activator; private readonly Subject? _animationTrigger; @@ -30,41 +30,42 @@ namespace Avalonia.Styling _activator = activator; IsActive = _activator is null; - if (setters is object) + if (setters is not null) { var setterCount = setters.Count; - _setters = new List(setterCount); + _setters = new ISetterInstance[setterCount]; for (var i = 0; i < setterCount; ++i) { - _setters.Add(setters[i].Instance(Target)); + _setters[i] = setters[i].Instance(Target); } } - if (animations is object && target is Animatable animatable) + if (animations is not null && target is Animatable animatable) { var animationsCount = animations.Count; - _animations = new List(animationsCount); + _animations = new IDisposable[animationsCount]; _animationTrigger = new Subject(); for (var i = 0; i < animationsCount; ++i) { - _animations.Add(animations[i].Apply(animatable, null, _animationTrigger)); + _animations[i] = animations[i].Apply(animatable, null, _animationTrigger); } } } + public bool HasActivator => _activator is not null; public bool IsActive { get; private set; } public IStyle Source { get; } public IStyleable Target { get; } public void Start() { - var hasActivator = _activator is object; + var hasActivator = HasActivator; - if (_setters is object) + if (_setters is not null) { foreach (var setter in _setters) { @@ -76,7 +77,7 @@ namespace Avalonia.Styling { _activator!.Subscribe(this, 0); } - else if (_animationTrigger != null) + else if (_animationTrigger is not null) { _animationTrigger.OnNext(true); } @@ -84,7 +85,7 @@ namespace Avalonia.Styling public void Dispose() { - if (_setters is object) + if (_setters is not null) { foreach (var setter in _setters) { @@ -92,11 +93,11 @@ namespace Avalonia.Styling } } - if (_animations is object) + if (_animations is not null) { - foreach (var subscripion in _animations) + foreach (var subscription in _animations) { - subscripion.Dispose(); + subscription.Dispose(); } } @@ -111,7 +112,7 @@ namespace Avalonia.Styling _animationTrigger?.OnNext(value); - if (_setters is object) + if (_setters is not null) { if (IsActive) { diff --git a/src/Avalonia.Base/Styling/Styles.cs b/src/Avalonia.Base/Styling/Styles.cs index d79081152e..7c0bc4ad7f 100644 --- a/src/Avalonia.Base/Styling/Styles.cs +++ b/src/Avalonia.Base/Styling/Styles.cs @@ -20,7 +20,7 @@ namespace Avalonia.Styling private readonly AvaloniaList _styles = new AvaloniaList(); private IResourceHost? _owner; private IResourceDictionary? _resources; - private Dictionary?>? _cache; + private StyleCache? _cache; public Styles() { @@ -111,43 +111,8 @@ namespace Avalonia.Styling public SelectorMatchResult TryAttach(IStyleable target, IStyleHost? host) { - _cache ??= new Dictionary?>(); - - if (_cache.TryGetValue(target.StyleKey, out var cached)) - { - if (cached is object) - { - foreach (var style in cached) - { - style.TryAttach(target, host); - } - - return SelectorMatchResult.AlwaysThisType; - } - else - { - return SelectorMatchResult.NeverThisType; - } - } - else - { - List? matches = null; - - foreach (var child in this) - { - if (child.TryAttach(target, host) != SelectorMatchResult.NeverThisType) - { - matches ??= new List(); - matches.Add(child); - } - } - - _cache.Add(target.StyleKey, matches); - - return matches is null ? - SelectorMatchResult.NeverThisType : - SelectorMatchResult.AlwaysThisType; - } + _cache ??= new StyleCache(); + return _cache.TryAttach(this, target, host); } /// diff --git a/src/Avalonia.Base/Styling/TemplateSelector.cs b/src/Avalonia.Base/Styling/TemplateSelector.cs index e8051efa6d..b0a2dae8d6 100644 --- a/src/Avalonia.Base/Styling/TemplateSelector.cs +++ b/src/Avalonia.Base/Styling/TemplateSelector.cs @@ -36,7 +36,7 @@ namespace Avalonia.Styling return _selectorString; } - protected override SelectorMatch Evaluate(IStyleable control, bool subscribe) + protected override SelectorMatch Evaluate(IStyleable control, IStyle? parent, bool subscribe) { var templatedParent = control.TemplatedParent as IStyleable; @@ -45,9 +45,10 @@ namespace Avalonia.Styling return SelectorMatch.NeverThisInstance; } - return _parent.Match(templatedParent, subscribe); + return _parent.Match(templatedParent, parent, subscribe); } protected override Selector? MovePrevious() => null; + internal override bool HasValidNestingSelector() => _parent?.HasValidNestingSelector() ?? false; } } diff --git a/src/Avalonia.Base/Styling/TypeNameAndClassSelector.cs b/src/Avalonia.Base/Styling/TypeNameAndClassSelector.cs index ef48c4a8cd..24d5d6bbbf 100644 --- a/src/Avalonia.Base/Styling/TypeNameAndClassSelector.cs +++ b/src/Avalonia.Base/Styling/TypeNameAndClassSelector.cs @@ -94,7 +94,7 @@ namespace Avalonia.Styling } /// - protected override SelectorMatch Evaluate(IStyleable control, bool subscribe) + protected override SelectorMatch Evaluate(IStyleable control, IStyle? parent, bool subscribe) { if (TargetType != null) { @@ -140,6 +140,7 @@ namespace Avalonia.Styling } protected override Selector? MovePrevious() => _previous; + internal override bool HasValidNestingSelector() => _previous?.HasValidNestingSelector() ?? false; private string BuildSelectorString() { diff --git a/src/Avalonia.Base/Threading/ThreadSafeObjectPool.cs b/src/Avalonia.Base/Threading/ThreadSafeObjectPool.cs index c6845485dc..827a02334a 100644 --- a/src/Avalonia.Base/Threading/ThreadSafeObjectPool.cs +++ b/src/Avalonia.Base/Threading/ThreadSafeObjectPool.cs @@ -5,12 +5,11 @@ namespace Avalonia.Threading public class ThreadSafeObjectPool where T : class, new() { private Stack _stack = new Stack(); - private object _lock = new object(); public static ThreadSafeObjectPool Default { get; } = new ThreadSafeObjectPool(); public T Get() { - lock (_lock) + lock (_stack) { if(_stack.Count == 0) return new T(); diff --git a/src/Avalonia.Base/VisualTree/IVisual.cs b/src/Avalonia.Base/VisualTree/IVisual.cs index b1251618c4..3b053fab38 100644 --- a/src/Avalonia.Base/VisualTree/IVisual.cs +++ b/src/Avalonia.Base/VisualTree/IVisual.cs @@ -1,10 +1,9 @@ using System; using Avalonia.Collections; using Avalonia.Media; +using Avalonia.Metadata; using Avalonia.Rendering; -#nullable enable - namespace Avalonia.VisualTree { /// @@ -18,6 +17,7 @@ namespace Avalonia.VisualTree /// implemented by . It should not be necessary to implement it /// anywhere else. /// + [NotClientImplementable] public interface IVisual { /// diff --git a/src/Avalonia.Controls.DataGrid/Primitives/DataGridCellsPresenter.cs b/src/Avalonia.Controls.DataGrid/Primitives/DataGridCellsPresenter.cs index e07c933039..38d559a031 100644 --- a/src/Avalonia.Controls.DataGrid/Primitives/DataGridCellsPresenter.cs +++ b/src/Avalonia.Controls.DataGrid/Primitives/DataGridCellsPresenter.cs @@ -218,6 +218,8 @@ namespace Avalonia.Controls.Primitives { // No explicit height values were set so we can autosize autoSizeHeight = true; + // We need to invalidate desired height in order to grow or shrink as needed + InvalidateDesiredHeight(); measureHeight = double.PositiveInfinity; } else diff --git a/src/Avalonia.Controls.DataGrid/Themes/Fluent.xaml b/src/Avalonia.Controls.DataGrid/Themes/Fluent.xaml index 8243d9c22d..2baa8c88c9 100644 --- a/src/Avalonia.Controls.DataGrid/Themes/Fluent.xaml +++ b/src/Avalonia.Controls.DataGrid/Themes/Fluent.xaml @@ -1,87 +1,51 @@ - + - 12,0,12,0 - 0.6 0.8 + 12,0,12,0 M1875 1011l-787 787v-1798h-128v1798l-787 -787l-90 90l941 941l941 -941z M1965 947l-941 -941l-941 941l90 90l787 -787v1798h128v-1798l787 787z M515 93l930 931l-930 931l90 90l1022 -1021l-1022 -1021z M1939 1581l90 -90l-1005 -1005l-1005 1005l90 90l915 -915z + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + Opacity="0.4" + Color="{DynamicResource SystemBaseMediumLowColor}" /> + + + diff --git a/src/Avalonia.Controls/ApplicationLifetimes/IApplicationLifetime.cs b/src/Avalonia.Controls/ApplicationLifetimes/IApplicationLifetime.cs index 9860d0cb38..b38a539d4a 100644 --- a/src/Avalonia.Controls/ApplicationLifetimes/IApplicationLifetime.cs +++ b/src/Avalonia.Controls/ApplicationLifetimes/IApplicationLifetime.cs @@ -1,5 +1,8 @@ +using Avalonia.Metadata; + namespace Avalonia.Controls.ApplicationLifetimes { + [NotClientImplementable] public interface IApplicationLifetime { diff --git a/src/Avalonia.Controls/ApplicationLifetimes/IClassicDesktopStyleApplicationLifetime.cs b/src/Avalonia.Controls/ApplicationLifetimes/IClassicDesktopStyleApplicationLifetime.cs index 2bd5c1238d..4b88f6b537 100644 --- a/src/Avalonia.Controls/ApplicationLifetimes/IClassicDesktopStyleApplicationLifetime.cs +++ b/src/Avalonia.Controls/ApplicationLifetimes/IClassicDesktopStyleApplicationLifetime.cs @@ -1,12 +1,14 @@ using System; using System.Collections.Generic; using System.ComponentModel; +using Avalonia.Metadata; namespace Avalonia.Controls.ApplicationLifetimes { /// /// Controls application lifetime in classic desktop style /// + [NotClientImplementable] public interface IClassicDesktopStyleApplicationLifetime : IControlledApplicationLifetime { /// diff --git a/src/Avalonia.Controls/ApplicationLifetimes/IControlledApplicationLifetime.cs b/src/Avalonia.Controls/ApplicationLifetimes/IControlledApplicationLifetime.cs index 3f61aeb536..d7eda790df 100644 --- a/src/Avalonia.Controls/ApplicationLifetimes/IControlledApplicationLifetime.cs +++ b/src/Avalonia.Controls/ApplicationLifetimes/IControlledApplicationLifetime.cs @@ -1,7 +1,9 @@ using System; +using Avalonia.Metadata; namespace Avalonia.Controls.ApplicationLifetimes { + [NotClientImplementable] public interface IControlledApplicationLifetime : IApplicationLifetime { /// diff --git a/src/Avalonia.Controls/ApplicationLifetimes/ISingleViewApplicationLifetime.cs b/src/Avalonia.Controls/ApplicationLifetimes/ISingleViewApplicationLifetime.cs index e25815602e..480c65e5ad 100644 --- a/src/Avalonia.Controls/ApplicationLifetimes/ISingleViewApplicationLifetime.cs +++ b/src/Avalonia.Controls/ApplicationLifetimes/ISingleViewApplicationLifetime.cs @@ -1,5 +1,8 @@ +using Avalonia.Metadata; + namespace Avalonia.Controls.ApplicationLifetimes { + [NotClientImplementable] public interface ISingleViewApplicationLifetime : IApplicationLifetime { Control? MainView { get; set; } diff --git a/src/Avalonia.Controls/Button.cs b/src/Avalonia.Controls/Button.cs index 0ef1ba4c8c..db4ca6bc43 100644 --- a/src/Avalonia.Controls/Button.cs +++ b/src/Avalonia.Controls/Button.cs @@ -309,6 +309,8 @@ namespace Avalonia.Controls IsPressed = false; e.Handled = true; } + + base.OnKeyUp(e); } /// @@ -393,6 +395,8 @@ namespace Avalonia.Controls /// protected override void OnPointerCaptureLost(PointerCaptureLostEventArgs e) { + base.OnPointerCaptureLost(e); + IsPressed = false; } @@ -407,6 +411,8 @@ namespace Avalonia.Controls /// protected override void OnApplyTemplate(TemplateAppliedEventArgs e) { + base.OnApplyTemplate(e); + UnregisterFlyoutEvents(Flyout); RegisterFlyoutEvents(Flyout); UpdatePseudoClasses(); diff --git a/src/Avalonia.Controls/Calendar/CalendarItem.cs b/src/Avalonia.Controls/Calendar/CalendarItem.cs index c44994f92f..32fdaceacb 100644 --- a/src/Avalonia.Controls/Calendar/CalendarItem.cs +++ b/src/Avalonia.Controls/Calendar/CalendarItem.cs @@ -7,6 +7,7 @@ using System; using System.Collections.Generic; using System.Diagnostics; using System.Globalization; +using Avalonia.Collections.Pooled; using Avalonia.Controls.Metadata; using Avalonia.Data; using Avalonia.Input; @@ -172,13 +173,13 @@ namespace Avalonia.Controls.Primitives if (MonthView != null) { var childCount = Calendar.RowsPerMonth + Calendar.RowsPerMonth * Calendar.ColumnsPerMonth; - var children = new List(childCount); + using var children = new PooledList(childCount); for (int i = 0; i < Calendar.RowsPerMonth; i++) { if (_dayTitleTemplate != null) { - var cell = _dayTitleTemplate.Build(); + var cell = (Control) _dayTitleTemplate.Build(); cell.DataContext = string.Empty; cell.SetValue(Grid.RowProperty, 0); cell.SetValue(Grid.ColumnProperty, i); @@ -186,11 +187,16 @@ namespace Avalonia.Controls.Primitives } } + EventHandler cellMouseLeftButtonDown = Cell_MouseLeftButtonDown; + EventHandler cellMouseLeftButtonUp = Cell_MouseLeftButtonUp; + EventHandler cellMouseEnter = Cell_MouseEnter; + EventHandler cellClick = Cell_Click; + for (int i = 1; i < Calendar.RowsPerMonth; i++) { for (int j = 0; j < Calendar.ColumnsPerMonth; j++) { - CalendarDayButton cell = new CalendarDayButton(); + var cell = new CalendarDayButton(); if (Owner != null) { @@ -198,10 +204,10 @@ namespace Avalonia.Controls.Primitives } cell.SetValue(Grid.RowProperty, i); cell.SetValue(Grid.ColumnProperty, j); - cell.CalendarDayButtonMouseDown += Cell_MouseLeftButtonDown; - cell.CalendarDayButtonMouseUp += Cell_MouseLeftButtonUp; - cell.PointerEnter += Cell_MouseEnter; - cell.Click += Cell_Click; + cell.CalendarDayButtonMouseDown += cellMouseLeftButtonDown; + cell.CalendarDayButtonMouseUp += cellMouseLeftButtonUp; + cell.PointerEnter += cellMouseEnter; + cell.Click += cellClick; children.Add(cell); } } @@ -214,12 +220,15 @@ namespace Avalonia.Controls.Primitives var childCount = Calendar.RowsPerYear * Calendar.ColumnsPerYear; var children = new List(childCount); - CalendarButton month; + EventHandler monthCalendarButtonMouseDown = Month_CalendarButtonMouseDown; + EventHandler monthCalendarButtonMouseUp = Month_CalendarButtonMouseUp; + EventHandler monthMouseEnter = Month_MouseEnter; + for (int i = 0; i < Calendar.RowsPerYear; i++) { for (int j = 0; j < Calendar.ColumnsPerYear; j++) { - month = new CalendarButton(); + var month = new CalendarButton(); if (Owner != null) { @@ -227,9 +236,9 @@ namespace Avalonia.Controls.Primitives } month.SetValue(Grid.RowProperty, i); month.SetValue(Grid.ColumnProperty, j); - month.CalendarLeftMouseButtonDown += Month_CalendarButtonMouseDown; - month.CalendarLeftMouseButtonUp += Month_CalendarButtonMouseUp; - month.PointerEnter += Month_MouseEnter; + month.CalendarLeftMouseButtonDown += monthCalendarButtonMouseDown; + month.CalendarLeftMouseButtonUp += monthCalendarButtonMouseUp; + month.PointerEnter += monthMouseEnter; children.Add(month); } } diff --git a/src/Avalonia.Controls/CalendarDatePicker/CalendarDatePicker.Properties.cs b/src/Avalonia.Controls/CalendarDatePicker/CalendarDatePicker.Properties.cs new file mode 100644 index 0000000000..6c2356b411 --- /dev/null +++ b/src/Avalonia.Controls/CalendarDatePicker/CalendarDatePicker.Properties.cs @@ -0,0 +1,303 @@ +using System; +using Avalonia.Controls.Primitives; +using Avalonia.Data; +using Avalonia.Layout; + +namespace Avalonia.Controls +{ + /// + public partial class CalendarDatePicker + { + /// + /// Defines the property. + /// + public static readonly DirectProperty DisplayDateProperty = + AvaloniaProperty.RegisterDirect( + nameof(DisplayDate), + o => o.DisplayDate, + (o, v) => o.DisplayDate = v); + + /// + /// Defines the property. + /// + public static readonly DirectProperty DisplayDateStartProperty = + AvaloniaProperty.RegisterDirect( + nameof(DisplayDateStart), + o => o.DisplayDateStart, + (o, v) => o.DisplayDateStart = v); + + /// + /// Defines the property. + /// + public static readonly DirectProperty DisplayDateEndProperty = + AvaloniaProperty.RegisterDirect( + nameof(DisplayDateEnd), + o => o.DisplayDateEnd, + (o, v) => o.DisplayDateEnd = v); + + /// + /// Defines the property. + /// + public static readonly StyledProperty FirstDayOfWeekProperty = + AvaloniaProperty.Register(nameof(FirstDayOfWeek)); + + /// + /// Defines the property. + /// + public static readonly DirectProperty IsDropDownOpenProperty = + AvaloniaProperty.RegisterDirect( + nameof(IsDropDownOpen), + o => o.IsDropDownOpen, + (o, v) => o.IsDropDownOpen = v); + + /// + /// Defines the property. + /// + public static readonly StyledProperty IsTodayHighlightedProperty = + AvaloniaProperty.Register(nameof(IsTodayHighlighted)); + + /// + /// Defines the property. + /// + public static readonly DirectProperty SelectedDateProperty = + AvaloniaProperty.RegisterDirect( + nameof(SelectedDate), + o => o.SelectedDate, + (o, v) => o.SelectedDate = v, + enableDataValidation: true, + defaultBindingMode:BindingMode.TwoWay); + + /// + /// Defines the property. + /// + public static readonly StyledProperty SelectedDateFormatProperty = + AvaloniaProperty.Register( + nameof(SelectedDateFormat), + defaultValue: CalendarDatePickerFormat.Short, + validate: IsValidSelectedDateFormat); + + /// + /// Defines the property. + /// + public static readonly StyledProperty CustomDateFormatStringProperty = + AvaloniaProperty.Register( + nameof(CustomDateFormatString), + defaultValue: "d", + validate: IsValidDateFormatString); + + /// + /// Defines the property. + /// + public static readonly DirectProperty TextProperty = + AvaloniaProperty.RegisterDirect( + nameof(Text), + o => o.Text, + (o, v) => o.Text = v); + + /// + /// Defines the property. + /// + public static readonly StyledProperty WatermarkProperty = + TextBox.WatermarkProperty.AddOwner(); + + /// + /// Defines the property. + /// + public static readonly StyledProperty UseFloatingWatermarkProperty = + TextBox.UseFloatingWatermarkProperty.AddOwner(); + + /// + /// Defines the property. + /// + public static readonly StyledProperty HorizontalContentAlignmentProperty = + ContentControl.HorizontalContentAlignmentProperty.AddOwner(); + + /// + /// Defines the property. + /// + public static readonly StyledProperty VerticalContentAlignmentProperty = + ContentControl.VerticalContentAlignmentProperty.AddOwner(); + + /// + /// Gets a collection of dates that are marked as not selectable. + /// + /// + /// A collection of dates that cannot be selected. The default value is + /// an empty collection. + /// + public CalendarBlackoutDatesCollection? BlackoutDates { get; private set; } + + /// + /// Gets or sets the date to display. + /// + /// + /// The date to display. The default is . + /// + /// + /// The specified date is not in the range defined by + /// + /// and + /// . + /// + public DateTime DisplayDate + { + get => _displayDate; + set => SetAndRaise(DisplayDateProperty, ref _displayDate, value); + } + + /// + /// Gets or sets the first date to be displayed. + /// + /// The first date to display. + public DateTime? DisplayDateStart + { + get => _displayDateStart; + set => SetAndRaise(DisplayDateStartProperty, ref _displayDateStart, value); + } + + /// + /// Gets or sets the last date to be displayed. + /// + /// The last date to display. + public DateTime? DisplayDateEnd + { + get => _displayDateEnd; + set => SetAndRaise(DisplayDateEndProperty, ref _displayDateEnd, value); + } + + /// + /// Gets or sets the day that is considered the beginning of the week. + /// + /// + /// A representing the beginning of + /// the week. The default is . + /// + public DayOfWeek FirstDayOfWeek + { + get => GetValue(FirstDayOfWeekProperty); + set => SetValue(FirstDayOfWeekProperty, value); + } + + /// + /// Gets or sets a value indicating whether the drop-down + /// is open or closed. + /// + /// + /// True if the is + /// open; otherwise, false. The default is false. + /// + public bool IsDropDownOpen + { + get => _isDropDownOpen; + set => SetAndRaise(IsDropDownOpenProperty, ref _isDropDownOpen, value); + } + + /// + /// Gets or sets a value indicating whether the current date will be + /// highlighted. + /// + /// + /// True if the current date is highlighted; otherwise, false. The + /// default is true. + /// + public bool IsTodayHighlighted + { + get => GetValue(IsTodayHighlightedProperty); + set => SetValue(IsTodayHighlightedProperty, value); + } + + /// + /// Gets or sets the currently selected date. + /// + /// + /// The date currently selected. The default is null. + /// + /// + /// The specified date is not in the range defined by + /// + /// and + /// , + /// or the specified date is in the + /// + /// collection. + /// + public DateTime? SelectedDate + { + get => _selectedDate; + set => SetAndRaise(SelectedDateProperty, ref _selectedDate, value); + } + + /// + /// Gets or sets the format that is used to display the selected date. + /// + /// + /// The format that is used to display the selected date. The default is + /// . + /// + /// + /// An specified format is not valid. + /// + public CalendarDatePickerFormat SelectedDateFormat + { + get => GetValue(SelectedDateFormatProperty); + set => SetValue(SelectedDateFormatProperty, value); + } + + public string CustomDateFormatString + { + get => GetValue(CustomDateFormatStringProperty); + set => SetValue(CustomDateFormatStringProperty, value); + } + + /// + /// Gets or sets the text that is displayed by the . + /// + /// + /// The text displayed by the . + /// + /// + /// The text entered cannot be parsed to a valid date, and the exception + /// is not suppressed. + /// + /// + /// The text entered parses to a date that is not selectable. + /// + public string? Text + { + get => _text; + set => SetAndRaise(TextProperty, ref _text, value); + } + + /// + public string? Watermark + { + get => GetValue(WatermarkProperty); + set => SetValue(WatermarkProperty, value); + } + + /// + public bool UseFloatingWatermark + { + get => GetValue(UseFloatingWatermarkProperty); + set => SetValue(UseFloatingWatermarkProperty, value); + } + + /// + /// Gets or sets the horizontal alignment of the content within the control. + /// + public HorizontalAlignment HorizontalContentAlignment + { + get => GetValue(HorizontalContentAlignmentProperty); + set => SetValue(HorizontalContentAlignmentProperty, value); + } + + /// + /// Gets or sets the vertical alignment of the content within the control. + /// + public VerticalAlignment VerticalContentAlignment + { + get => GetValue(VerticalContentAlignmentProperty); + set => SetValue(VerticalContentAlignmentProperty, value); + } + } +} diff --git a/src/Avalonia.Controls/Calendar/CalendarDatePicker.cs b/src/Avalonia.Controls/CalendarDatePicker/CalendarDatePicker.cs similarity index 55% rename from src/Avalonia.Controls/Calendar/CalendarDatePicker.cs rename to src/Avalonia.Controls/CalendarDatePicker/CalendarDatePicker.cs index 0409eb30aa..3d592e9ab5 100644 --- a/src/Avalonia.Controls/Calendar/CalendarDatePicker.cs +++ b/src/Avalonia.Controls/CalendarDatePicker/CalendarDatePicker.cs @@ -7,122 +7,28 @@ using System; using System.Collections.ObjectModel; using System.Diagnostics; using System.Globalization; +using System.Reactive.Disposables; using Avalonia.Controls.Metadata; using Avalonia.Controls.Primitives; using Avalonia.Data; using Avalonia.Input; using Avalonia.Interactivity; -using Avalonia.Layout; namespace Avalonia.Controls { /// - /// Provides data for the - /// - /// event. + /// A date selection control that allows the user to select dates from a drop down calendar. /// - public class CalendarDatePickerDateValidationErrorEventArgs : EventArgs - { - private bool _throwException; - - /// - /// Initializes a new instance of the - /// - /// class. - /// - /// - /// The initial exception from the - /// - /// event. - /// - /// - /// The text that caused the - /// - /// event. - /// - public CalendarDatePickerDateValidationErrorEventArgs(Exception exception, string text) - { - this.Text = text; - this.Exception = exception; - } - - /// - /// Gets the initial exception associated with the - /// - /// event. - /// - /// - /// The exception associated with the validation failure. - /// - public Exception Exception { get; private set; } - - /// - /// Gets the text that caused the - /// - /// event. - /// - /// - /// The text that caused the validation failure. - /// - public string Text { get; private set; } - - /// - /// Gets or sets a value indicating whether - /// - /// should be thrown. - /// - /// - /// True if the exception should be thrown; otherwise, false. - /// - /// - /// If set to true and - /// - /// is null. - /// - public bool ThrowException - { - get { return this._throwException; } - set - { - if (value && this.Exception == null) - { - throw new ArgumentException("Cannot Throw Null Exception"); - } - this._throwException = value; - } - } - } - - /// - /// Specifies date formats for a - /// . - /// - public enum CalendarDatePickerFormat - { - /// - /// Specifies that the date should be displayed using unabbreviated days - /// of the week and month names. - /// - Long = 0, - - /// - /// Specifies that the date should be displayed using abbreviated days - /// of the week and month names. - /// - Short = 1, - - /// - /// Specifies that the date should be displayed using a custom format string. - /// - Custom = 2 - } - [TemplatePart(ElementButton, typeof(Button))] [TemplatePart(ElementCalendar, typeof(Calendar))] [TemplatePart(ElementPopup, typeof(Popup))] [TemplatePart(ElementTextBox, typeof(TextBox))] - public class CalendarDatePicker : TemplatedControl + [PseudoClasses(pcFlyoutOpen, pcPressed)] + public partial class CalendarDatePicker : TemplatedControl { + protected const string pcPressed = ":pressed"; + protected const string pcFlyoutOpen = ":flyout-open"; + private const string ElementTextBox = "PART_TextBox"; private const string ElementButton = "PART_Button"; private const string ElementPopup = "PART_Popup"; @@ -131,8 +37,6 @@ namespace Avalonia.Controls private Calendar? _calendar; private string _defaultText; private Button? _dropDownButton; - //private Canvas _outsideCanvas; - //private Canvas _outsidePopupCanvas; private Popup? _popUp; private TextBox? _textBox; private IDisposable? _textBoxTextChangedSubscription; @@ -150,258 +54,8 @@ namespace Avalonia.Controls private bool _suspendTextChangeHandler = false; private bool _isPopupClosing = false; private bool _ignoreButtonClick = false; - - /// - /// Gets a collection of dates that are marked as not selectable. - /// - /// - /// A collection of dates that cannot be selected. The default value is - /// an empty collection. - /// - public CalendarBlackoutDatesCollection? BlackoutDates { get; private set; } - - public static readonly DirectProperty DisplayDateProperty = - AvaloniaProperty.RegisterDirect( - nameof(DisplayDate), - o => o.DisplayDate, - (o, v) => o.DisplayDate = v); - public static readonly DirectProperty DisplayDateStartProperty = - AvaloniaProperty.RegisterDirect( - nameof(DisplayDateStart), - o => o.DisplayDateStart, - (o, v) => o.DisplayDateStart = v); - public static readonly DirectProperty DisplayDateEndProperty = - AvaloniaProperty.RegisterDirect( - nameof(DisplayDateEnd), - o => o.DisplayDateEnd, - (o, v) => o.DisplayDateEnd = v); - public static readonly StyledProperty FirstDayOfWeekProperty = - AvaloniaProperty.Register(nameof(FirstDayOfWeek)); - - public static readonly DirectProperty IsDropDownOpenProperty = - AvaloniaProperty.RegisterDirect( - nameof(IsDropDownOpen), - o => o.IsDropDownOpen, - (o, v) => o.IsDropDownOpen = v); - - public static readonly StyledProperty IsTodayHighlightedProperty = - AvaloniaProperty.Register(nameof(IsTodayHighlighted)); - public static readonly DirectProperty SelectedDateProperty = - AvaloniaProperty.RegisterDirect( - nameof(SelectedDate), - o => o.SelectedDate, - (o, v) => o.SelectedDate = v, - enableDataValidation: true, - defaultBindingMode:BindingMode.TwoWay); - - public static readonly StyledProperty SelectedDateFormatProperty = - AvaloniaProperty.Register( - nameof(SelectedDateFormat), - defaultValue: CalendarDatePickerFormat.Short, - validate: IsValidSelectedDateFormat); - - public static readonly StyledProperty CustomDateFormatStringProperty = - AvaloniaProperty.Register( - nameof(CustomDateFormatString), - defaultValue: "d", - validate: IsValidDateFormatString); - - public static readonly DirectProperty TextProperty = - AvaloniaProperty.RegisterDirect( - nameof(Text), - o => o.Text, - (o, v) => o.Text = v); - public static readonly StyledProperty WatermarkProperty = - TextBox.WatermarkProperty.AddOwner(); - public static readonly StyledProperty UseFloatingWatermarkProperty = - TextBox.UseFloatingWatermarkProperty.AddOwner(); - - - /// - /// Defines the property. - /// - public static readonly StyledProperty HorizontalContentAlignmentProperty = - ContentControl.HorizontalContentAlignmentProperty.AddOwner(); - - /// - /// Defines the property. - /// - public static readonly StyledProperty VerticalContentAlignmentProperty = - ContentControl.VerticalContentAlignmentProperty.AddOwner(); - - /// - /// Gets or sets the date to display. - /// - /// - /// The date to display. The default - /// . - /// - /// - /// The specified date is not in the range defined by - /// - /// and - /// . - /// - public DateTime DisplayDate - { - get { return _displayDate; } - set { SetAndRaise(DisplayDateProperty, ref _displayDate, value); } - } - - /// - /// Gets or sets the first date to be displayed. - /// - /// The first date to display. - public DateTime? DisplayDateStart - { - get { return _displayDateStart; } - set { SetAndRaise(DisplayDateStartProperty, ref _displayDateStart, value); } - } - - /// - /// Gets or sets the last date to be displayed. - /// - /// The last date to display. - public DateTime? DisplayDateEnd - { - get { return _displayDateEnd; } - set { SetAndRaise(DisplayDateEndProperty, ref _displayDateEnd, value); } - } - - /// - /// Gets or sets the day that is considered the beginning of the week. - /// - /// - /// A representing the beginning of - /// the week. The default is . - /// - public DayOfWeek FirstDayOfWeek - { - get { return GetValue(FirstDayOfWeekProperty); } - set { SetValue(FirstDayOfWeekProperty, value); } - } - - /// - /// Gets or sets a value indicating whether the drop-down - /// is open or closed. - /// - /// - /// True if the is - /// open; otherwise, false. The default is false. - /// - public bool IsDropDownOpen - { - get { return _isDropDownOpen; } - set { SetAndRaise(IsDropDownOpenProperty, ref _isDropDownOpen, value); } - } - - /// - /// Gets or sets a value indicating whether the current date will be - /// highlighted. - /// - /// - /// True if the current date is highlighted; otherwise, false. The - /// default is true. - /// - public bool IsTodayHighlighted - { - get { return GetValue(IsTodayHighlightedProperty); } - set { SetValue(IsTodayHighlightedProperty, value); } - } - - /// - /// Gets or sets the currently selected date. - /// - /// - /// The date currently selected. The default is null. - /// - /// - /// The specified date is not in the range defined by - /// - /// and - /// , - /// or the specified date is in the - /// - /// collection. - /// - public DateTime? SelectedDate - { - get { return _selectedDate; } - set { SetAndRaise(SelectedDateProperty, ref _selectedDate, value); } - } - - /// - /// Gets or sets the format that is used to display the selected date. - /// - /// - /// The format that is used to display the selected date. The default is - /// . - /// - /// - /// An specified format is not valid. - /// - public CalendarDatePickerFormat SelectedDateFormat - { - get { return GetValue(SelectedDateFormatProperty); } - set { SetValue(SelectedDateFormatProperty, value); } - } - - public string CustomDateFormatString - { - get { return GetValue(CustomDateFormatStringProperty); } - set { SetValue(CustomDateFormatStringProperty, value); } - } - - /// - /// Gets or sets the text that is displayed by the - /// . - /// - /// - /// The text displayed by the - /// . - /// - /// - /// The text entered cannot be parsed to a valid date, and the exception - /// is not suppressed. - /// - /// - /// The text entered parses to a date that is not selectable. - /// - public string? Text - { - get { return _text; } - set { SetAndRaise(TextProperty, ref _text, value); } - } - - public string? Watermark - { - get { return GetValue(WatermarkProperty); } - set { SetValue(WatermarkProperty, value); } - } - public bool UseFloatingWatermark - { - get { return GetValue(UseFloatingWatermarkProperty); } - set { SetValue(UseFloatingWatermarkProperty, value); } - } - - - /// - /// Gets or sets the horizontal alignment of the content within the control. - /// - public HorizontalAlignment HorizontalContentAlignment - { - get => GetValue(HorizontalContentAlignmentProperty); - set => SetValue(HorizontalContentAlignmentProperty, value); - } - - /// - /// Gets or sets the vertical alignment of the content within the control. - /// - public VerticalAlignment VerticalContentAlignment - { - get => GetValue(VerticalContentAlignmentProperty); - set => SetValue(VerticalContentAlignmentProperty, value); - } + private bool _isFlyoutOpen = false; + private bool _isPressed = false; /// /// Occurs when the drop-down @@ -431,16 +85,10 @@ namespace Avalonia.Controls static CalendarDatePicker() { FocusableProperty.OverrideDefaultValue(true); - - IsDropDownOpenProperty.Changed.AddClassHandler((x,e) => x.OnIsDropDownOpenChanged(e)); - SelectedDateProperty.Changed.AddClassHandler((x,e) => x.OnSelectedDateChanged(e)); - SelectedDateFormatProperty.Changed.AddClassHandler((x,e) => x.OnSelectedDateFormatChanged(e)); - CustomDateFormatStringProperty.Changed.AddClassHandler((x,e) => x.OnCustomDateFormatStringChanged(e)); - TextProperty.Changed.AddClassHandler((x,e) => x.OnTextChanged(e)); } + /// - /// Initializes a new instance of the - /// class. + /// Initializes a new instance of the class. /// public CalendarDatePicker() { @@ -449,6 +97,16 @@ namespace Avalonia.Controls DisplayDate = DateTime.Today; } + /// + /// Updates the visual state of the control by applying latest PseudoClasses. + /// + protected void UpdatePseudoClasses() + { + PseudoClasses.Set(pcFlyoutOpen, _isFlyoutOpen); + PseudoClasses.Set(pcPressed, _isPressed); + } + + /// protected override void OnApplyTemplate(TemplateAppliedEventArgs e) { if (_calendar != null) @@ -505,8 +163,9 @@ namespace Avalonia.Controls if(_dropDownButton != null) { _dropDownButton.Click += DropDownButton_Click; - _buttonPointerPressedSubscription = - _dropDownButton.AddDisposableHandler(PointerPressedEvent, DropDownButton_PointerPressed, handledEventsToo: true); + _buttonPointerPressedSubscription = new CompositeDisposable( + _dropDownButton.AddDisposableHandler(PointerPressedEvent, DropDownButton_PointerPressed, handledEventsToo: true), + _dropDownButton.AddDisposableHandler(PointerReleasedEvent, DropDownButton_PointerReleased, handledEventsToo: true)); } if (_textBox != null) @@ -538,16 +197,200 @@ namespace Avalonia.Controls SetSelectedDate(); } } + + UpdatePseudoClasses(); + } + + /// + protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs change) + { + // CustomDateFormatString + if (change.Property == CustomDateFormatStringProperty) + { + if (SelectedDateFormat == CalendarDatePickerFormat.Custom) + { + OnDateFormatChanged(); + } + } + // IsDropDownOpen + else if (change.Property == IsDropDownOpenProperty) + { + var (oldValue, newValue) = change.GetOldAndNewValue(); + + if (_popUp != null && _popUp.Child != null) + { + if (newValue != oldValue) + { + if (_calendar!.DisplayMode != CalendarMode.Month) + { + _calendar.DisplayMode = CalendarMode.Month; + } + + if (newValue) + { + OpenDropDown(); + } + else + { + _popUp.IsOpen = false; + _isFlyoutOpen = _popUp.IsOpen; + _isPressed = false; + + UpdatePseudoClasses(); + OnCalendarClosed(new RoutedEventArgs()); + } + } + } + } + // SelectedDate + else if (change.Property == SelectedDateProperty) + { + var (removedDate, addedDate) = change.GetOldAndNewValue(); + + if (SelectedDate != null) + { + DateTime day = SelectedDate.Value; + + // When the SelectedDateProperty change is done from + // OnTextPropertyChanged method, two-way binding breaks if + // BeginInvoke is not used: + Threading.Dispatcher.UIThread.InvokeAsync(() => + { + _settingSelectedDate = true; + Text = DateTimeToString(day); + _settingSelectedDate = false; + OnDateSelected(addedDate, removedDate); + }); + + // When DatePickerDisplayDateFlag is TRUE, the SelectedDate + // change is coming from the Calendar UI itself, so, we + // shouldn't change the DisplayDate since it will automatically + // be changed by the Calendar + if ((day.Month != DisplayDate.Month || day.Year != DisplayDate.Year) && (_calendar == null || !_calendar.CalendarDatePickerDisplayDateFlag)) + { + DisplayDate = day; + } + + if(_calendar != null) + { + _calendar.CalendarDatePickerDisplayDateFlag = false; + } + } + else + { + _settingSelectedDate = true; + SetWaterMarkText(); + _settingSelectedDate = false; + OnDateSelected(addedDate, removedDate); + } + } + // SelectedDateFormat + else if (change.Property == SelectedDateFormatProperty) + { + OnDateFormatChanged(); + } + // Text + else if (change.Property == TextProperty) + { + var (oldValue, newValue) = change.GetOldAndNewValue(); + + if (!_suspendTextChangeHandler) + { + if (newValue != null) + { + if (_textBox != null) + { + _textBox.Text = newValue; + } + else + { + _defaultText = newValue; + } + + if (!_settingSelectedDate) + { + SetSelectedDate(); + } + } + else + { + if (!_settingSelectedDate) + { + _settingSelectedDate = true; + SelectedDate = null; + _settingSelectedDate = false; + } + } + } + else + { + SetWaterMarkText(); + } + } + + base.OnPropertyChanged(change); } + /// protected override void UpdateDataValidation(AvaloniaProperty property, BindingValueType state, Exception? error) { if (property == SelectedDateProperty) { DataValidationErrors.SetError(this, error); } + + base.UpdateDataValidation(property, state, error); + } + + /// + protected override void OnPointerPressed(PointerPressedEventArgs e) + { + base.OnPointerPressed(e); + + if (e.GetCurrentPoint(this).Properties.IsLeftButtonPressed) + { + e.Handled = true; + + _ignoreButtonClick = _isPopupClosing; + + _isPressed = true; + UpdatePseudoClasses(); + } } + /// + protected override void OnPointerReleased(PointerReleasedEventArgs e) + { + base.OnPointerReleased(e); + + if (_isPressed && e.InitialPressMouseButton == MouseButton.Left) + { + e.Handled = true; + + if (!_ignoreButtonClick) + { + TogglePopUp(); + } + else + { + _ignoreButtonClick = false; + } + + _isPressed = false; + UpdatePseudoClasses(); + } + } + + /// + protected override void OnPointerCaptureLost(PointerCaptureLostEventArgs e) + { + base.OnPointerCaptureLost(e); + + _isPressed = false; + UpdatePseudoClasses(); + } + + /// protected override void OnPointerWheelChanged(PointerWheelEventArgs e) { base.OnPointerWheelChanged(e); @@ -562,6 +405,8 @@ namespace Avalonia.Controls } } } + + /// protected override void OnGotFocus(GotFocusEventArgs e) { base.OnGotFocus(e); @@ -576,78 +421,57 @@ namespace Avalonia.Controls } } } + + /// protected override void OnLostFocus(RoutedEventArgs e) { base.OnLostFocus(e); + _isPressed = false; + UpdatePseudoClasses(); + SetSelectedDate(); } - - private void OnIsDropDownOpenChanged(AvaloniaPropertyChangedEventArgs e) + + /// + protected override void OnKeyUp(KeyEventArgs e) { - var oldValue = (bool)e.OldValue!; - var value = (bool)e.NewValue!; + var key = e.Key; - if (_popUp != null && _popUp.Child != null) + if ((key == Key.Space || key == Key.Enter) && IsEffectivelyEnabled) // Key.GamepadA is not currently supported + { + // Since the TextBox is used for direct date entry, + // it isn't supported to open the popup/flyout using these keys. + // Other controls open the popup/flyout here. + } + else if (key == Key.Down && e.KeyModifiers.HasAllFlags(KeyModifiers.Alt) && IsEffectivelyEnabled) { - if (value != oldValue) + // It is only possible to open the popup using these keys. + // This is important as the down key is handled by calendar. + // If down also closed the popup, the date would move 1 week + // and then close the popup. This isn't user friendly at all. + // (calendar doesn't mark as handled either). + // The Escape key will still close the popup. + if (IsDropDownOpen == false) { - if (_calendar!.DisplayMode != CalendarMode.Month) - { - _calendar.DisplayMode = CalendarMode.Month; - } + e.Handled = true; - if (value) + if (!_ignoreButtonClick) { - OpenDropDown(); + TogglePopUp(); } else { - _popUp.IsOpen = false; - OnCalendarClosed(new RoutedEventArgs()); + _ignoreButtonClick = false; } - } - } - } - private void OnSelectedDateChanged(AvaloniaPropertyChangedEventArgs e) - { - var addedDate = (DateTime?)e.NewValue; - var removedDate = (DateTime?)e.OldValue; - if (SelectedDate != null) - { - DateTime day = SelectedDate.Value; - - // When the SelectedDateProperty change is done from - // OnTextPropertyChanged method, two-way binding breaks if - // BeginInvoke is not used: - Threading.Dispatcher.UIThread.InvokeAsync(() => - { - _settingSelectedDate = true; - Text = DateTimeToString(day); - _settingSelectedDate = false; - OnDateSelected(addedDate, removedDate); - }); - - // When DatePickerDisplayDateFlag is TRUE, the SelectedDate - // change is coming from the Calendar UI itself, so, we - // shouldn't change the DisplayDate since it will automatically - // be changed by the Calendar - if ((day.Month != DisplayDate.Month || day.Year != DisplayDate.Year) && (_calendar == null || !_calendar.CalendarDatePickerDisplayDateFlag)) - { - DisplayDate = day; + UpdatePseudoClasses(); } - if(_calendar != null) - _calendar.CalendarDatePickerDisplayDateFlag = false; - } - else - { - _settingSelectedDate = true; - SetWaterMarkText(); - _settingSelectedDate = false; - OnDateSelected(addedDate, removedDate); } + + base.OnKeyUp(e); } + private void OnDateFormatChanged() { if (_textBox != null) @@ -672,54 +496,6 @@ namespace Avalonia.Controls } } } - private void OnSelectedDateFormatChanged(AvaloniaPropertyChangedEventArgs e) - { - OnDateFormatChanged(); - } - private void OnCustomDateFormatStringChanged(AvaloniaPropertyChangedEventArgs e) - { - if(SelectedDateFormat == CalendarDatePickerFormat.Custom) - { - OnDateFormatChanged(); - } - } - private void OnTextChanged(AvaloniaPropertyChangedEventArgs e) - { - var oldValue = (string?)e.OldValue; - var value = (string?)e.NewValue; - - if (!_suspendTextChangeHandler) - { - if (value != null) - { - if (_textBox != null) - { - _textBox.Text = value; - } - else - { - _defaultText = value; - } - if (!_settingSelectedDate) - { - SetSelectedDate(); - } - } - else - { - if (!_settingSelectedDate) - { - _settingSelectedDate = true; - SelectedDate = null; - _settingSelectedDate = false; - } - } - } - else - { - SetWaterMarkText(); - } - } /// /// Raises the @@ -735,6 +511,7 @@ namespace Avalonia.Controls { DateValidationError?.Invoke(this, e); } + private void OnDateSelected(DateTime? addedDate, DateTime? removedDate) { EventHandler? handler = this.SelectedDateChanged; @@ -756,10 +533,12 @@ namespace Avalonia.Controls handler(this, new SelectionChangedEventArgs(SelectingItemsControl.SelectionChangedEvent, removedItems, addedItems)); } } + private void OnCalendarClosed(EventArgs e) { CalendarClosed?.Invoke(this, e); } + private void OnCalendarOpened(EventArgs e) { CalendarOpened?.Invoke(this, e); @@ -769,7 +548,8 @@ namespace Avalonia.Controls { Focus(); IsDropDownOpen = false; - } + } + private void Calendar_DisplayDateChanged(object? sender, CalendarDateChangedEventArgs e) { if (e.AddedDate != this.DisplayDate) @@ -777,6 +557,7 @@ namespace Avalonia.Controls SetValue(DisplayDateProperty, (DateTime) e.AddedDate!); } } + private void Calendar_SelectedDatesChanged(object? sender, SelectionChangedEventArgs e) { Debug.Assert(e.AddedItems.Count < 2, "There should be less than 2 AddedItems!"); @@ -802,6 +583,7 @@ namespace Avalonia.Controls } } } + private void Calendar_PointerReleased(object? sender, PointerReleasedEventArgs e) { @@ -810,6 +592,7 @@ namespace Avalonia.Controls e.Handled = true; } } + private void Calendar_KeyDown(object? sender, KeyEventArgs e) { Calendar? c = sender as Calendar ?? throw new ArgumentException("Sender must be Calendar.", nameof(sender)); @@ -825,10 +608,12 @@ namespace Avalonia.Controls } } } + private void TextBox_GotFocus(object? sender, RoutedEventArgs e) { IsDropDownOpen = false; } + private void TextBox_KeyDown(object? sender, KeyEventArgs e) { if (!e.Handled) @@ -836,6 +621,7 @@ namespace Avalonia.Controls e.Handled = ProcessDatePickerKey(e); } } + private void TextBox_TextChanged() { if (_textBox != null) @@ -845,21 +631,33 @@ namespace Avalonia.Controls _suspendTextChangeHandler = false; } } + private void DropDownButton_PointerPressed(object? sender, PointerPressedEventArgs e) { _ignoreButtonClick = _isPopupClosing; + + _isPressed = true; + UpdatePseudoClasses(); + } + + private void DropDownButton_PointerReleased(object? sender, PointerReleasedEventArgs e) + { + _isPressed = false; + UpdatePseudoClasses(); } + private void DropDownButton_Click(object? sender, RoutedEventArgs e) { if (!_ignoreButtonClick) { - HandlePopUp(); + TogglePopUp(); } else { _ignoreButtonClick = false; } } + private void PopUp_Closed(object? sender, EventArgs e) { IsDropDownOpen = false; @@ -871,7 +669,11 @@ namespace Avalonia.Controls } } - private void HandlePopUp() + /// + /// Toggles the property to open/close the calendar popup. + /// This will automatically adjust control focus as well. + /// + private void TogglePopUp() { if (IsDropDownOpen) { @@ -880,24 +682,28 @@ namespace Avalonia.Controls } else { - ProcessTextBox(); + SetSelectedDate(); + IsDropDownOpen = true; + _calendar!.Focus(); } } + private void OpenDropDown() { if (_calendar != null) { _calendar.Focus(); - OpenPopUp(); + + // Open the PopUp + _onOpenSelectedDate = SelectedDate; + _popUp!.IsOpen = true; + _isFlyoutOpen = _popUp!.IsOpen; + + UpdatePseudoClasses(); _calendar.ResetStates(); OnCalendarOpened(new RoutedEventArgs()); } } - private void OpenPopUp() - { - _onOpenSelectedDate = SelectedDate; - _popUp!.IsOpen = true; - } /// /// Input text is parsed in the correct format and changed into a @@ -944,8 +750,10 @@ namespace Avalonia.Controls throw textParseError.Exception; } } + return null; } + private string? DateTimeToString(DateTime d) { DateTimeFormatInfo dtfi = DateTimeHelper.GetCurrentDateFormat(); @@ -959,11 +767,12 @@ namespace Avalonia.Controls case CalendarDatePickerFormat.Custom: return string.Format(CultureInfo.CurrentCulture, d.ToString(CustomDateFormatString, dtfi)); } + return null; } + private bool ProcessDatePickerKey(KeyEventArgs e) { - switch (e.Key) { case Key.Enter: @@ -975,20 +784,16 @@ namespace Avalonia.Controls { if ((e.KeyModifiers & KeyModifiers.Control) == KeyModifiers.Control) { - HandlePopUp(); + TogglePopUp(); return true; } break; } } + return false; } - private void ProcessTextBox() - { - SetSelectedDate(); - IsDropDownOpen = true; - _calendar!.Focus(); - } + private void SetSelectedDate() { if (_textBox != null) @@ -1037,6 +842,7 @@ namespace Avalonia.Controls } } } + private DateTime? SetTextBoxValue(string s) { if (string.IsNullOrEmpty(s)) @@ -1070,6 +876,7 @@ namespace Avalonia.Controls } } } + private void SetWaterMarkText() { if (_textBox != null) @@ -1111,32 +918,10 @@ namespace Avalonia.Controls || value == CalendarDatePickerFormat.Short || value == CalendarDatePickerFormat.Custom; } + private static bool IsValidDateFormatString(string formatString) { return !string.IsNullOrWhiteSpace(formatString); } - private static DateTime DiscardDayTime(DateTime d) - { - int year = d.Year; - int month = d.Month; - DateTime newD = new DateTime(year, month, 1, 0, 0, 0); - return newD; - } - private static DateTime? DiscardTime(DateTime? d) - { - if (d == null) - { - return null; - } - else - { - DateTime discarded = (DateTime) d; - int year = discarded.Year; - int month = discarded.Month; - int day = discarded.Day; - DateTime newD = new DateTime(year, month, day, 0, 0, 0); - return newD; - } - } } } diff --git a/src/Avalonia.Controls/CalendarDatePicker/CalendarDatePickerDateValidationErrorEventArgs.cs b/src/Avalonia.Controls/CalendarDatePicker/CalendarDatePickerDateValidationErrorEventArgs.cs new file mode 100644 index 0000000000..647910cb6b --- /dev/null +++ b/src/Avalonia.Controls/CalendarDatePicker/CalendarDatePickerDateValidationErrorEventArgs.cs @@ -0,0 +1,87 @@ +// (c) Copyright Microsoft Corporation. +// This source is subject to the Microsoft Public License (Ms-PL). +// Please see http://go.microsoft.com/fwlink/?LinkID=131993 for details. +// All other rights reserved. + +using System; + +namespace Avalonia.Controls +{ + /// + /// Provides data for the + /// + /// event. + /// + public class CalendarDatePickerDateValidationErrorEventArgs : EventArgs + { + private bool _throwException; + + /// + /// Initializes a new instance of the + /// + /// class. + /// + /// + /// The initial exception from the + /// + /// event. + /// + /// + /// The text that caused the + /// + /// event. + /// + public CalendarDatePickerDateValidationErrorEventArgs(Exception exception, string text) + { + Text = text; + Exception = exception; + } + + /// + /// Gets the initial exception associated with the + /// + /// event. + /// + /// + /// The exception associated with the validation failure. + /// + public Exception Exception { get; private set; } + + /// + /// Gets the text that caused the + /// + /// event. + /// + /// + /// The text that caused the validation failure. + /// + public string Text { get; private set; } + + /// + /// Gets or sets a value indicating whether + /// + /// should be thrown. + /// + /// + /// True if the exception should be thrown; otherwise, false. + /// + /// + /// If set to true and + /// + /// is null. + /// + public bool ThrowException + { + get => _throwException; + set + { + if (value && Exception == null) + { + throw new ArgumentException("Cannot Throw Null Exception"); + } + + _throwException = value; + } + } + } +} diff --git a/src/Avalonia.Controls/CalendarDatePicker/CalendarDatePickerFormat.cs b/src/Avalonia.Controls/CalendarDatePicker/CalendarDatePickerFormat.cs new file mode 100644 index 0000000000..4d96859d75 --- /dev/null +++ b/src/Avalonia.Controls/CalendarDatePicker/CalendarDatePickerFormat.cs @@ -0,0 +1,31 @@ +// (c) Copyright Microsoft Corporation. +// This source is subject to the Microsoft Public License (Ms-PL). +// Please see http://go.microsoft.com/fwlink/?LinkID=131993 for details. +// All other rights reserved. + +namespace Avalonia.Controls +{ + /// + /// Specifies date formats for a + /// . + /// + public enum CalendarDatePickerFormat + { + /// + /// Specifies that the date should be displayed using unabbreviated days + /// of the week and month names. + /// + Long = 0, + + /// + /// Specifies that the date should be displayed using abbreviated days + /// of the week and month names. + /// + Short = 1, + + /// + /// Specifies that the date should be displayed using a custom format string. + /// + Custom = 2 + } +} diff --git a/src/Avalonia.Controls/Canvas.cs b/src/Avalonia.Controls/Canvas.cs index fabf8978c7..adee7d4d90 100644 --- a/src/Avalonia.Controls/Canvas.cs +++ b/src/Avalonia.Controls/Canvas.cs @@ -1,4 +1,5 @@ using System; +using System.Reactive.Concurrency; using Avalonia.Input; using Avalonia.Layout; @@ -159,47 +160,57 @@ namespace Avalonia.Controls } /// - /// Arranges the control's children. + /// Arranges a single child. /// - /// The size allocated to the control. - /// The space taken. - protected override Size ArrangeOverride(Size finalSize) + /// The child to arrange. + /// The size allocated to the canvas. + protected virtual void ArrangeChild(Control child, Size finalSize) { - foreach (Control child in Children) - { - double x = 0.0; - double y = 0.0; - double elementLeft = GetLeft(child); + double x = 0.0; + double y = 0.0; + double elementLeft = GetLeft(child); - if (!double.IsNaN(elementLeft)) - { - x = elementLeft; - } - else + if (!double.IsNaN(elementLeft)) + { + x = elementLeft; + } + else + { + // Arrange with right. + double elementRight = GetRight(child); + if (!double.IsNaN(elementRight)) { - // Arrange with right. - double elementRight = GetRight(child); - if (!double.IsNaN(elementRight)) - { - x = finalSize.Width - child.DesiredSize.Width - elementRight; - } + x = finalSize.Width - child.DesiredSize.Width - elementRight; } + } - double elementTop = GetTop(child); - if (!double.IsNaN(elementTop) ) - { - y = elementTop; - } - else + double elementTop = GetTop(child); + if (!double.IsNaN(elementTop)) + { + y = elementTop; + } + else + { + double elementBottom = GetBottom(child); + if (!double.IsNaN(elementBottom)) { - double elementBottom = GetBottom(child); - if (!double.IsNaN(elementBottom)) - { - y = finalSize.Height - child.DesiredSize.Height - elementBottom; - } + y = finalSize.Height - child.DesiredSize.Height - elementBottom; } + } - child.Arrange(new Rect(new Point(x, y), child.DesiredSize)); + child.Arrange(new Rect(new Point(x, y), child.DesiredSize)); + } + + /// + /// Arranges the control's children. + /// + /// The size allocated to the control. + /// The space taken. + protected override Size ArrangeOverride(Size finalSize) + { + foreach (Control child in Children) + { + ArrangeChild(child, finalSize); } return finalSize; diff --git a/src/Avalonia.Controls/ContextMenu.cs b/src/Avalonia.Controls/ContextMenu.cs index ee2378101a..2b122d4174 100644 --- a/src/Avalonia.Controls/ContextMenu.cs +++ b/src/Avalonia.Controls/ContextMenu.cs @@ -63,7 +63,7 @@ namespace Avalonia.Controls /// Defines the property. /// public static readonly StyledProperty PlacementRectProperty = - AvaloniaProperty.Register(nameof(PlacementRect)); + Popup.PlacementRectProperty.AddOwner(); /// /// Defines the property. diff --git a/src/Avalonia.Controls/DateTimePickers/TimePicker.cs b/src/Avalonia.Controls/DateTimePickers/TimePicker.cs index f04c79505e..047667567d 100644 --- a/src/Avalonia.Controls/DateTimePickers/TimePicker.cs +++ b/src/Avalonia.Controls/DateTimePickers/TimePicker.cs @@ -38,13 +38,13 @@ namespace Avalonia.Controls /// Defines the property /// public static readonly StyledProperty HeaderProperty = - AvaloniaProperty.Register(nameof(Header)); + AvaloniaProperty.Register(nameof(Header)); /// /// Defines the property /// public static readonly StyledProperty HeaderTemplateProperty = - AvaloniaProperty.Register(nameof(HeaderTemplate)); + AvaloniaProperty.Register(nameof(HeaderTemplate)); /// /// Defines the property diff --git a/src/Avalonia.Controls/Diagnostics/IPopupHostProvider.cs b/src/Avalonia.Controls/Diagnostics/IPopupHostProvider.cs index 45cd1d727e..64978248e5 100644 --- a/src/Avalonia.Controls/Diagnostics/IPopupHostProvider.cs +++ b/src/Avalonia.Controls/Diagnostics/IPopupHostProvider.cs @@ -1,11 +1,13 @@ using System; using Avalonia.Controls.Primitives; +using Avalonia.Metadata; namespace Avalonia.Controls.Diagnostics { /// /// Diagnostics interface to retrieve an associated . /// + [NotClientImplementable] public interface IPopupHostProvider { /// diff --git a/src/Avalonia.Controls/DockPanel.cs b/src/Avalonia.Controls/DockPanel.cs index 8e23555c2d..3e3ed509b5 100644 --- a/src/Avalonia.Controls/DockPanel.cs +++ b/src/Avalonia.Controls/DockPanel.cs @@ -34,7 +34,7 @@ namespace Avalonia.Controls /// public static readonly StyledProperty LastChildFillProperty = AvaloniaProperty.Register( - nameof(LastChildFillProperty), + nameof(LastChildFill), defaultValue: true); /// diff --git a/src/Avalonia.Controls/Embedding/Offscreen/OffscreenTopLevelImpl.cs b/src/Avalonia.Controls/Embedding/Offscreen/OffscreenTopLevelImpl.cs index 08d559a5c1..1c7bdb9b37 100644 --- a/src/Avalonia.Controls/Embedding/Offscreen/OffscreenTopLevelImpl.cs +++ b/src/Avalonia.Controls/Embedding/Offscreen/OffscreenTopLevelImpl.cs @@ -2,11 +2,13 @@ using System.Collections.Generic; using Avalonia.Input; using Avalonia.Input.Raw; +using Avalonia.Metadata; using Avalonia.Platform; using Avalonia.Rendering; namespace Avalonia.Controls.Embedding.Offscreen { + [Unstable] public abstract class OffscreenTopLevelImplBase : ITopLevelImpl { private double _scaling = 1; diff --git a/src/Avalonia.Controls/IContentControl.cs b/src/Avalonia.Controls/IContentControl.cs index d28b0afb25..b4d8d0f574 100644 --- a/src/Avalonia.Controls/IContentControl.cs +++ b/src/Avalonia.Controls/IContentControl.cs @@ -1,5 +1,6 @@ using Avalonia.Controls.Templates; using Avalonia.Layout; +using Avalonia.Metadata; namespace Avalonia.Controls { @@ -7,6 +8,7 @@ namespace Avalonia.Controls /// Defines a control that displays according to a /// . /// + [NotClientImplementable] public interface IContentControl : IControl { /// diff --git a/src/Avalonia.Controls/IControl.cs b/src/Avalonia.Controls/IControl.cs index b501bc15a7..3395fc1059 100644 --- a/src/Avalonia.Controls/IControl.cs +++ b/src/Avalonia.Controls/IControl.cs @@ -1,6 +1,7 @@ using Avalonia.Controls.Templates; using Avalonia.Input; using Avalonia.Layout; +using Avalonia.Metadata; using Avalonia.VisualTree; namespace Avalonia.Controls @@ -8,6 +9,7 @@ namespace Avalonia.Controls /// /// Interface for Avalonia controls. /// + [NotClientImplementable] public interface IControl : IVisual, IDataTemplateHost, ILayoutable, diff --git a/src/Avalonia.Controls/IGlobalDataTemplates.cs b/src/Avalonia.Controls/IGlobalDataTemplates.cs index 92dcd2c189..f4499ddb5e 100644 --- a/src/Avalonia.Controls/IGlobalDataTemplates.cs +++ b/src/Avalonia.Controls/IGlobalDataTemplates.cs @@ -1,10 +1,12 @@ using Avalonia.Controls.Templates; +using Avalonia.Metadata; namespace Avalonia.Controls { /// /// Defines the application-global data templates. /// + [NotClientImplementable] public interface IGlobalDataTemplates : IDataTemplateHost { } diff --git a/src/Avalonia.Controls/IMenu.cs b/src/Avalonia.Controls/IMenu.cs index 0722a22f08..d90c5ea7a8 100644 --- a/src/Avalonia.Controls/IMenu.cs +++ b/src/Avalonia.Controls/IMenu.cs @@ -1,10 +1,12 @@ using Avalonia.Controls.Platform; +using Avalonia.Metadata; namespace Avalonia.Controls { /// /// Represents a or . /// + [NotClientImplementable] public interface IMenu : IMenuElement { /// diff --git a/src/Avalonia.Controls/IMenuElement.cs b/src/Avalonia.Controls/IMenuElement.cs index a3200d2b1b..c13c20b639 100644 --- a/src/Avalonia.Controls/IMenuElement.cs +++ b/src/Avalonia.Controls/IMenuElement.cs @@ -1,11 +1,13 @@ using System.Collections.Generic; using Avalonia.Input; +using Avalonia.Metadata; namespace Avalonia.Controls { /// /// Represents an or . /// + [NotClientImplementable] public interface IMenuElement : IControl { /// diff --git a/src/Avalonia.Controls/IMenuItem.cs b/src/Avalonia.Controls/IMenuItem.cs index 35e36eb0f4..9d7ef3c18d 100644 --- a/src/Avalonia.Controls/IMenuItem.cs +++ b/src/Avalonia.Controls/IMenuItem.cs @@ -1,8 +1,11 @@ -namespace Avalonia.Controls +using Avalonia.Metadata; + +namespace Avalonia.Controls { /// /// Represents a . /// + [NotClientImplementable] public interface IMenuItem : IMenuElement { /// diff --git a/src/Avalonia.Controls/INativeMenuExporterEventsImplBridge.cs b/src/Avalonia.Controls/INativeMenuExporterEventsImplBridge.cs index f492e6ca0f..29963e4821 100644 --- a/src/Avalonia.Controls/INativeMenuExporterEventsImplBridge.cs +++ b/src/Avalonia.Controls/INativeMenuExporterEventsImplBridge.cs @@ -1,5 +1,8 @@ +using Avalonia.Metadata; + namespace Avalonia.Controls { + [Unstable] public interface INativeMenuExporterEventsImplBridge { void RaiseNeedsUpdate (); diff --git a/src/Avalonia.Controls/INativeMenuItemExporterEventsImplBridge.cs b/src/Avalonia.Controls/INativeMenuItemExporterEventsImplBridge.cs index 6cb68d8ddd..a6c7489971 100644 --- a/src/Avalonia.Controls/INativeMenuItemExporterEventsImplBridge.cs +++ b/src/Avalonia.Controls/INativeMenuItemExporterEventsImplBridge.cs @@ -1,5 +1,8 @@ +using Avalonia.Metadata; + namespace Avalonia.Controls { + [Unstable] public interface INativeMenuItemExporterEventsImplBridge { void RaiseClicked (); diff --git a/src/Avalonia.Controls/IPanel.cs b/src/Avalonia.Controls/IPanel.cs index 7b9e2c2074..8f2564ec74 100644 --- a/src/Avalonia.Controls/IPanel.cs +++ b/src/Avalonia.Controls/IPanel.cs @@ -1,8 +1,11 @@ +using Avalonia.Metadata; + namespace Avalonia.Controls { /// /// Interface for controls that can contain multiple children. /// + [NotClientImplementable] public interface IPanel : IControl { /// @@ -10,4 +13,4 @@ namespace Avalonia.Controls /// Controls Children { get; } } -} \ No newline at end of file +} diff --git a/src/Avalonia.Controls/IScrollable.cs b/src/Avalonia.Controls/IScrollable.cs index 2a98b3910a..680088290c 100644 --- a/src/Avalonia.Controls/IScrollable.cs +++ b/src/Avalonia.Controls/IScrollable.cs @@ -1,4 +1,3 @@ - namespace Avalonia.Controls.Primitives { /// diff --git a/src/Avalonia.Controls/MaskedTextBox.cs b/src/Avalonia.Controls/MaskedTextBox.cs index 933788f9ea..080326606e 100644 --- a/src/Avalonia.Controls/MaskedTextBox.cs +++ b/src/Avalonia.Controls/MaskedTextBox.cs @@ -32,7 +32,7 @@ namespace Avalonia.Controls AvaloniaProperty.Register(nameof(Mask), string.Empty); public static new readonly StyledProperty PasswordCharProperty = - AvaloniaProperty.Register(nameof(PasswordChar), '\0'); + AvaloniaProperty.Register(nameof(PasswordChar), '\0'); public static readonly StyledProperty PromptCharProperty = AvaloniaProperty.Register(nameof(PromptChar), '_'); diff --git a/src/Avalonia.Controls/MenuBase.cs b/src/Avalonia.Controls/MenuBase.cs index bdcff5cd09..122d45d033 100644 --- a/src/Avalonia.Controls/MenuBase.cs +++ b/src/Avalonia.Controls/MenuBase.cs @@ -18,8 +18,8 @@ namespace Avalonia.Controls /// /// Defines the property. /// - public static readonly DirectProperty IsOpenProperty = - AvaloniaProperty.RegisterDirect( + public static readonly DirectProperty IsOpenProperty = + AvaloniaProperty.RegisterDirect( nameof(IsOpen), o => o.IsOpen); diff --git a/src/Avalonia.Controls/MenuItem.cs b/src/Avalonia.Controls/MenuItem.cs index 619eafb71b..58229a1772 100644 --- a/src/Avalonia.Controls/MenuItem.cs +++ b/src/Avalonia.Controls/MenuItem.cs @@ -85,13 +85,13 @@ namespace Avalonia.Controls /// Defines the event. /// public static readonly RoutedEvent PointerEnterItemEvent = - RoutedEvent.Register(nameof(PointerEnterItem), RoutingStrategies.Bubble); + RoutedEvent.Register(nameof(PointerEnterItem), RoutingStrategies.Bubble); /// /// Defines the event. /// public static readonly RoutedEvent PointerLeaveItemEvent = - RoutedEvent.Register(nameof(PointerLeaveItem), RoutingStrategies.Bubble); + RoutedEvent.Register(nameof(PointerLeaveItem), RoutingStrategies.Bubble); /// /// Defines the event. diff --git a/src/Avalonia.Controls/NativeMenuItemBase.cs b/src/Avalonia.Controls/NativeMenuItemBase.cs index a5e3308e3b..4946d16f01 100644 --- a/src/Avalonia.Controls/NativeMenuItemBase.cs +++ b/src/Avalonia.Controls/NativeMenuItemBase.cs @@ -11,8 +11,8 @@ namespace Avalonia.Controls } - public static readonly DirectProperty ParentProperty = - AvaloniaProperty.RegisterDirect("Parent", o => o.Parent, (o, v) => o.Parent = v); + public static readonly DirectProperty ParentProperty = + AvaloniaProperty.RegisterDirect("Parent", o => o.Parent, (o, v) => o.Parent = v); public NativeMenu? Parent { diff --git a/src/Avalonia.Controls/Notifications/IManagedNotificationManager.cs b/src/Avalonia.Controls/Notifications/IManagedNotificationManager.cs index 977544674d..b2e6e9e80b 100644 --- a/src/Avalonia.Controls/Notifications/IManagedNotificationManager.cs +++ b/src/Avalonia.Controls/Notifications/IManagedNotificationManager.cs @@ -1,4 +1,6 @@ -namespace Avalonia.Controls.Notifications +using Avalonia.Metadata; + +namespace Avalonia.Controls.Notifications { /// /// Represents a notification manager that can show arbitrary content. @@ -9,6 +11,7 @@ /// can display arbitrary content, as opposed to notification managers which display notifications /// using the host operating system's notification mechanism. /// + [NotClientImplementable] public interface IManagedNotificationManager : INotificationManager { /// diff --git a/src/Avalonia.Controls/Notifications/INotification.cs b/src/Avalonia.Controls/Notifications/INotification.cs index fa08233097..9ccce5b2c4 100644 --- a/src/Avalonia.Controls/Notifications/INotification.cs +++ b/src/Avalonia.Controls/Notifications/INotification.cs @@ -1,10 +1,12 @@ using System; +using Avalonia.Metadata; namespace Avalonia.Controls.Notifications { /// /// Represents a notification that can be shown in a window or by the host operating system. /// + [NotClientImplementable] public interface INotification { /// diff --git a/src/Avalonia.Controls/Notifications/INotificationManager.cs b/src/Avalonia.Controls/Notifications/INotificationManager.cs index 72fb8e6c08..5fa479f2c3 100644 --- a/src/Avalonia.Controls/Notifications/INotificationManager.cs +++ b/src/Avalonia.Controls/Notifications/INotificationManager.cs @@ -1,9 +1,12 @@ -namespace Avalonia.Controls.Notifications +using Avalonia.Metadata; + +namespace Avalonia.Controls.Notifications { /// /// Represents a notification manager that can be used to show notifications in a window or using /// the host operating system. /// + [NotClientImplementable] public interface INotificationManager { /// diff --git a/src/Avalonia.Controls/Platform/DefaultMenuInteractionHandler.cs b/src/Avalonia.Controls/Platform/DefaultMenuInteractionHandler.cs index 2f9bf0ac06..3a6d06f150 100644 --- a/src/Avalonia.Controls/Platform/DefaultMenuInteractionHandler.cs +++ b/src/Avalonia.Controls/Platform/DefaultMenuInteractionHandler.cs @@ -4,6 +4,7 @@ using Avalonia.Input; using Avalonia.Input.Raw; using Avalonia.Interactivity; using Avalonia.LogicalTree; +using Avalonia.Metadata; using Avalonia.Platform; using Avalonia.Rendering; using Avalonia.Threading; @@ -14,6 +15,7 @@ namespace Avalonia.Controls.Platform /// /// Provides the default keyboard and pointer interaction for menus. /// + [Unstable] public class DefaultMenuInteractionHandler : IMenuInteractionHandler { private readonly bool _isContextMenu; @@ -54,6 +56,7 @@ namespace Avalonia.Controls.Platform Menu.AddHandler(Avalonia.Controls.Menu.MenuOpenedEvent, this.MenuOpened); Menu.AddHandler(MenuItem.PointerEnterItemEvent, PointerEnter); Menu.AddHandler(MenuItem.PointerLeaveItemEvent, PointerLeave); + Menu.AddHandler(InputElement.PointerMovedEvent, PointerMoved); _root = Menu.VisualRoot; @@ -89,6 +92,7 @@ namespace Avalonia.Controls.Platform Menu.RemoveHandler(Avalonia.Controls.Menu.MenuOpenedEvent, this.MenuOpened); Menu.RemoveHandler(MenuItem.PointerEnterItemEvent, PointerEnter); Menu.RemoveHandler(MenuItem.PointerLeaveItemEvent, PointerLeave); + Menu.RemoveHandler(InputElement.PointerMovedEvent, PointerMoved); if (_root is InputElement inputRoot) { @@ -338,6 +342,22 @@ namespace Avalonia.Controls.Platform } } + protected internal virtual void PointerMoved(object? sender, PointerEventArgs e) + { + // HACK: #8179 needs to be addressed to correctly implement it in the PointerPressed method. + var item = GetMenuItem(e.Source as IControl) as MenuItem; + if (item?.TransformedBounds == null) + { + return; + } + var point = e.GetCurrentPoint(null); + + if (point.Properties.IsLeftButtonPressed && item.TransformedBounds.Value.Contains(point.Position) == false) + { + e.Pointer.Capture(null); + } + } + protected internal virtual void PointerLeave(object? sender, PointerEventArgs e) { var item = GetMenuItem(e.Source as IControl); diff --git a/src/Avalonia.Controls/Platform/IApplicationPlatformEvents.cs b/src/Avalonia.Controls/Platform/IApplicationPlatformEvents.cs index a8d3a3b3ac..99bbb8b56d 100644 --- a/src/Avalonia.Controls/Platform/IApplicationPlatformEvents.cs +++ b/src/Avalonia.Controls/Platform/IApplicationPlatformEvents.cs @@ -1,5 +1,8 @@ +using Avalonia.Metadata; + namespace Avalonia.Platform { + [Unstable] public interface IApplicationPlatformEvents { void RaiseUrlsOpened(string[] urls); diff --git a/src/Avalonia.Controls/Platform/IMenuInteractionHandler.cs b/src/Avalonia.Controls/Platform/IMenuInteractionHandler.cs index dd8503f768..47b5a048b0 100644 --- a/src/Avalonia.Controls/Platform/IMenuInteractionHandler.cs +++ b/src/Avalonia.Controls/Platform/IMenuInteractionHandler.cs @@ -1,8 +1,11 @@ -namespace Avalonia.Controls.Platform +using Avalonia.Metadata; + +namespace Avalonia.Controls.Platform { /// /// Handles user interaction for menus. /// + [Unstable] public interface IMenuInteractionHandler { /// diff --git a/src/Avalonia.Controls/Platform/IMountedVolumeInfoProvider.cs b/src/Avalonia.Controls/Platform/IMountedVolumeInfoProvider.cs index 6e10163175..daeb9076e6 100644 --- a/src/Avalonia.Controls/Platform/IMountedVolumeInfoProvider.cs +++ b/src/Avalonia.Controls/Platform/IMountedVolumeInfoProvider.cs @@ -1,13 +1,13 @@ using System; using System.Collections.ObjectModel; -using System.Threading.Tasks; -using Avalonia.Platform; +using Avalonia.Metadata; namespace Avalonia.Controls.Platform { /// /// Defines a platform-specific mount volumes info provider implementation. /// + [Unstable] public interface IMountedVolumeInfoProvider { /// diff --git a/src/Avalonia.Controls/Platform/INativeControlHostImpl.cs b/src/Avalonia.Controls/Platform/INativeControlHostImpl.cs index df13613848..ffa79aa8d6 100644 --- a/src/Avalonia.Controls/Platform/INativeControlHostImpl.cs +++ b/src/Avalonia.Controls/Platform/INativeControlHostImpl.cs @@ -1,10 +1,11 @@ using System; using System.Diagnostics.CodeAnalysis; +using Avalonia.Metadata; using Avalonia.Platform; -using Avalonia.VisualTree; namespace Avalonia.Controls.Platform { + [Unstable] public interface INativeControlHostImpl { INativeControlHostDestroyableControlHandle CreateDefaultChild(IPlatformHandle parent); @@ -13,11 +14,13 @@ namespace Avalonia.Controls.Platform bool IsCompatibleWith(IPlatformHandle handle); } + [Unstable] public interface INativeControlHostDestroyableControlHandle : IPlatformHandle { void Destroy(); } + [Unstable] public interface INativeControlHostControlTopLevelAttachment : IDisposable { INativeControlHostImpl? AttachedTo { get; set; } @@ -27,6 +30,7 @@ namespace Avalonia.Controls.Platform void ShowInBounds(Rect rect); } + [Unstable] public interface ITopLevelImplWithNativeControlHost { INativeControlHostImpl? NativeControlHost { get; } diff --git a/src/Avalonia.Controls/Platform/IPlatformIconLoader.cs b/src/Avalonia.Controls/Platform/IPlatformIconLoader.cs index ecbc6d2234..4c844ce30f 100644 --- a/src/Avalonia.Controls/Platform/IPlatformIconLoader.cs +++ b/src/Avalonia.Controls/Platform/IPlatformIconLoader.cs @@ -1,7 +1,9 @@ using System.IO; +using Avalonia.Metadata; namespace Avalonia.Platform { + [Unstable] public interface IPlatformIconLoader { IWindowIconImpl LoadIcon(string fileName); diff --git a/src/Avalonia.Controls/Platform/IPlatformLifetimeEventsImpl.cs b/src/Avalonia.Controls/Platform/IPlatformLifetimeEventsImpl.cs index 4cd6640453..0658f9211c 100644 --- a/src/Avalonia.Controls/Platform/IPlatformLifetimeEventsImpl.cs +++ b/src/Avalonia.Controls/Platform/IPlatformLifetimeEventsImpl.cs @@ -1,9 +1,10 @@ using System; -using System.ComponentModel; using Avalonia.Controls.ApplicationLifetimes; +using Avalonia.Metadata; namespace Avalonia.Platform { + [Unstable] public interface IPlatformLifetimeEventsImpl { /// diff --git a/src/Avalonia.Controls/Platform/IPlatformNativeSurfaceHandle.cs b/src/Avalonia.Controls/Platform/IPlatformNativeSurfaceHandle.cs index 264f5e4667..6ad07b1b13 100644 --- a/src/Avalonia.Controls/Platform/IPlatformNativeSurfaceHandle.cs +++ b/src/Avalonia.Controls/Platform/IPlatformNativeSurfaceHandle.cs @@ -1,7 +1,9 @@ using System; +using Avalonia.Metadata; namespace Avalonia.Platform { + [Unstable] public interface IPlatformNativeSurfaceHandle : IPlatformHandle { PixelSize Size { get; } diff --git a/src/Avalonia.Controls/Platform/IPopupImpl.cs b/src/Avalonia.Controls/Platform/IPopupImpl.cs index 477d5fab43..cd86045dee 100644 --- a/src/Avalonia.Controls/Platform/IPopupImpl.cs +++ b/src/Avalonia.Controls/Platform/IPopupImpl.cs @@ -1,10 +1,12 @@ using Avalonia.Controls.Primitives.PopupPositioning; +using Avalonia.Metadata; namespace Avalonia.Platform { /// /// Defines a platform-specific popup window implementation. /// + [Unstable] public interface IPopupImpl : IWindowBaseImpl { IPopupPositioner PopupPositioner { get; } diff --git a/src/Avalonia.Controls/Platform/IScreenImpl.cs b/src/Avalonia.Controls/Platform/IScreenImpl.cs index b68391aa52..fcae3b6493 100644 --- a/src/Avalonia.Controls/Platform/IScreenImpl.cs +++ b/src/Avalonia.Controls/Platform/IScreenImpl.cs @@ -1,9 +1,9 @@ using System.Collections.Generic; - -#nullable enable +using Avalonia.Metadata; namespace Avalonia.Platform { + [Unstable] public interface IScreenImpl { int ScreenCount { get; } diff --git a/src/Avalonia.Controls/Platform/ISystemDialogImpl.cs b/src/Avalonia.Controls/Platform/ISystemDialogImpl.cs index 1685a6a38c..715eda5cfa 100644 --- a/src/Avalonia.Controls/Platform/ISystemDialogImpl.cs +++ b/src/Avalonia.Controls/Platform/ISystemDialogImpl.cs @@ -1,10 +1,12 @@ using System.Threading.Tasks; +using Avalonia.Metadata; namespace Avalonia.Controls.Platform { /// /// Defines a platform-specific system dialog implementation. /// + [Unstable] public interface ISystemDialogImpl { /// diff --git a/src/Avalonia.Controls/Platform/ITopLevelImpl.cs b/src/Avalonia.Controls/Platform/ITopLevelImpl.cs index 80434882f7..bd0339f525 100644 --- a/src/Avalonia.Controls/Platform/ITopLevelImpl.cs +++ b/src/Avalonia.Controls/Platform/ITopLevelImpl.cs @@ -4,6 +4,7 @@ using Avalonia.Controls; using Avalonia.Input; using Avalonia.Input.Raw; using Avalonia.Layout; +using Avalonia.Metadata; using Avalonia.Rendering; using JetBrains.Annotations; @@ -50,6 +51,7 @@ namespace Avalonia.Platform /// This interface is the common interface to and /// . /// + [Unstable] public interface ITopLevelImpl : IDisposable { /// diff --git a/src/Avalonia.Controls/Platform/ITopLevelImplWithTextInputMethod.cs b/src/Avalonia.Controls/Platform/ITopLevelImplWithTextInputMethod.cs index bafb973765..a2e426ca08 100644 --- a/src/Avalonia.Controls/Platform/ITopLevelImplWithTextInputMethod.cs +++ b/src/Avalonia.Controls/Platform/ITopLevelImplWithTextInputMethod.cs @@ -1,9 +1,11 @@ using Avalonia.Input; using Avalonia.Input.TextInput; +using Avalonia.Metadata; using Avalonia.Platform; namespace Avalonia.Controls.Platform { + [Unstable] public interface ITopLevelImplWithTextInputMethod : ITopLevelImpl { public ITextInputMethodImpl? TextInputMethod { get; } diff --git a/src/Avalonia.Controls/Platform/ITopLevelNativeMenuExporter.cs b/src/Avalonia.Controls/Platform/ITopLevelNativeMenuExporter.cs index 9e72a40439..149a978c54 100644 --- a/src/Avalonia.Controls/Platform/ITopLevelNativeMenuExporter.cs +++ b/src/Avalonia.Controls/Platform/ITopLevelNativeMenuExporter.cs @@ -1,13 +1,16 @@ using System; +using Avalonia.Metadata; using Avalonia.Platform; namespace Avalonia.Controls.Platform { + [Unstable] public interface INativeMenuExporter { void SetNativeMenu(NativeMenu? menu); } + [Unstable] public interface ITopLevelNativeMenuExporter : INativeMenuExporter { bool IsNativeMenuExported { get; } @@ -15,11 +18,13 @@ namespace Avalonia.Controls.Platform event EventHandler OnIsNativeMenuExportedChanged; } + [Unstable] public interface INativeMenuExporterProvider { INativeMenuExporter? NativeMenuExporter { get; } } - + + [Unstable] public interface ITopLevelImplWithNativeMenuExporter : ITopLevelImpl { ITopLevelNativeMenuExporter? NativeMenuExporter { get; } diff --git a/src/Avalonia.Controls/Platform/ITrayIconImpl.cs b/src/Avalonia.Controls/Platform/ITrayIconImpl.cs index 289b569645..4ef9397d04 100644 --- a/src/Avalonia.Controls/Platform/ITrayIconImpl.cs +++ b/src/Avalonia.Controls/Platform/ITrayIconImpl.cs @@ -1,8 +1,10 @@ using System; using Avalonia.Controls.Platform; +using Avalonia.Metadata; namespace Avalonia.Platform { + [Unstable] public interface ITrayIconImpl : IDisposable { /// diff --git a/src/Avalonia.Controls/Platform/IWindowBaseImpl.cs b/src/Avalonia.Controls/Platform/IWindowBaseImpl.cs index 066f4579c0..512fad6dfc 100644 --- a/src/Avalonia.Controls/Platform/IWindowBaseImpl.cs +++ b/src/Avalonia.Controls/Platform/IWindowBaseImpl.cs @@ -1,8 +1,10 @@ using System; using Avalonia.Automation.Peers; +using Avalonia.Metadata; namespace Avalonia.Platform { + [Unstable] public interface IWindowBaseImpl : ITopLevelImpl { /// diff --git a/src/Avalonia.Controls/Platform/IWindowIconImpl.cs b/src/Avalonia.Controls/Platform/IWindowIconImpl.cs index 7086b7651c..4bb8844d97 100644 --- a/src/Avalonia.Controls/Platform/IWindowIconImpl.cs +++ b/src/Avalonia.Controls/Platform/IWindowIconImpl.cs @@ -1,7 +1,9 @@ using System.IO; +using Avalonia.Metadata; namespace Avalonia.Platform { + [Unstable] public interface IWindowIconImpl { void Save(Stream outputStream); diff --git a/src/Avalonia.Controls/Platform/IWindowImpl.cs b/src/Avalonia.Controls/Platform/IWindowImpl.cs index d4be4f9f45..af9392d440 100644 --- a/src/Avalonia.Controls/Platform/IWindowImpl.cs +++ b/src/Avalonia.Controls/Platform/IWindowImpl.cs @@ -1,12 +1,14 @@ using System; using Avalonia.Controls; using Avalonia.Input; +using Avalonia.Metadata; namespace Avalonia.Platform { /// /// Defines a platform-specific window implementation. /// + [Unstable] public interface IWindowImpl : IWindowBaseImpl { /// diff --git a/src/Avalonia.Controls/Platform/IWindowingPlatform.cs b/src/Avalonia.Controls/Platform/IWindowingPlatform.cs index fa26fe8fdd..5acc5adccd 100644 --- a/src/Avalonia.Controls/Platform/IWindowingPlatform.cs +++ b/src/Avalonia.Controls/Platform/IWindowingPlatform.cs @@ -1,5 +1,8 @@ +using Avalonia.Metadata; + namespace Avalonia.Platform { + [Unstable] public interface IWindowingPlatform { IWindowImpl CreateWindow(); diff --git a/src/Avalonia.Controls/Platform/InternalPlatformThreadingInterface.cs b/src/Avalonia.Controls/Platform/InternalPlatformThreadingInterface.cs index 4e5908456e..630d2d8efb 100644 --- a/src/Avalonia.Controls/Platform/InternalPlatformThreadingInterface.cs +++ b/src/Avalonia.Controls/Platform/InternalPlatformThreadingInterface.cs @@ -1,14 +1,13 @@ using System; -using System.Collections.Generic; using System.Runtime.InteropServices; using System.Threading; -using System.Threading.Tasks; +using Avalonia.Metadata; using Avalonia.Platform; -using Avalonia.Rendering; using Avalonia.Threading; namespace Avalonia.Controls.Platform { + [Unstable] public class InternalPlatformThreadingInterface : IPlatformThreadingInterface { public InternalPlatformThreadingInterface() diff --git a/src/Avalonia.Controls/Platform/MountedDriveInfo.cs b/src/Avalonia.Controls/Platform/MountedDriveInfo.cs index f3104e4360..620ac9303f 100644 --- a/src/Avalonia.Controls/Platform/MountedDriveInfo.cs +++ b/src/Avalonia.Controls/Platform/MountedDriveInfo.cs @@ -1,10 +1,12 @@ using System; +using Avalonia.Metadata; namespace Avalonia.Controls.Platform { /// /// Describes a Drive's properties. /// + [Unstable] public class MountedVolumeInfo : IEquatable { public string? VolumeLabel { get; set; } diff --git a/src/Avalonia.Controls/Platform/PlatformManager.cs b/src/Avalonia.Controls/Platform/PlatformManager.cs index ee62316922..92f6f1cb52 100644 --- a/src/Avalonia.Controls/Platform/PlatformManager.cs +++ b/src/Avalonia.Controls/Platform/PlatformManager.cs @@ -1,9 +1,11 @@ using System; using System.Reactive.Disposables; +using Avalonia.Metadata; using Avalonia.Platform; namespace Avalonia.Controls.Platform { + [Unstable] public static partial class PlatformManager { static bool s_designerMode; diff --git a/src/Avalonia.Controls/Platform/Surfaces/IFramebufferPlatformSurface.cs b/src/Avalonia.Controls/Platform/Surfaces/IFramebufferPlatformSurface.cs index 62cd012d51..0a7daeaa24 100644 --- a/src/Avalonia.Controls/Platform/Surfaces/IFramebufferPlatformSurface.cs +++ b/src/Avalonia.Controls/Platform/Surfaces/IFramebufferPlatformSurface.cs @@ -1,7 +1,9 @@ -using Avalonia.Platform; +using Avalonia.Metadata; +using Avalonia.Platform; namespace Avalonia.Controls.Platform.Surfaces { + [Unstable] public interface IFramebufferPlatformSurface { /// diff --git a/src/Avalonia.Controls/Presenters/ContentPresenter.cs b/src/Avalonia.Controls/Presenters/ContentPresenter.cs index 996cb29534..12a8dd747d 100644 --- a/src/Avalonia.Controls/Presenters/ContentPresenter.cs +++ b/src/Avalonia.Controls/Presenters/ContentPresenter.cs @@ -52,67 +52,67 @@ namespace Avalonia.Controls.Presenters /// /// Defines the property. /// - public static readonly AttachedProperty ForegroundProperty = + public static readonly StyledProperty ForegroundProperty = TextElement.ForegroundProperty.AddOwner(); /// /// Defines the property. /// - public static readonly AttachedProperty FontFamilyProperty = + public static readonly StyledProperty FontFamilyProperty = TextElement.FontFamilyProperty.AddOwner(); /// /// Defines the property. /// - public static readonly AttachedProperty FontSizeProperty = + public static readonly StyledProperty FontSizeProperty = TextElement.FontSizeProperty.AddOwner(); /// /// Defines the property. /// - public static readonly AttachedProperty FontStyleProperty = + public static readonly StyledProperty FontStyleProperty = TextElement.FontStyleProperty.AddOwner(); /// /// Defines the property. /// - public static readonly AttachedProperty FontWeightProperty = + public static readonly StyledProperty FontWeightProperty = TextElement.FontWeightProperty.AddOwner(); /// /// Defines the property. /// - public static readonly AttachedProperty FontStretchProperty = + public static readonly StyledProperty FontStretchProperty = TextElement.FontStretchProperty.AddOwner(); /// /// Defines the property /// - public static readonly AttachedProperty TextAlignmentProperty = + public static readonly StyledProperty TextAlignmentProperty = TextBlock.TextAlignmentProperty.AddOwner(); /// /// Defines the property /// - public static readonly AttachedProperty TextWrappingProperty = + public static readonly StyledProperty TextWrappingProperty = TextBlock.TextWrappingProperty.AddOwner(); /// /// Defines the property /// - public static readonly AttachedProperty TextTrimmingProperty = + public static readonly StyledProperty TextTrimmingProperty = TextBlock.TextTrimmingProperty.AddOwner(); /// /// Defines the property /// - public static readonly AttachedProperty LineHeightProperty = + public static readonly StyledProperty LineHeightProperty = TextBlock.LineHeightProperty.AddOwner(); /// /// Defines the property /// - public static readonly AttachedProperty MaxLinesProperty = + public static readonly StyledProperty MaxLinesProperty = TextBlock.MaxLinesProperty.AddOwner(); /// diff --git a/src/Avalonia.Controls/Presenters/IContentPresenter.cs b/src/Avalonia.Controls/Presenters/IContentPresenter.cs index ab4d61e3bd..673de4700b 100644 --- a/src/Avalonia.Controls/Presenters/IContentPresenter.cs +++ b/src/Avalonia.Controls/Presenters/IContentPresenter.cs @@ -1,4 +1,5 @@ using Avalonia.Controls.Primitives; +using Avalonia.Metadata; namespace Avalonia.Controls.Presenters { @@ -6,6 +7,7 @@ namespace Avalonia.Controls.Presenters /// Interface for controls that present a single item of data inside a /// template. /// + [NotClientImplementable] public interface IContentPresenter : IPresenter { /// diff --git a/src/Avalonia.Controls/Presenters/IContentPresenterHost.cs b/src/Avalonia.Controls/Presenters/IContentPresenterHost.cs index 78c4affe44..562638e94a 100644 --- a/src/Avalonia.Controls/Presenters/IContentPresenterHost.cs +++ b/src/Avalonia.Controls/Presenters/IContentPresenterHost.cs @@ -1,5 +1,6 @@ using Avalonia.Collections; using Avalonia.LogicalTree; +using Avalonia.Metadata; using Avalonia.Styling; namespace Avalonia.Controls.Presenters @@ -15,6 +16,7 @@ namespace Avalonia.Controls.Presenters /// parent control's template is instantiated so they register themselves using this /// interface. /// + [NotClientImplementable] public interface IContentPresenterHost : ITemplatedControl { /// diff --git a/src/Avalonia.Controls/Presenters/IItemsPresenter.cs b/src/Avalonia.Controls/Presenters/IItemsPresenter.cs index e7da3d4618..7cc72ef0a7 100644 --- a/src/Avalonia.Controls/Presenters/IItemsPresenter.cs +++ b/src/Avalonia.Controls/Presenters/IItemsPresenter.cs @@ -1,8 +1,10 @@ using System.Collections; using System.Collections.Specialized; +using Avalonia.Metadata; namespace Avalonia.Controls.Presenters { + [NotClientImplementable] public interface IItemsPresenter : IPresenter { IEnumerable? Items { get; set; } diff --git a/src/Avalonia.Controls/Presenters/IItemsPresenterHost.cs b/src/Avalonia.Controls/Presenters/IItemsPresenterHost.cs index ba9ee0fe31..db11474871 100644 --- a/src/Avalonia.Controls/Presenters/IItemsPresenterHost.cs +++ b/src/Avalonia.Controls/Presenters/IItemsPresenterHost.cs @@ -1,3 +1,4 @@ +using Avalonia.Metadata; using Avalonia.Styling; namespace Avalonia.Controls.Presenters @@ -13,6 +14,7 @@ namespace Avalonia.Controls.Presenters /// parent control's template is instantiated so they register themselves using this /// interface. /// + [NotClientImplementable] public interface IItemsPresenterHost : ITemplatedControl { /// diff --git a/src/Avalonia.Controls/Presenters/IPresenter.cs b/src/Avalonia.Controls/Presenters/IPresenter.cs index 5318ea2757..0399983189 100644 --- a/src/Avalonia.Controls/Presenters/IPresenter.cs +++ b/src/Avalonia.Controls/Presenters/IPresenter.cs @@ -1,4 +1,5 @@ using Avalonia.Controls.Primitives; +using Avalonia.Metadata; namespace Avalonia.Controls.Presenters { @@ -12,6 +13,7 @@ namespace Avalonia.Controls.Presenters /// of a then that signals that the visual child /// of the presenter is not a part of the template. /// + [NotClientImplementable] public interface IPresenter : IControl, INamed { } diff --git a/src/Avalonia.Controls/Presenters/TextPresenter.cs b/src/Avalonia.Controls/Presenters/TextPresenter.cs index 0785149a73..ea9ae7bb0f 100644 --- a/src/Avalonia.Controls/Presenters/TextPresenter.cs +++ b/src/Avalonia.Controls/Presenters/TextPresenter.cs @@ -26,14 +26,14 @@ namespace Avalonia.Controls.Presenters AvaloniaProperty.Register(nameof(PasswordChar)); public static readonly StyledProperty SelectionBrushProperty = - AvaloniaProperty.Register(nameof(SelectionBrushProperty)); + AvaloniaProperty.Register(nameof(SelectionBrush)); public static readonly StyledProperty SelectionForegroundBrushProperty = - AvaloniaProperty.Register(nameof(SelectionForegroundBrushProperty)); + AvaloniaProperty.Register(nameof(SelectionForegroundBrush)); public static readonly StyledProperty CaretBrushProperty = - AvaloniaProperty.Register(nameof(CaretBrushProperty)); - + AvaloniaProperty.Register(nameof(CaretBrush)); + public static readonly DirectProperty SelectionStartProperty = TextBox.SelectionStartProperty.AddOwner( o => o.SelectionStart, @@ -43,7 +43,7 @@ namespace Avalonia.Controls.Presenters TextBox.SelectionEndProperty.AddOwner( o => o.SelectionEnd, (o, v) => o.SelectionEnd = v); - + /// /// Defines the property. /// @@ -65,6 +65,12 @@ namespace Avalonia.Controls.Presenters public static readonly StyledProperty TextWrappingProperty = TextBlock.TextWrappingProperty.AddOwner(); + /// + /// Defines the property. + /// + public static readonly StyledProperty LineHeightProperty = + TextBlock.LineHeightProperty.AddOwner(); + /// /// Defines the property. /// @@ -179,6 +185,15 @@ namespace Avalonia.Controls.Presenters get => GetValue(TextWrappingProperty); set => SetValue(TextWrappingProperty, value); } + + /// + /// Gets or sets the line height. By default, this is set to , which determines the appropriate height automatically. + /// + public double LineHeight + { + get => GetValue(LineHeightProperty); + set => SetValue(LineHeightProperty, value); + } /// /// Gets or sets the text alignment. @@ -253,7 +268,7 @@ namespace Avalonia.Controls.Presenters get => GetValue(CaretBrushProperty); set => SetValue(CaretBrushProperty, value); } - + public int SelectionStart { get @@ -281,7 +296,7 @@ namespace Avalonia.Controls.Presenters SetAndRaise(SelectionEndProperty, ref _selectionEnd, value); } } - + protected override bool BypassFlowDirectionPolicies => true; /// @@ -301,7 +316,7 @@ namespace Avalonia.Controls.Presenters var textLayout = new TextLayout(text, typeface, FontSize, foreground, TextAlignment, TextWrapping, maxWidth: maxWidth, maxHeight: maxHeight, textStyleOverrides: textStyleOverrides, - flowDirection: FlowDirection); + flowDirection: FlowDirection, lineHeight: LineHeight); return textLayout; } diff --git a/src/Avalonia.Controls/Primitives/AdornerLayer.cs b/src/Avalonia.Controls/Primitives/AdornerLayer.cs index fb047d93df..5ad4e39baf 100644 --- a/src/Avalonia.Controls/Primitives/AdornerLayer.cs +++ b/src/Avalonia.Controls/Primitives/AdornerLayer.cs @@ -105,7 +105,7 @@ namespace Avalonia.Controls.Primitives } else { - child.Arrange(new Rect(finalSize)); + ArrangeChild((Control) child, finalSize); } } } diff --git a/src/Avalonia.Controls/Primitives/IPopupHost.cs b/src/Avalonia.Controls/Primitives/IPopupHost.cs index 8652924f90..bd2aa39621 100644 --- a/src/Avalonia.Controls/Primitives/IPopupHost.cs +++ b/src/Avalonia.Controls/Primitives/IPopupHost.cs @@ -3,6 +3,7 @@ using Avalonia.Controls.Presenters; using Avalonia.Controls.Primitives.PopupPositioning; using Avalonia.Input; using Avalonia.Media; +using Avalonia.Metadata; using Avalonia.VisualTree; namespace Avalonia.Controls.Primitives @@ -15,6 +16,7 @@ namespace Avalonia.Controls.Primitives /// () or an which is created /// on an . /// + [NotClientImplementable] public interface IPopupHost : IDisposable, IFocusScope { /// diff --git a/src/Avalonia.Controls/Primitives/Popup.cs b/src/Avalonia.Controls/Primitives/Popup.cs index 7a7e41b029..95e5e25c42 100644 --- a/src/Avalonia.Controls/Primitives/Popup.cs +++ b/src/Avalonia.Controls/Primitives/Popup.cs @@ -27,7 +27,7 @@ namespace Avalonia.Controls.Primitives #pragma warning restore CS0612 // Type or member is obsolete { public static readonly StyledProperty WindowManagerAddShadowHintProperty = - AvaloniaProperty.Register(nameof(WindowManagerAddShadowHint), false); + AvaloniaProperty.Register(nameof(WindowManagerAddShadowHint), false); /// /// Defines the property. diff --git a/src/Avalonia.Controls/Primitives/PopupPositioning/IPopupPositioner.cs b/src/Avalonia.Controls/Primitives/PopupPositioning/IPopupPositioner.cs index 8daf1ac68a..8d35a91e00 100644 --- a/src/Avalonia.Controls/Primitives/PopupPositioning/IPopupPositioner.cs +++ b/src/Avalonia.Controls/Primitives/PopupPositioning/IPopupPositioner.cs @@ -45,6 +45,7 @@ Copyright © 2019 Nikita Tsukanov */ using System; +using Avalonia.Metadata; using Avalonia.VisualTree; using Avalonia.Media; @@ -61,6 +62,7 @@ namespace Avalonia.Controls.Primitives.PopupPositioning /// requirement that a popup must intersect with or be at least partially adjacent to its parent /// surface. /// + [Unstable] public struct PopupPositionerParameters { private PopupGravity _gravity; @@ -429,6 +431,7 @@ namespace Avalonia.Controls.Primitives.PopupPositioning /// managed implementation is provided in for platforms /// on which popups can be arbitrarily positioned. /// + [NotClientImplementable] public interface IPopupPositioner { /// @@ -439,6 +442,7 @@ namespace Avalonia.Controls.Primitives.PopupPositioning void Update(PopupPositionerParameters parameters); } + [Unstable] static class PopupPositionerExtensions { public static void ConfigurePosition(ref this PopupPositionerParameters positionerParameters, diff --git a/src/Avalonia.Controls/Primitives/SelectingItemsControl.cs b/src/Avalonia.Controls/Primitives/SelectingItemsControl.cs index bff6799792..ea20247b4b 100644 --- a/src/Avalonia.Controls/Primitives/SelectingItemsControl.cs +++ b/src/Avalonia.Controls/Primitives/SelectingItemsControl.cs @@ -94,7 +94,7 @@ namespace Avalonia.Controls.Primitives /// Defines the property. /// public static readonly StyledProperty IsTextSearchEnabledProperty = - AvaloniaProperty.Register(nameof(IsTextSearchEnabled), false); + AvaloniaProperty.Register(nameof(IsTextSearchEnabled), false); /// /// Event that should be raised by items that implement to @@ -111,14 +111,14 @@ namespace Avalonia.Controls.Primitives /// public static readonly RoutedEvent SelectionChangedEvent = RoutedEvent.Register( - "SelectionChanged", + nameof(SelectionChanged), RoutingStrategies.Bubble); /// /// Defines the property. /// public static readonly StyledProperty WrapSelectionProperty = - AvaloniaProperty.Register(nameof(WrapSelection), defaultValue: false); + AvaloniaProperty.Register(nameof(WrapSelection), defaultValue: false); private static readonly IList Empty = Array.Empty(); private string _textSearchTerm = string.Empty; diff --git a/src/Avalonia.Controls/Primitives/TemplatedControl.cs b/src/Avalonia.Controls/Primitives/TemplatedControl.cs index 6e4ae748d9..db029d38c0 100644 --- a/src/Avalonia.Controls/Primitives/TemplatedControl.cs +++ b/src/Avalonia.Controls/Primitives/TemplatedControl.cs @@ -97,7 +97,7 @@ namespace Avalonia.Controls.Primitives /// public static readonly RoutedEvent TemplateAppliedEvent = RoutedEvent.Register( - "TemplateApplied", + nameof(TemplateApplied), RoutingStrategies.Direct); private IControlTemplate? _appliedTemplate; diff --git a/src/Avalonia.Controls/Primitives/Track.cs b/src/Avalonia.Controls/Primitives/Track.cs index 49f0cda982..14ec7a2849 100644 --- a/src/Avalonia.Controls/Primitives/Track.cs +++ b/src/Avalonia.Controls/Primitives/Track.cs @@ -44,7 +44,7 @@ namespace Avalonia.Controls.Primitives AvaloniaProperty.Register(nameof(IsDirectionReversed)); public static readonly StyledProperty IgnoreThumbDragProperty = - AvaloniaProperty.Register(nameof(IsThumbDragHandled)); + AvaloniaProperty.Register(nameof(IgnoreThumbDrag)); private double _minimum; private double _maximum = 100.0; @@ -118,7 +118,7 @@ namespace Avalonia.Controls.Primitives set { SetValue(IsDirectionReversedProperty, value); } } - public bool IsThumbDragHandled + public bool IgnoreThumbDrag { get { return GetValue(IgnoreThumbDragProperty); } set { SetValue(IgnoreThumbDragProperty, value); } @@ -442,7 +442,7 @@ namespace Avalonia.Controls.Primitives private void ThumbDragged(object? sender, VectorEventArgs e) { - if (IsThumbDragHandled) + if (IgnoreThumbDrag) return; Value = MathUtilities.Clamp( diff --git a/src/Avalonia.Controls/Remote/RemoteServer.cs b/src/Avalonia.Controls/Remote/RemoteServer.cs index f4cc91a0e6..2cf2d2b97d 100644 --- a/src/Avalonia.Controls/Remote/RemoteServer.cs +++ b/src/Avalonia.Controls/Remote/RemoteServer.cs @@ -1,11 +1,12 @@ using System; using Avalonia.Controls.Embedding; using Avalonia.Controls.Remote.Server; -using Avalonia.Platform; +using Avalonia.Metadata; using Avalonia.Remote.Protocol; namespace Avalonia.Controls.Remote { + [Unstable] public class RemoteServer { private EmbeddableControlRoot _topLevel; diff --git a/src/Avalonia.Controls/Remote/Server/RemoteServerTopLevelImpl.cs b/src/Avalonia.Controls/Remote/Server/RemoteServerTopLevelImpl.cs index c9fd1dc3b8..e800f2f4b0 100644 --- a/src/Avalonia.Controls/Remote/Server/RemoteServerTopLevelImpl.cs +++ b/src/Avalonia.Controls/Remote/Server/RemoteServerTopLevelImpl.cs @@ -6,6 +6,7 @@ using Avalonia.Controls.Platform.Surfaces; using Avalonia.Input; using Avalonia.Input.Raw; using Avalonia.Layout; +using Avalonia.Metadata; using Avalonia.Platform; using Avalonia.Remote.Protocol; using Avalonia.Remote.Protocol.Input; @@ -18,6 +19,7 @@ using ProtocolPixelFormat = Avalonia.Remote.Protocol.Viewport.PixelFormat; namespace Avalonia.Controls.Remote.Server { + [Unstable] public class RemoteServerTopLevelImpl : OffscreenTopLevelImplBase, IFramebufferPlatformSurface { private readonly IAvaloniaRemoteTransportConnection _transport; diff --git a/src/Avalonia.Controls/RepeatButton.cs b/src/Avalonia.Controls/RepeatButton.cs index 80f841fa18..6a62172934 100644 --- a/src/Avalonia.Controls/RepeatButton.cs +++ b/src/Avalonia.Controls/RepeatButton.cs @@ -13,13 +13,13 @@ namespace Avalonia.Controls /// Defines the property. /// public static readonly StyledProperty IntervalProperty = - AvaloniaProperty.Register(nameof(Interval), 100); + AvaloniaProperty.Register(nameof(Interval), 100); /// /// Defines the property. /// public static readonly StyledProperty DelayProperty = - AvaloniaProperty.Register(nameof(Delay), 300); + AvaloniaProperty.Register(nameof(Delay), 300); private DispatcherTimer? _repeatTimer; diff --git a/src/Avalonia.Controls/Slider.cs b/src/Avalonia.Controls/Slider.cs index 64dfce22d4..be87705b54 100644 --- a/src/Avalonia.Controls/Slider.cs +++ b/src/Avalonia.Controls/Slider.cs @@ -76,7 +76,7 @@ namespace Avalonia.Controls /// Defines the property. /// public static readonly StyledProperty TickPlacementProperty = - AvaloniaProperty.Register(nameof(TickPlacement), 0d); + AvaloniaProperty.Register(nameof(TickPlacement), 0d); /// /// Defines the property. @@ -197,7 +197,7 @@ namespace Avalonia.Controls if (_track != null) { - _track.IsThumbDragHandled = true; + _track.IgnoreThumbDrag = true; } if (_decreaseButton != null) diff --git a/src/Avalonia.Controls/SplitView.cs b/src/Avalonia.Controls/SplitView.cs index ae1605a985..532cb1d329 100644 --- a/src/Avalonia.Controls/SplitView.cs +++ b/src/Avalonia.Controls/SplitView.cs @@ -138,7 +138,7 @@ namespace Avalonia.Controls /// Defines the property. /// public static readonly StyledProperty PaneTemplateProperty = - AvaloniaProperty.Register(nameof(PaneTemplate)); + AvaloniaProperty.Register(nameof(PaneTemplate)); /// /// Defines the property diff --git a/src/Avalonia.Controls/Templates/IDataTemplateHost.cs b/src/Avalonia.Controls/Templates/IDataTemplateHost.cs index 61986a0661..ce763c3336 100644 --- a/src/Avalonia.Controls/Templates/IDataTemplateHost.cs +++ b/src/Avalonia.Controls/Templates/IDataTemplateHost.cs @@ -1,9 +1,11 @@ - +using Avalonia.Metadata; + namespace Avalonia.Controls.Templates { /// /// Defines an element that has a collection. /// + [NotClientImplementable] public interface IDataTemplateHost { /// diff --git a/src/Avalonia.Controls/TextBox.cs b/src/Avalonia.Controls/TextBox.cs index 0be58e7fcc..7652b23162 100644 --- a/src/Avalonia.Controls/TextBox.cs +++ b/src/Avalonia.Controls/TextBox.cs @@ -55,13 +55,13 @@ namespace Avalonia.Controls AvaloniaProperty.Register(nameof(PasswordChar)); public static readonly StyledProperty SelectionBrushProperty = - AvaloniaProperty.Register(nameof(SelectionBrushProperty)); + AvaloniaProperty.Register(nameof(SelectionBrush)); public static readonly StyledProperty SelectionForegroundBrushProperty = - AvaloniaProperty.Register(nameof(SelectionForegroundBrushProperty)); + AvaloniaProperty.Register(nameof(SelectionForegroundBrush)); public static readonly StyledProperty CaretBrushProperty = - AvaloniaProperty.Register(nameof(CaretBrushProperty)); + AvaloniaProperty.Register(nameof(CaretBrush)); public static readonly DirectProperty SelectionStartProperty = AvaloniaProperty.RegisterDirect( @@ -79,8 +79,8 @@ namespace Avalonia.Controls AvaloniaProperty.Register(nameof(MaxLength), defaultValue: 0); public static readonly StyledProperty MaxLinesProperty = - AvaloniaProperty.Register(nameof(MaxLines), defaultValue: 0); - + AvaloniaProperty.Register(nameof(MaxLines), defaultValue: 0); + public static readonly DirectProperty TextProperty = TextBlock.TextProperty.AddOwnerWithDataValidation( o => o.Text, @@ -105,6 +105,12 @@ namespace Avalonia.Controls public static readonly StyledProperty TextWrappingProperty = TextBlock.TextWrappingProperty.AddOwner(); + + /// + /// Defines see property. + /// + public static readonly StyledProperty LineHeightProperty = + TextBlock.LineHeightProperty.AddOwner(); public static readonly StyledProperty WatermarkProperty = AvaloniaProperty.Register(nameof(Watermark)); @@ -154,15 +160,15 @@ namespace Avalonia.Controls public static readonly RoutedEvent CopyingToClipboardEvent = RoutedEvent.Register( - "CopyingToClipboard", RoutingStrategies.Bubble); + nameof(CopyingToClipboard), RoutingStrategies.Bubble); public static readonly RoutedEvent CuttingToClipboardEvent = RoutedEvent.Register( - "CuttingToClipboard", RoutingStrategies.Bubble); + nameof(CuttingToClipboard), RoutingStrategies.Bubble); public static readonly RoutedEvent PastingFromClipboardEvent = RoutedEvent.Register( - "PastingFromClipboard", RoutingStrategies.Bubble); + nameof(PastingFromClipboard), RoutingStrategies.Bubble); readonly struct UndoRedoState : IEquatable { @@ -239,23 +245,19 @@ namespace Avalonia.Controls public bool AcceptsReturn { - get { return GetValue(AcceptsReturnProperty); } - set { SetValue(AcceptsReturnProperty, value); } + get => GetValue(AcceptsReturnProperty); + set => SetValue(AcceptsReturnProperty, value); } public bool AcceptsTab { - get { return GetValue(AcceptsTabProperty); } - set { SetValue(AcceptsTabProperty, value); } + get => GetValue(AcceptsTabProperty); + set => SetValue(AcceptsTabProperty, value); } public int CaretIndex { - get - { - return _caretIndex; - } - + get => _caretIndex; set { value = CoerceCaretIndex(value); @@ -271,8 +273,8 @@ namespace Avalonia.Controls public bool IsReadOnly { - get { return GetValue(IsReadOnlyProperty); } - set { SetValue(IsReadOnlyProperty, value); } + get => GetValue(IsReadOnlyProperty); + set => SetValue(IsReadOnlyProperty, value); } public char PasswordChar @@ -301,11 +303,7 @@ namespace Avalonia.Controls public int SelectionStart { - get - { - return _selectionStart; - } - + get => _selectionStart; set { value = CoerceCaretIndex(value); @@ -325,11 +323,7 @@ namespace Avalonia.Controls public int SelectionEnd { - get - { - return _selectionEnd; - } - + get => _selectionEnd; set { value = CoerceCaretIndex(value); @@ -349,20 +343,29 @@ namespace Avalonia.Controls public int MaxLength { - get { return GetValue(MaxLengthProperty); } - set { SetValue(MaxLengthProperty, value); } + get => GetValue(MaxLengthProperty); + set => SetValue(MaxLengthProperty, value); } public int MaxLines { - get { return GetValue(MaxLinesProperty); } - set { SetValue(MaxLinesProperty, value); } + get => GetValue(MaxLinesProperty); + set => SetValue(MaxLinesProperty, value); + } + + /// + /// Gets or sets the line height. + /// + public double LineHeight + { + get { return GetValue(LineHeightProperty); } + set { SetValue(LineHeightProperty, value); } } [Content] public string? Text { - get { return _text; } + get => _text; set { if (!_ignoreTextChanges) @@ -386,7 +389,7 @@ namespace Avalonia.Controls public string SelectedText { - get { return GetSelection(); } + get => GetSelection(); set { if (string.IsNullOrEmpty(value)) @@ -407,8 +410,8 @@ namespace Avalonia.Controls /// public HorizontalAlignment HorizontalContentAlignment { - get { return GetValue(HorizontalContentAlignmentProperty); } - set { SetValue(HorizontalContentAlignmentProperty, value); } + get => GetValue(HorizontalContentAlignmentProperty); + set => SetValue(HorizontalContentAlignmentProperty, value); } /// @@ -416,50 +419,58 @@ namespace Avalonia.Controls /// public VerticalAlignment VerticalContentAlignment { - get { return GetValue(VerticalContentAlignmentProperty); } - set { SetValue(VerticalContentAlignmentProperty, value); } + get => GetValue(VerticalContentAlignmentProperty); + set => SetValue(VerticalContentAlignmentProperty, value); } public TextAlignment TextAlignment { - get { return GetValue(TextAlignmentProperty); } - set { SetValue(TextAlignmentProperty, value); } + get => GetValue(TextAlignmentProperty); + set => SetValue(TextAlignmentProperty, value); } + /// + /// Gets or sets the placeholder or descriptive text that is displayed even if the + /// property is not yet set. + /// public string? Watermark { - get { return GetValue(WatermarkProperty); } - set { SetValue(WatermarkProperty, value); } + get => GetValue(WatermarkProperty); + set => SetValue(WatermarkProperty, value); } + /// + /// Gets or sets a value indicating whether the will still be shown above the + /// even after a text value is set. + /// public bool UseFloatingWatermark { - get { return GetValue(UseFloatingWatermarkProperty); } - set { SetValue(UseFloatingWatermarkProperty, value); } + get => GetValue(UseFloatingWatermarkProperty); + set => SetValue(UseFloatingWatermarkProperty, value); } public object InnerLeftContent { - get { return GetValue(InnerLeftContentProperty); } - set { SetValue(InnerLeftContentProperty, value); } + get => GetValue(InnerLeftContentProperty); + set => SetValue(InnerLeftContentProperty, value); } public object InnerRightContent { - get { return GetValue(InnerRightContentProperty); } - set { SetValue(InnerRightContentProperty, value); } + get => GetValue(InnerRightContentProperty); + set => SetValue(InnerRightContentProperty, value); } public bool RevealPassword { - get { return GetValue(RevealPasswordProperty); } - set { SetValue(RevealPasswordProperty, value); } + get => GetValue(RevealPasswordProperty); + set => SetValue(RevealPasswordProperty, value); } public TextWrapping TextWrapping { - get { return GetValue(TextWrappingProperty); } - set { SetValue(TextWrappingProperty, value); } + get => GetValue(TextWrappingProperty); + set => SetValue(TextWrappingProperty, value); } /// @@ -467,8 +478,8 @@ namespace Avalonia.Controls /// public string NewLine { - get { return _newLine; } - set { SetAndRaise(NewLineProperty, ref _newLine, value); } + get => _newLine; + set => SetAndRaise(NewLineProperty, ref _newLine, value); } /// @@ -484,8 +495,8 @@ namespace Avalonia.Controls /// public bool CanCut { - get { return _canCut; } - private set { SetAndRaise(CanCutProperty, ref _canCut, value); } + get => _canCut; + private set => SetAndRaise(CanCutProperty, ref _canCut, value); } /// @@ -493,8 +504,8 @@ namespace Avalonia.Controls /// public bool CanCopy { - get { return _canCopy; } - private set { SetAndRaise(CanCopyProperty, ref _canCopy, value); } + get => _canCopy; + private set => SetAndRaise(CanCopyProperty, ref _canCopy, value); } /// @@ -502,8 +513,8 @@ namespace Avalonia.Controls /// public bool CanPaste { - get { return _canPaste; } - private set { SetAndRaise(CanPasteProperty, ref _canPaste, value); } + get => _canPaste; + private set => SetAndRaise(CanPasteProperty, ref _canPaste, value); } /// @@ -511,13 +522,13 @@ namespace Avalonia.Controls /// public bool IsUndoEnabled { - get { return GetValue(IsUndoEnabledProperty); } - set { SetValue(IsUndoEnabledProperty, value); } + get => GetValue(IsUndoEnabledProperty); + set => SetValue(IsUndoEnabledProperty, value); } public int UndoLimit { - get { return _undoRedoHelper.Limit; } + get => _undoRedoHelper.Limit; set { if (_undoRedoHelper.Limit != value) @@ -1539,7 +1550,7 @@ namespace Avalonia.Controls UndoRedoState UndoRedoHelper.IUndoRedoHost.UndoRedoState { - get { return new UndoRedoState(Text, CaretIndex); } + get => new UndoRedoState(Text, CaretIndex); set { Text = value.Text; diff --git a/src/Avalonia.Controls/Viewbox.cs b/src/Avalonia.Controls/Viewbox.cs index 33a05f126d..01a41a0157 100644 --- a/src/Avalonia.Controls/Viewbox.cs +++ b/src/Avalonia.Controls/Viewbox.cs @@ -1,4 +1,5 @@ using Avalonia.Media; +using Avalonia.Media.Immutable; using Avalonia.Metadata; namespace Avalonia.Controls @@ -8,7 +9,7 @@ namespace Avalonia.Controls /// public class Viewbox : Control { - private Decorator _containerVisual; + private readonly ViewboxContainer _containerVisual; /// /// Defines the property. @@ -37,9 +38,10 @@ namespace Avalonia.Controls public Viewbox() { - _containerVisual = new Decorator(); + // The Child control is hosted inside a ViewboxContainer control so that the transform + // can be applied independently of the Viewbox and Child transforms. + _containerVisual = new ViewboxContainer(); _containerVisual.RenderTransformOrigin = RelativePoint.TopLeft; - LogicalChildren.Add(_containerVisual); VisualChildren.Add(_containerVisual); } @@ -88,7 +90,22 @@ namespace Avalonia.Controls if (change.Property == ChildProperty) { - _containerVisual.Child = change.GetNewValue(); + var (oldChild, newChild) = change.GetOldAndNewValue(); + + if (oldChild is not null) + { + ((ISetLogicalParent)oldChild).SetParent(null); + LogicalChildren.Remove(oldChild); + } + + _containerVisual.Child = newChild; + + if (newChild is not null) + { + ((ISetLogicalParent)newChild).SetParent(this); + LogicalChildren.Add(newChild); + } + InvalidateMeasure(); } } @@ -120,7 +137,7 @@ namespace Avalonia.Controls var childSize = child.DesiredSize; var scale = Stretch.CalculateScaling(finalSize, childSize, StretchDirection); - InternalTransform = new ScaleTransform(scale.X, scale.Y); + InternalTransform = new ImmutableTransform(Matrix.CreateScale(scale.X, scale.Y)); child.Arrange(new Rect(childSize)); @@ -129,5 +146,31 @@ namespace Avalonia.Controls return finalSize; } + + /// + /// A simple container control which hosts its child as a visual but not logical child. + /// + private class ViewboxContainer : Control + { + private IControl? _child; + + public IControl? Child + { + get => _child; + set + { + if (_child != value) + { + if (_child is not null) + VisualChildren.Remove(_child); + + _child = value; + + if (_child is not null) + VisualChildren.Add(_child); + } + } + } + } } } diff --git a/src/Avalonia.Controls/Window.cs b/src/Avalonia.Controls/Window.cs index a5f99918b2..a4f4534b88 100644 --- a/src/Avalonia.Controls/Window.cs +++ b/src/Avalonia.Controls/Window.cs @@ -171,13 +171,13 @@ namespace Avalonia.Controls /// /// Routed event that can be used for global tracking of window destruction /// - public static readonly RoutedEvent WindowClosedEvent = + public static readonly RoutedEvent WindowClosedEvent = RoutedEvent.Register("WindowClosed", RoutingStrategies.Direct); /// /// Routed event that can be used for global tracking of opening windows /// - public static readonly RoutedEvent WindowOpenedEvent = + public static readonly RoutedEvent WindowOpenedEvent = RoutedEvent.Register("WindowOpened", RoutingStrategies.Direct); @@ -917,6 +917,15 @@ namespace Avalonia.Controls var constraint = clientSize; var maxAutoSize = PlatformImpl?.MaxAutoSizeHint ?? Size.Infinity; + if (MaxWidth > 0 && MaxWidth < maxAutoSize.Width) + { + maxAutoSize = maxAutoSize.WithWidth(MaxWidth); + } + if (MaxHeight > 0 && MaxHeight < maxAutoSize.Height) + { + maxAutoSize = maxAutoSize.WithHeight(MaxHeight); + } + if (sizeToContent.HasAllFlags(SizeToContent.Width)) { constraint = constraint.WithWidth(maxAutoSize.Width); diff --git a/src/Avalonia.DesignerSupport/Avalonia.DesignerSupport.csproj b/src/Avalonia.DesignerSupport/Avalonia.DesignerSupport.csproj index 0270000d8c..0be695e0a1 100644 --- a/src/Avalonia.DesignerSupport/Avalonia.DesignerSupport.csproj +++ b/src/Avalonia.DesignerSupport/Avalonia.DesignerSupport.csproj @@ -18,7 +18,6 @@ - diff --git a/src/Avalonia.Diagnostics/Diagnostics/ViewModels/ControlDetailsViewModel.cs b/src/Avalonia.Diagnostics/Diagnostics/ViewModels/ControlDetailsViewModel.cs index a1fd425571..e383c160e3 100644 --- a/src/Avalonia.Diagnostics/Diagnostics/ViewModels/ControlDetailsViewModel.cs +++ b/src/Avalonia.Diagnostics/Diagnostics/ViewModels/ControlDetailsViewModel.cs @@ -60,7 +60,8 @@ namespace Avalonia.Diagnostics.ViewModels var styleDiagnostics = styledElement.GetStyleDiagnostics(); - foreach (var appliedStyle in styleDiagnostics.AppliedStyles) + // We need to place styles without activator first, such styles will be overwritten by ones with activators. + foreach (var appliedStyle in styleDiagnostics.AppliedStyles.OrderBy(s => s.HasActivator)) { var styleSource = appliedStyle.Source; diff --git a/src/Avalonia.FreeDesktop/LinuxMountedVolumeInfoListener.cs b/src/Avalonia.FreeDesktop/LinuxMountedVolumeInfoListener.cs index f9737b461d..39ddd9d769 100644 --- a/src/Avalonia.FreeDesktop/LinuxMountedVolumeInfoListener.cs +++ b/src/Avalonia.FreeDesktop/LinuxMountedVolumeInfoListener.cs @@ -36,6 +36,13 @@ namespace Avalonia.FreeDesktop private string GetSymlinkTarget(string x) => Path.GetFullPath(Path.Combine(DevByLabelDir, NativeMethods.ReadLink(x))); + private string UnescapeString(string input, string regexText, int escapeBase) => + new Regex(regexText).Replace(input, m => Convert.ToChar(Convert.ToByte(m.Groups[1].Value, escapeBase)).ToString()); + + private string UnescapePathFromProcMounts(string input) => UnescapeString(input, @"\\(\d{3})", 8); + + private string UnescapeDeviceLabel(string input) => UnescapeString(input, @"\\x([0-9a-f]{2})", 16); + private void Poll(long _) { var fProcPartitions = File.ReadAllLines(ProcPartitionsDir) @@ -47,14 +54,14 @@ namespace Avalonia.FreeDesktop var fProcMounts = File.ReadAllLines(ProcMountsDir) .Select(x => x.Split(' ')) - .Select(x => (x[0], x[1])) + .Select(x => (x[0], UnescapePathFromProcMounts(x[1]))) .Where(x => !x.Item2.StartsWith("/snap/", StringComparison.InvariantCultureIgnoreCase)); var labelDirEnum = Directory.Exists(DevByLabelDir) ? new DirectoryInfo(DevByLabelDir).GetFiles() : Enumerable.Empty(); var labelDevPathPairs = labelDirEnum - .Select(x => (GetSymlinkTarget(x.FullName), x.Name)); + .Select(x => (GetSymlinkTarget(x.FullName), UnescapeDeviceLabel(x.Name))); var q1 = from mount in fProcMounts join device in fProcPartitions on mount.Item1 equals device.Item2 @@ -64,7 +71,7 @@ namespace Avalonia.FreeDesktop { VolumePath = mount.Item2, VolumeSizeBytes = device.Item1, - VolumeLabel = x.Name + VolumeLabel = x.Item2 }; var mountVolInfos = q1.ToArray(); diff --git a/src/Avalonia.FreeDesktop/NativeMethods.cs b/src/Avalonia.FreeDesktop/NativeMethods.cs index 8bbda98bb2..147955b6a3 100644 --- a/src/Avalonia.FreeDesktop/NativeMethods.cs +++ b/src/Avalonia.FreeDesktop/NativeMethods.cs @@ -14,15 +14,15 @@ namespace Avalonia.FreeDesktop public static string ReadLink(string path) { - var symlinkMaxSize = Encoding.ASCII.GetMaxByteCount(path.Length); + var symlinkSize = Encoding.UTF8.GetByteCount(path); var bufferSize = 4097; // PATH_MAX is (usually?) 4096, but we need to know if the result was truncated - var symlink = ArrayPool.Shared.Rent(symlinkMaxSize + 1); + var symlink = ArrayPool.Shared.Rent(symlinkSize + 1); var buffer = ArrayPool.Shared.Rent(bufferSize); try { - var symlinkSize = Encoding.UTF8.GetBytes(path, 0, path.Length, symlink, 0); + Encoding.UTF8.GetBytes(path, 0, path.Length, symlink, 0); symlink[symlinkSize] = 0; var size = readlink(symlink, buffer, bufferSize); diff --git a/src/Avalonia.Native/WindowImpl.cs b/src/Avalonia.Native/WindowImpl.cs index c082bdb1b8..b5af927ea0 100644 --- a/src/Avalonia.Native/WindowImpl.cs +++ b/src/Avalonia.Native/WindowImpl.cs @@ -107,6 +107,13 @@ namespace Avalonia.Native private bool _isExtended; public bool IsClientAreaExtendedToDecorations => _isExtended; + public override void Show(bool activate, bool isDialog) + { + base.Show(activate, isDialog); + + InvalidateExtendedMargins(); + } + protected override bool ChromeHitTest (RawPointerEventArgs e) { if(_isExtended) diff --git a/src/Avalonia.Native/WindowImplBase.cs b/src/Avalonia.Native/WindowImplBase.cs index 735f11bcd5..94a3a5ed9b 100644 --- a/src/Avalonia.Native/WindowImplBase.cs +++ b/src/Avalonia.Native/WindowImplBase.cs @@ -116,8 +116,12 @@ namespace Avalonia.Native { if (_native != null) { - var s = _native.FrameSize; - return new Size(s.Width, s.Height); + unsafe + { + var s = new AvnSize { Width = -1, Height = -1 }; + _native.GetFrameSize(&s); + return s.Width < 0 && s.Height < 0 ? null : new Size(s.Width, s.Height); + } } return default; diff --git a/src/Avalonia.Native/avn.idl b/src/Avalonia.Native/avn.idl index 8092004989..a1c73e1f03 100644 --- a/src/Avalonia.Native/avn.idl +++ b/src/Avalonia.Native/avn.idl @@ -2,6 +2,7 @@ @clr-access internal @clr-map bool int @cpp-preamble @@ +#pragma once #include "com.h" #include "stddef.h" @@ @@ -503,7 +504,7 @@ interface IAvnWindowBase : IUnknown HRESULT Close(); HRESULT Activate(); HRESULT GetClientSize(AvnSize*ret); - HRESULT GetFrameSize(AvnSize*ret); + HRESULT GetFrameSize(AvnSize*result); HRESULT GetScaling(double*ret); HRESULT SetMinMaxSize(AvnSize minSize, AvnSize maxSize); HRESULT Resize(double width, double height, AvnPlatformResizeReason reason); diff --git a/src/Avalonia.OpenGL/Imaging/IOpenGlBitmapImpl.cs b/src/Avalonia.OpenGL/Imaging/IOpenGlBitmapImpl.cs index aef4f601be..22f0cebf57 100644 --- a/src/Avalonia.OpenGL/Imaging/IOpenGlBitmapImpl.cs +++ b/src/Avalonia.OpenGL/Imaging/IOpenGlBitmapImpl.cs @@ -1,15 +1,17 @@ using System; -using Avalonia.Media.Imaging; +using Avalonia.Metadata; using Avalonia.Platform; namespace Avalonia.OpenGL.Imaging { + [Unstable] public interface IOpenGlBitmapImpl : IBitmapImpl { IOpenGlBitmapAttachment CreateFramebufferAttachment(IGlContext context, Action presentCallback); bool SupportsContext(IGlContext context); } + [Unstable] public interface IOpenGlBitmapAttachment : IDisposable { void Present(); diff --git a/src/Avalonia.Themes.Default/Controls/TextBox.xaml b/src/Avalonia.Themes.Default/Controls/TextBox.xaml index 6e3898a828..1885d3b65b 100644 --- a/src/Avalonia.Themes.Default/Controls/TextBox.xaml +++ b/src/Avalonia.Themes.Default/Controls/TextBox.xaml @@ -76,6 +76,7 @@ SelectionEnd="{TemplateBinding SelectionEnd}" TextAlignment="{TemplateBinding TextAlignment}" TextWrapping="{TemplateBinding TextWrapping}" + LineHeight="{TemplateBinding LineHeight}" PasswordChar="{TemplateBinding PasswordChar}" RevealPassword="{TemplateBinding RevealPassword}" SelectionBrush="{TemplateBinding SelectionBrush}" diff --git a/src/Avalonia.Themes.Fluent/Accents/FluentControlResourcesDark.xaml b/src/Avalonia.Themes.Fluent/Accents/FluentControlResourcesDark.xaml index 5b86de02d5..c5bb70bed3 100644 --- a/src/Avalonia.Themes.Fluent/Accents/FluentControlResourcesDark.xaml +++ b/src/Avalonia.Themes.Fluent/Accents/FluentControlResourcesDark.xaml @@ -587,9 +587,24 @@ - + + + + + + + + + + + + + + + + 1 diff --git a/src/Avalonia.Themes.Fluent/Accents/FluentControlResourcesLight.xaml b/src/Avalonia.Themes.Fluent/Accents/FluentControlResourcesLight.xaml index eb68270354..8d38d39bd5 100644 --- a/src/Avalonia.Themes.Fluent/Accents/FluentControlResourcesLight.xaml +++ b/src/Avalonia.Themes.Fluent/Accents/FluentControlResourcesLight.xaml @@ -581,9 +581,24 @@ - + + + + + + + + + + + + + + + + 1 diff --git a/src/Avalonia.Themes.Fluent/Controls/Button.xaml b/src/Avalonia.Themes.Fluent/Controls/Button.xaml index f545206a2f..a93fb6831d 100644 --- a/src/Avalonia.Themes.Fluent/Controls/Button.xaml +++ b/src/Avalonia.Themes.Fluent/Controls/Button.xaml @@ -7,9 +7,11 @@ + 8,5,8,6 + - - + + - + - + - + - + - + + + + + - diff --git a/src/Avalonia.Themes.Fluent/Controls/CalendarDatePicker.xaml b/src/Avalonia.Themes.Fluent/Controls/CalendarDatePicker.xaml index ffd3972b66..c6f70d05e1 100644 --- a/src/Avalonia.Themes.Fluent/Controls/CalendarDatePicker.xaml +++ b/src/Avalonia.Themes.Fluent/Controls/CalendarDatePicker.xaml @@ -1,10 +1,3 @@ - - - 12 + 12 + 32 + + + - + + - - - - - - + + - - - - - + - - + - - - - - + -