diff --git a/Avalonia.sln b/Avalonia.sln
index 3c2fc7437b..4e7b4cc318 100644
--- a/Avalonia.sln
+++ b/Avalonia.sln
@@ -221,8 +221,6 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Avalonia.Markup.Xaml.Loader
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Sandbox", "samples\Sandbox\Sandbox.csproj", "{11BE52AF-E2DD-4CF0-B19A-05285ACAF571}"
EndProject
-Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MicroComGenerator", "src\tools\MicroComGenerator\MicroComGenerator.csproj", "{AEC9031E-06EA-4A9E-9E7F-7D7C719404DD}"
-EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Avalonia.MicroCom", "src\Avalonia.MicroCom\Avalonia.MicroCom.csproj", "{FE2F3E5E-1E34-4972-8DC1-5C2C588E5ECE}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MiniMvvm", "samples\MiniMvvm\MiniMvvm.csproj", "{BC594FD5-4AF2-409E-A1E6-04123F54D7C5}"
@@ -2027,30 +2025,6 @@ Global
{11BE52AF-E2DD-4CF0-B19A-05285ACAF571}.Release|iPhone.Build.0 = Release|Any CPU
{11BE52AF-E2DD-4CF0-B19A-05285ACAF571}.Release|iPhoneSimulator.ActiveCfg = Release|Any CPU
{11BE52AF-E2DD-4CF0-B19A-05285ACAF571}.Release|iPhoneSimulator.Build.0 = Release|Any CPU
- {AEC9031E-06EA-4A9E-9E7F-7D7C719404DD}.Ad-Hoc|Any CPU.ActiveCfg = Debug|Any CPU
- {AEC9031E-06EA-4A9E-9E7F-7D7C719404DD}.Ad-Hoc|Any CPU.Build.0 = Debug|Any CPU
- {AEC9031E-06EA-4A9E-9E7F-7D7C719404DD}.Ad-Hoc|iPhone.ActiveCfg = Debug|Any CPU
- {AEC9031E-06EA-4A9E-9E7F-7D7C719404DD}.Ad-Hoc|iPhone.Build.0 = Debug|Any CPU
- {AEC9031E-06EA-4A9E-9E7F-7D7C719404DD}.Ad-Hoc|iPhoneSimulator.ActiveCfg = Debug|Any CPU
- {AEC9031E-06EA-4A9E-9E7F-7D7C719404DD}.Ad-Hoc|iPhoneSimulator.Build.0 = Debug|Any CPU
- {AEC9031E-06EA-4A9E-9E7F-7D7C719404DD}.AppStore|Any CPU.ActiveCfg = Debug|Any CPU
- {AEC9031E-06EA-4A9E-9E7F-7D7C719404DD}.AppStore|Any CPU.Build.0 = Debug|Any CPU
- {AEC9031E-06EA-4A9E-9E7F-7D7C719404DD}.AppStore|iPhone.ActiveCfg = Debug|Any CPU
- {AEC9031E-06EA-4A9E-9E7F-7D7C719404DD}.AppStore|iPhone.Build.0 = Debug|Any CPU
- {AEC9031E-06EA-4A9E-9E7F-7D7C719404DD}.AppStore|iPhoneSimulator.ActiveCfg = Debug|Any CPU
- {AEC9031E-06EA-4A9E-9E7F-7D7C719404DD}.AppStore|iPhoneSimulator.Build.0 = Debug|Any CPU
- {AEC9031E-06EA-4A9E-9E7F-7D7C719404DD}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
- {AEC9031E-06EA-4A9E-9E7F-7D7C719404DD}.Debug|Any CPU.Build.0 = Debug|Any CPU
- {AEC9031E-06EA-4A9E-9E7F-7D7C719404DD}.Debug|iPhone.ActiveCfg = Debug|Any CPU
- {AEC9031E-06EA-4A9E-9E7F-7D7C719404DD}.Debug|iPhone.Build.0 = Debug|Any CPU
- {AEC9031E-06EA-4A9E-9E7F-7D7C719404DD}.Debug|iPhoneSimulator.ActiveCfg = Debug|Any CPU
- {AEC9031E-06EA-4A9E-9E7F-7D7C719404DD}.Debug|iPhoneSimulator.Build.0 = Debug|Any CPU
- {AEC9031E-06EA-4A9E-9E7F-7D7C719404DD}.Release|Any CPU.ActiveCfg = Release|Any CPU
- {AEC9031E-06EA-4A9E-9E7F-7D7C719404DD}.Release|Any CPU.Build.0 = Release|Any CPU
- {AEC9031E-06EA-4A9E-9E7F-7D7C719404DD}.Release|iPhone.ActiveCfg = Release|Any CPU
- {AEC9031E-06EA-4A9E-9E7F-7D7C719404DD}.Release|iPhone.Build.0 = Release|Any CPU
- {AEC9031E-06EA-4A9E-9E7F-7D7C719404DD}.Release|iPhoneSimulator.ActiveCfg = Release|Any CPU
- {AEC9031E-06EA-4A9E-9E7F-7D7C719404DD}.Release|iPhoneSimulator.Build.0 = Release|Any CPU
{FE2F3E5E-1E34-4972-8DC1-5C2C588E5ECE}.Ad-Hoc|Any CPU.ActiveCfg = Debug|Any CPU
{FE2F3E5E-1E34-4972-8DC1-5C2C588E5ECE}.Ad-Hoc|Any CPU.Build.0 = Debug|Any CPU
{FE2F3E5E-1E34-4972-8DC1-5C2C588E5ECE}.Ad-Hoc|iPhone.ActiveCfg = Debug|Any CPU
@@ -2253,7 +2227,6 @@ Global
{3C84E04B-36CF-4D0D-B965-C26DD649D1F3} = {A0CC0258-D18C-4AB3-854F-7101680FC3F9}
{909A8CBD-7D0E-42FD-B841-022AD8925820} = {8B6A8209-894F-4BA1-B880-965FD453982C}
{11BE52AF-E2DD-4CF0-B19A-05285ACAF571} = {9B9E3891-2366-4253-A952-D08BCEB71098}
- {AEC9031E-06EA-4A9E-9E7F-7D7C719404DD} = {4ED8B739-6F4E-4CD4-B993-545E6B5CE637}
{BC594FD5-4AF2-409E-A1E6-04123F54D7C5} = {9B9E3891-2366-4253-A952-D08BCEB71098}
{25831348-EB2A-483E-9576-E8F6528674A5} = {86A3F706-DC3C-43C6-BE1B-B98F5BAAA268}
{C08E9894-AA92-426E-BF56-033E262CAD3E} = {9B9E3891-2366-4253-A952-D08BCEB71098}
diff --git a/azure-pipelines.yml b/azure-pipelines.yml
index 903146cdd7..40669f4f53 100644
--- a/azure-pipelines.yml
+++ b/azure-pipelines.yml
@@ -2,6 +2,33 @@ variables:
MSBuildEnableWorkloadResolver: 'false'
jobs:
+
+- job: GetPRNumber
+ pool:
+ vmImage: 'windows-2022'
+ variables:
+ SolutionDir: '$(Build.SourcesDirectory)'
+ steps:
+
+ - task: PowerShell@2
+ displayName: Get PR Number
+ inputs:
+ targetType: 'inline'
+ script: |
+ $prId = $env:System_PullRequest_PullRequestNumber
+ Write-Host "PR Number is:-" $env:System_PullRequest_PullRequestNumber
+
+ if (!([string]::IsNullOrWhiteSpace($prId)))
+ {
+ Set-Content -Path $env:Build_ArtifactStagingDirectory\prId.txt -Value $prId
+ }
+
+ - task: PublishBuildArtifacts@1
+ inputs:
+ PathtoPublish: '$(Build.ArtifactStagingDirectory)'
+ ArtifactName: 'PRNumber'
+ publishLocation: 'Container'
+
- job: Linux
pool:
vmImage: 'ubuntu-20.04'
@@ -58,8 +85,10 @@ jobs:
displayName: 'Generate avalonia-native'
inputs:
script: |
- export PATH="`pwd`/sdk:$PATH"
- cd src/tools/MicroComGenerator; dotnet run -f net6.0 -i ../../Avalonia.Native/avn.idl --cpp ../../../native/Avalonia.Native/inc/avalonia-native.h
+ export COREHOST_TRACE=0
+ export DOTNET_SKIP_FIRST_TIME_EXPERIENCE=1
+ export DOTNET_CLI_TELEMETRY_OPTOUT=1
+ ./build.sh --target GenerateCppHeaders --configuration Release
- task: Xcode@5
inputs:
diff --git a/build/MicroCom.targets b/build/MicroCom.targets
deleted file mode 100644
index 029d7f95f5..0000000000
--- a/build/MicroCom.targets
+++ /dev/null
@@ -1,35 +0,0 @@
-
-
-
-
-
- false
- all
- true
- TargetFramework=net6.0
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- <_AvaloniaPatchComInterop>true
-
-
-
diff --git a/nukebuild/MicroComGen.cs b/nukebuild/MicroComGen.cs
index 06c8acbf23..b1e546cb97 100644
--- a/nukebuild/MicroComGen.cs
+++ b/nukebuild/MicroComGen.cs
@@ -1,14 +1,14 @@
using System.IO;
-using MicroComGenerator;
+using MicroCom.CodeGenerator;
using Nuke.Common;
partial class Build : NukeBuild
{
Target GenerateCppHeaders => _ => _.Executes(() =>
{
- var text = File.ReadAllText(RootDirectory / "src" / "Avalonia.Native" / "avn.idl");
- var ast = AstParser.Parse(text);
+ var file = MicroComCodeGenerator.Parse(
+ File.ReadAllText(RootDirectory / "src" / "Avalonia.Native" / "avn.idl"));
File.WriteAllText(RootDirectory / "native" / "Avalonia.Native" / "inc" / "avalonia-native.h",
- CppGen.GenerateCpp(ast));
+ file.GenerateCppHeader());
});
}
\ No newline at end of file
diff --git a/nukebuild/_build.csproj b/nukebuild/_build.csproj
index b28d3eb700..52b60b7d0f 100644
--- a/nukebuild/_build.csproj
+++ b/nukebuild/_build.csproj
@@ -15,7 +15,7 @@
-
+
@@ -37,10 +37,6 @@
-
- MicroComGenerator\%(Filename)%(Extension)
-
-
diff --git a/packages/Avalonia/Avalonia.csproj b/packages/Avalonia/Avalonia.csproj
index af9ed71c5f..4b28527465 100644
--- a/packages/Avalonia/Avalonia.csproj
+++ b/packages/Avalonia/Avalonia.csproj
@@ -1,6 +1,6 @@
- netstandard2.0;net461;netcoreapp2.0;net6.0
+ net6.0;netstandard2.0;net461;netcoreapp2.0
Avalonia
diff --git a/samples/ControlCatalog.Web/ControlCatalog.Web.csproj b/samples/ControlCatalog.Web/ControlCatalog.Web.csproj
index d463dfa84a..199fa85ad2 100644
--- a/samples/ControlCatalog.Web/ControlCatalog.Web.csproj
+++ b/samples/ControlCatalog.Web/ControlCatalog.Web.csproj
@@ -2,6 +2,7 @@
net6.0
enable
+ True
diff --git a/samples/ControlCatalog/App.xaml.cs b/samples/ControlCatalog/App.xaml.cs
index 36b6fc2dcd..505f486a6d 100644
--- a/samples/ControlCatalog/App.xaml.cs
+++ b/samples/ControlCatalog/App.xaml.cs
@@ -5,6 +5,7 @@ using Avalonia.Controls.ApplicationLifetimes;
using Avalonia.Markup.Xaml;
using Avalonia.Markup.Xaml.Styling;
using Avalonia.Styling;
+using Avalonia.Themes.Fluent;
using ControlCatalog.ViewModels;
namespace ControlCatalog
@@ -16,33 +17,17 @@ namespace ControlCatalog
DataContext = new ApplicationViewModel();
}
- private static readonly StyleInclude DataGridFluent = new StyleInclude(new Uri("avares://ControlCatalog/Styles"))
+ public static readonly StyleInclude DataGridFluent = new StyleInclude(new Uri("avares://ControlCatalog/Styles"))
{
Source = new Uri("avares://Avalonia.Controls.DataGrid/Themes/Fluent.xaml")
};
- private static readonly StyleInclude DataGridDefault = new StyleInclude(new Uri("avares://ControlCatalog/Styles"))
+ public static readonly StyleInclude DataGridDefault = new StyleInclude(new Uri("avares://ControlCatalog/Styles"))
{
Source = new Uri("avares://Avalonia.Controls.DataGrid/Themes/Default.xaml")
};
- public static Styles FluentDark = new Styles
- {
- new StyleInclude(new Uri("avares://ControlCatalog/Styles"))
- {
- Source = new Uri("avares://Avalonia.Themes.Fluent/FluentDark.xaml")
- },
- DataGridFluent
- };
-
- public static Styles FluentLight = new Styles
- {
- new StyleInclude(new Uri("avares://ControlCatalog/Styles"))
- {
- Source = new Uri("avares://Avalonia.Themes.Fluent/FluentLight.xaml")
- },
- DataGridFluent
- };
+ public static FluentTheme Fluent = new FluentTheme(new Uri("avares://ControlCatalog/Styles"));
public static Styles DefaultLight = new Styles
{
@@ -65,8 +50,7 @@ namespace ControlCatalog
new StyleInclude(new Uri("resm:Styles?assembly=ControlCatalog"))
{
Source = new Uri("avares://Avalonia.Themes.Default/DefaultTheme.xaml")
- },
- DataGridDefault
+ }
};
public static Styles DefaultDark = new Styles
@@ -90,14 +74,13 @@ namespace ControlCatalog
new StyleInclude(new Uri("resm:Styles?assembly=ControlCatalog"))
{
Source = new Uri("avares://Avalonia.Themes.Default/DefaultTheme.xaml")
- },
- DataGridDefault
+ }
};
public override void Initialize()
{
- Styles.Insert(0, FluentLight);
-
+ Styles.Insert(0, Fluent);
+ Styles.Insert(1, DataGridFluent);
AvaloniaXamlLoader.Load(this);
}
@@ -106,9 +89,16 @@ namespace ControlCatalog
if (ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktopLifetime)
{
desktopLifetime.MainWindow = new MainWindow();
+
+ this.AttachDevTools(new Avalonia.Diagnostics.DevToolsOptions()
+ {
+ StartupScreenIndex = 1,
+ });
}
else if (ApplicationLifetime is ISingleViewApplicationLifetime singleViewLifetime)
+ {
singleViewLifetime.MainView = new MainView();
+ }
base.OnFrameworkInitializationCompleted();
}
diff --git a/samples/ControlCatalog/DecoratedWindow.xaml.cs b/samples/ControlCatalog/DecoratedWindow.xaml.cs
index bdf5b8fbee..a1383b9107 100644
--- a/samples/ControlCatalog/DecoratedWindow.xaml.cs
+++ b/samples/ControlCatalog/DecoratedWindow.xaml.cs
@@ -11,7 +11,6 @@ namespace ControlCatalog
public DecoratedWindow()
{
this.InitializeComponent();
- this.AttachDevTools();
}
void SetupSide(string name, StandardCursorType cursor, WindowEdge edge)
diff --git a/samples/ControlCatalog/MainView.xaml.cs b/samples/ControlCatalog/MainView.xaml.cs
index abedda3c85..0579355831 100644
--- a/samples/ControlCatalog/MainView.xaml.cs
+++ b/samples/ControlCatalog/MainView.xaml.cs
@@ -3,14 +3,12 @@ using System.Collections;
using Avalonia;
using Avalonia.Controls;
using Avalonia.Markup.Xaml;
-using Avalonia.Markup.Xaml.MarkupExtensions;
-using Avalonia.Markup.Xaml.Styling;
-using Avalonia.Markup.Xaml.XamlIl;
using Avalonia.Media;
using Avalonia.Media.Immutable;
using Avalonia.Platform;
-using ControlCatalog.Pages;
+using Avalonia.Themes.Fluent;
using ControlCatalog.Models;
+using ControlCatalog.Pages;
namespace ControlCatalog
{
@@ -43,14 +41,36 @@ namespace ControlCatalog
{
if (themes.SelectedItem is CatalogTheme theme)
{
- Application.Current.Styles[0] = theme switch
+ var themeStyle = Application.Current.Styles[0];
+ if (theme == CatalogTheme.FluentLight)
+ {
+ if (App.Fluent.Mode != FluentThemeMode.Light)
+ {
+ App.Fluent.Mode = FluentThemeMode.Light;
+ }
+ Application.Current.Styles[0] = App.Fluent;
+ Application.Current.Styles[1] = App.DataGridFluent;
+ }
+ else if (theme == CatalogTheme.FluentDark)
+ {
+
+ if (App.Fluent.Mode != FluentThemeMode.Dark)
+ {
+ App.Fluent.Mode = FluentThemeMode.Dark;
+ }
+ Application.Current.Styles[0] = App.Fluent;
+ Application.Current.Styles[1] = App.DataGridFluent;
+ }
+ else if (theme == CatalogTheme.DefaultLight)
+ {
+ Application.Current.Styles[0] = App.DefaultLight;
+ Application.Current.Styles[1] = App.DataGridDefault;
+ }
+ else if (theme == CatalogTheme.DefaultDark)
{
- CatalogTheme.FluentLight => App.FluentLight,
- CatalogTheme.FluentDark => App.FluentDark,
- CatalogTheme.DefaultLight => App.DefaultLight,
- CatalogTheme.DefaultDark => App.DefaultDark,
- _ => Application.Current.Styles[0]
- };
+ Application.Current.Styles[0] = App.DefaultDark;
+ Application.Current.Styles[1] = App.DataGridDefault;
+ }
}
};
diff --git a/samples/ControlCatalog/MainWindow.xaml.cs b/samples/ControlCatalog/MainWindow.xaml.cs
index dd62698cc7..20591103b7 100644
--- a/samples/ControlCatalog/MainWindow.xaml.cs
+++ b/samples/ControlCatalog/MainWindow.xaml.cs
@@ -17,10 +17,7 @@ namespace ControlCatalog
public MainWindow()
{
this.InitializeComponent();
- this.AttachDevTools(new Avalonia.Diagnostics.DevToolsOptions()
- {
- StartupScreenIndex = 1,
- });
+
//Renderer.DrawFps = true;
//Renderer.DrawDirtyRects = Renderer.DrawFps = true;
diff --git a/samples/ControlCatalog/Models/Person.cs b/samples/ControlCatalog/Models/Person.cs
index 47f41bc584..cd70fa3959 100644
--- a/samples/ControlCatalog/Models/Person.cs
+++ b/samples/ControlCatalog/Models/Person.cs
@@ -16,6 +16,7 @@ namespace ControlCatalog.Models
string _firstName;
string _lastName;
bool _isBanned;
+ private int _age;
public string FirstName
{
@@ -59,6 +60,20 @@ namespace ControlCatalog.Models
}
}
+
+ ///
+ /// Gets or sets the age of the person
+ ///
+ public int Age
+ {
+ get => _age;
+ set
+ {
+ _age = value;
+ OnPropertyChanged(nameof(Age));
+ }
+ }
+
Dictionary> _errorLookup = new Dictionary>();
void SetError(string propertyName, string error)
diff --git a/samples/ControlCatalog/Pages/CalendarDatePickerPage.xaml b/samples/ControlCatalog/Pages/CalendarDatePickerPage.xaml
index 3e50bf8a08..2fe16ba8e3 100644
--- a/samples/ControlCatalog/Pages/CalendarDatePickerPage.xaml
+++ b/samples/ControlCatalog/Pages/CalendarDatePickerPage.xaml
@@ -1,5 +1,7 @@
A control for selecting dates with a calendar drop-down
@@ -39,6 +41,9 @@
+
+
+
diff --git a/samples/ControlCatalog/Pages/DataGridPage.xaml b/samples/ControlCatalog/Pages/DataGridPage.xaml
index 63e873d9b5..f7e3cf2441 100644
--- a/samples/ControlCatalog/Pages/DataGridPage.xaml
+++ b/samples/ControlCatalog/Pages/DataGridPage.xaml
@@ -64,6 +64,18 @@
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/samples/ControlCatalog/Pages/DataGridPage.xaml.cs b/samples/ControlCatalog/Pages/DataGridPage.xaml.cs
index dc5cc49a90..c010d38eac 100644
--- a/samples/ControlCatalog/Pages/DataGridPage.xaml.cs
+++ b/samples/ControlCatalog/Pages/DataGridPage.xaml.cs
@@ -6,7 +6,9 @@ using Avalonia.Controls;
using Avalonia.Markup.Xaml;
using ControlCatalog.Models;
using Avalonia.Collections;
+using Avalonia.Controls.Primitives;
using Avalonia.Data;
+using Avalonia.Threading;
namespace ControlCatalog.Pages
{
@@ -48,9 +50,9 @@ namespace ControlCatalog.Pages
var items = new List
{
- new Person { FirstName = "John", LastName = "Doe" },
- new Person { FirstName = "Elizabeth", LastName = "Thomas", IsBanned = true },
- new Person { FirstName = "Zack", LastName = "Ward" }
+ new Person { FirstName = "John", LastName = "Doe" , Age = 30},
+ new Person { FirstName = "Elizabeth", LastName = "Thomas", IsBanned = true , Age = 40 },
+ new Person { FirstName = "Zack", LastName = "Ward" , Age = 50 }
};
var collectionView3 = new DataGridCollectionView(items);
@@ -84,5 +86,19 @@ namespace ControlCatalog.Pages
return Comparer.Default.Compare(x, y);
}
}
+
+ private void NumericUpDown_OnTemplateApplied(object sender, TemplateAppliedEventArgs e)
+ {
+ // We want to focus the TextBox of the NumericUpDown. To do so we search for this control when the template
+ // is applied, but we postpone the action until the control is actually loaded.
+ if (e.NameScope.Find("PART_TextBox") is {} textBox)
+ {
+ Dispatcher.UIThread.InvokeAsync(() =>
+ {
+ textBox.Focus();
+ textBox.SelectAll();
+ }, DispatcherPriority.Loaded);
+ }
+ }
}
}
diff --git a/samples/ControlCatalog/ViewModels/MainWindowViewModel.cs b/samples/ControlCatalog/ViewModels/MainWindowViewModel.cs
index 4b3cfa9c9d..2b0c30f311 100644
--- a/samples/ControlCatalog/ViewModels/MainWindowViewModel.cs
+++ b/samples/ControlCatalog/ViewModels/MainWindowViewModel.cs
@@ -5,6 +5,7 @@ using Avalonia.Controls.Notifications;
using Avalonia.Dialogs;
using Avalonia.Platform;
using System;
+using System.ComponentModel.DataAnnotations;
using MiniMvvm;
namespace ControlCatalog.ViewModels
@@ -164,5 +165,17 @@ namespace ControlCatalog.ViewModels
public MiniCommand ExitCommand { get; }
public MiniCommand ToggleMenuItemCheckedCommand { get; }
+
+ private DateTime? _validatedDateExample;
+
+ ///
+ /// A required DateTime which should demonstrate validation for the DateTimePicker
+ ///
+ [Required]
+ public DateTime? ValidatedDateExample
+ {
+ get => _validatedDateExample;
+ set => this.RaiseAndSetIfChanged(ref _validatedDateExample, value);
+ }
}
}
diff --git a/src/Android/Avalonia.Android/AndroidPlatform.cs b/src/Android/Avalonia.Android/AndroidPlatform.cs
index 57f22e7a05..6a940a54f1 100644
--- a/src/Android/Avalonia.Android/AndroidPlatform.cs
+++ b/src/Android/Avalonia.Android/AndroidPlatform.cs
@@ -33,8 +33,16 @@ namespace Avalonia.Android
{
public static readonly AndroidPlatform Instance = new AndroidPlatform();
public static AndroidPlatformOptions Options { get; private set; }
- public Size DoubleClickSize => new Size(4, 4);
- public TimeSpan DoubleClickTime => TimeSpan.FromMilliseconds(200);
+
+ ///
+ public Size TouchDoubleClickSize => new Size(4, 4);
+
+ ///
+ public TimeSpan TouchDoubleClickTime => TimeSpan.FromMilliseconds(200);
+
+ public Size DoubleClickSize => TouchDoubleClickSize;
+
+ public TimeSpan DoubleClickTime => TimeSpan.FromMilliseconds(500);
public static void Initialize(Type appType, AndroidPlatformOptions options)
{
diff --git a/src/Avalonia.Animation/Avalonia.Animation.csproj b/src/Avalonia.Animation/Avalonia.Animation.csproj
index 9e3758658c..d81d14bbff 100644
--- a/src/Avalonia.Animation/Avalonia.Animation.csproj
+++ b/src/Avalonia.Animation/Avalonia.Animation.csproj
@@ -1,6 +1,6 @@
- netstandard2.0;net6.0
+ net6.0;netstandard2.0
diff --git a/src/Avalonia.Base/ApiCompatBaseline.txt b/src/Avalonia.Base/ApiCompatBaseline.txt
new file mode 100644
index 0000000000..4701a83175
--- /dev/null
+++ b/src/Avalonia.Base/ApiCompatBaseline.txt
@@ -0,0 +1,3 @@
+Compat issues with assembly Avalonia.Base:
+InterfacesShouldHaveSameMembers : Interface member 'public void Avalonia.Threading.IDispatcher.Post(System.Action, T, Avalonia.Threading.DispatcherPriority)' is present in the implementation but not in the contract.
+Total Issues: 1
diff --git a/src/Avalonia.Base/Avalonia.Base.csproj b/src/Avalonia.Base/Avalonia.Base.csproj
index 07614f31db..376075df2a 100644
--- a/src/Avalonia.Base/Avalonia.Base.csproj
+++ b/src/Avalonia.Base/Avalonia.Base.csproj
@@ -1,6 +1,6 @@
- netstandard2.0;net6.0
+ net6.0;netstandard2.0
Avalonia.Base
Avalonia
True
diff --git a/src/Avalonia.Base/Collections/NotifyCollectionChangedExtensions.cs b/src/Avalonia.Base/Collections/NotifyCollectionChangedExtensions.cs
index dcd32ddd76..689fcc89a4 100644
--- a/src/Avalonia.Base/Collections/NotifyCollectionChangedExtensions.cs
+++ b/src/Avalonia.Base/Collections/NotifyCollectionChangedExtensions.cs
@@ -59,7 +59,7 @@ namespace Avalonia.Collections
}
private class WeakCollectionChangedObservable : LightweightObservableBase,
- IWeakSubscriber
+ IWeakEventSubscriber
{
private WeakReference _sourceReference;
@@ -68,31 +68,22 @@ namespace Avalonia.Collections
_sourceReference = source;
}
- public void OnEvent(object? sender, NotifyCollectionChangedEventArgs e)
+ public void OnEvent(object? sender,
+ WeakEvent ev,
+ NotifyCollectionChangedEventArgs e)
{
PublishNext(e);
}
-
protected override void Initialize()
{
if (_sourceReference.TryGetTarget(out var instance))
- {
- WeakSubscriptionManager.Subscribe(
- instance,
- nameof(instance.CollectionChanged),
- this);
- }
+ WeakEvents.CollectionChanged.Subscribe(instance, this);
}
protected override void Deinitialize()
{
if (_sourceReference.TryGetTarget(out var instance))
- {
- WeakSubscriptionManager.Unsubscribe(
- instance,
- nameof(instance.CollectionChanged),
- this);
- }
+ WeakEvents.CollectionChanged.Unsubscribe(instance, this);
}
}
}
diff --git a/src/Avalonia.Base/Data/Core/IndexerNodeBase.cs b/src/Avalonia.Base/Data/Core/IndexerNodeBase.cs
index e197e29103..a808827896 100644
--- a/src/Avalonia.Base/Data/Core/IndexerNodeBase.cs
+++ b/src/Avalonia.Base/Data/Core/IndexerNodeBase.cs
@@ -23,18 +23,16 @@ namespace Avalonia.Data.Core
if (incc != null)
{
- inputs.Add(WeakObservable.FromEventPattern(
- incc,
- nameof(incc.CollectionChanged))
+ inputs.Add(WeakObservable.FromEventPattern(
+ incc, WeakEvents.CollectionChanged)
.Where(x => ShouldUpdate(x.Sender, x.EventArgs))
.Select(_ => GetValue(target)));
}
if (inpc != null)
{
- inputs.Add(WeakObservable.FromEventPattern(
- inpc,
- nameof(inpc.PropertyChanged))
+ inputs.Add(WeakObservable.FromEventPattern(
+ inpc, WeakEvents.PropertyChanged)
.Where(x => ShouldUpdate(x.Sender, x.EventArgs))
.Select(_ => GetValue(target)));
}
diff --git a/src/Avalonia.Base/Data/Core/Plugins/IndeiValidationPlugin.cs b/src/Avalonia.Base/Data/Core/Plugins/IndeiValidationPlugin.cs
index 9f827daf94..1e7a0d5c8f 100644
--- a/src/Avalonia.Base/Data/Core/Plugins/IndeiValidationPlugin.cs
+++ b/src/Avalonia.Base/Data/Core/Plugins/IndeiValidationPlugin.cs
@@ -11,6 +11,12 @@ namespace Avalonia.Data.Core.Plugins
///
public class IndeiValidationPlugin : IDataValidationPlugin
{
+ private static readonly WeakEvent
+ ErrorsChangedWeakEvent = WeakEvent.Register(
+ (s, h) => s.ErrorsChanged += h,
+ (s, h) => s.ErrorsChanged -= h
+ );
+
///
public bool Match(WeakReference
@@ -20,7 +21,7 @@
-
+
+
-
diff --git a/src/Avalonia.Native/AvaloniaNativeMenuExporter.cs b/src/Avalonia.Native/AvaloniaNativeMenuExporter.cs
index bda1c91750..40ffc31728 100644
--- a/src/Avalonia.Native/AvaloniaNativeMenuExporter.cs
+++ b/src/Avalonia.Native/AvaloniaNativeMenuExporter.cs
@@ -80,8 +80,8 @@ namespace Avalonia.Native
};
result.Add(aboutItem);
- var macOpts = AvaloniaLocator.Current.GetService();
- if (macOpts == null || !macOpts.DisableDefaultApplicationMenuItems)
+ var macOpts = AvaloniaLocator.Current.GetService() ?? new MacOSPlatformOptions();
+ if (!macOpts.DisableDefaultApplicationMenuItems)
{
result.Add(new NativeMenuItemSeparator());
@@ -131,7 +131,7 @@ namespace Avalonia.Native
};
quitItem.Click += (sender, args) =>
{
- _applicationCommands.ShowAll();
+ (Application.Current.ApplicationLifetime as IControlledApplicationLifetime)?.Shutdown();
};
result.Add(quitItem);
}
@@ -142,9 +142,9 @@ namespace Avalonia.Native
private void DoLayoutReset(bool forceUpdate = false)
{
- var macOpts = AvaloniaLocator.Current.GetService();
+ var macOpts = AvaloniaLocator.Current.GetService() ?? new MacOSPlatformOptions();
- if (macOpts != null && macOpts.DisableNativeMenus)
+ if (macOpts.DisableNativeMenus)
{
return;
}
diff --git a/src/Avalonia.Native/AvaloniaNativePlatform.cs b/src/Avalonia.Native/AvaloniaNativePlatform.cs
index 522db1b334..1eadf70b13 100644
--- a/src/Avalonia.Native/AvaloniaNativePlatform.cs
+++ b/src/Avalonia.Native/AvaloniaNativePlatform.cs
@@ -26,9 +26,15 @@ namespace Avalonia.Native
public TimeSpan DoubleClickTime => TimeSpan.FromMilliseconds(500); //TODO
+ ///
+ public Size TouchDoubleClickSize => new Size(16, 16);
+
+ ///
+ public TimeSpan TouchDoubleClickTime => DoubleClickTime;
+
public static AvaloniaNativePlatform Initialize(IntPtr factory, AvaloniaNativePlatformOptions options)
{
- var result = new AvaloniaNativePlatform(MicroComRuntime.CreateProxyFor(factory, true));
+ var result = new AvaloniaNativePlatform(MicroComRuntime.CreateProxyFor(factory, true));
result.DoInitialize(options);
return result;
@@ -55,14 +61,14 @@ namespace Avalonia.Native
return Initialize(CreateAvaloniaNative(), options);
}
- public void SetupApplicationMenuExporter ()
+ public void SetupApplicationMenuExporter()
{
var exporter = new AvaloniaNativeMenuExporter(_factory);
}
- public void SetupApplicationName ()
+ public void SetupApplicationName()
{
- if(!string.IsNullOrWhiteSpace(Application.Current.Name))
+ if (!string.IsNullOrWhiteSpace(Application.Current.Name))
{
_factory.MacOptions.SetApplicationTitle(Application.Current.Name);
}
@@ -80,19 +86,19 @@ namespace Avalonia.Native
GCHandle.FromIntPtr(handle).Free();
}
}
-
+
void DoInitialize(AvaloniaNativePlatformOptions options)
{
_options = options;
-
+
var applicationPlatform = new AvaloniaNativeApplicationPlatform();
-
+
_factory.Initialize(new GCHandleDeallocator(), applicationPlatform);
if (_factory.MacOptions != null)
{
- var macOpts = AvaloniaLocator.Current.GetService();
+ var macOpts = AvaloniaLocator.Current.GetService() ?? new MacOSPlatformOptions();
- _factory.MacOptions.SetShowInDock(macOpts?.ShowInDock != false ? 1 : 0);
+ _factory.MacOptions.SetShowInDock(macOpts.ShowInDock ? 1 : 0);
}
AvaloniaLocator.CurrentMutable
@@ -118,7 +124,7 @@ namespace Avalonia.Native
hotkeys.MoveCursorToTheStartOfLineWithSelection.Add(new KeyGesture(Key.Left, hotkeys.CommandModifiers | hotkeys.SelectionModifiers));
hotkeys.MoveCursorToTheEndOfLine.Add(new KeyGesture(Key.Right, hotkeys.CommandModifiers));
hotkeys.MoveCursorToTheEndOfLineWithSelection.Add(new KeyGesture(Key.Right, hotkeys.CommandModifiers | hotkeys.SelectionModifiers));
-
+
if (_options.UseGpu)
{
try
@@ -133,7 +139,7 @@ namespace Avalonia.Native
}
}
- public ITrayIconImpl CreateTrayIcon ()
+ public ITrayIconImpl CreateTrayIcon()
{
return new TrayIconImpl(_factory);
}
@@ -159,8 +165,8 @@ namespace Avalonia.Native
ShowInDock = true;
}
- public bool ShowInDock
- {
+ public bool ShowInDock
+ {
get => _showInDock;
set
{
diff --git a/src/Avalonia.Native/WindowImplBase.cs b/src/Avalonia.Native/WindowImplBase.cs
index 4a3baa2788..87b7a7608e 100644
--- a/src/Avalonia.Native/WindowImplBase.cs
+++ b/src/Avalonia.Native/WindowImplBase.cs
@@ -28,18 +28,18 @@ namespace Avalonia.Native
public string HandleDescriptor => "NSWindow";
- public IntPtr NSView => _native.ObtainNSViewHandle();
+ public IntPtr NSView => _native?.ObtainNSViewHandle() ?? IntPtr.Zero;
- public IntPtr NSWindow => _native.ObtainNSWindowHandle();
+ public IntPtr NSWindow => _native?.ObtainNSWindowHandle() ?? IntPtr.Zero;
public IntPtr GetNSViewRetained()
{
- return _native.ObtainNSViewHandleRetained();
+ return _native?.ObtainNSViewHandleRetained() ?? IntPtr.Zero;
}
public IntPtr GetNSWindowRetained()
{
- return _native.ObtainNSWindowHandleRetained();
+ return _native?.ObtainNSWindowHandleRetained() ?? IntPtr.Zero;
}
}
@@ -260,7 +260,7 @@ namespace Avalonia.Native
public void Activate()
{
- _native.Activate();
+ _native?.Activate();
}
public bool RawTextInputEvent(uint timeStamp, string text)
@@ -322,7 +322,7 @@ namespace Avalonia.Native
public void Resize(Size clientSize, PlatformResizeReason reason)
{
- _native.Resize(clientSize.Width, clientSize.Height, (AvnPlatformResizeReason)reason);
+ _native?.Resize(clientSize.Width, clientSize.Height, (AvnPlatformResizeReason)reason);
}
public IRenderer CreateRenderer(IRenderRoot root)
@@ -367,14 +367,14 @@ namespace Avalonia.Native
public virtual void Show(bool activate, bool isDialog)
{
- _native.Show(activate.AsComBool(), isDialog.AsComBool());
+ _native?.Show(activate.AsComBool(), isDialog.AsComBool());
}
public PixelPoint Position
{
- get => _native.Position.ToAvaloniaPixelPoint();
- set => _native.SetPosition(value.ToAvnPoint());
+ get => _native?.Position.ToAvaloniaPixelPoint() ?? default;
+ set => _native?.SetPosition(value.ToAvnPoint());
}
public Point PointToClient(PixelPoint point)
@@ -389,12 +389,12 @@ namespace Avalonia.Native
public void Hide()
{
- _native.Hide();
+ _native?.Hide();
}
public void BeginMoveDrag(PointerPressedEventArgs e)
{
- _native.BeginMoveDrag();
+ _native?.BeginMoveDrag();
}
public Size MaxAutoSizeHint => Screen.AllScreens.Select(s => s.Bounds.Size.ToSize(1))
@@ -402,7 +402,7 @@ namespace Avalonia.Native
public void SetTopmost(bool value)
{
- _native.SetTopMost(value.AsComBool());
+ _native?.SetTopMost(value.AsComBool());
}
public double RenderScaling => _native?.Scaling ?? 1;
@@ -438,7 +438,7 @@ namespace Avalonia.Native
public void SetMinMaxSize(Size minSize, Size maxSize)
{
- _native.SetMinMaxSize(minSize.ToAvnSize(), maxSize.ToAvnSize());
+ _native?.SetMinMaxSize(minSize.ToAvnSize(), maxSize.ToAvnSize());
}
public void BeginResizeDrag(WindowEdge edge, PointerPressedEventArgs e)
@@ -449,7 +449,7 @@ namespace Avalonia.Native
internal void BeginDraggingSession(AvnDragDropEffects effects, AvnPoint point, IAvnClipboard clipboard,
IAvnDndResultCallback callback, IntPtr sourceHandle)
{
- _native.BeginDragAndDropOperation(effects, point, clipboard, callback, sourceHandle);
+ _native?.BeginDragAndDropOperation(effects, point, clipboard, callback, sourceHandle);
}
public void SetTransparencyLevelHint(WindowTransparencyLevel transparencyLevel)
diff --git a/src/Avalonia.OpenGL/Avalonia.OpenGL.csproj b/src/Avalonia.OpenGL/Avalonia.OpenGL.csproj
index 5649d46102..2b32926008 100644
--- a/src/Avalonia.OpenGL/Avalonia.OpenGL.csproj
+++ b/src/Avalonia.OpenGL/Avalonia.OpenGL.csproj
@@ -1,7 +1,7 @@
- netstandard2.0;net6.0
+ net6.0;netstandard2.0
true
diff --git a/src/Avalonia.ReactiveUI/Avalonia.ReactiveUI.csproj b/src/Avalonia.ReactiveUI/Avalonia.ReactiveUI.csproj
index 189b156569..348f5f3a10 100644
--- a/src/Avalonia.ReactiveUI/Avalonia.ReactiveUI.csproj
+++ b/src/Avalonia.ReactiveUI/Avalonia.ReactiveUI.csproj
@@ -1,6 +1,6 @@
- netstandard2.0;net6.0
+ net6.0;netstandard2.0
Avalonia.ReactiveUI
false
diff --git a/src/Avalonia.ReactiveUI/RoutedViewHost.cs b/src/Avalonia.ReactiveUI/RoutedViewHost.cs
index 63456bc13a..a475cf5eac 100644
--- a/src/Avalonia.ReactiveUI/RoutedViewHost.cs
+++ b/src/Avalonia.ReactiveUI/RoutedViewHost.cs
@@ -57,7 +57,13 @@ namespace Avalonia.ReactiveUI
///
public static readonly StyledProperty RouterProperty =
AvaloniaProperty.Register(nameof(Router));
-
+
+ ///
+ /// for the property.
+ ///
+ public static readonly StyledProperty ViewContractProperty =
+ AvaloniaProperty.Register(nameof(ViewContract));
+
///
/// Initializes a new instance of the class.
///
@@ -70,15 +76,18 @@ namespace Avalonia.ReactiveUI
.Where(router => router == null)!
.Cast();
+ var viewContract = this.WhenAnyValue(x => x.ViewContract);
+
this.WhenAnyValue(x => x.Router)
.Where(router => router != null)
.SelectMany(router => router!.CurrentViewModel)
.Merge(routerRemoved)
- .Subscribe(NavigateToViewModel)
+ .CombineLatest(viewContract)
+ .Subscribe(tuple => NavigateToViewModel(tuple.First, tuple.Second))
.DisposeWith(disposables);
});
}
-
+
///
/// Gets or sets the of the view model stack.
///
@@ -87,17 +96,27 @@ namespace Avalonia.ReactiveUI
get => GetValue(RouterProperty);
set => SetValue(RouterProperty, value);
}
-
+
+ ///
+ /// Gets or sets the view contract.
+ ///
+ public string? ViewContract
+ {
+ get => GetValue(ViewContractProperty);
+ set => SetValue(ViewContractProperty, value);
+ }
+
///
/// Gets or sets the ReactiveUI view locator used by this router.
///
public IViewLocator? ViewLocator { get; set; }
-
+
///
/// Invoked when ReactiveUI router navigates to a view model.
///
/// ViewModel to which the user navigates.
- private void NavigateToViewModel(object? viewModel)
+ /// The contract for view resolution.
+ private void NavigateToViewModel(object? viewModel, string? contract)
{
if (Router == null)
{
@@ -112,17 +131,33 @@ namespace Avalonia.ReactiveUI
Content = DefaultContent;
return;
}
-
+
var viewLocator = ViewLocator ?? global::ReactiveUI.ViewLocator.Current;
- var viewInstance = viewLocator.ResolveView(viewModel);
+ var viewInstance = viewLocator.ResolveView(viewModel, contract);
if (viewInstance == null)
{
- this.Log().Warn($"Couldn't find view for '{viewModel}'. Is it registered? Falling back to default content.");
+ if (contract == null)
+ {
+ this.Log().Warn($"Couldn't find view for '{viewModel}'. Is it registered? Falling back to default content.");
+ }
+ else
+ {
+ this.Log().Warn($"Couldn't find view with contract '{contract}' for '{viewModel}'. Is it registered? Falling back to default content.");
+ }
+
Content = DefaultContent;
return;
}
-
- this.Log().Info($"Ready to show {viewInstance} with autowired {viewModel}.");
+
+ if (contract == null)
+ {
+ this.Log().Info($"Ready to show {viewInstance} with autowired {viewModel}.");
+ }
+ else
+ {
+ this.Log().Info($"Ready to show {viewInstance} with autowired {viewModel} and contract '{contract}'.");
+ }
+
viewInstance.ViewModel = viewModel;
if (viewInstance is IDataContextProvider provider)
provider.DataContext = viewModel;
diff --git a/src/Avalonia.ReactiveUI/ViewModelViewHost.cs b/src/Avalonia.ReactiveUI/ViewModelViewHost.cs
index c88323d674..16dee00ebc 100644
--- a/src/Avalonia.ReactiveUI/ViewModelViewHost.cs
+++ b/src/Avalonia.ReactiveUI/ViewModelViewHost.cs
@@ -3,7 +3,7 @@ using System.Reactive.Disposables;
using ReactiveUI;
using Splat;
-namespace Avalonia.ReactiveUI
+namespace Avalonia.ReactiveUI
{
///
/// This content control will automatically load the View associated with
@@ -18,6 +18,12 @@ namespace Avalonia.ReactiveUI
public static readonly AvaloniaProperty ViewModelProperty =
AvaloniaProperty.Register(nameof(ViewModel));
+ ///
+ /// for the property.
+ ///
+ public static readonly StyledProperty ViewContractProperty =
+ AvaloniaProperty.Register(nameof(ViewContract));
+
///
/// Initializes a new instance of the class.
///
@@ -25,8 +31,8 @@ namespace Avalonia.ReactiveUI
{
this.WhenActivated(disposables =>
{
- this.WhenAnyValue(x => x.ViewModel)
- .Subscribe(NavigateToViewModel)
+ this.WhenAnyValue(x => x.ViewModel, x => x.ViewContract)
+ .Subscribe(tuple => NavigateToViewModel(tuple.Item1, tuple.Item2))
.DisposeWith(disposables);
});
}
@@ -39,7 +45,16 @@ namespace Avalonia.ReactiveUI
get => GetValue(ViewModelProperty);
set => SetValue(ViewModelProperty, value);
}
-
+
+ ///
+ /// Gets or sets the view contract.
+ ///
+ public string? ViewContract
+ {
+ get => GetValue(ViewContractProperty);
+ set => SetValue(ViewContractProperty, value);
+ }
+
///
/// Gets or sets the view locator.
///
@@ -49,7 +64,8 @@ namespace Avalonia.ReactiveUI
/// Invoked when ReactiveUI router navigates to a view model.
///
/// ViewModel to which the user navigates.
- private void NavigateToViewModel(object? viewModel)
+ /// The contract for view resolution.
+ private void NavigateToViewModel(object? viewModel, string? contract)
{
if (viewModel == null)
{
@@ -57,17 +73,33 @@ namespace Avalonia.ReactiveUI
Content = DefaultContent;
return;
}
-
+
var viewLocator = ViewLocator ?? global::ReactiveUI.ViewLocator.Current;
- var viewInstance = viewLocator.ResolveView(viewModel);
+ var viewInstance = viewLocator.ResolveView(viewModel, contract);
if (viewInstance == null)
{
- this.Log().Warn($"Couldn't find view for '{viewModel}'. Is it registered? Falling back to default content.");
+ if (contract == null)
+ {
+ this.Log().Warn($"Couldn't find view for '{viewModel}'. Is it registered? Falling back to default content.");
+ }
+ else
+ {
+ this.Log().Warn($"Couldn't find view with contract '{contract}' for '{viewModel}'. Is it registered? Falling back to default content.");
+ }
+
Content = DefaultContent;
return;
}
-
- this.Log().Info($"Ready to show {viewInstance} with autowired {viewModel}.");
+
+ if (contract == null)
+ {
+ this.Log().Info($"Ready to show {viewInstance} with autowired {viewModel}.");
+ }
+ else
+ {
+ this.Log().Info($"Ready to show {viewInstance} with autowired {viewModel} and contract '{contract}'.");
+ }
+
viewInstance.ViewModel = viewModel;
if (viewInstance is IStyledElement styled)
styled.DataContext = viewModel;
diff --git a/src/Avalonia.Remote.Protocol/Avalonia.Remote.Protocol.csproj b/src/Avalonia.Remote.Protocol/Avalonia.Remote.Protocol.csproj
index 3be0a688ab..5a8301a2e9 100644
--- a/src/Avalonia.Remote.Protocol/Avalonia.Remote.Protocol.csproj
+++ b/src/Avalonia.Remote.Protocol/Avalonia.Remote.Protocol.csproj
@@ -1,6 +1,6 @@
- netstandard2.0;net6.0
+ net6.0;netstandard2.0
AVALONIA_REMOTE_PROTOCOL;$(DefineConstants)
true
Key.snk
diff --git a/src/Avalonia.Styling/Avalonia.Styling.csproj b/src/Avalonia.Styling/Avalonia.Styling.csproj
index 3548749846..139ba1bd2e 100644
--- a/src/Avalonia.Styling/Avalonia.Styling.csproj
+++ b/src/Avalonia.Styling/Avalonia.Styling.csproj
@@ -1,6 +1,6 @@
- netstandard2.0;net6.0
+ net6.0;netstandard2.0
Avalonia.Styling
Avalonia
diff --git a/src/Avalonia.Themes.Default/Avalonia.Themes.Default.csproj b/src/Avalonia.Themes.Default/Avalonia.Themes.Default.csproj
index d132dc4f5d..678f75b43f 100644
--- a/src/Avalonia.Themes.Default/Avalonia.Themes.Default.csproj
+++ b/src/Avalonia.Themes.Default/Avalonia.Themes.Default.csproj
@@ -1,6 +1,6 @@
- netstandard2.0;net6.0
+ net6.0;netstandard2.0
diff --git a/src/Avalonia.Themes.Default/ButtonSpinner.xaml b/src/Avalonia.Themes.Default/ButtonSpinner.xaml
index ce2b85d2b5..561ca8bedd 100644
--- a/src/Avalonia.Themes.Default/ButtonSpinner.xaml
+++ b/src/Avalonia.Themes.Default/ButtonSpinner.xaml
@@ -5,6 +5,7 @@
+
+
diff --git a/src/Avalonia.Themes.Fluent/Controls/ListBox.xaml b/src/Avalonia.Themes.Fluent/Controls/ListBox.xaml
index 47704c12bd..1b4d66405b 100644
--- a/src/Avalonia.Themes.Fluent/Controls/ListBox.xaml
+++ b/src/Avalonia.Themes.Fluent/Controls/ListBox.xaml
@@ -20,6 +20,7 @@
-
+
+
+
+
diff --git a/src/Avalonia.Themes.Fluent/FluentTheme.cs b/src/Avalonia.Themes.Fluent/FluentTheme.cs
index 43b71567fa..53be41e4d1 100644
--- a/src/Avalonia.Themes.Fluent/FluentTheme.cs
+++ b/src/Avalonia.Themes.Fluent/FluentTheme.cs
@@ -2,6 +2,7 @@
using System.Collections.Generic;
using Avalonia.Controls;
using Avalonia.Markup.Xaml;
+using Avalonia.Markup.Xaml.Styling;
using Avalonia.Styling;
#nullable enable
@@ -17,11 +18,14 @@ namespace Avalonia.Themes.Fluent
///
/// Includes the fluent theme in an application.
///
- public class FluentTheme : IStyle, IResourceProvider
+ public class FluentTheme : AvaloniaObject, IStyle, IResourceProvider
{
private readonly Uri _baseUri;
- private IStyle[]? _loaded;
+ private Styles _fluentDark = new();
+ private Styles _fluentLight = new();
+ private Styles _sharedStyles = new();
private bool _isLoading;
+ private IStyle? _loaded;
///
/// Initializes a new instance of the class.
@@ -30,6 +34,7 @@ namespace Avalonia.Themes.Fluent
public FluentTheme(Uri baseUri)
{
_baseUri = baseUri;
+ InitStyles(baseUri);
}
///
@@ -39,12 +44,37 @@ namespace Avalonia.Themes.Fluent
public FluentTheme(IServiceProvider serviceProvider)
{
_baseUri = ((IUriContext)serviceProvider.GetService(typeof(IUriContext))).BaseUri;
+ InitStyles(_baseUri);
}
+
+ public static readonly StyledProperty ModeProperty =
+ AvaloniaProperty.Register(nameof(Mode));
///
/// Gets or sets the mode of the fluent theme (light, dark).
///
- public FluentThemeMode Mode { get; set; }
+ public FluentThemeMode Mode
+ {
+ get => GetValue(ModeProperty);
+ set => SetValue(ModeProperty, value);
+ }
+ protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs change)
+ {
+ base.OnPropertyChanged(change);
+ if (change.Property == ModeProperty)
+ {
+ if (Mode == FluentThemeMode.Dark)
+ {
+ (Loaded as Styles)![1] = _fluentDark[0];
+ (Loaded as Styles)![2] = _fluentDark[1];
+ }
+ else
+ {
+ (Loaded as Styles)![1] = _fluentLight[0];
+ (Loaded as Styles)![2] = _fluentLight[1];
+ }
+ }
+ }
public IResourceHost? Owner => (Loaded as IResourceProvider)?.Owner;
@@ -58,18 +88,25 @@ namespace Avalonia.Themes.Fluent
if (_loaded == null)
{
_isLoading = true;
- var loaded = (IStyle)AvaloniaXamlLoader.Load(GetUri(), _baseUri);
- _loaded = new[] { loaded };
+
+ if (Mode == FluentThemeMode.Light)
+ {
+ _loaded = new Styles() { _sharedStyles , _fluentLight[0], _fluentLight[1] };
+ }
+ else if (Mode == FluentThemeMode.Dark)
+ {
+ _loaded = new Styles() { _sharedStyles, _fluentDark[0], _fluentDark[1] };
+ }
_isLoading = false;
}
- return _loaded?[0]!;
+ return _loaded!;
}
}
bool IResourceNode.HasResources => (Loaded as IResourceProvider)?.HasResources ?? false;
- IReadOnlyList IStyle.Children => _loaded ?? Array.Empty();
+ IReadOnlyList IStyle.Children => _loaded?.Children ?? Array.Empty();
public event EventHandler OwnerChanged
{
@@ -105,10 +142,47 @@ namespace Avalonia.Themes.Fluent
void IResourceProvider.AddOwner(IResourceHost owner) => (Loaded as IResourceProvider)?.AddOwner(owner);
void IResourceProvider.RemoveOwner(IResourceHost owner) => (Loaded as IResourceProvider)?.RemoveOwner(owner);
- private Uri GetUri() => Mode switch
+ private void InitStyles(Uri baseUri)
{
- FluentThemeMode.Dark => new Uri("avares://Avalonia.Themes.Fluent/FluentDark.xaml", UriKind.Absolute),
- _ => new Uri("avares://Avalonia.Themes.Fluent/FluentLight.xaml", UriKind.Absolute),
- };
+ _sharedStyles = new Styles
+ {
+ new StyleInclude(baseUri)
+ {
+ Source = new Uri("avares://Avalonia.Themes.Fluent/Accents/AccentColors.xaml")
+ },
+ new StyleInclude(baseUri)
+ {
+ Source = new Uri("avares://Avalonia.Themes.Fluent/Accents/Base.xaml")
+ },
+ new StyleInclude(baseUri)
+ {
+ Source = new Uri("avares://Avalonia.Themes.Fluent/Controls/FluentControls.xaml")
+ }
+ };
+
+ _fluentLight = new Styles
+ {
+ new StyleInclude(baseUri)
+ {
+ Source = new Uri("avares://Avalonia.Themes.Fluent/Accents/BaseLight.xaml")
+ },
+ new StyleInclude(baseUri)
+ {
+ Source = new Uri("avares://Avalonia.Themes.Fluent/Accents/FluentControlResourcesLight.xaml")
+ }
+ };
+
+ _fluentDark = new Styles
+ {
+ new StyleInclude(baseUri)
+ {
+ Source = new Uri("avares://Avalonia.Themes.Fluent/Accents/BaseDark.xaml")
+ },
+ new StyleInclude(baseUri)
+ {
+ Source = new Uri("avares://Avalonia.Themes.Fluent/Accents/FluentControlResourcesDark.xaml")
+ }
+ };
+ }
}
}
diff --git a/src/Avalonia.Visuals/ApiCompatBaseline.txt b/src/Avalonia.Visuals/ApiCompatBaseline.txt
index ad9a5fd71a..68e3673cfe 100644
--- a/src/Avalonia.Visuals/ApiCompatBaseline.txt
+++ b/src/Avalonia.Visuals/ApiCompatBaseline.txt
@@ -7,6 +7,9 @@ InterfacesShouldHaveSameMembers : Interface member 'public System.Threading.Task
MembersMustExist : Member 'public System.Threading.Tasks.Task Avalonia.Animation.PageSlide.Start(Avalonia.Visual, Avalonia.Visual, System.Boolean)' does not exist in the implementation but it does exist in the contract.
MembersMustExist : Member 'public void Avalonia.Media.GlyphRun..ctor()' does not exist in the implementation but it does exist in the contract.
MembersMustExist : Member 'public void Avalonia.Media.GlyphRun.GlyphTypeface.set(Avalonia.Media.GlyphTypeface)' does not exist in the implementation but it does exist in the contract.
+CannotSealType : Type 'Avalonia.Media.Pen' is actually (has the sealed modifier) sealed in the implementation but not sealed in the contract.
+MembersMustExist : Member 'protected void Avalonia.Media.Pen.AffectsRender(Avalonia.AvaloniaProperty[])' does not exist in the implementation but it does exist in the contract.
+MembersMustExist : Member 'protected void Avalonia.Media.Pen.RaiseInvalidated(System.EventArgs)' does not exist in the implementation but it does exist in the contract.
TypeCannotChangeClassification : Type 'Avalonia.Media.Immutable.ImmutableSolidColorBrush' is a 'class' in the implementation but is a 'struct' in the contract.
MembersMustExist : Member 'public void Avalonia.Media.TextFormatting.DrawableTextRun.Draw(Avalonia.Media.DrawingContext)' does not exist in the implementation but it does exist in the contract.
CannotAddAbstractMembers : Member 'public void Avalonia.Media.TextFormatting.DrawableTextRun.Draw(Avalonia.Media.DrawingContext, Avalonia.Point)' is abstract in the implementation but is missing in the contract.
@@ -79,4 +82,8 @@ InterfacesShouldHaveSameMembers : Interface member 'public Avalonia.Platform.IWr
InterfacesShouldHaveSameMembers : Interface member 'public Avalonia.Platform.IWriteableBitmapImpl Avalonia.Platform.IPlatformRenderInterface.LoadWriteableBitmap(System.String)' is present in the implementation but not in the contract.
InterfacesShouldHaveSameMembers : Interface member 'public Avalonia.Platform.IWriteableBitmapImpl Avalonia.Platform.IPlatformRenderInterface.LoadWriteableBitmapToHeight(System.IO.Stream, System.Int32, Avalonia.Visuals.Media.Imaging.BitmapInterpolationMode)' is present in the implementation but not in the contract.
InterfacesShouldHaveSameMembers : Interface member 'public Avalonia.Platform.IWriteableBitmapImpl Avalonia.Platform.IPlatformRenderInterface.LoadWriteableBitmapToWidth(System.IO.Stream, System.Int32, Avalonia.Visuals.Media.Imaging.BitmapInterpolationMode)' is present in the implementation but not in the contract.
-Total Issues: 79
+InterfacesShouldHaveSameMembers : Interface member 'public Avalonia.Size Avalonia.Platform.IPlatformSettings.TouchDoubleClickSize' is present in the implementation but not in the contract.
+InterfacesShouldHaveSameMembers : Interface member 'public System.TimeSpan Avalonia.Platform.IPlatformSettings.TouchDoubleClickTime' is present in the implementation but not in the contract.
+InterfacesShouldHaveSameMembers : Interface member 'public Avalonia.Size Avalonia.Platform.IPlatformSettings.TouchDoubleClickSize.get()' is present in the implementation but not in the contract.
+InterfacesShouldHaveSameMembers : Interface member 'public System.TimeSpan Avalonia.Platform.IPlatformSettings.TouchDoubleClickTime.get()' is present in the implementation but not in the contract.
+Total Issues: 87
diff --git a/src/Avalonia.Visuals/Avalonia.Visuals.csproj b/src/Avalonia.Visuals/Avalonia.Visuals.csproj
index 486f08913c..6c978e970e 100644
--- a/src/Avalonia.Visuals/Avalonia.Visuals.csproj
+++ b/src/Avalonia.Visuals/Avalonia.Visuals.csproj
@@ -1,6 +1,6 @@
- netstandard2.0;net6.0
+ net6.0;netstandard2.0
Avalonia
true
diff --git a/src/Avalonia.Visuals/Media/Pen.cs b/src/Avalonia.Visuals/Media/Pen.cs
index 7c966a35cf..65ba851100 100644
--- a/src/Avalonia.Visuals/Media/Pen.cs
+++ b/src/Avalonia.Visuals/Media/Pen.cs
@@ -7,7 +7,7 @@ namespace Avalonia.Media
///
/// Describes how a stroke is drawn.
///
- public class Pen : AvaloniaObject, IPen
+ public sealed class Pen : AvaloniaObject, IPen, IWeakEventSubscriber
{
///
/// Defines the property.
@@ -45,6 +45,10 @@ namespace Avalonia.Media
public static readonly StyledProperty MiterLimitProperty =
AvaloniaProperty.Register(nameof(MiterLimit), 10.0);
+ private EventHandler? _invalidated;
+ private IAffectsRender? _subscribedToBrush;
+ private IAffectsRender? _subscribedToDashes;
+
///
/// Initializes a new instance of the class.
///
@@ -96,17 +100,6 @@ namespace Avalonia.Media
DashStyle = dashStyle;
}
- static Pen()
- {
- AffectsRender(
- BrushProperty,
- ThicknessProperty,
- DashStyleProperty,
- LineCapProperty,
- LineJoinProperty,
- MiterLimitProperty);
- }
-
///
/// Gets or sets the brush used to draw the stroke.
///
@@ -116,6 +109,11 @@ namespace Avalonia.Media
set => SetValue(BrushProperty, value);
}
+ private static readonly WeakEvent InvalidatedWeakEvent =
+ WeakEvent.Register(
+ (s, h) => s.Invalidated += h,
+ (s, h) => s.Invalidated -= h);
+
///
/// Gets or sets the stroke thickness.
///
@@ -165,7 +163,19 @@ namespace Avalonia.Media
///
/// Raised when the pen changes.
///
- public event EventHandler? Invalidated;
+ public event EventHandler? Invalidated
+ {
+ add
+ {
+ _invalidated += value;
+ UpdateSubscriptions();
+ }
+ remove
+ {
+ _invalidated -= value;
+ UpdateSubscriptions();
+ }
+ }
///
/// Creates an immutable clone of the brush.
@@ -182,68 +192,42 @@ namespace Avalonia.Media
MiterLimit);
}
- ///
- /// Marks a property as affecting the pen's visual representation.
- ///
- /// The properties.
- ///
- /// After a call to this method in a pen's static constructor, any change to the
- /// property will cause the event to be raised on the pen.
- ///
- protected static void AffectsRender(params AvaloniaProperty[] properties)
- where T : Pen
+ protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs change)
{
- static void Invalidate(AvaloniaPropertyChangedEventArgs e)
- {
- if (e.Sender is T sender)
- {
- sender.RaiseInvalidated(EventArgs.Empty);
- }
- }
+ _invalidated?.Invoke(this, EventArgs.Empty);
+ if(change.Property == BrushProperty)
+ UpdateSubscription(ref _subscribedToBrush, Brush);
+ if(change.Property == DashStyleProperty)
+ UpdateSubscription(ref _subscribedToDashes, DashStyle);
+ base.OnPropertyChanged(change);
+ }
- static void InvalidateAndSubscribe(AvaloniaPropertyChangedEventArgs e)
+
+ void UpdateSubscription(ref IAffectsRender? field, object? value)
+ {
+ if ((_invalidated == null || field != value) && field != null)
{
- if (e.Sender is T sender)
- {
- if (e.OldValue is IAffectsRender oldValue)
- {
- WeakEventHandlerManager.Unsubscribe(
- oldValue,
- nameof(oldValue.Invalidated),
- sender.AffectsRenderInvalidated);
- }
-
- if (e.NewValue is IAffectsRender newValue)
- {
- WeakEventHandlerManager.Subscribe(
- newValue,
- nameof(newValue.Invalidated),
- sender.AffectsRenderInvalidated);
- }
-
- sender.RaiseInvalidated(EventArgs.Empty);
- }
+ InvalidatedWeakEvent.Unsubscribe(field, this);
+ field = null;
}
- foreach (var property in properties)
+ if (_invalidated != null && field != value && value is IAffectsRender affectsRender)
{
- if (property.CanValueAffectRender())
- {
- property.Changed.Subscribe(e => InvalidateAndSubscribe(e));
- }
- else
- {
- property.Changed.Subscribe(e => Invalidate(e));
- }
+ InvalidatedWeakEvent.Subscribe(affectsRender, this);
+ field = affectsRender;
}
}
- ///
- /// Raises the event.
- ///
- /// The event args.
- protected void RaiseInvalidated(EventArgs e) => Invalidated?.Invoke(this, e);
-
- private void AffectsRenderInvalidated(object? sender, EventArgs e) => RaiseInvalidated(EventArgs.Empty);
+ void UpdateSubscriptions()
+ {
+ UpdateSubscription(ref _subscribedToBrush, Brush);
+ UpdateSubscription(ref _subscribedToDashes, DashStyle);
+ }
+
+ void IWeakEventSubscriber.OnEvent(object? sender, WeakEvent ev, EventArgs e)
+ {
+ if (ev == InvalidatedWeakEvent)
+ _invalidated?.Invoke(this, EventArgs.Empty);
+ }
}
}
diff --git a/src/Avalonia.Visuals/Media/TextFormatting/Unicode/LineBreakEnumerator.cs b/src/Avalonia.Visuals/Media/TextFormatting/Unicode/LineBreakEnumerator.cs
index 40891a700d..0f6b81975a 100644
--- a/src/Avalonia.Visuals/Media/TextFormatting/Unicode/LineBreakEnumerator.cs
+++ b/src/Avalonia.Visuals/Media/TextFormatting/Unicode/LineBreakEnumerator.cs
@@ -80,12 +80,12 @@ namespace Avalonia.Media.TextFormatting.Unicode
}
}
- var shouldBreak = GetSimpleBreak() ?? (bool?)GetPairTableBreak(lastClass);
+ var shouldBreak = GetSimpleBreak() ?? GetPairTableBreak(lastClass);
// Rule LB8a
_lb8a = _nextClass == LineBreakClass.ZWJ;
- if (shouldBreak.Value)
+ if (shouldBreak)
{
Current = new LineBreak(FindPriorNonWhitespace(_lastPosition), _lastPosition);
return true;
diff --git a/src/Avalonia.Visuals/Platform/IPlatformSettings.cs b/src/Avalonia.Visuals/Platform/IPlatformSettings.cs
index bcb00df5d6..e4b28e6575 100644
--- a/src/Avalonia.Visuals/Platform/IPlatformSettings.cs
+++ b/src/Avalonia.Visuals/Platform/IPlatformSettings.cs
@@ -7,5 +7,15 @@ namespace Avalonia.Platform
Size DoubleClickSize { get; }
TimeSpan DoubleClickTime { get; }
+
+ ///
+ /// Determines the size of the area within that you should click twice in order for a double click to be counted.
+ ///
+ Size TouchDoubleClickSize { get; }
+
+ ///
+ /// Determines the time span that what will be used to determine the double-click.
+ ///
+ TimeSpan TouchDoubleClickTime { get; }
}
}
diff --git a/src/Avalonia.Visuals/Rendering/SceneGraph/SceneBuilder.cs b/src/Avalonia.Visuals/Rendering/SceneGraph/SceneBuilder.cs
index cb916293ac..63c22efc3f 100644
--- a/src/Avalonia.Visuals/Rendering/SceneGraph/SceneBuilder.cs
+++ b/src/Avalonia.Visuals/Rendering/SceneGraph/SceneBuilder.cs
@@ -126,7 +126,12 @@ namespace Avalonia.Rendering.SceneGraph
while (node == null && visual.IsVisible)
{
- visual = visual.VisualParent!;
+ var parent = visual.VisualParent;
+
+ if (parent is null)
+ return null;
+
+ visual = parent;
node = scene.FindNode(visual);
}
diff --git a/src/Avalonia.X11/Avalonia.X11.csproj b/src/Avalonia.X11/Avalonia.X11.csproj
index f9efe5581f..9ba5c9d15f 100644
--- a/src/Avalonia.X11/Avalonia.X11.csproj
+++ b/src/Avalonia.X11/Avalonia.X11.csproj
@@ -1,6 +1,6 @@
- netstandard2.0;net6.0
+ net6.0;netstandard2.0
true
Avalonia.X11
diff --git a/src/Avalonia.X11/Glx/GlxDisplay.cs b/src/Avalonia.X11/Glx/GlxDisplay.cs
index fa8c866c09..fcdc10e999 100644
--- a/src/Avalonia.X11/Glx/GlxDisplay.cs
+++ b/src/Avalonia.X11/Glx/GlxDisplay.cs
@@ -95,8 +95,8 @@ namespace Avalonia.X11.Glx
if (Environment.GetEnvironmentVariable("AVALONIA_GLX_IGNORE_RENDERER_BLACKLIST") != "1")
{
- var blacklist = AvaloniaLocator.Current.GetService()
- ?.GlxRendererBlacklist;
+ var opts = AvaloniaLocator.Current.GetService() ?? new X11PlatformOptions();
+ var blacklist = opts.GlxRendererBlacklist;
if (blacklist != null)
foreach (var item in blacklist)
if (glInterface.Renderer.Contains(item))
diff --git a/src/Avalonia.X11/Stubs.cs b/src/Avalonia.X11/Stubs.cs
index ec694ba9a8..f73512f1e8 100644
--- a/src/Avalonia.X11/Stubs.cs
+++ b/src/Avalonia.X11/Stubs.cs
@@ -1,10 +1,4 @@
using System;
-using System.IO;
-using System.Threading.Tasks;
-using Avalonia.Controls;
-using Avalonia.Controls.Platform;
-using Avalonia.Input;
-using Avalonia.Input.Platform;
using Avalonia.Platform;
namespace Avalonia.X11
@@ -13,5 +7,11 @@ namespace Avalonia.X11
{
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;
}
}
diff --git a/src/Avalonia.X11/X11Platform.cs b/src/Avalonia.X11/X11Platform.cs
index a18f91d301..ec3f29c806 100644
--- a/src/Avalonia.X11/X11Platform.cs
+++ b/src/Avalonia.X11/X11Platform.cs
@@ -68,6 +68,7 @@ namespace Avalonia.X11
//TODO: log
if (options.UseDBusMenu)
DBusHelper.TryInitialize();
+
AvaloniaLocator.CurrentMutable.BindToSelf(this)
.Bind().ToConstant(this)
.Bind().ToConstant(new X11PlatformThreading(this))
diff --git a/src/Avalonia.X11/X11Window.cs b/src/Avalonia.X11/X11Window.cs
index 982bca6439..211a8e4f79 100644
--- a/src/Avalonia.X11/X11Window.cs
+++ b/src/Avalonia.X11/X11Window.cs
@@ -1157,7 +1157,7 @@ namespace Avalonia.X11
public ITextInputMethodImpl TextInputMethod => _ime;
public void SetTransparencyLevelHint(WindowTransparencyLevel transparencyLevel) =>
- _transparencyHelper.SetTransparencyRequest(transparencyLevel);
+ _transparencyHelper?.SetTransparencyRequest(transparencyLevel);
public void SetWindowManagerAddShadowHint(bool enabled)
{
diff --git a/src/Directory.Build.props b/src/Directory.Build.props
index a3f0c01a51..869602e452 100644
--- a/src/Directory.Build.props
+++ b/src/Directory.Build.props
@@ -2,4 +2,10 @@
+
+
+ Shared\_ModuleInitializer.cs
+ false
+
+
diff --git a/src/Linux/Avalonia.LinuxFramebuffer/Avalonia.LinuxFramebuffer.csproj b/src/Linux/Avalonia.LinuxFramebuffer/Avalonia.LinuxFramebuffer.csproj
index 41e3049bb8..6a2eb18385 100644
--- a/src/Linux/Avalonia.LinuxFramebuffer/Avalonia.LinuxFramebuffer.csproj
+++ b/src/Linux/Avalonia.LinuxFramebuffer/Avalonia.LinuxFramebuffer.csproj
@@ -1,6 +1,6 @@
- netstandard2.0;net6.0
+ net6.0;netstandard2.0
true
Avalonia.LinuxFramebuffer
diff --git a/src/Linux/Avalonia.LinuxFramebuffer/LinuxFramebufferPlatform.cs b/src/Linux/Avalonia.LinuxFramebuffer/LinuxFramebufferPlatform.cs
index f4db6bf48a..4add4c423b 100644
--- a/src/Linux/Avalonia.LinuxFramebuffer/LinuxFramebufferPlatform.cs
+++ b/src/Linux/Avalonia.LinuxFramebuffer/LinuxFramebufferPlatform.cs
@@ -38,11 +38,11 @@ namespace Avalonia.LinuxFramebuffer
if (_fb is IGlOutputBackend gl)
AvaloniaLocator.CurrentMutable.Bind().ToConstant(gl.PlatformOpenGlInterface);
- var opts = AvaloniaLocator.Current.GetService();
+ var opts = AvaloniaLocator.Current.GetService() ?? new LinuxFramebufferPlatformOptions();
AvaloniaLocator.CurrentMutable
.Bind().ToConstant(Threading)
- .Bind().ToConstant(new DefaultRenderTimer(opts?.Fps ?? 60))
+ .Bind().ToConstant(new DefaultRenderTimer(opts.Fps))
.Bind().ToConstant(new RenderLoop())
.Bind().ToTransient()
.Bind().ToConstant(new KeyboardDevice())
diff --git a/src/Linux/Avalonia.LinuxFramebuffer/Stubs.cs b/src/Linux/Avalonia.LinuxFramebuffer/Stubs.cs
index 642be28c69..dd60d5f09d 100644
--- a/src/Linux/Avalonia.LinuxFramebuffer/Stubs.cs
+++ b/src/Linux/Avalonia.LinuxFramebuffer/Stubs.cs
@@ -18,5 +18,9 @@ namespace Avalonia.LinuxFramebuffer
{
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;
}
}
diff --git a/src/Markup/Avalonia.Markup.Xaml.Loader/Avalonia.Markup.Xaml.Loader.csproj b/src/Markup/Avalonia.Markup.Xaml.Loader/Avalonia.Markup.Xaml.Loader.csproj
index fdfb99789e..f75c96c759 100644
--- a/src/Markup/Avalonia.Markup.Xaml.Loader/Avalonia.Markup.Xaml.Loader.csproj
+++ b/src/Markup/Avalonia.Markup.Xaml.Loader/Avalonia.Markup.Xaml.Loader.csproj
@@ -1,7 +1,7 @@
- netstandard2.0;net6.0
+ net6.0;netstandard2.0
true
Avalonia.Markup.Xaml.Loader
$(DefineConstants);XAMLX_INTERNAL
diff --git a/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlBindingPathTransformer.cs b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlBindingPathTransformer.cs
index 4d21a29eb9..048a6220c5 100644
--- a/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlBindingPathTransformer.cs
+++ b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlBindingPathTransformer.cs
@@ -3,6 +3,7 @@ using System.Collections.Generic;
using System.Linq;
using XamlX.Ast;
using XamlX.Transform;
+using XamlX.Transform.Transformers;
using XamlX.TypeSystem;
namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers
@@ -15,7 +16,8 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers
{
IXamlType startType = null;
var sourceProperty = binding.Children.OfType().FirstOrDefault(c => c.Property.Name == "Source");
- if ((sourceProperty?.Values.Count ?? 0) == 1)
+ var dataTypeProperty = binding.Children.OfType().FirstOrDefault(c => c.Property.Name == "DataType");
+ if (sourceProperty?.Values.Count is 1)
{
var sourceValue = sourceProperty.Values[0];
switch (sourceValue)
@@ -99,6 +101,11 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers
}
}
+ if (dataTypeProperty?.Values.Count is 1 && dataTypeProperty.Values[0] is XamlAstTextNode text)
+ {
+ startType = TypeReferenceResolver.ResolveType(context, text.Text, isMarkupExtension: false, text, strict: true).Type;
+ }
+
Func startTypeResolver = startType is not null ? () => startType : () =>
{
var parentDataContextNode = context.ParentNodes().OfType().FirstOrDefault();
diff --git a/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlDataContextTypeTransformer.cs b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlDataContextTypeTransformer.cs
index 24dd3f7771..cf691db860 100644
--- a/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlDataContextTypeTransformer.cs
+++ b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlDataContextTypeTransformer.cs
@@ -155,6 +155,12 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers
{
Func startTypeResolver = () =>
{
+ var dataTypeProperty = obj.Children.OfType().FirstOrDefault(c => c.Property.Name == "DataType");
+ if (dataTypeProperty?.Values.Count is 1 && dataTypeProperty.Values[0] is XamlAstTextNode text)
+ {
+ return TypeReferenceResolver.ResolveType(context, text.Text, isMarkupExtension: false, text, strict: true).Type;
+ }
+
var parentDataContextNode = context.ParentNodes().OfType().FirstOrDefault();
if (parentDataContextNode is null)
{
diff --git a/src/Markup/Avalonia.Markup.Xaml/Avalonia.Markup.Xaml.csproj b/src/Markup/Avalonia.Markup.Xaml/Avalonia.Markup.Xaml.csproj
index ac6aced8cb..86132c5d27 100644
--- a/src/Markup/Avalonia.Markup.Xaml/Avalonia.Markup.Xaml.csproj
+++ b/src/Markup/Avalonia.Markup.Xaml/Avalonia.Markup.Xaml.csproj
@@ -1,6 +1,6 @@
- netstandard2.0;net6.0
+ net6.0;netstandard2.0
$(DefineConstants);PCL;NETSTANDARD;NETSTANDARD2_0;HAS_TYPE_CONVERTER;HAS_CUSTOM_ATTRIBUTE_PROVIDER;XAMLX_INTERNAL
false
$(DefineConstants);RUNTIME_XAML_CECIL
diff --git a/src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/CompiledBindingExtension.cs b/src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/CompiledBindingExtension.cs
index 41de2355aa..0125156750 100644
--- a/src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/CompiledBindingExtension.cs
+++ b/src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/CompiledBindingExtension.cs
@@ -71,5 +71,7 @@ namespace Avalonia.Markup.Xaml.MarkupExtensions
public CompiledBindingPath Path { get; set; }
public object Source { get; set; }
+
+ public Type DataType { get; set; }
}
}
diff --git a/src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/CompiledBindings/PropertyInfoAccessorFactory.cs b/src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/CompiledBindings/PropertyInfoAccessorFactory.cs
index b3f78bfbe3..c21a2d4299 100644
--- a/src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/CompiledBindings/PropertyInfoAccessorFactory.cs
+++ b/src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/CompiledBindings/PropertyInfoAccessorFactory.cs
@@ -72,7 +72,7 @@ namespace Avalonia.Markup.Xaml.MarkupExtensions.CompiledBindings
}
}
- internal class InpcPropertyAccessor : PropertyAccessorBase
+ internal class InpcPropertyAccessor : PropertyAccessorBase, IWeakEventSubscriber
{
protected readonly WeakReference _reference;
private readonly IPropertyInfo _property;
@@ -110,7 +110,7 @@ namespace Avalonia.Markup.Xaml.MarkupExtensions.CompiledBindings
return false;
}
- void OnNotifyPropertyChanged(object sender, PropertyChangedEventArgs e)
+ public void OnEvent(object sender, WeakEvent ev, PropertyChangedEventArgs e)
{
if (e.PropertyName == _property.Name || string.IsNullOrEmpty(e.PropertyName))
{
@@ -128,10 +128,7 @@ namespace Avalonia.Markup.Xaml.MarkupExtensions.CompiledBindings
{
if (_reference.TryGetTarget(out var o) && o is INotifyPropertyChanged inpc)
{
- WeakEventHandlerManager.Unsubscribe(
- inpc,
- nameof(INotifyPropertyChanged.PropertyChanged),
- OnNotifyPropertyChanged);
+ WeakEvents.PropertyChanged.Unsubscribe(inpc, this);
}
}
@@ -148,16 +145,11 @@ namespace Avalonia.Markup.Xaml.MarkupExtensions.CompiledBindings
private void SubscribeToChanges()
{
if (_reference.TryGetTarget(out var o) && o is INotifyPropertyChanged inpc)
- {
- WeakEventHandlerManager.Subscribe(
- inpc,
- nameof(INotifyPropertyChanged.PropertyChanged),
- OnNotifyPropertyChanged);
- }
+ WeakEvents.PropertyChanged.Subscribe(inpc, this);
}
}
- internal class IndexerAccessor : InpcPropertyAccessor
+ internal class IndexerAccessor : InpcPropertyAccessor, IWeakEventSubscriber
{
private int _index;
@@ -172,27 +164,17 @@ namespace Avalonia.Markup.Xaml.MarkupExtensions.CompiledBindings
{
base.SubscribeCore();
if (_reference.TryGetTarget(out var o) && o is INotifyCollectionChanged incc)
- {
- WeakEventHandlerManager.Subscribe(
- incc,
- nameof(INotifyCollectionChanged.CollectionChanged),
- OnNotifyCollectionChanged);
- }
+ WeakEvents.CollectionChanged.Subscribe(incc, this);
}
protected override void UnsubscribeCore()
{
base.UnsubscribeCore();
if (_reference.TryGetTarget(out var o) && o is INotifyCollectionChanged incc)
- {
- WeakEventHandlerManager.Unsubscribe(
- incc,
- nameof(INotifyCollectionChanged.CollectionChanged),
- OnNotifyCollectionChanged);
- }
+ WeakEvents.CollectionChanged.Unsubscribe(incc, this);
}
-
- void OnNotifyCollectionChanged(object sender, NotifyCollectionChangedEventArgs args)
+
+ public void OnEvent(object? sender, WeakEvent ev, NotifyCollectionChangedEventArgs args)
{
if (ShouldNotifyListeners(args))
{
diff --git a/src/Markup/Avalonia.Markup/Avalonia.Markup.csproj b/src/Markup/Avalonia.Markup/Avalonia.Markup.csproj
index c8f60924c6..21370b3bf5 100644
--- a/src/Markup/Avalonia.Markup/Avalonia.Markup.csproj
+++ b/src/Markup/Avalonia.Markup/Avalonia.Markup.csproj
@@ -1,6 +1,6 @@
- netstandard2.0;net6.0
+ net6.0;netstandard2.0
Avalonia
diff --git a/src/Shared/ModuleInitializer.cs b/src/Shared/ModuleInitializer.cs
new file mode 100644
index 0000000000..c14b150c1a
--- /dev/null
+++ b/src/Shared/ModuleInitializer.cs
@@ -0,0 +1,10 @@
+namespace System.Runtime.CompilerServices
+{
+#if !NET5_0_OR_GREATER
+ internal class ModuleInitializerAttribute : Attribute
+ {
+
+ }
+#endif
+}
+
diff --git a/src/Skia/Avalonia.Skia/Avalonia.Skia.csproj b/src/Skia/Avalonia.Skia/Avalonia.Skia.csproj
index 11a64c10ae..bc14494f6a 100644
--- a/src/Skia/Avalonia.Skia/Avalonia.Skia.csproj
+++ b/src/Skia/Avalonia.Skia/Avalonia.Skia.csproj
@@ -1,6 +1,6 @@
- netstandard2.0;net6.0
+ net6.0;netstandard2.0
Avalonia.Skia
Avalonia.Skia
Avalonia.Skia
diff --git a/src/Web/Avalonia.Web.Blazor/AvaloniaView.razor.cs b/src/Web/Avalonia.Web.Blazor/AvaloniaView.razor.cs
index bc2a2f3103..7644514687 100644
--- a/src/Web/Avalonia.Web.Blazor/AvaloniaView.razor.cs
+++ b/src/Web/Avalonia.Web.Blazor/AvaloniaView.razor.cs
@@ -22,6 +22,7 @@ namespace Avalonia.Web.Blazor
private DpiWatcherInterop _dpiWatcher = null!;
private SKHtmlCanvasInterop.GLInfo? _jsGlInfo = null!;
private InputHelperInterop _inputHelper = null!;
+ private InputHelperInterop _canvasHelper = null!;
private ElementReference _htmlCanvas;
private ElementReference _inputElement;
private double _dpi;
@@ -254,9 +255,15 @@ namespace Avalonia.Web.Blazor
Threading.Dispatcher.UIThread.Post(async () =>
{
_inputHelper = await InputHelperInterop.ImportAsync(Js, _inputElement);
+ _canvasHelper = await InputHelperInterop.ImportAsync(Js, _htmlCanvas);
_inputHelper.Hide();
- _inputHelper.SetCursor("default");
+ _canvasHelper.SetCursor("default");
+ _topLevelImpl.SetCssCursor = x =>
+ {
+ _inputHelper.SetCursor(x);//macOS
+ _canvasHelper.SetCursor(x);//windows
+ };
Console.WriteLine("starting html canvas setup");
_interop = await SKHtmlCanvasInterop.ImportAsync(Js, _htmlCanvas, OnRenderFrame);
diff --git a/src/Web/Avalonia.Web.Blazor/Cursor.cs b/src/Web/Avalonia.Web.Blazor/Cursor.cs
new file mode 100644
index 0000000000..d921b2fa6c
--- /dev/null
+++ b/src/Web/Avalonia.Web.Blazor/Cursor.cs
@@ -0,0 +1,93 @@
+using Avalonia.Input;
+using Avalonia.Platform;
+
+namespace Avalonia.Web.Blazor
+{
+ public class CssCursor : ICursorImpl
+ {
+ public const string Default = "default";
+ public string? Value { get; set; }
+
+ public CssCursor(StandardCursorType type)
+ {
+ Value = ToKeyword(type);
+ }
+
+ ///
+ /// Create a cursor from base64 image
+ ///
+ public CssCursor(string base64, string format, PixelPoint hotspot, StandardCursorType fallback)
+ {
+ Value = $"url(\"data:image/{format};base64,{base64}\") {hotspot.X} {hotspot.Y}, {ToKeyword(fallback)}";
+ }
+
+ ///
+ /// Create a cursor from url to *.cur file.
+ ///
+ public CssCursor(string url, StandardCursorType fallback)
+ {
+ Value = $"url('{url}'), {ToKeyword(fallback)}";
+ }
+
+ ///
+ /// Create a cursor from png/svg and hotspot position
+ ///
+ public CssCursor(string url, PixelPoint hotSpot, StandardCursorType fallback)
+ {
+ Value = $"url('{url}') {hotSpot.X} {hotSpot.Y}, {ToKeyword(fallback)}";
+ }
+
+ private static string ToKeyword(StandardCursorType type) => type switch
+ {
+ StandardCursorType.Hand => "pointer",
+ StandardCursorType.Cross => "crosshair",
+ StandardCursorType.Help => "help",
+ StandardCursorType.Ibeam => "text",
+ StandardCursorType.No => "not-allowed",
+ StandardCursorType.None => "none",
+ StandardCursorType.Wait => "progress",
+ StandardCursorType.AppStarting => "wait",
+
+ StandardCursorType.DragMove => "move",
+ StandardCursorType.DragCopy => "copy",
+ StandardCursorType.DragLink => "alias",
+
+ StandardCursorType.UpArrow => "default",/*not found matching one*/
+ StandardCursorType.SizeWestEast => "ew-resize",
+ StandardCursorType.SizeNorthSouth => "ns-resize",
+ StandardCursorType.SizeAll => "move",
+
+ StandardCursorType.TopSide => "n-resize",
+ StandardCursorType.BottomSide => "s-resize",
+ StandardCursorType.LeftSide => "w-resize",
+ StandardCursorType.RightSide => "e-resize",
+ StandardCursorType.TopLeftCorner => "nw-resize",
+ StandardCursorType.TopRightCorner => "ne-resize",
+ StandardCursorType.BottomLeftCorner => "sw-resize",
+ StandardCursorType.BottomRightCorner => "se-resize",
+
+ _ => Default,
+ };
+
+ public void Dispose() {}
+ }
+
+ internal class CssCursorFactory : ICursorFactory
+ {
+ public ICursorImpl CreateCursor(IBitmapImpl cursor, PixelPoint hotSpot)
+ {
+ using var imageStream = new MemoryStream();
+ cursor.Save(imageStream);
+
+ //not memory optimized because CryptoStream with ToBase64Transform is not supported in the browser.
+ var base64String = Convert.ToBase64String(imageStream.ToArray());
+ return new CssCursor(base64String, "png", hotSpot, StandardCursorType.Arrow);
+ }
+
+ ICursorImpl ICursorFactory.GetCursor(StandardCursorType cursorType)
+ {
+ return new CssCursor(cursorType);
+ }
+ }
+}
+
diff --git a/src/Web/Avalonia.Web.Blazor/RazorViewTopLevelImpl.cs b/src/Web/Avalonia.Web.Blazor/RazorViewTopLevelImpl.cs
index 3e9849d4dc..1d667c0f0c 100644
--- a/src/Web/Avalonia.Web.Blazor/RazorViewTopLevelImpl.cs
+++ b/src/Web/Avalonia.Web.Blazor/RazorViewTopLevelImpl.cs
@@ -21,6 +21,7 @@ namespace Avalonia.Web.Blazor
private readonly Stopwatch _sw = Stopwatch.StartNew();
private readonly ITextInputMethodImpl _textInputMethod;
private readonly TouchDevice _touchDevice;
+ private string _currentCursor = CssCursor.Default;
public RazorViewTopLevelImpl(ITextInputMethodImpl textInputMethod)
{
@@ -126,8 +127,12 @@ namespace Avalonia.Web.Blazor
public void SetCursor(ICursorImpl cursor)
{
- // nop
-
+ var val = (cursor as CssCursor)?.Value ?? CssCursor.Default;
+ if (_currentCursor != val)
+ {
+ SetCssCursor?.Invoke(val);
+ _currentCursor = val;
+ }
}
public IPopupImpl? CreatePopup()
@@ -142,10 +147,11 @@ namespace Avalonia.Web.Blazor
public Size ClientSize => _clientSize;
public Size? FrameSize => null;
- public double RenderScaling => 1;
+ public double RenderScaling => _currentSurface?.Scaling ?? 1;
public IEnumerable Surfaces => new object[] { _currentSurface! };
+ public Action? SetCssCursor { get; set; }
public Action? Input { get; set; }
public Action? Paint { get; set; }
public Action? Resized { get; set; }
diff --git a/src/Web/Avalonia.Web.Blazor/WinStubs.cs b/src/Web/Avalonia.Web.Blazor/WinStubs.cs
index 13d83a9ee3..7b2bff6bfd 100644
--- a/src/Web/Avalonia.Web.Blazor/WinStubs.cs
+++ b/src/Web/Avalonia.Web.Blazor/WinStubs.cs
@@ -23,27 +23,6 @@ namespace Avalonia.Web.Blazor
public Task GetDataAsync(string format) => Task.FromResult(new ());
}
- internal class CursorStub : ICursorImpl
- {
- public void Dispose()
- {
-
- }
- }
-
- internal class CursorFactoryStub : ICursorFactory
- {
- public ICursorImpl CreateCursor(IBitmapImpl cursor, PixelPoint hotSpot)
- {
- return new CursorStub();
- }
-
- ICursorImpl ICursorFactory.GetCursor(StandardCursorType cursorType)
- {
- return new CursorStub();
- }
- }
-
internal class IconLoaderStub : IPlatformIconLoader
{
private class IconStub : IWindowIconImpl
diff --git a/src/Web/Avalonia.Web.Blazor/WindowingPlatform.cs b/src/Web/Avalonia.Web.Blazor/WindowingPlatform.cs
index f30a36b8c9..ac970d067f 100644
--- a/src/Web/Avalonia.Web.Blazor/WindowingPlatform.cs
+++ b/src/Web/Avalonia.Web.Blazor/WindowingPlatform.cs
@@ -35,7 +35,7 @@ namespace Avalonia.Web.Blazor
s_keyboard = new KeyboardDevice();
AvaloniaLocator.CurrentMutable
.Bind().ToSingleton()
- .Bind().ToSingleton()
+ .Bind().ToSingleton()
.Bind().ToConstant(s_keyboard)
.Bind().ToConstant(instance)
.Bind().ToConstant(instance)
@@ -51,6 +51,9 @@ namespace Avalonia.Web.Blazor
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();
diff --git a/src/Windows/Avalonia.Direct2D1/Avalonia.Direct2D1.csproj b/src/Windows/Avalonia.Direct2D1/Avalonia.Direct2D1.csproj
index d6c3368b7d..bc529208ea 100644
--- a/src/Windows/Avalonia.Direct2D1/Avalonia.Direct2D1.csproj
+++ b/src/Windows/Avalonia.Direct2D1/Avalonia.Direct2D1.csproj
@@ -1,6 +1,6 @@
- netstandard2.0;net6.0
+ net6.0;netstandard2.0
true
Avalonia.Direct2D1
true
diff --git a/src/Windows/Avalonia.Win32/Avalonia.Win32.csproj b/src/Windows/Avalonia.Win32/Avalonia.Win32.csproj
index c8f08a5799..d727acdc22 100644
--- a/src/Windows/Avalonia.Win32/Avalonia.Win32.csproj
+++ b/src/Windows/Avalonia.Win32/Avalonia.Win32.csproj
@@ -1,16 +1,17 @@
- netstandard2.0;net6.0
+ net6.0;netstandard2.0
true
Avalonia.Win32
+ Avalonia.MicroCom
-
-
+
+
+
-
diff --git a/src/Windows/Avalonia.Win32/OpenGl/WglDisplay.cs b/src/Windows/Avalonia.Win32/OpenGl/WglDisplay.cs
index dc3c16b4a3..bc27589689 100644
--- a/src/Windows/Avalonia.Win32/OpenGl/WglDisplay.cs
+++ b/src/Windows/Avalonia.Win32/OpenGl/WglDisplay.cs
@@ -95,6 +95,7 @@ namespace Avalonia.Win32.OpenGl
WGL_DOUBLE_BUFFER_ARB, 1,
WGL_PIXEL_TYPE_ARB, WGL_TYPE_RGBA_ARB,
WGL_COLOR_BITS_ARB, 32,
+ WGL_ALPHA_BITS_ARB, 8,
WGL_DEPTH_BITS_ARB, 0,
WGL_STENCIL_BITS_ARB, 0,
0, // End
diff --git a/src/Windows/Avalonia.Win32/Win32GlManager.cs b/src/Windows/Avalonia.Win32/Win32GlManager.cs
index 289c100d51..0376a41f8c 100644
--- a/src/Windows/Avalonia.Win32/Win32GlManager.cs
+++ b/src/Windows/Avalonia.Win32/Win32GlManager.cs
@@ -13,19 +13,18 @@ namespace Avalonia.Win32
{
AvaloniaLocator.CurrentMutable.Bind().ToLazy(() =>
{
- var opts = AvaloniaLocator.Current.GetService();
- if (opts?.UseWgl == true)
+ var opts = AvaloniaLocator.Current.GetService() ?? new Win32PlatformOptions();
+ if (opts.UseWgl)
{
var wgl = WglPlatformOpenGlInterface.TryCreate();
return wgl;
}
- if (opts?.AllowEglInitialization ?? Win32Platform.WindowsVersion > PlatformConstants.Windows7)
+ if (opts.AllowEglInitialization ?? Win32Platform.WindowsVersion > PlatformConstants.Windows7)
{
var egl = EglPlatformOpenGlInterface.TryCreate(() => new AngleWin32EglDisplay());
- if (egl != null &&
- opts?.UseWindowsUIComposition == true)
+ if (egl != null && opts.UseWindowsUIComposition)
{
WinUICompositorConnection.TryCreateAndRegister(egl, opts.CompositionBackdropCornerRadius);
}
diff --git a/src/Windows/Avalonia.Win32/Win32Platform.cs b/src/Windows/Avalonia.Win32/Win32Platform.cs
index 2947493c41..5cfbab40e4 100644
--- a/src/Windows/Avalonia.Win32/Win32Platform.cs
+++ b/src/Windows/Avalonia.Win32/Win32Platform.cs
@@ -134,6 +134,11 @@ namespace Avalonia.Win32
public TimeSpan DoubleClickTime => TimeSpan.FromMilliseconds(UnmanagedMethods.GetDoubleClickTime());
+ ///
+ public Size TouchDoubleClickSize => new Size(16,16);
+
+ ///
+ public TimeSpan TouchDoubleClickTime => DoubleClickTime;
public static void Initialize()
{
Initialize(new Win32PlatformOptions());
diff --git a/src/iOS/Avalonia.iOS/Platform.cs b/src/iOS/Avalonia.iOS/Platform.cs
index 2cac5e6bcf..88f60ace1f 100644
--- a/src/iOS/Avalonia.iOS/Platform.cs
+++ b/src/iOS/Avalonia.iOS/Platform.cs
@@ -1,11 +1,9 @@
using System;
-using Avalonia.Controls;
using Avalonia.Input;
using Avalonia.Input.Platform;
using Avalonia.OpenGL;
using Avalonia.Platform;
using Avalonia.Rendering;
-using Avalonia.Shared.PlatformSupport;
namespace Avalonia.iOS
{
@@ -15,10 +13,17 @@ namespace Avalonia.iOS
public static DisplayLinkTimer Timer;
class PlatformSettings : IPlatformSettings
{
- public Size DoubleClickSize { get; } = new Size(10, 10);
- public TimeSpan DoubleClickTime { get; } = TimeSpan.FromMilliseconds(500);
+ ///
+ public Size TouchDoubleClickSize => new Size(10, 10);
+
+ ///
+ public TimeSpan TouchDoubleClickTime => TimeSpan.FromMilliseconds(500);
+
+ public Size DoubleClickSize => new Size(4, 4);
+
+ public TimeSpan DoubleClickTime => TouchDoubleClickTime;
}
-
+
public static void Register()
{
GlFeature ??= new EaglFeature();
diff --git a/src/tools/MicroComGenerator/Ast.cs b/src/tools/MicroComGenerator/Ast.cs
deleted file mode 100644
index e9a55308be..0000000000
--- a/src/tools/MicroComGenerator/Ast.cs
+++ /dev/null
@@ -1,241 +0,0 @@
-using System;
-using System.Collections.Generic;
-using System.Linq;
-
-namespace MicroComGenerator.Ast
-{
- public class AstAttributeNode
- {
- public string Name { get; set; }
- public string Value { get; set; }
-
- public AstAttributeNode(string name, string value)
- {
- Name = name;
- Value = value;
- }
-
- public override string ToString() => $"{Name} = {Value}";
- public AstAttributeNode Clone() => new AstAttributeNode(Name, Value);
- }
-
- public class AstAttributes : List
- {
- public bool HasAttribute(string a) => this.Any(x => x.Name == a);
-
- public AstAttributes Clone()
- {
- var rv= new AstAttributes();
- rv.AddRange(this.Select(x => x.Clone()));
- return rv;
- }
- }
-
- public interface IAstNodeWithAttributes
- {
- public AstAttributes Attributes { get; set; }
- }
-
- public class AstEnumNode : List, IAstNodeWithAttributes
- {
- public AstAttributes Attributes { get; set; } = new AstAttributes();
- public string Name { get; set; }
- public override string ToString() => "Enum " + Name;
-
- public AstEnumNode Clone()
- {
- var rv = new AstEnumNode { Name = Name, Attributes = Attributes.Clone() };
- rv.AddRange(this.Select(x => x.Clone()));
- return rv;
- }
- }
-
- public class AstEnumMemberNode
- {
- public string Name { get; set; }
- public string Value { get; set; }
-
- public AstEnumMemberNode(string name, string value)
- {
- Name = name;
- Value = value;
- }
-
- public override string ToString() => $"Enum member {Name} = {Value}";
- public AstEnumMemberNode Clone() => new AstEnumMemberNode(Name, Value);
- }
-
- public class AstStructNode : List, IAstNodeWithAttributes
- {
- public AstAttributes Attributes { get; set; } = new AstAttributes();
- public string Name { get; set; }
- public override string ToString() => "Struct " + Name;
-
- public AstStructNode Clone()
- {
- var rv = new AstStructNode { Name = Name, Attributes = Attributes.Clone() };
- rv.AddRange(this.Select(x => x.Clone()));
- return rv;
- }
- }
-
- public class AstTypeNode
- {
- public string Name { get; set; }
- public int PointerLevel { get; set; }
- public bool IsLink { get; set; }
-
- public string Format() => Name + new string('*', PointerLevel)
- + (IsLink ? "&" : "");
- public override string ToString() => Format();
- public AstTypeNode Clone() => new AstTypeNode() {
- Name = Name,
- PointerLevel = PointerLevel,
- IsLink = IsLink
- };
- }
-
- public class AstStructMemberNode : IAstNodeWithAttributes
- {
- public string Name { get; set; }
- public AstTypeNode Type { get; set; }
-
- public override string ToString() => $"Struct member {Type.Format()} {Name}";
- public AstStructMemberNode Clone() => new AstStructMemberNode() { Name = Name, Type = Type.Clone() };
- public AstAttributes Attributes { get; set; } = new AstAttributes();
- }
-
- public class AstInterfaceNode : List, IAstNodeWithAttributes
- {
- public AstAttributes Attributes { get; set; } = new AstAttributes();
- public string Name { get; set; }
- public string Inherits { get; set; }
-
- public override string ToString()
- {
- if (Inherits == null)
- return Name;
- return $"Interface {Name} : {Inherits}";
- }
- public AstInterfaceNode Clone()
- {
- var rv = new AstInterfaceNode { Name = Name, Inherits = Inherits, Attributes = Attributes.Clone() };
- rv.AddRange(this.Select(x => x.Clone()));
- return rv;
- }
- }
-
- public class AstInterfaceMemberNode : List, IAstNodeWithAttributes
- {
- public string Name { get; set; }
- public AstTypeNode ReturnType { get; set; }
- public AstAttributes Attributes { get; set; } = new AstAttributes();
-
- public AstInterfaceMemberNode Clone()
- {
- var rv = new AstInterfaceMemberNode()
- {
- Name = Name, Attributes = Attributes.Clone(), ReturnType = ReturnType
- };
- rv.AddRange(this.Select(x => x.Clone()));
- return rv;
- }
-
- public override string ToString() =>
- $"Interface member {ReturnType.Format()} {Name} ({string.Join(", ", this.Select(x => x.Format()))})";
- }
-
- public class AstInterfaceMemberArgumentNode : IAstNodeWithAttributes
- {
- public string Name { get; set; }
- public AstTypeNode Type { get; set; }
- public AstAttributes Attributes { get; set; } = new AstAttributes();
-
-
- public string Format() => $"{Type.Format()} {Name}";
- public override string ToString() => "Argument " + Format();
-
- public AstInterfaceMemberArgumentNode Clone() => new AstInterfaceMemberArgumentNode
- {
- Name = Name, Type = Type.Clone(), Attributes = Attributes.Clone()
- };
- }
-
- public static class AstExtensions
- {
- public static bool HasAttribute(this IAstNodeWithAttributes node, string s) => node.Attributes.HasAttribute(s);
-
- public static string GetAttribute(this IAstNodeWithAttributes node, string s)
- {
- var value = node.Attributes.FirstOrDefault(a => a.Name == s)?.Value;
- if (value == null)
- throw new CodeGenException("Expected attribute " + s + " for node " + node);
- return value;
- }
-
- public static string GetAttributeOrDefault(this IAstNodeWithAttributes node, string s)
- => node.Attributes.FirstOrDefault(a => a.Name == s)?.Value;
- }
-
- class AstVisitor
- {
- protected virtual void VisitType(AstTypeNode type)
- {
- }
-
- protected virtual void VisitArgument(AstInterfaceMemberArgumentNode argument)
- {
- VisitType(argument.Type);
- }
-
- protected virtual void VisitInterfaceMember(AstInterfaceMemberNode member)
- {
- foreach(var a in member)
- VisitArgument(a);
- VisitType(member.ReturnType);
- }
-
- protected virtual void VisitInterface(AstInterfaceNode iface)
- {
- foreach(var m in iface)
- VisitInterfaceMember(m);
- }
-
- protected virtual void VisitStructMember(AstStructMemberNode member)
- {
- VisitType(member.Type);
- }
-
- protected virtual void VisitStruct(AstStructNode node)
- {
- foreach(var m in node)
- VisitStructMember(m);
- }
-
- public virtual void VisitAst(AstIdlNode ast)
- {
- foreach(var iface in ast.Interfaces)
- VisitInterface(iface);
- foreach (var s in ast.Structs)
- VisitStruct(s);
- }
-
-
- }
-
- public class AstIdlNode : IAstNodeWithAttributes
- {
- public AstAttributes Attributes { get; set; } = new AstAttributes();
- public List Enums { get; set; } = new List();
- public List Structs { get; set; } = new List();
- public List Interfaces { get; set; } = new List();
-
- public AstIdlNode Clone() => new AstIdlNode()
- {
- Attributes = Attributes.Clone(),
- Enums = Enums.Select(x => x.Clone()).ToList(),
- Structs = Structs.Select(x => x.Clone()).ToList(),
- Interfaces = Interfaces.Select(x => x.Clone()).ToList()
- };
- }
-}
diff --git a/src/tools/MicroComGenerator/AstParser.cs b/src/tools/MicroComGenerator/AstParser.cs
deleted file mode 100644
index 388a8eb018..0000000000
--- a/src/tools/MicroComGenerator/AstParser.cs
+++ /dev/null
@@ -1,232 +0,0 @@
-using System.Collections.Generic;
-using MicroComGenerator.Ast;
-
-namespace MicroComGenerator
-{
- public class AstParser
- {
- public static AstIdlNode Parse(string source)
- {
- var parser = new TokenParser(source);
- var idl = new AstIdlNode { Attributes = ParseGlobalAttributes(ref parser) };
-
- while (!parser.Eof)
- {
- var attrs = ParseLocalAttributes(ref parser);
- if (parser.TryConsume(";"))
- continue;
- if (parser.TryParseKeyword("enum"))
- idl.Enums.Add(ParseEnum(attrs, ref parser));
- else if (parser.TryParseKeyword("struct"))
- idl.Structs.Add(ParseStruct(attrs, ref parser));
- else if (parser.TryParseKeyword("interface"))
- idl.Interfaces.Add(ParseInterface(attrs, ref parser));
- else
- throw new ParseException("Unexpected character", ref parser);
- }
-
- return idl;
- }
-
- static AstAttributes ParseGlobalAttributes(ref TokenParser parser)
- {
- var rv = new AstAttributes();
- while (!parser.Eof)
- {
- parser.SkipWhitespace();
- if (parser.TryConsume('@'))
- {
- var ident = parser.ParseIdentifier("-");
- var value = parser.ReadToEol().Trim();
- if (value == "@@")
- {
- parser.Advance(1);
- value = "";
- while (true)
- {
- var l = parser.ReadToEol();
- if (l == "@@")
- break;
- else
- value = value.Length == 0 ? l : (value + "\n" + l);
- parser.Advance(1);
- }
-
- }
- rv.Add(new AstAttributeNode(ident, value));
- }
- else
- return rv;
- }
-
- return rv;
- }
-
- static AstAttributes ParseLocalAttributes(ref TokenParser parser)
- {
- var rv = new AstAttributes();
- while (parser.TryConsume("["))
- {
- while (!parser.TryConsume("]") && !parser.Eof)
- {
- if (parser.TryConsume(','))
- continue;
-
- // Get identifier
- var ident = parser.ParseIdentifier("-");
-
- // No value, end of attribute list
- if (parser.TryConsume(']'))
- {
- rv.Add(new AstAttributeNode(ident, null));
- break;
- }
- // No value, next attribute
- else if (parser.TryConsume(','))
- rv.Add(new AstAttributeNode(ident, null));
- // Has value
- else if (parser.TryConsume('('))
- {
- var value = parser.ReadTo(')');
- parser.Consume(')');
- rv.Add(new AstAttributeNode(ident, value));
- }
- else
- throw new ParseException("Unexpected character", ref parser);
- }
-
- if (parser.Eof)
- throw new ParseException("Unexpected EOF", ref parser);
- }
-
- return rv;
- }
-
- static void EnsureOpenBracket(ref TokenParser parser)
- {
- if (!parser.TryConsume('{'))
- throw new ParseException("{ expected", ref parser);
- }
-
- static AstEnumNode ParseEnum(AstAttributes attrs, ref TokenParser parser)
- {
- var name = parser.ParseIdentifier();
- EnsureOpenBracket(ref parser);
- var rv = new AstEnumNode { Name = name, Attributes = attrs };
- while (!parser.TryConsume('}') && !parser.Eof)
- {
- if (parser.TryConsume(','))
- continue;
-
- var ident = parser.ParseIdentifier();
-
- // Automatic value
- if (parser.TryConsume(',') || parser.Peek == '}')
- {
- rv.Add(new AstEnumMemberNode(ident, null));
- continue;
- }
-
- if (!parser.TryConsume('='))
- throw new ParseException("Unexpected character", ref parser);
-
- var value = parser.ReadToAny(",}").Trim();
- rv.Add(new AstEnumMemberNode(ident, value));
-
- if (parser.Eof)
- throw new ParseException("Unexpected EOF", ref parser);
- }
-
-
- return rv;
- }
-
- static AstTypeNode ParseType(ref TokenParser parser)
- {
- var ident = parser.ParseIdentifier();
- var t = new AstTypeNode { Name = ident };
- while (parser.TryConsume('*'))
- t.PointerLevel++;
- if (parser.TryConsume("&"))
- t.IsLink = true;
- return t;
- }
-
- static AstStructNode ParseStruct(AstAttributes attrs, ref TokenParser parser)
- {
- var name = parser.ParseIdentifier();
- EnsureOpenBracket(ref parser);
- var rv = new AstStructNode { Name = name, Attributes = attrs };
- while (!parser.TryConsume('}') && !parser.Eof)
- {
- var memberAttrs = ParseLocalAttributes(ref parser);
- var t = ParseType(ref parser);
- bool parsedAtLeastOneMember = false;
- while (!parser.TryConsume(';'))
- {
- // Skip any ,
- while (parser.TryConsume(',')) { }
-
- var ident = parser.ParseIdentifier();
- parsedAtLeastOneMember = true;
- rv.Add(new AstStructMemberNode { Name = ident, Type = t, Attributes = memberAttrs});
- }
-
- if (!parsedAtLeastOneMember)
- throw new ParseException("Expected at least one enum member with declared type " + t, ref parser);
- }
-
- return rv;
- }
-
- static AstInterfaceNode ParseInterface(AstAttributes interfaceAttrs, ref TokenParser parser)
- {
- var interfaceName = parser.ParseIdentifier();
- string inheritsFrom = null;
- if (parser.TryConsume(":"))
- inheritsFrom = parser.ParseIdentifier();
-
- EnsureOpenBracket(ref parser);
- var rv = new AstInterfaceNode
- {
- Name = interfaceName, Attributes = interfaceAttrs, Inherits = inheritsFrom
- };
- while (!parser.TryConsume('}') && !parser.Eof)
- {
- var memberAttrs = ParseLocalAttributes(ref parser);
- var returnType = ParseType(ref parser);
- var name = parser.ParseIdentifier();
- var member = new AstInterfaceMemberNode
- {
- Name = name, ReturnType = returnType, Attributes = memberAttrs
- };
- rv.Add(member);
-
- parser.Consume('(');
- while (true)
- {
- if (parser.TryConsume(')'))
- break;
-
- var argumentAttrs = ParseLocalAttributes(ref parser);
- var type = ParseType(ref parser);
- var argName = parser.ParseIdentifier();
- member.Add(new AstInterfaceMemberArgumentNode
- {
- Name = argName, Type = type, Attributes = argumentAttrs
- });
-
- if (parser.TryConsume(')'))
- break;
- if (parser.TryConsume(','))
- continue;
- throw new ParseException("Unexpected character", ref parser);
- }
-
- parser.Consume(';');
- }
-
- return rv;
- }
- }
-}
diff --git a/src/tools/MicroComGenerator/CSharpGen.InterfaceGen.cs b/src/tools/MicroComGenerator/CSharpGen.InterfaceGen.cs
deleted file mode 100644
index adb8faf938..0000000000
--- a/src/tools/MicroComGenerator/CSharpGen.InterfaceGen.cs
+++ /dev/null
@@ -1,484 +0,0 @@
-using System;
-using System.Collections.Generic;
-using System.Linq;
-using System.Runtime.CompilerServices;
-using System.Runtime.InteropServices;
-using MicroComGenerator.Ast;
-using Microsoft.CodeAnalysis;
-using Microsoft.CodeAnalysis.CSharp;
-using Microsoft.CodeAnalysis.CSharp.Syntax;
-using static Microsoft.CodeAnalysis.CSharp.SyntaxFactory;
-
-// ReSharper disable CoVariantArrayConversion
-
-// HERE BE DRAGONS
-
-namespace MicroComGenerator
-{
- public partial class CSharpGen
- {
- abstract class Arg
- {
- public string Name;
- public string NativeType;
- public AstAttributes Attributes { get; set; }
- public virtual StatementSyntax CreateFixed(StatementSyntax inner) => inner;
-
- public virtual void PreMarshal(List body)
- {
- }
-
- public virtual void PreMarshalForReturn(List body) =>
- throw new InvalidOperationException("Don't know how to use " + NativeType + " as HRESULT-return");
-
- public virtual ExpressionSyntax Value(bool isHresultReturn) => ParseExpression(Name);
- public abstract string ManagedType { get; }
- public virtual string ReturnManagedType => ManagedType;
-
- public virtual StatementSyntax[] ReturnMarshalResult() => new[] { ParseStatement("return " + Name + ";") };
-
-
- public virtual void BackPreMarshal(List body)
- {
- }
-
- public virtual ExpressionSyntax BackMarshalValue() => ParseExpression(Name);
- public virtual ExpressionSyntax BackMarshalReturn(string resultVar) => ParseExpression(resultVar);
-
- }
-
- class InterfaceReturnArg : Arg
- {
- public string InterfaceType;
- public override ExpressionSyntax Value(bool isHresultReturn) => ParseExpression("&" + PName);
- public override string ManagedType => InterfaceType;
-
- private string PName => "__marshal_" + Name;
-
- public override void PreMarshalForReturn(List body)
- {
- body.Add(ParseStatement("void* " + PName + " = null;"));
- }
-
- public override StatementSyntax[] ReturnMarshalResult() => new[]
- {
- ParseStatement("return Avalonia.MicroCom.MicroComRuntime.CreateProxyFor<" + InterfaceType + ">(" +
- PName + ", true);")
- };
-
- public override ExpressionSyntax BackMarshalValue()
- {
- return ParseExpression("INVALID");
- }
-
- public override ExpressionSyntax BackMarshalReturn(string resultVar)
- {
- return ParseExpression($"Avalonia.MicroCom.MicroComRuntime.GetNativePointer({resultVar}, true)");
- }
- }
-
- class InterfaceArg : Arg
- {
- public string InterfaceType;
-
- public override ExpressionSyntax Value(bool isHresultReturn) =>
- ParseExpression("Avalonia.MicroCom.MicroComRuntime.GetNativePointer(" + Name + ")");
-
- public override string ManagedType => InterfaceType;
-
- public override StatementSyntax[] ReturnMarshalResult() => new[]
- {
- ParseStatement("return Avalonia.MicroCom.MicroComRuntime.CreateProxyFor<" + InterfaceType + ">(" +
- Name + ", true);")
- };
-
- public override ExpressionSyntax BackMarshalValue()
- {
- return ParseExpression("Avalonia.MicroCom.MicroComRuntime.CreateProxyFor<" + InterfaceType + ">(" +
- Name + ", false)");
- }
-
- public override ExpressionSyntax BackMarshalReturn(string resultVar)
- {
- return ParseExpression($"Avalonia.MicroCom.MicroComRuntime.GetNativePointer({resultVar}, true)");
- }
- }
-
- class BypassArg : Arg
- {
- public string Type { get; set; }
- public int PointerLevel;
- public override string ManagedType => Type + new string('*', PointerLevel);
- public override string ReturnManagedType => Type + new string('*', PointerLevel - 1);
-
- public override ExpressionSyntax Value(bool isHresultReturn)
- {
- if (isHresultReturn)
- return ParseExpression("&" + Name);
- return base.Value(false);
- }
-
- public override void PreMarshalForReturn(List body)
- {
- if (PointerLevel == 0)
- base.PreMarshalForReturn(body);
- else
- body.Add(ParseStatement(Type + new string('*', PointerLevel - 1) + " " + Name + "=default;"));
- }
- }
-
- class StringArg : Arg
- {
- private string BName => "__bytemarshal_" + Name;
- private string FName => "__fixedmarshal_" + Name;
-
- public override void PreMarshal(List body)
- {
- body.Add(ParseStatement($"var {BName} = new byte[System.Text.Encoding.UTF8.GetByteCount({Name})+1];"));
- body.Add(ParseStatement($"System.Text.Encoding.UTF8.GetBytes({Name}, 0, {Name}.Length, {BName}, 0);"));
- }
-
- public override StatementSyntax CreateFixed(StatementSyntax inner)
- {
- return FixedStatement(DeclareVar("byte*", FName, ParseExpression(BName)), inner);
- }
-
- public override ExpressionSyntax Value(bool isHresultReturn) => ParseExpression(FName);
- public override string ManagedType => "string";
- public override ExpressionSyntax BackMarshalValue()
- {
- return ParseExpression(
- $"({Name} == null ? null : System.Runtime.InteropServices.Marshal.PtrToStringAnsi(new IntPtr(" + Name + ")))");
- }
- }
-
- string ConvertNativeType(string type)
- {
- if (type == "size_t")
- return "System.IntPtr";
- if (type == "HRESULT")
- return "int";
- return type;
- }
-
- Arg ConvertArg(AstInterfaceMemberArgumentNode node)
- {
- var arg = ConvertArg(node.Name, node.Type);
- arg.Attributes = node.Attributes.Clone();
- return arg;
- }
-
- Arg ConvertArg(string name, AstTypeNode type)
- {
- type = new AstTypeNode { Name = ConvertNativeType(type.Name), PointerLevel = type.PointerLevel };
-
- if (type.PointerLevel == 2)
- {
- if (IsInterface(type))
- return new InterfaceReturnArg { Name = name, InterfaceType = type.Name, NativeType = "void**" };
- }
- else if (type.PointerLevel == 1)
- {
- if (IsInterface(type))
- return new InterfaceArg { Name = name, InterfaceType = type.Name, NativeType = "void*" };
- if (type.Name == "char")
- return new StringArg { Name = name, NativeType = "byte*" };
- }
-
- return new BypassArg
- {
- Name = name, Type = type.Name, PointerLevel = type.PointerLevel, NativeType = type.ToString()
- };
- }
-
-
- void GenerateInterfaceMember(AstInterfaceMemberNode member, ref InterfaceDeclarationSyntax iface,
- ref ClassDeclarationSyntax proxy, ref ClassDeclarationSyntax vtbl,
- List vtblCtor, int num)
- {
- // Prepare method information
- if (member.Name == "GetRenderingDevice")
- Console.WriteLine();
- var args = member.Select(ConvertArg).ToList();
- var returnArg = ConvertArg("__result", member.ReturnType);
- bool isHresult = member.ReturnType.Name == "HRESULT";
- bool isHresultLastArgumentReturn = isHresult
- && args.Count > 0
- && (args.Last().Name == "ppv"
- || args.Last().Name == "retOut"
- || args.Last().Name == "ret"
- || args.Last().Attributes.HasAttribute("out")
- || args.Last().Attributes.HasAttribute("retval")
- )
- && ((member.Last().Type.PointerLevel > 0
- && !IsInterface(member.Last().Type))
- || member.Last().Type.PointerLevel == 2);
-
- bool isVoidReturn = member.ReturnType.Name == "void" && member.ReturnType.PointerLevel == 0;
-
-
- // Generate method signature
- MethodDeclarationSyntax GenerateManagedSig(string returnType, string name,
- IEnumerable<(string n, string t)> args)
- => MethodDeclaration(ParseTypeName(returnType), name).WithParameterList(
- ParameterList(
- SeparatedList(args.Select(x => Parameter(Identifier(x.n)).WithType(ParseTypeName(x.t))))));
-
- var managedSig =
- isHresult ?
- GenerateManagedSig(isHresultLastArgumentReturn ? args.Last().ReturnManagedType : "void",
- member.Name,
- (isHresultLastArgumentReturn ? args.SkipLast(1) : args).Select(a => (a.Name, a.ManagedType))) :
- GenerateManagedSig(returnArg.ManagedType, member.Name, args.Select(a => (a.Name, a.ManagedType)));
-
- iface = iface.AddMembers(managedSig.WithSemicolonToken(Semicolon()));
-
- // Prepare args for marshaling
- var preMarshal = new List();
- if (!isVoidReturn)
- preMarshal.Add(ParseStatement(returnArg.NativeType + " __result;"));
-
- for (var idx = 0; idx < args.Count; idx++)
- {
- if (isHresultLastArgumentReturn && idx == args.Count - 1)
- args[idx].PreMarshalForReturn(preMarshal);
- else
- args[idx].PreMarshal(preMarshal);
- }
-
- // Generate call expression
- ExpressionSyntax callExpr = InvocationExpression(_localInterop.GetCaller(returnArg.NativeType,
- args.Select(x => x.NativeType).ToList()))
- .AddArgumentListArguments(Argument(ParseExpression("PPV")))
- .AddArgumentListArguments(args
- .Select((a, i) => Argument(a.Value(isHresultLastArgumentReturn && i == args.Count - 1))).ToArray())
- .AddArgumentListArguments(Argument(ParseExpression("(*PPV)[base.VTableSize + " + num + "]")));
-
- if (!isVoidReturn)
- callExpr = CastExpression(ParseTypeName(returnArg.NativeType), callExpr);
-
- // Save call result if needed
- if (!isVoidReturn)
- callExpr = AssignmentExpression(SyntaxKind.SimpleAssignmentExpression, ParseExpression("__result"),
- callExpr);
-
-
- // Wrap call into fixed() blocks
- StatementSyntax callStatement = ExpressionStatement(callExpr);
- foreach (var arg in args)
- callStatement = arg.CreateFixed(callStatement);
-
- // Build proxy body
- var proxyBody = Block()
- .AddStatements(preMarshal.ToArray())
- .AddStatements(callStatement);
-
- // Process return value
- if (!isVoidReturn)
- {
- if (isHresult)
- {
- proxyBody = proxyBody.AddStatements(
- ParseStatement(
- $"if(__result != 0) throw new System.Runtime.InteropServices.COMException(\"{member.Name} failed\", __result);"));
-
- if (isHresultLastArgumentReturn)
- proxyBody = proxyBody.AddStatements(args.Last().ReturnMarshalResult());
- }
- else
- proxyBody = proxyBody.AddStatements(returnArg.ReturnMarshalResult());
- }
-
- // Add the proxy method
- proxy = proxy.AddMembers(managedSig.AddModifiers(SyntaxKind.PublicKeyword)
- .WithBody(proxyBody));
-
-
- // Generate VTable method
- var shadowDelegate = DelegateDeclaration(ParseTypeName(returnArg.NativeType), member.Name + "Delegate")
- .AddParameterListParameters(Parameter(Identifier("@this")).WithType(ParseTypeName("IntPtr")))
- .AddParameterListParameters(args.Select(x =>
- Parameter(Identifier(x.Name)).WithType(ParseTypeName(x.NativeType))).ToArray())
- .AddAttribute("System.Runtime.InteropServices.UnmanagedFunctionPointer",
- "System.Runtime.InteropServices.CallingConvention.StdCall");
-
- var shadowMethod = MethodDeclaration(shadowDelegate.ReturnType, member.Name)
- .WithParameterList(shadowDelegate.ParameterList)
- .AddModifiers(Token(SyntaxKind.StaticKeyword));
-
- var backPreMarshal = new List();
- foreach (var arg in args)
- arg.BackPreMarshal(backPreMarshal);
-
- backPreMarshal.Add(
- ParseStatement($"__target = ({iface.Identifier.Text})Avalonia.MicroCom.MicroComRuntime.GetObjectFromCcw(@this);"));
-
- var isBackVoidReturn = isVoidReturn || (isHresult && !isHresultLastArgumentReturn);
-
- StatementSyntax backCallStatement;
-
- var backCallExpr =
- IsPropertyRewriteCandidate(managedSig) ?
- ParseExpression("__target." + member.Name.Substring(3)) :
- InvocationExpression(ParseExpression("__target." + member.Name))
- .WithArgumentList(ArgumentList(SeparatedList(
- (isHresultLastArgumentReturn ? args.SkipLast(1) : args)
- .Select(a =>
- Argument(a.BackMarshalValue())))));
-
- if (isBackVoidReturn)
- backCallStatement = ExpressionStatement(backCallExpr);
- else
- {
- backCallStatement = LocalDeclarationStatement(DeclareVar("var", "__result", backCallExpr));
- if (isHresultLastArgumentReturn)
- {
- backCallStatement = Block(backCallStatement,
- ExpressionStatement(AssignmentExpression(SyntaxKind.SimpleAssignmentExpression,
- ParseExpression("*" + args.Last().Name),
- args.Last().BackMarshalReturn("__result")
- )));
-
- }
- else
- backCallStatement = Block(backCallStatement,
- ReturnStatement(returnArg.BackMarshalReturn("__result")));
- }
-
- BlockSyntax backBodyBlock = Block().AddStatements(backPreMarshal.ToArray()).AddStatements(backCallStatement);
-
-
- var exceptions = new List()
- {
- CatchClause(
- CatchDeclaration(ParseTypeName("System.Exception"), Identifier("__exception__")), null,
- Block(
- ParseStatement(
- "Avalonia.MicroCom.MicroComRuntime.UnhandledException(__target, __exception__);"),
- isHresult ? ParseStatement("return unchecked((int)0x80004005u);")
- : isVoidReturn ? EmptyStatement() : ParseStatement("return default;")
- ))
- };
-
- if (isHresult)
- exceptions.Insert(0, CatchClause(
- CatchDeclaration(ParseTypeName("System.Runtime.InteropServices.COMException"),
- Identifier("__com_exception__")),
- null, Block(ParseStatement("return __com_exception__.ErrorCode;"))));
-
- backBodyBlock = Block(
- TryStatement(
- List(exceptions))
- .WithBlock(Block(backBodyBlock))
- );
- if (isHresult)
- backBodyBlock = backBodyBlock.AddStatements(ParseStatement("return 0;"));
-
-
- backBodyBlock = Block()
- .AddStatements(ParseStatement($"{iface.Identifier.Text} __target = null;"))
- .AddStatements(backBodyBlock.Statements.ToArray());
-
- shadowMethod = shadowMethod.WithBody(backBodyBlock);
-
- vtbl = vtbl.AddMembers(shadowDelegate).AddMembers(shadowMethod);
- vtblCtor.Add(ParseStatement("base.AddMethod((" + shadowDelegate.Identifier.Text + ")" +
- shadowMethod.Identifier.Text + ");"));
-
-
-
-
- }
-
- class LocalInteropHelper
- {
- public ClassDeclarationSyntax Class { get; private set; } = ClassDeclaration("LocalInterop");
- private HashSet _existing = new HashSet();
-
- public ExpressionSyntax GetCaller(string returnType, List args)
- {
- string ConvertType(string t) => t.EndsWith("*") ? "void*" : t;
- returnType = ConvertType(returnType);
- args = args.Select(ConvertType).ToList();
-
- var name = "CalliStdCall" + returnType.Replace("*", "_ptr");
- var signature = returnType + "::" + name + "::" + string.Join("::", args);
- if (_existing.Add(signature))
- {
- Class = Class.AddMembers(MethodDeclaration(ParseTypeName(returnType), name)
- .AddModifiers(SyntaxKind.StaticKeyword, SyntaxKind.UnsafeKeyword, SyntaxKind.PublicKeyword)
- .AddParameterListParameters(Parameter(Identifier("thisObj")).WithType(ParseTypeName("void*")))
- .AddParameterListParameters(args.Select((x, i) =>
- Parameter(Identifier("arg" + i)).WithType(ParseTypeName(x))).ToArray())
- .AddParameterListParameters(Parameter(Identifier("methodPtr")).WithType(ParseTypeName("void*")))
- .WithBody(Block(ExpressionStatement(ThrowExpression(ParseExpression("null"))))));
- }
-
- return ParseExpression("LocalInterop." + name);
- }
- }
-
-
- void GenerateInterface(ref NamespaceDeclarationSyntax ns, ref NamespaceDeclarationSyntax implNs,
- AstInterfaceNode iface)
- {
- var guidString = iface.GetAttribute("uuid");
- var inheritsUnknown = iface.Inherits == null || iface.Inherits == "IUnknown";
-
- var ifaceDec = InterfaceDeclaration(iface.Name)
- .WithBaseType(inheritsUnknown ? "Avalonia.MicroCom.IUnknown" : iface.Inherits)
- .AddModifiers(Token(_visibility), Token(SyntaxKind.UnsafeKeyword), Token(SyntaxKind.PartialKeyword));
-
- var proxyClassName = "__MicroCom" + iface.Name + "Proxy";
- var proxy = ClassDeclaration(proxyClassName)
- .AddModifiers(Token(SyntaxKind.UnsafeKeyword), Token(_visibility), Token(SyntaxKind.PartialKeyword))
- .WithBaseType(inheritsUnknown ?
- "Avalonia.MicroCom.MicroComProxyBase" :
- ("__MicroCom" + iface.Inherits + "Proxy"))
- .AddBaseListTypes(SimpleBaseType(ParseTypeName(iface.Name)));
-
-
- // Generate vtable
- var vtbl = ClassDeclaration("__MicroCom" + iface.Name + "VTable")
- .AddModifiers(Token(SyntaxKind.UnsafeKeyword));
-
- vtbl = vtbl.WithBaseType(inheritsUnknown ?
- "Avalonia.MicroCom.MicroComVtblBase" :
- "__MicroCom" + iface.Inherits + "VTable");
-
- var vtblCtor = new List();
- for (var idx = 0; idx < iface.Count; idx++)
- GenerateInterfaceMember(iface[idx], ref ifaceDec, ref proxy, ref vtbl, vtblCtor, idx);
-
- vtbl = vtbl.AddMembers(
- ConstructorDeclaration(vtbl.Identifier.Text)
- .AddModifiers(Token(SyntaxKind.PublicKeyword))
- .WithBody(Block(vtblCtor))
- )
- .AddMembers(MethodDeclaration(ParseTypeName("void"), "__MicroComModuleInit")
- .AddModifiers(Token(SyntaxKind.StaticKeyword), Token(SyntaxKind.InternalKeyword))
- .WithExpressionBody(ArrowExpressionClause(
- ParseExpression("Avalonia.MicroCom.MicroComRuntime.RegisterVTable(typeof(" +
- iface.Name + "), new " + vtbl.Identifier.Text + "().CreateVTable())")))
- .WithSemicolonToken(Semicolon()));
-
-
- // Finalize proxy code
- proxy = proxy.AddMembers(
- MethodDeclaration(ParseTypeName("void"), "__MicroComModuleInit")
- .AddModifiers(Token(SyntaxKind.StaticKeyword), Token(SyntaxKind.InternalKeyword))
- .WithBody(Block(
- ParseStatement("Avalonia.MicroCom.MicroComRuntime.Register(typeof(" +
- iface.Name + "), new Guid(\"" + guidString + "\"), (p, owns) => new " +
- proxyClassName + "(p, owns));")
- )))
- .AddMembers(ParseMemberDeclaration("public " + proxyClassName +
- "(IntPtr nativePointer, bool ownsHandle) : base(nativePointer, ownsHandle) {}"))
- .AddMembers(ParseMemberDeclaration("protected override int VTableSize => base.VTableSize + " +
- iface.Count + ";"));
-
- ns = ns.AddMembers(RewriteMethodsToProperties(ifaceDec));
- implNs = implNs.AddMembers(RewriteMethodsToProperties(proxy), RewriteMethodsToProperties(vtbl));
- }
- }
-}
diff --git a/src/tools/MicroComGenerator/CSharpGen.Utils.cs b/src/tools/MicroComGenerator/CSharpGen.Utils.cs
deleted file mode 100644
index 28baaa65f8..0000000000
--- a/src/tools/MicroComGenerator/CSharpGen.Utils.cs
+++ /dev/null
@@ -1,111 +0,0 @@
-using System;
-using System.Collections.Generic;
-using System.IO;
-using System.Linq;
-using MicroComGenerator.Ast;
-using Microsoft.CodeAnalysis;
-using Microsoft.CodeAnalysis.CSharp;
-using Microsoft.CodeAnalysis.CSharp.Formatting;
-using Microsoft.CodeAnalysis.CSharp.Syntax;
-
-using static Microsoft.CodeAnalysis.CSharp.SyntaxFactory;
-namespace MicroComGenerator
-{
- public partial class CSharpGen
- {
-
- CompilationUnitSyntax Unit()
- => CompilationUnit().WithUsings(List(new[]
- {
- "System", "System.Text", "System.Collections", "System.Collections.Generic", "Avalonia.MicroCom"
- }
- .Concat(_extraUsings).Select(u => UsingDirective(IdentifierName(u)))));
-
- string Format(CompilationUnitSyntax unit)
- {
- var cw = new AdhocWorkspace();
- return
- "#pragma warning disable 108\n" +
- Microsoft.CodeAnalysis.Formatting.Formatter.Format(unit.NormalizeWhitespace(), cw, cw.Options
- .WithChangedOption(CSharpFormattingOptions.NewLineForMembersInObjectInit, true)
- .WithChangedOption(CSharpFormattingOptions.NewLinesForBracesInObjectCollectionArrayInitializers,
- true)
- .WithChangedOption(CSharpFormattingOptions.NewLineForMembersInAnonymousTypes, true)
- .WithChangedOption(CSharpFormattingOptions.NewLinesForBracesInMethods, true)
-
- ).ToFullString();
- }
-
-
- SyntaxToken Semicolon() => Token(SyntaxKind.SemicolonToken);
-
- static VariableDeclarationSyntax DeclareVar(string type, string name,
- ExpressionSyntax initializer = null)
- => VariableDeclaration(ParseTypeName(type),
- SingletonSeparatedList(VariableDeclarator(name)
- .WithInitializer(initializer == null ? null : EqualsValueClause(initializer))));
-
- FieldDeclarationSyntax DeclareConstant(string type, string name, LiteralExpressionSyntax value)
- => FieldDeclaration(
- VariableDeclaration(ParseTypeName(type),
- SingletonSeparatedList(
- VariableDeclarator(name).WithInitializer(EqualsValueClause(value))
- ))
- ).WithSemicolonToken(Semicolon())
- .WithModifiers(TokenList(Token(SyntaxKind.PublicKeyword), Token(SyntaxKind.ConstKeyword)));
-
- FieldDeclarationSyntax DeclareField(string type, string name, params SyntaxKind[] modifiers) =>
- DeclareField(type, name, null, modifiers);
-
- FieldDeclarationSyntax DeclareField(string type, string name, EqualsValueClauseSyntax initializer,
- params SyntaxKind[] modifiers) =>
- FieldDeclaration(
- VariableDeclaration(ParseTypeName(type),
- SingletonSeparatedList(
- VariableDeclarator(name).WithInitializer(initializer))))
- .WithSemicolonToken(Semicolon())
- .WithModifiers(TokenList(modifiers.Select(x => Token(x))));
-
- bool IsPropertyRewriteCandidate(MethodDeclarationSyntax method)
- {
-
- return
- method.ReturnType.ToFullString() != "void"
- && method.Identifier.Text.StartsWith("Get")
- && method.ParameterList.Parameters.Count == 0;
- }
-
- TypeDeclarationSyntax RewriteMethodsToProperties(T decl) where T : TypeDeclarationSyntax
- {
- var replace = new Dictionary();
- foreach (var method in decl.Members.OfType().ToList())
- {
- if (IsPropertyRewriteCandidate(method))
- {
- var getter = AccessorDeclaration(SyntaxKind.GetAccessorDeclaration);
- if (method.Body != null)
- getter = getter.WithBody(method.Body);
- else
- getter = getter.WithSemicolonToken(Semicolon());
-
- replace[method] = PropertyDeclaration(method.ReturnType,
- method.Identifier.Text.Substring(3))
- .WithModifiers(method.Modifiers).AddAccessorListAccessors(getter);
-
- }
- }
-
- return decl.ReplaceNodes(replace.Keys, (m, m2) => replace[m]);
- }
-
- bool IsInterface(string name)
- {
- if (name == "IUnknown")
- return true;
- return _idl.Interfaces.Any(i => i.Name == name);
- }
-
- private bool IsInterface(AstTypeNode type) => IsInterface(type.Name);
-
- }
-}
diff --git a/src/tools/MicroComGenerator/CSharpGen.cs b/src/tools/MicroComGenerator/CSharpGen.cs
deleted file mode 100644
index ff4c351fd9..0000000000
--- a/src/tools/MicroComGenerator/CSharpGen.cs
+++ /dev/null
@@ -1,155 +0,0 @@
-using System;
-using System.Collections.Generic;
-using System.Linq;
-using System.Runtime.CompilerServices;
-using MicroComGenerator.Ast;
-using Microsoft.CodeAnalysis;
-using Microsoft.CodeAnalysis.CSharp;
-using Microsoft.CodeAnalysis.CSharp.Syntax;
-using static Microsoft.CodeAnalysis.CSharp.SyntaxFactory;
-// ReSharper disable CoVariantArrayConversion
-
-namespace MicroComGenerator
-{
- public partial class CSharpGen
- {
- private readonly AstIdlNode _idl;
- private List _extraUsings;
- private string _namespace;
- private SyntaxKind _visibility;
- private LocalInteropHelper _localInterop = new LocalInteropHelper();
-
- public CSharpGen(AstIdlNode idl)
- {
- _idl = idl.Clone();
- new AstRewriter(_idl.Attributes.Where(a => a.Name == "clr-map")
- .Select(x => x.Value.Trim().Split(' '))
- .ToDictionary(x => x[0], x => x[1])
- ).VisitAst(_idl);
-
- _extraUsings = _idl.Attributes.Where(u => u.Name == "clr-using").Select(u => u.Value).ToList();
- _namespace = _idl.GetAttribute("clr-namespace");
- var visibilityString = _idl.GetAttribute("clr-access");
-
- if (visibilityString == "internal")
- _visibility = SyntaxKind.InternalKeyword;
- else if (visibilityString == "public")
- _visibility = SyntaxKind.PublicKeyword;
- else
- throw new CodeGenException("Invalid clr-access attribute");
- }
-
- class AstRewriter : AstVisitor
- {
- private readonly Dictionary _typeMap = new Dictionary();
-
- public AstRewriter(Dictionary typeMap)
- {
- _typeMap = typeMap;
- }
-
- void ConvertIntPtr(AstTypeNode type)
- {
- if (type.Name == "void" && type.PointerLevel > 0)
- {
- type.Name = "IntPtr";
- type.PointerLevel--;
- }
- }
-
- protected override void VisitStructMember(AstStructMemberNode member)
- {
- if (member.HasAttribute("intptr"))
- ConvertIntPtr(member.Type);
- base.VisitStructMember(member);
- }
-
- protected override void VisitType(AstTypeNode type)
- {
- if (type.IsLink)
- {
- type.PointerLevel++;
- type.IsLink = false;
- }
-
- if (_typeMap.TryGetValue(type.Name, out var mapped))
- type.Name = mapped;
-
- base.VisitType(type);
- }
-
- protected override void VisitArgument(AstInterfaceMemberArgumentNode argument)
- {
- if (argument.HasAttribute("intptr"))
- {
- if(argument.Name == "retOut")
- Console.WriteLine();
- ConvertIntPtr(argument.Type);
- }
-
- base.VisitArgument(argument);
- }
-
- protected override void VisitInterfaceMember(AstInterfaceMemberNode member)
- {
- if (member.HasAttribute("intptr"))
- ConvertIntPtr(member.ReturnType);
- if (member.HasAttribute("propget") && !member.Name.StartsWith("Get"))
- member.Name = "Get" + member.Name;
- if (member.HasAttribute("propput") && !member.Name.StartsWith("Set"))
- member.Name = "Set" + member.Name;
- base.VisitInterfaceMember(member);
- }
- }
-
-
- public string Generate()
- {
- var ns = NamespaceDeclaration(ParseName(_namespace));
- var implNs = NamespaceDeclaration(ParseName(_namespace + ".Impl"));
- ns = GenerateEnums(ns);
- ns = GenerateStructs(ns);
- foreach (var i in _idl.Interfaces)
- GenerateInterface(ref ns, ref implNs, i);
-
- implNs = implNs.AddMembers(_localInterop.Class);
- var unit = Unit().AddMembers(ns, implNs);
-
- return Format(unit);
- }
-
- NamespaceDeclarationSyntax GenerateEnums(NamespaceDeclarationSyntax ns)
- {
- return ns.AddMembers(_idl.Enums.Select(e =>
- {
- var dec = EnumDeclaration(e.Name)
- .WithModifiers(TokenList(Token(_visibility)))
- .WithMembers(SeparatedList(e.Select(m =>
- {
- var member = EnumMemberDeclaration(m.Name);
- if (m.Value != null)
- return member.WithEqualsValue(EqualsValueClause(ParseExpression(m.Value)));
- return member;
- })));
- if (e.HasAttribute("flags"))
- dec = dec.AddAttribute("System.Flags");
- return dec;
- }).ToArray());
- }
-
- NamespaceDeclarationSyntax GenerateStructs(NamespaceDeclarationSyntax ns)
- {
- return ns.AddMembers(_idl.Structs.Select(e =>
- StructDeclaration(e.Name)
- .WithModifiers(TokenList(Token(_visibility)))
- .AddAttribute("System.Runtime.InteropServices.StructLayout", "System.Runtime.InteropServices.LayoutKind.Sequential")
- .AddModifiers(Token(SyntaxKind.UnsafeKeyword))
- .WithMembers(new SyntaxList(SeparatedList(e.Select(m =>
- DeclareField(m.Type.ToString(), m.Name, SyntaxKind.PublicKeyword)))))
- ).ToArray());
- }
-
-
-
- }
-}
diff --git a/src/tools/MicroComGenerator/CppGen.cs b/src/tools/MicroComGenerator/CppGen.cs
deleted file mode 100644
index b053088ca9..0000000000
--- a/src/tools/MicroComGenerator/CppGen.cs
+++ /dev/null
@@ -1,119 +0,0 @@
-using System;
-using System.Linq;
-using System.Text;
-using MicroComGenerator.Ast;
-
-namespace MicroComGenerator
-{
- public class CppGen
- {
- static string ConvertType(AstTypeNode type)
- {
- var name = type.Name;
- if (name == "byte")
- name = "unsigned char";
- else if(name == "uint")
- name = "unsigned int";
-
- type = type.Clone();
- type.Name = name;
- return type.Format();
- }
-
- public static string GenerateCpp(AstIdlNode idl)
- {
- var sb = new StringBuilder();
- var preamble = idl.GetAttributeOrDefault("cpp-preamble");
- if (preamble != null)
- sb.AppendLine(preamble);
-
- foreach (var s in idl.Structs)
- sb.AppendLine("struct " + s.Name + ";");
-
- foreach (var s in idl.Interfaces)
- sb.AppendLine("struct " + s.Name + ";");
-
- foreach (var en in idl.Enums)
- {
- sb.Append("enum ");
- if (en.Attributes.Any(a => a.Name == "class-enum"))
- sb.Append("class ");
- sb.AppendLine(en.Name).AppendLine("{");
-
- foreach (var m in en)
- {
- sb.Append(" ").Append(m.Name);
- if (m.Value != null)
- sb.Append(" = ").Append(m.Value);
- sb.AppendLine(",");
- }
-
- sb.AppendLine("};");
- }
-
- foreach (var s in idl.Structs)
- {
- sb.Append("struct ").AppendLine(s.Name).AppendLine("{");
- foreach (var m in s)
- sb.Append(" ").Append(ConvertType(m.Type)).Append(" ").Append(m.Name).AppendLine(";");
-
- sb.AppendLine("};");
- }
-
- foreach (var i in idl.Interfaces)
- {
- var guidString = i.GetAttribute("uuid");
- var guid = Guid.Parse(guidString).ToString().Replace("-", "");
-
-
- sb.Append("COMINTERFACE(").Append(i.Name).Append(", ")
- .Append(guid.Substring(0, 8)).Append(", ")
- .Append(guid.Substring(8, 4)).Append(", ")
- .Append(guid.Substring(12, 4));
- for (var c = 0; c < 8; c++)
- {
- sb.Append(", ").Append(guid.Substring(16 + c * 2, 2));
- }
-
- sb.Append(") : ");
- if (i.HasAttribute("cpp-virtual-inherits"))
- sb.Append("virtual ");
- sb.AppendLine(i.Inherits ?? "IUnknown")
- .AppendLine("{");
-
- foreach (var m in i)
- {
- sb.Append(" ")
- .Append("virtual ")
- .Append(ConvertType(m.ReturnType))
- .Append(" ").Append(m.Name).Append(" (");
- if (m.Count == 0)
- sb.AppendLine(") = 0;");
- else
- {
- sb.AppendLine();
- for (var c = 0; c < m.Count; c++)
- {
- var arg = m[c];
- sb.Append(" ");
- if (arg.Attributes.Any(a => a.Name == "const"))
- sb.Append("const ");
- sb.Append(ConvertType(arg.Type))
- .Append(" ")
- .Append(arg.Name);
- if (c != m.Count - 1)
- sb.Append(", ");
- sb.AppendLine();
- }
-
- sb.AppendLine(" ) = 0;");
- }
- }
-
- sb.AppendLine("};");
- }
-
- return sb.ToString();
- }
- }
-}
diff --git a/src/tools/MicroComGenerator/Extensions.cs b/src/tools/MicroComGenerator/Extensions.cs
deleted file mode 100644
index c8a4c8f45c..0000000000
--- a/src/tools/MicroComGenerator/Extensions.cs
+++ /dev/null
@@ -1,97 +0,0 @@
-
-using System;
-using System.Linq;
-using Microsoft.CodeAnalysis;
-using Microsoft.CodeAnalysis.CSharp;
-using Microsoft.CodeAnalysis.CSharp.Syntax;
-using static Microsoft.CodeAnalysis.CSharp.SyntaxFactory;
-namespace MicroComGenerator
-{
- public static class Extensions
- {
- public static ClassDeclarationSyntax AddModifiers(this ClassDeclarationSyntax cl, params SyntaxKind[] modifiers)
- {
- if (modifiers == null)
- return cl;
- return cl.AddModifiers(modifiers.Select(x => SyntaxFactory.Token(x)).ToArray());
- }
-
- public static MethodDeclarationSyntax AddModifiers(this MethodDeclarationSyntax cl, params SyntaxKind[] modifiers)
- {
- if (modifiers == null)
- return cl;
- return cl.AddModifiers(modifiers.Select(x => SyntaxFactory.Token(x)).ToArray());
- }
-
- public static PropertyDeclarationSyntax AddModifiers(this PropertyDeclarationSyntax cl, params SyntaxKind[] modifiers)
- {
- if (modifiers == null)
- return cl;
- return cl.AddModifiers(modifiers.Select(x => SyntaxFactory.Token(x)).ToArray());
- }
-
- public static ConstructorDeclarationSyntax AddModifiers(this ConstructorDeclarationSyntax cl, params SyntaxKind[] modifiers)
- {
- if (modifiers == null)
- return cl;
- return cl.AddModifiers(modifiers.Select(x => SyntaxFactory.Token(x)).ToArray());
- }
-
- public static AccessorDeclarationSyntax AddModifiers(this AccessorDeclarationSyntax cl, params SyntaxKind[] modifiers)
- {
- if (modifiers == null)
- return cl;
- return cl.AddModifiers(modifiers.Select(x => SyntaxFactory.Token(x)).ToArray());
- }
-
- public static string WithLowerFirst(this string s)
- {
- if (string.IsNullOrEmpty(s))
- return s;
- return char.ToLowerInvariant(s[0]) + s.Substring(1);
- }
-
- public static ExpressionSyntax MemberAccess(params string[] identifiers)
- {
- if (identifiers == null || identifiers.Length == 0)
- throw new ArgumentException();
- var expr = (ExpressionSyntax)IdentifierName(identifiers[0]);
- for (var c = 1; c < identifiers.Length; c++)
- expr = MemberAccess(expr, identifiers[c]);
- return expr;
- }
-
- public static ExpressionSyntax MemberAccess(ExpressionSyntax expr, params string[] identifiers)
- {
- foreach (var i in identifiers)
- expr = MemberAccess(expr, i);
- return expr;
- }
-
- public static MemberAccessExpressionSyntax MemberAccess(ExpressionSyntax expr, string identifier) =>
- MemberAccessExpression(SyntaxKind.SimpleMemberAccessExpression, expr, IdentifierName(identifier));
-
- public static ClassDeclarationSyntax WithBaseType(this ClassDeclarationSyntax cl, string bt)
- {
- return cl.AddBaseListTypes(SimpleBaseType(SyntaxFactory.ParseTypeName(bt)));
- }
-
- public static InterfaceDeclarationSyntax WithBaseType(this InterfaceDeclarationSyntax cl, string bt)
- {
- return cl.AddBaseListTypes(SimpleBaseType(SyntaxFactory.ParseTypeName(bt)));
- }
-
- public static T AddAttribute(this T member, string attribute, params string[] args) where T : MemberDeclarationSyntax
- {
- return (T)member.AddAttributeLists(AttributeList(SingletonSeparatedList(
- Attribute(ParseName(attribute), AttributeArgumentList(
- SeparatedList(args.Select(a => AttributeArgument(ParseExpression(a)))))))));
- }
-
- public static string StripPrefix(this string s, string prefix) => string.IsNullOrEmpty(s)
- ? s
- : s.StartsWith(prefix)
- ? s.Substring(prefix.Length)
- : s;
- }
-}
diff --git a/src/tools/MicroComGenerator/MicroComGenerator.csproj b/src/tools/MicroComGenerator/MicroComGenerator.csproj
deleted file mode 100644
index 68895b96ca..0000000000
--- a/src/tools/MicroComGenerator/MicroComGenerator.csproj
+++ /dev/null
@@ -1,10 +0,0 @@
-
-
- Exe
- net6.0
-
-
-
-
-
-
diff --git a/src/tools/MicroComGenerator/ParseException.cs b/src/tools/MicroComGenerator/ParseException.cs
deleted file mode 100644
index cb54918100..0000000000
--- a/src/tools/MicroComGenerator/ParseException.cs
+++ /dev/null
@@ -1,27 +0,0 @@
-using System;
-
-namespace MicroComGenerator
-{
- class ParseException : Exception
- {
- public int Line { get; }
- public int Position { get; }
-
- public ParseException(string message, int line, int position) : base($"({line}, {position}) {message}")
- {
- Line = line;
- Position = position;
- }
-
- public ParseException(string message, ref TokenParser parser) : this(message, parser.Line, parser.Position)
- {
- }
- }
-
- class CodeGenException : Exception
- {
- public CodeGenException(string message) : base(message)
- {
- }
- }
-}
diff --git a/src/tools/MicroComGenerator/Program.cs b/src/tools/MicroComGenerator/Program.cs
deleted file mode 100644
index 2468b1b5a4..0000000000
--- a/src/tools/MicroComGenerator/Program.cs
+++ /dev/null
@@ -1,52 +0,0 @@
-using System;
-using System.IO;
-using System.Linq;
-using CommandLine;
-
-namespace MicroComGenerator
-{
- class Program
- {
- public class Options
- {
- [Option('i', "input", Required = true, HelpText = "Input IDL file")]
- public string Input { get; set; }
-
- [Option("cpp", Required = false, HelpText = "C++ output file")]
- public string CppOutput { get; set; }
-
- [Option("cs", Required = false, HelpText = "C# output file")]
- public string CSharpOutput { get; set; }
-
- }
-
- static int Main(string[] args)
- {
- var p = Parser.Default.ParseArguments(args);
- if (p is NotParsed)
- {
- return 1;
- }
-
- var opts = ((Parsed)p).Value;
-
- var text = File.ReadAllText(opts.Input);
- var ast = AstParser.Parse(text);
-
- if (opts.CppOutput != null)
- File.WriteAllText(opts.CppOutput, CppGen.GenerateCpp(ast));
-
- if (opts.CSharpOutput != null)
- {
- File.WriteAllText(opts.CSharpOutput, new CSharpGen(ast).Generate());
-
- // HACK: Can't work out how to get the VS project system's fast up-to-date checks
- // to ignore the generated code, so as a workaround set the write time to that of
- // the input.
- File.SetLastWriteTime(opts.CSharpOutput, File.GetLastWriteTime(opts.Input));
- }
-
- return 0;
- }
- }
-}
diff --git a/src/tools/MicroComGenerator/TokenParser.cs b/src/tools/MicroComGenerator/TokenParser.cs
deleted file mode 100644
index ea8850b8e4..0000000000
--- a/src/tools/MicroComGenerator/TokenParser.cs
+++ /dev/null
@@ -1,417 +0,0 @@
-using System;
-using System.Globalization;
-using System.IO;
-
-namespace MicroComGenerator
-{
- internal ref struct TokenParser
- {
- private ReadOnlySpan _s;
- public int Position { get; private set; }
- public int Line { get; private set; }
- public TokenParser(ReadOnlySpan s)
- {
- _s = s;
- Position = 0;
- Line = 0;
- }
-
- public void SkipWhitespace()
- {
- while (true)
- {
- if(_s.Length == 0)
- return;
- if (char.IsWhiteSpace(_s[0]))
- Advance(1);
- else if (_s[0] == '/' && _s.Length>1)
- {
- if (_s[1] == '/')
- SkipOneLineComment();
- else if (_s[1] == '*')
- SkipMultiLineComment();
- else
- return;
- }
- else
- return;
- }
- }
-
- void SkipOneLineComment()
- {
- while (true)
- {
- if (_s.Length > 0 && _s[0] != '\n' && _s[0] != '\r')
- Advance(1);
- else
- return;
- }
- }
-
- void SkipMultiLineComment()
- {
- var l = Line;
- var p = Position;
- while (true)
- {
- if (_s.Length == 0)
- throw new ParseException("No matched */ found for /*", l, p);
-
- if (_s[0] == '*' && _s.Length > 1 && _s[1] == '/')
- {
- Advance(2);
- return;
- }
-
- Advance(1);
- }
- }
-
- static bool IsAlphaNumeric(char ch) => (ch >= '0' && ch <= '9') || (ch >= 'a' && ch <= 'z') ||
- (ch >= 'A' && ch <= 'Z');
-
- public void Consume(char c)
- {
- if (!TryConsume(c))
- throw new ParseException("Expected " + c, Line, Position);
- }
- public bool TryConsume(char c)
- {
- SkipWhitespace();
- if (_s.Length == 0 || _s[0] != c)
- return false;
-
- Advance(1);
- return true;
- }
-
- public bool TryConsume(string s)
- {
- SkipWhitespace();
- if (_s.Length < s.Length)
- return false;
- for (var c = 0; c < s.Length; c++)
- {
- if (_s[c] != s[c])
- return false;
- }
-
- Advance(s.Length);
- return true;
- }
-
- public bool TryConsumeAny(ReadOnlySpan chars, out char token)
- {
- SkipWhitespace();
- token = default;
- if (_s.Length == 0)
- return false;
-
- foreach (var c in chars)
- {
- if (c == _s[0])
- {
- token = c;
- Advance(1);
- return true;
- }
- }
-
- return false;
- }
-
-
- public bool TryParseKeyword(string keyword)
- {
- SkipWhitespace();
- if (keyword.Length > _s.Length)
- return false;
- for(var c=0; c keyword.Length && IsAlphaNumeric(_s[keyword.Length]))
- return false;
-
- Advance(keyword.Length);
- return true;
- }
-
- public bool TryParseKeywordLowerCase(string keywordInLowerCase)
- {
- SkipWhitespace();
- if (keywordInLowerCase.Length > _s.Length)
- return false;
- for(var c=0; c keywordInLowerCase.Length && IsAlphaNumeric(_s[keywordInLowerCase.Length]))
- return false;
-
- Advance(keywordInLowerCase.Length);
- return true;
- }
-
- public void Advance(int c)
- {
- while (c > 0)
- {
- if (_s[0] == '\n')
- {
- Line++;
- Position = 0;
- }
- else
- Position++;
-
- _s = _s.Slice(1);
- c--;
- }
- }
-
- public int Length => _s.Length;
- public bool Eof
- {
- get
- {
- SkipWhitespace();
- return Length == 0;
- }
- }
-
- public char Peek
- {
- get
- {
- if (_s.Length == 0)
- throw new ParseException("Unexpected EOF", Line, Position);
- return _s[0];
- }
- }
-
- public string ParseIdentifier(ReadOnlySpan extraValidChars)
- {
- if (!TryParseIdentifier(extraValidChars, out var ident))
- throw new ParseException("Identifier expected", Line, Position);
- return ident.ToString();
- }
-
- public string ParseIdentifier()
- {
- if (!TryParseIdentifier(out var ident))
- throw new ParseException("Identifier expected", Line, Position);
- return ident.ToString();
- }
-
- public bool TryParseIdentifier(ReadOnlySpan extraValidChars, out ReadOnlySpan res)
- {
- res = ReadOnlySpan.Empty;
- SkipWhitespace();
- if (_s.Length == 0)
- return false;
- var first = _s[0];
- if (!((first >= 'a' && first <= 'z') || (first >= 'A' && first <= 'Z') || first == '_'))
- return false;
- int len = 1;
- for (var c = 1; c < _s.Length; c++)
- {
- var ch = _s[c];
- if (IsAlphaNumeric(ch) || ch == '_')
- len++;
- else
- {
- var found = false;
- foreach(var vc in extraValidChars)
- if (vc == ch)
- {
- found = true;
- break;
- }
-
- if (found)
- len++;
- else
- break;
- }
- }
-
- res = _s.Slice(0, len);
- Advance(len);
- return true;
- }
-
- public bool TryParseIdentifier(out ReadOnlySpan res)
- {
- res = ReadOnlySpan.Empty;
- SkipWhitespace();
- if (_s.Length == 0)
- return false;
- var first = _s[0];
- if (!((first >= 'a' && first <= 'z') || (first >= 'A' && first <= 'Z') || first == '_'))
- return false;
- int len = 1;
- for (var c = 1; c < _s.Length; c++)
- {
- var ch = _s[c];
- if (IsAlphaNumeric(ch) || ch == '_')
- len++;
- else
- break;
- }
-
- res = _s.Slice(0, len);
- Advance(len);
- return true;
- }
-
- public string ReadToEol()
- {
- var initial = _s;
- var len = 0;
- while (true)
- {
- if (_s.Length > 0 && _s[0] != '\n' && _s[0] != '\r')
- {
- len++;
- Advance(1);
- }
- else
- return initial.Slice(0, len).ToString();
- }
- }
-
- public string ReadTo(char c)
- {
- var initial = _s;
- var len = 0;
- var l = Line;
- var p = Position;
- while (true)
- {
- if (_s.Length == 0)
- throw new ParseException("Expected " + c + " before EOF", l, p);
-
- if (_s[0] != c)
- {
- len++;
- Advance(1);
- }
- else
- return initial.Slice(0, len).ToString();
- }
- }
-
- public string ReadToAny(ReadOnlySpan chars)
- {
- var initial = _s;
- var len = 0;
- var l = Line;
- var p = Position;
- while (true)
- {
- if (_s.Length == 0)
- throw new ParseException("Expected any of '" + chars.ToString() + "' before EOF", l, p);
-
- var foundTerminator = false;
- foreach (var term in chars)
- {
- if (_s[0] == term)
- {
- foundTerminator = true;
- break;
- }
- }
-
- if (!foundTerminator)
- {
- len++;
- Advance(1);
- }
- else
- return initial.Slice(0, len).ToString();
- }
- }
-
- public bool TryParseCall(out ReadOnlySpan res)
- {
- res = ReadOnlySpan.Empty;
- SkipWhitespace();
- if (_s.Length == 0)
- return false;
- var first = _s[0];
- if (!((first >= 'a' && first <= 'z') || (first >= 'A' && first <= 'Z')))
- return false;
- int len = 1;
- for (var c = 1; c < _s.Length; c++)
- {
- var ch = _s[c];
- if ((ch >= '0' && ch <= '9') || (ch >= 'a' && ch <= 'z') || (ch >= 'A' && ch<= 'Z') || ch == '.')
- len++;
- else
- break;
- }
-
- res = _s.Slice(0, len);
-
- // Find '('
- for (var c = len; c < _s.Length; c++)
- {
- if(char.IsWhiteSpace(_s[c]))
- continue;
- if(_s[c]=='(')
- {
- Advance(c + 1);
- return true;
- }
-
- return false;
-
- }
-
- return false;
-
- }
-
-
- public bool TryParseFloat(out float res)
- {
- res = 0;
- SkipWhitespace();
- if (_s.Length == 0)
- return false;
-
- var len = 0;
- var dotCount = 0;
- for (var c = 0; c < _s.Length; c++)
- {
- var ch = _s[c];
- if (ch >= '0' && ch <= '9')
- len = c + 1;
- else if (ch == '.' && dotCount == 0)
- {
- len = c + 1;
- dotCount++;
- }
- else
- break;
- }
-
- var span = _s.Slice(0, len);
-
-#if NETSTANDARD2_0
- if (!float.TryParse(span.ToString(), NumberStyles.Number, CultureInfo.InvariantCulture, out res))
- return false;
-#else
- if (!float.TryParse(span, NumberStyles.Number, CultureInfo.InvariantCulture, out res))
- return false;
-#endif
- Advance(len);
- return true;
- }
-
- public override string ToString() => _s.ToString();
-
- }
-}
diff --git a/tests/Avalonia.Base.UnitTests/Data/Core/ExpressionObserverTests_DataValidation.cs b/tests/Avalonia.Base.UnitTests/Data/Core/ExpressionObserverTests_DataValidation.cs
index ecc43aa3a5..43192584af 100644
--- a/tests/Avalonia.Base.UnitTests/Data/Core/ExpressionObserverTests_DataValidation.cs
+++ b/tests/Avalonia.Base.UnitTests/Data/Core/ExpressionObserverTests_DataValidation.cs
@@ -6,6 +6,7 @@ using System.Reactive.Linq;
using Avalonia.Data;
using Avalonia.Data.Core;
using Avalonia.Markup.Parsers;
+using Avalonia.Threading;
using Avalonia.UnitTests;
using Xunit;
@@ -67,6 +68,8 @@ namespace Avalonia.Base.UnitTests.Data.Core
Assert.Equal(1, data.ErrorsChangedSubscriptionCount);
sub.Dispose();
+ // Forces WeakEvent compact
+ Dispatcher.UIThread.RunJobs();
Assert.Equal(0, data.ErrorsChangedSubscriptionCount);
}
diff --git a/tests/Avalonia.Base.UnitTests/Data/Core/ExpressionObserverTests_Indexer.cs b/tests/Avalonia.Base.UnitTests/Data/Core/ExpressionObserverTests_Indexer.cs
index 6289ec46c7..20a4cb6d98 100644
--- a/tests/Avalonia.Base.UnitTests/Data/Core/ExpressionObserverTests_Indexer.cs
+++ b/tests/Avalonia.Base.UnitTests/Data/Core/ExpressionObserverTests_Indexer.cs
@@ -9,6 +9,7 @@ using Avalonia.Data.Core;
using Avalonia.UnitTests;
using Xunit;
using Avalonia.Markup.Parsers;
+using Avalonia.Threading;
namespace Avalonia.Base.UnitTests.Data.Core
{
@@ -110,6 +111,9 @@ namespace Avalonia.Base.UnitTests.Data.Core
data.Foo.Add("baz");
}
+ // Forces WeakEvent compact
+ Dispatcher.UIThread.RunJobs();
+
Assert.Equal(new[] { AvaloniaProperty.UnsetValue, "baz" }, result);
Assert.Null(((INotifyCollectionChangedDebug)data.Foo).GetCollectionChangedSubscribers());
@@ -127,6 +131,8 @@ namespace Avalonia.Base.UnitTests.Data.Core
{
data.Foo.RemoveAt(0);
}
+ // Forces WeakEvent compact
+ Dispatcher.UIThread.RunJobs();
Assert.Equal(new[] { "foo", "bar" }, result);
Assert.Null(((INotifyCollectionChangedDebug)data.Foo).GetCollectionChangedSubscribers());
@@ -145,6 +151,9 @@ namespace Avalonia.Base.UnitTests.Data.Core
{
data.Foo[1] = "baz";
}
+
+ // Forces WeakEvent compact
+ Dispatcher.UIThread.RunJobs();
Assert.Equal(new[] { "bar", "baz" }, result);
Assert.Null(((INotifyCollectionChangedDebug)data.Foo).GetCollectionChangedSubscribers());
@@ -202,6 +211,9 @@ namespace Avalonia.Base.UnitTests.Data.Core
data.Foo["foo"] = "bar2";
}
+ // Forces WeakEvent compact
+ Dispatcher.UIThread.RunJobs();
+
var expected = new[] { "bar", "bar2" };
Assert.Equal(expected, result);
Assert.Equal(0, data.Foo.PropertyChangedSubscriptionCount);
diff --git a/tests/Avalonia.Base.UnitTests/Data/Core/ExpressionObserverTests_Observable.cs b/tests/Avalonia.Base.UnitTests/Data/Core/ExpressionObserverTests_Observable.cs
index a70d4574a6..4f88d2de7c 100644
--- a/tests/Avalonia.Base.UnitTests/Data/Core/ExpressionObserverTests_Observable.cs
+++ b/tests/Avalonia.Base.UnitTests/Data/Core/ExpressionObserverTests_Observable.cs
@@ -5,6 +5,7 @@ using System.Reactive.Subjects;
using Avalonia.Data;
using Avalonia.Data.Core;
using Avalonia.Markup.Parsers;
+using Avalonia.Threading;
using Avalonia.UnitTests;
using Xunit;
@@ -68,6 +69,8 @@ namespace Avalonia.Base.UnitTests.Data.Core
Assert.Equal(new[] { "foo" }, result);
sub.Dispose();
+ // Forces WeakEvent compact
+ Dispatcher.UIThread.RunJobs();
Assert.Equal(0, data.PropertyChangedSubscriptionCount);
GC.KeepAlive(data);
@@ -109,10 +112,16 @@ namespace Avalonia.Base.UnitTests.Data.Core
var sub = target.Subscribe(x => result.Add(x));
data1.Next.OnNext(data2);
sync.ExecutePostedCallbacks();
-
+
+ // Forces WeakEvent compact
+ Dispatcher.UIThread.RunJobs();
Assert.Equal(new[] { new BindingNotification("foo") }, result);
sub.Dispose();
+
+ // Forces WeakEvent compact
+ Dispatcher.UIThread.RunJobs();
+
Assert.Equal(0, data1.PropertyChangedSubscriptionCount);
GC.KeepAlive(data1);
diff --git a/tests/Avalonia.Base.UnitTests/Data/Core/ExpressionObserverTests_Property.cs b/tests/Avalonia.Base.UnitTests/Data/Core/ExpressionObserverTests_Property.cs
index 32cdd21e04..a9c62a3c4a 100644
--- a/tests/Avalonia.Base.UnitTests/Data/Core/ExpressionObserverTests_Property.cs
+++ b/tests/Avalonia.Base.UnitTests/Data/Core/ExpressionObserverTests_Property.cs
@@ -10,6 +10,7 @@ using Avalonia.UnitTests;
using Xunit;
using System.Threading.Tasks;
using Avalonia.Markup.Parsers;
+using Avalonia.Threading;
namespace Avalonia.Base.UnitTests.Data.Core
{
@@ -182,6 +183,9 @@ namespace Avalonia.Base.UnitTests.Data.Core
sub.Dispose();
+ // Forces WeakEvent compact
+ Dispatcher.UIThread.RunJobs();
+
Assert.Equal(0, data.PropertyChangedSubscriptionCount);
GC.KeepAlive(data);
@@ -209,8 +213,11 @@ namespace Avalonia.Base.UnitTests.Data.Core
data.RaisePropertyChanged(null);
Assert.Equal(new[] { "foo", "bar", "bar" }, result);
-
+
sub.Dispose();
+
+ // Forces WeakEvent compact
+ Dispatcher.UIThread.RunJobs();
Assert.Equal(0, data.PropertyChangedSubscriptionCount);
@@ -231,7 +238,9 @@ namespace Avalonia.Base.UnitTests.Data.Core
Assert.Equal(new[] { "bar", "baz", null }, result);
sub.Dispose();
-
+ // Forces WeakEvent compact
+ Dispatcher.UIThread.RunJobs();
+
Assert.Equal(0, data.PropertyChangedSubscriptionCount);
Assert.Equal(0, data.Next.PropertyChangedSubscriptionCount);
@@ -253,6 +262,9 @@ namespace Avalonia.Base.UnitTests.Data.Core
Assert.Equal(new[] { "bar", "baz", null }, result);
sub.Dispose();
+
+ // Forces WeakEvent compact
+ Dispatcher.UIThread.RunJobs();
Assert.Equal(0, data.PropertyChangedSubscriptionCount);
Assert.Equal(0, data.Next.PropertyChangedSubscriptionCount);
@@ -297,6 +309,9 @@ namespace Avalonia.Base.UnitTests.Data.Core
sub.Dispose();
+ // Forces WeakEvent compact
+ Dispatcher.UIThread.RunJobs();
+
Assert.Equal(0, data.PropertyChangedSubscriptionCount);
Assert.Equal(0, data.Next.PropertyChangedSubscriptionCount);
Assert.Equal(0, old.PropertyChangedSubscriptionCount);
@@ -329,6 +344,9 @@ namespace Avalonia.Base.UnitTests.Data.Core
result);
sub.Dispose();
+
+ // Forces WeakEvent compact
+ Dispatcher.UIThread.RunJobs();
Assert.Equal(0, data.PropertyChangedSubscriptionCount);
Assert.Equal(0, data.Next.PropertyChangedSubscriptionCount);
@@ -412,6 +430,9 @@ namespace Avalonia.Base.UnitTests.Data.Core
sub1.Dispose();
sub2.Dispose();
+ // Forces WeakEvent compact
+ Dispatcher.UIThread.RunJobs();
+
Assert.Equal(0, data.PropertyChangedSubscriptionCount);
GC.KeepAlive(data);
@@ -535,6 +556,8 @@ namespace Avalonia.Base.UnitTests.Data.Core
},
result);
+ // Forces WeakEvent compact
+ Dispatcher.UIThread.RunJobs();
Assert.Equal(0, first.PropertyChangedSubscriptionCount);
Assert.Equal(0, second.PropertyChangedSubscriptionCount);
diff --git a/tests/Avalonia.Base.UnitTests/Data/Core/Plugins/IndeiValidationPluginTests.cs b/tests/Avalonia.Base.UnitTests/Data/Core/Plugins/IndeiValidationPluginTests.cs
index d8eddf6330..e8f1f38b90 100644
--- a/tests/Avalonia.Base.UnitTests/Data/Core/Plugins/IndeiValidationPluginTests.cs
+++ b/tests/Avalonia.Base.UnitTests/Data/Core/Plugins/IndeiValidationPluginTests.cs
@@ -3,6 +3,7 @@ using System.Collections;
using System.Collections.Generic;
using Avalonia.Data;
using Avalonia.Data.Core.Plugins;
+using Avalonia.Threading;
using Xunit;
namespace Avalonia.Base.UnitTests.Data.Core.Plugins
@@ -57,6 +58,8 @@ namespace Avalonia.Base.UnitTests.Data.Core.Plugins
validator.Subscribe(_ => { });
Assert.Equal(1, data.ErrorsChangedSubscriptionCount);
validator.Unsubscribe();
+ // Forces WeakEvent compact
+ Dispatcher.UIThread.RunJobs();
Assert.Equal(0, data.ErrorsChangedSubscriptionCount);
}
diff --git a/tests/Avalonia.Base.UnitTests/WeakEventTests.cs b/tests/Avalonia.Base.UnitTests/WeakEventTests.cs
new file mode 100644
index 0000000000..2663b4858f
--- /dev/null
+++ b/tests/Avalonia.Base.UnitTests/WeakEventTests.cs
@@ -0,0 +1,75 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using Avalonia.Utilities;
+using Xunit;
+
+namespace Avalonia.Base.UnitTests
+{
+ public class WeakEventTests
+ {
+ class EventSource
+ {
+ public event EventHandler Event;
+
+ public void Fire()
+ {
+ Event?.Invoke(this, new EventArgs());
+ }
+
+ public static readonly WeakEvent WeakEv = WeakEvent.Register(
+ (t, s) => t.Event += s,
+ (t, s) => t.Event -= s);
+ }
+
+ class Subscriber : IWeakEventSubscriber
+ {
+ private readonly Action _onEvent;
+
+ public Subscriber(Action onEvent)
+ {
+ _onEvent = onEvent;
+ }
+
+ public void OnEvent(object sender, WeakEvent ev, EventArgs args)
+ {
+ _onEvent?.Invoke();
+ }
+ }
+
+ [Fact]
+ public void EventShouldBePassedToSubscriber()
+ {
+ bool handled = false;
+ var subscriber = new Subscriber(() => handled = true);
+ var source = new EventSource();
+ EventSource.WeakEv.Subscribe(source, subscriber);
+
+ source.Fire();
+ Assert.True(handled);
+ }
+
+
+ [Fact]
+ public void EventHandlerShouldNotBeKeptAlive()
+ {
+ bool handled = false;
+ var source = new EventSource();
+ AddSubscriber(source, () => handled = true);
+ for (int c = 0; c < 10; c++)
+ {
+ GC.Collect();
+ GC.Collect(3, GCCollectionMode.Forced, true);
+ }
+ source.Fire();
+ Assert.False(handled);
+ }
+
+ private void AddSubscriber(EventSource source, Action func)
+ {
+ EventSource.WeakEv.Subscribe(source, new Subscriber(func));
+ }
+ }
+}
diff --git a/tests/Avalonia.Input.UnitTests/TouchDeviceTests.cs b/tests/Avalonia.Input.UnitTests/TouchDeviceTests.cs
index 6c4416be47..80c5a45c1a 100644
--- a/tests/Avalonia.Input.UnitTests/TouchDeviceTests.cs
+++ b/tests/Avalonia.Input.UnitTests/TouchDeviceTests.cs
@@ -12,56 +12,47 @@ namespace Avalonia.Input.UnitTests
[Fact]
public void Tapped_Event_Is_Fired_With_Touch()
{
- using (UnitTestApplication.Start(
- new TestServices(inputManager: new InputManager())))
+ using var app = UnitTestApp(new TimeSpan(200));
+ var root = new TestRoot();
+ var touchDevice = new TouchDevice();
+
+ var isTapped = false;
+ var executedTimes = 0;
+ root.Tapped += (a, e) =>
{
- var root = new TestRoot();
- var touchDevice = new TouchDevice();
+ isTapped = true;
+ executedTimes++;
+ };
+ TapOnce(InputManager.Instance, touchDevice, root);
+ Assert.True(isTapped);
+ Assert.Equal(1, executedTimes);
- var isTapped = false;
- var executedTimes = 0;
- root.Tapped += (a, e) =>
- {
- isTapped = true;
- executedTimes++;
- };
- TapOnce(InputManager.Instance, touchDevice, root);
- Assert.True(isTapped);
- Assert.Equal(1, executedTimes);
- }
}
[Fact]
public void DoubleTapped_Event_Is_Fired_With_Touch()
{
- var platformSettingsMock = new Mock();
- platformSettingsMock.Setup(x => x.DoubleClickTime).Returns(new TimeSpan(200));
- AvaloniaLocator.CurrentMutable.BindToSelf(this)
- .Bind().ToConstant(platformSettingsMock.Object);
- using (UnitTestApplication.Start(
- new TestServices(inputManager: new InputManager())))
+ using var app = UnitTestApp(new TimeSpan(200));
+ var root = new TestRoot();
+ var touchDevice = new TouchDevice();
+
+ var isDoubleTapped = false;
+ var doubleTappedExecutedTimes = 0;
+ var tappedExecutedTimes = 0;
+ root.DoubleTapped += (a, e) =>
{
- var root = new TestRoot();
- var touchDevice = new TouchDevice();
-
- var isDoubleTapped = false;
- var doubleTappedExecutedTimes = 0;
- var tappedExecutedTimes = 0;
- root.DoubleTapped += (a, e) =>
- {
- isDoubleTapped = true;
- doubleTappedExecutedTimes++;
- };
- root.Tapped += (a, e) =>
- {
- tappedExecutedTimes++;
- };
- TapOnce(InputManager.Instance, touchDevice, root);
- TapOnce(InputManager.Instance, touchDevice, root, touchPointId: 1);
- Assert.Equal(1, tappedExecutedTimes);
- Assert.True(isDoubleTapped);
- Assert.Equal(1, doubleTappedExecutedTimes);
- }
+ isDoubleTapped = true;
+ doubleTappedExecutedTimes++;
+ };
+ root.Tapped += (a, e) =>
+ {
+ tappedExecutedTimes++;
+ };
+ TapOnce(InputManager.Instance, touchDevice, root);
+ TapOnce(InputManager.Instance, touchDevice, root, touchPointId: 1);
+ Assert.Equal(1, tappedExecutedTimes);
+ Assert.True(isDoubleTapped);
+ Assert.Equal(1, doubleTappedExecutedTimes);
}
[Theory]
@@ -72,173 +63,158 @@ namespace Avalonia.Input.UnitTests
[InlineData(5)]
public void PointerPressed_Counts_Clicks_Correctly(int clickCount)
{
- var platformSettingsMock = new Mock();
- platformSettingsMock.Setup(x => x.DoubleClickTime).Returns(new TimeSpan(200));
- AvaloniaLocator.CurrentMutable.BindToSelf(this)
- .Bind().ToConstant(platformSettingsMock.Object);
- using (UnitTestApplication.Start(
- new TestServices(inputManager: new InputManager())))
- {
- var root = new TestRoot();
- var touchDevice = new TouchDevice();
+ using var app = UnitTestApp(new TimeSpan(200));
+ var root = new TestRoot();
+ var touchDevice = new TouchDevice();
- var pointerPressedExecutedTimes = 0;
- var pointerPressedClicks = 0;
- root.PointerPressed += (a, e) =>
- {
- pointerPressedClicks = e.ClickCount;
- pointerPressedExecutedTimes++;
- };
- for (int i = 0; i < clickCount; i++)
- {
- TapOnce(InputManager.Instance, touchDevice, root, touchPointId: i);
- }
-
- Assert.Equal(clickCount, pointerPressedExecutedTimes);
- Assert.Equal(pointerPressedClicks, clickCount);
+ var pointerPressedExecutedTimes = 0;
+ var pointerPressedClicks = 0;
+ root.PointerPressed += (a, e) =>
+ {
+ pointerPressedClicks = e.ClickCount;
+ pointerPressedExecutedTimes++;
+ };
+ for (int i = 0; i < clickCount; i++)
+ {
+ TapOnce(InputManager.Instance, touchDevice, root, touchPointId: i);
}
+
+ Assert.Equal(clickCount, pointerPressedExecutedTimes);
+ Assert.Equal(pointerPressedClicks, clickCount);
}
[Fact]
public void DoubleTapped_Not_Fired_When_Click_Too_Late()
{
- var platformSettingsMock = new Mock();
- platformSettingsMock.Setup(x => x.DoubleClickTime).Returns(new TimeSpan(0, 0, 0, 0, 20));
- AvaloniaLocator.CurrentMutable.BindToSelf(this)
- .Bind().ToConstant(platformSettingsMock.Object);
- using (UnitTestApplication.Start(
- new TestServices(inputManager: new InputManager())))
+ using var app = UnitTestApp(new TimeSpan(0, 0, 0, 0, 20));
+ var root = new TestRoot();
+ var touchDevice = new TouchDevice();
+
+ var isDoubleTapped = false;
+ var doubleTappedExecutedTimes = 0;
+ var tappedExecutedTimes = 0;
+ root.DoubleTapped += (a, e) =>
{
- var root = new TestRoot();
- var touchDevice = new TouchDevice();
+ isDoubleTapped = true;
+ doubleTappedExecutedTimes++;
+ };
+ root.Tapped += (a, e) =>
+ {
+ tappedExecutedTimes++;
+ };
+ TapOnce(InputManager.Instance, touchDevice, root);
+ TapOnce(InputManager.Instance, touchDevice, root, 21, 1);
+ Assert.Equal(2, tappedExecutedTimes);
+ Assert.False(isDoubleTapped);
+ Assert.Equal(0, doubleTappedExecutedTimes);
- var isDoubleTapped = false;
- var doubleTappedExecutedTimes = 0;
- var tappedExecutedTimes = 0;
- root.DoubleTapped += (a, e) =>
- {
- isDoubleTapped = true;
- doubleTappedExecutedTimes++;
- };
- root.Tapped += (a, e) =>
- {
- tappedExecutedTimes++;
- };
- TapOnce(InputManager.Instance, touchDevice, root);
- TapOnce(InputManager.Instance, touchDevice, root, 21, 1);
- Assert.Equal(2, tappedExecutedTimes);
- Assert.False(isDoubleTapped);
- Assert.Equal(0, doubleTappedExecutedTimes);
- }
}
[Fact]
public void DoubleTapped_Not_Fired_When_Second_Click_Is_From_Different_Touch_Contact()
{
- var tmp = new Mock();
- tmp.Setup(x => x.DoubleClickTime).Returns(new TimeSpan(200));
- AvaloniaLocator.CurrentMutable.BindToSelf(this)
- .Bind().ToConstant(tmp.Object);
- using (UnitTestApplication.Start(
- new TestServices(inputManager: new InputManager())))
+ using var app = UnitTestApp(new TimeSpan(200));
+ var root = new TestRoot();
+ var touchDevice = new TouchDevice();
+
+ var isDoubleTapped = false;
+ var doubleTappedExecutedTimes = 0;
+ var tappedExecutedTimes = 0;
+ root.DoubleTapped += (a, e) =>
{
- var root = new TestRoot();
- var touchDevice = new TouchDevice();
-
- var isDoubleTapped = false;
- var doubleTappedExecutedTimes = 0;
- var tappedExecutedTimes = 0;
- root.DoubleTapped += (a, e) =>
- {
- isDoubleTapped = true;
- doubleTappedExecutedTimes++;
- };
- root.Tapped += (a, e) =>
- {
- tappedExecutedTimes++;
- };
- SendXTouchContactsWithIds(InputManager.Instance, touchDevice, root, RawPointerEventType.TouchBegin, 0, 1);
- SendXTouchContactsWithIds(InputManager.Instance, touchDevice, root, RawPointerEventType.TouchEnd, 0, 1);
- Assert.Equal(2, tappedExecutedTimes);
- Assert.False(isDoubleTapped);
- Assert.Equal(0, doubleTappedExecutedTimes);
- }
+ isDoubleTapped = true;
+ doubleTappedExecutedTimes++;
+ };
+ root.Tapped += (a, e) =>
+ {
+ tappedExecutedTimes++;
+ };
+ SendXTouchContactsWithIds(InputManager.Instance, touchDevice, root, RawPointerEventType.TouchBegin, 0, 1);
+ SendXTouchContactsWithIds(InputManager.Instance, touchDevice, root, RawPointerEventType.TouchEnd, 0, 1);
+ Assert.Equal(2, tappedExecutedTimes);
+ Assert.False(isDoubleTapped);
+ Assert.Equal(0, doubleTappedExecutedTimes);
}
[Fact]
public void Click_Counting_Should_Work_Correctly_With_Few_Touch_Contacts()
{
- var tmp = new Mock();
- tmp.Setup(x => x.DoubleClickTime).Returns(new TimeSpan(200));
- AvaloniaLocator.CurrentMutable.BindToSelf(this)
- .Bind().ToConstant(tmp.Object);
- using (UnitTestApplication.Start(
- new TestServices(inputManager: new InputManager())))
- {
- var root = new TestRoot();
- var touchDevice = new TouchDevice();
+ using var app = UnitTestApp(new TimeSpan(200));
- var pointerPressedExecutedTimes = 0;
- var tappedExecutedTimes = 0;
- var isDoubleTapped = false;
- var doubleTappedExecutedTimes = 0;
- root.PointerPressed += (a, e) =>
- {
- pointerPressedExecutedTimes++;
- switch (pointerPressedExecutedTimes)
- {
- case <= 2:
- Assert.True(e.ClickCount == 1);
- break;
- case 3:
- Assert.True(e.ClickCount == 2);
- break;
- case 4:
- Assert.True(e.ClickCount == 3);
- break;
- case 5:
- Assert.True(e.ClickCount == 4);
- break;
- case 6:
- Assert.True(e.ClickCount == 5);
- break;
- case 7:
- Assert.True(e.ClickCount == 1);
- break;
- case 8:
- Assert.True(e.ClickCount == 1);
- break;
- case 9:
- Assert.True(e.ClickCount == 2);
- break;
- default:
- break;
- }
- };
- root.DoubleTapped += (a, e) =>
- {
- isDoubleTapped = true;
- doubleTappedExecutedTimes++;
- };
- root.Tapped += (a, e) =>
+ var root = new TestRoot();
+ var touchDevice = new TouchDevice();
+
+ var pointerPressedExecutedTimes = 0;
+ var tappedExecutedTimes = 0;
+ var isDoubleTapped = false;
+ var doubleTappedExecutedTimes = 0;
+ root.PointerPressed += (a, e) =>
+ {
+ pointerPressedExecutedTimes++;
+ switch (pointerPressedExecutedTimes)
{
- tappedExecutedTimes++;
- };
- SendXTouchContactsWithIds(InputManager.Instance, touchDevice, root, RawPointerEventType.TouchBegin, 0, 1);
- SendXTouchContactsWithIds(InputManager.Instance, touchDevice, root, RawPointerEventType.TouchEnd, 0, 1);
- TapOnce(InputManager.Instance, touchDevice, root, touchPointId: 2);
- TapOnce(InputManager.Instance, touchDevice, root, touchPointId: 3);
- TapOnce(InputManager.Instance, touchDevice, root, touchPointId: 4);
- SendXTouchContactsWithIds(InputManager.Instance, touchDevice, root, RawPointerEventType.TouchBegin, 5, 6, 7);
- SendXTouchContactsWithIds(InputManager.Instance, touchDevice, root, RawPointerEventType.TouchEnd, 5, 6, 7);
- TapOnce(InputManager.Instance, touchDevice, root, touchPointId: 8);
- Assert.Equal(6, tappedExecutedTimes);
- Assert.Equal(9, pointerPressedExecutedTimes);
- Assert.True(isDoubleTapped);
- Assert.Equal(3, doubleTappedExecutedTimes);
+ case <= 2:
+ Assert.True(e.ClickCount == 1);
+ break;
+ case 3:
+ Assert.True(e.ClickCount == 2);
+ break;
+ case 4:
+ Assert.True(e.ClickCount == 3);
+ break;
+ case 5:
+ Assert.True(e.ClickCount == 4);
+ break;
+ case 6:
+ Assert.True(e.ClickCount == 5);
+ break;
+ case 7:
+ Assert.True(e.ClickCount == 1);
+ break;
+ case 8:
+ Assert.True(e.ClickCount == 1);
+ break;
+ case 9:
+ Assert.True(e.ClickCount == 2);
+ break;
+ default:
+ break;
+ }
+ };
+ root.DoubleTapped += (a, e) =>
+ {
+ isDoubleTapped = true;
+ doubleTappedExecutedTimes++;
+ };
+ root.Tapped += (a, e) =>
+ {
+ tappedExecutedTimes++;
+ };
+ SendXTouchContactsWithIds(InputManager.Instance, touchDevice, root, RawPointerEventType.TouchBegin, 0, 1);
+ SendXTouchContactsWithIds(InputManager.Instance, touchDevice, root, RawPointerEventType.TouchEnd, 0, 1);
+ TapOnce(InputManager.Instance, touchDevice, root, touchPointId: 2);
+ TapOnce(InputManager.Instance, touchDevice, root, touchPointId: 3);
+ TapOnce(InputManager.Instance, touchDevice, root, touchPointId: 4);
+ SendXTouchContactsWithIds(InputManager.Instance, touchDevice, root, RawPointerEventType.TouchBegin, 5, 6, 7);
+ SendXTouchContactsWithIds(InputManager.Instance, touchDevice, root, RawPointerEventType.TouchEnd, 5, 6, 7);
+ TapOnce(InputManager.Instance, touchDevice, root, touchPointId: 8);
+ Assert.Equal(6, tappedExecutedTimes);
+ Assert.Equal(9, pointerPressedExecutedTimes);
+ Assert.True(isDoubleTapped);
+ Assert.Equal(3, doubleTappedExecutedTimes);
+ }
- }
+ private IDisposable UnitTestApp(TimeSpan doubleClickTime = new TimeSpan())
+ {
+ var unitTestApp = UnitTestApplication.Start(
+ new TestServices(inputManager: new InputManager()));
+ var iSettingsMock = new Mock();
+ iSettingsMock.Setup(x => x.TouchDoubleClickTime).Returns(doubleClickTime);
+ AvaloniaLocator.CurrentMutable.BindToSelf(this)
+ .Bind().ToConstant(iSettingsMock.Object);
+ return unitTestApp;
}
+
private static void SendXTouchContactsWithIds(IInputManager inputManager, TouchDevice device, IInputRoot root, RawPointerEventType type, params long[] touchPointIds)
{
for (int i = 0; i < touchPointIds.Length; i++)
diff --git a/tests/Avalonia.LeakTests/AvaloniaObjectTests.cs b/tests/Avalonia.LeakTests/AvaloniaObjectTests.cs
index 54f9a87f94..19208b15f3 100644
--- a/tests/Avalonia.LeakTests/AvaloniaObjectTests.cs
+++ b/tests/Avalonia.LeakTests/AvaloniaObjectTests.cs
@@ -1,5 +1,6 @@
using System;
using System.Reactive.Subjects;
+using Avalonia.Threading;
using JetBrains.dotMemoryUnit;
using Xunit;
using Xunit.Abstractions;
@@ -56,7 +57,9 @@ namespace Avalonia.LeakTests
completeSource();
GC.Collect();
-
+ // Forces WeakEvent compact
+ Dispatcher.UIThread.RunJobs();
+ GC.Collect();
Assert.False(weakSource.IsAlive);
}
diff --git a/tests/Avalonia.Markup.UnitTests/Data/BindingTests.cs b/tests/Avalonia.Markup.UnitTests/Data/BindingTests.cs
index a0dd565a87..055de999e2 100644
--- a/tests/Avalonia.Markup.UnitTests/Data/BindingTests.cs
+++ b/tests/Avalonia.Markup.UnitTests/Data/BindingTests.cs
@@ -12,6 +12,7 @@ using System.Runtime.CompilerServices;
using Avalonia.UnitTests;
using Avalonia.Data.Converters;
using Avalonia.Data.Core;
+using Avalonia.Threading;
namespace Avalonia.Markup.UnitTests.Data
{
@@ -160,6 +161,9 @@ namespace Avalonia.Markup.UnitTests.Data
target.Bind(TextBlock.TextProperty, new Binding("Foo", BindingMode.OneTime));
target.DataContext = source;
+ // Forces WeakEvent compact
+ Dispatcher.UIThread.RunJobs();
+
Assert.Equal(0, source.SubscriberCount);
}
@@ -608,6 +612,9 @@ namespace Avalonia.Markup.UnitTests.Data
root.DataContext = source;
}
+ // Forces WeakEvent compact
+ Dispatcher.UIThread.RunJobs();
+
Assert.Equal(0, source.SubscriberCount);
}
diff --git a/tests/Avalonia.Markup.UnitTests/Parsers/ExpressionObserverBuilderTests_Indexer.cs b/tests/Avalonia.Markup.UnitTests/Parsers/ExpressionObserverBuilderTests_Indexer.cs
index 39d6152b69..dbf6ef2ce9 100644
--- a/tests/Avalonia.Markup.UnitTests/Parsers/ExpressionObserverBuilderTests_Indexer.cs
+++ b/tests/Avalonia.Markup.UnitTests/Parsers/ExpressionObserverBuilderTests_Indexer.cs
@@ -10,6 +10,7 @@ using System.Collections.ObjectModel;
using System.Reactive.Linq;
using System.Text;
using System.Threading.Tasks;
+using Avalonia.Threading;
using Xunit;
namespace Avalonia.Markup.UnitTests.Parsers
@@ -159,7 +160,10 @@ namespace Avalonia.Markup.UnitTests.Parsers
{
data.Foo.Add("baz");
}
-
+
+ // Forces WeakEvent compact
+ Dispatcher.UIThread.RunJobs();
+
Assert.Equal(new[] { AvaloniaProperty.UnsetValue, "baz" }, result);
Assert.Null(((INotifyCollectionChangedDebug)data.Foo).GetCollectionChangedSubscribers());
@@ -178,6 +182,9 @@ namespace Avalonia.Markup.UnitTests.Parsers
data.Foo.RemoveAt(0);
}
+ // Forces WeakEvent compact
+ Dispatcher.UIThread.RunJobs();
+
Assert.Equal(new[] { "foo", "bar" }, result);
Assert.Null(((INotifyCollectionChangedDebug)data.Foo).GetCollectionChangedSubscribers());
@@ -196,6 +203,9 @@ namespace Avalonia.Markup.UnitTests.Parsers
data.Foo[1] = "baz";
}
+ // Forces WeakEvent compact
+ Dispatcher.UIThread.RunJobs();
+
Assert.Equal(new[] { "bar", "baz" }, result);
Assert.Null(((INotifyCollectionChangedDebug)data.Foo).GetCollectionChangedSubscribers());
@@ -252,6 +262,9 @@ namespace Avalonia.Markup.UnitTests.Parsers
data.Foo["foo"] = "bar2";
}
+ // Forces WeakEvent compact
+ Dispatcher.UIThread.RunJobs();
+
var expected = new[] { "bar", "bar2" };
Assert.Equal(expected, result);
Assert.Equal(0, data.Foo.PropertyChangedSubscriptionCount);
diff --git a/tests/Avalonia.ReactiveUI.UnitTests/RoutedViewHostTest.cs b/tests/Avalonia.ReactiveUI.UnitTests/RoutedViewHostTest.cs
index b82b1b1acc..244b5abc4e 100644
--- a/tests/Avalonia.ReactiveUI.UnitTests/RoutedViewHostTest.cs
+++ b/tests/Avalonia.ReactiveUI.UnitTests/RoutedViewHostTest.cs
@@ -29,6 +29,8 @@ namespace Avalonia.ReactiveUI.UnitTests
public class FirstRoutableView : ReactiveUserControl { }
+ public class AlternativeFirstRoutableView : ReactiveUserControl { }
+
public class SecondRoutableViewModel : ReactiveObject, IRoutableViewModel
{
public string UrlPathSegment => "second";
@@ -38,16 +40,22 @@ namespace Avalonia.ReactiveUI.UnitTests
public class SecondRoutableView : ReactiveUserControl { }
+ public class AlternativeSecondRoutableView : ReactiveUserControl { }
+
public class ScreenViewModel : ReactiveObject, IScreen
{
public RoutingState Router { get; } = new RoutingState();
}
+ public static string AlternativeViewContract => "AlternativeView";
+
public RoutedViewHostTest()
{
Locator.CurrentMutable.RegisterConstant(new AvaloniaActivationForViewFetcher(), typeof(IActivationForViewFetcher));
Locator.CurrentMutable.Register(() => new FirstRoutableView(), typeof(IViewFor));
Locator.CurrentMutable.Register(() => new SecondRoutableView(), typeof(IViewFor));
+ Locator.CurrentMutable.Register(() => new AlternativeFirstRoutableView(), typeof(IViewFor), AlternativeViewContract);
+ Locator.CurrentMutable.Register(() => new AlternativeSecondRoutableView(), typeof(IViewFor), AlternativeViewContract);
}
[Fact]
@@ -101,6 +109,71 @@ namespace Avalonia.ReactiveUI.UnitTests
Assert.Equal(defaultContent, host.Content);
}
+ [Fact]
+ public void RoutedViewHost_Should_Stay_In_Sync_With_RoutingState_And_Contract()
+ {
+ var screen = new ScreenViewModel();
+ var defaultContent = new TextBlock();
+ var host = new RoutedViewHost
+ {
+ Router = screen.Router,
+ DefaultContent = defaultContent,
+ PageTransition = null
+ };
+
+ var root = new TestRoot
+ {
+ Child = host
+ };
+
+ Assert.NotNull(host.Content);
+ Assert.IsType(host.Content);
+ Assert.Equal(defaultContent, host.Content);
+
+ var first = new FirstRoutableViewModel();
+ screen.Router.Navigate.Execute(first).Subscribe();
+
+ host.ViewContract = null;
+ Assert.NotNull(host.Content);
+ Assert.IsType(host.Content);
+ Assert.Equal(first, ((FirstRoutableView)host.Content).DataContext);
+ Assert.Equal(first, ((FirstRoutableView)host.Content).ViewModel);
+
+ host.ViewContract = AlternativeViewContract;
+ Assert.NotNull(host.Content);
+ Assert.IsType(host.Content);
+ Assert.Equal(first, ((AlternativeFirstRoutableView)host.Content).DataContext);
+ Assert.Equal(first, ((AlternativeFirstRoutableView)host.Content).ViewModel);
+
+ var second = new SecondRoutableViewModel();
+ screen.Router.Navigate.Execute(second).Subscribe();
+
+ host.ViewContract = null;
+ Assert.NotNull(host.Content);
+ Assert.IsType(host.Content);
+ Assert.Equal(second, ((SecondRoutableView)host.Content).DataContext);
+ Assert.Equal(second, ((SecondRoutableView)host.Content).ViewModel);
+
+ host.ViewContract = AlternativeViewContract;
+ Assert.NotNull(host.Content);
+ Assert.IsType(host.Content);
+ Assert.Equal(second, ((AlternativeSecondRoutableView)host.Content).DataContext);
+ Assert.Equal(second, ((AlternativeSecondRoutableView)host.Content).ViewModel);
+
+ screen.Router.NavigateBack.Execute(Unit.Default).Subscribe();
+
+ Assert.NotNull(host.Content);
+ Assert.IsType(host.Content);
+ Assert.Equal(first, ((AlternativeFirstRoutableView)host.Content).DataContext);
+ Assert.Equal(first, ((AlternativeFirstRoutableView)host.Content).ViewModel);
+
+ screen.Router.NavigateBack.Execute(Unit.Default).Subscribe();
+
+ Assert.NotNull(host.Content);
+ Assert.IsType(host.Content);
+ Assert.Equal(defaultContent, host.Content);
+ }
+
[Fact]
public void RoutedViewHost_Should_Show_Default_Content_When_Router_Is_Null()
{
diff --git a/tests/Avalonia.ReactiveUI.UnitTests/ViewModelViewHostTest.cs b/tests/Avalonia.ReactiveUI.UnitTests/ViewModelViewHostTest.cs
index 35d1cbf62d..858c476227 100644
--- a/tests/Avalonia.ReactiveUI.UnitTests/ViewModelViewHostTest.cs
+++ b/tests/Avalonia.ReactiveUI.UnitTests/ViewModelViewHostTest.cs
@@ -12,15 +12,23 @@ namespace Avalonia.ReactiveUI.UnitTests
public class FirstView : ReactiveUserControl { }
+ public class AlternativeFirstView : ReactiveUserControl { }
+
public class SecondViewModel : ReactiveObject { }
public class SecondView : ReactiveUserControl { }
+ public class AlternativeSecondView : ReactiveUserControl { }
+
+ public static string AlternativeViewContract => "AlternativeView";
+
public ViewModelViewHostTest()
{
Locator.CurrentMutable.RegisterConstant(new AvaloniaActivationForViewFetcher(), typeof(IActivationForViewFetcher));
Locator.CurrentMutable.Register(() => new FirstView(), typeof(IViewFor));
Locator.CurrentMutable.Register(() => new SecondView(), typeof(IViewFor));
+ Locator.CurrentMutable.Register(() => new AlternativeFirstView(), typeof(IViewFor), AlternativeViewContract);
+ Locator.CurrentMutable.Register(() => new AlternativeSecondView(), typeof(IViewFor), AlternativeViewContract);
}
[Fact]
@@ -67,5 +75,67 @@ namespace Avalonia.ReactiveUI.UnitTests
Assert.Equal(first, ((FirstView)host.Content).DataContext);
Assert.Equal(first, ((FirstView)host.Content).ViewModel);
}
+
+ [Fact]
+ public void ViewModelViewHost_View_Should_Stay_In_Sync_With_ViewModel_And_Contract()
+ {
+ var defaultContent = new TextBlock();
+ var host = new ViewModelViewHost
+ {
+ DefaultContent = defaultContent,
+ PageTransition = null
+ };
+
+ var root = new TestRoot
+ {
+ Child = host
+ };
+
+ Assert.NotNull(host.Content);
+ Assert.Equal(typeof(TextBlock), host.Content.GetType());
+ Assert.Equal(defaultContent, host.Content);
+
+ var first = new FirstViewModel();
+ host.ViewModel = first;
+
+ host.ViewContract = null;
+ Assert.NotNull(host.Content);
+ Assert.Equal(typeof(FirstView), host.Content.GetType());
+ Assert.Equal(first, ((FirstView)host.Content).DataContext);
+ Assert.Equal(first, ((FirstView)host.Content).ViewModel);
+
+ host.ViewContract = AlternativeViewContract;
+ Assert.NotNull(host.Content);
+ Assert.Equal(typeof(AlternativeFirstView), host.Content.GetType());
+ Assert.Equal(first, ((AlternativeFirstView)host.Content).DataContext);
+ Assert.Equal(first, ((AlternativeFirstView)host.Content).ViewModel);
+
+ var second = new SecondViewModel();
+ host.ViewModel = second;
+
+ host.ViewContract = null;
+ Assert.NotNull(host.Content);
+ Assert.Equal(typeof(SecondView), host.Content.GetType());
+ Assert.Equal(second, ((SecondView)host.Content).DataContext);
+ Assert.Equal(second, ((SecondView)host.Content).ViewModel);
+
+ host.ViewContract = AlternativeViewContract;
+ Assert.NotNull(host.Content);
+ Assert.Equal(typeof(AlternativeSecondView), host.Content.GetType());
+ Assert.Equal(second, ((AlternativeSecondView)host.Content).DataContext);
+ Assert.Equal(second, ((AlternativeSecondView)host.Content).ViewModel);
+
+ host.ViewModel = null;
+
+ host.ViewContract = null;
+ Assert.NotNull(host.Content);
+ Assert.Equal(typeof(TextBlock), host.Content.GetType());
+ Assert.Equal(defaultContent, host.Content);
+
+ host.ViewContract = AlternativeViewContract;
+ Assert.NotNull(host.Content);
+ Assert.Equal(typeof(TextBlock), host.Content.GetType());
+ Assert.Equal(defaultContent, host.Content);
+ }
}
-}
\ No newline at end of file
+}
diff --git a/tests/Avalonia.UnitTests/ImmediateDispatcher.cs b/tests/Avalonia.UnitTests/ImmediateDispatcher.cs
index fac4ee64e7..5f0d41590f 100644
--- a/tests/Avalonia.UnitTests/ImmediateDispatcher.cs
+++ b/tests/Avalonia.UnitTests/ImmediateDispatcher.cs
@@ -21,6 +21,12 @@ namespace Avalonia.UnitTests
action();
}
+ ///
+ public void Post(Action action, T arg, DispatcherPriority priority = DispatcherPriority.Normal)
+ {
+ action(arg);
+ }
+
///
public Task InvokeAsync(Action action, DispatcherPriority priority = DispatcherPriority.Normal)
{