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/wwwroot
src/Browser/Avalonia.Browser/wwwroot
api/diff

2
.nuke/build.schema.json

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

13
Avalonia.sln

@ -236,6 +236,19 @@ EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{176582E8-46AF-416A-85C1-13A5C6744497}"
ProjectSection(SolutionItems) = preProject
.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
EndProject
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];
}
-(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
{
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()
{
START_COM_CALL;

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

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

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

@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.IO.Compression;
using System.Linq;
@ -10,9 +11,95 @@ using System.Threading.Tasks;
using Nuke.Common.Tooling;
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[]
{
@ -25,12 +112,6 @@ public static class ApiDiffValidation
Tool apiCompatTool, string packagePath, string baselineVersion,
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))
{
Directory.CreateDirectory(suppressionFilesFolder!);
@ -58,13 +139,7 @@ public static class ApiDiffValidation
// So, always use Unix '/'
foreach (var baselineDll in baselineDlls)
{
var baselineDllPath = $"baseline/{baselineDll.target}/{baselineDll.entry.Name}";
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 baselineDllPath = await ExtractDll("baseline", baselineDll, tempFolder);
var targetTfm = baselineDll.target;
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}");
}
var targetDllPath = $"target/{targetDll.target}/{targetDll.entry.Name}";
var targetDllRealPath = Path.Combine(tempFolder, targetDllPath);
Directory.CreateDirectory(Path.GetDirectoryName(targetDllRealPath)!);
await using (var targetDllFile = File.Create(targetDllRealPath))
{
await targetDll.entry.Open().CopyToAsync(targetDllFile);
}
var targetDllPath = await ExtractDll("target", targetDll, tempFolder);
left.Add(baselineDllPath);
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
.Where(e => Path.GetExtension(e.FullName) == ".dll"
@ -130,12 +201,18 @@ public static class ApiDiffValidation
)
.GroupBy(e => (e.target, e.entry.Name))
.Select(g => g.MaxBy(e => e.isRef))
.Select(e => (e.target, e.entry))
.Select(e => new DllEntry(e.target, e.entry))
.ToArray();
}
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:
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)
{
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 Serilog.Log;
using MicroCom.CodeGenerator;
using Nuke.Common.IO;
/*
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")]
Tool ApiCompatTool;
[PackageExecutable("Microsoft.DotNet.GenAPI.Tool", "Microsoft.DotNet.GenAPI.Tool.dll", Framework = "net8.0")]
Tool ApiGenTool;
protected override void OnBuildInitialized()
{
@ -283,11 +287,21 @@ partial class Build : NukeBuild
.Executes(async () =>
{
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,
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 => _ => _
.DependsOn(RunCoreLibsTests)
.DependsOn(RunRenderTests)

3
nukebuild/_build.csproj

@ -7,6 +7,8 @@
<NoWarn>$(NoWarn);CS0649;CS0169;SYSLIB0011</NoWarn>
<NukeTelemetryVersion>1</NukeTelemetryVersion>
<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>
<Import Project="..\build\JetBrains.dotMemoryUnit.props" />
@ -24,6 +26,7 @@
</PackageReference>
<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>

22
packages/Avalonia/AvaloniaBuildTasks.targets

@ -131,6 +131,7 @@
<AvaloniaXamlIlVerifyIl Condition="'$(AvaloniaXamlIlVerifyIl)' == ''">false</AvaloniaXamlIlVerifyIl>
<AvaloniaXamlIlDebuggerLaunch Condition="'$(AvaloniaXamlIlDebuggerLaunch)' == ''">false</AvaloniaXamlIlDebuggerLaunch>
<AvaloniaXamlVerboseExceptions Condition="'$(AvaloniaXamlVerboseExceptions)' == ''">false</AvaloniaXamlVerboseExceptions>
<_AvaloniaHasCompiledXaml>true</_AvaloniaHasCompiledXaml>
</PropertyGroup>
<WriteLinesToFile
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;"/>
</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>

14
readme.md

@ -8,7 +8,7 @@
## 📖 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.
@ -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)]
<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>
## Commercial Support

7
samples/ControlCatalog.Android/MainActivity.cs

@ -2,13 +2,16 @@
using Android.Content.PM;
using Avalonia;
using Avalonia.Android;
using static Android.Content.Intent;
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>
{
protected override Avalonia.AppBuilder CustomizeAppBuilder(Avalonia.AppBuilder builder)
protected override AppBuilder CustomizeAppBuilder(AppBuilder builder)
{
return base.CustomizeAppBuilder(builder)
.AfterSetup(_ =>

8
samples/ControlCatalog/App.xaml.cs

@ -51,6 +51,14 @@ namespace ControlCatalog
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();
}

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

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

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

@ -7,15 +7,29 @@ using Android.OS;
using Android.Runtime;
using Android.Views;
using AndroidX.AppCompat.App;
using Avalonia.Controls.ApplicationLifetimes;
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, string[], Permission[]> RequestPermissionsResult { get; set; }
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()
{
@ -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)
{
base.OnActivityResult(requestCode, resultCode, data);

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

@ -1,4 +1,5 @@
using System;
using Avalonia.Controls.ApplicationLifetimes;
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.Collections.Generic;
using System.Runtime.Versioning;
using System.Threading;
using Android.App;
using Android.Content;
using Android.Graphics;
using Android.Graphics.Drawables;
using Android.OS;
using Android.Runtime;
using Android.Text;
using Android.Views;
@ -27,10 +25,8 @@ using Avalonia.OpenGL.Egl;
using Avalonia.OpenGL.Surfaces;
using Avalonia.Platform;
using Avalonia.Platform.Storage;
using Avalonia.Rendering;
using Avalonia.Rendering.Composition;
using Java.Lang;
using static System.Net.Mime.MediaTypeNames;
using ClipboardManager = Android.Content.ClipboardManager;
namespace Avalonia.Android.Platform.SkiaPlatform
@ -76,6 +72,8 @@ namespace Avalonia.Android.Platform.SkiaPlatform
_transparencyLevel = WindowTransparencyLevel.None;
_systemNavigationManager = new AndroidSystemNavigationManagerImpl(avaloniaView.Context as IActivityNavigationService);
Surfaces = new object[] { _gl, _framebuffer, Handle };
}
public virtual Point GetAvaloniaPointFromEvent(MotionEvent e, int pointerIndex) =>
@ -107,7 +105,7 @@ namespace Avalonia.Android.Platform.SkiaPlatform
public IPlatformHandle Handle => _view;
public IEnumerable<object> Surfaces => new object[] { _gl, _framebuffer, Handle };
public IEnumerable<object> Surfaces { get; }
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 keySymbol = GetKeySymbol(e.UnicodeChar, physicalKey);
var keyDeviceType = GetKeyDeviceType(e);
var rawKeyEvent = new RawKeyEventArgs(
AndroidKeyboardDevice.Instance!,
@ -62,6 +63,7 @@ namespace Avalonia.Android.Platform.Specific.Helpers
AndroidKeyboardDevice.ConvertKey(e.KeyCode),
GetModifierKeys(e),
physicalKey,
keyDeviceType,
keySymbol);
_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()
{
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;
namespace Avalonia.Android
{
internal class SingleViewLifetime : ISingleViewApplicationLifetime
internal class SingleViewLifetime : ISingleViewApplicationLifetime, IActivatableApplicationLifetime
{
private readonly Activity _activity;
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
{
get => _view; internal set
@ -22,5 +36,10 @@ namespace Avalonia.Android
}
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>
internal class BoolAnimator : Animator<bool>
{
/// <inheritdocs/>
/// <inheritdoc/>
public override bool Interpolate(double progress, bool oldValue, bool newValue)
{
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;
/// <inheritdocs/>
/// <inheritdoc/>
public override byte Interpolate(double progress, byte oldValue, byte newValue)
{
var normOV = oldValue / maxVal;

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

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

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

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

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

@ -5,7 +5,7 @@
/// </summary>
internal class FloatAnimator : Animator<float>
{
/// <inheritdocs/>
/// <inheritdoc/>
public override float Interpolate(double progress, float oldValue, float newValue)
{
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;
/// <inheritdocs/>
/// <inheritdoc/>
public override Int16 Interpolate(double progress, Int16 oldValue, Int16 newValue)
{
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;
/// <inheritdocs/>
/// <inheritdoc/>
public override Int32 Interpolate(double progress, Int32 oldValue, Int32 newValue)
{
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;
/// <inheritdocs/>
/// <inheritdoc/>
public override Int64 Interpolate(double progress, Int64 oldValue, Int64 newValue)
{
var normOV = oldValue / maxVal;

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

@ -92,7 +92,7 @@ namespace Avalonia.Animation.Animators
return null;
}
/// <inheritdocs/>
/// <inheritdoc/>
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;
/// <inheritdocs/>
/// <inheritdoc/>
public override UInt16 Interpolate(double progress, UInt16 oldValue, UInt16 newValue)
{
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;
/// <inheritdocs/>
/// <inheritdoc/>
public override UInt32 Interpolate(double progress, UInt32 oldValue, UInt32 newValue)
{
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;
/// <inheritdocs/>
/// <inheritdoc/>
public override UInt64 Interpolate(double progress, UInt64 oldValue, UInt64 newValue)
{
var normOV = oldValue / maxVal;

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

@ -77,7 +77,7 @@ namespace Avalonia.Animation
set { SetAndRaise(EasingProperty, ref _easing, value); }
}
/// <inheritdocs/>
/// <inheritdoc/>
[DisallowNull]
public AvaloniaProperty? Property
{
@ -91,7 +91,7 @@ namespace Avalonia.Animation
set => Property = value;
}
/// <inheritdocs/>
/// <inheritdoc/>
IDisposable ITransition.Apply(Animatable control, IClock clock, object? oldValue, object? 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 Point s_lastPressPoint;
private static IPointer? s_lastPointer;
private static IPointer? s_lastHeldPointer;
public static readonly RoutedEvent<PinchEventArgs> PinchEvent =
RoutedEvent.Register<PinchEventArgs>(
@ -225,17 +225,17 @@ namespace Avalonia.Input
var e = (PointerPressedEventArgs)ev;
var visual = (Visual)ev.Source;
if(s_lastPointer != null)
if(s_lastHeldPointer != null)
{
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?.Dispose();
s_holdCancellationToken = null;
s_lastPointer = null;
s_lastHeldPointer = null;
}
s_isHolding = false;
@ -244,7 +244,7 @@ namespace Avalonia.Input
{
s_isDoubleTapped = false;
s_lastPress.SetTarget(ev.Source);
s_lastPointer = e.Pointer;
s_lastHeldPointer = e.Pointer;
s_lastPressPoint = e.GetPosition((Visual)ev.Source);
s_holdCancellationToken = new CancellationTokenSource();
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)))
{
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);
}
@ -281,7 +281,7 @@ namespace Avalonia.Input
{
var e = (PointerReleasedEventArgs)ev;
if (s_lastPress.TryGetTarget(out var target) &&
if (s_lastPress.TryGetTarget(out var target) &&
target == e.Source &&
e.InitialPressMouseButton is MouseButton.Left or MouseButton.Right &&
e.Source is Interactive i)
@ -294,10 +294,10 @@ namespace Avalonia.Input
if (tapRect.ContainsExclusive(point.Position))
{
if(s_isHolding)
if (s_isHolding)
{
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)
{
@ -310,12 +310,12 @@ namespace Avalonia.Input
i.RaiseEvent(new TappedEventArgs(TappedEvent, e));
}
}
s_lastHeldPointer = null;
}
s_holdCancellationToken?.Cancel();
s_holdCancellationToken?.Dispose();
s_holdCancellationToken = null;
s_lastPointer = null;
}
}
@ -326,7 +326,7 @@ namespace Avalonia.Input
var e = (PointerEventArgs)ev;
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 settings = ((IInputRoot?)i.GetVisualRoot())?.PlatformSettings;
@ -341,7 +341,8 @@ namespace Avalonia.Input
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
/// </summary>
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="PhysicalKey"/>
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(),
PhysicalKey = keyInput.PhysicalKey,
KeySymbol = keyInput.KeySymbol,
KeyDeviceType = keyInput.KeyDeviceType,
Source = element
};

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

@ -20,7 +20,7 @@ namespace Avalonia.Input.Raw
RawKeyEventType type,
Key key,
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;
Type = type;
@ -36,6 +36,18 @@ namespace Avalonia.Input.Raw
RawInputModifiers modifiers,
PhysicalKey physicalKey,
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)
{
Key = key;
@ -43,6 +55,7 @@ namespace Avalonia.Input.Raw
Type = type;
PhysicalKey = physicalKey;
KeySymbol = keySymbol;
KeyDeviceType = keyDeviceType;
}
public Key Key { get; set; }
@ -53,6 +66,8 @@ namespace Avalonia.Input.Raw
public PhysicalKey PhysicalKey { get; set; }
public KeyDeviceType KeyDeviceType { get; set; }
public string? KeySymbol { get; set; }
}
}

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

@ -1,4 +1,7 @@
using System;
using System.Collections.Generic;
using System.Collections.Specialized;
using Avalonia.Collections;
using Avalonia.Media.Immutable;
using Avalonia.Rendering.Composition;
using Avalonia.Rendering.Composition.Drawing;
@ -56,8 +59,7 @@ namespace Avalonia.Media
/// Initializes a new instance of the <see cref="Pen"/> class.
/// </summary>
public Pen()
{
}
{ }
/// <summary>
/// Initializes a new instance of the <see cref="Pen"/> class.
@ -75,8 +77,7 @@ namespace Avalonia.Media
PenLineCap lineCap = PenLineCap.Flat,
PenLineJoin lineJoin = PenLineJoin.Miter,
double miterLimit = 10.0) : this(new SolidColorBrush(color), thickness, dashStyle, lineCap, lineJoin, miterLimit)
{
}
{ }
/// <summary>
/// Initializes a new instance of the <see cref="Pen"/> class.
@ -178,6 +179,69 @@ namespace Avalonia.Media
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()
{
_resource.RegisterForInvalidationOnAllCompositors(this);
@ -186,21 +250,21 @@ namespace Avalonia.Media
protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs change)
{
RegisterForSerialization();
if (change.Property == BrushProperty)
if (change.Property == BrushProperty)
_resource.ProcessPropertyChangeNotification(change);
if(change.Property == DashStyleProperty)
if (change.Property == DashStyleProperty)
UpdateDashStyleSubscription();
base.OnPropertyChanged(change);
}
void UpdateDashStyleSubscription()
{
var newValue = _resource.IsAttached ? DashStyle as DashStyle : null;
if(ReferenceEquals(_subscribedToDashes, newValue))
if (ReferenceEquals(_subscribedToDashes, newValue))
return;
if (_subscribedToDashes != null && _weakSubscriber != null)
@ -221,9 +285,9 @@ namespace Avalonia.Media
_subscribedToDashes = newValue;
}
}
private CompositorResourceHolder<ServerCompositionSimplePen> _resource;
IPen ICompositionRenderResource<IPen>.GetForCompositor(Compositor c) => _resource.GetForCompositor(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.
/// </summary>
/// <param name="fillRule"></param>
public void SetFillRule(FillRule fillRule)
{
_impl.SetFillRule(fillRule);
}
/// <summary>
/// Draws an arc to the specified point.
/// </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>
/// <inheritdoc/>
public void ArcTo(Point point, Size size, double rotationAngle, bool isLargeArc, SweepDirection sweepDirection)
{
_impl.ArcTo(point, size, rotationAngle, isLargeArc, sweepDirection);
@ -54,8 +45,8 @@ namespace Avalonia.Media
/// <summary>
/// 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.
/// </summary>
/// Significantly more precise when drawing elliptic arcs with extreme width:height ratios.
/// </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>
@ -68,54 +59,37 @@ namespace Avalonia.Media
PreciseEllipticArcHelper.ArcTo(this, _currentPoint, point, size, rotationAngle, isLargeArc, sweepDirection);
}
/// <summary>
/// Begins a new figure.
/// </summary>
/// <param name="startPoint">The starting point for the figure.</param>
/// <param name="isFilled">Whether the figure is filled.</param>
/// <inheritdoc/>
public void BeginFigure(Point startPoint, bool isFilled)
{
_impl.BeginFigure(startPoint, isFilled);
_currentPoint = startPoint;
}
/// <summary>
/// Draws a Bezier curve to the specified point.
/// </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)
/// <inheritdoc/>
public void CubicBezierTo(Point controlPoint1, Point controlPoint2, Point endPoint)
{
_impl.CubicBezierTo(point1, point2, point3);
_currentPoint = point3;
_impl.CubicBezierTo(controlPoint1, controlPoint2, endPoint);
_currentPoint = endPoint;
}
/// <summary>
/// Draws a quadratic Bezier curve to the specified point
/// </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)
/// <inheritdoc/>
public void QuadraticBezierTo(Point controlPoint , Point endPoint)
{
_impl.QuadraticBezierTo(control, endPoint);
_impl.QuadraticBezierTo(controlPoint , endPoint);
_currentPoint = endPoint;
}
/// <summary>
/// Draws a line to the specified point.
/// </summary>
/// <param name="point">The destination point.</param>
public void LineTo(Point point)
/// <inheritdoc/>
public void LineTo(Point endPoint)
{
_impl.LineTo(point);
_currentPoint = point;
_impl.LineTo(endPoint);
_currentPoint = endPoint;
}
/// <summary>
/// Ends the figure started by <see cref="BeginFigure(Point, bool)"/>.
/// </summary>
/// <param name="isClosed">Whether the figure is closed.</param>
/// <inheritdoc/>
public void EndFigure(bool isClosed)
{
_impl.EndFigure(isClosed);

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

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

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

@ -20,6 +20,7 @@ namespace Avalonia.Visuals.Platform
_pathGeometry = null;
}
/// <inheritdoc/>
public void ArcTo(Point point, Size size, double rotationAngle, bool isLargeArc, SweepDirection sweepDirection)
{
var arcSegment = new ArcSegment
@ -34,6 +35,7 @@ namespace Avalonia.Visuals.Platform
CurrentFigureSegments().Add(arcSegment);
}
/// <inheritdoc/>
public void BeginFigure(Point startPoint, bool isFilled)
{
ThrowIfDisposed();
@ -44,30 +46,34 @@ namespace Avalonia.Visuals.Platform
_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);
}
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);
}
public void LineTo(Point point)
/// <inheritdoc/>
public void LineTo(Point endPoint)
{
var lineSegment = new LineSegment
{
Point = point
Point = endPoint
};
CurrentFigureSegments().Add(lineSegment);
}
/// <inheritdoc/>
public void EndFigure(bool isClosed)
{
if (_currentFigure != null)
@ -78,6 +84,7 @@ namespace Avalonia.Visuals.Platform
_currentFigure = null;
}
/// <inheritdoc/>
public void SetFillRule(FillRule fillRule)
{
ThrowIfDisposed();

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

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

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

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

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

@ -92,20 +92,24 @@ namespace Avalonia.Styling
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)
throw new InvalidOperationException("Styles can only be applied to AvaloniaObjects.");
StyleInstance instance;
if (_sharedInstance is not null)
if (_sharedInstance is not null && canShareInstance)
{
instance = _sharedInstance;
}
else
{
var canShareInstance = activator is null;
canShareInstance &= activator is null;
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="DataGridRowHoveredBackgroundColor" Color="{DynamicResource SystemListLowColor}" />
<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="DataGridCellFocusVisualSecondaryBrush" Color="{DynamicResource SystemAltMediumColor}" />
<SolidColorBrush x:Key="DataGridCellInvalidBrush" Color="{DynamicResource SystemErrorTextColor}" />
@ -34,6 +38,10 @@
<SolidColorBrush x:Key="DataGridRowGroupHeaderHoveredBackgroundBrush" Color="{DynamicResource SystemListLowColor}" />
<SolidColorBrush x:Key="DataGridRowHoveredBackgroundColor" Color="{DynamicResource SystemListLowColor}" />
<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="DataGridCellFocusVisualSecondaryBrush" Color="{DynamicResource SystemAltMediumColor}" />
<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>
<StaticResource x:Key="DataGridRowBackgroundBrush" ResourceKey="SystemControlTransparentBrush" />
<SolidColorBrush x:Key="DataGridRowSelectedBackgroundBrush" Color="{DynamicResource SystemAccentColor}" />
<StaticResource x:Key="DataGridRowSelectedBackgroundOpacity" ResourceKey="ListAccentLowOpacity" />
<SolidColorBrush x:Key="DataGridRowSelectedHoveredBackgroundBrush" Color="{DynamicResource SystemAccentColor}" />
<StaticResource x:Key="DataGridRowSelectedHoveredBackgroundOpacity" ResourceKey="ListAccentMediumOpacity" />
<SolidColorBrush x:Key="DataGridRowSelectedUnfocusedBackgroundBrush" Color="{DynamicResource SystemAccentColor}" />
<StaticResource x:Key="DataGridRowSelectedUnfocusedBackgroundOpacity" ResourceKey="ListAccentLowOpacity" />
<SolidColorBrush x:Key="DataGridRowSelectedHoveredUnfocusedBackgroundBrush" Color="{DynamicResource SystemAccentColor}" />
<StaticResource x:Key="DataGridRowSelectedHoveredUnfocusedBackgroundOpacity" ResourceKey="ListAccentMediumOpacity" />
<StaticResource x:Key="DataGridCellBackgroundBrush" 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(
this AppBuilder builder, string[] args, ShutdownMode shutdownMode = ShutdownMode.OnLastWindowClose)
{
var lifetime = new ClassicDesktopStyleApplicationLifetime()
var lifetime = AvaloniaLocator.Current.GetService<ClassicDesktopStyleApplicationLifetime>();
if (lifetime == null)
{
Args = args,
ShutdownMode = shutdownMode
};
lifetime = new ClassicDesktopStyleApplicationLifetime();
}
lifetime.Args = args;
lifetime.ShutdownMode = shutdownMode;
builder.SetupWithLifetime(lifetime);
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];
Open(control, PlacementTarget ?? control, false);
Open(control, PlacementTarget ?? control, Placement);
}
/// <summary>
@ -303,7 +303,7 @@ namespace Avalonia.Controls
remove => _popupHostChangedHandler -= value;
}
private void Open(Control control, Control placementTarget, bool requestedByPointer)
private void Open(Control control, Control placementTarget, PlacementMode placement)
{
if (IsOpen)
{
@ -330,9 +330,7 @@ namespace Avalonia.Controls
((ISetLogicalParent)_popup).SetParent(control);
}
_popup.Placement = !requestedByPointer && Placement == PlacementMode.Pointer
? PlacementMode.Bottom
: Placement;
_popup.Placement = placement;
//Position of the line below is really important.
//All styles are being applied only when control has logical parent.
@ -420,7 +418,10 @@ namespace Avalonia.Controls
&& !contextMenu.CancelOpening())
{
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;
}
}

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

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

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

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

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

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

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

@ -1,6 +1,5 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.ComponentModel;
using System.Diagnostics.CodeAnalysis;
@ -8,7 +7,6 @@ using System.Linq;
using Avalonia.Controls.Selection;
using Avalonia.Data;
using Avalonia.Input;
using Avalonia.Input.Platform;
using Avalonia.Interactivity;
using Avalonia.Metadata;
using Avalonia.Threading;
@ -187,14 +185,21 @@ namespace Avalonia.Controls.Primitives
/// </summary>
public int SelectedIndex
{
get =>
get
{
// 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
// 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.
_updateState?.SelectedIndex.HasValue == true ?
_updateState.SelectedIndex.Value :
Selection.SelectedIndex;
if (_updateState is not null)
{
return _updateState.SelectedIndex.HasValue ?
_updateState.SelectedIndex.Value :
TryGetExistingSelection()?.SelectedIndex ?? -1;
}
return Selection.SelectedIndex;
}
set
{
if (_updateState is object)
@ -213,11 +218,18 @@ namespace Avalonia.Controls.Primitives
/// </summary>
public object? SelectedItem
{
get =>
// See SelectedIndex setter for more information.
_updateState?.SelectedItem.HasValue == true ?
_updateState.SelectedItem.Value :
Selection.SelectedItem;
get
{
// See SelectedIndex getter for more information.
if (_updateState is not null)
{
return _updateState.SelectedItem.HasValue ?
_updateState.SelectedItem.Value :
TryGetExistingSelection()?.SelectedItem;
}
return Selection.SelectedItem;
}
set
{
if (_updateState is object)
@ -270,6 +282,7 @@ namespace Avalonia.Controls.Primitives
{
return _updateState.SelectedItems.Value;
}
else if (Selection is InternalSelectionModel ism)
{
var result = ism.WritableSelectedItems;
@ -456,10 +469,8 @@ namespace Avalonia.Controls.Primitives
protected override void OnAttachedToVisualTree(VisualTreeAttachmentEventArgs e)
{
base.OnAttachedToVisualTree(e);
if (Selection?.AnchorIndex is int index)
{
AutoScrollToSelectedItemIfNecessary(index);
}
AutoScrollToSelectedItemIfNecessary(GetAnchorIndex());
}
/// <inheritdoc />
@ -470,10 +481,8 @@ namespace Avalonia.Controls.Primitives
void ExecuteScrollWhenLayoutUpdated(object? sender, EventArgs e)
{
LayoutUpdated -= ExecuteScrollWhenLayoutUpdated;
if (Selection?.AnchorIndex is int index)
{
AutoScrollToSelectedItemIfNecessary(index);
}
AutoScrollToSelectedItemIfNecessary(GetAnchorIndex());
}
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)
{
// 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 (Selection?.AnchorIndex is int index)
{
AutoScrollToSelectedItemIfNecessary(index);
}
AutoScrollToSelectedItemIfNecessary(GetAnchorIndex());
}
else if (change.Property == SelectionModeProperty && _selection is object)
{
@ -671,7 +686,7 @@ namespace Avalonia.Controls.Primitives
return;
}
var value = change.GetNewValue<IBinding>();
var value = change.GetNewValue<IBinding?>();
if (value is null)
{
// Clearing SelectedValueBinding makes the SelectedValue the item itself
@ -921,11 +936,10 @@ namespace Avalonia.Controls.Primitives
if (e.PropertyName == nameof(ISelectionModel.AnchorIndex))
{
_hasScrolledToSelectedItem = false;
if (Selection?.AnchorIndex is int index)
{
KeyboardNavigation.SetTabOnceActiveElement(this, ContainerFromIndex(index));
AutoScrollToSelectedItemIfNecessary(index);
}
var anchorIndex = GetAnchorIndex();
KeyboardNavigation.SetTabOnceActiveElement(this, ContainerFromIndex(anchorIndex));
AutoScrollToSelectedItemIfNecessary(anchorIndex);
}
else if (e.PropertyName == nameof(ISelectionModel.SelectedIndex) && _oldSelectedIndex != SelectedIndex)
{
@ -1279,9 +1293,17 @@ namespace Avalonia.Controls.Primitives
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)
{
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)
{
@ -1338,39 +1360,12 @@ namespace Avalonia.Controls.Primitives
// - Both the old and new SelectionModels have the incorrect Source
private class UpdateState
{
private Optional<int> _selectedIndex;
private Optional<object?> _selectedItem;
private Optional<object?> _selectedValue;
public int UpdateCount { get; set; }
public Optional<ISelectionModel> Selection { get; set; }
public Optional<IList?> SelectedItems { get; set; }
public Optional<int> SelectedIndex
{
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;
}
public Optional<int> SelectedIndex { get; set; }
public Optional<object?> SelectedItem { get; set; }
public Optional<object?> SelectedValue { get; set; }
}
/// <summary>

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

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

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

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

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

@ -1,4 +1,5 @@
using System;
using System.Collections.Generic;
using Avalonia.Collections;
using Avalonia.Media;
using Avalonia.Media.Immutable;
@ -62,14 +63,7 @@ namespace Avalonia.Controls.Shapes
private Matrix _transform = Matrix.Identity;
private Geometry? _definingGeometry;
private Geometry? _renderedGeometry;
static Shape()
{
AffectsMeasure<Shape>(StretchProperty, StrokeThicknessProperty);
AffectsRender<Shape>(FillProperty, StrokeProperty, StrokeDashArrayProperty, StrokeDashOffsetProperty,
StrokeThicknessProperty, StrokeLineCapProperty, StrokeJoinProperty);
}
private IPen? _strokePen;
/// <summary>
/// Gets a value that represents the <see cref="Geometry"/> of the shape.
@ -199,30 +193,7 @@ namespace Avalonia.Controls.Shapes
if (geometry != null)
{
var stroke = Stroke;
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);
context.DrawGeometry(Fill, _strokePen, geometry);
}
}
@ -266,6 +237,34 @@ namespace Avalonia.Controls.Shapes
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)
{
if (DefiningGeometry is null)

28
src/Avalonia.Controls/TextBox.cs

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

1
src/Avalonia.Controls/TopLevel.cs

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

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

@ -17,6 +17,7 @@ namespace Avalonia.Controls.Utils
private Thickness _borderThickness;
private CornerRadius _cornerRadius;
private bool _initialized;
private IPen? _cachedPen;
void Update(Size finalSize, Thickness borderThickness, CornerRadius cornerRadius)
@ -87,22 +88,17 @@ namespace Avalonia.Controls.Utils
public void Render(DrawingContext context,
Size finalSize, Thickness borderThickness, CornerRadius cornerRadius,
IBrush? background, IBrush? borderBrush, BoxShadows boxShadows, double borderDashOffset = 0,
PenLineCap borderLineCap = PenLineCap.Flat, PenLineJoin borderLineJoin = PenLineJoin.Miter,
AvaloniaList<double>? borderDashArray = null)
IBrush? background, IBrush? borderBrush, BoxShadows boxShadows)
{
if (_size != finalSize
|| _borderThickness != borderThickness
|| _cornerRadius != cornerRadius
|| !_initialized)
Update(finalSize, borderThickness, cornerRadius);
RenderCore(context, background, borderBrush, boxShadows, borderDashOffset, borderLineCap, borderLineJoin,
borderDashArray);
RenderCore(context, background, borderBrush, boxShadows);
}
void RenderCore(DrawingContext context, IBrush? background, IBrush? borderBrush, BoxShadows boxShadows,
double borderDashOffset, PenLineCap borderLineCap, PenLineJoin borderLineJoin,
AvaloniaList<double>? borderDashArray)
void RenderCore(DrawingContext context, IBrush? background, IBrush? borderBrush, BoxShadows boxShadows)
{
if (_useComplexRendering)
{
@ -121,26 +117,8 @@ namespace Avalonia.Controls.Utils
else
{
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);
if (!MathUtilities.IsZero(borderThickness))
@ -148,7 +126,7 @@ namespace Avalonia.Controls.Utils
var rrect = new RoundedRect(rect, _cornerRadius.TopLeft, _cornerRadius.TopRight,
_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 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;
RebuildPreFlight();
}
if (obj is ClientRenderInfoMessage renderInfo)
if (arg is ClientRenderInfoMessage renderInfo)
{
s_renderInfoMessage = renderInfo;
RebuildPreFlight();
}
if (obj is ClientViewportAllocatedMessage viewport)
if (arg is ClientViewportAllocatedMessage viewport)
{
s_viewportAllocatedMessage = viewport;
RebuildPreFlight();
}
if (obj is UpdateXamlMessage xaml)
if (arg is UpdateXamlMessage xaml)
{
if (s_currentWindow is not null)
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"/>
/// </summary>
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;
ShowImplementedInterfaces = options.ShowImplementedInterfaces;
FocusHighlighter = options.FocusHighlighterBrush;
SelectedTab = (int)options.LaunchView;
}
public bool ShowImplementedInterfaces

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

@ -13,7 +13,7 @@
<ItemGroup>
<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>

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

@ -179,12 +179,23 @@ namespace Avalonia.FreeDesktop.DBusIme
_disposables.Add(d);
}
public void Dispose()
public async void Dispose()
{
foreach(var d in _disposables)
d.Dispose();
_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;
}

32
src/Avalonia.Native/AvaloniaNativeApplicationPlatform.cs

@ -13,6 +13,38 @@ namespace Avalonia.Native
void IAvnApplicationEvents.FilesOpened(IAvnStringArray urls)
{
((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()

12
src/Avalonia.Native/AvaloniaNativeMenuExporter.cs

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

4
src/Avalonia.Native/AvaloniaNativePlatformExtensions.cs

@ -1,6 +1,7 @@
using System;
using System.Collections.Generic;
using Avalonia.Controls;
using Avalonia.Controls.ApplicationLifetimes;
using Avalonia.Native;
namespace Avalonia
@ -24,6 +25,9 @@ namespace Avalonia
});
});
AvaloniaLocator.CurrentMutable.Bind<ClassicDesktopStyleApplicationLifetime>()
.ToConstant(new MacOSClassicDesktopStyleApplicationLifetime());
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;
}
public void ShowApp()
{
_commands.UnhideApp();
}
public void HideApp()
{
_commands.HideApp();
@ -28,7 +33,6 @@ namespace Avalonia.Native
_commands.HideOthers();
}
public static readonly AttachedProperty<bool> IsServicesSubmenuProperty =
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.Raw;
using Avalonia.Native.Interop;
using Avalonia.OpenGL;
using Avalonia.Platform;
using Avalonia.Platform.Storage;
using Avalonia.Rendering;
using Avalonia.Rendering.Composition;
using Avalonia.Threading;
@ -58,7 +56,6 @@ namespace Avalonia.Native
private readonly IKeyboardDevice _keyboard;
private readonly ICursorFactory _cursorFactory;
private Size _savedLogicalSize;
private Size _lastRenderedLogicalSize;
private double _savedScaling;
private NativeControlHostImpl _nativeControlHost;
private IStorageProvider _storageProvider;
@ -172,7 +169,6 @@ namespace Avalonia.Native
if (_parent._native != null && _target != null)
{
cb(_parent._native);
_parent._lastRenderedLogicalSize = _parent._savedLogicalSize;
}
}
}, (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);
bool TryShutdown();
void OnReopen ();
void OnHide ();
void OnUnhide ();
}
[uuid(b4284791-055b-4313-8c2e-50f0a8c72ce9)]
interface IAvnApplicationCommands : IUnknown
{
HRESULT UnhideApp();
HRESULT HideApp();
HRESULT ShowAll();
HRESULT HideOthers();

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

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

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

@ -385,6 +385,9 @@
<StaticResource x:Key="CalendarViewNavigationButtonBorderBrushPointerOver"
ResourceKey="SystemControlHighlightTransparentBrush" />
<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 -->
<!-- Expander:Header -->
@ -1180,6 +1183,9 @@
<StaticResource x:Key="CalendarViewNavigationButtonBorderBrushPointerOver"
ResourceKey="SystemControlHighlightTransparentBrush" />
<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 -->
<!-- Expander:Header -->

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

@ -28,7 +28,6 @@
<Setter Property="BorderBrush" Value="{DynamicResource TextControlBorderBrush}" />
<Setter Property="BorderThickness" Value="{DynamicResource TextControlBorderThemeThickness}" />
<Setter Property="CornerRadius" Value="{DynamicResource ControlCornerRadius}" />
<Setter Property="FontSize" Value="{DynamicResource ControlContentThemeFontSize}" />
<Setter Property="Padding" Value="{DynamicResource TextControlThemePadding}" />
<Setter Property="MaxDropDownHeight" Value="{DynamicResource AutoCompleteListMaxHeight}" />
<Setter Property="Template">
@ -43,9 +42,6 @@
BorderThickness="{TemplateBinding BorderThickness}"
CornerRadius="{TemplateBinding CornerRadius}"
CaretIndex="{TemplateBinding CaretIndex, Mode=TwoWay}"
FontSize="{TemplateBinding FontSize}"
FontFamily="{TemplateBinding FontFamily}"
FontWeight="{TemplateBinding FontWeight}"
Padding="{TemplateBinding Padding}"
Margin="0"
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="MinHeight" Value="{DynamicResource TextControlThemeMinHeight}" />
<Setter Property="MinWidth" Value="{DynamicResource TextControlThemeMinWidth}" />
<Setter Property="FontSize" Value="{DynamicResource ControlContentThemeFontSize}" />
<Setter Property="HorizontalContentAlignment" Value="Stretch" />
<Setter Property="VerticalContentAlignment" Value="Center" />
<Setter Property="Focusable" Value="True"/>
@ -104,7 +103,6 @@
VerticalAlignment="Stretch"
VerticalContentAlignment="Center"
Foreground="{TemplateBinding Foreground}"
FontSize="{TemplateBinding FontSize}"
MinWidth="34">
<PathIcon Width="16"
Height="8"
@ -119,7 +117,6 @@
VerticalAlignment="Stretch"
VerticalContentAlignment="Center"
Foreground="{TemplateBinding Foreground}"
FontSize="{TemplateBinding FontSize}"
MinWidth="34">
<PathIcon Width="16"
Height="8"

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

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

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

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

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

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

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

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

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

@ -18,7 +18,6 @@
<Setter Property="VerticalAlignment" Value="Center" />
<Setter Property="HorizontalContentAlignment" Value="Left" />
<Setter Property="VerticalContentAlignment" Value="Center" />
<Setter Property="FontSize" Value="{DynamicResource ControlContentThemeFontSize}" />
<Setter Property="CornerRadius" Value="{DynamicResource ControlCornerRadius}" />
<Setter Property="MinHeight" Value="32" />
<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="HorizontalAlignment" Value="Left" />
<Setter Property="VerticalAlignment" Value="Top" />
<Setter Property="FontSize" Value="{DynamicResource ControlContentThemeFontSize}" />
<Setter Property="PlaceholderForeground" Value="{DynamicResource ComboBoxPlaceHolderForeground}" />
<Setter Property="Template">
<ControlTemplate>

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

@ -42,8 +42,6 @@
<Setter Property="MinHeight" Value="{DynamicResource MenuFlyoutThemeMinHeight}" />
<Setter Property="Padding" Value="{DynamicResource MenuFlyoutPresenterThemePadding}" />
<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="Template">
<ControlTemplate>

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

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

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

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

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

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

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

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

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

@ -17,7 +17,6 @@
<Setter Property="Background" Value="{DynamicResource MenuFlyoutItemBackground}" />
<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. -->
<Setter Property="FontSize" Value="{DynamicResource ControlContentThemeFontSize}" />
<Setter Property="Padding" Value="{DynamicResource MenuBarItemPadding}" />
<Setter Property="Template">
<ControlTemplate>

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

@ -60,7 +60,6 @@
<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. -->
<Setter Property="Padding" Value="{DynamicResource MenuFlyoutItemThemePaddingNarrow}" />
<Setter Property="FontSize" Value="{DynamicResource ControlContentThemeFontSize}" />
<Setter Property="Template">
<ControlTemplate>
<Panel>
@ -81,15 +80,11 @@
SharedSizeGroup="MenuItemChevron" />
</Grid.ColumnDefinitions>
<Viewbox Name="PART_IconPresenter"
Margin="{DynamicResource MenuIconPresenterMargin}"
StretchDirection="DownOnly"
HorizontalAlignment="Center"
VerticalAlignment="Center"
IsVisible="False"
Width="16" Height="16">
<ContentPresenter Content="{TemplateBinding Icon}"/>
</Viewbox>
<ContentControl x:Name="PART_IconPresenter"
Theme="{StaticResource FluentMenuItemIconTheme}"
Content="{TemplateBinding Icon}"
IsVisible="False"
Margin="{DynamicResource MenuIconPresenterMargin}" />
<ContentPresenter Name="PART_HeaderPresenter"
Content="{TemplateBinding Header}"
@ -143,7 +138,7 @@
</ControlTemplate>
</Setter>
<Style Selector="^:icon /template/ Viewbox#PART_IconPresenter">
<Style Selector="^:icon /template/ ContentControl#PART_IconPresenter">
<Setter Property="IsVisible" Value="True" />
</Style>
<Style Selector="^:selected">
@ -210,4 +205,21 @@
<Setter Property="Padding" Value="{DynamicResource HorizontalMenuFlyoutItemThemePaddingNarrow}" />
<Setter Property="Margin" Value="{DynamicResource HorizontalMenuFlyoutItemMargin}" />
</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>

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

@ -9,7 +9,6 @@
<ControlTheme x:Key="{x:Type NotificationCard}" TargetType="NotificationCard">
<Setter Property="UseLayoutRounding" Value="True"/>
<Setter Property="Width" Value="350"/>
<Setter Property="FontSize" Value="14"/>
<Setter Property="Foreground" Value="{DynamicResource NotificationCardForegroundBrush}"/>
<Setter Property="RenderTransformOrigin" Value="50%,75%"/>
<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="MinHeight" Value="{DynamicResource TextControlThemeMinHeight}" />
<Setter Property="MinWidth" Value="{DynamicResource TextControlThemeMinWidth}" />
<Setter Property="FontSize" Value="{DynamicResource ControlContentThemeFontSize}" />
<Setter Property="Padding" Value="{DynamicResource TextControlThemePadding}" />
<Setter Property="CornerRadius" Value="{DynamicResource ControlCornerRadius}" />
<Setter Property="Template">
@ -51,7 +50,6 @@
Padding="{TemplateBinding Padding}"
MinWidth="0"
Foreground="{TemplateBinding Foreground}"
FontSize="{TemplateBinding FontSize}"
Watermark="{TemplateBinding Watermark}"
IsReadOnly="{TemplateBinding IsReadOnly}"
VerticalContentAlignment="{TemplateBinding VerticalContentAlignment}"

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

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

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

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

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

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

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

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

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

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

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

Loading…
Cancel
Save