diff --git a/Avalonia.sln b/Avalonia.sln
index a0314b1c33..8c857389dc 100644
--- a/Avalonia.sln
+++ b/Avalonia.sln
@@ -239,7 +239,6 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution
NOTICE.md = NOTICE.md
NuGet.Config = NuGet.Config
readme.md = readme.md
- Settings.StyleCop = Settings.StyleCop
EndProjectSection
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Avalonia.Generators", "src\tools\Avalonia.Generators\Avalonia.Generators.csproj", "{DDA28789-C21A-4654-86CE-D01E81F095C5}"
diff --git a/Directory.Build.props b/Directory.Build.props
index 117c0964d2..f124456eab 100644
--- a/Directory.Build.props
+++ b/Directory.Build.props
@@ -9,5 +9,9 @@
false
False
12
+ true
+ true
+ true
+ true
diff --git a/api/Avalonia.Skia.nupkg.xml b/api/Avalonia.Skia.nupkg.xml
index ed29e880a4..b275cbff58 100644
--- a/api/Avalonia.Skia.nupkg.xml
+++ b/api/Avalonia.Skia.nupkg.xml
@@ -1,6 +1,12 @@
+
+ CP0002
+ M:Avalonia.Skia.SkiaSharpExtensions.ToSKFilterQuality(Avalonia.Media.Imaging.BitmapInterpolationMode)
+ baseline/netstandard2.0/Avalonia.Skia.dll
+ target/netstandard2.0/Avalonia.Skia.dll
+
CP0006
M:Avalonia.Skia.ISkiaGpuWithPlatformGraphicsContext.TryGetGrContext
diff --git a/api/Avalonia.nupkg.xml b/api/Avalonia.nupkg.xml
index 0be3d69b7b..8d64cb2a82 100644
--- a/api/Avalonia.nupkg.xml
+++ b/api/Avalonia.nupkg.xml
@@ -7,6 +7,72 @@
baseline/netstandard2.0/Avalonia.Base.dll
target/netstandard2.0/Avalonia.Base.dll
+
+ CP0001
+ T:Avalonia.Data.Core.CastTypePropertyPathElement
+ baseline/netstandard2.0/Avalonia.Base.dll
+ target/netstandard2.0/Avalonia.Base.dll
+
+
+ CP0001
+ T:Avalonia.Data.Core.ChildTraversalPropertyPathElement
+ baseline/netstandard2.0/Avalonia.Base.dll
+ target/netstandard2.0/Avalonia.Base.dll
+
+
+ CP0001
+ T:Avalonia.Data.Core.EnsureTypePropertyPathElement
+ baseline/netstandard2.0/Avalonia.Base.dll
+ target/netstandard2.0/Avalonia.Base.dll
+
+
+ CP0001
+ T:Avalonia.Data.Core.IPropertyPathElement
+ baseline/netstandard2.0/Avalonia.Base.dll
+ target/netstandard2.0/Avalonia.Base.dll
+
+
+ CP0001
+ T:Avalonia.Data.Core.PropertyPath
+ baseline/netstandard2.0/Avalonia.Base.dll
+ target/netstandard2.0/Avalonia.Base.dll
+
+
+ CP0001
+ T:Avalonia.Data.Core.PropertyPathBuilder
+ baseline/netstandard2.0/Avalonia.Base.dll
+ target/netstandard2.0/Avalonia.Base.dll
+
+
+ CP0001
+ T:Avalonia.Data.Core.PropertyPropertyPathElement
+ baseline/netstandard2.0/Avalonia.Base.dll
+ target/netstandard2.0/Avalonia.Base.dll
+
+
+ CP0001
+ T:Avalonia.Utilities.CharacterReader
+ baseline/netstandard2.0/Avalonia.Base.dll
+ target/netstandard2.0/Avalonia.Base.dll
+
+
+ CP0001
+ T:Avalonia.Utilities.IdentifierParser
+ baseline/netstandard2.0/Avalonia.Base.dll
+ target/netstandard2.0/Avalonia.Base.dll
+
+
+ CP0001
+ T:Avalonia.Utilities.KeywordParser
+ baseline/netstandard2.0/Avalonia.Base.dll
+ target/netstandard2.0/Avalonia.Base.dll
+
+
+ CP0001
+ T:Avalonia.Utilities.StyleClassParser
+ baseline/netstandard2.0/Avalonia.Base.dll
+ target/netstandard2.0/Avalonia.Base.dll
+
CP0002
M:Avalonia.Diagnostics.AppliedStyle.get_HasActivator
@@ -43,6 +109,42 @@
baseline/netstandard2.0/Avalonia.Base.dll
target/netstandard2.0/Avalonia.Base.dll
+
+ CP0002
+ M:Avalonia.Threading.DispatcherPriorityAwaitable.get_IsCompleted
+ baseline/netstandard2.0/Avalonia.Base.dll
+ target/netstandard2.0/Avalonia.Base.dll
+
+
+ CP0002
+ M:Avalonia.Threading.DispatcherPriorityAwaitable.GetAwaiter
+ baseline/netstandard2.0/Avalonia.Base.dll
+ target/netstandard2.0/Avalonia.Base.dll
+
+
+ CP0002
+ M:Avalonia.Threading.DispatcherPriorityAwaitable.GetResult
+ baseline/netstandard2.0/Avalonia.Base.dll
+ target/netstandard2.0/Avalonia.Base.dll
+
+
+ CP0002
+ M:Avalonia.Threading.DispatcherPriorityAwaitable.OnCompleted(System.Action)
+ baseline/netstandard2.0/Avalonia.Base.dll
+ target/netstandard2.0/Avalonia.Base.dll
+
+
+ CP0002
+ M:Avalonia.Threading.DispatcherPriorityAwaitable`1.GetAwaiter
+ baseline/netstandard2.0/Avalonia.Base.dll
+ target/netstandard2.0/Avalonia.Base.dll
+
+
+ CP0002
+ M:Avalonia.Threading.DispatcherPriorityAwaitable`1.GetResult
+ baseline/netstandard2.0/Avalonia.Base.dll
+ target/netstandard2.0/Avalonia.Base.dll
+
CP0002
M:Avalonia.Controls.Primitives.IPopupHost.ConfigurePosition(Avalonia.Visual,Avalonia.Controls.PlacementMode,Avalonia.Point,Avalonia.Controls.Primitives.PopupPositioning.PopupAnchor,Avalonia.Controls.Primitives.PopupPositioning.PopupGravity,Avalonia.Controls.Primitives.PopupPositioning.PopupPositionerConstraintAdjustment,System.Nullable{Avalonia.Rect})
@@ -109,6 +211,42 @@
baseline/netstandard2.0/Avalonia.Controls.dll
target/netstandard2.0/Avalonia.Controls.dll
+
+ CP0006
+ P:Avalonia.Controls.Platform.IInsetsManager.DisplayEdgeToEdgePreference
+ baseline/netstandard2.0/Avalonia.Controls.dll
+ target/netstandard2.0/Avalonia.Controls.dll
+
+
+ CP0006
+ P:Avalonia.Controls.Platform.IInsetsManager.DisplaysEdgeToEdge
+ baseline/netstandard2.0/Avalonia.Controls.dll
+ target/netstandard2.0/Avalonia.Controls.dll
+
+
+ CP0007
+ T:Avalonia.Threading.DispatcherPriorityAwaitable
+ baseline/netstandard2.0/Avalonia.Base.dll
+ target/netstandard2.0/Avalonia.Base.dll
+
+
+ CP0007
+ T:Avalonia.Threading.DispatcherPriorityAwaitable`1
+ baseline/netstandard2.0/Avalonia.Base.dll
+ target/netstandard2.0/Avalonia.Base.dll
+
+
+ CP0008
+ T:Avalonia.Threading.DispatcherPriorityAwaitable
+ baseline/netstandard2.0/Avalonia.Base.dll
+ target/netstandard2.0/Avalonia.Base.dll
+
+
+ CP0008
+ T:Avalonia.Threading.DispatcherPriorityAwaitable`1
+ baseline/netstandard2.0/Avalonia.Base.dll
+ target/netstandard2.0/Avalonia.Base.dll
+
CP0009
T:Avalonia.Diagnostics.StyleDiagnostics
diff --git a/build/ExternalConsumers.props b/build/ExternalConsumers.props
index 96cf5cc608..79df2f6be4 100644
--- a/build/ExternalConsumers.props
+++ b/build/ExternalConsumers.props
@@ -30,5 +30,6 @@
+
diff --git a/build/SkiaSharp.props b/build/SkiaSharp.props
index 5b643efab7..74339fb125 100644
--- a/build/SkiaSharp.props
+++ b/build/SkiaSharp.props
@@ -1,10 +1,5 @@
-
-
-
-
-
-
+
diff --git a/docs/api-compat.md b/docs/api-compat.md
new file mode 100644
index 0000000000..1aa4fbd422
--- /dev/null
+++ b/docs/api-compat.md
@@ -0,0 +1,32 @@
+# API Compatibility
+
+Avalonia maintains strict **source and binary compatibility** within major versions. Automated API compatibility checks run on every CI build to enforce this policy—builds will fail if breaking changes are detected.
+
+## When Breaking Changes Are Permitted
+
+Breaking changes are only acceptable under these specific circumstances and **must be approved** by the Avalonia code team:
+
+- **Major version targeting**: When `master` branch is targeting a new major version
+- **Accidental public APIs**: When code was unintentionally exposed as a public API and is unlikely to have external dependencies
+- **Experimental features**: When the API is explicitly marked as unstable or experimental
+
+## Handling Approved Breaking Changes
+
+When a breaking change is approved, you can bypass CI validation using an API suppression file:
+
+1. **Generate suppression file**: Run `nuke --update-api-suppression true`
+2. **Commit changes**: Commit the [updated suppression file](../api/) in a separate commit
+
+> **Note**: The suppression file should only be updated after the breaking change has been reviewed and approved.
+
+## Baseline Version Configuration
+
+API changes are validated against a **baseline version**—the reference point for compatibility checks.
+
+- **Default behavior**: Uses the current major version (e.g., for version 11.0.5, baseline is 11.0.0)
+- **Custom baseline**: Override using the [`api-baseline`](https://github.com/AvaloniaUI/Avalonia/blob/56d94d64b9aa6f16200be39b3bcb17f03325b7f9/nukebuild/BuildParameters.cs#L27) parameter with `nuke`
+- **Fallback**: When not specified, uses the version defined in [`SharedVersion.props`](https://github.com/AvaloniaUI/Avalonia/blob/56d94d64b9aa6f16200be39b3bcb17f03325b7f9/build/SharedVersion.props#L6)
+
+## Additional Resources
+
+- [API Validation Tool Implementation](https://github.com/AvaloniaUI/Avalonia/pull/12072) - Pull request that introduced this feature
\ No newline at end of file
diff --git a/Documentation/build.md b/docs/build.md
similarity index 100%
rename from Documentation/build.md
rename to docs/build.md
diff --git a/docs/debug-xaml-compiler.md b/docs/debug-xaml-compiler.md
new file mode 100644
index 0000000000..da260e6d0a
--- /dev/null
+++ b/docs/debug-xaml-compiler.md
@@ -0,0 +1,15 @@
+# Debugging the XAML Compiler
+
+The Avalonia XAML compiler can be debugged by setting an MSBuild property which triggers a debugger launch during compilation. This allows you to step through the XAML compilation process and troubleshoot issues.
+
+To enable XAML compiler debugging, set the `AvaloniaXamlIlDebuggerLaunch` MSBuild property to `true` in your project file:
+
+```xml
+
+ true
+
+```
+
+When this property is enabled, the XAML compiler will call `Debugger.Launch()` on startup, which prompts you to attach a debugger to the compiler process.
+
+If you're working with the Sandbox project in the Avalonia repository, you can enable debugging by simply uncommenting the property line in the [project file](https://github.com/AvaloniaUI/Avalonia/blob/56d94d64b9aa6f16200be39b3bcb17f03325b7f9/samples/Sandbox/Sandbox.csproj#L8).
diff --git a/docs/images/xcode-product-path.png b/docs/images/xcode-product-path.png
new file mode 100644
index 0000000000..d254da9875
Binary files /dev/null and b/docs/images/xcode-product-path.png differ
diff --git a/docs/index.md b/docs/index.md
new file mode 100644
index 0000000000..6adc4fd450
--- /dev/null
+++ b/docs/index.md
@@ -0,0 +1,19 @@
+# Avalonia Developer Documentation
+
+This documentation covers Avalonia framework development. For user documentation on how to use Avalonia, visit https://docs.avaloniaui.net/.
+
+## Getting Started
+
+- [Building Avalonia](build.md) describes how to build Avalonia from source
+- You should read the [Contributing guidelines](../CONTRIBUTING.md) before you start
+- We have a [Code of Conduct](../CODE_OF_CONDUCT.md)
+
+## Development
+
+- [Debugging the XAML Compiler](debug-xaml-compiler.md)
+- [Porting Code from 3rd Party Sources](porting-code-from-3rd-party-sources.md)
+
+## Releases
+
+- [API Compatibility](api-compat.md) describes the API compatibility guarantees provided by Avalonia, and exceptions to these guarantees
+- Our [Release Process](release.md) describes the process for creating a new Avalonia release
diff --git a/docs/macos-native.md b/docs/macos-native.md
new file mode 100644
index 0000000000..5c16742963
--- /dev/null
+++ b/docs/macos-native.md
@@ -0,0 +1,71 @@
+# macOS Native Code
+
+The macOS platform backend has a native component, written in objective-c and located in [`native/Avalonia.Native/src/OSX`](../native/Avalonia.Native/src/OSX/). To work on this code, open the [`Avalonia.Native.OSX.xcodeproj`](../native/Avalonia.Native/src/OSX/Avalonia.Native.OSX.xcodeproj)project in Xcode.
+
+Changes to the native portion of the code can be recompiled in two ways:
+
+1. Using the [`build.sh`](build.md#build-native-libraries-macos-only) script: this has the downside that it will trigger a full recompile of Avalonia
+2. Using `AvaloniaNativeLibraryPath` described below
+
+## Using `AvaloniaNativeLibraryPath `
+
+When you make changes in Xcode and recompile using Cmd+B, the binary will be compiled to a location that can be seen under "Products":
+
+
+
+To use this build in Avalonia, one can specifiy the path by using `AvaloniaNativePlatformOptions.AvaloniaNativeLibraryPath` in an application's AppBuilder:
+
+```csharp
+public static AppBuilder BuildAvaloniaApp() =>
+ AppBuilder.Configure()
+ .UsePlatformDetect()
+ .With(new AvaloniaNativePlatformOptions
+ {
+ AvaloniaNativeLibraryPath = "[Path to your dylib]",
+ })
+```
+
+# Bundling Development Code
+
+In certain situations you need to run an Avalonia sample application as an app bundle. One of these situations is testing macOS Accessibility - Xcode's Accessibility Inspector fails to recognise the application otherwise. To facilitate this, the [`IntegrationTestApp`](../samples/IntegrationTestApp/) has a [`bundle.sh`](../samples/IntegrationTestApp/bundle.sh) script which can be run to create a bundle of that application.
+
+Alteratively, if you need to bundle another project, another solution is to change the sample's output path to resemble an app bundle. You can do this by modifying the output path in the csproj, e.g.:
+
+```xml
+bin\$(Configuration)\$(Platform)\ControlCatalog.NetCore.app/Contents/MacOS
+false
+true
+```
+
+And in the Contents output directory place a valid `Info.plist` file. An example for ControlCatalog.NetCore is:
+
+```xml
+
+
+
+
+ CFBundleName
+ ControlCatalog.NetCore
+ CFBundleDisplayName
+ ControlCatalog.NetCore
+ CFBundleIdentifier
+ ControlCatalog.NetCore
+ CFBundleVersion
+ 0.10.999
+ CFBundlePackageType
+ AAPL
+ CFBundleSignature
+ ????
+ CFBundleExecutable
+ ControlCatalog.NetCore
+ CFBundleIconFile
+ ControlCatalog.NetCore.icns
+ CFBundleShortVersionString
+ 0.1
+ NSPrincipalClass
+ NSApplication
+ NSHighResolutionCapable
+
+
+
+```
diff --git a/docs/porting-code-from-3rd-party-sources.md b/docs/porting-code-from-3rd-party-sources.md
new file mode 100644
index 0000000000..9aa72b8fce
--- /dev/null
+++ b/docs/porting-code-from-3rd-party-sources.md
@@ -0,0 +1,12 @@
+# Porting Code from 3rd Party Sources
+
+When porting code or adapting code from other projects with a MIT compatible license, the source file must contain appropriate license headers. For example when porting code from WPF the header should contain:
+
+```
+// This source file is adapted from the Windows Presentation Foundation project.
+// (https://github.com/dotnet/wpf/)
+//
+// Licensed to The Avalonia Project under MIT License, courtesy of The .NET Foundation.
+```
+
+If the file is a port of a specific commit of file from a 3rd party source, then consider including a permalink to the source file.
\ No newline at end of file
diff --git a/docs/release.md b/docs/release.md
new file mode 100644
index 0000000000..00c83cc47c
--- /dev/null
+++ b/docs/release.md
@@ -0,0 +1,30 @@
+# Release Process
+
+This document describes the process for creating a new Avalonia release
+
+## Backports
+
+- Go through PRs with "backport-candidate-*" tags, make sure this list makes sense
+- Checkout https://github.com/grokys/avalonia-backport tool from the source code, get github api token
+- Checkout stable release branch, like `release/11.0`
+- Run backport cherry-picking process to the stable release branch
+- Test if everything builds and tests are passing
+- Test nightly builds of Avalonia
+- Run labeling process (automatically replace "backport-candidate" with "backported" tags) and generate changelog
+
+## Release
+
+- Create a branch named e.g. `release/11.0.9` for the specific minor version
+- Update the version number in the file [SharedVersion.props](../build/SharedVersion.props), e.g. `11.0.9`
+- Add a tag for this version, e.g. `git tag 11.0.9`
+- Push the release branch and the tag.
+- Wait for azure pipelines to finish the build. Nightly build with 11.0.9 version should be released soon after.
+- Using the nightly build run a due diligence test to make sure you're happy with the package.
+- On azure pipelines, on the release for your release branch `release/11.0.9` click on the badge for "Nuget Release"
+- Click deploy
+- Make a release on Github releases
+- Press "Auto-generate changelog", so GitHub will append information about new contributors
+- Replace changelog with one generated by avalonia-backport tool. Enable discussion for the specific release
+- Review the release information and publish.
+- Update the dotnet templates, visual studio templates.
+- Announce on telegram (RU and EN), twitter, etc
\ No newline at end of file
diff --git a/global.json b/global.json
index 28c43eff46..f496cffc50 100644
--- a/global.json
+++ b/global.json
@@ -1,6 +1,6 @@
{
"sdk": {
- "version": "8.0.404",
+ "version": "8.0.411",
"rollForward": "latestFeature"
},
"msbuild-sdks": {
diff --git a/native/Avalonia.Native/src/OSX/AvnView.mm b/native/Avalonia.Native/src/OSX/AvnView.mm
index 2799425638..4cc495f321 100644
--- a/native/Avalonia.Native/src/OSX/AvnView.mm
+++ b/native/Avalonia.Native/src/OSX/AvnView.mm
@@ -13,7 +13,6 @@
{
ComObjectWeakPtr _parent;
NSTrackingArea* _area;
- bool _isLeftPressed, _isMiddlePressed, _isRightPressed, _isXButton1Pressed, _isXButton2Pressed;
AvnInputModifiers _modifierState;
NSEvent* _lastMouseDownEvent;
AvnPixelSize _lastPixelSize;
@@ -386,11 +385,57 @@ static void ConvertTilt(NSPoint tilt, float* xTilt, float* yTilt)
}
- (BOOL) resignFirstResponder
+{
+ auto window = [self window];
+ if (window != nullptr && window.keyWindow)
+ {
+ [self onLostFocus];
+ }
+
+ return YES;
+}
+
+- (void)viewWillMoveToWindow:(NSWindow *)newWindow
+{
+ auto oldWindow = [self window];
+ if (oldWindow == newWindow)
+ {
+ // viewWillMoveToWindow can be called with the same window when the view hierarchy changes
+ return;
+ }
+
+ if (oldWindow != nullptr)
+ {
+ [[NSNotificationCenter defaultCenter]
+ removeObserver:self
+ name:@"NSWindowDidResignKeyNotification"
+ object: oldWindow];
+ }
+
+ if (newWindow != nullptr)
+ {
+ [[NSNotificationCenter defaultCenter]
+ addObserver:self
+ selector:@selector(windowDidResignKey:)
+ name:@"NSWindowDidResignKeyNotification"
+ object: newWindow];
+ }
+}
+
+- (void)windowDidResignKey:(NSNotification*)notification
+{
+ auto window = [self window];
+ if (window != nullptr && notification.object == window && [window firstResponder] == self)
+ {
+ [self onLostFocus];
+ }
+}
+
+- (void)onLostFocus
{
auto parent = _parent.tryGet();
- if(parent)
+ if (parent)
parent->TopLevelEvents->LostFocus();
- return YES;
}
- (void)mouseMoved:(NSEvent *)event
@@ -400,7 +445,6 @@ static void ConvertTilt(NSPoint tilt, float* xTilt, float* yTilt)
- (void)mouseDown:(NSEvent *)event
{
- _isLeftPressed = true;
_lastMouseDownEvent = event;
[self mouseEvent:event withType:LeftButtonDown];
}
@@ -413,15 +457,12 @@ static void ConvertTilt(NSPoint tilt, float* xTilt, float* yTilt)
{
case 2:
case 3:
- _isMiddlePressed = true;
[self mouseEvent:event withType:MiddleButtonDown];
break;
case 4:
- _isXButton1Pressed = true;
[self mouseEvent:event withType:XButton1Down];
break;
case 5:
- _isXButton2Pressed = true;
[self mouseEvent:event withType:XButton2Down];
break;
@@ -432,14 +473,12 @@ static void ConvertTilt(NSPoint tilt, float* xTilt, float* yTilt)
- (void)rightMouseDown:(NSEvent *)event
{
- _isRightPressed = true;
_lastMouseDownEvent = event;
[self mouseEvent:event withType:RightButtonDown];
}
- (void)mouseUp:(NSEvent *)event
{
- _isLeftPressed = false;
[self mouseEvent:event withType:LeftButtonUp];
}
@@ -449,15 +488,12 @@ static void ConvertTilt(NSPoint tilt, float* xTilt, float* yTilt)
{
case 2:
case 3:
- _isMiddlePressed = false;
[self mouseEvent:event withType:MiddleButtonUp];
break;
case 4:
- _isXButton1Pressed = false;
[self mouseEvent:event withType:XButton1Up];
break;
case 5:
- _isXButton2Pressed = false;
[self mouseEvent:event withType:XButton2Up];
break;
@@ -468,7 +504,6 @@ static void ConvertTilt(NSPoint tilt, float* xTilt, float* yTilt)
- (void)rightMouseUp:(NSEvent *)event
{
- _isRightPressed = false;
[self mouseEvent:event withType:RightButtonUp];
}
@@ -551,15 +586,6 @@ static void ConvertTilt(NSPoint tilt, float* xTilt, float* yTilt)
_modifierState = [self getModifiers:modifierFlags];
}
-- (void)resetPressedMouseButtons
-{
- _isLeftPressed = false;
- _isRightPressed = false;
- _isMiddlePressed = false;
- _isXButton1Pressed = false;
- _isXButton2Pressed = false;
-}
-
- (void)flagsChanged:(NSEvent *)event
{
auto newModifierState = [self getModifiers:[event modifierFlags]];
@@ -706,15 +732,17 @@ static void ConvertTilt(NSPoint tilt, float* xTilt, float* yTilt)
if (mod & NSEventModifierFlagCommand)
rv |= Windows;
- if (_isLeftPressed)
+ NSUInteger pressedButtons = [NSEvent pressedMouseButtons];
+
+ if (pressedButtons & (1 << 0)) // Left mouse button
rv |= LeftMouseButton;
- if (_isMiddlePressed)
- rv |= MiddleMouseButton;
- if (_isRightPressed)
+ if (pressedButtons & (1 << 1)) // Right mouse button
rv |= RightMouseButton;
- if (_isXButton1Pressed)
+ if (pressedButtons & (1 << 2)) // Middle mouse button
+ rv |= MiddleMouseButton;
+ if (pressedButtons & (1 << 3)) // X1 button
rv |= XButton1MouseButton;
- if (_isXButton2Pressed)
+ if (pressedButtons & (1 << 4)) // X2 button
rv |= XButton2MouseButton;
return (AvnInputModifiers)rv;
diff --git a/native/Avalonia.Native/src/OSX/AvnWindow.mm b/native/Avalonia.Native/src/OSX/AvnWindow.mm
index 2dee90bfa3..03daa2f296 100644
--- a/native/Avalonia.Native/src/OSX/AvnWindow.mm
+++ b/native/Avalonia.Native/src/OSX/AvnWindow.mm
@@ -435,9 +435,23 @@
return;
}
- if(window->WindowState() == Maximized)
+ // If the window has been moved into a position where it's "zoomed"
+ // Then it should be set as Maximized.
+ if (window->WindowState() != Maximized && window->IsZoomed())
{
- window->SetWindowState(Normal);
+ window->SetWindowState(Maximized, false);
+ }
+ // We should only return the window state to normal if
+ // the internal window state is maximized, and macOS says
+ // the window is no longer zoomed (I.E, the user has moved it)
+ // Stage Manager will "move" the window when repositioning it
+ // So if the window was "maximized" before, it should stay maximized
+ else if(window->WindowState() == Maximized && !window->IsZoomed())
+ {
+ // If we're moving the window while maximized,
+ // we need to let macOS handle if it should be resized
+ // And not handle it ourselves.
+ window->SetWindowState(Normal, false);
}
}
@@ -462,8 +476,95 @@
}
}
+- (BOOL)isPointInTitlebar:(NSPoint)windowPoint
+{
+ auto parent = _parent.tryGetWithCast();
+ if (!parent || !_isExtended) {
+ return NO;
+ }
+
+ AvnView* view = parent->View;
+ NSPoint viewPoint = [view convertPoint:windowPoint fromView:nil];
+ double titlebarHeight = [self getExtendedTitleBarHeight];
+
+ // Check if click is in titlebar area (top portion of view)
+ if (viewPoint.y <= titlebarHeight) {
+ // Verify we're actually in a toolbar-related area
+ NSView* hitView = [[self findRootView:view] hitTest:windowPoint];
+ if (hitView) {
+ NSString* hitViewClass = [hitView className];
+ if ([hitViewClass containsString:@"Toolbar"] || [hitViewClass containsString:@"Titlebar"]) {
+ return YES;
+ }
+ }
+ }
+ return NO;
+}
+
+- (void)forwardToAvnView:(NSEvent *)event
+{
+ auto parent = _parent.tryGetWithCast();
+ if (!parent) {
+ return;
+ }
+
+ switch(event.type) {
+ case NSEventTypeLeftMouseDown:
+ [parent->View mouseDown:event];
+ break;
+ case NSEventTypeLeftMouseUp:
+ [parent->View mouseUp:event];
+ break;
+ case NSEventTypeLeftMouseDragged:
+ [parent->View mouseDragged:event];
+ break;
+ case NSEventTypeRightMouseDown:
+ [parent->View rightMouseDown:event];
+ break;
+ case NSEventTypeRightMouseUp:
+ [parent->View rightMouseUp:event];
+ break;
+ case NSEventTypeRightMouseDragged:
+ [parent->View rightMouseDragged:event];
+ break;
+ case NSEventTypeOtherMouseDown:
+ [parent->View otherMouseDown:event];
+ break;
+ case NSEventTypeOtherMouseUp:
+ [parent->View otherMouseUp:event];
+ break;
+ case NSEventTypeOtherMouseDragged:
+ [parent->View otherMouseDragged:event];
+ break;
+ case NSEventTypeMouseMoved:
+ [parent->View mouseMoved:event];
+ break;
+ default:
+ break;
+ }
+}
+
- (void)sendEvent:(NSEvent *_Nonnull)event
{
+ // Event-tracking loop for thick titlebar mouse events
+ if (event.type == NSEventTypeLeftMouseDown && [self isPointInTitlebar:event.locationInWindow])
+ {
+ NSEventMask mask = NSEventMaskLeftMouseDragged | NSEventMaskLeftMouseUp;
+ NSEvent *ev = event;
+ while (ev.type != NSEventTypeLeftMouseUp)
+ {
+ [self forwardToAvnView:ev];
+ [super sendEvent:ev];
+ ev = [NSApp nextEventMatchingMask:mask
+ untilDate:[NSDate distantFuture]
+ inMode:NSEventTrackingRunLoopMode
+ dequeue:YES];
+ }
+ [self forwardToAvnView:ev];
+ [super sendEvent:ev];
+ return;
+ }
+
[super sendEvent:event];
auto parent = _parent.tryGetWithCast();
diff --git a/native/Avalonia.Native/src/OSX/StorageProvider.mm b/native/Avalonia.Native/src/OSX/StorageProvider.mm
index abf7f85c5f..92278a85e9 100644
--- a/native/Avalonia.Native/src/OSX/StorageProvider.mm
+++ b/native/Avalonia.Native/src/OSX/StorageProvider.mm
@@ -235,11 +235,6 @@ public:
panel.title = [NSString stringWithUTF8String:title];
}
- if(initialDirectory != nullptr)
- {
- auto directoryString = [NSString stringWithUTF8String:initialDirectory];
- panel.directoryURL = [NSURL URLWithString:directoryString];
- }
if(initialFile != nullptr)
{
@@ -248,6 +243,12 @@ public:
SetAccessoryView(panel, filters, false);
+ if(initialDirectory != nullptr)
+ {
+ auto directoryString = [NSString stringWithUTF8String:initialDirectory];
+ panel.directoryURL = [NSURL URLWithString:directoryString];
+ }
+
auto handler = ^(NSModalResponse result) {
if(result == NSFileHandlingPanelOKButton)
{
@@ -304,11 +305,6 @@ public:
panel.title = [NSString stringWithUTF8String:title];
}
- if(initialDirectory != nullptr)
- {
- auto directoryString = [NSString stringWithUTF8String:initialDirectory];
- panel.directoryURL = [NSURL URLWithString:directoryString];
- }
if(initialFile != nullptr)
{
@@ -317,6 +313,12 @@ public:
SetAccessoryView(panel, filters, true);
+ if(initialDirectory != nullptr)
+ {
+ auto directoryString = [NSString stringWithUTF8String:initialDirectory];
+ panel.directoryURL = [NSURL URLWithString:directoryString];
+ }
+
auto handler = ^(NSModalResponse result) {
if(result == NSFileHandlingPanelOKButton)
{
diff --git a/native/Avalonia.Native/src/OSX/WindowImpl.h b/native/Avalonia.Native/src/OSX/WindowImpl.h
index fce7273f30..37699082ed 100644
--- a/native/Avalonia.Native/src/OSX/WindowImpl.h
+++ b/native/Avalonia.Native/src/OSX/WindowImpl.h
@@ -71,6 +71,8 @@ BEGIN_INTERFACE_MAP()
void ExitFullScreenMode ();
virtual HRESULT SetWindowState (AvnWindowState state) override;
+
+ virtual HRESULT SetWindowState (AvnWindowState state, bool shouldResize);
virtual bool IsModal() override;
diff --git a/native/Avalonia.Native/src/OSX/WindowImpl.mm b/native/Avalonia.Native/src/OSX/WindowImpl.mm
index 03f3319bcd..341085ec08 100644
--- a/native/Avalonia.Native/src/OSX/WindowImpl.mm
+++ b/native/Avalonia.Native/src/OSX/WindowImpl.mm
@@ -451,6 +451,10 @@ void WindowImpl::ExitFullScreenMode() {
}
HRESULT WindowImpl::SetWindowState(AvnWindowState state) {
+ return SetWindowState(state, true);
+}
+
+HRESULT WindowImpl::SetWindowState(AvnWindowState state, bool shouldResize) {
START_COM_CALL;
@autoreleasepool {
@@ -474,61 +478,63 @@ HRESULT WindowImpl::SetWindowState(AvnWindowState state) {
if (_shown) {
_actualWindowState = _lastWindowState;
- switch (state) {
- case Maximized:
- if (currentState == FullScreen) {
- ExitFullScreenMode();
- }
+ if (shouldResize) {
+ switch (state) {
+ case Maximized:
+ if (currentState == FullScreen) {
+ ExitFullScreenMode();
+ }
- lastPositionSet.X = 0;
- lastPositionSet.Y = 0;
+ lastPositionSet.X = 0;
+ lastPositionSet.Y = 0;
- if ([Window isMiniaturized]) {
- [Window deminiaturize:Window];
- }
+ if ([Window isMiniaturized]) {
+ [Window deminiaturize:Window];
+ }
- if (!IsZoomed()) {
- DoZoom();
- }
- break;
+ if (!IsZoomed()) {
+ DoZoom();
+ }
+ break;
- case Minimized:
- if (currentState == FullScreen) {
- ExitFullScreenMode();
- } else {
- [Window miniaturize:Window];
- }
- break;
+ case Minimized:
+ if (currentState == FullScreen) {
+ ExitFullScreenMode();
+ } else {
+ [Window miniaturize:Window];
+ }
+ break;
- case FullScreen:
- if ([Window isMiniaturized]) {
- [Window deminiaturize:Window];
- }
+ case FullScreen:
+ if ([Window isMiniaturized]) {
+ [Window deminiaturize:Window];
+ }
- EnterFullScreenMode();
- break;
+ EnterFullScreenMode();
+ break;
- case Normal:
- if ([Window isMiniaturized]) {
- [Window deminiaturize:Window];
- }
+ case Normal:
+ if ([Window isMiniaturized]) {
+ [Window deminiaturize:Window];
+ }
- if (currentState == FullScreen) {
- ExitFullScreenMode();
- }
+ if (currentState == FullScreen) {
+ ExitFullScreenMode();
+ }
- if (IsZoomed()) {
- if (_decorations == SystemDecorationsFull) {
- DoZoom();
- } else {
- [Window setFrame:_preZoomSize display:true];
- auto newFrame = [Window contentRectForFrameRect:[Window frame]].size;
+ if (IsZoomed()) {
+ if (_decorations == SystemDecorationsFull) {
+ DoZoom();
+ } else {
+ [Window setFrame:_preZoomSize display:true];
+ auto newFrame = [Window contentRectForFrameRect:[Window frame]].size;
- [View setFrameSize:newFrame];
- }
+ [View setFrameSize:newFrame];
+ }
- }
- break;
+ }
+ break;
+ }
}
WindowEvents->WindowStateChanged(_actualWindowState);
diff --git a/native/Avalonia.Native/src/OSX/app.mm b/native/Avalonia.Native/src/OSX/app.mm
index dfa4062f0a..5dc994fb6b 100644
--- a/native/Avalonia.Native/src/OSX/app.mm
+++ b/native/Avalonia.Native/src/OSX/app.mm
@@ -30,8 +30,6 @@ ComPtr _events;
break;
}
- [[NSApplication sharedApplication] setActivationPolicy: AvnDesiredActivationPolicy];
-
[[NSUserDefaults standardUserDefaults] setBool:NO forKey:@"NSFullScreenMenuItemEverywhere"];
[[NSApplication sharedApplication] setHelpMenu: [[NSMenu new] initWithTitle:@""]];
diff --git a/native/Avalonia.Native/src/OSX/main.mm b/native/Avalonia.Native/src/OSX/main.mm
index d1dbe9d186..0e3621517e 100644
--- a/native/Avalonia.Native/src/OSX/main.mm
+++ b/native/Avalonia.Native/src/OSX/main.mm
@@ -129,14 +129,22 @@ public:
}
}
- virtual HRESULT SetShowInDock(int show) override
+ virtual HRESULT SetShowInDock(int show) override
{
START_COM_CALL;
@autoreleasepool
{
- AvnDesiredActivationPolicy = show
- ? NSApplicationActivationPolicyRegular : NSApplicationActivationPolicyAccessory;
+ NSApplication* app = [NSApplication sharedApplication];
+ NSApplicationActivationPolicy requestedPolicy = show
+ ? NSApplicationActivationPolicyRegular
+ : NSApplicationActivationPolicyAccessory;
+
+ if ([app activationPolicy] != requestedPolicy)
+ {
+ [app setActivationPolicy:requestedPolicy];
+ }
+
return S_OK;
}
}
diff --git a/readme.md b/readme.md
index 5954a81ac5..08fd014798 100644
--- a/readme.md
+++ b/readme.md
@@ -28,7 +28,7 @@ You can also see what [breaking changes](https://github.com/AvaloniaUI/Avalonia/
See our [Get Started](https://avaloniaui.net/gettingstarted?utm_source=github&utm_medium=referral&utm_content=readme_link) guide to begin developing apps with Avalonia UI.
### Visual Studio
-The Avalonia [Visual Studio Extension](https://marketplace.visualstudio.com/items?itemName=AvaloniaTeam.AvaloniaforVisualStudio) contains project and control templates that will help you get started, or you can use the .NET Core CLI. For a starter guide see our [documentation](https://docs.avaloniaui.net/docs/getting-started).
+The Avalonia [Visual Studio Extension](https://marketplace.visualstudio.com/items?itemName=AvaloniaTeam.AvaloniaVS) contains project and control templates that will help you get started, or you can use the .NET Core CLI. For a starter guide see our [documentation](https://docs.avaloniaui.net/docs/getting-started).
### JetBrains Rider
[JetBrains Rider](https://www.jetbrains.com/rider/whatsnew/?mkt_tok=eyJpIjoiTURBNU1HSmhNV0kwTUdFMiIsInQiOiJtNnU2VEc1TlNLa1ZRVkROYmdZYVpYREJsaU1qdUhmS3dxSzRHczdYWHl0RVlTNDMwSFwvNUs3VENTNVM0bVcyNFdaRmVYZzVWTTF1N3VrQWNGTkJreEhlam1hMlB4UVVWcHBGM1dNOUxoXC95YnRQdGgyUXl1YmZCM3h3d3BVWWdBIn0%3D#avalonia-support) now has official support for Avalonia.
@@ -49,6 +49,12 @@ Install-Package Avalonia.Desktop
See what others have built with Avalonia UI on our [Showcase](https://avaloniaui.net/showcase?utm_source=github&utm_medium=referral&utm_content=readme_link). We welcome submissions!
+## Sponsors
+Avalonia development is supported by the generous sponsorship of [Devolutions](https://devolutions.net/?utm_source=pr&utm_medium=partnership&utm_campaign=avalonia&utm_id=C086&member_status=responded).
+
+
+
+
## Community
Join our community hub to get early access to upcoming features, share your thoughts, and connect directly with the Avalonia team.
[](https://avaloniaui.community)
@@ -70,7 +76,7 @@ We have a [range of samples](https://github.com/AvaloniaUI/Avalonia.Samples) to
## Building and Using
-See the [build instructions here](Documentation/build.md).
+See the [build instructions here](docs/build.md).
## Contributing
diff --git a/samples/ControlCatalog.iOS/Info.plist b/samples/ControlCatalog.iOS/Info.plist
index b4c7c07eb6..a1aa23e506 100644
--- a/samples/ControlCatalog.iOS/Info.plist
+++ b/samples/ControlCatalog.iOS/Info.plist
@@ -16,7 +16,6 @@
1
2
- 3
UIRequiredDeviceCapabilities
@@ -38,5 +37,7 @@
UIInterfaceOrientationLandscapeLeft
UIInterfaceOrientationLandscapeRight
+ com.apple.security.files.user-selected.read-write
+
diff --git a/samples/ControlCatalog/App.xaml.cs b/samples/ControlCatalog/App.xaml.cs
index a29a23ff61..b08df1223d 100644
--- a/samples/ControlCatalog/App.xaml.cs
+++ b/samples/ControlCatalog/App.xaml.cs
@@ -44,6 +44,10 @@ namespace ControlCatalog
{
desktopLifetime.MainWindow = new MainWindow { DataContext = new MainWindowViewModel() };
}
+ else if(ApplicationLifetime is IActivityApplicationLifetime singleViewFactoryApplicationLifetime)
+ {
+ singleViewFactoryApplicationLifetime.MainViewFactory = () => new MainView { DataContext = new MainWindowViewModel() };
+ }
else if (ApplicationLifetime is ISingleViewApplicationLifetime singleViewLifetime)
{
singleViewLifetime.MainView = new MainView { DataContext = new MainWindowViewModel() };
@@ -97,6 +101,10 @@ namespace ControlCatalog
newWindow.Show();
oldWindow?.Close();
}
+ else if (app.ApplicationLifetime is IActivityApplicationLifetime singleViewFactoryApplicationLifetime)
+ {
+ singleViewFactoryApplicationLifetime.MainViewFactory = () => new MainView { DataContext = new MainWindowViewModel() };
+ }
else if (app.ApplicationLifetime is ISingleViewApplicationLifetime singleViewLifetime)
{
singleViewLifetime.MainView = new MainView();
diff --git a/samples/ControlCatalog/MainView.xaml.cs b/samples/ControlCatalog/MainView.xaml.cs
index 89bccb4475..1fefe766e2 100644
--- a/samples/ControlCatalog/MainView.xaml.cs
+++ b/samples/ControlCatalog/MainView.xaml.cs
@@ -108,14 +108,14 @@ namespace ControlCatalog
ViewModel.SafeAreaPadding = insets.SafeAreaPadding;
};
- ViewModel.DisplayEdgeToEdge = insets.DisplayEdgeToEdge;
+ ViewModel.DisplayEdgeToEdge = insets.DisplayEdgeToEdgePreference;
ViewModel.IsSystemBarVisible = insets.IsSystemBarVisible ?? true;
ViewModel.PropertyChanged += async (sender, args) =>
{
if (args.PropertyName == nameof(ViewModel.DisplayEdgeToEdge))
{
- insets.DisplayEdgeToEdge = ViewModel.DisplayEdgeToEdge;
+ insets.DisplayEdgeToEdgePreference = ViewModel.DisplayEdgeToEdge;
}
else if (args.PropertyName == nameof(ViewModel.IsSystemBarVisible))
{
@@ -124,7 +124,7 @@ namespace ControlCatalog
// Give the OS some time to apply new values and refresh the view model.
await Task.Delay(100);
- ViewModel.DisplayEdgeToEdge = insets.DisplayEdgeToEdge;
+ ViewModel.DisplayEdgeToEdge = insets.DisplayEdgeToEdgePreference;
ViewModel.IsSystemBarVisible = insets.IsSystemBarVisible ?? true;
};
}
diff --git a/samples/ControlCatalog/Pages/HeaderedContentPage.axaml b/samples/ControlCatalog/Pages/HeaderedContentPage.axaml
index ff44c2206d..86a2b00b72 100644
--- a/samples/ControlCatalog/Pages/HeaderedContentPage.axaml
+++ b/samples/ControlCatalog/Pages/HeaderedContentPage.axaml
@@ -13,6 +13,9 @@
CornerRadius="3">
+
+
+
diff --git a/samples/ControlCatalog/Pages/ListBoxPage.xaml b/samples/ControlCatalog/Pages/ListBoxPage.xaml
index 7694845009..e3a706bfed 100644
--- a/samples/ControlCatalog/Pages/ListBoxPage.xaml
+++ b/samples/ControlCatalog/Pages/ListBoxPage.xaml
@@ -20,6 +20,9 @@
+
Hosts a collection of ListBoxItem.
diff --git a/samples/ControlCatalog/Pages/SliderPage.xaml b/samples/ControlCatalog/Pages/SliderPage.xaml
index 9aa4322ad5..7821e885ba 100644
--- a/samples/ControlCatalog/Pages/SliderPage.xaml
+++ b/samples/ControlCatalog/Pages/SliderPage.xaml
@@ -21,6 +21,15 @@
IsSnapToTickEnabled="True"
Ticks="0,20,25,40,75,100"
Width="300" />
+
-
diff --git a/src/Avalonia.Themes.Fluent/Controls/FluentControls.xaml b/src/Avalonia.Themes.Fluent/Controls/FluentControls.xaml
index 90279214e5..18996d6a2d 100644
--- a/src/Avalonia.Themes.Fluent/Controls/FluentControls.xaml
+++ b/src/Avalonia.Themes.Fluent/Controls/FluentControls.xaml
@@ -22,6 +22,7 @@
+
diff --git a/src/Avalonia.Themes.Fluent/Controls/GroupBox.xaml b/src/Avalonia.Themes.Fluent/Controls/GroupBox.xaml
new file mode 100644
index 0000000000..1fee84269d
--- /dev/null
+++ b/src/Avalonia.Themes.Fluent/Controls/GroupBox.xaml
@@ -0,0 +1,83 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/Avalonia.Themes.Fluent/Controls/Slider.xaml b/src/Avalonia.Themes.Fluent/Controls/Slider.xaml
index 5834df311c..c7e41e1a02 100644
--- a/src/Avalonia.Themes.Fluent/Controls/Slider.xaml
+++ b/src/Avalonia.Themes.Fluent/Controls/Slider.xaml
@@ -129,6 +129,7 @@
VerticalAlignment="Bottom"
Placement="Top"
IsVisible="False"
+ IsDirectionReversed="{TemplateBinding IsDirectionReversed}"
Fill="{DynamicResource SliderTickBarFill}"/>
-