Browse Source

Merge branch 'master' into xy-focus

xy-focus-and-tvos
Max Katz 2 years ago
committed by GitHub
parent
commit
b7759cfef4
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 1
      .gitignore
  2. 2
      .nuke/build.schema.json
  3. 13
      Avalonia.sln
  4. 23
      native/Avalonia.Native/src/OSX/app.mm
  5. 1
      native/Avalonia.Native/src/OSX/common.h
  6. 150
      nukebuild/ApiDiffHelper.cs
  7. 16
      nukebuild/Build.cs
  8. 3
      nukebuild/_build.csproj
  9. 22
      packages/Avalonia/AvaloniaBuildTasks.targets
  10. 14
      readme.md
  11. 7
      samples/ControlCatalog.Android/MainActivity.cs
  12. 8
      samples/ControlCatalog/App.xaml.cs
  13. 2
      src/Android/Avalonia.Android/AvaloniaMainActivity.App.cs
  14. 40
      src/Android/Avalonia.Android/AvaloniaMainActivity.cs
  15. 1
      src/Android/Avalonia.Android/IAndroidNavigationService.cs
  16. 10
      src/Android/Avalonia.Android/IAvaloniaActivity.cs
  17. 8
      src/Android/Avalonia.Android/Platform/SkiaPlatform/TopLevelImpl.cs
  18. 16
      src/Android/Avalonia.Android/Platform/Specific/Helpers/AndroidKeyboardEventsHelper.cs
  19. 23
      src/Android/Avalonia.Android/SingleViewLifetime.cs
  20. 2
      src/Avalonia.Base/Animation/Animators/BoolAnimator.cs
  21. 2
      src/Avalonia.Base/Animation/Animators/ByteAnimator.cs
  22. 2
      src/Avalonia.Base/Animation/Animators/DecimalAnimator.cs
  23. 2
      src/Avalonia.Base/Animation/Animators/DoubleAnimator.cs
  24. 2
      src/Avalonia.Base/Animation/Animators/FloatAnimator.cs
  25. 2
      src/Avalonia.Base/Animation/Animators/Int16Animator.cs
  26. 2
      src/Avalonia.Base/Animation/Animators/Int32Animator.cs
  27. 2
      src/Avalonia.Base/Animation/Animators/Int64Animator.cs
  28. 2
      src/Avalonia.Base/Animation/Animators/TransformAnimator.cs
  29. 2
      src/Avalonia.Base/Animation/Animators/UInt16Animator.cs
  30. 2
      src/Avalonia.Base/Animation/Animators/UInt32Animator.cs
  31. 2
      src/Avalonia.Base/Animation/Animators/UInt64Animator.cs
  32. 4
      src/Avalonia.Base/Animation/TransitionBase.cs
  33. 25
      src/Avalonia.Base/Input/Gestures.cs
  34. 69
      src/Avalonia.Base/Input/Key.cs
  35. 15
      src/Avalonia.Base/Input/KeyDeviceType.cs
  36. 5
      src/Avalonia.Base/Input/KeyEventArgs.cs
  37. 1
      src/Avalonia.Base/Input/KeyboardDevice.cs
  38. 17
      src/Avalonia.Base/Input/Raw/RawKeyEventArgs.cs
  39. 90
      src/Avalonia.Base/Media/Pen.cs
  40. 64
      src/Avalonia.Base/Media/StreamGeometryContext.cs
  41. 20
      src/Avalonia.Base/Platform/IGeometryContext.cs
  42. 19
      src/Avalonia.Base/Platform/PathGeometryContext.cs
  43. 2
      src/Avalonia.Base/Styling/ControlTheme.cs
  44. 2
      src/Avalonia.Base/Styling/Style.cs
  45. 10
      src/Avalonia.Base/Styling/StyleBase.cs
  46. 12
      src/Avalonia.Controls.DataGrid/Themes/Fluent.xaml
  47. 23
      src/Avalonia.Controls/ApplicationLifetimes/ActivatedEventArgs.cs
  48. 25
      src/Avalonia.Controls/ApplicationLifetimes/ActivationKind.cs
  49. 13
      src/Avalonia.Controls/ApplicationLifetimes/ClassicDesktopStyleApplicationLifetime.cs
  50. 35
      src/Avalonia.Controls/ApplicationLifetimes/IActivatableApplicationLifetime.cs
  51. 13
      src/Avalonia.Controls/ApplicationLifetimes/ProtocolActivatedEventArgs.cs
  52. 13
      src/Avalonia.Controls/ContextMenu.cs
  53. 1
      src/Avalonia.Controls/Platform/INativeApplicationCommands.cs
  54. 7
      src/Avalonia.Controls/Presenters/ContentPresenter.cs
  55. 20
      src/Avalonia.Controls/Presenters/TextPresenter.cs
  56. 115
      src/Avalonia.Controls/Primitives/SelectingItemsControl.cs
  57. 2
      src/Avalonia.Controls/Primitives/TextSelectionCanvas.cs
  58. 4
      src/Avalonia.Controls/Remote/RemoteWidget.cs
  59. 63
      src/Avalonia.Controls/Shapes/Shape.cs
  60. 28
      src/Avalonia.Controls/TextBox.cs
  61. 1
      src/Avalonia.Controls/TopLevel.cs
  62. 34
      src/Avalonia.Controls/Utils/BorderRenderHelper.cs
  63. 12
      src/Avalonia.DesignerSupport/Remote/RemoteDesignerEntryPoint.cs
  64. 5
      src/Avalonia.Diagnostics/Diagnostics/DevToolsOptions.cs
  65. 20
      src/Avalonia.Diagnostics/Diagnostics/DevToolsViewKind.cs
  66. 1
      src/Avalonia.Diagnostics/Diagnostics/ViewModels/MainViewModel.cs
  67. 2
      src/Avalonia.FreeDesktop/Avalonia.FreeDesktop.csproj
  68. 15
      src/Avalonia.FreeDesktop/DBusIme/DBusTextInputMethodBase.cs
  69. 32
      src/Avalonia.Native/AvaloniaNativeApplicationPlatform.cs
  70. 12
      src/Avalonia.Native/AvaloniaNativeMenuExporter.cs
  71. 4
      src/Avalonia.Native/AvaloniaNativePlatformExtensions.cs
  72. 50
      src/Avalonia.Native/MacOSClassicDesktopStyleApplicationLifetime.cs
  73. 6
      src/Avalonia.Native/MacOSNativeMenuCommands.cs
  74. 4
      src/Avalonia.Native/WindowImplBase.cs
  75. 4
      src/Avalonia.Native/avn.idl
  76. 8
      src/Avalonia.OpenGL/Controls/OpenGlControlBase.cs
  77. 2
      src/Avalonia.OpenGL/Egl/EglContext.cs
  78. 6
      src/Avalonia.Themes.Fluent/Accents/FluentControlResources.xaml
  79. 4
      src/Avalonia.Themes.Fluent/Controls/AutoCompleteBox.xaml
  80. 3
      src/Avalonia.Themes.Fluent/Controls/ButtonSpinner.xaml
  81. 1
      src/Avalonia.Themes.Fluent/Controls/Calendar.xaml
  82. 4
      src/Avalonia.Themes.Fluent/Controls/CalendarButton.xaml
  83. 2
      src/Avalonia.Themes.Fluent/Controls/CalendarDayButton.xaml
  84. 3
      src/Avalonia.Themes.Fluent/Controls/CalendarItem.xaml
  85. 1
      src/Avalonia.Themes.Fluent/Controls/CheckBox.xaml
  86. 1
      src/Avalonia.Themes.Fluent/Controls/ComboBox.xaml
  87. 2
      src/Avalonia.Themes.Fluent/Controls/ContextMenu.xaml
  88. 18
      src/Avalonia.Themes.Fluent/Controls/DatePicker.xaml
  89. 8
      src/Avalonia.Themes.Fluent/Controls/HeaderedContentControl.xaml
  90. 1
      src/Avalonia.Themes.Fluent/Controls/ListBox.xaml
  91. 5
      src/Avalonia.Themes.Fluent/Controls/ListBoxItem.xaml
  92. 1
      src/Avalonia.Themes.Fluent/Controls/Menu.xaml
  93. 34
      src/Avalonia.Themes.Fluent/Controls/MenuItem.xaml
  94. 1
      src/Avalonia.Themes.Fluent/Controls/NotificationCard.xaml
  95. 2
      src/Avalonia.Themes.Fluent/Controls/NumericUpDown.xaml
  96. 2
      src/Avalonia.Themes.Fluent/Controls/OverlayPopupHost.xaml
  97. 2
      src/Avalonia.Themes.Fluent/Controls/PopupRoot.xaml
  98. 1
      src/Avalonia.Themes.Fluent/Controls/RadioButton.xaml
  99. 2
      src/Avalonia.Themes.Fluent/Controls/RepeatButton.xaml
  100. 1
      src/Avalonia.Themes.Fluent/Controls/Slider.xaml

1
.gitignore

@ -217,3 +217,4 @@ node_modules
src/Browser/Avalonia.Browser.Blazor/webapp/package-lock.json src/Browser/Avalonia.Browser.Blazor/webapp/package-lock.json
src/Browser/Avalonia.Browser.Blazor/wwwroot src/Browser/Avalonia.Browser.Blazor/wwwroot
src/Browser/Avalonia.Browser/wwwroot src/Browser/Avalonia.Browser/wwwroot
api/diff

2
.nuke/build.schema.json

@ -83,6 +83,7 @@
"CreateIntermediateNugetPackages", "CreateIntermediateNugetPackages",
"CreateNugetPackages", "CreateNugetPackages",
"GenerateCppHeaders", "GenerateCppHeaders",
"OutputApiDiff",
"Package", "Package",
"RunCoreLibsTests", "RunCoreLibsTests",
"RunHtmlPreviewerTests", "RunHtmlPreviewerTests",
@ -117,6 +118,7 @@
"CreateIntermediateNugetPackages", "CreateIntermediateNugetPackages",
"CreateNugetPackages", "CreateNugetPackages",
"GenerateCppHeaders", "GenerateCppHeaders",
"OutputApiDiff",
"Package", "Package",
"RunCoreLibsTests", "RunCoreLibsTests",
"RunHtmlPreviewerTests", "RunHtmlPreviewerTests",

13
Avalonia.sln

@ -236,6 +236,19 @@ EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{176582E8-46AF-416A-85C1-13A5C6744497}" Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{176582E8-46AF-416A-85C1-13A5C6744497}"
ProjectSection(SolutionItems) = preProject ProjectSection(SolutionItems) = preProject
.editorconfig = .editorconfig .editorconfig = .editorconfig
azure-pipelines.yml = azure-pipelines.yml
azure-pipelines-integrationtests.yml = azure-pipelines-integrationtests.yml
CODE_OF_CONDUCT.md = CODE_OF_CONDUCT.md
CONTRIBUTING.md = CONTRIBUTING.md
Directory.Build.props = Directory.Build.props
Directory.Build.targets = Directory.Build.targets
dirs.proj = dirs.proj
global.json = global.json
licence.md = licence.md
NOTICE.md = NOTICE.md
NuGet.Config = NuGet.Config
readme.md = readme.md
Settings.StyleCop = Settings.StyleCop
EndProjectSection EndProjectSection
EndProject EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Avalonia.Controls.ItemsRepeater", "src\Avalonia.Controls.ItemsRepeater\Avalonia.Controls.ItemsRepeater.csproj", "{EE0F0DD4-A70D-472B-BD5D-B7D32D0E9386}" Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Avalonia.Controls.ItemsRepeater", "src\Avalonia.Controls.ItemsRepeater\Avalonia.Controls.ItemsRepeater.csproj", "{EE0F0DD4-A70D-472B-BD5D-B7D32D0E9386}"

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

@ -43,6 +43,22 @@ ComPtr<IAvnApplicationEvents> _events;
[[NSRunningApplication currentApplication] activateWithOptions:NSApplicationActivateIgnoringOtherApps]; [[NSRunningApplication currentApplication] activateWithOptions:NSApplicationActivateIgnoringOtherApps];
} }
-(BOOL)applicationShouldHandleReopen:(NSApplication *)sender hasVisibleWindows:(BOOL)flag
{
_events->OnReopen();
return YES;
}
- (void)applicationDidHide:(NSNotification *)notification
{
_events->OnHide();
}
- (void)applicationDidUnhide:(NSNotification *)notification
{
_events->OnUnhide();
}
- (void)application:(NSApplication *)sender openFiles:(NSArray<NSString *> *)filenames - (void)application:(NSApplication *)sender openFiles:(NSArray<NSString *> *)filenames
{ {
auto array = CreateAvnStringArray(filenames); auto array = CreateAvnStringArray(filenames);
@ -123,6 +139,13 @@ extern void ReleaseAvnAppEvents()
} }
} }
HRESULT AvnApplicationCommands::UnhideApp()
{
START_COM_CALL;
[[NSApplication sharedApplication] unhide:[NSApp delegate]];
return S_OK;
}
HRESULT AvnApplicationCommands::HideApp() HRESULT AvnApplicationCommands::HideApp()
{ {
START_COM_CALL; START_COM_CALL;

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

@ -101,6 +101,7 @@ class AvnApplicationCommands : public ComSingleObject<IAvnApplicationCommands, &
public: public:
FORWARD_IUNKNOWN() FORWARD_IUNKNOWN()
virtual HRESULT UnhideApp() override;
virtual HRESULT HideApp() override; virtual HRESULT HideApp() override;
virtual HRESULT ShowAll() override; virtual HRESULT ShowAll() override;
virtual HRESULT HideOthers() override; virtual HRESULT HideOthers() override;

150
nukebuild/ApiDiffValidation.cs → nukebuild/ApiDiffHelper.cs

@ -1,5 +1,6 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Diagnostics;
using System.IO; using System.IO;
using System.IO.Compression; using System.IO.Compression;
using System.Linq; using System.Linq;
@ -10,9 +11,95 @@ using System.Threading.Tasks;
using Nuke.Common.Tooling; using Nuke.Common.Tooling;
using static Serilog.Log; using static Serilog.Log;
public static class ApiDiffValidation public static class ApiDiffHelper
{ {
private static readonly HttpClient s_httpClient = new(); static readonly HttpClient s_httpClient = new();
public static async Task GetDiff(
Tool apiDiffTool, string outputFolder,
string packagePath, string baselineVersion)
{
await using var baselineStream = await DownloadBaselinePackage(packagePath, baselineVersion);
if (baselineStream == null)
return;
if (!Directory.Exists(outputFolder))
{
Directory.CreateDirectory(outputFolder!);
}
using (var target = new ZipArchive(File.Open(packagePath, FileMode.Open, FileAccess.Read), ZipArchiveMode.Read))
using (var baseline = new ZipArchive(baselineStream, ZipArchiveMode.Read))
using (Helpers.UseTempDir(out var tempFolder))
{
var targetDlls = GetDlls(target);
var baselineDlls = GetDlls(baseline);
var pairs = new List<(string baseline, string target)>();
var packageId = GetPackageId(packagePath);
// 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.
// So, always use Unix '/'
foreach (var baselineDll in baselineDlls)
{
var baselineDllPath = await ExtractDll("baseline", baselineDll, tempFolder);
var targetTfm = baselineDll.target;
if (s_tfmRedirects.FirstOrDefault(t => baselineDll.target.StartsWith(t.oldTfm)).newTfm is {} newTfm)
{
targetTfm = newTfm;
}
var targetDll = targetDlls.FirstOrDefault(e =>
e.target.StartsWith(targetTfm) && e.entry.Name == baselineDll.entry.Name);
if (targetDll?.entry is null)
{
throw new InvalidOperationException($"Some assemblies are missing in the new package {packageId}: {baselineDll.entry.Name} for {baselineDll.target}");
}
var targetDllPath = await ExtractDll("target", targetDll, tempFolder);
pairs.Add((baselineDllPath, targetDllPath));
}
await Task.WhenAll(pairs.Select(p => Task.Run(() =>
{
var baselineApi = p.baseline + ".api.cs";
var targetApi = p.target + ".api.cs";
var resultDiff = p.target + ".api.diff.cs";
GenerateApiListing(apiDiffTool, p.baseline, baselineApi, tempFolder);
GenerateApiListing(apiDiffTool, p.target, targetApi, tempFolder);
var args = $"""-c core.autocrlf=false diff --no-index --minimal """;
args += """--ignore-matching-lines="^\[assembly: System.Reflection.AssemblyVersionAttribute" """;
args += $""" --output {resultDiff} {baselineApi} {targetApi}""";
using (var gitProcess = new Process())
{
gitProcess.StartInfo = new ProcessStartInfo
{
CreateNoWindow = true,
RedirectStandardError = false,
RedirectStandardOutput = false,
FileName = "git",
Arguments = args,
WorkingDirectory = tempFolder
};
gitProcess.Start();
gitProcess.WaitForExit();
}
var resultFile = new FileInfo(Path.Combine(tempFolder, resultDiff));
if (resultFile.Length > 0)
{
resultFile.CopyTo(Path.Combine(outputFolder, Path.GetFileName(resultDiff)), true);
}
})));
}
}
private static readonly (string oldTfm, string newTfm)[] s_tfmRedirects = new[] private static readonly (string oldTfm, string newTfm)[] s_tfmRedirects = new[]
{ {
@ -25,12 +112,6 @@ public static class ApiDiffValidation
Tool apiCompatTool, string packagePath, string baselineVersion, Tool apiCompatTool, string packagePath, string baselineVersion,
string suppressionFilesFolder, bool updateSuppressionFile) string suppressionFilesFolder, bool updateSuppressionFile)
{ {
if (baselineVersion is null)
{
throw new InvalidOperationException(
"Build \"api-baseline\" parameter must be set when running Nuke CreatePackages");
}
if (!Directory.Exists(suppressionFilesFolder)) if (!Directory.Exists(suppressionFilesFolder))
{ {
Directory.CreateDirectory(suppressionFilesFolder!); Directory.CreateDirectory(suppressionFilesFolder!);
@ -58,13 +139,7 @@ public static class ApiDiffValidation
// So, always use Unix '/' // So, always use Unix '/'
foreach (var baselineDll in baselineDlls) foreach (var baselineDll in baselineDlls)
{ {
var baselineDllPath = $"baseline/{baselineDll.target}/{baselineDll.entry.Name}"; var baselineDllPath = await ExtractDll("baseline", baselineDll, tempFolder);
var baselineDllRealPath = Path.Combine(tempFolder, baselineDllPath);
Directory.CreateDirectory(Path.GetDirectoryName(baselineDllRealPath)!);
await using (var baselineDllFile = File.Create(baselineDllRealPath))
{
await baselineDll.entry.Open().CopyToAsync(baselineDllFile);
}
var targetTfm = baselineDll.target; var targetTfm = baselineDll.target;
if (s_tfmRedirects.FirstOrDefault(t => baselineDll.target.StartsWith(t.oldTfm)).newTfm is {} newTfm) if (s_tfmRedirects.FirstOrDefault(t => baselineDll.target.StartsWith(t.oldTfm)).newTfm is {} newTfm)
@ -79,13 +154,7 @@ public static class ApiDiffValidation
throw new InvalidOperationException($"Some assemblies are missing in the new package {packageId}: {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}"; var targetDllPath = await ExtractDll("target", targetDll, tempFolder);
var targetDllRealPath = Path.Combine(tempFolder, targetDllPath);
Directory.CreateDirectory(Path.GetDirectoryName(targetDllRealPath)!);
await using (var targetDllFile = File.Create(targetDllRealPath))
{
await targetDll.entry.Open().CopyToAsync(targetDllFile);
}
left.Add(baselineDllPath); left.Add(baselineDllPath);
right.Add(targetDllPath); right.Add(targetDllPath);
@ -116,7 +185,9 @@ public static class ApiDiffValidation
} }
} }
private static IReadOnlyCollection<(string target, ZipArchiveEntry entry)> GetDlls(ZipArchive archive) record DllEntry(string target, ZipArchiveEntry entry);
static IReadOnlyCollection<DllEntry> GetDlls(ZipArchive archive)
{ {
return archive.Entries return archive.Entries
.Where(e => Path.GetExtension(e.FullName) == ".dll" .Where(e => Path.GetExtension(e.FullName) == ".dll"
@ -130,12 +201,18 @@ public static class ApiDiffValidation
) )
.GroupBy(e => (e.target, e.entry.Name)) .GroupBy(e => (e.target, e.entry.Name))
.Select(g => g.MaxBy(e => e.isRef)) .Select(g => g.MaxBy(e => e.isRef))
.Select(e => (e.target, e.entry)) .Select(e => new DllEntry(e.target, e.entry))
.ToArray(); .ToArray();
} }
static async Task<Stream> DownloadBaselinePackage(string packagePath, string baselineVersion) static async Task<Stream> DownloadBaselinePackage(string packagePath, string baselineVersion)
{ {
if (baselineVersion is null)
{
throw new InvalidOperationException(
"Build \"api-baseline\" parameter must be set when running Nuke CreatePackages");
}
/* /*
Gets package name from versions like: Gets package name from versions like:
Avalonia.0.10.0-preview1 Avalonia.0.10.0-preview1
@ -167,6 +244,31 @@ public static class ApiDiffValidation
} }
} }
static async Task<string> ExtractDll(string basePath, DllEntry dllEntry, string targetFolder)
{
var dllPath = $"{basePath}/{dllEntry.target}/{dllEntry.entry.Name}";
var dllRealPath = Path.Combine(targetFolder, dllPath);
Directory.CreateDirectory(Path.GetDirectoryName(dllRealPath)!);
await using (var dllFile = File.Create(dllRealPath))
{
await dllEntry.entry.Open().CopyToAsync(dllFile);
}
return dllPath;
}
static void GenerateApiListing(Tool apiDiffTool, string inputFile, string outputFile, string workingDif)
{
var args = $""" --assembly={inputFile} --output-path={outputFile} --include-assembly-attributes=true""";
var result = apiDiffTool(args, workingDif)
.Where(t => t.Type == OutputType.Err).ToArray();
if (result.Any())
{
throw new AggregateException($"GetApi tool failed task has failed",
result.Select(r => new Exception(r.Text)));
}
}
static string GetPackageId(string packagePath) static string GetPackageId(string packagePath)
{ {
return Regex.Replace( return Regex.Replace(

16
nukebuild/Build.cs

@ -18,6 +18,7 @@ using static Nuke.Common.Tools.Xunit.XunitTasks;
using static Nuke.Common.Tools.VSWhere.VSWhereTasks; using static Nuke.Common.Tools.VSWhere.VSWhereTasks;
using static Serilog.Log; using static Serilog.Log;
using MicroCom.CodeGenerator; using MicroCom.CodeGenerator;
using Nuke.Common.IO;
/* /*
Before editing this file, install support plugin for your IDE, Before editing this file, install support plugin for your IDE,
@ -33,6 +34,9 @@ partial class Build : NukeBuild
[PackageExecutable("Microsoft.DotNet.ApiCompat.Tool", "Microsoft.DotNet.ApiCompat.Tool.dll", Framework = "net6.0")] [PackageExecutable("Microsoft.DotNet.ApiCompat.Tool", "Microsoft.DotNet.ApiCompat.Tool.dll", Framework = "net6.0")]
Tool ApiCompatTool; Tool ApiCompatTool;
[PackageExecutable("Microsoft.DotNet.GenAPI.Tool", "Microsoft.DotNet.GenAPI.Tool.dll", Framework = "net8.0")]
Tool ApiGenTool;
protected override void OnBuildInitialized() protected override void OnBuildInitialized()
{ {
@ -283,11 +287,21 @@ partial class Build : NukeBuild
.Executes(async () => .Executes(async () =>
{ {
await Task.WhenAll( await Task.WhenAll(
Directory.GetFiles(Parameters.NugetRoot, "*.nupkg").Select(nugetPackage => ApiDiffValidation.ValidatePackage( Directory.GetFiles(Parameters.NugetRoot, "*.nupkg").Select(nugetPackage => ApiDiffHelper.ValidatePackage(
ApiCompatTool, nugetPackage, Parameters.ApiValidationBaseline, ApiCompatTool, nugetPackage, Parameters.ApiValidationBaseline,
Parameters.ApiValidationSuppressionFiles, Parameters.UpdateApiValidationSuppression))); Parameters.ApiValidationSuppressionFiles, Parameters.UpdateApiValidationSuppression)));
}); });
Target OutputApiDiff => _ => _
.DependsOn(CreateNugetPackages)
.Executes(async () =>
{
await Task.WhenAll(
Directory.GetFiles(Parameters.NugetRoot, "*.nupkg").Select(nugetPackage => ApiDiffHelper.GetDiff(
ApiGenTool, RootDirectory / "api" / "diff",
nugetPackage, Parameters.ApiValidationBaseline)));
});
Target RunTests => _ => _ Target RunTests => _ => _
.DependsOn(RunCoreLibsTests) .DependsOn(RunCoreLibsTests)
.DependsOn(RunRenderTests) .DependsOn(RunRenderTests)

3
nukebuild/_build.csproj

@ -7,6 +7,8 @@
<NoWarn>$(NoWarn);CS0649;CS0169;SYSLIB0011</NoWarn> <NoWarn>$(NoWarn);CS0649;CS0169;SYSLIB0011</NoWarn>
<NukeTelemetryVersion>1</NukeTelemetryVersion> <NukeTelemetryVersion>1</NukeTelemetryVersion>
<TargetFramework>net7.0</TargetFramework> <TargetFramework>net7.0</TargetFramework>
<!-- Necessary for Microsoft.DotNet.GenAPI.Tool -->
<RestoreAdditionalProjectSources>https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet8-transport/nuget/v3/index.json</RestoreAdditionalProjectSources>
</PropertyGroup> </PropertyGroup>
<Import Project="..\build\JetBrains.dotMemoryUnit.props" /> <Import Project="..\build\JetBrains.dotMemoryUnit.props" />
@ -24,6 +26,7 @@
</PackageReference> </PackageReference>
<PackageDownload Include="Microsoft.DotNet.ApiCompat.Tool" Version="[7.0.305]" /> <PackageDownload Include="Microsoft.DotNet.ApiCompat.Tool" Version="[7.0.305]" />
<PackageDownload Include="Microsoft.DotNet.GenAPI.Tool" Version="[8.0.101-servicing.23580.12]" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>

22
packages/Avalonia/AvaloniaBuildTasks.targets

@ -131,6 +131,7 @@
<AvaloniaXamlIlVerifyIl Condition="'$(AvaloniaXamlIlVerifyIl)' == ''">false</AvaloniaXamlIlVerifyIl> <AvaloniaXamlIlVerifyIl Condition="'$(AvaloniaXamlIlVerifyIl)' == ''">false</AvaloniaXamlIlVerifyIl>
<AvaloniaXamlIlDebuggerLaunch Condition="'$(AvaloniaXamlIlDebuggerLaunch)' == ''">false</AvaloniaXamlIlDebuggerLaunch> <AvaloniaXamlIlDebuggerLaunch Condition="'$(AvaloniaXamlIlDebuggerLaunch)' == ''">false</AvaloniaXamlIlDebuggerLaunch>
<AvaloniaXamlVerboseExceptions Condition="'$(AvaloniaXamlVerboseExceptions)' == ''">false</AvaloniaXamlVerboseExceptions> <AvaloniaXamlVerboseExceptions Condition="'$(AvaloniaXamlVerboseExceptions)' == ''">false</AvaloniaXamlVerboseExceptions>
<_AvaloniaHasCompiledXaml>true</_AvaloniaHasCompiledXaml>
</PropertyGroup> </PropertyGroup>
<WriteLinesToFile <WriteLinesToFile
Condition="'$(_AvaloniaForceInternalMSBuild)' != 'true'" Condition="'$(_AvaloniaForceInternalMSBuild)' != 'true'"
@ -195,4 +196,25 @@
<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;"/> <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> </Target>
<!--
Deletes the target ref assembly before the CopyRefAssembly task (in target CopyFilesToOutputDirectory) tries to access it.
CopyRefAssembly reads the ref assembly's MVID from the .mvid PE section to avoid copying if necessary.
However, Cecil doesn't preserve that PE section: this results in a warning.
By deleting the file beforehand, we're preventing the warning.
There are no changes in behavior since CopyRefAssembly always copy the file if it couldn't read the MVID.
-->
<Target
Name="AvaloniaDeleteRefAssemblyBeforeOutputCopy"
BeforeTargets="CopyFilesToOutputDirectory"
Condition="
'$(_AvaloniaHasCompiledXaml)' == 'true' and
'$(TargetRefPath)' != '' and
'$(ProduceReferenceAssembly)' == 'true' and
('$(CopyBuildOutputToOutputDirectory)' == '' or '$(CopyBuildOutputToOutputDirectory)' == 'true') and
'$(SkipCopyBuildProduct)' != 'true'">
<Delete Files="$(TargetRefPath)" Condition="Exists('$(TargetRefPath)')" />
</Target>
</Project> </Project>

14
readme.md

@ -8,7 +8,7 @@
## 📖 About ## 📖 About
[Avalonia](https://avaloniaui.net) is a cross-platform UI framework for dotnet, providing a flexible styling system and supporting a wide range of platforms such as Windows, macOS, Linux, iOS, Android and WebAssembly. Avalonia is mature and production ready and is used by companies, including [Schneider Electric](https://avaloniaui.net/showcase#se), [Unity](https://avaloniaui.net/showcase#unity), [JetBrains](https://avaloniaui.net/showcase#rider) and [Github](https://avaloniaui.net/showcase#github). [Avalonia](https://avaloniaui.net) is a cross-platform UI framework for dotnet, providing a flexible styling system and supporting a wide range of platforms such as Windows, macOS, Linux, iOS, Android and WebAssembly. Avalonia is mature and production ready and is used by companies, including [Schneider Electric](https://avaloniaui.net/showcase#se), [Unity](https://avaloniaui.net/showcase#unity), [JetBrains](https://avaloniaui.net/showcase#rider) and [GitHub](https://avaloniaui.net/showcase#github).
Considered by many to be the spiritual successor to WPF, Avalonia UI provides a familiar, modern development experience for XAML developers creating cross-platform applications. While Avalonia UI is [similar to WPF](https://docs.avaloniaui.net/docs/next/get-started/wpf/), it isn't a 1:1 copy, and you'll find plenty of improvements. Considered by many to be the spiritual successor to WPF, Avalonia UI provides a familiar, modern development experience for XAML developers creating cross-platform applications. While Avalonia UI is [similar to WPF](https://docs.avaloniaui.net/docs/next/get-started/wpf/), it isn't a 1:1 copy, and you'll find plenty of improvements.
@ -106,18 +106,6 @@ Thank you to all our backers! 🙏 [[Become a backer](https://opencollective.com
Support this project by becoming a sponsor. Your logo will show up here with a link to your website. [[Become a sponsor](https://opencollective.com/Avalonia#sponsor)] Support this project by becoming a sponsor. Your logo will show up here with a link to your website. [[Become a sponsor](https://opencollective.com/Avalonia#sponsor)]
<a href="https://opencollective.com/Avalonia/sponsor/0/website" target="_blank"><img src="https://opencollective.com/Avalonia/sponsor/0/avatar.svg"></a>
<a href="https://opencollective.com/Avalonia/sponsor/1/website" target="_blank"><img src="https://opencollective.com/Avalonia/sponsor/1/avatar.svg"></a>
<a href="https://opencollective.com/Avalonia/sponsor/2/website" target="_blank"><img src="https://opencollective.com/Avalonia/sponsor/2/avatar.svg"></a>
<a href="https://opencollective.com/Avalonia/sponsor/3/website" target="_blank"><img src="https://opencollective.com/Avalonia/sponsor/3/avatar.svg"></a>
<a href="https://opencollective.com/Avalonia/sponsor/4/website" target="_blank"><img src="https://opencollective.com/Avalonia/sponsor/4/avatar.svg"></a>
<a href="https://opencollective.com/Avalonia/sponsor/5/website" target="_blank"><img src="https://opencollective.com/Avalonia/sponsor/5/avatar.svg"></a>
<a href="https://opencollective.com/Avalonia/sponsor/6/website" target="_blank"><img src="https://opencollective.com/Avalonia/sponsor/6/avatar.svg"></a>
<a href="https://opencollective.com/Avalonia/sponsor/7/website" target="_blank"><img src="https://opencollective.com/Avalonia/sponsor/7/avatar.svg"></a>
<a href="https://opencollective.com/Avalonia/sponsor/8/website" target="_blank"><img src="https://opencollective.com/Avalonia/sponsor/8/avatar.svg"></a>
<a href="https://opencollective.com/Avalonia/sponsor/9/website" target="_blank"><img src="https://opencollective.com/Avalonia/sponsor/9/avatar.svg"></a>
<a href="https://baseheadinc.com/" target="_blank"><img height="50" src="https://baseheadinc.com/wp-content/uploads/2020/09/BH-Logo-for-Site-Header-New.png"></a>
<a href="https://aws.amazon.com/developer/language/net/" target="_blank"><img src="https://github.com/AvaloniaUI/Avalonia/assets/552074/7771d8b9-ef84-4503-9889-033a87d2c852"></a> <a href="https://aws.amazon.com/developer/language/net/" target="_blank"><img src="https://github.com/AvaloniaUI/Avalonia/assets/552074/7771d8b9-ef84-4503-9889-033a87d2c852"></a>
## Commercial Support ## Commercial Support

7
samples/ControlCatalog.Android/MainActivity.cs

@ -2,13 +2,16 @@
using Android.Content.PM; using Android.Content.PM;
using Avalonia; using Avalonia;
using Avalonia.Android; using Avalonia.Android;
using static Android.Content.Intent;
namespace ControlCatalog.Android namespace ControlCatalog.Android
{ {
[Activity(Label = "ControlCatalog.Android", Theme = "@style/MyTheme.NoActionBar", Icon = "@drawable/icon", MainLauncher = true, ConfigurationChanges = ConfigChanges.Orientation | ConfigChanges.ScreenSize | ConfigChanges.UiMode)] [Activity(Label = "ControlCatalog.Android", Theme = "@style/MyTheme.NoActionBar", Icon = "@drawable/icon", MainLauncher = true, Exported = true, ConfigurationChanges = ConfigChanges.Orientation | ConfigChanges.ScreenSize | ConfigChanges.UiMode)]
// IntentFilter are here to test IActivatableApplicationLifetime
[IntentFilter(new [] { ActionView }, Categories = new [] { CategoryDefault, CategoryBrowsable }, DataScheme = "avln" )]
public class MainActivity : AvaloniaMainActivity<App> public class MainActivity : AvaloniaMainActivity<App>
{ {
protected override Avalonia.AppBuilder CustomizeAppBuilder(Avalonia.AppBuilder builder) protected override AppBuilder CustomizeAppBuilder(AppBuilder builder)
{ {
return base.CustomizeAppBuilder(builder) return base.CustomizeAppBuilder(builder)
.AfterSetup(_ => .AfterSetup(_ =>

8
samples/ControlCatalog/App.xaml.cs

@ -51,6 +51,14 @@ namespace ControlCatalog
singleViewLifetime.MainView = new MainView { DataContext = new MainWindowViewModel() }; singleViewLifetime.MainView = new MainView { DataContext = new MainWindowViewModel() };
} }
if (ApplicationLifetime is IActivatableApplicationLifetime activatableApplicationLifetime)
{
activatableApplicationLifetime.Activated += (sender, args) =>
Console.WriteLine($"App activated: {args.Kind}");
activatableApplicationLifetime.Deactivated += (sender, args) =>
Console.WriteLine($"App deactivated: {args.Kind}");
}
base.OnFrameworkInitializationCompleted(); base.OnFrameworkInitializationCompleted();
} }

2
src/Android/Avalonia.Android/AvaloniaMainActivity.App.cs

@ -36,7 +36,7 @@ namespace Avalonia.Android
{ {
var builder = CreateAppBuilder(); var builder = CreateAppBuilder();
builder.SetupWithLifetime(new SingleViewLifetime()); builder.SetupWithLifetime(new SingleViewLifetime(this));
s_appBuilder = builder; s_appBuilder = builder;
} }

40
src/Android/Avalonia.Android/AvaloniaMainActivity.cs

@ -7,15 +7,29 @@ using Android.OS;
using Android.Runtime; using Android.Runtime;
using Android.Views; using Android.Views;
using AndroidX.AppCompat.App; using AndroidX.AppCompat.App;
using Avalonia.Controls.ApplicationLifetimes;
namespace Avalonia.Android namespace Avalonia.Android
{ {
public class AvaloniaMainActivity : AppCompatActivity, IActivityResultHandler, IActivityNavigationService public class AvaloniaMainActivity : AppCompatActivity, IAvaloniaActivity
{ {
private EventHandler<ActivatedEventArgs> _onActivated, _onDeactivated;
public Action<int, Result, Intent> ActivityResult { get; set; } public Action<int, Result, Intent> ActivityResult { get; set; }
public Action<int, string[], Permission[]> RequestPermissionsResult { get; set; } public Action<int, string[], Permission[]> RequestPermissionsResult { get; set; }
public event EventHandler<AndroidBackRequestedEventArgs> BackRequested; public event EventHandler<AndroidBackRequestedEventArgs> BackRequested;
event EventHandler<ActivatedEventArgs> IAvaloniaActivity.Activated
{
add { _onActivated += value; }
remove { _onActivated -= value; }
}
event EventHandler<ActivatedEventArgs> IAvaloniaActivity.Deactivated
{
add { _onDeactivated += value; }
remove { _onDeactivated -= value; }
}
public override void OnBackPressed() public override void OnBackPressed()
{ {
@ -29,6 +43,30 @@ namespace Avalonia.Android
} }
} }
protected override void OnCreate(Bundle savedInstanceState)
{
base.OnCreate(savedInstanceState);
if (Intent?.Data is {} androidUri
&& androidUri.IsAbsolute
&& Uri.TryCreate(androidUri.ToString(), UriKind.Absolute, out var protocolUri))
{
_onActivated?.Invoke(this, new ProtocolActivatedEventArgs(ActivationKind.OpenUri, protocolUri));
}
}
protected override void OnStop()
{
_onDeactivated?.Invoke(this, new ActivatedEventArgs(ActivationKind.Background));
base.OnStop();
}
protected override void OnStart()
{
_onActivated?.Invoke(this, new ActivatedEventArgs(ActivationKind.Background));
base.OnStart();
}
protected override void OnActivityResult(int requestCode, [GeneratedEnum] Result resultCode, Intent data) protected override void OnActivityResult(int requestCode, [GeneratedEnum] Result resultCode, Intent data)
{ {
base.OnActivityResult(requestCode, resultCode, data); base.OnActivityResult(requestCode, resultCode, data);

1
src/Android/Avalonia.Android/IAndroidNavigationService.cs

@ -1,4 +1,5 @@
using System; using System;
using Avalonia.Controls.ApplicationLifetimes;
namespace Avalonia.Android namespace Avalonia.Android
{ {

10
src/Android/Avalonia.Android/IAvaloniaActivity.cs

@ -0,0 +1,10 @@
using System;
using Avalonia.Controls.ApplicationLifetimes;
namespace Avalonia.Android;
public interface IAvaloniaActivity : IActivityResultHandler, IActivityNavigationService
{
event EventHandler<ActivatedEventArgs> Activated;
event EventHandler<ActivatedEventArgs> Deactivated;
}

8
src/Android/Avalonia.Android/Platform/SkiaPlatform/TopLevelImpl.cs

@ -1,12 +1,10 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Runtime.Versioning;
using System.Threading; using System.Threading;
using Android.App; using Android.App;
using Android.Content; using Android.Content;
using Android.Graphics; using Android.Graphics;
using Android.Graphics.Drawables; using Android.Graphics.Drawables;
using Android.OS;
using Android.Runtime; using Android.Runtime;
using Android.Text; using Android.Text;
using Android.Views; using Android.Views;
@ -27,10 +25,8 @@ using Avalonia.OpenGL.Egl;
using Avalonia.OpenGL.Surfaces; using Avalonia.OpenGL.Surfaces;
using Avalonia.Platform; using Avalonia.Platform;
using Avalonia.Platform.Storage; using Avalonia.Platform.Storage;
using Avalonia.Rendering;
using Avalonia.Rendering.Composition; using Avalonia.Rendering.Composition;
using Java.Lang; using Java.Lang;
using static System.Net.Mime.MediaTypeNames;
using ClipboardManager = Android.Content.ClipboardManager; using ClipboardManager = Android.Content.ClipboardManager;
namespace Avalonia.Android.Platform.SkiaPlatform namespace Avalonia.Android.Platform.SkiaPlatform
@ -76,6 +72,8 @@ namespace Avalonia.Android.Platform.SkiaPlatform
_transparencyLevel = WindowTransparencyLevel.None; _transparencyLevel = WindowTransparencyLevel.None;
_systemNavigationManager = new AndroidSystemNavigationManagerImpl(avaloniaView.Context as IActivityNavigationService); _systemNavigationManager = new AndroidSystemNavigationManagerImpl(avaloniaView.Context as IActivityNavigationService);
Surfaces = new object[] { _gl, _framebuffer, Handle };
} }
public virtual Point GetAvaloniaPointFromEvent(MotionEvent e, int pointerIndex) => public virtual Point GetAvaloniaPointFromEvent(MotionEvent e, int pointerIndex) =>
@ -107,7 +105,7 @@ namespace Avalonia.Android.Platform.SkiaPlatform
public IPlatformHandle Handle => _view; public IPlatformHandle Handle => _view;
public IEnumerable<object> Surfaces => new object[] { _gl, _framebuffer, Handle }; public IEnumerable<object> Surfaces { get; }
public Compositor Compositor => AndroidPlatform.Compositor; public Compositor Compositor => AndroidPlatform.Compositor;

16
src/Android/Avalonia.Android/Platform/Specific/Helpers/AndroidKeyboardEventsHelper.cs

@ -53,6 +53,7 @@ namespace Avalonia.Android.Platform.Specific.Helpers
var physicalKey = AndroidKeyInterop.PhysicalKeyFromScanCode(e.ScanCode); var physicalKey = AndroidKeyInterop.PhysicalKeyFromScanCode(e.ScanCode);
var keySymbol = GetKeySymbol(e.UnicodeChar, physicalKey); var keySymbol = GetKeySymbol(e.UnicodeChar, physicalKey);
var keyDeviceType = GetKeyDeviceType(e);
var rawKeyEvent = new RawKeyEventArgs( var rawKeyEvent = new RawKeyEventArgs(
AndroidKeyboardDevice.Instance!, AndroidKeyboardDevice.Instance!,
@ -62,6 +63,7 @@ namespace Avalonia.Android.Platform.Specific.Helpers
AndroidKeyboardDevice.ConvertKey(e.KeyCode), AndroidKeyboardDevice.ConvertKey(e.KeyCode),
GetModifierKeys(e), GetModifierKeys(e),
physicalKey, physicalKey,
keyDeviceType,
keySymbol); keySymbol);
_view.Input?.Invoke(rawKeyEvent); _view.Input?.Invoke(rawKeyEvent);
@ -124,6 +126,20 @@ namespace Avalonia.Android.Platform.Specific.Helpers
} }
} }
private KeyDeviceType GetKeyDeviceType(KeyEvent e)
{
var source = e.Device?.Sources ?? InputSourceType.Unknown;
if (source is InputSourceType.Joystick or
InputSourceType.ClassJoystick or
InputSourceType.Gamepad)
return KeyDeviceType.Gamepad;
if (source == InputSourceType.Dpad && e.Device?.KeyboardType == InputKeyboardType.NonAlphabetic)
return KeyDeviceType.Remote;
return KeyDeviceType.Keyboard;
}
public void Dispose() public void Dispose()
{ {
HandleEvents = false; HandleEvents = false;

23
src/Android/Avalonia.Android/SingleViewLifetime.cs

@ -1,12 +1,26 @@
using Avalonia.Controls; using System;
using Android.App;
using Avalonia.Controls;
using Avalonia.Controls.ApplicationLifetimes; using Avalonia.Controls.ApplicationLifetimes;
namespace Avalonia.Android namespace Avalonia.Android
{ {
internal class SingleViewLifetime : ISingleViewApplicationLifetime internal class SingleViewLifetime : ISingleViewApplicationLifetime, IActivatableApplicationLifetime
{ {
private readonly Activity _activity;
private AvaloniaView _view; private AvaloniaView _view;
public SingleViewLifetime(Activity activity)
{
_activity = activity;
if (activity is IAvaloniaActivity activableActivity)
{
activableActivity.Activated += (_, args) => Activated?.Invoke(this, args);
activableActivity.Deactivated += (_, args) => Deactivated?.Invoke(this, args);
}
}
public AvaloniaView View public AvaloniaView View
{ {
get => _view; internal set get => _view; internal set
@ -22,5 +36,10 @@ namespace Avalonia.Android
} }
public Control MainView { get; set; } public Control MainView { get; set; }
public event EventHandler<ActivatedEventArgs> Activated;
public event EventHandler<ActivatedEventArgs> Deactivated;
public bool TryLeaveBackground() => _activity.MoveTaskToBack(true);
public bool TryEnterBackground() => false;
} }
} }

2
src/Avalonia.Base/Animation/Animators/BoolAnimator.cs

@ -5,7 +5,7 @@
/// </summary> /// </summary>
internal class BoolAnimator : Animator<bool> internal class BoolAnimator : Animator<bool>
{ {
/// <inheritdocs/> /// <inheritdoc/>
public override bool Interpolate(double progress, bool oldValue, bool newValue) public override bool Interpolate(double progress, bool oldValue, bool newValue)
{ {
if(progress >= 1d) if(progress >= 1d)

2
src/Avalonia.Base/Animation/Animators/ByteAnimator.cs

@ -9,7 +9,7 @@ namespace Avalonia.Animation.Animators
{ {
const double maxVal = (double)byte.MaxValue; const double maxVal = (double)byte.MaxValue;
/// <inheritdocs/> /// <inheritdoc/>
public override byte Interpolate(double progress, byte oldValue, byte newValue) public override byte Interpolate(double progress, byte oldValue, byte newValue)
{ {
var normOV = oldValue / maxVal; var normOV = oldValue / maxVal;

2
src/Avalonia.Base/Animation/Animators/DecimalAnimator.cs

@ -5,7 +5,7 @@
/// </summary> /// </summary>
internal class DecimalAnimator : Animator<decimal> internal class DecimalAnimator : Animator<decimal>
{ {
/// <inheritdocs/> /// <inheritdoc/>
public override decimal Interpolate(double progress, decimal oldValue, decimal newValue) public override decimal Interpolate(double progress, decimal oldValue, decimal newValue)
{ {
return ((newValue - oldValue) * (decimal)progress) + oldValue; return ((newValue - oldValue) * (decimal)progress) + oldValue;

2
src/Avalonia.Base/Animation/Animators/DoubleAnimator.cs

@ -5,7 +5,7 @@
/// </summary> /// </summary>
internal class DoubleAnimator : Animator<double> internal class DoubleAnimator : Animator<double>
{ {
/// <inheritdocs/> /// <inheritdoc/>
public override double Interpolate(double progress, double oldValue, double newValue) public override double Interpolate(double progress, double oldValue, double newValue)
{ {
return ((newValue - oldValue) * progress) + oldValue; return ((newValue - oldValue) * progress) + oldValue;

2
src/Avalonia.Base/Animation/Animators/FloatAnimator.cs

@ -5,7 +5,7 @@
/// </summary> /// </summary>
internal class FloatAnimator : Animator<float> internal class FloatAnimator : Animator<float>
{ {
/// <inheritdocs/> /// <inheritdoc/>
public override float Interpolate(double progress, float oldValue, float newValue) public override float Interpolate(double progress, float oldValue, float newValue)
{ {
return (float)(((newValue - oldValue) * progress) + oldValue); return (float)(((newValue - oldValue) * progress) + oldValue);

2
src/Avalonia.Base/Animation/Animators/Int16Animator.cs

@ -9,7 +9,7 @@ namespace Avalonia.Animation.Animators
{ {
const double maxVal = (double)Int16.MaxValue; const double maxVal = (double)Int16.MaxValue;
/// <inheritdocs/> /// <inheritdoc/>
public override Int16 Interpolate(double progress, Int16 oldValue, Int16 newValue) public override Int16 Interpolate(double progress, Int16 oldValue, Int16 newValue)
{ {
var normOV = oldValue / maxVal; var normOV = oldValue / maxVal;

2
src/Avalonia.Base/Animation/Animators/Int32Animator.cs

@ -9,7 +9,7 @@ namespace Avalonia.Animation.Animators
{ {
const double maxVal = (double)Int32.MaxValue; const double maxVal = (double)Int32.MaxValue;
/// <inheritdocs/> /// <inheritdoc/>
public override Int32 Interpolate(double progress, Int32 oldValue, Int32 newValue) public override Int32 Interpolate(double progress, Int32 oldValue, Int32 newValue)
{ {
var normOV = oldValue / maxVal; var normOV = oldValue / maxVal;

2
src/Avalonia.Base/Animation/Animators/Int64Animator.cs

@ -9,7 +9,7 @@ namespace Avalonia.Animation.Animators
{ {
const double maxVal = (double)Int64.MaxValue; const double maxVal = (double)Int64.MaxValue;
/// <inheritdocs/> /// <inheritdoc/>
public override Int64 Interpolate(double progress, Int64 oldValue, Int64 newValue) public override Int64 Interpolate(double progress, Int64 oldValue, Int64 newValue)
{ {
var normOV = oldValue / maxVal; var normOV = oldValue / maxVal;

2
src/Avalonia.Base/Animation/Animators/TransformAnimator.cs

@ -92,7 +92,7 @@ namespace Avalonia.Animation.Animators
return null; return null;
} }
/// <inheritdocs/> /// <inheritdoc/>
public override double Interpolate(double p, double o, double n) => 0; public override double Interpolate(double p, double o, double n) => 0;
} }
} }

2
src/Avalonia.Base/Animation/Animators/UInt16Animator.cs

@ -9,7 +9,7 @@ namespace Avalonia.Animation.Animators
{ {
const double maxVal = (double)UInt16.MaxValue; const double maxVal = (double)UInt16.MaxValue;
/// <inheritdocs/> /// <inheritdoc/>
public override UInt16 Interpolate(double progress, UInt16 oldValue, UInt16 newValue) public override UInt16 Interpolate(double progress, UInt16 oldValue, UInt16 newValue)
{ {
var normOV = oldValue / maxVal; var normOV = oldValue / maxVal;

2
src/Avalonia.Base/Animation/Animators/UInt32Animator.cs

@ -9,7 +9,7 @@ namespace Avalonia.Animation.Animators
{ {
const double maxVal = (double)UInt32.MaxValue; const double maxVal = (double)UInt32.MaxValue;
/// <inheritdocs/> /// <inheritdoc/>
public override UInt32 Interpolate(double progress, UInt32 oldValue, UInt32 newValue) public override UInt32 Interpolate(double progress, UInt32 oldValue, UInt32 newValue)
{ {
var normOV = oldValue / maxVal; var normOV = oldValue / maxVal;

2
src/Avalonia.Base/Animation/Animators/UInt64Animator.cs

@ -9,7 +9,7 @@ namespace Avalonia.Animation.Animators
{ {
const double maxVal = (double)UInt64.MaxValue; const double maxVal = (double)UInt64.MaxValue;
/// <inheritdocs/> /// <inheritdoc/>
public override UInt64 Interpolate(double progress, UInt64 oldValue, UInt64 newValue) public override UInt64 Interpolate(double progress, UInt64 oldValue, UInt64 newValue)
{ {
var normOV = oldValue / maxVal; var normOV = oldValue / maxVal;

4
src/Avalonia.Base/Animation/TransitionBase.cs

@ -77,7 +77,7 @@ namespace Avalonia.Animation
set { SetAndRaise(EasingProperty, ref _easing, value); } set { SetAndRaise(EasingProperty, ref _easing, value); }
} }
/// <inheritdocs/> /// <inheritdoc/>
[DisallowNull] [DisallowNull]
public AvaloniaProperty? Property public AvaloniaProperty? Property
{ {
@ -91,7 +91,7 @@ namespace Avalonia.Animation
set => Property = value; set => Property = value;
} }
/// <inheritdocs/> /// <inheritdoc/>
IDisposable ITransition.Apply(Animatable control, IClock clock, object? oldValue, object? newValue) IDisposable ITransition.Apply(Animatable control, IClock clock, object? oldValue, object? newValue)
=> Apply(control, clock, oldValue, newValue); => Apply(control, clock, oldValue, newValue);

25
src/Avalonia.Base/Input/Gestures.cs

@ -67,7 +67,7 @@ namespace Avalonia.Input
private static readonly WeakReference<object?> s_lastPress = new WeakReference<object?>(null); private static readonly WeakReference<object?> s_lastPress = new WeakReference<object?>(null);
private static Point s_lastPressPoint; private static Point s_lastPressPoint;
private static IPointer? s_lastPointer; private static IPointer? s_lastHeldPointer;
public static readonly RoutedEvent<PinchEventArgs> PinchEvent = public static readonly RoutedEvent<PinchEventArgs> PinchEvent =
RoutedEvent.Register<PinchEventArgs>( RoutedEvent.Register<PinchEventArgs>(
@ -225,17 +225,17 @@ namespace Avalonia.Input
var e = (PointerPressedEventArgs)ev; var e = (PointerPressedEventArgs)ev;
var visual = (Visual)ev.Source; var visual = (Visual)ev.Source;
if(s_lastPointer != null) if(s_lastHeldPointer != null)
{ {
if(s_isHolding && ev.Source is Interactive i) if(s_isHolding && ev.Source is Interactive i)
{ {
i.RaiseEvent(new HoldingRoutedEventArgs(HoldingState.Cancelled, s_lastPressPoint, s_lastPointer.Type, e)); i.RaiseEvent(new HoldingRoutedEventArgs(HoldingState.Cancelled, s_lastPressPoint, s_lastHeldPointer.Type, e));
} }
s_holdCancellationToken?.Cancel(); s_holdCancellationToken?.Cancel();
s_holdCancellationToken?.Dispose(); s_holdCancellationToken?.Dispose();
s_holdCancellationToken = null; s_holdCancellationToken = null;
s_lastPointer = null; s_lastHeldPointer = null;
} }
s_isHolding = false; s_isHolding = false;
@ -244,7 +244,7 @@ namespace Avalonia.Input
{ {
s_isDoubleTapped = false; s_isDoubleTapped = false;
s_lastPress.SetTarget(ev.Source); s_lastPress.SetTarget(ev.Source);
s_lastPointer = e.Pointer; s_lastHeldPointer = e.Pointer;
s_lastPressPoint = e.GetPosition((Visual)ev.Source); s_lastPressPoint = e.GetPosition((Visual)ev.Source);
s_holdCancellationToken = new CancellationTokenSource(); s_holdCancellationToken = new CancellationTokenSource();
var token = s_holdCancellationToken.Token; var token = s_holdCancellationToken.Token;
@ -257,7 +257,7 @@ namespace Avalonia.Input
if (!token.IsCancellationRequested && e.Source is InputElement i && GetIsHoldingEnabled(i) && (e.Pointer.Type != PointerType.Mouse || GetIsHoldWithMouseEnabled(i))) if (!token.IsCancellationRequested && e.Source is InputElement i && GetIsHoldingEnabled(i) && (e.Pointer.Type != PointerType.Mouse || GetIsHoldWithMouseEnabled(i)))
{ {
s_isHolding = true; s_isHolding = true;
i.RaiseEvent(new HoldingRoutedEventArgs(HoldingState.Started, s_lastPressPoint, s_lastPointer.Type, e)); i.RaiseEvent(new HoldingRoutedEventArgs(HoldingState.Started, s_lastPressPoint, s_lastHeldPointer.Type, e));
} }
}, settings.HoldWaitDuration); }, settings.HoldWaitDuration);
} }
@ -281,7 +281,7 @@ namespace Avalonia.Input
{ {
var e = (PointerReleasedEventArgs)ev; var e = (PointerReleasedEventArgs)ev;
if (s_lastPress.TryGetTarget(out var target) && if (s_lastPress.TryGetTarget(out var target) &&
target == e.Source && target == e.Source &&
e.InitialPressMouseButton is MouseButton.Left or MouseButton.Right && e.InitialPressMouseButton is MouseButton.Left or MouseButton.Right &&
e.Source is Interactive i) e.Source is Interactive i)
@ -294,10 +294,10 @@ namespace Avalonia.Input
if (tapRect.ContainsExclusive(point.Position)) if (tapRect.ContainsExclusive(point.Position))
{ {
if(s_isHolding) if (s_isHolding)
{ {
s_isHolding = false; s_isHolding = false;
i.RaiseEvent(new HoldingRoutedEventArgs(HoldingState.Completed, s_lastPressPoint, s_lastPointer!.Type, e)); i.RaiseEvent(new HoldingRoutedEventArgs(HoldingState.Completed, s_lastPressPoint, s_lastHeldPointer!.Type, e));
} }
else if (e.InitialPressMouseButton == MouseButton.Right) else if (e.InitialPressMouseButton == MouseButton.Right)
{ {
@ -310,12 +310,12 @@ namespace Avalonia.Input
i.RaiseEvent(new TappedEventArgs(TappedEvent, e)); i.RaiseEvent(new TappedEventArgs(TappedEvent, e));
} }
} }
s_lastHeldPointer = null;
} }
s_holdCancellationToken?.Cancel(); s_holdCancellationToken?.Cancel();
s_holdCancellationToken?.Dispose(); s_holdCancellationToken?.Dispose();
s_holdCancellationToken = null; s_holdCancellationToken = null;
s_lastPointer = null;
} }
} }
@ -326,7 +326,7 @@ namespace Avalonia.Input
var e = (PointerEventArgs)ev; var e = (PointerEventArgs)ev;
if (s_lastPress.TryGetTarget(out var target)) if (s_lastPress.TryGetTarget(out var target))
{ {
if (e.Pointer == s_lastPointer && ev.Source is Interactive i) if (e.Pointer == s_lastHeldPointer && ev.Source is Interactive i)
{ {
var point = e.GetCurrentPoint((Visual)target); var point = e.GetCurrentPoint((Visual)target);
var settings = ((IInputRoot?)i.GetVisualRoot())?.PlatformSettings; var settings = ((IInputRoot?)i.GetVisualRoot())?.PlatformSettings;
@ -341,7 +341,8 @@ namespace Avalonia.Input
if (s_isHolding) if (s_isHolding)
{ {
i.RaiseEvent(new HoldingRoutedEventArgs(HoldingState.Cancelled, s_lastPressPoint, s_lastPointer!.Type, e)); i.RaiseEvent(new HoldingRoutedEventArgs(HoldingState.Cancelled, s_lastPressPoint, s_lastHeldPointer!.Type, e));
s_lastHeldPointer = null;
} }
} }
} }

69
src/Avalonia.Base/Input/Key.cs

@ -1036,5 +1036,74 @@ namespace Avalonia.Input
/// OSX Platform-specific Fn+Down key /// OSX Platform-specific Fn+Down key
/// </summary> /// </summary>
FnDownArrow = 10004, FnDownArrow = 10004,
/// <summary>
/// Remove control home button
/// </summary>
MediaHome = 100000,
/// <summary>
/// TV Channel up
/// </summary>
MediaChannelList = 100001,
/// <summary>
/// TV Channel up
/// </summary>
MediaChannelRaise = 100002,
/// <summary>
/// TV Channel down
/// </summary>
MediaChannelLower = 100003,
/// <summary>
/// TV Channel down
/// </summary>
MediaRecord = 100005,
/// <summary>
/// Remote control Red button
/// </summary>
MediaRed = 100010,
/// <summary>
/// Remote control Green button
/// </summary>
MediaGreen = 100011,
/// <summary>
/// Remote control Yellow button
/// </summary>
MediaYellow = 100012,
/// <summary>
/// Remote control Blue button
/// </summary>
MediaBlue = 100013,
/// <summary>
/// Remote control Menu button
/// </summary>
MediaMenu = 100020,
/// <summary>
/// Remote control dots button
/// </summary>
MediaMore = 100021,
/// <summary>
/// Remote control option button
/// </summary>
MediaOption = 100022,
/// <summary>
/// Remote control channel info button
/// </summary>
MediaInfo = 100023,
/// <summary>
/// Remote control search button
/// </summary>
MediaSearch = 100024,
/// <summary>
/// Remote control subtitle/caption button
/// </summary>
MediaSubtitle = 100025,
/// <summary>
/// Remote control Tv guide detail button
/// </summary>
MediaTvGuide = 100026,
/// <summary>
/// Remote control Previous Channel
/// </summary>
MediaPreviousChannel = 100027,
} }
} }

15
src/Avalonia.Base/Input/KeyDeviceType.cs

@ -0,0 +1,15 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Avalonia.Input
{
public enum KeyDeviceType
{
Keyboard,
Gamepad,
Remote
}
}

5
src/Avalonia.Base/Input/KeyEventArgs.cs

@ -71,4 +71,9 @@ public class KeyEventArgs : RoutedEventArgs
/// <see cref="Key"/> /// <see cref="Key"/>
/// <see cref="PhysicalKey"/> /// <see cref="PhysicalKey"/>
public string? KeySymbol { get; init; } public string? KeySymbol { get; init; }
/// <summary>
/// Type of the device that fire the event
/// </summary>
public KeyDeviceType KeyDeviceType { get; init; }
} }

1
src/Avalonia.Base/Input/KeyboardDevice.cs

@ -196,6 +196,7 @@ namespace Avalonia.Input
KeyModifiers = keyInput.Modifiers.ToKeyModifiers(), KeyModifiers = keyInput.Modifiers.ToKeyModifiers(),
PhysicalKey = keyInput.PhysicalKey, PhysicalKey = keyInput.PhysicalKey,
KeySymbol = keyInput.KeySymbol, KeySymbol = keyInput.KeySymbol,
KeyDeviceType = keyInput.KeyDeviceType,
Source = element Source = element
}; };

17
src/Avalonia.Base/Input/Raw/RawKeyEventArgs.cs

@ -20,7 +20,7 @@ namespace Avalonia.Input.Raw
RawKeyEventType type, RawKeyEventType type,
Key key, Key key,
RawInputModifiers modifiers) RawInputModifiers modifiers)
: this(device, timestamp, root, type, key, modifiers, PhysicalKey.None, null) : this(device, timestamp, root, type, key, modifiers, PhysicalKey.None, KeyDeviceType.Keyboard, null)
{ {
Key = key; Key = key;
Type = type; Type = type;
@ -36,6 +36,18 @@ namespace Avalonia.Input.Raw
RawInputModifiers modifiers, RawInputModifiers modifiers,
PhysicalKey physicalKey, PhysicalKey physicalKey,
string? keySymbol) string? keySymbol)
: this(device, timestamp, root, type, key, modifiers, physicalKey, KeyDeviceType.Keyboard, keySymbol) { }
public RawKeyEventArgs(
IInputDevice device,
ulong timestamp,
IInputRoot root,
RawKeyEventType type,
Key key,
RawInputModifiers modifiers,
PhysicalKey physicalKey,
KeyDeviceType keyDeviceType,
string? keySymbol)
: base(device, timestamp, root) : base(device, timestamp, root)
{ {
Key = key; Key = key;
@ -43,6 +55,7 @@ namespace Avalonia.Input.Raw
Type = type; Type = type;
PhysicalKey = physicalKey; PhysicalKey = physicalKey;
KeySymbol = keySymbol; KeySymbol = keySymbol;
KeyDeviceType = keyDeviceType;
} }
public Key Key { get; set; } public Key Key { get; set; }
@ -53,6 +66,8 @@ namespace Avalonia.Input.Raw
public PhysicalKey PhysicalKey { get; set; } public PhysicalKey PhysicalKey { get; set; }
public KeyDeviceType KeyDeviceType { get; set; }
public string? KeySymbol { get; set; } public string? KeySymbol { get; set; }
} }
} }

90
src/Avalonia.Base/Media/Pen.cs

@ -1,4 +1,7 @@
using System; using System;
using System.Collections.Generic;
using System.Collections.Specialized;
using Avalonia.Collections;
using Avalonia.Media.Immutable; using Avalonia.Media.Immutable;
using Avalonia.Rendering.Composition; using Avalonia.Rendering.Composition;
using Avalonia.Rendering.Composition.Drawing; using Avalonia.Rendering.Composition.Drawing;
@ -56,8 +59,7 @@ namespace Avalonia.Media
/// Initializes a new instance of the <see cref="Pen"/> class. /// Initializes a new instance of the <see cref="Pen"/> class.
/// </summary> /// </summary>
public Pen() public Pen()
{ { }
}
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="Pen"/> class. /// Initializes a new instance of the <see cref="Pen"/> class.
@ -75,8 +77,7 @@ namespace Avalonia.Media
PenLineCap lineCap = PenLineCap.Flat, PenLineCap lineCap = PenLineCap.Flat,
PenLineJoin lineJoin = PenLineJoin.Miter, PenLineJoin lineJoin = PenLineJoin.Miter,
double miterLimit = 10.0) : this(new SolidColorBrush(color), thickness, dashStyle, lineCap, lineJoin, miterLimit) double miterLimit = 10.0) : this(new SolidColorBrush(color), thickness, dashStyle, lineCap, lineJoin, miterLimit)
{ { }
}
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="Pen"/> class. /// Initializes a new instance of the <see cref="Pen"/> class.
@ -178,6 +179,69 @@ namespace Avalonia.Media
MiterLimit); MiterLimit);
} }
/// <summary>
/// Smart reuse and update pen properties.
/// </summary>
/// <param name="pen">Old pen to modify.</param>
/// <param name="brush">The brush used to draw.</param>
/// <param name="thickness">The stroke thickness.</param>
/// <param name="strokeDashArray">The stroke dask array.</param>
/// <param name="strokeDaskOffset">The stroke dask offset.</param>
/// <param name="lineCap">The line cap.</param>
/// <param name="lineJoin">The line join.</param>
/// <param name="miterLimit">The miter limit.</param>
/// <returns>If a new instance was created and visual invalidation required.</returns>
internal static bool TryModifyOrCreate(ref IPen? pen,
IBrush? brush,
double thickness,
IList<double>? strokeDashArray = null,
double strokeDaskOffset = default,
PenLineCap lineCap = PenLineCap.Flat,
PenLineJoin lineJoin = PenLineJoin.Miter,
double miterLimit = 10.0)
{
var previousPen = pen;
if (brush is null)
{
pen = null;
return previousPen is not null;
}
IDashStyle? dashStyle = null;
if (strokeDashArray is { Count: > 0 })
{
// strokeDashArray can be IList (instead of AvaloniaList) in future
// So, if it supports notification - create a mutable DashStyle
dashStyle = strokeDashArray is INotifyCollectionChanged
? new DashStyle(strokeDashArray, strokeDaskOffset)
: new ImmutableDashStyle(strokeDashArray, strokeDaskOffset);
}
if (brush is IImmutableBrush immutableBrush && dashStyle is null or ImmutableDashStyle)
{
pen = new ImmutablePen(
immutableBrush,
thickness,
(ImmutableDashStyle?)dashStyle,
lineCap,
lineJoin,
miterLimit);
return true;
}
var mutablePen = previousPen as Pen ?? new Pen();
mutablePen.Brush = brush;
mutablePen.Thickness = thickness;
mutablePen.LineCap = lineCap;
mutablePen.LineJoin = lineJoin;
mutablePen.DashStyle = dashStyle;
mutablePen.MiterLimit = miterLimit;
pen = mutablePen;
return !Equals(previousPen, pen);
}
void RegisterForSerialization() void RegisterForSerialization()
{ {
_resource.RegisterForInvalidationOnAllCompositors(this); _resource.RegisterForInvalidationOnAllCompositors(this);
@ -186,21 +250,21 @@ namespace Avalonia.Media
protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs change) protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs change)
{ {
RegisterForSerialization(); RegisterForSerialization();
if (change.Property == BrushProperty) if (change.Property == BrushProperty)
_resource.ProcessPropertyChangeNotification(change); _resource.ProcessPropertyChangeNotification(change);
if(change.Property == DashStyleProperty) if (change.Property == DashStyleProperty)
UpdateDashStyleSubscription(); UpdateDashStyleSubscription();
base.OnPropertyChanged(change); base.OnPropertyChanged(change);
} }
void UpdateDashStyleSubscription() void UpdateDashStyleSubscription()
{ {
var newValue = _resource.IsAttached ? DashStyle as DashStyle : null; var newValue = _resource.IsAttached ? DashStyle as DashStyle : null;
if(ReferenceEquals(_subscribedToDashes, newValue)) if (ReferenceEquals(_subscribedToDashes, newValue))
return; return;
if (_subscribedToDashes != null && _weakSubscriber != null) if (_subscribedToDashes != null && _weakSubscriber != null)
@ -221,9 +285,9 @@ namespace Avalonia.Media
_subscribedToDashes = newValue; _subscribedToDashes = newValue;
} }
} }
private CompositorResourceHolder<ServerCompositionSimplePen> _resource; private CompositorResourceHolder<ServerCompositionSimplePen> _resource;
IPen ICompositionRenderResource<IPen>.GetForCompositor(Compositor c) => _resource.GetForCompositor(c); IPen ICompositionRenderResource<IPen>.GetForCompositor(Compositor c) => _resource.GetForCompositor(c);
void ICompositionRenderResource.AddRefOnCompositor(Compositor c) void ICompositionRenderResource.AddRefOnCompositor(Compositor c)

64
src/Avalonia.Base/Media/StreamGeometryContext.cs

@ -29,22 +29,13 @@ namespace Avalonia.Media
/// Sets path's winding rule (default is EvenOdd). You should call this method before any calls to BeginFigure. If you wonder why, ask Direct2D guys about their design decisions. /// Sets path's winding rule (default is EvenOdd). You should call this method before any calls to BeginFigure. If you wonder why, ask Direct2D guys about their design decisions.
/// </summary> /// </summary>
/// <param name="fillRule"></param> /// <param name="fillRule"></param>
public void SetFillRule(FillRule fillRule) public void SetFillRule(FillRule fillRule)
{ {
_impl.SetFillRule(fillRule); _impl.SetFillRule(fillRule);
} }
/// <summary>
/// Draws an arc to the specified point. /// <inheritdoc/>
/// </summary>
/// <param name="point">The destination point.</param>
/// <param name="size">The radii of an oval whose perimeter is used to draw the angle.</param>
/// <param name="rotationAngle">The rotation angle (in radians) of the oval that specifies the curve.</param>
/// <param name="isLargeArc">true to draw the arc greater than 180 degrees; otherwise, false.</param>
/// <param name="sweepDirection">
/// A value that indicates whether the arc is drawn in the Clockwise or Counterclockwise direction.
/// </param>
public void ArcTo(Point point, Size size, double rotationAngle, bool isLargeArc, SweepDirection sweepDirection) public void ArcTo(Point point, Size size, double rotationAngle, bool isLargeArc, SweepDirection sweepDirection)
{ {
_impl.ArcTo(point, size, rotationAngle, isLargeArc, sweepDirection); _impl.ArcTo(point, size, rotationAngle, isLargeArc, sweepDirection);
@ -54,8 +45,8 @@ namespace Avalonia.Media
/// <summary> /// <summary>
/// Draws an arc to the specified point using polylines, quadratic or cubic Bezier curves /// Draws an arc to the specified point using polylines, quadratic or cubic Bezier curves
/// Significantly more precise when drawing elliptic arcs with extreme width:height ratios. /// Significantly more precise when drawing elliptic arcs with extreme width:height ratios.
/// </summary> /// </summary>
/// <param name="point">The destination point.</param> /// <param name="point">The destination point.</param>
/// <param name="size">The radii of an oval whose perimeter is used to draw the angle.</param> /// <param name="size">The radii of an oval whose perimeter is used to draw the angle.</param>
/// <param name="rotationAngle">The rotation angle (in radians) of the oval that specifies the curve.</param> /// <param name="rotationAngle">The rotation angle (in radians) of the oval that specifies the curve.</param>
@ -68,54 +59,37 @@ namespace Avalonia.Media
PreciseEllipticArcHelper.ArcTo(this, _currentPoint, point, size, rotationAngle, isLargeArc, sweepDirection); PreciseEllipticArcHelper.ArcTo(this, _currentPoint, point, size, rotationAngle, isLargeArc, sweepDirection);
} }
/// <summary>
/// Begins a new figure. /// <inheritdoc/>
/// </summary>
/// <param name="startPoint">The starting point for the figure.</param>
/// <param name="isFilled">Whether the figure is filled.</param>
public void BeginFigure(Point startPoint, bool isFilled) public void BeginFigure(Point startPoint, bool isFilled)
{ {
_impl.BeginFigure(startPoint, isFilled); _impl.BeginFigure(startPoint, isFilled);
_currentPoint = startPoint; _currentPoint = startPoint;
} }
/// <summary> /// <inheritdoc/>
/// Draws a Bezier curve to the specified point. public void CubicBezierTo(Point controlPoint1, Point controlPoint2, Point endPoint)
/// </summary>
/// <param name="point1">The first control point used to specify the shape of the curve.</param>
/// <param name="point2">The second control point used to specify the shape of the curve.</param>
/// <param name="point3">The destination point for the end of the curve.</param>
public void CubicBezierTo(Point point1, Point point2, Point point3)
{ {
_impl.CubicBezierTo(point1, point2, point3); _impl.CubicBezierTo(controlPoint1, controlPoint2, endPoint);
_currentPoint = point3; _currentPoint = endPoint;
} }
/// <summary> /// <inheritdoc/>
/// Draws a quadratic Bezier curve to the specified point public void QuadraticBezierTo(Point controlPoint , Point endPoint)
/// </summary>
/// <param name="control">The control point used to specify the shape of the curve.</param>
/// <param name="endPoint">The destination point for the end of the curve.</param>
public void QuadraticBezierTo(Point control, Point endPoint)
{ {
_impl.QuadraticBezierTo(control, endPoint); _impl.QuadraticBezierTo(controlPoint , endPoint);
_currentPoint = endPoint; _currentPoint = endPoint;
} }
/// <summary>
/// Draws a line to the specified point. /// <inheritdoc/>
/// </summary> public void LineTo(Point endPoint)
/// <param name="point">The destination point.</param>
public void LineTo(Point point)
{ {
_impl.LineTo(point); _impl.LineTo(endPoint);
_currentPoint = point; _currentPoint = endPoint;
} }
/// <summary> /// <inheritdoc/>
/// Ends the figure started by <see cref="BeginFigure(Point, bool)"/>.
/// </summary>
/// <param name="isClosed">Whether the figure is closed.</param>
public void EndFigure(bool isClosed) public void EndFigure(bool isClosed)
{ {
_impl.EndFigure(isClosed); _impl.EndFigure(isClosed);

20
src/Avalonia.Base/Platform/IGeometryContext.cs

@ -30,23 +30,23 @@ namespace Avalonia.Platform
/// <summary> /// <summary>
/// Draws a Bezier curve to the specified point. /// Draws a Bezier curve to the specified point.
/// </summary> /// </summary>
/// <param name="point1">The first control point used to specify the shape of the curve.</param> /// <param name="controlPoint1">The first control point used to specify the shape of the curve.</param>
/// <param name="point2">The second control point used to specify the shape of the curve.</param> /// <param name="controlPoint2">The second control point used to specify the shape of the curve.</param>
/// <param name="point3">The destination point for the end of the curve.</param> /// <param name="endPoint">The destination point for the end of the curve.</param>
void CubicBezierTo(Point point1, Point point2, Point point3); void CubicBezierTo(Point controlPoint1, Point controlPoint2, Point endPoint);
/// <summary> /// <summary>
/// Draws a quadratic Bezier curve to the specified point /// Draws a quadratic Bezier curve to the specified point
/// </summary> /// </summary>
/// <param name="control">Control point</param> /// <param name="controlPoint ">Control point</param>
/// <param name="endPoint">DestinationPoint</param> /// <param name="endPoint">DestinationPoint</param>
void QuadraticBezierTo(Point control, Point endPoint); void QuadraticBezierTo(Point controlPoint , Point endPoint);
/// <summary> /// <summary>
/// Draws a line to the specified point. /// Draws a line to the specified point.
/// </summary> /// </summary>
/// <param name="point">The destination point.</param> /// <param name="endPoint">The destination point.</param>
void LineTo(Point point); void LineTo(Point endPoint);
/// <summary> /// <summary>
/// Ends the figure started by <see cref="BeginFigure(Point, bool)"/>. /// Ends the figure started by <see cref="BeginFigure(Point, bool)"/>.
@ -55,9 +55,9 @@ namespace Avalonia.Platform
void EndFigure(bool isClosed); void EndFigure(bool isClosed);
/// <summary> /// <summary>
/// Sets the fill rule. /// Sets path's winding rule (default is EvenOdd). You should call this method before any calls to BeginFigure.
/// </summary> /// </summary>
/// <param name="fillRule">The fill rule.</param> /// <param name="fillRule"></param>
void SetFillRule(FillRule fillRule); void SetFillRule(FillRule fillRule);
} }
} }

19
src/Avalonia.Base/Platform/PathGeometryContext.cs

@ -20,6 +20,7 @@ namespace Avalonia.Visuals.Platform
_pathGeometry = null; _pathGeometry = null;
} }
/// <inheritdoc/>
public void ArcTo(Point point, Size size, double rotationAngle, bool isLargeArc, SweepDirection sweepDirection) public void ArcTo(Point point, Size size, double rotationAngle, bool isLargeArc, SweepDirection sweepDirection)
{ {
var arcSegment = new ArcSegment var arcSegment = new ArcSegment
@ -34,6 +35,7 @@ namespace Avalonia.Visuals.Platform
CurrentFigureSegments().Add(arcSegment); CurrentFigureSegments().Add(arcSegment);
} }
/// <inheritdoc/>
public void BeginFigure(Point startPoint, bool isFilled) public void BeginFigure(Point startPoint, bool isFilled)
{ {
ThrowIfDisposed(); ThrowIfDisposed();
@ -44,30 +46,34 @@ namespace Avalonia.Visuals.Platform
_pathGeometry.Figures.Add(_currentFigure); _pathGeometry.Figures.Add(_currentFigure);
} }
public void CubicBezierTo(Point point1, Point point2, Point point3) /// <inheritdoc/>
public void CubicBezierTo(Point controlPoint1, Point controlPoint2, Point endPoint)
{ {
var bezierSegment = new BezierSegment { Point1 = point1, Point2 = point2, Point3 = point3 }; var bezierSegment = new BezierSegment { Point1 = controlPoint1, Point2 = controlPoint2, Point3 = endPoint };
CurrentFigureSegments().Add(bezierSegment); CurrentFigureSegments().Add(bezierSegment);
} }
public void QuadraticBezierTo(Point control, Point endPoint) /// <inheritdoc/>
public void QuadraticBezierTo(Point controlPoint , Point endPoint)
{ {
var quadraticBezierSegment = new QuadraticBezierSegment { Point1 = control, Point2 = endPoint }; var quadraticBezierSegment = new QuadraticBezierSegment { Point1 = controlPoint , Point2 = endPoint };
CurrentFigureSegments().Add(quadraticBezierSegment); CurrentFigureSegments().Add(quadraticBezierSegment);
} }
public void LineTo(Point point) /// <inheritdoc/>
public void LineTo(Point endPoint)
{ {
var lineSegment = new LineSegment var lineSegment = new LineSegment
{ {
Point = point Point = endPoint
}; };
CurrentFigureSegments().Add(lineSegment); CurrentFigureSegments().Add(lineSegment);
} }
/// <inheritdoc/>
public void EndFigure(bool isClosed) public void EndFigure(bool isClosed)
{ {
if (_currentFigure != null) if (_currentFigure != null)
@ -78,6 +84,7 @@ namespace Avalonia.Visuals.Platform
_currentFigure = null; _currentFigure = null;
} }
/// <inheritdoc/>
public void SetFillRule(FillRule fillRule) public void SetFillRule(FillRule fillRule)
{ {
ThrowIfDisposed(); ThrowIfDisposed();

2
src/Avalonia.Base/Styling/ControlTheme.cs

@ -48,7 +48,7 @@ namespace Avalonia.Styling
if (HasSettersOrAnimations && TargetType.IsAssignableFrom(StyledElement.GetStyleKey(target))) if (HasSettersOrAnimations && TargetType.IsAssignableFrom(StyledElement.GetStyleKey(target)))
{ {
Attach(target, null, type); Attach(target, null, type, true);
return SelectorMatchResult.AlwaysThisType; return SelectorMatchResult.AlwaysThisType;
} }

2
src/Avalonia.Base/Styling/Style.cs

@ -74,7 +74,7 @@ namespace Avalonia.Styling
if (match.IsMatch) if (match.IsMatch)
{ {
Attach(target, match.Activator, type); Attach(target, match.Activator, type, Selector is not OrSelector);
} }
result = match.Result; result = match.Result;

10
src/Avalonia.Base/Styling/StyleBase.cs

@ -92,20 +92,24 @@ namespace Avalonia.Styling
return false; return false;
} }
internal ValueFrame Attach(StyledElement target, IStyleActivator? activator, FrameType type) internal ValueFrame Attach(
StyledElement target,
IStyleActivator? activator,
FrameType type,
bool canShareInstance)
{ {
if (target is not AvaloniaObject ao) if (target is not AvaloniaObject ao)
throw new InvalidOperationException("Styles can only be applied to AvaloniaObjects."); throw new InvalidOperationException("Styles can only be applied to AvaloniaObjects.");
StyleInstance instance; StyleInstance instance;
if (_sharedInstance is not null) if (_sharedInstance is not null && canShareInstance)
{ {
instance = _sharedInstance; instance = _sharedInstance;
} }
else else
{ {
var canShareInstance = activator is null; canShareInstance &= activator is null;
instance = new StyleInstance(this, activator, type); instance = new StyleInstance(this, activator, type);

12
src/Avalonia.Controls.DataGrid/Themes/Fluent.xaml

@ -15,6 +15,10 @@
<SolidColorBrush x:Key="DataGridRowGroupHeaderHoveredBackgroundBrush" Color="{DynamicResource SystemListLowColor}" /> <SolidColorBrush x:Key="DataGridRowGroupHeaderHoveredBackgroundBrush" Color="{DynamicResource SystemListLowColor}" />
<SolidColorBrush x:Key="DataGridRowHoveredBackgroundColor" Color="{DynamicResource SystemListLowColor}" /> <SolidColorBrush x:Key="DataGridRowHoveredBackgroundColor" Color="{DynamicResource SystemListLowColor}" />
<SolidColorBrush x:Key="DataGridRowInvalidBrush" Color="{DynamicResource SystemErrorTextColor}" /> <SolidColorBrush x:Key="DataGridRowInvalidBrush" Color="{DynamicResource SystemErrorTextColor}" />
<SolidColorBrush x:Key="DataGridRowSelectedBackgroundBrush" Color="{DynamicResource SystemAccentColor}" />
<SolidColorBrush x:Key="DataGridRowSelectedHoveredBackgroundBrush" Color="{DynamicResource SystemAccentColor}" />
<SolidColorBrush x:Key="DataGridRowSelectedUnfocusedBackgroundBrush" Color="{DynamicResource SystemAccentColor}" />
<SolidColorBrush x:Key="DataGridRowSelectedHoveredUnfocusedBackgroundBrush" Color="{DynamicResource SystemAccentColor}" />
<SolidColorBrush x:Key="DataGridCellFocusVisualPrimaryBrush" Color="{DynamicResource SystemBaseHighColor}" /> <SolidColorBrush x:Key="DataGridCellFocusVisualPrimaryBrush" Color="{DynamicResource SystemBaseHighColor}" />
<SolidColorBrush x:Key="DataGridCellFocusVisualSecondaryBrush" Color="{DynamicResource SystemAltMediumColor}" /> <SolidColorBrush x:Key="DataGridCellFocusVisualSecondaryBrush" Color="{DynamicResource SystemAltMediumColor}" />
<SolidColorBrush x:Key="DataGridCellInvalidBrush" Color="{DynamicResource SystemErrorTextColor}" /> <SolidColorBrush x:Key="DataGridCellInvalidBrush" Color="{DynamicResource SystemErrorTextColor}" />
@ -34,6 +38,10 @@
<SolidColorBrush x:Key="DataGridRowGroupHeaderHoveredBackgroundBrush" Color="{DynamicResource SystemListLowColor}" /> <SolidColorBrush x:Key="DataGridRowGroupHeaderHoveredBackgroundBrush" Color="{DynamicResource SystemListLowColor}" />
<SolidColorBrush x:Key="DataGridRowHoveredBackgroundColor" Color="{DynamicResource SystemListLowColor}" /> <SolidColorBrush x:Key="DataGridRowHoveredBackgroundColor" Color="{DynamicResource SystemListLowColor}" />
<SolidColorBrush x:Key="DataGridRowInvalidBrush" Color="{DynamicResource SystemErrorTextColor}" /> <SolidColorBrush x:Key="DataGridRowInvalidBrush" Color="{DynamicResource SystemErrorTextColor}" />
<SolidColorBrush x:Key="DataGridRowSelectedBackgroundBrush" Color="{DynamicResource SystemAccentColor}" />
<SolidColorBrush x:Key="DataGridRowSelectedHoveredBackgroundBrush" Color="{DynamicResource SystemAccentColor}" />
<SolidColorBrush x:Key="DataGridRowSelectedUnfocusedBackgroundBrush" Color="{DynamicResource SystemAccentColor}" />
<SolidColorBrush x:Key="DataGridRowSelectedHoveredUnfocusedBackgroundBrush" Color="{DynamicResource SystemAccentColor}" />
<SolidColorBrush x:Key="DataGridCellFocusVisualPrimaryBrush" Color="{DynamicResource SystemBaseHighColor}" /> <SolidColorBrush x:Key="DataGridCellFocusVisualPrimaryBrush" Color="{DynamicResource SystemBaseHighColor}" />
<SolidColorBrush x:Key="DataGridCellFocusVisualSecondaryBrush" Color="{DynamicResource SystemAltMediumColor}" /> <SolidColorBrush x:Key="DataGridCellFocusVisualSecondaryBrush" Color="{DynamicResource SystemAltMediumColor}" />
<SolidColorBrush x:Key="DataGridCellInvalidBrush" Color="{DynamicResource SystemErrorTextColor}" /> <SolidColorBrush x:Key="DataGridCellInvalidBrush" Color="{DynamicResource SystemErrorTextColor}" />
@ -53,13 +61,9 @@
<StreamGeometry x:Key="DataGridRowGroupHeaderIconOpenedPath">M109 486 19 576 1024 1581 2029 576 1939 486 1024 1401z</StreamGeometry> <StreamGeometry x:Key="DataGridRowGroupHeaderIconOpenedPath">M109 486 19 576 1024 1581 2029 576 1939 486 1024 1401z</StreamGeometry>
<StaticResource x:Key="DataGridRowBackgroundBrush" ResourceKey="SystemControlTransparentBrush" /> <StaticResource x:Key="DataGridRowBackgroundBrush" ResourceKey="SystemControlTransparentBrush" />
<SolidColorBrush x:Key="DataGridRowSelectedBackgroundBrush" Color="{DynamicResource SystemAccentColor}" />
<StaticResource x:Key="DataGridRowSelectedBackgroundOpacity" ResourceKey="ListAccentLowOpacity" /> <StaticResource x:Key="DataGridRowSelectedBackgroundOpacity" ResourceKey="ListAccentLowOpacity" />
<SolidColorBrush x:Key="DataGridRowSelectedHoveredBackgroundBrush" Color="{DynamicResource SystemAccentColor}" />
<StaticResource x:Key="DataGridRowSelectedHoveredBackgroundOpacity" ResourceKey="ListAccentMediumOpacity" /> <StaticResource x:Key="DataGridRowSelectedHoveredBackgroundOpacity" ResourceKey="ListAccentMediumOpacity" />
<SolidColorBrush x:Key="DataGridRowSelectedUnfocusedBackgroundBrush" Color="{DynamicResource SystemAccentColor}" />
<StaticResource x:Key="DataGridRowSelectedUnfocusedBackgroundOpacity" ResourceKey="ListAccentLowOpacity" /> <StaticResource x:Key="DataGridRowSelectedUnfocusedBackgroundOpacity" ResourceKey="ListAccentLowOpacity" />
<SolidColorBrush x:Key="DataGridRowSelectedHoveredUnfocusedBackgroundBrush" Color="{DynamicResource SystemAccentColor}" />
<StaticResource x:Key="DataGridRowSelectedHoveredUnfocusedBackgroundOpacity" ResourceKey="ListAccentMediumOpacity" /> <StaticResource x:Key="DataGridRowSelectedHoveredUnfocusedBackgroundOpacity" ResourceKey="ListAccentMediumOpacity" />
<StaticResource x:Key="DataGridCellBackgroundBrush" ResourceKey="SystemControlTransparentBrush" /> <StaticResource x:Key="DataGridCellBackgroundBrush" ResourceKey="SystemControlTransparentBrush" />
<StaticResource x:Key="DataGridCurrencyVisualPrimaryBrush" ResourceKey="SystemControlTransparentBrush" /> <StaticResource x:Key="DataGridCurrencyVisualPrimaryBrush" ResourceKey="SystemControlTransparentBrush" />

23
src/Avalonia.Controls/ApplicationLifetimes/ActivatedEventArgs.cs

@ -0,0 +1,23 @@
using System;
namespace Avalonia.Controls.ApplicationLifetimes;
/// <summary>
/// Event args for an Application Lifetime Activated or Deactivated events.
/// </summary>
public class ActivatedEventArgs : EventArgs
{
/// <summary>
/// Ctor for ActivatedEventArgs
/// </summary>
/// <param name="kind">The <see cref="ActivationKind"/> that this event represents</param>
public ActivatedEventArgs(ActivationKind kind)
{
Kind = kind;
}
/// <summary>
/// The <see cref="ActivationKind"/> that this event represents.
/// </summary>
public ActivationKind Kind { get; }
}

25
src/Avalonia.Controls/ApplicationLifetimes/ActivationKind.cs

@ -0,0 +1,25 @@
namespace Avalonia.Controls.ApplicationLifetimes;
public enum ActivationKind
{
/// <summary>
/// When the application is passed a URI to open.
/// </summary>
OpenUri = 20,
/// <summary>
/// When the application is asked to reopen.
/// An example of this is on MacOS when all the windows are closed,
/// application continues to run in the background and the user clicks
/// the application's dock icon.
/// </summary>
Reopen = 30,
/// <summary>
/// When the application enters or leaves a background state.
/// An example is when on MacOS the user hides or shows and application (not window),
/// or when a browser application switchs tabs or when a mobile applications goes into
/// the background.
/// </summary>
Background = 40
}

13
src/Avalonia.Controls/ApplicationLifetimes/ClassicDesktopStyleApplicationLifetime.cs

@ -211,11 +211,16 @@ namespace Avalonia
public static int StartWithClassicDesktopLifetime( public static int StartWithClassicDesktopLifetime(
this AppBuilder builder, string[] args, ShutdownMode shutdownMode = ShutdownMode.OnLastWindowClose) this AppBuilder builder, string[] args, ShutdownMode shutdownMode = ShutdownMode.OnLastWindowClose)
{ {
var lifetime = new ClassicDesktopStyleApplicationLifetime() var lifetime = AvaloniaLocator.Current.GetService<ClassicDesktopStyleApplicationLifetime>();
if (lifetime == null)
{ {
Args = args, lifetime = new ClassicDesktopStyleApplicationLifetime();
ShutdownMode = shutdownMode }
};
lifetime.Args = args;
lifetime.ShutdownMode = shutdownMode;
builder.SetupWithLifetime(lifetime); builder.SetupWithLifetime(lifetime);
return lifetime.Start(args); return lifetime.Start(args);
} }

35
src/Avalonia.Controls/ApplicationLifetimes/IActivatableApplicationLifetime.cs

@ -0,0 +1,35 @@
using System;
namespace Avalonia.Controls.ApplicationLifetimes;
/// <summary>
/// An interface for ApplicationLifetimes where the application can be Activated and Deactivated.
/// </summary>
public interface IActivatableApplicationLifetime
{
/// <summary>
/// An event that is raised when the application is Activated for various reasons
/// as described by the <see cref="ActivationKind"/> enumeration.
/// </summary>
event EventHandler<ActivatedEventArgs> Activated;
/// <summary>
/// An event that is raised when the application is Deactivated for various reasons
/// as described by the <see cref="ActivationKind"/> enumeration.
/// </summary>
event EventHandler<ActivatedEventArgs> Deactivated;
/// <summary>
/// Tells the application that it should attempt to leave its background state.
/// For example on OSX this would be [NSApp unhide]
/// </summary>
/// <returns>true if it was possible and the platform supports this. false otherwise</returns>
public bool TryLeaveBackground();
/// <summary>
/// Tells the application that it should attempt to enter its background state.
/// For example on OSX this would be [NSApp hide]
/// </summary>
/// <returns>true if it was possible and the platform supports this. false otherwise</returns>
public bool TryEnterBackground();
}

13
src/Avalonia.Controls/ApplicationLifetimes/ProtocolActivatedEventArgs.cs

@ -0,0 +1,13 @@
using System;
namespace Avalonia.Controls.ApplicationLifetimes;
public class ProtocolActivatedEventArgs : ActivatedEventArgs
{
public ProtocolActivatedEventArgs(ActivationKind kind, Uri uri) : base(kind)
{
Uri = uri;
}
public Uri Uri { get; }
}

13
src/Avalonia.Controls/ContextMenu.cs

@ -265,7 +265,7 @@ namespace Avalonia.Controls
} }
control ??= _attachedControls![0]; control ??= _attachedControls![0];
Open(control, PlacementTarget ?? control, false); Open(control, PlacementTarget ?? control, Placement);
} }
/// <summary> /// <summary>
@ -303,7 +303,7 @@ namespace Avalonia.Controls
remove => _popupHostChangedHandler -= value; remove => _popupHostChangedHandler -= value;
} }
private void Open(Control control, Control placementTarget, bool requestedByPointer) private void Open(Control control, Control placementTarget, PlacementMode placement)
{ {
if (IsOpen) if (IsOpen)
{ {
@ -330,9 +330,7 @@ namespace Avalonia.Controls
((ISetLogicalParent)_popup).SetParent(control); ((ISetLogicalParent)_popup).SetParent(control);
} }
_popup.Placement = !requestedByPointer && Placement == PlacementMode.Pointer _popup.Placement = placement;
? PlacementMode.Bottom
: Placement;
//Position of the line below is really important. //Position of the line below is really important.
//All styles are being applied only when control has logical parent. //All styles are being applied only when control has logical parent.
@ -420,7 +418,10 @@ namespace Avalonia.Controls
&& !contextMenu.CancelOpening()) && !contextMenu.CancelOpening())
{ {
var requestedByPointer = e.TryGetPosition(null, out _); var requestedByPointer = e.TryGetPosition(null, out _);
contextMenu.Open(control, e.Source as Control ?? control, requestedByPointer); contextMenu.Open(
control,
e.Source as Control ?? control,
requestedByPointer ? contextMenu.Placement : PlacementMode.Bottom);
e.Handled = true; e.Handled = true;
} }
} }

1
src/Avalonia.Controls/Platform/INativeApplicationCommands.cs

@ -5,6 +5,7 @@ namespace Avalonia.Controls.Platform
/// </summary> /// </summary>
internal interface INativeApplicationCommands internal interface INativeApplicationCommands
{ {
void ShowApp();
void HideApp(); void HideApp();
void ShowAll(); void ShowAll();
void HideOthers(); void HideOthers();

7
src/Avalonia.Controls/Presenters/ContentPresenter.cs

@ -169,7 +169,12 @@ namespace Avalonia.Controls.Presenters
/// </summary> /// </summary>
static ContentPresenter() static ContentPresenter()
{ {
AffectsRender<ContentPresenter>(BackgroundProperty, BorderBrushProperty, BorderThicknessProperty, CornerRadiusProperty); AffectsRender<ContentPresenter>(
BackgroundProperty,
BorderBrushProperty,
BorderThicknessProperty,
CornerRadiusProperty,
BoxShadowProperty);
AffectsArrange<ContentPresenter>(HorizontalContentAlignmentProperty, VerticalContentAlignmentProperty); AffectsArrange<ContentPresenter>(HorizontalContentAlignmentProperty, VerticalContentAlignmentProperty);
AffectsMeasure<ContentPresenter>(BorderThicknessProperty, PaddingProperty); AffectsMeasure<ContentPresenter>(BorderThicknessProperty, PaddingProperty);
} }

20
src/Avalonia.Controls/Presenters/TextPresenter.cs

@ -595,12 +595,6 @@ namespace Avalonia.Controls.Presenters
InvalidateMeasure(); InvalidateMeasure();
} }
protected override void OnLoaded(RoutedEventArgs e)
{
base.OnLoaded(e);
EnsureTextSelectionLayer();
}
protected override Size MeasureOverride(Size availableSize) protected override Size MeasureOverride(Size availableSize)
{ {
_constraint = availableSize; _constraint = availableSize;
@ -894,8 +888,7 @@ namespace Avalonia.Controls.Presenters
ResetCaretTimer(); ResetCaretTimer();
if (TextSelectionHandleCanvas is { } canvas && _layer != null && !_layer.Children.Contains(canvas)) EnsureTextSelectionLayer();
_layer?.Add(TextSelectionHandleCanvas);
} }
private void EnsureTextSelectionLayer() private void EnsureTextSelectionLayer()
@ -903,10 +896,17 @@ namespace Avalonia.Controls.Presenters
if (TextSelectionHandleCanvas == null) if (TextSelectionHandleCanvas == null)
{ {
TextSelectionHandleCanvas = new TextSelectionHandleCanvas(); TextSelectionHandleCanvas = new TextSelectionHandleCanvas();
TextSelectionHandleCanvas.SetPresenter(this);
} }
TextSelectionHandleCanvas.SetPresenter(this);
_layer = TextSelectorLayer.GetTextSelectorLayer(this); _layer = TextSelectorLayer.GetTextSelectorLayer(this);
if (_layer != null && !_layer.Children.Contains(TextSelectionHandleCanvas)) if (TextSelectionHandleCanvas.VisualParent is { } parent && parent != _layer)
{
if (parent is TextSelectorLayer l)
{
l.Remove(TextSelectionHandleCanvas);
}
}
if (_layer != null && TextSelectionHandleCanvas.VisualParent != _layer)
_layer?.Add(TextSelectionHandleCanvas); _layer?.Add(TextSelectionHandleCanvas);
} }

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

@ -1,6 +1,5 @@
using System; using System;
using System.Collections; using System.Collections;
using System.Collections.Generic;
using System.Collections.Specialized; using System.Collections.Specialized;
using System.ComponentModel; using System.ComponentModel;
using System.Diagnostics.CodeAnalysis; using System.Diagnostics.CodeAnalysis;
@ -8,7 +7,6 @@ using System.Linq;
using Avalonia.Controls.Selection; using Avalonia.Controls.Selection;
using Avalonia.Data; using Avalonia.Data;
using Avalonia.Input; using Avalonia.Input;
using Avalonia.Input.Platform;
using Avalonia.Interactivity; using Avalonia.Interactivity;
using Avalonia.Metadata; using Avalonia.Metadata;
using Avalonia.Threading; using Avalonia.Threading;
@ -187,14 +185,21 @@ namespace Avalonia.Controls.Primitives
/// </summary> /// </summary>
public int SelectedIndex public int SelectedIndex
{ {
get => get
{
// When a Begin/EndInit/DataContext update is in place we return the value to be // When a Begin/EndInit/DataContext update is in place we return the value to be
// updated here, even though it's not yet active and the property changed notification // updated here, even though it's not yet active and the property changed notification
// has not yet been raised. If we don't do this then the old value will be written back // has not yet been raised. If we don't do this then the old value will be written back
// to the source when two-way bound, and the update value will be lost. // to the source when two-way bound, and the update value will be lost.
_updateState?.SelectedIndex.HasValue == true ? if (_updateState is not null)
_updateState.SelectedIndex.Value : {
Selection.SelectedIndex; return _updateState.SelectedIndex.HasValue ?
_updateState.SelectedIndex.Value :
TryGetExistingSelection()?.SelectedIndex ?? -1;
}
return Selection.SelectedIndex;
}
set set
{ {
if (_updateState is object) if (_updateState is object)
@ -213,11 +218,18 @@ namespace Avalonia.Controls.Primitives
/// </summary> /// </summary>
public object? SelectedItem public object? SelectedItem
{ {
get => get
// See SelectedIndex setter for more information. {
_updateState?.SelectedItem.HasValue == true ? // See SelectedIndex getter for more information.
_updateState.SelectedItem.Value : if (_updateState is not null)
Selection.SelectedItem; {
return _updateState.SelectedItem.HasValue ?
_updateState.SelectedItem.Value :
TryGetExistingSelection()?.SelectedItem;
}
return Selection.SelectedItem;
}
set set
{ {
if (_updateState is object) if (_updateState is object)
@ -270,6 +282,7 @@ namespace Avalonia.Controls.Primitives
{ {
return _updateState.SelectedItems.Value; return _updateState.SelectedItems.Value;
} }
else if (Selection is InternalSelectionModel ism) else if (Selection is InternalSelectionModel ism)
{ {
var result = ism.WritableSelectedItems; var result = ism.WritableSelectedItems;
@ -456,10 +469,8 @@ namespace Avalonia.Controls.Primitives
protected override void OnAttachedToVisualTree(VisualTreeAttachmentEventArgs e) protected override void OnAttachedToVisualTree(VisualTreeAttachmentEventArgs e)
{ {
base.OnAttachedToVisualTree(e); base.OnAttachedToVisualTree(e);
if (Selection?.AnchorIndex is int index)
{ AutoScrollToSelectedItemIfNecessary(GetAnchorIndex());
AutoScrollToSelectedItemIfNecessary(index);
}
} }
/// <inheritdoc /> /// <inheritdoc />
@ -470,10 +481,8 @@ namespace Avalonia.Controls.Primitives
void ExecuteScrollWhenLayoutUpdated(object? sender, EventArgs e) void ExecuteScrollWhenLayoutUpdated(object? sender, EventArgs e)
{ {
LayoutUpdated -= ExecuteScrollWhenLayoutUpdated; LayoutUpdated -= ExecuteScrollWhenLayoutUpdated;
if (Selection?.AnchorIndex is int index)
{ AutoScrollToSelectedItemIfNecessary(GetAnchorIndex());
AutoScrollToSelectedItemIfNecessary(index);
}
} }
if (AutoScrollToSelectedItem) if (AutoScrollToSelectedItem)
@ -482,6 +491,15 @@ namespace Avalonia.Controls.Primitives
} }
} }
internal int GetAnchorIndex()
{
var selection = _updateState is not null ? TryGetExistingSelection() : Selection;
return selection?.AnchorIndex ?? -1;
}
private ISelectionModel? TryGetExistingSelection()
=> _updateState?.Selection.HasValue == true ? _updateState.Selection.Value : _selection;
protected internal override void PrepareContainerForItemOverride(Control container, object? item, int index) protected internal override void PrepareContainerForItemOverride(Control container, object? item, int index)
{ {
// Ensure that the selection model is created at this point so that accessing it in // Ensure that the selection model is created at this point so that accessing it in
@ -634,10 +652,7 @@ namespace Avalonia.Controls.Primitives
if (change.Property == AutoScrollToSelectedItemProperty) if (change.Property == AutoScrollToSelectedItemProperty)
{ {
if (Selection?.AnchorIndex is int index) AutoScrollToSelectedItemIfNecessary(GetAnchorIndex());
{
AutoScrollToSelectedItemIfNecessary(index);
}
} }
else if (change.Property == SelectionModeProperty && _selection is object) else if (change.Property == SelectionModeProperty && _selection is object)
{ {
@ -671,7 +686,7 @@ namespace Avalonia.Controls.Primitives
return; return;
} }
var value = change.GetNewValue<IBinding>(); var value = change.GetNewValue<IBinding?>();
if (value is null) if (value is null)
{ {
// Clearing SelectedValueBinding makes the SelectedValue the item itself // Clearing SelectedValueBinding makes the SelectedValue the item itself
@ -921,11 +936,10 @@ namespace Avalonia.Controls.Primitives
if (e.PropertyName == nameof(ISelectionModel.AnchorIndex)) if (e.PropertyName == nameof(ISelectionModel.AnchorIndex))
{ {
_hasScrolledToSelectedItem = false; _hasScrolledToSelectedItem = false;
if (Selection?.AnchorIndex is int index)
{ var anchorIndex = GetAnchorIndex();
KeyboardNavigation.SetTabOnceActiveElement(this, ContainerFromIndex(index)); KeyboardNavigation.SetTabOnceActiveElement(this, ContainerFromIndex(anchorIndex));
AutoScrollToSelectedItemIfNecessary(index); AutoScrollToSelectedItemIfNecessary(anchorIndex);
}
} }
else if (e.PropertyName == nameof(ISelectionModel.SelectedIndex) && _oldSelectedIndex != SelectedIndex) else if (e.PropertyName == nameof(ISelectionModel.SelectedIndex) && _oldSelectedIndex != SelectedIndex)
{ {
@ -1279,9 +1293,17 @@ namespace Avalonia.Controls.Primitives
state.SelectedItem = item; state.SelectedItem = item;
} }
// SelectedIndex vs SelectedItem:
// - If only one has a value, use it
// - If both have a value, prefer the one having a "non-empty" value, e.g. not -1 nor null
// - If both have a "non-empty" value, prefer the index
if (state.SelectedIndex.HasValue) if (state.SelectedIndex.HasValue)
{ {
SelectedIndex = state.SelectedIndex.Value; var selectedIndex = state.SelectedIndex.Value;
if (selectedIndex >= 0 || !state.SelectedItem.HasValue)
SelectedIndex = selectedIndex;
else
SelectedItem = state.SelectedItem.Value;
} }
else if (state.SelectedItem.HasValue) else if (state.SelectedItem.HasValue)
{ {
@ -1338,39 +1360,12 @@ namespace Avalonia.Controls.Primitives
// - Both the old and new SelectionModels have the incorrect Source // - Both the old and new SelectionModels have the incorrect Source
private class UpdateState private class UpdateState
{ {
private Optional<int> _selectedIndex;
private Optional<object?> _selectedItem;
private Optional<object?> _selectedValue;
public int UpdateCount { get; set; } public int UpdateCount { get; set; }
public Optional<ISelectionModel> Selection { get; set; } public Optional<ISelectionModel> Selection { get; set; }
public Optional<IList?> SelectedItems { get; set; } public Optional<IList?> SelectedItems { get; set; }
public Optional<int> SelectedIndex { get; set; }
public Optional<int> SelectedIndex public Optional<object?> SelectedItem { get; set; }
{ public Optional<object?> SelectedValue { get; set; }
get => _selectedIndex;
set
{
_selectedIndex = value;
_selectedItem = default;
}
}
public Optional<object?> SelectedItem
{
get => _selectedItem;
set
{
_selectedItem = value;
_selectedIndex = default;
}
}
public Optional<object?> SelectedValue
{
get => _selectedValue;
set => _selectedValue = value;
}
} }
/// <summary> /// <summary>

2
src/Avalonia.Controls/Primitives/TextSelectionCanvas.cs

@ -250,6 +250,8 @@ namespace Avalonia.Controls.Primitives
internal void SetPresenter(TextPresenter? textPresenter) internal void SetPresenter(TextPresenter? textPresenter)
{ {
if (_presenter == textPresenter)
return;
_presenter = textPresenter; _presenter = textPresenter;
if (_presenter != null) if (_presenter != null)
{ {

4
src/Avalonia.Controls/Remote/RemoteWidget.cs

@ -26,7 +26,7 @@ namespace Avalonia.Controls.Remote
Mode = SizingMode.Local; Mode = SizingMode.Local;
_connection = connection; _connection = connection;
_connection.OnMessage += (t, msg) => Dispatcher.UIThread.Post(() => OnMessage(msg)); _connection.OnMessage += (t, msg) => Dispatcher.UIThread.Post(OnMessage, msg);
_connection.Send(new ClientSupportedPixelFormatsMessage _connection.Send(new ClientSupportedPixelFormatsMessage
{ {
Formats = new[] Formats = new[]
@ -39,7 +39,7 @@ namespace Avalonia.Controls.Remote
public SizingMode Mode { get; set; } public SizingMode Mode { get; set; }
private void OnMessage(object msg) private void OnMessage(object? msg)
{ {
if (msg is FrameMessage frame) if (msg is FrameMessage frame)
{ {

63
src/Avalonia.Controls/Shapes/Shape.cs

@ -1,4 +1,5 @@
using System; using System;
using System.Collections.Generic;
using Avalonia.Collections; using Avalonia.Collections;
using Avalonia.Media; using Avalonia.Media;
using Avalonia.Media.Immutable; using Avalonia.Media.Immutable;
@ -62,14 +63,7 @@ namespace Avalonia.Controls.Shapes
private Matrix _transform = Matrix.Identity; private Matrix _transform = Matrix.Identity;
private Geometry? _definingGeometry; private Geometry? _definingGeometry;
private Geometry? _renderedGeometry; private Geometry? _renderedGeometry;
private IPen? _strokePen;
static Shape()
{
AffectsMeasure<Shape>(StretchProperty, StrokeThicknessProperty);
AffectsRender<Shape>(FillProperty, StrokeProperty, StrokeDashArrayProperty, StrokeDashOffsetProperty,
StrokeThicknessProperty, StrokeLineCapProperty, StrokeJoinProperty);
}
/// <summary> /// <summary>
/// Gets a value that represents the <see cref="Geometry"/> of the shape. /// Gets a value that represents the <see cref="Geometry"/> of the shape.
@ -199,30 +193,7 @@ namespace Avalonia.Controls.Shapes
if (geometry != null) if (geometry != null)
{ {
var stroke = Stroke; context.DrawGeometry(Fill, _strokePen, geometry);
ImmutablePen? pen = null;
if (stroke != null)
{
var strokeDashArray = StrokeDashArray;
ImmutableDashStyle? dashStyle = null;
if (strokeDashArray != null && strokeDashArray.Count > 0)
{
dashStyle = new ImmutableDashStyle(strokeDashArray, StrokeDashOffset);
}
pen = new ImmutablePen(
stroke.ToImmutable(),
StrokeThickness,
dashStyle,
StrokeLineCap,
StrokeJoin);
}
context.DrawGeometry(Fill, pen, geometry);
} }
} }
@ -266,6 +237,34 @@ namespace Avalonia.Controls.Shapes
InvalidateMeasure(); InvalidateMeasure();
} }
protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs change)
{
base.OnPropertyChanged(change);
if (change.Property == StrokeProperty
|| change.Property == StrokeThicknessProperty
|| change.Property == StrokeDashArrayProperty
|| change.Property == StrokeDashOffsetProperty
|| change.Property == StrokeLineCapProperty
|| change.Property == StrokeJoinProperty)
{
if (change.Property == StrokeProperty
|| change.Property == StrokeThicknessProperty)
{
InvalidateMeasure();
}
if (!Pen.TryModifyOrCreate(ref _strokePen, Stroke, StrokeThickness, StrokeDashArray, StrokeDashOffset, StrokeLineCap, StrokeJoin))
{
InvalidateVisual();
}
}
else if (change.Property == FillProperty)
{
InvalidateVisual();
}
}
protected override Size MeasureOverride(Size availableSize) protected override Size MeasureOverride(Size availableSize)
{ {
if (DefiningGeometry is null) if (DefiningGeometry is null)

28
src/Avalonia.Controls/TextBox.cs

@ -1307,13 +1307,13 @@ namespace Avalonia.Controls
{ {
case Key.Left: case Key.Left:
selection = DetectSelection(); selection = DetectSelection();
MoveHorizontal(-1, hasWholeWordModifiers, selection); MoveHorizontal(-1, hasWholeWordModifiers, selection, true);
movement = true; movement = true;
break; break;
case Key.Right: case Key.Right:
selection = DetectSelection(); selection = DetectSelection();
MoveHorizontal(1, hasWholeWordModifiers, selection); MoveHorizontal(1, hasWholeWordModifiers, selection, true);
movement = true; movement = true;
break; break;
@ -1781,7 +1781,7 @@ namespace Avalonia.Controls
/// </summary> /// </summary>
public void Clear() => SetCurrentValue(TextProperty, string.Empty); public void Clear() => SetCurrentValue(TextProperty, string.Empty);
private void MoveHorizontal(int direction, bool wholeWord, bool isSelecting) private void MoveHorizontal(int direction, bool wholeWord, bool isSelecting, bool moveCaretPosition)
{ {
if (_presenter == null) if (_presenter == null)
{ {
@ -1836,10 +1836,13 @@ namespace Avalonia.Controls
} }
SetCurrentValue(SelectionEndProperty, SelectionEnd + offset); SetCurrentValue(SelectionEndProperty, SelectionEnd + offset);
if (moveCaretPosition)
{
_presenter.MoveCaretToTextPosition(SelectionEnd);
}
_presenter.MoveCaretToTextPosition(SelectionEnd); if (!isSelecting && moveCaretPosition)
if (!isSelecting)
{ {
SetCurrentValue(CaretIndexProperty, SelectionEnd); SetCurrentValue(CaretIndexProperty, SelectionEnd);
} }
@ -1976,7 +1979,7 @@ namespace Avalonia.Controls
_presenter?.MoveCaretToTextPosition(start); _presenter?.MoveCaretToTextPosition(start);
SetCurrentValue(CaretIndexProperty, start); SetCurrentValue(SelectionStartProperty, start);
ClearSelection(); ClearSelection();
@ -2066,9 +2069,16 @@ namespace Avalonia.Controls
private void SetSelectionForControlBackspace() private void SetSelectionForControlBackspace()
{ {
var text = Text ?? string.Empty;
var selectionStart = CaretIndex; var selectionStart = CaretIndex;
MoveHorizontal(-1, true, false); MoveHorizontal(-1, true, false, false);
if (SelectionEnd > 0 &&
selectionStart < text.Length && text[selectionStart] == ' ')
{
SetCurrentValue(SelectionEndProperty, SelectionEnd - 1);
}
SetCurrentValue(SelectionStartProperty, selectionStart); SetCurrentValue(SelectionStartProperty, selectionStart);
} }
@ -2083,7 +2093,7 @@ namespace Avalonia.Controls
SetCurrentValue(SelectionStartProperty, CaretIndex); SetCurrentValue(SelectionStartProperty, CaretIndex);
MoveHorizontal(1, true, true); MoveHorizontal(1, true, true, false);
if (SelectionEnd < textLength && Text![SelectionEnd] == ' ') if (SelectionEnd < textLength && Text![SelectionEnd] == ' ')
{ {

1
src/Avalonia.Controls/TopLevel.cs

@ -291,6 +291,7 @@ namespace Avalonia.Controls
KeyModifiers = (KeyModifiers)rawKeyEventArgs.Modifiers, KeyModifiers = (KeyModifiers)rawKeyEventArgs.Modifiers,
Key = rawKeyEventArgs.Key, Key = rawKeyEventArgs.Key,
PhysicalKey = rawKeyEventArgs.PhysicalKey, PhysicalKey = rawKeyEventArgs.PhysicalKey,
KeyDeviceType= rawKeyEventArgs.KeyDeviceType,
KeySymbol = rawKeyEventArgs.KeySymbol KeySymbol = rawKeyEventArgs.KeySymbol
}; };

34
src/Avalonia.Controls/Utils/BorderRenderHelper.cs

@ -17,6 +17,7 @@ namespace Avalonia.Controls.Utils
private Thickness _borderThickness; private Thickness _borderThickness;
private CornerRadius _cornerRadius; private CornerRadius _cornerRadius;
private bool _initialized; private bool _initialized;
private IPen? _cachedPen;
void Update(Size finalSize, Thickness borderThickness, CornerRadius cornerRadius) void Update(Size finalSize, Thickness borderThickness, CornerRadius cornerRadius)
@ -87,22 +88,17 @@ namespace Avalonia.Controls.Utils
public void Render(DrawingContext context, public void Render(DrawingContext context,
Size finalSize, Thickness borderThickness, CornerRadius cornerRadius, Size finalSize, Thickness borderThickness, CornerRadius cornerRadius,
IBrush? background, IBrush? borderBrush, BoxShadows boxShadows, double borderDashOffset = 0, IBrush? background, IBrush? borderBrush, BoxShadows boxShadows)
PenLineCap borderLineCap = PenLineCap.Flat, PenLineJoin borderLineJoin = PenLineJoin.Miter,
AvaloniaList<double>? borderDashArray = null)
{ {
if (_size != finalSize if (_size != finalSize
|| _borderThickness != borderThickness || _borderThickness != borderThickness
|| _cornerRadius != cornerRadius || _cornerRadius != cornerRadius
|| !_initialized) || !_initialized)
Update(finalSize, borderThickness, cornerRadius); Update(finalSize, borderThickness, cornerRadius);
RenderCore(context, background, borderBrush, boxShadows, borderDashOffset, borderLineCap, borderLineJoin, RenderCore(context, background, borderBrush, boxShadows);
borderDashArray);
} }
void RenderCore(DrawingContext context, IBrush? background, IBrush? borderBrush, BoxShadows boxShadows, void RenderCore(DrawingContext context, IBrush? background, IBrush? borderBrush, BoxShadows boxShadows)
double borderDashOffset, PenLineCap borderLineCap, PenLineJoin borderLineJoin,
AvaloniaList<double>? borderDashArray)
{ {
if (_useComplexRendering) if (_useComplexRendering)
{ {
@ -121,26 +117,8 @@ namespace Avalonia.Controls.Utils
else else
{ {
var borderThickness = _borderThickness.Top; var borderThickness = _borderThickness.Top;
IPen? pen = null;
ImmutableDashStyle? dashStyle = null;
if (borderDashArray != null && borderDashArray.Count > 0)
{
dashStyle = new ImmutableDashStyle(borderDashArray, borderDashOffset);
}
if (borderBrush != null && borderThickness > 0)
{
pen = new ImmutablePen(
borderBrush.ToImmutable(),
borderThickness,
dashStyle,
borderLineCap,
borderLineJoin);
}
Pen.TryModifyOrCreate(ref _cachedPen, borderBrush, borderThickness);
var rect = new Rect(_size); var rect = new Rect(_size);
if (!MathUtilities.IsZero(borderThickness)) if (!MathUtilities.IsZero(borderThickness))
@ -148,7 +126,7 @@ namespace Avalonia.Controls.Utils
var rrect = new RoundedRect(rect, _cornerRadius.TopLeft, _cornerRadius.TopRight, var rrect = new RoundedRect(rect, _cornerRadius.TopLeft, _cornerRadius.TopRight,
_cornerRadius.BottomRight, _cornerRadius.BottomLeft); _cornerRadius.BottomRight, _cornerRadius.BottomLeft);
context.DrawRectangle(background, pen, rrect, boxShadows); context.DrawRectangle(background, _cachedPen, rrect, boxShadows);
} }
} }

12
src/Avalonia.DesignerSupport/Remote/RemoteDesignerEntryPoint.cs

@ -202,24 +202,24 @@ namespace Avalonia.DesignerSupport.Remote
} }
private static Window s_currentWindow; private static Window s_currentWindow;
private static void OnTransportMessage(IAvaloniaRemoteTransportConnection transport, object obj) => Dispatcher.UIThread.Post(() => private static void OnTransportMessage(IAvaloniaRemoteTransportConnection transport, object obj) => Dispatcher.UIThread.Post(static arg =>
{ {
if (obj is ClientSupportedPixelFormatsMessage formats) if (arg is ClientSupportedPixelFormatsMessage formats)
{ {
s_supportedPixelFormats = formats; s_supportedPixelFormats = formats;
RebuildPreFlight(); RebuildPreFlight();
} }
if (obj is ClientRenderInfoMessage renderInfo) if (arg is ClientRenderInfoMessage renderInfo)
{ {
s_renderInfoMessage = renderInfo; s_renderInfoMessage = renderInfo;
RebuildPreFlight(); RebuildPreFlight();
} }
if (obj is ClientViewportAllocatedMessage viewport) if (arg is ClientViewportAllocatedMessage viewport)
{ {
s_viewportAllocatedMessage = viewport; s_viewportAllocatedMessage = viewport;
RebuildPreFlight(); RebuildPreFlight();
} }
if (obj is UpdateXamlMessage xaml) if (arg is UpdateXamlMessage xaml)
{ {
if (s_currentWindow is not null) if (s_currentWindow is not null)
s_lastRenderScaling = s_currentWindow.RenderScaling; s_lastRenderScaling = s_currentWindow.RenderScaling;
@ -247,6 +247,6 @@ namespace Avalonia.DesignerSupport.Remote
}); });
} }
} }
}); }, obj);
} }
} }

5
src/Avalonia.Diagnostics/Diagnostics/DevToolsOptions.cs

@ -52,5 +52,10 @@ namespace Avalonia.Diagnostics
/// Get or set Focus Highlighter <see cref="Brush"/> /// Get or set Focus Highlighter <see cref="Brush"/>
/// </summary> /// </summary>
public IBrush? FocusHighlighterBrush { get; set; } public IBrush? FocusHighlighterBrush { get; set; }
/// <summary>
/// Set the <see cref="DevToolsViewKind">kind</see> of diagnostic view that show at launch of DevTools
/// </summary>
public DevToolsViewKind LaunchView { get; init; }
} }
} }

20
src/Avalonia.Diagnostics/Diagnostics/DevToolsViewKind.cs

@ -0,0 +1,20 @@
namespace Avalonia.Diagnostics;
/// <summary>
/// Kinds of diagnostic views available in DevTools
/// </summary>
public enum DevToolsViewKind
{
/// <summary>
/// The Logical Tree diagnostic view
/// </summary>
LogicalTree,
/// <summary>
/// The Visual Tree diagnostic view
/// </summary>
VisualTree,
/// <summary>
/// Events diagnostic view
/// </summary>
Events,
}

1
src/Avalonia.Diagnostics/Diagnostics/ViewModels/MainViewModel.cs

@ -338,6 +338,7 @@ namespace Avalonia.Diagnostics.ViewModels
StartupScreenIndex = options.StartupScreenIndex; StartupScreenIndex = options.StartupScreenIndex;
ShowImplementedInterfaces = options.ShowImplementedInterfaces; ShowImplementedInterfaces = options.ShowImplementedInterfaces;
FocusHighlighter = options.FocusHighlighterBrush; FocusHighlighter = options.FocusHighlighterBrush;
SelectedTab = (int)options.LaunchView;
} }
public bool ShowImplementedInterfaces public bool ShowImplementedInterfaces

2
src/Avalonia.FreeDesktop/Avalonia.FreeDesktop.csproj

@ -13,7 +13,7 @@
<ItemGroup> <ItemGroup>
<PackageReference Include="Tmds.DBus.Protocol" Version="0.15.0" /> <PackageReference Include="Tmds.DBus.Protocol" Version="0.15.0" />
<PackageReference Include="Tmds.DBus.SourceGenerator" Version="0.0.11" PrivateAssets="All" /> <PackageReference Include="Tmds.DBus.SourceGenerator" Version="0.0.13" PrivateAssets="All" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>

15
src/Avalonia.FreeDesktop/DBusIme/DBusTextInputMethodBase.cs

@ -179,12 +179,23 @@ namespace Avalonia.FreeDesktop.DBusIme
_disposables.Add(d); _disposables.Add(d);
} }
public void Dispose() public async void Dispose()
{ {
foreach(var d in _disposables) foreach(var d in _disposables)
d.Dispose(); d.Dispose();
_disposables.Clear(); _disposables.Clear();
_ = DisconnectAsync(); if (!IsConnected)
return;
try
{
await DisconnectAsync();
}
catch (Exception ex)
{
Logger.TryGet(LogEventLevel.Error, "IME")
?.Log(this, "Error while destroying the context:\n" + ex);
}
_currentName = null; _currentName = null;
} }

32
src/Avalonia.Native/AvaloniaNativeApplicationPlatform.cs

@ -13,6 +13,38 @@ namespace Avalonia.Native
void IAvnApplicationEvents.FilesOpened(IAvnStringArray urls) void IAvnApplicationEvents.FilesOpened(IAvnStringArray urls)
{ {
((IApplicationPlatformEvents)Application.Current).RaiseUrlsOpened(urls.ToStringArray()); ((IApplicationPlatformEvents)Application.Current).RaiseUrlsOpened(urls.ToStringArray());
if (Application.Current?.ApplicationLifetime is MacOSClassicDesktopStyleApplicationLifetime lifetime)
{
foreach (var url in urls.ToStringArray())
{
lifetime.RaiseUrl(new Uri(url));
}
}
}
void IAvnApplicationEvents.OnReopen()
{
if (Application.Current?.ApplicationLifetime is MacOSClassicDesktopStyleApplicationLifetime lifetime)
{
lifetime.RaiseActivated(ActivationKind.Reopen);
}
}
void IAvnApplicationEvents.OnHide()
{
if (Application.Current?.ApplicationLifetime is MacOSClassicDesktopStyleApplicationLifetime lifetime)
{
lifetime.RaiseDeactivated(ActivationKind.Background);
}
}
void IAvnApplicationEvents.OnUnhide()
{
if (Application.Current?.ApplicationLifetime is MacOSClassicDesktopStyleApplicationLifetime lifetime)
{
lifetime.RaiseActivated(ActivationKind.Background);
}
} }
public int TryShutdown() public int TryShutdown()

12
src/Avalonia.Native/AvaloniaNativeMenuExporter.cs

@ -146,6 +146,8 @@ namespace Avalonia.Native
appMenu.Add(quitItem); appMenu.Add(quitItem);
} }
private void DoLayoutReset() => DoLayoutReset(false);
private void DoLayoutReset(bool forceUpdate = false) private void DoLayoutReset(bool forceUpdate = false)
{ {
var macOpts = AvaloniaLocator.Current.GetService<MacOSPlatformOptions>() ?? new MacOSPlatformOptions(); var macOpts = AvaloniaLocator.Current.GetService<MacOSPlatformOptions>() ?? new MacOSPlatformOptions();
@ -195,7 +197,7 @@ namespace Avalonia.Native
if (_resetQueued) if (_resetQueued)
return; return;
_resetQueued = true; _resetQueued = true;
Dispatcher.UIThread.Post(() => DoLayoutReset(), DispatcherPriority.Background); Dispatcher.UIThread.Post(DoLayoutReset, DispatcherPriority.Background);
} }
private void SetMenu(NativeMenu menu) private void SetMenu(NativeMenu menu)
@ -252,9 +254,9 @@ namespace Avalonia.Native
{ {
_nativeMenu = __MicroComIAvnMenuProxy.Create(_factory); _nativeMenu = __MicroComIAvnMenuProxy.Create(_factory);
_nativeMenu.Initialize(this, menu, ""); _nativeMenu.Initialize(this, menu, "");
setMenu = true; setMenu = true;
} }
_nativeMenu.Update(_factory, menu); _nativeMenu.Update(_factory, menu);
@ -273,9 +275,9 @@ namespace Avalonia.Native
{ {
_nativeMenu = __MicroComIAvnMenuProxy.Create(_factory); _nativeMenu = __MicroComIAvnMenuProxy.Create(_factory);
_nativeMenu.Initialize(this, menu, ""); _nativeMenu.Initialize(this, menu, "");
setMenu = true; setMenu = true;
} }
_nativeMenu.Update(_factory, menu); _nativeMenu.Update(_factory, menu);

4
src/Avalonia.Native/AvaloniaNativePlatformExtensions.cs

@ -1,6 +1,7 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using Avalonia.Controls; using Avalonia.Controls;
using Avalonia.Controls.ApplicationLifetimes;
using Avalonia.Native; using Avalonia.Native;
namespace Avalonia namespace Avalonia
@ -24,6 +25,9 @@ namespace Avalonia
}); });
}); });
AvaloniaLocator.CurrentMutable.Bind<ClassicDesktopStyleApplicationLifetime>()
.ToConstant(new MacOSClassicDesktopStyleApplicationLifetime());
return builder; return builder;
} }
} }

50
src/Avalonia.Native/MacOSClassicDesktopStyleApplicationLifetime.cs

@ -0,0 +1,50 @@
using System;
using Avalonia.Controls.ApplicationLifetimes;
using Avalonia.Controls.Platform;
namespace Avalonia.Native;
#nullable enable
internal class MacOSClassicDesktopStyleApplicationLifetime : ClassicDesktopStyleApplicationLifetime,
IActivatableApplicationLifetime
{
/// <inheritdoc />
public event EventHandler<ActivatedEventArgs>? Activated;
/// <inheritdoc />
public event EventHandler<ActivatedEventArgs>? Deactivated;
/// <inheritdoc />
public bool TryLeaveBackground()
{
var nativeApplicationCommands = AvaloniaLocator.Current.GetService<INativeApplicationCommands>();
nativeApplicationCommands?.ShowApp();
return true;
}
/// <inheritdoc />
public bool TryEnterBackground()
{
var nativeApplicationCommands = AvaloniaLocator.Current.GetService<INativeApplicationCommands>();
nativeApplicationCommands?.HideApp();
return true;
}
internal void RaiseUrl(Uri uri)
{
Activated?.Invoke(this, new ProtocolActivatedEventArgs(ActivationKind.OpenUri, uri));
}
internal void RaiseActivated(ActivationKind kind)
{
Activated?.Invoke(this, new ActivatedEventArgs(kind));
}
internal void RaiseDeactivated(ActivationKind kind)
{
Deactivated?.Invoke(this, new ActivatedEventArgs(kind));
}
}

6
src/Avalonia.Native/MacOSNativeMenuCommands.cs

@ -13,6 +13,11 @@ namespace Avalonia.Native
_commands = commands; _commands = commands;
} }
public void ShowApp()
{
_commands.UnhideApp();
}
public void HideApp() public void HideApp()
{ {
_commands.HideApp(); _commands.HideApp();
@ -28,7 +33,6 @@ namespace Avalonia.Native
_commands.HideOthers(); _commands.HideOthers();
} }
public static readonly AttachedProperty<bool> IsServicesSubmenuProperty = public static readonly AttachedProperty<bool> IsServicesSubmenuProperty =
AvaloniaProperty.RegisterAttached<MacOSNativeMenuCommands, NativeMenu, bool>("IsServicesSubmenu", false); AvaloniaProperty.RegisterAttached<MacOSNativeMenuCommands, NativeMenu, bool>("IsServicesSubmenu", false);
} }

4
src/Avalonia.Native/WindowImplBase.cs

@ -10,10 +10,8 @@ using Avalonia.Input;
using Avalonia.Input.Platform; using Avalonia.Input.Platform;
using Avalonia.Input.Raw; using Avalonia.Input.Raw;
using Avalonia.Native.Interop; using Avalonia.Native.Interop;
using Avalonia.OpenGL;
using Avalonia.Platform; using Avalonia.Platform;
using Avalonia.Platform.Storage; using Avalonia.Platform.Storage;
using Avalonia.Rendering;
using Avalonia.Rendering.Composition; using Avalonia.Rendering.Composition;
using Avalonia.Threading; using Avalonia.Threading;
@ -58,7 +56,6 @@ namespace Avalonia.Native
private readonly IKeyboardDevice _keyboard; private readonly IKeyboardDevice _keyboard;
private readonly ICursorFactory _cursorFactory; private readonly ICursorFactory _cursorFactory;
private Size _savedLogicalSize; private Size _savedLogicalSize;
private Size _lastRenderedLogicalSize;
private double _savedScaling; private double _savedScaling;
private NativeControlHostImpl _nativeControlHost; private NativeControlHostImpl _nativeControlHost;
private IStorageProvider _storageProvider; private IStorageProvider _storageProvider;
@ -172,7 +169,6 @@ namespace Avalonia.Native
if (_parent._native != null && _target != null) if (_parent._native != null && _target != null)
{ {
cb(_parent._native); cb(_parent._native);
_parent._lastRenderedLogicalSize = _parent._savedLogicalSize;
} }
} }
}, (int)w, (int)h, new Vector(dpi, dpi)); }, (int)w, (int)h, new Vector(dpi, dpi));

4
src/Avalonia.Native/avn.idl

@ -1087,11 +1087,15 @@ interface IAvnApplicationEvents : IUnknown
{ {
void FilesOpened (IAvnStringArray* urls); void FilesOpened (IAvnStringArray* urls);
bool TryShutdown(); bool TryShutdown();
void OnReopen ();
void OnHide ();
void OnUnhide ();
} }
[uuid(b4284791-055b-4313-8c2e-50f0a8c72ce9)] [uuid(b4284791-055b-4313-8c2e-50f0a8c72ce9)]
interface IAvnApplicationCommands : IUnknown interface IAvnApplicationCommands : IUnknown
{ {
HRESULT UnhideApp();
HRESULT HideApp(); HRESULT HideApp();
HRESULT ShowAll(); HRESULT ShowAll();
HRESULT HideOthers(); HRESULT HideOthers();

8
src/Avalonia.OpenGL/Controls/OpenGlControlBase.cs

@ -220,15 +220,15 @@ namespace Avalonia.OpenGL.Controls
[Obsolete("Use RequestNextFrameRendering()"), EditorBrowsable(EditorBrowsableState.Never)] [Obsolete("Use RequestNextFrameRendering()"), EditorBrowsable(EditorBrowsableState.Never)]
// ReSharper disable once MemberCanBeProtected.Global // ReSharper disable once MemberCanBeProtected.Global
public new void InvalidateVisual() => RequestNextFrameRendering(); public new void InvalidateVisual() => RequestNextFrameRendering();
public void RequestNextFrameRendering() public void RequestNextFrameRendering()
{ {
if ((_initialization == null || _initialization is { Status: TaskStatus.RanToCompletion }) && if ((_initialization == null || _initialization is { Status: TaskStatus.RanToCompletion }) &&
!_updateQueued) !_updateQueued && _compositor != null)
{ {
_updateQueued = true; _updateQueued = true;
_compositor?.RequestCompositionUpdate(_update); _compositor.RequestCompositionUpdate(_update);
} }
} }

2
src/Avalonia.OpenGL/Egl/EglContext.cs

@ -154,7 +154,7 @@ namespace Avalonia.OpenGL.Egl
ShareWith = _sharedWith ?? this ShareWith = _sharedWith ?? this
}); });
public bool IsCurrent => _egl.GetCurrentDisplay() == _disp.Handle && _egl.GetCurrentContext() == Context; public bool IsCurrent => _context != default && _egl.GetCurrentDisplay() == _disp.Handle && _egl.GetCurrentContext() == _context;
public void Dispose() public void Dispose()
{ {

6
src/Avalonia.Themes.Fluent/Accents/FluentControlResources.xaml

@ -385,6 +385,9 @@
<StaticResource x:Key="CalendarViewNavigationButtonBorderBrushPointerOver" <StaticResource x:Key="CalendarViewNavigationButtonBorderBrushPointerOver"
ResourceKey="SystemControlHighlightTransparentBrush" /> ResourceKey="SystemControlHighlightTransparentBrush" />
<StaticResource x:Key="CalendarViewNavigationButtonBorderBrush" ResourceKey="SystemControlTransparentBrush" /> <StaticResource x:Key="CalendarViewNavigationButtonBorderBrush" ResourceKey="SystemControlTransparentBrush" />
<x:Double x:Key="CalendarFontSize">20</x:Double>
<x:Double x:Key="CalendarDayButtonFontSize">12</x:Double>
<FontWeight x:Key="CalendarViewTodayFontWeight">SemiBold</FontWeight>
<!-- Resources for Expander.xaml --> <!-- Resources for Expander.xaml -->
<!-- Expander:Header --> <!-- Expander:Header -->
@ -1180,6 +1183,9 @@
<StaticResource x:Key="CalendarViewNavigationButtonBorderBrushPointerOver" <StaticResource x:Key="CalendarViewNavigationButtonBorderBrushPointerOver"
ResourceKey="SystemControlHighlightTransparentBrush" /> ResourceKey="SystemControlHighlightTransparentBrush" />
<StaticResource x:Key="CalendarViewNavigationButtonBorderBrush" ResourceKey="SystemControlTransparentBrush" /> <StaticResource x:Key="CalendarViewNavigationButtonBorderBrush" ResourceKey="SystemControlTransparentBrush" />
<x:Double x:Key="CalendarFontSize">20</x:Double>
<x:Double x:Key="CalendarDayButtonFontSize">12</x:Double>
<FontWeight x:Key="CalendarViewTodayFontWeight">SemiBold</FontWeight>
<!-- Resources for Expander.xaml --> <!-- Resources for Expander.xaml -->
<!-- Expander:Header --> <!-- Expander:Header -->

4
src/Avalonia.Themes.Fluent/Controls/AutoCompleteBox.xaml

@ -28,7 +28,6 @@
<Setter Property="BorderBrush" Value="{DynamicResource TextControlBorderBrush}" /> <Setter Property="BorderBrush" Value="{DynamicResource TextControlBorderBrush}" />
<Setter Property="BorderThickness" Value="{DynamicResource TextControlBorderThemeThickness}" /> <Setter Property="BorderThickness" Value="{DynamicResource TextControlBorderThemeThickness}" />
<Setter Property="CornerRadius" Value="{DynamicResource ControlCornerRadius}" /> <Setter Property="CornerRadius" Value="{DynamicResource ControlCornerRadius}" />
<Setter Property="FontSize" Value="{DynamicResource ControlContentThemeFontSize}" />
<Setter Property="Padding" Value="{DynamicResource TextControlThemePadding}" /> <Setter Property="Padding" Value="{DynamicResource TextControlThemePadding}" />
<Setter Property="MaxDropDownHeight" Value="{DynamicResource AutoCompleteListMaxHeight}" /> <Setter Property="MaxDropDownHeight" Value="{DynamicResource AutoCompleteListMaxHeight}" />
<Setter Property="Template"> <Setter Property="Template">
@ -43,9 +42,6 @@
BorderThickness="{TemplateBinding BorderThickness}" BorderThickness="{TemplateBinding BorderThickness}"
CornerRadius="{TemplateBinding CornerRadius}" CornerRadius="{TemplateBinding CornerRadius}"
CaretIndex="{TemplateBinding CaretIndex, Mode=TwoWay}" CaretIndex="{TemplateBinding CaretIndex, Mode=TwoWay}"
FontSize="{TemplateBinding FontSize}"
FontFamily="{TemplateBinding FontFamily}"
FontWeight="{TemplateBinding FontWeight}"
Padding="{TemplateBinding Padding}" Padding="{TemplateBinding Padding}"
Margin="0" Margin="0"
DataValidationErrors.Errors="{TemplateBinding (DataValidationErrors.Errors)}" /> DataValidationErrors.Errors="{TemplateBinding (DataValidationErrors.Errors)}" />

3
src/Avalonia.Themes.Fluent/Controls/ButtonSpinner.xaml

@ -79,7 +79,6 @@
<Setter Property="CornerRadius" Value="{DynamicResource ControlCornerRadius}" /> <Setter Property="CornerRadius" Value="{DynamicResource ControlCornerRadius}" />
<Setter Property="MinHeight" Value="{DynamicResource TextControlThemeMinHeight}" /> <Setter Property="MinHeight" Value="{DynamicResource TextControlThemeMinHeight}" />
<Setter Property="MinWidth" Value="{DynamicResource TextControlThemeMinWidth}" /> <Setter Property="MinWidth" Value="{DynamicResource TextControlThemeMinWidth}" />
<Setter Property="FontSize" Value="{DynamicResource ControlContentThemeFontSize}" />
<Setter Property="HorizontalContentAlignment" Value="Stretch" /> <Setter Property="HorizontalContentAlignment" Value="Stretch" />
<Setter Property="VerticalContentAlignment" Value="Center" /> <Setter Property="VerticalContentAlignment" Value="Center" />
<Setter Property="Focusable" Value="True"/> <Setter Property="Focusable" Value="True"/>
@ -104,7 +103,6 @@
VerticalAlignment="Stretch" VerticalAlignment="Stretch"
VerticalContentAlignment="Center" VerticalContentAlignment="Center"
Foreground="{TemplateBinding Foreground}" Foreground="{TemplateBinding Foreground}"
FontSize="{TemplateBinding FontSize}"
MinWidth="34"> MinWidth="34">
<PathIcon Width="16" <PathIcon Width="16"
Height="8" Height="8"
@ -119,7 +117,6 @@
VerticalAlignment="Stretch" VerticalAlignment="Stretch"
VerticalContentAlignment="Center" VerticalContentAlignment="Center"
Foreground="{TemplateBinding Foreground}" Foreground="{TemplateBinding Foreground}"
FontSize="{TemplateBinding FontSize}"
MinWidth="34"> MinWidth="34">
<PathIcon Width="16" <PathIcon Width="16"
Height="8" Height="8"

1
src/Avalonia.Themes.Fluent/Controls/Calendar.xaml

@ -21,6 +21,7 @@
<Setter Property="BorderThickness" Value="1" /> <Setter Property="BorderThickness" Value="1" />
<Setter Property="HorizontalAlignment" Value="Left" /> <Setter Property="HorizontalAlignment" Value="Left" />
<Setter Property="VerticalAlignment" Value="Center" /> <Setter Property="VerticalAlignment" Value="Center" />
<Setter Property="FontSize" Value="{DynamicResource CalendarFontSize}" />
<Setter Property="Template"> <Setter Property="Template">
<ControlTemplate> <ControlTemplate>
<StackPanel <StackPanel

4
src/Avalonia.Themes.Fluent/Controls/CalendarButton.xaml

@ -26,7 +26,6 @@
<Setter Property="Background" Value="{DynamicResource CalendarViewCalendarItemRevealBackground}" /> <Setter Property="Background" Value="{DynamicResource CalendarViewCalendarItemRevealBackground}" />
<Setter Property="BorderBrush" Value="{DynamicResource CalendarViewCalendarItemRevealBorderBrush}" /> <Setter Property="BorderBrush" Value="{DynamicResource CalendarViewCalendarItemRevealBorderBrush}" />
<Setter Property="BorderThickness" Value="2" /> <Setter Property="BorderThickness" Value="2" />
<Setter Property="FontSize" Value="20" />
<Setter Property="ClipToBounds" Value="False" /> <Setter Property="ClipToBounds" Value="False" />
<Setter Property="HorizontalContentAlignment" Value="Center" /> <Setter Property="HorizontalContentAlignment" Value="Center" />
<Setter Property="VerticalContentAlignment" Value="Center" /> <Setter Property="VerticalContentAlignment" Value="Center" />
@ -41,7 +40,6 @@
Content="{TemplateBinding Content}" Content="{TemplateBinding Content}"
HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}" HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}"
VerticalAlignment="{TemplateBinding VerticalContentAlignment}" VerticalAlignment="{TemplateBinding VerticalContentAlignment}"
FontSize="{TemplateBinding FontSize}"
Margin="{TemplateBinding Padding}" /> Margin="{TemplateBinding Padding}" />
</Border> </Border>
@ -73,7 +71,7 @@
<Style Selector="^ /template/ ContentPresenter#Content"> <Style Selector="^ /template/ ContentPresenter#Content">
<Setter Property="Foreground" Value="{DynamicResource CalendarViewTodayForeground}" /> <Setter Property="Foreground" Value="{DynamicResource CalendarViewTodayForeground}" />
<Setter Property="FontWeight" Value="SemiBold" /> <Setter Property="FontWeight" Value="{DynamicResource CalendarViewTodayFontWeight}" />
</Style> </Style>
<Style Selector="^:pointerover"> <Style Selector="^:pointerover">

2
src/Avalonia.Themes.Fluent/Controls/CalendarDayButton.xaml

@ -26,7 +26,6 @@
<Setter Property="Background" Value="{DynamicResource CalendarViewCalendarItemRevealBackground}" /> <Setter Property="Background" Value="{DynamicResource CalendarViewCalendarItemRevealBackground}" />
<Setter Property="BorderBrush" Value="{DynamicResource CalendarViewCalendarItemRevealBorderBrush}" /> <Setter Property="BorderBrush" Value="{DynamicResource CalendarViewCalendarItemRevealBorderBrush}" />
<Setter Property="BorderThickness" Value="2" /> <Setter Property="BorderThickness" Value="2" />
<Setter Property="FontSize" Value="20" />
<Setter Property="ClipToBounds" Value="False" /> <Setter Property="ClipToBounds" Value="False" />
<Setter Property="HorizontalContentAlignment" Value="Center" /> <Setter Property="HorizontalContentAlignment" Value="Center" />
<Setter Property="VerticalContentAlignment" Value="Center" /> <Setter Property="VerticalContentAlignment" Value="Center" />
@ -42,7 +41,6 @@
Content="{TemplateBinding Content}" Content="{TemplateBinding Content}"
HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}" HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}"
VerticalAlignment="{TemplateBinding VerticalContentAlignment}" VerticalAlignment="{TemplateBinding VerticalContentAlignment}"
FontSize="{TemplateBinding FontSize}"
Margin="{TemplateBinding Padding}" /> Margin="{TemplateBinding Padding}" />
</Border> </Border>

3
src/Avalonia.Themes.Fluent/Controls/CalendarItem.xaml

@ -27,7 +27,6 @@
<Setter Property="HorizontalAlignment" Value="Stretch" /> <Setter Property="HorizontalAlignment" Value="Stretch" />
<Setter Property="VerticalAlignment" Value="Stretch" /> <Setter Property="VerticalAlignment" Value="Stretch" />
<Setter Property="VerticalContentAlignment" Value="Center" /> <Setter Property="VerticalContentAlignment" Value="Center" />
<Setter Property="FontSize" Value="20" />
<Setter Property="Background" Value="{DynamicResource CalendarViewNavigationButtonBackground}" /> <Setter Property="Background" Value="{DynamicResource CalendarViewNavigationButtonBackground}" />
<Setter Property="Template"> <Setter Property="Template">
<ControlTemplate> <ControlTemplate>
@ -61,7 +60,7 @@
<TextBlock Text="{Binding}" <TextBlock Text="{Binding}"
HorizontalAlignment="Center" HorizontalAlignment="Center"
VerticalAlignment="Center" VerticalAlignment="Center"
FontSize="12" /> FontSize="{DynamicResource CalendarDayButtonFontSize}" />
</Template> </Template>
</Setter> </Setter>

1
src/Avalonia.Themes.Fluent/Controls/CheckBox.xaml

@ -18,7 +18,6 @@
<Setter Property="VerticalAlignment" Value="Center" /> <Setter Property="VerticalAlignment" Value="Center" />
<Setter Property="HorizontalContentAlignment" Value="Left" /> <Setter Property="HorizontalContentAlignment" Value="Left" />
<Setter Property="VerticalContentAlignment" Value="Center" /> <Setter Property="VerticalContentAlignment" Value="Center" />
<Setter Property="FontSize" Value="{DynamicResource ControlContentThemeFontSize}" />
<Setter Property="CornerRadius" Value="{DynamicResource ControlCornerRadius}" /> <Setter Property="CornerRadius" Value="{DynamicResource ControlCornerRadius}" />
<Setter Property="MinHeight" Value="32" /> <Setter Property="MinHeight" Value="32" />
<Setter Property="Foreground" Value="{DynamicResource CheckBoxForegroundUnchecked}" /> <Setter Property="Foreground" Value="{DynamicResource CheckBoxForegroundUnchecked}" />

1
src/Avalonia.Themes.Fluent/Controls/ComboBox.xaml

@ -53,7 +53,6 @@
<Setter Property="VerticalContentAlignment" Value="Center" /> <Setter Property="VerticalContentAlignment" Value="Center" />
<Setter Property="HorizontalAlignment" Value="Left" /> <Setter Property="HorizontalAlignment" Value="Left" />
<Setter Property="VerticalAlignment" Value="Top" /> <Setter Property="VerticalAlignment" Value="Top" />
<Setter Property="FontSize" Value="{DynamicResource ControlContentThemeFontSize}" />
<Setter Property="PlaceholderForeground" Value="{DynamicResource ComboBoxPlaceHolderForeground}" /> <Setter Property="PlaceholderForeground" Value="{DynamicResource ComboBoxPlaceHolderForeground}" />
<Setter Property="Template"> <Setter Property="Template">
<ControlTemplate> <ControlTemplate>

2
src/Avalonia.Themes.Fluent/Controls/ContextMenu.xaml

@ -42,8 +42,6 @@
<Setter Property="MinHeight" Value="{DynamicResource MenuFlyoutThemeMinHeight}" /> <Setter Property="MinHeight" Value="{DynamicResource MenuFlyoutThemeMinHeight}" />
<Setter Property="Padding" Value="{DynamicResource MenuFlyoutPresenterThemePadding}" /> <Setter Property="Padding" Value="{DynamicResource MenuFlyoutPresenterThemePadding}" />
<Setter Property="HorizontalAlignment" Value="Stretch" /> <Setter Property="HorizontalAlignment" Value="Stretch" />
<Setter Property="TextBlock.FontSize" Value="{DynamicResource ControlContentThemeFontSize}" />
<Setter Property="TextBlock.FontWeight" Value="Normal" />
<Setter Property="WindowManagerAddShadowHint" Value="False" /> <Setter Property="WindowManagerAddShadowHint" Value="False" />
<Setter Property="Template"> <Setter Property="Template">
<ControlTemplate> <ControlTemplate>

18
src/Avalonia.Themes.Fluent/Controls/DatePicker.xaml

@ -72,7 +72,6 @@
</ControlTheme> </ControlTheme>
<ControlTheme x:Key="{x:Type DatePicker}" TargetType="DatePicker"> <ControlTheme x:Key="{x:Type DatePicker}" TargetType="DatePicker">
<Setter Property="FontSize" Value="{DynamicResource ControlContentThemeFontSize}" />
<Setter Property="Foreground" Value="{DynamicResource DatePickerButtonForeground}" /> <Setter Property="Foreground" Value="{DynamicResource DatePickerButtonForeground}" />
<Setter Property="Background" Value="{DynamicResource DatePickerButtonBackground}"/> <Setter Property="Background" Value="{DynamicResource DatePickerButtonBackground}"/>
<Setter Property="BorderBrush" Value="{DynamicResource DatePickerButtonBorderBrush}"/> <Setter Property="BorderBrush" Value="{DynamicResource DatePickerButtonBorderBrush}"/>
@ -99,20 +98,11 @@
TemplatedControl.IsTemplateFocusTarget="True"> TemplatedControl.IsTemplateFocusTarget="True">
<Grid Name="PART_ButtonContentGrid" ColumnDefinitions="78*,Auto,132*,Auto,78*"> <Grid Name="PART_ButtonContentGrid" ColumnDefinitions="78*,Auto,132*,Auto,78*">
<TextBlock Name="PART_DayTextBlock" Text="day" HorizontalAlignment="Center" <TextBlock Name="PART_DayTextBlock" Text="day" HorizontalAlignment="Center"
Padding="{DynamicResource DatePickerHostPadding}" Padding="{DynamicResource DatePickerHostPadding}"/>
FontFamily="{TemplateBinding FontFamily}"
FontWeight="{TemplateBinding FontWeight}"
FontSize="{TemplateBinding FontSize}"/>
<TextBlock Name="PART_MonthTextBlock" Text="month" TextAlignment="Left" <TextBlock Name="PART_MonthTextBlock" Text="month" TextAlignment="Left"
Padding="{DynamicResource DatePickerHostMonthPadding}" Padding="{DynamicResource DatePickerHostMonthPadding}"/>
FontFamily="{TemplateBinding FontFamily}"
FontWeight="{TemplateBinding FontWeight}"
FontSize="{TemplateBinding FontSize}"/>
<TextBlock Name="PART_YearTextBlock" Text="year" HorizontalAlignment="Center" <TextBlock Name="PART_YearTextBlock" Text="year" HorizontalAlignment="Center"
Padding="{DynamicResource DatePickerHostPadding}" Padding="{DynamicResource DatePickerHostPadding}"/>
FontFamily="{TemplateBinding FontFamily}"
FontWeight="{TemplateBinding FontWeight}"
FontSize="{TemplateBinding FontSize}"/>
<Rectangle x:Name="PART_FirstSpacer" <Rectangle x:Name="PART_FirstSpacer"
Fill="{DynamicResource DatePickerSpacerFill}" Fill="{DynamicResource DatePickerSpacerFill}"
HorizontalAlignment="Center" HorizontalAlignment="Center"
@ -156,8 +146,6 @@
<Setter Property="Width" Value="296" /> <Setter Property="Width" Value="296" />
<Setter Property="MinWidth" Value="296" /> <Setter Property="MinWidth" Value="296" />
<Setter Property="MaxHeight" Value="398" /> <Setter Property="MaxHeight" Value="398" />
<Setter Property="FontWeight" Value="Normal" />
<Setter Property="FontSize" Value="{DynamicResource ControlContentThemeFontSize}" />
<Setter Property="Background" Value="{DynamicResource DatePickerFlyoutPresenterBackground}" /> <Setter Property="Background" Value="{DynamicResource DatePickerFlyoutPresenterBackground}" />
<Setter Property="BorderBrush" Value="{DynamicResource DatePickerFlyoutPresenterBorderBrush}" /> <Setter Property="BorderBrush" Value="{DynamicResource DatePickerFlyoutPresenterBorderBrush}" />
<Setter Property="BorderThickness" Value="{DynamicResource DateTimeFlyoutBorderThickness}" /> <Setter Property="BorderThickness" Value="{DynamicResource DateTimeFlyoutBorderThickness}" />

8
src/Avalonia.Themes.Fluent/Controls/HeaderedContentControl.xaml

@ -19,10 +19,6 @@
ContentTemplate="{TemplateBinding HeaderTemplate}" ContentTemplate="{TemplateBinding HeaderTemplate}"
HorizontalContentAlignment="{TemplateBinding HorizontalContentAlignment}" HorizontalContentAlignment="{TemplateBinding HorizontalContentAlignment}"
VerticalContentAlignment="{TemplateBinding VerticalContentAlignment}" VerticalContentAlignment="{TemplateBinding VerticalContentAlignment}"
FontSize="{TemplateBinding FontSize}"
FontWeight="{TemplateBinding FontWeight}"
FontFamily="{TemplateBinding FontFamily}"
FontStyle="{TemplateBinding FontStyle}"
Grid.Row="0" /> Grid.Row="0" />
<ContentPresenter Name="PART_ContentPresenter" <ContentPresenter Name="PART_ContentPresenter"
ContentTemplate="{TemplateBinding ContentTemplate}" ContentTemplate="{TemplateBinding ContentTemplate}"
@ -30,10 +26,6 @@
RecognizesAccessKey="True" RecognizesAccessKey="True"
VerticalContentAlignment="{TemplateBinding VerticalContentAlignment}" VerticalContentAlignment="{TemplateBinding VerticalContentAlignment}"
HorizontalContentAlignment="{TemplateBinding HorizontalContentAlignment}" HorizontalContentAlignment="{TemplateBinding HorizontalContentAlignment}"
FontSize="{TemplateBinding FontSize}"
FontWeight="{TemplateBinding FontWeight}"
FontFamily="{TemplateBinding FontFamily}"
FontStyle="{TemplateBinding FontStyle}"
Grid.Row="1" /> Grid.Row="1" />
</Grid> </Grid>
</Border> </Border>

1
src/Avalonia.Themes.Fluent/Controls/ListBox.xaml

@ -21,7 +21,6 @@
<Setter Property="ScrollViewer.VerticalScrollBarVisibility" Value="Auto" /> <Setter Property="ScrollViewer.VerticalScrollBarVisibility" Value="Auto" />
<Setter Property="ScrollViewer.IsScrollChainingEnabled" Value="True" /> <Setter Property="ScrollViewer.IsScrollChainingEnabled" Value="True" />
<Setter Property="ScrollViewer.IsScrollInertiaEnabled" Value="True" /> <Setter Property="ScrollViewer.IsScrollInertiaEnabled" Value="True" />
<Setter Property="FontSize" Value="{DynamicResource ControlContentThemeFontSize}" />
<Setter Property="Template"> <Setter Property="Template">
<ControlTemplate> <ControlTemplate>
<Border Name="border" <Border Name="border"

5
src/Avalonia.Themes.Fluent/Controls/ListBoxItem.xaml

@ -14,12 +14,9 @@
</Border> </Border>
</Design.PreviewWith> </Design.PreviewWith>
<Thickness x:Key="ListBoxItemPadding">12,9,12,12</Thickness> <Thickness x:Key="ListBoxItemPadding">12,9,12,12</Thickness>
<FontWeight x:Key="ListBoxItemFontWeight">Normal</FontWeight>
<ControlTheme x:Key="{x:Type ListBoxItem}" TargetType="ListBoxItem"> <ControlTheme x:Key="{x:Type ListBoxItem}" TargetType="ListBoxItem">
<Setter Property="Background" Value="Transparent" /> <Setter Property="Background" Value="Transparent" />
<Setter Property="Padding" Value="{DynamicResource ListBoxItemPadding}" /> <Setter Property="Padding" Value="{DynamicResource ListBoxItemPadding}" />
<Setter Property="FontWeight" Value="{DynamicResource ListBoxItemFontWeight}" />
<Setter Property="FontSize" Value="{DynamicResource ControlContentThemeFontSize}" />
<Setter Property="Template"> <Setter Property="Template">
<ControlTemplate> <ControlTemplate>
<ContentPresenter Name="PART_ContentPresenter" <ContentPresenter Name="PART_ContentPresenter"
@ -29,8 +26,6 @@
CornerRadius="{TemplateBinding CornerRadius}" CornerRadius="{TemplateBinding CornerRadius}"
ContentTemplate="{TemplateBinding ContentTemplate}" ContentTemplate="{TemplateBinding ContentTemplate}"
Content="{TemplateBinding Content}" Content="{TemplateBinding Content}"
FontWeight="{TemplateBinding FontWeight}"
FontSize="{TemplateBinding FontSize}"
Padding="{TemplateBinding Padding}" Padding="{TemplateBinding Padding}"
VerticalContentAlignment="{TemplateBinding VerticalContentAlignment}" VerticalContentAlignment="{TemplateBinding VerticalContentAlignment}"
HorizontalContentAlignment="{TemplateBinding HorizontalContentAlignment}" /> HorizontalContentAlignment="{TemplateBinding HorizontalContentAlignment}" />

1
src/Avalonia.Themes.Fluent/Controls/Menu.xaml

@ -17,7 +17,6 @@
<Setter Property="Background" Value="{DynamicResource MenuFlyoutItemBackground}" /> <Setter Property="Background" Value="{DynamicResource MenuFlyoutItemBackground}" />
<Setter Property="Foreground" Value="{DynamicResource MenuFlyoutItemForeground}" /> <Setter Property="Foreground" Value="{DynamicResource MenuFlyoutItemForeground}" />
<!-- Narrow padding should be used for mouse input, when non-narrow one should be used for touch input in future. --> <!-- Narrow padding should be used for mouse input, when non-narrow one should be used for touch input in future. -->
<Setter Property="FontSize" Value="{DynamicResource ControlContentThemeFontSize}" />
<Setter Property="Padding" Value="{DynamicResource MenuBarItemPadding}" /> <Setter Property="Padding" Value="{DynamicResource MenuBarItemPadding}" />
<Setter Property="Template"> <Setter Property="Template">
<ControlTemplate> <ControlTemplate>

34
src/Avalonia.Themes.Fluent/Controls/MenuItem.xaml

@ -60,7 +60,6 @@
<Setter Property="Foreground" Value="{DynamicResource MenuFlyoutItemForeground}" /> <Setter Property="Foreground" Value="{DynamicResource MenuFlyoutItemForeground}" />
<!-- Narrow padding should be used for mouse input, when non-narrow one should be used for touch input in future. --> <!-- Narrow padding should be used for mouse input, when non-narrow one should be used for touch input in future. -->
<Setter Property="Padding" Value="{DynamicResource MenuFlyoutItemThemePaddingNarrow}" /> <Setter Property="Padding" Value="{DynamicResource MenuFlyoutItemThemePaddingNarrow}" />
<Setter Property="FontSize" Value="{DynamicResource ControlContentThemeFontSize}" />
<Setter Property="Template"> <Setter Property="Template">
<ControlTemplate> <ControlTemplate>
<Panel> <Panel>
@ -81,15 +80,11 @@
SharedSizeGroup="MenuItemChevron" /> SharedSizeGroup="MenuItemChevron" />
</Grid.ColumnDefinitions> </Grid.ColumnDefinitions>
<Viewbox Name="PART_IconPresenter" <ContentControl x:Name="PART_IconPresenter"
Margin="{DynamicResource MenuIconPresenterMargin}" Theme="{StaticResource FluentMenuItemIconTheme}"
StretchDirection="DownOnly" Content="{TemplateBinding Icon}"
HorizontalAlignment="Center" IsVisible="False"
VerticalAlignment="Center" Margin="{DynamicResource MenuIconPresenterMargin}" />
IsVisible="False"
Width="16" Height="16">
<ContentPresenter Content="{TemplateBinding Icon}"/>
</Viewbox>
<ContentPresenter Name="PART_HeaderPresenter" <ContentPresenter Name="PART_HeaderPresenter"
Content="{TemplateBinding Header}" Content="{TemplateBinding Header}"
@ -143,7 +138,7 @@
</ControlTemplate> </ControlTemplate>
</Setter> </Setter>
<Style Selector="^:icon /template/ Viewbox#PART_IconPresenter"> <Style Selector="^:icon /template/ ContentControl#PART_IconPresenter">
<Setter Property="IsVisible" Value="True" /> <Setter Property="IsVisible" Value="True" />
</Style> </Style>
<Style Selector="^:selected"> <Style Selector="^:selected">
@ -210,4 +205,21 @@
<Setter Property="Padding" Value="{DynamicResource HorizontalMenuFlyoutItemThemePaddingNarrow}" /> <Setter Property="Padding" Value="{DynamicResource HorizontalMenuFlyoutItemThemePaddingNarrow}" />
<Setter Property="Margin" Value="{DynamicResource HorizontalMenuFlyoutItemMargin}" /> <Setter Property="Margin" Value="{DynamicResource HorizontalMenuFlyoutItemMargin}" />
</ControlTheme> </ControlTheme>
<ControlTheme x:Key="FluentMenuItemIconTheme"
TargetType="ContentControl">
<Setter Property="Width"
Value="16" />
<Setter Property="Height"
Value="16" />
<Setter Property="Template">
<ControlTemplate>
<Viewbox
StretchDirection="DownOnly"
HorizontalAlignment="Center"
VerticalAlignment="Center">
<ContentPresenter x:Name="PART_ContentPresenter" Content="{TemplateBinding Content}" />
</Viewbox>
</ControlTemplate>
</Setter>
</ControlTheme>
</ResourceDictionary> </ResourceDictionary>

1
src/Avalonia.Themes.Fluent/Controls/NotificationCard.xaml

@ -9,7 +9,6 @@
<ControlTheme x:Key="{x:Type NotificationCard}" TargetType="NotificationCard"> <ControlTheme x:Key="{x:Type NotificationCard}" TargetType="NotificationCard">
<Setter Property="UseLayoutRounding" Value="True"/> <Setter Property="UseLayoutRounding" Value="True"/>
<Setter Property="Width" Value="350"/> <Setter Property="Width" Value="350"/>
<Setter Property="FontSize" Value="14"/>
<Setter Property="Foreground" Value="{DynamicResource NotificationCardForegroundBrush}"/> <Setter Property="Foreground" Value="{DynamicResource NotificationCardForegroundBrush}"/>
<Setter Property="RenderTransformOrigin" Value="50%,75%"/> <Setter Property="RenderTransformOrigin" Value="50%,75%"/>
<Setter Property="BorderThickness" Value="0" /> <Setter Property="BorderThickness" Value="0" />

2
src/Avalonia.Themes.Fluent/Controls/NumericUpDown.xaml

@ -26,7 +26,6 @@
<Setter Property="BorderBrush" Value="{DynamicResource TextControlBorderBrush}" /> <Setter Property="BorderBrush" Value="{DynamicResource TextControlBorderBrush}" />
<Setter Property="MinHeight" Value="{DynamicResource TextControlThemeMinHeight}" /> <Setter Property="MinHeight" Value="{DynamicResource TextControlThemeMinHeight}" />
<Setter Property="MinWidth" Value="{DynamicResource TextControlThemeMinWidth}" /> <Setter Property="MinWidth" Value="{DynamicResource TextControlThemeMinWidth}" />
<Setter Property="FontSize" Value="{DynamicResource ControlContentThemeFontSize}" />
<Setter Property="Padding" Value="{DynamicResource TextControlThemePadding}" /> <Setter Property="Padding" Value="{DynamicResource TextControlThemePadding}" />
<Setter Property="CornerRadius" Value="{DynamicResource ControlCornerRadius}" /> <Setter Property="CornerRadius" Value="{DynamicResource ControlCornerRadius}" />
<Setter Property="Template"> <Setter Property="Template">
@ -51,7 +50,6 @@
Padding="{TemplateBinding Padding}" Padding="{TemplateBinding Padding}"
MinWidth="0" MinWidth="0"
Foreground="{TemplateBinding Foreground}" Foreground="{TemplateBinding Foreground}"
FontSize="{TemplateBinding FontSize}"
Watermark="{TemplateBinding Watermark}" Watermark="{TemplateBinding Watermark}"
IsReadOnly="{TemplateBinding IsReadOnly}" IsReadOnly="{TemplateBinding IsReadOnly}"
VerticalContentAlignment="{TemplateBinding VerticalContentAlignment}" VerticalContentAlignment="{TemplateBinding VerticalContentAlignment}"

2
src/Avalonia.Themes.Fluent/Controls/OverlayPopupHost.xaml

@ -5,7 +5,7 @@
<Setter Property="Foreground" Value="{DynamicResource SystemControlForegroundBaseHighBrush}"/> <Setter Property="Foreground" Value="{DynamicResource SystemControlForegroundBaseHighBrush}"/>
<Setter Property="FontSize" Value="{DynamicResource ControlContentThemeFontSize}"/> <Setter Property="FontSize" Value="{DynamicResource ControlContentThemeFontSize}"/>
<Setter Property="FontFamily" Value="{DynamicResource ContentControlThemeFontFamily}" /> <Setter Property="FontFamily" Value="{DynamicResource ContentControlThemeFontFamily}" />
<Setter Property="FontWeight" Value="400" /> <Setter Property="FontWeight" Value="Normal" />
<Setter Property="FontStyle" Value="Normal" /> <Setter Property="FontStyle" Value="Normal" />
<Setter Property="Template"> <Setter Property="Template">
<ControlTemplate> <ControlTemplate>

2
src/Avalonia.Themes.Fluent/Controls/PopupRoot.xaml

@ -7,7 +7,7 @@
<Setter Property="Foreground" Value="{DynamicResource SystemControlForegroundBaseHighBrush}" /> <Setter Property="Foreground" Value="{DynamicResource SystemControlForegroundBaseHighBrush}" />
<Setter Property="FontSize" Value="{DynamicResource ControlContentThemeFontSize}" /> <Setter Property="FontSize" Value="{DynamicResource ControlContentThemeFontSize}" />
<Setter Property="FontFamily" Value="{DynamicResource ContentControlThemeFontFamily}" /> <Setter Property="FontFamily" Value="{DynamicResource ContentControlThemeFontFamily}" />
<Setter Property="FontWeight" Value="400" /> <Setter Property="FontWeight" Value="Normal" />
<Setter Property="FontStyle" Value="Normal" /> <Setter Property="FontStyle" Value="Normal" />
<Setter Property="Template"> <Setter Property="Template">
<ControlTemplate> <ControlTemplate>

1
src/Avalonia.Themes.Fluent/Controls/RadioButton.xaml

@ -21,7 +21,6 @@
<Setter Property="VerticalAlignment" Value="Center" /> <Setter Property="VerticalAlignment" Value="Center" />
<Setter Property="HorizontalContentAlignment" Value="Left" /> <Setter Property="HorizontalContentAlignment" Value="Left" />
<Setter Property="VerticalContentAlignment" Value="Center" /> <Setter Property="VerticalContentAlignment" Value="Center" />
<Setter Property="FontSize" Value="{DynamicResource ControlContentThemeFontSize}" />
<Setter Property="Template"> <Setter Property="Template">
<ControlTemplate TargetType="RadioButton"> <ControlTemplate TargetType="RadioButton">
<Border <Border

2
src/Avalonia.Themes.Fluent/Controls/RepeatButton.xaml

@ -20,8 +20,6 @@
<Setter Property="Padding" Value="{StaticResource ButtonPadding}" /> <Setter Property="Padding" Value="{StaticResource ButtonPadding}" />
<Setter Property="HorizontalAlignment" Value="Left" /> <Setter Property="HorizontalAlignment" Value="Left" />
<Setter Property="VerticalAlignment" Value="Center" /> <Setter Property="VerticalAlignment" Value="Center" />
<Setter Property="FontWeight" Value="Normal" />
<Setter Property="FontSize" Value="{DynamicResource ControlContentThemeFontSize}" />
<Setter Property="RenderTransform" Value="none" /> <Setter Property="RenderTransform" Value="none" />
<Setter Property="Template"> <Setter Property="Template">
<ControlTemplate> <ControlTemplate>

1
src/Avalonia.Themes.Fluent/Controls/Slider.xaml

@ -91,7 +91,6 @@
<Setter Property="Background" Value="{DynamicResource SliderTrackFill}" /> <Setter Property="Background" Value="{DynamicResource SliderTrackFill}" />
<Setter Property="BorderThickness" Value="{DynamicResource SliderBorderThemeThickness}" /> <Setter Property="BorderThickness" Value="{DynamicResource SliderBorderThemeThickness}" />
<Setter Property="Foreground" Value="{DynamicResource SliderTrackValueFill}" /> <Setter Property="Foreground" Value="{DynamicResource SliderTrackValueFill}" />
<Setter Property="FontSize" Value="{DynamicResource ControlContentThemeFontSize}" />
<Style Selector="^:horizontal"> <Style Selector="^:horizontal">
<Setter Property="Template"> <Setter Property="Template">
<ControlTemplate> <ControlTemplate>

Some files were not shown because too many files changed in this diff

Loading…
Cancel
Save