diff --git a/.ncrunch/Avalonia.UnitTests.v3.ncrunchproject b/.ncrunch/Avalonia.UnitTests.v3.ncrunchproject
new file mode 100644
index 0000000000..cff5044edf
--- /dev/null
+++ b/.ncrunch/Avalonia.UnitTests.v3.ncrunchproject
@@ -0,0 +1,5 @@
+
+
+ False
+
+
\ No newline at end of file
diff --git a/.ncrunch/GpuInterop.v3.ncrunchproject b/.ncrunch/GpuInterop.v3.ncrunchproject
new file mode 100644
index 0000000000..319cd523ce
--- /dev/null
+++ b/.ncrunch/GpuInterop.v3.ncrunchproject
@@ -0,0 +1,5 @@
+
+
+ True
+
+
\ No newline at end of file
diff --git a/Avalonia.Desktop.slnf b/Avalonia.Desktop.slnf
index 3acd4bf9f2..4a7a329fc6 100644
--- a/Avalonia.Desktop.slnf
+++ b/Avalonia.Desktop.slnf
@@ -42,9 +42,11 @@
"src\\Windows\\Avalonia.Win32\\Avalonia.Win32.csproj",
"src\\tools\\DevAnalyzers\\DevAnalyzers.csproj",
"src\\tools\\DevGenerators\\DevGenerators.csproj",
+ "src\\tools\\PublicAnalyzers\\Avalonia.Analyzers.csproj",
"tests\\Avalonia.Base.UnitTests\\Avalonia.Base.UnitTests.csproj",
"tests\\Avalonia.Benchmarks\\Avalonia.Benchmarks.csproj",
"tests\\Avalonia.Controls.DataGrid.UnitTests\\Avalonia.Controls.DataGrid.UnitTests.csproj",
+ "tests\\Avalonia.Controls.ItemsRepeater.UnitTests\\Avalonia.Controls.ItemsRepeater.UnitTests.csproj",
"tests\\Avalonia.Controls.UnitTests\\Avalonia.Controls.UnitTests.csproj",
"tests\\Avalonia.DesignerSupport.TestApp\\Avalonia.DesignerSupport.TestApp.csproj",
"tests\\Avalonia.DesignerSupport.Tests\\Avalonia.DesignerSupport.Tests.csproj",
@@ -60,4 +62,4 @@
"tests\\Avalonia.UnitTests\\Avalonia.UnitTests.csproj"
]
}
-}
\ No newline at end of file
+}
diff --git a/Avalonia.sln b/Avalonia.sln
index 525e01c891..56847bae31 100644
--- a/Avalonia.sln
+++ b/Avalonia.sln
@@ -231,7 +231,14 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ControlCatalog.Browser.Blaz
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ReactiveUIDemo", "samples\ReactiveUIDemo\ReactiveUIDemo.csproj", "{75C47156-C5D8-44BC-A5A7-E8657C2248D6}"
EndProject
-Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "GpuInterop", "samples\GpuInterop\GpuInterop.csproj", "{C810060E-3809-4B74-A125-F11533AF9C1B}"
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "GpuInterop", "samples\GpuInterop\GpuInterop.csproj", "{C810060E-3809-4B74-A125-F11533AF9C1B}"
+EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Avalonia.Analyzers", "src\tools\PublicAnalyzers\Avalonia.Analyzers.csproj", "{C692FE73-43DB-49CE-87FC-F03ED61F25C9}"
+EndProject
+Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{176582E8-46AF-416A-85C1-13A5C6744497}"
+ ProjectSection(SolutionItems) = preProject
+ .editorconfig = .editorconfig
+ EndProjectSection
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Avalonia.Controls.ItemsRepeater", "src\Avalonia.Controls.ItemsRepeater\Avalonia.Controls.ItemsRepeater.csproj", "{EE0F0DD4-A70D-472B-BD5D-B7D32D0E9386}"
EndProject
@@ -548,7 +555,6 @@ Global
{75C47156-C5D8-44BC-A5A7-E8657C2248D6}.Debug|Any CPU.Build.0 = Debug|Any CPU
{75C47156-C5D8-44BC-A5A7-E8657C2248D6}.Release|Any CPU.ActiveCfg = Release|Any CPU
{75C47156-C5D8-44BC-A5A7-E8657C2248D6}.Release|Any CPU.Build.0 = Release|Any CPU
- {C810060E-3809-4B74-A125-F11533AF9C1B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{C810060E-3809-4B74-A125-F11533AF9C1B}.Debug|Any CPU.Build.0 = Debug|Any CPU
{C810060E-3809-4B74-A125-F11533AF9C1B}.Release|Any CPU.ActiveCfg = Release|Any CPU
{C810060E-3809-4B74-A125-F11533AF9C1B}.Release|Any CPU.Build.0 = Release|Any CPU
@@ -560,6 +566,10 @@ Global
{F4E36AA8-814E-4704-BC07-291F70F45193}.Debug|Any CPU.Build.0 = Debug|Any CPU
{F4E36AA8-814E-4704-BC07-291F70F45193}.Release|Any CPU.ActiveCfg = Release|Any CPU
{F4E36AA8-814E-4704-BC07-291F70F45193}.Release|Any CPU.Build.0 = Release|Any CPU
+ {C692FE73-43DB-49CE-87FC-F03ED61F25C9}.Debug|Any CPU.ActiveCfg = Release|Any CPU
+ {C692FE73-43DB-49CE-87FC-F03ED61F25C9}.Debug|Any CPU.Build.0 = Release|Any CPU
+ {C692FE73-43DB-49CE-87FC-F03ED61F25C9}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {C692FE73-43DB-49CE-87FC-F03ED61F25C9}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
@@ -626,6 +636,7 @@ Global
{75C47156-C5D8-44BC-A5A7-E8657C2248D6} = {9B9E3891-2366-4253-A952-D08BCEB71098}
{C810060E-3809-4B74-A125-F11533AF9C1B} = {9B9E3891-2366-4253-A952-D08BCEB71098}
{F4E36AA8-814E-4704-BC07-291F70F45193} = {C5A00AC3-B34C-4564-9BDD-2DA473EF4D8B}
+ {C692FE73-43DB-49CE-87FC-F03ED61F25C9} = {4ED8B739-6F4E-4CD4-B993-545E6B5CE637}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {87366D66-1391-4D90-8999-95A620AD786A}
diff --git a/build/DevAnalyzers.props b/build/DevAnalyzers.props
index 14e4f6a563..7d021d051f 100644
--- a/build/DevAnalyzers.props
+++ b/build/DevAnalyzers.props
@@ -5,5 +5,10 @@
ReferenceOutputAssembly="false"
OutputItemType="Analyzer"
SetTargetFramework="TargetFramework=netstandard2.0"/>
+
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 41d1534f8d..c49290314d 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
@@ -43,6 +43,7 @@
523484CA26EA688F00EA0C2C /* trayicon.mm in Sources */ = {isa = PBXBuildFile; fileRef = 523484C926EA688F00EA0C2C /* trayicon.mm */; };
5B21A982216530F500CEE36E /* cursor.mm in Sources */ = {isa = PBXBuildFile; fileRef = 5B21A981216530F500CEE36E /* cursor.mm */; };
5B8BD94F215BFEA6005ED2A7 /* clipboard.mm in Sources */ = {isa = PBXBuildFile; fileRef = 5B8BD94E215BFEA6005ED2A7 /* clipboard.mm */; };
+ 855EDC9F28C6546F00807998 /* PlatformBehaviorInhibition.mm in Sources */ = {isa = PBXBuildFile; fileRef = 855EDC9E28C6546F00807998 /* PlatformBehaviorInhibition.mm */; };
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 */; };
@@ -95,6 +96,7 @@
5B21A981216530F500CEE36E /* cursor.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = cursor.mm; sourceTree = ""; };
5B8BD94E215BFEA6005ED2A7 /* clipboard.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = clipboard.mm; sourceTree = ""; };
5BF943652167AD1D009CAE35 /* cursor.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = cursor.h; sourceTree = ""; };
+ 855EDC9E28C6546F00807998 /* PlatformBehaviorInhibition.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = PlatformBehaviorInhibition.mm; sourceTree = ""; };
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; };
@@ -140,6 +142,7 @@
AB7A61E62147C814003C5833 = {
isa = PBXGroup;
children = (
+ 855EDC9E28C6546F00807998 /* PlatformBehaviorInhibition.mm */,
BC11A5BC2608D58F0017BAD0 /* automation.h */,
BC11A5BD2608D58F0017BAD0 /* automation.mm */,
1A1852DB23E05814008F0DED /* deadlock.mm */,
@@ -288,6 +291,7 @@
1A3E5EAE23E9FB1300EDE661 /* cgl.mm in Sources */,
BC11A5BF2608D58F0017BAD0 /* automation.mm in Sources */,
37E2330F21583241000CB7E2 /* KeyTransform.mm in Sources */,
+ 855EDC9F28C6546F00807998 /* PlatformBehaviorInhibition.mm in Sources */,
520624B322973F4100C4DCEF /* menu.mm in Sources */,
37A517B32159597E00FBA241 /* Screens.mm in Sources */,
1AFD334123E03C4F0042899B /* controlhost.mm in Sources */,
diff --git a/native/Avalonia.Native/src/OSX/AvnWindow.mm b/native/Avalonia.Native/src/OSX/AvnWindow.mm
index d3b7b4ede6..23abf1d53f 100644
--- a/native/Avalonia.Native/src/OSX/AvnWindow.mm
+++ b/native/Avalonia.Native/src/OSX/AvnWindow.mm
@@ -223,6 +223,19 @@
}
}
+// From chromium:
+//
+// > The delegate or the window class should implement this method so that
+// > -[NSWindow isZoomed] can be then determined by whether or not the current
+// > window frame is equal to the zoomed frame.
+//
+// If we don't implement this, then isZoomed always returns true for a non-
+// resizable window ¯\_(ツ)_/¯
+- (NSRect)windowWillUseStandardFrame:(NSWindow*)window
+ defaultFrame:(NSRect)newFrame {
+ return newFrame;
+}
+
-(BOOL)canBecomeKeyWindow
{
if(_canBecomeKeyWindow)
@@ -261,10 +274,6 @@
-(void) setEnabled:(bool)enable
{
_isEnabled = enable;
-
- [[self standardWindowButton:NSWindowCloseButton] setEnabled:enable];
- [[self standardWindowButton:NSWindowMiniaturizeButton] setEnabled:enable];
- [[self standardWindowButton:NSWindowZoomButton] setEnabled:enable];
}
-(void)becomeKeyWindow
diff --git a/native/Avalonia.Native/src/OSX/PlatformBehaviorInhibition.mm b/native/Avalonia.Native/src/OSX/PlatformBehaviorInhibition.mm
new file mode 100644
index 0000000000..db054d82ef
--- /dev/null
+++ b/native/Avalonia.Native/src/OSX/PlatformBehaviorInhibition.mm
@@ -0,0 +1,39 @@
+#include "common.h"
+
+namespace
+{
+ id s_inhibitAppSleepHandle{};
+}
+
+class PlatformBehaviorInhibition : public ComSingleObject
+{
+public:
+ FORWARD_IUNKNOWN()
+
+ virtual void SetInhibitAppSleep(bool inhibitAppSleep, char* reason) override
+ {
+ START_COM_CALL;
+
+ @autoreleasepool
+ {
+ if (inhibitAppSleep && s_inhibitAppSleepHandle == nullptr)
+ {
+ NSActivityOptions options = NSActivityUserInitiatedAllowingIdleSystemSleep;
+ s_inhibitAppSleepHandle = [[NSProcessInfo processInfo] beginActivityWithOptions:options reason:[NSString stringWithUTF8String: reason]];
+ }
+
+ if (!inhibitAppSleep)
+ {
+ s_inhibitAppSleepHandle = nullptr;
+ }
+ }
+ }
+};
+
+extern IAvnPlatformBehaviorInhibition* CreatePlatformBehaviorInhibition()
+{
+ @autoreleasepool
+ {
+ return new PlatformBehaviorInhibition();
+ }
+}
diff --git a/native/Avalonia.Native/src/OSX/PopupImpl.mm b/native/Avalonia.Native/src/OSX/PopupImpl.mm
index 9820a9f052..972d03d08c 100644
--- a/native/Avalonia.Native/src/OSX/PopupImpl.mm
+++ b/native/Avalonia.Native/src/OSX/PopupImpl.mm
@@ -29,7 +29,7 @@ private:
[Window setLevel:NSPopUpMenuWindowLevel];
}
protected:
- virtual NSWindowStyleMask GetStyle() override
+ virtual NSWindowStyleMask CalculateStyleMask() override
{
return NSWindowStyleMaskBorderless;
}
diff --git a/native/Avalonia.Native/src/OSX/WindowBaseImpl.h b/native/Avalonia.Native/src/OSX/WindowBaseImpl.h
index 4c2758f6c6..93decef136 100644
--- a/native/Avalonia.Native/src/OSX/WindowBaseImpl.h
+++ b/native/Avalonia.Native/src/OSX/WindowBaseImpl.h
@@ -105,9 +105,8 @@ BEGIN_INTERFACE_MAP()
virtual void BringToFront ();
protected:
- virtual NSWindowStyleMask GetStyle();
-
- void UpdateStyle();
+ virtual NSWindowStyleMask CalculateStyleMask() = 0;
+ virtual void UpdateStyle();
private:
void CreateNSWindow (bool isDialog);
diff --git a/native/Avalonia.Native/src/OSX/WindowBaseImpl.mm b/native/Avalonia.Native/src/OSX/WindowBaseImpl.mm
index 038e9a048c..59102e15a6 100644
--- a/native/Avalonia.Native/src/OSX/WindowBaseImpl.mm
+++ b/native/Avalonia.Native/src/OSX/WindowBaseImpl.mm
@@ -35,18 +35,14 @@ WindowBaseImpl::WindowBaseImpl(IAvnWindowBaseEvents *events, IAvnGlContext *gl,
lastSize = NSSize { 100, 100 };
lastMaxSize = NSSize { CGFLOAT_MAX, CGFLOAT_MAX};
lastMinSize = NSSize { 0, 0 };
-
lastMenu = nullptr;
CreateNSWindow(usePanel);
[Window setContentView:StandardContainer];
- [Window setStyleMask:NSWindowStyleMaskBorderless];
[Window setBackingType:NSBackingStoreBuffered];
-
[Window setContentMinSize:lastMinSize];
[Window setContentMaxSize:lastMaxSize];
-
[Window setOpaque:false];
}
@@ -564,12 +560,8 @@ bool WindowBaseImpl::IsModal() {
return false;
}
-NSWindowStyleMask WindowBaseImpl::GetStyle() {
- return NSWindowStyleMaskBorderless;
-}
-
void WindowBaseImpl::UpdateStyle() {
- [Window setStyleMask:GetStyle()];
+ [Window setStyleMask:CalculateStyleMask()];
}
void WindowBaseImpl::CleanNSWindow() {
@@ -580,21 +572,12 @@ void WindowBaseImpl::CleanNSWindow() {
}
}
-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()];
-
- [Window setHidesOnDeactivate:false];
- }
+void WindowBaseImpl::CreateNSWindow(bool usePanel) {
+ if (usePanel) {
+ Window = [[AvnPanel alloc] initWithParent:this contentRect:NSRect{0, 0, lastSize} styleMask:NSWindowStyleMaskBorderless];
+ [Window setHidesOnDeactivate:false];
} else {
- if (![Window isKindOfClass:[AvnWindow class]]) {
- CleanNSWindow();
-
- Window = [[AvnWindow alloc] initWithParent:this contentRect:NSRect{0, 0, lastSize} styleMask:GetStyle()];
- }
+ Window = [[AvnWindow alloc] initWithParent:this contentRect:NSRect{0, 0, lastSize} styleMask:NSWindowStyleMaskBorderless];
}
}
diff --git a/native/Avalonia.Native/src/OSX/WindowImpl.h b/native/Avalonia.Native/src/OSX/WindowImpl.h
index 3861aaf170..29bb659039 100644
--- a/native/Avalonia.Native/src/OSX/WindowImpl.h
+++ b/native/Avalonia.Native/src/OSX/WindowImpl.h
@@ -41,8 +41,6 @@ BEGIN_INTERFACE_MAP()
WindowImpl(IAvnWindowEvents* events, IAvnGlContext* gl);
- void HideOrShowTrafficLights ();
-
virtual HRESULT Show (bool activate, bool isDialog) override;
virtual HRESULT SetEnabled (bool enable) override;
@@ -100,9 +98,11 @@ BEGIN_INTERFACE_MAP()
bool CanBecomeKeyWindow ();
protected:
- virtual NSWindowStyleMask GetStyle() override;
+ virtual NSWindowStyleMask CalculateStyleMask() override;
+ void UpdateStyle () override;
private:
+ void ZOrderChildWindows();
void OnInitialiseNSWindow();
NSString *_lastTitle;
};
diff --git a/native/Avalonia.Native/src/OSX/WindowImpl.mm b/native/Avalonia.Native/src/OSX/WindowImpl.mm
index ce82f7d83f..cf1ee6943d 100644
--- a/native/Avalonia.Native/src/OSX/WindowImpl.mm
+++ b/native/Avalonia.Native/src/OSX/WindowImpl.mm
@@ -30,19 +30,6 @@ WindowImpl::WindowImpl(IAvnWindowEvents *events, IAvnGlContext *gl) : WindowBase
OnInitialiseNSWindow();
}
-void WindowImpl::HideOrShowTrafficLights() {
- if (Window == nil) {
- return;
- }
-
- bool wantsChrome = (_extendClientHints & AvnSystemChrome) || (_extendClientHints & AvnPreferSystemChrome);
- bool hasTrafficLights = _isClientAreaExtended ? wantsChrome : _decorations == SystemDecorationsFull;
-
- [[Window standardWindowButton:NSWindowCloseButton] setHidden:!hasTrafficLights];
- [[Window standardWindowButton:NSWindowMiniaturizeButton] setHidden:!hasTrafficLights];
- [[Window standardWindowButton:NSWindowZoomButton] setHidden:!hasTrafficLights];
-}
-
void WindowImpl::OnInitialiseNSWindow(){
[GetWindowProtocol() setCanBecomeKeyWindow:true];
@@ -67,8 +54,6 @@ HRESULT WindowImpl::Show(bool activate, bool isDialog) {
WindowBaseImpl::Show(activate, isDialog);
GetWindowState(&_actualWindowState);
- HideOrShowTrafficLights();
-
return SetWindowState(_lastWindowState);
}
}
@@ -134,14 +119,19 @@ void WindowImpl::BringToFront()
}
[Window invalidateShadow];
+ ZOrderChildWindows();
+ }
+}
+
+void WindowImpl::ZOrderChildWindows()
+{
+ for(auto iterator = _children.begin(); iterator != _children.end(); iterator++)
+ {
+ auto window = (*iterator)->Window;
- for(auto iterator = _children.begin(); iterator != _children.end(); iterator++)
- {
- auto window = (*iterator)->Window;
-
- // #9565: Only bring window to front if it's on the currently active space
- if ([window isOnActiveSpace])
- (*iterator)->BringToFront();
+ // #9565: Only bring window to front if it's on the currently active space
+ if ([window isOnActiveSpace]) {
+ (*iterator)->BringToFront();
}
}
}
@@ -161,13 +151,15 @@ bool WindowImpl::CanBecomeKeyWindow()
void WindowImpl::StartStateTransition() {
_transitioningWindowState = true;
+ UpdateStyle();
}
void WindowImpl::EndStateTransition() {
_transitioningWindowState = false;
-
+ UpdateStyle();
+
// Ensure correct order of child windows after fullscreen transition.
- BringToFront();
+ ZOrderChildWindows();
}
SystemDecorations WindowImpl::Decorations() {
@@ -225,16 +217,12 @@ bool WindowImpl::IsZoomed() {
}
void WindowImpl::DoZoom() {
- switch (_decorations) {
- case SystemDecorationsNone:
- case SystemDecorationsBorderOnly:
- [Window setFrame:[Window screen].visibleFrame display:true];
- break;
-
-
- case SystemDecorationsFull:
- [Window performZoom:Window];
- break;
+ if (_decorations == SystemDecorationsNone ||
+ _decorations == SystemDecorationsBorderOnly ||
+ _canResize == false) {
+ [Window setFrame:[Window screen].visibleFrame display:true];
+ } else {
+ [Window performZoom:Window];
}
}
@@ -261,8 +249,6 @@ HRESULT WindowImpl::SetDecorations(SystemDecorations value) {
UpdateStyle();
- HideOrShowTrafficLights();
-
switch (_decorations) {
case SystemDecorationsNone:
[Window setHasShadow:NO];
@@ -419,9 +405,6 @@ HRESULT WindowImpl::SetExtendClientArea(bool enable) {
}
[GetWindowProtocol() setIsExtended:enable];
-
- HideOrShowTrafficLights();
-
UpdateStyle();
}
@@ -577,14 +560,16 @@ bool WindowImpl::IsOwned() {
return _parent != nullptr;
}
-NSWindowStyleMask WindowImpl::GetStyle() {
- unsigned long s = NSWindowStyleMaskBorderless;
+NSWindowStyleMask WindowImpl::CalculateStyleMask() {
+ // Use the current style mask and only clear the flags we're going to be modifying.
+ unsigned long s = [Window styleMask] &
+ ~(NSWindowStyleMaskFullSizeContentView |
+ NSWindowStyleMaskTitled |
+ NSWindowStyleMaskClosable |
+ NSWindowStyleMaskResizable |
+ NSWindowStyleMaskMiniaturizable |
+ NSWindowStyleMaskTexturedBackground);
- if(_actualWindowState == FullScreen)
- {
- s |= NSWindowStyleMaskFullScreen;
- }
-
switch (_decorations) {
case SystemDecorationsNone:
s = s | NSWindowStyleMaskFullSizeContentView;
@@ -597,7 +582,7 @@ NSWindowStyleMask WindowImpl::GetStyle() {
case SystemDecorationsFull:
s = s | NSWindowStyleMaskTitled | NSWindowStyleMaskClosable;
- if (_canResize && _isEnabled) {
+ if ((_canResize && _isEnabled) || _transitioningWindowState) {
s = s | NSWindowStyleMaskResizable;
}
break;
@@ -612,3 +597,25 @@ NSWindowStyleMask WindowImpl::GetStyle() {
}
return s;
}
+
+void WindowImpl::UpdateStyle() {
+ WindowBaseImpl::UpdateStyle();
+
+ if (Window == nil) {
+ return;
+ }
+
+ bool wantsChrome = (_extendClientHints & AvnSystemChrome) || (_extendClientHints & AvnPreferSystemChrome);
+ bool hasTrafficLights = _isClientAreaExtended ? wantsChrome : _decorations == SystemDecorationsFull;
+
+ NSButton* closeButton = [Window standardWindowButton:NSWindowCloseButton];
+ NSButton* miniaturizeButton = [Window standardWindowButton:NSWindowMiniaturizeButton];
+ NSButton* zoomButton = [Window standardWindowButton:NSWindowZoomButton];
+
+ [closeButton setHidden:!hasTrafficLights];
+ [closeButton setEnabled:_isEnabled];
+ [miniaturizeButton setHidden:!hasTrafficLights];
+ [miniaturizeButton setEnabled:_isEnabled];
+ [zoomButton setHidden:!hasTrafficLights];
+ [zoomButton setEnabled:_isEnabled && _canResize];
+}
diff --git a/native/Avalonia.Native/src/OSX/common.h b/native/Avalonia.Native/src/OSX/common.h
index 972927b99d..4353737dc8 100644
--- a/native/Avalonia.Native/src/OSX/common.h
+++ b/native/Avalonia.Native/src/OSX/common.h
@@ -26,6 +26,7 @@ extern IAvnTrayIcon* CreateTrayIcon();
extern IAvnMenuItem* CreateAppMenuItem();
extern IAvnMenuItem* CreateAppMenuItemSeparator();
extern IAvnApplicationCommands* CreateApplicationCommands();
+extern IAvnPlatformBehaviorInhibition* CreatePlatformBehaviorInhibition();
extern IAvnNativeControlHost* CreateNativeControlHost(NSView* parent);
extern IAvnPlatformSettings* CreatePlatformSettings();
extern void SetAppMenu(IAvnMenu *menu);
diff --git a/native/Avalonia.Native/src/OSX/main.mm b/native/Avalonia.Native/src/OSX/main.mm
index 99063e600e..4bfda4b531 100644
--- a/native/Avalonia.Native/src/OSX/main.mm
+++ b/native/Avalonia.Native/src/OSX/main.mm
@@ -408,6 +408,17 @@ public:
return S_OK;
}
}
+
+ virtual HRESULT CreatePlatformBehaviorInhibition(IAvnPlatformBehaviorInhibition** ppv) override
+ {
+ START_COM_CALL;
+
+ @autoreleasepool
+ {
+ *ppv = ::CreatePlatformBehaviorInhibition();
+ return S_OK;
+ }
+ }
};
extern "C" IAvaloniaNativeFactory* CreateAvaloniaNative()
diff --git a/readme.md b/readme.md
index c2be487af3..2600cf83cc 100644
--- a/readme.md
+++ b/readme.md
@@ -1,3 +1,5 @@
+[](https://avaloniaui.net/xpf)
+
[](https://t.me/Avalonia)
[](https://gitter.im/AvaloniaUI/Avalonia?utm_campaign=pr-badge&utm_content=badge&utm_medium=badge&utm_source=badge) []( https://aka.ms/dotnet-discord) [](https://dev.azure.com/AvaloniaUI/AvaloniaUI/_build/latest?definitionId=4) [](#backers) [](#sponsors) 
diff --git a/samples/ControlCatalog.Browser.Blazor/App.razor.cs b/samples/ControlCatalog.Browser.Blazor/App.razor.cs
index f38db2b055..c331625664 100644
--- a/samples/ControlCatalog.Browser.Blazor/App.razor.cs
+++ b/samples/ControlCatalog.Browser.Blazor/App.razor.cs
@@ -1,3 +1,5 @@
+using System;
+using System.Threading.Tasks;
using Avalonia;
using Avalonia.Browser.Blazor;
@@ -5,13 +7,4 @@ namespace ControlCatalog.Browser.Blazor;
public partial class App
{
- protected override void OnParametersSet()
- {
- AppBuilder.Configure()
- .UseBlazor()
- // .With(new SkiaOptions { CustomGpuFactory = null }) // uncomment to disable GPU/GL rendering
- .SetupWithSingleViewLifetime();
-
- base.OnParametersSet();
- }
}
diff --git a/samples/ControlCatalog.Browser.Blazor/Program.cs b/samples/ControlCatalog.Browser.Blazor/Program.cs
index eb99ca518e..e68e9b14d9 100644
--- a/samples/ControlCatalog.Browser.Blazor/Program.cs
+++ b/samples/ControlCatalog.Browser.Blazor/Program.cs
@@ -1,6 +1,8 @@
using System;
using System.Net.Http;
using System.Threading.Tasks;
+using Avalonia;
+using Avalonia.Browser.Blazor;
using Microsoft.AspNetCore.Components.WebAssembly.Hosting;
using Microsoft.Extensions.DependencyInjection;
using ControlCatalog.Browser.Blazor;
@@ -9,9 +11,17 @@ public class Program
{
public static async Task Main(string[] args)
{
- await CreateHostBuilder(args).Build().RunAsync();
+ var host = CreateHostBuilder(args).Build();
+ await StartAvaloniaApp();
+ await host.RunAsync();
}
+ public static async Task StartAvaloniaApp()
+ {
+ await AppBuilder.Configure()
+ .StartBlazorAppAsync();
+ }
+
public static WebAssemblyHostBuilder CreateHostBuilder(string[] args)
{
var builder = WebAssemblyHostBuilder.CreateDefault(args);
diff --git a/samples/ControlCatalog.Browser/Program.cs b/samples/ControlCatalog.Browser/Program.cs
index 53b7c60a6f..e1a4500173 100644
--- a/samples/ControlCatalog.Browser/Program.cs
+++ b/samples/ControlCatalog.Browser/Program.cs
@@ -1,6 +1,8 @@
using System.Runtime.Versioning;
+using System.Threading.Tasks;
using Avalonia;
using Avalonia.Browser;
+using Avalonia.Controls;
using ControlCatalog;
using ControlCatalog.Browser;
@@ -8,15 +10,27 @@ using ControlCatalog.Browser;
internal partial class Program
{
- private static void Main(string[] args)
+ public static async Task Main(string[] args)
{
- BuildAvaloniaApp()
+ await BuildAvaloniaApp()
.AfterSetup(_ =>
{
ControlCatalog.Pages.EmbedSample.Implementation = new EmbedSampleWeb();
- }).SetupBrowserApp("out");
+ })
+ .StartBrowserAppAsync("out");
}
+ // Example without a ISingleViewApplicationLifetime
+ // private static AvaloniaView _avaloniaView;
+ // public static async Task Main(string[] args)
+ // {
+ // await BuildAvaloniaApp()
+ // .SetupBrowserApp();
+ //
+ // _avaloniaView = new AvaloniaView("out");
+ // _avaloniaView.Content = new TextBlock { Text = "Hello world" };
+ // }
+
public static AppBuilder BuildAvaloniaApp()
=> AppBuilder.Configure();
}
diff --git a/samples/ControlCatalog.Browser/main.js b/samples/ControlCatalog.Browser/main.js
index 87f8a4f943..9d90db8bd2 100644
--- a/samples/ControlCatalog.Browser/main.js
+++ b/samples/ControlCatalog.Browser/main.js
@@ -2,7 +2,6 @@
// The .NET Foundation licenses this file to you under the MIT license.
import { dotnet } from './dotnet.js'
-import { registerAvaloniaModule } from './avalonia.js';
const is_browser = typeof window != "undefined";
if (!is_browser) throw new Error(`Expected to be running in a browser`);
@@ -12,8 +11,6 @@ const dotnetRuntime = await dotnet
.withApplicationArgumentsFromQuery()
.create();
-await registerAvaloniaModule(dotnetRuntime);
-
const config = dotnetRuntime.getConfig();
await dotnetRuntime.runMainAndExit(config.mainAssemblyName, ["dotnet", "is", "great!"]);
diff --git a/samples/ControlCatalog/Pages/CustomDrawingExampleControl.cs b/samples/ControlCatalog/Pages/CustomDrawingExampleControl.cs
index 11e5e32cf1..938c45a4e2 100644
--- a/samples/ControlCatalog/Pages/CustomDrawingExampleControl.cs
+++ b/samples/ControlCatalog/Pages/CustomDrawingExampleControl.cs
@@ -59,10 +59,12 @@ namespace ControlCatalog.Pages
};
StreamGeometry sg = new StreamGeometry();
- var cntx = sg.Open();
- cntx.BeginFigure(new Point(-25.0d, -10.0d), false);
- cntx.ArcTo(new Point(25.0d, -10.0d), new Size(10.0d, 10.0d), 0.0d, false, SweepDirection.Clockwise);
- cntx.EndFigure(true);
+ using (var cntx = sg.Open())
+ {
+ cntx.BeginFigure(new Point(-25.0d, -10.0d), false);
+ cntx.ArcTo(new Point(25.0d, -10.0d), new Size(10.0d, 10.0d), 0.0d, false, SweepDirection.Clockwise);
+ cntx.EndFigure(true);
+ }
_smileGeometry = sg.Clone();
}
diff --git a/samples/ControlCatalog/Pages/DialogsPage.xaml.cs b/samples/ControlCatalog/Pages/DialogsPage.xaml.cs
index f7b020678d..e24860e3e1 100644
--- a/samples/ControlCatalog/Pages/DialogsPage.xaml.cs
+++ b/samples/ControlCatalog/Pages/DialogsPage.xaml.cs
@@ -40,7 +40,7 @@ namespace ControlCatalog.Pages
if (Enum.TryParse(currentFolderBox.Text, true, out var folderEnum))
{
- lastSelectedDirectory = await GetStorageProvider().TryGetWellKnownFolder(folderEnum);
+ lastSelectedDirectory = await GetStorageProvider().TryGetWellKnownFolderAsync(folderEnum);
}
else
{
@@ -51,7 +51,7 @@ namespace ControlCatalog.Pages
if (folderLink is not null)
{
- lastSelectedDirectory = await GetStorageProvider().TryGetFolderFromPath(folderLink);
+ lastSelectedDirectory = await GetStorageProvider().TryGetFolderFromPathAsync(folderLink);
}
}
};
@@ -82,7 +82,13 @@ namespace ControlCatalog.Pages
return new List
{
FilePickerFileTypes.All,
- FilePickerFileTypes.TextPlain
+ FilePickerFileTypes.TextPlain,
+ new("Binary Log")
+ {
+ Patterns = new[] { "*.binlog", "*.buildlog" },
+ MimeTypes = new[] { "application/binlog", "application/buildlog" },
+ AppleUniformTypeIdentifiers = new []{ "public.data" }
+ }
};
}
@@ -142,7 +148,7 @@ namespace ControlCatalog.Pages
}
else
{
- SetFolder(await GetStorageProvider().TryGetFolderFromPath(result));
+ SetFolder(await GetStorageProvider().TryGetFolderFromPathAsync(result));
results.Items = new[] { result };
resultsVisible.IsVisible = true;
}
@@ -223,7 +229,7 @@ namespace ControlCatalog.Pages
ShowOverwritePrompt = false
});
- if (file is not null && file.CanOpenWrite)
+ if (file is not null)
{
// Sync disposal of StreamWriter is not supported on WASM
#if NET6_0_OR_GREATER
@@ -275,7 +281,7 @@ namespace ControlCatalog.Pages
{
ignoreTextChanged = true;
lastSelectedDirectory = folder;
- currentFolderBox.Text = folder?.Path.LocalPath;
+ currentFolderBox.Text = folder?.Path is { IsAbsoluteUri: true } abs ? abs.LocalPath : folder?.Path?.ToString();
ignoreTextChanged = false;
}
async Task SetPickerResult(IReadOnlyCollection? items)
@@ -298,31 +304,26 @@ namespace ControlCatalog.Pages
if (item is IStorageFile file)
{
resultText += @$"
- CanOpenRead: {file.CanOpenRead}
- CanOpenWrite: {file.CanOpenWrite}
Content:
";
- if (file.CanOpenRead)
- {
#if NET6_0_OR_GREATER
- await using var stream = await file.OpenReadAsync();
+ await using var stream = await file.OpenReadAsync();
#else
- using var stream = await file.OpenReadAsync();
+ using var stream = await file.OpenReadAsync();
#endif
- using var reader = new System.IO.StreamReader(stream);
+ using var reader = new System.IO.StreamReader(stream);
- // 4GB file test, shouldn't load more than 10000 chars into a memory.
- const int length = 10000;
- var buffer = ArrayPool.Shared.Rent(length);
- try
- {
- var charsRead = await reader.ReadAsync(buffer, 0, length);
- resultText += new string(buffer, 0, charsRead);
- }
- finally
- {
- ArrayPool.Shared.Return(buffer);
- }
+ // 4GB file test, shouldn't load more than 10000 chars into a memory.
+ const int length = 10000;
+ var buffer = ArrayPool.Shared.Rent(length);
+ try
+ {
+ var charsRead = await reader.ReadAsync(buffer, 0, length);
+ resultText += new string(buffer, 0, charsRead);
+ }
+ finally
+ {
+ ArrayPool.Shared.Return(buffer);
}
}
diff --git a/samples/Directory.Build.props b/samples/Directory.Build.props
index 3b14f0ce12..ac78d9c739 100644
--- a/samples/Directory.Build.props
+++ b/samples/Directory.Build.props
@@ -6,4 +6,5 @@
11
+
diff --git a/samples/GpuInterop/VulkanDemo/D3DMemoryHelper.cs b/samples/GpuInterop/VulkanDemo/D3DMemoryHelper.cs
index a0b7d32d3b..ac09c48ccd 100644
--- a/samples/GpuInterop/VulkanDemo/D3DMemoryHelper.cs
+++ b/samples/GpuInterop/VulkanDemo/D3DMemoryHelper.cs
@@ -47,7 +47,7 @@ public class D3DMemoryHelper
MipLevels = 1,
SampleDescription = new SampleDescription { Count = 1, Quality = 0 },
CpuAccessFlags = default,
- OptionFlags = ResourceOptionFlags.SharedKeyedmutex,
+ OptionFlags = ResourceOptionFlags.SharedKeyedmutex|ResourceOptionFlags.SharedNthandle,
BindFlags = BindFlags.RenderTarget | BindFlags.ShaderResource
});
}
diff --git a/samples/GpuInterop/VulkanDemo/VulkanContext.cs b/samples/GpuInterop/VulkanDemo/VulkanContext.cs
index 3fdd9695f2..1d44549089 100644
--- a/samples/GpuInterop/VulkanDemo/VulkanContext.cs
+++ b/samples/GpuInterop/VulkanDemo/VulkanContext.cs
@@ -173,7 +173,7 @@ public unsafe class VulkanContext : IDisposable
api.GetPhysicalDeviceQueueFamilyProperties(physicalDevice, ref queueFamilyCount, familyProperties);
for (uint queueFamilyIndex = 0; queueFamilyIndex < queueFamilyCount; queueFamilyIndex++)
{
- var family = familyProperties[c];
+ var family = familyProperties[queueFamilyIndex];
if (!family.QueueFlags.HasAllFlags(QueueFlags.GraphicsBit))
continue;
diff --git a/samples/GpuInterop/VulkanDemo/VulkanImage.cs b/samples/GpuInterop/VulkanDemo/VulkanImage.cs
index 59b2ef7e30..c1865a817d 100644
--- a/samples/GpuInterop/VulkanDemo/VulkanImage.cs
+++ b/samples/GpuInterop/VulkanDemo/VulkanImage.cs
@@ -4,10 +4,13 @@ using System.Runtime.InteropServices;
using Avalonia;
using Avalonia.Platform;
using Avalonia.Vulkan;
+using SharpDX.DXGI;
using Silk.NET.Vulkan;
using Silk.NET.Vulkan.Extensions.KHR;
using SilkNetDemo;
using SkiaSharp;
+using Device = Silk.NET.Vulkan.Device;
+using Format = Silk.NET.Vulkan.Format;
namespace GpuInterop.VulkanDemo;
@@ -24,7 +27,6 @@ public unsafe class VulkanImage : IDisposable
private ImageView? _imageView { get; set; }
private DeviceMemory _imageMemory { get; set; }
private SharpDX.Direct3D11.Texture2D? _d3dTexture2D;
- private IntPtr _win32ShareHandle;
internal Image? InternalHandle { get; private set; }
internal Format Format { get; }
@@ -60,7 +62,7 @@ public unsafe class VulkanImage : IDisposable
//MipLevels = MipLevels != 0 ? MipLevels : (uint)Math.Floor(Math.Log(Math.Max(Size.Width, Size.Height), 2));
var handleType = RuntimeInformation.IsOSPlatform(OSPlatform.Windows) ?
- ExternalMemoryHandleTypeFlags.D3D11TextureKmtBit :
+ ExternalMemoryHandleTypeFlags.D3D11TextureBit :
ExternalMemoryHandleTypeFlags.OpaqueFDBit;
var externalMemoryCreateInfo = new ExternalMemoryImageCreateInfo
{
@@ -108,14 +110,14 @@ public unsafe class VulkanImage : IDisposable
if (exportable && RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
{
_d3dTexture2D = D3DMemoryHelper.CreateMemoryHandle(vk.D3DDevice, size, Format);
- using var dxgi = _d3dTexture2D.QueryInterface();
- _win32ShareHandle = dxgi.SharedHandle;
+ using var dxgi = _d3dTexture2D.QueryInterface();
+
handleImport = new ImportMemoryWin32HandleInfoKHR
{
PNext = &dedicatedAllocation,
SType = StructureType.ImportMemoryWin32HandleInfoKhr,
- HandleType = ExternalMemoryHandleTypeFlags.D3D11TextureKmtBit,
- Handle = _win32ShareHandle,
+ HandleType = ExternalMemoryHandleTypeFlags.D3D11TextureBit,
+ Handle = dxgi.CreateSharedHandle(null, SharedResourceFlags.Read | SharedResourceFlags.Write),
};
}
@@ -185,11 +187,19 @@ public unsafe class VulkanImage : IDisposable
return fd;
}
- public IPlatformHandle Export() => RuntimeInformation.IsOSPlatform(OSPlatform.Windows) ?
- new PlatformHandle(_win32ShareHandle,
- KnownPlatformGraphicsExternalImageHandleTypes.D3D11TextureGlobalSharedHandle) :
- new PlatformHandle(new IntPtr(ExportFd()),
- KnownPlatformGraphicsExternalImageHandleTypes.VulkanOpaquePosixFileDescriptor);
+ public IPlatformHandle Export()
+ {
+ if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
+ {
+ using var dxgi = _d3dTexture2D!.QueryInterface();
+ return new PlatformHandle(
+ dxgi.CreateSharedHandle(null, SharedResourceFlags.Read | SharedResourceFlags.Write),
+ KnownPlatformGraphicsExternalImageHandleTypes.D3D11TextureNtHandle);
+ }
+ else
+ return new PlatformHandle(new IntPtr(ExportFd()),
+ KnownPlatformGraphicsExternalImageHandleTypes.VulkanOpaquePosixFileDescriptor);
+ }
public ImageTiling Tiling => ImageTiling.Optimal;
diff --git a/samples/IntegrationTestApp/MainWindow.axaml b/samples/IntegrationTestApp/MainWindow.axaml
index b116e4c789..353e01dca7 100644
--- a/samples/IntegrationTestApp/MainWindow.axaml
+++ b/samples/IntegrationTestApp/MainWindow.axaml
@@ -70,6 +70,7 @@
Item 0
Item 1
+ Wrap Selection
@@ -139,6 +140,7 @@
Maximized
FullScreen
+ Can Resize
@@ -151,6 +153,9 @@
+
+
+
diff --git a/samples/IntegrationTestApp/MainWindow.axaml.cs b/samples/IntegrationTestApp/MainWindow.axaml.cs
index 3cd5350cce..087f25666b 100644
--- a/samples/IntegrationTestApp/MainWindow.axaml.cs
+++ b/samples/IntegrationTestApp/MainWindow.axaml.cs
@@ -66,11 +66,13 @@ namespace IntegrationTestApp
var locationComboBox = this.GetControl("ShowWindowLocation");
var stateComboBox = this.GetControl("ShowWindowState");
var size = !string.IsNullOrWhiteSpace(sizeTextBox.Text) ? Size.Parse(sizeTextBox.Text) : (Size?)null;
+ var canResizeCheckBox = this.GetControl("ShowWindowCanResize");
var owner = (Window)this.GetVisualRoot()!;
var window = new ShowWindowTest
{
WindowStartupLocation = (WindowStartupLocation)locationComboBox.SelectedIndex,
+ CanResize = canResizeCheckBox.IsChecked.Value,
};
if (Application.Current?.ApplicationLifetime is IClassicDesktopStyleApplicationLifetime lifetime)
diff --git a/samples/MobileSandbox/MainView.xaml b/samples/MobileSandbox/MainView.xaml
index 1eab13aa75..5d35ec3fec 100644
--- a/samples/MobileSandbox/MainView.xaml
+++ b/samples/MobileSandbox/MainView.xaml
@@ -5,8 +5,8 @@
x:DataType="mobileSandbox:MainView">
-
-
+
+
diff --git a/samples/interop/WindowsInteropTest/Program.cs b/samples/interop/WindowsInteropTest/Program.cs
index fac06d74b0..c2d30c67bb 100644
--- a/samples/interop/WindowsInteropTest/Program.cs
+++ b/samples/interop/WindowsInteropTest/Program.cs
@@ -1,5 +1,4 @@
using System;
-using Avalonia.Controls;
using ControlCatalog;
using Avalonia;
@@ -15,7 +14,15 @@ namespace WindowsInteropTest
{
System.Windows.Forms.Application.EnableVisualStyles();
System.Windows.Forms.Application.SetCompatibleTextRenderingDefault(false);
- AppBuilder.Configure().UseWin32().UseDirect2D1().SetupWithoutStarting();
+ AppBuilder.Configure()
+ .UseWin32()
+ .UseDirect2D1()
+ .With(new Win32PlatformOptions
+ {
+ UseWindowsUIComposition = false,
+ ShouldRenderOnUIThread = true // necessary for WPF
+ })
+ .SetupWithoutStarting();
System.Windows.Forms.Application.Run(new SelectorForm());
}
}
diff --git a/samples/interop/WindowsInteropTest/WindowsInteropTest.csproj b/samples/interop/WindowsInteropTest/WindowsInteropTest.csproj
index 1643ca3ee2..95f77f6df9 100644
--- a/samples/interop/WindowsInteropTest/WindowsInteropTest.csproj
+++ b/samples/interop/WindowsInteropTest/WindowsInteropTest.csproj
@@ -2,7 +2,7 @@
WinExe
net461
-
+ x64
true
true
@@ -10,9 +10,6 @@
-
- {d0a739b9-3c68-4ba6-a328-41606954b6bd}
- ControlCatalog
-
+
diff --git a/src/Android/Avalonia.Android/AndroidInputMethod.cs b/src/Android/Avalonia.Android/AndroidInputMethod.cs
index c885a7768c..27dcfe8645 100644
--- a/src/Android/Avalonia.Android/AndroidInputMethod.cs
+++ b/src/Android/Avalonia.Android/AndroidInputMethod.cs
@@ -5,8 +5,10 @@ using Android.Text;
using Android.Views;
using Android.Views.InputMethods;
using Avalonia.Android.Platform.SkiaPlatform;
+using Avalonia.Controls.Presenters;
using Avalonia.Input;
using Avalonia.Input.TextInput;
+using Avalonia.Reactive;
namespace Avalonia.Android
{
@@ -32,7 +34,7 @@ namespace Avalonia.Android
ActionPrevious = 0x00000007,
}
- class AndroidInputMethod : ITextInputMethodImpl, IAndroidInputMethod
+ internal class AndroidInputMethod : ITextInputMethodImpl, IAndroidInputMethod
where TView : View, IInitEditorInfo
{
private readonly TView _host;
@@ -68,23 +70,10 @@ namespace Avalonia.Android
public void SetClient(ITextInputMethodClient client)
{
- if (_client != null)
- {
- _client.SurroundingTextChanged -= SurroundingTextChanged;
- }
-
- if(_inputConnection != null)
- {
- _inputConnection.ComposingText = null;
- _inputConnection.ComposingRegion = default;
- }
-
_client = client;
if (IsActive)
{
- _client.SurroundingTextChanged += SurroundingTextChanged;
-
_host.RequestFocus();
_imm.RestartInput(View);
@@ -101,24 +90,6 @@ namespace Avalonia.Android
}
}
- private void SurroundingTextChanged(object sender, EventArgs e)
- {
- if (IsActive && _inputConnection != null)
- {
- var surroundingText = Client.SurroundingText;
-
- _inputConnection.SurroundingText = surroundingText;
-
- _imm.UpdateSelection(_host, surroundingText.AnchorOffset, surroundingText.CursorOffset, surroundingText.AnchorOffset, surroundingText.CursorOffset);
-
- if (_inputConnection.ComposingText != null && !_inputConnection.IsCommiting && surroundingText.AnchorOffset == surroundingText.CursorOffset)
- {
- _inputConnection.CommitText(_inputConnection.ComposingText, 0);
- _inputConnection.SetSelection(surroundingText.AnchorOffset, surroundingText.CursorOffset);
- }
- }
- }
-
public void SetCursorRect(Rect rect)
{
@@ -157,17 +128,20 @@ namespace Avalonia.Android
TextInputReturnKeyType.Search => (ImeFlags)CustomImeFlags.ActionSearch,
TextInputReturnKeyType.Next => (ImeFlags)CustomImeFlags.ActionNext,
TextInputReturnKeyType.Previous => (ImeFlags)CustomImeFlags.ActionPrevious,
- _ => (ImeFlags)CustomImeFlags.ActionDone
+ TextInputReturnKeyType.Done => (ImeFlags)CustomImeFlags.ActionDone,
+ _ => options.Multiline ? ImeFlags.NoEnterAction : (ImeFlags)CustomImeFlags.ActionDone
};
outAttrs.ImeOptions |= ImeFlags.NoFullscreen | ImeFlags.NoExtractUi;
+ _client.TextEditable = _inputConnection.InputEditable;
+
return _inputConnection;
});
}
}
- public readonly record struct ComposingRegion
+ internal readonly record struct ComposingRegion
{
private readonly int _start = -1;
private readonly int _end = -1;
diff --git a/src/Android/Avalonia.Android/Avalonia.Android.csproj b/src/Android/Avalonia.Android/Avalonia.Android.csproj
index 66557418dd..2533016e9f 100644
--- a/src/Android/Avalonia.Android/Avalonia.Android.csproj
+++ b/src/Android/Avalonia.Android/Avalonia.Android.csproj
@@ -5,6 +5,7 @@
true
true
portable
+ Avalonia.Android.Internal
diff --git a/src/Android/Avalonia.Android/InputEditable.cs b/src/Android/Avalonia.Android/InputEditable.cs
new file mode 100644
index 0000000000..c5b68d2652
--- /dev/null
+++ b/src/Android/Avalonia.Android/InputEditable.cs
@@ -0,0 +1,127 @@
+using System;
+using Android.Runtime;
+using Android.Text;
+using Android.Views;
+using Android.Views.InputMethods;
+using Avalonia.Android.Platform.SkiaPlatform;
+using Avalonia.Controls.Presenters;
+using Avalonia.Input;
+using Avalonia.Input.Raw;
+using Avalonia.Input.TextInput;
+using Java.Lang;
+using static System.Net.Mime.MediaTypeNames;
+
+namespace Avalonia.Android
+{
+ internal class InputEditable : SpannableStringBuilder, ITextEditable
+ {
+ private readonly TopLevelImpl _topLevel;
+ private readonly IAndroidInputMethod _inputMethod;
+ private readonly AvaloniaInputConnection _avaloniaInputConnection;
+ private int _currentBatchLevel;
+ private string _previousText;
+ private int _previousSelectionStart;
+ private int _previousSelectionEnd;
+
+ public event EventHandler TextChanged;
+ public event EventHandler SelectionChanged;
+ public event EventHandler CompositionChanged;
+
+ public InputEditable(TopLevelImpl topLevel, IAndroidInputMethod inputMethod, AvaloniaInputConnection avaloniaInputConnection)
+ {
+ _topLevel = topLevel;
+ _inputMethod = inputMethod;
+ _avaloniaInputConnection = avaloniaInputConnection;
+ }
+
+ public InputEditable(ICharSequence text) : base(text)
+ {
+ }
+
+ public InputEditable(string text) : base(text)
+ {
+ }
+
+ public InputEditable(ICharSequence text, int start, int end) : base(text, start, end)
+ {
+ }
+
+ public InputEditable(string text, int start, int end) : base(text, start, end)
+ {
+ }
+
+ protected InputEditable(IntPtr javaReference, JniHandleOwnership transfer) : base(javaReference, transfer)
+ {
+ }
+
+ public int SelectionStart
+ {
+ get => Selection.GetSelectionStart(this); set
+ {
+ var end = SelectionEnd < 0 ? 0 : SelectionEnd;
+ _avaloniaInputConnection.SetSelection(value, end);
+ _inputMethod.IMM.UpdateSelection(_topLevel.View, value, end, value, end);
+ }
+ }
+ public int SelectionEnd
+ {
+ get => Selection.GetSelectionEnd(this); set
+ {
+ var start = SelectionStart < 0 ? 0 : SelectionStart;
+ _avaloniaInputConnection.SetSelection(start, value);
+ _inputMethod.IMM.UpdateSelection(_topLevel.View, start, value, start, value);
+ }
+ }
+
+ public string? Text
+ {
+ get => ToString(); set
+ {
+ if (Text != value)
+ {
+ Clear();
+ Insert(0, value ?? "");
+ }
+ }
+ }
+
+ public int CompositionStart => BaseInputConnection.GetComposingSpanStart(this);
+
+ public int CompositionEnd => BaseInputConnection.GetComposingSpanEnd(this);
+
+ public void BeginBatchEdit()
+ {
+ _currentBatchLevel++;
+
+ if (_currentBatchLevel == 1)
+ {
+ _previousText = ToString();
+ _previousSelectionStart = SelectionStart;
+ _previousSelectionEnd = SelectionEnd;
+ }
+ }
+
+ public void EndBatchEdit()
+ {
+ if (_currentBatchLevel == 1)
+ {
+ if(_previousText != Text)
+ {
+ TextChanged?.Invoke(this, EventArgs.Empty);
+ }
+
+ if (_previousSelectionStart != SelectionStart || _previousSelectionEnd != SelectionEnd)
+ {
+ SelectionChanged?.Invoke(this, EventArgs.Empty);
+ }
+ }
+
+ _currentBatchLevel--;
+ }
+
+ public void RaiseCompositionChanged()
+ {
+ CompositionChanged?.Invoke(this, EventArgs.Empty);
+ }
+ }
+}
diff --git a/src/Android/Avalonia.Android/Platform/Input/AndroidKeyboardDevice.cs b/src/Android/Avalonia.Android/Platform/Input/AndroidKeyboardDevice.cs
index 726ccdbbdd..ab84801e57 100644
--- a/src/Android/Avalonia.Android/Platform/Input/AndroidKeyboardDevice.cs
+++ b/src/Android/Avalonia.Android/Platform/Input/AndroidKeyboardDevice.cs
@@ -5,7 +5,7 @@ using Avalonia.Input;
namespace Avalonia.Android.Platform.Input
{
- public class AndroidKeyboardDevice : KeyboardDevice, IKeyboardDevice {
+ internal class AndroidKeyboardDevice : KeyboardDevice, IKeyboardDevice {
private static readonly Dictionary KeyDic = new Dictionary
{
// { Keycode.Cancel?, Key.Cancel },
diff --git a/src/Android/Avalonia.Android/Platform/SkiaPlatform/InvalidationAwareSurfaceView.cs b/src/Android/Avalonia.Android/Platform/SkiaPlatform/InvalidationAwareSurfaceView.cs
index f205458f0e..47297a4f76 100644
--- a/src/Android/Avalonia.Android/Platform/SkiaPlatform/InvalidationAwareSurfaceView.cs
+++ b/src/Android/Avalonia.Android/Platform/SkiaPlatform/InvalidationAwareSurfaceView.cs
@@ -10,7 +10,7 @@ using Avalonia.Platform;
namespace Avalonia.Android
{
- public abstract class InvalidationAwareSurfaceView : SurfaceView, ISurfaceHolderCallback, IPlatformNativeSurfaceHandle
+ internal abstract class InvalidationAwareSurfaceView : SurfaceView, ISurfaceHolderCallback, IPlatformNativeSurfaceHandle
{
bool _invalidateQueued;
readonly object _lock = new object();
diff --git a/src/Android/Avalonia.Android/Platform/SkiaPlatform/TopLevelImpl.cs b/src/Android/Avalonia.Android/Platform/SkiaPlatform/TopLevelImpl.cs
index 693a26f3bd..e511ed9a8b 100644
--- a/src/Android/Avalonia.Android/Platform/SkiaPlatform/TopLevelImpl.cs
+++ b/src/Android/Avalonia.Android/Platform/SkiaPlatform/TopLevelImpl.cs
@@ -28,6 +28,7 @@ using Math = System.Math;
using AndroidRect = Android.Graphics.Rect;
using Window = Android.Views.Window;
using Android.Graphics.Drawables;
+using Java.Util;
namespace Avalonia.Android.Platform.SkiaPlatform
{
@@ -410,159 +411,73 @@ namespace Avalonia.Android.Platform.SkiaPlatform
{
private readonly TopLevelImpl _topLevel;
private readonly IAndroidInputMethod _inputMethod;
+ private readonly InputEditable _editable;
public AvaloniaInputConnection(TopLevelImpl topLevel, IAndroidInputMethod inputMethod) : base(inputMethod.View, true)
{
_topLevel = topLevel;
_inputMethod = inputMethod;
+ _editable = new InputEditable(_topLevel, _inputMethod, this);
}
- public TextInputMethodSurroundingText SurroundingText { get; set; }
+ public override IEditable Editable => _editable;
- public string ComposingText { get; internal set; }
-
- public ComposingRegion? ComposingRegion { get; internal set; }
-
- public bool IsComposing => !string.IsNullOrEmpty(ComposingText);
- public bool IsCommiting { get; private set; }
+ internal InputEditable InputEditable => _editable;
public override bool SetComposingRegion(int start, int end)
{
- //System.Diagnostics.Debug.WriteLine($"Composing Region: [{start}|{end}] {SurroundingText.Text?.Substring(start, end - start)}");
+ var ret = base.SetComposingRegion(start, end);
- ComposingRegion = new ComposingRegion(start, end);
+ InputEditable.RaiseCompositionChanged();
- return base.SetComposingRegion(start, end);
+ return ret;
}
public override bool SetComposingText(ICharSequence text, int newCursorPosition)
{
var composingText = text.ToString();
- ComposingText = composingText;
-
- _inputMethod.Client?.SetPreeditText(ComposingText);
-
- return base.SetComposingText(text, newCursorPosition);
- }
-
- public override bool FinishComposingText()
- {
- if (!string.IsNullOrEmpty(ComposingText))
+ if (string.IsNullOrEmpty(composingText))
{
- CommitText(ComposingText, ComposingText.Length);
+ return CommitText(text, newCursorPosition);
}
else
{
- ComposingRegion = new ComposingRegion(SurroundingText.CursorOffset, SurroundingText.CursorOffset);
- }
-
- return base.FinishComposingText();
- }
-
- public override ICharSequence GetTextBeforeCursorFormatted(int length, [GeneratedEnum] GetTextFlags flags)
- {
- if (!string.IsNullOrEmpty(SurroundingText.Text) && length > 0)
- {
- var start = System.Math.Max(SurroundingText.CursorOffset - length, 0);
+ var ret = base.SetComposingText(text, newCursorPosition);
- var end = System.Math.Min(start + length - 1, SurroundingText.CursorOffset);
+ InputEditable.RaiseCompositionChanged();
- var text = SurroundingText.Text.Substring(start, end - start);
-
- //System.Diagnostics.Debug.WriteLine($"Text Before: {text}");
-
- return new Java.Lang.String(text);
+ return ret;
}
-
- return null;
}
- public override ICharSequence GetTextAfterCursorFormatted(int length, [GeneratedEnum] GetTextFlags flags)
+ public override bool BeginBatchEdit()
{
- if (!string.IsNullOrEmpty(SurroundingText.Text))
- {
- var start = SurroundingText.CursorOffset;
-
- var end = System.Math.Min(start + length, SurroundingText.Text.Length);
-
- var text = SurroundingText.Text.Substring(start, end - start);
+ _editable.BeginBatchEdit();
- //System.Diagnostics.Debug.WriteLine($"Text After: {text}");
-
- return new Java.Lang.String(text);
- }
-
- return null;
+ return base.BeginBatchEdit();
}
- public override bool CommitText(ICharSequence text, int newCursorPosition)
+ public override bool EndBatchEdit()
{
- IsCommiting = true;
- var committedText = text.ToString();
-
- _inputMethod.Client.SetPreeditText(null);
+ var ret = base.EndBatchEdit();
+ _editable.EndBatchEdit();
- int? start, end;
-
- if(SurroundingText.CursorOffset != SurroundingText.AnchorOffset)
- {
- start = Math.Min(SurroundingText.CursorOffset, SurroundingText.AnchorOffset);
- end = Math.Max(SurroundingText.CursorOffset, SurroundingText.AnchorOffset);
- }
- else if (ComposingRegion != null)
- {
- start = ComposingRegion?.Start;
- end = ComposingRegion?.End;
-
- ComposingRegion = null;
- }
- else
- {
- start = end = _inputMethod.Client.SurroundingText.CursorOffset;
- }
-
- _inputMethod.Client.SelectInSurroundingText((int)start, (int)end);
-
- var time = DateTime.Now.TimeOfDay;
-
- var rawTextEvent = new RawTextInputEventArgs(KeyboardDevice.Instance, (ulong)time.Ticks, _topLevel.InputRoot, committedText);
-
- _topLevel.Input(rawTextEvent);
-
- ComposingText = null;
-
- ComposingRegion = new ComposingRegion(newCursorPosition, newCursorPosition);
-
- return base.CommitText(text, newCursorPosition);
+ return ret;
}
- public override bool DeleteSurroundingText(int beforeLength, int afterLength)
+ public override bool FinishComposingText()
{
- var surroundingText = _inputMethod.Client.SurroundingText;
-
- var selectionStart = surroundingText.CursorOffset;
-
- _inputMethod.Client.SelectInSurroundingText(selectionStart - beforeLength, selectionStart + afterLength);
-
- _inputMethod.View.DispatchKeyEvent(new KeyEvent(KeyEventActions.Down, Keycode.ForwardDel));
-
- surroundingText = _inputMethod.Client.SurroundingText;
-
- selectionStart = surroundingText.CursorOffset;
-
- ComposingRegion = new ComposingRegion(selectionStart, selectionStart);
-
- return base.DeleteSurroundingText(beforeLength, afterLength);
+ var ret = base.FinishComposingText();
+ InputEditable.RaiseCompositionChanged();
+ return ret;
}
- public override bool SetSelection(int start, int end)
+ public override bool CommitText(ICharSequence text, int newCursorPosition)
{
- _inputMethod.Client.SelectInSurroundingText(start, end);
-
- ComposingRegion = new ComposingRegion(start, end);
-
- return base.SetSelection(start, end);
+ var ret = base.CommitText(text, newCursorPosition);
+ InputEditable.RaiseCompositionChanged();
+ return ret;
}
public override bool PerformEditorAction([GeneratedEnum] ImeAction actionCode)
diff --git a/src/Android/Avalonia.Android/Platform/Storage/AndroidStorageItem.cs b/src/Android/Avalonia.Android/Platform/Storage/AndroidStorageItem.cs
index 9838bb06c8..9d6dd46d0e 100644
--- a/src/Android/Avalonia.Android/Platform/Storage/AndroidStorageItem.cs
+++ b/src/Android/Avalonia.Android/Platform/Storage/AndroidStorageItem.cs
@@ -177,11 +177,7 @@ internal sealed class AndroidStorageFile : AndroidStorageItem, IStorageBookmarkF
public AndroidStorageFile(Activity activity, AndroidUri uri) : base(activity, uri, false)
{
}
-
- public bool CanOpenRead => true;
-
- public bool CanOpenWrite => true;
-
+
public Task OpenReadAsync() => Task.FromResult(OpenContentStream(Activity, Uri, false)
?? throw new InvalidOperationException("Failed to open content stream"));
diff --git a/src/Android/Avalonia.Android/Platform/Storage/AndroidStorageProvider.cs b/src/Android/Avalonia.Android/Platform/Storage/AndroidStorageProvider.cs
index f611f50164..e35bde0acd 100644
--- a/src/Android/Avalonia.Android/Platform/Storage/AndroidStorageProvider.cs
+++ b/src/Android/Avalonia.Android/Platform/Storage/AndroidStorageProvider.cs
@@ -37,7 +37,7 @@ internal class AndroidStorageProvider : IStorageProvider
return Task.FromResult(new AndroidStorageFolder(_activity, uri, false));
}
- public async Task TryGetFileFromPath(Uri filePath)
+ public async Task TryGetFileFromPathAsync(Uri filePath)
{
if (filePath is null)
{
@@ -70,7 +70,7 @@ internal class AndroidStorageProvider : IStorageProvider
return new AndroidStorageFile(_activity, androidUri);
}
- public async Task TryGetFolderFromPath(Uri folderPath)
+ public async Task TryGetFolderFromPathAsync(Uri folderPath)
{
if (folderPath is null)
{
@@ -103,7 +103,7 @@ internal class AndroidStorageProvider : IStorageProvider
return new AndroidStorageFolder(_activity, androidUri, false);
}
- public Task TryGetWellKnownFolder(WellKnownFolder wellKnownFolder)
+ public Task TryGetWellKnownFolderAsync(WellKnownFolder wellKnownFolder)
{
var dirCode = wellKnownFolder switch
{
diff --git a/src/Android/Avalonia.Android/PlatformIconLoader.cs b/src/Android/Avalonia.Android/PlatformIconLoader.cs
index 88677a9375..f557685dd2 100644
--- a/src/Android/Avalonia.Android/PlatformIconLoader.cs
+++ b/src/Android/Avalonia.Android/PlatformIconLoader.cs
@@ -3,7 +3,7 @@ using Avalonia.Platform;
namespace Avalonia.Android
{
- class PlatformIconLoader : IPlatformIconLoader
+ internal class PlatformIconLoader : IPlatformIconLoader
{
public IWindowIconImpl LoadIcon(IBitmapImpl bitmap)
{
@@ -29,7 +29,7 @@ namespace Avalonia.Android
}
// Stores the icon created as a stream to support saving even though an icon is never shown
- public class FakeIcon : IWindowIconImpl
+ internal class FakeIcon : IWindowIconImpl
{
private Stream stream = new MemoryStream();
diff --git a/src/Android/Avalonia.Android/Stubs.cs b/src/Android/Avalonia.Android/Stubs.cs
index f36c01dbc8..05638cdf88 100644
--- a/src/Android/Avalonia.Android/Stubs.cs
+++ b/src/Android/Avalonia.Android/Stubs.cs
@@ -4,7 +4,7 @@ using Avalonia.Platform;
namespace Avalonia.Android
{
- class WindowingPlatformStub : IWindowingPlatform
+ internal class WindowingPlatformStub : IWindowingPlatform
{
public IWindowImpl CreateWindow() => throw new NotSupportedException();
@@ -13,7 +13,7 @@ namespace Avalonia.Android
public ITrayIconImpl CreateTrayIcon() => null;
}
- class PlatformIconLoaderStub : IPlatformIconLoader
+ internal class PlatformIconLoaderStub : IPlatformIconLoader
{
public IWindowIconImpl LoadIcon(IBitmapImpl bitmap)
{
@@ -38,7 +38,7 @@ namespace Avalonia.Android
}
}
- public class IconStub : IWindowIconImpl
+ internal class IconStub : IWindowIconImpl
{
private readonly MemoryStream _ms;
diff --git a/src/Avalonia.Base/AvaloniaObject.cs b/src/Avalonia.Base/AvaloniaObject.cs
index 50a7a5c831..d89d6f3690 100644
--- a/src/Avalonia.Base/AvaloniaObject.cs
+++ b/src/Avalonia.Base/AvaloniaObject.cs
@@ -118,7 +118,7 @@ namespace Avalonia
{
_ = property ?? throw new ArgumentNullException(nameof(property));
VerifyAccess();
- _values.ClearLocalValue(property);
+ _values.ClearValue(property);
}
///
@@ -152,7 +152,7 @@ namespace Avalonia
property = property ?? throw new ArgumentNullException(nameof(property));
VerifyAccess();
- _values.ClearLocalValue(property);
+ _values.ClearValue(property);
}
///
@@ -277,8 +277,8 @@ namespace Avalonia
/// The property.
/// True if the property is set, otherwise false.
///
- /// Checks whether a value is assigned to the property, or that there is a binding to the
- /// property that is producing a value other than .
+ /// Returns true if is a styled property which has a value
+ /// assigned to it or a binding targeting it; otherwise false.
///
public bool IsSet(AvaloniaProperty property)
{
@@ -329,7 +329,7 @@ namespace Avalonia
if (value is UnsetValueType)
{
if (priority == BindingPriority.LocalValue)
- _values.ClearLocalValue(property);
+ _values.ClearValue(property);
}
else if (value is not DoNothingType)
{
@@ -355,6 +355,57 @@ namespace Avalonia
SetDirectValueUnchecked(property, value);
}
+ ///
+ /// Sets the value of a dependency property without changing its value source.
+ ///
+ /// The property.
+ /// The value.
+ ///
+ /// This method is used by a component that programmatically sets the value of one of its
+ /// own properties without disabling an application's declared use of the property. The
+ /// method changes the effective value of the property, but existing data bindings and
+ /// styles will continue to work.
+ ///
+ /// The new value will have the property's current , even if
+ /// that priority is or
+ /// .
+ ///
+ public void SetCurrentValue(AvaloniaProperty property, object? value) =>
+ property.RouteSetCurrentValue(this, value);
+
+ ///
+ /// Sets the value of a dependency property without changing its value source.
+ ///
+ /// The type of the property.
+ /// The property.
+ /// The value.
+ ///
+ /// This method is used by a component that programmatically sets the value of one of its
+ /// own properties without disabling an application's declared use of the property. The
+ /// method changes the effective value of the property, but existing data bindings and
+ /// styles will continue to work.
+ ///
+ /// The new value will have the property's current , even if
+ /// that priority is or
+ /// .
+ ///
+ public void SetCurrentValue(StyledProperty property, T value)
+ {
+ _ = property ?? throw new ArgumentNullException(nameof(property));
+ VerifyAccess();
+
+ LogPropertySet(property, value, BindingPriority.LocalValue);
+
+ if (value is UnsetValueType)
+ {
+ _values.ClearValue(property);
+ }
+ else if (value is not DoNothingType)
+ {
+ _values.SetCurrentValue(property, value);
+ }
+ }
+
///
/// Binds a to an observable.
///
@@ -547,7 +598,8 @@ namespace Avalonia
property,
GetValue(property),
BindingPriority.LocalValue,
- null);
+ null,
+ false);
}
return _values.GetDiagnostic(property);
@@ -612,14 +664,12 @@ namespace Avalonia
/// The property that has changed.
/// The old property value.
/// The new property value.
- /// The priority of the binding that produced the value.
protected void RaisePropertyChanged(
DirectPropertyBase property,
- Optional oldValue,
- BindingValue newValue,
- BindingPriority priority = BindingPriority.LocalValue)
+ T oldValue,
+ T newValue)
{
- RaisePropertyChanged(property, oldValue, newValue, priority, true);
+ RaisePropertyChanged(property, oldValue, newValue, BindingPriority.LocalValue, true);
}
///
@@ -668,7 +718,7 @@ namespace Avalonia
///
/// True if the value changed, otherwise false.
///
- protected bool SetAndRaise(AvaloniaProperty property, ref T field, T value)
+ protected bool SetAndRaise(DirectPropertyBase property, ref T field, T value)
{
VerifyAccess();
diff --git a/src/Avalonia.Base/AvaloniaProperty.cs b/src/Avalonia.Base/AvaloniaProperty.cs
index 5db4d81f03..45ab293a89 100644
--- a/src/Avalonia.Base/AvaloniaProperty.cs
+++ b/src/Avalonia.Base/AvaloniaProperty.cs
@@ -225,13 +225,8 @@ namespace Avalonia
/// The default value of the property.
/// Whether the property inherits its value.
/// The default binding mode for the property.
- /// A value validation callback.
+ /// A value validation callback.
/// A value coercion callback.
- ///
- /// A method that gets called before and after the property starts being notified on an
- /// object; the bool argument will be true before and false afterwards. This callback is
- /// intended to support IsDataContextChanging.
- ///
/// A
public static StyledProperty Register(
string name,
@@ -239,8 +234,40 @@ namespace Avalonia
bool inherits = false,
BindingMode defaultBindingMode = BindingMode.OneWay,
Func? validate = null,
- Func? coerce = null,
- Action? notifying = null)
+ Func? coerce = null)
+ where TOwner : AvaloniaObject
+ {
+ _ = name ?? throw new ArgumentNullException(nameof(name));
+
+ var metadata = new StyledPropertyMetadata(
+ defaultValue,
+ defaultBindingMode: defaultBindingMode,
+ coerce: coerce);
+
+ var result = new StyledProperty(
+ name,
+ typeof(TOwner),
+ metadata,
+ inherits,
+ validate);
+ AvaloniaPropertyRegistry.Instance.Register(typeof(TOwner), result);
+ return result;
+ }
+
+ ///
+ ///
+ /// A method that gets called before and after the property starts being notified on an
+ /// object; the bool argument will be true before and false afterwards. This callback is
+ /// intended to support IsDataContextChanging.
+ ///
+ internal static StyledProperty Register(
+ string name,
+ TValue defaultValue,
+ bool inherits,
+ BindingMode defaultBindingMode,
+ Func? validate,
+ Func? coerce,
+ Action? notifying)
where TOwner : AvaloniaObject
{
_ = name ?? throw new ArgumentNullException(nameof(name));
@@ -496,6 +523,13 @@ namespace Avalonia
object? value,
BindingPriority priority);
+ ///
+ /// Routes an untyped SetCurrentValue call to a typed call.
+ ///
+ /// The object instance.
+ /// The value.
+ internal abstract void RouteSetCurrentValue(AvaloniaObject o, object? value);
+
///
/// Routes an untyped Bind call to a typed call.
///
diff --git a/src/Avalonia.Base/Controls/ResourceNodeExtensions.cs b/src/Avalonia.Base/Controls/ResourceNodeExtensions.cs
index 4b0bab0c92..8aed1545a5 100644
--- a/src/Avalonia.Base/Controls/ResourceNodeExtensions.cs
+++ b/src/Avalonia.Base/Controls/ResourceNodeExtensions.cs
@@ -138,18 +138,18 @@ namespace Avalonia.Controls
protected override void Initialize()
{
_target.ResourcesChanged += ResourcesChanged;
- if (_target is StyledElement themeStyleable)
+ if (_target is IThemeVariantHost themeVariantHost)
{
- themeStyleable.PropertyChanged += PropertyChanged;
+ themeVariantHost.ActualThemeVariantChanged += ActualThemeVariantChanged;
}
}
protected override void Deinitialize()
{
_target.ResourcesChanged -= ResourcesChanged;
- if (_target is StyledElement themeStyleable)
+ if (_target is IThemeVariantHost themeVariantHost)
{
- themeStyleable.PropertyChanged -= PropertyChanged;
+ themeVariantHost.ActualThemeVariantChanged -= ActualThemeVariantChanged;
}
}
@@ -163,18 +163,15 @@ namespace Avalonia.Controls
PublishNext(GetValue());
}
- private void PropertyChanged(object? sender, AvaloniaPropertyChangedEventArgs e)
+ private void ActualThemeVariantChanged(object? sender, EventArgs e)
{
- if (e.Property == StyledElement.ActualThemeVariantProperty)
- {
- PublishNext(GetValue());
- }
+ PublishNext(GetValue());
}
private object? GetValue()
{
- if (_target is not StyledElement themeStyleable
- || !_target.TryFindResource(_key, themeStyleable.ActualThemeVariant, out var value))
+ if (_target is not IThemeVariantHost themeVariantHost
+ || !_target.TryFindResource(_key, themeVariantHost.ActualThemeVariant, out var value))
{
value = _target.FindResource(_key) ?? AvaloniaProperty.UnsetValue;
}
@@ -236,9 +233,9 @@ namespace Avalonia.Controls
{
_owner.ResourcesChanged -= ResourcesChanged;
}
- if (_owner is StyledElement styleable)
+ if (_owner is IThemeVariantHost themeVariantHost)
{
- styleable.PropertyChanged += PropertyChanged;
+ themeVariantHost.ActualThemeVariantChanged += ActualThemeVariantChanged;
}
_owner = _target.Owner;
@@ -247,20 +244,18 @@ namespace Avalonia.Controls
{
_owner.ResourcesChanged += ResourcesChanged;
}
- if (_owner is StyledElement styleable2)
+ if (_owner is IThemeVariantHost themeVariantHost2)
{
- styleable2.PropertyChanged += PropertyChanged;
+ themeVariantHost2.ActualThemeVariantChanged -= ActualThemeVariantChanged;
}
+
PublishNext();
}
- private void PropertyChanged(object? sender, AvaloniaPropertyChangedEventArgs e)
+ private void ActualThemeVariantChanged(object? sender, EventArgs e)
{
- if (e.Property == StyledElement.ActualThemeVariantProperty)
- {
- PublishNext();
- }
+ PublishNext();
}
private void ResourcesChanged(object? sender, ResourcesChangedEventArgs e)
@@ -270,8 +265,8 @@ namespace Avalonia.Controls
private object? GetValue()
{
- if (!(_target.Owner is StyledElement themeStyleable)
- || !_target.Owner.TryFindResource(_key, themeStyleable.ActualThemeVariant, out var value))
+ if (!(_target.Owner is IThemeVariantHost themeVariantHost)
+ || !_target.Owner.TryFindResource(_key, themeVariantHost.ActualThemeVariant, out var value))
{
value = _target.Owner?.FindResource(_key) ?? AvaloniaProperty.UnsetValue;
}
diff --git a/src/Avalonia.Base/Data/Core/IndexerNodeBase.cs b/src/Avalonia.Base/Data/Core/IndexerNodeBase.cs
index 9ec256225b..57e4fa4a8e 100644
--- a/src/Avalonia.Base/Data/Core/IndexerNodeBase.cs
+++ b/src/Avalonia.Base/Data/Core/IndexerNodeBase.cs
@@ -22,7 +22,7 @@ namespace Avalonia.Data.Core
if (target is INotifyPropertyChanged inpc)
{
- WeakEvents.PropertyChanged.Subscribe(inpc, this);
+ WeakEvents.ThreadSafePropertyChanged.Subscribe(inpc, this);
}
ValueChanged(GetValue(target));
@@ -39,7 +39,7 @@ namespace Avalonia.Data.Core
if (target is INotifyPropertyChanged inpc)
{
- WeakEvents.PropertyChanged.Unsubscribe(inpc, this);
+ WeakEvents.ThreadSafePropertyChanged.Unsubscribe(inpc, this);
}
}
}
diff --git a/src/Avalonia.Base/Data/Core/Plugins/InpcPropertyAccessorPlugin.cs b/src/Avalonia.Base/Data/Core/Plugins/InpcPropertyAccessorPlugin.cs
index 7c2caf02b4..e8e3e6d509 100644
--- a/src/Avalonia.Base/Data/Core/Plugins/InpcPropertyAccessorPlugin.cs
+++ b/src/Avalonia.Base/Data/Core/Plugins/InpcPropertyAccessorPlugin.cs
@@ -160,7 +160,7 @@ namespace Avalonia.Data.Core.Plugins
var inpc = GetReferenceTarget() as INotifyPropertyChanged;
if (inpc != null)
- WeakEvents.PropertyChanged.Unsubscribe(inpc, this);
+ WeakEvents.ThreadSafePropertyChanged.Unsubscribe(inpc, this);
}
private object? GetReferenceTarget()
@@ -185,7 +185,7 @@ namespace Avalonia.Data.Core.Plugins
var inpc = GetReferenceTarget() as INotifyPropertyChanged;
if (inpc != null)
- WeakEvents.PropertyChanged.Subscribe(inpc, this);
+ WeakEvents.ThreadSafePropertyChanged.Subscribe(inpc, this);
}
}
}
diff --git a/src/Avalonia.Base/Diagnostics/AvaloniaPropertyValue.cs b/src/Avalonia.Base/Diagnostics/AvaloniaPropertyValue.cs
index 4189fd5234..0b3e62f1cc 100644
--- a/src/Avalonia.Base/Diagnostics/AvaloniaPropertyValue.cs
+++ b/src/Avalonia.Base/Diagnostics/AvaloniaPropertyValue.cs
@@ -3,28 +3,23 @@ using Avalonia.Data;
namespace Avalonia.Diagnostics
{
///
- /// Holds diagnostic-related information about the value of a
- /// on a .
+ /// Holds diagnostic-related information about the value of an
+ /// on an .
///
public class AvaloniaPropertyValue
{
- ///
- /// Initializes a new instance of the class.
- ///
- /// The property.
- /// The current property value.
- /// The priority of the current value.
- /// A diagnostic string.
- public AvaloniaPropertyValue(
+ internal AvaloniaPropertyValue(
AvaloniaProperty property,
object? value,
BindingPriority priority,
- string? diagnostic)
+ string? diagnostic,
+ bool isOverriddenCurrentValue)
{
Property = property;
Value = value;
Priority = priority;
Diagnostic = diagnostic;
+ IsOverriddenCurrentValue = isOverriddenCurrentValue;
}
///
@@ -46,5 +41,11 @@ namespace Avalonia.Diagnostics
/// Gets a diagnostic string.
///
public string? Diagnostic { get; }
+
+ ///
+ /// Gets a value indicating whether the was overridden by a call to
+ /// .
+ ///
+ public bool IsOverriddenCurrentValue { get; }
}
}
diff --git a/src/Avalonia.Base/DirectPropertyBase.cs b/src/Avalonia.Base/DirectPropertyBase.cs
index 9ee1eee0fa..94dfaaab01 100644
--- a/src/Avalonia.Base/DirectPropertyBase.cs
+++ b/src/Avalonia.Base/DirectPropertyBase.cs
@@ -152,6 +152,11 @@ namespace Avalonia
return null;
}
+ internal override void RouteSetCurrentValue(AvaloniaObject o, object? value)
+ {
+ RouteSetValue(o, value, BindingPriority.LocalValue);
+ }
+
///
/// Routes an untyped Bind call to a typed call.
///
diff --git a/src/Avalonia.Base/Input/Platform/IClipboard.cs b/src/Avalonia.Base/Input/Platform/IClipboard.cs
index bf2a5a8602..3de352fc4f 100644
--- a/src/Avalonia.Base/Input/Platform/IClipboard.cs
+++ b/src/Avalonia.Base/Input/Platform/IClipboard.cs
@@ -6,9 +6,9 @@ namespace Avalonia.Input.Platform
[NotClientImplementable]
public interface IClipboard
{
- Task GetTextAsync();
+ Task GetTextAsync();
- Task SetTextAsync(string text);
+ Task SetTextAsync(string? text);
Task ClearAsync();
@@ -16,6 +16,6 @@ namespace Avalonia.Input.Platform
Task GetFormatsAsync();
- Task
void SetPreeditText(string? text);
+ ///
+ /// Sets the current composing region. This doesn't remove the composing text from the commited text.
+ ///
+ void SetComposingRegion(TextRange? region);
+
///
/// Indicates if text input client is capable of providing the text around the cursor
///
@@ -43,6 +49,11 @@ namespace Avalonia.Input.TextInput
///
event EventHandler? SurroundingTextChanged;
+ ///
+ /// Gets or sets a platform editable. Text and selection changes made in the editable are forwarded to the IM client.
+ ///
+ ITextEditable? TextEditable { get; set; }
+
void SelectInSurroundingText(int start, int end);
}
diff --git a/src/Avalonia.Base/Layout/LayoutInformation.cs b/src/Avalonia.Base/Layout/LayoutInformation.cs
new file mode 100644
index 0000000000..9b821053a2
--- /dev/null
+++ b/src/Avalonia.Base/Layout/LayoutInformation.cs
@@ -0,0 +1,27 @@
+namespace Avalonia.Layout;
+
+///
+/// Provides access to layout information of a control.
+///
+public static class LayoutInformation
+{
+ ///
+ /// Gets the available size constraint passed in the previous layout pass.
+ ///
+ /// The control.
+ /// Previous control measure constraint, if any.
+ public static Size? GetPreviousMeasureConstraint(Layoutable control)
+ {
+ return control.PreviousMeasure;
+ }
+
+ ///
+ /// Gets the control bounds used in the previous layout arrange pass.
+ ///
+ /// The control.
+ /// Previous control arrange bounds, if any.
+ public static Rect? GetPreviousArrangeBounds(Layoutable control)
+ {
+ return control.PreviousArrange;
+ }
+}
diff --git a/src/Avalonia.Base/Layout/Layoutable.cs b/src/Avalonia.Base/Layout/Layoutable.cs
index c45fc16929..ea84dc84bd 100644
--- a/src/Avalonia.Base/Layout/Layoutable.cs
+++ b/src/Avalonia.Base/Layout/Layoutable.cs
@@ -323,6 +323,9 @@ namespace Avalonia.Layout
set { SetValue(UseLayoutRoundingProperty, value); }
}
+ ///
+ /// Gets the available size passed in the previous layout pass, if any.
+ ///
internal Size? PreviousMeasure => _previousMeasure;
///
diff --git a/src/Avalonia.Base/Media/Color.cs b/src/Avalonia.Base/Media/Color.cs
index ab89177295..74e70b2a14 100644
--- a/src/Avalonia.Base/Media/Color.cs
+++ b/src/Avalonia.Base/Media/Color.cs
@@ -331,7 +331,7 @@ namespace Avalonia.Media
///
/// Parses the given string representing a CSS color value into a new .
///
- private static bool TryParseCssFormat(string s, out Color color)
+ private static bool TryParseCssFormat(string? s, out Color color)
{
bool prefixMatched = false;
diff --git a/src/Avalonia.Base/Media/DrawingContext.cs b/src/Avalonia.Base/Media/DrawingContext.cs
index 622181dba0..a37fa6fd32 100644
--- a/src/Avalonia.Base/Media/DrawingContext.cs
+++ b/src/Avalonia.Base/Media/DrawingContext.cs
@@ -361,11 +361,12 @@ namespace Avalonia.Media
/// Pushes an opacity value.
///
/// The opacity.
+ /// The bounds.
/// A disposable used to undo the opacity.
- public PushedState PushOpacity(double opacity)
+ public PushedState PushOpacity(double opacity, Rect bounds)
//TODO: Eliminate platform-specific push opacity call
{
- PlatformImpl.PushOpacity(opacity);
+ PlatformImpl.PushOpacity(opacity, bounds);
return new PushedState(this, PushedState.PushedStateType.Opacity);
}
diff --git a/src/Avalonia.Base/Media/DrawingGroup.cs b/src/Avalonia.Base/Media/DrawingGroup.cs
index b7abda2c61..7b02649b6c 100644
--- a/src/Avalonia.Base/Media/DrawingGroup.cs
+++ b/src/Avalonia.Base/Media/DrawingGroup.cs
@@ -74,10 +74,12 @@ namespace Avalonia.Media
public override void Draw(DrawingContext context)
{
+ var bounds = GetBounds();
+
using (context.PushPreTransform(Transform?.Value ?? Matrix.Identity))
- using (context.PushOpacity(Opacity))
+ using (context.PushOpacity(Opacity, bounds))
using (ClipGeometry != null ? context.PushGeometryClip(ClipGeometry) : default)
- using (OpacityMask != null ? context.PushOpacityMask(OpacityMask, GetBounds()) : default)
+ using (OpacityMask != null ? context.PushOpacityMask(OpacityMask, bounds) : default)
{
foreach (var drawing in Children)
{
@@ -284,7 +286,7 @@ namespace Avalonia.Media
throw new NotImplementedException();
}
- public void PushOpacity(double opacity)
+ public void PushOpacity(double opacity, Rect bounds)
{
throw new NotImplementedException();
}
diff --git a/src/Avalonia.Base/Media/IImageBrush.cs b/src/Avalonia.Base/Media/IImageBrush.cs
index 732f1957d0..07fd2d56fa 100644
--- a/src/Avalonia.Base/Media/IImageBrush.cs
+++ b/src/Avalonia.Base/Media/IImageBrush.cs
@@ -12,6 +12,6 @@ namespace Avalonia.Media
///
/// Gets the image to draw.
///
- IBitmap Source { get; }
+ IBitmap? Source { get; }
}
}
diff --git a/src/Avalonia.Base/Media/ImageBrush.cs b/src/Avalonia.Base/Media/ImageBrush.cs
index 2f2a0fb627..718ebf1686 100644
--- a/src/Avalonia.Base/Media/ImageBrush.cs
+++ b/src/Avalonia.Base/Media/ImageBrush.cs
@@ -11,8 +11,8 @@ namespace Avalonia.Media
///
/// Defines the property.
///
- public static readonly StyledProperty SourceProperty =
- AvaloniaProperty.Register(nameof(Source));
+ public static readonly StyledProperty SourceProperty =
+ AvaloniaProperty.Register(nameof(Source));
static ImageBrush()
{
@@ -30,7 +30,7 @@ namespace Avalonia.Media
/// Initializes a new instance of the class.
///
/// The image to draw.
- public ImageBrush(IBitmap source)
+ public ImageBrush(IBitmap? source)
{
Source = source;
}
@@ -38,7 +38,7 @@ namespace Avalonia.Media
///
/// Gets or sets the image to draw.
///
- public IBitmap Source
+ public IBitmap? Source
{
get { return GetValue(SourceProperty); }
set { SetValue(SourceProperty, value); }
diff --git a/src/Avalonia.Base/Media/ImmediateDrawingContext.cs b/src/Avalonia.Base/Media/ImmediateDrawingContext.cs
index 7d9534c414..2564d89bac 100644
--- a/src/Avalonia.Base/Media/ImmediateDrawingContext.cs
+++ b/src/Avalonia.Base/Media/ImmediateDrawingContext.cs
@@ -281,11 +281,12 @@ namespace Avalonia.Media
/// Pushes an opacity value.
///
/// The opacity.
+ /// The bounds.
/// A disposable used to undo the opacity.
- public PushedState PushOpacity(double opacity)
+ public PushedState PushOpacity(double opacity, Rect bounds)
//TODO: Eliminate platform-specific push opacity call
{
- PlatformImpl.PushOpacity(opacity);
+ PlatformImpl.PushOpacity(opacity, bounds);
return new PushedState(this, PushedState.PushedStateType.Opacity);
}
diff --git a/src/Avalonia.Base/Media/Immutable/ImmutableImageBrush.cs b/src/Avalonia.Base/Media/Immutable/ImmutableImageBrush.cs
index f9892bf60c..668a907fdf 100644
--- a/src/Avalonia.Base/Media/Immutable/ImmutableImageBrush.cs
+++ b/src/Avalonia.Base/Media/Immutable/ImmutableImageBrush.cs
@@ -24,13 +24,13 @@ namespace Avalonia.Media.Immutable
/// The tile mode.
/// The bitmap interpolation mode.
public ImmutableImageBrush(
- IBitmap source,
+ IBitmap? source,
AlignmentX alignmentX = AlignmentX.Center,
AlignmentY alignmentY = AlignmentY.Center,
RelativeRect? destinationRect = null,
double opacity = 1,
ImmutableTransform? transform = null,
- RelativePoint transformOrigin = new RelativePoint(),
+ RelativePoint transformOrigin = default,
RelativeRect? sourceRect = null,
Stretch stretch = Stretch.Uniform,
TileMode tileMode = TileMode.None,
@@ -61,6 +61,6 @@ namespace Avalonia.Media.Immutable
}
///
- public IBitmap Source { get; }
+ public IBitmap? Source { get; }
}
}
diff --git a/src/Avalonia.Base/Media/Immutable/ImmutableVisualBrush.cs b/src/Avalonia.Base/Media/Immutable/ImmutableVisualBrush.cs
index 0b625080e3..e9086eee37 100644
--- a/src/Avalonia.Base/Media/Immutable/ImmutableVisualBrush.cs
+++ b/src/Avalonia.Base/Media/Immutable/ImmutableVisualBrush.cs
@@ -24,7 +24,7 @@ namespace Avalonia.Media.Immutable
/// The tile mode.
/// Controls the quality of interpolation.
public ImmutableVisualBrush(
- Visual visual,
+ Visual? visual,
AlignmentX alignmentX = AlignmentX.Center,
AlignmentY alignmentY = AlignmentY.Center,
RelativeRect? destinationRect = null,
diff --git a/src/Avalonia.Base/Platform/DefaultPlatformSettings.cs b/src/Avalonia.Base/Platform/DefaultPlatformSettings.cs
index b5e7298b7e..08fcdb50aa 100644
--- a/src/Avalonia.Base/Platform/DefaultPlatformSettings.cs
+++ b/src/Avalonia.Base/Platform/DefaultPlatformSettings.cs
@@ -37,7 +37,7 @@ namespace Avalonia.Platform
};
}
- public event EventHandler? ColorValuesChanged;
+ public virtual event EventHandler? ColorValuesChanged;
protected void OnColorValuesChanged(PlatformColorValues colorValues)
{
diff --git a/src/Avalonia.Base/Platform/IDrawingContextImpl.cs b/src/Avalonia.Base/Platform/IDrawingContextImpl.cs
index 8509067cd0..8962bc1586 100644
--- a/src/Avalonia.Base/Platform/IDrawingContextImpl.cs
+++ b/src/Avalonia.Base/Platform/IDrawingContextImpl.cs
@@ -128,7 +128,7 @@ namespace Avalonia.Platform
/// Pushes an opacity value.
///
/// The opacity.
- void PushOpacity(double opacity);
+ void PushOpacity(double opacity, Rect bounds);
///
/// Pops the latest pushed opacity value.
diff --git a/src/Avalonia.Base/Platform/IGeometryImpl.cs b/src/Avalonia.Base/Platform/IGeometryImpl.cs
index 5826cfb2ff..d1964bf07e 100644
--- a/src/Avalonia.Base/Platform/IGeometryImpl.cs
+++ b/src/Avalonia.Base/Platform/IGeometryImpl.cs
@@ -1,3 +1,4 @@
+using System.Diagnostics.CodeAnalysis;
using Avalonia.Media;
using Avalonia.Metadata;
@@ -47,7 +48,7 @@ namespace Avalonia.Platform
/// The stroke to use.
/// The point.
/// true if the geometry contains the point; otherwise, false.
- bool StrokeContains(IPen pen, Point point);
+ bool StrokeContains(IPen? pen, Point point);
///
/// Makes a clone of the geometry with the specified transform.
@@ -87,6 +88,7 @@ namespace Avalonia.Platform
/// If ture, the resulting snipped path will start with a BeginFigure call.
/// The resulting snipped path.
/// If the snipping operation is successful.
- bool TryGetSegment(double startDistance, double stopDistance, bool startOnBeginFigure, out IGeometryImpl segmentGeometry);
+ bool TryGetSegment(double startDistance, double stopDistance, bool startOnBeginFigure,
+ [NotNullWhen(true)] out IGeometryImpl? segmentGeometry);
}
}
diff --git a/src/Avalonia.Base/Platform/IPlatformBehaviorInhibition.cs b/src/Avalonia.Base/Platform/IPlatformBehaviorInhibition.cs
new file mode 100644
index 0000000000..227e65c08d
--- /dev/null
+++ b/src/Avalonia.Base/Platform/IPlatformBehaviorInhibition.cs
@@ -0,0 +1,12 @@
+using System.Threading.Tasks;
+
+namespace Avalonia.Platform
+{
+ ///
+ /// Allows to inhibit platform specific behavior.
+ ///
+ public interface IPlatformBehaviorInhibition
+ {
+ Task SetInhibitAppSleep(bool inhibitAppSleep, string reason);
+ }
+}
diff --git a/src/Avalonia.Base/Platform/IPlatformThreadingInterface.cs b/src/Avalonia.Base/Platform/IPlatformThreadingInterface.cs
index bf18a7da5b..3dbc7c1bb2 100644
--- a/src/Avalonia.Base/Platform/IPlatformThreadingInterface.cs
+++ b/src/Avalonia.Base/Platform/IPlatformThreadingInterface.cs
@@ -26,6 +26,6 @@ namespace Avalonia.Platform
bool CurrentThreadIsLoopThread { get; }
- event Action Signaled;
+ event Action? Signaled;
}
}
diff --git a/src/Avalonia.Base/Platform/Internal/AssemblyDescriptor.cs b/src/Avalonia.Base/Platform/Internal/AssemblyDescriptor.cs
index d1a803fefb..6a577c204c 100644
--- a/src/Avalonia.Base/Platform/Internal/AssemblyDescriptor.cs
+++ b/src/Avalonia.Base/Platform/Internal/AssemblyDescriptor.cs
@@ -1,4 +1,5 @@
-using System.Collections.Generic;
+using System;
+using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Reflection;
@@ -18,7 +19,7 @@ internal class AssemblyDescriptor : IAssemblyDescriptor
{
public AssemblyDescriptor(Assembly assembly)
{
- Assembly = assembly;
+ Assembly = assembly ?? throw new ArgumentNullException(nameof(assembly));
Resources = assembly.GetManifestResourceNames()
.ToDictionary(n => n, n => (IAssetDescriptor)new AssemblyResourceDescriptor(assembly, n));
Name = assembly.GetName().Name;
diff --git a/src/Avalonia.Base/Platform/Storage/FileIO/BclStorageFile.cs b/src/Avalonia.Base/Platform/Storage/FileIO/BclStorageFile.cs
index a4005d4f5f..5bf9ff9d9a 100644
--- a/src/Avalonia.Base/Platform/Storage/FileIO/BclStorageFile.cs
+++ b/src/Avalonia.Base/Platform/Storage/FileIO/BclStorageFile.cs
@@ -18,11 +18,7 @@ internal class BclStorageFile : IStorageBookmarkFile
}
public FileInfo FileInfo { get; }
-
- public bool CanOpenRead => true;
-
- public bool CanOpenWrite => true;
-
+
public string Name => FileInfo.Name;
public virtual bool CanBookmark => true;
diff --git a/src/Avalonia.Base/Platform/Storage/FileIO/BclStorageProvider.cs b/src/Avalonia.Base/Platform/Storage/FileIO/BclStorageProvider.cs
index ee169d62a5..34409f5fda 100644
--- a/src/Avalonia.Base/Platform/Storage/FileIO/BclStorageProvider.cs
+++ b/src/Avalonia.Base/Platform/Storage/FileIO/BclStorageProvider.cs
@@ -34,7 +34,7 @@ internal abstract class BclStorageProvider : IStorageProvider
: Task.FromResult(null);
}
- public virtual Task TryGetFileFromPath(Uri filePath)
+ public virtual Task TryGetFileFromPathAsync(Uri filePath)
{
if (filePath.IsAbsoluteUri)
{
@@ -48,7 +48,7 @@ internal abstract class BclStorageProvider : IStorageProvider
return Task.FromResult(null);
}
- public virtual Task TryGetFolderFromPath(Uri folderPath)
+ public virtual Task TryGetFolderFromPathAsync(Uri folderPath)
{
if (folderPath.IsAbsoluteUri)
{
@@ -62,7 +62,7 @@ internal abstract class BclStorageProvider : IStorageProvider
return Task.FromResult(null);
}
- public virtual Task TryGetWellKnownFolder(WellKnownFolder wellKnownFolder)
+ public virtual Task TryGetWellKnownFolderAsync(WellKnownFolder wellKnownFolder)
{
// Note, this BCL API returns different values depending on the .NET version.
// We should also document it.
diff --git a/src/Avalonia.Base/Platform/Storage/FilePickerFileType.cs b/src/Avalonia.Base/Platform/Storage/FilePickerFileType.cs
index 75076e2bb8..7b0446e224 100644
--- a/src/Avalonia.Base/Platform/Storage/FilePickerFileType.cs
+++ b/src/Avalonia.Base/Platform/Storage/FilePickerFileType.cs
@@ -1,4 +1,6 @@
using System.Collections.Generic;
+using System.IO;
+using System.Linq;
namespace Avalonia.Platform.Storage;
@@ -7,9 +9,9 @@ namespace Avalonia.Platform.Storage;
///
public sealed class FilePickerFileType
{
- public FilePickerFileType(string name)
+ public FilePickerFileType(string? name)
{
- Name = name;
+ Name = name ?? string.Empty;
}
///
@@ -21,7 +23,7 @@ public sealed class FilePickerFileType
/// List of extensions in GLOB format. I.e. "*.png" or "*.*".
///
///
- /// Used on Windows and Linux systems.
+ /// Used on Windows, Linux and Browser platforms.
///
public IReadOnlyList? Patterns { get; set; }
@@ -29,7 +31,7 @@ public sealed class FilePickerFileType
/// List of extensions in MIME format.
///
///
- /// Used on Android, Browser and Linux systems.
+ /// Used on Android, Linux and Browser platforms.
///
public IReadOnlyList? MimeTypes { get; set; }
@@ -41,4 +43,14 @@ public sealed class FilePickerFileType
/// See https://developer.apple.com/documentation/uniformtypeidentifiers/system_declared_uniform_type_identifiers.
///
public IReadOnlyList? AppleUniformTypeIdentifiers { get; set; }
+
+ internal IReadOnlyList? TryGetExtensions()
+ {
+ // Converts random glob pattern to a simple extension name.
+ // GetExtension should be sufficient here.
+ // Only exception is "*.*proj" patterns that should be filtered as well.
+ return Patterns?.Select(Path.GetExtension)
+ .Where(e => !string.IsNullOrEmpty(e) && !e.Contains('*') && e.StartsWith("."))
+ .ToArray()!;
+ }
}
diff --git a/src/Avalonia.Base/Platform/Storage/IStorageFile.cs b/src/Avalonia.Base/Platform/Storage/IStorageFile.cs
index 4aa84e3ec4..2a0ce15279 100644
--- a/src/Avalonia.Base/Platform/Storage/IStorageFile.cs
+++ b/src/Avalonia.Base/Platform/Storage/IStorageFile.cs
@@ -10,22 +10,12 @@ namespace Avalonia.Platform.Storage;
[NotClientImplementable]
public interface IStorageFile : IStorageItem
{
- ///
- /// Returns true, if file is readable.
- ///
- bool CanOpenRead { get; }
-
///
/// Opens a stream for read access.
///
///
Task OpenReadAsync();
-
- ///
- /// Returns true, if file is writeable.
- ///
- bool CanOpenWrite { get; }
-
+
///
/// Opens stream for writing to the file.
///
diff --git a/src/Avalonia.Base/Platform/Storage/IStorageProvider.cs b/src/Avalonia.Base/Platform/Storage/IStorageProvider.cs
index 6922151e02..9d3c961e51 100644
--- a/src/Avalonia.Base/Platform/Storage/IStorageProvider.cs
+++ b/src/Avalonia.Base/Platform/Storage/IStorageProvider.cs
@@ -66,7 +66,7 @@ public interface IStorageProvider
/// It also might ask user for the permission, and throw an exception if it was denied.
///
/// File or null if it doesn't exist.
- Task TryGetFileFromPath(Uri filePath);
+ Task TryGetFileFromPathAsync(Uri filePath);
///
/// Attempts to read folder from the file-system by its path.
@@ -78,12 +78,12 @@ public interface IStorageProvider
/// It also might ask user for the permission, and throw an exception if it was denied.
///
/// Folder or null if it doesn't exist.
- Task TryGetFolderFromPath(Uri folderPath);
+ Task TryGetFolderFromPathAsync(Uri folderPath);
///
/// Attempts to read folder from the file-system by its path
///
/// Well known folder identifier.
/// Folder or null if it doesn't exist.
- Task TryGetWellKnownFolder(WellKnownFolder wellKnownFolder);
+ Task TryGetWellKnownFolderAsync(WellKnownFolder wellKnownFolder);
}
diff --git a/src/Avalonia.Base/Platform/Storage/StorageProviderExtensions.cs b/src/Avalonia.Base/Platform/Storage/StorageProviderExtensions.cs
index c7772d1196..6f8b945cd6 100644
--- a/src/Avalonia.Base/Platform/Storage/StorageProviderExtensions.cs
+++ b/src/Avalonia.Base/Platform/Storage/StorageProviderExtensions.cs
@@ -8,48 +8,47 @@ namespace Avalonia.Platform.Storage;
///
public static class StorageProviderExtensions
{
- ///
- public static Task TryGetFileFromPath(this IStorageProvider provider, string filePath)
+ ///
+ public static Task TryGetFileFromPathAsync(this IStorageProvider provider, string filePath)
{
- return provider.TryGetFileFromPath(StorageProviderHelpers.FilePathToUri(filePath));
+ return provider.TryGetFileFromPathAsync(StorageProviderHelpers.FilePathToUri(filePath));
}
- ///
- public static Task TryGetFolderFromPath(this IStorageProvider provider, string folderPath)
+ ///
+ public static Task TryGetFolderFromPathAsync(this IStorageProvider provider, string folderPath)
{
- return provider.TryGetFolderFromPath(StorageProviderHelpers.FilePathToUri(folderPath));
+ return provider.TryGetFolderFromPathAsync(StorageProviderHelpers.FilePathToUri(folderPath));
}
- internal static string? TryGetFullPath(this IStorageFolder folder)
+ ///
+ /// Gets the local file system path of the item as a string.
+ ///
+ /// Storage folder or file.
+ /// Full local path to the folder or file if possible, otherwise null.
+ ///
+ /// Android platform usually uses "content:" virtual file paths
+ /// and Browser platform has isolated access without full paths,
+ /// so on these platforms this method will return null.
+ ///
+ public static string? TryGetLocalPath(this IStorageItem item)
{
// We can avoid double escaping of the path by checking for BclStorageFolder.
// Ideally, `folder.Path.LocalPath` should also work, as that's only available way for the users.
- if (folder is BclStorageFolder storageFolder)
+ if (item is BclStorageFolder storageFolder)
{
return storageFolder.DirectoryInfo.FullName;
}
-
- if (folder.Path is { IsAbsoluteUri: true, Scheme: "file" } absolutePath)
- {
- return absolutePath.LocalPath;
- }
-
- // android "content:", browser and ios relative links go here.
- return null;
- }
-
- internal static string? TryGetFullPath(this IStorageFile file)
- {
- if (file is BclStorageFile storageFolder)
+ if (item is BclStorageFile storageFile)
{
- return storageFolder.FileInfo.FullName;
+ return storageFile.FileInfo.FullName;
}
- if (file.Path is { IsAbsoluteUri: true, Scheme: "file" } absolutePath)
+ if (item.Path is { IsAbsoluteUri: true, Scheme: "file" } absolutePath)
{
return absolutePath.LocalPath;
}
+ // android "content:", browser and ios relative links go here.
return null;
}
}
diff --git a/src/Avalonia.Base/PropertyStore/BindingEntryBase.cs b/src/Avalonia.Base/PropertyStore/BindingEntryBase.cs
index 11dc80ef8f..e1ff0970c2 100644
--- a/src/Avalonia.Base/PropertyStore/BindingEntryBase.cs
+++ b/src/Avalonia.Base/PropertyStore/BindingEntryBase.cs
@@ -16,6 +16,8 @@ namespace Avalonia.PropertyStore
private IDisposable? _subscription;
private bool _hasValue;
private TValue? _value;
+ private TValue? _defaultValue;
+ private bool _isDefaultValueInitialized;
protected BindingEntryBase(
ValueFrame frame,
@@ -89,6 +91,7 @@ namespace Avalonia.PropertyStore
protected abstract BindingValue ConvertAndValidate(TSource value);
protected abstract BindingValue ConvertAndValidate(BindingValue value);
+ protected abstract TValue GetDefaultValue(Type ownerType);
protected virtual void Start(bool produceValue)
{
@@ -104,17 +107,6 @@ namespace Avalonia.PropertyStore
};
}
- private void ClearValue()
- {
- if (_hasValue)
- {
- _hasValue = false;
- _value = default;
- if (_subscription is not null)
- Frame.Owner?.OnBindingValueCleared(Property, Frame.Priority);
- }
- }
-
private void SetValue(BindingValue value)
{
static void Execute(BindingEntryBase instance, BindingValue value)
@@ -124,24 +116,20 @@ namespace Avalonia.PropertyStore
LoggingUtils.LogIfNecessary(instance.Frame.Owner.Owner, instance.Property, value);
- if (value.HasValue)
- {
- if (!instance._hasValue || !EqualityComparer.Default.Equals(instance._value, value.Value))
- {
- instance._value = value.Value;
- instance._hasValue = true;
- if (instance._subscription is not null && instance._subscription != s_creatingQuiet)
- instance.Frame.Owner?.OnBindingValueChanged(instance, instance.Frame.Priority);
- }
- }
- else if (value.Type != BindingValueType.DoNothing)
+ var effectiveValue = value.HasValue ? value.Value : instance.GetCachedDefaultValue();
+
+ if (!instance._hasValue || !EqualityComparer.Default.Equals(instance._value, effectiveValue))
{
- instance.ClearValue();
+ instance._value = effectiveValue;
+ instance._hasValue = true;
if (instance._subscription is not null && instance._subscription != s_creatingQuiet)
- instance.Frame.Owner?.OnBindingValueCleared(instance.Property, instance.Frame.Priority);
+ instance.Frame.Owner?.OnBindingValueChanged(instance, instance.Frame.Priority);
}
}
+ if (value.Type == BindingValueType.DoNothing)
+ return;
+
if (Dispatcher.UIThread.CheckAccess())
{
Execute(this, value);
@@ -161,5 +149,16 @@ namespace Avalonia.PropertyStore
_subscription = null;
Frame.OnBindingCompleted(this);
}
+
+ private TValue GetCachedDefaultValue()
+ {
+ if (!_isDefaultValueInitialized)
+ {
+ _defaultValue = GetDefaultValue(Frame.Owner!.Owner.GetType());
+ _isDefaultValueInitialized = true;
+ }
+
+ return _defaultValue!;
+ }
}
}
diff --git a/src/Avalonia.Base/PropertyStore/EffectiveValue.cs b/src/Avalonia.Base/PropertyStore/EffectiveValue.cs
index 04d3c805c2..78f0ad46b7 100644
--- a/src/Avalonia.Base/PropertyStore/EffectiveValue.cs
+++ b/src/Avalonia.Base/PropertyStore/EffectiveValue.cs
@@ -29,6 +29,12 @@ namespace Avalonia.PropertyStore
///
public BindingPriority BasePriority { get; protected set; }
+ ///
+ /// Gets a value indicating whether the was overridden by a call to
+ /// .
+ ///
+ public bool IsOverridenCurrentValue { get; set; }
+
///
/// Begins a reevaluation pass on the effective value.
///
diff --git a/src/Avalonia.Base/PropertyStore/EffectiveValue`1.cs b/src/Avalonia.Base/PropertyStore/EffectiveValue`1.cs
index 3e20dcce56..c469034f9b 100644
--- a/src/Avalonia.Base/PropertyStore/EffectiveValue`1.cs
+++ b/src/Avalonia.Base/PropertyStore/EffectiveValue`1.cs
@@ -19,13 +19,16 @@ namespace Avalonia.PropertyStore
private T? _baseValue;
private UncommonFields? _uncommon;
- public EffectiveValue(AvaloniaObject owner, StyledProperty property)
+ public EffectiveValue(
+ AvaloniaObject owner,
+ StyledProperty property,
+ EffectiveValue? inherited)
{
Priority = BindingPriority.Unset;
BasePriority = BindingPriority.Unset;
_metadata = property.GetMetadata(owner.GetType());
- var value = _metadata.DefaultValue;
+ var value = inherited is null ? _metadata.DefaultValue : inherited.Value;
if (property.HasCoercion && _metadata.CoerceValue is { } coerce)
{
@@ -57,7 +60,7 @@ namespace Avalonia.PropertyStore
Debug.Assert(priority != BindingPriority.LocalValue);
UpdateValueEntry(value, priority);
- SetAndRaiseCore(owner, (StyledProperty)value.Property, GetValue(value), priority);
+ SetAndRaiseCore(owner, (StyledProperty)value.Property, GetValue(value), priority, false);
}
public void SetLocalValueAndRaise(
@@ -65,7 +68,16 @@ namespace Avalonia.PropertyStore
StyledProperty property,
T value)
{
- SetAndRaiseCore(owner, property, value, BindingPriority.LocalValue);
+ SetAndRaiseCore(owner, property, value, BindingPriority.LocalValue, false);
+ }
+
+ public void SetCurrentValueAndRaise(
+ ValueStore owner,
+ StyledProperty property,
+ T value)
+ {
+ IsOverridenCurrentValue = true;
+ SetAndRaiseCore(owner, property, value, Priority, true);
}
public bool TryGetBaseValue([MaybeNullWhen(false)] out T value)
@@ -98,7 +110,7 @@ namespace Avalonia.PropertyStore
Debug.Assert(Priority != BindingPriority.Animation);
Debug.Assert(BasePriority != BindingPriority.Unset);
UpdateValueEntry(null, BindingPriority.Animation);
- SetAndRaiseCore(owner, (StyledProperty)property, _baseValue!, BasePriority);
+ SetAndRaiseCore(owner, (StyledProperty)property, _baseValue!, BasePriority, false);
}
public override void CoerceValue(ValueStore owner, AvaloniaProperty property)
@@ -158,15 +170,16 @@ namespace Avalonia.PropertyStore
ValueStore owner,
StyledProperty property,
T value,
- BindingPriority priority)
+ BindingPriority priority,
+ bool isOverriddenCurrentValue)
{
- Debug.Assert(priority < BindingPriority.Inherited);
-
var oldValue = Value;
var valueChanged = false;
var baseValueChanged = false;
var v = value;
+ IsOverridenCurrentValue = isOverriddenCurrentValue;
+
if (_uncommon?._coerce is { } coerce)
v = coerce(owner.Owner, value);
@@ -209,7 +222,6 @@ namespace Avalonia.PropertyStore
T baseValue,
BindingPriority basePriority)
{
- Debug.Assert(priority < BindingPriority.Inherited);
Debug.Assert(basePriority > BindingPriority.Animation);
Debug.Assert(priority <= basePriority);
@@ -225,7 +237,7 @@ namespace Avalonia.PropertyStore
bv = coerce(owner.Owner, baseValue);
}
- if (priority != BindingPriority.Unset && !EqualityComparer.Default.Equals(Value, v))
+ if (!EqualityComparer.Default.Equals(Value, v))
{
Value = v;
valueChanged = true;
@@ -233,9 +245,7 @@ namespace Avalonia.PropertyStore
_uncommon._uncoercedValue = value;
}
- if (priority != BindingPriority.Unset &&
- (BasePriority == BindingPriority.Unset ||
- !EqualityComparer.Default.Equals(_baseValue, bv)))
+ if (!EqualityComparer.Default.Equals(_baseValue, bv))
{
_baseValue = v;
baseValueChanged = true;
diff --git a/src/Avalonia.Base/PropertyStore/LocalValueBindingObserver.cs b/src/Avalonia.Base/PropertyStore/LocalValueBindingObserver.cs
index f89cb029b6..5908d9e535 100644
--- a/src/Avalonia.Base/PropertyStore/LocalValueBindingObserver.cs
+++ b/src/Avalonia.Base/PropertyStore/LocalValueBindingObserver.cs
@@ -10,6 +10,8 @@ namespace Avalonia.PropertyStore
{
private readonly ValueStore _owner;
private IDisposable? _subscription;
+ private T? _defaultValue;
+ private bool _isDefaultValueInitialized;
public LocalValueBindingObserver(ValueStore owner, StyledProperty property)
{
@@ -41,26 +43,28 @@ namespace Avalonia.PropertyStore
public void OnNext(T value)
{
- static void Execute(ValueStore owner, StyledProperty property, T value)
+ static void Execute(LocalValueBindingObserver instance, T value)
{
- if (property.ValidateValue?.Invoke(value) != false)
- owner.SetValue(property, value, BindingPriority.LocalValue);
- else
- owner.ClearLocalValue(property);
+ var owner = instance._owner;
+ var property = instance.Property;
+
+ if (property.ValidateValue?.Invoke(value) == false)
+ value = instance.GetCachedDefaultValue();
+
+ owner.SetValue(property, value, BindingPriority.LocalValue);
}
if (Dispatcher.UIThread.CheckAccess())
{
- Execute(_owner, Property, value);
+ Execute(this, value);
}
else
{
// To avoid allocating closure in the outer scope we need to capture variables
// locally. This allows us to skip most of the allocations when on UI thread.
- var instance = _owner;
- var property = Property;
+ var instance = this;
var newValue = value;
- Dispatcher.UIThread.Post(() => Execute(instance, property, newValue));
+ Dispatcher.UIThread.Post(() => Execute(instance, newValue));
}
}
@@ -74,11 +78,21 @@ namespace Avalonia.PropertyStore
LoggingUtils.LogIfNecessary(owner.Owner, property, value);
if (value.HasValue)
- owner.SetValue(property, value.Value, BindingPriority.LocalValue);
- else if (value.Type != BindingValueType.DataValidationError)
- owner.ClearLocalValue(property);
+ {
+ var effectiveValue = value.Value;
+ if (property.ValidateValue?.Invoke(effectiveValue) == false)
+ effectiveValue = instance.GetCachedDefaultValue();
+ owner.SetValue(property, effectiveValue, BindingPriority.LocalValue);
+ }
+ else
+ {
+ owner.SetValue(property, instance.GetCachedDefaultValue(), BindingPriority.LocalValue);
+ }
}
+ if (value.Type is BindingValueType.DoNothing or BindingValueType.DataValidationError)
+ return;
+
if (Dispatcher.UIThread.CheckAccess())
{
Execute(this, value);
@@ -92,5 +106,16 @@ namespace Avalonia.PropertyStore
Dispatcher.UIThread.Post(() => Execute(instance, newValue));
}
}
+
+ private T GetCachedDefaultValue()
+ {
+ if (!_isDefaultValueInitialized)
+ {
+ _defaultValue = Property.GetDefaultValue(_owner.Owner.GetType());
+ _isDefaultValueInitialized = true;
+ }
+
+ return _defaultValue!;
+ }
}
}
diff --git a/src/Avalonia.Base/PropertyStore/LocalValueUntypedBindingObserver.cs b/src/Avalonia.Base/PropertyStore/LocalValueUntypedBindingObserver.cs
index 2d157b2519..46e6ed810a 100644
--- a/src/Avalonia.Base/PropertyStore/LocalValueUntypedBindingObserver.cs
+++ b/src/Avalonia.Base/PropertyStore/LocalValueUntypedBindingObserver.cs
@@ -1,5 +1,4 @@
using System;
-using System.Security.Cryptography;
using Avalonia.Data;
using Avalonia.Threading;
@@ -10,6 +9,8 @@ namespace Avalonia.PropertyStore
{
private readonly ValueStore _owner;
private IDisposable? _subscription;
+ private T? _defaultValue;
+ private bool _isDefaultValueInitialized;
public LocalValueUntypedBindingObserver(ValueStore owner, StyledProperty property)
{
@@ -49,11 +50,7 @@ namespace Avalonia.PropertyStore
if (value == AvaloniaProperty.UnsetValue)
{
- owner.ClearLocalValue(property);
- }
- else if (value == BindingOperations.DoNothing)
- {
- // Do nothing!
+ owner.SetValue(property, instance.GetCachedDefaultValue(), BindingPriority.LocalValue);
}
else if (UntypedValueUtils.TryConvertAndValidate(property, value, out var typedValue))
{
@@ -61,11 +58,14 @@ namespace Avalonia.PropertyStore
}
else
{
- owner.ClearLocalValue(property);
+ owner.SetValue(property, instance.GetCachedDefaultValue(), BindingPriority.LocalValue);
LoggingUtils.LogInvalidValue(owner.Owner, property, typeof(T), value);
}
}
+ if (value == BindingOperations.DoNothing)
+ return;
+
if (Dispatcher.UIThread.CheckAccess())
{
Execute(this, value);
@@ -79,5 +79,16 @@ namespace Avalonia.PropertyStore
Dispatcher.UIThread.Post(() => Execute(instance, newValue));
}
}
+
+ private T GetCachedDefaultValue()
+ {
+ if (!_isDefaultValueInitialized)
+ {
+ _defaultValue = Property.GetDefaultValue(_owner.Owner.GetType());
+ _isDefaultValueInitialized = true;
+ }
+
+ return _defaultValue!;
+ }
}
}
diff --git a/src/Avalonia.Base/PropertyStore/SourceUntypedBindingEntry.cs b/src/Avalonia.Base/PropertyStore/SourceUntypedBindingEntry.cs
index b56d0d4529..b82714817b 100644
--- a/src/Avalonia.Base/PropertyStore/SourceUntypedBindingEntry.cs
+++ b/src/Avalonia.Base/PropertyStore/SourceUntypedBindingEntry.cs
@@ -31,5 +31,7 @@ namespace Avalonia.PropertyStore
{
throw new NotSupportedException();
}
+
+ protected override TTarget GetDefaultValue(Type ownerType) => Property.GetDefaultValue(ownerType);
}
}
diff --git a/src/Avalonia.Base/PropertyStore/TypedBindingEntry.cs b/src/Avalonia.Base/PropertyStore/TypedBindingEntry.cs
index 697725c87b..550f5c0001 100644
--- a/src/Avalonia.Base/PropertyStore/TypedBindingEntry.cs
+++ b/src/Avalonia.Base/PropertyStore/TypedBindingEntry.cs
@@ -48,5 +48,7 @@ namespace Avalonia.PropertyStore
return value;
}
+
+ protected override T GetDefaultValue(Type ownerType) => Property.GetDefaultValue(ownerType);
}
}
diff --git a/src/Avalonia.Base/PropertyStore/UntypedBindingEntry.cs b/src/Avalonia.Base/PropertyStore/UntypedBindingEntry.cs
index f8becb2e06..a77d7fddb6 100644
--- a/src/Avalonia.Base/PropertyStore/UntypedBindingEntry.cs
+++ b/src/Avalonia.Base/PropertyStore/UntypedBindingEntry.cs
@@ -29,5 +29,10 @@ namespace Avalonia.PropertyStore
{
throw new NotSupportedException();
}
+
+ protected override object? GetDefaultValue(Type ownerType)
+ {
+ return ((IStyledPropertyMetadata)Property.GetMetadata(ownerType)).DefaultValue;
+ }
}
}
diff --git a/src/Avalonia.Base/PropertyStore/ValueStore.cs b/src/Avalonia.Base/PropertyStore/ValueStore.cs
index f36a96992b..ec6ed392c1 100644
--- a/src/Avalonia.Base/PropertyStore/ValueStore.cs
+++ b/src/Avalonia.Base/PropertyStore/ValueStore.cs
@@ -7,7 +7,6 @@ using Avalonia.Data;
using Avalonia.Diagnostics;
using Avalonia.Styling;
using Avalonia.Utilities;
-using static Avalonia.Rendering.Composition.Animations.PropertySetSnapshot;
namespace Avalonia.PropertyStore
{
@@ -156,11 +155,12 @@ namespace Avalonia.PropertyStore
return observer;
}
- public void ClearLocalValue(AvaloniaProperty property)
+ public void ClearValue(AvaloniaProperty property)
{
if (TryGetEffectiveValue(property, out var effective) &&
- effective.Priority == BindingPriority.LocalValue)
+ (effective.Priority == BindingPriority.LocalValue || effective.IsOverridenCurrentValue))
{
+ effective.IsOverridenCurrentValue = false;
ReevaluateEffectiveValue(property, effective, ignoreLocalValue: true);
}
}
@@ -184,7 +184,7 @@ namespace Avalonia.PropertyStore
}
else
{
- var effectiveValue = new EffectiveValue(Owner, property);
+ var effectiveValue = CreateEffectiveValue(property);
AddEffectiveValue(property, effectiveValue);
effectiveValue.SetAndRaise(this, result, priority);
}
@@ -200,7 +200,7 @@ namespace Avalonia.PropertyStore
}
else
{
- var effectiveValue = new EffectiveValue(Owner, property);
+ var effectiveValue = CreateEffectiveValue(property);
AddEffectiveValue(property, effectiveValue);
effectiveValue.SetLocalValueAndRaise(this, property, value);
}
@@ -209,6 +209,20 @@ namespace Avalonia.PropertyStore
}
}
+ public void SetCurrentValue(StyledProperty property, T value)
+ {
+ if (TryGetEffectiveValue(property, out var v))
+ {
+ ((EffectiveValue)v).SetCurrentValueAndRaise(this, property, value);
+ }
+ else
+ {
+ var effectiveValue = CreateEffectiveValue(property);
+ AddEffectiveValue(property, effectiveValue);
+ effectiveValue.SetCurrentValueAndRaise(this, property, value);
+ }
+ }
+
public object? GetValue(AvaloniaProperty property)
{
if (_effectiveValues.TryGetValue(property, out var v))
@@ -235,12 +249,7 @@ namespace Avalonia.PropertyStore
return false;
}
- public bool IsSet(AvaloniaProperty property)
- {
- if (_effectiveValues.TryGetValue(property, out var v))
- return v.Priority < BindingPriority.Inherited;
- return false;
- }
+ public bool IsSet(AvaloniaProperty property) => _effectiveValues.TryGetValue(property, out _);
public void CoerceValue(AvaloniaProperty property)
{
@@ -278,6 +287,16 @@ namespace Avalonia.PropertyStore
return false;
}
+ public EffectiveValue CreateEffectiveValue(StyledProperty property)
+ {
+ EffectiveValue? inherited = null;
+
+ if (property.Inherits && TryGetInheritedValue(property, out var v))
+ inherited = (EffectiveValue)v;
+
+ return new EffectiveValue(Owner, property, inherited);
+ }
+
public void SetInheritanceParent(AvaloniaObject? newParent)
{
var values = AvaloniaPropertyDictionaryPool.Get();
@@ -380,23 +399,6 @@ namespace Avalonia.PropertyStore
}
}
- ///
- /// Called by non-LocalValue binding entries to re-evaluate the effective value when the
- /// binding produces an unset value.
- ///
- /// The bound property.
- /// The priority of binding which produced a new value.
- public void OnBindingValueCleared(AvaloniaProperty property, BindingPriority priority)
- {
- Debug.Assert(priority != BindingPriority.LocalValue);
-
- if (TryGetEffectiveValue(property, out var existing))
- {
- if (priority <= existing.Priority)
- ReevaluateEffectiveValue(property, existing);
- }
- }
-
///
/// Called by a when its
/// state changes.
@@ -507,7 +509,7 @@ namespace Avalonia.PropertyStore
if (existing == observer)
{
_localValueBindings?.Remove(property.Id);
- ClearLocalValue(property);
+ ClearValue(property);
}
}
}
@@ -633,11 +635,13 @@ namespace Avalonia.PropertyStore
{
object? value;
BindingPriority priority;
+ bool overridden = false;
if (_effectiveValues.TryGetValue(property, out var v))
{
value = v.Value;
priority = v.Priority;
+ overridden = v.IsOverridenCurrentValue;
}
else if (property.Inherits && TryGetInheritedValue(property, out v))
{
@@ -654,7 +658,8 @@ namespace Avalonia.PropertyStore
property,
value,
priority,
- null);
+ null,
+ overridden);
}
private int InsertFrame(ValueFrame frame)
@@ -804,7 +809,7 @@ namespace Avalonia.PropertyStore
// - The value is a non-animation value and its priority is higher than the current
// effective value's base priority
var isRelevantPriority = current is null ||
- priority < current.Priority ||
+ (priority < current.Priority && priority < current.BasePriority) ||
(priority > BindingPriority.Animation && priority < current.BasePriority);
if (foundEntry && isRelevantPriority && entry!.HasValue)
diff --git a/src/Avalonia.Base/Rendering/Composition/Drawing/CompositionDrawingContext.cs b/src/Avalonia.Base/Rendering/Composition/Drawing/CompositionDrawingContext.cs
index b75d080cfd..6b380608fe 100644
--- a/src/Avalonia.Base/Rendering/Composition/Drawing/CompositionDrawingContext.cs
+++ b/src/Avalonia.Base/Rendering/Composition/Drawing/CompositionDrawingContext.cs
@@ -313,13 +313,13 @@ internal class CompositionDrawingContext : IDrawingContextImpl, IDrawingContextW
}
///
- public void PushOpacity(double opacity)
+ public void PushOpacity(double opacity, Rect bounds)
{
var next = NextDrawAs();
- if (next == null || !next.Item.Equals(opacity))
+ if (next == null || !next.Item.Equals(opacity, bounds))
{
- Add(new OpacityNode(opacity));
+ Add(new OpacityNode(opacity, bounds));
}
else
{
diff --git a/src/Avalonia.Base/Rendering/Composition/Server/DrawingContextProxy.cs b/src/Avalonia.Base/Rendering/Composition/Server/DrawingContextProxy.cs
index 50df8bd32b..08e506536f 100644
--- a/src/Avalonia.Base/Rendering/Composition/Server/DrawingContextProxy.cs
+++ b/src/Avalonia.Base/Rendering/Composition/Server/DrawingContextProxy.cs
@@ -111,9 +111,9 @@ internal class CompositorDrawingContextProxy : IDrawingContextImpl, IDrawingCont
_impl.PopClip();
}
- public void PushOpacity(double opacity)
+ public void PushOpacity(double opacity, Rect bounds)
{
- _impl.PushOpacity(opacity);
+ _impl.PushOpacity(opacity, bounds);
}
public void PopOpacity()
diff --git a/src/Avalonia.Base/Rendering/Composition/Server/ServerCompositionVisual.cs b/src/Avalonia.Base/Rendering/Composition/Server/ServerCompositionVisual.cs
index e33dc999dc..f9492d0015 100644
--- a/src/Avalonia.Base/Rendering/Composition/Server/ServerCompositionVisual.cs
+++ b/src/Avalonia.Base/Rendering/Composition/Server/ServerCompositionVisual.cs
@@ -41,27 +41,29 @@ namespace Avalonia.Rendering.Composition.Server
return;
Root!.RenderedVisuals++;
-
- if (Opacity != 1)
- canvas.PushOpacity(Opacity);
+
+ var boundsRect = new Rect(new Size(Size.X, Size.Y));
+
if (AdornedVisual != null)
{
canvas.PostTransform = Matrix.Identity;
canvas.Transform = Matrix.Identity;
- canvas.PushClip(AdornedVisual._combinedTransformedClipBounds);
+ if (AdornerIsClipped)
+ canvas.PushClip(AdornedVisual._combinedTransformedClipBounds);
}
var transform = GlobalTransformMatrix;
canvas.PostTransform = MatrixUtils.ToMatrix(transform);
canvas.Transform = Matrix.Identity;
-
- var boundsRect = new Rect(new Size(Size.X, Size.Y));
+
+ if (Opacity != 1)
+ canvas.PushOpacity(Opacity, boundsRect);
if (ClipToBounds && !HandlesClipToBounds)
canvas.PushClip(Root!.SnapToDevicePixels(boundsRect));
if (Clip != null)
canvas.PushGeometryClip(Clip);
if(OpacityMaskBrush != null)
canvas.PushOpacityMask(OpacityMaskBrush, boundsRect);
-
+
RenderCore(canvas, currentTransformedClip);
// Hack to force invalidation of SKMatrix
@@ -74,7 +76,7 @@ namespace Avalonia.Rendering.Composition.Server
canvas.PopGeometryClip();
if (ClipToBounds && !HandlesClipToBounds)
canvas.PopClip();
- if (AdornedVisual != null)
+ if (AdornedVisual != null && AdornerIsClipped)
canvas.PopClip();
if(Opacity != 1)
canvas.PopOpacity();
diff --git a/src/Avalonia.Base/Rendering/DefaultRenderTimer.cs b/src/Avalonia.Base/Rendering/DefaultRenderTimer.cs
index d0d3dd9715..7b0fecf675 100644
--- a/src/Avalonia.Base/Rendering/DefaultRenderTimer.cs
+++ b/src/Avalonia.Base/Rendering/DefaultRenderTimer.cs
@@ -1,6 +1,4 @@
using System;
-using System.Diagnostics;
-using System.Threading.Tasks;
using Avalonia.Platform;
namespace Avalonia.Rendering
@@ -59,7 +57,8 @@ namespace Avalonia.Rendering
}
}
- public bool RunsInBackground => true;
+ ///
+ public virtual bool RunsInBackground => true;
///
/// Starts the timer.
diff --git a/src/Avalonia.Base/Rendering/IRenderLoop.cs b/src/Avalonia.Base/Rendering/IRenderLoop.cs
index e500ecdf8b..ebe683949d 100644
--- a/src/Avalonia.Base/Rendering/IRenderLoop.cs
+++ b/src/Avalonia.Base/Rendering/IRenderLoop.cs
@@ -27,7 +27,10 @@ namespace Avalonia.Rendering
///
/// The update task.
void Remove(IRenderLoopTask i);
-
+
+ ///
+ /// Indicates if the rendering is done on a non-UI thread.
+ ///
bool RunsInBackground { get; }
}
}
diff --git a/src/Avalonia.Base/Rendering/IRenderTimer.cs b/src/Avalonia.Base/Rendering/IRenderTimer.cs
index 07af7eeec8..14fcffd6a9 100644
--- a/src/Avalonia.Base/Rendering/IRenderTimer.cs
+++ b/src/Avalonia.Base/Rendering/IRenderTimer.cs
@@ -1,5 +1,4 @@
using System;
-using System.Threading.Tasks;
using Avalonia.Metadata;
namespace Avalonia.Rendering
diff --git a/src/Avalonia.Base/Rendering/ImmediateRenderer.cs b/src/Avalonia.Base/Rendering/ImmediateRenderer.cs
index 8e5dc38317..09d2d55ce3 100644
--- a/src/Avalonia.Base/Rendering/ImmediateRenderer.cs
+++ b/src/Avalonia.Base/Rendering/ImmediateRenderer.cs
@@ -117,7 +117,7 @@ namespace Avalonia.Rendering
}
using (context.PushPostTransform(m))
- using (context.PushOpacity(opacity))
+ using (context.PushOpacity(opacity, bounds))
using (clipToBounds
#pragma warning disable CS0618 // Type or member is obsolete
? visual is IVisualWithRoundRectClip roundClipVisual
diff --git a/src/Avalonia.Base/Rendering/RenderLoop.cs b/src/Avalonia.Base/Rendering/RenderLoop.cs
index 1f58ca3827..185f44d29a 100644
--- a/src/Avalonia.Base/Rendering/RenderLoop.cs
+++ b/src/Avalonia.Base/Rendering/RenderLoop.cs
@@ -87,6 +87,7 @@ namespace Avalonia.Rendering
}
}
+ ///
public bool RunsInBackground => Timer.RunsInBackground;
private void TimerTick(TimeSpan time)
diff --git a/src/Avalonia.Base/Rendering/SceneGraph/OpacityNode.cs b/src/Avalonia.Base/Rendering/SceneGraph/OpacityNode.cs
index e41e639067..f76a055934 100644
--- a/src/Avalonia.Base/Rendering/SceneGraph/OpacityNode.cs
+++ b/src/Avalonia.Base/Rendering/SceneGraph/OpacityNode.cs
@@ -12,9 +12,11 @@ namespace Avalonia.Rendering.SceneGraph
/// opacity push.
///
/// The opacity to push.
- public OpacityNode(double opacity)
+ /// The bounds.
+ public OpacityNode(double opacity, Rect bounds)
{
Opacity = opacity;
+ Bounds = bounds;
}
///
@@ -26,7 +28,7 @@ namespace Avalonia.Rendering.SceneGraph
}
///
- public Rect Bounds => default;
+ public Rect Bounds { get; }
///
/// Gets the opacity to be pushed or null if the operation represents a pop.
@@ -40,19 +42,20 @@ namespace Avalonia.Rendering.SceneGraph
/// Determines if this draw operation equals another.
///
/// The opacity of the other draw operation.
+ /// The bounds of the other draw operation.
/// True if the draw operations are the same, otherwise false.
///
/// The properties of the other draw operation are passed in as arguments to prevent
/// allocation of a not-yet-constructed draw operation object.
///
- public bool Equals(double? opacity) => Opacity == opacity;
+ public bool Equals(double? opacity, Rect bounds) => Opacity == opacity && Bounds == bounds;
///
public void Render(IDrawingContextImpl context)
{
if (Opacity.HasValue)
{
- context.PushOpacity(Opacity.Value);
+ context.PushOpacity(Opacity.Value, Bounds);
}
else
{
diff --git a/src/Avalonia.Base/Rendering/UiThreadRenderTimer.cs b/src/Avalonia.Base/Rendering/UiThreadRenderTimer.cs
index 1bbf804b5f..7f2eedc98c 100644
--- a/src/Avalonia.Base/Rendering/UiThreadRenderTimer.cs
+++ b/src/Avalonia.Base/Rendering/UiThreadRenderTimer.cs
@@ -8,13 +8,20 @@ namespace Avalonia.Rendering
///
/// Render timer that ticks on UI thread. Useful for debugging or bootstrapping on new platforms
///
-
public class UiThreadRenderTimer : DefaultRenderTimer
{
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The number of frames per second at which the loop should run.
public UiThreadRenderTimer(int framesPerSecond) : base(framesPerSecond)
{
}
+ ///
+ public override bool RunsInBackground => false;
+
+ ///
protected override IDisposable StartCore(Action tick)
{
bool cancelled = false;
diff --git a/src/Avalonia.Base/StyledElement.cs b/src/Avalonia.Base/StyledElement.cs
index b910012444..39aaf55014 100644
--- a/src/Avalonia.Base/StyledElement.cs
+++ b/src/Avalonia.Base/StyledElement.cs
@@ -28,7 +28,7 @@ namespace Avalonia
public class StyledElement : Animatable,
IDataContextProvider,
ILogical,
- IResourceHost,
+ IThemeVariantHost,
IStyleHost,
IStyleable,
ISetLogicalParent,
@@ -41,7 +41,11 @@ namespace Avalonia
public static readonly StyledProperty DataContextProperty =
AvaloniaProperty.Register(
nameof(DataContext),
+ defaultValue: null,
inherits: true,
+ defaultBindingMode: BindingMode.OneWay,
+ validate: null,
+ coerce: null,
notifying: DataContextNotifying);
///
@@ -71,23 +75,6 @@ namespace Avalonia
public static readonly StyledProperty ThemeProperty =
AvaloniaProperty.Register(nameof(Theme));
- ///
- /// Defines the property.
- ///
- public static readonly StyledProperty ActualThemeVariantProperty =
- AvaloniaProperty.Register(
- nameof(ThemeVariant),
- inherits: true,
- defaultValue: ThemeVariant.Light);
-
- ///
- /// Defines the RequestedThemeVariant property.
- ///
- public static readonly StyledProperty RequestedThemeVariantProperty =
- AvaloniaProperty.Register(
- nameof(ThemeVariant),
- defaultValue: ThemeVariant.Default);
-
private static readonly ControlTheme s_invalidTheme = new ControlTheme();
private int _initCount;
private string? _name;
@@ -156,6 +143,9 @@ namespace Avalonia
///
public event EventHandler? ResourcesChanged;
+ ///
+ public event EventHandler? ActualThemeVariantChanged;
+
///
/// Gets or sets the name of the styled element.
///
@@ -274,15 +264,6 @@ namespace Avalonia
set => SetValue(ThemeProperty, value);
}
- ///
- /// Gets the UI theme that is currently used by the element, which might be different than the .
- ///
- ///
- /// If current control is contained in the ThemeVariantScope, TopLevel or Application with non-default RequestedThemeVariant, that value will be returned.
- /// Otherwise, current OS theme variant is returned.
- ///
- public ThemeVariant ActualThemeVariant => GetValue(ActualThemeVariantProperty);
-
///
/// Gets the styled element's logical children.
///
@@ -321,6 +302,9 @@ namespace Avalonia
///
public StyledElement? Parent { get; private set; }
+ ///
+ public ThemeVariant ActualThemeVariant => GetValue(ThemeVariant.ActualThemeVariantProperty);
+
///
/// Gets the styled element's logical parent.
///
@@ -520,13 +504,7 @@ namespace Avalonia
NotifyResourcesChanged();
}
-#nullable disable
- RaisePropertyChanged(
- ParentProperty,
- new Optional(old),
- new BindingValue(Parent),
- BindingPriority.LocalValue);
-#nullable enable
+ RaisePropertyChanged(ParentProperty, old, Parent);
}
}
@@ -646,13 +624,19 @@ namespace Avalonia
base.OnPropertyChanged(change);
if (change.Property == ThemeProperty)
+ {
OnControlThemeChanged();
- else if (change.Property == RequestedThemeVariantProperty)
+ }
+ else if (change.Property == ThemeVariant.RequestedThemeVariantProperty)
{
if (change.GetNewValue() is {} themeVariant && themeVariant != ThemeVariant.Default)
- SetValue(ActualThemeVariantProperty, themeVariant);
+ SetValue(ThemeVariant.ActualThemeVariantProperty, themeVariant);
else
- ClearValue(ActualThemeVariantProperty);
+ ClearValue(ThemeVariant.ActualThemeVariantProperty);
+ }
+ else if (change.Property == ThemeVariant.ActualThemeVariantProperty)
+ {
+ ActualThemeVariantChanged?.Invoke(this, EventArgs.Empty);
}
}
diff --git a/src/Avalonia.Base/StyledProperty.cs b/src/Avalonia.Base/StyledProperty.cs
index 79d1b9202d..8695918c18 100644
--- a/src/Avalonia.Base/StyledProperty.cs
+++ b/src/Avalonia.Base/StyledProperty.cs
@@ -171,7 +171,7 @@ namespace Avalonia
internal override EffectiveValue CreateEffectiveValue(AvaloniaObject o)
{
- return new EffectiveValue(o, this);
+ return o.GetValueStore().CreateEffectiveValue(this);
}
///
@@ -194,24 +194,48 @@ namespace Avalonia
}
///
- [UnconditionalSuppressMessage("Trimming", "IL2026", Justification = TrimmingMessages.ImplicitTypeConvertionSupressWarningMessage)]
internal override IDisposable? RouteSetValue(
AvaloniaObject target,
object? value,
BindingPriority priority)
+ {
+ if (ShouldSetValue(target, value, out var converted))
+ return target.SetValue(this, converted, priority);
+ return null;
+ }
+
+ internal override void RouteSetCurrentValue(AvaloniaObject target, object? value)
+ {
+ if (ShouldSetValue(target, value, out var converted))
+ target.SetCurrentValue(this, converted);
+ }
+
+ internal override IDisposable RouteBind(
+ AvaloniaObject target,
+ IObservable source,
+ BindingPriority priority)
+ {
+ return target.Bind(this, source, priority);
+ }
+
+ [UnconditionalSuppressMessage("Trimming", "IL2026", Justification = TrimmingMessages.ImplicitTypeConvertionSupressWarningMessage)]
+ private bool ShouldSetValue(AvaloniaObject target, object? value, [NotNullWhen(true)] out TValue? converted)
{
if (value == BindingOperations.DoNothing)
{
- return null;
+ converted = default;
+ return false;
}
- else if (value == UnsetValue)
+ if (value == UnsetValue)
{
target.ClearValue(this);
- return null;
+ converted = default;
+ return false;
}
- else if (TypeUtilities.TryConvertImplicit(PropertyType, value, out var converted))
+ else if (TypeUtilities.TryConvertImplicit(PropertyType, value, out var v))
{
- return target.SetValue(this, (TValue)converted!, priority);
+ converted = (TValue)v!;
+ return true;
}
else
{
@@ -220,14 +244,6 @@ namespace Avalonia
}
}
- internal override IDisposable RouteBind(
- AvaloniaObject target,
- IObservable source,
- BindingPriority priority)
- {
- return target.Bind(this, source, priority);
- }
-
private object? GetDefaultBoxedValue(Type type)
{
_ = type ?? throw new ArgumentNullException(nameof(type));
diff --git a/src/Avalonia.Base/Styling/IGlobalThemeVariantProvider.cs b/src/Avalonia.Base/Styling/IGlobalThemeVariantProvider.cs
deleted file mode 100644
index 2467d99b3b..0000000000
--- a/src/Avalonia.Base/Styling/IGlobalThemeVariantProvider.cs
+++ /dev/null
@@ -1,22 +0,0 @@
-using System;
-using Avalonia.Controls;
-using Avalonia.Metadata;
-
-namespace Avalonia.Styling;
-
-///
-/// Interface for an application host element with a root theme variant.
-///
-[Unstable]
-public interface IGlobalThemeVariantProvider : IResourceHost
-{
- ///
- /// Gets the UI theme variant that is used by the control (and its child elements) for resource determination.
- ///
- ThemeVariant ActualThemeVariant { get; }
-
- ///
- /// Raised when the theme variant is changed on the element or an ancestor of the element.
- ///
- event EventHandler? ActualThemeVariantChanged;
-}
diff --git a/src/Avalonia.Base/Styling/IThemeVariantHost.cs b/src/Avalonia.Base/Styling/IThemeVariantHost.cs
new file mode 100644
index 0000000000..01583148a8
--- /dev/null
+++ b/src/Avalonia.Base/Styling/IThemeVariantHost.cs
@@ -0,0 +1,26 @@
+using System;
+using Avalonia.Controls;
+using Avalonia.Metadata;
+
+namespace Avalonia.Styling;
+
+///
+/// Interface for the host element with a theme variant.
+///
+[Unstable]
+public interface IThemeVariantHost : IResourceHost
+{
+ ///
+ /// Gets the UI theme that is currently used by the element, which might be different than the RequestedThemeVariantProperty.
+ ///
+ ///
+ /// If current control is contained in the ThemeVariantScope, TopLevel or Application with non-default RequestedThemeVariant, that value will be returned.
+ /// Otherwise, current OS theme variant is returned.
+ ///
+ ThemeVariant ActualThemeVariant { get; }
+
+ ///
+ /// Raised when the theme variant is changed on the element or an ancestor of the element.
+ ///
+ event EventHandler? ActualThemeVariantChanged;
+}
diff --git a/src/Avalonia.Base/Styling/ThemeVariant.cs b/src/Avalonia.Base/Styling/ThemeVariant.cs
index 8218533f4f..389136b0f5 100644
--- a/src/Avalonia.Base/Styling/ThemeVariant.cs
+++ b/src/Avalonia.Base/Styling/ThemeVariant.cs
@@ -6,11 +6,26 @@ using Avalonia.Platform;
namespace Avalonia.Styling;
///
-/// Specifies a UI theme variant that should be used for the
+/// Specifies a UI theme variant that should be used for the Control and Application types.
///
[TypeConverter(typeof(ThemeVariantTypeConverter))]
public sealed record ThemeVariant
{
+ ///
+ /// Defines the ActualThemeVariant property.
+ ///
+ internal static readonly StyledProperty ActualThemeVariantProperty =
+ AvaloniaProperty.Register(
+ "ActualThemeVariant",
+ inherits: true);
+
+ ///
+ /// Defines the RequestedThemeVariant property.
+ ///
+ internal static readonly StyledProperty RequestedThemeVariantProperty =
+ AvaloniaProperty.Register(
+ "RequestedThemeVariant", defaultValue: Default);
+
///
/// Creates a new instance of the
///
diff --git a/src/Avalonia.Base/Utilities/WeakEvents.cs b/src/Avalonia.Base/Utilities/WeakEvents.cs
index 6da899bab2..2f62564e0e 100644
--- a/src/Avalonia.Base/Utilities/WeakEvents.cs
+++ b/src/Avalonia.Base/Utilities/WeakEvents.cs
@@ -2,6 +2,7 @@ using System;
using System.Collections.Specialized;
using System.ComponentModel;
using System.Windows.Input;
+using Avalonia.Threading;
namespace Avalonia.Utilities;
@@ -20,15 +21,30 @@ public class WeakEvents
});
///
- /// Represents PropertyChanged event from
+ /// Represents PropertyChanged event from with auto-dispatching to the UI thread
///
public static readonly WeakEvent
- PropertyChanged = WeakEvent.Register(
+ ThreadSafePropertyChanged = WeakEvent.Register(
(s, h) =>
{
- PropertyChangedEventHandler handler = (_, e) => h(s, e);
+ bool unsubscribed = false;
+ PropertyChangedEventHandler handler = (_, e) =>
+ {
+ if (Dispatcher.UIThread.CheckAccess())
+ h(s, e);
+ else
+ Dispatcher.UIThread.Post(() =>
+ {
+ if (!unsubscribed)
+ h(s, e);
+ });
+ };
s.PropertyChanged += handler;
- return () => s.PropertyChanged -= handler;
+ return () =>
+ {
+ unsubscribed = true;
+ s.PropertyChanged -= handler;
+ };
});
diff --git a/src/Avalonia.Base/Visual.cs b/src/Avalonia.Base/Visual.cs
index 87bb1d3790..8b0cc06136 100644
--- a/src/Avalonia.Base/Visual.cs
+++ b/src/Avalonia.Base/Visual.cs
@@ -573,7 +573,7 @@ namespace Avalonia
/// The new visual parent.
protected virtual void OnVisualParentChanged(Visual? oldParent, Visual? newParent)
{
- RaisePropertyChanged(VisualParentProperty, oldParent, newParent, BindingPriority.LocalValue);
+ RaisePropertyChanged(VisualParentProperty, oldParent, newParent);
}
internal override ParametrizedLogger? GetBindingWarningLogger(
diff --git a/src/Avalonia.Base/composition-schema.xml b/src/Avalonia.Base/composition-schema.xml
index 36fd9fe709..31722974ee 100644
--- a/src/Avalonia.Base/composition-schema.xml
+++ b/src/Avalonia.Base/composition-schema.xml
@@ -26,6 +26,7 @@
+
diff --git a/src/Avalonia.Controls.ColorPicker/ColorSlider/ColorSlider.cs b/src/Avalonia.Controls.ColorPicker/ColorSlider/ColorSlider.cs
index be320246b3..dd5e7d5b01 100644
--- a/src/Avalonia.Controls.ColorPicker/ColorSlider/ColorSlider.cs
+++ b/src/Avalonia.Controls.ColorPicker/ColorSlider/ColorSlider.cs
@@ -41,18 +41,6 @@ namespace Avalonia.Controls.Primitives
{
}
- ///
- protected override void OnAttachedToVisualTree(VisualTreeAttachmentEventArgs e)
- {
- base.OnAttachedToVisualTree(e);
- }
-
- ///
- protected override void OnDetachedFromVisualTree(VisualTreeAttachmentEventArgs e)
- {
- base.OnDetachedFromVisualTree(e);
- }
-
///
/// Updates the visual state of the control by applying latest PseudoClasses.
///
@@ -123,28 +111,25 @@ namespace Avalonia.Controls.Primitives
IsAlphaMaxForced,
IsSaturationValueMaxForced);
- if (bgraPixelData != null)
+ if (_backgroundBitmap != null)
{
- if (_backgroundBitmap != null)
- {
- // TODO: CURRENTLY DISABLED DUE TO INTERMITTENT CRASHES IN SKIA/RENDERER
- //
- // Re-use the existing WriteableBitmap
- // This assumes the height, width and byte counts are the same and must be set to null
- // elsewhere if that assumption is ever not true.
- // ColorPickerHelpers.UpdateBitmapFromPixelData(_backgroundBitmap, bgraPixelData);
-
- // TODO: ALSO DISABLED DISPOSE DUE TO INTERMITTENT CRASHES
- //_backgroundBitmap?.Dispose();
- _backgroundBitmap = ColorPickerHelpers.CreateBitmapFromPixelData(bgraPixelData, pixelWidth, pixelHeight);
- }
- else
- {
- _backgroundBitmap = ColorPickerHelpers.CreateBitmapFromPixelData(bgraPixelData, pixelWidth, pixelHeight);
- }
-
- Background = new ImageBrush(_backgroundBitmap);
+ // TODO: CURRENTLY DISABLED DUE TO INTERMITTENT CRASHES IN SKIA/RENDERER
+ //
+ // Re-use the existing WriteableBitmap
+ // This assumes the height, width and byte counts are the same and must be set to null
+ // elsewhere if that assumption is ever not true.
+ // ColorPickerHelpers.UpdateBitmapFromPixelData(_backgroundBitmap, bgraPixelData);
+
+ // TODO: ALSO DISABLED DISPOSE DUE TO INTERMITTENT CRASHES
+ //_backgroundBitmap?.Dispose();
+ _backgroundBitmap = ColorPickerHelpers.CreateBitmapFromPixelData(bgraPixelData, pixelWidth, pixelHeight);
}
+ else
+ {
+ _backgroundBitmap = ColorPickerHelpers.CreateBitmapFromPixelData(bgraPixelData, pixelWidth, pixelHeight);
+ }
+
+ Background = new ImageBrush(_backgroundBitmap);
}
}
diff --git a/src/Avalonia.Controls.DataGrid/Themes/Fluent.xaml b/src/Avalonia.Controls.DataGrid/Themes/Fluent.xaml
index dd8575c989..ca516c8918 100644
--- a/src/Avalonia.Controls.DataGrid/Themes/Fluent.xaml
+++ b/src/Avalonia.Controls.DataGrid/Themes/Fluent.xaml
@@ -1,6 +1,48 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
0.6
0.8
@@ -9,19 +51,6 @@
M515 93l930 931l-930 931l90 90l1022 -1021l-1022 -1021z
M109 486 19 576 1024 1581 2029 576 1939 486 1024 1401z
-
-
-
-
-
-
-
-
-
-
-
@@ -29,23 +58,10 @@
-
+
-
-
-
-
-
-
-
-
-
@@ -565,5 +581,6 @@
+
diff --git a/src/Avalonia.Controls.ItemsRepeater/Controls/ItemsRepeater.cs b/src/Avalonia.Controls.ItemsRepeater/Controls/ItemsRepeater.cs
index 6c761ab4cf..951e60c25b 100644
--- a/src/Avalonia.Controls.ItemsRepeater/Controls/ItemsRepeater.cs
+++ b/src/Avalonia.Controls.ItemsRepeater/Controls/ItemsRepeater.cs
@@ -44,8 +44,8 @@ namespace Avalonia.Controls
///
/// Defines the property.
///
- public static readonly StyledProperty LayoutProperty =
- AvaloniaProperty.Register(nameof(Layout), new StackLayout());
+ public static readonly StyledProperty LayoutProperty =
+ AvaloniaProperty.Register(nameof(Layout), new StackLayout());
///
/// Defines the property.
@@ -53,8 +53,8 @@ namespace Avalonia.Controls
public static readonly StyledProperty VerticalCacheLengthProperty =
AvaloniaProperty.Register(nameof(VerticalCacheLength), 2.0);
- private static readonly StyledProperty VirtualizationInfoProperty =
- AvaloniaProperty.RegisterAttached("VirtualizationInfo");
+ private static readonly StyledProperty VirtualizationInfoProperty =
+ AvaloniaProperty.RegisterAttached("VirtualizationInfo");
internal static readonly Rect InvalidRect = new Rect(-1, -1, -1, -1);
internal static readonly Point ClearedElementsArrangePosition = new Point(-10000.0, -10000.0);
@@ -63,7 +63,7 @@ namespace Avalonia.Controls
private readonly ViewportManager _viewportManager;
private readonly TargetWeakEventSubscriber _layoutWeakSubscriber;
private IEnumerable? _items;
- private VirtualizingLayoutContext? _layoutContext;
+ private RepeaterLayoutContext? _layoutContext;
private EventHandler? _childIndexChanged;
private bool _isLayoutInProgress;
private NotifyCollectionChangedEventArgs? _processingItemsSourceChange;
@@ -104,7 +104,7 @@ namespace Avalonia.Controls
/// The layout used to size and position elements. The default is a StackLayout with
/// vertical orientation.
///
- public AttachedLayout Layout
+ public AttachedLayout? Layout
{
get => GetValue(LayoutProperty);
set => SetValue(LayoutProperty, value);
@@ -164,18 +164,7 @@ namespace Avalonia.Controls
private bool IsProcessingCollectionChange => _processingItemsSourceChange != null;
- private LayoutContext LayoutContext
- {
- get
- {
- if (_layoutContext == null)
- {
- _layoutContext = new RepeaterLayoutContext(this);
- }
-
- return _layoutContext;
- }
- }
+ private RepeaterLayoutContext LayoutContext => _layoutContext ??= new RepeaterLayoutContext(this);
event EventHandler? IChildIndexProvider.ChildIndexChanged
{
@@ -269,39 +258,22 @@ namespace Avalonia.Controls
internal void UnpinElement(Control element) => _viewManager.UpdatePin(element, false);
- internal static VirtualizationInfo? TryGetVirtualizationInfo(Control element)
+ internal static VirtualizationInfo? TryGetVirtualizationInfo(Control? element)
{
- return (element as AvaloniaObject)?.GetValue(VirtualizationInfoProperty);
- }
-
- internal static VirtualizationInfo CreateAndInitializeVirtualizationInfo(Control element)
- {
- if (TryGetVirtualizationInfo(element) != null)
- {
- throw new InvalidOperationException("VirtualizationInfo already created.");
- }
-
- var result = new VirtualizationInfo();
- element.SetValue(VirtualizationInfoProperty, result);
- return result;
+ return element?.GetValue(VirtualizationInfoProperty);
}
internal static VirtualizationInfo GetVirtualizationInfo(Control element)
{
- if (element is AvaloniaObject ao)
- {
- var result = ao.GetValue(VirtualizationInfoProperty);
-
- if (result == null)
- {
- result = new VirtualizationInfo();
- ao.SetValue(VirtualizationInfoProperty, result);
- }
+ var result = element.GetValue(VirtualizationInfoProperty);
- return result;
+ if (result == null)
+ {
+ result = new VirtualizationInfo();
+ element.SetValue(VirtualizationInfoProperty, result);
}
- throw new NotSupportedException("Custom implementations of AvaloniaObject not supported.");
+ return result;
}
private protected override void InvalidateMeasureOnChildrenChanged()
@@ -309,6 +281,7 @@ namespace Avalonia.Controls
// Don't invalidate measure when children change.
}
+ ///
protected override Size MeasureOverride(Size availableSize)
{
if (_isLayoutInProgress)
@@ -334,7 +307,7 @@ namespace Avalonia.Controls
if (layout != null)
{
- var layoutContext = GetLayoutContext();
+ var layoutContext = LayoutContext;
desiredSize = layout.Measure(layoutContext, availableSize);
extent = new Rect(LayoutOrigin.X, LayoutOrigin.Y, desiredSize.Width, desiredSize.Height);
@@ -364,6 +337,7 @@ namespace Avalonia.Controls
}
}
+ ///
protected override Size ArrangeOverride(Size finalSize)
{
if (_isLayoutInProgress)
@@ -380,7 +354,7 @@ namespace Avalonia.Controls
try
{
- var arrangeSize = Layout?.Arrange(GetLayoutContext(), finalSize) ?? default;
+ var arrangeSize = Layout?.Arrange(LayoutContext, finalSize) ?? default;
// The view manager might clear elements during this call.
// That's why we call it before arranging cleared elements
@@ -421,6 +395,7 @@ namespace Avalonia.Controls
}
}
+ ///
protected override void OnAttachedToVisualTree(VisualTreeAttachmentEventArgs e)
{
base.OnAttachedToVisualTree(e);
@@ -428,11 +403,13 @@ namespace Avalonia.Controls
_viewportManager.ResetScrollers();
}
+ ///
protected override void OnDetachedFromVisualTree(VisualTreeAttachmentEventArgs e)
{
_viewportManager.ResetScrollers();
}
+ ///
protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs change)
{
if (change.Property == ItemsProperty)
@@ -501,7 +478,7 @@ namespace Avalonia.Controls
if (parent == this)
{
var virtInfo = TryGetVirtualizationInfo(element);
- return _viewManager.GetElementIndex(virtInfo!);
+ return _viewManager.GetElementIndex(virtInfo);
}
return -1;
@@ -529,7 +506,7 @@ namespace Avalonia.Controls
{
if (index >= 0 && index >= (ItemsSourceView?.Count ?? 0))
{
- throw new ArgumentException("Argument index is invalid.", "index");
+ throw new ArgumentException("Argument index is invalid.", nameof(index));
}
if (_isLayoutInProgress)
@@ -547,7 +524,7 @@ namespace Avalonia.Controls
throw new InvalidOperationException("Cannot make an Anchor when there is no attached layout.");
}
- element = (Control)GetLayoutContext().GetOrCreateElementAt(index);
+ element = (Control)LayoutContext.GetOrCreateElementAt(index);
element.Measure(Size.Infinity);
}
@@ -647,9 +624,9 @@ namespace Avalonia.Controls
if (Layout is VirtualizingLayout virtualLayout)
{
- virtualLayout.OnItemsChanged(GetLayoutContext(), newValue, args);
+ virtualLayout.OnItemsChanged(LayoutContext, newValue, args);
}
- else if (Layout is NonVirtualizingLayout nonVirtualLayout)
+ else if (Layout is NonVirtualizingLayout)
{
// Walk through all the elements and make sure they are cleared for
// non-virtualizing layouts.
@@ -693,7 +670,7 @@ namespace Avalonia.Controls
try
{
- virtualLayout.OnItemsChanged(GetLayoutContext(), newValue, args);
+ virtualLayout.OnItemsChanged(LayoutContext, newValue, args);
}
finally
{
@@ -760,7 +737,7 @@ namespace Avalonia.Controls
AttachedLayout.ArrangeInvalidatedWeakEvent.Subscribe(newValue, _layoutWeakSubscriber);
}
- bool isVirtualizingLayout = newValue != null && newValue is VirtualizingLayout;
+ bool isVirtualizingLayout = newValue is VirtualizingLayout;
_viewportManager.OnLayoutChanged(isVirtualizingLayout);
InvalidateMeasure();
}
@@ -788,7 +765,7 @@ namespace Avalonia.Controls
{
if (Layout is VirtualizingLayout virtualLayout)
{
- virtualLayout.OnItemsChanged(GetLayoutContext(), sender, args);
+ virtualLayout.OnItemsChanged(LayoutContext, sender, args);
}
else
{
@@ -807,15 +784,5 @@ namespace Avalonia.Controls
{
_viewportManager.OnBringIntoViewRequested(e);
}
-
- private VirtualizingLayoutContext GetLayoutContext()
- {
- if (_layoutContext == null)
- {
- _layoutContext = new RepeaterLayoutContext(this);
- }
-
- return _layoutContext;
- }
}
}
diff --git a/src/Avalonia.Controls.ItemsRepeater/Controls/ViewManager.cs b/src/Avalonia.Controls.ItemsRepeater/Controls/ViewManager.cs
index 2d302a95dd..6b9d7934bf 100644
--- a/src/Avalonia.Controls.ItemsRepeater/Controls/ViewManager.cs
+++ b/src/Avalonia.Controls.ItemsRepeater/Controls/ViewManager.cs
@@ -53,7 +53,7 @@ namespace Avalonia.Controls
}
}
}
- if (element == null) { element = GetElementFromUniqueIdResetPool(index); };
+ if (element == null) { element = GetElementFromUniqueIdResetPool(index); }
if (element == null) { element = GetElementFromPinnedElements(index); }
if (element == null) { element = GetElementFromElementFactory(index); }
@@ -221,7 +221,7 @@ namespace Avalonia.Controls
return nextElement;
}
- public int GetElementIndex(VirtualizationInfo virtInfo)
+ public int GetElementIndex(VirtualizationInfo? virtInfo)
{
if (virtInfo == null)
{
@@ -627,11 +627,7 @@ namespace Avalonia.Controls
var element = GetElement();
- var virtInfo = ItemsRepeater.TryGetVirtualizationInfo(element);
- if (virtInfo == null)
- {
- virtInfo = ItemsRepeater.CreateAndInitializeVirtualizationInfo(element);
- }
+ var virtInfo = ItemsRepeater.GetVirtualizationInfo(element);
// Clear flag
virtInfo.MustClearDataContext = false;
@@ -710,9 +706,8 @@ namespace Avalonia.Controls
{
if (parent is ItemsRepeater repeater)
{
- var element = child as Control;
if (repeater == owner &&
- element is not null &&
+ child is Control element &&
ItemsRepeater.GetVirtualizationInfo(element).IsRealized)
{
focusedElement = element;
@@ -722,7 +717,7 @@ namespace Avalonia.Controls
}
child = parent;
- parent = child?.GetVisualParent();
+ parent = child.GetVisualParent();
}
}
diff --git a/src/Avalonia.Controls.ItemsRepeater/Controls/ViewportManager.cs b/src/Avalonia.Controls.ItemsRepeater/Controls/ViewportManager.cs
index 336fb2d228..6ed817c238 100644
--- a/src/Avalonia.Controls.ItemsRepeater/Controls/ViewportManager.cs
+++ b/src/Avalonia.Controls.ItemsRepeater/Controls/ViewportManager.cs
@@ -166,7 +166,7 @@ namespace Avalonia.Controls
if (Math.Abs(_expectedViewportShift.X) > 1 || Math.Abs(_expectedViewportShift.Y) > 1)
{
Logger.TryGet(LogEventLevel.Verbose, "Repeater")?.Log(this, "{LayoutId}: Expecting viewport shift of ({Shift})",
- _owner.Layout.LayoutId, _expectedViewportShift);
+ _owner.Layout?.LayoutId, _expectedViewportShift);
// There are cases where we might be expecting a shift but not get it. We will
// be waiting for the effective viewport event but if the scroll viewer is not able
@@ -287,7 +287,7 @@ namespace Avalonia.Controls
if (_pendingViewportShift.X != 0 || _pendingViewportShift.Y != 0)
{
Logger.TryGet(LogEventLevel.Verbose, "Repeater")?.Log(this, "{LayoutId}: Layout Updated with pending shift {Shift}- invalidating measure",
- _owner.Layout.LayoutId,
+ _owner.Layout?.LayoutId,
_pendingViewportShift);
// Assume this is never going to come.
@@ -436,7 +436,7 @@ namespace Avalonia.Controls
private void OnEffectiveViewportChanged(object? sender, EffectiveViewportChangedEventArgs e)
{
- Logger.TryGet(LogEventLevel.Verbose, "Repeater")?.Log(this, "{LayoutId}: EffectiveViewportChanged event callback", _owner.Layout.LayoutId);
+ Logger.TryGet(LogEventLevel.Verbose, "Repeater")?.Log(this, "{LayoutId}: EffectiveViewportChanged event callback", _owner.Layout?.LayoutId);
UpdateViewport(e.EffectiveViewport);
_pendingViewportShift = default;
@@ -490,14 +490,14 @@ namespace Avalonia.Controls
var previousVisibleWindow = _visibleWindow;
Logger.TryGet(LogEventLevel.Verbose, "Repeater")?.Log(this, "{LayoutId}: Effective Viewport: ({Before})->({After})",
- _owner.Layout.LayoutId,
+ _owner.Layout?.LayoutId,
previousVisibleWindow,
viewport);
if (-currentVisibleWindow.X <= ItemsRepeater.ClearedElementsArrangePosition.X &&
-currentVisibleWindow.Y <= ItemsRepeater.ClearedElementsArrangePosition.Y)
{
- Logger.TryGet(LogEventLevel.Verbose, "Repeater")?.Log(this, "{LayoutId}: Viewport is invalid. visible window cleared", _owner.Layout.LayoutId);
+ Logger.TryGet(LogEventLevel.Verbose, "Repeater")?.Log(this, "{LayoutId}: Viewport is invalid. visible window cleared", _owner.Layout?.LayoutId);
// We got cleared.
_visibleWindow = default;
}
@@ -509,7 +509,7 @@ namespace Avalonia.Controls
if (_visibleWindow != previousVisibleWindow)
{
Logger.TryGet(LogEventLevel.Verbose, "Repeater")?.Log(this, "{LayoutId}: Used Viewport: ({Before})->({After})",
- _owner.Layout.LayoutId,
+ _owner.Layout?.LayoutId,
previousVisibleWindow,
currentVisibleWindow);
TryInvalidateMeasure();
@@ -532,7 +532,7 @@ namespace Avalonia.Controls
// We invalidate measure instead of just invalidating arrange because
// we don't invalidate measure in UpdateViewport if the view is changing to
// avoid layout cycles.
- Logger.TryGet(LogEventLevel.Verbose, "Repeater")?.Log(this, "{LayoutId}: Invalidating measure due to viewport change", _owner.Layout.LayoutId);
+ Logger.TryGet(LogEventLevel.Verbose, "Repeater")?.Log(this, "{LayoutId}: Invalidating measure due to viewport change", _owner.Layout?.LayoutId);
_owner.InvalidateMeasure();
}
}
diff --git a/src/Avalonia.Controls/Application.cs b/src/Avalonia.Controls/Application.cs
index 6d3ba3cf8a..6d9a6bd493 100644
--- a/src/Avalonia.Controls/Application.cs
+++ b/src/Avalonia.Controls/Application.cs
@@ -29,7 +29,7 @@ namespace Avalonia
/// method.
/// - Tracks the lifetime of the application.
///
- public class Application : AvaloniaObject, IDataContextProvider, IGlobalDataTemplates, IGlobalStyles, IGlobalThemeVariantProvider, IApplicationPlatformEvents
+ public class Application : AvaloniaObject, IDataContextProvider, IGlobalDataTemplates, IGlobalStyles, IThemeVariantHost, IApplicationPlatformEvents
{
///
/// The application-global data templates.
@@ -50,13 +50,13 @@ namespace Avalonia
public static readonly StyledProperty DataContextProperty =
StyledElement.DataContextProperty.AddOwner();
- ///
+ ///
public static readonly StyledProperty ActualThemeVariantProperty =
- StyledElement.ActualThemeVariantProperty.AddOwner();
+ ThemeVariantScope.ActualThemeVariantProperty.AddOwner();
- ///
+ ///
public static readonly StyledProperty RequestedThemeVariantProperty =
- StyledElement.RequestedThemeVariantProperty.AddOwner();
+ ThemeVariantScope.RequestedThemeVariantProperty.AddOwner();
///
public event EventHandler? ResourcesChanged;
@@ -95,11 +95,8 @@ namespace Avalonia
set => SetValue(RequestedThemeVariantProperty, value);
}
- ///
- public ThemeVariant ActualThemeVariant
- {
- get => GetValue(ActualThemeVariantProperty);
- }
+ ///
+ public ThemeVariant ActualThemeVariant => GetValue(ActualThemeVariantProperty);
///
/// Gets the current instance of the class.
@@ -256,7 +253,7 @@ namespace Avalonia
.Bind().ToTransient()
.Bind().ToConstant(this)
.Bind().ToConstant(this)
- .Bind().ToConstant(this)
+ .Bind().ToConstant(this)
.Bind().ToConstant(FocusManager)
.Bind().ToConstant(InputManager)
.Bind().ToTransient()
diff --git a/src/Avalonia.Controls/ApplicationLifetimes/ClassicDesktopStyleApplicationLifetime.cs b/src/Avalonia.Controls/ApplicationLifetimes/ClassicDesktopStyleApplicationLifetime.cs
index fde401fb01..ada0b94124 100644
--- a/src/Avalonia.Controls/ApplicationLifetimes/ClassicDesktopStyleApplicationLifetime.cs
+++ b/src/Avalonia.Controls/ApplicationLifetimes/ClassicDesktopStyleApplicationLifetime.cs
@@ -1,6 +1,5 @@
using System;
using System.Collections.Generic;
-using System.ComponentModel;
using System.Linq;
using System.Runtime.CompilerServices;
using System.Threading;
@@ -17,32 +16,34 @@ namespace Avalonia.Controls.ApplicationLifetimes
private int _exitCode;
private CancellationTokenSource? _cts;
private bool _isShuttingDown;
- private HashSet _windows = new HashSet();
+ private readonly HashSet _windows = new();
+
+ private static ClassicDesktopStyleApplicationLifetime? s_activeLifetime;
- private static ClassicDesktopStyleApplicationLifetime? _activeLifetime;
static ClassicDesktopStyleApplicationLifetime()
{
Window.WindowOpenedEvent.AddClassHandler(typeof(Window), OnWindowOpened);
- Window.WindowClosedEvent.AddClassHandler(typeof(Window), WindowClosedEvent);
+ Window.WindowClosedEvent.AddClassHandler(typeof(Window), OnWindowClosed);
}
- private static void WindowClosedEvent(object? sender, RoutedEventArgs e)
+ private static void OnWindowClosed(object? sender, RoutedEventArgs e)
{
- _activeLifetime?._windows.Remove((Window)sender!);
- _activeLifetime?.HandleWindowClosed((Window)sender!);
+ var window = (Window)sender!;
+ s_activeLifetime?._windows.Remove(window);
+ s_activeLifetime?.HandleWindowClosed(window);
}
private static void OnWindowOpened(object? sender, RoutedEventArgs e)
{
- _activeLifetime?._windows.Add((Window)sender!);
+ s_activeLifetime?._windows.Add((Window)sender!);
}
public ClassicDesktopStyleApplicationLifetime()
{
- if (_activeLifetime != null)
+ if (s_activeLifetime != null)
throw new InvalidOperationException(
"Can not have multiple active ClassicDesktopStyleApplicationLifetime instances and the previously created one was not disposed");
- _activeLifetime = this;
+ s_activeLifetime = this;
}
///
@@ -65,9 +66,10 @@ namespace Avalonia.Controls.ApplicationLifetimes
///
public Window? MainWindow { get; set; }
+ ///
public IReadOnlyList Windows => _windows.ToArray();
- private void HandleWindowClosed(Window window)
+ private void HandleWindowClosed(Window? window)
{
if (window == null)
return;
@@ -130,8 +132,8 @@ namespace Avalonia.Controls.ApplicationLifetimes
public void Dispose()
{
- if (_activeLifetime == this)
- _activeLifetime = null;
+ if (s_activeLifetime == this)
+ s_activeLifetime = null;
}
private bool DoShutdown(
diff --git a/src/Avalonia.Controls/ApplicationLifetimes/IClassicDesktopStyleApplicationLifetime.cs b/src/Avalonia.Controls/ApplicationLifetimes/IClassicDesktopStyleApplicationLifetime.cs
index 22b5f8236d..b9a372f935 100644
--- a/src/Avalonia.Controls/ApplicationLifetimes/IClassicDesktopStyleApplicationLifetime.cs
+++ b/src/Avalonia.Controls/ApplicationLifetimes/IClassicDesktopStyleApplicationLifetime.cs
@@ -40,7 +40,10 @@ namespace Avalonia.Controls.ApplicationLifetimes
/// The main window.
///
Window? MainWindow { get; set; }
-
+
+ ///
+ /// Gets the list of all open windows in the application.
+ ///
IReadOnlyList Windows { get; }
///
diff --git a/src/Avalonia.Controls/AutoCompleteBox/AutoCompleteBox.cs b/src/Avalonia.Controls/AutoCompleteBox/AutoCompleteBox.cs
index 55649660f7..9a949e31d4 100644
--- a/src/Avalonia.Controls/AutoCompleteBox/AutoCompleteBox.cs
+++ b/src/Avalonia.Controls/AutoCompleteBox/AutoCompleteBox.cs
@@ -1711,7 +1711,7 @@ namespace Avalonia.Controls
/// The predicate to use for the partial or
/// exact match.
/// Returns the object or null.
- private object? TryGetMatch(string? searchText, AvaloniaList view, AutoCompleteFilterPredicate? predicate)
+ private object? TryGetMatch(string? searchText, AvaloniaList? view, AutoCompleteFilterPredicate? predicate)
{
if (predicate is null)
return null;
diff --git a/src/Avalonia.Controls/Automation/AutomationProperties.cs b/src/Avalonia.Controls/Automation/AutomationProperties.cs
index 35f94722ce..3ea9c170ff 100644
--- a/src/Avalonia.Controls/Automation/AutomationProperties.cs
+++ b/src/Avalonia.Controls/Automation/AutomationProperties.cs
@@ -38,8 +38,8 @@ namespace Avalonia.Automation
///
/// Defines the AutomationProperties.AcceleratorKey attached property.
///
- public static readonly AttachedProperty AcceleratorKeyProperty =
- AvaloniaProperty.RegisterAttached(
+ public static readonly AttachedProperty AcceleratorKeyProperty =
+ AvaloniaProperty.RegisterAttached(
"AcceleratorKey",
typeof(AutomationProperties));
@@ -54,16 +54,16 @@ namespace Avalonia.Automation
///
/// Defines the AutomationProperties.AccessKey attached property
///
- public static readonly AttachedProperty AccessKeyProperty =
- AvaloniaProperty.RegisterAttached(
+ public static readonly AttachedProperty AccessKeyProperty =
+ AvaloniaProperty.RegisterAttached(
"AccessKey",
typeof(AutomationProperties));
///
/// Defines the AutomationProperties.AutomationId attached property.
///
- public static readonly AttachedProperty AutomationIdProperty =
- AvaloniaProperty.RegisterAttached(
+ public static readonly AttachedProperty AutomationIdProperty =
+ AvaloniaProperty.RegisterAttached(
"AutomationId",
typeof(AutomationProperties));
@@ -78,8 +78,8 @@ namespace Avalonia.Automation
///
/// Defines the AutomationProperties.HelpText attached property.
///
- public static readonly AttachedProperty HelpTextProperty =
- AvaloniaProperty.RegisterAttached(
+ public static readonly AttachedProperty HelpTextProperty =
+ AvaloniaProperty.RegisterAttached(
"HelpText",
typeof(AutomationProperties));
@@ -122,16 +122,16 @@ namespace Avalonia.Automation
///
/// Defines the AutomationProperties.ItemStatus attached property.
///
- public static readonly AttachedProperty ItemStatusProperty =
- AvaloniaProperty.RegisterAttached(
+ public static readonly AttachedProperty ItemStatusProperty =
+ AvaloniaProperty.RegisterAttached(
"ItemStatus",
typeof(AutomationProperties));
///
/// Defines the AutomationProperties.ItemType attached property.
///
- public static readonly AttachedProperty ItemTypeProperty =
- AvaloniaProperty.RegisterAttached(
+ public static readonly AttachedProperty ItemTypeProperty =
+ AvaloniaProperty.RegisterAttached(
"ItemType",
typeof(AutomationProperties));
@@ -155,8 +155,8 @@ namespace Avalonia.Automation
///
/// Defines the AutomationProperties.Name attached attached property.
///
- public static readonly AttachedProperty NameProperty =
- AvaloniaProperty.RegisterAttached(
+ public static readonly AttachedProperty NameProperty =
+ AvaloniaProperty.RegisterAttached(
"Name",
typeof(AutomationProperties));
@@ -193,25 +193,17 @@ namespace Avalonia.Automation
///
public static void SetAcceleratorKey(StyledElement element, string value)
{
- if (element == null)
- {
- throw new ArgumentNullException(nameof(element));
- }
-
+ _ = element ?? throw new ArgumentNullException(nameof(element));
element.SetValue(AcceleratorKeyProperty, value);
}
///
/// Helper for reading AcceleratorKey property from a StyledElement.
///
- public static string GetAcceleratorKey(StyledElement element)
+ public static string? GetAcceleratorKey(StyledElement element)
{
- if (element == null)
- {
- throw new ArgumentNullException(nameof(element));
- }
-
- return ((string)element.GetValue(AcceleratorKeyProperty));
+ _ = element ?? throw new ArgumentNullException(nameof(element));
+ return element.GetValue(AcceleratorKeyProperty);
}
///
@@ -219,11 +211,7 @@ namespace Avalonia.Automation
///
public static void SetAccessibilityView(StyledElement element, AccessibilityView value)
{
- if (element == null)
- {
- throw new ArgumentNullException(nameof(element));
- }
-
+ _ = element ?? throw new ArgumentNullException(nameof(element));
element.SetValue(AccessibilityViewProperty, value);
}
@@ -232,11 +220,7 @@ namespace Avalonia.Automation
///
public static AccessibilityView GetAccessibilityView(StyledElement element)
{
- if (element == null)
- {
- throw new ArgumentNullException(nameof(element));
- }
-
+ _ = element ?? throw new ArgumentNullException(nameof(element));
return element.GetValue(AccessibilityViewProperty);
}
@@ -245,50 +229,34 @@ namespace Avalonia.Automation
///
public static void SetAccessKey(StyledElement element, string value)
{
- if (element == null)
- {
- throw new ArgumentNullException(nameof(element));
- }
-
+ _ = element ?? throw new ArgumentNullException(nameof(element));
element.SetValue(AccessKeyProperty, value);
}
///
/// Helper for reading AccessKey property from a StyledElement.
///
- public static string GetAccessKey(StyledElement element)
+ public static string? GetAccessKey(StyledElement element)
{
- if (element == null)
- {
- throw new ArgumentNullException(nameof(element));
- }
-
- return ((string)element.GetValue(AccessKeyProperty));
+ _ = element ?? throw new ArgumentNullException(nameof(element));
+ return element.GetValue(AccessKeyProperty);
}
///
/// Helper for setting AutomationId property on a StyledElement.
///
- public static void SetAutomationId(StyledElement element, string value)
+ public static void SetAutomationId(StyledElement element, string? value)
{
- if (element == null)
- {
- throw new ArgumentNullException(nameof(element));
- }
-
+ _ = element ?? throw new ArgumentNullException(nameof(element));
element.SetValue(AutomationIdProperty, value);
}
///
/// Helper for reading AutomationId property from a StyledElement.
///
- public static string GetAutomationId(StyledElement element)
+ public static string? GetAutomationId(StyledElement element)
{
- if (element == null)
- {
- throw new ArgumentNullException(nameof(element));
- }
-
+ _ = element ?? throw new ArgumentNullException(nameof(element));
return element.GetValue(AutomationIdProperty);
}
@@ -297,11 +265,7 @@ namespace Avalonia.Automation
///
public static void SetControlTypeOverride(StyledElement element, AutomationControlType? value)
{
- if (element == null)
- {
- throw new ArgumentNullException(nameof(element));
- }
-
+ _ = element ?? throw new ArgumentNullException(nameof(element));
element.SetValue(ControlTypeOverrideProperty, value);
}
@@ -310,38 +274,26 @@ namespace Avalonia.Automation
///
public static AutomationControlType? GetControlTypeOverride(StyledElement element)
{
- if (element == null)
- {
- throw new ArgumentNullException(nameof(element));
- }
-
+ _ = element ?? throw new ArgumentNullException(nameof(element));
return element.GetValue(ControlTypeOverrideProperty);
}
///
/// Helper for setting HelpText property on a StyledElement.
///
- public static void SetHelpText(StyledElement element, string value)
+ public static void SetHelpText(StyledElement element, string? value)
{
- if (element == null)
- {
- throw new ArgumentNullException(nameof(element));
- }
-
+ _ = element ?? throw new ArgumentNullException(nameof(element));
element.SetValue(HelpTextProperty, value);
}
///
/// Helper for reading HelpText property from a StyledElement.
///
- public static string GetHelpText(StyledElement element)
+ public static string? GetHelpText(StyledElement element)
{
- if (element == null)
- {
- throw new ArgumentNullException(nameof(element));
- }
-
- return ((string)element.GetValue(HelpTextProperty));
+ _ = element ?? throw new ArgumentNullException(nameof(element));
+ return element.GetValue(HelpTextProperty);
}
///
@@ -349,11 +301,7 @@ namespace Avalonia.Automation
///
public static void SetIsColumnHeader(StyledElement element, bool value)
{
- if (element == null)
- {
- throw new ArgumentNullException(nameof(element));
- }
-
+ _ = element ?? throw new ArgumentNullException(nameof(element));
element.SetValue(IsColumnHeaderProperty, value);
}
@@ -362,12 +310,8 @@ namespace Avalonia.Automation
///
public static bool GetIsColumnHeader(StyledElement element)
{
- if (element == null)
- {
- throw new ArgumentNullException(nameof(element));
- }
-
- return ((bool)element.GetValue(IsColumnHeaderProperty));
+ _ = element ?? throw new ArgumentNullException(nameof(element));
+ return element.GetValue(IsColumnHeaderProperty);
}
///
@@ -375,11 +319,7 @@ namespace Avalonia.Automation
///
public static void SetIsRequiredForForm(StyledElement element, bool value)
{
- if (element == null)
- {
- throw new ArgumentNullException(nameof(element));
- }
-
+ _ = element ?? throw new ArgumentNullException(nameof(element));
element.SetValue(IsRequiredForFormProperty, value);
}
@@ -388,12 +328,8 @@ namespace Avalonia.Automation
///
public static bool GetIsRequiredForForm(StyledElement element)
{
- if (element == null)
- {
- throw new ArgumentNullException(nameof(element));
- }
-
- return ((bool)element.GetValue(IsRequiredForFormProperty));
+ _ = element ?? throw new ArgumentNullException(nameof(element));
+ return element.GetValue(IsRequiredForFormProperty);
}
///
@@ -401,12 +337,8 @@ namespace Avalonia.Automation
///
public static bool GetIsRowHeader(StyledElement element)
{
- if (element == null)
- {
- throw new ArgumentNullException(nameof(element));
- }
-
- return ((bool)element.GetValue(IsRowHeaderProperty));
+ _ = element ?? throw new ArgumentNullException(nameof(element));
+ return element.GetValue(IsRowHeaderProperty);
}
///
@@ -414,11 +346,7 @@ namespace Avalonia.Automation
///
public static void SetIsRowHeader(StyledElement element, bool value)
{
- if (element == null)
- {
- throw new ArgumentNullException(nameof(element));
- }
-
+ _ = element ?? throw new ArgumentNullException(nameof(element));
element.SetValue(IsRowHeaderProperty, value);
}
@@ -427,11 +355,7 @@ namespace Avalonia.Automation
///
public static void SetIsOffscreenBehavior(StyledElement element, IsOffscreenBehavior value)
{
- if (element == null)
- {
- throw new ArgumentNullException(nameof(element));
- }
-
+ _ = element ?? throw new ArgumentNullException(nameof(element));
element.SetValue(IsOffscreenBehaviorProperty, value);
}
@@ -440,64 +364,44 @@ namespace Avalonia.Automation
///
public static IsOffscreenBehavior GetIsOffscreenBehavior(StyledElement element)
{
- if (element == null)
- {
- throw new ArgumentNullException(nameof(element));
- }
-
- return ((IsOffscreenBehavior)element.GetValue(IsOffscreenBehaviorProperty));
+ _ = element ?? throw new ArgumentNullException(nameof(element));
+ return element.GetValue(IsOffscreenBehaviorProperty);
}
///
/// Helper for setting ItemStatus property on a StyledElement.
///
- public static void SetItemStatus(StyledElement element, string value)
+ public static void SetItemStatus(StyledElement element, string? value)
{
- if (element == null)
- {
- throw new ArgumentNullException(nameof(element));
- }
-
+ _ = element ?? throw new ArgumentNullException(nameof(element));
element.SetValue(ItemStatusProperty, value);
}
///
/// Helper for reading ItemStatus property from a StyledElement.
///
- public static string GetItemStatus(StyledElement element)
+ public static string? GetItemStatus(StyledElement element)
{
- if (element == null)
- {
- throw new ArgumentNullException(nameof(element));
- }
-
- return ((string)element.GetValue(ItemStatusProperty));
+ _ = element ?? throw new ArgumentNullException(nameof(element));
+ return element.GetValue(ItemStatusProperty);
}
///
/// Helper for setting ItemType property on a StyledElement.
///
- public static void SetItemType(StyledElement element, string value)
+ public static void SetItemType(StyledElement element, string? value)
{
- if (element == null)
- {
- throw new ArgumentNullException(nameof(element));
- }
-
+ _ = element ?? throw new ArgumentNullException(nameof(element));
element.SetValue(ItemTypeProperty, value);
}
///
/// Helper for reading ItemType property from a StyledElement.
///
- public static string GetItemType(StyledElement element)
+ public static string? GetItemType(StyledElement element)
{
- if (element == null)
- {
- throw new ArgumentNullException(nameof(element));
- }
-
- return ((string)element.GetValue(ItemTypeProperty));
+ _ = element ?? throw new ArgumentNullException(nameof(element));
+ return element.GetValue(ItemTypeProperty);
}
///
@@ -505,11 +409,7 @@ namespace Avalonia.Automation
///
public static void SetLabeledBy(StyledElement element, Control value)
{
- if (element == null)
- {
- throw new ArgumentNullException(nameof(element));
- }
-
+ _ = element ?? throw new ArgumentNullException(nameof(element));
element.SetValue(LabeledByProperty, value);
}
@@ -518,11 +418,7 @@ namespace Avalonia.Automation
///
public static Control GetLabeledBy(StyledElement element)
{
- if (element == null)
- {
- throw new ArgumentNullException(nameof(element));
- }
-
+ _ = element ?? throw new ArgumentNullException(nameof(element));
return element.GetValue(LabeledByProperty);
}
@@ -531,11 +427,7 @@ namespace Avalonia.Automation
///
public static void SetLiveSetting(StyledElement element, AutomationLiveSetting value)
{
- if (element == null)
- {
- throw new ArgumentNullException(nameof(element));
- }
-
+ _ = element ?? throw new ArgumentNullException(nameof(element));
element.SetValue(LiveSettingProperty, value);
}
@@ -544,38 +436,26 @@ namespace Avalonia.Automation
///
public static AutomationLiveSetting GetLiveSetting(StyledElement element)
{
- if (element == null)
- {
- throw new ArgumentNullException(nameof(element));
- }
-
- return ((AutomationLiveSetting)element.GetValue(LiveSettingProperty));
+ _ = element ?? throw new ArgumentNullException(nameof(element));
+ return element.GetValue(LiveSettingProperty);
}
///
/// Helper for setting Name property on a StyledElement.
///
- public static void SetName(StyledElement element, string value)
+ public static void SetName(StyledElement element, string? value)
{
- if (element == null)
- {
- throw new ArgumentNullException(nameof(element));
- }
-
+ _ = element ?? throw new ArgumentNullException(nameof(element));
element.SetValue(NameProperty, value);
}
///
/// Helper for reading Name property from a StyledElement.
///
- public static string GetName(StyledElement element)
+ public static string? GetName(StyledElement element)
{
- if (element == null)
- {
- throw new ArgumentNullException(nameof(element));
- }
-
- return ((string)element.GetValue(NameProperty));
+ _ = element ?? throw new ArgumentNullException(nameof(element));
+ return element.GetValue(NameProperty);
}
///
@@ -583,11 +463,7 @@ namespace Avalonia.Automation
///
public static void SetPositionInSet(StyledElement element, int value)
{
- if (element == null)
- {
- throw new ArgumentNullException(nameof(element));
- }
-
+ _ = element ?? throw new ArgumentNullException(nameof(element));
element.SetValue(PositionInSetProperty, value);
}
@@ -596,12 +472,8 @@ namespace Avalonia.Automation
///
public static int GetPositionInSet(StyledElement element)
{
- if (element == null)
- {
- throw new ArgumentNullException(nameof(element));
- }
-
- return ((int)element.GetValue(PositionInSetProperty));
+ _ = element ?? throw new ArgumentNullException(nameof(element));
+ return element.GetValue(PositionInSetProperty);
}
///
@@ -609,11 +481,7 @@ namespace Avalonia.Automation
///
public static void SetSizeOfSet(StyledElement element, int value)
{
- if (element == null)
- {
- throw new ArgumentNullException(nameof(element));
- }
-
+ _ = element ?? throw new ArgumentNullException(nameof(element));
element.SetValue(SizeOfSetProperty, value);
}
@@ -622,12 +490,8 @@ namespace Avalonia.Automation
///
public static int GetSizeOfSet(StyledElement element)
{
- if (element == null)
- {
- throw new ArgumentNullException(nameof(element));
- }
-
- return ((int)element.GetValue(SizeOfSetProperty));
+ _ = element ?? throw new ArgumentNullException(nameof(element));
+ return element.GetValue(SizeOfSetProperty);
}
}
}
diff --git a/src/Avalonia.Controls/Automation/Peers/ProgressBarAutomationPeer.cs b/src/Avalonia.Controls/Automation/Peers/ProgressBarAutomationPeer.cs
new file mode 100644
index 0000000000..3c59f74c90
--- /dev/null
+++ b/src/Avalonia.Controls/Automation/Peers/ProgressBarAutomationPeer.cs
@@ -0,0 +1,62 @@
+using System;
+using Avalonia.Automation.Peers;
+using Avalonia.Automation.Provider;
+using Avalonia.Controls.Primitives;
+
+namespace Avalonia.Controls.Automation.Peers
+{
+ public class ProgressBarAutomationPeer : RangeBaseAutomationPeer, IRangeValueProvider
+ {
+ public ProgressBarAutomationPeer(RangeBase owner) : base(owner)
+ {
+ }
+
+ protected override string GetClassNameCore()
+ {
+ return "ProgressBar";
+ }
+
+ protected override AutomationControlType GetAutomationControlTypeCore()
+ {
+ return AutomationControlType.ProgressBar;
+ }
+
+ ///
+ /// Request to set the value that this UI element is representing
+ ///
+ /// Value to set the UI to, as an object
+ /// true if the UI element was successfully set to the specified value
+ void IRangeValueProvider.SetValue(double val)
+ {
+ throw new InvalidOperationException("ProgressBar is ReadOnly, value can't be set.");
+ }
+
+ ///Indicates that the value can only be read, not modified.
+ ///returns True if the control is read-only
+ bool IRangeValueProvider.IsReadOnly
+ {
+ get
+ {
+ return true;
+ }
+ }
+
+ ///Value of a Large Change
+ double IRangeValueProvider.LargeChange
+ {
+ get
+ {
+ return double.NaN;
+ }
+ }
+
+ ///Value of a Small Change
+ double IRangeValueProvider.SmallChange
+ {
+ get
+ {
+ return double.NaN;
+ }
+ }
+ }
+}
diff --git a/src/Avalonia.Controls/Automation/Peers/SliderAutomationPeer.cs b/src/Avalonia.Controls/Automation/Peers/SliderAutomationPeer.cs
new file mode 100644
index 0000000000..42b15eec96
--- /dev/null
+++ b/src/Avalonia.Controls/Automation/Peers/SliderAutomationPeer.cs
@@ -0,0 +1,22 @@
+using Avalonia.Automation.Peers;
+
+namespace Avalonia.Controls.Automation.Peers
+{
+ public class SliderAutomationPeer : RangeBaseAutomationPeer
+ {
+ public SliderAutomationPeer(Slider owner) : base(owner)
+ {
+ }
+
+ override protected string GetClassNameCore()
+ {
+ return "Slider";
+ }
+
+ override protected AutomationControlType GetAutomationControlTypeCore()
+ {
+ return AutomationControlType.Slider;
+ }
+
+ }
+}
diff --git a/src/Avalonia.Controls/Border.cs b/src/Avalonia.Controls/Border.cs
index 1bb574acd2..78ba23c1dd 100644
--- a/src/Avalonia.Controls/Border.cs
+++ b/src/Avalonia.Controls/Border.cs
@@ -225,7 +225,7 @@ namespace Avalonia.Controls
/// Renders the control.
///
/// The drawing context.
- public override void Render(DrawingContext context)
+ public sealed override void Render(DrawingContext context)
{
_borderRenderHelper.Render(context, Bounds.Size, LayoutThickness, CornerRadius, Background, BorderBrush,
BoxShadow, BorderDashOffset, BorderLineCap, BorderLineJoin, BorderDashArray);
diff --git a/src/Avalonia.Controls/Calendar/Calendar.cs b/src/Avalonia.Controls/Calendar/Calendar.cs
index 9c88bae5f6..3300292857 100644
--- a/src/Avalonia.Controls/Calendar/Calendar.cs
+++ b/src/Avalonia.Controls/Calendar/Calendar.cs
@@ -237,11 +237,11 @@ namespace Avalonia.Controls
private DateTime _selectedYear;
private DateTime _displayDate = DateTime.Today;
- private DateTime? _displayDateStart = null;
- private DateTime? _displayDateEnd = null;
+ private DateTime? _displayDateStart;
+ private DateTime? _displayDateEnd;
private bool _isShiftPressed;
- private bool _displayDateIsChanging = false;
+ private bool _displayDateIsChanging;
internal CalendarDayButton? FocusButton { get; set; }
internal CalendarButton? FocusCalendarButton { get; set; }
@@ -291,7 +291,7 @@ namespace Avalonia.Controls
}
else
{
- throw new ArgumentOutOfRangeException("d", "Invalid DayOfWeek");
+ throw new ArgumentOutOfRangeException(nameof(e), "Invalid DayOfWeek");
}
}
@@ -346,10 +346,10 @@ namespace Avalonia.Controls
}
}
- public static readonly StyledProperty HeaderBackgroundProperty =
- AvaloniaProperty.Register(nameof(HeaderBackground));
+ public static readonly StyledProperty HeaderBackgroundProperty =
+ AvaloniaProperty.Register(nameof(HeaderBackground));
- public IBrush HeaderBackground
+ public IBrush? HeaderBackground
{
get { return GetValue(HeaderBackgroundProperty); }
set { SetValue(HeaderBackgroundProperty, value); }
@@ -478,7 +478,7 @@ namespace Avalonia.Controls
}
else
{
- throw new ArgumentOutOfRangeException("d", "Invalid SelectionMode");
+ throw new ArgumentOutOfRangeException(nameof(e), "Invalid SelectionMode");
}
}
@@ -574,7 +574,7 @@ namespace Avalonia.Controls
}
else
{
- throw new ArgumentOutOfRangeException("d", "SelectedDate value is not valid.");
+ throw new ArgumentOutOfRangeException(nameof(e), "SelectedDate value is not valid.");
}
}
else
diff --git a/src/Avalonia.Controls/Calendar/CalendarBlackoutDatesCollection.cs b/src/Avalonia.Controls/Calendar/CalendarBlackoutDatesCollection.cs
index fe8b616e02..8fb9b66f3d 100644
--- a/src/Avalonia.Controls/Calendar/CalendarBlackoutDatesCollection.cs
+++ b/src/Avalonia.Controls/Calendar/CalendarBlackoutDatesCollection.cs
@@ -15,7 +15,7 @@ namespace Avalonia.Controls.Primitives
///
/// The Calendar whose dates this object represents.
///
- private Calendar _owner;
+ private readonly Calendar _owner;
///
/// Initializes a new instance of the
@@ -79,13 +79,13 @@ namespace Avalonia.Controls.Primitives
if (DateTime.Compare(end, start) > -1)
{
- rangeStart = DateTimeHelper.DiscardTime(start).Value;
- rangeEnd = DateTimeHelper.DiscardTime(end).Value;
+ rangeStart = DateTimeHelper.DiscardTime(start);
+ rangeEnd = DateTimeHelper.DiscardTime(end);
}
else
{
- rangeStart = DateTimeHelper.DiscardTime(end).Value;
- rangeEnd = DateTimeHelper.DiscardTime(start).Value;
+ rangeStart = DateTimeHelper.DiscardTime(end);
+ rangeEnd = DateTimeHelper.DiscardTime(start);
}
int count = Count;
@@ -144,7 +144,7 @@ namespace Avalonia.Controls.Primitives
if (!IsValid(item))
{
- throw new ArgumentOutOfRangeException("Value is not valid.");
+ throw new ArgumentOutOfRangeException(nameof(item), "Value is not valid.");
}
base.InsertItem(index, item);
@@ -186,7 +186,7 @@ namespace Avalonia.Controls.Primitives
if (!IsValid(item))
{
- throw new ArgumentOutOfRangeException("Value is not valid.");
+ throw new ArgumentOutOfRangeException(nameof(item), "Value is not valid.");
}
base.SetItem(index, item);
diff --git a/src/Avalonia.Controls/Calendar/CalendarItem.cs b/src/Avalonia.Controls/Calendar/CalendarItem.cs
index 032f452111..3d436b4485 100644
--- a/src/Avalonia.Controls/Calendar/CalendarItem.cs
+++ b/src/Avalonia.Controls/Calendar/CalendarItem.cs
@@ -44,30 +44,30 @@ namespace Avalonia.Controls.Primitives
private ITemplate? _dayTitleTemplate;
private DateTime _currentMonth;
- private bool _isMouseLeftButtonDown = false;
- private bool _isMouseLeftButtonDownYearView = false;
- private bool _isControlPressed = false;
+ private bool _isMouseLeftButtonDown;
+ private bool _isMouseLeftButtonDownYearView;
+ private bool _isControlPressed;
- private System.Globalization.Calendar _calendar = new System.Globalization.GregorianCalendar();
-
- private PointerPressedEventArgs? _downEventArg;
- private PointerPressedEventArgs? _downEventArgYearView;
+ private readonly System.Globalization.Calendar _calendar = new GregorianCalendar();
internal Calendar? Owner { get; set; }
internal CalendarDayButton? CurrentButton { get; set; }
- public static readonly StyledProperty HeaderBackgroundProperty = Calendar.HeaderBackgroundProperty.AddOwner();
- public IBrush HeaderBackground
+ public static readonly StyledProperty HeaderBackgroundProperty = Calendar.HeaderBackgroundProperty.AddOwner();
+
+ public IBrush? HeaderBackground
{
get { return GetValue(HeaderBackgroundProperty); }
set { SetValue(HeaderBackgroundProperty, value); }
}
+
public static readonly DirectProperty?> DayTitleTemplateProperty =
AvaloniaProperty.RegisterDirect?>(
nameof(DayTitleTemplate),
o => o.DayTitleTemplate,
(o,v) => o.DayTitleTemplate = v,
defaultBindingMode: BindingMode.OneTime);
+
public ITemplate? DayTitleTemplate
{
get { return _dayTitleTemplate; }
@@ -178,7 +178,7 @@ namespace Avalonia.Controls.Primitives
{
if (_dayTitleTemplate != null)
{
- var cell = (Control) _dayTitleTemplate.Build();
+ var cell = _dayTitleTemplate.Build();
cell.DataContext = string.Empty;
cell.SetValue(Grid.RowProperty, 0);
cell.SetValue(Grid.ColumnProperty, i);
@@ -308,16 +308,13 @@ namespace Avalonia.Controls.Primitives
for (int childIndex = 0; childIndex < Calendar.ColumnsPerMonth; childIndex++)
{
var daytitle = MonthView!.Children[childIndex];
- if (daytitle != null)
+ if (Owner != null)
{
- if (Owner != null)
- {
- daytitle.DataContext = DateTimeHelper.GetCurrentDateFormat().ShortestDayNames[(childIndex + (int)Owner.FirstDayOfWeek) % NumberOfDaysPerWeek];
- }
- else
- {
- daytitle.DataContext = DateTimeHelper.GetCurrentDateFormat().ShortestDayNames[(childIndex + (int)DateTimeHelper.GetCurrentDateFormat().FirstDayOfWeek) % NumberOfDaysPerWeek];
- }
+ daytitle.DataContext = DateTimeHelper.GetCurrentDateFormat().ShortestDayNames[(childIndex + (int)Owner.FirstDayOfWeek) % NumberOfDaysPerWeek];
+ }
+ else
+ {
+ daytitle.DataContext = DateTimeHelper.GetCurrentDateFormat().ShortestDayNames[(childIndex + (int)DateTimeHelper.GetCurrentDateFormat().FirstDayOfWeek) % NumberOfDaysPerWeek];
}
}
}
@@ -527,7 +524,7 @@ namespace Avalonia.Controls.Primitives
childButton.Content = dateToAdd.Day.ToString(DateTimeHelper.GetCurrentDateFormat());
childButton.DataContext = dateToAdd;
- if (DateTime.Compare((DateTime)DateTimeHelper.DiscardTime(DateTime.MaxValue), dateToAdd) > 0)
+ if (DateTime.Compare(DateTimeHelper.DiscardTime(DateTime.MaxValue), dateToAdd) > 0)
{
// Since we are sure DisplayDate is not equal to
// DateTime.MaxValue, it is safe to use AddDays
@@ -587,7 +584,7 @@ namespace Avalonia.Controls.Primitives
{
if (Owner != null)
{
- _currentMonth = (DateTime)Owner.SelectedMonth;
+ _currentMonth = Owner.SelectedMonth;
}
else
{
@@ -676,7 +673,7 @@ namespace Avalonia.Controls.Primitives
if (Owner != null)
{
selectedYear = Owner.SelectedYear;
- _currentMonth = (DateTime)Owner.SelectedMonth;
+ _currentMonth = Owner.SelectedMonth;
}
else
{
@@ -696,9 +693,9 @@ namespace Avalonia.Controls.Primitives
SetYearButtons(decade, decadeEnd);
}
}
- internal void UpdateYearViewSelection(CalendarButton calendarButton)
+ internal void UpdateYearViewSelection(CalendarButton? calendarButton)
{
- if (Owner != null && calendarButton != null && calendarButton.DataContext != null)
+ if (Owner != null && calendarButton?.DataContext is DateTime selectedDate)
{
Owner.FocusCalendarButton!.IsCalendarButtonFocused = false;
Owner.FocusCalendarButton = calendarButton;
@@ -706,11 +703,11 @@ namespace Avalonia.Controls.Primitives
if (Owner.DisplayMode == CalendarMode.Year)
{
- Owner.SelectedMonth = (DateTime)calendarButton.DataContext;
+ Owner.SelectedMonth = selectedDate;
}
else
{
- Owner.SelectedYear = (DateTime)calendarButton.DataContext;
+ Owner.SelectedYear = selectedDate;
}
}
}
@@ -719,7 +716,7 @@ namespace Avalonia.Controls.Primitives
{
int year;
int count = -1;
- foreach (object child in YearView!.Children)
+ foreach (var child in YearView!.Children)
{
CalendarButton childButton = (CalendarButton)child;
year = decade + count;
@@ -859,7 +856,8 @@ namespace Avalonia.Controls.Primitives
{
if (Owner != null)
{
- if (_isMouseLeftButtonDown && sender is CalendarDayButton b && b.IsEnabled && !b.IsBlackout)
+ if (_isMouseLeftButtonDown
+ && sender is CalendarDayButton { IsEnabled: true, IsBlackout: false, DataContext: DateTime selectedDate } b)
{
// Update the states of all buttons to be selected starting
// from HoverStart to b
@@ -867,7 +865,6 @@ namespace Avalonia.Controls.Primitives
{
case CalendarSelectionMode.SingleDate:
{
- DateTime selectedDate = (DateTime)b.DataContext!;
Owner.CalendarDatePickerDisplayDateFlag = true;
if (Owner.SelectedDates.Count == 0)
{
@@ -882,10 +879,9 @@ namespace Avalonia.Controls.Primitives
case CalendarSelectionMode.SingleRange:
case CalendarSelectionMode.MultipleRange:
{
- Debug.Assert(b.DataContext != null, "The DataContext should not be null!");
Owner.UnHighlightDays();
Owner.HoverEndIndex = b.Index;
- Owner.HoverEnd = (DateTime?)b.DataContext;
+ Owner.HoverEnd = selectedDate;
// Update the States of the buttons
Owner.HighlightDays();
return;
@@ -904,22 +900,14 @@ namespace Avalonia.Controls.Primitives
Owner.Focus();
}
- bool ctrl, shift;
- CalendarExtensions.GetMetaKeyState(e.KeyModifiers, out ctrl, out shift);
- CalendarDayButton b = (CalendarDayButton)sender!;
+ CalendarExtensions.GetMetaKeyState(e.KeyModifiers, out var ctrl, out var shift);
- if (b != null)
+ if (sender is CalendarDayButton b)
{
_isControlPressed = ctrl;
- if (b.IsEnabled && !b.IsBlackout)
+ if (b.IsEnabled && !b.IsBlackout && b.DataContext is DateTime selectedDate)
{
- DateTime selectedDate = (DateTime)b.DataContext!;
_isMouseLeftButtonDown = true;
- // null check is added for unit tests
- if (e != null)
- {
- _downEventArg = e;
- }
switch (Owner.SelectionMode)
{
@@ -1010,12 +998,12 @@ namespace Avalonia.Controls.Primitives
}
}
}
- private void AddSelection(CalendarDayButton b)
+ private void AddSelection(CalendarDayButton b, DateTime selectedDate)
{
if (Owner != null)
{
Owner.HoverEndIndex = b.Index;
- Owner.HoverEnd = (DateTime)b.DataContext!;
+ Owner.HoverEnd = selectedDate;
if (Owner.HoverEnd != null && Owner.HoverStart != null)
{
@@ -1025,7 +1013,7 @@ namespace Avalonia.Controls.Primitives
// SelectionMode
Owner.IsMouseSelection = true;
Owner.SelectedDates.AddRange(Owner.HoverStart.Value, Owner.HoverEnd.Value);
- Owner.OnDayClick((DateTime)b.DataContext);
+ Owner.OnDayClick(selectedDate);
}
}
}
@@ -1039,11 +1027,11 @@ namespace Avalonia.Controls.Primitives
Owner.OnDayButtonMouseUp(e);
}
_isMouseLeftButtonDown = false;
- if (b != null && b.DataContext != null)
+ if (b != null && b.DataContext is DateTime selectedDate)
{
if (Owner.SelectionMode == CalendarSelectionMode.None || Owner.SelectionMode == CalendarSelectionMode.SingleDate)
{
- Owner.OnDayClick((DateTime)b.DataContext);
+ Owner.OnDayClick(selectedDate);
return;
}
if (Owner.HoverStart.HasValue)
@@ -1058,14 +1046,14 @@ namespace Avalonia.Controls.Primitives
Owner.RemovedItems.Add(item);
}
Owner.SelectedDates.ClearInternal();
- AddSelection(b);
+ AddSelection(b, selectedDate);
return;
}
case CalendarSelectionMode.MultipleRange:
{
// add the selection (either single day or
// SingleRange day)
- AddSelection(b);
+ AddSelection(b, selectedDate);
return;
}
}
@@ -1076,7 +1064,7 @@ namespace Avalonia.Controls.Primitives
// be able to switch months
if (b.IsInactive && b.IsBlackout)
{
- Owner.OnDayClick((DateTime)b.DataContext);
+ Owner.OnDayClick(selectedDate);
}
}
}
@@ -1095,9 +1083,9 @@ namespace Avalonia.Controls.Primitives
Owner.HoverStart = null;
_isMouseLeftButtonDown = false;
b.IsSelected = false;
- if (b.DataContext != null)
+ if (b.DataContext is DateTime selectedDate)
{
- Owner.SelectedDates.Remove((DateTime)b.DataContext);
+ Owner.SelectedDates.Remove(selectedDate);
}
}
}
@@ -1107,35 +1095,26 @@ namespace Avalonia.Controls.Primitives
private void Month_CalendarButtonMouseDown(object? sender, PointerPressedEventArgs e)
{
- CalendarButton b = (CalendarButton)sender!;
-
_isMouseLeftButtonDownYearView = true;
- if (e != null)
- {
- _downEventArgYearView = e;
- }
-
- UpdateYearViewSelection(b);
+ UpdateYearViewSelection(sender as CalendarButton);
}
internal void Month_CalendarButtonMouseUp(object? sender, PointerReleasedEventArgs e)
{
_isMouseLeftButtonDownYearView = false;
- if (Owner != null)
+ if (Owner != null && (sender as CalendarButton)?.DataContext is DateTime newMonth)
{
- DateTime newmonth = (DateTime)((CalendarButton)sender!).DataContext!;
-
if (Owner.DisplayMode == CalendarMode.Year)
{
- Owner.DisplayDate = newmonth;
+ Owner.DisplayDate = newMonth;
Owner.DisplayMode = CalendarMode.Month;
}
else
{
Debug.Assert(Owner.DisplayMode == CalendarMode.Decade, "The owning Calendar should be in decade mode!");
- Owner.SelectedMonth = newmonth;
+ Owner.SelectedMonth = newMonth;
Owner.DisplayMode = CalendarMode.Year;
}
}
@@ -1145,8 +1124,7 @@ namespace Avalonia.Controls.Primitives
{
if (_isMouseLeftButtonDownYearView)
{
- CalendarButton b = (CalendarButton)sender!;
- UpdateYearViewSelection(b);
+ UpdateYearViewSelection(sender as CalendarButton);
}
}
diff --git a/src/Avalonia.Controls/Calendar/DateTimeHelper.cs b/src/Avalonia.Controls/Calendar/DateTimeHelper.cs
index bfff03a926..570f05cfe8 100644
--- a/src/Avalonia.Controls/Calendar/DateTimeHelper.cs
+++ b/src/Avalonia.Controls/Calendar/DateTimeHelper.cs
@@ -53,7 +53,7 @@ namespace Avalonia.Controls
public static int CompareDays(DateTime dt1, DateTime dt2)
{
- return DateTime.Compare(DiscardTime(dt1).Value, DiscardTime(dt2).Value);
+ return DateTime.Compare(DiscardTime(dt1), DiscardTime(dt2));
}
public static int CompareYearMonth(DateTime dt1, DateTime dt2)
@@ -71,14 +71,9 @@ namespace Avalonia.Controls
return new DateTime(d.Year, d.Month, 1, 0, 0, 0);
}
- [return: NotNullIfNotNull("d")]
- public static DateTime? DiscardTime(DateTime? d)
+ public static DateTime DiscardTime(DateTime d)
{
- if (d == null)
- {
- return null;
- }
- return d.Value.Date;
+ return d.Date;
}
public static int EndOfDecade(DateTime date)
@@ -127,28 +122,14 @@ namespace Avalonia.Controls
public static string ToYearMonthPatternString(DateTime date)
{
- string result = string.Empty;
- DateTimeFormatInfo format = GetCurrentDateFormat();
-
- if (format != null)
- {
- result = date.ToString(format.YearMonthPattern, format);
- }
-
- return result;
+ var format = GetCurrentDateFormat();
+ return date.ToString(format.YearMonthPattern, format);
}
public static string ToYearString(DateTime date)
{
- string result = string.Empty;
- DateTimeFormatInfo format = GetCurrentDateFormat();
-
- if (format != null)
- {
- result = date.Year.ToString(format);
- }
-
- return result;
+ var format = GetCurrentDateFormat();
+ return date.Year.ToString(format);
}
}
}
diff --git a/src/Avalonia.Controls/CalendarDatePicker/CalendarDatePicker.cs b/src/Avalonia.Controls/CalendarDatePicker/CalendarDatePicker.cs
index b17648f5bb..869bdeabea 100644
--- a/src/Avalonia.Controls/CalendarDatePicker/CalendarDatePicker.cs
+++ b/src/Avalonia.Controls/CalendarDatePicker/CalendarDatePicker.cs
@@ -51,11 +51,11 @@ namespace Avalonia.Controls
private bool _isDropDownOpen;
private DateTime? _selectedDate;
private string? _text;
- private bool _suspendTextChangeHandler = false;
- private bool _isPopupClosing = false;
- private bool _ignoreButtonClick = false;
- private bool _isFlyoutOpen = false;
- private bool _isPressed = false;
+ private bool _suspendTextChangeHandler;
+ private bool _isPopupClosing;
+ private bool _ignoreButtonClick;
+ private bool _isFlyoutOpen;
+ private bool _isPressed;
///
/// Occurs when the drop-down
@@ -185,7 +185,7 @@ namespace Avalonia.Controls
{
_textBox.KeyDown += TextBox_KeyDown;
_textBox.GotFocus += TextBox_GotFocus;
- _textBoxTextChangedSubscription = _textBox.GetObservable(TextBox.TextProperty).Subscribe(txt => TextBox_TextChanged());
+ _textBoxTextChangedSubscription = _textBox.GetObservable(TextBox.TextProperty).Subscribe(_ => TextBox_TextChanged());
if(SelectedDate.HasValue)
{
@@ -292,7 +292,7 @@ namespace Avalonia.Controls
// Text
else if (change.Property == TextProperty)
{
- var (oldValue, newValue) = change.GetOldAndNewValue();
+ var (_, newValue) = change.GetOldAndNewValue();
if (!_suspendTextChangeHandler)
{
@@ -595,9 +595,9 @@ namespace Avalonia.Controls
private void Calendar_KeyDown(object? sender, KeyEventArgs e)
{
- Calendar? c = sender as Calendar ?? throw new ArgumentException("Sender must be Calendar.", nameof(sender));
-
- if (!e.Handled && (e.Key == Key.Enter || e.Key == Key.Space || e.Key == Key.Escape) && c.DisplayMode == CalendarMode.Month)
+ if (!e.Handled
+ && sender is Calendar { DisplayMode: CalendarMode.Month }
+ && (e.Key == Key.Enter || e.Key == Key.Space || e.Key == Key.Escape))
{
Focus();
IsDropDownOpen = false;
diff --git a/src/Avalonia.Controls/Chrome/TitleBar.cs b/src/Avalonia.Controls/Chrome/TitleBar.cs
index 47b0bb6e2d..368c9d4c2f 100644
--- a/src/Avalonia.Controls/Chrome/TitleBar.cs
+++ b/src/Avalonia.Controls/Chrome/TitleBar.cs
@@ -17,28 +17,26 @@ namespace Avalonia.Controls.Chrome
private void UpdateSize(Window window)
{
- if (window != null)
+ Margin = new Thickness(
+ window.OffScreenMargin.Left,
+ window.OffScreenMargin.Top,
+ window.OffScreenMargin.Right,
+ window.OffScreenMargin.Bottom);
+
+ if (window.WindowState != WindowState.FullScreen)
{
- Margin = new Thickness(
- window.OffScreenMargin.Left,
- window.OffScreenMargin.Top,
- window.OffScreenMargin.Right,
- window.OffScreenMargin.Bottom);
+ Height = window.WindowDecorationMargin.Top;
- if (window.WindowState != WindowState.FullScreen)
+ if (_captionButtons != null)
{
- Height = window.WindowDecorationMargin.Top;
-
- if (_captionButtons != null)
- {
- _captionButtons.Height = Height;
- }
+ _captionButtons.Height = Height;
}
-
- IsVisible = window.PlatformImpl?.NeedsManagedDecorations ?? false;
}
+
+ IsVisible = window.PlatformImpl?.NeedsManagedDecorations ?? false;
}
+ ///
protected override void OnApplyTemplate(TemplateAppliedEventArgs e)
{
base.OnApplyTemplate(e);
@@ -55,6 +53,7 @@ namespace Avalonia.Controls.Chrome
}
}
+ ///
protected override void OnAttachedToVisualTree(VisualTreeAttachmentEventArgs e)
{
base.OnAttachedToVisualTree(e);
@@ -64,13 +63,13 @@ namespace Avalonia.Controls.Chrome
_disposables = new CompositeDisposable(6)
{
window.GetObservable(Window.WindowDecorationMarginProperty)
- .Subscribe(x => UpdateSize(window)),
+ .Subscribe(_ => UpdateSize(window)),
window.GetObservable(Window.ExtendClientAreaTitleBarHeightHintProperty)
- .Subscribe(x => UpdateSize(window)),
+ .Subscribe(_ => UpdateSize(window)),
window.GetObservable(Window.OffScreenMarginProperty)
- .Subscribe(x => UpdateSize(window)),
+ .Subscribe(_ => UpdateSize(window)),
window.GetObservable(Window.ExtendClientAreaChromeHintsProperty)
- .Subscribe(x => UpdateSize(window)),
+ .Subscribe(_ => UpdateSize(window)),
window.GetObservable(Window.WindowStateProperty)
.Subscribe(x =>
{
@@ -80,11 +79,12 @@ namespace Avalonia.Controls.Chrome
PseudoClasses.Set(":fullscreen", x == WindowState.FullScreen);
}),
window.GetObservable(Window.IsExtendedIntoWindowDecorationsProperty)
- .Subscribe(x => UpdateSize(window))
+ .Subscribe(_ => UpdateSize(window))
};
}
}
+ ///
protected override void OnDetachedFromVisualTree(VisualTreeAttachmentEventArgs e)
{
base.OnDetachedFromVisualTree(e);
diff --git a/src/Avalonia.Controls/ComboBox.cs b/src/Avalonia.Controls/ComboBox.cs
index b7a298bb16..17a6ad7a09 100644
--- a/src/Avalonia.Controls/ComboBox.cs
+++ b/src/Avalonia.Controls/ComboBox.cs
@@ -1,19 +1,19 @@
using System;
+using System.Diagnostics;
using System.Linq;
using Avalonia.Automation.Peers;
-using Avalonia.Reactive;
-using Avalonia.Controls.Generators;
-using Avalonia.Controls.Mixins;
-using Avalonia.Controls.Presenters;
+using Avalonia.Controls.Metadata;
using Avalonia.Controls.Primitives;
+using Avalonia.Controls.Selection;
using Avalonia.Controls.Shapes;
using Avalonia.Controls.Templates;
+using Avalonia.Controls.Utils;
using Avalonia.Input;
using Avalonia.Interactivity;
using Avalonia.Layout;
using Avalonia.Media;
+using Avalonia.Reactive;
using Avalonia.VisualTree;
-using Avalonia.Controls.Metadata;
namespace Avalonia.Controls
{
@@ -219,7 +219,7 @@ namespace Avalonia.Controls
}
else if (e.Key == Key.Up)
{
- SelectPrev();
+ SelectPrevious();
e.Handled = true;
}
}
@@ -250,7 +250,7 @@ namespace Avalonia.Controls
if (e.Delta.Y < 0)
SelectNext();
else
- SelectPrev();
+ SelectPrevious();
e.Handled = true;
}
@@ -478,19 +478,40 @@ namespace Avalonia.Controls
}
}
- private void SelectNext()
- {
- if (ItemCount >= 1)
- {
- MoveSelection(NavigationDirection.Next, WrapSelection);
- }
- }
+ private void SelectNext() => MoveSelection(SelectedIndex, 1, WrapSelection);
+ private void SelectPrevious() => MoveSelection(SelectedIndex, -1, WrapSelection);
- private void SelectPrev()
+ private void MoveSelection(int startIndex, int step, bool wrap)
{
- if (ItemCount >= 1)
+ static bool IsSelectable(object? o) => (o as AvaloniaObject)?.GetValue(IsEnabledProperty) ?? true;
+
+ var count = ItemCount;
+
+ for (int i = startIndex + step; i != startIndex; i += step)
{
- MoveSelection(NavigationDirection.Previous, WrapSelection);
+ if (i < 0 || i >= count)
+ {
+ if (wrap)
+ {
+ if (i < 0)
+ i += count;
+ else if (i >= count)
+ i %= count;
+ }
+ else
+ {
+ return;
+ }
+ }
+
+ var item = ItemsView[i];
+ var container = ContainerFromIndex(i);
+
+ if (IsSelectable(item) && IsSelectable(container))
+ {
+ SelectedIndex = i;
+ break;
+ }
}
}
}
diff --git a/src/Avalonia.Controls/Control.cs b/src/Avalonia.Controls/Control.cs
index ab7c9948c4..dbece12575 100644
--- a/src/Avalonia.Controls/Control.cs
+++ b/src/Avalonia.Controls/Control.cs
@@ -161,7 +161,7 @@ namespace Avalonia.Controls
get => GetValue(TagProperty);
set => SetValue(TagProperty, value);
}
-
+
///
/// Occurs when the user has completed a context input gesture, such as a right-click.
///
diff --git a/src/Avalonia.Controls/Controls.cs b/src/Avalonia.Controls/Controls.cs
index 8b0e998f64..736c7e8a77 100644
--- a/src/Avalonia.Controls/Controls.cs
+++ b/src/Avalonia.Controls/Controls.cs
@@ -1,3 +1,4 @@
+using System;
using System.Collections.Generic;
using Avalonia.Collections;
@@ -13,7 +14,7 @@ namespace Avalonia.Controls
///
public Controls()
{
- ResetBehavior = ResetBehavior.Remove;
+ Configure();
}
///
@@ -21,9 +22,22 @@ namespace Avalonia.Controls
///
/// The initial items in the collection.
public Controls(IEnumerable items)
- : base(items)
+ {
+ Configure();
+ AddRange(items); // virtual member call in ctor, ok for our current implementation
+ }
+
+ private void Configure()
{
ResetBehavior = ResetBehavior.Remove;
+ Validate = item =>
+ {
+ if (item is null)
+ {
+ throw new ArgumentNullException(nameof(item),
+ $"A null control cannot be added to a {nameof(Controls)} collection.");
+ }
+ };
}
}
}
diff --git a/src/Avalonia.Controls/Converters/MenuScrollingVisibilityConverter.cs b/src/Avalonia.Controls/Converters/MenuScrollingVisibilityConverter.cs
index 9d859a753a..18d668e9a4 100644
--- a/src/Avalonia.Controls/Converters/MenuScrollingVisibilityConverter.cs
+++ b/src/Avalonia.Controls/Converters/MenuScrollingVisibilityConverter.cs
@@ -14,7 +14,6 @@ namespace Avalonia.Controls.Converters
public object? Convert(IList values, Type targetType, object? parameter, CultureInfo culture)
{
if (parameter == null ||
- values == null ||
values.Count != 4 ||
!(values[0] is ScrollBarVisibility visibility) ||
!(values[1] is double offset) ||
diff --git a/src/Avalonia.Controls/DefinitionBase.cs b/src/Avalonia.Controls/DefinitionBase.cs
index 5c35a09f1c..eb587fb157 100644
--- a/src/Avalonia.Controls/DefinitionBase.cs
+++ b/src/Avalonia.Controls/DefinitionBase.cs
@@ -21,9 +21,9 @@ namespace Avalonia.Controls
///
/// SharedSizeGroup property.
///
- public string SharedSizeGroup
+ public string? SharedSizeGroup
{
- get { return (string)GetValue(SharedSizeGroupProperty); }
+ get { return GetValue(SharedSizeGroupProperty); }
set { SetValue(SharedSizeGroupProperty, value); }
}
@@ -32,20 +32,15 @@ namespace Avalonia.Controls
///
internal void OnEnterParentTree()
{
- this.InheritanceParent = Parent;
+ InheritanceParent = Parent;
if (_sharedState == null)
{
// start with getting SharedSizeGroup value.
// this property is NOT inherited which should result in better overall perf.
- string sharedSizeGroupId = SharedSizeGroup;
- if (sharedSizeGroupId != null)
+ if (SharedSizeGroup is { } sharedSizeGroupId && PrivateSharedSizeScope is { } privateSharedSizeScope)
{
- SharedSizeScope? privateSharedSizeScope = PrivateSharedSizeScope;
- if (privateSharedSizeScope != null)
- {
- _sharedState = privateSharedSizeScope.EnsureSharedState(sharedSizeGroupId);
- _sharedState.AddMember(this);
- }
+ _sharedState = privateSharedSizeScope.EnsureSharedState(sharedSizeGroupId);
+ _sharedState.AddMember(this);
}
}
@@ -321,13 +316,12 @@ namespace Avalonia.Controls
return ((_flags & flags) == flags);
}
- private static void OnSharedSizeGroupPropertyChanged(AvaloniaObject d, AvaloniaPropertyChangedEventArgs e)
+ private static void OnSharedSizeGroupPropertyChanged(DefinitionBase definition,
+ AvaloniaPropertyChangedEventArgs e)
{
- DefinitionBase definition = (DefinitionBase)d;
-
if (definition.Parent != null)
{
- string sharedSizeGroupId = (string)e.NewValue!;
+ string? sharedSizeGroupId = e.NewValue.Value;
if (definition._sharedState != null)
{
@@ -337,16 +331,14 @@ namespace Avalonia.Controls
definition._sharedState = null;
}
- if ((definition._sharedState == null) && (sharedSizeGroupId != null))
+ if (definition._sharedState == null
+ && sharedSizeGroupId != null
+ && definition.PrivateSharedSizeScope is { } privateSharedSizeScope)
{
- SharedSizeScope? privateSharedSizeScope = definition.PrivateSharedSizeScope;
- if (privateSharedSizeScope != null)
- {
- // if definition is not registered and both: shared size group id AND private shared scope
- // are available, then register definition.
- definition._sharedState = privateSharedSizeScope.EnsureSharedState(sharedSizeGroupId);
- definition._sharedState.AddMember(definition);
- }
+ // if definition is not registered and both: shared size group id AND private shared scope
+ // are available, then register definition.
+ definition._sharedState = privateSharedSizeScope.EnsureSharedState(sharedSizeGroupId);
+ definition._sharedState.AddMember(definition);
}
}
}
@@ -357,17 +349,15 @@ namespace Avalonia.Controls
/// b) contains only letters, digits and underscore ('_').
/// c) does not start with a digit.
///
- private static bool SharedSizeGroupPropertyValueValid(string value)
+ private static bool SharedSizeGroupPropertyValueValid(string? id)
{
// null is default value
- if (value == null)
+ if (id == null)
{
return true;
}
- string id = (string)value;
-
- if (!string.IsNullOrEmpty(id))
+ if (id.Length > 0)
{
int i = -1;
while (++i < id.Length)
@@ -397,14 +387,11 @@ namespace Avalonia.Controls
/// existing scope just left. In both cases if the DefinitionBase object is already registered
/// in SharedSizeState, it should un-register and register itself in a new one.
///
- private static void OnPrivateSharedSizeScopePropertyChanged(AvaloniaObject d, AvaloniaPropertyChangedEventArgs e)
+ private static void OnPrivateSharedSizeScopePropertyChanged(DefinitionBase definition,
+ AvaloniaPropertyChangedEventArgs e)
{
- DefinitionBase definition = (DefinitionBase)d;
-
if (definition.Parent != null)
{
- SharedSizeScope privateSharedSizeScope = (SharedSizeScope)e.NewValue!;
-
if (definition._sharedState != null)
{
// if definition is already registered And shared size scope is changing,
@@ -413,16 +400,14 @@ namespace Avalonia.Controls
definition._sharedState = null;
}
- if ((definition._sharedState == null) && (privateSharedSizeScope != null))
+ if (definition._sharedState == null
+ && e.NewValue.Value is { } privateSharedSizeScope
+ && definition.SharedSizeGroup is { } sharedSizeGroup)
{
- string sharedSizeGroup = definition.SharedSizeGroup;
- if (sharedSizeGroup != null)
- {
- // if definition is not registered and both: shared size group id AND private shared scope
- // are available, then register definition.
- definition._sharedState = privateSharedSizeScope.EnsureSharedState(definition.SharedSizeGroup);
- definition._sharedState.AddMember(definition);
- }
+ // if definition is not registered and both: shared size group id AND private shared scope
+ // are available, then register definition.
+ definition._sharedState = privateSharedSizeScope.EnsureSharedState(sharedSizeGroup);
+ definition._sharedState.AddMember(definition);
}
}
}
@@ -432,7 +417,7 @@ namespace Avalonia.Controls
///
private SharedSizeScope? PrivateSharedSizeScope
{
- get { return (SharedSizeScope?)GetValue(PrivateSharedSizeScopeProperty); }
+ get { return GetValue(PrivateSharedSizeScopeProperty); }
}
///
@@ -465,7 +450,7 @@ namespace Avalonia.Controls
private SharedSizeState? _sharedState; // reference to shared state object this instance is registered with
- [System.Flags]
+ [Flags]
private enum Flags : byte
{
//
@@ -520,11 +505,10 @@ namespace Avalonia.Controls
///
internal SharedSizeState(SharedSizeScope sharedSizeScope, string sharedSizeGroupId)
{
- Debug.Assert(sharedSizeScope != null && sharedSizeGroupId != null);
_sharedSizeScope = sharedSizeScope;
_sharedSizeGroupId = sharedSizeGroupId;
_registry = new List();
- _layoutUpdated = new EventHandler(OnLayoutUpdated);
+ _layoutUpdated = OnLayoutUpdated;
_broadcastInvalidation = true;
}
@@ -568,7 +552,7 @@ namespace Avalonia.Controls
{
for (int i = 0, count = _registry.Count; i < count; ++i)
{
- Grid parentGrid = (Grid)(_registry[i].Parent!);
+ Grid parentGrid = _registry[i].Parent!;
parentGrid.Invalidate();
}
_broadcastInvalidation = false;
@@ -703,7 +687,7 @@ namespace Avalonia.Controls
// measure is invalid - it used the old shared size,
// which is larger than d's (possibly changed) minSize
measureIsValid = (definitionBase.LayoutWasUpdated &&
- MathUtilities.GreaterThanOrClose(definitionBase._minSize, this.MinSize));
+ MathUtilities.GreaterThanOrClose(definitionBase._minSize, MinSize));
}
if(!measureIsValid)
@@ -786,8 +770,8 @@ namespace Avalonia.Controls
///
///
///
- public static readonly AttachedProperty SharedSizeGroupProperty =
- AvaloniaProperty.RegisterAttached(
+ public static readonly AttachedProperty SharedSizeGroupProperty =
+ AvaloniaProperty.RegisterAttached(
"SharedSizeGroup",
validate: SharedSizeGroupPropertyValueValid);
@@ -796,8 +780,8 @@ namespace Avalonia.Controls
///
static DefinitionBase()
{
- SharedSizeGroupProperty.Changed.AddClassHandler(OnSharedSizeGroupPropertyChanged);
- PrivateSharedSizeScopeProperty.Changed.AddClassHandler(OnPrivateSharedSizeScopePropertyChanged);
+ SharedSizeGroupProperty.Changed.AddClassHandler(OnSharedSizeGroupPropertyChanged);
+ PrivateSharedSizeScopeProperty.Changed.AddClassHandler(OnPrivateSharedSizeScopePropertyChanged);
}
///
diff --git a/src/Avalonia.Controls/DockPanel.cs b/src/Avalonia.Controls/DockPanel.cs
index 3e3ed509b5..1a0cf1644a 100644
--- a/src/Avalonia.Controls/DockPanel.cs
+++ b/src/Avalonia.Controls/DockPanel.cs
@@ -101,9 +101,6 @@ namespace Avalonia.Controls
Size childConstraint; // Contains the suggested input constraint for this child.
Size childDesiredSize; // Contains the return size from child measure.
- if (child == null)
- { continue; }
-
// Child constraint is the remaining size; this is total size minus size consumed by previous children.
childConstraint = new Size(Math.Max(0.0, constraint.Width - accumulatedWidth),
Math.Max(0.0, constraint.Height - accumulatedHeight));
@@ -122,7 +119,7 @@ namespace Avalonia.Controls
// will deal with computing our minimum size (parentSize) due to that accumulation.
// Therefore, we only need to compute our minimum size (parentSize) in dimensions that this child does
// not accumulate: Width for Top/Bottom, Height for Left/Right.
- switch (DockPanel.GetDock((Control)child))
+ switch (GetDock(child))
{
case Dock.Left:
case Dock.Right:
@@ -164,8 +161,6 @@ namespace Avalonia.Controls
for (int i = 0; i < totalChildrenCount; ++i)
{
var child = children[i];
- if (child == null)
- { continue; }
Size childDesiredSize = child.DesiredSize;
Rect rcChild = new Rect(
@@ -176,7 +171,7 @@ namespace Avalonia.Controls
if (i < nonFillChildrenCount)
{
- switch (DockPanel.GetDock((Control)child))
+ switch (GetDock(child))
{
case Dock.Left:
accumulatedLeft += childDesiredSize.Width;
diff --git a/src/Avalonia.Controls/Documents/Inline.cs b/src/Avalonia.Controls/Documents/Inline.cs
index 47581e87f1..23b806583e 100644
--- a/src/Avalonia.Controls/Documents/Inline.cs
+++ b/src/Avalonia.Controls/Documents/Inline.cs
@@ -13,8 +13,8 @@ namespace Avalonia.Controls.Documents
///
/// AvaloniaProperty for property.
///
- public static readonly StyledProperty TextDecorationsProperty =
- AvaloniaProperty.Register(
+ public static readonly StyledProperty TextDecorationsProperty =
+ AvaloniaProperty.Register(
nameof(TextDecorations));
///
@@ -28,7 +28,7 @@ namespace Avalonia.Controls.Documents
///
/// The TextDecorations property specifies decorations that are added to the text of an element.
///
- public TextDecorationCollection TextDecorations
+ public TextDecorationCollection? TextDecorations
{
get { return GetValue(TextDecorationsProperty); }
set { SetValue(TextDecorationsProperty, value); }
@@ -83,7 +83,8 @@ namespace Avalonia.Controls.Documents
return new GenericTextRunProperties(new Typeface(FontFamily, fontStyle, fontWeight), FontSize,
textDecorations, Foreground, background, BaselineAlignment);
}
-
+
+ ///
protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs change)
{
base.OnPropertyChanged(change);
diff --git a/src/Avalonia.Controls/Documents/Span.cs b/src/Avalonia.Controls/Documents/Span.cs
index a7a702ceae..d3565cbdd5 100644
--- a/src/Avalonia.Controls/Documents/Span.cs
+++ b/src/Avalonia.Controls/Documents/Span.cs
@@ -1,3 +1,4 @@
+using System;
using System.Collections.Generic;
using System.Text;
using Avalonia.Media.TextFormatting;
@@ -51,6 +52,7 @@ namespace Avalonia.Controls.Documents
}
}
+ ///
protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs change)
{
base.OnPropertyChanged(change);
@@ -68,26 +70,26 @@ namespace Avalonia.Controls.Documents
{
base.OnInlineHostChanged(oldValue, newValue);
- if (Inlines is not null)
- {
- Inlines.InlineHost = newValue;
- }
+ Inlines.InlineHost = newValue;
}
private void OnInlinesChanged(InlineCollection? oldValue, InlineCollection? newValue)
{
+ void OnInlinesInvalidated(object? sender, EventArgs e)
+ => InlineHost?.Invalidate();
+
if (oldValue is not null)
{
oldValue.LogicalChildren = null;
oldValue.InlineHost = null;
- oldValue.Invalidated -= (s, e) => InlineHost?.Invalidate();
+ oldValue.Invalidated -= OnInlinesInvalidated;
}
if (newValue is not null)
{
newValue.LogicalChildren = LogicalChildren;
newValue.InlineHost = InlineHost;
- newValue.Invalidated += (s, e) => InlineHost?.Invalidate();
+ newValue.Invalidated += OnInlinesInvalidated;
}
}
}
diff --git a/src/Avalonia.Controls/ExperimentalAcrylicBorder.cs b/src/Avalonia.Controls/ExperimentalAcrylicBorder.cs
index e4487d29fa..e1f840672d 100644
--- a/src/Avalonia.Controls/ExperimentalAcrylicBorder.cs
+++ b/src/Avalonia.Controls/ExperimentalAcrylicBorder.cs
@@ -80,7 +80,7 @@ namespace Avalonia.Controls
_subscription?.Dispose();
}
- public override void Render(DrawingContext context)
+ public sealed override void Render(DrawingContext context)
{
if (context.PlatformImpl is IDrawingContextWithAcrylicLikeSupport idc)
{
diff --git a/src/Avalonia.Controls/Grid.cs b/src/Avalonia.Controls/Grid.cs
index 7737fdac2e..ff9fb4e31d 100644
--- a/src/Avalonia.Controls/Grid.cs
+++ b/src/Avalonia.Controls/Grid.cs
@@ -164,20 +164,21 @@ namespace Avalonia.Controls
///
/// Returns a ColumnDefinitions of column definitions.
///
+ [MemberNotNull(nameof(_extData))]
public ColumnDefinitions ColumnDefinitions
{
get
{
- if (_data == null) { _data = new ExtendedData(); }
- if (_data.ColumnDefinitions == null) { _data.ColumnDefinitions = new ColumnDefinitions() { Parent = this }; }
+ if (_extData == null) { _extData = new ExtendedData(); }
+ if (_extData.ColumnDefinitions == null) { _extData.ColumnDefinitions = new ColumnDefinitions() { Parent = this }; }
- return (_data.ColumnDefinitions);
+ return (_extData.ColumnDefinitions);
}
set
{
- if (_data == null) { _data = new ExtendedData(); }
- _data.ColumnDefinitions = value;
- _data.ColumnDefinitions.Parent = this;
+ if (_extData == null) { _extData = new ExtendedData(); }
+ _extData.ColumnDefinitions = value;
+ _extData.ColumnDefinitions.Parent = this;
InvalidateMeasure();
}
}
@@ -185,20 +186,21 @@ namespace Avalonia.Controls
///
/// Returns a RowDefinitions of row definitions.
///
+ [MemberNotNull(nameof(_extData))]
public RowDefinitions RowDefinitions
{
get
{
- if (_data == null) { _data = new ExtendedData(); }
- if (_data.RowDefinitions == null) { _data.RowDefinitions = new RowDefinitions() { Parent = this }; }
+ if (_extData == null) { _extData = new ExtendedData(); }
+ if (_extData.RowDefinitions == null) { _extData.RowDefinitions = new RowDefinitions() { Parent = this }; }
- return (_data.RowDefinitions);
+ return (_extData.RowDefinitions);
}
set
{
- if (_data == null) { _data = new ExtendedData(); }
- _data.RowDefinitions = value;
- _data.RowDefinitions.Parent = this;
+ if (_extData == null) { _extData = new ExtendedData(); }
+ _extData.RowDefinitions = value;
+ _extData.RowDefinitions.Parent = this;
InvalidateMeasure();
}
}
@@ -211,7 +213,7 @@ namespace Avalonia.Controls
protected override Size MeasureOverride(Size constraint)
{
Size gridDesiredSize;
- ExtendedData extData = ExtData;
+ var extData = _extData;
try
{
@@ -221,17 +223,14 @@ namespace Avalonia.Controls
if (extData == null)
{
gridDesiredSize = new Size();
- var children = this.Children;
+ var children = Children;
for (int i = 0, count = children.Count; i < count; ++i)
{
var child = children[i];
- if (child != null)
- {
- child.Measure(constraint);
- gridDesiredSize = new Size(Math.Max(gridDesiredSize.Width, child.DesiredSize.Width),
- Math.Max(gridDesiredSize.Height, child.DesiredSize.Height));
- }
+ child.Measure(constraint);
+ gridDesiredSize = new Size(Math.Max(gridDesiredSize.Width, child.DesiredSize.Width),
+ Math.Max(gridDesiredSize.Height, child.DesiredSize.Height));
}
}
else
@@ -512,17 +511,14 @@ namespace Avalonia.Controls
{
ArrangeOverrideInProgress = true;
- if (_data == null)
+ if (_extData is null)
{
- var children = this.Children;
+ var children = Children;
for (int i = 0, count = children.Count; i < count; ++i)
{
var child = children[i];
- if (child != null)
- {
- child.Arrange(new Rect(arrangeSize));
- }
+ child.Arrange(new Rect(arrangeSize));
}
}
else
@@ -532,15 +528,11 @@ namespace Avalonia.Controls
SetFinalSize(DefinitionsU, arrangeSize.Width, true);
SetFinalSize(DefinitionsV, arrangeSize.Height, false);
- var children = this.Children;
+ var children = Children;
for (int currentCell = 0; currentCell < PrivateCells.Length; ++currentCell)
{
var cell = children[currentCell];
- if (cell == null)
- {
- continue;
- }
int columnIndex = PrivateCells[currentCell].ColumnIndex;
int rowIndex = PrivateCells[currentCell].RowIndex;
@@ -599,7 +591,7 @@ namespace Avalonia.Controls
{
double value = 0.0;
- Debug.Assert(_data != null);
+ Debug.Assert(_extData != null);
// actual value calculations require structure to be up-to-date
if (!ColumnDefinitionsDirty)
@@ -621,7 +613,7 @@ namespace Avalonia.Controls
{
double value = 0.0;
- Debug.Assert(_data != null);
+ Debug.Assert(_extData != null);
// actual value calculations require structure to be up-to-date
if (!RowDefinitionsDirty)
@@ -654,18 +646,20 @@ namespace Avalonia.Controls
///
/// Convenience accessor to ValidDefinitionsUStructure bit flag.
///
+ [MemberNotNull(nameof(_extData))]
internal bool ColumnDefinitionsDirty
{
- get => ColumnDefinitions?.IsDirty ?? false;
+ get => ColumnDefinitions.IsDirty;
set => ColumnDefinitions.IsDirty = value;
}
///
/// Convenience accessor to ValidDefinitionsVStructure bit flag.
///
+ [MemberNotNull(nameof(_extData))]
internal bool RowDefinitionsDirty
{
- get => RowDefinitions?.IsDirty ?? false;
+ get => RowDefinitions.IsDirty;
set => RowDefinitions.IsDirty = value;
}
@@ -686,8 +680,10 @@ namespace Avalonia.Controls
///
private void ValidateCellsCore()
{
- var children = this.Children;
- ExtendedData extData = ExtData;
+ Debug.Assert(_extData is not null);
+
+ var children = Children;
+ var extData = _extData!;
extData.CellCachesCollection = new CellCache[children.Count];
extData.CellGroup1 = int.MaxValue;
@@ -702,10 +698,6 @@ namespace Avalonia.Controls
for (int i = PrivateCells.Length - 1; i >= 0; --i)
{
var child = children[i];
- if (child == null)
- {
- continue;
- }
CellCache cell = new CellCache();
@@ -713,19 +705,19 @@ namespace Avalonia.Controls
// Read indices from the corresponding properties:
// clamp to value < number_of_columns
// column >= 0 is guaranteed by property value validation callback
- cell.ColumnIndex = Math.Min(GetColumn((Control)child), DefinitionsU.Count - 1);
+ cell.ColumnIndex = Math.Min(GetColumn(child), DefinitionsU.Count - 1);
// clamp to value < number_of_rows
// row >= 0 is guaranteed by property value validation callback
- cell.RowIndex = Math.Min(GetRow((Control)child), DefinitionsV.Count - 1);
+ cell.RowIndex = Math.Min(GetRow(child), DefinitionsV.Count - 1);
// Read span properties:
// clamp to not exceed beyond right side of the grid
// column_span > 0 is guaranteed by property value validation callback
- cell.ColumnSpan = Math.Min(GetColumnSpan((Control)child), DefinitionsU.Count - cell.ColumnIndex);
+ cell.ColumnSpan = Math.Min(GetColumnSpan(child), DefinitionsU.Count - cell.ColumnIndex);
// clamp to not exceed beyond bottom side of the grid
// row_span > 0 is guaranteed by property value validation callback
- cell.RowSpan = Math.Min(GetRowSpan((Control)child), DefinitionsV.Count - cell.RowIndex);
+ cell.RowSpan = Math.Min(GetRowSpan(child), DefinitionsV.Count - cell.RowIndex);
Debug.Assert(0 <= cell.ColumnIndex && cell.ColumnIndex < DefinitionsU.Count);
Debug.Assert(0 <= cell.RowIndex && cell.RowIndex < DefinitionsV.Count);
@@ -792,7 +784,7 @@ namespace Avalonia.Controls
{
if (ColumnDefinitionsDirty)
{
- ExtendedData extData = ExtData;
+ var extData = _extData;
if (extData.ColumnDefinitions == null)
{
@@ -818,7 +810,7 @@ namespace Avalonia.Controls
ColumnDefinitionsDirty = false;
}
- Debug.Assert(ExtData.DefinitionsU != null && ExtData.DefinitionsU.Count > 0);
+ Debug.Assert(_extData is { DefinitionsU.Count: > 0 });
}
///
@@ -833,7 +825,7 @@ namespace Avalonia.Controls
{
if (RowDefinitionsDirty)
{
- ExtendedData extData = ExtData;
+ var extData = _extData;
if (extData.RowDefinitions == null)
{
@@ -859,7 +851,7 @@ namespace Avalonia.Controls
RowDefinitionsDirty = false;
}
- Debug.Assert(ExtData.DefinitionsV != null && ExtData.DefinitionsV.Count > 0);
+ Debug.Assert(_extData is { DefinitionsV.Count: > 0 });
}
///
@@ -965,8 +957,7 @@ namespace Avalonia.Controls
bool ignoreDesiredSizeU,
bool forceInfinityV)
{
- bool unusedHasDesiredSizeUChanged;
- MeasureCellsGroup(cellsHead, referenceSize, ignoreDesiredSizeU, forceInfinityV, out unusedHasDesiredSizeUChanged);
+ MeasureCellsGroup(cellsHead, referenceSize, ignoreDesiredSizeU, forceInfinityV, out _);
}
///
@@ -994,7 +985,7 @@ namespace Avalonia.Controls
return;
}
- var children = this.Children;
+ var children = Children;
Hashtable? spanStore = null;
bool ignoreDesiredSizeV = forceInfinityV;
@@ -1101,8 +1092,6 @@ namespace Avalonia.Controls
int cell,
bool forceInfinityV)
{
-
-
double cellMeasureWidth;
double cellMeasureHeight;
@@ -1144,15 +1133,9 @@ namespace Avalonia.Controls
}
- var child = this.Children[cell];
- if (child != null)
- {
- Size childConstraint = new Size(cellMeasureWidth, cellMeasureHeight);
- child.Measure(childConstraint);
- }
-
-
-
+ var child = Children[cell];
+ Size childConstraint = new Size(cellMeasureWidth, cellMeasureHeight);
+ child.Measure(childConstraint);
}
///
@@ -1230,7 +1213,7 @@ namespace Avalonia.Controls
// avoid processing when asked to distribute "0"
if (!MathUtilities.IsZero(requestedSize))
{
- DefinitionBase[] tempDefinitions = TempDefinitions; // temp array used to remember definitions for sorting
+ DefinitionBase?[] tempDefinitions = TempDefinitions; // temp array used to remember definitions for sorting
int end = start + count;
int autoDefinitionsCount = 0;
double rangeMinSize = 0;
@@ -1288,20 +1271,24 @@ namespace Avalonia.Controls
Array.Sort(tempDefinitions, 0, count, s_spanPreferredDistributionOrderComparer);
for (i = 0, sizeToDistribute = requestedSize; i < autoDefinitionsCount; ++i)
{
+ var tempDefinition = tempDefinitions[i]!;
+
// sanity check: only auto definitions allowed in this loop
- Debug.Assert(tempDefinitions[i].UserSize.IsAuto);
+ Debug.Assert(tempDefinition.UserSize.IsAuto);
// adjust sizeToDistribute value by subtracting auto definition min size
- sizeToDistribute -= (tempDefinitions[i].MinSize);
+ sizeToDistribute -= (tempDefinition.MinSize);
}
for (; i < count; ++i)
{
+ var tempDefinition = tempDefinitions[i]!;
+
// sanity check: no auto definitions allowed in this loop
- Debug.Assert(!tempDefinitions[i].UserSize.IsAuto);
+ Debug.Assert(!tempDefinition.UserSize.IsAuto);
- double newMinSize = Math.Min(sizeToDistribute / (count - i), tempDefinitions[i].PreferredSize);
- if (newMinSize > tempDefinitions[i].MinSize) { tempDefinitions[i].UpdateMinSize(newMinSize); }
+ double newMinSize = Math.Min(sizeToDistribute / (count - i), tempDefinition.PreferredSize);
+ if (newMinSize > tempDefinition.MinSize) { tempDefinition.UpdateMinSize(newMinSize); }
sizeToDistribute -= newMinSize;
}
@@ -1325,24 +1312,28 @@ namespace Avalonia.Controls
Array.Sort(tempDefinitions, 0, count, s_spanMaxDistributionOrderComparer);
for (i = 0, sizeToDistribute = requestedSize - rangePreferredSize; i < count - autoDefinitionsCount; ++i)
{
+ var tempDefinition = tempDefinitions[i]!;
+
// sanity check: no auto definitions allowed in this loop
- Debug.Assert(!tempDefinitions[i].UserSize.IsAuto);
+ Debug.Assert(!tempDefinition.UserSize.IsAuto);
- double preferredSize = tempDefinitions[i].PreferredSize;
+ double preferredSize = tempDefinition.PreferredSize;
double newMinSize = preferredSize + sizeToDistribute / (count - autoDefinitionsCount - i);
- tempDefinitions[i].UpdateMinSize(Math.Min(newMinSize, tempDefinitions[i].SizeCache));
- sizeToDistribute -= (tempDefinitions[i].MinSize - preferredSize);
+ tempDefinition.UpdateMinSize(Math.Min(newMinSize, tempDefinition.SizeCache));
+ sizeToDistribute -= (tempDefinition.MinSize - preferredSize);
}
for (; i < count; ++i)
{
+ var tempDefinition = tempDefinitions[i]!;
+
// sanity check: only auto definitions allowed in this loop
- Debug.Assert(tempDefinitions[i].UserSize.IsAuto);
+ Debug.Assert(tempDefinition.UserSize.IsAuto);
- double preferredSize = tempDefinitions[i].MinSize;
+ double preferredSize = tempDefinition.MinSize;
double newMinSize = preferredSize + sizeToDistribute / (count - i);
- tempDefinitions[i].UpdateMinSize(Math.Min(newMinSize, tempDefinitions[i].SizeCache));
- sizeToDistribute -= (tempDefinitions[i].MinSize - preferredSize);
+ tempDefinition.UpdateMinSize(Math.Min(newMinSize, tempDefinition.SizeCache));
+ sizeToDistribute -= (tempDefinition.MinSize - preferredSize);
}
// sanity check: requested size must all be distributed
@@ -1376,8 +1367,10 @@ namespace Avalonia.Controls
for (int i = 0; i < count; ++i)
{
- double deltaSize = (maxMaxSize - tempDefinitions[i].SizeCache) * sizeToDistribute / totalRemainingSize;
- tempDefinitions[i].UpdateMinSize(tempDefinitions[i].SizeCache + deltaSize);
+ var tempDefinition = tempDefinitions[i]!;
+
+ double deltaSize = (maxMaxSize - tempDefinition.SizeCache) * sizeToDistribute / totalRemainingSize;
+ tempDefinition.UpdateMinSize(tempDefinition.SizeCache + deltaSize);
}
}
else
@@ -1388,7 +1381,7 @@ namespace Avalonia.Controls
//
for (int i = 0; i < count; ++i)
{
- tempDefinitions[i].UpdateMinSize(equalSize);
+ tempDefinitions[i]!.UpdateMinSize(equalSize);
}
}
}
@@ -1429,7 +1422,7 @@ namespace Avalonia.Controls
double availableSize)
{
int defCount = definitions.Count;
- DefinitionBase[] tempDefinitions = TempDefinitions;
+ DefinitionBase?[] tempDefinitions = TempDefinitions;
int minCount = 0, maxCount = 0;
double takenSize = 0;
double totalStarWeight = 0.0;
@@ -1560,8 +1553,8 @@ namespace Avalonia.Controls
remainingStarWeight = totalStarWeight - takenStarWeight;
}
- double minRatio = (minCount > 0) ? tempDefinitions[minCount - 1].MeasureSize : Double.PositiveInfinity;
- double maxRatio = (maxCount > 0) ? tempDefinitions[defCount + maxCount - 1].SizeCache : -1.0;
+ double minRatio = (minCount > 0) ? tempDefinitions[minCount - 1]!.MeasureSize : Double.PositiveInfinity;
+ double maxRatio = (maxCount > 0) ? tempDefinitions[defCount + maxCount - 1]!.SizeCache : -1.0;
// choose the def with larger ratio to the current proportion ("max discrepancy")
double proportion = remainingStarWeight / remainingAvailableSize;
@@ -1579,13 +1572,13 @@ namespace Avalonia.Controls
double resolvedSize;
if (chooseMin == true)
{
- resolvedDef = tempDefinitions[minCount - 1];
+ resolvedDef = tempDefinitions[minCount - 1]!;
resolvedSize = resolvedDef.MinSize;
--minCount;
}
else
{
- resolvedDef = tempDefinitions[defCount + maxCount - 1];
+ resolvedDef = tempDefinitions[defCount + maxCount - 1]!;
resolvedSize = Math.Max(resolvedDef.MinSize, resolvedDef.UserMaxSize);
--maxCount;
}
@@ -1603,12 +1596,12 @@ namespace Avalonia.Controls
// advance to the next candidate defs, removing ones that have been resolved.
// Both counts are advanced, as a def might appear in both lists.
- while (minCount > 0 && tempDefinitions[minCount - 1].MeasureSize < 0.0)
+ while (minCount > 0 && tempDefinitions[minCount - 1]!.MeasureSize < 0.0)
{
--minCount;
tempDefinitions[minCount] = null!;
}
- while (maxCount > 0 && tempDefinitions[defCount + maxCount - 1].MeasureSize < 0.0)
+ while (maxCount > 0 && tempDefinitions[defCount + maxCount - 1]!.MeasureSize < 0.0)
{
--maxCount;
tempDefinitions[defCount + maxCount] = null!;
@@ -1637,8 +1630,7 @@ namespace Avalonia.Controls
// resolved as 'min'. Their allocation can be increased to make up the gap.
for (int i = minCount; i < minCountPhase2; ++i)
{
- DefinitionBase def = tempDefinitions[i];
- if (def != null)
+ if (tempDefinitions[i] is { } def)
{
def.MeasureSize = 1.0; // mark as 'not yet resolved'
++starCount;
@@ -1653,8 +1645,7 @@ namespace Avalonia.Controls
// resolved as 'max'. Their allocation can be decreased to make up the gap.
for (int i = maxCount; i < maxCountPhase2; ++i)
{
- DefinitionBase def = tempDefinitions[defCount + i];
- if (def != null)
+ if (tempDefinitions[defCount + i] is { } def)
{
def.MeasureSize = 1.0; // mark as 'not yet resolved'
++starCount;
@@ -1695,7 +1686,7 @@ namespace Avalonia.Controls
totalStarWeight = 0.0;
for (int i = 0; i < starCount; ++i)
{
- DefinitionBase def = tempDefinitions[i];
+ DefinitionBase def = tempDefinitions[i]!;
totalStarWeight += def.MeasureSize;
def.SizeCache = totalStarWeight;
}
@@ -1703,7 +1694,7 @@ namespace Avalonia.Controls
// resolve the defs, in decreasing order of weight
for (int i = starCount - 1; i >= 0; --i)
{
- DefinitionBase def = tempDefinitions[i];
+ DefinitionBase def = tempDefinitions[i]!;
double resolvedSize = (def.MeasureSize > 0.0) ? Math.Max(availableSize - takenSize, 0.0) * (def.MeasureSize / def.SizeCache) : 0.0;
// min and max should have no effect by now, but just in case...
@@ -2095,7 +2086,7 @@ namespace Avalonia.Controls
{
// DpiScale dpiScale = GetDpi();
// double dpi = columns ? dpiScale.DpiScaleX : dpiScale.DpiScaleY;
- var dpi = (VisualRoot as Layout.ILayoutRoot)?.LayoutScaling ?? 1.0;
+ var dpi = (VisualRoot as ILayoutRoot)?.LayoutScaling ?? 1.0;
double[] roundingErrors = RoundingErrors;
double roundedTakenSize = 0.0;
@@ -2302,8 +2293,7 @@ namespace Avalonia.Controls
///
private void SetValid()
{
- ExtendedData extData = ExtData;
- if (extData != null)
+ if (_extData is { } extData)
{
// for (int i = 0; i < PrivateColumnCount; ++i) DefinitionsU[i].SetValid ();
// for (int i = 0; i < PrivateRowCount; ++i) DefinitionsV[i].SetValid ();
@@ -2330,12 +2320,12 @@ namespace Avalonia.Controls
if (ShowGridLines && (_gridLinesRenderer == null))
{
_gridLinesRenderer = new GridLinesRenderer();
- this.VisualChildren.Add(_gridLinesRenderer);
+ VisualChildren.Add(_gridLinesRenderer);
}
if ((!ShowGridLines) && (_gridLinesRenderer != null))
{
- this.VisualChildren.Add(_gridLinesRenderer);
+ VisualChildren.Add(_gridLinesRenderer);
_gridLinesRenderer = null;
}
@@ -2364,7 +2354,7 @@ namespace Avalonia.Controls
{
Grid grid = (Grid)d;
- if (grid.ExtData != null // trivial grid is 1 by 1. there is no grid lines anyway
+ if (grid._extData != null // trivial grid is 1 by 1. there is no grid lines anyway
&& grid.ListenToNotifications)
{
grid.InvalidateVisual();
@@ -2375,13 +2365,11 @@ namespace Avalonia.Controls
private static void OnCellAttachedPropertyChanged(AvaloniaObject d, AvaloniaPropertyChangedEventArgs e)
{
- Visual? child = d as Visual;
-
- if (child != null)
+ if (d is Visual child)
{
Grid? grid = child.GetVisualParent();
if (grid != null
- && grid.ExtData != null
+ && grid._extData != null
&& grid.ListenToNotifications)
{
grid.CellsStructureDirty = true;
@@ -2427,7 +2415,7 @@ namespace Avalonia.Controls
///
private IReadOnlyList DefinitionsU
{
- get { return (ExtData.DefinitionsU!); }
+ get { return _extData!.DefinitionsU!; }
}
///
@@ -2435,17 +2423,19 @@ namespace Avalonia.Controls
///
private IReadOnlyList DefinitionsV
{
- get { return (ExtData.DefinitionsV!); }
+ get { return _extData!.DefinitionsV!; }
}
///
/// Helper accessor to layout time array of definitions.
///
- private DefinitionBase[] TempDefinitions
+ private DefinitionBase?[] TempDefinitions
{
get
{
- ExtendedData extData = ExtData;
+ Debug.Assert(_extData is not null);
+
+ var extData = _extData!;
int requiredLength = Math.Max(DefinitionsU.Count, DefinitionsV.Count) * 2;
if (extData.TempDefinitions == null
@@ -2516,7 +2506,7 @@ namespace Avalonia.Controls
///
private CellCache[] PrivateCells
{
- get { return (ExtData.CellCachesCollection!); }
+ get { return _extData!.CellCachesCollection!; }
}
///
@@ -2582,18 +2572,10 @@ namespace Avalonia.Controls
set { SetFlags(value, Flags.HasGroup3CellsInAutoRows); }
}
- ///
- /// Returns reference to extended data bag.
- ///
- private ExtendedData ExtData
- {
- get { return (_data!); }
- }
-
///
/// Returns *-weight, adjusted for scale computed during Phase 1
///
- static double StarWeight(DefinitionBase def, double scale)
+ private static double StarWeight(DefinitionBase def, double scale)
{
if (scale < 0.0)
{
@@ -2609,17 +2591,17 @@ namespace Avalonia.Controls
}
// Extended data instantiated on demand, for non-trivial case handling only
- private ExtendedData? _data;
+ private ExtendedData? _extData;
// Grid validity / property caches dirtiness flags
private Flags _flags;
private GridLinesRenderer? _gridLinesRenderer;
// Keeps track of definition indices.
- int[]? _definitionIndices;
+ private int[]? _definitionIndices;
// Stores unrounded values and rounding errors during layout rounding.
- double[]? _roundingErrors;
+ private double[]? _roundingErrors;
// 5 is an arbitrary constant chosen to end the measure loop
private const int c_layoutLoopMaxCount = 5;
@@ -2645,14 +2627,14 @@ namespace Avalonia.Controls
internal int CellGroup2; // index of the first cell in second cell group
internal int CellGroup3; // index of the first cell in third cell group
internal int CellGroup4; // index of the first cell in forth cell group
- internal DefinitionBase[]? TempDefinitions; // temporary array used during layout for various purposes
+ internal DefinitionBase?[]? TempDefinitions; // temporary array used during layout for various purposes
// TempDefinitions.Length == Max(definitionsU.Length, definitionsV.Length)
}
///
/// Grid validity / property caches dirtiness flags
///
- [System.Flags]
+ [Flags]
private enum Flags
{
//
@@ -2768,7 +2750,7 @@ namespace Avalonia.Controls
///
/// LayoutTimeSizeType is used internally and reflects layout-time size type.
///
- [System.Flags]
+ [Flags]
internal enum LayoutTimeSizeType : byte
{
None = 0x00,
@@ -3274,7 +3256,7 @@ namespace Avalonia.Controls
///
/// UpdateRenderBounds.
///
- public override void Render(DrawingContext drawingContext)
+ public sealed override void Render(DrawingContext drawingContext)
{
var grid = this.GetVisualParent();
@@ -3317,7 +3299,7 @@ namespace Avalonia.Controls
internal void UpdateRenderBounds(Size arrangeSize)
{
_lastArrangeSize = arrangeSize;
- this.InvalidateVisual();
+ InvalidateVisual();
}
private static Size _lastArrangeSize;
diff --git a/src/Avalonia.Controls/Image.cs b/src/Avalonia.Controls/Image.cs
index 7408bff902..3e76835e92 100644
--- a/src/Avalonia.Controls/Image.cs
+++ b/src/Avalonia.Controls/Image.cs
@@ -14,8 +14,8 @@ namespace Avalonia.Controls
///
/// Defines the property.
///
- public static readonly StyledProperty SourceProperty =
- AvaloniaProperty.Register(nameof(Source));
+ public static readonly StyledProperty SourceProperty =
+ AvaloniaProperty.Register(nameof(Source));
///
/// Defines the property.
@@ -42,7 +42,7 @@ namespace Avalonia.Controls
/// Gets or sets the image that will be displayed.
///
[Content]
- public IImage Source
+ public IImage? Source
{
get { return GetValue(SourceProperty); }
set { SetValue(SourceProperty, value); }
@@ -66,13 +66,14 @@ namespace Avalonia.Controls
set { SetValue(StretchDirectionProperty, value); }
}
+ ///
protected override bool BypassFlowDirectionPolicies => true;
///
/// Renders the control.
///
/// The drawing context.
- public override void Render(DrawingContext context)
+ public sealed override void Render(DrawingContext context)
{
var source = Source;
diff --git a/src/Avalonia.Controls/ItemsControl.cs b/src/Avalonia.Controls/ItemsControl.cs
index 59b5bf48a5..ce12d5f2bf 100644
--- a/src/Avalonia.Controls/ItemsControl.cs
+++ b/src/Avalonia.Controls/ItemsControl.cs
@@ -2,7 +2,6 @@ using System;
using System.Collections;
using System.Collections.Generic;
using System.Collections.Specialized;
-using System.Diagnostics.CodeAnalysis;
using Avalonia.Automation.Peers;
using Avalonia.Collections;
using Avalonia.Controls.Generators;
@@ -17,7 +16,6 @@ using Avalonia.Layout;
using Avalonia.LogicalTree;
using Avalonia.Metadata;
using Avalonia.Styling;
-using Avalonia.VisualTree;
namespace Avalonia.Controls
{
@@ -91,10 +89,11 @@ namespace Avalonia.Controls
/// Gets or sets the to use for binding to the display member of each item.
///
[AssignBinding]
+ [InheritDataTypeFromItems(nameof(Items))]
public IBinding? DisplayMemberBinding
{
- get { return GetValue(DisplayMemberBindingProperty); }
- set { SetValue(DisplayMemberBindingProperty, value); }
+ get => GetValue(DisplayMemberBindingProperty);
+ set => SetValue(DisplayMemberBindingProperty, value);
}
private IEnumerable? _items = new AvaloniaList();
@@ -106,7 +105,6 @@ namespace Avalonia.Controls
private Tuple? _containerBeingPrepared;
private ScrollViewer? _scrollViewer;
private ItemsPresenter? _itemsPresenter;
- private IScrollSnapPointsInfo? _scrolSnapPointInfo;
///
/// Initializes a new instance of the class.
@@ -134,8 +132,8 @@ namespace Avalonia.Controls
[Content]
public IEnumerable? Items
{
- get { return _items; }
- set { SetAndRaise(ItemsProperty, ref _items, value); }
+ get => _items;
+ set => SetAndRaise(ItemsProperty, ref _items, value);
}
///
@@ -143,8 +141,8 @@ namespace Avalonia.Controls
///
public ControlTheme? ItemContainerTheme
{
- get { return GetValue(ItemContainerThemeProperty); }
- set { SetValue(ItemContainerThemeProperty, value); }
+ get => GetValue(ItemContainerThemeProperty);
+ set => SetValue(ItemContainerThemeProperty, value);
}
///
@@ -161,8 +159,8 @@ namespace Avalonia.Controls
///
public ITemplate ItemsPanel
{
- get { return GetValue(ItemsPanelProperty); }
- set { SetValue(ItemsPanelProperty, value); }
+ get => GetValue(ItemsPanelProperty);
+ set => SetValue(ItemsPanelProperty, value);
}
///
@@ -171,8 +169,8 @@ namespace Avalonia.Controls
[InheritDataTypeFromItems(nameof(Items))]
public IDataTemplate? ItemTemplate
{
- get { return GetValue(ItemTemplateProperty); }
- set { SetValue(ItemTemplateProperty, value); }
+ get => GetValue(ItemTemplateProperty);
+ set => SetValue(ItemTemplateProperty, value);
}
///
@@ -221,6 +219,7 @@ namespace Avalonia.Controls
}
+ ///
public event EventHandler HorizontalSnapPointsChanged
{
add
@@ -240,6 +239,7 @@ namespace Avalonia.Controls
}
}
+ ///
public event EventHandler VerticalSnapPointsChanged
{
add
@@ -264,8 +264,8 @@ namespace Avalonia.Controls
///
public bool AreHorizontalSnapPointsRegular
{
- get { return GetValue(AreHorizontalSnapPointsRegularProperty); }
- set { SetValue(AreHorizontalSnapPointsRegularProperty, value); }
+ get => GetValue(AreHorizontalSnapPointsRegularProperty);
+ set => SetValue(AreHorizontalSnapPointsRegularProperty, value);
}
///
@@ -273,8 +273,8 @@ namespace Avalonia.Controls
///
public bool AreVerticalSnapPointsRegular
{
- get { return GetValue(AreVerticalSnapPointsRegularProperty); }
- set { SetValue(AreVerticalSnapPointsRegularProperty, value); }
+ get => GetValue(AreVerticalSnapPointsRegularProperty);
+ set => SetValue(AreVerticalSnapPointsRegularProperty, value);
}
///
@@ -424,13 +424,12 @@ namespace Avalonia.Controls
/// true if the item is (or is eligible to be) its own container; otherwise, false.
protected internal virtual bool IsItemItsOwnContainerOverride(Control item) => true;
+ ///
protected override void OnApplyTemplate(TemplateAppliedEventArgs e)
{
base.OnApplyTemplate(e);
_scrollViewer = e.NameScope.Find("PART_ScrollViewer");
_itemsPresenter = e.NameScope.Find("PART_ItemsPresenter");
-
- _scrolSnapPointInfo = _itemsPresenter as IScrollSnapPointsInfo;
}
///
@@ -477,11 +476,13 @@ namespace Avalonia.Controls
base.OnKeyDown(e);
}
+ ///
protected override AutomationPeer OnCreateAutomationPeer()
{
return new ItemsControlAutomationPeer(this);
}
+ ///
protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs change)
{
base.OnPropertyChanged(change);
@@ -558,7 +559,12 @@ namespace Avalonia.Controls
return new ItemContainerGenerator(this);
}
- internal void AddLogicalChild(Control c) => LogicalChildren.Add(c);
+ internal void AddLogicalChild(Control c)
+ {
+ if (!LogicalChildren.Contains(c))
+ LogicalChildren.Add(c);
+ }
+
internal void RemoveLogicalChild(Control c) => LogicalChildren.Remove(c);
///
@@ -748,11 +754,13 @@ namespace Avalonia.Controls
return true;
}
+ ///
public IReadOnlyList GetIrregularSnapPoints(Orientation orientation, SnapPointsAlignment snapPointsAlignment)
{
return _itemsPresenter?.GetIrregularSnapPoints(orientation, snapPointsAlignment) ?? new List();
}
+ ///
public double GetRegularSnapPoints(Orientation orientation, SnapPointsAlignment snapPointsAlignment, out double offset)
{
offset = 0;
diff --git a/src/Avalonia.Controls/LayoutTransformControl.cs b/src/Avalonia.Controls/LayoutTransformControl.cs
index ce254684b7..387dc27562 100644
--- a/src/Avalonia.Controls/LayoutTransformControl.cs
+++ b/src/Avalonia.Controls/LayoutTransformControl.cs
@@ -28,7 +28,7 @@ namespace Avalonia.Controls
.AddClassHandler((x, e) => x.OnLayoutTransformChanged(e));
ChildProperty.Changed
- .AddClassHandler((x, e) => x.OnChildChanged(e));
+ .AddClassHandler((x, _) => x.OnChildChanged());
UseRenderTransformProperty.Changed
.AddClassHandler((x, e) => x.OnUseRenderTransformPropertyChanged(e));
@@ -146,7 +146,7 @@ namespace Avalonia.Controls
return transformedDesiredSize;
}
- IDisposable? _renderTransformChangedEvent;
+ private IDisposable? _renderTransformChangedEvent;
private void OnUseRenderTransformPropertyChanged(AvaloniaPropertyChangedEventArgs e)
{
@@ -167,8 +167,7 @@ namespace Avalonia.Controls
.Subscribe(
(x) =>
{
- var target2 = x.Sender as LayoutTransformControl;
- if (target2 != null)
+ if (x.Sender is LayoutTransformControl target2)
{
target2.LayoutTransform = target2.RenderTransform;
}
@@ -182,7 +181,7 @@ namespace Avalonia.Controls
}
}
- private void OnChildChanged(AvaloniaPropertyChangedEventArgs e)
+ private void OnChildChanged()
{
if (null != TransformRoot)
{
@@ -206,18 +205,18 @@ namespace Avalonia.Controls
///
/// Actual DesiredSize of Child element (the value it returned from its MeasureOverride method).
///
- private Size _childActualSize = default;
+ private Size _childActualSize;
///
/// RenderTransform/MatrixTransform applied to TransformRoot.
///
- private MatrixTransform _matrixTransform = new MatrixTransform();
+ private readonly MatrixTransform _matrixTransform = new();
///
/// Transformation matrix corresponding to _matrixTransform.
///
private Matrix _transformation;
- private IDisposable? _transformChangedEvent = null;
+ private IDisposable? _transformChangedEvent;
///
/// Returns true if Size a is smaller than Size b in either dimension.
@@ -263,10 +262,7 @@ namespace Avalonia.Controls
// Get the transform matrix and apply it
_transformation = RoundMatrix(LayoutTransform.Value, DecimalsAfterRound);
- if (null != _matrixTransform)
- {
- _matrixTransform.Matrix = _transformation;
- }
+ _matrixTransform.Matrix = _transformation;
// New transform means re-layout is necessary
InvalidateMeasure();
diff --git a/src/Avalonia.Controls/MaskedTextBox.cs b/src/Avalonia.Controls/MaskedTextBox.cs
index 080326606e..5a3eb47ce4 100644
--- a/src/Avalonia.Controls/MaskedTextBox.cs
+++ b/src/Avalonia.Controls/MaskedTextBox.cs
@@ -178,12 +178,11 @@ namespace Avalonia.Controls
}
}
-
-
}
Type IStyleable.StyleKey => typeof(TextBox);
+ ///
protected override void OnGotFocus(GotFocusEventArgs e)
{
if (HidePromptOnLeave == true && MaskProvider != null)
@@ -193,6 +192,7 @@ namespace Avalonia.Controls
base.OnGotFocus(e);
}
+ ///
protected override async void OnKeyDown(KeyEventArgs e)
{
if (MaskProvider == null)
@@ -271,15 +271,17 @@ namespace Avalonia.Controls
}
}
+ ///
protected override void OnLostFocus(RoutedEventArgs e)
{
- if (HidePromptOnLeave == true && MaskProvider != null)
+ if (HidePromptOnLeave && MaskProvider != null)
{
Text = MaskProvider.ToString(!HidePromptOnLeave, true);
}
base.OnLostFocus(e);
}
+ ///
protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs change)
{
void UpdateMaskProvider()
@@ -357,6 +359,8 @@ namespace Avalonia.Controls
}
base.OnPropertyChanged(change);
}
+
+ ///
protected override void OnTextInput(TextInputEventArgs e)
{
_ignoreTextChanges = true;
@@ -423,7 +427,7 @@ namespace Avalonia.Controls
return startPosition;
}
- private void RefreshText(MaskedTextProvider provider, int position)
+ private void RefreshText(MaskedTextProvider? provider, int position)
{
if (provider != null)
{
diff --git a/src/Avalonia.Controls/NativeControlHost.cs b/src/Avalonia.Controls/NativeControlHost.cs
index 6b9e378d3d..a94a1ee983 100644
--- a/src/Avalonia.Controls/NativeControlHost.cs
+++ b/src/Avalonia.Controls/NativeControlHost.cs
@@ -16,19 +16,17 @@ namespace Avalonia.Controls
private IPlatformHandle? _nativeControlHandle;
private bool _queuedForDestruction;
private bool _queuedForMoveResize;
- private readonly List _propertyChangedSubscriptions = new List();
+ private readonly List _propertyChangedSubscriptions = new();
+ ///
protected override void OnAttachedToVisualTree(VisualTreeAttachmentEventArgs e)
{
_currentRoot = e.Root as TopLevel;
var visual = (Visual)this;
while (visual != null)
{
- if (visual is Visual v)
- {
- v.PropertyChanged += PropertyChangedHandler;
- _propertyChangedSubscriptions.Add(v);
- }
+ visual.PropertyChanged += PropertyChangedHandler;
+ _propertyChangedSubscriptions.Add(visual);
visual = visual.GetVisualParent();
}
@@ -42,15 +40,13 @@ namespace Avalonia.Controls
EnqueueForMoveResize();
}
+ ///
protected override void OnDetachedFromVisualTree(VisualTreeAttachmentEventArgs e)
{
_currentRoot = null;
- if (_propertyChangedSubscriptions != null)
- {
- foreach (var v in _propertyChangedSubscriptions)
- v.PropertyChanged -= PropertyChangedHandler;
- _propertyChangedSubscriptions.Clear();
- }
+ foreach (var v in _propertyChangedSubscriptions)
+ v.PropertyChanged -= PropertyChangedHandler;
+ _propertyChangedSubscriptions.Clear();
UpdateHost();
}
@@ -128,7 +124,7 @@ namespace Avalonia.Controls
return new Rect(position.Value, bounds.Size);
}
- void EnqueueForMoveResize()
+ private void EnqueueForMoveResize()
{
if(_queuedForMoveResize)
return;
diff --git a/src/Avalonia.Controls/NativeMenu.Export.cs b/src/Avalonia.Controls/NativeMenu.Export.cs
index 9c1fb93a48..ab64416a2c 100644
--- a/src/Avalonia.Controls/NativeMenu.Export.cs
+++ b/src/Avalonia.Controls/NativeMenu.Export.cs
@@ -12,10 +12,10 @@ namespace Avalonia.Controls
public static bool GetIsNativeMenuExported(TopLevel tl) => tl.GetValue(IsNativeMenuExportedProperty);
- private static readonly AttachedProperty s_nativeMenuInfoProperty =
- AvaloniaProperty.RegisterAttached("___NativeMenuInfo");
-
- class NativeMenuInfo
+ private static readonly AttachedProperty s_nativeMenuInfoProperty =
+ AvaloniaProperty.RegisterAttached("___NativeMenuInfo");
+
+ private sealed class NativeMenuInfo
{
public bool ChangingIsExported { get; set; }
public ITopLevelNativeMenuExporter? Exporter { get; }
@@ -33,7 +33,7 @@ namespace Avalonia.Controls
}
}
- static NativeMenuInfo GetInfo(TopLevel target)
+ private static NativeMenuInfo GetInfo(TopLevel target)
{
var rv = target.GetValue(s_nativeMenuInfoProperty);
if (rv == null)
@@ -45,18 +45,18 @@ namespace Avalonia.Controls
return rv;
}
- static void SetIsNativeMenuExported(TopLevel tl, bool value)
+ private static void SetIsNativeMenuExported(TopLevel tl, bool value)
{
GetInfo(tl).ChangingIsExported = true;
tl.SetValue(IsNativeMenuExportedProperty, value);
}
- public static readonly AttachedProperty MenuProperty
- = AvaloniaProperty.RegisterAttached("Menu");
+ public static readonly AttachedProperty MenuProperty
+ = AvaloniaProperty.RegisterAttached("Menu");
- public static void SetMenu(AvaloniaObject o, NativeMenu menu) => o.SetValue(MenuProperty, menu);
+ public static void SetMenu(AvaloniaObject o, NativeMenu? menu) => o.SetValue(MenuProperty, menu);
- public static NativeMenu GetMenu(AvaloniaObject o) => o.GetValue(MenuProperty);
+ public static NativeMenu? GetMenu(AvaloniaObject o) => o.GetValue(MenuProperty);
static NativeMenu()
{
diff --git a/src/Avalonia.Controls/Panel.cs b/src/Avalonia.Controls/Panel.cs
index 007d18c813..a7dc035459 100644
--- a/src/Avalonia.Controls/Panel.cs
+++ b/src/Avalonia.Controls/Panel.cs
@@ -68,7 +68,7 @@ namespace Avalonia.Controls
/// Renders the visual to a .
///
/// The drawing context.
- public override void Render(DrawingContext context)
+ public sealed override void Render(DrawingContext context)
{
var background = Background;
if (background != null)
diff --git a/src/Avalonia.Controls/Platform/Dialogs/SystemDialogImpl.cs b/src/Avalonia.Controls/Platform/Dialogs/SystemDialogImpl.cs
index a8a266e378..20bfb440e3 100644
--- a/src/Avalonia.Controls/Platform/Dialogs/SystemDialogImpl.cs
+++ b/src/Avalonia.Controls/Platform/Dialogs/SystemDialogImpl.cs
@@ -27,7 +27,7 @@ namespace Avalonia.Controls.Platform
var files = await filePicker.OpenFilePickerAsync(options);
return files
- .Select(file => file.TryGetFullPath() ?? file.Name)
+ .Select(file => file.TryGetLocalPath() ?? file.Name)
.ToArray();
}
else if (dialog is SaveFileDialog saveDialog)
@@ -46,7 +46,7 @@ namespace Avalonia.Controls.Platform
return null;
}
- var filePath = file.TryGetFullPath() ?? file.Name;
+ var filePath = file.TryGetLocalPath() ?? file.Name;
return new[] { filePath };
}
return null;
@@ -64,7 +64,7 @@ namespace Avalonia.Controls.Platform
var folders = await filePicker.OpenFolderPickerAsync(options);
return folders
- .Select(folder => folder.TryGetFullPath() ?? folder.Name)
+ .Select(folder => folder.TryGetLocalPath() ?? folder.Name)
.FirstOrDefault(u => u is not null);
}
}
diff --git a/src/Avalonia.Controls/Platform/IPlatformLifetimeEventsImpl.cs b/src/Avalonia.Controls/Platform/IPlatformLifetimeEventsImpl.cs
index 0658f9211c..d609dd94c8 100644
--- a/src/Avalonia.Controls/Platform/IPlatformLifetimeEventsImpl.cs
+++ b/src/Avalonia.Controls/Platform/IPlatformLifetimeEventsImpl.cs
@@ -13,6 +13,6 @@ namespace Avalonia.Platform
///
/// Raised on on OSX via the Quit menu or right-clicking on the application icon and selecting Quit.
///
- event EventHandler ShutdownRequested;
+ event EventHandler? ShutdownRequested;
}
}
diff --git a/src/Avalonia.Controls/Platform/IWindowImpl.cs b/src/Avalonia.Controls/Platform/IWindowImpl.cs
index 8d9d8e0e7b..31b144ce00 100644
--- a/src/Avalonia.Controls/Platform/IWindowImpl.cs
+++ b/src/Avalonia.Controls/Platform/IWindowImpl.cs
@@ -19,7 +19,7 @@ namespace Avalonia.Platform
///
/// Gets or sets a method called when the minimized/maximized state of the window changes.
///
- Action WindowStateChanged { get; set; }
+ Action? WindowStateChanged { get; set; }
///
/// Sets the title of the window.
@@ -42,7 +42,7 @@ namespace Avalonia.Platform
///
/// Called when a disabled window received input. Can be used to activate child windows.
///
- Action GotInputWhenDisabled { get; set; }
+ Action? GotInputWhenDisabled { get; set; }
///
/// Enables or disables system window decorations (title bar, buttons, etc)
@@ -68,7 +68,7 @@ namespace Avalonia.Platform
/// Gets or sets a method called before the underlying implementation is destroyed.
/// Return true to prevent the underlying implementation from closing.
///
- Func Closing { get; set; }
+ Func? Closing { get; set; }
///
/// Gets a value to indicate if the platform was able to extend client area to non-client area.
@@ -78,7 +78,7 @@ namespace Avalonia.Platform
///
/// Gets or Sets an action that is called whenever one of the extend client area properties changed.
///
- Action ExtendClientAreaToDecorationsChanged { get; set; }
+ Action? ExtendClientAreaToDecorationsChanged { get; set; }
///
/// Gets a flag that indicates if Managed decorations i.e. caption buttons are required.
diff --git a/src/Avalonia.Controls/PlatformInhibitionType.cs b/src/Avalonia.Controls/PlatformInhibitionType.cs
new file mode 100644
index 0000000000..03e3270e0b
--- /dev/null
+++ b/src/Avalonia.Controls/PlatformInhibitionType.cs
@@ -0,0 +1,13 @@
+namespace Avalonia.Controls
+{
+ ///
+ /// A platform specific behavior that can be inhibited.
+ ///
+ public enum PlatformInhibitionType
+ {
+ ///
+ /// When inhibited, prevents the app from being put to sleep or being given a lower priority when not in focus.
+ ///
+ AppSleep
+ }
+}
diff --git a/src/Avalonia.Controls/Presenters/ContentPresenter.cs b/src/Avalonia.Controls/Presenters/ContentPresenter.cs
index 584dfea97f..be61bb18a1 100644
--- a/src/Avalonia.Controls/Presenters/ContentPresenter.cs
+++ b/src/Avalonia.Controls/Presenters/ContentPresenter.cs
@@ -534,7 +534,7 @@ namespace Avalonia.Controls.Presenters
}
///
- public override void Render(DrawingContext context)
+ public sealed override void Render(DrawingContext context)
{
_borderRenderer.Render(context, Bounds.Size, LayoutThickness, CornerRadius, Background, BorderBrush,
BoxShadow);
diff --git a/src/Avalonia.Controls/Presenters/ScrollContentPresenter.cs b/src/Avalonia.Controls/Presenters/ScrollContentPresenter.cs
index 454f7eac9d..bc86558ab3 100644
--- a/src/Avalonia.Controls/Presenters/ScrollContentPresenter.cs
+++ b/src/Avalonia.Controls/Presenters/ScrollContentPresenter.cs
@@ -427,6 +427,7 @@ namespace Avalonia.Controls.Presenters
Viewport = finalSize;
Extent = Child!.Bounds.Size.Inflate(Child.Margin);
+ Offset = ScrollViewer.CoerceOffset(Extent, finalSize, Offset);
_isAnchorElementDirty = true;
return finalSize;
diff --git a/src/Avalonia.Controls/Presenters/TextPresenter.cs b/src/Avalonia.Controls/Presenters/TextPresenter.cs
index f599511392..bb6b03d59a 100644
--- a/src/Avalonia.Controls/Presenters/TextPresenter.cs
+++ b/src/Avalonia.Controls/Presenters/TextPresenter.cs
@@ -63,6 +63,15 @@ namespace Avalonia.Controls.Presenters
o => o.PreeditText,
(o, v) => o.PreeditText = v);
+ ///
+ /// Defines the property.
+ ///
+ public static readonly DirectProperty CompositionRegionProperty =
+ AvaloniaProperty.RegisterDirect(
+ nameof(CompositionRegion),
+ o => o.CompositionRegion,
+ (o, v) => o.CompositionRegion = v);
+
///
/// Defines the property.
///
@@ -106,6 +115,7 @@ namespace Avalonia.Controls.Presenters
private Rect _caretBounds;
private Point _navigationPosition;
private string? _preeditText;
+ private TextRange? _compositionRegion;
static TextPresenter()
{
@@ -146,6 +156,12 @@ namespace Avalonia.Controls.Presenters
set => SetAndRaise(PreeditTextProperty, ref _preeditText, value);
}
+ public TextRange? CompositionRegion
+ {
+ get => _compositionRegion;
+ set => SetAndRaise(CompositionRegionProperty, ref _compositionRegion, value);
+ }
+
///
/// Gets or sets the font family.
///
@@ -388,7 +404,7 @@ namespace Avalonia.Controls.Presenters
TextLayout.Draw(context, new Point(left, top));
}
- public override void Render(DrawingContext context)
+ public sealed override void Render(DrawingContext context)
{
var selectionStart = SelectionStart;
var selectionEnd = SelectionEnd;
@@ -548,7 +564,20 @@ namespace Avalonia.Controls.Presenters
var foreground = Foreground;
- if (!string.IsNullOrEmpty(_preeditText))
+ if(_compositionRegion != null)
+ {
+ var preeditHighlight = new ValueSpan(_compositionRegion?.Start ?? 0, _compositionRegion?.Length ?? 0,
+ new GenericTextRunProperties(typeface, FontSize,
+ foregroundBrush: foreground,
+ textDecorations: TextDecorations.Underline));
+
+ textStyleOverrides = new[]
+ {
+ preeditHighlight
+ };
+
+ }
+ else if (!string.IsNullOrEmpty(_preeditText))
{
var preeditHighlight = new ValueSpan(_caretIndex, _preeditText.Length,
new GenericTextRunProperties(typeface, FontSize,
@@ -911,6 +940,7 @@ namespace Avalonia.Controls.Presenters
break;
}
+ case nameof(CompositionRegion):
case nameof(Foreground):
case nameof(FontSize):
case nameof(FontStyle):
@@ -931,7 +961,6 @@ namespace Avalonia.Controls.Presenters
case nameof(PasswordChar):
case nameof(RevealPassword):
-
case nameof(FlowDirection):
{
InvalidateTextLayout();
diff --git a/src/Avalonia.Controls/Primitives/AccessText.cs b/src/Avalonia.Controls/Primitives/AccessText.cs
index 49e76d0728..ed3412bb45 100644
--- a/src/Avalonia.Controls/Primitives/AccessText.cs
+++ b/src/Avalonia.Controls/Primitives/AccessText.cs
@@ -60,10 +60,9 @@ namespace Avalonia.Controls.Primitives
/// Renders the to a drawing context.
///
/// The drawing context.
- public override void Render(DrawingContext context)
+ protected internal override void RenderCore(DrawingContext context)
{
- base.Render(context);
-
+ base.RenderCore(context);
int underscore = Text?.IndexOf('_') ?? -1;
if (underscore != -1 && ShowAccessKey)
diff --git a/src/Avalonia.Controls/Primitives/AdornerLayer.cs b/src/Avalonia.Controls/Primitives/AdornerLayer.cs
index 3464857131..611d57a980 100644
--- a/src/Avalonia.Controls/Primitives/AdornerLayer.cs
+++ b/src/Avalonia.Controls/Primitives/AdornerLayer.cs
@@ -34,8 +34,8 @@ namespace Avalonia.Controls.Primitives
public static readonly AttachedProperty AdornerProperty =
AvaloniaProperty.RegisterAttached("Adorner");
- private static readonly AttachedProperty s_adornedElementInfoProperty =
- AvaloniaProperty.RegisterAttached("AdornedElementInfo");
+ private static readonly AttachedProperty s_adornedElementInfoProperty =
+ AvaloniaProperty.RegisterAttached("AdornedElementInfo");
private static readonly AttachedProperty s_savedAdornerLayerProperty =
AvaloniaProperty.RegisterAttached("SavedAdornerLayer");
@@ -159,8 +159,8 @@ namespace Avalonia.Controls.Primitives
return;
}
- AdornerLayer.SetAdornedElement(adorner, visual);
- AdornerLayer.SetIsClipEnabled(adorner, false);
+ SetAdornedElement(adorner, visual);
+ SetIsClipEnabled(adorner, false);
((ISetLogicalParent) adorner).SetParent(visual);
layer.Children.Add(adorner);
@@ -177,6 +177,7 @@ namespace Avalonia.Controls.Primitives
((ISetLogicalParent) adorner).SetParent(null);
}
+ ///
protected override Size MeasureOverride(Size availableSize)
{
foreach (var child in Children)
@@ -199,6 +200,7 @@ namespace Avalonia.Controls.Primitives
return default;
}
+ ///
protected override Size ArrangeOverride(Size finalSize)
{
foreach (var child in Children)
@@ -217,7 +219,7 @@ namespace Avalonia.Controls.Primitives
}
else
{
- ArrangeChild((Control) child, finalSize);
+ ArrangeChild(child, finalSize);
}
}
}
@@ -277,8 +279,11 @@ namespace Avalonia.Controls.Primitives
private void UpdateAdornedElement(Visual adorner, Visual? adorned)
{
if (adorner.CompositionVisual != null)
+ {
adorner.CompositionVisual.AdornedVisual = adorned?.CompositionVisual;
-
+ adorner.CompositionVisual.AdornerIsClipped = GetIsClipEnabled(adorner);
+ }
+
var info = adorner.GetValue(s_adornedElementInfoProperty);
if (info != null)
diff --git a/src/Avalonia.Controls/Primitives/OverlayPopupHost.cs b/src/Avalonia.Controls/Primitives/OverlayPopupHost.cs
index d466edeb33..e16633483b 100644
--- a/src/Avalonia.Controls/Primitives/OverlayPopupHost.cs
+++ b/src/Avalonia.Controls/Primitives/OverlayPopupHost.cs
@@ -1,6 +1,5 @@
using System;
using System.Collections.Generic;
-using Avalonia.Reactive;
using Avalonia.Controls.Primitives.PopupPositioning;
using Avalonia.Interactivity;
using Avalonia.Media;
@@ -18,8 +17,8 @@ namespace Avalonia.Controls.Primitives
PopupRoot.TransformProperty.AddOwner();
private readonly OverlayLayer _overlayLayer;
- private PopupPositionerParameters _positionerParameters = new PopupPositionerParameters();
- private ManagedPopupPositioner _positioner;
+ private readonly ManagedPopupPositioner _positioner;
+ private PopupPositionerParameters _positionerParameters;
private Point _lastRequestedPosition;
private bool _shown;
@@ -29,13 +28,16 @@ namespace Avalonia.Controls.Primitives
_positioner = new ManagedPopupPositioner(this);
}
+ ///
public void SetChild(Control? control)
{
Content = control;
}
+ ///
public Visual? HostedVisualTreeRoot => null;
+ ///
public Transform? Transform
{
get => GetValue(TransformProperty);
@@ -48,23 +50,27 @@ namespace Avalonia.Controls.Primitives
set { /* Not currently supported in overlay popups */ }
}
+ ///
protected internal override Interactive? InteractiveParent => (Interactive?)VisualParent;
+ ///
public void Dispose() => Hide();
-
+ ///
public void Show()
{
_overlayLayer.Children.Add(this);
_shown = true;
}
+ ///
public void Hide()
{
_overlayLayer.Children.Remove(this);
_shown = false;
}
+ ///
public void ConfigurePosition(Visual target, PlacementMode placement, Point offset,
PopupAnchor anchor = PopupAnchor.None, PopupGravity gravity = PopupGravity.None,
PopupPositionerConstraintAdjustment constraintAdjustment = PopupPositionerConstraintAdjustment.All,
@@ -75,6 +81,7 @@ namespace Avalonia.Controls.Primitives
UpdatePosition();
}
+ ///
protected override Size ArrangeOverride(Size finalSize)
{
if (_positionerParameters.Size != finalSize)
@@ -123,17 +130,18 @@ namespace Avalonia.Controls.Primitives
public static IPopupHost CreatePopupHost(Visual target, IAvaloniaDependencyResolver? dependencyResolver)
{
- var platform = TopLevel.GetTopLevel(target)?.PlatformImpl?.CreatePopup();
- if (platform != null)
- return new PopupRoot((TopLevel)target.GetVisualRoot()!, platform, dependencyResolver);
-
- var overlayLayer = OverlayLayer.GetOverlayLayer(target);
- if (overlayLayer == null)
- throw new InvalidOperationException(
- "Unable to create IPopupImpl and no overlay layer is found for the target control");
+ if (TopLevel.GetTopLevel(target) is { } topLevel && topLevel.PlatformImpl?.CreatePopup() is { } popupImpl)
+ {
+ return new PopupRoot(topLevel, popupImpl, dependencyResolver);
+ }
+ if (OverlayLayer.GetOverlayLayer(target) is { } overlayLayer)
+ {
+ return new OverlayPopupHost(overlayLayer);
+ }
- return new OverlayPopupHost(overlayLayer);
+ throw new InvalidOperationException(
+ "Unable to create IPopupImpl and no overlay layer is found for the target control");
}
}
}
diff --git a/src/Avalonia.Controls/Primitives/Popup.cs b/src/Avalonia.Controls/Primitives/Popup.cs
index 3b68cd2ae8..9d443d9289 100644
--- a/src/Avalonia.Controls/Primitives/Popup.cs
+++ b/src/Avalonia.Controls/Primitives/Popup.cs
@@ -120,7 +120,7 @@ namespace Avalonia.Controls.Primitives
public static readonly StyledProperty TopmostProperty =
AvaloniaProperty.Register(nameof(Topmost));
- private bool _isOpenRequested = false;
+ private bool _isOpenRequested;
private bool _isOpen;
private bool _ignoreIsOpenChanged;
private PopupOpenState? _openState;
@@ -377,9 +377,9 @@ namespace Avalonia.Controls.Primitives
popupHost.SetChild(Child);
((ISetLogicalParent)popupHost).SetParent(this);
- if (InheritsTransform && placementTarget is Control c)
+ if (InheritsTransform)
{
- TransformTrackingHelper.Track(c, PlacementTargetTransformChanged)
+ TransformTrackingHelper.Track(placementTarget, PlacementTargetTransformChanged)
.DisposeWith(handlerCleanup);
}
else
@@ -387,6 +387,15 @@ namespace Avalonia.Controls.Primitives
popupHost.Transform = null;
}
+ if (popupHost is PopupRoot topLevelPopup)
+ {
+ topLevelPopup
+ .Bind(
+ ThemeVariantScope.ActualThemeVariantProperty,
+ this.GetBindingObservable(ThemeVariantScope.ActualThemeVariantProperty))
+ .DisposeWith(handlerCleanup);
+ }
+
UpdateHostPosition(popupHost, placementTarget);
SubscribeToEventHandler>(popupHost, RootTemplateApplied,
@@ -518,6 +527,7 @@ namespace Avalonia.Controls.Primitives
Close();
}
+ ///
protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs change)
{
base.OnPropertyChanged(change);
@@ -579,7 +589,7 @@ namespace Avalonia.Controls.Primitives
var scaleX = 1.0;
var scaleY = 1.0;
- if (InheritsTransform && placementTarget.TransformToVisual(topLevel) is Matrix m)
+ if (InheritsTransform && placementTarget.TransformToVisual(topLevel) is { } m)
{
scaleX = Math.Sqrt(m.M11 * m.M11 + m.M12 * m.M12);
scaleY = Math.Sqrt(m.M11 * m.M11 + m.M12 * m.M12);
@@ -623,6 +633,7 @@ namespace Avalonia.Controls.Primitives
}
}
+ ///
protected override AutomationPeer OnCreateAutomationPeer()
{
return new PopupAutomationPeer(this);
@@ -850,7 +861,7 @@ namespace Avalonia.Controls.Primitives
var popupHost = _openState.PopupHost;
- return popupHost != null && ((Visual)popupHost).IsVisualAncestorOf(visual);
+ return ((Visual)popupHost).IsVisualAncestorOf(visual);
}
public bool IsPointerOverPopup => ((IInputElement?)_openState?.PopupHost)?.IsPointerOver ?? false;
diff --git a/src/Avalonia.Controls/Primitives/SelectingItemsControl.cs b/src/Avalonia.Controls/Primitives/SelectingItemsControl.cs
index 5210362505..2ee32b0dda 100644
--- a/src/Avalonia.Controls/Primitives/SelectingItemsControl.cs
+++ b/src/Avalonia.Controls/Primitives/SelectingItemsControl.cs
@@ -3,16 +3,16 @@ using System.Collections;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.ComponentModel;
+using System.Diagnostics.CodeAnalysis;
using System.Linq;
-using System.Xml.Linq;
-using Avalonia.Controls.Generators;
using Avalonia.Controls.Selection;
+using Avalonia.Controls.Utils;
using Avalonia.Data;
using Avalonia.Input;
using Avalonia.Input.Platform;
using Avalonia.Interactivity;
+using Avalonia.Metadata;
using Avalonia.Threading;
-using Avalonia.VisualTree;
namespace Avalonia.Controls.Primitives
{
@@ -66,6 +66,19 @@ namespace Avalonia.Controls.Primitives
(o, v) => o.SelectedItem = v,
defaultBindingMode: BindingMode.TwoWay, enableDataValidation: true);
+ ///
+ /// Defines the property
+ ///
+ public static readonly StyledProperty SelectedValueProperty =
+ AvaloniaProperty.Register(nameof(SelectedValue),
+ defaultBindingMode: BindingMode.TwoWay);
+
+ ///
+ /// Defines the property
+ ///
+ public static readonly StyledProperty SelectedValueBindingProperty =
+ AvaloniaProperty.Register(nameof(SelectedValueBinding));
+
///
/// Defines the property.
///
@@ -129,6 +142,8 @@ namespace Avalonia.Controls.Primitives
private bool _ignoreContainerSelectionChanged;
private UpdateState? _updateState;
private bool _hasScrolledToSelectedItem;
+ private BindingHelper? _bindingHelper;
+ private bool _isSelectionChangeActive;
///
/// Initializes static members of the class.
@@ -143,8 +158,8 @@ namespace Avalonia.Controls.Primitives
///
public event EventHandler? SelectionChanged
{
- add { AddHandler(SelectionChangedEvent, value); }
- remove { RemoveHandler(SelectionChangedEvent, value); }
+ add => AddHandler(SelectionChangedEvent, value);
+ remove => RemoveHandler(SelectionChangedEvent, value);
}
///
@@ -152,8 +167,8 @@ namespace Avalonia.Controls.Primitives
///
public bool AutoScrollToSelectedItem
{
- get { return GetValue(AutoScrollToSelectedItemProperty); }
- set { SetValue(AutoScrollToSelectedItemProperty, value); }
+ get => GetValue(AutoScrollToSelectedItemProperty);
+ set => SetValue(AutoScrollToSelectedItemProperty, value);
}
///
@@ -209,6 +224,28 @@ namespace Avalonia.Controls.Primitives
}
}
+ ///
+ /// Gets the instance used to obtain the
+ /// property
+ ///
+ [AssignBinding]
+ [InheritDataTypeFromItems(nameof(Items))]
+ public IBinding? SelectedValueBinding
+ {
+ get => GetValue(SelectedValueBindingProperty);
+ set => SetValue(SelectedValueBindingProperty, value);
+ }
+
+ ///
+ /// Gets or sets the value of the selected item, obtained using
+ ///
+ ///
+ public object? SelectedValue
+ {
+ get => GetValue(SelectedValueProperty);
+ set => SetValue(SelectedValueProperty, value);
+ }
+
///
/// Gets or sets the selected items.
///
@@ -255,6 +292,7 @@ namespace Avalonia.Controls.Primitives
///
/// Gets or sets the model that holds the current selection.
///
+ [AllowNull]
protected ISelectionModel Selection
{
get
@@ -307,10 +345,7 @@ namespace Avalonia.Controls.Primitives
if (_oldSelectedItems != SelectedItems)
{
- RaisePropertyChanged(
- SelectedItemsProperty,
- new Optional(_oldSelectedItems),
- new BindingValue(SelectedItems));
+ RaisePropertyChanged(SelectedItemsProperty, _oldSelectedItems, SelectedItems);
_oldSelectedItems = SelectedItems;
}
}
@@ -322,8 +357,8 @@ namespace Avalonia.Controls.Primitives
///
public bool IsTextSearchEnabled
{
- get { return GetValue(IsTextSearchEnabledProperty); }
- set { SetValue(IsTextSearchEnabledProperty, value); }
+ get => GetValue(IsTextSearchEnabledProperty);
+ set => SetValue(IsTextSearchEnabledProperty, value);
}
///
@@ -332,8 +367,8 @@ namespace Avalonia.Controls.Primitives
///
public bool WrapSelection
{
- get { return GetValue(WrapSelectionProperty); }
- set { SetValue(WrapSelectionProperty, value); }
+ get => GetValue(WrapSelectionProperty);
+ set => SetValue(WrapSelectionProperty, value);
}
///
@@ -345,8 +380,8 @@ namespace Avalonia.Controls.Primitives
///
protected SelectionMode SelectionMode
{
- get { return GetValue(SelectionModeProperty); }
- set { SetValue(SelectionModeProperty, value); }
+ get => GetValue(SelectionModeProperty);
+ set => SetValue(SelectionModeProperty, value);
}
///
@@ -399,6 +434,7 @@ namespace Avalonia.Controls.Primitives
return null;
}
+ ///
protected override void ItemsCollectionChanged(object? sender, NotifyCollectionChangedEventArgs e)
{
base.ItemsCollectionChanged(sender!, e);
@@ -409,12 +445,14 @@ namespace Avalonia.Controls.Primitives
}
}
+ ///
protected override void OnAttachedToVisualTree(VisualTreeAttachmentEventArgs e)
{
base.OnAttachedToVisualTree(e);
AutoScrollToSelectedItemIfNecessary();
}
+ ///
protected override void OnApplyTemplate(TemplateAppliedEventArgs e)
{
base.OnApplyTemplate(e);
@@ -431,6 +469,7 @@ namespace Avalonia.Controls.Primitives
}
}
+ ///
protected internal override void PrepareContainerForItemOverride(Control element, object? item, int index)
{
base.PrepareContainerForItemOverride(element, item, index);
@@ -447,12 +486,14 @@ namespace Avalonia.Controls.Primitives
}
}
+ ///
protected override void ContainerIndexChangedOverride(Control container, int oldIndex, int newIndex)
{
base.ContainerIndexChangedOverride(container, oldIndex, newIndex);
MarkContainerSelected(container, Selection.IsSelected(newIndex));
}
+ ///
protected internal override void ClearContainerForItemOverride(Control element)
{
base.ClearContainerForItemOverride(element);
@@ -463,7 +504,7 @@ namespace Avalonia.Controls.Primitives
KeyboardNavigation.SetTabOnceActiveElement(panel, null);
}
- if (element is ISelectable selectable)
+ if (element is ISelectable)
MarkContainerSelected(element, false);
}
@@ -498,7 +539,8 @@ namespace Avalonia.Controls.Primitives
DataValidationErrors.SetError(this, error);
}
}
-
+
+ ///
protected override void OnInitialized()
{
base.OnInitialized();
@@ -509,6 +551,7 @@ namespace Avalonia.Controls.Primitives
}
}
+ ///
protected override void OnTextInput(TextInputEventArgs e)
{
if (!e.Handled)
@@ -551,6 +594,7 @@ namespace Avalonia.Controls.Primitives
base.OnTextInput(e);
}
+ ///
protected override void OnKeyDown(KeyEventArgs e)
{
base.OnKeyDown(e);
@@ -582,6 +626,7 @@ namespace Avalonia.Controls.Primitives
}
}
+ ///
protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs change)
{
base.OnPropertyChanged(change);
@@ -592,7 +637,7 @@ namespace Avalonia.Controls.Primitives
}
if (change.Property == ItemsProperty && _updateState is null && _selection is object)
{
- var newValue = change.GetNewValue();
+ var newValue = change.GetNewValue();
_selection.Source = newValue;
if (newValue is null)
@@ -609,6 +654,60 @@ namespace Avalonia.Controls.Primitives
{
WrapFocus = WrapSelection;
}
+ else if (change.Property == SelectedValueProperty)
+ {
+ if (_isSelectionChangeActive)
+ return;
+
+ if (_updateState is not null)
+ {
+ _updateState.SelectedValue = change.NewValue;
+ return;
+ }
+
+ SelectItemWithValue(change.NewValue);
+ }
+ else if (change.Property == SelectedValueBindingProperty)
+ {
+ var idx = SelectedIndex;
+
+ // If no selection is active, don't do anything as SelectedValue is already null
+ if (idx == -1)
+ {
+ return;
+ }
+
+ var value = change.GetNewValue();
+ if (value is null)
+ {
+ // Clearing SelectedValueBinding makes the SelectedValue the item itself
+ SelectedValue = SelectedItem;
+ return;
+ }
+
+ var selectedItem = SelectedItem;
+
+ try
+ {
+ _isSelectionChangeActive = true;
+
+ if (_bindingHelper is null)
+ {
+ _bindingHelper = new BindingHelper(value);
+ }
+ else
+ {
+ _bindingHelper.UpdateBinding(value);
+ }
+
+ // Re-evaluate SelectedValue with the new binding
+ SelectedValue = _bindingHelper.Evaluate(selectedItem);
+ }
+ finally
+ {
+ _isSelectionChangeActive = false;
+ }
+ }
}
///
@@ -695,7 +794,7 @@ namespace Avalonia.Controls.Primitives
{
if (multi)
{
- if (Selection.IsSelected(index) == true)
+ if (Selection.IsSelected(index))
{
Selection.Deselect(index);
}
@@ -716,12 +815,10 @@ namespace Avalonia.Controls.Primitives
Selection.Select(index);
}
- if (Presenter?.Panel != null)
+ if (Presenter?.Panel is { } panel)
{
var container = ContainerFromIndex(index);
- KeyboardNavigation.SetTabOnceActiveElement(
- (InputElement)Presenter.Panel,
- container);
+ KeyboardNavigation.SetTabOnceActiveElement(panel, container);
}
}
@@ -809,12 +906,13 @@ namespace Avalonia.Controls.Primitives
else if (e.PropertyName == nameof(InternalSelectionModel.WritableSelectedItems) &&
_oldSelectedItems != (Selection as InternalSelectionModel)?.SelectedItems)
{
- RaisePropertyChanged(
- SelectedItemsProperty,
- new Optional(_oldSelectedItems),
- new BindingValue(SelectedItems));
+ RaisePropertyChanged(SelectedItemsProperty, _oldSelectedItems, SelectedItems);
_oldSelectedItems = SelectedItems;
}
+ else if (e.PropertyName == nameof(ISelectionModel.Source))
+ {
+ ClearValue(SelectedValueProperty);
+ }
}
///
@@ -845,6 +943,11 @@ namespace Avalonia.Controls.Primitives
Mark(i, false);
}
+ if (!_isSelectionChangeActive)
+ {
+ UpdateSelectedValueFromItem();
+ }
+
var route = BuildEventRoute(SelectionChangedEvent);
if (route.HasHandlers)
@@ -871,6 +974,109 @@ namespace Avalonia.Controls.Primitives
}
}
+ private void SelectItemWithValue(object? value)
+ {
+ if (ItemCount == 0 || _isSelectionChangeActive)
+ return;
+
+ try
+ {
+ _isSelectionChangeActive = true;
+ var si = FindItemWithValue(value);
+ if (si != AvaloniaProperty.UnsetValue)
+ {
+ SelectedItem = si;
+ }
+ else
+ {
+ SelectedItem = null;
+ }
+ }
+ finally
+ {
+ _isSelectionChangeActive = false;
+ }
+ }
+
+ private object FindItemWithValue(object? value)
+ {
+ if (ItemCount == 0 || value is null)
+ {
+ return AvaloniaProperty.UnsetValue;
+ }
+
+ var items = Items;
+ var binding = SelectedValueBinding;
+
+ if (binding is null)
+ {
+ // No SelectedValueBinding set, SelectedValue is the item itself
+ // Still verify the value passed in is in the Items list
+ var index = items!.IndexOf(value);
+
+ if (index >= 0)
+ {
+ return value;
+ }
+ else
+ {
+ return AvaloniaProperty.UnsetValue;
+ }
+ }
+
+ _bindingHelper ??= new BindingHelper(binding);
+
+ // Matching UWP behavior, if duplicates are present, return the first item matching
+ // the SelectedValue provided
+ foreach (var item in items!)
+ {
+ var itemValue = _bindingHelper.Evaluate(item);
+
+ if (itemValue.Equals(value))
+ {
+ return item;
+ }
+ }
+
+ return AvaloniaProperty.UnsetValue;
+ }
+
+ private void UpdateSelectedValueFromItem()
+ {
+ if (_isSelectionChangeActive)
+ return;
+
+ var binding = SelectedValueBinding;
+ var item = SelectedItem;
+
+ if (binding is null || item is null)
+ {
+ // No SelectedValueBinding, SelectedValue is Item itself
+ try
+ {
+ _isSelectionChangeActive = true;
+ SelectedValue = item;
+ }
+ finally
+ {
+ _isSelectionChangeActive = false;
+ }
+ return;
+ }
+
+ _bindingHelper ??= new BindingHelper(binding);
+
+ try
+ {
+ _isSelectionChangeActive = true;
+ SelectedValue = _bindingHelper.Evaluate(item);
+ }
+ finally
+ {
+ _isSelectionChangeActive = false;
+ }
+ }
+
private void AutoScrollToSelectedItemIfNecessary()
{
if (AutoScrollToSelectedItem &&
@@ -940,7 +1146,7 @@ namespace Avalonia.Controls.Primitives
private void UpdateContainerSelection()
{
- if (Presenter?.Panel is Panel panel)
+ if (Presenter?.Panel is { } panel)
{
foreach (var container in panel.Children)
{
@@ -1037,6 +1243,13 @@ namespace Avalonia.Controls.Primitives
Selection.Clear();
}
+ if (state.SelectedValue.HasValue)
+ {
+ var item = FindItemWithValue(state.SelectedValue.Value);
+ if (item != AvaloniaProperty.UnsetValue)
+ state.SelectedItem = item;
+ }
+
if (state.SelectedIndex.HasValue)
{
SelectedIndex = state.SelectedIndex.Value;
@@ -1098,6 +1311,7 @@ namespace Avalonia.Controls.Primitives
{
private Optional _selectedIndex;
private Optional _selectedItem;
+ private Optional _selectedValue;
public int UpdateCount { get; set; }
public Optional Selection { get; set; }
@@ -1122,6 +1336,54 @@ namespace Avalonia.Controls.Primitives
_selectedIndex = default;
}
}
+
+ public Optional SelectedValue
+ {
+ get => _selectedValue;
+ set
+ {
+ _selectedValue = value;
+ }
+ }
+ }
+
+ ///
+ /// Helper class for evaluating a binding from an Item and IBinding instance
+ ///
+ private class BindingHelper : StyledElement
+ {
+ public BindingHelper(IBinding binding)
+ {
+ UpdateBinding(binding);
+ }
+
+ public static readonly StyledProperty ValueProperty =
+ AvaloniaProperty.Register("Value");
+
+ public object Evaluate(object? dataContext)
+ {
+ dataContext = dataContext ?? throw new ArgumentNullException(nameof(dataContext));
+
+ // Only update the DataContext if necessary
+ if (!dataContext.Equals(DataContext))
+ DataContext = dataContext;
+
+ return GetValue(ValueProperty);
+ }
+
+ public void UpdateBinding(IBinding binding)
+ {
+ _lastBinding = binding;
+ var ib = binding.Initiate(this, ValueProperty);
+ if (ib is null)
+ {
+ throw new InvalidOperationException("Unable to create binding");
+ }
+
+ BindingOperations.Apply(this, ValueProperty, ib, null);
+ }
+
+ private IBinding? _lastBinding;
}
}
}
diff --git a/src/Avalonia.Controls/Primitives/TemplatedControl.cs b/src/Avalonia.Controls/Primitives/TemplatedControl.cs
index 9a684c4534..d8874832bd 100644
--- a/src/Avalonia.Controls/Primitives/TemplatedControl.cs
+++ b/src/Avalonia.Controls/Primitives/TemplatedControl.cs
@@ -290,12 +290,6 @@ namespace Avalonia.Controls.Primitives
ApplyTemplatedParent(child, this);
((ISetLogicalParent)child).SetParent(this);
VisualChildren.Add(child);
-
- // Existing code kinda expect to see a NameScope even if it's empty
- if (nameScope == null)
- {
- nameScope = new NameScope();
- }
var e = new TemplateAppliedEventArgs(nameScope);
OnApplyTemplate(e);
@@ -320,6 +314,7 @@ namespace Avalonia.Controls.Primitives
return this;
}
+ ///
protected sealed override void NotifyChildResourcesChanged(ResourcesChangedEventArgs e)
{
var count = VisualChildren.Count;
diff --git a/src/Avalonia.Controls/Primitives/TextSearch.cs b/src/Avalonia.Controls/Primitives/TextSearch.cs
index 949532cb16..962fba361e 100644
--- a/src/Avalonia.Controls/Primitives/TextSearch.cs
+++ b/src/Avalonia.Controls/Primitives/TextSearch.cs
@@ -11,15 +11,15 @@ namespace Avalonia.Controls.Primitives
/// Defines the Text attached property.
/// This text will be considered during text search in (such as )
///
- public static readonly AttachedProperty TextProperty
- = AvaloniaProperty.RegisterAttached("Text", typeof(TextSearch));
+ public static readonly AttachedProperty TextProperty
+ = AvaloniaProperty.RegisterAttached("Text", typeof(TextSearch));
///
/// Sets the for a control.
///
/// The control
/// The search text to set
- public static void SetText(Control control, string text)
+ public static void SetText(Control control, string? text)
{
control.SetValue(TextProperty, text);
}
@@ -29,7 +29,7 @@ namespace Avalonia.Controls.Primitives
///
/// The control
/// The property value
- public static string GetText(Control control)
+ public static string? GetText(Control control)
{
return control.GetValue(TextProperty);
}
diff --git a/src/Avalonia.Controls/Primitives/Track.cs b/src/Avalonia.Controls/Primitives/Track.cs
index 14ec7a2849..9e8d1478fa 100644
--- a/src/Avalonia.Controls/Primitives/Track.cs
+++ b/src/Avalonia.Controls/Primitives/Track.cs
@@ -5,7 +5,6 @@
using System;
using Avalonia.Controls.Metadata;
-using Avalonia.Data;
using Avalonia.Input;
using Avalonia.Layout;
using Avalonia.Metadata;
@@ -31,14 +30,14 @@ namespace Avalonia.Controls.Primitives
public static readonly StyledProperty OrientationProperty =
ScrollBar.OrientationProperty.AddOwner
public class ThemeVariantScope : Decorator
{
+ ///
+ public static readonly StyledProperty ActualThemeVariantProperty =
+ ThemeVariant.ActualThemeVariantProperty.AddOwner();
+
+ ///
+ public static readonly StyledProperty RequestedThemeVariantProperty =
+ ThemeVariant.RequestedThemeVariantProperty.AddOwner();
+
///
/// Gets or sets the UI theme variant that is used by the control (and its child elements) for resource determination.
/// The UI theme you specify with ThemeVariant can override the app-level ThemeVariant.
diff --git a/src/Avalonia.Controls/TickBar.cs b/src/Avalonia.Controls/TickBar.cs
index 12ae766052..63fe9c9384 100644
--- a/src/Avalonia.Controls/TickBar.cs
+++ b/src/Avalonia.Controls/TickBar.cs
@@ -51,20 +51,16 @@ namespace Avalonia.Controls
TicksProperty);
}
- public TickBar() : base()
- {
- }
-
///
/// Defines the property.
///
- public static readonly StyledProperty FillProperty =
- AvaloniaProperty.Register(nameof(Fill));
+ public static readonly StyledProperty FillProperty =
+ AvaloniaProperty.Register(nameof(Fill));
///
/// Brush used to fill the TickBar's Ticks.
///
- public IBrush Fill
+ public IBrush? Fill
{
get { return GetValue(FillProperty); }
set { SetValue(FillProperty, value); }
@@ -136,15 +132,15 @@ namespace Avalonia.Controls
///
/// Defines the property.
///
- public static readonly StyledProperty> TicksProperty =
- AvaloniaProperty.Register>(nameof(Ticks));
+ public static readonly StyledProperty?> TicksProperty =
+ AvaloniaProperty.Register?>(nameof(Ticks));
///
/// The Ticks property contains collection of value of type Double which
/// are the logical positions use to draw the ticks.
/// The property value is a .
///
- public AvaloniaList Ticks
+ public AvaloniaList? Ticks
{
get { return GetValue(TicksProperty); }
set { SetValue(TicksProperty, value); }
@@ -217,7 +213,7 @@ namespace Avalonia.Controls
///
/// Brush that use to fill ticks is specified by Fill property.
///
- public override void Render(DrawingContext dc)
+ public sealed override void Render(DrawingContext dc)
{
var size = new Size(Bounds.Width, Bounds.Height);
var range = Maximum - Minimum;
@@ -281,7 +277,7 @@ namespace Avalonia.Controls
endPoint = new Point(0d, halfReservedSpace);
logicalToPhysical = size.Height / range * -1;
break;
- };
+ }
tickLen2 = tickLen * 0.75;
diff --git a/src/Avalonia.Controls/TopLevel.cs b/src/Avalonia.Controls/TopLevel.cs
index 4db71abfa8..fdcb8cc537 100644
--- a/src/Avalonia.Controls/TopLevel.cs
+++ b/src/Avalonia.Controls/TopLevel.cs
@@ -20,6 +20,7 @@ using Avalonia.Styling;
using Avalonia.Utilities;
using Avalonia.Input.Platform;
using System.Linq;
+using System.Threading.Tasks;
namespace Avalonia.Controls
{
@@ -79,6 +80,14 @@ namespace Avalonia.Controls
public static readonly StyledProperty TransparencyBackgroundFallbackProperty =
AvaloniaProperty.Register(nameof(TransparencyBackgroundFallback), Brushes.White);
+ ///
+ public static readonly StyledProperty ActualThemeVariantProperty =
+ ThemeVariantScope.ActualThemeVariantProperty.AddOwner();
+
+ ///
+ public static readonly StyledProperty RequestedThemeVariantProperty =
+ ThemeVariantScope.RequestedThemeVariantProperty.AddOwner();
+
///
/// Defines the event.
///
@@ -95,7 +104,7 @@ namespace Avalonia.Controls
private readonly IAccessKeyHandler? _accessKeyHandler;
private readonly IKeyboardNavigationHandler? _keyboardNavigationHandler;
private readonly IGlobalStyles? _globalStyles;
- private readonly IGlobalThemeVariantProvider? _applicationThemeHost;
+ private readonly IThemeVariantHost? _applicationThemeHost;
private readonly PointerOverPreProcessor? _pointerOverPreProcessor;
private readonly IDisposable? _pointerOverPreProcessorSubscription;
private readonly IDisposable? _backGestureSubscription;
@@ -146,7 +155,7 @@ namespace Avalonia.Controls
_inputManager = TryGetService(dependencyResolver);
_keyboardNavigationHandler = TryGetService(dependencyResolver);
_globalStyles = TryGetService(dependencyResolver);
- _applicationThemeHost = TryGetService(dependencyResolver);
+ _applicationThemeHost = TryGetService(dependencyResolver);
Renderer = impl.CreateRenderer(this);
Renderer.SceneInvalidated += SceneInvalidated;
@@ -402,9 +411,33 @@ namespace Avalonia.Controls
/// The TopLevel
public static TopLevel? GetTopLevel(Visual? visual)
{
- return visual == null ? null : visual.VisualRoot as TopLevel;
+ return visual?.VisualRoot as TopLevel;
}
-
+
+ ///
+ /// Requests a to be inhibited.
+ /// The behavior remains inhibited until the return value is disposed.
+ /// The available set of s depends on the platform.
+ /// If a behavior is inhibited on a platform where this type is not supported the request will have no effect.
+ ///
+ public async Task RequestPlatformInhibition(PlatformInhibitionType type, string reason)
+ {
+ var platformBehaviorInhibition = PlatformImpl?.TryGetFeature();
+ if (platformBehaviorInhibition == null)
+ {
+ return Disposable.Create(() => { });
+ }
+
+ switch (type)
+ {
+ case PlatformInhibitionType.AppSleep:
+ await platformBehaviorInhibition.SetInhibitAppSleep(true, reason);
+ return Disposable.Create(() => platformBehaviorInhibition.SetInhibitAppSleep(false, reason).Wait());
+ default:
+ return Disposable.Create(() => { });
+ }
+ }
+
protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs change)
{
base.OnPropertyChanged(change);
@@ -617,7 +650,7 @@ namespace Avalonia.Controls
private void GlobalActualThemeVariantChanged(object? sender, EventArgs e)
{
- SetValue(ActualThemeVariantProperty, ((IGlobalThemeVariantProvider)sender!).ActualThemeVariant, BindingPriority.Template);
+ SetValue(ActualThemeVariantProperty, ((IThemeVariantHost)sender!).ActualThemeVariant, BindingPriority.Template);
}
private void SceneInvalidated(object? sender, SceneInvalidatedEventArgs e)
diff --git a/src/Avalonia.Controls/TrayIcon.cs b/src/Avalonia.Controls/TrayIcon.cs
index d1a7a1f727..93b177715e 100644
--- a/src/Avalonia.Controls/TrayIcon.cs
+++ b/src/Avalonia.Controls/TrayIcon.cs
@@ -100,8 +100,8 @@ namespace Avalonia.Controls
///
/// Defines the attached property.
///
- public static readonly AttachedProperty IconsProperty
- = AvaloniaProperty.RegisterAttached("Icons");
+ public static readonly AttachedProperty IconsProperty
+ = AvaloniaProperty.RegisterAttached("Icons");
///
/// Defines the property.
@@ -127,9 +127,9 @@ namespace Avalonia.Controls
public static readonly StyledProperty IsVisibleProperty =
Visual.IsVisibleProperty.AddOwner();
- public static void SetIcons(Application o, TrayIcons trayIcons) => o.SetValue(IconsProperty, trayIcons);
+ public static void SetIcons(Application o, TrayIcons? trayIcons) => o.SetValue(IconsProperty, trayIcons);
- public static TrayIcons GetIcons(Application o) => o.GetValue(IconsProperty);
+ public static TrayIcons? GetIcons(Application o) => o.GetValue(IconsProperty);
///
/// Gets or sets the property of a TrayIcon.
@@ -213,6 +213,7 @@ namespace Avalonia.Controls
}
}
+ ///
protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs change)
{
base.OnPropertyChanged(change);
diff --git a/src/Avalonia.Controls/TreeView.cs b/src/Avalonia.Controls/TreeView.cs
index 67e0d85436..8f2636a783 100644
--- a/src/Avalonia.Controls/TreeView.cs
+++ b/src/Avalonia.Controls/TreeView.cs
@@ -3,17 +3,13 @@ using System;
using System.Collections;
using System.Collections.Generic;
using System.Collections.Specialized;
-using System.ComponentModel;
+using System.Diagnostics.CodeAnalysis;
using System.Linq;
-using Avalonia.Reactive;
using Avalonia.Collections;
using Avalonia.Controls.Generators;
using Avalonia.Controls.Primitives;
-using Avalonia.Controls.Utils;
-using Avalonia.Data;
using Avalonia.Input;
using Avalonia.Input.Platform;
-using Avalonia.Interactivity;
using Avalonia.Threading;
using Avalonia.VisualTree;
@@ -132,6 +128,7 @@ namespace Avalonia.Controls
///
/// Gets or sets the selected items.
///
+ [AllowNull]
public IList SelectedItems
{
get
@@ -144,7 +141,6 @@ namespace Avalonia.Controls
return _selectedItems;
}
-
set
{
if (value?.IsFixedSize == true || value?.IsReadOnly == true)
@@ -167,9 +163,9 @@ namespace Avalonia.Controls
{
item.IsExpanded = true;
- if (item.Presenter?.Panel != null)
+ if (item.Presenter?.Panel is { } panel)
{
- foreach (var child in item.Presenter.Panel.Children)
+ foreach (var child in panel.Children)
{
if (child is TreeViewItem treeViewItem)
{
@@ -589,7 +585,7 @@ namespace Avalonia.Controls
case NavigationDirection.Right:
if (from?.IsExpanded == true && intoChildren && from.ItemCount > 0)
{
- result = (TreeViewItem)from.ItemContainerGenerator.ContainerFromIndex(0)!;
+ result = (TreeViewItem)from.ContainerFromIndex(0)!;
}
else if (index < parent?.ItemCount - 1)
{
@@ -865,7 +861,7 @@ namespace Avalonia.Controls
///
/// The container.
/// Whether the control is selected
- private void MarkContainerSelected(Control container, bool selected)
+ private void MarkContainerSelected(Control? container, bool selected)
{
if (container == null)
{
diff --git a/src/Avalonia.Controls/TreeViewItem.cs b/src/Avalonia.Controls/TreeViewItem.cs
index 9f8e3e38c0..e9abfef673 100644
--- a/src/Avalonia.Controls/TreeViewItem.cs
+++ b/src/Avalonia.Controls/TreeViewItem.cs
@@ -190,7 +190,7 @@ namespace Avalonia.Controls
{
if (treeViewItem.ItemCount > 0 && !treeViewItem.IsExpanded)
{
- treeViewItem.IsExpanded = true;
+ treeViewItem.SetCurrentValue(IsExpandedProperty, true);
return true;
}
@@ -201,7 +201,7 @@ namespace Avalonia.Controls
{
if (treeViewItem.ItemCount > 0 && treeViewItem.IsExpanded)
{
- treeViewItem.IsExpanded = false;
+ treeViewItem.SetCurrentValue(IsExpandedProperty, false);
return true;
}
@@ -214,7 +214,7 @@ namespace Avalonia.Controls
{
if (treeViewItem.IsFocused)
{
- treeViewItem.IsExpanded = false;
+ treeViewItem.SetCurrentValue(IsExpandedProperty, false);
}
else
{
@@ -265,7 +265,7 @@ namespace Avalonia.Controls
{
if (ItemCount > 0)
{
- IsExpanded = !IsExpanded;
+ SetCurrentValue(IsExpandedProperty, !IsExpanded);
e.Handled = true;
}
}
diff --git a/src/Avalonia.Controls/Viewbox.cs b/src/Avalonia.Controls/Viewbox.cs
index 1c2ee7ec2c..1518fb49e3 100644
--- a/src/Avalonia.Controls/Viewbox.cs
+++ b/src/Avalonia.Controls/Viewbox.cs
@@ -36,6 +36,9 @@ namespace Avalonia.Controls
AffectsMeasure(StretchProperty, StretchDirectionProperty);
}
+ ///
+ /// Initializes a new instance of the class.
+ ///
public Viewbox()
{
// The Child control is hosted inside a ViewboxContainer control so that the transform
@@ -85,13 +88,14 @@ namespace Avalonia.Controls
set => _containerVisual.RenderTransform = value;
}
+ ///
protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs change)
{
base.OnPropertyChanged(change);
if (change.Property == ChildProperty)
{
- var (oldChild, newChild) = change.GetOldAndNewValue();
+ var (oldChild, newChild) = change.GetOldAndNewValue();
if (oldChild is not null)
{
@@ -111,41 +115,33 @@ namespace Avalonia.Controls
}
}
+ ///
protected override Size MeasureOverride(Size availableSize)
{
var child = _containerVisual;
- if (child != null)
- {
- child.Measure(Size.Infinity);
-
- var childSize = child.DesiredSize;
+ child.Measure(Size.Infinity);
- var size = Stretch.CalculateSize(availableSize, childSize, StretchDirection);
+ var childSize = child.DesiredSize;
- return size;
- }
+ var size = Stretch.CalculateSize(availableSize, childSize, StretchDirection);
- return new Size();
+ return size;
}
+ ///
protected override Size ArrangeOverride(Size finalSize)
{
var child = _containerVisual;
- if (child != null)
- {
- var childSize = child.DesiredSize;
- var scale = Stretch.CalculateScaling(finalSize, childSize, StretchDirection);
-
- InternalTransform = new ImmutableTransform(Matrix.CreateScale(scale.X, scale.Y));
+ var childSize = child.DesiredSize;
+ var scale = Stretch.CalculateScaling(finalSize, childSize, StretchDirection);
- child.Arrange(new Rect(childSize));
+ InternalTransform = new ImmutableTransform(Matrix.CreateScale(scale.X, scale.Y));
- return childSize * scale;
- }
+ child.Arrange(new Rect(childSize));
- return finalSize;
+ return childSize * scale;
}
///
diff --git a/src/Avalonia.Controls/VirtualizingStackPanel.cs b/src/Avalonia.Controls/VirtualizingStackPanel.cs
index 634efbd699..8e7690aa6c 100644
--- a/src/Avalonia.Controls/VirtualizingStackPanel.cs
+++ b/src/Avalonia.Controls/VirtualizingStackPanel.cs
@@ -403,7 +403,7 @@ namespace Avalonia.Controls
if (firstIndex == -1)
{
estimatedElementSize = EstimateElementSizeU();
- firstIndex = (int)(viewportStart / estimatedElementSize);
+ firstIndex = Math.Min((int)(viewportStart / estimatedElementSize), maxIndex);
firstIndexU = firstIndex * estimatedElementSize;
}
@@ -411,13 +411,13 @@ namespace Avalonia.Controls
{
if (estimatedElementSize == -1)
estimatedElementSize = EstimateElementSizeU();
- lastIndex = (int)(viewportEnd / estimatedElementSize);
+ lastIndex = Math.Min((int)(viewportEnd / estimatedElementSize), maxIndex);
}
return new MeasureViewport
{
- firstIndex = MathUtilities.Clamp(firstIndex, 0, maxIndex),
- lastIndex = MathUtilities.Clamp(lastIndex, 0, maxIndex),
+ firstIndex = firstIndex,
+ lastIndex = lastIndex,
viewportUStart = viewportStart,
viewportUEnd = viewportEnd,
startU = firstIndexU,
@@ -1131,6 +1131,7 @@ namespace Avalonia.Controls
// The removed range was before the realized elements. Update the first index and
// the indexes of the realized elements.
_firstIndex -= count;
+ _startUUnstable = true;
var newIndex = _firstIndex;
for (var i = 0; i < _elements.Count; ++i)
diff --git a/src/Avalonia.Controls/WindowBase.cs b/src/Avalonia.Controls/WindowBase.cs
index 0c9a91148b..26e11f0d4a 100644
--- a/src/Avalonia.Controls/WindowBase.cs
+++ b/src/Avalonia.Controls/WindowBase.cs
@@ -94,7 +94,7 @@ namespace Avalonia.Controls
private set { SetAndRaise(IsActiveProperty, ref _isActive, value); }
}
- public Screens Screens { get; private set; }
+ public Screens Screens { get; }
///
/// Gets or sets the owner of the window.
diff --git a/src/Avalonia.Controls/WrapPanel.cs b/src/Avalonia.Controls/WrapPanel.cs
index 0426291d67..71b3234aff 100644
--- a/src/Avalonia.Controls/WrapPanel.cs
+++ b/src/Avalonia.Controls/WrapPanel.cs
@@ -144,35 +144,32 @@ namespace Avalonia.Controls
for (int i = 0, count = children.Count; i < count; i++)
{
var child = children[i];
- if (child != null)
- {
- // Flow passes its own constraint to children
- child.Measure(childConstraint);
+ // Flow passes its own constraint to children
+ child.Measure(childConstraint);
- // This is the size of the child in UV space
- var sz = new UVSize(orientation,
- itemWidthSet ? itemWidth : child.DesiredSize.Width,
- itemHeightSet ? itemHeight : child.DesiredSize.Height);
+ // This is the size of the child in UV space
+ var sz = new UVSize(orientation,
+ itemWidthSet ? itemWidth : child.DesiredSize.Width,
+ itemHeightSet ? itemHeight : child.DesiredSize.Height);
- if (MathUtilities.GreaterThan(curLineSize.U + sz.U, uvConstraint.U)) // Need to switch to another line
- {
- panelSize.U = Max(curLineSize.U, panelSize.U);
- panelSize.V += curLineSize.V;
- curLineSize = sz;
-
- if (MathUtilities.GreaterThan(sz.U, uvConstraint.U)) // The element is wider then the constraint - give it a separate line
- {
- panelSize.U = Max(sz.U, panelSize.U);
- panelSize.V += sz.V;
- curLineSize = new UVSize(orientation);
- }
- }
- else // Continue to accumulate a line
+ if (MathUtilities.GreaterThan(curLineSize.U + sz.U, uvConstraint.U)) // Need to switch to another line
+ {
+ panelSize.U = Max(curLineSize.U, panelSize.U);
+ panelSize.V += curLineSize.V;
+ curLineSize = sz;
+
+ if (MathUtilities.GreaterThan(sz.U, uvConstraint.U)) // The element is wider then the constraint - give it a separate line
{
- curLineSize.U += sz.U;
- curLineSize.V = Max(sz.V, curLineSize.V);
+ panelSize.U = Max(sz.U, panelSize.U);
+ panelSize.V += sz.V;
+ curLineSize = new UVSize(orientation);
}
}
+ else // Continue to accumulate a line
+ {
+ curLineSize.U += sz.U;
+ curLineSize.V = Max(sz.V, curLineSize.V);
+ }
}
// The last line size, if any should be added
@@ -202,34 +199,31 @@ namespace Avalonia.Controls
for (int i = 0; i < children.Count; i++)
{
var child = children[i];
- if (child != null)
- {
- var sz = new UVSize(orientation,
- itemWidthSet ? itemWidth : child.DesiredSize.Width,
- itemHeightSet ? itemHeight : child.DesiredSize.Height);
+ var sz = new UVSize(orientation,
+ itemWidthSet ? itemWidth : child.DesiredSize.Width,
+ itemHeightSet ? itemHeight : child.DesiredSize.Height);
- if (MathUtilities.GreaterThan(curLineSize.U + sz.U, uvFinalSize.U)) // Need to switch to another line
- {
- ArrangeLine(accumulatedV, curLineSize.V, firstInLine, i, useItemU, itemU);
-
- accumulatedV += curLineSize.V;
- curLineSize = sz;
+ if (MathUtilities.GreaterThan(curLineSize.U + sz.U, uvFinalSize.U)) // Need to switch to another line
+ {
+ ArrangeLine(accumulatedV, curLineSize.V, firstInLine, i, useItemU, itemU);
- if (MathUtilities.GreaterThan(sz.U, uvFinalSize.U)) // The element is wider then the constraint - give it a separate line
- {
- // Switch to next line which only contain one element
- ArrangeLine(accumulatedV, sz.V, i, ++i, useItemU, itemU);
+ accumulatedV += curLineSize.V;
+ curLineSize = sz;
- accumulatedV += sz.V;
- curLineSize = new UVSize(orientation);
- }
- firstInLine = i;
- }
- else // Continue to accumulate a line
+ if (MathUtilities.GreaterThan(sz.U, uvFinalSize.U)) // The element is wider then the constraint - give it a separate line
{
- curLineSize.U += sz.U;
- curLineSize.V = Max(sz.V, curLineSize.V);
+ // Switch to next line which only contain one element
+ ArrangeLine(accumulatedV, sz.V, i, ++i, useItemU, itemU);
+
+ accumulatedV += sz.V;
+ curLineSize = new UVSize(orientation);
}
+ firstInLine = i;
+ }
+ else // Continue to accumulate a line
+ {
+ curLineSize.U += sz.U;
+ curLineSize.V = Max(sz.V, curLineSize.V);
}
}
@@ -252,17 +246,14 @@ namespace Avalonia.Controls
for (int i = start; i < end; i++)
{
var child = children[i];
- if (child != null)
- {
- var childSize = new UVSize(orientation, child.DesiredSize.Width, child.DesiredSize.Height);
- double layoutSlotU = useItemU ? itemU : childSize.U;
- child.Arrange(new Rect(
- isHorizontal ? u : v,
- isHorizontal ? v : u,
- isHorizontal ? layoutSlotU : lineV,
- isHorizontal ? lineV : layoutSlotU));
- u += layoutSlotU;
- }
+ var childSize = new UVSize(orientation, child.DesiredSize.Width, child.DesiredSize.Height);
+ double layoutSlotU = useItemU ? itemU : childSize.U;
+ child.Arrange(new Rect(
+ isHorizontal ? u : v,
+ isHorizontal ? v : u,
+ isHorizontal ? layoutSlotU : lineV,
+ isHorizontal ? lineV : layoutSlotU));
+ u += layoutSlotU;
}
}
diff --git a/src/Avalonia.Diagnostics/Diagnostics/Controls/Application.cs b/src/Avalonia.Diagnostics/Diagnostics/Controls/Application.cs
index a0ff3a714f..a8f609c507 100644
--- a/src/Avalonia.Diagnostics/Diagnostics/Controls/Application.cs
+++ b/src/Avalonia.Diagnostics/Diagnostics/Controls/Application.cs
@@ -14,7 +14,7 @@ namespace Avalonia.Diagnostics.Controls
public event EventHandler? Closed;
public static readonly StyledProperty RequestedThemeVariantProperty =
- StyledElement.RequestedThemeVariantProperty.AddOwner();
+ ThemeVariantScope.RequestedThemeVariantProperty.AddOwner();
public Application(Avalonia.Application application)
{
diff --git a/src/Avalonia.Diagnostics/Diagnostics/Controls/FilterTextBox.axaml b/src/Avalonia.Diagnostics/Diagnostics/Controls/FilterTextBox.axaml
index 3bfe511fbc..1b5f431f36 100644
--- a/src/Avalonia.Diagnostics/Diagnostics/Controls/FilterTextBox.axaml
+++ b/src/Avalonia.Diagnostics/Diagnostics/Controls/FilterTextBox.axaml
@@ -16,6 +16,8 @@
- keyEvent != null &&
(KeyModifiers)(keyEvent.Modifiers & RawInputModifiers.KeyboardMask) == gesture.KeyModifiers &&
ResolveNumPadOperationKey(keyEvent.Key) == ResolveNumPadOperationKey(gesture.Key);
diff --git a/src/Avalonia.Diagnostics/Diagnostics/Screenshots/FilePickerHandler.cs b/src/Avalonia.Diagnostics/Diagnostics/Screenshots/FilePickerHandler.cs
index a7d279741e..6ea46b6d54 100644
--- a/src/Avalonia.Diagnostics/Diagnostics/Screenshots/FilePickerHandler.cs
+++ b/src/Avalonia.Diagnostics/Diagnostics/Screenshots/FilePickerHandler.cs
@@ -54,8 +54,8 @@ namespace Avalonia.Diagnostics.Screenshots
protected override async Task GetStream(Control control)
{
var storageProvider = GetTopLevel(control).StorageProvider;
- var defaultFolder = await storageProvider.TryGetFolderFromPath(_screenshotRoot)
- ?? await storageProvider.TryGetWellKnownFolder(WellKnownFolder.Pictures);
+ var defaultFolder = await storageProvider.TryGetFolderFromPathAsync(_screenshotRoot)
+ ?? await storageProvider.TryGetWellKnownFolderAsync(WellKnownFolder.Pictures);
var result = await storageProvider.SaveFilePickerAsync(new FilePickerSaveOptions
{
@@ -68,10 +68,6 @@ namespace Avalonia.Diagnostics.Screenshots
{
return null;
}
- if (!result.CanOpenWrite)
- {
- throw new InvalidOperationException("Read-only file was selected.");
- }
return await result.OpenWriteAsync();
}
diff --git a/src/Avalonia.Diagnostics/Diagnostics/ViewModels/ControlLayoutViewModel.cs b/src/Avalonia.Diagnostics/Diagnostics/ViewModels/ControlLayoutViewModel.cs
index 6b56806250..efa93ce52e 100644
--- a/src/Avalonia.Diagnostics/Diagnostics/ViewModels/ControlLayoutViewModel.cs
+++ b/src/Avalonia.Diagnostics/Diagnostics/ViewModels/ControlLayoutViewModel.cs
@@ -103,13 +103,13 @@ namespace Avalonia.Diagnostics.ViewModels
public HorizontalAlignment HorizontalAlignment
{
get => _horizontalAlignment;
- private set => RaiseAndSetIfChanged(ref _horizontalAlignment, value);
+ set => RaiseAndSetIfChanged(ref _horizontalAlignment, value);
}
public VerticalAlignment VerticalAlignment
{
get => _verticalAlignment;
- private set => RaiseAndSetIfChanged(ref _verticalAlignment, value);
+ set => RaiseAndSetIfChanged(ref _verticalAlignment, value);
}
public bool HasPadding { get; }
diff --git a/src/Avalonia.Dialogs/Internal/ManagedFileChooserViewModel.cs b/src/Avalonia.Dialogs/Internal/ManagedFileChooserViewModel.cs
index 46de460b1a..c929cb9ac9 100644
--- a/src/Avalonia.Dialogs/Internal/ManagedFileChooserViewModel.cs
+++ b/src/Avalonia.Dialogs/Internal/ManagedFileChooserViewModel.cs
@@ -260,7 +260,7 @@ namespace Avalonia.Dialogs.Internal
public void Navigate(IStorageFolder path, string initialSelectionName = null)
{
- var fullDirectoryPath = path?.TryGetFullPath() ?? Directory.GetCurrentDirectory();
+ var fullDirectoryPath = path?.TryGetLocalPath() ?? Directory.GetCurrentDirectory();
Navigate(fullDirectoryPath, initialSelectionName);
}
diff --git a/src/Avalonia.Dialogs/ManagedFileDialogExtensions.cs b/src/Avalonia.Dialogs/ManagedFileDialogExtensions.cs
index cad938ac35..e9a75ab46a 100644
--- a/src/Avalonia.Dialogs/ManagedFileDialogExtensions.cs
+++ b/src/Avalonia.Dialogs/ManagedFileDialogExtensions.cs
@@ -51,7 +51,7 @@ namespace Avalonia.Dialogs
var files = await impl.OpenFilePickerAsync(dialog.ToFilePickerOpenOptions());
return files
- .Select(file => file.TryGetFullPath() ?? file.Name)
+ .Select(file => file.TryGetLocalPath() ?? file.Name)
.ToArray();
}
}
diff --git a/src/Avalonia.FreeDesktop/Avalonia.FreeDesktop.csproj b/src/Avalonia.FreeDesktop/Avalonia.FreeDesktop.csproj
index 3b1c6cc7b1..51af5862cf 100644
--- a/src/Avalonia.FreeDesktop/Avalonia.FreeDesktop.csproj
+++ b/src/Avalonia.FreeDesktop/Avalonia.FreeDesktop.csproj
@@ -5,16 +5,40 @@
enable
+
+
+
+
+
+
+
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/Avalonia.FreeDesktop/DBusCallQueue.cs b/src/Avalonia.FreeDesktop/DBusCallQueue.cs
index e7c07dcbf9..b853626d45 100644
--- a/src/Avalonia.FreeDesktop/DBusCallQueue.cs
+++ b/src/Avalonia.FreeDesktop/DBusCallQueue.cs
@@ -7,19 +7,20 @@ namespace Avalonia.FreeDesktop
class DBusCallQueue
{
private readonly Func _errorHandler;
+ private readonly Queue- _q = new();
- record Item(Func Callback)
+ private bool _processing;
+
+ private record Item(Func Callback)
{
public Action? OnFinish;
}
- private Queue
- _q = new Queue
- ();
- private bool _processing;
public DBusCallQueue(Func errorHandler)
{
_errorHandler = errorHandler;
}
-
+
public void Enqueue(Func cb)
{
_q.Enqueue(new Item(cb));
@@ -42,7 +43,7 @@ namespace Avalonia.FreeDesktop
Process();
return tcs.Task;
}
-
+
public Task EnqueueAsync(Func> cb)
{
var tcs = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously);
@@ -62,7 +63,7 @@ namespace Avalonia.FreeDesktop
return tcs.Task;
}
- async void Process()
+ private async void Process()
{
if(_processing)
return;
diff --git a/src/Avalonia.FreeDesktop/DBusFileChooser.cs b/src/Avalonia.FreeDesktop/DBusFileChooser.cs
deleted file mode 100644
index 24db614a02..0000000000
--- a/src/Avalonia.FreeDesktop/DBusFileChooser.cs
+++ /dev/null
@@ -1,32 +0,0 @@
-using System;
-using System.Collections.Generic;
-using System.Runtime.CompilerServices;
-using System.Threading.Tasks;
-using Tmds.DBus;
-
-[assembly: InternalsVisibleTo(Connection.DynamicAssemblyName)]
-namespace Avalonia.FreeDesktop
-{
- [DBusInterface("org.freedesktop.portal.FileChooser")]
- internal interface IFileChooser : IDBusObject
- {
- Task OpenFileAsync(string ParentWindow, string Title, IDictionary Options);
- Task SaveFileAsync(string ParentWindow, string Title, IDictionary Options);
- Task SaveFilesAsync(string ParentWindow, string Title, IDictionary Options);
- Task GetAsync(string prop);
- Task GetAllAsync();
- Task SetAsync(string prop, object val);
- Task WatchPropertiesAsync(Action handler);
- }
-
- [Dictionary]
- internal class FileChooserProperties
- {
- public uint Version { get; set; }
- }
-
- internal static class FileChooserExtensions
- {
- public static Task GetVersionAsync(this IFileChooser o) => o.GetAsync("version");
- }
-}
diff --git a/src/Avalonia.FreeDesktop/DBusHelper.cs b/src/Avalonia.FreeDesktop/DBusHelper.cs
index ef99838208..da74f15a3e 100644
--- a/src/Avalonia.FreeDesktop/DBusHelper.cs
+++ b/src/Avalonia.FreeDesktop/DBusHelper.cs
@@ -1,51 +1,12 @@
using System;
using System.Threading;
using Avalonia.Logging;
-using Avalonia.Threading;
-using Tmds.DBus;
+using Tmds.DBus.Protocol;
namespace Avalonia.FreeDesktop
{
- public static class DBusHelper
+ internal static class DBusHelper
{
- ///
- /// This class uses synchronous execution at DBus connection establishment stage
- /// then switches to using AvaloniaSynchronizationContext
- ///
- private class DBusSyncContext : SynchronizationContext
- {
- private readonly object _lock = new();
- private SynchronizationContext? _ctx;
-
- public override void Post(SendOrPostCallback d, object? state)
- {
- lock (_lock)
- {
- if (_ctx is not null)
- _ctx?.Post(d, state);
- else
- d(state);
- }
- }
-
- public override void Send(SendOrPostCallback d, object? state)
- {
- lock (_lock)
- {
- if (_ctx is not null)
- _ctx?.Send(d, state);
- else
- d(state);
- }
- }
-
- public void Initialized()
- {
- lock (_lock)
- _ctx = new AvaloniaSynchronizationContext();
- }
- }
-
public static Connection? Connection { get; private set; }
public static Connection? TryInitialize(string? dbusAddress = null)
@@ -56,19 +17,14 @@ namespace Avalonia.FreeDesktop
var oldContext = SynchronizationContext.Current;
try
{
-
- var dbusContext = new DBusSyncContext();
- SynchronizationContext.SetSynchronizationContext(dbusContext);
- var conn = new Connection(new ClientConnectionOptions(dbusAddress ?? Address.Session)
+ var conn = new Connection(new ClientConnectionOptions(dbusAddress ?? Address.Session!)
{
- AutoConnect = false,
- SynchronizationContext = dbusContext
+ AutoConnect = false
});
+
// Connect synchronously
- conn.ConnectAsync().Wait();
+ conn.ConnectAsync().GetAwaiter().GetResult();
- // Initialize a brand new sync-context
- dbusContext.Initialized();
Connection = conn;
}
catch (Exception e)
diff --git a/src/Avalonia.FreeDesktop/DBusIme/DBusTextInputMethodBase.cs b/src/Avalonia.FreeDesktop/DBusIme/DBusTextInputMethodBase.cs
index 7d97c7cd36..d3c14f285d 100644
--- a/src/Avalonia.FreeDesktop/DBusIme/DBusTextInputMethodBase.cs
+++ b/src/Avalonia.FreeDesktop/DBusIme/DBusTextInputMethodBase.cs
@@ -5,7 +5,8 @@ using System.Threading.Tasks;
using Avalonia.Input.Raw;
using Avalonia.Input.TextInput;
using Avalonia.Logging;
-using Tmds.DBus;
+using Tmds.DBus.Protocol;
+using Tmds.DBus.SourceGenerator;
namespace Avalonia.FreeDesktop.DBusIme
{
@@ -46,20 +47,25 @@ namespace Avalonia.FreeDesktop.DBusIme
public DBusTextInputMethodBase(Connection connection, params string[] knownNames)
{
- _queue = new DBusCallQueue(QueueOnError);
+ _queue = new DBusCallQueue(QueueOnErrorAsync);
Connection = connection;
_knownNames = knownNames;
- Watch();
+ _ = WatchAsync();
}
public ITextInputMethodClient Client => _client;
- public bool IsActive => _client != null;
+ public bool IsActive => _client is not null;
- async void Watch()
+ private async Task WatchAsync()
{
foreach (var name in _knownNames)
- _disposables.Add(await Connection.ResolveServiceOwnerAsync(name, OnNameChange));
+ {
+ var dbus = new OrgFreedesktopDBus(Connection, "org.freedesktop.DBus", "/org/freedesktop/DBus");
+ _disposables.Add(await dbus.WatchNameOwnerChangedAsync(OnNameChange));
+ var nameOwner = await dbus.GetNameOwnerAsync(name);
+ OnNameChange(null, (name, null, nameOwner));
+ }
}
protected abstract Task Connect(string name);
@@ -67,9 +73,12 @@ namespace Avalonia.FreeDesktop.DBusIme
protected string GetAppName() =>
Application.Current?.Name ?? Assembly.GetEntryAssembly()?.GetName()?.Name ?? "Avalonia";
- private async void OnNameChange(ServiceOwnerChangedEventArgs args)
+ private async void OnNameChange(Exception? e, (string ServiceName, string? OldOwner, string? NewOwner) args)
{
- if (args.NewOwner != null && _currentName == null)
+ if (e is not null)
+ return;
+
+ if (args.NewOwner is not null && _currentName is null)
{
_onlineNamesQueue.Enqueue(args.ServiceName);
if (!_connecting)
@@ -89,10 +98,10 @@ namespace Avalonia.FreeDesktop.DBusIme
return;
}
}
- catch (Exception e)
+ catch (Exception ex)
{
Logger.TryGet(LogEventLevel.Error, "IME")
- ?.Log(this, "Unable to create IME input context:\n" + e);
+ ?.Log(this, "Unable to create IME input context:\n" + ex);
}
}
}
@@ -105,7 +114,7 @@ namespace Avalonia.FreeDesktop.DBusIme
}
// IME has crashed
- if (args.NewOwner == null && args.ServiceName == _currentName)
+ if (args.NewOwner is null && args.ServiceName == _currentName)
{
_currentName = null;
foreach (var s in _disposables)
@@ -116,11 +125,11 @@ namespace Avalonia.FreeDesktop.DBusIme
Reset();
// Watch again
- Watch();
+ _ = WatchAsync();
}
}
- protected virtual Task Disconnect()
+ protected virtual Task DisconnectAsync()
{
return Task.CompletedTask;
}
@@ -136,13 +145,13 @@ namespace Avalonia.FreeDesktop.DBusIme
_imeActive = null;
}
- async Task QueueOnError(Exception e)
+ private async Task QueueOnErrorAsync(Exception e)
{
Logger.TryGet(LogEventLevel.Error, "IME")
?.Log(this, "Error:\n" + e);
try
{
- await Disconnect();
+ await DisconnectAsync();
}
catch (Exception ex)
{
@@ -157,23 +166,16 @@ namespace Avalonia.FreeDesktop.DBusIme
protected void AddDisposable(IDisposable? d)
{
- if(d is { })
+ if (d is { })
_disposables.Add(d);
}
-
+
public void Dispose()
{
foreach(var d in _disposables)
d.Dispose();
_disposables.Clear();
- try
- {
- Disconnect().ContinueWith(_ => { });
- }
- catch
- {
- // fire and forget
- }
+ _ = DisconnectAsync();
_currentName = null;
}
@@ -182,13 +184,13 @@ namespace Avalonia.FreeDesktop.DBusIme
protected abstract Task ResetContextCore();
protected abstract Task HandleKeyCore(RawKeyEventArgs args, int keyVal, int keyCode);
- void UpdateActive()
+ private void UpdateActive()
{
_queue.Enqueue(async () =>
{
if(!IsConnected)
return;
-
+
var active = _windowActive && IsActive;
if (active != _imeActive)
{
@@ -204,7 +206,7 @@ namespace Avalonia.FreeDesktop.DBusIme
_windowActive = active;
UpdateActive();
}
-
+
void ITextInputMethodImpl.SetClient(ITextInputMethodClient? client)
{
_client = client;
@@ -227,7 +229,7 @@ namespace Avalonia.FreeDesktop.DBusIme
// Error, disconnect
catch (Exception e)
{
- await QueueOnError(e);
+ await QueueOnErrorAsync(e);
return false;
}
}
@@ -240,7 +242,7 @@ namespace Avalonia.FreeDesktop.DBusIme
}
protected void FireCommit(string s) => _onCommit?.Invoke(s);
-
+
private Action? _onForward;
event Action IX11InputMethodControl.ForwardKey
{
@@ -249,8 +251,8 @@ namespace Avalonia.FreeDesktop.DBusIme
}
protected void FireForward(X11InputMethodForwardedKey k) => _onForward?.Invoke(k);
-
- void UpdateCursorRect()
+
+ private void UpdateCursorRect()
{
_queue.Enqueue(async () =>
{
@@ -265,7 +267,7 @@ namespace Avalonia.FreeDesktop.DBusIme
}
});
}
-
+
void IX11InputMethodControl.UpdateWindowInfo(PixelPoint position, double scaling)
{
_windowPosition = position;
diff --git a/src/Avalonia.FreeDesktop/DBusIme/Fcitx/FcitxDBus.cs b/src/Avalonia.FreeDesktop/DBusIme/Fcitx/FcitxDBus.cs
deleted file mode 100644
index 06afacaa29..0000000000
--- a/src/Avalonia.FreeDesktop/DBusIme/Fcitx/FcitxDBus.cs
+++ /dev/null
@@ -1,69 +0,0 @@
-using System;
-using System.Collections.Generic;
-using System.Runtime.CompilerServices;
-using System.Threading.Tasks;
-using Tmds.DBus;
-
-[assembly: InternalsVisibleTo(Tmds.DBus.Connection.DynamicAssemblyName)]
-namespace Avalonia.FreeDesktop.DBusIme.Fcitx
-{
- [DBusInterface("org.fcitx.Fcitx.InputMethod")]
- interface IFcitxInputMethod : IDBusObject
- {
- Task<(int icid, bool enable, uint keyval1, uint state1, uint keyval2, uint state2)> CreateICv3Async(
- string Appname, int Pid);
- }
-
-
- [DBusInterface("org.fcitx.Fcitx.InputContext")]
- interface IFcitxInputContext : IDBusObject
- {
- Task EnableICAsync();
- Task CloseICAsync();
- Task FocusInAsync();
- Task FocusOutAsync();
- Task ResetAsync();
- Task MouseEventAsync(int X);
- Task SetCursorLocationAsync(int X, int Y);
- Task SetCursorRectAsync(int X, int Y, int W, int H);
- Task SetCapacityAsync(uint Caps);
- Task SetSurroundingTextAsync(string Text, uint Cursor, uint Anchor);
- Task SetSurroundingTextPositionAsync(uint Cursor, uint Anchor);
- Task DestroyICAsync();
- Task ProcessKeyEventAsync(uint Keyval, uint Keycode, uint State, int Type, uint Time);
- Task WatchEnableIMAsync(Action handler, Action? onError = null);
- Task WatchCloseIMAsync(Action handler, Action? onError = null);
- Task WatchCommitStringAsync(Action handler, Action? onError = null);
- Task WatchCurrentIMAsync(Action<(string name, string uniqueName, string langCode)> handler, Action? onError = null);
- Task WatchUpdatePreeditAsync(Action<(string str, int cursorpos)> handler, Action? onError = null);
- Task WatchUpdateFormattedPreeditAsync(Action<((string, int)[] str, int cursorpos)> handler, Action? onError = null);
- Task WatchUpdateClientSideUIAsync(Action<(string auxup, string auxdown, string preedit, string candidateword, string imname, int cursorpos)> handler, Action? onError = null);
- Task WatchForwardKeyAsync(Action<(uint keyval, uint state, int type)> handler, Action? onError = null);
- Task WatchDeleteSurroundingTextAsync(Action<(int offset, uint nchar)> handler, Action? onError = null);
- }
-
- [DBusInterface("org.fcitx.Fcitx.InputContext1")]
- interface IFcitxInputContext1 : IDBusObject
- {
- Task FocusInAsync();
- Task FocusOutAsync();
- Task ResetAsync();
- Task SetCursorRectAsync(int X, int Y, int W, int H);
- Task SetCapabilityAsync(ulong Caps);
- Task SetSurroundingTextAsync(string Text, uint Cursor, uint Anchor);
- Task SetSurroundingTextPositionAsync(uint Cursor, uint Anchor);
- Task DestroyICAsync();
- Task ProcessKeyEventAsync(uint Keyval, uint Keycode, uint State, bool Type, uint Time);
- Task WatchCommitStringAsync(Action