Browse Source

Merge branch 'master' into feature/untyped-property-observable-converter

pull/12160/head
Steven Kirk 3 years ago
committed by GitHub
parent
commit
159932a6d6
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 10
      api/Avalonia.nupkg.xml
  2. 8
      build/SourceLink.props
  3. 18
      native/Avalonia.Native/src/OSX/app.mm
  4. 1
      native/Avalonia.Native/src/OSX/common.h
  5. 8
      native/Avalonia.Native/src/OSX/main.mm
  6. 5
      nukebuild/ApiDiffValidation.cs
  7. 2
      nukebuild/Build.cs
  8. 2
      nukebuild/Numerge
  9. 12
      nukebuild/_build.csproj
  10. 26
      packages/Avalonia/AvaloniaBuildTasks.targets
  11. 2
      readme.md
  12. 6
      src/Avalonia.Base/Avalonia.Base.csproj
  13. 5
      src/Avalonia.Base/Interactivity/EventRoute.cs
  14. 6
      src/Avalonia.Base/Media/GlyphRun.cs
  15. 321
      src/Avalonia.Base/Media/TextFormatting/TextLineImpl.cs
  16. 13
      src/Avalonia.Base/Rendering/Composition/CompositionExternalMemory.cs
  17. 8
      src/Avalonia.Base/Rendering/Composition/CompositionInterop.cs
  18. 21
      src/Avalonia.Base/Rendering/Composition/Drawing/ServerCompositionRenderData.cs
  19. 6
      src/Avalonia.Base/Rendering/Composition/Server/ServerCompositionDrawingSurface.cs
  20. 7
      src/Avalonia.Base/Threading/AvaloniaSynchronizationContext.cs
  21. 16
      src/Avalonia.Base/Threading/Dispatcher.Invoke.cs
  22. 2
      src/Avalonia.Base/Threading/Dispatcher.MainLoop.cs
  23. 20
      src/Avalonia.Base/Threading/Dispatcher.Queue.cs
  24. 2
      src/Avalonia.Base/Threading/Dispatcher.Timers.cs
  25. 3
      src/Avalonia.Base/Threading/Dispatcher.cs
  26. 8
      src/Avalonia.Base/Threading/DispatcherFrame.cs
  27. 2
      src/Avalonia.Base/Threading/DispatcherOperation.cs
  28. 16
      src/Avalonia.Base/Threading/DispatcherTimer.cs
  29. 16
      src/Avalonia.Base/Utilities/SmallDictionary.cs
  30. 4
      src/Avalonia.Controls.DataGrid/Collections/DataGridCollectionView.cs
  31. 2
      src/Avalonia.Controls.DataGrid/DataGridCheckBoxColumn.cs
  32. 2
      src/Avalonia.Controls.DataGrid/DataGridColumnCollection.cs
  33. 2
      src/Avalonia.Controls.DataGrid/DataGridLength.cs
  34. 2
      src/Avalonia.Controls/Automation/Peers/ControlAutomationPeer.cs
  35. 4
      src/Avalonia.Controls/Automation/Peers/ScrollViewerAutomationPeer.cs
  36. 10
      src/Avalonia.Controls/ComboBox.cs
  37. 4
      src/Avalonia.Controls/DateTimePickers/DateTimePickerPanel.cs
  38. 4
      src/Avalonia.Controls/NumericUpDown/NumericUpDown.cs
  39. 4
      src/Avalonia.Controls/Primitives/SelectingItemsControl.cs
  40. 10
      src/Avalonia.Controls/PullToRefresh/ScrollViewerIRefreshInfoProviderAdapter.cs
  41. 22
      src/Avalonia.Controls/Utils/StringUtils.cs
  42. 2
      src/Avalonia.Controls/Utils/VirtualizingSnapPointsList.cs
  43. 10
      src/Avalonia.Native/AvaloniaNativePlatform.cs
  44. 2
      src/Avalonia.Native/NativePlatformSettings.cs
  45. 8
      src/Avalonia.Remote.Protocol/MetsysBson.cs
  46. 50
      src/Avalonia.X11/X11Clipboard.cs
  47. 5
      src/Avalonia.X11/X11Structs.cs
  48. 3
      src/Windows/Avalonia.Win32/Automation/RootAutomationNode.cs
  49. 3
      src/Windows/Avalonia.Win32/WinRT/Composition/D2DEffects.cs
  50. 104
      src/Windows/Avalonia.Win32/WinRT/Composition/WinUIEffectBase.cs
  51. 30
      src/Windows/Avalonia.Win32/WinRT/Composition/WinUiCompositedWindow.cs
  52. 9
      src/Windows/Avalonia.Win32/WinRT/Composition/WinUiCompositionShared.cs
  53. 72
      src/Windows/Avalonia.Win32/WinRT/Composition/WinUiCompositionUtils.cs
  54. 3
      src/Windows/Avalonia.Win32/WinRT/IBlurHost.cs
  55. 23
      src/Windows/Avalonia.Win32/WinRT/WinRTPropertyValue.cs
  56. 13
      src/Windows/Avalonia.Win32/WindowImpl.cs
  57. 1
      src/tools/Avalonia.Analyzers/Avalonia.Analyzers.csproj
  58. 1
      src/tools/Avalonia.Generators/Avalonia.Generators.csproj
  59. 2
      tests/Avalonia.Base.UnitTests/Media/GlyphRunTests.cs
  60. 53
      tests/Avalonia.Base.UnitTests/Utilities/InlineDictionaryTests.cs
  61. 16
      tests/Avalonia.Controls.UnitTests/Primitives/SelectingItemsControlTests_SelectedValue.cs
  62. 116
      tests/Avalonia.Skia.UnitTests/Media/TextFormatting/TextLineTests.cs

10
api/Avalonia.nupkg.xml

@ -0,0 +1,10 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- https://learn.microsoft.com/en-us/dotnet/fundamentals/package-validation/diagnostic-ids -->
<Suppressions xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<Suppression>
<DiagnosticId>CP0006</DiagnosticId>
<Target>P:Avalonia.Rendering.Composition.ICompositionGpuImportedObject.ImportCompleted</Target>
<Left>baseline/netstandard2.0/Avalonia.Base.dll</Left>
<Right>target/netstandard2.0/Avalonia.Base.dll</Right>
</Suppression>
</Suppressions>

8
build/SourceLink.props

@ -1,9 +1,9 @@
<Project>
<PropertyGroup>
<PublishRepositoryUrl>true</PublishRepositoryUrl>
<IncludeSymbols>false</IncludeSymbols>
<IncludeSymbols>true</IncludeSymbols>
<SymbolPackageFormat>snupkg</SymbolPackageFormat>
<EmbedUntrackedSources>true</EmbedUntrackedSources>
<AllowedOutputExtensionsInPackageBuildOutputFolder>$(AllowedOutputExtensionsInPackageBuildOutputFolder);.pdb</AllowedOutputExtensionsInPackageBuildOutputFolder>
</PropertyGroup>
<PropertyGroup Condition="'$(TF_BUILD)' == 'true'">
@ -14,10 +14,6 @@
<ContinuousIntegrationBuild>true</ContinuousIntegrationBuild>
</PropertyGroup>
<PropertyGroup>
<DebugType Condition="$(ContinuousIntegrationBuild) == 'true'">embedded</DebugType>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.SourceLink.GitHub" Version="1.1.1" PrivateAssets="All"/>
</ItemGroup>

18
native/Avalonia.Native/src/OSX/app.mm

@ -2,6 +2,7 @@
#include "AvnString.h"
@interface AvnAppDelegate : NSObject<NSApplicationDelegate>
-(AvnAppDelegate* _Nonnull) initWithEvents: (IAvnApplicationEvents* _Nonnull) events;
-(void) releaseEvents;
@end
NSApplicationActivationPolicy AvnDesiredActivationPolicy = NSApplicationActivationPolicyRegular;
@ -15,6 +16,11 @@ ComPtr<IAvnApplicationEvents> _events;
return self;
}
- (void)releaseEvents
{
_events = nil;
}
- (void)applicationWillFinishLaunching:(NSNotification *)notification
{
if([[NSApplication sharedApplication] activationPolicy] != AvnDesiredActivationPolicy)
@ -105,6 +111,18 @@ extern void InitializeAvnApp(IAvnApplicationEvents* events, bool disableAppDeleg
}
}
extern void ReleaseAvnAppEvents()
{
NSApplication* app = [AvnApplication sharedApplication];
id delegate = [app delegate];
if ([delegate isMemberOfClass:[AvnAppDelegate class]])
{
AvnAppDelegate* avnDelegate = delegate;
[avnDelegate releaseEvents];
[app setDelegate:nil];
}
}
HRESULT AvnApplicationCommands::HideApp()
{
START_COM_CALL;

1
native/Avalonia.Native/src/OSX/common.h

@ -38,6 +38,7 @@ extern IAvnMenu* GetAppMenu ();
extern NSMenuItem* GetAppMenuItem ();
extern void InitializeAvnApp(IAvnApplicationEvents* events, bool disableAppDelegate);
extern void ReleaseAvnAppEvents();
extern NSApplicationActivationPolicy AvnDesiredActivationPolicy;
extern NSPoint ToNSPoint (AvnPoint p);
extern NSRect ToNSRect (AvnRect r);

8
native/Avalonia.Native/src/OSX/main.mm

@ -197,6 +197,14 @@ class AvaloniaNative : public ComSingleObject<IAvaloniaNativeFactory, &IID_IAval
public:
FORWARD_IUNKNOWN()
virtual ~AvaloniaNative() override
{
ReleaseAvnAppEvents();
_deallocator = nullptr;
_dispatcher = nullptr;
}
virtual HRESULT Initialize(IAvnGCHandleDeallocatorCallback* deallocator,
IAvnApplicationEvents* events,
IAvnDispatcher* dispatcher) override

5
nukebuild/ApiDiffValidation.cs

@ -38,7 +38,8 @@ public static class ApiDiffValidation
var left = new List<string>();
var right = new List<string>();
var suppressionFile = Path.Combine(suppressionFilesFolder, GetPackageId(packagePath) + ".nupkg.xml");
var packageId = GetPackageId(packagePath);
var suppressionFile = Path.Combine(suppressionFilesFolder, packageId + ".nupkg.xml");
// Don't use Path.Combine with these left and right tool parameters.
// Microsoft.DotNet.ApiCompat.Tool is stupid and treats '/' and '\' as different assemblies in suppression files.
@ -57,7 +58,7 @@ public static class ApiDiffValidation
e.target == baselineDll.target && e.entry.Name == baselineDll.entry.Name);
if (targetDll.entry is null)
{
throw new InvalidOperationException($"Some assemblies are missing in the new package: {baselineDll.entry.Name} for {baselineDll.target}");
throw new InvalidOperationException($"Some assemblies are missing in the new package {packageId}: {baselineDll.entry.Name} for {baselineDll.target}");
}
var targetDllPath = $"target/{targetDll.target}/{targetDll.entry.Name}";

2
nukebuild/Build.cs

@ -288,7 +288,7 @@ partial class Build : NukeBuild
.Executes(async () =>
{
await Task.WhenAll(
Directory.GetFiles(Parameters.NugetRoot).Select(nugetPackage => ApiDiffValidation.ValidatePackage(
Directory.GetFiles(Parameters.NugetRoot, "*.nupkg").Select(nugetPackage => ApiDiffValidation.ValidatePackage(
ApiCompatTool, nugetPackage, Parameters.ApiValidationBaseline,
Parameters.ApiValidationSuppressionFiles, Parameters.UpdateApiValidationSuppression)));
});

2
nukebuild/Numerge

@ -1 +1 @@
Subproject commit aef10ae67dc55c95f49b52a505a0be33bfa297a5
Subproject commit 9738c6121fdd143c78d3e25686a7e4e13c00f586

12
nukebuild/_build.csproj

@ -8,8 +8,8 @@
<NukeTelemetryVersion>1</NukeTelemetryVersion>
<TargetFramework>net7.0</TargetFramework>
</PropertyGroup>
<Import Project="..\build\JetBrains.dotMemoryUnit.props" />
<Import Project="..\build\JetBrains.dotMemoryUnit.props" />
<ItemGroup>
<PackageReference Include="Nuke.Common" Version="6.2.1" />
<PackageReference Include="vswhere" Version="2.6.7" Condition=" '$(OS)' == 'Windows_NT' " />
@ -32,9 +32,9 @@
<!-- Common build related files -->
<Compile Remove="Numerge/**/*.*" />
<Compile Include="Numerge/Numerge/**/*.cs" />
<EmbeddedResource Include="$(NuGetPackageRoot)sourcelink/1.1.0/tools/pdbstr.exe"></EmbeddedResource>
<EmbeddedResource Include="../build/avalonia.snk"></EmbeddedResource>
<Compile Include="Numerge/Numerge/**/*.cs" Exclude="Numerge/Numerge/obj/**/*.cs" />
<EmbeddedResource Include="$(NuGetPackageRoot)sourcelink/1.1.0/tools/pdbstr.exe" />
<EmbeddedResource Include="../build/avalonia.snk" />
<Compile Remove="il-repack\ILRepack\Application.cs" />
</ItemGroup>
@ -43,7 +43,5 @@
<Link>dirs.proj</Link>
</Content>
</ItemGroup>
</Project>

26
packages/Avalonia/AvaloniaBuildTasks.targets

@ -144,4 +144,30 @@
<UpToDateCheckInput Include="@(AvaloniaResource)" />
<UpToDateCheckInput Include="@(AvaloniaXaml)" />
</ItemGroup>
<PropertyGroup>
<AvaloniaFilePreviewDependsOn Condition="'$(SkipBuild)'!='True'">Build</AvaloniaFilePreviewDependsOn>
</PropertyGroup>
<Target Name="AvaloniaFilePreview" DependsOnTargets="$(AvaloniaFilePreviewDependsOn)">
<PropertyGroup>
<APreviewerUrl>http://127.0.0.1:6001</APreviewerUrl>
<APreviewExecutable>$(OutputPath)/$(AssemblyName).dll</APreviewExecutable>
<APreviewFile Condition="$(APreviewFile) == ''">MainWindow.axaml</APreviewFile>
<APreviewAssembly Condition="$(APreviewAssembly) == ''">$(APreviewExecutable)</APreviewAssembly>
<APreviewerDepsJsonPath>$([System.IO.Path]::ChangeExtension('$(APreviewExecutable)', '.deps.json'))</APreviewerDepsJsonPath>
<APreviewerRuntimeConfigPath>$([System.IO.Path]::ChangeExtension('$(APreviewExecutable)', '.runtimeconfig.json'))</APreviewerRuntimeConfigPath>
<APreviewTransportUrl>$([System.IO.Path]::GetFullPath('$(APreviewFile)'))</APreviewTransportUrl>
</PropertyGroup>
<Message Importance="high" Text="Launching previewer for"/>
<Message Importance="high" Text="File (APreviewFile): $(APreviewTransportUrl)"/>
<Message Importance="high" Text="Containing assembly (APreviewAssembly): $(APreviewDefiningAssembly)"/>
<Message Importance="high" Text="Executable: $(APreviewExecutable)"/>
<Message Importance="high" Text="Url (APreviewerUrl): $(APreviewerUrl)"/>
<Exec Command="dotnet exec --runtimeconfig &quot;$(APreviewerRuntimeConfigPath)&quot; --depsfile &quot;$(APreviewerDepsJsonPath)&quot; &quot;$(AvaloniaPreviewerNetCoreToolPath)&quot; --method html --html-url $(APreviewerUrl) --transport $(APreviewTransportUrl) &quot;$(APreviewExecutable)&quot;"/>
</Target>
</Project>

2
readme.md

@ -15,7 +15,7 @@ Considered by many to be the spiritual successor to WPF, Avalonia UI provides a
For those seeking a cross-platform WPF, we have created [Avalonia XPF](https://avaloniaui.net/xpf), enabling WPF applications to run on macOS and Linux with little to no code changes. Avalonia XPF is a commercial product and is licensed per-app, per-platform.
#### Roadmap
To see the status of some of our features, please see our [Roadmap](https://github.com/AvaloniaUI/Avalonia/issues/2239).
To see the status of some of our features, look at our [Roadmap](https://github.com/AvaloniaUI/Avalonia/issues/2239).
#### Breaking Changes
You can also see what [breaking changes](https://github.com/AvaloniaUI/Avalonia/issues/3538) we have planned and what our [past breaking changes](https://github.com/AvaloniaUI/Avalonia/wiki/Breaking-Changes) have been.

6
src/Avalonia.Base/Avalonia.Base.csproj

@ -43,21 +43,15 @@
<InternalsVisibleTo Include="Avalonia.FreeDesktop, PublicKey=$(AvaloniaPublicKey)" />
<InternalsVisibleTo Include="Avalonia.X11, PublicKey=$(AvaloniaPublicKey)" />
<InternalsVisibleTo Include="Avalonia.Browser, PublicKey=$(AvaloniaPublicKey)" />
<InternalsVisibleTo Include="Avalonia.OpenGL, PublicKey=$(AvaloniaPublicKey)" />
<InternalsVisibleTo Include="Avalonia.Skia, PublicKey=$(AvaloniaPublicKey)" />
<InternalsVisibleTo Include="Avalonia.Controls.UnitTests, PublicKey=$(AvaloniaPublicKey)" />
<InternalsVisibleTo Include="Avalonia.DesignerSupport, PublicKey=$(AvaloniaPublicKey)" />
<InternalsVisibleTo Include="Avalonia.Direct2D1.RenderTests, PublicKey=$(AvaloniaPublicKey)" />
<InternalsVisibleTo Include="Avalonia.LeakTests, PublicKey=$(AvaloniaPublicKey)" />
<InternalsVisibleTo Include="Avalonia.Markup, PublicKey=$(AvaloniaPublicKey)" />
<InternalsVisibleTo Include="Avalonia.Markup.Xaml, PublicKey=$(AvaloniaPublicKey)" />
<InternalsVisibleTo Include="Avalonia.Markup.UnitTests, PublicKey=$(AvaloniaPublicKey)" />
<InternalsVisibleTo Include="Avalonia.Markup.Xaml.UnitTests, PublicKey=$(AvaloniaPublicKey)" />
<InternalsVisibleTo Include="Avalonia.Skia.RenderTests, PublicKey=$(AvaloniaPublicKey)" />
<InternalsVisibleTo Include="Avalonia.Skia.UnitTests, PublicKey=$(AvaloniaPublicKey)" />
<InternalsVisibleTo Include="Avalonia.UnitTests, PublicKey=$(AvaloniaPublicKey)" />
<InternalsVisibleTo Include="Avalonia.Benchmarks, PublicKey=$(AvaloniaPublicKey)" />
<InternalsVisibleTo Include="Avalonia.X11, PublicKey=$(AvaloniaPublicKey)" />
<InternalsVisibleTo Include="Avalonia.Win32, PublicKey=$(AvaloniaPublicKey)" />
<InternalsVisibleTo Include="Avalonia.Android, PublicKey=$(AvaloniaPublicKey)" />
<InternalsVisibleTo Include="Avalonia.iOS, PublicKey=$(AvaloniaPublicKey)" />

5
src/Avalonia.Base/Interactivity/EventRoute.cs

@ -120,11 +120,6 @@ namespace Avalonia.Interactivity
return;
}
if (e.Source is null)
{
throw new ArgumentException("Event source may not be null", nameof(e));
}
Interactive? lastTarget = null;
var start = 0;
var end = _route.Count;

6
src/Avalonia.Base/Media/GlyphRun.cs

@ -424,13 +424,13 @@ namespace Avalonia.Media
/// </returns>
public CharacterHit GetPreviousCaretCharacterHit(CharacterHit characterHit)
{
var previousCharacterHit = FindNearestCharacterHit(characterHit.FirstCharacterIndex - 1, out _);
if (characterHit.TrailingLength != 0)
{
return new CharacterHit(characterHit.FirstCharacterIndex);
return previousCharacterHit;
}
var previousCharacterHit = FindNearestCharacterHit(characterHit.FirstCharacterIndex - 1, out _);
return new CharacterHit(previousCharacterHit.FirstCharacterIndex);
}

321
src/Avalonia.Base/Media/TextFormatting/TextLineImpl.cs

@ -9,7 +9,7 @@ namespace Avalonia.Media.TextFormatting
internal static Comparer<TextBounds> TextBoundsComparer { get; } =
Comparer<TextBounds>.Create((x, y) => x.Rectangle.Left.CompareTo(y.Rectangle.Left));
private IReadOnlyList<IndexedTextRun>? _indexedTextRuns;
internal IReadOnlyList<IndexedTextRun>? _indexedTextRuns;
private readonly TextRun[] _textRuns;
private readonly double _paragraphWidth;
private readonly TextParagraphProperties _paragraphProperties;
@ -512,38 +512,45 @@ namespace Avalonia.Media.TextFormatting
/// <inheritdoc/>
public override CharacterHit GetNextCaretCharacterHit(CharacterHit characterHit)
{
if (_textRuns.Length == 0)
if (_textRuns.Length == 0 || _indexedTextRuns is null)
{
return new CharacterHit();
}
if (TryFindNextCharacterHit(characterHit, out var nextCharacterHit))
{
return nextCharacterHit;
}
var lastTextPosition = FirstTextSourceIndex + Length;
var currentCharacterrHit = characterHit;
var characterIndex = characterHit.FirstCharacterIndex + characterHit.TrailingLength;
// Can't move, we're after the last character
var runIndex = GetRunIndexAtCharacterIndex(lastTextPosition, LogicalDirection.Forward, out var currentPosition);
var currentRun = GetRunAtCharacterIndex(characterIndex, LogicalDirection.Forward, out var currentPosition);
var currentRun = _textRuns[runIndex];
var nextCharacterHit = characterHit;
switch (currentRun)
{
case ShapedTextRun shapedRun:
{
nextCharacterHit = shapedRun.GlyphRun.GetNextCaretCharacterHit(characterHit);
var offset = Math.Max(0, currentPosition - shapedRun.GlyphRun.Metrics.FirstCluster - characterHit.TrailingLength);
if (offset > 0)
{
currentCharacterrHit = new CharacterHit(Math.Max(0, characterHit.FirstCharacterIndex - offset), characterHit.TrailingLength);
}
nextCharacterHit = shapedRun.GlyphRun.GetNextCaretCharacterHit(currentCharacterrHit);
if (offset > 0)
{
nextCharacterHit = new CharacterHit(nextCharacterHit.FirstCharacterIndex + offset, nextCharacterHit.TrailingLength);
}
break;
}
default:
case TextRun:
{
nextCharacterHit = new CharacterHit(currentPosition + currentRun.Length);
break;
}
}
if (characterHit.FirstCharacterIndex + characterHit.TrailingLength == nextCharacterHit.FirstCharacterIndex + nextCharacterHit.TrailingLength)
if (characterIndex == nextCharacterHit.FirstCharacterIndex + nextCharacterHit.TrailingLength)
{
return characterHit;
}
@ -554,17 +561,75 @@ namespace Avalonia.Media.TextFormatting
/// <inheritdoc/>
public override CharacterHit GetPreviousCaretCharacterHit(CharacterHit characterHit)
{
if (TryFindPreviousCharacterHit(characterHit, out var previousCharacterHit))
if (_textRuns.Length == 0 || _indexedTextRuns is null)
{
return new CharacterHit();
}
if (characterHit.TrailingLength > 0 && characterHit.FirstCharacterIndex <= FirstTextSourceIndex)
{
return new CharacterHit(FirstTextSourceIndex);
}
var characterIndex = characterHit.FirstCharacterIndex + characterHit.TrailingLength;
if (characterIndex <= FirstTextSourceIndex)
{
return previousCharacterHit;
return new CharacterHit(FirstTextSourceIndex);
}
var currentCharacterrHit = characterHit;
var currentRun = GetRunAtCharacterIndex(characterIndex, LogicalDirection.Backward, out var currentPosition);
if (currentPosition == characterHit.FirstCharacterIndex)
{
currentRun = GetRunAtCharacterIndex(characterHit.FirstCharacterIndex, LogicalDirection.Backward, out currentPosition);
}
var previousCharacterHit = characterHit;
switch (currentRun)
{
case ShapedTextRun shapedRun:
{
var offset = Math.Max(0, currentPosition - shapedRun.GlyphRun.Metrics.FirstCluster);
if (offset > 0)
{
currentCharacterrHit = new CharacterHit(Math.Max(0, characterHit.FirstCharacterIndex - offset), characterHit.TrailingLength);
}
previousCharacterHit = shapedRun.GlyphRun.GetPreviousCaretCharacterHit(currentCharacterrHit);
if (offset > 0)
{
previousCharacterHit = new CharacterHit(previousCharacterHit.FirstCharacterIndex + offset, previousCharacterHit.TrailingLength);
}
break;
}
case TextRun:
{
if (characterHit.TrailingLength > 0)
{
previousCharacterHit = new CharacterHit(currentPosition, currentRun.Length);
}
else
{
previousCharacterHit = new CharacterHit(currentPosition + currentRun.Length);
}
break;
}
}
if (characterHit.FirstCharacterIndex <= FirstTextSourceIndex)
if (characterIndex == previousCharacterHit.FirstCharacterIndex + previousCharacterHit.TrailingLength)
{
characterHit = new CharacterHit(FirstTextSourceIndex);
return characterHit;
}
return characterHit; // Can't move, we're before the first character
return previousCharacterHit;
}
/// <inheritdoc/>
@ -1009,161 +1074,7 @@ namespace Avalonia.Media.TextFormatting
if (_textLineBreak is null && _textRuns.Length > 1 && _textRuns[_textRuns.Length - 1] is TextEndOfLine textEndOfLine)
{
_textLineBreak = new TextLineBreak(textEndOfLine);
}
}
/// <summary>
/// Tries to find the next character hit.
/// </summary>
/// <param name="characterHit">The current character hit.</param>
/// <param name="nextCharacterHit">The next character hit.</param>
/// <returns></returns>
private bool TryFindNextCharacterHit(CharacterHit characterHit, out CharacterHit nextCharacterHit)
{
nextCharacterHit = characterHit;
var codepointIndex = characterHit.FirstCharacterIndex + characterHit.TrailingLength;
var lastCodepointIndex = FirstTextSourceIndex + Length;
if (codepointIndex >= lastCodepointIndex)
{
return false; // Cannot go forward anymore
}
if (codepointIndex < FirstTextSourceIndex)
{
codepointIndex = FirstTextSourceIndex;
}
var runIndex = GetRunIndexAtCharacterIndex(codepointIndex, LogicalDirection.Forward, out var currentPosition);
while (runIndex < _textRuns.Length)
{
var currentRun = _textRuns[runIndex];
switch (currentRun)
{
case ShapedTextRun shapedRun:
{
var foundCharacterHit = shapedRun.GlyphRun.FindNearestCharacterHit(characterHit.FirstCharacterIndex + characterHit.TrailingLength, out _);
var isAtEnd = foundCharacterHit.FirstCharacterIndex + foundCharacterHit.TrailingLength == FirstTextSourceIndex + Length;
if (isAtEnd && !shapedRun.GlyphRun.IsLeftToRight)
{
nextCharacterHit = foundCharacterHit;
return true;
}
nextCharacterHit = isAtEnd || characterHit.TrailingLength != 0 ?
foundCharacterHit :
new CharacterHit(foundCharacterHit.FirstCharacterIndex + foundCharacterHit.TrailingLength);
if (isAtEnd || nextCharacterHit.FirstCharacterIndex > characterHit.FirstCharacterIndex)
{
return true;
}
break;
}
default:
{
var textPosition = characterHit.FirstCharacterIndex + characterHit.TrailingLength;
if (textPosition == currentPosition)
{
nextCharacterHit = new CharacterHit(currentPosition + currentRun.Length);
return true;
}
break;
}
}
currentPosition += currentRun.Length;
runIndex++;
}
return false;
}
/// <summary>
/// Tries to find the previous character hit.
/// </summary>
/// <param name="characterHit">The current character hit.</param>
/// <param name="previousCharacterHit">The previous character hit.</param>
/// <returns></returns>
private bool TryFindPreviousCharacterHit(CharacterHit characterHit, out CharacterHit previousCharacterHit)
{
var characterIndex = characterHit.FirstCharacterIndex + characterHit.TrailingLength;
if (characterIndex == FirstTextSourceIndex)
{
previousCharacterHit = new CharacterHit(FirstTextSourceIndex);
return true;
}
previousCharacterHit = characterHit;
if (characterIndex < FirstTextSourceIndex)
{
return false; // Cannot go backward anymore.
}
var runIndex = GetRunIndexAtCharacterIndex(characterIndex, LogicalDirection.Backward, out var currentPosition);
while (runIndex >= 0)
{
var currentRun = _textRuns[runIndex];
switch (currentRun)
{
case ShapedTextRun shapedRun:
{
var foundCharacterHit = shapedRun.GlyphRun.FindNearestCharacterHit(characterHit.FirstCharacterIndex - 1, out _);
if (foundCharacterHit.FirstCharacterIndex + foundCharacterHit.TrailingLength < characterIndex)
{
previousCharacterHit = foundCharacterHit;
return true;
}
var previousPosition = foundCharacterHit.FirstCharacterIndex + foundCharacterHit.TrailingLength;
if (foundCharacterHit.TrailingLength > 0 && previousPosition == characterIndex)
{
previousCharacterHit = new CharacterHit(foundCharacterHit.FirstCharacterIndex);
}
if (previousCharacterHit != characterHit)
{
return true;
}
break;
}
default:
{
if (characterIndex == currentPosition + currentRun.Length)
{
previousCharacterHit = new CharacterHit(currentPosition);
return true;
}
break;
}
}
currentPosition -= currentRun.Length;
runIndex--;
}
return false;
}
/// <summary>
@ -1173,15 +1084,23 @@ namespace Avalonia.Media.TextFormatting
/// <param name="direction">The logical direction.</param>
/// <param name="textPosition">The text position of the found run index.</param>
/// <returns>The text run index.</returns>
private int GetRunIndexAtCharacterIndex(int codepointIndex, LogicalDirection direction, out int textPosition)
private TextRun? GetRunAtCharacterIndex(int codepointIndex, LogicalDirection direction, out int textPosition)
{
var runIndex = 0;
textPosition = FirstTextSourceIndex;
if (_indexedTextRuns is null)
{
return null;
}
TextRun? currentRun = null;
TextRun? previousRun = null;
while (runIndex < _textRuns.Length)
while (runIndex < _indexedTextRuns.Count)
{
var currentRun = _textRuns[runIndex];
var indexedRun = _indexedTextRuns[runIndex];
currentRun = indexedRun.TextRun;
switch (currentRun)
{
@ -1189,64 +1108,49 @@ namespace Avalonia.Media.TextFormatting
{
var firstCluster = shapedRun.GlyphRun.Metrics.FirstCluster;
if (firstCluster > codepointIndex)
{
break;
}
if (previousRun is ShapedTextRun previousShaped && !previousShaped.ShapedBuffer.IsLeftToRight)
{
if (shapedRun.ShapedBuffer.IsLeftToRight)
{
if (firstCluster >= codepointIndex)
{
return --runIndex;
}
}
else
{
if (codepointIndex > firstCluster + currentRun.Length)
{
return --runIndex;
}
}
}
firstCluster += Math.Max(0, indexedRun.TextSourceCharacterIndex - firstCluster);
if (direction == LogicalDirection.Forward)
{
if (codepointIndex >= firstCluster && codepointIndex <= firstCluster + currentRun.Length)
if (codepointIndex >= firstCluster && codepointIndex < firstCluster + currentRun.Length)
{
return runIndex;
return currentRun;
}
}
else
{
if (codepointIndex > firstCluster &&
codepointIndex <= firstCluster + currentRun.Length)
if (previousRun is not null && previousRun is not ShapedTextRun && codepointIndex == textPosition + firstCluster)
{
textPosition -= previousRun.Length;
return previousRun;
}
if (codepointIndex > firstCluster && codepointIndex <= firstCluster + currentRun.Length)
{
return runIndex;
return currentRun;
}
}
if (runIndex + 1 >= _textRuns.Length)
{
return runIndex;
return currentRun;
}
textPosition += currentRun.Length;
break;
}
default:
case TextRun:
{
if (codepointIndex == textPosition)
{
return runIndex;
return currentRun;
}
if (runIndex + 1 >= _textRuns.Length)
{
return runIndex;
return currentRun;
}
textPosition += currentRun.Length;
@ -1257,10 +1161,11 @@ namespace Avalonia.Media.TextFormatting
}
runIndex++;
previousRun = currentRun;
}
return runIndex;
return currentRun;
}
private TextLineMetrics CreateLineMetrics()

13
src/Avalonia.Base/Rendering/Composition/CompositionExternalMemory.cs

@ -1,10 +1,13 @@
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Threading.Tasks;
using Avalonia.Metadata;
using Avalonia.Platform;
namespace Avalonia.Rendering.Composition;
[NotClientImplementable]
public interface ICompositionGpuInterop
{
/// <summary>
@ -84,14 +87,22 @@ public enum CompositionGpuImportedImageSynchronizationCapabilities
/// <summary>
/// An imported GPU object that's usable by composition APIs
/// </summary>
[NotClientImplementable]
public interface ICompositionGpuImportedObject : IAsyncDisposable
{
/// <summary>
/// Tracks the import status of the object. Once the task is completed,
/// the user code is allowed to free the resource owner in case when a non-owning
/// sharing handle was used
/// sharing handle was used.
/// </summary>
Task ImportCompleted { get; }
/// <inheritdoc cref="ImportCompleted"/>
/// <seealso cref="ImportCompleted">ImportCompleted (recommended replacement)</seealso>
[Obsolete("Please use ICompositionGpuImportedObject.ImportCompleted instead")]
[EditorBrowsable(EditorBrowsableState.Never)]
Task ImportCompeted { get; }
/// <summary>
/// Indicates if the device context this instance is associated with is no longer available
/// </summary>

8
src/Avalonia.Base/Rendering/Composition/CompositionInterop.cs

@ -67,18 +67,20 @@ abstract class CompositionGpuImportedObjectBase : ICompositionGpuImportedObject
Context = context;
Feature = feature;
ImportCompeted = Compositor.InvokeServerJobAsync(Import);
ImportCompleted = Compositor.InvokeServerJobAsync(Import);
}
protected abstract void Import();
public abstract void Dispose();
public Task ImportCompeted { get; }
public Task ImportCompleted { get; }
public Task ImportCompeted => ImportCompleted;
public bool IsLost => Context.IsLost;
public ValueTask DisposeAsync() => new(Compositor.InvokeServerJobAsync(() =>
{
if (ImportCompeted.Status == TaskStatus.RanToCompletion)
if (ImportCompleted.Status == TaskStatus.RanToCompletion)
Dispose();
}));
}

21
src/Avalonia.Base/Rendering/Composition/Drawing/ServerCompositionRenderData.cs

@ -42,22 +42,31 @@ class ServerCompositionRenderData : SimpleServerRenderResource
_items.Add(reader.ReadObject<IRenderDataItem>());
var collector = s_resourceHashSetPool.Get();
foreach(var item in _items)
if (item is IRenderDataItemWithServerResources resourceItem)
resourceItem.Collect(collector);
CollectResources(_items, collector);
foreach (var r in collector.Resources)
{
_referencedResources.Add(r);
r.AddObserver(this);
}
collector.Resources.Clear();
s_resourceHashSetPool.ReturnAndSetNull(ref collector);
base.DeserializeChangesCore(reader, committedAt);
}
private static void CollectResources(PooledInlineList<IRenderDataItem> items, IRenderDataServerResourcesCollector collector)
{
foreach (var item in items)
{
if (item is IRenderDataItemWithServerResources resourceItem)
resourceItem.Collect(collector);
else if (item is RenderDataPushNode pushNode)
CollectResources(pushNode.Children, collector);
}
}
public Rect? Bounds
{
get
@ -133,4 +142,4 @@ class ServerCompositionRenderData : SimpleServerRenderResource
Reset();
base.Dispose();
}
}
}

6
src/Avalonia.Base/Rendering/Composition/Server/ServerCompositionDrawingSurface.cs

@ -31,12 +31,12 @@ internal class ServerCompositionDrawingSurface : ServerCompositionSurface, IDisp
throw new PlatformGraphicsContextLostException();
// This should never happen, but check for it anyway to avoid a deadlock
if (!image.ImportCompeted.IsCompleted)
if (!image.ImportCompleted.IsCompleted)
throw new InvalidOperationException("The import operation is not completed yet");
// Rethrow the import here exception
if (image.ImportCompeted.IsFaulted)
image.ImportCompeted.GetAwaiter().GetResult();
if (image.ImportCompleted.IsFaulted)
image.ImportCompleted.GetAwaiter().GetResult();
}
void Update(IBitmapImpl newImage, IPlatformRenderInterfaceContext context)

7
src/Avalonia.Base/Threading/AvaloniaSynchronizationContext.cs

@ -99,14 +99,15 @@ namespace Avalonia.Threading
}
}
public static RestoreContext Ensure(DispatcherPriority priority)
public static RestoreContext Ensure(DispatcherPriority priority) => Ensure(Dispatcher.UIThread, priority);
public static RestoreContext Ensure(Dispatcher dispatcher, DispatcherPriority priority)
{
if (Current is AvaloniaSynchronizationContext avaloniaContext
&& avaloniaContext.Priority == priority)
return default;
var oldContext = Current;
Dispatcher.UIThread.VerifyAccess();
SetSynchronizationContext(Dispatcher.UIThread.GetContextWithPriority(priority));
dispatcher.VerifyAccess();
SetSynchronizationContext(dispatcher.GetContextWithPriority(priority));
return new RestoreContext(oldContext);
}
}

16
src/Avalonia.Base/Threading/Dispatcher.Invoke.cs

@ -90,7 +90,7 @@ public partial class Dispatcher
{
if (callback == null)
{
throw new ArgumentNullException("callback");
throw new ArgumentNullException(nameof(callback));
}
DispatcherPriority.Validate(priority, "priority");
@ -98,7 +98,7 @@ public partial class Dispatcher
if (timeout.TotalMilliseconds < 0 &&
timeout != TimeSpan.FromMilliseconds(-1))
{
throw new ArgumentOutOfRangeException("timeout");
throw new ArgumentOutOfRangeException(nameof(timeout));
}
// Fast-Path: if on the same thread, and invoking at Send priority,
@ -106,7 +106,7 @@ public partial class Dispatcher
// call the callback directly.
if (!cancellationToken.IsCancellationRequested && priority == DispatcherPriority.Send && CheckAccess())
{
using (AvaloniaSynchronizationContext.Ensure(priority))
using (AvaloniaSynchronizationContext.Ensure(this, priority))
callback();
return;
}
@ -212,7 +212,7 @@ public partial class Dispatcher
{
if (callback == null)
{
throw new ArgumentNullException("callback");
throw new ArgumentNullException(nameof(callback));
}
DispatcherPriority.Validate(priority, "priority");
@ -220,7 +220,7 @@ public partial class Dispatcher
if (timeout.TotalMilliseconds < 0 &&
timeout != TimeSpan.FromMilliseconds(-1))
{
throw new ArgumentOutOfRangeException("timeout");
throw new ArgumentOutOfRangeException(nameof(timeout));
}
// Fast-Path: if on the same thread, and invoking at Send priority,
@ -228,7 +228,7 @@ public partial class Dispatcher
// call the callback directly.
if (!cancellationToken.IsCancellationRequested && priority == DispatcherPriority.Send && CheckAccess())
{
using (AvaloniaSynchronizationContext.Ensure(priority))
using (AvaloniaSynchronizationContext.Ensure(this, priority))
return callback();
}
@ -304,7 +304,7 @@ public partial class Dispatcher
{
if (callback == null)
{
throw new ArgumentNullException("callback");
throw new ArgumentNullException(nameof(callback));
}
DispatcherPriority.Validate(priority, "priority");
@ -379,7 +379,7 @@ public partial class Dispatcher
{
if (callback == null)
{
throw new ArgumentNullException("callback");
throw new ArgumentNullException(nameof(callback));
}
DispatcherPriority.Validate(priority, "priority");

2
src/Avalonia.Base/Threading/Dispatcher.MainLoop.cs

@ -49,7 +49,7 @@ public partial class Dispatcher
try
{
_frames.Push(frame);
using (AvaloniaSynchronizationContext.Ensure(DispatcherPriority.Normal))
using (AvaloniaSynchronizationContext.Ensure(this, DispatcherPriority.Normal))
frame.Run(_controlledImpl);
}
finally

20
src/Avalonia.Base/Threading/Dispatcher.Queue.cs

@ -9,7 +9,9 @@ public partial class Dispatcher
private readonly DispatcherPriorityQueue _queue = new();
private bool _signaled;
private bool _explicitBackgroundProcessingRequested;
private const int MaximumTimeProcessingBackgroundJobs = 50;
private const int MaximumInputStarvationTimeInFallbackMode = 50;
private const int MaximumInputStarvationTimeInExplicitProcessingExplicitMode = 50;
private int _maximumInputStarvationTime;
void RequestBackgroundProcessing()
{
@ -35,8 +37,8 @@ public partial class Dispatcher
lock (InstanceLock)
{
_explicitBackgroundProcessingRequested = false;
ExecuteJobsCore();
}
ExecuteJobsCore(true);
}
/// <summary>
@ -130,10 +132,10 @@ public partial class Dispatcher
lock (InstanceLock)
_signaled = false;
ExecuteJobsCore();
ExecuteJobsCore(false);
}
void ExecuteJobsCore()
void ExecuteJobsCore(bool fromExplicitBackgroundProcessingCallback)
{
long? backgroundJobExecutionStartedAt = null;
while (true)
@ -151,7 +153,6 @@ public partial class Dispatcher
if (job.Priority > DispatcherPriority.Input)
{
ExecuteJob(job);
backgroundJobExecutionStartedAt = null;
}
// If platform supports pending input query, ask the platform if we can continue running low priority jobs
else if (_pendingInputImpl?.CanQueryPendingInput == true)
@ -164,6 +165,13 @@ public partial class Dispatcher
return;
}
}
// We can't ask if the implementation has pending input, so we should let it to call us back
// Once it thinks that input is handled
else if (_backgroundProcessingImpl != null && !fromExplicitBackgroundProcessingCallback)
{
RequestBackgroundProcessing();
return;
}
// We can't check if there is pending input, but still need to enforce interactivity
// so we stop processing background jobs after some timeout and start a timer to continue later
else
@ -171,7 +179,7 @@ public partial class Dispatcher
if (backgroundJobExecutionStartedAt == null)
backgroundJobExecutionStartedAt = Now;
if (Now - backgroundJobExecutionStartedAt.Value > MaximumTimeProcessingBackgroundJobs)
if (Now - backgroundJobExecutionStartedAt.Value > _maximumInputStarvationTime)
{
_signaled = true;
RequestBackgroundProcessing();

2
src/Avalonia.Base/Threading/Dispatcher.Timers.cs

@ -127,7 +127,7 @@ public partial class Dispatcher
if (needToPromoteTimers)
PromoteTimers();
if (needToProcessQueue)
ExecuteJobsCore();
ExecuteJobsCore(false);
UpdateOSTimer();
}

3
src/Avalonia.Base/Threading/Dispatcher.cs

@ -34,6 +34,9 @@ public partial class Dispatcher : IDispatcher
_controlledImpl = _impl as IControlledDispatcherImpl;
_pendingInputImpl = _impl as IDispatcherImplWithPendingInput;
_backgroundProcessingImpl = _impl as IDispatcherImplWithExplicitBackgroundProcessing;
_maximumInputStarvationTime = _backgroundProcessingImpl == null ?
MaximumInputStarvationTimeInFallbackMode :
MaximumInputStarvationTimeInExplicitProcessingExplicitMode;
if (_backgroundProcessingImpl != null)
_backgroundProcessingImpl.ReadyForBackgroundProcessing += OnReadyForExplicitBackgroundProcessing;
}

8
src/Avalonia.Base/Threading/DispatcherFrame.cs

@ -38,10 +38,14 @@ public class DispatcherFrame
/// for their important criteria to be met. These frames
/// should have a timeout associated with them.
/// </param>
public DispatcherFrame(bool exitWhenRequested)
public DispatcherFrame(bool exitWhenRequested) : this(Dispatcher.UIThread, exitWhenRequested)
{
Dispatcher = Dispatcher.UIThread;
Dispatcher.VerifyAccess();
}
internal DispatcherFrame(Dispatcher dispatcher, bool exitWhenRequested)
{
Dispatcher = dispatcher;
_exitWhenRequested = exitWhenRequested;
_continue = true;
}

2
src/Avalonia.Base/Threading/DispatcherOperation.cs

@ -258,7 +258,7 @@ public class DispatcherOperation
try
{
using (AvaloniaSynchronizationContext.Ensure(Priority))
using (AvaloniaSynchronizationContext.Ensure(Dispatcher, Priority))
InvokeCore();
}
finally

16
src/Avalonia.Base/Threading/DispatcherTimer.cs

@ -62,7 +62,7 @@ public partial class DispatcherTimer
{
if (callback == null)
{
throw new ArgumentNullException("callback");
throw new ArgumentNullException(nameof(callback));
}
Tick += callback;
@ -112,11 +112,11 @@ public partial class DispatcherTimer
bool updateOSTimer = false;
if (value.TotalMilliseconds < 0)
throw new ArgumentOutOfRangeException("value",
throw new ArgumentOutOfRangeException(nameof(value),
"TimeSpan period must be greater than or equal to zero.");
if (value.TotalMilliseconds > Int32.MaxValue)
throw new ArgumentOutOfRangeException("value",
throw new ArgumentOutOfRangeException(nameof(value),
"TimeSpan period must be less than or equal to Int32.MaxValue.");
lock (_instanceLock)
@ -253,20 +253,20 @@ public partial class DispatcherTimer
{
if (dispatcher == null)
{
throw new ArgumentNullException("dispatcher");
throw new ArgumentNullException(nameof(dispatcher));
}
DispatcherPriority.Validate(priority, "priority");
if (priority == DispatcherPriority.Inactive)
{
throw new ArgumentException("Specified priority is not valid.", "priority");
throw new ArgumentException("Specified priority is not valid.", nameof(priority));
}
if (interval.TotalMilliseconds < 0)
throw new ArgumentOutOfRangeException("interval", "TimeSpan period must be greater than or equal to zero.");
throw new ArgumentOutOfRangeException(nameof(interval), "TimeSpan period must be greater than or equal to zero.");
if (interval.TotalMilliseconds > Int32.MaxValue)
throw new ArgumentOutOfRangeException("interval",
throw new ArgumentOutOfRangeException(nameof(interval),
"TimeSpan period must be less than or equal to Int32.MaxValue.");
@ -349,4 +349,4 @@ public partial class DispatcherTimer
// used by Dispatcher
internal long DueTimeInMs { get; private set; }
}
}

16
src/Avalonia.Base/Utilities/SmallDictionary.cs

@ -51,7 +51,7 @@ internal struct InlineDictionary<TKey, TValue> : IEnumerable<KeyValuePair<TKey,
throw new ArgumentException("Key already exists in dictionary");
}
if (arr[c].Key == null)
if (arr[c].Key == null && free == -1)
free = c;
}
@ -337,11 +337,15 @@ internal struct InlineDictionary<TKey, TValue> : IEnumerable<KeyValuePair<TKey,
}
else if (_type == Type.Array)
{
var next = _index + 1;
if (_arr!.Length - 1 < next || _arr[next].Key == null)
return false;
_index = next;
return true;
for (var next = _index + 1; next < _arr!.Length; ++next)
{
if (_arr[next].Key != null)
{
_index = next;
return true;
}
}
return false;
}
else if (_type == Type.Dictionary)
return _inner.MoveNext();

4
src/Avalonia.Controls.DataGrid/Collections/DataGridCollectionView.cs

@ -807,7 +807,7 @@ namespace Avalonia.Collections
{
if (value < 0)
{
throw new ArgumentOutOfRangeException("PageSize cannot have a negative value.");
throw new ArgumentOutOfRangeException(nameof(value), "PageSize cannot have a negative value.");
}
// if the Refresh is currently deferred, cache the desired PageSize
@ -1954,7 +1954,7 @@ namespace Avalonia.Collections
// for indices larger than the count
if (index >= Count || index < 0)
{
throw new ArgumentOutOfRangeException("index");
throw new ArgumentOutOfRangeException(nameof(index));
}
if (IsGrouping)

2
src/Avalonia.Controls.DataGrid/DataGridCheckBoxColumn.cs

@ -222,7 +222,7 @@ namespace Avalonia.Controls
{
if (element == null)
{
throw new ArgumentNullException("element");
throw new ArgumentNullException(nameof(element));
}
if (element is CheckBox checkBox)
{

2
src/Avalonia.Controls.DataGrid/DataGridColumnCollection.cs

@ -191,7 +191,7 @@ namespace Avalonia.Controls
}
if (dataGridColumn == null)
{
throw new ArgumentNullException("dataGridColumn");
throw new ArgumentNullException(nameof(dataGridColumn));
}
int columnIndexWithFiller = columnIndex;

2
src/Avalonia.Controls.DataGrid/DataGridLength.cs

@ -499,7 +499,7 @@ namespace Avalonia.Controls
{
if (destinationType == null)
{
throw new ArgumentNullException("destinationType");
throw new ArgumentNullException(nameof(destinationType));
}
if (destinationType != typeof(string))
{

2
src/Avalonia.Controls/Automation/Peers/ControlAutomationPeer.cs

@ -18,7 +18,7 @@ namespace Avalonia.Automation.Peers
public ControlAutomationPeer(Control owner)
{
Owner = owner ?? throw new ArgumentNullException("owner");
Owner = owner ?? throw new ArgumentNullException(nameof(owner));
Initialize();
}

4
src/Avalonia.Controls/Automation/Peers/ScrollViewerAutomationPeer.cs

@ -146,12 +146,12 @@ namespace Avalonia.Automation.Peers
if (scrollHorizontally && (horizontalPercent < 0.0) || (horizontalPercent > 100.0))
{
throw new ArgumentOutOfRangeException("horizontalPercent");
throw new ArgumentOutOfRangeException(nameof(horizontalPercent));
}
if (scrollVertically && (verticalPercent < 0.0) || (verticalPercent > 100.0))
{
throw new ArgumentOutOfRangeException("verticalPercent");
throw new ArgumentOutOfRangeException(nameof(verticalPercent));
}
var offset = Owner.Offset;

10
src/Avalonia.Controls/ComboBox.cs

@ -270,6 +270,7 @@ namespace Avalonia.Controls
{
if (_popup?.IsInsidePopup(source) == true)
{
e.Handled = true;
return;
}
}
@ -516,5 +517,14 @@ namespace Avalonia.Controls
}
}
}
/// <summary>
/// Clears the selection
/// </summary>
public void Clear()
{
SelectedItem = null;
SelectedIndex = -1;
}
}
}

4
src/Avalonia.Controls/DateTimePickers/DateTimePickerPanel.cs

@ -165,7 +165,7 @@ namespace Avalonia.Controls.Primitives
set
{
if (value > MaximumValue || value < MinimumValue)
throw new ArgumentOutOfRangeException("SelectedValue");
throw new ArgumentOutOfRangeException(nameof(value));
var sel = CoerceSelected(value);
_selectedValue = sel;
@ -195,7 +195,7 @@ namespace Avalonia.Controls.Primitives
set
{
if (value <= 0 || value > _range)
throw new ArgumentOutOfRangeException("Increment");
throw new ArgumentOutOfRangeException(nameof(value));
_increment = value;
UpdateHelperInfo();
var sel = CoerceSelected(SelectedValue);

4
src/Avalonia.Controls/NumericUpDown/NumericUpDown.cs

@ -1113,11 +1113,11 @@ namespace Avalonia.Controls
}
if (value < Minimum)
{
throw new ArgumentOutOfRangeException(nameof(value), string.Format("Value must be greater than Minimum value of {0}", Minimum));
throw new ArgumentOutOfRangeException(nameof(value), $"Value must be greater than Minimum value of {Minimum}");
}
else if (value > Maximum)
{
throw new ArgumentOutOfRangeException(nameof(value), string.Format("Value must be less than Maximum value of {0}", Maximum));
throw new ArgumentOutOfRangeException(nameof(value), $"Value must be less than Maximum value of {Maximum}");
}
}

4
src/Avalonia.Controls/Primitives/SelectingItemsControl.cs

@ -1395,10 +1395,8 @@ namespace Avalonia.Controls.Primitives
public object Evaluate(object? dataContext)
{
dataContext = dataContext ?? throw new ArgumentNullException(nameof(dataContext));
// Only update the DataContext if necessary
if (!dataContext.Equals(DataContext))
if (!Equals(dataContext, DataContext))
DataContext = dataContext;
return GetValue(ValueProperty);

10
src/Avalonia.Controls/PullToRefresh/ScrollViewerIRefreshInfoProviderAdapter.cs

@ -98,14 +98,14 @@ namespace Avalonia.Controls.PullToRefresh
if (_scrollViewer.Content == null)
{
throw new ArgumentException(nameof(adaptee), "Adaptee's content property cannot be null.");
throw new ArgumentException("Adaptee's content property cannot be null.", nameof(adaptee));
}
var content = adaptee.Content as Visual;
if (content == null)
{
throw new ArgumentException(nameof(adaptee), "Adaptee's content property must be a Visual");
throw new ArgumentException("Adaptee's content property must be a Visual", nameof(adaptee));
}
if (content.GetVisualParent() == null)
@ -118,7 +118,7 @@ namespace Avalonia.Controls.PullToRefresh
if (content.Parent is not InputElement)
{
throw new ArgumentException(nameof(adaptee), "Adaptee's content's parent must be a InputElement");
throw new ArgumentException("Adaptee's content's parent must be a InputElement", nameof(adaptee));
}
}
@ -194,12 +194,12 @@ namespace Avalonia.Controls.PullToRefresh
var content = _scrollViewer?.Content as Visual;
if (content == null)
{
throw new ArgumentException(nameof(_scrollViewer), "Adaptee's content property must be a Visual");
throw new ArgumentException("Adaptee's content property must be a Visual", nameof(_scrollViewer));
}
if (content.Parent is not InputElement parent)
{
throw new ArgumentException(nameof(_scrollViewer), "Adaptee's content parent must be an InputElement");
throw new ArgumentException("Adaptee's content parent must be an InputElement", nameof(_scrollViewer));
}
MakeInteractionSource(parent);

22
src/Avalonia.Controls/Utils/StringUtils.cs

@ -76,7 +76,7 @@ namespace Avalonia.Controls.Utils
}
var codepoint = new Codepoint(text[index]);
if (!codepoint.IsWhiteSpace)
{
return false;
@ -85,12 +85,20 @@ namespace Avalonia.Controls.Utils
// preceeded by lwsp.
if (index > 0)
{
var nextCodePoint = new Codepoint(text[index + 1]);
if (index + 1 < text.Length)
{
var nextCodePoint = new Codepoint(text[index + 1]);
if (nextCodePoint.IsBreakChar)
if (nextCodePoint.IsBreakChar)
{
return true;
}
}
else
{
return true;
}
}
switch (codepoint.GeneralCategory)
@ -125,7 +133,7 @@ namespace Avalonia.Controls.Utils
}
cursor = Math.Min(cursor, text.Length);
int begin;
int i;
int cr;
@ -181,7 +189,7 @@ namespace Avalonia.Controls.Utils
{
return cursor;
}
if (cr < text.Length && text[cr] == '\r' && cr + 1 < text.Length && text[cr + 1] == '\n')
{
lf = cr + 1;
@ -214,9 +222,9 @@ namespace Avalonia.Controls.Utils
{
return i;
}
var cc = GetCharClass(text[i]);
// skip over the word, punctuation, or run of whitespace
while (i < cr && GetCharClass(text[i]) == cc)
{

2
src/Avalonia.Controls/Utils/VirtualizingSnapPointsList.cs

@ -39,7 +39,7 @@ namespace Avalonia.Controls.Utils
get
{
if(index < 0 || index >= Count)
throw new ArgumentOutOfRangeException("index");
throw new ArgumentOutOfRangeException(nameof(index));
index += _start;

10
src/Avalonia.Native/AvaloniaNativePlatform.cs

@ -3,9 +3,7 @@ using System.Runtime.InteropServices;
using Avalonia.Controls.Platform;
using Avalonia.Input;
using Avalonia.Input.Platform;
using Avalonia.MicroCom;
using Avalonia.Native.Interop;
using Avalonia.OpenGL;
using Avalonia.Platform;
using Avalonia.Rendering;
using Avalonia.Rendering.Composition;
@ -163,6 +161,14 @@ namespace Avalonia.Native
Compositor = new Compositor(_platformGraphics, true);
AppDomain.CurrentDomain.ProcessExit += OnProcessExit;
}
private void OnProcessExit(object? sender, EventArgs e)
{
AppDomain.CurrentDomain.ProcessExit -= OnProcessExit;
_factory.Dispose();
}
public ITrayIconImpl CreateTrayIcon()

2
src/Avalonia.Native/NativePlatformSettings.cs

@ -23,7 +23,7 @@ internal class NativePlatformSettings : DefaultPlatformSettings
AvnPlatformThemeVariant.Dark => (PlatformThemeVariant.Dark, ColorContrastPreference.NoPreference),
AvnPlatformThemeVariant.Light => (PlatformThemeVariant.Light, ColorContrastPreference.NoPreference),
AvnPlatformThemeVariant.HighContrastDark => (PlatformThemeVariant.Dark, ColorContrastPreference.High),
AvnPlatformThemeVariant.HighContrastLight => (PlatformThemeVariant.Dark, ColorContrastPreference.High),
AvnPlatformThemeVariant.HighContrastLight => (PlatformThemeVariant.Light, ColorContrastPreference.High),
_ => throw new ArgumentOutOfRangeException()
};
var color = _platformSettings.AccentColor;

8
src/Avalonia.Remote.Protocol/MetsysBson.cs

@ -752,7 +752,7 @@ namespace Metsys.Bson
if (memberExpression.Expression.NodeType != ExpressionType.Parameter && memberExpression.Expression.NodeType != ExpressionType.Convert)
{
throw new ArgumentException(string.Format("Expression '{0}' must resolve to top-level member.", lambdaExpression), nameof(lambdaExpression));
throw new ArgumentException($"Expression '{lambdaExpression}' must resolve to top-level member.", nameof(lambdaExpression));
}
return memberExpression.Member.Name;
default:
@ -942,7 +942,7 @@ namespace Metsys.Bson
return new ListWrapper();
}
}
throw new BsonException(string.Format("Collection of type {0} cannot be deserialized", type.FullName));
throw new BsonException($"Collection of type {type.FullName} cannot be deserialized");
}
public abstract void Add(object value);
@ -1514,7 +1514,7 @@ namespace Metsys.Bson.Configuration
result = Visit((MemberExpression)expression.Left);
}
var index = Expression.Lambda(expression.Right).Compile().DynamicInvoke();
return result + string.Format("[{0}]", index);
return result + $"[{index}]";
}
private string Visit(MemberExpression expression)
@ -1540,7 +1540,7 @@ namespace Metsys.Bson.Configuration
if (expression.Method.Name == "get_Item" && expression.Arguments.Count == 1)
{
var index = Expression.Lambda(expression.Arguments[0]).Compile().DynamicInvoke();
name += string.Format("[{0}]", index);
name += $"[{index}]";
}
return name;
}

50
src/Avalonia.X11/X11Clipboard.cs

@ -14,6 +14,7 @@ namespace Avalonia.X11
private readonly X11Info _x11;
private IDataObject _storedDataObject;
private IntPtr _handle;
private TaskCompletionSource<bool> _storeAtomTcs;
private TaskCompletionSource<IntPtr[]> _requestedFormatsTcs;
private TaskCompletionSource<object> _requestedDataTcs;
private readonly IntPtr[] _textAtoms;
@ -52,6 +53,12 @@ namespace Avalonia.X11
private unsafe void OnEvent(ref XEvent ev)
{
if (ev.type == XEventName.SelectionClear)
{
_storeAtomTcs?.TrySetResult(true);
return;
}
if (ev.type == XEventName.SelectionRequest)
{
var sel = ev.SelectionRequestEvent;
@ -82,18 +89,9 @@ namespace Avalonia.X11
Encoding textEnc;
if (target == _x11.Atoms.TARGETS)
{
var atoms = new HashSet<IntPtr> { _x11.Atoms.TARGETS, _x11.Atoms.MULTIPLE };
foreach (var fmt in _storedDataObject.GetDataFormats())
{
if (fmt == DataFormats.Text)
foreach (var ta in _textAtoms)
atoms.Add(ta);
else
atoms.Add(_x11.Atoms.GetAtom(fmt));
}
var atoms = ConvertDataObject(_storedDataObject);
XChangeProperty(_x11.Display, window, property,
_x11.Atoms.XA_ATOM, 32, PropertyMode.Replace, atoms.ToArray(), atoms.Count);
_x11.Atoms.XA_ATOM, 32, PropertyMode.Replace, atoms, atoms.Length);
return property;
}
else if(target == _x11.Atoms.SAVE_TARGETS && _x11.Atoms.SAVE_TARGETS != IntPtr.Zero)
@ -252,20 +250,41 @@ namespace Avalonia.X11
return (string)await SendDataRequest(target);
}
private void StoreAtomsInClipboardManager(IntPtr[] atoms)
private IntPtr[] ConvertDataObject(IDataObject data)
{
var atoms = new HashSet<IntPtr> { _x11.Atoms.TARGETS, _x11.Atoms.MULTIPLE };
foreach (var fmt in data.GetDataFormats())
{
if (fmt == DataFormats.Text)
foreach (var ta in _textAtoms)
atoms.Add(ta);
else
atoms.Add(_x11.Atoms.GetAtom(fmt));
}
return atoms.ToArray();
}
private Task StoreAtomsInClipboardManager(IDataObject data)
{
if (_x11.Atoms.CLIPBOARD_MANAGER != IntPtr.Zero && _x11.Atoms.SAVE_TARGETS != IntPtr.Zero)
{
var clipboardManager = XGetSelectionOwner(_x11.Display, _x11.Atoms.CLIPBOARD_MANAGER);
if (clipboardManager != IntPtr.Zero)
{
{
if (_storeAtomTcs == null || _storeAtomTcs.Task.IsCompleted)
_storeAtomTcs = new TaskCompletionSource<bool>();
var atoms = ConvertDataObject(data);
XChangeProperty(_x11.Display, _handle, _avaloniaSaveTargetsAtom, _x11.Atoms.XA_ATOM, 32,
PropertyMode.Replace,
atoms, atoms.Length);
XConvertSelection(_x11.Display, _x11.Atoms.CLIPBOARD_MANAGER, _x11.Atoms.SAVE_TARGETS,
_avaloniaSaveTargetsAtom, _handle, IntPtr.Zero);
return _storeAtomTcs.Task;
}
}
return Task.CompletedTask;
}
public Task SetTextAsync(string text)
@ -283,9 +302,8 @@ namespace Avalonia.X11
public Task SetDataObjectAsync(IDataObject data)
{
_storedDataObject = data;
XSetSelectionOwner(_x11.Display, _x11.Atoms.CLIPBOARD, _handle, IntPtr.Zero);
StoreAtomsInClipboardManager(_textAtoms);
return Task.CompletedTask;
XSetSelectionOwner(_x11.Display, _x11.Atoms.CLIPBOARD, _handle, IntPtr.Zero);
return StoreAtomsInClipboardManager(data);
}
public async Task<string[]> GetFormatsAsync()

5
src/Avalonia.X11/X11Structs.cs

@ -1109,7 +1109,7 @@ namespace Avalonia.X11 {
public override string ToString ()
{
return string.Format("MotifWmHints <flags={0}, functions={1}, decorations={2}, input_mode={3}, status={4}", (MotifFlags) flags.ToInt32 (), (MotifFunctions) functions.ToInt32 (), (MotifDecorations) decorations.ToInt32 (), (MotifInputMode) input_mode.ToInt32 (), status.ToInt32 ());
return $"MotifWmHints <flags={(MotifFlags)flags.ToInt32()}, functions={(MotifFunctions)functions.ToInt32()}, decorations={(MotifDecorations)decorations.ToInt32()}, input_mode={(MotifInputMode)input_mode.ToInt32()}, status={status.ToInt32()}";
}
}
@ -1707,8 +1707,7 @@ namespace Avalonia.X11 {
public override string ToString ()
{
return string.Format ("XCursorImage (version: {0}, size: {1}, width: {2}, height: {3}, xhot: {4}, yhot: {5}, delay: {6}, pixels: {7}",
version, size, width, height, xhot, yhot, delay, pixels);
return $"XCursorImage (version: {version}, size: {size}, width: {width}, height: {height}, xhot: {xhot}, yhot: {yhot}, delay: {delay}, pixels: {pixels}";
}
} ;

3
src/Windows/Avalonia.Win32/Automation/RootAutomationNode.cs

@ -33,8 +33,7 @@ namespace Avalonia.Win32.Automation
return null;
var p = WindowImpl.PointToClient(new PixelPoint((int)x, (int)y));
var peer = (WindowBaseAutomationPeer)Peer;
var found = InvokeSync(() => peer.GetPeerFromPoint(p));
var found = InvokeSync(() => Peer.GetPeerFromPoint(p));
var result = GetOrCreate(found) as IRawElementProviderFragment;
return result;
}

3
src/Windows/Avalonia.Win32/WinRT/Composition/D2DEffects.cs

@ -28,6 +28,9 @@ namespace Avalonia.Win32.WinRT.Composition
public static readonly Guid CLSID_D2D1Border =
new Guid(0x2A2D49C0, 0x4ACF, 0x43C7, 0x8C, 0x6A, 0x7C, 0x4A, 0x27, 0x87, 0x4D, 0x27);
public static readonly Guid CLSID_D2D1Opacity =
new Guid("811d79a4-de28-4454-8094-c64685f8bd4c");
public static readonly Guid CLSID_D2D1Brightness =
new Guid(0x8CEA8D1E, 0x77B0, 0x4986, 0xB3, 0xB9, 0x2F, 0x0C, 0x0E, 0xAE, 0x78, 0x87);

104
src/Windows/Avalonia.Win32/WinRT/Composition/WinUIEffectBase.cs

@ -52,6 +52,110 @@ namespace Avalonia.Win32.WinRT.Composition
_sources = null;
}
}
class BorderEffect : WinUIEffectBase
{
private readonly int _x;
private readonly int _y;
public override Guid EffectId => D2DEffects.CLSID_D2D1Border;
public override uint PropertyCount => 2;
public BorderEffect(int x, int y, params IGraphicsEffectSource[] _sources):base(_sources)
{
_x = x;
_y = y;
}
public override IPropertyValue? GetProperty(uint index)
{
if (index == 0)
return new WinRTPropertyValue((uint)_x);
if (index == 1)
return new WinRTPropertyValue((uint)_y);
return null;
}
}
class BlendEffect : WinUIEffectBase
{
private readonly int _mode;
public BlendEffect(int mode, params IGraphicsEffectSource[] _sources) : base(_sources)
{
_mode = mode;
}
public override Guid EffectId => D2DEffects.CLSID_D2D1Blend;
public override uint PropertyCount => 1;
public override IPropertyValue? GetProperty(uint index)
{
if (index == 0)
return new WinRTPropertyValue((uint)_mode);
return null;
}
}
class CompositeStepEffect : WinUIEffectBase
{
private readonly float _mode;
public CompositeStepEffect(int mode, params IGraphicsEffectSource[] _sources) : base(_sources)
{
_mode = mode;
}
public override Guid EffectId => D2DEffects.CLSID_D2D1Composite;
public override uint PropertyCount => 1;
public override IPropertyValue? GetProperty(uint index)
{
if (index == 0)
return new WinRTPropertyValue((uint)_mode);
return null;
}
}
class OpacityEffect : WinUIEffectBase
{
private readonly float _opacity;
public OpacityEffect(float opacity, params IGraphicsEffectSource[] _sources) : base(_sources)
{
_opacity = opacity;
}
public override Guid EffectId => D2DEffects.CLSID_D2D1Opacity;
public override uint PropertyCount => 1;
public override IPropertyValue? GetProperty(uint index)
{
if (index == 0)
return new WinRTPropertyValue(_opacity);
return null;
}
}
class ColorSourceEffect : WinUIEffectBase
{
private readonly float[] _color;
public ColorSourceEffect(float[] color)
{
_color = color;
}
public override Guid EffectId => D2DEffects.CLSID_D2D1Flood;
public override uint PropertyCount => 1;
public override IPropertyValue? GetProperty(uint index)
{
if (index == 0)
return new WinRTPropertyValue(_color);
return null;
}
}
internal class WinUIGaussianBlurEffect : WinUIEffectBase
{

30
src/Windows/Avalonia.Win32/WinRT/Composition/WinUiCompositedWindow.cs

@ -12,7 +12,8 @@ internal class WinUiCompositedWindow : IDisposable
public EglGlPlatformSurface.IEglWindowGlPlatformSurfaceInfo WindowInfo { get; }
private readonly WinUiCompositionShared _shared;
private readonly ICompositionRoundedRectangleGeometry? _compositionRoundedRectangleGeometry;
private readonly IVisual? _mica;
private readonly IVisual? _micaLight;
private readonly IVisual? _micaDark;
private readonly IVisual _blur;
private readonly IVisual _visual;
private PixelSize _size;
@ -25,7 +26,8 @@ internal class WinUiCompositedWindow : IDisposable
{
_compositionRoundedRectangleGeometry?.Dispose();
_blur.Dispose();
_mica?.Dispose();
_micaLight?.Dispose();
_micaDark?.Dispose();
_visual.Dispose();
_surfaceBrush.Dispose();
_target.Dispose();
@ -50,14 +52,20 @@ internal class WinUiCompositedWindow : IDisposable
_target.SetRoot(containerVisual);
_blur = WinUiCompositionUtils.CreateBlurVisual(shared.Compositor, shared.BlurBrush);
if (shared.MicaBrush != null)
if (shared.MicaBrushLight != null)
{
_mica = WinUiCompositionUtils.CreateBlurVisual(shared.Compositor, shared.MicaBrush);
containerChildren.InsertAtTop(_mica);
_micaLight = WinUiCompositionUtils.CreateBlurVisual(shared.Compositor, shared.MicaBrushLight);
containerChildren.InsertAtTop(_micaLight);
}
if (shared.MicaBrushDark != null)
{
_micaDark = WinUiCompositionUtils.CreateBlurVisual(shared.Compositor, shared.MicaBrushDark);
containerChildren.InsertAtTop(_micaDark);
}
_compositionRoundedRectangleGeometry =
WinUiCompositionUtils.ClipVisual(shared.Compositor, backdropCornerRadius, _blur, _mica);
WinUiCompositionUtils.ClipVisual(shared.Compositor, backdropCornerRadius, _blur, _micaLight, _micaDark);
containerChildren.InsertAtTop(_blur);
using var spriteVisual = shared.Compositor.CreateSpriteVisual();
@ -68,9 +76,6 @@ internal class WinUiCompositedWindow : IDisposable
using var compositionBrush = _surfaceBrush.QueryInterface<ICompositionBrush>();
spriteVisual.SetBrush(compositionBrush);
_target.SetRoot(containerVisual);
}
public void SetSurface(ICompositionSurface surface) => _surfaceBrush.SetSurface(surface);
@ -79,12 +84,13 @@ internal class WinUiCompositedWindow : IDisposable
{
lock (_shared.SyncRoot)
{
_blur.SetIsVisible(blurEffect == BlurEffect.Acrylic
|| blurEffect == BlurEffect.Mica && _mica == null ?
|| (blurEffect == BlurEffect.MicaLight && _micaLight == null) ||
(blurEffect == BlurEffect.MicaDark && _micaDark == null) ?
1 :
0);
_mica?.SetIsVisible(blurEffect == BlurEffect.Mica ? 1 : 0);
_micaLight?.SetIsVisible(blurEffect == BlurEffect.MicaLight ? 1 : 0);
_micaDark?.SetIsVisible(blurEffect == BlurEffect.MicaDark ? 1 : 0);
}
}

9
src/Windows/Avalonia.Win32/WinRT/Composition/WinUiCompositionShared.cs

@ -9,7 +9,8 @@ internal class WinUiCompositionShared : IDisposable
public ICompositor5 Compositor5 { get; }
public ICompositorDesktopInterop DesktopInterop { get; }
public ICompositionBrush BlurBrush { get; }
public ICompositionBrush? MicaBrush { get; }
public ICompositionBrush? MicaBrushLight { get; }
public ICompositionBrush? MicaBrushDark { get; }
public object SyncRoot { get; } = new();
public static readonly Version MinWinCompositionVersion = new(10, 0, 17134);
@ -21,14 +22,16 @@ internal class WinUiCompositionShared : IDisposable
Compositor = compositor.CloneReference();
Compositor5 = compositor.QueryInterface<ICompositor5>();
BlurBrush = WinUiCompositionUtils.CreateAcrylicBlurBackdropBrush(compositor);
MicaBrush = WinUiCompositionUtils.CreateMicaBackdropBrush(compositor);
MicaBrushLight = WinUiCompositionUtils.CreateMicaBackdropBrush(compositor, 242, 0.6f);
MicaBrushDark = WinUiCompositionUtils.CreateMicaBackdropBrush(compositor, 32, 0.8f);
DesktopInterop = compositor.QueryInterface<ICompositorDesktopInterop>();
}
public void Dispose()
{
BlurBrush.Dispose();
MicaBrush?.Dispose();
MicaBrushLight?.Dispose();
MicaBrushDark?.Dispose();
DesktopInterop.Dispose();
Compositor.Dispose();
Compositor5.Dispose();

72
src/Windows/Avalonia.Win32/WinRT/Composition/WinUiCompositionUtils.cs

@ -1,3 +1,4 @@
using System;
using System.Numerics;
using MicroCom.Runtime;
@ -5,16 +6,72 @@ namespace Avalonia.Win32.WinRT.Composition;
internal static class WinUiCompositionUtils
{
public static ICompositionBrush? CreateMicaBackdropBrush(ICompositor compositor)
public static ICompositionBrush? CreateMicaBackdropBrush(ICompositor compositor, float color, float opacity)
{
if (Win32Platform.WindowsVersion.Build < 22000)
return null;
using var backDropParameterFactory =
NativeWinRTMethods.CreateActivationFactory<ICompositionEffectSourceParameterFactory>(
"Windows.UI.Composition.CompositionEffectSourceParameter");
var tint = new[] { color / 255f, color / 255f, color / 255f, 255f / 255f };
using var tintColorEffect = new ColorSourceEffect(tint);
using var tintOpacityEffect = new OpacityEffect(1.0f, tintColorEffect);
using var tintOpacityEffectFactory = compositor.CreateEffectFactory(tintOpacityEffect);
using var tintOpacityEffectBrushEffect = tintOpacityEffectFactory.CreateBrush();
using var tintOpacityEffectBrush = tintOpacityEffectBrushEffect.QueryInterface<ICompositionBrush>();
using var luminosityColorEffect = new ColorSourceEffect(tint);
using var luminosityOpacityEffect = new OpacityEffect(opacity, luminosityColorEffect);
using var luminosityOpacityEffectFactory = compositor.CreateEffectFactory(luminosityOpacityEffect);
using var luminosityOpacityEffectBrushEffect = luminosityOpacityEffectFactory.CreateBrush();
using var luminosityOpacityEffectBrush =
luminosityOpacityEffectBrushEffect.QueryInterface<ICompositionBrush>();
using var compositorWithBlurredWallpaperBackdropBrush =
compositor.QueryInterface<ICompositorWithBlurredWallpaperBackdropBrush>();
using var blurredWallpaperBackdropBrush =
compositorWithBlurredWallpaperBackdropBrush?.TryCreateBlurredWallpaperBackdropBrush();
return blurredWallpaperBackdropBrush?.QueryInterface<ICompositionBrush>();
using var micaBackdropBrush = blurredWallpaperBackdropBrush?.QueryInterface<ICompositionBrush>();
using var backgroundParameterAsSource =
GetParameterSource("Background", backDropParameterFactory, out var backgroundHandle);
using var foregroundParameterAsSource =
GetParameterSource("Foreground", backDropParameterFactory, out var foregroundHandle);
using var luminosityBlendEffect =
new BlendEffect(23, backgroundParameterAsSource, foregroundParameterAsSource);
using var luminosityBlendEffectFactory = compositor.CreateEffectFactory(luminosityBlendEffect);
using var luminosityBlendEffectBrush = luminosityBlendEffectFactory.CreateBrush();
using var luminosityBlendEffectBrush1 = luminosityBlendEffectBrush.QueryInterface<ICompositionBrush>();
luminosityBlendEffectBrush.SetSourceParameter(backgroundHandle, micaBackdropBrush);
luminosityBlendEffectBrush.SetSourceParameter(foregroundHandle, luminosityOpacityEffectBrush);
using var backgroundParameterAsSource1 =
GetParameterSource("Background", backDropParameterFactory, out var backgroundHandle1);
using var foregroundParameterAsSource1 =
GetParameterSource("Foreground", backDropParameterFactory, out var foregroundHandle1);
using var colorBlendEffect =
new BlendEffect(22, backgroundParameterAsSource1, foregroundParameterAsSource1);
using var colorBlendEffectFactory = compositor.CreateEffectFactory(colorBlendEffect);
using var colorBlendEffectBrush = colorBlendEffectFactory.CreateBrush();
colorBlendEffectBrush.SetSourceParameter(backgroundHandle1, luminosityBlendEffectBrush1);
colorBlendEffectBrush.SetSourceParameter(foregroundHandle1, tintOpacityEffectBrush);
// colorBlendEffectBrush.SetSourceParameter(backgroundHandle, micaBackdropBrush);
using var micaBackdropBrush1 = colorBlendEffectBrush.QueryInterface<ICompositionBrush>();
return micaBackdropBrush1.CloneReference();
}
public static ICompositionBrush CreateAcrylicBlurBackdropBrush(ICompositor compositor)
@ -97,4 +154,15 @@ internal static class WinUiCompositionUtils
brush?.Dispose();
}
}
private static IGraphicsEffectSource GetParameterSource(string name,
ICompositionEffectSourceParameterFactory backDropParameterFactory, out IntPtr handle)
{
var backdropString = new HStringInterop(name);
var backDropParameter =
backDropParameterFactory.Create(backdropString.Handle);
var backDropParameterAsSource = backDropParameter.QueryInterface<IGraphicsEffectSource>();
handle = backdropString.Handle;
return backDropParameterAsSource;
}
}

3
src/Windows/Avalonia.Win32/WinRT/IBlurHost.cs

@ -4,7 +4,8 @@
{
None,
Acrylic,
Mica
MicaLight,
MicaDark
}
internal interface IBlurHost

23
src/Windows/Avalonia.Win32/WinRT/WinRTPropertyValue.cs

@ -1,4 +1,5 @@
using System;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
namespace Avalonia.Win32.WinRT
@ -16,7 +17,15 @@ namespace Avalonia.Win32.WinRT
UInt32 = u;
Type = PropertyType.UInt32;
}
public WinRTPropertyValue(float[] uiColor)
{
Type = PropertyType.SingleArray;
_singleArray = uiColor;
}
private readonly float[]? _singleArray;
public PropertyType Type { get; }
public int IsNumericScalar { get; }
public byte UInt8 { get; }
@ -62,7 +71,17 @@ namespace Avalonia.Win32.WinRT
public unsafe ulong* GetUInt64Array(uint* __valueSize) => throw NotImplemented;
public unsafe float* GetSingleArray(uint* __valueSize) => throw NotImplemented;
public unsafe float* GetSingleArray(uint* __valueSize)
{
if (_singleArray == null)
throw NotImplemented;
*__valueSize = (uint)_singleArray.Length;
var allocCoTaskMem = Marshal.AllocCoTaskMem(_singleArray.Length * Unsafe.SizeOf<float>());
Marshal.Copy(_singleArray, 0, allocCoTaskMem, _singleArray.Length);
float* s = (float*)allocCoTaskMem;
return s;
}
public unsafe double* GetDoubleArray(uint* __valueSize) => throw NotImplemented;

13
src/Windows/Avalonia.Win32/WindowImpl.cs

@ -106,6 +106,7 @@ namespace Avalonia.Win32
private static POINTER_PEN_INFO[]? s_historyPenInfos;
private static POINTER_INFO[]? s_historyInfos;
private static MOUSEMOVEPOINT[]? s_mouseHistoryInfos;
private PlatformThemeVariant _currentThemeVariant;
public WindowImpl()
{
@ -474,7 +475,12 @@ namespace Avalonia.Win32
return;
SetUseHostBackdropBrush(false);
_blurHost?.SetBlur(BlurEffect.Mica);
_blurHost?.SetBlur(_currentThemeVariant switch
{
PlatformThemeVariant.Light => BlurEffect.MicaLight,
PlatformThemeVariant.Dark => BlurEffect.MicaDark,
_ => throw new ArgumentOutOfRangeException()
});
}
private void SetAccentState(AccentState state)
@ -778,6 +784,7 @@ namespace Avalonia.Win32
public unsafe void SetFrameThemeVariant(PlatformThemeVariant themeVariant)
{
_currentThemeVariant = themeVariant;
if (Win32Platform.WindowsVersion.Build >= 22000)
{
var pvUseBackdropBrush = themeVariant == PlatformThemeVariant.Dark ? 1 : 0;
@ -786,6 +793,10 @@ namespace Avalonia.Win32
(int)DwmWindowAttribute.DWMWA_USE_IMMERSIVE_DARK_MODE,
&pvUseBackdropBrush,
sizeof(int));
if (TransparencyLevel == WindowTransparencyLevel.Mica)
{
SetTransparencyMica(Win32Platform.WindowsVersion);
}
}
}

1
src/tools/Avalonia.Analyzers/Avalonia.Analyzers.csproj

@ -5,6 +5,7 @@
<PackageId>Avalonia.Analyzers</PackageId>
<EnforceExtendedAnalyzerRules>true</EnforceExtendedAnalyzerRules>
<IsPackable>true</IsPackable>
<IncludeSymbols>false</IncludeSymbols>
<IsRoslynComponent>true</IsRoslynComponent>
</PropertyGroup>

1
src/tools/Avalonia.Generators/Avalonia.Generators.csproj

@ -5,6 +5,7 @@
<PackageId>Avalonia.Generators</PackageId>
<DefineConstants>$(DefineConstants);XAMLX_INTERNAL</DefineConstants>
<IsPackable>true</IsPackable>
<IncludeSymbols>false</IncludeSymbols>
<IsRoslynComponent>true</IsRoslynComponent>
<EnforceExtendedAnalyzerRules>true</EnforceExtendedAnalyzerRules>
</PropertyGroup>

2
tests/Avalonia.Base.UnitTests/Media/GlyphRunTests.cs

@ -111,7 +111,7 @@ namespace Avalonia.Base.UnitTests.Media
using(UnitTestApplication.Start(TestServices.MockPlatformRenderInterface))
using (var glyphRun = CreateGlyphRun(advances, clusters, bidiLevel))
{
var characterHit = glyphRun.GetPreviousCaretCharacterHit(new CharacterHit(currentIndex, currentLength));
var characterHit = glyphRun.GetPreviousCaretCharacterHit(new CharacterHit(currentIndex + currentLength));
Assert.Equal(previousIndex, characterHit.FirstCharacterIndex);

53
tests/Avalonia.Base.UnitTests/Utilities/InlineDictionaryTests.cs

@ -0,0 +1,53 @@
#nullable enable
using System.Collections.Generic;
using Avalonia.Utilities;
using Xunit;
namespace Avalonia.Base.UnitTests.Utilities;
public class InlineDictionaryTests
{
[Fact]
public void Enumeration_After_Add_With_Internal_Array_Works()
{
var dic = new InlineDictionary<string, int>();
dic.Add("foo", 1);
dic.Add("bar", 2);
dic.Add("baz", 3);
Assert.Equal(
new[] {
new KeyValuePair<string, int>("foo", 1),
new KeyValuePair<string, int>("bar", 2),
new KeyValuePair<string, int>("baz", 3)
},
dic);
}
[Fact]
public void Enumeration_After_Remove_With_Internal_Array_Works()
{
var dic = new InlineDictionary<string, int>();
dic.Add("foo", 1);
dic.Add("bar", 2);
dic.Add("baz", 3);
Assert.Equal(
new[] {
new KeyValuePair<string, int>("foo", 1),
new KeyValuePair<string, int>("bar", 2),
new KeyValuePair<string, int>("baz", 3)
},
dic);
dic.Remove("bar");
Assert.Equal(
new[] {
new KeyValuePair<string, int>("foo", 1),
new KeyValuePair<string, int>("baz", 3)
},
dic);
}
}

16
tests/Avalonia.Controls.UnitTests/Primitives/SelectingItemsControlTests_SelectedValue.cs

@ -268,6 +268,22 @@ namespace Avalonia.Controls.UnitTests.Primitives
Assert.True(called);
}
[Fact]
public void Handles_Null_SelectedItem_When_SelectedValueBinding_Assigned()
{
// Issue #11220
var items = new object[] { null };
var sic = new SelectingItemsControl
{
ItemsSource = items,
SelectedIndex = 0,
SelectedValueBinding = new Binding("Name"),
Template = Template()
};
Assert.Null(sic.SelectedValue);
}
private static FuncControlTemplate Template()
{
return new FuncControlTemplate<SelectingItemsControl>((control, scope) =>

116
tests/Avalonia.Skia.UnitTests/Media/TextFormatting/TextLineTests.cs

@ -194,7 +194,7 @@ namespace Avalonia.Skia.UnitTests.Media.TextFormatting
for (var i = 0; i < clusters.Count; i++)
{
var expectedCluster = clusters[i];
var actualCluster = nextCharacterHit.FirstCharacterIndex;
var actualCluster = nextCharacterHit.FirstCharacterIndex + nextCharacterHit.TrailingLength;
Assert.Equal(expectedCluster, actualCluster);
@ -278,16 +278,6 @@ namespace Avalonia.Skia.UnitTests.Media.TextFormatting
Assert.Equal(clusters[i],
previousCharacterHit.FirstCharacterIndex + previousCharacterHit.TrailingLength);
}
firstCharacterHit = previousCharacterHit;
firstCharacterHit = textLine.GetPreviousCaretCharacterHit(firstCharacterHit);
previousCharacterHit = textLine.GetPreviousCaretCharacterHit(firstCharacterHit);
Assert.Equal(firstCharacterHit.FirstCharacterIndex, previousCharacterHit.FirstCharacterIndex);
Assert.Equal(0, previousCharacterHit.TrailingLength);
}
}
@ -728,6 +718,110 @@ namespace Avalonia.Skia.UnitTests.Media.TextFormatting
}
}
[Fact]
public void Should_GetNextCaretCharacterHit_From_Mixed_TextBuffer()
{
using (Start())
{
var defaultProperties = new GenericTextRunProperties(Typeface.Default);
var textSource = new MixedTextBufferTextSource();
var formatter = new TextFormatterImpl();
var textLine =
formatter.FormatLine(textSource, 0, double.PositiveInfinity,
new GenericTextParagraphProperties(defaultProperties));
var characterHit = textLine.GetNextCaretCharacterHit(new CharacterHit(9, 1));
Assert.Equal(10, characterHit.FirstCharacterIndex);
Assert.Equal(1, characterHit.TrailingLength);
characterHit = textLine.GetNextCaretCharacterHit(characterHit);
Assert.Equal(11, characterHit.FirstCharacterIndex);
Assert.Equal(1, characterHit.TrailingLength);
characterHit = textLine.GetNextCaretCharacterHit(new CharacterHit(19, 1));
Assert.Equal(20, characterHit.FirstCharacterIndex);
Assert.Equal(1, characterHit.TrailingLength);
characterHit = textLine.GetNextCaretCharacterHit(new CharacterHit(10));
Assert.Equal(11, characterHit.FirstCharacterIndex);
Assert.Equal(0, characterHit.TrailingLength);
characterHit = textLine.GetNextCaretCharacterHit(characterHit);
Assert.Equal(12, characterHit.FirstCharacterIndex);
Assert.Equal(0, characterHit.TrailingLength);
characterHit = textLine.GetNextCaretCharacterHit(new CharacterHit(20));
Assert.Equal(21, characterHit.FirstCharacterIndex);
Assert.Equal(0, characterHit.TrailingLength);
}
}
[Fact]
public void Should_GetPreviousCaretCharacterHit_From_Mixed_TextBuffer()
{
using (Start())
{
var defaultProperties = new GenericTextRunProperties(Typeface.Default);
var textSource = new MixedTextBufferTextSource();
var formatter = new TextFormatterImpl();
var textLine =
formatter.FormatLine(textSource, 0, double.PositiveInfinity,
new GenericTextParagraphProperties(defaultProperties));
var characterHit = textLine.GetPreviousCaretCharacterHit(new CharacterHit(20, 1));
Assert.Equal(19, characterHit.FirstCharacterIndex);
Assert.Equal(1, characterHit.TrailingLength);
characterHit = textLine.GetPreviousCaretCharacterHit(new CharacterHit(10, 1));
Assert.Equal(9, characterHit.FirstCharacterIndex);
Assert.Equal(1, characterHit.TrailingLength);
characterHit = textLine.GetPreviousCaretCharacterHit(characterHit);
Assert.Equal(8, characterHit.FirstCharacterIndex);
Assert.Equal(1, characterHit.TrailingLength);
characterHit = textLine.GetPreviousCaretCharacterHit(new CharacterHit(21));
Assert.Equal(20, characterHit.FirstCharacterIndex);
Assert.Equal(0, characterHit.TrailingLength);
characterHit = textLine.GetPreviousCaretCharacterHit(new CharacterHit(11));
Assert.Equal(10, characterHit.FirstCharacterIndex);
Assert.Equal(0, characterHit.TrailingLength);
characterHit = textLine.GetPreviousCaretCharacterHit(characterHit);
Assert.Equal(9, characterHit.FirstCharacterIndex);
Assert.Equal(0, characterHit.TrailingLength);
}
}
private class MixedTextBufferTextSource : ITextSource
{
public TextRun? GetTextRun(int textSourceIndex)

Loading…
Cancel
Save