diff --git a/Avalonia.Desktop.slnf b/Avalonia.Desktop.slnf
index 741570061b..4a7a329fc6 100644
--- a/Avalonia.Desktop.slnf
+++ b/Avalonia.Desktop.slnf
@@ -42,6 +42,7 @@
"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",
@@ -61,4 +62,4 @@
"tests\\Avalonia.UnitTests\\Avalonia.UnitTests.csproj"
]
}
-}
\ No newline at end of file
+}
diff --git a/Avalonia.sln b/Avalonia.sln
index 525e01c891..56847bae31 100644
--- a/Avalonia.sln
+++ b/Avalonia.sln
@@ -231,7 +231,14 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ControlCatalog.Browser.Blaz
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ReactiveUIDemo", "samples\ReactiveUIDemo\ReactiveUIDemo.csproj", "{75C47156-C5D8-44BC-A5A7-E8657C2248D6}"
EndProject
-Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "GpuInterop", "samples\GpuInterop\GpuInterop.csproj", "{C810060E-3809-4B74-A125-F11533AF9C1B}"
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "GpuInterop", "samples\GpuInterop\GpuInterop.csproj", "{C810060E-3809-4B74-A125-F11533AF9C1B}"
+EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Avalonia.Analyzers", "src\tools\PublicAnalyzers\Avalonia.Analyzers.csproj", "{C692FE73-43DB-49CE-87FC-F03ED61F25C9}"
+EndProject
+Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{176582E8-46AF-416A-85C1-13A5C6744497}"
+ ProjectSection(SolutionItems) = preProject
+ .editorconfig = .editorconfig
+ EndProjectSection
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Avalonia.Controls.ItemsRepeater", "src\Avalonia.Controls.ItemsRepeater\Avalonia.Controls.ItemsRepeater.csproj", "{EE0F0DD4-A70D-472B-BD5D-B7D32D0E9386}"
EndProject
@@ -548,7 +555,6 @@ Global
{75C47156-C5D8-44BC-A5A7-E8657C2248D6}.Debug|Any CPU.Build.0 = Debug|Any CPU
{75C47156-C5D8-44BC-A5A7-E8657C2248D6}.Release|Any CPU.ActiveCfg = Release|Any CPU
{75C47156-C5D8-44BC-A5A7-E8657C2248D6}.Release|Any CPU.Build.0 = Release|Any CPU
- {C810060E-3809-4B74-A125-F11533AF9C1B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{C810060E-3809-4B74-A125-F11533AF9C1B}.Debug|Any CPU.Build.0 = Debug|Any CPU
{C810060E-3809-4B74-A125-F11533AF9C1B}.Release|Any CPU.ActiveCfg = Release|Any CPU
{C810060E-3809-4B74-A125-F11533AF9C1B}.Release|Any CPU.Build.0 = Release|Any CPU
@@ -560,6 +566,10 @@ Global
{F4E36AA8-814E-4704-BC07-291F70F45193}.Debug|Any CPU.Build.0 = Debug|Any CPU
{F4E36AA8-814E-4704-BC07-291F70F45193}.Release|Any CPU.ActiveCfg = Release|Any CPU
{F4E36AA8-814E-4704-BC07-291F70F45193}.Release|Any CPU.Build.0 = Release|Any CPU
+ {C692FE73-43DB-49CE-87FC-F03ED61F25C9}.Debug|Any CPU.ActiveCfg = Release|Any CPU
+ {C692FE73-43DB-49CE-87FC-F03ED61F25C9}.Debug|Any CPU.Build.0 = Release|Any CPU
+ {C692FE73-43DB-49CE-87FC-F03ED61F25C9}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {C692FE73-43DB-49CE-87FC-F03ED61F25C9}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
@@ -626,6 +636,7 @@ Global
{75C47156-C5D8-44BC-A5A7-E8657C2248D6} = {9B9E3891-2366-4253-A952-D08BCEB71098}
{C810060E-3809-4B74-A125-F11533AF9C1B} = {9B9E3891-2366-4253-A952-D08BCEB71098}
{F4E36AA8-814E-4704-BC07-291F70F45193} = {C5A00AC3-B34C-4564-9BDD-2DA473EF4D8B}
+ {C692FE73-43DB-49CE-87FC-F03ED61F25C9} = {4ED8B739-6F4E-4CD4-B993-545E6B5CE637}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {87366D66-1391-4D90-8999-95A620AD786A}
diff --git a/build/DevAnalyzers.props b/build/DevAnalyzers.props
index 14e4f6a563..7d021d051f 100644
--- a/build/DevAnalyzers.props
+++ b/build/DevAnalyzers.props
@@ -5,5 +5,10 @@
ReferenceOutputAssembly="false"
OutputItemType="Analyzer"
SetTargetFramework="TargetFramework=netstandard2.0"/>
+
diff --git a/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/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/IntegrationTestApp/MainWindow.axaml b/samples/IntegrationTestApp/MainWindow.axaml
index a6f463fdcb..353e01dca7 100644
--- a/samples/IntegrationTestApp/MainWindow.axaml
+++ b/samples/IntegrationTestApp/MainWindow.axaml
@@ -153,6 +153,9 @@
+
+
+
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/Avalonia.Base/AvaloniaObject.cs b/src/Avalonia.Base/AvaloniaObject.cs
index 2c9efc7767..d89d6f3690 100644
--- a/src/Avalonia.Base/AvaloniaObject.cs
+++ b/src/Avalonia.Base/AvaloniaObject.cs
@@ -664,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);
}
///
@@ -720,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/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/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/DrawingContext.cs b/src/Avalonia.Base/Media/DrawingContext.cs
index 622181dba0..a37fa6fd32 100644
--- a/src/Avalonia.Base/Media/DrawingContext.cs
+++ b/src/Avalonia.Base/Media/DrawingContext.cs
@@ -361,11 +361,12 @@ namespace Avalonia.Media
/// Pushes an opacity value.
///
/// The opacity.
+ /// The bounds.
/// A disposable used to undo the opacity.
- public PushedState PushOpacity(double opacity)
+ public PushedState PushOpacity(double opacity, Rect bounds)
//TODO: Eliminate platform-specific push opacity call
{
- PlatformImpl.PushOpacity(opacity);
+ PlatformImpl.PushOpacity(opacity, bounds);
return new PushedState(this, PushedState.PushedStateType.Opacity);
}
diff --git a/src/Avalonia.Base/Media/DrawingGroup.cs b/src/Avalonia.Base/Media/DrawingGroup.cs
index b7abda2c61..7b02649b6c 100644
--- a/src/Avalonia.Base/Media/DrawingGroup.cs
+++ b/src/Avalonia.Base/Media/DrawingGroup.cs
@@ -74,10 +74,12 @@ namespace Avalonia.Media
public override void Draw(DrawingContext context)
{
+ var bounds = GetBounds();
+
using (context.PushPreTransform(Transform?.Value ?? Matrix.Identity))
- using (context.PushOpacity(Opacity))
+ using (context.PushOpacity(Opacity, bounds))
using (ClipGeometry != null ? context.PushGeometryClip(ClipGeometry) : default)
- using (OpacityMask != null ? context.PushOpacityMask(OpacityMask, GetBounds()) : default)
+ using (OpacityMask != null ? context.PushOpacityMask(OpacityMask, bounds) : default)
{
foreach (var drawing in Children)
{
@@ -284,7 +286,7 @@ namespace Avalonia.Media
throw new NotImplementedException();
}
- public void PushOpacity(double opacity)
+ public void PushOpacity(double opacity, Rect bounds)
{
throw new NotImplementedException();
}
diff --git a/src/Avalonia.Base/Media/IImageBrush.cs b/src/Avalonia.Base/Media/IImageBrush.cs
index 732f1957d0..07fd2d56fa 100644
--- a/src/Avalonia.Base/Media/IImageBrush.cs
+++ b/src/Avalonia.Base/Media/IImageBrush.cs
@@ -12,6 +12,6 @@ namespace Avalonia.Media
///
/// Gets the image to draw.
///
- IBitmap Source { get; }
+ IBitmap? Source { get; }
}
}
diff --git a/src/Avalonia.Base/Media/ImageBrush.cs b/src/Avalonia.Base/Media/ImageBrush.cs
index 2f2a0fb627..718ebf1686 100644
--- a/src/Avalonia.Base/Media/ImageBrush.cs
+++ b/src/Avalonia.Base/Media/ImageBrush.cs
@@ -11,8 +11,8 @@ namespace Avalonia.Media
///
/// Defines the property.
///
- public static readonly StyledProperty SourceProperty =
- AvaloniaProperty.Register(nameof(Source));
+ public static readonly StyledProperty SourceProperty =
+ AvaloniaProperty.Register(nameof(Source));
static ImageBrush()
{
@@ -30,7 +30,7 @@ namespace Avalonia.Media
/// Initializes a new instance of the class.
///
/// The image to draw.
- public ImageBrush(IBitmap source)
+ public ImageBrush(IBitmap? source)
{
Source = source;
}
@@ -38,7 +38,7 @@ namespace Avalonia.Media
///
/// Gets or sets the image to draw.
///
- public IBitmap Source
+ public IBitmap? Source
{
get { return GetValue(SourceProperty); }
set { SetValue(SourceProperty, value); }
diff --git a/src/Avalonia.Base/Media/ImmediateDrawingContext.cs b/src/Avalonia.Base/Media/ImmediateDrawingContext.cs
index 7d9534c414..2564d89bac 100644
--- a/src/Avalonia.Base/Media/ImmediateDrawingContext.cs
+++ b/src/Avalonia.Base/Media/ImmediateDrawingContext.cs
@@ -281,11 +281,12 @@ namespace Avalonia.Media
/// Pushes an opacity value.
///
/// The opacity.
+ /// The bounds.
/// A disposable used to undo the opacity.
- public PushedState PushOpacity(double opacity)
+ public PushedState PushOpacity(double opacity, Rect bounds)
//TODO: Eliminate platform-specific push opacity call
{
- PlatformImpl.PushOpacity(opacity);
+ PlatformImpl.PushOpacity(opacity, bounds);
return new PushedState(this, PushedState.PushedStateType.Opacity);
}
diff --git a/src/Avalonia.Base/Media/Immutable/ImmutableImageBrush.cs b/src/Avalonia.Base/Media/Immutable/ImmutableImageBrush.cs
index f9892bf60c..668a907fdf 100644
--- a/src/Avalonia.Base/Media/Immutable/ImmutableImageBrush.cs
+++ b/src/Avalonia.Base/Media/Immutable/ImmutableImageBrush.cs
@@ -24,13 +24,13 @@ namespace Avalonia.Media.Immutable
/// The tile mode.
/// The bitmap interpolation mode.
public ImmutableImageBrush(
- IBitmap source,
+ IBitmap? source,
AlignmentX alignmentX = AlignmentX.Center,
AlignmentY alignmentY = AlignmentY.Center,
RelativeRect? destinationRect = null,
double opacity = 1,
ImmutableTransform? transform = null,
- RelativePoint transformOrigin = new RelativePoint(),
+ RelativePoint transformOrigin = default,
RelativeRect? sourceRect = null,
Stretch stretch = Stretch.Uniform,
TileMode tileMode = TileMode.None,
@@ -61,6 +61,6 @@ namespace Avalonia.Media.Immutable
}
///
- public IBitmap Source { get; }
+ public IBitmap? Source { get; }
}
}
diff --git a/src/Avalonia.Base/Media/Immutable/ImmutableVisualBrush.cs b/src/Avalonia.Base/Media/Immutable/ImmutableVisualBrush.cs
index 0b625080e3..e9086eee37 100644
--- a/src/Avalonia.Base/Media/Immutable/ImmutableVisualBrush.cs
+++ b/src/Avalonia.Base/Media/Immutable/ImmutableVisualBrush.cs
@@ -24,7 +24,7 @@ namespace Avalonia.Media.Immutable
/// The tile mode.
/// Controls the quality of interpolation.
public ImmutableVisualBrush(
- Visual visual,
+ Visual? visual,
AlignmentX alignmentX = AlignmentX.Center,
AlignmentY alignmentY = AlignmentY.Center,
RelativeRect? destinationRect = null,
diff --git a/src/Avalonia.Base/Platform/DefaultPlatformSettings.cs b/src/Avalonia.Base/Platform/DefaultPlatformSettings.cs
index b5e7298b7e..08fcdb50aa 100644
--- a/src/Avalonia.Base/Platform/DefaultPlatformSettings.cs
+++ b/src/Avalonia.Base/Platform/DefaultPlatformSettings.cs
@@ -37,7 +37,7 @@ namespace Avalonia.Platform
};
}
- public event EventHandler? ColorValuesChanged;
+ public virtual event EventHandler? ColorValuesChanged;
protected void OnColorValuesChanged(PlatformColorValues colorValues)
{
diff --git a/src/Avalonia.Base/Platform/IDrawingContextImpl.cs b/src/Avalonia.Base/Platform/IDrawingContextImpl.cs
index 8509067cd0..8962bc1586 100644
--- a/src/Avalonia.Base/Platform/IDrawingContextImpl.cs
+++ b/src/Avalonia.Base/Platform/IDrawingContextImpl.cs
@@ -128,7 +128,7 @@ namespace Avalonia.Platform
/// Pushes an opacity value.
///
/// The opacity.
- void PushOpacity(double opacity);
+ void PushOpacity(double opacity, Rect bounds);
///
/// Pops the latest pushed opacity value.
diff --git a/src/Avalonia.Base/Platform/IGeometryImpl.cs b/src/Avalonia.Base/Platform/IGeometryImpl.cs
index 5826cfb2ff..d1964bf07e 100644
--- a/src/Avalonia.Base/Platform/IGeometryImpl.cs
+++ b/src/Avalonia.Base/Platform/IGeometryImpl.cs
@@ -1,3 +1,4 @@
+using System.Diagnostics.CodeAnalysis;
using Avalonia.Media;
using Avalonia.Metadata;
@@ -47,7 +48,7 @@ namespace Avalonia.Platform
/// The stroke to use.
/// The point.
/// true if the geometry contains the point; otherwise, false.
- bool StrokeContains(IPen pen, Point point);
+ bool StrokeContains(IPen? pen, Point point);
///
/// Makes a clone of the geometry with the specified transform.
@@ -87,6 +88,7 @@ namespace Avalonia.Platform
/// If ture, the resulting snipped path will start with a BeginFigure call.
/// The resulting snipped path.
/// If the snipping operation is successful.
- bool TryGetSegment(double startDistance, double stopDistance, bool startOnBeginFigure, out IGeometryImpl segmentGeometry);
+ bool TryGetSegment(double startDistance, double stopDistance, bool startOnBeginFigure,
+ [NotNullWhen(true)] out IGeometryImpl? segmentGeometry);
}
}
diff --git a/src/Avalonia.Base/Platform/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 f9c7f9685d..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;
@@ -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/Rendering/Composition/Drawing/CompositionDrawingContext.cs b/src/Avalonia.Base/Rendering/Composition/Drawing/CompositionDrawingContext.cs
index b75d080cfd..6b380608fe 100644
--- a/src/Avalonia.Base/Rendering/Composition/Drawing/CompositionDrawingContext.cs
+++ b/src/Avalonia.Base/Rendering/Composition/Drawing/CompositionDrawingContext.cs
@@ -313,13 +313,13 @@ internal class CompositionDrawingContext : IDrawingContextImpl, IDrawingContextW
}
///
- public void PushOpacity(double opacity)
+ public void PushOpacity(double opacity, Rect bounds)
{
var next = NextDrawAs();
- if (next == null || !next.Item.Equals(opacity))
+ if (next == null || !next.Item.Equals(opacity, bounds))
{
- Add(new OpacityNode(opacity));
+ Add(new OpacityNode(opacity, bounds));
}
else
{
diff --git a/src/Avalonia.Base/Rendering/Composition/Server/DrawingContextProxy.cs b/src/Avalonia.Base/Rendering/Composition/Server/DrawingContextProxy.cs
index 50df8bd32b..08e506536f 100644
--- a/src/Avalonia.Base/Rendering/Composition/Server/DrawingContextProxy.cs
+++ b/src/Avalonia.Base/Rendering/Composition/Server/DrawingContextProxy.cs
@@ -111,9 +111,9 @@ internal class CompositorDrawingContextProxy : IDrawingContextImpl, IDrawingCont
_impl.PopClip();
}
- public void PushOpacity(double opacity)
+ public void PushOpacity(double opacity, Rect bounds)
{
- _impl.PushOpacity(opacity);
+ _impl.PushOpacity(opacity, bounds);
}
public void PopOpacity()
diff --git a/src/Avalonia.Base/Rendering/Composition/Server/ServerCompositionVisual.cs b/src/Avalonia.Base/Rendering/Composition/Server/ServerCompositionVisual.cs
index e33dc999dc..f9492d0015 100644
--- a/src/Avalonia.Base/Rendering/Composition/Server/ServerCompositionVisual.cs
+++ b/src/Avalonia.Base/Rendering/Composition/Server/ServerCompositionVisual.cs
@@ -41,27 +41,29 @@ namespace Avalonia.Rendering.Composition.Server
return;
Root!.RenderedVisuals++;
-
- if (Opacity != 1)
- canvas.PushOpacity(Opacity);
+
+ var boundsRect = new Rect(new Size(Size.X, Size.Y));
+
if (AdornedVisual != null)
{
canvas.PostTransform = Matrix.Identity;
canvas.Transform = Matrix.Identity;
- canvas.PushClip(AdornedVisual._combinedTransformedClipBounds);
+ if (AdornerIsClipped)
+ canvas.PushClip(AdornedVisual._combinedTransformedClipBounds);
}
var transform = GlobalTransformMatrix;
canvas.PostTransform = MatrixUtils.ToMatrix(transform);
canvas.Transform = Matrix.Identity;
-
- var boundsRect = new Rect(new Size(Size.X, Size.Y));
+
+ if (Opacity != 1)
+ canvas.PushOpacity(Opacity, boundsRect);
if (ClipToBounds && !HandlesClipToBounds)
canvas.PushClip(Root!.SnapToDevicePixels(boundsRect));
if (Clip != null)
canvas.PushGeometryClip(Clip);
if(OpacityMaskBrush != null)
canvas.PushOpacityMask(OpacityMaskBrush, boundsRect);
-
+
RenderCore(canvas, currentTransformedClip);
// Hack to force invalidation of SKMatrix
@@ -74,7 +76,7 @@ namespace Avalonia.Rendering.Composition.Server
canvas.PopGeometryClip();
if (ClipToBounds && !HandlesClipToBounds)
canvas.PopClip();
- if (AdornedVisual != null)
+ if (AdornedVisual != null && AdornerIsClipped)
canvas.PopClip();
if(Opacity != 1)
canvas.PopOpacity();
diff --git a/src/Avalonia.Base/Rendering/ImmediateRenderer.cs b/src/Avalonia.Base/Rendering/ImmediateRenderer.cs
index 8e5dc38317..09d2d55ce3 100644
--- a/src/Avalonia.Base/Rendering/ImmediateRenderer.cs
+++ b/src/Avalonia.Base/Rendering/ImmediateRenderer.cs
@@ -117,7 +117,7 @@ namespace Avalonia.Rendering
}
using (context.PushPostTransform(m))
- using (context.PushOpacity(opacity))
+ using (context.PushOpacity(opacity, bounds))
using (clipToBounds
#pragma warning disable CS0618 // Type or member is obsolete
? visual is IVisualWithRoundRectClip roundClipVisual
diff --git a/src/Avalonia.Base/Rendering/SceneGraph/OpacityNode.cs b/src/Avalonia.Base/Rendering/SceneGraph/OpacityNode.cs
index e41e639067..f76a055934 100644
--- a/src/Avalonia.Base/Rendering/SceneGraph/OpacityNode.cs
+++ b/src/Avalonia.Base/Rendering/SceneGraph/OpacityNode.cs
@@ -12,9 +12,11 @@ namespace Avalonia.Rendering.SceneGraph
/// opacity push.
///
/// The opacity to push.
- public OpacityNode(double opacity)
+ /// The bounds.
+ public OpacityNode(double opacity, Rect bounds)
{
Opacity = opacity;
+ Bounds = bounds;
}
///
@@ -26,7 +28,7 @@ namespace Avalonia.Rendering.SceneGraph
}
///
- public Rect Bounds => default;
+ public Rect Bounds { get; }
///
/// Gets the opacity to be pushed or null if the operation represents a pop.
@@ -40,19 +42,20 @@ namespace Avalonia.Rendering.SceneGraph
/// Determines if this draw operation equals another.
///
/// The opacity of the other draw operation.
+ /// The bounds of the other draw operation.
/// True if the draw operations are the same, otherwise false.
///
/// The properties of the other draw operation are passed in as arguments to prevent
/// allocation of a not-yet-constructed draw operation object.
///
- public bool Equals(double? opacity) => Opacity == opacity;
+ public bool Equals(double? opacity, Rect bounds) => Opacity == opacity && Bounds == bounds;
///
public void Render(IDrawingContextImpl context)
{
if (Opacity.HasValue)
{
- context.PushOpacity(Opacity.Value);
+ context.PushOpacity(Opacity.Value, Bounds);
}
else
{
diff --git a/src/Avalonia.Base/StyledElement.cs b/src/Avalonia.Base/StyledElement.cs
index a80351e654..405b874fed 100644
--- a/src/Avalonia.Base/StyledElement.cs
+++ b/src/Avalonia.Base/StyledElement.cs
@@ -498,13 +498,7 @@ namespace Avalonia
NotifyResourcesChanged();
}
-#nullable disable
- RaisePropertyChanged(
- ParentProperty,
- new Optional(old),
- new BindingValue(Parent),
- BindingPriority.LocalValue);
-#nullable enable
+ RaisePropertyChanged(ParentProperty, old, Parent);
}
}
diff --git a/src/Avalonia.Base/Utilities/WeakEvents.cs b/src/Avalonia.Base/Utilities/WeakEvents.cs
index 6da899bab2..2f62564e0e 100644
--- a/src/Avalonia.Base/Utilities/WeakEvents.cs
+++ b/src/Avalonia.Base/Utilities/WeakEvents.cs
@@ -2,6 +2,7 @@ using System;
using System.Collections.Specialized;
using System.ComponentModel;
using System.Windows.Input;
+using Avalonia.Threading;
namespace Avalonia.Utilities;
@@ -20,15 +21,30 @@ public class WeakEvents
});
///
- /// Represents PropertyChanged event from
+ /// Represents PropertyChanged event from with auto-dispatching to the UI thread
///
public static readonly WeakEvent
- PropertyChanged = WeakEvent.Register(
+ ThreadSafePropertyChanged = WeakEvent.Register(
(s, h) =>
{
- PropertyChangedEventHandler handler = (_, e) => h(s, e);
+ bool unsubscribed = false;
+ PropertyChangedEventHandler handler = (_, e) =>
+ {
+ if (Dispatcher.UIThread.CheckAccess())
+ h(s, e);
+ else
+ Dispatcher.UIThread.Post(() =>
+ {
+ if (!unsubscribed)
+ h(s, e);
+ });
+ };
s.PropertyChanged += handler;
- return () => s.PropertyChanged -= handler;
+ return () =>
+ {
+ unsubscribed = true;
+ s.PropertyChanged -= handler;
+ };
});
diff --git a/src/Avalonia.Base/Visual.cs b/src/Avalonia.Base/Visual.cs
index 87bb1d3790..8b0cc06136 100644
--- a/src/Avalonia.Base/Visual.cs
+++ b/src/Avalonia.Base/Visual.cs
@@ -573,7 +573,7 @@ namespace Avalonia
/// The new visual parent.
protected virtual void OnVisualParentChanged(Visual? oldParent, Visual? newParent)
{
- RaisePropertyChanged(VisualParentProperty, oldParent, newParent, BindingPriority.LocalValue);
+ RaisePropertyChanged(VisualParentProperty, oldParent, newParent);
}
internal override ParametrizedLogger? GetBindingWarningLogger(
diff --git a/src/Avalonia.Base/composition-schema.xml b/src/Avalonia.Base/composition-schema.xml
index 36fd9fe709..31722974ee 100644
--- a/src/Avalonia.Base/composition-schema.xml
+++ b/src/Avalonia.Base/composition-schema.xml
@@ -26,6 +26,7 @@
+
diff --git a/src/Avalonia.Controls/Automation/Peers/SliderAutomationPeer.cs b/src/Avalonia.Controls/Automation/Peers/SliderAutomationPeer.cs
new file mode 100644
index 0000000000..42b15eec96
--- /dev/null
+++ b/src/Avalonia.Controls/Automation/Peers/SliderAutomationPeer.cs
@@ -0,0 +1,22 @@
+using Avalonia.Automation.Peers;
+
+namespace Avalonia.Controls.Automation.Peers
+{
+ public class SliderAutomationPeer : RangeBaseAutomationPeer
+ {
+ public SliderAutomationPeer(Slider owner) : base(owner)
+ {
+ }
+
+ override protected string GetClassNameCore()
+ {
+ return "Slider";
+ }
+
+ override protected AutomationControlType GetAutomationControlTypeCore()
+ {
+ return AutomationControlType.Slider;
+ }
+
+ }
+}
diff --git a/src/Avalonia.Controls/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/Presenters/ScrollContentPresenter.cs b/src/Avalonia.Controls/Presenters/ScrollContentPresenter.cs
index 454f7eac9d..bc86558ab3 100644
--- a/src/Avalonia.Controls/Presenters/ScrollContentPresenter.cs
+++ b/src/Avalonia.Controls/Presenters/ScrollContentPresenter.cs
@@ -427,6 +427,7 @@ namespace Avalonia.Controls.Presenters
Viewport = finalSize;
Extent = Child!.Bounds.Size.Inflate(Child.Margin);
+ Offset = ScrollViewer.CoerceOffset(Extent, finalSize, Offset);
_isAnchorElementDirty = true;
return finalSize;
diff --git a/src/Avalonia.Controls/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/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/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/TopLevel.cs b/src/Avalonia.Controls/TopLevel.cs
index 90a28e3c2b..fdcb8cc537 100644
--- a/src/Avalonia.Controls/TopLevel.cs
+++ b/src/Avalonia.Controls/TopLevel.cs
@@ -413,7 +413,31 @@ namespace Avalonia.Controls
{
return visual?.VisualRoot as TopLevel;
}
-
+
+ ///
+ /// Requests a to be inhibited.
+ /// The behavior remains inhibited until the return value is disposed.
+ /// The available set of s depends on the platform.
+ /// If a behavior is inhibited on a platform where this type is not supported the request will have no effect.
+ ///
+ public async Task RequestPlatformInhibition(PlatformInhibitionType type, string reason)
+ {
+ var platformBehaviorInhibition = PlatformImpl?.TryGetFeature();
+ if (platformBehaviorInhibition == null)
+ {
+ return Disposable.Create(() => { });
+ }
+
+ switch (type)
+ {
+ case PlatformInhibitionType.AppSleep:
+ await platformBehaviorInhibition.SetInhibitAppSleep(true, reason);
+ return Disposable.Create(() => platformBehaviorInhibition.SetInhibitAppSleep(false, reason).Wait());
+ default:
+ return Disposable.Create(() => { });
+ }
+ }
+
protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs change)
{
base.OnPropertyChanged(change);
@@ -579,30 +603,6 @@ 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/VirtualizingStackPanel.cs b/src/Avalonia.Controls/VirtualizingStackPanel.cs
index 634efbd699..8e7690aa6c 100644
--- a/src/Avalonia.Controls/VirtualizingStackPanel.cs
+++ b/src/Avalonia.Controls/VirtualizingStackPanel.cs
@@ -403,7 +403,7 @@ namespace Avalonia.Controls
if (firstIndex == -1)
{
estimatedElementSize = EstimateElementSizeU();
- firstIndex = (int)(viewportStart / estimatedElementSize);
+ firstIndex = Math.Min((int)(viewportStart / estimatedElementSize), maxIndex);
firstIndexU = firstIndex * estimatedElementSize;
}
@@ -411,13 +411,13 @@ namespace Avalonia.Controls
{
if (estimatedElementSize == -1)
estimatedElementSize = EstimateElementSizeU();
- lastIndex = (int)(viewportEnd / estimatedElementSize);
+ lastIndex = Math.Min((int)(viewportEnd / estimatedElementSize), maxIndex);
}
return new MeasureViewport
{
- firstIndex = MathUtilities.Clamp(firstIndex, 0, maxIndex),
- lastIndex = MathUtilities.Clamp(lastIndex, 0, maxIndex),
+ firstIndex = firstIndex,
+ lastIndex = lastIndex,
viewportUStart = viewportStart,
viewportUEnd = viewportEnd,
startU = firstIndexU,
@@ -1131,6 +1131,7 @@ namespace Avalonia.Controls
// The removed range was before the realized elements. Update the first index and
// the indexes of the realized elements.
_firstIndex -= count;
+ _startUUnstable = true;
var newIndex = _firstIndex;
for (var i = 0; i < _elements.Count; ++i)
diff --git a/src/Avalonia.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 @@