Browse Source

Merge branch 'stable/outsystems-0.9' into foreign-embed

pull/3473/head
danwalmsley 6 years ago
committed by GitHub
parent
commit
b92f20a015
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 11
      Avalonia.sln
  2. 21
      azure-pipelines.yml
  3. 2
      build/SharedVersion.props
  4. 3
      global.json
  5. 7
      native/Avalonia.Native/src/OSX/platformthreading.mm
  6. 98
      native/Avalonia.Native/src/OSX/window.mm
  7. 166
      nukebuild/Build.cs
  8. 21
      nukebuild/BuildParameters.cs
  9. 8
      nukebuild/Shims.cs
  10. 8
      nukebuild/_build.csproj
  11. 2
      samples/BindingDemo/BindingDemo.csproj
  12. 2
      samples/ControlCatalog.NetCore/ControlCatalog.NetCore.csproj
  13. 1
      samples/ControlCatalog/Pages/DialogsPage.xaml
  14. 26
      samples/ControlCatalog/Pages/DialogsPage.xaml.cs
  15. 2
      samples/PlatformSanityChecks/PlatformSanityChecks.csproj
  16. 2
      samples/Previewer/Previewer.csproj
  17. 2
      samples/RemoteDemo/RemoteDemo.csproj
  18. 2
      samples/RenderDemo/RenderDemo.csproj
  19. 2
      samples/VirtualizationDemo/VirtualizationDemo.csproj
  20. 10
      scripts/ReplaceNugetCache.ps1
  21. 8
      scripts/ReplaceNugetCache.sh
  22. 18
      src/Avalonia.Base/Data/Converters/FuncMultiValueConverter.cs
  23. 20
      src/Avalonia.Base/EnumExtensions.cs
  24. 9
      src/Avalonia.Base/Utilities/TypeUtilities.cs
  25. 9
      src/Avalonia.Base/Utilities/WeakEventHandlerManager.cs
  26. 59
      src/Avalonia.Controls/ContextMenu.cs
  27. 5
      src/Avalonia.Controls/ItemsControl.cs
  28. 2
      src/Avalonia.Controls/Platform/DefaultMenuInteractionHandler.cs
  29. 39
      src/Avalonia.Controls/Primitives/PopupRoot.cs
  30. 71
      src/Avalonia.Controls/Window.cs
  31. 49
      src/Avalonia.Controls/WindowBase.cs
  32. 2
      src/Avalonia.Dialogs/ManagedFileChooser.xaml
  33. 12
      src/Avalonia.Dialogs/ManagedFileChooserViewModel.cs
  34. 20
      src/Avalonia.Dialogs/ManagedFileDialogExtensions.cs
  35. 7
      src/Avalonia.Dialogs/ManagedFileDialogOptions.cs
  36. 2
      src/Avalonia.Input/FocusManager.cs
  37. 1
      src/Avalonia.Input/GestureRecognizers/ScrollGestureRecognizer.cs
  38. 2
      src/Avalonia.Input/Raw/RawPointerEventArgs.cs
  39. 74
      src/Avalonia.Layout/LayoutHelper.cs
  40. 12
      src/Avalonia.Layout/LayoutManager.cs
  41. 11
      src/Avalonia.Native/WindowImplBase.cs
  42. 2
      src/Avalonia.ReactiveUI/AppBuilderExtensions.cs
  43. 3
      src/Avalonia.Themes.Default/TreeViewItem.xaml
  44. 3
      src/Avalonia.X11/NativeDialogs/Gtk.cs
  45. 8
      src/Avalonia.X11/NativeDialogs/GtkNativeFileDialogs.cs
  46. 110
      src/Avalonia.X11/X11ImmediateRendererProxy.cs
  47. 1
      src/Avalonia.X11/X11Platform.cs
  48. 117
      src/Avalonia.X11/X11Window.cs
  49. 10
      src/Avalonia.X11/XI2Manager.cs
  50. 40
      src/Markup/Avalonia.Markup.Xaml/XamlIl/CompilerExtensions/Transformers/AvaloniaXamlIlSelectorTransformer.cs
  51. 2
      src/Windows/Avalonia.Win32/WindowImpl.cs
  52. 2
      tests/Avalonia.Animation.UnitTests/Avalonia.Animation.UnitTests.csproj
  53. 2
      tests/Avalonia.Base.UnitTests/Avalonia.Base.UnitTests.csproj
  54. 178
      tests/Avalonia.Base.UnitTests/AvaloniaObjectTests_MultiBinding.cs
  55. 12
      tests/Avalonia.Base.UnitTests/Data/DefaultValueConverterTests.cs
  56. 2
      tests/Avalonia.Benchmarks/Avalonia.Benchmarks.csproj
  57. 2
      tests/Avalonia.Controls.DataGrid.UnitTests/Avalonia.Controls.DataGrid.UnitTests.csproj
  58. 2
      tests/Avalonia.Controls.UnitTests/Avalonia.Controls.UnitTests.csproj
  59. 24
      tests/Avalonia.Controls.UnitTests/ContextMenuTests.cs
  60. 155
      tests/Avalonia.Controls.UnitTests/Primitives/PopupRootTests.cs
  61. 4
      tests/Avalonia.Controls.UnitTests/Primitives/PopupTests.cs
  62. 22
      tests/Avalonia.Controls.UnitTests/Primitives/SelectingItemsControlTests.cs
  63. 219
      tests/Avalonia.Controls.UnitTests/WindowTests.cs
  64. 2
      tests/Avalonia.DesignerSupport.TestApp/Avalonia.DesignerSupport.TestApp.csproj
  65. 2
      tests/Avalonia.DesignerSupport.Tests/Avalonia.DesignerSupport.Tests.csproj
  66. 4
      tests/Avalonia.DesignerSupport.Tests/DesignerSupportTests.cs
  67. 2
      tests/Avalonia.Direct2D1.RenderTests/Avalonia.Direct2D1.RenderTests.csproj
  68. 2
      tests/Avalonia.Direct2D1.UnitTests/Avalonia.Direct2D1.UnitTests.csproj
  69. 2
      tests/Avalonia.Input.UnitTests/Avalonia.Input.UnitTests.csproj
  70. 3
      tests/Avalonia.Interactivity.UnitTests/Avalonia.Interactivity.UnitTests.csproj
  71. 2
      tests/Avalonia.Layout.UnitTests/Avalonia.Layout.UnitTests.csproj
  72. 108
      tests/Avalonia.Layout.UnitTests/FullLayoutTests.cs
  73. 33
      tests/Avalonia.Layout.UnitTests/LayoutManagerTests.cs
  74. 130
      tests/Avalonia.LeakTests/ControlTests.cs
  75. 2
      tests/Avalonia.Markup.UnitTests/Avalonia.Markup.UnitTests.csproj
  76. 2
      tests/Avalonia.Markup.Xaml.UnitTests/Avalonia.Markup.Xaml.UnitTests.csproj
  77. 62
      tests/Avalonia.Markup.Xaml.UnitTests/Xaml/StyleTests.cs
  78. 2
      tests/Avalonia.ReactiveUI.UnitTests/Avalonia.ReactiveUI.UnitTests.csproj
  79. 2
      tests/Avalonia.Skia.RenderTests/Avalonia.Skia.RenderTests.csproj
  80. 2
      tests/Avalonia.Skia.UnitTests/Avalonia.Skia.UnitTests.csproj
  81. 2
      tests/Avalonia.Styling.UnitTests/Avalonia.Styling.UnitTests.csproj
  82. 115
      tests/Avalonia.UnitTests/MockWindowingPlatform.cs
  83. 2
      tests/Avalonia.Visuals.UnitTests/Avalonia.Visuals.UnitTests.csproj

11
Avalonia.sln

@ -201,19 +201,20 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Avalonia.Controls.DataGrid"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Avalonia.Dialogs", "src\Avalonia.Dialogs\Avalonia.Dialogs.csproj", "{4D55985A-1EE2-4F25-AD39-6EA8BC04F8FB}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Avalonia.FreeDesktop", "src\Avalonia.FreeDesktop\Avalonia.FreeDesktop.csproj", "{4D36CEC8-53F2-40A5-9A37-79AAE356E2DA}"
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Avalonia.FreeDesktop", "src\Avalonia.FreeDesktop\Avalonia.FreeDesktop.csproj", "{4D36CEC8-53F2-40A5-9A37-79AAE356E2DA}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Avalonia.Controls.DataGrid.UnitTests", "tests\Avalonia.Controls.DataGrid.UnitTests\Avalonia.Controls.DataGrid.UnitTests.csproj", "{351337F5-D66F-461B-A957-4EF60BDB4BA6}"
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "NativeEmbedSample", "samples\interop\NativeEmbedSample\NativeEmbedSample.csproj", "{3C84E04B-36CF-4D0D-B965-C26DD649D1F3}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Avalonia.Controls.DataGrid.UnitTests", "tests\Avalonia.Controls.DataGrid.UnitTests\Avalonia.Controls.DataGrid.UnitTests.csproj", "{351337F5-D66F-461B-A957-4EF60BDB4BA6}"
EndProject
Global
GlobalSection(SharedMSBuildProjectFiles) = preSolution
src\Shared\RenderHelpers\RenderHelpers.projitems*{3c4c0cb4-0c0f-4450-a37b-148c84ff905f}*SharedItemsImports = 13
src\Shared\RenderHelpers\RenderHelpers.projitems*{3e908f67-5543-4879-a1dc-08eace79b3cd}*SharedItemsImports = 4
src\Shared\RenderHelpers\RenderHelpers.projitems*{3e908f67-5543-4879-a1dc-08eace79b3cd}*SharedItemsImports = 5
src\Shared\PlatformSupport\PlatformSupport.projitems*{4488ad85-1495-4809-9aa4-ddfe0a48527e}*SharedItemsImports = 4
src\Shared\PlatformSupport\PlatformSupport.projitems*{7b92af71-6287-4693-9dcb-bd5b6e927e23}*SharedItemsImports = 4
src\Shared\RenderHelpers\RenderHelpers.projitems*{7d2d3083-71dd-4cc9-8907-39a0d86fb322}*SharedItemsImports = 4
tests\Avalonia.RenderTests\Avalonia.RenderTests.projitems*{dabfd304-d6a4-4752-8123-c2ccf7ac7831}*SharedItemsImports = 4
src\Shared\RenderHelpers\RenderHelpers.projitems*{7d2d3083-71dd-4cc9-8907-39a0d86fb322}*SharedItemsImports = 5
src\Shared\PlatformSupport\PlatformSupport.projitems*{88060192-33d5-4932-b0f9-8bd2763e857d}*SharedItemsImports = 5
src\Shared\PlatformSupport\PlatformSupport.projitems*{e4d9629c-f168-4224-3f51-a5e482ffbc42}*SharedItemsImports = 13
EndGlobalSection
GlobalSection(SolutionConfigurationPlatforms) = preSolution

21
azure-pipelines.yml

@ -14,7 +14,7 @@ jobs:
displayName: 'Install Nuke'
inputs:
script: |
dotnet tool install --global Nuke.GlobalTool --version 0.12.3
dotnet tool install --global Nuke.GlobalTool --version 0.24.0
- task: CmdLine@2
displayName: 'Run Nuke'
inputs:
@ -34,9 +34,17 @@ jobs:
pool:
vmImage: 'macOS-10.14'
steps:
- task: DotNetCoreInstaller@0
- task: UseDotNet@2
displayName: 'Use .NET Core SDK 3.1.101'
inputs:
version: '2.1.403'
packageType: sdk
version: 3.1.101
- task: UseDotNet@2
displayName: 'Use .NET Core Runtime 3.1.1'
inputs:
packageType: runtime
version: 3.1.1
- task: CmdLine@2
displayName: 'Install Mono 5.18'
@ -60,13 +68,13 @@ jobs:
inputs:
script: |
brew update
brew install castxml
brew install https://raw.githubusercontent.com/Homebrew/homebrew-core/8a004a91a7fcd3f6620d5b01b6541ff0a640ffba/Formula/castxml.rb
- task: CmdLine@2
displayName: 'Install Nuke'
inputs:
script: |
dotnet tool install --global Nuke.GlobalTool --version 0.12.3
dotnet tool install --global Nuke.GlobalTool --version 0.24.0
- task: CmdLine@2
displayName: 'Run Nuke'
@ -108,7 +116,7 @@ jobs:
displayName: 'Install Nuke'
inputs:
script: |
dotnet tool install --global Nuke.GlobalTool --version 0.12.3
dotnet tool install --global Nuke.GlobalTool --version 0.24.0
- task: CmdLine@2
displayName: 'Run Nuke'
@ -134,4 +142,3 @@ jobs:
pathToPublish: '$(Build.SourcesDirectory)/artifacts/zip'
artifactName: 'Samples'
condition: succeeded()

2
build/SharedVersion.props

@ -2,7 +2,7 @@
xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
<Product>Avalonia</Product>
<Version>0.9.3</Version>
<Version>0.9.10-preview</Version>
<Copyright>Copyright 2020 &#169; The AvaloniaUI Project</Copyright>
<PackageProjectUrl>https://avaloniaui.net</PackageProjectUrl>
<RepositoryUrl>https://github.com/AvaloniaUI/Avalonia/</RepositoryUrl>

3
global.json

@ -1,4 +1,7 @@
{
"sdk": {
"version": "3.1.101"
},
"msbuild-sdks": {
"Microsoft.Build.Traversal": "1.0.43",
"MSBuild.Sdk.Extras": "2.0.46",

7
native/Avalonia.Native/src/OSX/platformthreading.mm

@ -157,11 +157,14 @@ NSArray<NSString*>* _modes;
-(void) perform
{
ComPtr<IAvnSignaledCallback> cb;
@synchronized (self) {
_signaled = false;
if(_parent != NULL && _parent->SignaledCallback != NULL)
_parent->SignaledCallback->Signaled(0, false);
if(_parent != NULL)
cb = _parent->SignaledCallback;
}
if(cb != nullptr)
cb->Signaled(0, false);
}
-(void) setParent:(PlatformThreadingInterface *)parent

98
native/Avalonia.Native/src/OSX/window.mm

@ -31,9 +31,11 @@ public:
AvnPoint lastPositionSet;
NSString* _lastTitle;
IAvnAppMenu* _mainMenu;
bool _shown;
WindowBaseImpl(IAvnWindowBaseEvents* events, IAvnGlContext* gl)
{
_shown = false;
_mainMenu = nullptr;
BaseEvents = events;
_glContext = gl;
@ -116,6 +118,8 @@ public:
[Window setTitle:_lastTitle];
[Window setTitleVisibility:NSWindowTitleVisible];
_shown = true;
return S_OK;
}
@ -409,6 +413,7 @@ protected:
[Window setStyleMask:GetStyle()];
}
public:
virtual void OnResized ()
{
@ -435,6 +440,7 @@ private:
ComPtr<IAvnWindowEvents> WindowEvents;
WindowImpl(IAvnWindowEvents* events, IAvnGlContext* gl) : WindowBaseImpl(events, gl)
{
_lastWindowState = Normal;
WindowEvents = events;
[Window setCanBecomeKeyAndMain];
[Window disableCursorRects];
@ -448,7 +454,7 @@ private:
[[Window parentWindow] removeChildWindow:Window];
WindowBaseImpl::Show();
return SetWindowState(Normal);
return SetWindowState(_lastWindowState);
}
}
@ -599,57 +605,63 @@ private:
{
_lastWindowState = state;
switch (state) {
case Maximized:
lastPositionSet.X = 0;
lastPositionSet.Y = 0;
if([Window isMiniaturized])
{
[Window deminiaturize:Window];
}
if(!IsZoomed())
{
DoZoom();
}
break;
case Minimized:
[Window miniaturize:Window];
break;
default:
if([Window isMiniaturized])
{
[Window deminiaturize:Window];
}
if(IsZoomed())
{
DoZoom();
}
break;
if(_shown)
{
switch (state) {
case Maximized:
lastPositionSet.X = 0;
lastPositionSet.Y = 0;
if([Window isMiniaturized])
{
[Window deminiaturize:Window];
}
if(!IsZoomed())
{
DoZoom();
}
break;
case Minimized:
[Window miniaturize:Window];
break;
default:
if([Window isMiniaturized])
{
[Window deminiaturize:Window];
}
if(IsZoomed())
{
DoZoom();
}
break;
}
}
return S_OK;
}
}
protected:
virtual void OnResized () override
{
auto windowState = [Window isMiniaturized] ? Minimized
: (IsZoomed() ? Maximized : Normal);
if (windowState != _lastWindowState)
if(_shown)
{
_lastWindowState = windowState;
auto windowState = [Window isMiniaturized] ? Minimized
: (IsZoomed() ? Maximized : Normal);
WindowEvents->WindowStateChanged(windowState);
if (windowState != _lastWindowState)
{
_lastWindowState = windowState;
WindowEvents->WindowStateChanged(windowState);
}
}
}
protected:
virtual NSWindowStyleMask GetStyle() override
{
unsigned long s = NSWindowStyleMaskBorderless;
@ -1312,6 +1324,11 @@ NSArray* AllLoopModes = [NSArray arrayWithObjects: NSDefaultRunLoopMode, NSEvent
}
}
- (void)windowDidResize:(NSNotification *)notification
{
_parent->OnResized();
}
- (BOOL)windowShouldZoom:(NSWindow *)window toFrame:(NSRect)newFrame
{
return true;
@ -1382,6 +1399,7 @@ protected:
[Window setContentSize:NSSize{x, y}];
[Window setFrameTopLeftPoint:ToNSPoint(ConvertPointY(lastPositionSet))];
return S_OK;
}
}

166
nukebuild/Build.cs

@ -13,6 +13,7 @@ using Nuke.Common.Tooling;
using Nuke.Common.Tools.DotNet;
using Nuke.Common.Tools.MSBuild;
using Nuke.Common.Utilities;
using Nuke.Common.Utilities.Collections;
using static Nuke.Common.EnvironmentInfo;
using static Nuke.Common.IO.FileSystemTasks;
using static Nuke.Common.IO.PathConstruction;
@ -26,11 +27,13 @@ using static Nuke.Common.Tools.VSWhere.VSWhereTasks;
running and debugging a particular target (optionally without deps) would be way easier
ReSharper/Rider - https://plugins.jetbrains.com/plugin/10803-nuke-support
VSCode - https://marketplace.visualstudio.com/items?itemName=nuke.support
*/
partial class Build : NukeBuild
{
[Solution("Avalonia.sln")] readonly Solution Solution;
static Lazy<string> MsBuildExe = new Lazy<string>(() =>
{
if (!RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
@ -54,7 +57,7 @@ partial class Build : NukeBuild
protected override void OnBuildInitialized()
{
Parameters = new BuildParameters(this);
Information("Building version {0} of Avalonia ({1}) using version {2} of Nuke.",
Information("Building version {0} of Avalonia ({1}) using version {2} of Nuke.",
Parameters.Version,
Parameters.Configuration,
typeof(NukeBuild).Assembly.GetName().Version.ToString());
@ -93,29 +96,24 @@ partial class Build : NukeBuild
string projectFile,
Configure<MSBuildSettings> configurator = null)
{
return MSBuild(projectFile, c =>
{
return MSBuild(c => c
.SetProjectFile(projectFile)
// This is required for VS2019 image on Azure Pipelines
if (Parameters.IsRunningOnWindows && Parameters.IsRunningOnAzure)
{
var javaSdk = Environment.GetEnvironmentVariable("JAVA_HOME_8_X64");
if (javaSdk != null)
c = c.AddProperty("JavaSdkDirectory", javaSdk);
}
c = c.AddProperty("PackageVersion", Parameters.Version)
.AddProperty("iOSRoslynPathHackRequired", "true")
.SetToolPath(MsBuildExe.Value)
.SetConfiguration(Parameters.Configuration)
.SetVerbosity(MSBuildVerbosity.Minimal);
c = configurator?.Invoke(c) ?? c;
return c;
});
.When(Parameters.IsRunningOnWindows &&
Parameters.IsRunningOnAzure, c => c
.AddProperty("JavaSdkDirectory", GetVariable<string>("JAVA_HOME_8_X64")))
.AddProperty("PackageVersion", Parameters.Version)
.AddProperty("iOSRoslynPathHackRequired", true)
.SetToolPath(MsBuildExe.Value)
.SetConfiguration(Parameters.Configuration)
.SetVerbosity(MSBuildVerbosity.Minimal)
.Apply(configurator));
}
Target Clean => _ => _.Executes(() =>
{
DeleteDirectories(Parameters.BuildDirs);
EnsureCleanDirectories(Parameters.BuildDirs);
Parameters.BuildDirs.ForEach(DeleteDirectory);
Parameters.BuildDirs.ForEach(EnsureCleanDirectory);
EnsureCleanDirectory(Parameters.ArtifactsDir);
EnsureCleanDirectory(Parameters.NugetIntermediateRoot);
EnsureCleanDirectory(Parameters.NugetRoot);
@ -134,97 +132,84 @@ partial class Build : NukeBuild
);
else
DotNetBuild(Parameters.MSBuildSolution, c => c
DotNetBuild(c => c
.SetProjectFile(Parameters.MSBuildSolution)
.AddProperty("PackageVersion", Parameters.Version)
.SetConfiguration(Parameters.Configuration)
);
});
void RunCoreTest(string project)
void RunCoreTest(string projectName)
{
if(!project.EndsWith(".csproj"))
project = System.IO.Path.Combine(project, System.IO.Path.GetFileName(project)+".csproj");
Information("Running tests from " + project);
XDocument xdoc;
using (var s = File.OpenRead(project))
xdoc = XDocument.Load(s);
List<string> frameworks = null;
var targets = xdoc.Root.Descendants("TargetFrameworks").FirstOrDefault();
if (targets != null)
frameworks = targets.Value.Split(';').Where(f => !string.IsNullOrWhiteSpace(f)).ToList();
else
frameworks = new List<string> {xdoc.Root.Descendants("TargetFramework").First().Value};
foreach(var fw in frameworks)
Information($"Running tests from {projectName}");
var project = Solution.GetProject(projectName).NotNull("project != null");
foreach (var fw in project.GetTargetFrameworks())
{
if (fw.StartsWith("net4")
&& RuntimeInformation.IsOSPlatform(OSPlatform.Linux)
&& RuntimeInformation.IsOSPlatform(OSPlatform.Linux)
&& Environment.GetEnvironmentVariable("FORCE_LINUX_TESTS") != "1")
{
Information($"Skipping {fw} tests on Linux - https://github.com/mono/mono/issues/13969");
Information($"Skipping {projectName} ({fw}) tests on Linux - https://github.com/mono/mono/issues/13969");
continue;
}
Information("Running for " + fw);
DotNetTest(c =>
{
c = c
.SetProjectFile(project)
.SetConfiguration(Parameters.Configuration)
.SetFramework(fw)
.EnableNoBuild()
.EnableNoRestore();
// NOTE: I can see that we could maybe add another extension method "Switch" or "If" to make this more convenient
if (Parameters.PublishTestResults)
c = c.SetLogger("trx").SetResultsDirectory(Parameters.TestResultsRoot);
return c;
});
Information($"Running for {projectName} ({fw}) ...");
DotNetTest(c => c
.SetProjectFile(project)
.SetConfiguration(Parameters.Configuration)
.SetFramework(fw)
.EnableNoBuild()
.EnableNoRestore()
.When(Parameters.PublishTestResults, c => c
.SetLogger("trx")
.SetResultsDirectory(Parameters.TestResultsRoot)));
}
}
Target RunCoreLibsTests => _ => _
.OnlyWhen(() => !Parameters.SkipTests)
.OnlyWhenStatic(() => !Parameters.SkipTests)
.DependsOn(Compile)
.Executes(() =>
{
RunCoreTest("./tests/Avalonia.Animation.UnitTests");
RunCoreTest("./tests/Avalonia.Base.UnitTests");
RunCoreTest("./tests/Avalonia.Controls.UnitTests");
RunCoreTest("./tests/Avalonia.Controls.DataGrid.UnitTests");
RunCoreTest("./tests/Avalonia.Input.UnitTests");
RunCoreTest("./tests/Avalonia.Interactivity.UnitTests");
RunCoreTest("./tests/Avalonia.Layout.UnitTests");
RunCoreTest("./tests/Avalonia.Markup.UnitTests");
RunCoreTest("./tests/Avalonia.Markup.Xaml.UnitTests");
RunCoreTest("./tests/Avalonia.Styling.UnitTests");
RunCoreTest("./tests/Avalonia.Visuals.UnitTests");
RunCoreTest("./tests/Avalonia.Skia.UnitTests");
RunCoreTest("./tests/Avalonia.ReactiveUI.UnitTests");
RunCoreTest("Avalonia.Animation.UnitTests");
RunCoreTest("Avalonia.Base.UnitTests");
RunCoreTest("Avalonia.Controls.UnitTests");
RunCoreTest("Avalonia.Controls.DataGrid.UnitTests");
RunCoreTest("Avalonia.Input.UnitTests");
RunCoreTest("Avalonia.Interactivity.UnitTests");
RunCoreTest("Avalonia.Layout.UnitTests");
RunCoreTest("Avalonia.Markup.UnitTests");
RunCoreTest("Avalonia.Markup.Xaml.UnitTests");
RunCoreTest("Avalonia.Styling.UnitTests");
RunCoreTest("Avalonia.Visuals.UnitTests");
RunCoreTest("Avalonia.Skia.UnitTests");
RunCoreTest("Avalonia.ReactiveUI.UnitTests");
});
Target RunRenderTests => _ => _
.OnlyWhen(() => !Parameters.SkipTests)
.OnlyWhenStatic(() => !Parameters.SkipTests)
.DependsOn(Compile)
.Executes(() =>
{
RunCoreTest("./tests/Avalonia.Skia.RenderTests/Avalonia.Skia.RenderTests.csproj");
RunCoreTest("Avalonia.Skia.RenderTests");
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
RunCoreTest("./tests/Avalonia.Direct2D1.RenderTests/Avalonia.Direct2D1.RenderTests.csproj");
RunCoreTest("Avalonia.Direct2D1.RenderTests");
});
Target RunDesignerTests => _ => _
.OnlyWhen(() => !Parameters.SkipTests && Parameters.IsRunningOnWindows)
.OnlyWhenStatic(() => !Parameters.SkipTests && Parameters.IsRunningOnWindows)
.DependsOn(Compile)
.Executes(() =>
{
RunCoreTest("./tests/Avalonia.DesignerSupport.Tests");
RunCoreTest("Avalonia.DesignerSupport.Tests");
});
[PackageExecutable("JetBrains.dotMemoryUnit", "dotMemoryUnit.exe")] readonly Tool DotMemoryUnit;
Target RunLeakTests => _ => _
.OnlyWhen(() => !Parameters.SkipTests && Parameters.IsRunningOnWindows)
.OnlyWhenStatic(() => !Parameters.SkipTests && Parameters.IsRunningOnWindows)
.DependsOn(Compile)
.Executes(() =>
{
@ -235,7 +220,7 @@ partial class Build : NukeBuild
});
Target ZipFiles => _ => _
.After(CreateNugetPackages, Compile, RunCoreLibsTests, Package)
.After(CreateNugetPackages, Compile, RunCoreLibsTests, Package)
.Executes(() =>
{
var data = Parameters;
@ -259,9 +244,10 @@ partial class Build : NukeBuild
MsBuildCommon(Parameters.MSBuildSolution, c => c
.AddTargets("Pack"));
else
DotNetPack(Parameters.MSBuildSolution, c =>
c.SetConfiguration(Parameters.Configuration)
.AddProperty("PackageVersion", Parameters.Version));
DotNetPack(c => c
.SetProject(Parameters.MSBuildSolution)
.SetConfiguration(Parameters.Configuration)
.AddProperty("PackageVersion", Parameters.Version));
});
Target CreateNugetPackages => _ => _
@ -274,32 +260,40 @@ partial class Build : NukeBuild
new NumergeNukeLogger()))
throw new Exception("Package merge failed");
});
Target RunTests => _ => _
.DependsOn(RunCoreLibsTests)
.DependsOn(RunRenderTests)
.DependsOn(RunDesignerTests)
.DependsOn(RunLeakTests);
Target Package => _ => _
.DependsOn(RunTests)
.DependsOn(CreateNugetPackages);
Target CiAzureLinux => _ => _
.DependsOn(RunTests);
Target CiAzureOSX => _ => _
.DependsOn(Package)
.DependsOn(ZipFiles);
Target CiAzureWindows => _ => _
.DependsOn(Package)
.DependsOn(ZipFiles);
public static int Main() =>
RuntimeInformation.IsOSPlatform(OSPlatform.Windows)
? Execute<Build>(x => x.Package)
: Execute<Build>(x => x.RunTests);
}
public static class ToolSettingsExtensions
{
public static T Apply<T>(this T settings, Configure<T> configurator)
{
return configurator != null ? configurator(settings) : settings;
}
}

21
nukebuild/BuildParameters.cs

@ -4,24 +4,21 @@ using System.Linq;
using System.Runtime.InteropServices;
using System.Xml.Linq;
using Nuke.Common;
using Nuke.Common.BuildServers;
using Nuke.Common.Execution;
using Nuke.Common.CI.AzurePipelines;
using Nuke.Common.IO;
using static Nuke.Common.IO.FileSystemTasks;
using static Nuke.Common.IO.PathConstruction;
using static Nuke.Common.Tools.MSBuild.MSBuildTasks;
public partial class Build
{
[Parameter("configuration")]
public string Configuration { get; set; }
[Parameter("skip-tests")]
public bool SkipTests { get; set; }
[Parameter("force-nuget-version")]
public string ForceNugetVersion { get; set; }
public class BuildParameters
{
public string Configuration { get; }
@ -79,15 +76,15 @@ public partial class Build
IsRunningOnUnix = Environment.OSVersion.Platform == PlatformID.Unix ||
Environment.OSVersion.Platform == PlatformID.MacOSX;
IsRunningOnWindows = RuntimeInformation.IsOSPlatform(OSPlatform.Windows);
IsRunningOnAzure = Host == HostType.TeamServices ||
IsRunningOnAzure = Host == HostType.AzurePipelines ||
Environment.GetEnvironmentVariable("LOGNAME") == "vsts";
if (IsRunningOnAzure)
{
RepositoryName = TeamServices.Instance.RepositoryUri;
RepositoryBranch = TeamServices.Instance.SourceBranch;
IsPullRequest = TeamServices.Instance.PullRequestId.HasValue;
IsMainRepo = StringComparer.OrdinalIgnoreCase.Equals(MainRepo, TeamServices.Instance.RepositoryUri);
RepositoryName = AzurePipelines.Instance.RepositoryUri;
RepositoryBranch = AzurePipelines.Instance.SourceBranch;
IsPullRequest = AzurePipelines.Instance.PullRequestId.HasValue;
IsMainRepo = StringComparer.OrdinalIgnoreCase.Equals(MainRepo, AzurePipelines.Instance.RepositoryUri);
}
IsMainRepo =
StringComparer.OrdinalIgnoreCase.Equals(MainRepo,

8
nukebuild/Shims.cs

@ -19,9 +19,9 @@ public partial class Build
Logger.Info(info, args);
}
private void Zip(PathConstruction.AbsolutePath target, params string[] paths) => Zip(target, paths.AsEnumerable());
private void Zip(AbsolutePath target, params string[] paths) => Zip(target, paths.AsEnumerable());
private void Zip(PathConstruction.AbsolutePath target, IEnumerable<string> paths)
private void Zip(AbsolutePath target, IEnumerable<string> paths)
{
var targetPath = target.ToString();
bool finished = false, atLeastOneFileAdded = false;
@ -38,7 +38,7 @@ public partial class Build
fileStream.CopyTo(entryStream);
atLeastOneFileAdded = true;
}
foreach (var path in paths)
{
if (Directory.Exists(path))
@ -64,7 +64,7 @@ public partial class Build
finished = true;
}
finally
finally
{
try
{

8
nukebuild/_build.csproj

@ -2,7 +2,7 @@
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>netcoreapp2.0</TargetFramework>
<TargetFramework>netcoreapp3.1</TargetFramework>
<AppendTargetFrameworkToOutputPath>false</AppendTargetFrameworkToOutputPath>
<RootNamespace></RootNamespace>
<IsPackable>False</IsPackable>
@ -10,7 +10,7 @@
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Nuke.Common" Version="0.12.3" />
<PackageReference Include="Nuke.Common" Version="0.24.0" />
<PackageReference Include="xunit.runner.console" Version="2.3.1" />
<PackageReference Include="JetBrains.dotMemoryUnit" Version="3.0.20171219.105559" />
<PackageReference Include="vswhere" Version="2.6.7" Condition=" '$(OS)' == 'Windows_NT' " />
@ -20,11 +20,11 @@
<NukeMetadata Include="**\*.json" Exclude="bin\**;obj\**" />
<NukeExternalFiles Include="**\*.*.ext" Exclude="bin\**;obj\**" />
<None Remove="*.csproj.DotSettings;*.ref.*.txt" />
<!-- Common build related files -->
<None Include="..\build.ps1" />
<None Include="..\build.sh" />
<None Include="..\.nuke" />
<None Include="..\.nuke" />
<None Include="..\global.json" Condition="Exists('..\global.json')" />
<None Include="..\nuget.config" Condition="Exists('..\nuget.config')" />
<None Include="..\Jenkinsfile" Condition="Exists('..\Jenkinsfile')" />

2
samples/BindingDemo/BindingDemo.csproj

@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFrameworks>netcoreapp2.0;net461</TargetFrameworks>
<TargetFramework>netcoreapp3.1</TargetFramework>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\..\src\Linux\Avalonia.LinuxFramebuffer\Avalonia.LinuxFramebuffer.csproj" />

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

@ -2,7 +2,7 @@
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>netcoreapp2.0</TargetFramework>
<TargetFramework>netcoreapp3.1</TargetFramework>
<TargetLatestRuntimePatch>true</TargetLatestRuntimePatch>
</PropertyGroup>

1
samples/ControlCatalog/Pages/DialogsPage.xaml

@ -6,6 +6,7 @@
<Button Name="OpenFile">Open File</Button>
<Button Name="SaveFile">Save File</Button>
<Button Name="SelectFolder">Select Folder</Button>
<Button Name="OpenBoth">Select Both</Button>
<Button Name="DecoratedWindow">Decorated window</Button>
<Button Name="DecoratedWindowDialog">Decorated window (dialog)</Button>
<Button Name="Dialog">Dialog</Button>

26
samples/ControlCatalog/Pages/DialogsPage.xaml.cs

@ -1,5 +1,9 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using Avalonia.Controls;
using Avalonia.Dialogs;
using Avalonia.Markup.Xaml;
#pragma warning disable 4014
@ -34,7 +38,9 @@ namespace ControlCatalog.Pages
new OpenFileDialog()
{
Title = "Open file",
Filters = GetFilters()
Filters = GetFilters(),
// Almost guaranteed to exist
InitialFileName = Assembly.GetEntryAssembly()?.GetModules().FirstOrDefault()?.FullyQualifiedName
}.ShowAsync(GetWindow());
};
this.FindControl<Button>("SaveFile").Click += delegate
@ -42,16 +48,30 @@ namespace ControlCatalog.Pages
new SaveFileDialog()
{
Title = "Save file",
Filters = GetFilters()
Filters = GetFilters(),
InitialFileName = "test.txt"
}.ShowAsync(GetWindow());
};
this.FindControl<Button>("SelectFolder").Click += delegate
{
new OpenFolderDialog()
{
Title = "Select folder"
Title = "Select folder",
}.ShowAsync(GetWindow());
};
this.FindControl<Button>("OpenBoth").Click += async delegate
{
var res = await new OpenFileDialog()
{
Title = "Select both",
AllowMultiple = true
}.ShowManagedAsync(GetWindow(), new ManagedFileDialogOptions
{
AllowDirectorySelection = true
});
if (res != null)
Console.WriteLine("Selected: \n" + string.Join("\n", res));
};
this.FindControl<Button>("DecoratedWindow").Click += delegate
{
new DecoratedWindow().Show();

2
samples/PlatformSanityChecks/PlatformSanityChecks.csproj

@ -2,7 +2,7 @@
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>netcoreapp2.0</TargetFramework>
<TargetFramework>netcoreapp3.1</TargetFramework>
</PropertyGroup>
<ItemGroup>

2
samples/Previewer/Previewer.csproj

@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>netcoreapp2.0</TargetFramework>
<TargetFramework>netcoreapp3.1</TargetFramework>
</PropertyGroup>
<ItemGroup>
<Compile Update="**\*.xaml.cs">

2
samples/RemoteDemo/RemoteDemo.csproj

@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>netcoreapp2.0</TargetFramework>
<TargetFramework>netcoreapp3.1</TargetFramework>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\ControlCatalog\ControlCatalog.csproj" />

2
samples/RenderDemo/RenderDemo.csproj

@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFrameworks>netcoreapp2.0;net461</TargetFrameworks>
<TargetFramework>netcoreapp3.1</TargetFramework>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\..\src\Linux\Avalonia.LinuxFramebuffer\Avalonia.LinuxFramebuffer.csproj" />

2
samples/VirtualizationDemo/VirtualizationDemo.csproj

@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFrameworks>netcoreapp2.0;net461</TargetFrameworks>
<TargetFramework>netcoreapp3.1</TargetFramework>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\..\src\Linux\Avalonia.LinuxFramebuffer\Avalonia.LinuxFramebuffer.csproj" />

10
scripts/ReplaceNugetCache.ps1

@ -1,5 +1,5 @@
copy ..\samples\ControlCatalog.NetCore\bin\Debug\netcoreapp2.0\Avalonia**.dll ~\.nuget\packages\avalonia\$args\lib\netcoreapp2.0\
copy ..\samples\ControlCatalog.NetCore\bin\Debug\netcoreapp2.0\Avalonia**.dll ~\.nuget\packages\avalonia\$args\lib\netstandard2.0\
copy ..\samples\ControlCatalog.NetCore\bin\Debug\netcoreapp2.0\Avalonia.Win32.dll ~\.nuget\packages\avalonia.win32\$args\lib\netstandard2.0\
copy ..\samples\ControlCatalog.NetCore\bin\Debug\netcoreapp2.0\Avalonia.Skia.dll ~\.nuget\packages\avalonia.skia\$args\lib\netstandard2.0\
copy ..\samples\ControlCatalog.NetCore\bin\Debug\netcoreapp2.0\Avalonia.Skia.dll ~\.nuget\packages\avalonia.direct2d1\$args\lib\netstandard2.0\
copy ..\samples\ControlCatalog.NetCore\bin\Debug\netcoreapp3.1\Avalonia**.dll ~\.nuget\packages\avalonia\$args\lib\netcoreapp3.1\
copy ..\samples\ControlCatalog.NetCore\bin\Debug\netcoreapp3.1\Avalonia**.dll ~\.nuget\packages\avalonia\$args\lib\netstandard2.0\
copy ..\samples\ControlCatalog.NetCore\bin\Debug\netcoreapp3.1\Avalonia.Win32.dll ~\.nuget\packages\avalonia.win32\$args\lib\netstandard2.0\
copy ..\samples\ControlCatalog.NetCore\bin\Debug\netcoreapp3.1\Avalonia.Skia.dll ~\.nuget\packages\avalonia.skia\$args\lib\netstandard2.0\
copy ..\samples\ControlCatalog.NetCore\bin\Debug\netcoreapp3.1\Avalonia.Skia.dll ~\.nuget\packages\avalonia.direct2d1\$args\lib\netstandard2.0\

8
scripts/ReplaceNugetCache.sh

@ -1,8 +1,8 @@
#!/usr/bin/env bash
cp ../samples/ControlCatalog.NetCore/bin/Debug/netcoreapp2.0/Avalonia**.dll ~/.nuget/packages/avalonia/$1/lib/netcoreapp2.0/
cp ../samples/ControlCatalog.NetCore/bin/Debug/netcoreapp2.0/Avalonia**.dll ~/.nuget/packages/avalonia/$1/lib/netstandard2.0/
cp ../samples/ControlCatalog.NetCore/bin/Debug/netcoreapp2.0/Avalonia**.dll ~/.nuget/packages/avalonia.skia/$1/lib/netstandard2.0/
cp ../samples/ControlCatalog.NetCore/bin/Debug/netcoreapp2.0/Avalonia**.dll ~/.nuget/packages/avalonia.native/$1/lib/netstandard2.0/
cp ../samples/ControlCatalog.NetCore/bin/Debug/netcoreapp3.1/Avalonia**.dll ~/.nuget/packages/avalonia/$1/lib/netcoreapp3.1/
cp ../samples/ControlCatalog.NetCore/bin/Debug/netcoreapp3.1/Avalonia**.dll ~/.nuget/packages/avalonia/$1/lib/netstandard2.0/
cp ../samples/ControlCatalog.NetCore/bin/Debug/netcoreapp3.1/Avalonia**.dll ~/.nuget/packages/avalonia.skia/$1/lib/netstandard2.0/
cp ../samples/ControlCatalog.NetCore/bin/Debug/netcoreapp3.1/Avalonia**.dll ~/.nuget/packages/avalonia.native/$1/lib/netstandard2.0/

18
src/Avalonia.Base/Data/Converters/FuncMultiValueConverter.cs

@ -30,7 +30,23 @@ namespace Avalonia.Data.Converters
/// <inheritdoc/>
public object Convert(IList<object> values, Type targetType, object parameter, CultureInfo culture)
{
var converted = values.OfType<TIn>().ToList();
//standard OfType skip null values, even they are valid for the Type
static IEnumerable<TIn> OfTypeWithDefaultSupport(IList<object> list)
{
foreach (object obj in list)
{
if (obj is TIn result)
{
yield return result;
}
else if (Equals(obj, default(TIn)))
{
yield return default(TIn);
}
}
}
var converted = OfTypeWithDefaultSupport(values).ToList();
if (converted.Count == values.Count)
{

20
src/Avalonia.Base/EnumExtensions.cs

@ -0,0 +1,20 @@
using System;
using System.Runtime.CompilerServices;
namespace Avalonia
{
/// <summary>
/// Provides extension methods for enums.
/// </summary>
public static class EnumExtensions
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static unsafe bool HasFlagCustom<T>(this T value, T flag) where T : unmanaged, Enum
{
var intValue = *(int*)&value;
var intFlag = *(int*)&flag;
return (intValue & intFlag) == intFlag;
}
}
}

9
src/Avalonia.Base/Utilities/TypeUtilities.cs

@ -2,6 +2,7 @@
// Licensed under the MIT license. See licence.md file in the project root for full license information.
using System;
using System.ComponentModel;
using System.Globalization;
using System.Linq;
using System.Reflection;
@ -188,6 +189,14 @@ namespace Avalonia.Utilities
}
}
var typeConverter = TypeDescriptor.GetConverter(to);
if (typeConverter.CanConvertFrom(from) == true)
{
result = typeConverter.ConvertFrom(null, culture, value);
return true;
}
var cast = FindTypeConversionOperatorMethod(from, to, OperatorType.Implicit | OperatorType.Explicit);
if (cast != null)

9
src/Avalonia.Base/Utilities/WeakEventHandlerManager.cs

@ -183,11 +183,16 @@ namespace Avalonia.Utilities
for (int c = 0; c < _count; c++)
{
var r = _data[c];
TSubscriber target = null;
r.Subscriber?.TryGetTarget(out target);
//Mark current index as first empty
if (r.Subscriber == null && empty == -1)
if (target == null && empty == -1)
empty = c;
//If current element isn't null and we have an empty one
if (r.Subscriber != null && empty != -1)
if (target != null && empty != -1)
{
_data[c] = default;
_data[empty] = r;

59
src/Avalonia.Controls/ContextMenu.cs

@ -20,6 +20,8 @@ namespace Avalonia.Controls
private static readonly ITemplate<IPanel> DefaultPanel =
new FuncTemplate<IPanel>(() => new StackPanel { Orientation = Orientation.Vertical });
private Popup _popup;
private Control _attachedControl;
private IInputElement _previousFocus;
/// <summary>
/// Initializes a new instance of the <see cref="ContextMenu"/> class.
@ -69,13 +71,16 @@ namespace Avalonia.Controls
{
var control = (Control)e.Sender;
if (e.OldValue != null)
if (e.OldValue is ContextMenu oldMenu)
{
control.PointerReleased -= ControlPointerReleased;
oldMenu._attachedControl = null;
((ISetLogicalParent)oldMenu._popup)?.SetParent(null);
}
if (e.NewValue != null)
if (e.NewValue is ContextMenu newMenu)
{
newMenu._attachedControl = control;
control.PointerReleased += ControlPointerReleased;
}
}
@ -91,8 +96,18 @@ namespace Avalonia.Controls
/// <param name="control">The control.</param>
public void Open(Control control)
{
if (control == null)
if (control is null && _attachedControl is null)
{
throw new ArgumentNullException(nameof(control));
}
if (control is object && _attachedControl is object && control != _attachedControl)
{
throw new ArgumentException(
"Cannot show ContentMenu on a different control to the one it is attached to.",
nameof(control));
}
if (IsOpen)
{
return;
@ -146,36 +161,38 @@ namespace Avalonia.Controls
return new MenuItemContainerGenerator(this);
}
private void CloseCore()
{
SelectedIndex = -1;
IsOpen = false;
RaiseEvent(new RoutedEventArgs
{
RoutedEvent = MenuClosedEvent,
Source = this,
});
}
private void PopupOpened(object sender, EventArgs e)
{
_previousFocus = FocusManager.Instance?.Current;
Focus();
}
private void PopupClosed(object sender, EventArgs e)
{
var contextMenu = (sender as Popup)?.Child as ContextMenu;
if (contextMenu != null)
foreach (var i in LogicalChildren)
{
foreach (var i in contextMenu.GetLogicalChildren().OfType<MenuItem>())
if (i is MenuItem menuItem)
{
i.IsSubMenuOpen = false;
menuItem.IsSubMenuOpen = false;
}
}
contextMenu.CloseCore();
SelectedIndex = -1;
IsOpen = false;
if (_attachedControl is null)
{
((ISetLogicalParent)_popup).SetParent(null);
}
// HACK: Reset the focus when the popup is closed. We need to fix this so it's automatic.
FocusManager.Instance?.Focus(_previousFocus);
RaiseEvent(new RoutedEventArgs
{
RoutedEvent = MenuClosedEvent,
Source = this,
});
}
private static void ControlPointerReleased(object sender, PointerReleasedEventArgs e)

5
src/Avalonia.Controls/ItemsControl.cs

@ -504,7 +504,10 @@ namespace Avalonia.Controls
result = container.GetControl(direction, c, wrap);
from = from ?? result;
if (result?.Focusable == true)
if (result != null &&
result.Focusable &&
result.IsEffectivelyEnabled &&
result.IsEffectivelyVisible)
{
return result;
}

2
src/Avalonia.Controls/Platform/DefaultMenuInteractionHandler.cs

@ -101,7 +101,7 @@ namespace Avalonia.Controls.Platform
if (_root is TopLevel tl && tl.PlatformImpl is IEmbeddableWindowImpl eimpl)
eimpl.LostFocus -= TopLevelLostPlatformFocus;
_inputManagerSubscription.Dispose();
_inputManagerSubscription?.Dispose();
Menu = null;
_root = null;

39
src/Avalonia.Controls/Primitives/PopupRoot.cs

@ -118,20 +118,41 @@ namespace Avalonia.Controls.Primitives
});
}
/// <summary>
/// Carries out the arrange pass of the window.
/// </summary>
/// <param name="finalSize">The final window size.</param>
/// <returns>The <paramref name="finalSize"/> parameter unchanged.</returns>
protected override Size ArrangeOverride(Size finalSize)
protected override Size MeasureOverride(Size availableSize)
{
var measured = base.MeasureOverride(availableSize);
var width = measured.Width;
var height = measured.Height;
var widthCache = Width;
var heightCache = Height;
if (!double.IsNaN(widthCache))
{
width = widthCache;
}
width = Math.Min(width, MaxWidth);
width = Math.Max(width, MinWidth);
if (!double.IsNaN(heightCache))
{
height = heightCache;
}
height = Math.Min(height, MaxHeight);
height = Math.Max(height, MinHeight);
return new Size(width, height);
}
protected override sealed Size ArrangeSetBounds(Size size)
{
using (BeginAutoSizing())
{
_positionerParameters.Size = finalSize;
_positionerParameters.Size = size;
UpdatePosition();
return ClientSize;
}
return base.ArrangeOverride(PlatformImpl?.ClientSize ?? default(Size));
}
}
}

71
src/Avalonia.Controls/Window.cs

@ -129,7 +129,7 @@ namespace Avalonia.Controls
ShowInTaskbarProperty.Changed.AddClassHandler<Window>((w, e) => w.PlatformImpl?.ShowTaskbarIcon((bool)e.NewValue));
IconProperty.Changed.AddClassHandler<Window>((s, e) => s.PlatformImpl?.SetIcon(((WindowIcon)e.NewValue).PlatformImpl));
IconProperty.Changed.AddClassHandler<Window>((s, e) => s.PlatformImpl?.SetIcon(((WindowIcon)e.NewValue)?.PlatformImpl));
CanResizeProperty.Changed.AddClassHandler<Window>((w, e) => w.PlatformImpl?.CanResize((bool)e.NewValue));
@ -268,22 +268,7 @@ namespace Avalonia.Controls
/// Should be called from left mouse button press event handler
/// </summary>
public void BeginResizeDrag(WindowEdge edge, PointerPressedEventArgs e) => PlatformImpl?.BeginResizeDrag(edge, e);
/// <summary>
/// Carries out the arrange pass of the window.
/// </summary>
/// <param name="finalSize">The final window size.</param>
/// <returns>The <paramref name="finalSize"/> parameter unchanged.</returns>
protected override Size ArrangeOverride(Size finalSize)
{
using (BeginAutoSizing())
{
PlatformImpl?.Resize(finalSize);
}
return base.ArrangeOverride(PlatformImpl?.ClientSize ?? default(Size));
}
/// <inheritdoc/>
Size ILayoutRoot.MaxClientSize => _maxPlatformClientSize;
@ -405,6 +390,19 @@ namespace Avalonia.Controls
EnsureInitialized();
IsVisible = true;
var initialSize = new Size(
double.IsNaN(Width) ? ClientSize.Width : Width,
double.IsNaN(Height) ? ClientSize.Height : Height);
if (initialSize != ClientSize)
{
using (BeginAutoSizing())
{
PlatformImpl?.Resize(initialSize);
}
}
LayoutManager.ExecuteInitialLayoutPass(this);
using (BeginAutoSizing())
@ -524,38 +522,60 @@ namespace Avalonia.Controls
}
}
/// <inheritdoc/>
protected override Size MeasureOverride(Size availableSize)
{
var sizeToContent = SizeToContent;
var clientSize = ClientSize;
Size constraint = clientSize;
var constraint = clientSize;
if ((sizeToContent & SizeToContent.Width) != 0)
if (sizeToContent.HasFlagCustom(SizeToContent.Width))
{
constraint = constraint.WithWidth(double.PositiveInfinity);
}
if ((sizeToContent & SizeToContent.Height) != 0)
if (sizeToContent.HasFlagCustom(SizeToContent.Height))
{
constraint = constraint.WithHeight(double.PositiveInfinity);
}
var result = base.MeasureOverride(constraint);
if ((sizeToContent & SizeToContent.Width) == 0)
if (!sizeToContent.HasFlagCustom(SizeToContent.Width))
{
result = result.WithWidth(clientSize.Width);
if (!double.IsInfinity(availableSize.Width))
{
result = result.WithWidth(availableSize.Width);
}
else
{
result = result.WithWidth(clientSize.Width);
}
}
if ((sizeToContent & SizeToContent.Height) == 0)
if (!sizeToContent.HasFlagCustom(SizeToContent.Height))
{
result = result.WithHeight(clientSize.Height);
if (!double.IsInfinity(availableSize.Height))
{
result = result.WithHeight(availableSize.Height);
}
else
{
result = result.WithHeight(clientSize.Height);
}
}
return result;
}
protected sealed override Size ArrangeSetBounds(Size size)
{
using (BeginAutoSizing())
{
PlatformImpl?.Resize(size);
return ClientSize;
}
}
protected sealed override void HandleClosed()
{
RaiseEvent(new RoutedEventArgs(WindowClosedEvent));
@ -571,6 +591,9 @@ namespace Avalonia.Controls
SizeToContent = SizeToContent.Manual;
}
Width = clientSize.Width;
Height = clientSize.Height;
base.HandleResized(clientSize);
}

49
src/Avalonia.Controls/WindowBase.cs

@ -224,16 +224,53 @@ namespace Avalonia.Controls
/// <param name="clientSize">The new client size.</param>
protected override void HandleResized(Size clientSize)
{
if (!AutoSizing)
{
Width = clientSize.Width;
Height = clientSize.Height;
}
ClientSize = clientSize;
LayoutManager.ExecuteLayoutPass();
Renderer?.Resized(clientSize);
}
/// <summary>
/// Overrides the core measure logic for windows.
/// </summary>
/// <param name="availableSize">The available size.</param>
/// <returns>The measured size.</returns>
/// <remarks>
/// The layout logic for top-level windows is different than for other controls because
/// they don't have a parent, meaning that many layout properties handled by the default
/// MeasureCore (such as margins and alignment) make no sense.
/// </remarks>
protected override Size MeasureCore(Size availableSize)
{
ApplyTemplate();
var constraint = LayoutHelper.ApplyLayoutConstraints(this, availableSize);
return MeasureOverride(constraint);
}
/// <summary>
/// Overrides the core arrange logic for windows.
/// </summary>
/// <param name="finalRect">The final arrange rect.</param>
/// <remarks>
/// The layout logic for top-level windows is different than for other controls because
/// they don't have a parent, meaning that many layout properties handled by the default
/// ArrangeCore (such as margins and alignment) make no sense.
/// </remarks>
protected override void ArrangeCore(Rect finalRect)
{
var constraint = ArrangeSetBounds(finalRect.Size);
var arrangeSize = ArrangeOverride(constraint);
Bounds = new Rect(arrangeSize);
}
/// <summary>
/// Called durung the arrange pass to set the size of the window.
/// </summary>
/// <param name="size">The requested size of the window.</param>
/// <returns>The actual size of the window.</returns>
protected virtual Size ArrangeSetBounds(Size size) => size;
/// <summary>
/// Handles a window position change notification from
/// <see cref="IWindowBaseImpl.PositionChanged"/>.
@ -255,7 +292,7 @@ namespace Avalonia.Controls
if (scope != null)
{
FocusManager.Instance.SetFocusScope(scope);
FocusManager.Instance?.SetFocusScope(scope);
}
IsActive = true;

2
src/Avalonia.Dialogs/ManagedFileChooser.xaml

@ -58,7 +58,7 @@
<StackPanel Orientation="Horizontal" HorizontalAlignment="Right" Spacing="10">
<StackPanel.Styles>
<Style Selector="Button">
<Setter Property="Margin">4</Setter>
<Setter Property="Margin" Value="4"/>
</Style>
</StackPanel.Styles>
<Button Command="{Binding Ok}">OK</Button>

12
src/Avalonia.Dialogs/ManagedFileChooserViewModel.cs

@ -14,6 +14,7 @@ namespace Avalonia.Dialogs
{
internal class ManagedFileChooserViewModel : InternalViewModelBase
{
private readonly ManagedFileDialogOptions _options;
public event Action CancelRequested;
public event Action<string[]> CompleteRequested;
@ -103,8 +104,9 @@ namespace Avalonia.Dialogs
QuickLinks.AddRange(quickSources.GetAllItems().Select(i => new ManagedFileChooserItemViewModel(i)));
}
public ManagedFileChooserViewModel(FileSystemDialog dialog)
public ManagedFileChooserViewModel(FileSystemDialog dialog, ManagedFileDialogOptions options)
{
_options = options;
_disposables = new CompositeDisposable();
var quickSources = AvaloniaLocator.Current
@ -202,10 +204,12 @@ namespace Avalonia.Dialogs
}
else
{
var invalidItems = SelectedItems.Where(i => i.ItemType == ManagedFileChooserItemType.Folder).ToList();
foreach (var item in invalidItems)
if (!_options.AllowDirectorySelection)
{
SelectedItems.Remove(item);
var invalidItems = SelectedItems.Where(i => i.ItemType == ManagedFileChooserItemType.Folder)
.ToList();
foreach (var item in invalidItems)
SelectedItems.Remove(item);
}
if (!_selectingDirectory)

20
src/Avalonia.Dialogs/ManagedFileDialogExtensions.cs

@ -13,13 +13,15 @@ namespace Avalonia.Dialogs
{
class ManagedSystemDialogImpl<T> : ISystemDialogImpl where T : Window, new()
{
async Task<string[]> Show(SystemDialog d, IWindowImpl parent)
async Task<string[]> Show(SystemDialog d, IWindowImpl parent, ManagedFileDialogOptions options = null)
{
var model = new ManagedFileChooserViewModel((FileSystemDialog)d);
var model = new ManagedFileChooserViewModel((FileSystemDialog)d,
options ?? new ManagedFileDialogOptions());
var dialog = new T
{
Content = new ManagedFileChooser(),
Title = d.Title,
DataContext = model
};
@ -48,6 +50,11 @@ namespace Avalonia.Dialogs
{
return (await Show(dialog, parent))?.FirstOrDefault();
}
public async Task<string[]> ShowFileDialogAsync(FileDialog dialog, Window parent, ManagedFileDialogOptions options)
{
return await Show(dialog, parent.PlatformImpl, options);
}
}
public static TAppBuilder UseManagedSystemDialogs<TAppBuilder>(this TAppBuilder builder)
@ -65,5 +72,14 @@ namespace Avalonia.Dialogs
AvaloniaLocator.CurrentMutable.Bind<ISystemDialogImpl>().ToSingleton<ManagedSystemDialogImpl<TWindow>>());
return builder;
}
public static Task<string[]> ShowManagedAsync(this OpenFileDialog dialog, Window parent,
ManagedFileDialogOptions options = null) => ShowManagedAsync<Window>(dialog, parent, options);
public static Task<string[]> ShowManagedAsync<TWindow>(this OpenFileDialog dialog, Window parent,
ManagedFileDialogOptions options = null) where TWindow : Window, new()
{
return new ManagedSystemDialogImpl<TWindow>().ShowFileDialogAsync(dialog, parent, options);
}
}
}

7
src/Avalonia.Dialogs/ManagedFileDialogOptions.cs

@ -0,0 +1,7 @@
namespace Avalonia.Dialogs
{
public class ManagedFileDialogOptions
{
public bool AllowDirectorySelection { get; set; }
}
}

2
src/Avalonia.Input/FocusManager.cs

@ -171,7 +171,7 @@ namespace Avalonia.Input
{
var scope = control as IFocusScope;
if (scope != null)
if (scope != null && control.VisualRoot?.IsVisible == true)
{
yield return scope;
}

1
src/Avalonia.Input/GestureRecognizers/ScrollGestureRecognizer.cs

@ -148,6 +148,7 @@ namespace Avalonia.Input.GestureRecognizers
EndGesture();
else
{
_tracking = null;
var savedGestureId = _gestureId;
var st = Stopwatch.StartNew();
var lastTime = TimeSpan.Zero;

2
src/Avalonia.Input/Raw/RawPointerEventArgs.cs

@ -62,7 +62,7 @@ namespace Avalonia.Input.Raw
/// <summary>
/// Gets the type of the event.
/// </summary>
public RawPointerEventType Type { get; private set; }
public RawPointerEventType Type { get; set; }
/// <summary>
/// Gets the input modifiers.

74
src/Avalonia.Layout/LayoutHelper.cs

@ -2,6 +2,8 @@
// Licensed under the MIT license. See licence.md file in the project root for full license information.
using System;
using Avalonia.Utilities;
using Avalonia.VisualTree;
namespace Avalonia.Layout
{
@ -21,13 +23,11 @@ namespace Avalonia.Layout
/// <returns>The control's size.</returns>
public static Size ApplyLayoutConstraints(ILayoutable control, Size constraints)
{
double width = (control.Width > 0) ? control.Width : constraints.Width;
double height = (control.Height > 0) ? control.Height : constraints.Height;
width = Math.Min(width, control.MaxWidth);
width = Math.Max(width, control.MinWidth);
height = Math.Min(height, control.MaxHeight);
height = Math.Max(height, control.MinHeight);
return new Size(width, height);
var minmax = new MinMax(control);
return new Size(
MathUtilities.Clamp(constraints.Width, minmax.MinWidth, minmax.MaxWidth),
MathUtilities.Clamp(constraints.Height, minmax.MinHeight, minmax.MaxHeight));
}
public static Size MeasureChild(ILayoutable control, Size availableSize, Thickness padding,
@ -58,5 +58,65 @@ namespace Avalonia.Layout
return availableSize;
}
/// <summary>
/// Invalidates measure for given control and all visual children recursively.
/// </summary>
public static void InvalidateSelfAndChildrenMeasure(ILayoutable control)
{
void InnerInvalidateMeasure(IVisual target)
{
if (target is ILayoutable targetLayoutable)
{
targetLayoutable.InvalidateMeasure();
}
var visualChildren = target.VisualChildren;
var visualChildrenCount = visualChildren.Count;
for (int i = 0; i < visualChildrenCount; i++)
{
IVisual child = visualChildren[i];
InnerInvalidateMeasure(child);
}
}
InnerInvalidateMeasure(control);
}
/// <summary>
/// Calculates the min and max height for a control. Ported from WPF.
/// </summary>
private readonly struct MinMax
{
public MinMax(ILayoutable e)
{
MaxHeight = e.MaxHeight;
MinHeight = e.MinHeight;
double l = e.Height;
double height = (double.IsNaN(l) ? double.PositiveInfinity : l);
MaxHeight = Math.Max(Math.Min(height, MaxHeight), MinHeight);
height = (double.IsNaN(l) ? 0 : l);
MinHeight = Math.Max(Math.Min(MaxHeight, height), MinHeight);
MaxWidth = e.MaxWidth;
MinWidth = e.MinWidth;
l = e.Width;
double width = (double.IsNaN(l) ? double.PositiveInfinity : l);
MaxWidth = Math.Max(Math.Min(width, MaxWidth), MinWidth);
width = (double.IsNaN(l) ? 0 : l);
MinWidth = Math.Max(Math.Min(MaxWidth, width), MinWidth);
}
public double MinWidth { get; }
public double MaxWidth { get; }
public double MinHeight { get; }
public double MaxHeight { get; }
}
}
}

12
src/Avalonia.Layout/LayoutManager.cs

@ -126,8 +126,16 @@ namespace Avalonia.Layout
/// <inheritdoc/>
public void ExecuteInitialLayoutPass(ILayoutRoot root)
{
Measure(root);
Arrange(root);
try
{
_running = true;
Measure(root);
Arrange(root);
}
finally
{
_running = false;
}
// Running the initial layout pass may have caused some control to be invalidated
// so run a full layout pass now (this usually due to scrollbars; its not known

11
src/Avalonia.Native/WindowImplBase.cs

@ -170,9 +170,12 @@ namespace Avalonia.Native
void IAvnWindowBaseEvents.Resized(AvnSize size)
{
var s = new Size(size.Width, size.Height);
_parent._savedLogicalSize = s;
_parent.Resized?.Invoke(s);
if (_parent._native != null)
{
var s = new Size(size.Width, size.Height);
_parent._savedLogicalSize = s;
_parent.Resized?.Invoke(s);
}
}
void IAvnWindowBaseEvents.PositionChanged(AvnPoint position)
@ -333,7 +336,7 @@ namespace Avalonia.Native
_native.SetTopMost(value);
}
public double Scaling => _native.GetScaling();
public double Scaling => _native?.GetScaling() ?? 1;
public Action Deactivated { get; set; }
public Action Activated { get; set; }

2
src/Avalonia.ReactiveUI/AppBuilderExtensions.cs

@ -18,7 +18,7 @@ namespace Avalonia.ReactiveUI
public static TAppBuilder UseReactiveUI<TAppBuilder>(this TAppBuilder builder)
where TAppBuilder : AppBuilderBase<TAppBuilder>, new()
{
return builder.AfterSetup(_ =>
return builder.AfterPlatformServicesSetup(_ =>
{
RxApp.MainThreadScheduler = AvaloniaScheduler.Instance;
Locator.CurrentMutable.RegisterConstant(new AvaloniaActivationForViewFetcher(), typeof(IActivationForViewFetcher));

3
src/Avalonia.Themes.Default/TreeViewItem.xaml

@ -17,8 +17,7 @@
BorderThickness="{TemplateBinding BorderThickness}"
TemplatedControl.IsTemplateFocusTarget="True">
<Grid Name="PART_Header"
ColumnDefinitions="16, Auto"
HorizontalAlignment="Left"
ColumnDefinitions="16, *"
Margin="{TemplateBinding Level, Mode=OneWay, Converter={StaticResource LeftMarginConverter}}" >
<ToggleButton Name="expander"
Focusable="False"

3
src/Avalonia.X11/NativeDialogs/Gtk.cs

@ -190,6 +190,9 @@ namespace Avalonia.X11.NativeDialogs
[DllImport(GtkName)]
public static extern void gtk_file_chooser_set_filename(IntPtr chooser, Utf8Buffer file);
[DllImport(GtkName)]
public static extern void gtk_file_chooser_set_current_name(IntPtr chooser, Utf8Buffer file);
[DllImport(GtkName)]
public static extern IntPtr gtk_file_filter_new();

8
src/Avalonia.X11/NativeDialogs/GtkNativeFileDialogs.cs

@ -91,7 +91,13 @@ namespace Avalonia.X11.NativeDialogs
gtk_dialog_add_button(dlg, open, GtkResponseType.Cancel);
if (initialFileName != null)
using (var fn = new Utf8Buffer(initialFileName))
gtk_file_chooser_set_filename(dlg, fn);
{
if (action == GtkFileChooserAction.Save)
gtk_file_chooser_set_current_name(dlg, fn);
else
gtk_file_chooser_set_filename(dlg, fn);
}
gtk_window_present(dlg);
return tcs.Task;
}

110
src/Avalonia.X11/X11ImmediateRendererProxy.cs

@ -0,0 +1,110 @@
using System;
using System.Collections.Generic;
using Avalonia.Rendering;
using Avalonia.Threading;
using Avalonia.VisualTree;
namespace Avalonia.X11
{
public class X11ImmediateRendererProxy : IRenderer, IRenderLoopTask
{
private readonly IRenderLoop _loop;
private ImmediateRenderer _renderer;
private bool _invalidated;
private bool _running;
private object _lock = new object();
public X11ImmediateRendererProxy(IVisual root, IRenderLoop loop)
{
_loop = loop;
_renderer = new ImmediateRenderer(root);
}
public void Dispose()
{
_running = false;
_renderer.Dispose();
}
public bool DrawFps
{
get => _renderer.DrawFps;
set => _renderer.DrawFps = value;
}
public bool DrawDirtyRects
{
get => _renderer.DrawDirtyRects;
set => _renderer.DrawDirtyRects = value;
}
public event EventHandler<SceneInvalidatedEventArgs> SceneInvalidated
{
add => _renderer.SceneInvalidated += value;
remove => _renderer.SceneInvalidated -= value;
}
public void AddDirty(IVisual visual)
{
lock (_lock)
_invalidated = true;
_renderer.AddDirty(visual);
}
public IEnumerable<IVisual> HitTest(Point p, IVisual root, Func<IVisual, bool> filter)
{
return _renderer.HitTest(p, root, filter);
}
public void RecalculateChildren(IVisual visual)
{
_renderer.RecalculateChildren(visual);
}
public void Resized(Size size)
{
_renderer.Resized(size);
}
public void Paint(Rect rect)
{
_invalidated = false;
_renderer.Paint(rect);
}
public void Start()
{
_running = true;
_loop.Add(this);
_renderer.Start();
}
public void Stop()
{
_running = false;
_loop.Remove(this);
_renderer.Stop();
}
public bool NeedsUpdate => false;
public void Update(TimeSpan time)
{
}
public void Render()
{
if (_invalidated)
{
lock (_lock)
_invalidated = false;
Dispatcher.UIThread.Post(() =>
{
if (_running)
Paint(new Rect(0, 0, 100000, 100000));
});
}
}
}
}

1
src/Avalonia.X11/X11Platform.cs

@ -99,6 +99,7 @@ namespace Avalonia
public bool UseGpu { get; set; } = true;
public bool OverlayPopups { get; set; }
public bool UseDBusMenu { get; set; }
public bool UseDeferredRendering { get; set; } = true;
public List<string> GlxRendererBlacklist { get; set; } = new List<string>
{

117
src/Avalonia.X11/X11Window.cs

@ -29,7 +29,6 @@ namespace Avalonia.X11
private readonly IWindowImpl _popupParent;
private readonly bool _popup;
private readonly X11Info _x11;
private bool _invalidated;
private XConfigureEvent? _configure;
private PixelPoint? _configurePoint;
private bool _triggeredExpose;
@ -306,8 +305,13 @@ namespace Avalonia.X11
public Action Closed { get; set; }
public Action<PixelPoint> PositionChanged { get; set; }
public IRenderer CreateRenderer(IRenderRoot root) =>
new DeferredRenderer(root, AvaloniaLocator.Current.GetService<IRenderLoop>());
public IRenderer CreateRenderer(IRenderRoot root)
{
var loop = AvaloniaLocator.Current.GetService<IRenderLoop>();
return _platform.Options.UseDeferredRendering ?
new DeferredRenderer(root, loop) :
(IRenderer)new X11ImmediateRendererProxy(root, loop);
}
void OnEvent(XEvent ev)
{
@ -421,7 +425,7 @@ namespace Avalonia.X11
updatedSizeViaScaling = UpdateScaling();
}
if (changedSize && !updatedSizeViaScaling)
if (changedSize && !updatedSizeViaScaling && !_popup)
Resized?.Invoke(ClientSize);
Dispatcher.UIThread.RunJobs(DispatcherPriority.Layout);
@ -530,14 +534,14 @@ namespace Avalonia.X11
}
else if (value == WindowState.Maximized)
{
SendNetWMMessage(_x11.Atoms._NET_WM_STATE, (IntPtr)0, _x11.Atoms._NET_WM_STATE_HIDDEN, IntPtr.Zero);
SendNetWMMessage(_x11.Atoms._NET_WM_STATE, (IntPtr)1, _x11.Atoms._NET_WM_STATE_MAXIMIZED_VERT,
ChangeWMAtoms(false, _x11.Atoms._NET_WM_STATE_HIDDEN);
ChangeWMAtoms(true, _x11.Atoms._NET_WM_STATE_MAXIMIZED_VERT,
_x11.Atoms._NET_WM_STATE_MAXIMIZED_HORZ);
}
else
{
SendNetWMMessage(_x11.Atoms._NET_WM_STATE, (IntPtr)0, _x11.Atoms._NET_WM_STATE_HIDDEN, IntPtr.Zero);
SendNetWMMessage(_x11.Atoms._NET_WM_STATE, (IntPtr)0, _x11.Atoms._NET_WM_STATE_MAXIMIZED_VERT,
ChangeWMAtoms(false, _x11.Atoms._NET_WM_STATE_HIDDEN);
ChangeWMAtoms(false, _x11.Atoms._NET_WM_STATE_MAXIMIZED_VERT,
_x11.Atoms._NET_WM_STATE_MAXIMIZED_HORZ);
}
}
@ -624,7 +628,27 @@ namespace Avalonia.X11
ScheduleInput(args);
}
public void ScheduleInput(RawInputEventArgs args)
public void ScheduleXI2Input(RawInputEventArgs args)
{
if (args is RawPointerEventArgs pargs)
{
if ((pargs.Type == RawPointerEventType.TouchBegin
|| pargs.Type == RawPointerEventType.TouchUpdate
|| pargs.Type == RawPointerEventType.LeftButtonDown
|| pargs.Type == RawPointerEventType.RightButtonDown
|| pargs.Type == RawPointerEventType.MiddleButtonDown
|| pargs.Type == RawPointerEventType.NonClientLeftButtonDown)
&& ActivateTransientChildIfNeeded())
return;
if (pargs.Type == RawPointerEventType.TouchEnd
&& ActivateTransientChildIfNeeded())
pargs.Type = RawPointerEventType.TouchCancel;
}
ScheduleInput(args);
}
private void ScheduleInput(RawInputEventArgs args)
{
if (args is RawPointerEventArgs mouse)
mouse.Position = mouse.Position / Scaling;
@ -663,20 +687,12 @@ namespace Avalonia.X11
void DoPaint()
{
_invalidated = false;
Paint?.Invoke(new Rect());
}
public void Invalidate(Rect rect)
{
if(_invalidated)
return;
_invalidated = true;
Dispatcher.UIThread.InvokeAsync(() =>
{
if (_mapped)
DoPaint();
});
}
public IInputRoot InputRoot => _inputRoot;
@ -738,7 +754,15 @@ namespace Avalonia.X11
_transientParent = window;
_transientParent?._transientChildren.Add(this);
if (informServer)
XSetTransientForHint(_x11.Display, _handle, _transientParent?._handle ?? IntPtr.Zero);
SetTransientForHint(_transientParent?._handle);
}
void SetTransientForHint(IntPtr? parent)
{
if (parent == null || parent == IntPtr.Zero)
XDeleteProperty(_x11.Display, _handle, _x11.Atoms.XA_WM_TRANSIENT_FOR);
else
XSetTransientForHint(_x11.Display, _handle, parent.Value);
}
public void Show()
@ -968,8 +992,7 @@ namespace Avalonia.X11
public void SetTopmost(bool value)
{
SendNetWMMessage(_x11.Atoms._NET_WM_STATE,
(IntPtr)(value ? 1 : 0), _x11.Atoms._NET_WM_STATE_ABOVE, IntPtr.Zero);
ChangeWMAtoms(value, _x11.Atoms._NET_WM_STATE_ABOVE);
}
public void ShowDialog(IWindowImpl parent)
@ -980,17 +1003,57 @@ namespace Avalonia.X11
public void SetIcon(IWindowIconImpl icon)
{
var data = ((X11IconData)icon).Data;
fixed (void* pdata = data)
XChangeProperty(_x11.Display, _handle, _x11.Atoms._NET_WM_ICON,
new IntPtr((int)Atom.XA_CARDINAL), 32, PropertyMode.Replace,
pdata, data.Length);
if (icon != null)
{
var data = ((X11IconData)icon).Data;
fixed (void* pdata = data)
XChangeProperty(_x11.Display, _handle, _x11.Atoms._NET_WM_ICON,
new IntPtr((int)Atom.XA_CARDINAL), 32, PropertyMode.Replace,
pdata, data.Length);
}
else
{
XDeleteProperty(_x11.Display, _handle, _x11.Atoms._NET_WM_ICON);
}
}
public void ShowTaskbarIcon(bool value)
{
ChangeWMAtoms(!value, _x11.Atoms._NET_WM_STATE_SKIP_TASKBAR);
}
void ChangeWMAtoms(bool enable, params IntPtr[] atoms)
{
if (atoms.Length < 1 || atoms.Length > 4)
throw new ArgumentException();
if (!_mapped)
{
XGetWindowProperty(_x11.Display, _handle, _x11.Atoms._NET_WM_STATE, IntPtr.Zero, new IntPtr(256),
false, (IntPtr)Atom.XA_ATOM, out _, out _, out var nitems, out _,
out var prop);
var ptr = (IntPtr*)prop.ToPointer();
var newAtoms = new HashSet<IntPtr>();
for (var c = 0; c < nitems.ToInt64(); c++)
newAtoms.Add(*ptr);
XFree(prop);
foreach(var atom in atoms)
if (enable)
newAtoms.Add(atom);
else
newAtoms.Remove(atom);
XChangeProperty(_x11.Display, _handle, _x11.Atoms._NET_WM_STATE, (IntPtr)Atom.XA_ATOM, 32,
PropertyMode.Replace, newAtoms.ToArray(), newAtoms.Count);
}
SendNetWMMessage(_x11.Atoms._NET_WM_STATE,
(IntPtr)(value ? 0 : 1), _x11.Atoms._NET_WM_STATE_SKIP_TASKBAR, IntPtr.Zero);
(IntPtr)(enable ? 1 : 0),
atoms[0],
atoms.Length > 1 ? atoms[1] : IntPtr.Zero,
atoms.Length > 2 ? atoms[2] : IntPtr.Zero,
atoms.Length > 3 ? atoms[3] : IntPtr.Zero
);
}
public IPopupPositioner PopupPositioner { get; }

10
src/Avalonia.X11/XI2Manager.cs

@ -196,7 +196,7 @@ namespace Avalonia.X11
(ev.Type == XiEventType.XI_TouchUpdate ?
RawPointerEventType.TouchUpdate :
RawPointerEventType.TouchEnd);
client.ScheduleInput(new RawTouchEventArgs(client.TouchDevice,
client.ScheduleXI2Input(new RawTouchEventArgs(client.TouchDevice,
ev.Timestamp, client.InputRoot, type, ev.Position, ev.Modifiers, ev.Detail));
return;
}
@ -230,10 +230,10 @@ namespace Avalonia.X11
}
if (scrollDelta != default)
client.ScheduleInput(new RawMouseWheelEventArgs(client.MouseDevice, ev.Timestamp,
client.ScheduleXI2Input(new RawMouseWheelEventArgs(client.MouseDevice, ev.Timestamp,
client.InputRoot, ev.Position, scrollDelta, ev.Modifiers));
if (_pointerDevice.HasMotion(ev))
client.ScheduleInput(new RawPointerEventArgs(client.MouseDevice, ev.Timestamp, client.InputRoot,
client.ScheduleXI2Input(new RawPointerEventArgs(client.MouseDevice, ev.Timestamp, client.InputRoot,
RawPointerEventType.Move, ev.Position, ev.Modifiers));
}
@ -246,7 +246,7 @@ namespace Avalonia.X11
: ev.Button == 3 ? (down ? RawPointerEventType.RightButtonDown : RawPointerEventType.RightButtonUp)
: (RawPointerEventType?)null;
if (type.HasValue)
client.ScheduleInput(new RawPointerEventArgs(client.MouseDevice, ev.Timestamp, client.InputRoot,
client.ScheduleXI2Input(new RawPointerEventArgs(client.MouseDevice, ev.Timestamp, client.InputRoot,
type.Value, ev.Position, ev.Modifiers));
}
@ -307,7 +307,7 @@ namespace Avalonia.X11
interface IXI2Client
{
IInputRoot InputRoot { get; }
void ScheduleInput(RawInputEventArgs args);
void ScheduleXI2Input(RawInputEventArgs args);
IMouseDevice MouseDevice { get; }
TouchDevice TouchDevice { get; }
}

40
src/Markup/Avalonia.Markup.Xaml/XamlIl/CompilerExtensions/Transformers/AvaloniaXamlIlSelectorTransformer.cs

@ -104,6 +104,11 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers
}
}
if (results != null && result != null)
{
results.Add(result);
}
return results ?? result;
}
@ -158,9 +163,7 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers
protected void EmitCall(XamlIlEmitContext context, IXamlIlEmitter codeGen, Func<IXamlIlMethod, bool> method)
{
var selectors = context.Configuration.TypeSystem.GetType("Avalonia.Styling.Selectors");
var found = selectors.FindMethod(m => m.IsStatic && m.Parameters.Count > 0 &&
m.Parameters[0].FullName == "Avalonia.Styling.Selector"
&& method(m));
var found = selectors.FindMethod(m => m.IsStatic && m.Parameters.Count > 0 && method(m));
codeGen.EmitCall(found);
}
}
@ -308,8 +311,35 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers
_selectors.Add(node);
}
//TODO: actually find the type
public override IXamlIlType TargetType => _selectors.FirstOrDefault()?.TargetType;
public override IXamlIlType TargetType
{
get
{
IXamlIlType result = null;
foreach (var selector in _selectors)
{
if (selector.TargetType == null)
{
return null;
}
else if (result == null)
{
result = selector.TargetType;
}
else
{
while (!result.IsAssignableFrom(selector.TargetType))
{
result = result.BaseType;
}
}
}
return result;
}
}
protected override void DoEmit(XamlIlEmitContext context, IXamlIlEmitter codeGen)
{
if (_selectors.Count == 0)

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

@ -940,7 +940,7 @@ namespace Avalonia.Win32
public void SetIcon(IWindowIconImpl icon)
{
var impl = (IconImpl)icon;
var hIcon = impl.HIcon;
var hIcon = impl?.HIcon ?? IntPtr.Zero;
UnmanagedMethods.PostMessage(_hwnd, (int)UnmanagedMethods.WindowsMessage.WM_SETICON,
new IntPtr((int)UnmanagedMethods.Icons.ICON_BIG), hIcon);
}

2
tests/Avalonia.Animation.UnitTests/Avalonia.Animation.UnitTests.csproj

@ -1,6 +1,6 @@
<Project Sdk="Microsoft.NET.Sdk" ToolsVersion="15.0">
<PropertyGroup>
<TargetFrameworks>netcoreapp2.0;net47</TargetFrameworks>
<TargetFrameworks>netcoreapp3.1;net47</TargetFrameworks>
<OutputType>Library</OutputType>
<IsTestProject>true</IsTestProject>
</PropertyGroup>

2
tests/Avalonia.Base.UnitTests/Avalonia.Base.UnitTests.csproj

@ -1,6 +1,6 @@
<Project Sdk="Microsoft.NET.Sdk" ToolsVersion="15.0">
<PropertyGroup>
<TargetFrameworks>netcoreapp2.0;net47</TargetFrameworks>
<TargetFrameworks>netcoreapp3.1;net47</TargetFrameworks>
<OutputType>Library</OutputType>
<IsTestProject>true</IsTestProject>
</PropertyGroup>

178
tests/Avalonia.Base.UnitTests/AvaloniaObjectTests_MultiBinding.cs

@ -0,0 +1,178 @@
// Copyright (c) The Avalonia Project. All rights reserved.
// Licensed under the MIT license. See licence.md file in the project root for full license information.
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Reactive.Linq;
using System.Reactive.Subjects;
using Avalonia.Data;
using Avalonia.Data.Converters;
using Xunit;
namespace Avalonia.Base.UnitTests
{
public class AvaloniaObjectTests_MultiBinding
{
[Fact]
public void Should_Update()
{
var target = new Class1();
var b = new Subject<object>();
var mb = new MultiBinding()
{
Converter = StringJoinConverter,
Bindings = new[]
{
b.ToBinding()
}
};
target.Bind(Class1.FooProperty, mb);
Assert.Equal(null, target.Foo);
b.OnNext("Foo");
Assert.Equal("Foo", target.Foo);
b.OnNext("Bar");
Assert.Equal("Bar", target.Foo);
}
[Fact]
public void Should_Update_With_Multiple_Bindings()
{
var target = new Class1();
var bindings = Enumerable.Range(0, 3).Select(i => new BehaviorSubject<object>("Empty")).ToArray();
var mb = new MultiBinding()
{
Converter = StringJoinConverter,
Bindings = bindings.Select(b => b.ToBinding()).ToArray()
};
target.Bind(Class1.FooProperty, mb);
Assert.Equal("Empty,Empty,Empty", target.Foo);
bindings[0].OnNext("Foo");
Assert.Equal("Foo,Empty,Empty", target.Foo);
bindings[1].OnNext("Bar");
Assert.Equal("Foo,Bar,Empty", target.Foo);
bindings[2].OnNext("Baz");
Assert.Equal("Foo,Bar,Baz", target.Foo);
}
[Fact]
public void Should_Update_When_Null_Value_In_Bindings()
{
var target = new Class1();
var b = new Subject<object>();
var mb = new MultiBinding()
{
Converter = StringJoinConverter,
Bindings = new[]
{
b.ToBinding()
}
};
target.Bind(Class1.FooProperty, mb);
Assert.Equal(null, target.Foo);
b.OnNext("Foo");
Assert.Equal("Foo", target.Foo);
b.OnNext(null);
Assert.Equal("", target.Foo);
}
[Fact]
public void Should_Update_When_Null_Value_In_Bindings_With_StringFormat()
{
var target = new Class1();
var b = new Subject<object>();
var mb = new MultiBinding()
{
StringFormat = "Converted: {0}",
Bindings = new[]
{
b.ToBinding()
}
};
target.Bind(Class1.FooProperty, mb);
Assert.Equal(null, target.Foo);
b.OnNext("Foo");
Assert.Equal("Converted: Foo", target.Foo);
b.OnNext(null);
Assert.Equal("Converted: ", target.Foo);
}
[Fact]
public void MultiValueConverter_Should_Not_Skip_Valid_Null_ReferenceType_Value()
{
var target = new FuncMultiValueConverter<string, string>(v => string.Join(",", v.ToArray()));
object value = target.Convert(new[] { "Foo", "Bar", "Baz" }, typeof(string), null, CultureInfo.InvariantCulture);
Assert.Equal("Foo,Bar,Baz", value);
value = target.Convert(new[] { null, "Bar", "Baz" }, typeof(string), null, CultureInfo.InvariantCulture);
Assert.Equal(",Bar,Baz", value);
}
[Fact]
public void MultiValueConverter_Should_Not_Skip_Valid_Default_ValueType_Value()
{
var target = new FuncMultiValueConverter<StringValueTypeWrapper, string>(v => string.Join(",", v.ToArray()));
IList<object> create(string[] values) =>
values.Select(v => (object)(v != null ? new StringValueTypeWrapper() { Value = v } : default)).ToList();
object value = target.Convert(create(new[] { "Foo", "Bar", "Baz" }), typeof(string), null, CultureInfo.InvariantCulture);
Assert.Equal("Foo,Bar,Baz", value);
value = target.Convert(create(new[] { null, "Bar", "Baz" }), typeof(string), null, CultureInfo.InvariantCulture);
Assert.Equal(",Bar,Baz", value);
}
private struct StringValueTypeWrapper
{
public string Value;
public override string ToString() => Value;
}
private static IMultiValueConverter StringJoinConverter = new FuncMultiValueConverter<object, string>(v => string.Join(",", v.ToArray()));
private class Class1 : AvaloniaObject
{
public static readonly StyledProperty<string> FooProperty =
AvaloniaProperty.Register<Class1, string>("Foo");
public string Foo
{
get => GetValue(FooProperty);
set => SetValue(FooProperty, value);
}
}
}
}

12
tests/Avalonia.Base.UnitTests/Data/DefaultValueConverterTests.cs

@ -50,6 +50,18 @@ namespace Avalonia.Base.UnitTests.Data.Converters
Assert.Equal(TestEnum.Bar, result);
}
[Fact]
public void Can_Convert_String_To_TimeSpan()
{
var result = DefaultValueConverter.Instance.Convert(
"00:00:10",
typeof(TimeSpan),
null,
CultureInfo.InvariantCulture);
Assert.Equal(TimeSpan.FromSeconds(10), result);
}
[Fact]
public void Can_Convert_Int_To_Enum()
{

2
tests/Avalonia.Benchmarks/Avalonia.Benchmarks.csproj

@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>netcoreapp2.0</TargetFramework>
<TargetFramework>netcoreapp3.1</TargetFramework>
<OutputType>Exe</OutputType>
<IsPackable>false</IsPackable>
</PropertyGroup>

2
tests/Avalonia.Controls.DataGrid.UnitTests/Avalonia.Controls.DataGrid.UnitTests.csproj

@ -1,6 +1,6 @@
<Project Sdk="Microsoft.NET.Sdk" ToolsVersion="15.0">
<PropertyGroup>
<TargetFrameworks>netcoreapp2.0;net47</TargetFrameworks>
<TargetFrameworks>netcoreapp3.1;net47</TargetFrameworks>
<LangVersion>latest</LangVersion>
<OutputType>Library</OutputType>
<IsTestProject>true</IsTestProject>

2
tests/Avalonia.Controls.UnitTests/Avalonia.Controls.UnitTests.csproj

@ -1,6 +1,6 @@
<Project Sdk="Microsoft.NET.Sdk" ToolsVersion="15.0">
<PropertyGroup>
<TargetFrameworks>netcoreapp2.0;net47</TargetFrameworks>
<TargetFrameworks>netcoreapp3.1;net47</TargetFrameworks>
<LangVersion>latest</LangVersion>
<OutputType>Library</OutputType>
<IsTestProject>true</IsTestProject>

24
tests/Avalonia.Controls.UnitTests/ContextMenuTests.cs

@ -1,9 +1,5 @@
using System;
using System.Windows.Input;
using Avalonia.Controls.Primitives;
using Avalonia.Data;
using Avalonia.Input;
using Avalonia.Markup.Data;
using Avalonia.Platform;
using Avalonia.UnitTests;
using Moq;
@ -159,6 +155,19 @@ namespace Avalonia.Controls.UnitTests
}
}
[Fact]
public void Can_Set_Clear_ContextMenu_Property()
{
using (Application())
{
var target = new ContextMenu();
var control = new Panel();
control.ContextMenu = target;
control.ContextMenu = null;
}
}
[Fact(Skip = "The only reason this test was 'passing' before was that the author forgot to call Window.ApplyTemplate()")]
public void Cancelling_Closing_Leaves_ContextMenuOpen()
{
@ -200,16 +209,17 @@ namespace Avalonia.Controls.UnitTests
screenImpl.Setup(x => x.ScreenCount).Returns(1);
screenImpl.Setup(X => X.AllScreens).Returns( new[] { new Screen(1, screen, screen, true) });
popupImpl = MockWindowingPlatform.CreatePopupMock();
var windowImpl = MockWindowingPlatform.CreateWindowMock();
popupImpl = MockWindowingPlatform.CreatePopupMock(windowImpl.Object);
popupImpl.SetupGet(x => x.Scaling).Returns(1);
windowImpl.Setup(x => x.CreatePopup()).Returns(popupImpl.Object);
var windowImpl = MockWindowingPlatform.CreateWindowMock(() => popupImpl.Object);
windowImpl.Setup(x => x.Screen).Returns(screenImpl.Object);
var services = TestServices.StyledWindow.With(
inputManager: new InputManager(),
windowImpl: windowImpl.Object,
windowingPlatform: new MockWindowingPlatform(() => windowImpl.Object, () => popupImpl.Object));
windowingPlatform: new MockWindowingPlatform(() => windowImpl.Object, x => popupImpl.Object));
return UnitTestApplication.Start(services);
}

155
tests/Avalonia.Controls.UnitTests/Primitives/PopupRootTests.cs

@ -5,11 +5,14 @@ using System;
using System.Linq;
using Avalonia.Controls.Presenters;
using Avalonia.Controls.Primitives;
using Avalonia.Controls.Primitives.PopupPositioning;
using Avalonia.Controls.Templates;
using Avalonia.LogicalTree;
using Avalonia.Platform;
using Avalonia.Styling;
using Avalonia.UnitTests;
using Avalonia.VisualTree;
using Moq;
using Xunit;
namespace Avalonia.Controls.UnitTests.Primitives
@ -175,9 +178,146 @@ namespace Avalonia.Controls.UnitTests.Primitives
}
}
private PopupRoot CreateTarget(TopLevel popupParent)
[Fact]
public void Child_Should_Be_Measured_With_Infinity()
{
using (UnitTestApplication.Start(TestServices.StyledWindow))
{
var child = new ChildControl();
var window = new Window();
var target = CreateTarget(window);
target.Content = child;
target.Show();
Assert.Equal(Size.Infinity, child.MeasureSize);
}
}
[Fact]
public void Child_Should_Be_Measured_With_Width_Height_When_Set()
{
var result = new PopupRoot(popupParent, popupParent.PlatformImpl.CreatePopup())
using (UnitTestApplication.Start(TestServices.StyledWindow))
{
var child = new ChildControl();
var window = new Window();
var target = CreateTarget(window);
target.Width = 500;
target.Height = 600;
target.Content = child;
target.Show();
Assert.Equal(new Size(500, 600), child.MeasureSize);
}
}
[Fact]
public void Child_Should_Be_Measured_With_MaxWidth_MaxHeight_When_Set()
{
using (UnitTestApplication.Start(TestServices.StyledWindow))
{
var child = new ChildControl();
var window = new Window();
var target = CreateTarget(window);
target.MaxWidth = 500;
target.MaxHeight = 600;
target.Content = child;
target.Show();
Assert.Equal(new Size(500, 600), child.MeasureSize);
}
}
[Fact]
public void Should_Not_Have_Offset_On_Bounds_When_Content_Larger_Than_Max_Window_Size()
{
// Issue #3784.
using (UnitTestApplication.Start(TestServices.StyledWindow))
{
var window = new Window();
var popupImpl = MockWindowingPlatform.CreatePopupMock(window.PlatformImpl);
var child = new Canvas
{
Width = 400,
Height = 1344,
};
var target = CreateTarget(window, popupImpl.Object);
target.Content = child;
target.Show();
Assert.Equal(new Size(400, 1024), target.Bounds.Size);
// Issue #3784 causes this to be (0, 160) which makes no sense as Window has no
// parent control to be offset against.
Assert.Equal(new Point(0, 0), target.Bounds.Position);
}
}
[Fact]
public void MinWidth_MinHeight_Should_Be_Respected()
{
// Issue #3796
using (UnitTestApplication.Start(TestServices.StyledWindow))
{
var window = new Window();
var popupImpl = MockWindowingPlatform.CreatePopupMock(window.PlatformImpl);
var target = CreateTarget(window, popupImpl.Object);
target.MinWidth = 400;
target.MinHeight = 800;
target.Content = new Border
{
Width = 100,
Height = 100,
};
target.Show();
Assert.Equal(new Rect(0, 0, 400, 800), target.Bounds);
Assert.Equal(new Size(400, 800), target.ClientSize);
Assert.Equal(new Size(400, 800), target.PlatformImpl.ClientSize);
}
}
[Fact]
public void Setting_Width_Should_Resize_WindowImpl()
{
// Issue #3796
using (UnitTestApplication.Start(TestServices.StyledWindow))
{
var window = new Window();
var popupImpl = MockWindowingPlatform.CreatePopupMock(window.PlatformImpl);
var positioner = new Mock<IPopupPositioner>();
popupImpl.Setup(x => x.PopupPositioner).Returns(positioner.Object);
var target = CreateTarget(window, popupImpl.Object);
target.Width = 400;
target.Height = 800;
target.Show();
Assert.Equal(400, target.Width);
Assert.Equal(800, target.Height);
target.Width = 410;
target.LayoutManager.ExecuteLayoutPass();
positioner.Verify(x =>
x.Update(It.Is<PopupPositionerParameters>(x => x.Size.Width == 410)));
Assert.Equal(410, target.Width);
}
}
private PopupRoot CreateTarget(TopLevel popupParent, IPopupImpl impl = null)
{
impl ??= popupParent.PlatformImpl.CreatePopup();
var result = new PopupRoot(popupParent, impl)
{
Template = new FuncControlTemplate<PopupRoot>((parent, scope) =>
new ContentPresenter
@ -220,5 +360,16 @@ namespace Avalonia.Controls.UnitTests.Primitives
Popup = (Popup)this.GetVisualChildren().Single();
}
}
private class ChildControl : Control
{
public Size MeasureSize { get; private set; }
protected override Size MeasureOverride(Size availableSize)
{
MeasureSize = availableSize;
return base.MeasureOverride(availableSize);
}
}
}
}

4
tests/Avalonia.Controls.UnitTests/Primitives/PopupTests.cs

@ -332,11 +332,11 @@ namespace Avalonia.Controls.UnitTests.Primitives
{
return UnitTestApplication.Start(TestServices.StyledWindow.With(windowingPlatform:
new MockWindowingPlatform(null,
() =>
x =>
{
if(UsePopupHost)
return null;
return MockWindowingPlatform.CreatePopupMock().Object;
return MockWindowingPlatform.CreatePopupMock(x).Object;
})));
}

22
tests/Avalonia.Controls.UnitTests/Primitives/SelectingItemsControlTests.cs

@ -1202,6 +1202,28 @@ namespace Avalonia.Controls.UnitTests.Primitives
target.MoveSelection(NavigationDirection.Next, true);
}
[Fact]
public void MoveSelection_Does_Select_Disabled_Controls()
{
// Issue #3426.
var target = new TestSelector
{
Template = Template(),
Items = new[]
{
new ListBoxItem(),
new ListBoxItem { IsEnabled = false },
},
SelectedIndex = 0,
};
target.Measure(new Size(100, 100));
target.Arrange(new Rect(0, 0, 100, 100));
target.MoveSelection(NavigationDirection.Next, true);
Assert.Equal(0, target.SelectedIndex);
}
[Fact]
public void Pre_Selecting_Item_Should_Set_Selection_After_It_Was_Added_When_AlwaysSelected()
{

219
tests/Avalonia.Controls.UnitTests/WindowTests.cs

@ -7,6 +7,7 @@
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using Avalonia.Layout;
using Avalonia.Platform;
using Avalonia.Rendering;
using Avalonia.UnitTests;
@ -341,11 +342,229 @@ namespace Avalonia.Controls.UnitTests
}
}
[Fact]
public void Child_Should_Be_Measured_With_Width_And_Height_If_SizeToContent_Is_Manual()
{
using (UnitTestApplication.Start(TestServices.StyledWindow))
{
var child = new ChildControl();
var target = new Window
{
Width = 100,
Height = 50,
SizeToContent = SizeToContent.Manual,
Content = child
};
target.Show();
Assert.Equal(1, child.MeasureSizes.Count);
Assert.Equal(new Size(100, 50), child.MeasureSizes[0]);
}
}
[Fact]
public void Child_Should_Be_Measured_With_ClientSize_If_SizeToContent_Is_Manual_And_No_Width_Height_Specified()
{
using (UnitTestApplication.Start(TestServices.StyledWindow))
{
var windowImpl = MockWindowingPlatform.CreateWindowMock();
windowImpl.Setup(x => x.ClientSize).Returns(new Size(550, 450));
var child = new ChildControl();
var target = new Window(windowImpl.Object)
{
SizeToContent = SizeToContent.Manual,
Content = child
};
target.Show();
Assert.Equal(1, child.MeasureSizes.Count);
Assert.Equal(new Size(550, 450), child.MeasureSizes[0]);
}
}
[Fact]
public void Child_Should_Be_Measured_With_Infinity_If_SizeToContent_Is_WidthAndHeight()
{
using (UnitTestApplication.Start(TestServices.StyledWindow))
{
var child = new ChildControl();
var target = new Window
{
Width = 100,
Height = 50,
SizeToContent = SizeToContent.WidthAndHeight,
Content = child
};
target.Show();
Assert.Equal(1, child.MeasureSizes.Count);
Assert.Equal(Size.Infinity, child.MeasureSizes[0]);
}
}
[Fact]
public void Should_Not_Have_Offset_On_Bounds_When_Content_Larger_Than_Max_Window_Size()
{
// Issue #3784.
using (UnitTestApplication.Start(TestServices.StyledWindow))
{
var windowImpl = MockWindowingPlatform.CreateWindowMock();
var clientSize = new Size(200, 200);
var maxClientSize = new Size(480, 480);
windowImpl.Setup(x => x.Resize(It.IsAny<Size>())).Callback<Size>(size =>
{
clientSize = size.Constrain(maxClientSize);
windowImpl.Object.Resized?.Invoke(clientSize);
});
windowImpl.Setup(x => x.ClientSize).Returns(() => clientSize);
var child = new Canvas
{
Width = 400,
Height = 800,
};
var target = new Window(windowImpl.Object)
{
SizeToContent = SizeToContent.WidthAndHeight,
Content = child
};
target.Show();
Assert.Equal(new Size(400, 480), target.Bounds.Size);
// Issue #3784 causes this to be (0, 160) which makes no sense as Window has no
// parent control to be offset against.
Assert.Equal(new Point(0, 0), target.Bounds.Position);
}
}
[Fact]
public void Width_Height_Should_Not_Be_NaN_After_Show_With_SizeToContent_WidthAndHeight()
{
using (UnitTestApplication.Start(TestServices.StyledWindow))
{
var child = new Canvas
{
Width = 400,
Height = 800,
};
var target = new Window()
{
SizeToContent = SizeToContent.WidthAndHeight,
Content = child
};
target.Show();
Assert.Equal(400, target.Width);
Assert.Equal(800, target.Height);
}
}
[Fact]
public void SizeToContent_Should_Not_Be_Lost_On_Show()
{
using (UnitTestApplication.Start(TestServices.StyledWindow))
{
var child = new Canvas
{
Width = 400,
Height = 800,
};
var target = new Window()
{
SizeToContent = SizeToContent.WidthAndHeight,
Content = child
};
target.Show();
Assert.Equal(SizeToContent.WidthAndHeight, target.SizeToContent);
}
}
[Fact]
public void Width_Height_Should_Be_Updated_When_SizeToContent_Is_WidthAndHeight()
{
using (UnitTestApplication.Start(TestServices.StyledWindow))
{
var child = new Canvas
{
Width = 400,
Height = 800,
};
var target = new Window()
{
SizeToContent = SizeToContent.WidthAndHeight,
Content = child
};
target.Show();
Assert.Equal(400, target.Width);
Assert.Equal(800, target.Height);
child.Width = 410;
target.LayoutManager.ExecuteLayoutPass();
Assert.Equal(410, target.Width);
Assert.Equal(800, target.Height);
Assert.Equal(SizeToContent.WidthAndHeight, target.SizeToContent);
}
}
[Fact]
public void Setting_Width_Should_Resize_WindowImpl()
{
// Issue #3796
using (UnitTestApplication.Start(TestServices.StyledWindow))
{
var target = new Window()
{
Width = 400,
Height = 800,
};
target.Show();
Assert.Equal(400, target.Width);
Assert.Equal(800, target.Height);
target.Width = 410;
target.LayoutManager.ExecuteLayoutPass();
var windowImpl = Mock.Get(target.PlatformImpl);
windowImpl.Verify(x => x.Resize(new Size(410, 800)));
Assert.Equal(410, target.Width);
}
}
private IWindowImpl CreateImpl(Mock<IRenderer> renderer)
{
return Mock.Of<IWindowImpl>(x =>
x.Scaling == 1 &&
x.CreateRenderer(It.IsAny<IRenderRoot>()) == renderer.Object);
}
private class ChildControl : Control
{
public List<Size> MeasureSizes { get; } = new List<Size>();
protected override Size MeasureOverride(Size availableSize)
{
MeasureSizes.Add(availableSize);
return base.MeasureOverride(availableSize);
}
}
}
}

2
tests/Avalonia.DesignerSupport.TestApp/Avalonia.DesignerSupport.TestApp.csproj

@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>netcoreapp2.0</TargetFramework>
<TargetFramework>netcoreapp3.1</TargetFramework>
<IsPackable>false</IsPackable>
</PropertyGroup>
<ItemGroup>

2
tests/Avalonia.DesignerSupport.Tests/Avalonia.DesignerSupport.Tests.csproj

@ -1,6 +1,6 @@
<Project Sdk="Microsoft.NET.Sdk" ToolsVersion="15.0">
<PropertyGroup>
<TargetFrameworks>netcoreapp2.0</TargetFrameworks>
<TargetFrameworks>netcoreapp3.1</TargetFrameworks>
<OutputType>Library</OutputType>
</PropertyGroup>
<Import Project="..\..\build\UnitTests.NetCore.targets" />

4
tests/Avalonia.DesignerSupport.Tests/DesignerSupportTests.cs

@ -28,12 +28,12 @@ namespace Avalonia.DesignerSupport.Tests
[SkippableTheory,
InlineData(
@"..\..\..\..\..\tests/Avalonia.DesignerSupport.TestApp/bin/$BUILD/netcoreapp2.0/",
@"..\..\..\..\..\tests/Avalonia.DesignerSupport.TestApp/bin/$BUILD/netcoreapp3.1/",
"Avalonia.DesignerSupport.TestApp",
"Avalonia.DesignerSupport.TestApp.dll",
@"..\..\..\..\..\tests\Avalonia.DesignerSupport.TestApp\MainWindow.xaml"),
InlineData(
@"..\..\..\..\..\samples\ControlCatalog.NetCore\bin\$BUILD\netcoreapp2.0\",
@"..\..\..\..\..\samples\ControlCatalog.NetCore\bin\$BUILD\netcoreapp3.1\",
"ControlCatalog.NetCore",
"ControlCatalog.dll",
@"..\..\..\..\..\samples\ControlCatalog\MainWindow.xaml")]

2
tests/Avalonia.Direct2D1.RenderTests/Avalonia.Direct2D1.RenderTests.csproj

@ -1,6 +1,6 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFrameworks>netcoreapp2.0</TargetFrameworks>
<TargetFrameworks>netcoreapp3.1</TargetFrameworks>
</PropertyGroup>
<ItemGroup>
<Compile Include="..\Avalonia.RenderTests\**\*.cs" />

2
tests/Avalonia.Direct2D1.UnitTests/Avalonia.Direct2D1.UnitTests.csproj

@ -1,6 +1,6 @@
<Project Sdk="Microsoft.NET.Sdk" ToolsVersion="15.0">
<PropertyGroup>
<TargetFrameworks>netcoreapp2.0</TargetFrameworks>
<TargetFrameworks>netcoreapp3.1</TargetFrameworks>
</PropertyGroup>
<Import Project="..\..\build\UnitTests.NetCore.targets" />
<Import Project="..\..\build\Moq.props" />

2
tests/Avalonia.Input.UnitTests/Avalonia.Input.UnitTests.csproj

@ -1,6 +1,6 @@
<Project Sdk="Microsoft.NET.Sdk" ToolsVersion="15.0">
<PropertyGroup>
<TargetFrameworks>netcoreapp2.0;net47</TargetFrameworks>
<TargetFrameworks>netcoreapp3.1;net47</TargetFrameworks>
<OutputType>Library</OutputType>
<IsTestProject>true</IsTestProject>
</PropertyGroup>

3
tests/Avalonia.Interactivity.UnitTests/Avalonia.Interactivity.UnitTests.csproj

@ -1,7 +1,6 @@
<Project Sdk="Microsoft.NET.Sdk" ToolsVersion="15.0">
<PropertyGroup>
<TargetFrameworks>netcoreapp2.0</TargetFrameworks>
<TargetFrameworks>netcoreapp2.0;net47</TargetFrameworks>
<TargetFrameworks>netcoreapp3.1;net47</TargetFrameworks>
<OutputType>Library</OutputType>
<IsTestProject>true</IsTestProject>
<LangVersion>latest</LangVersion>

2
tests/Avalonia.Layout.UnitTests/Avalonia.Layout.UnitTests.csproj

@ -1,6 +1,6 @@
<Project Sdk="Microsoft.NET.Sdk" ToolsVersion="15.0">
<PropertyGroup>
<TargetFrameworks>netcoreapp2.0</TargetFrameworks>
<TargetFrameworks>netcoreapp3.1</TargetFrameworks>
<OutputType>Library</OutputType>
</PropertyGroup>
<Import Project="..\..\build\UnitTests.NetCore.targets" />

108
tests/Avalonia.Layout.UnitTests/FullLayoutTests.cs

@ -1,28 +1,12 @@
// Copyright (c) The Avalonia Project. All rights reserved.
// Licensed under the MIT license. See licence.md file in the project root for full license information.
using System.Diagnostics;
using System.IO;
using System.Linq;
using Moq;
using Avalonia.Controls;
using Avalonia.Controls.Presenters;
using Avalonia.Controls.Primitives;
using Avalonia.Controls.Templates;
using Avalonia.Diagnostics;
using Avalonia.Input;
using Avalonia.Platform;
using Avalonia.Rendering;
using Avalonia.Shared.PlatformSupport;
using Avalonia.Styling;
using Avalonia.Themes.Default;
using Avalonia.UnitTests;
using Avalonia.VisualTree;
using Xunit;
using Avalonia.Media;
using System;
using System.Collections.Generic;
using Avalonia.Controls.UnitTests;
using Avalonia.UnitTests;
namespace Avalonia.Layout.UnitTests
{
@ -31,10 +15,8 @@ namespace Avalonia.Layout.UnitTests
[Fact]
public void Grandchild_Size_Changed()
{
using (var context = AvaloniaLocator.EnterScope())
using (UnitTestApplication.Start(TestServices.StyledWindow))
{
RegisterServices();
Border border;
TextBlock textBlock;
@ -58,7 +40,6 @@ namespace Avalonia.Layout.UnitTests
};
window.Show();
window.LayoutManager.ExecuteInitialLayoutPass(window);
Assert.Equal(new Size(400, 400), border.Bounds.Size);
textBlock.Width = 200;
@ -71,10 +52,8 @@ namespace Avalonia.Layout.UnitTests
[Fact]
public void Test_ScrollViewer_With_TextBlock()
{
using (var context = AvaloniaLocator.EnterScope())
using (UnitTestApplication.Start(TestServices.StyledWindow))
{
RegisterServices();
ScrollViewer scrollViewer;
TextBlock textBlock;
@ -82,7 +61,6 @@ namespace Avalonia.Layout.UnitTests
{
Width = 800,
Height = 600,
SizeToContent = SizeToContent.WidthAndHeight,
Content = scrollViewer = new ScrollViewer
{
Width = 200,
@ -102,7 +80,6 @@ namespace Avalonia.Layout.UnitTests
window.Resources["ScrollBarThickness"] = 10.0;
window.Show();
window.LayoutManager.ExecuteInitialLayoutPass(window);
Assert.Equal(new Size(800, 600), window.Bounds.Size);
Assert.Equal(new Size(200, 200), scrollViewer.Bounds.Size);
@ -134,84 +111,5 @@ namespace Avalonia.Layout.UnitTests
{
return v.Bounds.Position;
}
class FormattedTextMock : IFormattedTextImpl
{
public FormattedTextMock(string text)
{
Text = text;
}
public Size Constraint { get; set; }
public string Text { get; }
public Rect Bounds => Rect.Empty;
public void Dispose()
{
}
public IEnumerable<FormattedTextLine> GetLines() => new FormattedTextLine[0];
public TextHitTestResult HitTestPoint(Point point) => new TextHitTestResult();
public Rect HitTestTextPosition(int index) => new Rect();
public IEnumerable<Rect> HitTestTextRange(int index, int length) => new Rect[0];
public Size Measure() => Constraint;
}
private void RegisterServices()
{
var globalStyles = new Mock<IGlobalStyles>();
var globalStylesResources = globalStyles.As<IResourceNode>();
var outObj = (object)10;
globalStylesResources.Setup(x => x.TryGetResource("FontSizeNormal", out outObj)).Returns(true);
var renderInterface = new Mock<IPlatformRenderInterface>();
renderInterface.Setup(x =>
x.CreateFormattedText(
It.IsAny<string>(),
It.IsAny<Typeface>(),
It.IsAny<TextAlignment>(),
It.IsAny<TextWrapping>(),
It.IsAny<Size>(),
It.IsAny<IReadOnlyList<FormattedTextStyleSpan>>()))
.Returns(new FormattedTextMock("TEST"));
var streamGeometry = new Mock<IStreamGeometryImpl>();
streamGeometry.Setup(x =>
x.Open())
.Returns(new Mock<IStreamGeometryContextImpl>().Object);
renderInterface.Setup(x =>
x.CreateStreamGeometry())
.Returns(streamGeometry.Object);
var windowImpl = new Mock<IWindowImpl>();
Size clientSize = default(Size);
windowImpl.SetupGet(x => x.ClientSize).Returns(() => clientSize);
windowImpl.Setup(x => x.Resize(It.IsAny<Size>())).Callback<Size>(s => clientSize = s);
windowImpl.Setup(x => x.MaxClientSize).Returns(new Size(1024, 1024));
windowImpl.SetupGet(x => x.Scaling).Returns(1);
AvaloniaLocator.CurrentMutable
.Bind<IStandardCursorFactory>().ToConstant(new CursorFactoryMock())
.Bind<IAssetLoader>().ToConstant(new AssetLoader())
.Bind<IInputManager>().ToConstant(new Mock<IInputManager>().Object)
.Bind<IGlobalStyles>().ToConstant(globalStyles.Object)
.Bind<IRuntimePlatform>().ToConstant(new AppBuilder().RuntimePlatform)
.Bind<IPlatformRenderInterface>().ToConstant(renderInterface.Object)
.Bind<IStyler>().ToConstant(new Styler())
.Bind<IWindowingPlatform>().ToConstant(new Avalonia.Controls.UnitTests.WindowingPlatformMock(() => windowImpl.Object));
var theme = new DefaultTheme();
globalStyles.Setup(x => x.IsStylesInitialized).Returns(true);
globalStyles.Setup(x => x.Styles).Returns(theme);
}
}
}

33
tests/Avalonia.Layout.UnitTests/LayoutManagerTests.cs

@ -374,5 +374,38 @@ namespace Avalonia.Layout.UnitTests
Assert.True(control.Measured);
Assert.True(control.IsMeasureValid);
}
[Fact]
public void Calling_ExecuteLayoutPass_From_ExecuteInitialLayoutPass_Does_Not_Break_Measure()
{
// Test for issue #3550.
var control = new LayoutTestControl();
var root = new LayoutTestRoot { Child = control };
var count = 0;
root.LayoutManager.ExecuteInitialLayoutPass(root);
control.Measured = false;
control.DoMeasureOverride = (l, s) =>
{
if (count++ == 0)
{
control.InvalidateMeasure();
root.LayoutManager.ExecuteLayoutPass();
return new Size(100, 100);
}
else
{
return new Size(200, 200);
}
};
root.InvalidateMeasure();
control.InvalidateMeasure();
root.LayoutManager.ExecuteInitialLayoutPass(root);
Assert.Equal(new Size(200, 200), control.Bounds.Size);
Assert.Equal(new Size(200, 200), control.DesiredSize);
}
}
}

130
tests/Avalonia.LeakTests/ControlTests.cs

@ -4,12 +4,16 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.Remoting.Contexts;
using Avalonia.Controls;
using Avalonia.Controls.Templates;
using Avalonia.Diagnostics;
using Avalonia.Input;
using Avalonia.Layout;
using Avalonia.Media;
using Avalonia.Platform;
using Avalonia.Rendering;
using Avalonia.Styling;
using Avalonia.UnitTests;
using Avalonia.VisualTree;
using JetBrains.dotMemoryUnit;
@ -370,9 +374,133 @@ namespace Avalonia.LeakTests
}
}
[Fact]
public void Control_With_Style_RenderTransform_Is_Freed()
{
// # Issue #3545
using (Start())
{
Func<Window> run = () =>
{
var window = new Window
{
Styles =
{
new Style(x => x.OfType<Canvas>())
{
Setters =
{
new Setter
{
Property = Visual.RenderTransformProperty,
Value = new RotateTransform(45),
}
}
}
},
Content = new Canvas()
};
window.Show();
// Do a layout and make sure that Canvas gets added to visual tree with
// its render transform.
window.LayoutManager.ExecuteInitialLayoutPass(window);
var canvas = Assert.IsType<Canvas>(window.Presenter.Child);
Assert.IsType<RotateTransform>(canvas.RenderTransform);
// Clear the content and ensure the Canvas is removed.
window.Content = null;
window.LayoutManager.ExecuteLayoutPass();
Assert.Null(window.Presenter.Child);
return window;
};
var result = run();
dotMemory.Check(memory =>
Assert.Equal(0, memory.GetObjects(where => where.Type.Is<Canvas>()).ObjectsCount));
}
}
[Fact]
public void Attached_ContextMenu_Is_Freed()
{
using (Start())
{
void AttachShowAndDetachContextMenu(Control control)
{
var contextMenu = new ContextMenu
{
Items = new[]
{
new MenuItem { Header = "Foo" },
new MenuItem { Header = "Foo" },
}
};
control.ContextMenu = contextMenu;
contextMenu.Open(control);
contextMenu.Close();
control.ContextMenu = null;
}
var window = new Window();
window.Show();
Assert.Same(window, FocusManager.Instance.Current);
AttachShowAndDetachContextMenu(window);
dotMemory.Check(memory =>
Assert.Equal(0, memory.GetObjects(where => where.Type.Is<ContextMenu>()).ObjectsCount));
dotMemory.Check(memory =>
Assert.Equal(0, memory.GetObjects(where => where.Type.Is<MenuItem>()).ObjectsCount));
}
}
[Fact]
public void Standalone_ContextMenu_Is_Freed()
{
using (Start())
{
void BuildAndShowContextMenu(Control control)
{
var contextMenu = new ContextMenu
{
Items = new[]
{
new MenuItem { Header = "Foo" },
new MenuItem { Header = "Foo" },
}
};
contextMenu.Open(control);
contextMenu.Close();
}
var window = new Window();
window.Show();
Assert.Same(window, FocusManager.Instance.Current);
BuildAndShowContextMenu(window);
BuildAndShowContextMenu(window);
dotMemory.Check(memory =>
Assert.Equal(0, memory.GetObjects(where => where.Type.Is<ContextMenu>()).ObjectsCount));
dotMemory.Check(memory =>
Assert.Equal(0, memory.GetObjects(where => where.Type.Is<MenuItem>()).ObjectsCount));
}
}
private IDisposable Start()
{
return UnitTestApplication.Start(TestServices.StyledWindow);
return UnitTestApplication.Start(TestServices.StyledWindow.With(
focusManager: new FocusManager(),
keyboardDevice: () => new KeyboardDevice(),
inputManager: new InputManager()));
}
private class Node

2
tests/Avalonia.Markup.UnitTests/Avalonia.Markup.UnitTests.csproj

@ -1,6 +1,6 @@
<Project Sdk="Microsoft.NET.Sdk" ToolsVersion="15.0">
<PropertyGroup>
<TargetFrameworks>netcoreapp2.0;net47</TargetFrameworks>
<TargetFrameworks>netcoreapp3.1;net47</TargetFrameworks>
<OutputType>Library</OutputType>
<IsTestProject>true</IsTestProject>
</PropertyGroup>

2
tests/Avalonia.Markup.Xaml.UnitTests/Avalonia.Markup.Xaml.UnitTests.csproj

@ -1,6 +1,6 @@
<Project Sdk="Microsoft.NET.Sdk" ToolsVersion="15.0">
<PropertyGroup>
<TargetFrameworks>netcoreapp2.0;net47</TargetFrameworks>
<TargetFrameworks>netcoreapp3.1;net47</TargetFrameworks>
<OutputType>Library</OutputType>
<IsTestProject>true</IsTestProject>
</PropertyGroup>

62
tests/Avalonia.Markup.Xaml.UnitTests/Xaml/StyleTests.cs

@ -278,5 +278,67 @@ namespace Avalonia.Markup.Xaml.UnitTests.Xaml
Assert.Equal(Colors.Red, ((ISolidColorBrush)notFoo.Background).Color);
}
}
[Fact]
public void Style_Can_Use_Or_Selector_1()
{
using (UnitTestApplication.Start(TestServices.StyledWindow))
{
var xaml = @"
<Window xmlns='https://github.com/avaloniaui'
xmlns:x='http://schemas.microsoft.com/winfx/2006/xaml'>
<Window.Styles>
<Style Selector='Border.foo, Border.bar'>
<Setter Property='Background' Value='Red'/>
</Style>
</Window.Styles>
<StackPanel>
<Border Name='foo' Classes='foo'/>
<Border Name='bar' Classes='bar'/>
<Border Name='baz' Classes='baz'/>
</StackPanel>
</Window>";
var loader = new AvaloniaXamlLoader();
var window = (Window)loader.Load(xaml);
var foo = window.FindControl<Border>("foo");
var bar = window.FindControl<Border>("bar");
var baz = window.FindControl<Border>("baz");
Assert.Equal(Brushes.Red, foo.Background);
Assert.Equal(Brushes.Red, bar.Background);
Assert.Null(baz.Background);
}
}
[Fact]
public void Style_Can_Use_Or_Selector_2()
{
using (UnitTestApplication.Start(TestServices.StyledWindow))
{
var xaml = @"
<Window xmlns='https://github.com/avaloniaui'
xmlns:x='http://schemas.microsoft.com/winfx/2006/xaml'>
<Window.Styles>
<Style Selector='Button,Carousel,ListBox'>
<Setter Property='Background' Value='Red'/>
</Style>
</Window.Styles>
<StackPanel>
<Button Name='button'/>
<Carousel Name='carousel'/>
<ListBox Name='listBox'/>
</StackPanel>
</Window>";
var loader = new AvaloniaXamlLoader();
var window = (Window)loader.Load(xaml);
var button = window.FindControl<Button>("button");
var carousel = window.FindControl<Carousel>("carousel");
var listBox = window.FindControl<ListBox>("listBox");
Assert.Equal(Brushes.Red, button.Background);
Assert.Equal(Brushes.Red, carousel.Background);
Assert.Equal(Brushes.Red, listBox.Background);
}
}
}
}

2
tests/Avalonia.ReactiveUI.UnitTests/Avalonia.ReactiveUI.UnitTests.csproj

@ -1,6 +1,6 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netcoreapp2.0</TargetFramework>
<TargetFramework>netcoreapp3.1</TargetFramework>
</PropertyGroup>
<Import Project="..\..\build\UnitTests.NetCore.targets" />
<Import Project="..\..\build\Moq.props" />

2
tests/Avalonia.Skia.RenderTests/Avalonia.Skia.RenderTests.csproj

@ -1,6 +1,6 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFrameworks>netcoreapp2.0</TargetFrameworks>
<TargetFrameworks>netcoreapp3.1</TargetFrameworks>
<DefineConstants>AVALONIA_SKIA;AVALONIA_SKIA_SKIP_FAIL</DefineConstants>
</PropertyGroup>
<ItemGroup>

2
tests/Avalonia.Skia.UnitTests/Avalonia.Skia.UnitTests.csproj

@ -1,6 +1,6 @@
<Project Sdk="Microsoft.NET.Sdk" ToolsVersion="15.0">
<PropertyGroup>
<TargetFrameworks>netcoreapp2.0</TargetFrameworks>
<TargetFrameworks>netcoreapp3.1</TargetFrameworks>
</PropertyGroup>
<Import Project="..\..\build\UnitTests.NetCore.targets" />
<Import Project="..\..\build\Moq.props" />

2
tests/Avalonia.Styling.UnitTests/Avalonia.Styling.UnitTests.csproj

@ -1,6 +1,6 @@
<Project Sdk="Microsoft.NET.Sdk" ToolsVersion="15.0">
<PropertyGroup>
<TargetFrameworks>netcoreapp2.0;net47</TargetFrameworks>
<TargetFrameworks>netcoreapp3.1;net47</TargetFrameworks>
<OutputType>Library</OutputType>
<NoWarn>CS0067</NoWarn>
<IsTestProject>true</IsTestProject>

115
tests/Avalonia.UnitTests/MockWindowingPlatform.cs

@ -8,61 +8,122 @@ namespace Avalonia.UnitTests
{
public class MockWindowingPlatform : IWindowingPlatform
{
private static readonly Size s_screenSize = new Size(1280, 1024);
private readonly Func<IWindowImpl> _windowImpl;
private readonly Func<IPopupImpl> _popupImpl;
private readonly Func<IWindowBaseImpl, IPopupImpl> _popupImpl;
public MockWindowingPlatform(Func<IWindowImpl> windowImpl = null, Func<IPopupImpl> popupImpl = null )
public MockWindowingPlatform(
Func<IWindowImpl> windowImpl = null,
Func<IWindowBaseImpl, IPopupImpl> popupImpl = null )
{
_windowImpl = windowImpl;
_popupImpl = popupImpl;
}
public static Mock<IWindowImpl> CreateWindowMock(Func<IPopupImpl> popupImpl = null)
public static Mock<IWindowImpl> CreateWindowMock()
{
var win = Mock.Of<IWindowImpl>(x => x.Scaling == 1);
var mock = Mock.Get(win);
mock.Setup(x => x.CreatePopup()).Returns(() =>
var windowImpl = new Mock<IWindowImpl>();
var position = new PixelPoint();
var clientSize = new Size(800, 600);
windowImpl.SetupAllProperties();
windowImpl.Setup(x => x.ClientSize).Returns(() => clientSize);
windowImpl.Setup(x => x.Scaling).Returns(1);
windowImpl.Setup(x => x.Screen).Returns(CreateScreenMock().Object);
windowImpl.Setup(x => x.Position).Returns(() => position);
SetupToplevel(windowImpl);
windowImpl.Setup(x => x.CreatePopup()).Returns(() =>
{
return CreatePopupMock(windowImpl.Object).Object;
});
windowImpl.Setup(x => x.Dispose()).Callback(() =>
{
if (popupImpl != null)
return popupImpl();
return CreatePopupMock().Object;
windowImpl.Object.Closed?.Invoke();
});
windowImpl.Setup(x => x.Move(It.IsAny<PixelPoint>())).Callback<PixelPoint>(x =>
{
position = x;
windowImpl.Object.PositionChanged?.Invoke(x);
});
mock.Setup(x => x.Dispose()).Callback(() =>
windowImpl.Setup(x => x.Resize(It.IsAny<Size>())).Callback<Size>(x =>
{
mock.Object.Closed?.Invoke();
clientSize = x.Constrain(s_screenSize);
windowImpl.Object.Resized?.Invoke(clientSize);
});
PixelPoint pos = default;
mock.SetupGet(x => x.Position).Returns(() => pos);
mock.Setup(x => x.Move(It.IsAny<PixelPoint>())).Callback(new Action<PixelPoint>(np => pos = np));
SetupToplevel(mock);
return mock;
windowImpl.Setup(x => x.Show()).Callback(() =>
{
windowImpl.Object.Activated?.Invoke();
});
return windowImpl;
}
static void SetupToplevel<T>(Mock<T> mock) where T : class, ITopLevelImpl
public static Mock<IPopupImpl> CreatePopupMock(IWindowBaseImpl parent)
{
mock.SetupGet(x => x.MouseDevice).Returns(new MouseDevice());
var popupImpl = new Mock<IPopupImpl>();
var clientSize = new Size();
var positionerHelper = new ManagedPopupPositionerPopupImplHelper(parent, (pos, size, scale) =>
{
clientSize = size.Constrain(s_screenSize);
popupImpl.Object.PositionChanged?.Invoke(pos);
popupImpl.Object.Resized?.Invoke(clientSize);
});
var positioner = new ManagedPopupPositioner(positionerHelper);
popupImpl.SetupAllProperties();
popupImpl.Setup(x => x.ClientSize).Returns(() => clientSize);
popupImpl.Setup(x => x.Scaling).Returns(1);
popupImpl.Setup(x => x.PopupPositioner).Returns(positioner);
SetupToplevel(popupImpl);
return popupImpl;
}
public static Mock<IPopupImpl> CreatePopupMock()
public static Mock<IScreenImpl> CreateScreenMock()
{
var positioner = Mock.Of<IPopupPositioner>();
var popup = Mock.Of<IPopupImpl>(x => x.Scaling == 1);
var mock = Mock.Get(popup);
mock.SetupGet(x => x.PopupPositioner).Returns(positioner);
SetupToplevel(mock);
return mock;
var screenImpl = new Mock<IScreenImpl>();
var bounds = new PixelRect(0, 0, (int)s_screenSize.Width, (int)s_screenSize.Height);
var screen = new Screen(96, bounds, bounds, true);
screenImpl.Setup(x => x.AllScreens).Returns(new[] { screen });
screenImpl.Setup(x => x.ScreenCount).Returns(1);
return screenImpl;
}
public IWindowImpl CreateWindow()
{
return _windowImpl?.Invoke() ?? CreateWindowMock(_popupImpl).Object;
if (_windowImpl is object)
{
return _windowImpl();
}
else
{
var mock = CreateWindowMock();
if (_popupImpl is object)
{
mock.Setup(x => x.CreatePopup()).Returns(() => _popupImpl(mock.Object));
}
return mock.Object;
}
}
public IEmbeddableWindowImpl CreateEmbeddableWindow()
{
throw new NotImplementedException();
}
private static void SetupToplevel<T>(Mock<T> mock) where T : class, ITopLevelImpl
{
mock.SetupGet(x => x.MouseDevice).Returns(new MouseDevice());
}
}
}

2
tests/Avalonia.Visuals.UnitTests/Avalonia.Visuals.UnitTests.csproj

@ -1,6 +1,6 @@
<Project Sdk="Microsoft.NET.Sdk" ToolsVersion="15.0">
<PropertyGroup>
<TargetFrameworks>netcoreapp2.0;net47</TargetFrameworks>
<TargetFrameworks>netcoreapp3.1;net47</TargetFrameworks>
<OutputType>Library</OutputType>
<IsTestProject>true</IsTestProject>
</PropertyGroup>

Loading…
Cancel
Save