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..1d182b1357 100644
--- a/Avalonia.Desktop.slnf
+++ b/Avalonia.Desktop.slnf
@@ -8,9 +8,9 @@
"samples\\GpuInterop\\GpuInterop.csproj",
"samples\\IntegrationTestApp\\IntegrationTestApp.csproj",
"samples\\MiniMvvm\\MiniMvvm.csproj",
+ "samples\\ReactiveUIDemo\\ReactiveUIDemo.csproj",
"samples\\SampleControls\\ControlSamples.csproj",
"samples\\Sandbox\\Sandbox.csproj",
- "samples\\ReactiveUIDemo\\ReactiveUIDemo.csproj",
"src\\Avalonia.Base\\Avalonia.Base.csproj",
"src\\Avalonia.Build.Tasks\\Avalonia.Build.Tasks.csproj",
"src\\Avalonia.Controls.ColorPicker\\Avalonia.Controls.ColorPicker.csproj",
@@ -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",
diff --git a/Avalonia.sln b/Avalonia.sln
index 525e01c891..1e8ee85ffb 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
@@ -560,6 +567,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 +637,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/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/TextInput/ITextEditable.cs b/src/Avalonia.Base/Input/TextInput/ITextEditable.cs
new file mode 100644
index 0000000000..f9f5ec8d40
--- /dev/null
+++ b/src/Avalonia.Base/Input/TextInput/ITextEditable.cs
@@ -0,0 +1,23 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using Avalonia.Metadata;
+
+namespace Avalonia.Input.TextInput
+{
+ [NotClientImplementable]
+ public interface ITextEditable
+ {
+ event EventHandler TextChanged;
+ event EventHandler SelectionChanged;
+ event EventHandler CompositionChanged;
+ int SelectionStart { get; set; }
+ int SelectionEnd { get; set; }
+ int CompositionStart { get; }
+ int CompositionEnd { get; }
+
+ string? Text { get; set; }
+ }
+}
diff --git a/src/Avalonia.Base/Input/TextInput/ITextInputMethodClient.cs b/src/Avalonia.Base/Input/TextInput/ITextInputMethodClient.cs
index 531cf3c704..2cdcd33626 100644
--- a/src/Avalonia.Base/Input/TextInput/ITextInputMethodClient.cs
+++ b/src/Avalonia.Base/Input/TextInput/ITextInputMethodClient.cs
@@ -1,4 +1,5 @@
using System;
+using Avalonia.Media.TextFormatting;
using Avalonia.VisualTree;
namespace Avalonia.Input.TextInput
@@ -30,6 +31,11 @@ namespace Avalonia.Input.TextInput
///
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 775b8adddd..4a273b0291 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/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/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/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/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/Server/ServerCompositionVisual.cs b/src/Avalonia.Base/Rendering/Composition/Server/ServerCompositionVisual.cs
index e33dc999dc..98be861afa 100644
--- a/src/Avalonia.Base/Rendering/Composition/Server/ServerCompositionVisual.cs
+++ b/src/Avalonia.Base/Rendering/Composition/Server/ServerCompositionVisual.cs
@@ -48,7 +48,8 @@ namespace Avalonia.Rendering.Composition.Server
{
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);
@@ -74,7 +75,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/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/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 5bf022cd51..5b8dac2f53 100644
--- a/src/Avalonia.Base/StyledElement.cs
+++ b/src/Avalonia.Base/StyledElement.cs
@@ -41,7 +41,11 @@ namespace Avalonia
public static readonly StyledProperty
/// 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 2f02a48d55..ce12d5f2bf 100644
--- a/src/Avalonia.Controls/ItemsControl.cs
+++ b/src/Avalonia.Controls/ItemsControl.cs
@@ -559,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);
///
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/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 79719912ea..611d57a980 100644
--- a/src/Avalonia.Controls/Primitives/AdornerLayer.cs
+++ b/src/Avalonia.Controls/Primitives/AdornerLayer.cs
@@ -279,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/SelectingItemsControl.cs b/src/Avalonia.Controls/Primitives/SelectingItemsControl.cs
index 065c4ff2e5..2ee32b0dda 100644
--- a/src/Avalonia.Controls/Primitives/SelectingItemsControl.cs
+++ b/src/Avalonia.Controls/Primitives/SelectingItemsControl.cs
@@ -345,10 +345,7 @@ namespace Avalonia.Controls.Primitives
if (_oldSelectedItems != SelectedItems)
{
- RaisePropertyChanged(
- SelectedItemsProperty,
- new Optional(_oldSelectedItems),
- new BindingValue(SelectedItems));
+ RaisePropertyChanged(SelectedItemsProperty, _oldSelectedItems, SelectedItems);
_oldSelectedItems = SelectedItems;
}
}
@@ -909,10 +906,7 @@ 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))
diff --git a/src/Avalonia.Controls/RelativePanel.AttachedProperties.cs b/src/Avalonia.Controls/RelativePanel.AttachedProperties.cs
index 18cf96ddca..d2b91def7d 100644
--- a/src/Avalonia.Controls/RelativePanel.AttachedProperties.cs
+++ b/src/Avalonia.Controls/RelativePanel.AttachedProperties.cs
@@ -1,37 +1,30 @@
using Avalonia.Layout;
+using Avalonia.Threading;
namespace Avalonia.Controls
{
public partial class RelativePanel
{
- private static void OnAlignPropertiesChanged(AvaloniaObject d, AvaloniaPropertyChangedEventArgs e)
- {
- if (d is Layoutable layoutable && layoutable.Parent is Layoutable layoutableParent)
- {
- layoutableParent.InvalidateArrange();
- }
- }
static RelativePanel()
{
ClipToBoundsProperty.OverrideDefaultValue(true);
- AboveProperty.Changed.AddClassHandler(OnAlignPropertiesChanged);
- AlignBottomWithPanelProperty.Changed.AddClassHandler(OnAlignPropertiesChanged);
- AlignBottomWithProperty.Changed.AddClassHandler(OnAlignPropertiesChanged);
- AlignHorizontalCenterWithPanelProperty.Changed.AddClassHandler(OnAlignPropertiesChanged);
- AlignHorizontalCenterWithProperty.Changed.AddClassHandler(OnAlignPropertiesChanged);
- AlignLeftWithPanelProperty.Changed.AddClassHandler(OnAlignPropertiesChanged);
- AlignLeftWithProperty.Changed.AddClassHandler(OnAlignPropertiesChanged);
- AlignRightWithPanelProperty.Changed.AddClassHandler(OnAlignPropertiesChanged);
- AlignRightWithProperty.Changed.AddClassHandler(OnAlignPropertiesChanged);
- AlignTopWithPanelProperty.Changed.AddClassHandler(OnAlignPropertiesChanged);
- AlignTopWithProperty.Changed.AddClassHandler(OnAlignPropertiesChanged);
- AlignVerticalCenterWithPanelProperty.Changed.AddClassHandler(OnAlignPropertiesChanged);
- AlignVerticalCenterWithProperty.Changed.AddClassHandler(OnAlignPropertiesChanged);
- BelowProperty.Changed.AddClassHandler(OnAlignPropertiesChanged);
- LeftOfProperty.Changed.AddClassHandler(OnAlignPropertiesChanged);
- RightOfProperty.Changed.AddClassHandler(OnAlignPropertiesChanged);
+ AffectsParentArrange(
+ AlignLeftWithPanelProperty, AlignLeftWithProperty, LeftOfProperty,
+ AlignRightWithPanelProperty, AlignRightWithProperty, RightOfProperty,
+ AlignTopWithPanelProperty, AlignTopWithProperty, AboveProperty,
+ AlignBottomWithPanelProperty, AlignBottomWithProperty, BelowProperty,
+ AlignHorizontalCenterWithPanelProperty, AlignHorizontalCenterWithProperty,
+ AlignVerticalCenterWithPanelProperty, AlignVerticalCenterWithProperty);
+
+ AffectsParentMeasure(
+ AlignLeftWithPanelProperty, AlignLeftWithProperty, LeftOfProperty,
+ AlignRightWithPanelProperty, AlignRightWithProperty, RightOfProperty,
+ AlignTopWithPanelProperty, AlignTopWithProperty, AboveProperty,
+ AlignBottomWithPanelProperty, AlignBottomWithProperty, BelowProperty,
+ AlignHorizontalCenterWithPanelProperty, AlignHorizontalCenterWithProperty,
+ AlignVerticalCenterWithPanelProperty, AlignVerticalCenterWithProperty);
}
///
diff --git a/src/Avalonia.Controls/Remote/RemoteWidget.cs b/src/Avalonia.Controls/Remote/RemoteWidget.cs
index 5cefb0d89f..b9854ab837 100644
--- a/src/Avalonia.Controls/Remote/RemoteWidget.cs
+++ b/src/Avalonia.Controls/Remote/RemoteWidget.cs
@@ -69,7 +69,7 @@ namespace Avalonia.Controls.Remote
base.ArrangeCore(finalRect);
}
- public override void Render(DrawingContext context)
+ public sealed override void Render(DrawingContext context)
{
if (_lastFrame != null && _lastFrame.Width != 0 && _lastFrame.Height != 0)
{
diff --git a/src/Avalonia.Controls/ScrollViewer.cs b/src/Avalonia.Controls/ScrollViewer.cs
index ab114da933..af874bd380 100644
--- a/src/Avalonia.Controls/ScrollViewer.cs
+++ b/src/Avalonia.Controls/ScrollViewer.cs
@@ -775,6 +775,22 @@ namespace Avalonia.Controls
control.SetValue(VerticalScrollBarVisibilityProperty, value);
}
+ ///
+ /// Gets whether scroll gestures should include inertia in their behavior and value.
+ ///
+ public static bool GetIsScrollInertiaEnabled(Control control)
+ {
+ return control.GetValue(IsScrollInertiaEnabledProperty);
+ }
+
+ ///
+ /// Sets whether scroll gestures should include inertia in their behavior and value.
+ ///
+ public static void SetIsScrollInertiaEnabled(Control control, bool value)
+ {
+ control.SetValue(IsScrollInertiaEnabledProperty, value);
+ }
+
///
public void RegisterAnchorCandidate(Control element)
{
diff --git a/src/Avalonia.Controls/SelectableTextBlock.cs b/src/Avalonia.Controls/SelectableTextBlock.cs
index f8ce5d23f6..6603e20a2a 100644
--- a/src/Avalonia.Controls/SelectableTextBlock.cs
+++ b/src/Avalonia.Controls/SelectableTextBlock.cs
@@ -336,7 +336,7 @@ namespace Avalonia.Controls
point = new Point(
MathUtilities.Clamp(point.X, 0, Math.Max(TextLayout.Bounds.Width, 0)),
- MathUtilities.Clamp(point.Y, 0, Math.Max(TextLayout.Bounds.Width, 0)));
+ MathUtilities.Clamp(point.Y, 0, Math.Max(TextLayout.Bounds.Height, 0)));
var hit = TextLayout.HitTestPoint(point);
var textPosition = hit.TextPosition;
diff --git a/src/Avalonia.Controls/Shapes/Shape.cs b/src/Avalonia.Controls/Shapes/Shape.cs
index c8bf95b3f7..461dc1c947 100644
--- a/src/Avalonia.Controls/Shapes/Shape.cs
+++ b/src/Avalonia.Controls/Shapes/Shape.cs
@@ -193,7 +193,7 @@ namespace Avalonia.Controls.Shapes
set { SetValue(StrokeJoinProperty, value); }
}
- public override void Render(DrawingContext context)
+ public sealed override void Render(DrawingContext context)
{
var geometry = RenderedGeometry;
diff --git a/src/Avalonia.Controls/Slider.cs b/src/Avalonia.Controls/Slider.cs
index 828bf2a1fb..7de726a932 100644
--- a/src/Avalonia.Controls/Slider.cs
+++ b/src/Avalonia.Controls/Slider.cs
@@ -10,6 +10,7 @@ using Avalonia.Interactivity;
using Avalonia.Layout;
using Avalonia.Utilities;
using Avalonia.Automation;
+using Avalonia.Controls.Automation.Peers;
namespace Avalonia.Controls
{
@@ -380,6 +381,11 @@ namespace Avalonia.Controls
}
}
+ protected override AutomationPeer OnCreateAutomationPeer()
+ {
+ return new SliderAutomationPeer(this);
+ }
+
///
protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs change)
{
diff --git a/src/Avalonia.Controls/TextBlock.cs b/src/Avalonia.Controls/TextBlock.cs
index ec31470126..df98d1073e 100644
--- a/src/Avalonia.Controls/TextBlock.cs
+++ b/src/Avalonia.Controls/TextBlock.cs
@@ -549,7 +549,13 @@ namespace Avalonia.Controls
/// Renders the to a drawing context.
///
/// The drawing context.
- public override void Render(DrawingContext context)
+ public sealed override void Render(DrawingContext context)
+ {
+ RenderCore(context);
+ }
+
+ // Workaround to seal Render method, we need to make so because AccessText was overriding Render method which is sealed now.
+ internal protected virtual void RenderCore(DrawingContext context)
{
var background = Background;
diff --git a/src/Avalonia.Controls/TextBoxTextInputMethodClient.cs b/src/Avalonia.Controls/TextBoxTextInputMethodClient.cs
index 10c2f36f43..3a28836a99 100644
--- a/src/Avalonia.Controls/TextBoxTextInputMethodClient.cs
+++ b/src/Avalonia.Controls/TextBoxTextInputMethodClient.cs
@@ -5,6 +5,7 @@ using Avalonia.Media.TextFormatting;
using Avalonia.Threading;
using Avalonia.Utilities;
using Avalonia.VisualTree;
+using static System.Net.Mime.MediaTypeNames;
namespace Avalonia.Controls
{
@@ -12,6 +13,7 @@ namespace Avalonia.Controls
{
private TextBox? _parent;
private TextPresenter? _presenter;
+ private ITextEditable? _textEditable;
public Visual TextViewVisual => _presenter!;
@@ -45,7 +47,7 @@ namespace Avalonia.Controls
{
get
{
- if(_presenter is null || _parent is null)
+ if (_presenter is null || _parent is null)
{
return default;
}
@@ -71,13 +73,70 @@ namespace Avalonia.Controls
}
}
+ public ITextEditable? TextEditable
+ {
+ get => _textEditable; set
+ {
+ if(_textEditable != null)
+ {
+ _textEditable.TextChanged -= TextEditable_TextChanged;
+ _textEditable.SelectionChanged -= TextEditable_SelectionChanged;
+ _textEditable.CompositionChanged -= TextEditable_CompositionChanged;
+ }
+
+ _textEditable = value;
+
+ if(_textEditable != null)
+ {
+ _textEditable.TextChanged += TextEditable_TextChanged;
+ _textEditable.SelectionChanged += TextEditable_SelectionChanged;
+ _textEditable.CompositionChanged += TextEditable_CompositionChanged;
+
+ if (_presenter != null)
+ {
+ _textEditable.Text = _presenter.Text;
+ _textEditable.SelectionStart = _presenter.SelectionStart;
+ _textEditable.SelectionEnd = _presenter.SelectionEnd;
+ }
+ }
+ }
+ }
+
+ private void TextEditable_CompositionChanged(object? sender, EventArgs e)
+ {
+ if (_presenter != null && _textEditable != null)
+ {
+ _presenter.CompositionRegion = new TextRange(_textEditable.CompositionStart, _textEditable.CompositionEnd);
+ }
+ }
+
+ private void TextEditable_SelectionChanged(object? sender, EventArgs e)
+ {
+ if(_parent != null && _textEditable != null)
+ {
+ _parent.SelectionStart = _textEditable.SelectionStart;
+ _parent.SelectionEnd = _textEditable.SelectionEnd;
+ }
+ }
+
+ private void TextEditable_TextChanged(object? sender, EventArgs e)
+ {
+ if (_parent != null)
+ {
+ if (_parent.Text != _textEditable?.Text)
+ {
+ _parent.Text = _textEditable?.Text;
+ }
+ }
+ }
+
private static string GetTextLineText(TextLine textLine)
{
var builder = StringBuilderCache.Acquire(textLine.Length);
foreach (var run in textLine.TextRuns)
{
- if(run.Length > 0)
+ if (run.Length > 0)
{
#if NET6_0_OR_GREATER
builder.Append(run.Text.Span);
@@ -110,9 +169,18 @@ namespace Avalonia.Controls
_presenter.PreeditText = text;
}
+ public void SetComposingRegion(TextRange? region)
+ {
+ if (_presenter == null)
+ {
+ return;
+ }
+ _presenter.CompositionRegion = region;
+ }
+
public void SelectInSurroundingText(int start, int end)
{
- if(_parent is null ||_presenter is null)
+ if (_parent is null || _presenter is null)
{
return;
}
@@ -125,21 +193,21 @@ namespace Avalonia.Controls
var selectionStart = lineStart + start;
var selectionEnd = lineStart + end;
-
+
_parent.SelectionStart = selectionStart;
_parent.SelectionEnd = selectionEnd;
- }
-
+ }
+
public void SetPresenter(TextPresenter? presenter, TextBox? parent)
{
- if(_parent != null)
+ if (_parent != null)
{
_parent.PropertyChanged -= OnParentPropertyChanged;
}
_parent = parent;
- if(_parent != null)
+ if (_parent != null)
{
_parent.PropertyChanged += OnParentPropertyChanged;
}
@@ -148,16 +216,18 @@ namespace Avalonia.Controls
{
_presenter.PreeditText = null;
- _presenter.CaretBoundsChanged -= OnCaretBoundsChanged;
+ _presenter.CompositionRegion = null;
+
+ _presenter.CaretBoundsChanged -= OnCaretBoundsChanged;
}
-
+
_presenter = presenter;
-
+
if (_presenter != null)
{
_presenter.CaretBoundsChanged += OnCaretBoundsChanged;
}
-
+
TextViewVisualChanged?.Invoke(this, EventArgs.Empty);
OnCaretBoundsChanged(this, EventArgs.Empty);
@@ -165,12 +235,33 @@ namespace Avalonia.Controls
private void OnParentPropertyChanged(object? sender, AvaloniaPropertyChangedEventArgs e)
{
- if(e.Property == TextBox.SelectionStartProperty || e.Property == TextBox.SelectionEndProperty)
+ if (e.Property == TextBox.SelectionStartProperty || e.Property == TextBox.SelectionEndProperty)
{
if (SupportsSurroundingText)
{
SurroundingTextChanged?.Invoke(this, e);
}
+ if (_textEditable != null)
+ {
+ var value = (int)(e.NewValue ?? 0);
+ if (e.Property == TextBox.SelectionStartProperty)
+ {
+ _textEditable.SelectionStart = value;
+ }
+
+ if (e.Property == TextBox.SelectionEndProperty)
+ {
+ _textEditable.SelectionEnd = value;
+ }
+ }
+ }
+
+ if(e.Property == TextBox.TextProperty)
+ {
+ if(_textEditable != null)
+ {
+ _textEditable.Text = (string?)e.NewValue;
+ }
}
}
diff --git a/src/Avalonia.Controls/TickBar.cs b/src/Avalonia.Controls/TickBar.cs
index 4d902d3d5f..63fe9c9384 100644
--- a/src/Avalonia.Controls/TickBar.cs
+++ b/src/Avalonia.Controls/TickBar.cs
@@ -213,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;
diff --git a/src/Avalonia.Controls/TopLevel.cs b/src/Avalonia.Controls/TopLevel.cs
index f956fb8724..ed11dec1d0 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
{
@@ -570,6 +571,30 @@ namespace Avalonia.Controls
/// The event args.
protected virtual void OnClosed(EventArgs e) => Closed?.Invoke(this, e);
+ ///
+ /// 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.
+ ///
+ protected 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(() => { });
+ }
+ }
+
///
/// Tries to get a service from an , logging a
/// warning if not found.
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/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.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 @@