Browse Source

Merge branch 'master' into features/NetAnalyzers/CA1822

pull/9189/head
Giuseppe Lippolis 4 years ago
parent
commit
9d1aa89cab
  1. 5
      .ncrunch/Avalonia.Web.Blazor.v3.ncrunchproject
  2. 5
      .ncrunch/Avalonia.Web.v3.ncrunchproject
  3. 6
      .ncrunch/Avalonia.Win32.net6.0.v3.ncrunchproject
  4. 6
      .ncrunch/Avalonia.Win32.netstandard2.0.v3.ncrunchproject
  5. 5
      .ncrunch/ControlCatalog.Blazor.Web.v3.ncrunchproject
  6. 5
      .ncrunch/MobileSandbox.Android.v3.ncrunchproject
  7. 5
      .ncrunch/MobileSandbox.Desktop.v3.ncrunchproject
  8. 5
      .ncrunch/MobileSandbox.iOS.v3.ncrunchproject
  9. 5
      .ncrunch/MobileSandbox.net6.0.v3.ncrunchproject
  10. 5
      .ncrunch/MobileSandbox.netstandard2.0.v3.ncrunchproject
  11. 5
      .ncrunch/_build.v3.ncrunchproject
  12. 1
      Avalonia.sln
  13. 10
      azure-pipelines-integrationtests.yml
  14. 12
      azure-pipelines.yml
  15. 2
      global.json
  16. 19
      nukebuild/Build.cs
  17. 4
      nukebuild/_build.csproj
  18. 8
      samples/ControlCatalog.Blazor.Web/Properties/launchSettings.json
  19. 2
      samples/ControlCatalog.NetCore/ControlCatalog.NetCore.csproj
  20. 9
      samples/ControlCatalog/MainView.xaml
  21. 12
      samples/ControlCatalog/MainWindow.xaml.cs
  22. 15
      samples/ControlCatalog/Pages/NotificationsPage.xaml.cs
  23. 51
      samples/ControlCatalog/Pages/PlatformInfoPage.xaml
  24. 20
      samples/ControlCatalog/Pages/PlatformInfoPage.xaml.cs
  25. 32
      samples/ControlCatalog/ViewModels/MainWindowViewModel.cs
  26. 29
      samples/ControlCatalog/ViewModels/NotificationViewModel.cs
  27. 54
      samples/ControlCatalog/ViewModels/PlatformInformationViewModel.cs
  28. 2
      samples/IntegrationTestApp/IntegrationTestApp.csproj
  29. 17
      samples/IntegrationTestApp/MainWindow.axaml
  30. 34
      samples/IntegrationTestApp/MainWindow.axaml.cs
  31. 1
      samples/MobileSandbox/MainView.xaml
  32. 46
      src/Android/Avalonia.Android/AndroidInputMethod.cs
  33. 14
      src/Android/Avalonia.Android/AndroidPlatform.cs
  34. 114
      src/Android/Avalonia.Android/Platform/SkiaPlatform/TopLevelImpl.cs
  35. 2
      src/Android/Avalonia.Android/Platform/Specific/Helpers/AndroidKeyboardEventsHelper.cs
  36. 25
      src/Avalonia.Base/Input/Gestures.cs
  37. 6
      src/Avalonia.Base/Input/MouseDevice.cs
  38. 5
      src/Avalonia.Base/Input/PenDevice.cs
  39. 1
      src/Avalonia.Base/Input/TextInput/ITextInputMethodClient.cs
  40. 7
      src/Avalonia.Base/Input/TouchDevice.cs
  41. 2
      src/Avalonia.Base/Media/TextFormatting/FormattedTextSource.cs
  42. 22
      src/Avalonia.Base/Metadata/MarkupExtensionOption.cs
  43. 30
      src/Avalonia.Base/Platform/DefaultPlatformSettings.cs
  44. 22
      src/Avalonia.Base/Platform/IPlatformSettings.cs
  45. 13
      src/Avalonia.Base/Platform/IRuntimePlatform.cs
  46. 6
      src/Avalonia.Build.Tasks/XamlCompilerTaskExecutor.cs
  47. 8
      src/Avalonia.Controls/Automation/AutomationProperties.cs
  48. 14
      src/Avalonia.Controls/Automation/Peers/AutomationPeer.cs
  49. 16
      src/Avalonia.Controls/Automation/Peers/ControlAutomationPeer.cs
  50. 17
      src/Avalonia.Controls/Notifications/WindowNotificationManager.cs
  51. 29
      src/Avalonia.Controls/Presenters/TextPresenter.cs
  52. 2
      src/Avalonia.Controls/TextBox.cs
  53. 1
      src/Avalonia.Controls/TopLevel.cs
  54. 11
      src/Avalonia.DesignerSupport/Remote/PreviewerWindowingPlatform.cs
  55. 5
      src/Avalonia.Diagnostics/Diagnostics/Conventions.cs
  56. 7
      src/Avalonia.Diagnostics/Diagnostics/DevToolsOptions.cs
  57. 5
      src/Avalonia.Diagnostics/Diagnostics/Screenshots/FilePickerHandler.cs
  58. 1
      src/Avalonia.Diagnostics/Diagnostics/ViewModels/AvaloniaPropertyViewModel.cs
  59. 1
      src/Avalonia.Diagnostics/Diagnostics/ViewModels/ClrPropertyViewModel.cs
  60. 9
      src/Avalonia.Diagnostics/Diagnostics/ViewModels/ControlDetailsViewModel.cs
  61. 14
      src/Avalonia.Diagnostics/Diagnostics/ViewModels/MainViewModel.cs
  62. 1
      src/Avalonia.Diagnostics/Diagnostics/ViewModels/TreeNode.cs
  63. 10
      src/Avalonia.Diagnostics/Diagnostics/Views/ControlDetailsView.xaml
  64. 4
      src/Avalonia.Diagnostics/Diagnostics/Views/MainView.xaml
  65. 2
      src/Avalonia.Headless/AvaloniaHeadlessPlatform.cs
  66. 10
      src/Avalonia.Headless/HeadlessPlatformStubs.cs
  67. 14
      src/Avalonia.Native/AvaloniaNativePlatform.cs
  68. 6
      src/Avalonia.Native/DoubleClickHelper.cs
  69. 5
      src/Avalonia.OpenGL/Egl/EglPlatformOpenGlInterface.cs
  70. 6
      src/Avalonia.Themes.Fluent/Controls/TextBox.xaml
  71. 6
      src/Avalonia.Themes.Simple/Controls/TextBox.xaml
  72. 17
      src/Avalonia.X11/Stubs.cs
  73. 2
      src/Avalonia.X11/X11Platform.cs
  74. 110
      src/Linux/Avalonia.LinuxFramebuffer/Input/LibInput/LibInputBackend.Pointer.cs
  75. 59
      src/Linux/Avalonia.LinuxFramebuffer/Input/LibInput/LibInputBackend.cs
  76. 53
      src/Linux/Avalonia.LinuxFramebuffer/Input/LibInput/LibInputNativeUnsafeMethods.cs
  77. 2
      src/Linux/Avalonia.LinuxFramebuffer/LinuxFramebufferPlatform.cs
  78. 9
      src/Linux/Avalonia.LinuxFramebuffer/Stubs.cs
  79. 1
      src/Markup/Avalonia.Markup.Xaml.Loader/AvaloniaXamlIlRuntimeCompiler.cs
  80. 24
      src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/AvaloniaXamlIlCompiler.cs
  81. 9
      src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/AvaloniaXamlIlLanguage.cs
  82. 69
      src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/AvaloniaXamlIlLanguageParseIntrinsics.cs
  83. 5
      src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlMetadataRemover.cs
  84. 403
      src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlOptionMarkupExtensionTransformer.cs
  85. 53
      src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlSelectorTransformer.cs
  86. 15
      src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlWellKnownTypes.cs
  87. 2
      src/Markup/Avalonia.Markup.Xaml.Loader/xamlil.github
  88. 3
      src/Markup/Avalonia.Markup.Xaml/Avalonia.Markup.Xaml.csproj
  89. 15
      src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/On.cs
  90. 60
      src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/OnFormFactorExtension.cs
  91. 73
      src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/OnPlatformExtension.cs
  92. 22
      src/Markup/Avalonia.Markup.Xaml/XamlIl/Runtime/XamlIlRuntimeHelpers.cs
  93. 10
      src/Skia/Avalonia.Skia/PlatformRenderInterface.cs
  94. 2
      src/Web/Avalonia.Web/Avalonia.Web.targets
  95. 26
      src/Web/Avalonia.Web/BrowserRuntimePlatform.cs
  96. 2
      src/Web/Avalonia.Web/BrowserSingleViewLifetime.cs
  97. 5
      src/Web/Avalonia.Web/Interop/AvaloniaModule.cs
  98. 19
      src/Web/Avalonia.Web/WindowingPlatform.cs
  99. 18
      src/Web/Avalonia.Web/webapp/modules/avalonia/caniuse.ts
  100. 31
      src/Windows/Avalonia.Win32/Win32Platform.cs

5
.ncrunch/Avalonia.Web.Blazor.v3.ncrunchproject

@ -0,0 +1,5 @@
<ProjectConfiguration>
<Settings>
<IgnoreThisComponentCompletely>True</IgnoreThisComponentCompletely>
</Settings>
</ProjectConfiguration>

5
.ncrunch/Avalonia.Web.v3.ncrunchproject

@ -0,0 +1,5 @@
<ProjectConfiguration>
<Settings>
<IgnoreThisComponentCompletely>True</IgnoreThisComponentCompletely>
</Settings>
</ProjectConfiguration>

6
.ncrunch/Avalonia.Win32.net6.0.v3.ncrunchproject

@ -1,7 +1,3 @@
<ProjectConfiguration>
<Settings>
<AdditionalFilesToIncludeForProject>
<Value>..\..\tools\MicroComGenerator\bin\Debug\net6.0\**.*</Value>
</AdditionalFilesToIncludeForProject>
</Settings>
<Settings />
</ProjectConfiguration>

6
.ncrunch/Avalonia.Win32.netstandard2.0.v3.ncrunchproject

@ -1,7 +1,3 @@
<ProjectConfiguration>
<Settings>
<AdditionalFilesToIncludeForProject>
<Value>..\..\tools\MicroComGenerator\bin\Debug\net6.0\**.*</Value>
</AdditionalFilesToIncludeForProject>
</Settings>
<Settings />
</ProjectConfiguration>

5
.ncrunch/ControlCatalog.Blazor.Web.v3.ncrunchproject

@ -0,0 +1,5 @@
<ProjectConfiguration>
<Settings>
<IgnoreThisComponentCompletely>True</IgnoreThisComponentCompletely>
</Settings>
</ProjectConfiguration>

5
.ncrunch/MobileSandbox.Android.v3.ncrunchproject

@ -0,0 +1,5 @@
<ProjectConfiguration>
<Settings>
<IgnoreThisComponentCompletely>True</IgnoreThisComponentCompletely>
</Settings>
</ProjectConfiguration>

5
.ncrunch/MobileSandbox.Desktop.v3.ncrunchproject

@ -0,0 +1,5 @@
<ProjectConfiguration>
<Settings>
<IgnoreThisComponentCompletely>True</IgnoreThisComponentCompletely>
</Settings>
</ProjectConfiguration>

5
.ncrunch/MobileSandbox.iOS.v3.ncrunchproject

@ -0,0 +1,5 @@
<ProjectConfiguration>
<Settings>
<IgnoreThisComponentCompletely>True</IgnoreThisComponentCompletely>
</Settings>
</ProjectConfiguration>

5
.ncrunch/MobileSandbox.net6.0.v3.ncrunchproject

@ -0,0 +1,5 @@
<ProjectConfiguration>
<Settings>
<IgnoreThisComponentCompletely>True</IgnoreThisComponentCompletely>
</Settings>
</ProjectConfiguration>

5
.ncrunch/MobileSandbox.netstandard2.0.v3.ncrunchproject

@ -0,0 +1,5 @@
<ProjectConfiguration>
<Settings>
<IgnoreThisComponentCompletely>True</IgnoreThisComponentCompletely>
</Settings>
</ProjectConfiguration>

5
.ncrunch/_build.v3.ncrunchproject

@ -0,0 +1,5 @@
<ProjectConfiguration>
<Settings>
<IgnoreThisComponentCompletely>True</IgnoreThisComponentCompletely>
</Settings>
</ProjectConfiguration>

1
Avalonia.sln

@ -522,6 +522,7 @@ Global
{3B8519C1-2F51-4F12-A348-120AB91D4532}.Release|Any CPU.Build.0 = Release|Any CPU
{C90FE60B-B01E-4F35-91D6-379D6966030F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{C90FE60B-B01E-4F35-91D6-379D6966030F}.Debug|Any CPU.Build.0 = Debug|Any CPU
{C90FE60B-B01E-4F35-91D6-379D6966030F}.Debug|Any CPU.Deploy.0 = Debug|Any CPU
{C90FE60B-B01E-4F35-91D6-379D6966030F}.Release|Any CPU.ActiveCfg = Release|Any CPU
{C90FE60B-B01E-4F35-91D6-379D6966030F}.Release|Any CPU.Build.0 = Release|Any CPU
{FED9A71D-00D7-4F40-A9E4-1229EEA28EEB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU

10
azure-pipelines-integrationtests.yml

@ -18,9 +18,9 @@ jobs:
version: 6.0.401
- task: UseDotNet@2
displayName: 'Use .NET Core SDK 7.0.100-rc.2.22477.23'
displayName: 'Use .NET Core SDK 7.0.100'
inputs:
version: 7.0.100-rc.2.22477.23
version: 7.0.100
- script: system_profiler SPDisplaysDataType |grep Resolution
@ -32,7 +32,7 @@ jobs:
rm -rf $(osascript -e "POSIX path of (path to application id \"net.avaloniaui.avalonia.integrationtestapp\")")
pkill IntegrationTestApp
./samples/IntegrationTestApp/bundle.sh
open -n ./samples/IntegrationTestApp/bin/Debug/net6.0/osx-arm64/publish/IntegrationTestApp.app
open -n ./samples/IntegrationTestApp/bin/Debug/net7.0/osx-arm64/publish/IntegrationTestApp.app
pkill IntegrationTestApp
- task: DotNetCoreCLI@2
@ -56,9 +56,9 @@ jobs:
version: 6.0.401
- task: UseDotNet@2
displayName: 'Use .NET Core SDK 7.0.100-rc.2.22477.23'
displayName: 'Use .NET Core SDK 7.0.100'
inputs:
version: 7.0.100-rc.2.22477.23
version: 7.0.100
- task: Windows Application Driver@0
inputs:

12
azure-pipelines.yml

@ -35,9 +35,9 @@ jobs:
version: 6.0.401
- task: UseDotNet@2
displayName: 'Use .NET Core SDK 7.0.100-rc.2.22477.23'
displayName: 'Use .NET Core SDK 7.0'
inputs:
version: 7.0.100-rc.2.22477.23
version: 7.0.100
- task: CmdLine@2
displayName: 'Install Workloads'
@ -72,9 +72,9 @@ jobs:
version: 6.0.401
- task: UseDotNet@2
displayName: 'Use .NET Core SDK 7.0.100-rc.2.22477.23'
displayName: 'Use .NET Core SDK 7.0.100'
inputs:
version: 7.0.100-rc.2.22477.23
version: 7.0.100
- task: CmdLine@2
displayName: 'Install Workloads'
@ -143,9 +143,9 @@ jobs:
version: 6.0.401
- task: UseDotNet@2
displayName: 'Use .NET Core SDK 7.0.100-rc.2.22477.23'
displayName: 'Use .NET Core SDK 7.0.100'
inputs:
version: 7.0.100-rc.2.22477.23
version: 7.0.100
- task: CmdLine@2
displayName: 'Install Workloads'

2
global.json

@ -1,6 +1,6 @@
{
"sdk": {
"version": "7.0.100-rc.2.22477.23",
"version": "7.0.100",
"rollForward": "latestFeature"
},
"msbuild-sdks": {

19
nukebuild/Build.cs

@ -145,8 +145,25 @@ partial class Build : NukeBuild
{
Information($"Running tests from {projectName}");
var project = Solution.GetProject(projectName).NotNull("project != null");
// Nuke and MSBuild tools have build-in helpers to get target frameworks from the project.
// Unfortunately, it gets broken with every second SDK update, so we had to do it manually.
var fileXml = XDocument.Parse(File.ReadAllText(project.Path));
var targetFrameworks = fileXml.Descendants("TargetFrameworks")
.FirstOrDefault()?.Value.Split(';').Select(f => f.Trim());
if (targetFrameworks is null)
{
var targetFramework = fileXml.Descendants("TargetFramework").FirstOrDefault()?.Value;
if (targetFramework is not null)
{
targetFrameworks = new[] { targetFramework };
}
}
if (targetFrameworks is null)
{
throw new InvalidOperationException("No target frameworks were found in the test project");
}
foreach (var fw in project.GetTargetFrameworks())
foreach (var fw in targetFrameworks)
{
if (fw.StartsWith("net4")
&& RuntimeInformation.IsOSPlatform(OSPlatform.Linux)

4
nukebuild/_build.csproj

@ -16,8 +16,8 @@
<PackageReference Include="MicroCom.CodeGenerator" Version="0.11.0" />
<!-- Keep in sync with Avalonia.Build.Tasks -->
<PackageReference Include="Mono.Cecil" Version="0.11.4" />
<PackageReference Include="SourceLink" Version="1.1.0" GeneratePathProperty="true" />
<PackageReference Include="Microsoft.Build.Framework" Version="17.3.1" PrivateAssets="All" />
<PackageReference Include="SourceLink" Version="1.1.0" GeneratePathProperty="true" />
<PackageReference Include="Microsoft.Build.Framework" Version="17.3.2" PrivateAssets="All" />
<PackageReference Include="xunit.runner.console" Version="2.4.2">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>

8
samples/ControlCatalog.Blazor.Web/Properties/launchSettings.json

@ -8,14 +8,6 @@
}
},
"profiles": {
"ControlCatalog.Web - IIS Express": {
"commandName": "IISExpress",
"launchBrowser": true,
"inspectUri": "{wsProtocol}://{url.hostname}:{url.port}/_framework/debug/ws-proxy?browser={browserInspectUri}",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
}
},
"ControlCatalog.Web": {
"commandName": "Project",
"dotnetRunMessages": "true",

2
samples/ControlCatalog.NetCore/ControlCatalog.NetCore.csproj

@ -5,7 +5,7 @@
<TargetFramework>net6.0</TargetFramework>
<TargetLatestRuntimePatch>true</TargetLatestRuntimePatch>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
<RuntimeFrameworkVersion>6.0.9</RuntimeFrameworkVersion>
<RuntimeFrameworkVersion>6.0.8</RuntimeFrameworkVersion>
</PropertyGroup>
<PropertyGroup Condition="'$(RunNativeAotCompilation)' == 'true'">

9
samples/ControlCatalog/MainView.xaml

@ -12,8 +12,8 @@
<Setter Property="HorizontalAlignment" Value="Left" />
</Style>
</Grid.Styles>
<controls:HamburgerMenu Name="Sidebar">
<TabItem Header="Composition">
<controls:HamburgerMenu Name="Sidebar">
<TabItem Header="Composition">
<pages:CompositionPage/>
</TabItem>
<TabItem Header="Acrylic">
@ -118,6 +118,9 @@
<TabItem Header="OpenGL">
<pages:OpenGlPage />
</TabItem>
<TabItem Header="Platform Information">
<pages:PlatformInfoPage />
</TabItem>
<TabItem Header="Pointers">
<pages:PointersPage />
</TabItem>
@ -130,7 +133,7 @@
<TabItem Header="RelativePanel">
<pages:RelativePanelPage />
</TabItem>
<TabItem Header="ScrollViewer">
<TabItem Header="ScrollViewer">
<pages:ScrollViewerPage />
</TabItem>
<TabItem Header="Slider">

12
samples/ControlCatalog/MainWindow.xaml.cs

@ -11,23 +11,13 @@ namespace ControlCatalog
{
public class MainWindow : Window
{
private WindowNotificationManager _notificationArea;
private NativeMenu? _recentMenu;
public MainWindow()
{
this.InitializeComponent();
//Renderer.DrawFps = true;
//Renderer.DrawDirtyRects = Renderer.DrawFps = true;
_notificationArea = new WindowNotificationManager(this)
{
Position = NotificationPosition.TopRight,
MaxItems = 3
};
DataContext = new MainWindowViewModel(_notificationArea);
DataContext = new MainWindowViewModel();
_recentMenu = ((NativeMenu.GetMenu(this)?.Items[0] as NativeMenuItem)?.Menu?.Items[2] as NativeMenuItem)?.Menu;
}

15
samples/ControlCatalog/Pages/NotificationsPage.xaml.cs

@ -1,18 +1,33 @@
using Avalonia;
using Avalonia.Controls;
using Avalonia.Markup.Xaml;
using ControlCatalog.ViewModels;
namespace ControlCatalog.Pages
{
public class NotificationsPage : UserControl
{
private NotificationViewModel _viewModel;
public NotificationsPage()
{
this.InitializeComponent();
_viewModel = new NotificationViewModel();
DataContext = _viewModel;
}
private void InitializeComponent()
{
AvaloniaXamlLoader.Load(this);
}
protected override void OnAttachedToVisualTree(VisualTreeAttachmentEventArgs e)
{
base.OnAttachedToVisualTree(e);
_viewModel.NotificationManager = new Avalonia.Controls.Notifications.WindowNotificationManager(VisualRoot as TopLevel);
}
}
}

51
samples/ControlCatalog/Pages/PlatformInfoPage.xaml

@ -0,0 +1,51 @@
<UserControl x:Class="ControlCatalog.Pages.PlatformInfoPage"
xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
d:DesignHeight="800"
d:DesignWidth="400"
mc:Ignorable="d">
<StackPanel Spacing="20">
<TextBlock Text="{Binding PlatformInfo}" />
<StackPanel TextElement.Foreground="White">
<StackPanel Orientation="Horizontal">
<Border Height="100" Width="100" Background="{OnFormFactor Gray, Desktop=Green}">
<TextBlock Text="Desktop" />
</Border>
<Border Height="100" Width="100" Background="{OnFormFactor Gray, Mobile=Green}">
<TextBlock Text="Mobile" />
</Border>
</StackPanel>
<WrapPanel>
<Border Height="100" Width="100" Background="{OnPlatform Gray, Windows=Green}">
<TextBlock Text="Windows" />
</Border>
<Border Height="100" Width="100" Background="{OnPlatform Gray, macOS=Green}">
<TextBlock Text="macOS" />
</Border>
<Border Height="100" Width="100" Background="{OnPlatform Gray, Linux=Green}">
<TextBlock Text="Linux" />
</Border>
<Border Height="100" Width="100" Background="{OnPlatform Gray, Browser=Green}">
<TextBlock Text="Browser" />
</Border>
<Border Height="100" Width="100" Background="{OnPlatform Gray, iOS=Green}">
<TextBlock Text="iOS" />
</Border>
<Border Height="100" Width="100" Background="{OnPlatform Gray, Android=Green}">
<TextBlock Text="Android" />
</Border>
<Border Height="100" Width="100">
<Border.Background>
<OnPlatform Default="Gray" >
<On Options="macOS, Linux, Windows" Content="Green" />
</OnPlatform>
</Border.Background>
<TextBlock Text="Win, Lin or Mac" />
</Border>
</WrapPanel>
</StackPanel>
</StackPanel>
</UserControl>

20
samples/ControlCatalog/Pages/PlatformInfoPage.xaml.cs

@ -0,0 +1,20 @@
using Avalonia.Controls;
using Avalonia.Markup.Xaml;
using ControlCatalog.ViewModels;
namespace ControlCatalog.Pages
{
public class PlatformInfoPage : UserControl
{
public PlatformInfoPage()
{
this.InitializeComponent();
DataContext = new PlatformInformationViewModel();
}
private void InitializeComponent()
{
AvaloniaXamlLoader.Load(this);
}
}
}

32
samples/ControlCatalog/ViewModels/MainWindowViewModel.cs

@ -24,25 +24,8 @@ namespace ControlCatalog.ViewModels
private bool _preferSystemChromeEnabled;
private double _titleBarHeight;
public MainWindowViewModel(IManagedNotificationManager notificationManager)
public MainWindowViewModel()
{
_notificationManager = notificationManager;
ShowCustomManagedNotificationCommand = MiniCommand.Create(() =>
{
NotificationManager.Show(new NotificationViewModel(NotificationManager) { Title = "Hey There!", Message = "Did you know that Avalonia now supports Custom In-Window Notifications?" });
});
ShowManagedNotificationCommand = MiniCommand.Create(() =>
{
NotificationManager.Show(new Avalonia.Controls.Notifications.Notification("Welcome", "Avalonia now supports Notifications.", NotificationType.Information));
});
ShowNativeNotificationCommand = MiniCommand.Create(() =>
{
NotificationManager.Show(new Avalonia.Controls.Notifications.Notification("Error", "Native Notifications are not quite ready. Coming soon.", NotificationType.Error));
});
AboutCommand = MiniCommand.CreateFromTask(async () =>
{
var dialog = new AboutAvaloniaDialog();
@ -52,7 +35,6 @@ namespace ControlCatalog.ViewModels
await dialog.ShowDialog(mainWindow);
}
});
ExitCommand = MiniCommand.Create(() =>
{
(App.Current?.ApplicationLifetime as IClassicDesktopStyleApplicationLifetime)?.Shutdown();
@ -143,24 +125,12 @@ namespace ControlCatalog.ViewModels
set { this.RaiseAndSetIfChanged(ref _windowStates, value); }
}
public IManagedNotificationManager NotificationManager
{
get { return _notificationManager; }
set { this.RaiseAndSetIfChanged(ref _notificationManager, value); }
}
public bool IsMenuItemChecked
{
get { return _isMenuItemChecked; }
set { this.RaiseAndSetIfChanged(ref _isMenuItemChecked, value); }
}
public MiniCommand ShowCustomManagedNotificationCommand { get; }
public MiniCommand ShowManagedNotificationCommand { get; }
public MiniCommand ShowNativeNotificationCommand { get; }
public MiniCommand AboutCommand { get; }
public MiniCommand ExitCommand { get; }

29
samples/ControlCatalog/ViewModels/NotificationViewModel.cs

@ -6,16 +6,33 @@ namespace ControlCatalog.ViewModels
{
public class NotificationViewModel
{
public NotificationViewModel(INotificationManager manager)
public WindowNotificationManager? NotificationManager { get; set; }
public NotificationViewModel()
{
ShowCustomManagedNotificationCommand = MiniCommand.Create(() =>
{
NotificationManager?.Show(new NotificationViewModel() { Title = "Hey There!", Message = "Did you know that Avalonia now supports Custom In-Window Notifications?" , NotificationManager = NotificationManager});
});
ShowManagedNotificationCommand = MiniCommand.Create(() =>
{
NotificationManager?.Show(new Avalonia.Controls.Notifications.Notification("Welcome", "Avalonia now supports Notifications.", NotificationType.Information));
});
ShowNativeNotificationCommand = MiniCommand.Create(() =>
{
NotificationManager?.Show(new Avalonia.Controls.Notifications.Notification("Error", "Native Notifications are not quite ready. Coming soon.", NotificationType.Error));
});
YesCommand = MiniCommand.Create(() =>
{
manager.Show(new Avalonia.Controls.Notifications.Notification("Avalonia Notifications", "Start adding notifications to your app today."));
NotificationManager?.Show(new Avalonia.Controls.Notifications.Notification("Avalonia Notifications", "Start adding notifications to your app today."));
});
NoCommand = MiniCommand.Create(() =>
{
manager.Show(new Avalonia.Controls.Notifications.Notification("Avalonia Notifications", "Start adding notifications to your app today. To find out more visit..."));
NotificationManager?.Show(new Avalonia.Controls.Notifications.Notification("Avalonia Notifications", "Start adding notifications to your app today. To find out more visit..."));
});
}
@ -26,5 +43,11 @@ namespace ControlCatalog.ViewModels
public MiniCommand NoCommand { get; }
public MiniCommand ShowCustomManagedNotificationCommand { get; }
public MiniCommand ShowManagedNotificationCommand { get; }
public MiniCommand ShowNativeNotificationCommand { get; }
}
}

54
samples/ControlCatalog/ViewModels/PlatformInformationViewModel.cs

@ -0,0 +1,54 @@
using Avalonia;
using Avalonia.Platform;
using MiniMvvm;
namespace ControlCatalog.ViewModels;
#nullable enable
public class PlatformInformationViewModel : ViewModelBase
{
public PlatformInformationViewModel()
{
var runtimeInfo = AvaloniaLocator.Current.GetService<IRuntimePlatform>()?.GetRuntimeInfo();
if (runtimeInfo is { } info)
{
if (info.IsBrowser)
{
if (info.IsDesktop)
{
PlatformInfo = "Platform: Desktop (browser)";
}
else if (info.IsMobile)
{
PlatformInfo = "Platform: Mobile (browser)";
}
else
{
PlatformInfo = "Platform: Unknown (browser) - please report";
}
}
else
{
if (info.IsDesktop)
{
PlatformInfo = "Platform: Desktop (native)";
}
else if (info.IsMobile)
{
PlatformInfo = "Platform: Mobile (native)";
}
else
{
PlatformInfo = "Platform: Unknown (native) - please report";
}
}
}
else
{
}
}
public string PlatformInfo { get; }
}

2
samples/IntegrationTestApp/IntegrationTestApp.csproj

@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>WinExe</OutputType>
<TargetFramework>net6.0</TargetFramework>
<TargetFramework>net7.0</TargetFramework>
<Nullable>enable</Nullable>
</PropertyGroup>

17
samples/IntegrationTestApp/MainWindow.axaml

@ -69,6 +69,23 @@
</StackPanel>
</TabItem>
<TabItem Header="Gestures">
<DockPanel>
<DockPanel DockPanel.Dock="Top">
<Button Name="ResetGestures" DockPanel.Dock="Right">Reset</Button>
<TextBlock Name="LastGesture" />
</DockPanel>
<Panel>
<Border Name="GestureBorder" Background="Blue"
AutomationProperties.AccessibilityView="Content"
AutomationProperties.ControlTypeOverride="Image"/>
<Border Name="GestureBorder2" Background="Green" IsVisible="False"
AutomationProperties.AccessibilityView="Content"
AutomationProperties.ControlTypeOverride="Image"/>
</Panel>
</DockPanel>
</TabItem>
<TabItem Header="ListBox">
<DockPanel>
<StackPanel DockPanel.Dock="Bottom">

34
samples/IntegrationTestApp/MainWindow.axaml.cs

@ -4,6 +4,7 @@ using Avalonia;
using Avalonia.Automation;
using Avalonia.Controls;
using Avalonia.Controls.ApplicationLifetimes;
using Avalonia.Input;
using Avalonia.Interactivity;
using Avalonia.Markup.Xaml;
using Avalonia.VisualTree;
@ -17,6 +18,7 @@ namespace IntegrationTestApp
{
InitializeComponent();
InitializeViewMenu();
InitializeGesturesTab();
this.AttachDevTools();
AddHandler(Button.ClickEvent, OnButtonClick);
ListBoxItems = Enumerable.Range(0, 100).Select(x => "Item " + x).ToList();
@ -120,6 +122,38 @@ namespace IntegrationTestApp
}
}
private void InitializeGesturesTab()
{
var gestureBorder = this.GetControl<Border>("GestureBorder");
var gestureBorder2 = this.GetControl<Border>("GestureBorder2");
var lastGesture = this.GetControl<TextBlock>("LastGesture");
var resetGestures = this.GetControl<Button>("ResetGestures");
gestureBorder.Tapped += (s, e) => lastGesture.Text = "Tapped";
gestureBorder.DoubleTapped += (s, e) =>
{
lastGesture.Text = "DoubleTapped";
// Testing #8733
gestureBorder.IsVisible = false;
gestureBorder2.IsVisible = true;
};
gestureBorder2.DoubleTapped += (s, e) =>
{
lastGesture.Text = "DoubleTapped2";
};
Gestures.AddRightTappedHandler(gestureBorder, (s, e) => lastGesture.Text = "RightTapped");
resetGestures.Click += (s, e) =>
{
lastGesture.Text = string.Empty;
gestureBorder.IsVisible = true;
gestureBorder2.IsVisible = false;
};
}
private void MenuClicked(object? sender, RoutedEventArgs e)
{
var clickedMenuItemTextBlock = this.Get<TextBlock>("ClickedMenuItem");

1
samples/MobileSandbox/MainView.xaml

@ -3,6 +3,7 @@
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<StackPanel Margin="100 50" Spacing="50">
<TextBlock Text="Login" Foreground="White" />
<TextBox Watermark="Text" />
<TextBox Watermark="Username" TextInputOptions.ContentType="Email" AcceptsReturn="True" TextInputOptions.ReturnKeyType="Search" />
<TextBox Watermark="Password" PasswordChar="*" TextInputOptions.ContentType="Password" />
<TextBox Watermark="Pin" PasswordChar="*" TextInputOptions.ContentType="Digits" />

46
src/Android/Avalonia.Android/AndroidInputMethod.cs

@ -1,6 +1,7 @@
using System;
using Android.Content;
using Android.Runtime;
using Android.Text;
using Android.Views;
using Android.Views.InputMethods;
using Avalonia.Android.Platform.SkiaPlatform;
@ -62,22 +63,21 @@ namespace Avalonia.Android
public void Reset()
{
_imm.RestartInput(_host);
}
public void SetClient(ITextInputMethodClient client)
{
if(client is null)
{
_inputConnection?.SetComposingText("", 0);
}
if (_client != null)
{
_client.SurroundingTextChanged -= SurroundingTextChanged;
}
Reset();
if(_inputConnection != null)
{
_inputConnection.ComposingText = null;
_inputConnection.ComposingRegion = default;
}
_client = client;
@ -87,23 +87,31 @@ namespace Avalonia.Android
_host.RequestFocus();
_imm.RestartInput(View);
_imm.ShowSoftInput(_host, ShowFlags.Implicit);
}
else
{
_imm.HideSoftInputFromWindow(_host.WindowToken, HideSoftInputFlags.None);
var surroundingText = Client.SurroundingText;
_imm.UpdateSelection(_host, surroundingText.AnchorOffset, surroundingText.CursorOffset, surroundingText.AnchorOffset, surroundingText.CursorOffset);
}
}
private void SurroundingTextChanged(object sender, EventArgs e)
{
if (IsActive)
if (IsActive && _inputConnection != null)
{
var surroundingText = Client.SurroundingText;
_inputConnection.SurroundingText = surroundingText;
_imm.UpdateSelection(_host, surroundingText.AnchorOffset, surroundingText.CursorOffset, surroundingText.AnchorOffset, surroundingText.CursorOffset);
if (_inputConnection.ComposingText != null && !_inputConnection.IsCommiting && surroundingText.AnchorOffset == surroundingText.CursorOffset)
{
_inputConnection.CommitText(_inputConnection.ComposingText, 0);
_inputConnection.SetSelection(surroundingText.AnchorOffset, surroundingText.CursorOffset);
}
}
}
@ -153,10 +161,20 @@ namespace Avalonia.Android
return _inputConnection;
});
}
}
private void RestoreSoftKeyboard(object sender, PointerReleasedEventArgs e)
public readonly struct ComposingRegion
{
private readonly int _start = -1;
private readonly int _end = -1;
public ComposingRegion(int start, int end)
{
_imm.ShowSoftInput(_host, ShowFlags.Implicit);
_start = start;
_end = end;
}
public int Start => _start;
public int End => _end;
}
}

14
src/Android/Avalonia.Android/AndroidPlatform.cs

@ -26,21 +26,11 @@ namespace Avalonia
namespace Avalonia.Android
{
class AndroidPlatform : IPlatformSettings
class AndroidPlatform
{
public static readonly AndroidPlatform Instance = new AndroidPlatform();
public static AndroidPlatformOptions Options { get; private set; }
/// <inheritdoc cref="IPlatformSettings.TouchDoubleClickSize"/>
public Size TouchDoubleClickSize => new Size(4, 4);
/// <inheritdoc cref="IPlatformSettings.TouchDoubleClickTime"/>
public TimeSpan TouchDoubleClickTime => TimeSpan.FromMilliseconds(200);
public Size DoubleClickSize => TouchDoubleClickSize;
public TimeSpan DoubleClickTime => TimeSpan.FromMilliseconds(500);
internal static Compositor Compositor { get; private set; }
public static void Initialize()
@ -52,7 +42,7 @@ namespace Avalonia.Android
.Bind<ICursorFactory>().ToTransient<CursorFactory>()
.Bind<IWindowingPlatform>().ToConstant(new WindowingPlatformStub())
.Bind<IKeyboardDevice>().ToSingleton<AndroidKeyboardDevice>()
.Bind<IPlatformSettings>().ToConstant(Instance)
.Bind<IPlatformSettings>().ToSingleton<DefaultPlatformSettings>()
.Bind<IPlatformThreadingInterface>().ToConstant(new AndroidThreadingInterface())
.Bind<IPlatformIconLoader>().ToSingleton<PlatformIconLoaderStub>()
.Bind<IRenderTimer>().ToConstant(new ChoreographerTimer())

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

@ -3,7 +3,9 @@ using System.Collections.Generic;
using Android.App;
using Android.Content;
using Android.Graphics;
using Android.OS;
using Android.Runtime;
using Android.Text;
using Android.Views;
using Android.Views.InputMethods;
using Avalonia.Android.OpenGL;
@ -23,6 +25,7 @@ using Avalonia.Platform.Storage;
using Avalonia.Rendering;
using Avalonia.Rendering.Composition;
using Java.Lang;
using Math = System.Math;
namespace Avalonia.Android.Platform.SkiaPlatform
{
@ -165,6 +168,28 @@ namespace Avalonia.Android.Platform.SkiaPlatform
_tl.Draw();
}
protected override void DispatchDraw(global::Android.Graphics.Canvas canvas)
{
// Workaround issue #9230 on where screen remains gray after splash screen.
// base.DispatchDraw should punch a hole into the canvas so the surface
// can be seen below, but it does not.
if (OperatingSystem.IsAndroidVersionAtLeast(29))
{
// Android 10+ does this (BlendMode was new)
var paint = new Paint();
paint.SetColor(0);
paint.BlendMode = BlendMode.Clear;
canvas.DrawRect(0, 0, Width, Height, paint);
}
else
{
// Android 9 did this
canvas.DrawColor(Color.Transparent, PorterDuff.Mode.Clear);
}
base.DispatchDraw(canvas);
}
protected override bool DispatchGenericPointerEvent(MotionEvent e)
{
bool callBase;
@ -254,8 +279,6 @@ namespace Avalonia.Android.Platform.SkiaPlatform
{
throw new NotImplementedException();
}
}
internal class AvaloniaInputConnection : BaseInputConnection
@ -271,11 +294,12 @@ namespace Avalonia.Android.Platform.SkiaPlatform
public TextInputMethodSurroundingText SurroundingText { get; set; }
public string ComposingText { get; private set; }
public string ComposingText { get; internal set; }
public ComposingRegion ComposingRegion { get; private set; }
public ComposingRegion? ComposingRegion { get; internal set; }
public bool IsComposing { get; private set; }
public bool IsComposing => !string.IsNullOrEmpty(ComposingText);
public bool IsCommiting { get; private set; }
public override bool SetComposingRegion(int start, int end)
{
@ -292,8 +316,6 @@ namespace Avalonia.Android.Platform.SkiaPlatform
ComposingText = composingText;
IsComposing = true;
_inputMethod.Client?.SetPreeditText(ComposingText);
return base.SetComposingText(text, newCursorPosition);
@ -301,20 +323,25 @@ namespace Avalonia.Android.Platform.SkiaPlatform
public override bool FinishComposingText()
{
IsComposing = false;
ComposingRegion = new ComposingRegion(SurroundingText.CursorOffset, SurroundingText.CursorOffset);
if (!string.IsNullOrEmpty(ComposingText))
{
CommitText(ComposingText, ComposingText.Length);
}
else
{
ComposingRegion = new ComposingRegion(SurroundingText.CursorOffset, SurroundingText.CursorOffset);
}
return base.FinishComposingText();
}
public override ICharSequence GetTextBeforeCursorFormatted(int length, [GeneratedEnum] GetTextFlags flags)
{
if (!string.IsNullOrEmpty(SurroundingText.Text))
if (!string.IsNullOrEmpty(SurroundingText.Text) && length > 0)
{
var start = System.Math.Max(SurroundingText.CursorOffset - length, 0);
var end = System.Math.Min(start + length, SurroundingText.CursorOffset);
var end = System.Math.Min(start + length - 1, SurroundingText.CursorOffset);
var text = SurroundingText.Text.Substring(start, end - start);
@ -346,11 +373,31 @@ namespace Avalonia.Android.Platform.SkiaPlatform
public override bool CommitText(ICharSequence text, int newCursorPosition)
{
IsCommiting = true;
var committedText = text.ToString();
_inputMethod.Client.SetPreeditText(null);
_inputMethod.Client.SelectInSurroundingText(ComposingRegion.Start, ComposingRegion.End);
int? start, end;
if(SurroundingText.CursorOffset != SurroundingText.AnchorOffset)
{
start = Math.Min(SurroundingText.CursorOffset, SurroundingText.AnchorOffset);
end = Math.Max(SurroundingText.CursorOffset, SurroundingText.AnchorOffset);
}
else if (ComposingRegion != null)
{
start = ComposingRegion?.Start;
end = ComposingRegion?.End;
ComposingRegion = null;
}
else
{
start = end = _inputMethod.Client.SurroundingText.CursorOffset;
}
_inputMethod.Client.SelectInSurroundingText((int)start, (int)end);
var time = DateTime.Now.TimeOfDay;
@ -358,15 +405,29 @@ namespace Avalonia.Android.Platform.SkiaPlatform
_topLevel.Input(rawTextEvent);
ComposingText = null;
ComposingRegion = new ComposingRegion(newCursorPosition, newCursorPosition);
return base.CommitText(text, newCursorPosition);
}
public override bool DeleteSurroundingText(int beforeLength, int afterLength)
{
_inputMethod.Client.SelectInSurroundingText(beforeLength, afterLength);
var surroundingText = _inputMethod.Client.SurroundingText;
var selectionStart = surroundingText.CursorOffset;
_inputMethod.Client.SelectInSurroundingText(selectionStart - beforeLength, selectionStart + afterLength);
_inputMethod.View.DispatchKeyEvent(new KeyEvent(KeyEventActions.Down, Keycode.ForwardDel));
surroundingText = _inputMethod.Client.SurroundingText;
selectionStart = surroundingText.CursorOffset;
ComposingRegion = new ComposingRegion(selectionStart, selectionStart);
return base.DeleteSurroundingText(beforeLength, afterLength);
}
@ -374,22 +435,23 @@ namespace Avalonia.Android.Platform.SkiaPlatform
{
_inputMethod.Client.SelectInSurroundingText(start, end);
ComposingRegion = new ComposingRegion(start, end);
return base.SetSelection(start, end);
}
}
public readonly struct ComposingRegion
{
private readonly int _start = -1;
private readonly int _end = -1;
public ComposingRegion(int start, int end)
public override bool PerformEditorAction([GeneratedEnum] ImeAction actionCode)
{
_start = start;
_end = end;
}
switch (actionCode)
{
case ImeAction.Done:
{
_inputMethod.IMM.HideSoftInputFromWindow(_inputMethod.View.WindowToken, HideSoftInputFlags.ImplicitOnly);
break;
}
}
public int Start => _start;
public int End => _end;
return base.PerformEditorAction(actionCode);
}
}
}

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

@ -63,7 +63,7 @@ namespace Avalonia.Android.Platform.Specific.Helpers
{
var rawTextEvent = new RawTextInputEventArgs(
AndroidKeyboardDevice.Instance,
Convert.ToUInt32(e.EventTime),
Convert.ToUInt64(e.EventTime),
_view.InputRoot,
unicodeTextInput ?? Convert.ToChar(e.UnicodeChar).ToString()
);

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

@ -1,5 +1,6 @@
using System;
using Avalonia.Interactivity;
using Avalonia.Platform;
using Avalonia.VisualTree;
namespace Avalonia.Input
@ -42,9 +43,8 @@ namespace Avalonia.Input
RoutedEvent.Register<PointerDeltaEventArgs>(
"PointerSwipeGesture", RoutingStrategies.Bubble, typeof(Gestures));
#pragma warning disable CS8625 // Cannot convert null literal to non-nullable reference type.
private static readonly WeakReference<IInteractive> s_lastPress = new WeakReference<IInteractive>(null);
#pragma warning restore CS8625 // Cannot convert null literal to non-nullable reference type.
private static readonly WeakReference<IInteractive?> s_lastPress = new WeakReference<IInteractive?>(null);
private static Point s_lastPressPoint;
static Gestures()
{
@ -94,10 +94,11 @@ namespace Avalonia.Input
var e = (PointerPressedEventArgs)ev;
var visual = (IVisual)ev.Source;
if (e.ClickCount <= 1)
if (e.ClickCount % 2 == 1)
{
s_isDoubleTapped = false;
s_lastPress.SetTarget(ev.Source);
s_lastPressPoint = e.GetPosition((IVisual)ev.Source);
}
else if (e.ClickCount % 2 == 0 && e.GetCurrentPoint(visual).Properties.IsLeftButtonPressed)
{
@ -107,10 +108,6 @@ namespace Avalonia.Input
e.Source.RaiseEvent(new TappedEventArgs(DoubleTappedEvent, e));
}
}
else
{
s_isDoubleTapped = false;
}
}
}
@ -120,9 +117,17 @@ namespace Avalonia.Input
{
var e = (PointerReleasedEventArgs)ev;
if (s_lastPress.TryGetTarget(out var target) && target == e.Source)
if (s_lastPress.TryGetTarget(out var target) &&
target == e.Source &&
e.InitialPressMouseButton is MouseButton.Left or MouseButton.Right)
{
if (e.InitialPressMouseButton == MouseButton.Left || e.InitialPressMouseButton == MouseButton.Right)
var point = e.GetCurrentPoint((IVisual)target);
var settings = AvaloniaLocator.Current.GetService<IPlatformSettings>();
var tapSize = settings?.GetTapSize(point.Pointer.Type) ?? new Size(4, 4);
var tapRect = new Rect(s_lastPressPoint, new Size())
.Inflate(new Thickness(tapSize.Width, tapSize.Height));
if (tapRect.ContainsExclusive(point.Position))
{
if (e.InitialPressMouseButton == MouseButton.Right)
{

6
src/Avalonia.Base/Input/MouseDevice.cs

@ -124,9 +124,9 @@ namespace Avalonia.Input
_pointer.Capture(source);
if (source != null)
{
var settings = AvaloniaLocator.Current.GetService<IPlatformSettings>();
var doubleClickTime = settings?.DoubleClickTime.TotalMilliseconds ?? 500;
var doubleClickSize = settings?.DoubleClickSize ?? new Size(4, 4);
var settings = AvaloniaLocator.Current.GetRequiredService<IPlatformSettings>();
var doubleClickTime = settings.GetDoubleTapTime(PointerType.Mouse).TotalMilliseconds;
var doubleClickSize = settings.GetDoubleTapSize(PointerType.Mouse);
if (!_lastClickRect.Contains(p) || timestamp - _lastClickTime > doubleClickTime)
{

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

@ -1,6 +1,7 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using Avalonia.Input.Raw;
using Avalonia.Platform;
@ -77,8 +78,8 @@ namespace Avalonia.Input
{
pointer.Capture(source);
var settings = AvaloniaLocator.Current.GetService<IPlatformSettings>();
var doubleClickTime = settings?.DoubleClickTime.TotalMilliseconds ?? 500;
var doubleClickSize = settings?.DoubleClickSize ?? new Size(4, 4);
var doubleClickTime = settings?.GetDoubleTapTime(PointerType.Pen).TotalMilliseconds ?? 500;
var doubleClickSize = settings?.GetDoubleTapSize(PointerType.Pen) ?? new Size(4, 4);
if (!_lastClickRect.Contains(p) || timestamp - _lastClickTime > doubleClickTime)
{

1
src/Avalonia.Base/Input/TextInput/ITextInputMethodClient.cs

@ -29,6 +29,7 @@ namespace Avalonia.Input.TextInput
/// Sets the non-committed input string
/// </summary>
void SetPreeditText(string? text);
/// <summary>
/// Indicates if text input client is capable of providing the text around the cursor
/// </summary>

7
src/Avalonia.Base/Input/TouchDevice.cs

@ -1,6 +1,7 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using Avalonia.Input.Raw;
using Avalonia.Platform;
@ -59,16 +60,18 @@ namespace Avalonia.Input
else
{
var settings = AvaloniaLocator.Current.GetRequiredService<IPlatformSettings>();
var doubleClickTime = settings.GetDoubleTapTime(PointerType.Touch).TotalMilliseconds;
var doubleClickSize = settings.GetDoubleTapSize(PointerType.Touch);
if (!_lastClickRect.Contains(args.Position)
|| ev.Timestamp - _lastClickTime > settings.TouchDoubleClickTime.TotalMilliseconds)
|| ev.Timestamp - _lastClickTime > doubleClickTime)
{
_clickCount = 0;
}
++_clickCount;
_lastClickTime = ev.Timestamp;
_lastClickRect = new Rect(args.Position, new Size())
.Inflate(new Thickness(settings.TouchDoubleClickSize.Width / 2, settings.TouchDoubleClickSize.Height / 2));
.Inflate(new Thickness(doubleClickSize.Width / 2, doubleClickSize.Height / 2));
}
target.RaiseEvent(new PointerPressedEventArgs(target, pointer,

2
src/Avalonia.Base/Media/TextFormatting/FormattedTextSource.cs

@ -140,7 +140,7 @@ namespace Avalonia.Media.TextFormatting
}
}
return length;
return Math.Min(length, text.Length);
}
}
}

22
src/Avalonia.Base/Metadata/MarkupExtensionOption.cs

@ -0,0 +1,22 @@
using System;
namespace Avalonia.Metadata;
[AttributeUsage(AttributeTargets.Property, AllowMultiple = false)]
public sealed class MarkupExtensionOptionAttribute : Attribute
{
public MarkupExtensionOptionAttribute(object value)
{
Value = value;
}
public object Value { get; }
public int Priority { get; set; } = 0;
}
[AttributeUsage(AttributeTargets.Property, AllowMultiple = false)]
public sealed class MarkupExtensionDefaultOptionAttribute : Attribute
{
}

30
src/Avalonia.Base/Platform/DefaultPlatformSettings.cs

@ -0,0 +1,30 @@
using System;
using Avalonia.Input;
namespace Avalonia.Platform
{
/// <summary>
/// A default implementation of <see cref="IPlatformSettings"/> for platforms which don't have
/// an OS-specific implementation.
/// </summary>
public class DefaultPlatformSettings : IPlatformSettings
{
public Size GetTapSize(PointerType type)
{
return type switch
{
PointerType.Touch => new(10, 10),
_ => new(4, 4),
};
}
public Size GetDoubleTapSize(PointerType type)
{
return type switch
{
PointerType.Touch => new(16, 16),
_ => new(4, 4),
};
}
public TimeSpan GetDoubleTapTime(PointerType type) => TimeSpan.FromMilliseconds(500);
}
}

22
src/Avalonia.Base/Platform/IPlatformSettings.cs

@ -1,4 +1,5 @@
using System;
using Avalonia.Input;
using Avalonia.Metadata;
namespace Avalonia.Platform
@ -6,18 +7,25 @@ namespace Avalonia.Platform
[Unstable]
public interface IPlatformSettings
{
Size DoubleClickSize { get; }
TimeSpan DoubleClickTime { get; }
/// <summary>
/// The size of the rectangle around the location of a pointer down that a pointer up
/// must occur within in order to register a tap gesture, in device-independent pixels.
/// </summary>
/// <param name="type">The pointer type.</param>
Size GetTapSize(PointerType type);
/// <summary>
/// Determines the size of the area within that you should click twice in order for a double click to be counted.
/// The size of the rectangle around the location of a pointer down that a pointer up
/// must occur within in order to register a double-tap gesture, in device-independent
/// pixels.
/// </summary>
Size TouchDoubleClickSize { get; }
/// <param name="type">The pointer type.</param>
Size GetDoubleTapSize(PointerType type);
/// <summary>
/// Determines the time span that what will be used to determine the double-click.
/// Gets the maximum time that may occur between the first and second click of a double-
/// tap gesture.
/// </summary>
TimeSpan TouchDoubleClickTime { get; }
TimeSpan GetDoubleTapTime(PointerType type);
}
}

13
src/Avalonia.Base/Platform/IRuntimePlatform.cs

@ -17,13 +17,16 @@ namespace Avalonia.Platform
IntPtr Address { get; }
int Size { get; }
bool IsDisposed { get; }
}
[Unstable]
public struct RuntimePlatformInfo
{
public OperatingSystemType OperatingSystem { get; set; }
public FormFactorType FormFactor => IsDesktop ? FormFactorType.Desktop :
IsMobile ? FormFactorType.Mobile : FormFactorType.Unknown;
public bool IsDesktop { get; set; }
public bool IsMobile { get; set; }
public bool IsBrowser { get; set; }
@ -44,4 +47,12 @@ namespace Avalonia.Platform
iOS,
Browser
}
[Unstable]
public enum FormFactorType
{
Unknown,
Desktop,
Mobile
}
}

6
src/Avalonia.Build.Tasks/XamlCompilerTaskExecutor.cs

@ -178,10 +178,11 @@ namespace Avalonia.Build.Tasks
var stringEquals = asm.MainModule.ImportReference(asm.MainModule.TypeSystem.String.Resolve().Methods.First(
m =>
m.IsStatic && m.Name == "Equals" && m.Parameters.Count == 2 &&
m.IsStatic && m.Name == "Equals" && m.Parameters.Count == 3 &&
m.ReturnType.FullName == "System.Boolean"
&& m.Parameters[0].ParameterType.FullName == "System.String"
&& m.Parameters[1].ParameterType.FullName == "System.String"));
&& m.Parameters[1].ParameterType.FullName == "System.String"
&& m.Parameters[2].ParameterType.FullName == "System.StringComparison"));
bool CompileGroup(IResourceGroup group)
{
@ -384,6 +385,7 @@ namespace Avalonia.Build.Tasks
var nop = Instruction.Create(OpCodes.Nop);
i.Add(Instruction.Create(OpCodes.Ldarg_0));
i.Add(Instruction.Create(OpCodes.Ldstr, res.Uri));
i.Add(Instruction.Create(OpCodes.Ldc_I4, (int)StringComparison.OrdinalIgnoreCase));
i.Add(Instruction.Create(OpCodes.Call, stringEquals));
i.Add(Instruction.Create(OpCodes.Brfalse, nop));
if (parameterlessConstructor != null)

8
src/Avalonia.Controls/Automation/AutomationProperties.cs

@ -9,6 +9,11 @@ namespace Avalonia.Automation
/// </summary>
public enum AccessibilityView
{
/// <summary>
/// The control's view is defined by its automation peer.
/// </summary>
Default,
/// <summary>
/// The control is included in the Raw view of the automation tree.
/// </summary>
@ -44,8 +49,7 @@ namespace Avalonia.Automation
public static readonly AttachedProperty<AccessibilityView> AccessibilityViewProperty =
AvaloniaProperty.RegisterAttached<StyledElement, AccessibilityView>(
"AccessibilityView",
typeof(AutomationProperties),
defaultValue: AccessibilityView.Content);
typeof(AutomationProperties));
/// <summary>
/// Defines the AutomationProperties.AccessKey attached property

14
src/Avalonia.Controls/Automation/Peers/AutomationPeer.cs

@ -128,13 +128,13 @@ namespace Avalonia.Automation.Peers
/// Gets a value that indicates whether the element that is associated with this automation
/// peer contains data that is presented to the user.
/// </summary>
public bool IsContentElement() => IsControlElement() && IsContentElementCore();
public bool IsContentElement() => IsContentElementOverrideCore();
/// <summary>
/// Gets a value that indicates whether the element is understood by the user as
/// interactive or as contributing to the logical structure of the control in the GUI.
/// </summary>
public bool IsControlElement() => IsControlElementCore();
public bool IsControlElement() => IsControlElementOverrideCore();
/// <summary>
/// Gets a value indicating whether the control is enabled for user interaction.
@ -247,6 +247,16 @@ namespace Avalonia.Automation.Peers
return GetAutomationControlTypeCore();
}
protected virtual bool IsContentElementOverrideCore()
{
return IsControlElement() && IsContentElementCore();
}
protected virtual bool IsControlElementOverrideCore()
{
return IsControlElementCore();
}
protected virtual object? GetProviderCore(Type providerType)
{
return providerType.IsAssignableFrom(this.GetType()) ? this : null;

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

@ -149,8 +149,8 @@ namespace Avalonia.Automation.Peers
protected override Rect GetBoundingRectangleCore() => GetBounds(Owner);
protected override string GetClassNameCore() => Owner.GetType().Name;
protected override bool HasKeyboardFocusCore() => Owner.IsFocused;
protected override bool IsContentElementCore() => AutomationProperties.GetAccessibilityView(Owner) >= AccessibilityView.Content;
protected override bool IsControlElementCore() => AutomationProperties.GetAccessibilityView(Owner) >= AccessibilityView.Control;
protected override bool IsContentElementCore() => true;
protected override bool IsControlElementCore() => true;
protected override bool IsEnabledCore() => Owner.IsEnabled;
protected override bool IsKeyboardFocusableCore() => Owner.Focusable;
protected override void SetFocusCore() => Owner.Focus();
@ -160,6 +160,18 @@ namespace Avalonia.Automation.Peers
return AutomationProperties.GetControlTypeOverride(Owner) ?? GetAutomationControlTypeCore();
}
protected override bool IsContentElementOverrideCore()
{
var view = AutomationProperties.GetAccessibilityView(Owner);
return view == AccessibilityView.Default ? IsContentElementCore() : view >= AccessibilityView.Content;
}
protected override bool IsControlElementOverrideCore()
{
var view = AutomationProperties.GetAccessibilityView(Owner);
return view == AccessibilityView.Default ? IsControlElementCore() : view >= AccessibilityView.Control;
}
private static Rect GetBounds(Control control)
{
var root = control.GetVisualRoot();

17
src/Avalonia.Controls/Notifications/WindowNotificationManager.cs

@ -3,11 +3,10 @@ using System.Collections;
using System.Linq;
using System.Reactive.Linq;
using System.Threading.Tasks;
using Avalonia.Controls.Metadata;
using Avalonia.Controls.Primitives;
using Avalonia.Rendering;
using Avalonia.Data;
using Avalonia.VisualTree;
using Avalonia.Controls.Metadata;
namespace Avalonia.Controls.Notifications
{
@ -55,20 +54,12 @@ namespace Avalonia.Controls.Notifications
/// Initializes a new instance of the <see cref="WindowNotificationManager"/> class.
/// </summary>
/// <param name="host">The window that will host the control.</param>
public WindowNotificationManager(Window host)
public WindowNotificationManager(TopLevel? host)
{
if (VisualChildren.Count != 0)
if (host != null)
{
Install(host);
}
else
{
Observable.FromEventPattern<TemplateAppliedEventArgs>(host, nameof(host.TemplateApplied)).Take(1)
.Subscribe(_ =>
{
Install(host);
});
}
UpdatePseudoClasses(Position);
}
@ -151,7 +142,7 @@ namespace Avalonia.Controls.Notifications
/// of the host <see cref="Window"/>.
/// </summary>
/// <param name="host">The <see cref="Window"/> that will be the host.</param>
private void Install(Window host)
private void Install(TemplatedControl host)
{
var adornerLayer = host.FindDescendantOfType<VisualLayerManager>()?.AdornerLayer;

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

@ -9,6 +9,7 @@ using Avalonia.VisualTree;
using Avalonia.Layout;
using Avalonia.Media.Immutable;
using Avalonia.Controls.Documents;
using Avalonia.Input.TextInput;
namespace Avalonia.Controls.Presenters
{
@ -514,7 +515,12 @@ namespace Avalonia.Controls.Presenters
{
if (!string.IsNullOrEmpty(_preeditText))
{
var text = _text?.Substring(0, _caretIndex) + _preeditText + _text?.Substring(_caretIndex);
if (string.IsNullOrEmpty(_text) || _caretIndex > _text.Length)
{
return _preeditText;
}
var text = _text.Substring(0, _caretIndex) + _preeditText + _text.Substring(_caretIndex);
return text;
}
@ -868,28 +874,11 @@ namespace Avalonia.Controls.Presenters
if (string.IsNullOrEmpty(newValue))
{
if (!string.IsNullOrEmpty(oldValue))
{
var textPosition = _compositionStartHit.FirstCharacterIndex + _compositionStartHit.TrailingLength + newValue?.Length ?? 0;
var characterHit = GetCharacterHitFromTextPosition(textPosition);
UpdateCaret(characterHit, true);
}
_compositionStartHit = new CharacterHit(-1);
UpdateCaret(_lastCharacterHit);
}
else
{
if (_compositionStartHit.FirstCharacterIndex == -1)
{
_compositionStartHit = _lastCharacterHit;
}
}
if (_compositionStartHit.FirstCharacterIndex != -1)
{
var textPosition = _compositionStartHit.FirstCharacterIndex + _compositionStartHit.TrailingLength + newValue?.Length ?? 0;
var textPosition = _caretIndex + newValue?.Length ?? 0;
var characterHit = GetCharacterHitFromTextPosition(textPosition);

2
src/Avalonia.Controls/TextBox.cs

@ -1211,7 +1211,7 @@ namespace Avalonia.Controls
protected override void OnPointerPressed(PointerPressedEventArgs e)
{
if (_presenter == null || !string.IsNullOrEmpty(_presenter.PreeditText))
if (_presenter == null )
{
return;
}

1
src/Avalonia.Controls/TopLevel.cs

@ -1,6 +1,7 @@
using System;
using System.Reactive.Linq;
using Avalonia.Controls.Metadata;
using Avalonia.Controls.Notifications;
using Avalonia.Controls.Platform;
using Avalonia.Controls.Primitives;
using Avalonia.Input;

11
src/Avalonia.DesignerSupport/Remote/PreviewerWindowingPlatform.cs

@ -9,7 +9,7 @@ using Avalonia.Rendering;
namespace Avalonia.DesignerSupport.Remote
{
class PreviewerWindowingPlatform : IWindowingPlatform, IPlatformSettings
class PreviewerWindowingPlatform : IWindowingPlatform
{
static readonly IKeyboardDevice Keyboard = new KeyboardDevice();
private static IAvaloniaRemoteTransportConnection s_transport;
@ -51,7 +51,7 @@ namespace Avalonia.DesignerSupport.Remote
.Bind<IClipboard>().ToSingleton<ClipboardStub>()
.Bind<ICursorFactory>().ToSingleton<CursorFactoryStub>()
.Bind<IKeyboardDevice>().ToConstant(Keyboard)
.Bind<IPlatformSettings>().ToConstant(instance)
.Bind<IPlatformSettings>().ToSingleton<DefaultPlatformSettings>()
.Bind<IPlatformThreadingInterface>().ToConstant(threading)
.Bind<IRenderLoop>().ToConstant(new RenderLoop())
.Bind<IRenderTimer>().ToConstant(new DefaultRenderTimer(60))
@ -60,12 +60,5 @@ namespace Avalonia.DesignerSupport.Remote
.Bind<PlatformHotkeyConfiguration>().ToSingleton<PlatformHotkeyConfiguration>();
}
public Size DoubleClickSize { get; } = new Size(2, 2);
public TimeSpan DoubleClickTime { get; } = TimeSpan.FromMilliseconds(500);
public Size TouchDoubleClickSize => new Size(16, 16);
public TimeSpan TouchDoubleClickTime => DoubleClickTime;
}
}

5
src/Avalonia.Diagnostics/Diagnostics/Convetions.cs → src/Avalonia.Diagnostics/Diagnostics/Conventions.cs

@ -1,11 +1,8 @@
using System;
using System.Reflection;
using Avalonia.Controls;
using Avalonia.VisualTree;
namespace Avalonia.Diagnostics
{
static class Convetions
internal static class Conventions
{
public static string DefaultScreenshotsRoot =>
System.IO.Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.MyPictures, Environment.SpecialFolderOption.Create),

7
src/Avalonia.Diagnostics/Diagnostics/DevToolsOptions.cs

@ -1,5 +1,4 @@
using System;
using Avalonia.Input;
using Avalonia.Input;
namespace Avalonia.Diagnostics
{
@ -36,11 +35,11 @@ namespace Avalonia.Diagnostics
public bool ShowImplementedInterfaces { get; set; } = true;
/// <summary>
/// Allow to customizze SreenshotHandler
/// Allow to customize SreenshotHandler
/// </summary>
/// <remarks>Default handler is <see cref="Screenshots.FilePickerHandler"/></remarks>
public IScreenshotHandler ScreenshotHandler { get; set; }
= Convetions.DefaultScreenshotHandler;
= Conventions.DefaultScreenshotHandler;
/// <summary>
/// Gets or sets whether DevTools should use the dark mode theme

5
src/Avalonia.Diagnostics/Diagnostics/Screenshots/FilePickerHandler.cs

@ -1,5 +1,4 @@
using System;
using System.IO;
using System.IO;
using System.Linq;
using System.Threading.Tasks;
using Avalonia.Controls;
@ -40,7 +39,7 @@ namespace Avalonia.Diagnostics.Screenshots
/// The default root folder is [Environment.SpecialFolder.MyPictures]/Screenshots.
/// </summary>
public string ScreenshotsRoot { get; }
= Convetions.DefaultScreenshotsRoot;
= Conventions.DefaultScreenshotsRoot;
/// <summary>
/// SaveFilePicker Title

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

@ -1,6 +1,5 @@
using System;
using Avalonia.Data;
using Avalonia.Media;
namespace Avalonia.Diagnostics.ViewModels
{

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

@ -1,6 +1,5 @@
using System;
using System.Reflection;
using Avalonia.Media;
namespace Avalonia.Diagnostics.ViewModels
{

9
src/Avalonia.Diagnostics/Diagnostics/ViewModels/ControlDetailsViewModel.cs

@ -1,7 +1,6 @@
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Collections.Specialized;
using System.ComponentModel;
using System.Linq;
using System.Reflection;
@ -15,7 +14,7 @@ using Avalonia.VisualTree;
namespace Avalonia.Diagnostics.ViewModels
{
internal class ControlDetailsViewModel : ViewModelBase, IDisposable
internal class ControlDetailsViewModel : ViewModelBase, IDisposable, IClassesChangedListener
{
private readonly IAvaloniaObject _avaloniaObject;
private IDictionary<object, PropertyViewModel[]>? _propertyIndex;
@ -46,7 +45,7 @@ namespace Avalonia.Diagnostics.ViewModels
if (avaloniaObject is StyledElement styledElement)
{
styledElement.Classes.CollectionChanged += OnClassesChanged;
styledElement.Classes.AddListener(this);
var pseudoClassAttributes = styledElement.GetType().GetCustomAttributes<PseudoClassesAttribute>(true);
@ -250,7 +249,7 @@ namespace Avalonia.Diagnostics.ViewModels
if (_avaloniaObject is StyledElement se)
{
se.Classes.CollectionChanged -= OnClassesChanged;
se.Classes.RemoveListener(this);
}
}
@ -325,7 +324,7 @@ namespace Avalonia.Diagnostics.ViewModels
}
}
private void OnClassesChanged(object? sender, NotifyCollectionChangedEventArgs e)
void IClassesChangedListener.Changed()
{
if (!SnapshotStyles)
{

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

@ -1,13 +1,11 @@
using System;
using System.ComponentModel;
using System.Reactive.Linq;
using System.Threading.Tasks;
using Avalonia.Controls;
using Avalonia.Diagnostics.Models;
using Avalonia.Input;
using Avalonia.Metadata;
using Avalonia.Threading;
using System.Linq;
namespace Avalonia.Diagnostics.ViewModels
{
@ -96,7 +94,9 @@ namespace Avalonia.Diagnostics.ViewModels
changed = false;
}
if (changed)
RaiseAndSetIfChanged(ref _shouldVisualizeDirtyRects, value);
{
RaiseAndSetIfChanged(ref _shouldVisualizeDirtyRects, value);
}
}
}
@ -331,7 +331,7 @@ namespace Avalonia.Diagnostics.ViewModels
private set => RaiseAndSetIfChanged(ref _showImplementedInterfaces , value);
}
public void ToggleShowImplementedInterfaces(object parametr)
public void ToggleShowImplementedInterfaces(object parameter)
{
ShowImplementedInterfaces = !ShowImplementedInterfaces;
if (Content is TreePageViewModel viewModel)
@ -340,15 +340,15 @@ namespace Avalonia.Diagnostics.ViewModels
}
}
public bool ShowDettailsPropertyType
public bool ShowDetailsPropertyType
{
get => _showPropertyType;
private set => RaiseAndSetIfChanged(ref _showPropertyType , value);
}
public void ToggleShowDettailsPropertyType(object paramter)
public void ToggleShowDetailsPropertyType(object parameter)
{
ShowDettailsPropertyType = !ShowDettailsPropertyType;
ShowDetailsPropertyType = !ShowDetailsPropertyType;
}
}
}

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

@ -6,7 +6,6 @@ using Avalonia.Controls;
using Avalonia.Controls.Primitives;
using Avalonia.LogicalTree;
using Avalonia.Media;
using Avalonia.VisualTree;
namespace Avalonia.Diagnostics.ViewModels
{

10
src/Avalonia.Diagnostics/Diagnostics/Views/ControlDetailsView.xaml

@ -19,7 +19,7 @@
<ColumnDefinition Width="Auto"/>
<!--
When selecting the Application node, we need this trick to hide Layout Visualizer
because when using the GridSplitter it sets the Witdth property of ColumnDefinition
because when using the GridSplitter it sets the Width property of ColumnDefinition
(see https://github.com/AvaloniaUI/Avalonia/blob/master/src/Avalonia.Controls/GridSplitter.cs#L528)
and if we hide the contents of the column, the space is not reclaimed
(see discussion https://github.com/AvaloniaUI/Avalonia/discussions/6773).
@ -62,15 +62,15 @@
<DataGridTextColumn Header="Value" Binding="{Binding Value}" />
<DataGridTextColumn Header="Type" Binding="{Binding Type}"
IsReadOnly="True"
IsVisible="{Binding !$parent[UserControl;2].DataContext.ShowDettailsPropertyType}"
IsVisible="{Binding !$parent[UserControl;2].DataContext.ShowDetailsPropertyType}"
/>
<DataGridTextColumn Header="Assinged Type" Binding="{Binding AssignedType, Converter={StaticResource GetTypeName}}"
<DataGridTextColumn Header="Assigned Type" Binding="{Binding AssignedType, Converter={StaticResource GetTypeName}}"
IsReadOnly="True"
IsVisible="{Binding $parent[UserControl;2].DataContext.ShowDettailsPropertyType}"
IsVisible="{Binding $parent[UserControl;2].DataContext.ShowDetailsPropertyType}"
/>
<DataGridTextColumn Header="Property Type" Binding="{Binding PropertyType, Converter={StaticResource GetTypeName}}"
IsReadOnly="True"
IsVisible="{Binding $parent[UserControl;2].DataContext.ShowDettailsPropertyType}"
IsVisible="{Binding $parent[UserControl;2].DataContext.ShowDetailsPropertyType}"
/>
<DataGridTextColumn Header="Priority" Binding="{Binding Priority}" IsReadOnly="True" />
</DataGrid.Columns>

4
src/Avalonia.Diagnostics/Diagnostics/Views/MainView.xaml

@ -53,10 +53,10 @@
IsEnabled="False" />
</MenuItem.Icon>
</MenuItem>
<MenuItem Header="Split Property Type" Command="{Binding ToggleShowDettailsPropertyType}">
<MenuItem Header="Split Property Type" Command="{Binding ToggleShowDetailsPropertyType}">
<MenuItem.Icon>
<CheckBox BorderThickness="0"
IsChecked="{Binding ShowDettailsPropertyType}"
IsChecked="{Binding ShowDetailsPropertyType}"
IsEnabled="False"/>
</MenuItem.Icon>

2
src/Avalonia.Headless/AvaloniaHeadlessPlatform.cs

@ -63,7 +63,7 @@ namespace Avalonia.Headless
.Bind<IPlatformThreadingInterface>().ToConstant(new HeadlessPlatformThreadingInterface())
.Bind<IClipboard>().ToSingleton<HeadlessClipboardStub>()
.Bind<ICursorFactory>().ToSingleton<HeadlessCursorFactoryStub>()
.Bind<IPlatformSettings>().ToConstant(new HeadlessPlatformSettingsStub())
.Bind<IPlatformSettings>().ToSingleton<DefaultPlatformSettings>()
.Bind<IPlatformIconLoader>().ToSingleton<HeadlessIconLoaderStub>()
.Bind<IKeyboardDevice>().ToConstant(new KeyboardDevice())
.Bind<IRenderLoop>().ToConstant(new RenderLoop())

10
src/Avalonia.Headless/HeadlessPlatformStubs.cs

@ -65,16 +65,6 @@ namespace Avalonia.Headless
}
}
class HeadlessPlatformSettingsStub : IPlatformSettings
{
public Size DoubleClickSize { get; } = new Size(2, 2);
public TimeSpan DoubleClickTime { get; } = TimeSpan.FromMilliseconds(500);
public Size TouchDoubleClickSize => new Size(16,16);
public TimeSpan TouchDoubleClickTime => DoubleClickTime;
}
class HeadlessGlyphTypefaceImpl : IGlyphTypeface
{
public FontMetrics Metrics => new FontMetrics

14
src/Avalonia.Native/AvaloniaNativePlatform.cs

@ -14,7 +14,7 @@ using MicroCom.Runtime;
namespace Avalonia.Native
{
class AvaloniaNativePlatform : IPlatformSettings, IWindowingPlatform
class AvaloniaNativePlatform : IWindowingPlatform
{
private readonly IAvaloniaNativeFactory _factory;
private AvaloniaNativePlatformOptions _options;
@ -26,16 +26,6 @@ namespace Avalonia.Native
internal static readonly KeyboardDevice KeyboardDevice = new KeyboardDevice();
[CanBeNull] internal static Compositor Compositor { get; private set; }
public Size DoubleClickSize => new Size(4, 4);
public TimeSpan DoubleClickTime => TimeSpan.FromMilliseconds(500); //TODO
/// <inheritdoc cref="IPlatformSettings.TouchDoubleClickSize"/>
public Size TouchDoubleClickSize => new Size(16, 16);
/// <inheritdoc cref="IPlatformSettings.TouchDoubleClickTime"/>
public TimeSpan TouchDoubleClickTime => DoubleClickTime;
public static AvaloniaNativePlatform Initialize(IntPtr factory, AvaloniaNativePlatformOptions options)
{
var result = new AvaloniaNativePlatform(MicroComRuntime.CreateProxyFor<IAvaloniaNativeFactory>(factory, true));
@ -113,7 +103,7 @@ namespace Avalonia.Native
.Bind<ICursorFactory>().ToConstant(new CursorFactory(_factory.CreateCursorFactory()))
.Bind<IPlatformIconLoader>().ToSingleton<IconLoader>()
.Bind<IKeyboardDevice>().ToConstant(KeyboardDevice)
.Bind<IPlatformSettings>().ToConstant(this)
.Bind<IPlatformSettings>().ToSingleton<DefaultPlatformSettings>()
.Bind<IWindowingPlatform>().ToConstant(this)
.Bind<IClipboard>().ToConstant(new ClipboardImpl(_factory.CreateClipboard()))
.Bind<IRenderTimer>().ToConstant(new DefaultRenderTimer(60))

6
src/Avalonia.Native/DoubleClickHelper.cs

@ -1,3 +1,4 @@
using Avalonia.Input;
using Avalonia.Platform;
namespace Avalonia.Native
@ -13,7 +14,8 @@ namespace Avalonia.Native
Point p)
{
var settings = AvaloniaLocator.Current.GetService<IPlatformSettings>();
var doubleClickTime = settings.DoubleClickTime.TotalMilliseconds;
var doubleClickTime = settings?.GetDoubleTapTime(PointerType.Mouse).TotalMilliseconds ?? 500;
var doubleClickSize = settings?.GetDoubleTapSize(PointerType.Mouse) ?? new Size(4, 4);
if (!_lastClickRect.Contains(p) || timestamp - _lastClickTime > doubleClickTime)
{
@ -23,7 +25,7 @@ namespace Avalonia.Native
++_clickCount;
_lastClickTime = timestamp;
_lastClickRect = new Rect(p, new Size())
.Inflate(new Thickness(settings.DoubleClickSize.Width / 2, settings.DoubleClickSize.Height / 2));
.Inflate(new Thickness(doubleClickSize.Width / 2, doubleClickSize.Height / 2));
return _clickCount == 2;
}

5
src/Avalonia.OpenGL/Egl/EglPlatformOpenGlInterface.cs

@ -44,10 +44,13 @@ namespace Avalonia.OpenGL.Egl
public IGlContext CreateContext() => Display.CreateContext(null);
public IGlContext CreateSharedContext() => Display.CreateContext(PrimaryEglContext);
public EglSurface CreateWindowSurface(IntPtr window)
{
if (window == IntPtr.Zero)
throw new OpenGlException($"Window {window} is invalid.");
using (PrimaryContext.MakeCurrent())
{
var s = Display.EglInterface.CreateWindowSurface(Display.Handle, Display.Config, window,

6
src/Avalonia.Themes.Fluent/Controls/TextBox.xaml

@ -235,7 +235,8 @@
<Setter Property="InnerRightContent">
<Template>
<ToggleButton Theme="{StaticResource FluentTextBoxToggleButton}"
IsChecked="{Binding $parent[TextBox].RevealPassword, Mode=TwoWay}">
IsChecked="{Binding $parent[TextBox].RevealPassword, Mode=TwoWay}"
ClipToBounds="True">
<Panel>
<PathIcon Data="{StaticResource PasswordBoxRevealButtonData}"
Height="8" Width="12"
@ -253,7 +254,8 @@
<Setter Property="InnerRightContent">
<Template>
<Button Theme="{StaticResource FluentTextBoxButton}"
Command="{Binding $parent[TextBox].Clear}">
Command="{Binding $parent[TextBox].Clear}"
ClipToBounds="True">
<PathIcon Data="{StaticResource TextBoxClearButtonData}" Height="10" Width="10"/>
</Button>
</Template>

6
src/Avalonia.Themes.Simple/Controls/TextBox.xaml

@ -192,7 +192,8 @@
<Template>
<Button Command="{Binding $parent[TextBox].Clear}"
Focusable="False"
Theme="{StaticResource SimpleTextBoxClearButtonTheme}" />
Theme="{StaticResource SimpleTextBoxClearButtonTheme}"
ClipToBounds="True" />
</Template>
</Setter>
</Style>
@ -212,7 +213,8 @@
<ToggleButton Background="Transparent"
Focusable="False"
IsChecked="{Binding $parent[TextBox].RevealPassword, Mode=TwoWay}"
Theme="{StaticResource SimplePasswordBoxRevealButtonTheme}" />
Theme="{StaticResource SimplePasswordBoxRevealButtonTheme}"
ClipToBounds="True" />
</Panel>
</Template>
</Setter>

17
src/Avalonia.X11/Stubs.cs

@ -1,17 +0,0 @@
using System;
using Avalonia.Platform;
namespace Avalonia.X11
{
class PlatformSettingsStub : IPlatformSettings
{
public Size DoubleClickSize { get; } = new Size(2, 2);
public TimeSpan DoubleClickTime { get; } = TimeSpan.FromMilliseconds(500);
/// <inheritdoc cref="IPlatformSettings.TouchDoubleClickSize"/>
public Size TouchDoubleClickSize => new Size(16, 16);
/// <inheritdoc cref="IPlatformSettings.TouchDoubleClickTime"/>
public TimeSpan TouchDoubleClickTime => DoubleClickTime;
}
}

2
src/Avalonia.X11/X11Platform.cs

@ -79,7 +79,7 @@ namespace Avalonia.X11
.Bind<IKeyboardDevice>().ToFunc(() => KeyboardDevice)
.Bind<ICursorFactory>().ToConstant(new X11CursorFactory(Display))
.Bind<IClipboard>().ToConstant(new X11Clipboard(this))
.Bind<IPlatformSettings>().ToConstant(new PlatformSettingsStub())
.Bind<IPlatformSettings>().ToSingleton<DefaultPlatformSettings>()
.Bind<IPlatformIconLoader>().ToConstant(new X11IconLoader())
.Bind<IMountedVolumeInfoProvider>().ToConstant(new LinuxMountedVolumeInfoProvider())
.Bind<IPlatformLifetimeEventsImpl>().ToConstant(new X11PlatformLifetimeEvents(this));

110
src/Linux/Avalonia.LinuxFramebuffer/Input/LibInput/LibInputBackend.Pointer.cs

@ -0,0 +1,110 @@
using System;
using Avalonia.Input;
using Avalonia.Input.Raw;
using Avalonia.Logging;
using static Avalonia.LinuxFramebuffer.Input.LibInput.LibInputNativeUnsafeMethods;
namespace Avalonia.LinuxFramebuffer.Input.LibInput;
public partial class LibInputBackend
{
private MouseDevice _mouse = new MouseDevice();
private Point _mousePosition;
private const string Pointer = LibInput + "/" + nameof(Pointer);
private void HandlePointer(IntPtr ev, LibInputEventType type)
{
var modifiers = RawInputModifiers.None; //TODO: support input modifiers
var pev = libinput_event_get_pointer_event(ev);
var info = _screen.ScaledSize;
var ts = libinput_event_pointer_get_time_usec(pev) / 1000;
switch (type)
{
case LibInputEventType.LIBINPUT_EVENT_POINTER_MOTION_ABSOLUTE:
_mousePosition = new Point(libinput_event_pointer_get_absolute_x_transformed(pev, (int)info.Width),
libinput_event_pointer_get_absolute_y_transformed(pev, (int)info.Height));
ScheduleInput(new RawPointerEventArgs(_mouse, ts, _inputRoot, RawPointerEventType.Move, _mousePosition,
modifiers));
break;
case LibInputEventType.LIBINPUT_EVENT_POINTER_BUTTON:
{
var button = (EvKey)libinput_event_pointer_get_button(pev);
var buttonState = libinput_event_pointer_get_button_state(pev);
RawPointerEventArgs evnt = button switch
{
EvKey.BTN_LEFT when buttonState == 1
=> new(_mouse, ts, _inputRoot, RawPointerEventType.LeftButtonDown, _mousePosition, modifiers),
EvKey.BTN_LEFT when buttonState == 0
=> new(_mouse, ts, _inputRoot, RawPointerEventType.LeftButtonUp, _mousePosition, modifiers),
EvKey.BTN_RIGHT when buttonState == 1
=> new(_mouse, ts, _inputRoot, RawPointerEventType.RightButtonUp, _mousePosition, modifiers),
EvKey.BTN_RIGHT when buttonState == 2
=> new(_mouse, ts, _inputRoot, RawPointerEventType.RightButtonDown, _mousePosition, modifiers),
EvKey.BTN_MIDDLE when buttonState == 1
=> new(_mouse, ts, _inputRoot, RawPointerEventType.MiddleButtonDown, _mousePosition, modifiers),
EvKey.BTN_MIDDLE when buttonState == 2
=> new(_mouse, ts, _inputRoot, RawPointerEventType.MiddleButtonUp, _mousePosition, modifiers),
_ => default,
};
if (evnt is not null)
{
ScheduleInput(evnt);
}
else
{
Logger.TryGet(LogEventLevel.Warning, Pointer)
?.Log(this, $"The button {button} is not associated");
}
}
break;
// Backward compatibility with low-res wheel
case LibInputEventType.LIBINPUT_EVENT_POINTER_AXIS:
{
var sourceAxis = libinput_event_pointer_get_axis_source(pev);
switch (sourceAxis)
{
case LibInputPointerAxisSource.LIBINPUT_POINTER_AXIS_SOURCE_WHEEL:
{
var value = libinput_event_pointer_get_axis_value_discrete(pev,
LibInputPointerAxis.LIBINPUT_POINTER_AXIS_SCROLL_VERTICAL);
ScheduleInput(new RawMouseWheelEventArgs(_mouse
, ts
, _inputRoot
, _mousePosition
, new Vector(0, -value)
, modifiers));
}
break;
case LibInputPointerAxisSource.LIBINPUT_POINTER_AXIS_SOURCE_FINGER:
case LibInputPointerAxisSource.LIBINPUT_POINTER_AXIS_SOURCE_CONTINUOUS:
case LibInputPointerAxisSource.LIBINPUT_POINTER_AXIS_SOURCE_WHEEL_TILT:
default:
Logger.TryGet(LogEventLevel.Debug, Pointer)
?.Log(this, $"The pointer axis {sourceAxis} is not managed.");
break;
}
}
break;
// Hi-Res wheel
case LibInputEventType.LIBINPUT_EVENT_POINTER_SCROLL_WHEEL:
{
var value = new Vector(0,
-libinput_event_pointer_get_scroll_value_v120(pev,
LibInputPointerAxis.LIBINPUT_POINTER_AXIS_SCROLL_VERTICAL) / 120);
ScheduleInput(new RawMouseWheelEventArgs(_mouse
, ts
, _inputRoot
, _mousePosition
, value
, modifiers));
}
break;
default:
Logger.TryGet(LogEventLevel.Warning, Pointer)
?.Log(this, $"The pointer event {type} is not mapped.");
break;
}
}
}

59
src/Linux/Avalonia.LinuxFramebuffer/Input/LibInput/LibInputBackend.cs

@ -4,18 +4,16 @@ using System.IO;
using System.Threading;
using Avalonia.Input;
using Avalonia.Input.Raw;
using static Avalonia.LinuxFramebuffer.Input.LibInput.LibInputNativeUnsafeMethods;
using static Avalonia.LinuxFramebuffer.Input.LibInput.LibInputNativeUnsafeMethods;
namespace Avalonia.LinuxFramebuffer.Input.LibInput
{
public class LibInputBackend : IInputBackend
public partial class LibInputBackend : IInputBackend
{
private IScreenInfoProvider _screen;
private IInputRoot _inputRoot;
private readonly Queue<Action> _inputThreadActions = new Queue<Action>();
private TouchDevice _touch = new TouchDevice();
private MouseDevice _mouse = new MouseDevice();
private Point _mousePosition;
private const string LibInput = nameof(Avalonia.LinuxFramebuffer) + "/" + nameof(Avalonia.LinuxFramebuffer.Input) + "/" + nameof(LibInput);
private readonly RawEventGroupingThreadingHelper _inputQueue;
private Action<RawInputEventArgs> _onInput;
private Dictionary<int, Point> _pointers = new Dictionary<int, Point>();
@ -24,13 +22,13 @@ namespace Avalonia.LinuxFramebuffer.Input.LibInput
{
var ctx = libinput_path_create_context();
_inputQueue = new(e => _onInput?.Invoke(e));
new Thread(()=>InputThread(ctx)).Start();
new Thread(() => InputThread(ctx)).Start();
}
private unsafe void InputThread(IntPtr ctx)
{
var fd = libinput_get_fd(ctx);
var timeval = stackalloc IntPtr[2];
@ -38,12 +36,11 @@ namespace Avalonia.LinuxFramebuffer.Input.LibInput
libinput_path_add_device(ctx, f);
while (true)
{
IntPtr ev;
libinput_dispatch(ctx);
while ((ev = libinput_get_event(ctx)) != IntPtr.Zero)
{
var type = libinput_event_get_type(ev);
if (type >= LibInputEventType.LIBINPUT_EVENT_TOUCH_DOWN &&
type <= LibInputEventType.LIBINPUT_EVENT_TOUCH_CANCEL)
@ -52,12 +49,12 @@ namespace Avalonia.LinuxFramebuffer.Input.LibInput
if (type >= LibInputEventType.LIBINPUT_EVENT_POINTER_MOTION
&& type <= LibInputEventType.LIBINPUT_EVENT_POINTER_AXIS)
HandlePointer(ev, type);
libinput_event_destroy(ev);
libinput_dispatch(ctx);
}
pollfd pfd = new pollfd {fd = fd, events = 1};
pollfd pfd = new pollfd { fd = fd, events = 1 };
NativeUnsafeMethods.poll(&pfd, new IntPtr(1), 10);
}
}
@ -67,7 +64,7 @@ namespace Avalonia.LinuxFramebuffer.Input.LibInput
private void HandleTouch(IntPtr ev, LibInputEventType type)
{
var tev = libinput_event_get_touch_event(ev);
if(tev == IntPtr.Zero)
if (tev == IntPtr.Zero)
return;
if (type < LibInputEventType.LIBINPUT_EVENT_TOUCH_FRAME)
{
@ -102,44 +99,6 @@ namespace Avalonia.LinuxFramebuffer.Input.LibInput
}
}
private void HandlePointer(IntPtr ev, LibInputEventType type)
{
//TODO: support input modifiers
var pev = libinput_event_get_pointer_event(ev);
var info = _screen.ScaledSize;
var ts = libinput_event_pointer_get_time_usec(pev) / 1000;
if (type == LibInputEventType.LIBINPUT_EVENT_POINTER_MOTION_ABSOLUTE)
{
_mousePosition = new Point(libinput_event_pointer_get_absolute_x_transformed(pev, (int)info.Width),
libinput_event_pointer_get_absolute_y_transformed(pev, (int)info.Height));
ScheduleInput(new RawPointerEventArgs(_mouse, ts, _inputRoot, RawPointerEventType.Move, _mousePosition,
RawInputModifiers.None));
}
else if (type == LibInputEventType.LIBINPUT_EVENT_POINTER_BUTTON)
{
var button = (EvKey)libinput_event_pointer_get_button(pev);
var buttonState = libinput_event_pointer_get_button_state(pev);
var evnt = button == EvKey.BTN_LEFT ?
(buttonState == 1 ? RawPointerEventType.LeftButtonDown : RawPointerEventType.LeftButtonUp) :
button == EvKey.BTN_MIDDLE ?
(buttonState == 1 ? RawPointerEventType.MiddleButtonDown : RawPointerEventType.MiddleButtonUp) :
button == EvKey.BTN_RIGHT ?
(buttonState == 1 ?
RawPointerEventType.RightButtonDown :
RawPointerEventType.RightButtonUp) :
(RawPointerEventType)(-1);
if (evnt == (RawPointerEventType)(-1))
return;
ScheduleInput(
new RawPointerEventArgs(_mouse, ts, _inputRoot, evnt, _mousePosition, RawInputModifiers.None));
}
}
public void Initialize(IScreenInfoProvider screen, Action<RawInputEventArgs> onInput)
{
_screen = screen;

53
src/Linux/Avalonia.LinuxFramebuffer/Input/LibInput/LibInputNativeUnsafeMethods.cs

@ -77,6 +77,9 @@ namespace Avalonia.LinuxFramebuffer.Input.LibInput
LIBINPUT_EVENT_POINTER_MOTION_ABSOLUTE,
LIBINPUT_EVENT_POINTER_BUTTON,
LIBINPUT_EVENT_POINTER_AXIS,
LIBINPUT_EVENT_POINTER_SCROLL_WHEEL,
LIBINPUT_EVENT_POINTER_SCROLL_FINGER,
LIBINPUT_EVENT_POINTER_SCROLL_CONTINUOUS,
LIBINPUT_EVENT_TOUCH_DOWN = 500,
LIBINPUT_EVENT_TOUCH_UP,
LIBINPUT_EVENT_TOUCH_MOTION,
@ -97,8 +100,38 @@ namespace Avalonia.LinuxFramebuffer.Input.LibInput
LIBINPUT_EVENT_GESTURE_PINCH_END,
LIBINPUT_EVENT_SWITCH_TOGGLE = 900,
}
public enum LibInputPointerAxisSource
{
/**
* The event is caused by the rotation of a wheel.
**/
LIBINPUT_POINTER_AXIS_SOURCE_WHEEL = 1,
/**
* The event is caused by the movement of one or more fingers on a device.
**/
LIBINPUT_POINTER_AXIS_SOURCE_FINGER,
/**
* The event is caused by the motion of some device.
**/
LIBINPUT_POINTER_AXIS_SOURCE_CONTINUOUS,
/**
* The event is caused by the tilting of a mouse wheel rather than
* its rotation. This method is commonly used on mice without
* separate horizontal scroll wheels.
* @deprecated This axis source is deprecated as of libinput 1.16.
* It was never used by any device before libinput 1.16. All wheel
* tilt devices use @ref LIBINPUT_POINTER_AXIS_SOURCE_WHEEL instead.
**/
LIBINPUT_POINTER_AXIS_SOURCE_WHEEL_TILT,
};
public enum LibInputPointerAxis
{
LIBINPUT_POINTER_AXIS_SCROLL_VERTICAL = 0,
LIBINPUT_POINTER_AXIS_SCROLL_HORIZONTAL = 1,
};
[DllImport(LibInput)]
public extern static void libinput_event_destroy(IntPtr ev);
@ -119,21 +152,29 @@ namespace Avalonia.LinuxFramebuffer.Input.LibInput
[DllImport(LibInput)]
public extern static IntPtr libinput_event_get_pointer_event(IntPtr ev);
[DllImport(LibInput)]
public extern static ulong libinput_event_pointer_get_time_usec(IntPtr ev);
[DllImport(LibInput)]
public extern static double libinput_event_pointer_get_absolute_x_transformed(IntPtr ev, int width);
[DllImport(LibInput)]
public extern static double libinput_event_pointer_get_absolute_y_transformed(IntPtr ev, int height);
[DllImport(LibInput)]
public extern static int libinput_event_pointer_get_button(IntPtr ev);
[DllImport(LibInput)]
public extern static int libinput_event_pointer_get_button_state(IntPtr ev);
[DllImport(LibInput)]
public extern static LibInputPointerAxisSource libinput_event_pointer_get_axis_source(IntPtr ev);
[DllImport((LibInput))]
public extern static double libinput_event_pointer_get_axis_value_discrete(IntPtr ev, LibInputPointerAxis axis);
[DllImport(LibInput)]
public extern static double libinput_event_pointer_get_scroll_value_v120(IntPtr ev, LibInputPointerAxis axis);
}
}

2
src/Linux/Avalonia.LinuxFramebuffer/LinuxFramebufferPlatform.cs

@ -51,7 +51,7 @@ namespace Avalonia.LinuxFramebuffer
.Bind<IRenderLoop>().ToConstant(new RenderLoop())
.Bind<ICursorFactory>().ToTransient<CursorFactoryStub>()
.Bind<IKeyboardDevice>().ToConstant(new KeyboardDevice())
.Bind<IPlatformSettings>().ToSingleton<PlatformSettings>()
.Bind<IPlatformSettings>().ToSingleton<DefaultPlatformSettings>()
.Bind<PlatformHotkeyConfiguration>().ToSingleton<PlatformHotkeyConfiguration>();
Compositor = new Compositor(

9
src/Linux/Avalonia.LinuxFramebuffer/Stubs.cs

@ -14,13 +14,4 @@ namespace Avalonia.LinuxFramebuffer
public void Dispose() { }
}
}
internal class PlatformSettings : IPlatformSettings
{
public Size DoubleClickSize { get; } = new Size(4, 4);
public TimeSpan DoubleClickTime { get; } = new TimeSpan(0, 0, 0, 0, 500);
public Size TouchDoubleClickSize => new Size(16,16);
public TimeSpan TouchDoubleClickTime => DoubleClickTime;
}
}

1
src/Markup/Avalonia.Markup.Xaml.Loader/AvaloniaXamlIlRuntimeCompiler.cs

@ -198,6 +198,7 @@ namespace Avalonia.Markup.Xaml.XamlIl
compiler.ParseAndCompile(xaml, uri?.ToString(), null, _sreTypeSystem.CreateTypeBuilder(tb), overrideType);
var created = tb.CreateTypeInfo();
clrPropertyBuilder.CreateTypeInfo();
indexerClosureType.CreateTypeInfo();
trampolineBuilder.CreateTypeInfo();
return LoadOrPopulate(created, rootInstance);

24
src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/AvaloniaXamlIlCompiler.cs

@ -22,20 +22,20 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions
private AvaloniaXamlIlCompiler(TransformerConfiguration configuration, XamlLanguageEmitMappings<IXamlILEmitter, XamlILNodeEmitResult> emitMappings)
: base(configuration, emitMappings, true)
{
void InsertAfter<T>(params IXamlAstTransformer[] t)
void InsertAfter<T>(params IXamlAstTransformer[] t)
=> Transformers.InsertRange(Transformers.FindIndex(x => x is T) + 1, t);
void InsertBefore<T>(params IXamlAstTransformer[] t)
void InsertBefore<T>(params IXamlAstTransformer[] t)
=> Transformers.InsertRange(Transformers.FindIndex(x => x is T), t);
// Before everything else
Transformers.Insert(0, new XNameTransformer());
Transformers.Insert(1, new IgnoredDirectivesTransformer());
Transformers.Insert(2, _designTransformer = new AvaloniaXamlIlDesignPropertiesTransformer());
Transformers.Insert(3, _bindingTransformer = new AvaloniaBindingExtensionTransformer());
// Targeted
InsertBefore<PropertyReferenceResolver>(
new AvaloniaXamlIlResolveClassesPropertiesTransformer(),
@ -49,7 +49,7 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions
InsertBefore<ContentConvertTransformer>(
new AvaloniaXamlIlControlThemeTransformer(),
new AvaloniaXamlIlSelectorTransformer(),
new AvaloniaXamlIlControlTemplateTargetTypeMetadataTransformer(),
new AvaloniaXamlIlControlTemplateTargetTypeMetadataTransformer(),
new AvaloniaXamlIlBindingPathParser(),
new AvaloniaXamlIlPropertyPathTransformer(),
new AvaloniaXamlIlSetterTargetTypeMetadataTransformer(),
@ -58,6 +58,8 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions
new AvaloniaXamlIlTransitionsTypeMetadataTransformer(),
new AvaloniaXamlIlResolveByNameMarkupExtensionReplacer()
);
InsertBefore<ConvertPropertyValuesToAssignmentsTransformer>(
new AvaloniaXamlIlOptionMarkupExtensionTransformer());
InsertAfter<TypeReferenceResolver>(
new XDataTypeTransformer());
@ -89,14 +91,14 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions
_contextType = CreateContextType(contextTypeBuilder);
}
public AvaloniaXamlIlCompiler(TransformerConfiguration configuration,
XamlLanguageEmitMappings<IXamlILEmitter, XamlILNodeEmitResult> emitMappings,
IXamlType contextType) : this(configuration, emitMappings)
{
_contextType = contextType;
}
public const string PopulateName = "__AvaloniaXamlIlPopulate";
public const string BuildName = "__AvaloniaXamlIlBuild";
@ -118,7 +120,7 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions
{
{XamlNamespaces.Blend2008, XamlNamespaces.Blend2008}
});
var rootObject = (XamlAstObjectNode)parsed.Root;
var classDirective = rootObject.Children
@ -133,8 +135,8 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions
false) :
TypeReferenceResolver.ResolveType(CreateTransformationContext(parsed, true),
(XamlAstXmlTypeReference)rootObject.Type, true);
if (overrideRootType != null)
{
if (!rootType.Type.IsAssignableFrom(overrideRootType))
@ -147,7 +149,7 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions
Transform(parsed);
Compile(parsed, tb, _contextType, PopulateName, BuildName, "__AvaloniaXamlIlNsInfo", baseUri, fileSource);
}
public void OverrideRootType(XamlDocument doc, IXamlAstTypeReference newType)

9
src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/AvaloniaXamlIlLanguage.cs

@ -185,6 +185,15 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions
public static bool CustomValueConverter(AstTransformationContext context,
IXamlAstValueNode node, IXamlType type, out IXamlAstValueNode result)
{
if (node is AvaloniaXamlIlOptionMarkupExtensionTransformer.OptionsMarkupExtensionNode optionsNode)
{
if (optionsNode.ConvertToReturnType(context, type, out var newOptionsNode))
{
result = newOptionsNode;
return true;
}
}
if (!(node is XamlAstTextNode textNode))
{
result = null;

69
src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/AvaloniaXamlIlLanguageParseIntrinsics.cs

@ -48,10 +48,10 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions
try
{
var thickness = Thickness.Parse(text);
result = new AvaloniaXamlIlVectorLikeConstantAstNode(node, types, types.Thickness, types.ThicknessFullConstructor,
new[] { thickness.Left, thickness.Top, thickness.Right, thickness.Bottom });
return true;
}
catch
@ -65,10 +65,10 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions
try
{
var point = Point.Parse(text);
result = new AvaloniaXamlIlVectorLikeConstantAstNode(node, types, types.Point, types.PointFullConstructor,
new[] { point.X, point.Y });
return true;
}
catch
@ -76,16 +76,16 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions
throw new XamlX.XamlLoadException($"Unable to parse \"{text}\" as a point", node);
}
}
if (type.Equals(types.Vector))
{
try
{
var vector = Vector.Parse(text);
result = new AvaloniaXamlIlVectorLikeConstantAstNode(node, types, types.Vector, types.VectorFullConstructor,
new[] { vector.X, vector.Y });
return true;
}
catch
@ -93,16 +93,16 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions
throw new XamlX.XamlLoadException($"Unable to parse \"{text}\" as a vector", node);
}
}
if (type.Equals(types.Size))
{
try
{
var size = Size.Parse(text);
result = new AvaloniaXamlIlVectorLikeConstantAstNode(node, types, types.Size, types.SizeFullConstructor,
new[] { size.Width, size.Height });
return true;
}
catch
@ -110,16 +110,16 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions
throw new XamlX.XamlLoadException($"Unable to parse \"{text}\" as a size", node);
}
}
if (type.Equals(types.Matrix))
{
try
{
var matrix = Matrix.Parse(text);
result = new AvaloniaXamlIlVectorLikeConstantAstNode(node, types, types.Matrix, types.MatrixFullConstructor,
new[] { matrix.M11, matrix.M12, matrix.M21, matrix.M22, matrix.M31, matrix.M32 });
return true;
}
catch
@ -127,16 +127,16 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions
throw new XamlX.XamlLoadException($"Unable to parse \"{text}\" as a matrix", node);
}
}
if (type.Equals(types.CornerRadius))
{
try
{
var cornerRadius = CornerRadius.Parse(text);
result = new AvaloniaXamlIlVectorLikeConstantAstNode(node, types, types.CornerRadius, types.CornerRadiusFullConstructor,
new[] { cornerRadius.TopLeft, cornerRadius.TopRight, cornerRadius.BottomRight, cornerRadius.BottomLeft });
return true;
}
catch
@ -144,7 +144,7 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions
throw new XamlX.XamlLoadException($"Unable to parse \"{text}\" as a corner radius", node);
}
}
if (type.Equals(types.Color))
{
if (!Color.TryParse(text, out Color color))
@ -165,9 +165,9 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions
try
{
var relativePoint = RelativePoint.Parse(text);
var relativePointTypeRef = new XamlAstClrTypeReference(node, types.RelativePoint, false);
result = new XamlAstNewClrObjectNode(node, relativePointTypeRef, types.RelativePointFullConstructor, new List<IXamlAstValueNode>
{
new XamlConstantNode(node, types.XamlIlTypes.Double, relativePoint.Point.X),
@ -188,9 +188,9 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions
try
{
var gridLength = GridLength.Parse(text);
result = new AvaloniaXamlIlGridLengthAstNode(node, types, gridLength);
return true;
}
catch
@ -201,12 +201,12 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions
if (type.Equals(types.Cursor))
{
if (TypeSystemHelpers.TryGetEnumValueNode(types.StandardCursorType, text, node, out var enumConstantNode))
if (TypeSystemHelpers.TryGetEnumValueNode(types.StandardCursorType, text, node, false, out var enumConstantNode))
{
var cursorTypeRef = new XamlAstClrTypeReference(node, types.Cursor, false);
result = new XamlAstNewClrObjectNode(node, cursorTypeRef, types.CursorTypeConstructor, new List<IXamlAstValueNode> { enumConstantNode });
return true;
}
}
@ -270,12 +270,33 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions
}
}
if (type.Equals(types.Uri))
{
var uriText = text.Trim();
var kind = ((!uriText?.StartsWith("/") == true) ? UriKind.Absolute : UriKind.Relative);
if (string.IsNullOrWhiteSpace(uriText) || !Uri.TryCreate(uriText, kind, out var _))
{
throw new XamlX.XamlLoadException($"Unable to parse text {uriText} as a {kind} uri.", node);
}
result = new XamlAstNewClrObjectNode(node
, new(node, types.Uri, false)
, types.UriConstructor
, new List<IXamlAstValueNode>()
{
new XamlConstantNode(node, context.Configuration.WellKnownTypes.String, uriText),
new XamlConstantNode(node, types.UriKind, (int)kind),
});
return true;
}
result = null;
return false;
}
private static bool ConvertDefinitionList(
IXamlAstValueNode node,
IXamlAstValueNode node,
string text,
AvaloniaXamlIlWellKnownTypes types,
IXamlType listType,

5
src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlMetadataRemover.cs

@ -1,4 +1,3 @@
using System.Linq;
using XamlX.Ast;
using XamlX.Transform;
@ -8,8 +7,8 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers
{
public IXamlAstNode Transform(AstTransformationContext context, IXamlAstNode node)
{
if (node is AvaloniaXamlIlTargetTypeMetadataNode targetType)
return targetType.Value;
while (node is AvaloniaXamlIlTargetTypeMetadataNode targetType)
node = targetType.Value;
return node;
}

403
src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlOptionMarkupExtensionTransformer.cs

@ -0,0 +1,403 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection.Emit;
using XamlX;
using XamlX.Ast;
using XamlX.Emit;
using XamlX.IL;
using XamlX.Transform;
using XamlX.TypeSystem;
namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers;
internal class AvaloniaXamlIlOptionMarkupExtensionTransformer : IXamlAstTransformer
{
public IXamlAstNode Transform(AstTransformationContext context, IXamlAstNode node)
{
if (node is XamlMarkupExtensionNode
{
Value: XamlAstObjectNode { Type: XamlAstClrTypeReference { Type: { } type } } objectNode
} markupExtensionNode
&& type.FindMethods(m => m.IsPublic && m.Parameters.Count is 1 or 2 && m.ReturnType == context.Configuration.WellKnownTypes.Boolean && m.Name == "ShouldProvideOption").ToArray() is { } methods
&& methods.Any())
{
var optionAttribute = context.GetAvaloniaTypes().MarkupExtensionOptionAttribute;
var defaultOptionAttribute = context.GetAvaloniaTypes().MarkupExtensionDefaultOptionAttribute;
var typeArgument = type.GenericArguments?.FirstOrDefault();
IXamlAstValueNode defaultValue = null;
var values = new List<OptionsMarkupExtensionBranch>();
if (objectNode.Arguments.FirstOrDefault() is { } argument)
{
var hasDefaultProp = objectNode.Type.GetClrType().GetAllProperties().Any(p =>
p.CustomAttributes.Any(a => a.Type == defaultOptionAttribute));
if (hasDefaultProp)
{
if (objectNode.Arguments.Count > 1)
{
throw new XamlParseException("Options MarkupExtensions allow only single argument", objectNode);
}
defaultValue = TransformNode(new[] { argument }, typeArgument, objectNode);
objectNode.Arguments.Remove(argument);
}
}
foreach (var extProp in objectNode.Children.OfType<XamlAstXamlPropertyValueNode>().ToArray())
{
if (!extProp.Values.Any())
{
continue;
}
var shouldRemoveProp = false;
var onObjs = extProp.Values.OfType<XamlAstObjectNode>()
.Where(o => o.Type.GetClrType() == context.GetAvaloniaTypes().OnExtensionType).ToArray();
if (onObjs.Any())
{
shouldRemoveProp = true;
foreach (var onObj in onObjs)
{
var optionsPropNode = onObj.Children.OfType<XamlAstXamlPropertyValueNode>()
.SingleOrDefault(v => v.Property.GetClrProperty().Name == "Options")
?.Values.Single();
var options = (optionsPropNode as XamlAstTextNode)?.Text?.Split(new[] { ',', ' ' }, StringSplitOptions.RemoveEmptyEntries)
?? Array.Empty<string>();
if (options.Length == 0)
{
throw new XamlParseException("On.Options string must be set", onObj);
}
var content = onObj.Children.OfType<XamlAstXamlPropertyValueNode>()
.SingleOrDefault(v => v.Property.GetClrProperty().Name == "Content");
if (content is null)
{
throw new XamlParseException("On content object must be set", onObj);
}
var propertiesSet = options
.Select(o => type.GetAllProperties()
.FirstOrDefault(p => o.Equals(p.Name, StringComparison.Ordinal))
?? throw new XamlParseException($"Property \"{o}\" wasn't found on the \"{type.Name}\" type", onObj))
.ToArray();
foreach (var propertySet in propertiesSet)
{
AddBranchNode(content.Values, propertySet.CustomAttributes, content);
}
}
}
else
{
shouldRemoveProp = AddBranchNode(extProp.Values, extProp.Property.GetClrProperty().CustomAttributes, extProp);
}
if (shouldRemoveProp)
{
objectNode.Children.Remove(extProp);
}
}
if (defaultValue is null && !values.Any())
{
throw new XamlParseException("Options markup extension requires at least one option to be set", objectNode);
}
return new OptionsMarkupExtensionNode(
markupExtensionNode, values.ToArray(), defaultValue,
context.Configuration.TypeMappings.ServiceProvider);
bool AddBranchNode(
IReadOnlyCollection<IXamlAstValueNode> valueNodes,
IReadOnlyCollection<IXamlCustomAttribute> propAttributes,
IXamlLineInfo li)
{
var transformed = TransformNode(valueNodes, typeArgument, li);
if (propAttributes.FirstOrDefault(a => a.Type == defaultOptionAttribute) is { } defAttr)
{
defaultValue = transformed;
return true;
}
else if (propAttributes.FirstOrDefault(a => a.Type == optionAttribute) is { } optAttr)
{
var option = optAttr.Parameters.Single();
if (option is null)
{
throw new XamlParseException("MarkupExtension option must not be null", li);
}
var optionAsString = option.ToString();
IXamlAstValueNode optionNode = null;
foreach (var method in methods)
{
try
{
var targetType = method.Parameters.Last();
if (targetType.FullName == "System.Type")
{
if (option is IXamlType typeOption)
{
optionNode = new XamlTypeExtensionNode(li,
new XamlAstClrTypeReference(li, typeOption, false), targetType);
}
}
else if (targetType == context.Configuration.WellKnownTypes.String)
{
optionNode = new XamlConstantNode(li, targetType, optionAsString);
}
else if (targetType.IsEnum)
{
if (TypeSystemHelpers.TryGetEnumValueNode(targetType, optionAsString, li, false,
out var enumConstantNode))
{
optionNode = enumConstantNode;
}
}
else if (TypeSystemHelpers.ParseConstantIfTypeAllows(optionAsString, targetType, li,
out var constantNode))
{
optionNode = constantNode;
}
}
catch (FormatException)
{
// try next method overload
}
if (optionNode is not null)
{
values.Add(new OptionsMarkupExtensionBranch(optionNode, transformed, method));
return true;
}
}
throw new XamlParseException($"Option value \"{optionAsString}\" is not assignable to any of existing ShouldProvideOption methods", li);
}
return false;
}
}
return node;
IXamlAstValueNode TransformNode(
IReadOnlyCollection<IXamlAstValueNode> values,
IXamlType suggestedType,
IXamlLineInfo line)
{
if (suggestedType is not null)
{
values = values
.Select(v => XamlTransformHelpers
.TryGetCorrectlyTypedValue(context, v, suggestedType, out var converted)
? converted : v)
.ToArray();
}
if (values.Count > 1)
{
throw new XamlParseException("Options markup extension supports only a singular value", line);
}
return values.Single();
}
}
internal sealed class OptionsMarkupExtensionNode : XamlMarkupExtensionNode, IXamlAstValueNode
{
private readonly IXamlType _contextParameter;
public OptionsMarkupExtensionNode(
XamlMarkupExtensionNode original,
OptionsMarkupExtensionBranch[] branches,
IXamlAstValueNode defaultNode,
IXamlType contextParameter)
: base(
original.Value,
new OptionsMarkupExtensionMethod(new OptionsMarkupExtensionNodesContainer(branches, defaultNode), original.Value.Type.GetClrType(), contextParameter),
original.Value)
{
_contextParameter = contextParameter;
}
public new OptionsMarkupExtensionMethod ProvideValue => (OptionsMarkupExtensionMethod)base.ProvideValue;
IXamlAstTypeReference IXamlAstValueNode.Type => new XamlAstClrTypeReference(this, ProvideValue.ReturnType, false);
public override void VisitChildren(IXamlAstVisitor visitor)
{
ProvideValue.ExtensionNodeContainer.Visit(visitor);
base.VisitChildren(visitor);
}
public bool ConvertToReturnType(AstTransformationContext context, IXamlType type, out OptionsMarkupExtensionNode res)
{
IXamlAstValueNode convertedDefaultNode = null;
if (ProvideValue.ExtensionNodeContainer.DefaultNode is { } defaultNode)
{
if (!XamlTransformHelpers.TryGetCorrectlyTypedValue(context, defaultNode, type, out convertedDefaultNode))
{
res = null;
return false;
}
}
var convertedBranches = ProvideValue.ExtensionNodeContainer.Branches.Select(b => XamlTransformHelpers
.TryGetCorrectlyTypedValue(context, b.Value, type, out var convertedValue) ?
new OptionsMarkupExtensionBranch(b.Option, convertedValue, b.ConditionMethod) :
null).ToArray();
if (convertedBranches.Any(b => b is null))
{
res = null;
return false;
}
res = new OptionsMarkupExtensionNode(this, convertedBranches, convertedDefaultNode, _contextParameter);
return true;
}
}
internal sealed class OptionsMarkupExtensionNodesContainer : XamlAstNode
{
public OptionsMarkupExtensionNodesContainer(
OptionsMarkupExtensionBranch[] branches,
IXamlAstValueNode defaultNode) : base(branches.FirstOrDefault()?.Value ?? defaultNode)
{
Branches = branches;
DefaultNode = defaultNode;
}
public OptionsMarkupExtensionBranch[] Branches { get; }
public IXamlAstValueNode DefaultNode { get; private set; }
public override void VisitChildren(IXamlAstVisitor visitor)
{
VisitList(Branches, visitor);
DefaultNode = (IXamlAstValueNode)DefaultNode?.Visit(visitor);
}
public IXamlType GetReturnType()
{
var types = Branches.Select(b => b.Value.Type);
if (DefaultNode?.Type is { } type)
{
types = types.Concat(new [] { type });
}
return types.Select(t => t.GetClrType()).ToArray().GetCommonBaseClass();
}
}
internal sealed class OptionsMarkupExtensionBranch : XamlAstNode
{
public OptionsMarkupExtensionBranch(IXamlAstValueNode option, IXamlAstValueNode value, IXamlMethod conditionMethod) : base(value)
{
Option = option;
Value = value;
ConditionMethod = conditionMethod;
}
public IXamlAstValueNode Option { get; set; }
public IXamlAstValueNode Value { get; set; }
public IXamlMethod ConditionMethod { get; }
public bool HasContext => ConditionMethod.Parameters.Count > 1;
public override void VisitChildren(IXamlAstVisitor visitor)
{
Option = (IXamlAstValueNode)Option.Visit(visitor);
Value = (IXamlAstValueNode)Value.Visit(visitor);
}
}
internal sealed class OptionsMarkupExtensionMethod : IXamlCustomEmitMethodWithContext<IXamlILEmitter, XamlILNodeEmitResult>
{
public OptionsMarkupExtensionMethod(
OptionsMarkupExtensionNodesContainer extensionNodeContainer,
IXamlType declaringType,
IXamlType contextParameter)
{
ExtensionNodeContainer = extensionNodeContainer;
DeclaringType = declaringType;
Parameters = extensionNodeContainer.Branches.Any(c => c.HasContext) ?
new[] { contextParameter } :
Array.Empty<IXamlType>();
}
public OptionsMarkupExtensionNodesContainer ExtensionNodeContainer { get; }
public string Name => "ProvideValue";
public bool IsPublic => true;
public bool IsStatic => false;
public IXamlType ReturnType => ExtensionNodeContainer.GetReturnType();
public IReadOnlyList<IXamlType> Parameters { get; }
public IXamlType DeclaringType { get; }
public IXamlMethod MakeGenericMethod(IReadOnlyList<IXamlType> typeArguments) => throw new NotImplementedException();
public IReadOnlyList<IXamlCustomAttribute> CustomAttributes => Array.Empty<IXamlCustomAttribute>();
public void EmitCall(XamlEmitContext<IXamlILEmitter, XamlILNodeEmitResult> context, IXamlILEmitter codeGen)
{
// At this point this extension will be called from MarkupExtensionEmitter.
// Since it's a "fake" method, we share stack and locals with parent method.
// Real ProvideValue method would pop 2 parameters from the stack and return one. This method should do the same.
// At this point we will have on stack:
// - context (if parameters > 1)
// - markup ext "@this" instance (always)
// We always pop context from the stack, as this method decide by itself either context is needed.
// We store "@this" as a local variable. But only if any conditional method is an instance method.
IXamlLocal @this = null;
if (Parameters.Count > 0)
{
codeGen.Pop();
}
if (ExtensionNodeContainer.Branches.Any(b => !b.ConditionMethod.IsStatic))
{
codeGen.Stloc(@this = codeGen.DefineLocal(DeclaringType));
}
else
{
codeGen.Pop();
}
// Iterate over all branches and push prepared locals into the stack if needed.
var ret = codeGen.DefineLabel();
foreach (var branch in ExtensionNodeContainer.Branches)
{
var next = codeGen.DefineLabel();
if (branch.HasContext)
{
codeGen.Ldloc(context.ContextLocal);
}
if (!branch.ConditionMethod.IsStatic)
{
codeGen.Ldloc(@this);
}
context.Emit(branch.Option, codeGen, branch.Option.Type.GetClrType());
codeGen.EmitCall(branch.ConditionMethod);
codeGen.Brfalse(next);
context.Emit(branch.Value, codeGen, branch.Value.Type.GetClrType());
codeGen.Br(ret);
codeGen.MarkLabel(next);
}
if (ExtensionNodeContainer.DefaultNode is {} defaultNode)
{
// Nop is needed, otherwise Label wouldn't be set on nested CALL op (limitation of our IL validator).
codeGen.Emit(OpCodes.Nop);
context.Emit(defaultNode, codeGen, defaultNode.Type.GetClrType());
}
else
{
codeGen.EmitDefault(ReturnType);
}
codeGen.MarkLabel(ret);
}
public bool Equals(IXamlMethod other) => ReferenceEquals(this, other);
}
}

53
src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlSelectorTransformer.cs

@ -128,13 +128,13 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers
break;
}
case SelectorGrammar.ChildSyntax child:
result = new XamlIlCombinatorSelector(result, XamlIlCombinatorSelector.SelectorType.Child);
result = new XamlIlCombinatorSelector(result, XamlIlCombinatorSelector.CombinatorSelectorType.Child);
break;
case SelectorGrammar.DescendantSyntax descendant:
result = new XamlIlCombinatorSelector(result, XamlIlCombinatorSelector.SelectorType.Descendant);
result = new XamlIlCombinatorSelector(result, XamlIlCombinatorSelector.CombinatorSelectorType.Descendant);
break;
case SelectorGrammar.TemplateSyntax template:
result = new XamlIlCombinatorSelector(result, XamlIlCombinatorSelector.SelectorType.Template);
result = new XamlIlCombinatorSelector(result, XamlIlCombinatorSelector.CombinatorSelectorType.Template);
break;
case SelectorGrammar.NotSyntax not:
result = new XamlIlNotSelector(result, Create(not.Argument, typeResolver));
@ -186,18 +186,50 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers
=> TypeReferenceResolver.ResolveType(context, $"{p}:{n}", true, node, true));
pn.Values[0] = selector;
return new AvaloniaXamlIlTargetTypeMetadataNode(on,
var templateType = GetLastTemplateTypeFromSelector(selector);
var styleNode = new AvaloniaXamlIlTargetTypeMetadataNode(on,
new XamlAstClrTypeReference(selector, selector.TargetType, false),
AvaloniaXamlIlTargetTypeMetadataNode.ScopeTypes.Style);
return templateType switch
{
null => styleNode,
_ => new AvaloniaXamlIlTargetTypeMetadataNode(styleNode,
new XamlAstClrTypeReference(styleNode, templateType, false),
AvaloniaXamlIlTargetTypeMetadataNode.ScopeTypes.ControlTemplate)
};
}
}
private static IXamlType GetLastTemplateTypeFromSelector(XamlIlSelectorNode node)
{
while (node is not null)
{
if (node is XamlIlCombinatorSelector
{
SelectorType: XamlIlCombinatorSelector.CombinatorSelectorType.Template
} templateSelector)
{
if (templateSelector.Previous is XamlIlNestingSelector nestingSelector)
{
return nestingSelector.TargetType;
}
if (templateSelector.Previous is XamlIlTypeSelector typeSelector)
{
return typeSelector.TargetType;
}
return null;
}
node = node.Previous;
}
return null;
}
}
abstract class XamlIlSelectorNode : XamlAstNode, IXamlAstValueNode, IXamlAstEmitableNode<IXamlILEmitter, XamlILNodeEmitResult>
{
protected XamlIlSelectorNode Previous { get; }
internal XamlIlSelectorNode Previous { get; }
public abstract IXamlType TargetType { get; }
public XamlIlSelectorNode(XamlIlSelectorNode previous,
@ -289,19 +321,20 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers
class XamlIlCombinatorSelector : XamlIlSelectorNode
{
private readonly SelectorType _type;
private readonly CombinatorSelectorType _type;
public enum SelectorType
public enum CombinatorSelectorType
{
Child,
Descendant,
Template
}
public XamlIlCombinatorSelector(XamlIlSelectorNode previous, SelectorType type) : base(previous)
public XamlIlCombinatorSelector(XamlIlSelectorNode previous, CombinatorSelectorType type) : base(previous)
{
_type = type;
}
public CombinatorSelectorType SelectorType => _type;
public override IXamlType TargetType => null;
protected override void DoEmit(XamlEmitContext<IXamlILEmitter, XamlILNodeEmitResult> context, IXamlILEmitter codeGen)
{

15
src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlWellKnownTypes.cs

@ -9,6 +9,7 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers
{
class AvaloniaXamlIlWellKnownTypes
{
public IXamlType RuntimeHelpers { get; }
public IXamlType AvaloniaObject { get; }
public IXamlType IAvaloniaObject { get; }
public IXamlType BindingPriority { get; }
@ -29,6 +30,9 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers
public IXamlType AssignBindingAttribute { get; }
public IXamlType DependsOnAttribute { get; }
public IXamlType DataTypeAttribute { get; }
public IXamlType MarkupExtensionOptionAttribute { get; }
public IXamlType MarkupExtensionDefaultOptionAttribute { get; }
public IXamlType OnExtensionType { get; }
public IXamlType UnsetValueType { get; }
public IXamlType StyledElement { get; }
public IXamlType IStyledElement { get; }
@ -101,9 +105,13 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers
public IXamlType IResourceDictionary { get; }
public IXamlType ResourceDictionary { get; }
public IXamlMethod ResourceDictionaryDeferredAdd { get; }
public IXamlType UriKind { get; }
public IXamlConstructor UriConstructor { get; }
public AvaloniaXamlIlWellKnownTypes(TransformerConfiguration cfg)
{
RuntimeHelpers = cfg.TypeSystem.GetType("Avalonia.Markup.Xaml.XamlIl.Runtime.XamlIlRuntimeHelpers");
XamlIlTypes = cfg.WellKnownTypes;
AvaloniaObject = cfg.TypeSystem.GetType("Avalonia.AvaloniaObject");
IAvaloniaObject = cfg.TypeSystem.GetType("Avalonia.IAvaloniaObject");
@ -125,6 +133,9 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers
AssignBindingAttribute = cfg.TypeSystem.GetType("Avalonia.Data.AssignBindingAttribute");
DependsOnAttribute = cfg.TypeSystem.GetType("Avalonia.Metadata.DependsOnAttribute");
DataTypeAttribute = cfg.TypeSystem.GetType("Avalonia.Metadata.DataTypeAttribute");
MarkupExtensionOptionAttribute = cfg.TypeSystem.GetType("Avalonia.Metadata.MarkupExtensionOptionAttribute");
MarkupExtensionDefaultOptionAttribute = cfg.TypeSystem.GetType("Avalonia.Metadata.MarkupExtensionDefaultOptionAttribute");
OnExtensionType = cfg.TypeSystem.GetType("Avalonia.Markup.Xaml.MarkupExtensions.On");
AvaloniaObjectBindMethod = AvaloniaObjectExtensions.FindMethod("Bind", IDisposable, false, IAvaloniaObject,
AvaloniaProperty,
IBinding, cfg.WellKnownTypes.Object);
@ -227,6 +238,8 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers
cfg.TypeSystem.GetType("System.Func`2").MakeGenericType(
cfg.TypeSystem.GetType("System.IServiceProvider"),
XamlIlTypes.Object));
UriKind = cfg.TypeSystem.GetType("System.UriKind");
UriConstructor = Uri.GetConstructor(new List<IXamlType>() { cfg.WellKnownTypes.String, UriKind });
}
}
@ -239,7 +252,7 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers
ctx.SetItem(rv = new AvaloniaXamlIlWellKnownTypes(ctx.Configuration));
return rv;
}
public static AvaloniaXamlIlWellKnownTypes GetAvaloniaTypes(this XamlEmitContext<IXamlILEmitter, XamlILNodeEmitResult> ctx)
{
if (ctx.TryGetItem<AvaloniaXamlIlWellKnownTypes>(out var rv))

2
src/Markup/Avalonia.Markup.Xaml.Loader/xamlil.github

@ -1 +1 @@
Subproject commit c1c0594ec2c35b08988183b1a5b3e34dfa19179d
Subproject commit 880ba5742e52b67afda048c4023cf7e3c3c16a46

3
src/Markup/Avalonia.Markup.Xaml/Avalonia.Markup.Xaml.csproj

@ -29,6 +29,9 @@
<Compile Include="MarkupExtensions\CompiledBindings\StrongTypeCastNode.cs" />
<Compile Include="MarkupExtensions\CompiledBindings\TaskStreamPlugin.cs" />
<Compile Include="MarkupExtensions\DynamicResourceExtension.cs" />
<Compile Include="MarkupExtensions\On.cs" />
<Compile Include="MarkupExtensions\OnFormFactorExtension.cs" />
<Compile Include="MarkupExtensions\OnPlatformExtension.cs" />
<Compile Include="MarkupExtensions\ResolveByNameExtension.cs" />
<Compile Include="MarkupExtensions\ResourceInclude.cs" />
<Compile Include="MarkupExtensions\StaticResourceExtension.cs" />

15
src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/On.cs

@ -0,0 +1,15 @@
#nullable enable
using System.Collections.Generic;
using Avalonia.Metadata;
namespace Avalonia.Markup.Xaml.MarkupExtensions;
public class On<TReturn>
{
public IReadOnlyList<string> Options { get; } = new List<string>();
[Content]
public TReturn? Content { get; set; }
}
public class On : On<object> {}

60
src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/OnFormFactorExtension.cs

@ -0,0 +1,60 @@
#nullable enable
using System;
using Avalonia.Metadata;
using Avalonia.Platform;
namespace Avalonia.Markup.Xaml.MarkupExtensions;
public class OnFormFactorExtension : OnFormFactorExtensionBase<object, On>
{
public OnFormFactorExtension()
{
}
public OnFormFactorExtension(object defaultValue)
{
Default = defaultValue;
}
public static bool ShouldProvideOption(IServiceProvider serviceProvider, FormFactorType option)
{
return serviceProvider.GetService<IRuntimePlatform>().GetRuntimeInfo().FormFactor == option;
}
}
public class OnFormFactorExtension<TReturn> : OnFormFactorExtensionBase<TReturn, On<TReturn>>
{
public OnFormFactorExtension()
{
}
public OnFormFactorExtension(TReturn defaultValue)
{
Default = defaultValue;
}
public static bool ShouldProvideOption(IServiceProvider serviceProvider, FormFactorType option)
{
return serviceProvider.GetService<IRuntimePlatform>().GetRuntimeInfo().FormFactor == option;
}
}
public abstract class OnFormFactorExtensionBase<TReturn, TOn> : IAddChild<TOn>
where TOn : On<TReturn>
{
[MarkupExtensionDefaultOption]
public TReturn? Default { get; set; }
[MarkupExtensionOption(FormFactorType.Desktop)]
public TReturn? Desktop { get; set; }
[MarkupExtensionOption(FormFactorType.Mobile)]
public TReturn? Mobile { get; set; }
// Required for the compiler, will be replaced with actual method compile time.
public object ProvideValue() { return this; }
void IAddChild<TOn>.AddChild(TOn child) {}
}

73
src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/OnPlatformExtension.cs

@ -0,0 +1,73 @@
#nullable enable
using System;
using Avalonia.Metadata;
using Avalonia.Platform;
namespace Avalonia.Markup.Xaml.MarkupExtensions;
public class OnPlatformExtension : OnPlatformExtensionBase<object, On>
{
public OnPlatformExtension()
{
}
public OnPlatformExtension(object defaultValue)
{
Default = defaultValue;
}
public static bool ShouldProvideOption(IServiceProvider serviceProvider, OperatingSystemType option)
{
return serviceProvider.GetService<IRuntimePlatform>().GetRuntimeInfo().OperatingSystem == option;
}
}
public class OnPlatformExtension<TReturn> : OnPlatformExtensionBase<TReturn, On<TReturn>>
{
public OnPlatformExtension()
{
}
public OnPlatformExtension(TReturn defaultValue)
{
Default = defaultValue;
}
public static bool ShouldProvideOption(IServiceProvider serviceProvider, OperatingSystemType option)
{
return serviceProvider.GetService<IRuntimePlatform>().GetRuntimeInfo().OperatingSystem == option;
}
}
public abstract class OnPlatformExtensionBase<TReturn, TOn> : IAddChild<TOn>
where TOn : On<TReturn>
{
[MarkupExtensionDefaultOption]
public TReturn? Default { get; set; }
[MarkupExtensionOption(OperatingSystemType.WinNT)]
public TReturn? Windows { get; set; }
[MarkupExtensionOption(OperatingSystemType.OSX)]
// ReSharper disable once InconsistentNaming
public TReturn? macOS { get; set; }
[MarkupExtensionOption(OperatingSystemType.Linux)]
public TReturn? Linux { get; set; }
[MarkupExtensionOption(OperatingSystemType.Android)]
public TReturn? Android { get; set; }
[MarkupExtensionOption(OperatingSystemType.iOS)]
// ReSharper disable once InconsistentNaming
public TReturn? iOS { get; set; }
[MarkupExtensionOption(OperatingSystemType.Browser)]
public TReturn? Browser { get; set; }
// Required for the compiler, will be replaced with actual method compile time.
public object ProvideValue() { return this; }
void IAddChild<TOn>.AddChild(TOn child) {}
}

22
src/Markup/Avalonia.Markup.Xaml/XamlIl/Runtime/XamlIlRuntimeHelpers.cs

@ -1,11 +1,12 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Reflection;
using Avalonia.Controls;
using Avalonia.Controls.Templates;
using Avalonia.Data;
using Avalonia.Platform;
// ReSharper disable UnusedMember.Global
// ReSharper disable UnusedParameter.Global
@ -18,7 +19,7 @@ namespace Avalonia.Markup.Xaml.XamlIl.Runtime
{
return DeferredTransformationFactoryV2<IControl>(builder, provider);
}
public static Func<IServiceProvider, object> DeferredTransformationFactoryV2<T>(Func<IServiceProvider, object> builder,
IServiceProvider provider)
{
@ -31,7 +32,7 @@ namespace Avalonia.Markup.Xaml.XamlIl.Runtime
var scope = parentScope != null ? new ChildNameScope(parentScope) : (INameScope)new NameScope();
var obj = builder(new DeferredParentServiceProvider(sp, resourceNodes, rootObject, scope));
scope.Complete();
if(typeof(T) == typeof(IControl))
return new ControlTemplateResult((IControl)obj, scope);
@ -107,9 +108,9 @@ namespace Avalonia.Markup.Xaml.XamlIl.Runtime
throw new ArgumentException("Don't know what to do with " + value.GetType());
}
public static IServiceProvider CreateInnerServiceProviderV1(IServiceProvider compiled)
public static IServiceProvider CreateInnerServiceProviderV1(IServiceProvider compiled)
=> new InnerServiceProvider(compiled);
class InnerServiceProvider : IServiceProvider
{
private readonly IServiceProvider _compiledProvider;
@ -136,7 +137,7 @@ namespace Avalonia.Markup.Xaml.XamlIl.Runtime
{
_nsInfo = nsInfo;
}
public Type Resolve(string qualifiedTypeName)
{
var sp = qualifiedTypeName.Split(new[] {':'}, 2);
@ -166,22 +167,27 @@ namespace Avalonia.Markup.Xaml.XamlIl.Runtime
return new RootServiceProvider(new NameScope());
}
#line default
class RootServiceProvider : IServiceProvider, IAvaloniaXamlIlParentStackProvider
{
private readonly INameScope _nameScope;
private readonly IRuntimePlatform _runtimePlatform;
public RootServiceProvider(INameScope nameScope)
{
_nameScope = nameScope;
_runtimePlatform = AvaloniaLocator.Current.GetService<IRuntimePlatform>();
}
public object GetService(Type serviceType)
{
if (serviceType == typeof(INameScope))
return _nameScope;
if (serviceType == typeof(IAvaloniaXamlIlParentStackProvider))
return this;
if (serviceType == typeof(IRuntimePlatform))
return _runtimePlatform ?? throw new KeyNotFoundException($"{nameof(IRuntimePlatform)} was not registered");
return null;
}

10
src/Skia/Avalonia.Skia/PlatformRenderInterface.cs

@ -242,7 +242,7 @@ namespace Avalonia.Skia
"Current GPU acceleration backend does not support OpenGL integration");
}
public IGlyphRunImpl CreateGlyphRun(IGlyphTypeface glyphTypeface, double fontRenderingEmSize, IReadOnlyList<ushort> glyphIndices,
public IGlyphRunImpl CreateGlyphRun(IGlyphTypeface glyphTypeface, double fontRenderingEmSize, IReadOnlyList<ushort> glyphIndices,
IReadOnlyList<double> glyphAdvances, IReadOnlyList<Vector> glyphOffsets)
{
if (glyphTypeface == null)
@ -273,7 +273,7 @@ namespace Avalonia.Skia
var count = glyphIndices.Count;
if(glyphOffsets != null && glyphAdvances != null)
if (glyphOffsets != null && glyphAdvances != null)
{
var runBuffer = builder.AllocatePositionedRun(font, count);
@ -295,7 +295,7 @@ namespace Avalonia.Skia
}
else
{
if(glyphAdvances != null)
if (glyphAdvances != null)
{
var runBuffer = builder.AllocateHorizontalRun(font, count, 0);
@ -304,7 +304,7 @@ namespace Avalonia.Skia
var currentX = 0.0;
for (int i = 0; i < glyphOffsets.Count; i++)
for (int i = 0; i < glyphAdvances.Count; i++)
{
glyphSpan[i] = glyphIndices[i];
@ -319,7 +319,7 @@ namespace Avalonia.Skia
var glyphSpan = runBuffer.GetGlyphSpan();
for (int i = 0; i < glyphOffsets.Count; i++)
for (int i = 0; i < glyphIndices.Count; i++)
{
glyphSpan[i] = glyphIndices[i];
}

2
src/Web/Avalonia.Web/Avalonia.Web.targets

@ -12,7 +12,7 @@
<WasmBuildNative>true</WasmBuildNative>
</PropertyGroup>
<PropertyGroup Condition="'$(UseAvaloniaWasmDefaultOptimizations)'=='True'">
<PropertyGroup Condition="'$(UseAvaloniaWasmDefaultOptimizations)'=='True' And '$(Configuration)' == 'Release'">
<PublishTrimmed>true</PublishTrimmed>
<TrimMode>full</TrimMode>
<InvariantGlobalization>true</InvariantGlobalization>

26
src/Web/Avalonia.Web/BrowserRuntimePlatform.cs

@ -0,0 +1,26 @@
using System;
using System.Runtime.InteropServices;
using System.Runtime.InteropServices.JavaScript;
using System.Text.RegularExpressions;
using Avalonia.Platform;
using Avalonia.Web.Interop;
namespace Avalonia.Web;
internal class BrowserRuntimePlatform : StandardRuntimePlatform
{
private static readonly Lazy<RuntimePlatformInfo> Info = new(() =>
{
var result = new RuntimePlatformInfo
{
IsCoreClr = true, // WASM browser is always CoreCLR
IsBrowser = true, // BrowserRuntimePlatform only runs on Browser.
OperatingSystem = OperatingSystemType.Browser,
IsMobile = AvaloniaModule.IsMobile()
};
return result;
});
public override RuntimePlatformInfo GetRuntimeInfo() => Info.Value;
}

2
src/Web/Avalonia.Web/BrowserSingleViewLifetime.cs

@ -3,6 +3,7 @@ using Avalonia.Controls;
using Avalonia.Controls.ApplicationLifetimes;
using Avalonia.Web.Skia;
using System.Runtime.Versioning;
using Avalonia.Platform;
namespace Avalonia.Web;
@ -23,7 +24,6 @@ public class BrowserPlatformOptions
public Func<string, string> FrameworkAssetPathResolver { get; set; } = new(fileName => $"./{fileName}");
}
[SupportedOSPlatform("browser")]
public static class WebAppBuilder
{

5
src/Web/Avalonia.Web/Interop/AvaloniaModule.cs

@ -3,7 +3,7 @@ using System.Threading.Tasks;
namespace Avalonia.Web.Interop;
internal static class AvaloniaModule
internal static partial class AvaloniaModule
{
public const string MainModuleName = "avalonia";
public const string StorageModuleName = "storage";
@ -19,4 +19,7 @@ internal static class AvaloniaModule
var options = AvaloniaLocator.Current.GetService<BrowserPlatformOptions>() ?? new BrowserPlatformOptions();
return JSHost.ImportAsync(StorageModuleName, options.FrameworkAssetPathResolver("storage.js"));
}
[JSImport("Caniuse.isMobile", AvaloniaModule.MainModuleName)]
public static partial bool IsMobile();
}

19
src/Web/Avalonia.Web/WindowingPlatform.cs

@ -8,7 +8,7 @@ using Avalonia.Threading;
namespace Avalonia.Web
{
internal class BrowserWindowingPlatform : IWindowingPlatform, IPlatformSettings, IPlatformThreadingInterface
internal class BrowserWindowingPlatform : IWindowingPlatform, IPlatformThreadingInterface
{
private bool _signaled;
private static KeyboardDevice? s_keyboard;
@ -31,12 +31,14 @@ namespace Avalonia.Web
public static void Register()
{
var instance = new BrowserWindowingPlatform();
s_keyboard = new KeyboardDevice();
AvaloniaLocator.CurrentMutable
.Bind<IRuntimePlatform>().ToSingleton<BrowserRuntimePlatform>()
.Bind<IClipboard>().ToSingleton<ClipboardImpl>()
.Bind<ICursorFactory>().ToSingleton<CssCursorFactory>()
.Bind<IKeyboardDevice>().ToConstant(s_keyboard)
.Bind<IPlatformSettings>().ToConstant(instance)
.Bind<IPlatformSettings>().ToSingleton<DefaultPlatformSettings>()
.Bind<IPlatformThreadingInterface>().ToConstant(instance)
.Bind<IRenderLoop>().ToConstant(new RenderLoop())
.Bind<IRenderTimer>().ToConstant(ManualTriggerRenderTimer.Instance)
@ -45,13 +47,6 @@ namespace Avalonia.Web
.Bind<PlatformHotkeyConfiguration>().ToSingleton<PlatformHotkeyConfiguration>();
}
public Size DoubleClickSize { get; } = new Size(2, 2);
public TimeSpan DoubleClickTime { get; } = TimeSpan.FromMilliseconds(500);
public Size TouchDoubleClickSize => new Size(16, 16);
public TimeSpan TouchDoubleClickTime => DoubleClickTime;
public void RunLoop(CancellationToken cancellationToken)
{
throw new NotSupportedException();
@ -71,11 +66,11 @@ namespace Avalonia.Web
{
if (_signaled)
return;
_signaled = true;
IDisposable? disp = null;
disp = GetRuntimePlatform()
.StartSystemTimer(TimeSpan.FromMilliseconds(1),
() =>

18
src/Web/Avalonia.Web/webapp/modules/avalonia/caniuse.ts

@ -1,13 +1,25 @@
export class Caniuse {
public static canShowOpenFilePicker(): boolean {
return typeof window.showOpenFilePicker !== "undefined";
return typeof globalThis.showOpenFilePicker !== "undefined";
}
public static canShowSaveFilePicker(): boolean {
return typeof window.showSaveFilePicker !== "undefined";
return typeof globalThis.showSaveFilePicker !== "undefined";
}
public static canShowDirectoryPicker(): boolean {
return typeof window.showDirectoryPicker !== "undefined";
return typeof globalThis.showDirectoryPicker !== "undefined";
}
public static isMobile(): boolean {
const userAgentData = (globalThis.navigator as any)?.userAgentData;
if (userAgentData) {
return userAgentData.mobile;
}
const userAgent = navigator.userAgent;
const regex1 = /(android|bb\d+|meego).+mobile|avantgo|bada\/|blackberry|blazer|compal|elaine|fennec|hiptop|iemobile|ip(hone|od)|iris|kindle|lge |maemo|midp|mmp|mobile.+firefox|netfront|opera m(ob|in)i|palm( os)?|phone|p(ixi|re)\/|plucker|pocket|psp|series(4|6)0|symbian|treo|up\.(browser|link)|vodafone|wap|windows ce|xda|xiino/i;
const regex2 = /1207|6310|6590|3gso|4thp|50[1-6]i|770s|802s|a wa|abac|ac(er|oo|s)|ai(ko|rn)|al(av|ca|co)|amoi|an(ex|ny|yw)|aptu|ar(ch|go)|as(te|us)|attw|au(di|m|r |s )|avan|be(ck|ll|nq)|bi(lb|rd)|bl(ac|az)|br(e|v)w|bumb|bw(n|u)|c55\/|capi|ccwa|cdm|cell|chtm|cldc|cmd|co(mp|nd)|craw|da(it|ll|ng)|dbte|dcs|devi|dica|dmob|do(c|p)o|ds(12|d)|el(49|ai)|em(l2|ul)|er(ic|k0)|esl8|ez([4-7]0|os|wa|ze)|fetc|fly(|_)|g1 u|g560|gene|gf5|gmo|go(\.w|od)|gr(ad|un)|haie|hcit|hd(m|p|t)|hei|hi(pt|ta)|hp( i|ip)|hsc|ht(c(| |_|a|g|p|s|t)|tp)|hu(aw|tc)|i(20|go|ma)|i230|iac( ||\/)|ibro|idea|ig01|ikom|im1k|inno|ipaq|iris|ja(t|v)a|jbro|jemu|jigs|kddi|keji|kgt( |\/)|klon|kpt |kwc|kyo(c|k)|le(no|xi)|lg( g|\/(k|l|u)|50|54|[a-w])|libw|lynx|m1w|m3ga|m50\/|ma(te|ui|xo)|mc(01|21|ca)|mcr|me(rc|ri)|mi(o8|oa|ts)|mmef|mo(01|02|bi|de|do|t(| |o|v)|zz)|mt(50|p1|v )|mwbp|mywa|n10[0-2]|n20[2-3]|n30(0|2)|n50(0|2|5)|n7(0(0|1)|10)|ne((c|m)|on|tf|wf|wg|wt)|nok(6|i)|nzph|o2im|op(ti|wv)|oran|owg1|p800|pan(a|d|t)|pdxg|pg(13|([1-8]|c))|phil|pire|pl(ay|uc)|pn2|po(ck|rt|se)|prox|psio|ptg|qaa|qc(07|12|21|32|60|[2-7]|i)|qtek|r380|r600|raks|rim9|ro(ve|zo)|s55\/|sa(ge|ma|mm|ms|ny|va)|sc(01|h|oo|p)|sdk\/|se(c(|0|1)|47|mc|nd|ri)|sgh|shar|sie(|m)|sk0|sl(45|id)|sm(al|ar|b3|it|t5)|so(ft|ny)|sp(01|h|v|v )|sy(01|mb)|t2(18|50)|t6(00|10|18)|ta(gt|lk)|tcl|tdg|tel(i|m)|tim|tmo|to(pl|sh)|ts(70|m|m3|m5)|tx9|up(\.b|g1|si)|utst|v400|v750|veri|vi(rg|te)|vk(40|5[0-3]|v)|vm40|voda|vulc|vx(52|53|60|61|70|80|81|83|85|98)|w3c(| )|webc|whit|wi(g |nc|nw)|wmlb|wonu|x700|yas|your|zeto|zte/i;
return regex1.test(userAgent) || regex2.test(userAgent.substr(0, 4));
}
}

31
src/Windows/Avalonia.Win32/Win32Platform.cs

@ -130,17 +130,6 @@ namespace Avalonia.Win32
internal static Compositor Compositor { get; private set; }
public Size DoubleClickSize => new Size(
GetSystemMetrics(SystemMetric.SM_CXDOUBLECLK),
GetSystemMetrics(SystemMetric.SM_CYDOUBLECLK));
public TimeSpan DoubleClickTime => TimeSpan.FromMilliseconds(GetDoubleClickTime());
/// <inheritdoc cref="IPlatformSettings.TouchDoubleClickSize"/>
public Size TouchDoubleClickSize => new Size(16,16);
/// <inheritdoc cref="IPlatformSettings.TouchDoubleClickTime"/>
public TimeSpan TouchDoubleClickTime => DoubleClickTime;
public static void Initialize()
{
Initialize(new Win32PlatformOptions());
@ -398,5 +387,25 @@ namespace Avalonia.Win32
SetProcessDPIAware();
}
Size IPlatformSettings.GetTapSize(PointerType type)
{
return type switch
{
PointerType.Touch => new(10, 10),
_ => new(GetSystemMetrics(SystemMetric.SM_CXDRAG), GetSystemMetrics(SystemMetric.SM_CYDRAG)),
};
}
Size IPlatformSettings.GetDoubleTapSize(PointerType type)
{
return type switch
{
PointerType.Touch => new(16, 16),
_ => new(GetSystemMetrics(SystemMetric.SM_CXDOUBLECLK), GetSystemMetrics(SystemMetric.SM_CYDOUBLECLK)),
};
}
TimeSpan IPlatformSettings.GetDoubleTapTime(PointerType type) => TimeSpan.FromMilliseconds(GetDoubleClickTime());
}
}

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

Loading…
Cancel
Save