diff --git a/.editorconfig b/.editorconfig
index 3620896f34..a144ec8843 100644
--- a/.editorconfig
+++ b/.editorconfig
@@ -55,16 +55,17 @@ dotnet_naming_symbols.constant_fields.required_modifiers = const
dotnet_naming_style.pascal_case_style.capitalization = pascal_case
-# static fields should have s_ prefix
-dotnet_naming_rule.static_fields_should_have_prefix.severity = suggestion
-dotnet_naming_rule.static_fields_should_have_prefix.symbols = static_fields
-dotnet_naming_rule.static_fields_should_have_prefix.style = static_prefix_style
+# private static fields should have s_ prefix
+dotnet_naming_rule.private_static_fields_should_have_prefix.severity = suggestion
+dotnet_naming_rule.private_static_fields_should_have_prefix.symbols = private_static_fields
+dotnet_naming_rule.private_static_fields_should_have_prefix.style = private_static_prefix_style
-dotnet_naming_symbols.static_fields.applicable_kinds = field
-dotnet_naming_symbols.static_fields.required_modifiers = static
+dotnet_naming_symbols.private_static_fields.applicable_kinds = field
+dotnet_naming_symbols.private_static_fields.required_modifiers = static
+dotnet_naming_symbols.private_static_fields.applicable_accessibilities = private
-dotnet_naming_style.static_prefix_style.required_prefix = s_
-dotnet_naming_style.static_prefix_style.capitalization = camel_case
+dotnet_naming_style.private_static_prefix_style.required_prefix = s_
+dotnet_naming_style.private_static_prefix_style.capitalization = camel_case
# internal and private fields should be _camelCase
dotnet_naming_rule.camel_case_for_private_internal_fields.severity = suggestion
@@ -117,7 +118,7 @@ csharp_space_after_dot = false
csharp_space_after_keywords_in_control_flow_statements = true
csharp_space_after_semicolon_in_for_statement = true
csharp_space_around_binary_operators = before_and_after
-csharp_space_around_declaration_statements = do_not_ignore
+csharp_space_around_declaration_statements = false
csharp_space_before_colon_in_inheritance_clause = true
csharp_space_before_comma = false
csharp_space_before_dot = false
@@ -137,15 +138,22 @@ space_within_single_line_array_initializer_braces = true
#Net Analyzer
dotnet_analyzer_diagnostic.category-Performance.severity = none #error - Uncomment when all violations are fixed.
+# CS0649: Field 'field' is never assigned to, and will always have its default value 'value'
+dotnet_diagnostic.CS0649.severity = error
+
# CS1591: Missing XML comment for publicly visible type or member
dotnet_diagnostic.CS1591.severity = suggestion
# CS0162: Remove unreachable code
dotnet_diagnostic.CS0162.severity = error
+# CA1018: Mark attributes with AttributeUsageAttribute
+dotnet_diagnostic.CA1018.severity = error
# CA1304: Specify CultureInfo
dotnet_diagnostic.CA1304.severity = warning
# CA1802: Use literals where appropriate
dotnet_diagnostic.CA1802.severity = warning
+# CA1813: Avoid unsealed attributes
+dotnet_diagnostic.CA1813.severity = error
# CA1815: Override equals and operator equals on value types
dotnet_diagnostic.CA1815.severity = warning
# CA1820: Test for empty strings using string length
@@ -204,5 +212,5 @@ indent_size = 2
# Shell scripts
[*.sh]
end_of_line = lf
-[*.{cmd, bat}]
+[*.{cmd,bat}]
end_of_line = crlf
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 3fa8e969c8..4a7a329fc6 100644
--- a/Avalonia.Desktop.slnf
+++ b/Avalonia.Desktop.slnf
@@ -5,6 +5,7 @@
"packages\\Avalonia\\Avalonia.csproj",
"samples\\ControlCatalog.NetCore\\ControlCatalog.NetCore.csproj",
"samples\\ControlCatalog\\ControlCatalog.csproj",
+ "samples\\GpuInterop\\GpuInterop.csproj",
"samples\\IntegrationTestApp\\IntegrationTestApp.csproj",
"samples\\MiniMvvm\\MiniMvvm.csproj",
"samples\\SampleControls\\ControlSamples.csproj",
@@ -14,6 +15,7 @@
"src\\Avalonia.Build.Tasks\\Avalonia.Build.Tasks.csproj",
"src\\Avalonia.Controls.ColorPicker\\Avalonia.Controls.ColorPicker.csproj",
"src\\Avalonia.Controls.DataGrid\\Avalonia.Controls.DataGrid.csproj",
+ "src\\Avalonia.Controls.ItemsRepeater\\Avalonia.Controls.ItemsRepeater.csproj",
"src\\Avalonia.Controls\\Avalonia.Controls.csproj",
"src\\Avalonia.DesignerSupport\\Avalonia.DesignerSupport.csproj",
"src\\Avalonia.Desktop\\Avalonia.Desktop.csproj",
@@ -40,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",
@@ -58,4 +62,4 @@
"tests\\Avalonia.UnitTests\\Avalonia.UnitTests.csproj"
]
}
-}
\ No newline at end of file
+}
diff --git a/Avalonia.sln b/Avalonia.sln
index fc42a5d63b..56847bae31 100644
--- a/Avalonia.sln
+++ b/Avalonia.sln
@@ -231,6 +231,19 @@ 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("{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
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Avalonia.Controls.ItemsRepeater.UnitTests", "tests\Avalonia.Controls.ItemsRepeater.UnitTests\Avalonia.Controls.ItemsRepeater.UnitTests.csproj", "{F4E36AA8-814E-4704-BC07-291F70F45193}"
+EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@@ -542,6 +555,21 @@ 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.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
+ {EE0F0DD4-A70D-472B-BD5D-B7D32D0E9386}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {EE0F0DD4-A70D-472B-BD5D-B7D32D0E9386}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {EE0F0DD4-A70D-472B-BD5D-B7D32D0E9386}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {EE0F0DD4-A70D-472B-BD5D-B7D32D0E9386}.Release|Any CPU.Build.0 = Release|Any CPU
+ {F4E36AA8-814E-4704-BC07-291F70F45193}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {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
@@ -606,6 +634,9 @@ Global
{15B93A4C-1B46-43F6-B534-7B25B6E99932} = {9B9E3891-2366-4253-A952-D08BCEB71098}
{90B08091-9BBD-4362-B712-E9F2CC62B218} = {9B9E3891-2366-4253-A952-D08BCEB71098}
{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/Base.props b/build/Base.props
index 26f19e3abc..2d50a7eae0 100644
--- a/build/Base.props
+++ b/build/Base.props
@@ -1,6 +1,8 @@
-
+
+
+
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/build/HarfBuzzSharp.props b/build/HarfBuzzSharp.props
index 620ec58ff3..75d317be1a 100644
--- a/build/HarfBuzzSharp.props
+++ b/build/HarfBuzzSharp.props
@@ -1,7 +1,7 @@
-
-
-
+
+
+
diff --git a/build/ImageSharp.props b/build/ImageSharp.props
index 178c274ac9..66e6580070 100644
--- a/build/ImageSharp.props
+++ b/build/ImageSharp.props
@@ -1,5 +1,5 @@
-
+
diff --git a/build/Moq.props b/build/Moq.props
index 9e2fd1db5d..357f0c9a5f 100644
--- a/build/Moq.props
+++ b/build/Moq.props
@@ -1,5 +1,5 @@
-
+
diff --git a/build/SharedVersion.props b/build/SharedVersion.props
index eca3ba37b0..2849262591 100644
--- a/build/SharedVersion.props
+++ b/build/SharedVersion.props
@@ -3,6 +3,7 @@
Avalonia
11.0.999
+ Avalonia Team
Copyright 2022 © The AvaloniaUI Project
https://avaloniaui.net
https://github.com/AvaloniaUI/Avalonia/
diff --git a/build/SharpDX.props b/build/SharpDX.props
index 69aa817a01..ff521977fd 100644
--- a/build/SharpDX.props
+++ b/build/SharpDX.props
@@ -1,9 +1,14 @@
+
+ 4.0.1
+
-
-
-
-
-
+
+
+
+
+
+
+
diff --git a/build/SkiaSharp.props b/build/SkiaSharp.props
index 31619399f9..f45addaa2a 100644
--- a/build/SkiaSharp.props
+++ b/build/SkiaSharp.props
@@ -1,7 +1,7 @@
-
-
-
+
+
+
diff --git a/build/System.Memory.props b/build/System.Memory.props
index a413e18927..35a87a3a2f 100644
--- a/build/System.Memory.props
+++ b/build/System.Memory.props
@@ -1,5 +1,6 @@
-
+
+
diff --git a/build/XUnit.props b/build/XUnit.props
index 17ead91aa3..3c89c8b52b 100644
--- a/build/XUnit.props
+++ b/build/XUnit.props
@@ -1,13 +1,12 @@
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
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 f345043f61..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];
@@ -66,9 +53,7 @@ HRESULT WindowImpl::Show(bool activate, bool isDialog) {
_isModal = isDialog;
WindowBaseImpl::Show(activate, isDialog);
-
- HideOrShowTrafficLights();
-
+ GetWindowState(&_actualWindowState);
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/BindingDemo/App.xaml b/samples/BindingDemo/App.xaml
index 5a8e65ed22..84f54293ef 100644
--- a/samples/BindingDemo/App.xaml
+++ b/samples/BindingDemo/App.xaml
@@ -2,13 +2,6 @@
xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
x:Class="BindingDemo.App">
-
-
-
-
-
-
-
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/ControlCatalog.Browser.Blazor.csproj b/samples/ControlCatalog.Browser.Blazor/ControlCatalog.Browser.Blazor.csproj
index d0fb614840..733a4b7194 100644
--- a/samples/ControlCatalog.Browser.Blazor/ControlCatalog.Browser.Blazor.csproj
+++ b/samples/ControlCatalog.Browser.Blazor/ControlCatalog.Browser.Blazor.csproj
@@ -9,8 +9,8 @@
-
-
+
+
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.NetCore/ControlCatalog.NetCore.csproj b/samples/ControlCatalog.NetCore/ControlCatalog.NetCore.csproj
index e4c83dca49..e465e9caf3 100644
--- a/samples/ControlCatalog.NetCore/ControlCatalog.NetCore.csproj
+++ b/samples/ControlCatalog.NetCore/ControlCatalog.NetCore.csproj
@@ -31,7 +31,6 @@
-
diff --git a/samples/ControlCatalog.NetCore/Program.cs b/samples/ControlCatalog.NetCore/Program.cs
index d5e5cb14dc..e55f003133 100644
--- a/samples/ControlCatalog.NetCore/Program.cs
+++ b/samples/ControlCatalog.NetCore/Program.cs
@@ -55,8 +55,7 @@ namespace ControlCatalog.NetCore
return builder
.UseHeadless(new AvaloniaHeadlessPlatformOptions
{
- UseHeadlessDrawing = true,
- UseCompositor = true
+ UseHeadlessDrawing = true
})
.AfterSetup(_ =>
{
diff --git a/samples/ControlCatalog/App.xaml b/samples/ControlCatalog/App.xaml
index 8f32fa01dd..3b847adcbb 100644
--- a/samples/ControlCatalog/App.xaml
+++ b/samples/ControlCatalog/App.xaml
@@ -6,18 +6,34 @@
x:Class="ControlCatalog.App">
+
+
+
+
+ #33000000
+ #99000000
+ #FFE6E6E6
+ #FF000000
+
+
+ #33FFFFFF
+ #99FFFFFF
+ #FF1F1F1F
+ #FFFFFFFF
+
+
+ #FF0078D7
+ #FF005A9E
+
+
-
-
-
-
diff --git a/samples/ControlCatalog/App.xaml.cs b/samples/ControlCatalog/App.xaml.cs
index 6c99eb5289..d71d51f068 100644
--- a/samples/ControlCatalog/App.xaml.cs
+++ b/samples/ControlCatalog/App.xaml.cs
@@ -16,7 +16,6 @@ namespace ControlCatalog
private readonly Styles _themeStylesContainer = new();
private FluentTheme? _fluentTheme;
private SimpleTheme? _simpleTheme;
- private IResourceDictionary? _fluentBaseLightColors, _fluentBaseDarkColors;
private IStyle? _colorPickerFluent, _colorPickerSimple;
private IStyle? _dataGridFluent, _dataGridSimple;
@@ -33,16 +32,12 @@ namespace ControlCatalog
_fluentTheme = new FluentTheme();
_simpleTheme = new SimpleTheme();
- _simpleTheme.Resources.MergedDictionaries.Add((IResourceDictionary)Resources["FluentAccentColors"]!);
- _simpleTheme.Resources.MergedDictionaries.Add((IResourceDictionary)Resources["FluentBaseColors"]!);
_colorPickerFluent = (IStyle)Resources["ColorPickerFluent"]!;
_colorPickerSimple = (IStyle)Resources["ColorPickerSimple"]!;
_dataGridFluent = (IStyle)Resources["DataGridFluent"]!;
_dataGridSimple = (IStyle)Resources["DataGridSimple"]!;
- _fluentBaseLightColors = (IResourceDictionary)Resources["FluentBaseLightColors"]!;
- _fluentBaseDarkColors = (IResourceDictionary)Resources["FluentBaseDarkColors"]!;
- SetThemeVariant(CatalogTheme.FluentLight);
+ SetCatalogThemes(CatalogTheme.Fluent);
}
public override void OnFrameworkInitializationCompleted()
@@ -61,19 +56,12 @@ namespace ControlCatalog
private CatalogTheme _prevTheme;
public static CatalogTheme CurrentTheme => ((App)Current!)._prevTheme;
- public static void SetThemeVariant(CatalogTheme theme)
+ public static void SetCatalogThemes(CatalogTheme theme)
{
var app = (App)Current!;
var prevTheme = app._prevTheme;
app._prevTheme = theme;
- var shouldReopenWindow = theme switch
- {
- CatalogTheme.FluentLight => prevTheme is CatalogTheme.SimpleDark or CatalogTheme.SimpleLight,
- CatalogTheme.FluentDark => prevTheme is CatalogTheme.SimpleDark or CatalogTheme.SimpleLight,
- CatalogTheme.SimpleLight => prevTheme is CatalogTheme.FluentDark or CatalogTheme.FluentLight,
- CatalogTheme.SimpleDark => prevTheme is CatalogTheme.FluentDark or CatalogTheme.FluentLight,
- _ => throw new ArgumentOutOfRangeException(nameof(theme), theme, null)
- };
+ var shouldReopenWindow = prevTheme != theme;
if (app._themeStylesContainer.Count == 0)
{
@@ -81,36 +69,16 @@ namespace ControlCatalog
app._themeStylesContainer.Add(new Style());
app._themeStylesContainer.Add(new Style());
}
-
- if (theme == CatalogTheme.FluentLight)
- {
- app._fluentTheme!.Mode = FluentThemeMode.Light;
- app._themeStylesContainer[0] = app._fluentTheme;
- app._themeStylesContainer[1] = app._colorPickerFluent!;
- app._themeStylesContainer[2] = app._dataGridFluent!;
- }
- else if (theme == CatalogTheme.FluentDark)
+
+ if (theme == CatalogTheme.Fluent)
{
- app._fluentTheme!.Mode = FluentThemeMode.Dark;
- app._themeStylesContainer[0] = app._fluentTheme;
+ app._themeStylesContainer[0] = app._fluentTheme!;
app._themeStylesContainer[1] = app._colorPickerFluent!;
app._themeStylesContainer[2] = app._dataGridFluent!;
}
- else if (theme == CatalogTheme.SimpleLight)
- {
- app._simpleTheme!.Mode = SimpleThemeMode.Light;
- app._simpleTheme.Resources.MergedDictionaries.Remove(app._fluentBaseDarkColors!);
- app._simpleTheme.Resources.MergedDictionaries.Add(app._fluentBaseLightColors!);
- app._themeStylesContainer[0] = app._simpleTheme;
- app._themeStylesContainer[1] = app._colorPickerSimple!;
- app._themeStylesContainer[2] = app._dataGridSimple!;
- }
- else if (theme == CatalogTheme.SimpleDark)
+ else if (theme == CatalogTheme.Simple)
{
- app._simpleTheme!.Mode = SimpleThemeMode.Dark;
- app._simpleTheme.Resources.MergedDictionaries.Remove(app._fluentBaseLightColors!);
- app._simpleTheme.Resources.MergedDictionaries.Add(app._fluentBaseDarkColors!);
- app._themeStylesContainer[0] = app._simpleTheme;
+ app._themeStylesContainer[0] = app._simpleTheme!;
app._themeStylesContainer[1] = app._colorPickerSimple!;
app._themeStylesContainer[2] = app._dataGridSimple!;
}
diff --git a/samples/ControlCatalog/ControlCatalog.csproj b/samples/ControlCatalog/ControlCatalog.csproj
index 18f0dd16ba..c223bfe1a9 100644
--- a/samples/ControlCatalog/ControlCatalog.csproj
+++ b/samples/ControlCatalog/ControlCatalog.csproj
@@ -26,6 +26,7 @@
+
diff --git a/samples/ControlCatalog/MainView.xaml b/samples/ControlCatalog/MainView.xaml
index 166b98436e..3681298a72 100644
--- a/samples/ControlCatalog/MainView.xaml
+++ b/samples/ControlCatalog/MainView.xaml
@@ -14,8 +14,8 @@
-
-
+
+
@@ -144,9 +144,12 @@
-
+
+
+
+
@@ -165,6 +168,9 @@
+
+
+
@@ -198,14 +204,22 @@
Full
+
+
+ Default
+ Light
+ Dark
+
+
- FluentLight
- FluentDark
- SimpleLight
- SimpleDark
+ Fluent
+ Simple
PlatformThemeVariant.Light,
- CatalogTheme.FluentDark => PlatformThemeVariant.Dark,
- CatalogTheme.SimpleLight => PlatformThemeVariant.Light,
- CatalogTheme.SimpleDark => PlatformThemeVariant.Dark,
- _ => throw new ArgumentOutOfRangeException()
- });
+ App.SetCatalogThemes(theme);
+ }
+ };
+ var themeVariants = this.Get("ThemeVariants");
+ themeVariants.SelectedItem = Application.Current!.RequestedThemeVariant;
+ themeVariants.SelectionChanged += (sender, e) =>
+ {
+ if (themeVariants.SelectedItem is ThemeVariant themeVariant)
+ {
+ Application.Current!.RequestedThemeVariant = themeVariant;
}
};
@@ -60,7 +61,7 @@ namespace ControlCatalog
{
if (flowDirections.SelectedItem is FlowDirection flowDirection)
{
- this.FlowDirection = flowDirection;
+ TopLevel.GetTopLevel(this).FlowDirection = flowDirection;
}
};
@@ -118,25 +119,13 @@ namespace ControlCatalog
private void PlatformSettingsOnColorValuesChanged(object? sender, PlatformColorValues e)
{
- var themes = this.Get("Themes");
- var currentTheme = (CatalogTheme?)themes.SelectedItem ?? CatalogTheme.FluentLight;
- var newTheme = (currentTheme, e.ThemeVariant) switch
- {
- (CatalogTheme.FluentDark, PlatformThemeVariant.Light) => CatalogTheme.FluentLight,
- (CatalogTheme.FluentLight, PlatformThemeVariant.Dark) => CatalogTheme.FluentDark,
- (CatalogTheme.SimpleDark, PlatformThemeVariant.Light) => CatalogTheme.SimpleLight,
- (CatalogTheme.SimpleLight, PlatformThemeVariant.Dark) => CatalogTheme.SimpleDark,
- _ => currentTheme
- };
- themes.SelectedItem = newTheme;
-
Application.Current!.Resources["SystemAccentColor"] = e.AccentColor1;
Application.Current.Resources["SystemAccentColorDark1"] = ChangeColorLuminosity(e.AccentColor1, -0.3);
Application.Current.Resources["SystemAccentColorDark2"] = ChangeColorLuminosity(e.AccentColor1, -0.5);
Application.Current.Resources["SystemAccentColorDark3"] = ChangeColorLuminosity(e.AccentColor1, -0.7);
- Application.Current.Resources["SystemAccentColorLight1"] = ChangeColorLuminosity(e.AccentColor1, -0.3);
- Application.Current.Resources["SystemAccentColorLight2"] = ChangeColorLuminosity(e.AccentColor1, -0.5);
- Application.Current.Resources["SystemAccentColorLight3"] = ChangeColorLuminosity(e.AccentColor1, -0.7);
+ Application.Current.Resources["SystemAccentColorLight1"] = ChangeColorLuminosity(e.AccentColor1, 0.3);
+ Application.Current.Resources["SystemAccentColorLight2"] = ChangeColorLuminosity(e.AccentColor1, 0.5);
+ Application.Current.Resources["SystemAccentColorLight3"] = ChangeColorLuminosity(e.AccentColor1, 0.7);
static Color ChangeColorLuminosity(Color color, double luminosityFactor)
{
diff --git a/samples/ControlCatalog/Models/CatalogTheme.cs b/samples/ControlCatalog/Models/CatalogTheme.cs
index 37224ed26e..79b3182d20 100644
--- a/samples/ControlCatalog/Models/CatalogTheme.cs
+++ b/samples/ControlCatalog/Models/CatalogTheme.cs
@@ -2,9 +2,7 @@
{
public enum CatalogTheme
{
- FluentLight,
- FluentDark,
- SimpleLight,
- SimpleDark
+ Fluent,
+ Simple
}
}
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/DataGridPage.xaml b/samples/ControlCatalog/Pages/DataGridPage.xaml
index 4c3c211ca5..c39e9f0a81 100644
--- a/samples/ControlCatalog/Pages/DataGridPage.xaml
+++ b/samples/ControlCatalog/Pages/DataGridPage.xaml
@@ -1,7 +1,9 @@
+ xmlns:pages="clr-namespace:ControlCatalog.Pages"
+ x:Class="ControlCatalog.Pages.DataGridPage"
+ x:DataType="pages:DataGridPage">
@@ -33,7 +35,7 @@
-
+
-
+
+
-
-
-
-
+
+
+
+
-
+
-
+
diff --git a/samples/ControlCatalog/Pages/DataGridPage.xaml.cs b/samples/ControlCatalog/Pages/DataGridPage.xaml.cs
index 3565d113bc..b0c3e3a553 100644
--- a/samples/ControlCatalog/Pages/DataGridPage.xaml.cs
+++ b/samples/ControlCatalog/Pages/DataGridPage.xaml.cs
@@ -1,5 +1,6 @@
using System.Collections;
using System.Collections.Generic;
+using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Linq;
using Avalonia.Controls;
@@ -48,20 +49,22 @@ namespace ControlCatalog.Pages
var dg3 = this.Get("dataGridEdit");
dg3.IsReadOnly = false;
- var items = new List
+ var list = new ObservableCollection
{
new Person { FirstName = "John", LastName = "Doe" , Age = 30},
new Person { FirstName = "Elizabeth", LastName = "Thomas", IsBanned = true , Age = 40 },
new Person { FirstName = "Zack", LastName = "Ward" , Age = 50 }
};
- var collectionView3 = new DataGridCollectionView(items);
-
- dg3.Items = collectionView3;
+ DataGrid3Source = list;
var addButton = this.Get
diff --git a/samples/ControlCatalog/Pages/ScrollViewerPage.xaml.cs b/samples/ControlCatalog/Pages/ScrollViewerPage.xaml.cs
index dcd7a88a56..a097f1f951 100644
--- a/samples/ControlCatalog/Pages/ScrollViewerPage.xaml.cs
+++ b/samples/ControlCatalog/Pages/ScrollViewerPage.xaml.cs
@@ -9,6 +9,7 @@ namespace ControlCatalog.Pages
public class ScrollViewerPageViewModel : ViewModelBase
{
private bool _allowAutoHide;
+ private bool _enableInertia;
private ScrollBarVisibility _horizontalScrollVisibility;
private ScrollBarVisibility _verticalScrollVisibility;
@@ -25,6 +26,7 @@ namespace ControlCatalog.Pages
HorizontalScrollVisibility = ScrollBarVisibility.Auto;
VerticalScrollVisibility = ScrollBarVisibility.Auto;
AllowAutoHide = true;
+ EnableInertia = true;
}
public bool AllowAutoHide
@@ -33,6 +35,12 @@ namespace ControlCatalog.Pages
set => this.RaiseAndSetIfChanged(ref _allowAutoHide, value);
}
+ public bool EnableInertia
+ {
+ get => _enableInertia;
+ set => this.RaiseAndSetIfChanged(ref _enableInertia, value);
+ }
+
public ScrollBarVisibility HorizontalScrollVisibility
{
get => _horizontalScrollVisibility;
diff --git a/samples/ControlCatalog/Pages/SplitViewPage.xaml b/samples/ControlCatalog/Pages/SplitViewPage.xaml
index 61bfb490b8..2edd895349 100644
--- a/samples/ControlCatalog/Pages/SplitViewPage.xaml
+++ b/samples/ControlCatalog/Pages/SplitViewPage.xaml
@@ -32,7 +32,7 @@
- SystemControlBackgroundChromeMediumLowBrush
+ CatalogChromeMediumColor
Red
Blue
Green
@@ -48,7 +48,7 @@
-
@@ -89,11 +89,11 @@
-
-
-
-
-
+
+
+
+
+
diff --git a/samples/ControlCatalog/Pages/TextBlockPage.xaml b/samples/ControlCatalog/Pages/TextBlockPage.xaml
index 6bb428e2c7..6511e2136a 100644
--- a/samples/ControlCatalog/Pages/TextBlockPage.xaml
+++ b/samples/ControlCatalog/Pages/TextBlockPage.xaml
@@ -9,7 +9,7 @@
@@ -101,7 +115,7 @@
VerticalAlignment="Center"
Background="{DynamicResource TabItemHeaderSelectedPipeFill}"
IsVisible="False"
- CornerRadius="{DynamicResource ControlCornerRadius}"/>
+ CornerRadius="4"/>
@@ -136,18 +150,18 @@
-
-
-
+
+
+
diff --git a/samples/Sandbox/App.axaml b/samples/Sandbox/App.axaml
index f601f9f78f..cf3e5e445a 100644
--- a/samples/Sandbox/App.axaml
+++ b/samples/Sandbox/App.axaml
@@ -3,6 +3,6 @@
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
x:Class="Sandbox.App">
-
+
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/AndroidPlatform.cs b/src/Android/Avalonia.Android/AndroidPlatform.cs
index c73fd92423..daecb58a60 100644
--- a/src/Android/Avalonia.Android/AndroidPlatform.cs
+++ b/src/Android/Avalonia.Android/AndroidPlatform.cs
@@ -32,7 +32,6 @@ namespace Avalonia.Android
public static AndroidPlatformOptions Options { get; private set; }
internal static Compositor Compositor { get; private set; }
- internal static PlatformRenderInterfaceContextManager RenderInterface { get; private set; }
public static void Initialize()
{
@@ -55,16 +54,11 @@ namespace Avalonia.Android
EglPlatformGraphics.TryInitialize();
}
- if (Options.UseCompositor)
- {
- Compositor = new Compositor(
- AvaloniaLocator.Current.GetRequiredService(),
- AvaloniaLocator.Current.GetService());
- }
- else
- RenderInterface =
- new PlatformRenderInterfaceContextManager(AvaloniaLocator.Current
- .GetService());
+ Compositor = new Compositor(
+ AvaloniaLocator.Current.GetRequiredService(),
+ AvaloniaLocator.Current.GetService());
+
+
}
}
@@ -72,6 +66,5 @@ namespace Avalonia.Android
{
public bool UseDeferredRendering { get; set; } = false;
public bool UseGpu { get; set; } = true;
- public bool UseCompositor { get; set; } = true;
}
}
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/AvaloniaMainActivity.cs b/src/Android/Avalonia.Android/AvaloniaMainActivity.cs
index c06f5c74ec..247008c503 100644
--- a/src/Android/Avalonia.Android/AvaloniaMainActivity.cs
+++ b/src/Android/Avalonia.Android/AvaloniaMainActivity.cs
@@ -1,6 +1,8 @@
using System;
using Android.App;
using Android.Content;
+using Android.Content.PM;
+using Android.Content.Res;
using Android.OS;
using Android.Runtime;
using Android.Views;
@@ -13,6 +15,7 @@ namespace Avalonia.Android
internal static object ViewContent;
public Action ActivityResult { get; set; }
+ public Action RequestPermissionsResult { get; set; }
internal AvaloniaView View;
private GlobalLayoutListener _listener;
@@ -82,6 +85,13 @@ namespace Avalonia.Android
ActivityResult?.Invoke(requestCode, resultCode, data);
}
+ public override void OnRequestPermissionsResult(int requestCode, string[] permissions, Permission[] grantResults)
+ {
+ base.OnRequestPermissionsResult(requestCode, permissions, grantResults);
+
+ RequestPermissionsResult?.Invoke(requestCode, permissions, grantResults);
+ }
+
class GlobalLayoutListener : Java.Lang.Object, ViewTreeObserver.IOnGlobalLayoutListener
{
private AvaloniaView _view;
diff --git a/src/Android/Avalonia.Android/IActivityResultHandler.cs b/src/Android/Avalonia.Android/IActivityResultHandler.cs
index 14094ee185..40a8b5cbcf 100644
--- a/src/Android/Avalonia.Android/IActivityResultHandler.cs
+++ b/src/Android/Avalonia.Android/IActivityResultHandler.cs
@@ -1,11 +1,14 @@
using System;
using Android.App;
using Android.Content;
+using Android.Content.PM;
namespace Avalonia.Android
{
public interface IActivityResultHandler
{
public Action ActivityResult { get; set; }
+
+ public Action RequestPermissionsResult { get; set; }
}
}
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/AndroidSystemNavigationManager.cs b/src/Android/Avalonia.Android/Platform/AndroidSystemNavigationManager.cs
index bb9dc66f5a..4ed91c248d 100644
--- a/src/Android/Avalonia.Android/Platform/AndroidSystemNavigationManager.cs
+++ b/src/Android/Avalonia.Android/Platform/AndroidSystemNavigationManager.cs
@@ -4,11 +4,11 @@ using Avalonia.Platform;
namespace Avalonia.Android.Platform
{
- internal class AndroidSystemNavigationManager : ISystemNavigationManager
+ internal class AndroidSystemNavigationManagerImpl : ISystemNavigationManagerImpl
{
public event EventHandler BackRequested;
- public AndroidSystemNavigationManager(IActivityNavigationService? navigationService)
+ public AndroidSystemNavigationManagerImpl(IActivityNavigationService? navigationService)
{
if(navigationService != null)
{
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/PlatformSupport.cs b/src/Android/Avalonia.Android/Platform/PlatformSupport.cs
new file mode 100644
index 0000000000..9877f48664
--- /dev/null
+++ b/src/Android/Avalonia.Android/Platform/PlatformSupport.cs
@@ -0,0 +1,52 @@
+using System;
+using System.Linq;
+using System.Threading.Tasks;
+using Android.App;
+using Android.Content;
+using Android.Content.PM;
+
+namespace Avalonia.Android.Platform;
+
+internal static class PlatformSupport
+{
+ private static int s_lastRequestCode = 20000;
+
+ public static int GetNextRequestCode() => s_lastRequestCode++;
+
+ public static async Task CheckPermission(this Activity activity, string permission)
+ {
+ if (activity is not IActivityResultHandler mainActivity)
+ {
+ throw new InvalidOperationException("Main activity must implement IActivityResultHandler interface.");
+ }
+
+ if (!OperatingSystem.IsAndroidVersionAtLeast(23))
+ {
+ return true;
+ }
+
+ if (activity.CheckSelfPermission(permission) == Permission.Granted)
+ {
+ return true;
+ }
+
+ var currentRequestCode = GetNextRequestCode();
+ var tcs = new TaskCompletionSource();
+ mainActivity.RequestPermissionsResult += RequestPermissionsResult;
+ activity.RequestPermissions(new [] { permission }, currentRequestCode);
+
+ return await tcs.Task;
+
+ void RequestPermissionsResult(int requestCode, string[] arg2, Permission[] arg3)
+ {
+ if (currentRequestCode != requestCode)
+ {
+ return;
+ }
+
+ mainActivity.RequestPermissionsResult -= RequestPermissionsResult;
+
+ _ = tcs.TrySetResult(arg3.All(p => p == Permission.Granted));
+ }
+ }
+}
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 0e452b0bdd..e511ed9a8b 100644
--- a/src/Android/Avalonia.Android/Platform/SkiaPlatform/TopLevelImpl.cs
+++ b/src/Android/Avalonia.Android/Platform/SkiaPlatform/TopLevelImpl.cs
@@ -28,12 +28,11 @@ 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
{
- class TopLevelImpl : IAndroidView, ITopLevelImpl, EglGlPlatformSurface.IEglWindowGlPlatformSurfaceInfo,
- ITopLevelImplWithTextInputMethod, ITopLevelImplWithNativeControlHost, ITopLevelImplWithStorageProvider,
- ITopLevelWithSystemNavigationManager
+ class TopLevelImpl : IAndroidView, ITopLevelImpl, EglGlPlatformSurface.IEglWindowGlPlatformSurfaceInfo
{
private readonly IGlPlatformSurface _gl;
private readonly IFramebufferPlatformSurface _framebuffer;
@@ -41,6 +40,9 @@ namespace Avalonia.Android.Platform.SkiaPlatform
private readonly AndroidKeyboardEventsHelper _keyboardHelper;
private readonly AndroidMotionEventsHelper _pointerHelper;
private readonly AndroidInputMethod _textInputMethod;
+ private readonly INativeControlHostImpl _nativeControlHost;
+ private readonly IStorageProvider _storageProvider;
+ private readonly ISystemNavigationManagerImpl _systemNavigationManager;
private ViewImpl _view;
public TopLevelImpl(AvaloniaView avaloniaView, bool placeOnTop = false)
@@ -57,10 +59,10 @@ namespace Avalonia.Android.Platform.SkiaPlatform
MaxClientSize = new PixelSize(_view.Resources.DisplayMetrics.WidthPixels,
_view.Resources.DisplayMetrics.HeightPixels).ToSize(RenderScaling);
- NativeControlHost = new AndroidNativeControlHostImpl(avaloniaView);
- StorageProvider = new AndroidStorageProvider((Activity)avaloniaView.Context);
+ _nativeControlHost = new AndroidNativeControlHostImpl(avaloniaView);
+ _storageProvider = new AndroidStorageProvider((Activity)avaloniaView.Context);
- SystemNavigationManager = new AndroidSystemNavigationManager(avaloniaView.Context as IActivityNavigationService);
+ _systemNavigationManager = new AndroidSystemNavigationManagerImpl(avaloniaView.Context as IActivityNavigationService);
}
public virtual Point GetAvaloniaPointFromEvent(MotionEvent e, int pointerIndex) =>
@@ -109,16 +111,7 @@ namespace Avalonia.Android.Platform.SkiaPlatform
public IEnumerable