Browse Source

Merge branch 'master' into refactor/2333-remove-memberselector

pull/2690/head
Steven Kirk 7 years ago
committed by GitHub
parent
commit
ecb437086a
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 8
      .gitignore
  2. 2
      azure-pipelines.yml
  3. 0
      build-native.sh
  4. 2
      build/Base.props
  5. 2
      build/SharedVersion.props
  6. 21
      native/Avalonia.Native/src/OSX/KeyTransform.mm
  7. 37
      nukebuild/Build.cs
  8. 3
      readme.md
  9. 2
      samples/ControlCatalog.Android/ControlCatalog.Android.csproj
  10. 2
      src/Android/Avalonia.Android/Avalonia.Android.csproj
  11. 3
      src/Android/Avalonia.AndroidTestApplication/Avalonia.AndroidTestApplication.csproj
  12. 2
      src/Avalonia.Base/Data/BindingOperations.cs
  13. 2
      src/Avalonia.Base/Data/Converters/DefaultValueConverter.cs
  14. 31
      src/Avalonia.Base/Data/Core/BindingExpression.cs
  15. 106
      src/Avalonia.Base/Utilities/SynchronousCompletionAsyncResult.cs
  16. 10
      src/Avalonia.Build.Tasks/XamlCompilerTaskExecutor.cs
  17. 2
      src/Avalonia.Controls/ApplicationLifetimes/ControlledApplicationLifetimeExitEventArgs.cs
  18. 2
      src/Avalonia.Controls/ApplicationLifetimes/StartupEventArgs.cs
  19. 2
      src/Avalonia.Controls/AutoCompleteBox.cs
  20. 39
      src/Avalonia.Controls/ContextMenu.cs
  21. 21
      src/Avalonia.Controls/Embedding/EmbeddableControlRoot.cs
  22. 19
      src/Avalonia.Controls/Embedding/Offscreen/OffscreenTopLevel.cs
  23. 14
      src/Avalonia.Controls/Generators/TreeItemContainerGenerator.cs
  24. 8
      src/Avalonia.Controls/ListBox.cs
  25. 46
      src/Avalonia.Controls/Menu.cs
  26. 5
      src/Avalonia.Controls/MenuBase.cs
  27. 6
      src/Avalonia.Controls/Presenters/ContentPresenter.cs
  28. 35
      src/Avalonia.Controls/Primitives/Popup.cs
  29. 38
      src/Avalonia.Controls/Primitives/SelectingItemsControl.cs
  30. 30
      src/Avalonia.Controls/Primitives/TemplatedControl.cs
  31. 4
      src/Avalonia.Controls/ShutdownMode.cs
  32. 10
      src/Avalonia.Controls/Templates/FuncControlTemplate.cs
  33. 6
      src/Avalonia.Controls/Templates/FuncControlTemplate`2.cs
  34. 8
      src/Avalonia.Controls/Templates/FuncDataTemplate.cs
  35. 26
      src/Avalonia.Controls/Templates/FuncDataTemplate`1.cs
  36. 14
      src/Avalonia.Controls/Templates/FuncTemplateNameScopeExtensions.cs
  37. 15
      src/Avalonia.Controls/Templates/FuncTemplate`2.cs
  38. 4
      src/Avalonia.Controls/Templates/FuncTreeDataTemplate.cs
  39. 15
      src/Avalonia.Controls/Templates/FuncTreeDataTemplate`1.cs
  40. 22
      src/Avalonia.Controls/Templates/IControlTemplate.cs
  41. 4
      src/Avalonia.Controls/Templates/ITemplate`2.cs
  42. 34
      src/Avalonia.Controls/UserControl.cs
  43. 34
      src/Avalonia.Controls/Window.cs
  44. 18
      src/Avalonia.OpenGL/GlInterface.cs
  45. 4
      src/Avalonia.ReactiveUI/AutoDataTemplateBindingHook.cs
  46. 87
      src/Avalonia.ReactiveUI/AutoSuspendHelper.cs
  47. 73
      src/Avalonia.Styling/Controls/ChildNameScope.cs
  48. 34
      src/Avalonia.Styling/Controls/INameScope.cs
  49. 99
      src/Avalonia.Styling/Controls/NameScope.cs
  50. 41
      src/Avalonia.Styling/Controls/NameScopeExtensions.cs
  51. 64
      src/Avalonia.Styling/Controls/NameScopeLocator.cs
  52. 74
      src/Avalonia.Styling/LogicalTree/ControlLocator.cs
  53. 21
      src/Avalonia.Styling/StyledElement.cs
  54. 1
      src/Avalonia.Styling/Styling/Setter.cs
  55. 2
      src/Avalonia.Styling/Styling/Styles.cs
  56. 1
      src/Avalonia.Themes.Default/Avalonia.Themes.Default.csproj
  57. 5
      src/Avalonia.Visuals/Rendering/RenderLayers.cs
  58. 13
      src/Avalonia.X11/Glx/GlxDisplay.cs
  59. 10
      src/Avalonia.X11/X11KeyTransform.cs
  60. 8
      src/Avalonia.X11/X11Platform.cs
  61. 15
      src/Avalonia.X11/X11Window.cs
  62. 3
      src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/BindingExtension.cs
  63. 4
      src/Markup/Avalonia.Markup.Xaml/Templates/ControlTemplate.cs
  64. 4
      src/Markup/Avalonia.Markup.Xaml/Templates/DataTemplate.cs
  65. 4
      src/Markup/Avalonia.Markup.Xaml/Templates/ItemsPanelTemplate.cs
  66. 4
      src/Markup/Avalonia.Markup.Xaml/Templates/Template.cs
  67. 5
      src/Markup/Avalonia.Markup.Xaml/Templates/TemplateContent.cs
  68. 4
      src/Markup/Avalonia.Markup.Xaml/Templates/TreeDataTemplate.cs
  69. 6
      src/Markup/Avalonia.Markup.Xaml/XamlIl/AvaloniaXamlIlRuntimeCompiler.cs
  70. 22
      src/Markup/Avalonia.Markup.Xaml/XamlIl/CompilerExtensions/AvaloniaXamlIlLanguage.cs
  71. 107
      src/Markup/Avalonia.Markup.Xaml/XamlIl/CompilerExtensions/Transformers/AddNameScopeRegistration.cs
  72. 26
      src/Markup/Avalonia.Markup.Xaml/XamlIl/CompilerExtensions/Transformers/AvaloniaXamlIlWellKnownTypes.cs
  73. 36
      src/Markup/Avalonia.Markup.Xaml/XamlIl/Runtime/XamlIlRuntimeHelpers.cs
  74. 2
      src/Markup/Avalonia.Markup.Xaml/XamlIl/xamlil.github
  75. 8
      src/Markup/Avalonia.Markup.Xaml/XamlTypes.cs
  76. 14
      src/Markup/Avalonia.Markup/Data/Binding.cs
  77. 7
      src/Markup/Avalonia.Markup/Data/MultiBinding.cs
  78. 6
      src/Markup/Avalonia.Markup/Markup/Parsers/ExpressionObserverBuilder.cs
  79. 7
      src/Markup/Avalonia.Markup/Markup/Parsers/ExpressionParser.cs
  80. 13
      src/Markup/Avalonia.Markup/Markup/Parsers/Nodes/ElementNameNode.cs
  81. 1
      src/Skia/Avalonia.Skia/DrawingContextImpl.cs
  82. 1
      src/Skia/Avalonia.Skia/ISkiaDrawingContextImpl.cs
  83. 8
      src/Windows/Avalonia.Win32/Interop/UnmanagedMethods.cs
  84. 4
      src/Windows/Avalonia.Win32/WindowImpl.cs
  85. 78
      tests/Avalonia.Base.UnitTests/AvaloniaObjectTests_DataValidation.cs
  86. 8
      tests/Avalonia.Controls.UnitTests/AutoCompleteBoxTests.cs
  87. 4
      tests/Avalonia.Controls.UnitTests/CarouselTests.cs
  88. 8
      tests/Avalonia.Controls.UnitTests/ComboBoxTests.cs
  89. 10
      tests/Avalonia.Controls.UnitTests/ContentControlTests.cs
  90. 54
      tests/Avalonia.Controls.UnitTests/ContextMenuTests.cs
  91. 10
      tests/Avalonia.Controls.UnitTests/DatePickerTests.cs
  92. 21
      tests/Avalonia.Controls.UnitTests/GridSplitterTests.cs
  93. 4
      tests/Avalonia.Controls.UnitTests/HeaderedItemsControlTests .cs
  94. 31
      tests/Avalonia.Controls.UnitTests/ItemsControlTests.cs
  95. 30
      tests/Avalonia.Controls.UnitTests/ListBoxTests.cs
  96. 11
      tests/Avalonia.Controls.UnitTests/ListBoxTests_Single.cs
  97. 13
      tests/Avalonia.Controls.UnitTests/Mixins/ContentControlMixinTests.cs
  98. 132
      tests/Avalonia.Controls.UnitTests/NameScopeTests.cs
  99. 29
      tests/Avalonia.Controls.UnitTests/Presenters/ContentPresenterTests_InTemplate.cs
  100. 16
      tests/Avalonia.Controls.UnitTests/Presenters/ContentPresenterTests_Standalone.cs

8
.gitignore

@ -198,3 +198,11 @@ info.plist
build-intermediate
obj-Direct2D1/
obj-Skia/
##################
# Vim
##################
.vim
coc-settings.json
.ccls-cache
.ccls

2
azure-pipelines.yml

@ -102,7 +102,7 @@ jobs:
- job: Windows
pool:
vmImage: 'vs2017-win2016'
vmImage: 'windows-2019'
steps:
- task: CmdLine@2
displayName: 'Install Nuke'

0
build-native.sh

2
build/Base.props

@ -2,4 +2,4 @@
<ItemGroup>
<PackageReference Include="System.ValueTuple" Version="4.5.0" />
</ItemGroup>
</Project>
</Project>

2
build/SharedVersion.props

@ -2,7 +2,7 @@
xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
<Product>Avalonia</Product>
<Version>0.8.1</Version>
<Version>0.8.999</Version>
<Copyright>Copyright 2019 &#169; The AvaloniaUI Project</Copyright>
<PackageLicenseUrl>https://github.com/AvaloniaUI/Avalonia/blob/master/licence.md</PackageLicenseUrl>
<PackageProjectUrl>https://github.com/AvaloniaUI/Avalonia/</PackageProjectUrl>

21
native/Avalonia.Native/src/OSX/KeyTransform.mm

@ -26,7 +26,7 @@ const int kVK_ANSI_3 = 0x14;
const int kVK_ANSI_4 = 0x15;
const int kVK_ANSI_6 = 0x16;
const int kVK_ANSI_5 = 0x17;
//const int kVK_ANSI_Equal = 0x18;
const int kVK_ANSI_Equal = 0x18;
const int kVK_ANSI_9 = 0x19;
const int kVK_ANSI_7 = 0x1A;
const int kVK_ANSI_Minus = 0x1B;
@ -45,11 +45,11 @@ const int kVK_ANSI_K = 0x28;
const int kVK_ANSI_Semicolon = 0x29;
const int kVK_ANSI_Backslash = 0x2A;
const int kVK_ANSI_Comma = 0x2B;
//const int kVK_ANSI_Slash = 0x2C;
const int kVK_ANSI_Slash = 0x2C;
const int kVK_ANSI_N = 0x2D;
const int kVK_ANSI_M = 0x2E;
const int kVK_ANSI_Period = 0x2F;
//const int kVK_ANSI_Grave = 0x32;
const int kVK_ANSI_Grave = 0x32;
const int kVK_ANSI_KeypadDecimal = 0x41;
const int kVK_ANSI_KeypadMultiply = 0x43;
const int kVK_ANSI_KeypadPlus = 0x45;
@ -57,7 +57,7 @@ const int kVK_ANSI_KeypadClear = 0x47;
const int kVK_ANSI_KeypadDivide = 0x4B;
const int kVK_ANSI_KeypadEnter = 0x4C;
const int kVK_ANSI_KeypadMinus = 0x4E;
//const int kVK_ANSI_KeypadEquals = 0x51;
const int kVK_ANSI_KeypadEquals = 0x51;
const int kVK_ANSI_Keypad0 = 0x52;
const int kVK_ANSI_Keypad1 = 0x53;
const int kVK_ANSI_Keypad2 = 0x54;
@ -121,7 +121,7 @@ const int kVK_UpArrow = 0x7E;
//const int kVK_JIS_Underscore = 0x5E;
//const int kVK_JIS_KeypadComma = 0x5F;
//const int kVK_JIS_Eisu = 0x66;
//const int kVK_JIS_Kana = 0x68;
const int kVK_JIS_Kana = 0x68;
std::map<int, AvnKey> s_KeyMap =
{
@ -148,7 +148,7 @@ const int kVK_UpArrow = 0x7E;
{kVK_ANSI_4, D4},
{kVK_ANSI_6, D6},
{kVK_ANSI_5, D5},
//{kVK_ANSI_Equal, ?},
{kVK_ANSI_Equal, OemPlus},
{kVK_ANSI_9, D9},
{kVK_ANSI_7, D7},
{kVK_ANSI_Minus, OemMinus},
@ -167,11 +167,11 @@ const int kVK_UpArrow = 0x7E;
{kVK_ANSI_Semicolon, OemSemicolon},
{kVK_ANSI_Backslash, OemBackslash},
{kVK_ANSI_Comma, OemComma},
//{kVK_ANSI_Slash, ?},
{kVK_ANSI_Slash, Oem2},
{kVK_ANSI_N, N},
{kVK_ANSI_M, M},
{kVK_ANSI_Period, OemPeriod},
//{kVK_ANSI_Grave, ?},
{kVK_ANSI_Grave, OemTilde},
{kVK_ANSI_KeypadDecimal, Decimal},
{kVK_ANSI_KeypadMultiply, Multiply},
{kVK_ANSI_KeypadPlus, OemPlus},
@ -179,7 +179,7 @@ const int kVK_UpArrow = 0x7E;
{kVK_ANSI_KeypadDivide, Divide},
{kVK_ANSI_KeypadEnter, AvnKeyEnter},
{kVK_ANSI_KeypadMinus, OemMinus},
//{kVK_ANSI_KeypadEquals, ?},
{kVK_ANSI_KeypadEquals, OemPlus},
{kVK_ANSI_Keypad0, NumPad0},
{kVK_ANSI_Keypad1, NumPad1},
{kVK_ANSI_Keypad2, NumPad2},
@ -237,5 +237,6 @@ const int kVK_UpArrow = 0x7E;
{kVK_LeftArrow, Left},
{kVK_RightArrow, Right},
{kVK_DownArrow, Down},
{kVK_UpArrow, Up}
{kVK_UpArrow, Up},
{kVK_JIS_Kana, AvnKeyKanaMode},
};

37
nukebuild/Build.cs

@ -89,6 +89,29 @@ partial class Build : NukeBuild
}
IReadOnlyCollection<Output> MsBuildCommon(
string projectFile,
Configure<MSBuildSettings> configurator = null)
{
return MSBuild(projectFile, c =>
{
// 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;
});
}
Target Clean => _ => _.Executes(() =>
{
DeleteDirectories(Parameters.BuildDirs);
@ -105,13 +128,8 @@ partial class Build : NukeBuild
.Executes(() =>
{
if (Parameters.IsRunningOnWindows)
MSBuild(Parameters.MSBuildSolution, c => c
MsBuildCommon(Parameters.MSBuildSolution, c => c
.SetArgumentConfigurator(a => a.Add("/r"))
.SetConfiguration(Parameters.Configuration)
.SetVerbosity(MSBuildVerbosity.Minimal)
.AddProperty("PackageVersion", Parameters.Version)
.AddProperty("iOSRoslynPathHackRequired", "true")
.SetToolPath(MsBuildExe.Value)
.AddTargets("Build")
);
@ -237,12 +255,7 @@ partial class Build : NukeBuild
{
if (Parameters.IsRunningOnWindows)
MSBuild(Parameters.MSBuildSolution, c => c
.SetConfiguration(Parameters.Configuration)
.SetVerbosity(MSBuildVerbosity.Minimal)
.AddProperty("PackageVersion", Parameters.Version)
.AddProperty("iOSRoslynPathHackRequired", "true")
.SetToolPath(MsBuildExe.Value)
MsBuildCommon(Parameters.MSBuildSolution, c => c
.AddTargets("Pack"));
else
DotNetPack(Parameters.MSBuildSolution, c =>

3
readme.md

@ -32,9 +32,6 @@ Install-Package Avalonia.Desktop
## Bleeding Edge Builds
Try out the latest build of Avalonia available for download here:
https://ci.appveyor.com/project/AvaloniaUI/Avalonia/branch/master/artifacts
or use nightly build feeds as described here:
https://github.com/AvaloniaUI/Avalonia/wiki/Using-nightly-build-feed

2
samples/ControlCatalog.Android/ControlCatalog.Android.csproj

@ -16,7 +16,7 @@
<AndroidResgenFile>Resources\Resource.Designer.cs</AndroidResgenFile>
<GenerateSerializationAssemblies>Off</GenerateSerializationAssemblies>
<AndroidUseLatestPlatformSdk>False</AndroidUseLatestPlatformSdk>
<TargetFrameworkVersion>v4.4</TargetFrameworkVersion>
<TargetFrameworkVersion>v8.0</TargetFrameworkVersion>
<AndroidManifest>Properties\AndroidManifest.xml</AndroidManifest>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">

2
src/Android/Avalonia.Android/Avalonia.Android.csproj

@ -1,6 +1,6 @@
<Project Sdk="MSBuild.Sdk.Extras">
<PropertyGroup>
<TargetFramework>monoandroid44</TargetFramework>
<TargetFramework>monoandroid80</TargetFramework>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
</PropertyGroup>
<ItemGroup>

3
src/Android/Avalonia.AndroidTestApplication/Avalonia.AndroidTestApplication.csproj

@ -16,7 +16,7 @@
<AndroidResgenFile>Resources\Resource.Designer.cs</AndroidResgenFile>
<GenerateSerializationAssemblies>Off</GenerateSerializationAssemblies>
<AndroidUseLatestPlatformSdk>False</AndroidUseLatestPlatformSdk>
<TargetFrameworkVersion>v4.4</TargetFrameworkVersion>
<TargetFrameworkVersion>v8.0</TargetFrameworkVersion>
<AndroidManifest>Properties\AndroidManifest.xml</AndroidManifest>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
@ -150,6 +150,7 @@
</ItemGroup>
<Import Project="$(MSBuildExtensionsPath)\Xamarin\Android\Xamarin.Android.CSharp.targets" />
<Import Project="..\..\..\build\Serilog.props" />
<Import Project="..\..\..\build\Base.props" />
<Import Project="..\..\..\build\Rx.props" />
<Import Project="..\..\..\build\System.Memory.props" />
<Import Project="..\..\..\build\AndroidWorkarounds.props" />

2
src/Avalonia.Base/Data/BindingOperations.cs

@ -10,6 +10,8 @@ namespace Avalonia.Data
{
public static class BindingOperations
{
public static readonly object DoNothing = new object();
/// <summary>
/// Applies an <see cref="InstancedBinding"/> a property on an <see cref="IAvaloniaObject"/>.
/// </summary>

2
src/Avalonia.Base/Data/Converters/DefaultValueConverter.cs

@ -31,7 +31,7 @@ namespace Avalonia.Data.Converters
{
if (value == null)
{
return AvaloniaProperty.UnsetValue;
return targetType.IsValueType ? AvaloniaProperty.UnsetValue : null;
}
if (typeof(ICommand).IsAssignableFrom(targetType) && value is Delegate d && d.Method.GetParameters().Length <= 1)

31
src/Avalonia.Base/Data/Core/BindingExpression.cs

@ -114,6 +114,11 @@ namespace Avalonia.Data.Core
/// <inheritdoc/>
public void OnNext(object value)
{
if (value == BindingOperations.DoNothing)
{
return;
}
using (_inner.Subscribe(_ => { }))
{
var type = _inner.ResultType;
@ -126,6 +131,11 @@ namespace Avalonia.Data.Core
ConverterParameter,
CultureInfo.CurrentCulture);
if (converted == BindingOperations.DoNothing)
{
return;
}
if (converted == AvaloniaProperty.UnsetValue)
{
converted = TypeUtilities.Default(type);
@ -186,6 +196,11 @@ namespace Avalonia.Data.Core
/// <inheritdoc/>
private object ConvertValue(object value)
{
if (value == BindingOperations.DoNothing)
{
return value;
}
var notification = value as BindingNotification;
if (notification == null)
@ -196,6 +211,11 @@ namespace Avalonia.Data.Core
ConverterParameter,
CultureInfo.CurrentCulture);
if (converted == BindingOperations.DoNothing)
{
return converted;
}
notification = converted as BindingNotification;
if (notification?.ErrorType == BindingErrorType.None)
@ -327,7 +347,18 @@ namespace Avalonia.Data.Core
public void OnNext(object value)
{
if (value == BindingOperations.DoNothing)
{
return;
}
var converted = _owner.ConvertValue(value);
if (converted == BindingOperations.DoNothing)
{
return;
}
_owner._value = new WeakReference<object>(converted);
_owner.PublishNext(converted);
}

106
src/Avalonia.Base/Utilities/SynchronousCompletionAsyncResult.cs

@ -0,0 +1,106 @@
using System;
using System.Collections.Generic;
using System.Runtime.CompilerServices;
namespace Avalonia.Utilities
{
/// <summary>
/// A task-like operation that is guaranteed to finish continuations synchronously,
/// can be used for parametrized one-shot events
/// </summary>
public struct SynchronousCompletionAsyncResult<T> : INotifyCompletion
{
private readonly SynchronousCompletionAsyncResultSource<T> _source;
private readonly T _result;
private readonly bool _isValid;
internal SynchronousCompletionAsyncResult(SynchronousCompletionAsyncResultSource<T> source)
{
_source = source;
_result = default;
_isValid = true;
}
public SynchronousCompletionAsyncResult(T result)
{
_result = result;
_source = null;
_isValid = true;
}
static void ThrowNotInitialized() =>
throw new InvalidOperationException("This SynchronousCompletionAsyncResult was not initialized");
public bool IsCompleted
{
get
{
if (!_isValid)
ThrowNotInitialized();
return _source == null || _source.IsCompleted;
}
}
public T GetResult()
{
if (!_isValid)
ThrowNotInitialized();
return _source == null ? _result : _source.Result;
}
public void OnCompleted(Action continuation)
{
if (!_isValid)
ThrowNotInitialized();
if (_source == null)
continuation();
else
_source.OnCompleted(continuation);
}
public SynchronousCompletionAsyncResult<T> GetAwaiter() => this;
}
/// <summary>
/// Source for incomplete SynchronousCompletionAsyncResult
/// </summary>
/// <typeparam name="T"></typeparam>
public class SynchronousCompletionAsyncResultSource<T>
{
private T _result;
internal bool IsCompleted { get; private set; }
public SynchronousCompletionAsyncResult<T> AsyncResult => new SynchronousCompletionAsyncResult<T>(this);
internal T Result => IsCompleted ?
_result :
throw new InvalidOperationException("Asynchronous operation is not yet completed");
private List<Action> _continuations;
internal void OnCompleted(Action continuation)
{
if(_continuations==null)
_continuations = new List<Action>();
_continuations.Add(continuation);
}
public void SetResult(T result)
{
if (IsCompleted)
throw new InvalidOperationException("Asynchronous operation is already completed");
_result = result;
IsCompleted = true;
if(_continuations!=null)
foreach (var c in _continuations)
c();
_continuations = null;
}
public void TrySetResult(T result)
{
if(IsCompleted)
return;
SetResult(result);
}
}
}

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

@ -75,9 +75,9 @@ namespace Avalonia.Build.Tasks
.First(c => c.Parameters.Count == 1));
var runtimeHelpers = typeSystem.GetType("Avalonia.Markup.Xaml.XamlIl.Runtime.XamlIlRuntimeHelpers");
var rootServiceProviderField = asm.MainModule.ImportReference(
typeSystem.GetTypeReference(runtimeHelpers).Resolve().Fields
.First(x => x.Name == "RootServiceProviderV1"));
var createRootServiceProviderMethod = asm.MainModule.ImportReference(
typeSystem.GetTypeReference(runtimeHelpers).Resolve().Methods
.First(x => x.Name == "CreateRootServiceProviderV2"));
var loaderDispatcherDef = new TypeDefinition("CompiledAvaloniaXaml", "!XamlLoader",
TypeAttributes.Class, asm.MainModule.TypeSystem.Object);
@ -211,7 +211,7 @@ namespace Avalonia.Build.Tasks
trampoline.Parameters.Add(new ParameterDefinition(classTypeDefinition));
classTypeDefinition.Methods.Add(trampoline);
var regularStart = Instruction.Create(OpCodes.Ldsfld, rootServiceProviderField);
var regularStart = Instruction.Create(OpCodes.Call, createRootServiceProviderMethod);
trampoline.Body.Instructions.Add(Instruction.Create(OpCodes.Ldsfld, designLoaderField));
trampoline.Body.Instructions.Add(Instruction.Create(OpCodes.Brfalse, regularStart));
@ -307,7 +307,7 @@ namespace Avalonia.Build.Tasks
i.Add(Instruction.Create(OpCodes.Newobj, parameterlessConstructor));
else
{
i.Add(Instruction.Create(OpCodes.Ldsfld, rootServiceProviderField));
i.Add(Instruction.Create(OpCodes.Call, createRootServiceProviderMethod));
i.Add(Instruction.Create(OpCodes.Call, compiledBuildMethod));
}

2
src/Avalonia.Controls/ApplicationLifetimes/ControlledApplicationLifetimeExitEventArgs.cs

@ -6,7 +6,7 @@ using System;
namespace Avalonia.Controls.ApplicationLifetimes
{
/// <summary>
/// Contains the arguments for the <see cref="IClassicDesktopStyleApplicationLifetime.Exit"/> event.
/// Contains the arguments for the <see cref="IControlledApplicationLifetime.Exit"/> event.
/// </summary>
public class ControlledApplicationLifetimeExitEventArgs : EventArgs
{

2
src/Avalonia.Controls/ApplicationLifetimes/StartupEventArgs.cs

@ -8,7 +8,7 @@ using System.Linq;
namespace Avalonia.Controls.ApplicationLifetimes
{
/// <summary>
/// Contains the arguments for the <see cref="IClassicDesktopStyleApplicationLifetime.Startup"/> event.
/// Contains the arguments for the <see cref="IControlledApplicationLifetime.Startup"/> event.
/// </summary>
public class ControlledApplicationLifetimeStartupEventArgs : EventArgs
{

2
src/Avalonia.Controls/AutoCompleteBox.cs

@ -788,7 +788,7 @@ namespace Avalonia.Controls
var template =
new FuncDataTemplate(
typeof(object),
o =>
(o, _) =>
{
var control = new ContentControl();
control.Bind(ContentControl.ContentProperty, value);

39
src/Avalonia.Controls/ContextMenu.cs

@ -1,12 +1,12 @@
using System;
using System.ComponentModel;
using System.Linq;
using System.Reactive.Linq;
using Avalonia.Controls.Generators;
using Avalonia.Controls.Platform;
using Avalonia.Controls.Primitives;
using Avalonia.Controls.Templates;
using Avalonia.Input;
using Avalonia.Interactivity;
using Avalonia.LogicalTree;
namespace Avalonia.Controls
@ -90,9 +90,14 @@ namespace Avalonia.Controls
/// <param name="control">The control.</param>
public void Open(Control control)
{
if (IsOpen)
{
return;
}
if (_popup == null)
{
_popup = new Popup()
_popup = new Popup
{
PlacementMode = PlacementMode.Pointer,
PlacementTarget = control,
@ -107,7 +112,14 @@ namespace Avalonia.Controls
((ISetLogicalParent)_popup).SetParent(control);
_popup.Child = this;
_popup.IsOpen = true;
IsOpen = true;
RaiseEvent(new RoutedEventArgs
{
RoutedEvent = MenuOpenedEvent,
Source = this,
});
}
/// <summary>
@ -115,13 +127,15 @@ namespace Avalonia.Controls
/// </summary>
public override void Close()
{
if (!IsOpen)
{
return;
}
if (_popup != null && _popup.IsVisible)
{
_popup.IsOpen = false;
}
SelectedIndex = -1;
IsOpen = false;
}
protected override IItemContainerGenerator CreateItemContainerGenerator()
@ -129,6 +143,18 @@ 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)
{
Focus();
@ -145,8 +171,7 @@ namespace Avalonia.Controls
i.IsSubMenuOpen = false;
}
contextMenu.IsOpen = false;
contextMenu.SelectedIndex = -1;
contextMenu.CloseCore();
}
}

21
src/Avalonia.Controls/Embedding/EmbeddableControlRoot.cs

@ -8,7 +8,7 @@ using JetBrains.Annotations;
namespace Avalonia.Controls.Embedding
{
public class EmbeddableControlRoot : TopLevel, IStyleable, IFocusScope, INameScope, IDisposable
public class EmbeddableControlRoot : TopLevel, IStyleable, IFocusScope, IDisposable
{
public EmbeddableControlRoot(IEmbeddableWindowImpl impl) : base(impl)
{
@ -51,25 +51,6 @@ namespace Avalonia.Controls.Embedding
return rv;
}
private readonly NameScope _nameScope = new NameScope();
public event EventHandler<NameScopeEventArgs> Registered
{
add { _nameScope.Registered += value; }
remove { _nameScope.Registered -= value; }
}
public event EventHandler<NameScopeEventArgs> Unregistered
{
add { _nameScope.Unregistered += value; }
remove { _nameScope.Unregistered -= value; }
}
public void Register(string name, object element) => _nameScope.Register(name, element);
public object Find(string name) => _nameScope.Find(name);
public void Unregister(string name) => _nameScope.Unregister(name);
Type IStyleable.StyleKey => typeof(EmbeddableControlRoot);
public void Dispose() => PlatformImpl?.Dispose();
}

19
src/Avalonia.Controls/Embedding/Offscreen/OffscreenTopLevel.cs

@ -30,25 +30,6 @@ namespace Avalonia.Controls.Embedding.Offscreen
init.EndInit();
}
}
private readonly NameScope _nameScope = new NameScope();
public event EventHandler<NameScopeEventArgs> Registered
{
add { _nameScope.Registered += value; }
remove { _nameScope.Registered -= value; }
}
public event EventHandler<NameScopeEventArgs> Unregistered
{
add { _nameScope.Unregistered += value; }
remove { _nameScope.Unregistered -= value; }
}
public void Register(string name, object element) => _nameScope.Register(name, element);
public object Find(string name) => _nameScope.Find(name);
public void Unregister(string name) => _nameScope.Unregister(name);
Type IStyleable.StyleKey => typeof(EmbeddableControlRoot);
public void Dispose()

14
src/Avalonia.Controls/Generators/TreeItemContainerGenerator.cs

@ -92,7 +92,6 @@ namespace Avalonia.Controls.Generators
result.DataContext = item;
}
NameScope.SetNameScope((Control)(object)result, new NameScope());
Index.Add(item, result);
return result;
@ -120,11 +119,20 @@ namespace Avalonia.Controls.Generators
public override bool TryRecycle(int oldIndex, int newIndex, object item) => false;
class WrapperTreeDataTemplate : ITreeDataTemplate
{
private readonly IDataTemplate _inner;
public WrapperTreeDataTemplate(IDataTemplate inner) => _inner = inner;
public IControl Build(object param) => _inner.Build(param);
public bool SupportsRecycling => _inner.SupportsRecycling;
public bool Match(object data) => _inner.Match(data);
public InstancedBinding ItemsSelector(object item) => null;
}
private ITreeDataTemplate GetTreeDataTemplate(object item, IDataTemplate primary)
{
var template = Owner.FindDataTemplate(item, primary) ?? FuncDataTemplate.Default;
var treeTemplate = template as ITreeDataTemplate ??
new FuncTreeDataTemplate(typeof(object), template.Build, x => null);
var treeTemplate = template as ITreeDataTemplate ?? new WrapperTreeDataTemplate(template);
return treeTemplate;
}
}

8
src/Avalonia.Controls/ListBox.cs

@ -68,7 +68,13 @@ namespace Avalonia.Controls
/// <inheritdoc/>
public new IList SelectedItems => base.SelectedItems;
/// <inheritdoc/>
/// <summary>
/// Gets or sets the selection mode.
/// </summary>
/// <remarks>
/// Note that the selection mode only applies to selections made via user interaction.
/// Multiple selections can be made programatically regardless of the value of this property.
/// </remarks>
public new SelectionMode SelectionMode
{
get { return base.SelectionMode; }

46
src/Avalonia.Controls/Menu.cs

@ -40,37 +40,41 @@ namespace Avalonia.Controls
/// <inheritdoc/>
public override void Close()
{
if (IsOpen)
if (!IsOpen)
{
foreach (var i in ((IMenu)this).SubItems)
{
i.Close();
}
IsOpen = false;
SelectedIndex = -1;
return;
}
RaiseEvent(new RoutedEventArgs
{
RoutedEvent = MenuClosedEvent,
Source = this,
});
foreach (var i in ((IMenu)this).SubItems)
{
i.Close();
}
IsOpen = false;
SelectedIndex = -1;
RaiseEvent(new RoutedEventArgs
{
RoutedEvent = MenuClosedEvent,
Source = this,
});
}
/// <inheritdoc/>
public override void Open()
{
if (!IsOpen)
if (IsOpen)
{
IsOpen = true;
RaiseEvent(new RoutedEventArgs
{
RoutedEvent = MenuOpenedEvent,
Source = this,
});
return;
}
IsOpen = true;
RaiseEvent(new RoutedEventArgs
{
RoutedEvent = MenuOpenedEvent,
Source = this,
});
}
/// <inheritdoc/>

5
src/Avalonia.Controls/MenuBase.cs

@ -7,7 +7,6 @@ using System.Linq;
using Avalonia.Controls.Generators;
using Avalonia.Controls.Platform;
using Avalonia.Controls.Primitives;
using Avalonia.Controls.Templates;
using Avalonia.Input;
using Avalonia.Interactivity;
using Avalonia.LogicalTree;
@ -31,13 +30,13 @@ namespace Avalonia.Controls
/// Defines the <see cref="MenuOpened"/> event.
/// </summary>
public static readonly RoutedEvent<RoutedEventArgs> MenuOpenedEvent =
RoutedEvent.Register<MenuItem, RoutedEventArgs>(nameof(MenuOpened), RoutingStrategies.Bubble);
RoutedEvent.Register<MenuBase, RoutedEventArgs>(nameof(MenuOpened), RoutingStrategies.Bubble);
/// <summary>
/// Defines the <see cref="MenuClosed"/> event.
/// </summary>
public static readonly RoutedEvent<RoutedEventArgs> MenuClosedEvent =
RoutedEvent.Register<MenuItem, RoutedEventArgs>(nameof(MenuClosed), RoutingStrategies.Bubble);
RoutedEvent.Register<MenuBase, RoutedEventArgs>(nameof(MenuClosed), RoutingStrategies.Bubble);
private bool _isOpen;

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

@ -325,12 +325,6 @@ namespace Avalonia.Controls.Presenters
{
_dataTemplate = dataTemplate;
newChild = _dataTemplate.Build(content);
// Give the new control its own name scope.
if (newChild is Control controlResult)
{
NameScope.SetNameScope(controlResult, new NameScope());
}
}
}
else

35
src/Avalonia.Controls/Primitives/Popup.cs

@ -6,7 +6,6 @@ using System.Linq;
using Avalonia.Input;
using Avalonia.Input.Raw;
using Avalonia.Interactivity;
using Avalonia.Layout;
using Avalonia.LogicalTree;
using Avalonia.Metadata;
using Avalonia.VisualTree;
@ -270,9 +269,10 @@ namespace Avalonia.Controls.Primitives
_popupRoot.SnapInsideScreenEdges();
}
_ignoreIsOpenChanged = true;
IsOpen = true;
_ignoreIsOpenChanged = false;
using (BeginIgnoringIsOpen())
{
IsOpen = true;
}
Opened?.Invoke(this, EventArgs.Empty);
}
@ -305,7 +305,11 @@ namespace Avalonia.Controls.Primitives
_popupRoot.Hide();
}
IsOpen = false;
using (BeginIgnoringIsOpen())
{
IsOpen = false;
}
Closed?.Invoke(this, EventArgs.Empty);
}
@ -467,5 +471,26 @@ namespace Avalonia.Controls.Primitives
Close();
}
}
private IgnoreIsOpenScope BeginIgnoringIsOpen()
{
return new IgnoreIsOpenScope(this);
}
private readonly struct IgnoreIsOpenScope : IDisposable
{
private readonly Popup _owner;
public IgnoreIsOpenScope(Popup owner)
{
_owner = owner;
_owner._ignoreIsOpenChanged = true;
}
public void Dispose()
{
_owner._ignoreIsOpenChanged = false;
}
}
}
}

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

@ -222,6 +222,10 @@ namespace Avalonia.Controls.Primitives
/// <summary>
/// Gets or sets the selection mode.
/// </summary>
/// <remarks>
/// Note that the selection mode only applies to selections made via user interaction.
/// Multiple selections can be made programatically regardless of the value of this property.
/// </remarks>
protected SelectionMode SelectionMode
{
get { return GetValue(SelectionModeProperty); }
@ -338,24 +342,36 @@ namespace Avalonia.Controls.Primitives
{
base.OnContainersMaterialized(e);
var selectedIndex = SelectedIndex;
var selectedContainer = e.Containers
.FirstOrDefault(x => (x.ContainerControl as ISelectable)?.IsSelected == true);
var resetSelectedItems = false;
if (selectedContainer != null)
foreach (var container in e.Containers)
{
SelectedIndex = selectedContainer.Index;
}
else if (selectedIndex >= e.StartingIndex &&
selectedIndex < e.StartingIndex + e.Containers.Count)
{
var container = e.Containers[selectedIndex - e.StartingIndex];
if ((container.ContainerControl as ISelectable)?.IsSelected == true)
{
if (SelectedIndex == -1)
{
SelectedIndex = container.Index;
}
else
{
if (_selection.Add(container.Index))
{
resetSelectedItems = true;
}
}
if (container.ContainerControl != null)
MarkContainerSelected(container.ContainerControl, true);
}
else if (_selection.Contains(container.Index))
{
MarkContainerSelected(container.ContainerControl, true);
}
}
if (resetSelectedItems)
{
ResetSelectedItems();
}
}
/// <inheritdoc/>

30
src/Avalonia.Controls/Primitives/TemplatedControl.cs

@ -257,13 +257,14 @@ namespace Avalonia.Controls.Primitives
{
Logger.Verbose(LogArea.Control, this, "Creating control template");
var child = template.Build(this);
var nameScope = new NameScope();
NameScope.SetNameScope((Control)child, nameScope);
var (child, nameScope) = template.Build(this);
ApplyTemplatedParent(child);
RegisterNames(child, nameScope);
((ISetLogicalParent)child).SetParent(this);
VisualChildren.Add(child);
// Existing code kinda expect to see a NameScope even if it's empty
if (nameScope == null)
nameScope = new NameScope();
OnTemplateApplied(new TemplateAppliedEventArgs(nameScope));
}
@ -342,26 +343,5 @@ namespace Avalonia.Controls.Primitives
}
}
}
/// <summary>
/// Registers each control with its name scope.
/// </summary>
/// <param name="control">The control.</param>
/// <param name="nameScope">The name scope.</param>
private void RegisterNames(IControl control, INameScope nameScope)
{
if (control.Name != null)
{
nameScope.Register(control.Name, control);
}
if (control.TemplatedParent == this)
{
foreach (IControl child in control.GetLogicalChildren())
{
RegisterNames(child, nameScope);
}
}
}
}
}

4
src/Avalonia.Controls/ShutdownMode.cs

@ -1,10 +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 Avalonia.Controls.ApplicationLifetimes;
namespace Avalonia.Controls
{
/// <summary>
/// Describes the possible values for <see cref="Application.ShutdownMode"/>.
/// Describes the possible values for <see cref="IClassicDesktopStyleApplicationLifetime.ShutdownMode"/>.
/// </summary>
public enum ShutdownMode
{

10
src/Avalonia.Controls/Templates/FuncControlTemplate.cs

@ -16,9 +16,15 @@ namespace Avalonia.Controls.Templates
/// Initializes a new instance of the <see cref="FuncControlTemplate"/> class.
/// </summary>
/// <param name="build">The build function.</param>
public FuncControlTemplate(Func<ITemplatedControl, IControl> build)
public FuncControlTemplate(Func<ITemplatedControl, INameScope, IControl> build)
: base(build)
{
}
public new ControlTemplateResult Build(ITemplatedControl param)
{
var (control, scope) = BuildWithNameScope(param);
return new ControlTemplateResult(control, scope);
}
}
}
}

6
src/Avalonia.Controls/Templates/FuncControlTemplate`2.cs

@ -17,9 +17,9 @@ namespace Avalonia.Controls.Templates
/// Initializes a new instance of the <see cref="FuncControlTemplate{T}"/> class.
/// </summary>
/// <param name="build">The build function.</param>
public FuncControlTemplate(Func<T, IControl> build)
: base(x => build((T)x))
public FuncControlTemplate(Func<T, INameScope, IControl> build)
: base((x, s) => build((T)x, s))
{
}
}
}
}

8
src/Avalonia.Controls/Templates/FuncDataTemplate.cs

@ -17,7 +17,7 @@ namespace Avalonia.Controls.Templates
/// </summary>
public static readonly FuncDataTemplate Default =
new FuncDataTemplate<object>(
data =>
(data, s) =>
{
if (data != null)
{
@ -49,7 +49,7 @@ namespace Avalonia.Controls.Templates
/// <param name="supportsRecycling">Whether the control can be recycled.</param>
public FuncDataTemplate(
Type type,
Func<object, IControl> build,
Func<object, INameScope, IControl> build,
bool supportsRecycling = false)
: this(o => IsInstance(o, type), build, supportsRecycling)
{
@ -67,7 +67,7 @@ namespace Avalonia.Controls.Templates
/// <param name="supportsRecycling">Whether the control can be recycled.</param>
public FuncDataTemplate(
Func<object, bool> match,
Func<object, IControl> build,
Func<object, INameScope, IControl> build,
bool supportsRecycling = false)
: base(build)
{
@ -105,4 +105,4 @@ namespace Avalonia.Controls.Templates
return (o != null) && t.GetTypeInfo().IsAssignableFrom(o.GetType().GetTypeInfo());
}
}
}
}

26
src/Avalonia.Controls/Templates/FuncDataTemplate`1.cs

@ -18,7 +18,7 @@ namespace Avalonia.Controls.Templates
/// A function which when passed an object of <typeparamref name="T"/> returns a control.
/// </param>
/// <param name="supportsRecycling">Whether the control can be recycled.</param>
public FuncDataTemplate(Func<T, IControl> build, bool supportsRecycling = false)
public FuncDataTemplate(Func<T, INameScope, IControl> build, bool supportsRecycling = false)
: base(typeof(T), CastBuild(build), supportsRecycling)
{
}
@ -35,12 +35,30 @@ namespace Avalonia.Controls.Templates
/// <param name="supportsRecycling">Whether the control can be recycled.</param>
public FuncDataTemplate(
Func<T, bool> match,
Func<T, IControl> build,
Func<T, INameScope, IControl> build,
bool supportsRecycling = false)
: base(CastMatch(match), CastBuild(build), supportsRecycling)
{
}
/// <summary>
/// Initializes a new instance of the <see cref="FuncDataTemplate{T}"/> class.
/// </summary>
/// <param name="match">
/// A function which determines whether the data template matches the specified data.
/// </param>
/// <param name="build">
/// A function which when passed an object of <typeparamref name="T"/> returns a control.
/// </param>
/// <param name="supportsRecycling">Whether the control can be recycled.</param>
public FuncDataTemplate(
Func<T, bool> match,
Func<T, IControl> build,
bool supportsRecycling = false)
: this(match, (a, _) => build(a), supportsRecycling)
{
}
/// <summary>
/// Casts a strongly typed match function to a weakly typed one.
/// </summary>
@ -57,9 +75,9 @@ namespace Avalonia.Controls.Templates
/// <typeparam name="TResult">The strong data type.</typeparam>
/// <param name="f">The strongly typed function.</param>
/// <returns>The weakly typed function.</returns>
private static Func<object, TResult> CastBuild<TResult>(Func<T, TResult> f)
private static Func<object, INameScope, TResult> CastBuild<TResult>(Func<T, INameScope, TResult> f)
{
return o => f((T)o);
return (o, s) => f((T)o, s);
}
}
}

14
src/Avalonia.Controls/Templates/FuncTemplateNameScopeExtensions.cs

@ -0,0 +1,14 @@
using System;
namespace Avalonia.Controls.Templates
{
public static class FuncTemplateNameScopeExtensions
{
public static T RegisterInNameScope<T>(this T control, INameScope scope)
where T : StyledElement
{
scope.Register(control.Name, control);
return control;
}
}
}

15
src/Avalonia.Controls/Templates/FuncTemplate`2.cs

@ -13,13 +13,13 @@ namespace Avalonia.Controls.Templates
public class FuncTemplate<TParam, TControl> : ITemplate<TParam, TControl>
where TControl : IControl
{
private readonly Func<TParam, TControl> _func;
private readonly Func<TParam, INameScope, TControl> _func;
/// <summary>
/// Initializes a new instance of the <see cref="FuncTemplate{TControl, TParam}"/> class.
/// </summary>
/// <param name="func">The function used to create the control.</param>
public FuncTemplate(Func<TParam, TControl> func)
public FuncTemplate(Func<TParam, INameScope, TControl> func)
{
Contract.Requires<ArgumentNullException>(func != null);
@ -35,7 +35,14 @@ namespace Avalonia.Controls.Templates
/// </returns>
public TControl Build(TParam param)
{
return _func(param);
return BuildWithNameScope(param).control;
}
protected (TControl control, INameScope nameScope) BuildWithNameScope(TParam param)
{
var scope = new NameScope();
var rv = _func(param, scope);
return (rv, scope);
}
}
}
}

4
src/Avalonia.Controls/Templates/FuncTreeDataTemplate.cs

@ -28,7 +28,7 @@ namespace Avalonia.Controls.Templates
/// </param>
public FuncTreeDataTemplate(
Type type,
Func<object, IControl> build,
Func<object, INameScope, IControl> build,
Func<object, IEnumerable> itemsSelector)
: this(o => IsInstance(o, type), build, itemsSelector)
{
@ -48,7 +48,7 @@ namespace Avalonia.Controls.Templates
/// </param>
public FuncTreeDataTemplate(
Func<object, bool> match,
Func<object, IControl> build,
Func<object, INameScope, IControl> build,
Func<object, IEnumerable> itemsSelector)
: base(match, build)
{

15
src/Avalonia.Controls/Templates/FuncTreeDataTemplate`1.cs

@ -23,7 +23,7 @@ namespace Avalonia.Controls.Templates
/// items.
/// </param>
public FuncTreeDataTemplate(
Func<T, Control> build,
Func<T, INameScope, Control> build,
Func<T, IEnumerable> itemsSelector)
: base(
typeof(T),
@ -46,7 +46,7 @@ namespace Avalonia.Controls.Templates
/// </param>
public FuncTreeDataTemplate(
Func<T, bool> match,
Func<T, Control> build,
Func<T, INameScope, Control> build,
Func<T, IEnumerable> itemsSelector)
: base(
CastMatch(match),
@ -65,6 +65,17 @@ namespace Avalonia.Controls.Templates
return o => (o is T) && f((T)o);
}
/// <summary>
/// Casts a function with a typed parameter to an untyped function.
/// </summary>
/// <typeparam name="TResult">The result.</typeparam>
/// <param name="f">The typed function.</param>
/// <returns>The untyped function.</returns>
private static Func<object, INameScope, TResult> Cast<TResult>(Func<T, INameScope, TResult> f)
{
return (o, s) => f((T)o, s);
}
/// <summary>
/// Casts a function with a typed parameter to an untyped function.
/// </summary>

22
src/Avalonia.Controls/Templates/IControlTemplate.cs

@ -9,7 +9,25 @@ namespace Avalonia.Controls.Templates
/// <summary>
/// Interface representing a template used to build a <see cref="TemplatedControl"/>.
/// </summary>
public interface IControlTemplate : ITemplate<ITemplatedControl, IControl>
public interface IControlTemplate : ITemplate<ITemplatedControl, ControlTemplateResult>
{
}
}
public class ControlTemplateResult
{
public IControl Control { get; }
public INameScope NameScope { get; }
public ControlTemplateResult(IControl control, INameScope nameScope)
{
Control = control;
NameScope = nameScope;
}
public void Deconstruct(out IControl control, out INameScope scope)
{
control = Control;
scope = NameScope;
}
}
}

4
src/Avalonia.Controls/Templates/ITemplate`2.cs

@ -8,7 +8,7 @@ namespace Avalonia.Controls.Templates
/// </summary>
/// <typeparam name="TParam">The type of the parameter.</typeparam>
/// <typeparam name="TControl">The type of control.</typeparam>
public interface ITemplate<TParam, TControl> where TControl : IControl
public interface ITemplate<TParam, TControl>
{
/// <summary>
/// Creates the control.
@ -19,4 +19,4 @@ namespace Avalonia.Controls.Templates
/// </returns>
TControl Build(TParam param);
}
}
}

34
src/Avalonia.Controls/UserControl.cs

@ -9,40 +9,8 @@ namespace Avalonia.Controls
/// <summary>
/// Provides the base class for defining a new control that encapsulates related existing controls and provides its own logic.
/// </summary>
public class UserControl : ContentControl, IStyleable, INameScope
public class UserControl : ContentControl, IStyleable
{
private readonly NameScope _nameScope = new NameScope();
/// <inheritdoc/>
event EventHandler<NameScopeEventArgs> INameScope.Registered
{
add { _nameScope.Registered += value; }
remove { _nameScope.Registered -= value; }
}
/// <inheritdoc/>
event EventHandler<NameScopeEventArgs> INameScope.Unregistered
{
add { _nameScope.Unregistered += value; }
remove { _nameScope.Unregistered -= value; }
}
/// <inheritdoc/>
void INameScope.Register(string name, object element)
{
_nameScope.Register(name, element);
}
/// <inheritdoc/>
object INameScope.Find(string name)
{
return _nameScope.Find(name);
}
/// <inheritdoc/>
void INameScope.Unregister(string name)
{
_nameScope.Unregister(name);
}
}
}

34
src/Avalonia.Controls/Window.cs

@ -48,7 +48,7 @@ namespace Avalonia.Controls
/// <summary>
/// A top-level window.
/// </summary>
public class Window : WindowBase, IStyleable, IFocusScope, ILayoutRoot, INameScope
public class Window : WindowBase, IStyleable, IFocusScope, ILayoutRoot
{
/// <summary>
/// Defines the <see cref="SizeToContent"/> property.
@ -157,20 +157,6 @@ namespace Avalonia.Controls
_maxPlatformClientSize = PlatformImpl?.MaxClientSize ?? default(Size);
}
/// <inheritdoc/>
event EventHandler<NameScopeEventArgs> INameScope.Registered
{
add { _nameScope.Registered += value; }
remove { _nameScope.Registered -= value; }
}
/// <inheritdoc/>
event EventHandler<NameScopeEventArgs> INameScope.Unregistered
{
add { _nameScope.Unregistered += value; }
remove { _nameScope.Unregistered -= value; }
}
/// <summary>
/// Gets the platform-specific window implementation.
/// </summary>
@ -494,24 +480,6 @@ namespace Avalonia.Controls
}
}
/// <inheritdoc/>
void INameScope.Register(string name, object element)
{
_nameScope.Register(name, element);
}
/// <inheritdoc/>
object INameScope.Find(string name)
{
return _nameScope.Find(name);
}
/// <inheritdoc/>
void INameScope.Unregister(string name)
{
_nameScope.Unregister(name);
}
/// <inheritdoc/>
protected override Size MeasureOverride(Size availableSize)
{

18
src/Avalonia.OpenGL/GlInterface.cs

@ -9,12 +9,14 @@ namespace Avalonia.OpenGL
public class GlInterface : GlInterfaceBase
{
public string Version { get; }
public string Vendor { get; }
public string Renderer { get; }
public GlInterface(Func<string, bool, IntPtr> getProcAddress) : base(getProcAddress)
{
var versionPtr = GetString(GlConsts.GL_VERSION);
if (versionPtr != IntPtr.Zero)
Version = Marshal.PtrToStringAnsi(versionPtr);
Version = GetString(GlConsts.GL_VERSION);
Renderer = GetString(GlConsts.GL_RENDERER);
Vendor = GetString(GlConsts.GL_VENDOR);
}
public GlInterface(Func<Utf8Buffer, IntPtr> n) : this(ConvertNative(n))
@ -54,7 +56,15 @@ namespace Avalonia.OpenGL
public delegate IntPtr GlGetString(int v);
[GlEntryPoint("glGetString")]
public GlGetString GetString { get; }
public GlGetString GetStringNative { get; }
public string GetString(int v)
{
var ptr = GetStringNative(v);
if (ptr != IntPtr.Zero)
return Marshal.PtrToStringAnsi(ptr);
return null;
}
public delegate void GlGetIntegerv(int name, out int rv);
[GlEntryPoint("glGetIntegerv")]

4
src/Avalonia.ReactiveUI/AutoDataTemplateBindingHook.cs

@ -16,7 +16,7 @@ namespace Avalonia.ReactiveUI
/// </summary>
public class AutoDataTemplateBindingHook : IPropertyBindingHook
{
private static FuncDataTemplate DefaultItemTemplate = new FuncDataTemplate<object>(x =>
private static FuncDataTemplate DefaultItemTemplate = new FuncDataTemplate<object>((x, _) =>
{
var control = new ViewModelViewHost();
var context = control.GetObservable(Control.DataContextProperty);
@ -52,4 +52,4 @@ namespace Avalonia.ReactiveUI
return true;
}
}
}
}

87
src/Avalonia.ReactiveUI/AutoSuspendHelper.cs

@ -0,0 +1,87 @@
// 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 Avalonia;
using Avalonia.VisualTree;
using Avalonia.Controls;
using System.Threading;
using System.Reactive.Disposables;
using System.Reactive.Subjects;
using System.Reactive.Linq;
using System.Reactive;
using ReactiveUI;
using System;
using Avalonia.Controls.ApplicationLifetimes;
using Splat;
namespace Avalonia.ReactiveUI
{
/// <summary>
/// A ReactiveUI AutoSuspendHelper which initializes suspension hooks for
/// Avalonia applications. Call its constructor in your app's composition root,
/// before calling the RxApp.SuspensionHost.SetupDefaultSuspendResume method.
/// </summary>
public sealed class AutoSuspendHelper : IEnableLogger, IDisposable
{
private readonly Subject<IDisposable> _shouldPersistState = new Subject<IDisposable>();
private readonly Subject<Unit> _isLaunchingNew = new Subject<Unit>();
/// <summary>
/// Initializes a new instance of the <see cref="AutoSuspendHelper"/> class.
/// </summary>
/// <param name="lifetime">Pass in the Application.ApplicationLifetime property.</param>
public AutoSuspendHelper(IApplicationLifetime lifetime)
{
RxApp.SuspensionHost.IsResuming = Observable.Never<Unit>();
RxApp.SuspensionHost.IsLaunchingNew = _isLaunchingNew;
if (lifetime is IControlledApplicationLifetime controlled)
{
this.Log().Debug("Using IControlledApplicationLifetime events to handle app exit.");
controlled.Exit += (sender, args) => OnControlledApplicationLifetimeExit();
RxApp.SuspensionHost.ShouldPersistState = _shouldPersistState;
}
else if (lifetime != null)
{
var type = lifetime.GetType().FullName;
var message = $"Don't know how to detect app exit event for {type}.";
throw new NotSupportedException(message);
}
else
{
var message = "ApplicationLifetime is null. "
+ "Ensure you are initializing AutoSuspendHelper "
+ "when Avalonia application initialization is completed.";
throw new ArgumentNullException(message);
}
var errored = new Subject<Unit>();
AppDomain.CurrentDomain.UnhandledException += (o, e) => errored.OnNext(Unit.Default);
RxApp.SuspensionHost.ShouldInvalidateState = errored;
}
/// <summary>
/// Call this method in your App.OnFrameworkInitializationCompleted method.
/// </summary>
public void OnFrameworkInitializationCompleted() => _isLaunchingNew.OnNext(Unit.Default);
/// <summary>
/// Disposes internally stored observers.
/// </summary>
public void Dispose()
{
_shouldPersistState.Dispose();
_isLaunchingNew.Dispose();
}
private void OnControlledApplicationLifetimeExit()
{
this.Log().Debug("Received IControlledApplicationLifetime exit event.");
var manual = new ManualResetEvent(false);
_shouldPersistState.OnNext(Disposable.Create(() => manual.Set()));
manual.WaitOne();
this.Log().Debug("Completed actions on IControlledApplicationLifetime exit event.");
}
}
}

73
src/Avalonia.Styling/Controls/ChildNameScope.cs

@ -0,0 +1,73 @@
using System.Threading.Tasks;
using Avalonia.Utilities;
namespace Avalonia.Controls
{
public class ChildNameScope : INameScope
{
private readonly INameScope _parentScope;
private readonly NameScope _inner = new NameScope();
public ChildNameScope(INameScope parentScope)
{
_parentScope = parentScope;
}
public void Register(string name, object element) => _inner.Register(name, element);
public SynchronousCompletionAsyncResult<object> FindAsync(string name)
{
var found = Find(name);
if (found != null)
return new SynchronousCompletionAsyncResult<object>(found);
// Not found and both current and parent scope are in completed state
if(IsCompleted)
return new SynchronousCompletionAsyncResult<object>(null);
return DoFindAsync(name);
}
public SynchronousCompletionAsyncResult<object> DoFindAsync(string name)
{
var src = new SynchronousCompletionAsyncResultSource<object>();
void ParentSearch()
{
var parentSearch = _parentScope.FindAsync(name);
if (parentSearch.IsCompleted)
src.SetResult(parentSearch.GetResult());
else
parentSearch.OnCompleted(() => src.SetResult(parentSearch.GetResult()));
}
if (!_inner.IsCompleted)
{
// Guaranteed to be incomplete at this point
var innerSearch = _inner.FindAsync(name);
innerSearch.OnCompleted(() =>
{
var value = innerSearch.GetResult();
if (value != null)
src.SetResult(value);
else ParentSearch();
});
}
else
ParentSearch();
return src.AsyncResult;
}
public object Find(string name)
{
var found = _inner.Find(name);
if (found != null)
return found;
if (_inner.IsCompleted)
return _parentScope.Find(name);
return null;
}
public void Complete() => _inner.Complete();
public bool IsCompleted => _inner.IsCompleted && _parentScope.IsCompleted;
}
}

34
src/Avalonia.Styling/Controls/INameScope.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 System.Threading.Tasks;
using Avalonia.Utilities;
namespace Avalonia.Controls
{
@ -10,16 +12,6 @@ namespace Avalonia.Controls
/// </summary>
public interface INameScope
{
/// <summary>
/// Raised when an element is registered with the name scope.
/// </summary>
event EventHandler<NameScopeEventArgs> Registered;
/// <summary>
/// Raised when an element is unregistered with the name scope.
/// </summary>
event EventHandler<NameScopeEventArgs> Unregistered;
/// <summary>
/// Registers an element in the name scope.
/// </summary>
@ -28,16 +20,30 @@ namespace Avalonia.Controls
void Register(string name, object element);
/// <summary>
/// Finds a named element in the name scope.
/// Finds a named element in the name scope, waits for the scope to be completely populated before returning null
/// Returned task is configured to run any continuations synchronously.
/// </summary>
/// <param name="name">The name.</param>
/// <returns>The element, or null if the name was not found.</returns>
SynchronousCompletionAsyncResult<object> FindAsync(string name);
/// <summary>
/// Finds a named element in the name scope, returns immediately, doesn't traverse the name scope stack
/// </summary>
/// <param name="name">The name.</param>
/// <returns>The element, or null if the name was not found.</returns>
object Find(string name);
/// <summary>
/// Unregisters an element with the name scope.
/// Marks the name scope as completed, no further registrations will be allowed
/// </summary>
/// <param name="name">The name.</param>
void Unregister(string name);
void Complete();
/// <summary>
/// Returns whether further registrations are allowed on the scope
/// </summary>
bool IsCompleted { get; }
}
}

99
src/Avalonia.Styling/Controls/NameScope.cs

@ -3,7 +3,9 @@
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using Avalonia.LogicalTree;
using Avalonia.Utilities;
namespace Avalonia.Controls
{
@ -18,44 +20,14 @@ namespace Avalonia.Controls
public static readonly AttachedProperty<INameScope> NameScopeProperty =
AvaloniaProperty.RegisterAttached<NameScope, StyledElement, INameScope>("NameScope");
/// <inheritdoc/>
public bool IsCompleted { get; private set; }
private readonly Dictionary<string, object> _inner = new Dictionary<string, object>();
/// <summary>
/// Raised when an element is registered with the name scope.
/// </summary>
public event EventHandler<NameScopeEventArgs> Registered;
/// <summary>
/// Raised when an element is unregistered with the name scope.
/// </summary>
public event EventHandler<NameScopeEventArgs> Unregistered;
/// <summary>
/// Finds the containing name scope for a styled element.
/// </summary>
/// <param name="styled">The styled element.</param>
/// <returns>The containing name scope.</returns>
public static INameScope FindNameScope(StyledElement styled)
{
Contract.Requires<ArgumentNullException>(styled != null);
INameScope result;
while (styled != null)
{
result = styled as INameScope ?? GetNameScope(styled);
if (result != null)
{
return result;
}
styled = (styled as ILogical)?.LogicalParent as StyledElement;
}
return null;
}
private readonly Dictionary<string, SynchronousCompletionAsyncResultSource<object>> _pendingSearches =
new Dictionary<string, SynchronousCompletionAsyncResultSource<object>>();
/// <summary>
/// Gets the value of the attached <see cref="NameScopeProperty"/> on a styled element.
/// </summary>
@ -80,13 +52,11 @@ namespace Avalonia.Controls
styled.SetValue(NameScopeProperty, value);
}
/// <summary>
/// Registers an element with the name scope.
/// </summary>
/// <param name="name">The element name.</param>
/// <param name="element">The element.</param>
/// <inheritdoc />
public void Register(string name, object element)
{
if (IsCompleted)
throw new InvalidOperationException("NameScope is completed, no further registrations are allowed");
Contract.Requires<ArgumentNullException>(name != null);
Contract.Requires<ArgumentNullException>(element != null);
@ -102,15 +72,29 @@ namespace Avalonia.Controls
else
{
_inner.Add(name, element);
Registered?.Invoke(this, new NameScopeEventArgs(name, element));
if (_pendingSearches.TryGetValue(name, out var tcs))
{
_pendingSearches.Remove(name);
tcs.SetResult(element);
}
}
}
/// <summary>
/// Finds a named element in the name scope.
/// </summary>
/// <param name="name">The name.</param>
/// <returns>The element, or null if the name was not found.</returns>
public SynchronousCompletionAsyncResult<object> FindAsync(string name)
{
var found = Find(name);
if (found != null)
return new SynchronousCompletionAsyncResult<object>(found);
if (IsCompleted)
return new SynchronousCompletionAsyncResult<object>((object)null);
if (!_pendingSearches.TryGetValue(name, out var tcs))
// We are intentionally running continuations synchronously here
_pendingSearches[name] = tcs = new SynchronousCompletionAsyncResultSource<object>();
return tcs.AsyncResult;
}
/// <inheritdoc />
public object Find(string name)
{
Contract.Requires<ArgumentNullException>(name != null);
@ -120,21 +104,14 @@ namespace Avalonia.Controls
return result;
}
/// <summary>
/// Unregisters an element with the name scope.
/// </summary>
/// <param name="name">The name.</param>
public void Unregister(string name)
public void Complete()
{
Contract.Requires<ArgumentNullException>(name != null);
object element;
if (_inner.TryGetValue(name, out element))
{
_inner.Remove(name);
Unregistered?.Invoke(this, new NameScopeEventArgs(name, element));
}
IsCompleted = true;
foreach (var kp in _pendingSearches)
kp.Value.TrySetResult(null);
_pendingSearches.Clear();
}
}
}

41
src/Avalonia.Styling/Controls/NameScopeExtensions.cs

@ -37,6 +37,25 @@ namespace Avalonia.Controls
return (T)result;
}
/// <summary>
/// Finds a named element in an <see cref="INameScope"/>.
/// </summary>
/// <typeparam name="T">The element type.</typeparam>
/// <param name="anchor">The control to take the name scope from.</param>
/// <param name="name">The name.</param>
/// <returns>The named element or null if not found.</returns>
public static T Find<T>(this ILogical anchor, string name)
where T : class
{
Contract.Requires<ArgumentNullException>(anchor != null);
Contract.Requires<ArgumentNullException>(name != null);
var styledAnchor = anchor as StyledElement;
if (styledAnchor == null)
return null;
var nameScope = (anchor as INameScope) ?? NameScope.GetNameScope(styledAnchor);
return nameScope?.Find<T>(name);
}
/// <summary>
/// Gets a named element from an <see cref="INameScope"/> or throws if no element of the
/// requested name was found.
@ -67,6 +86,28 @@ namespace Avalonia.Controls
return (T)result;
}
/// <summary>
/// Gets a named element from an <see cref="INameScope"/> or throws if no element of the
/// requested name was found.
/// </summary>
/// <typeparam name="T">The element type.</typeparam>
/// <param name="anchor">The control to take the name scope from.</param>
/// <param name="name">The name.</param>
/// <returns>The named element.</returns>
public static T Get<T>(this ILogical anchor, string name)
where T : class
{
Contract.Requires<ArgumentNullException>(anchor != null);
Contract.Requires<ArgumentNullException>(name != null);
var nameScope = (anchor as INameScope) ?? NameScope.GetNameScope((StyledElement)anchor);
if (nameScope == null)
throw new InvalidOperationException(
"The control doesn't have an associated name scope, probably no registrations has been done yet");
return nameScope.Get<T>(name);
}
public static INameScope FindNameScope(this ILogical control)
{
Contract.Requires<ArgumentNullException>(control != null);

64
src/Avalonia.Styling/Controls/NameScopeLocator.cs

@ -0,0 +1,64 @@
using System;
using System.Linq;
using System.Reactive.Disposables;
using System.Reactive.Threading.Tasks;
using System.Reflection;
using System.Threading.Tasks;
using Avalonia.LogicalTree;
using Avalonia.Reactive;
using Avalonia.Utilities;
namespace Avalonia.Controls
{
public class NameScopeLocator
{
/// <summary>
/// Tracks a named control relative to another control.
/// </summary>
/// <param name="relativeTo">
/// The control relative from which the other control should be found.
/// </param>
/// <param name="name">The name of the control to find.</param>
public static IObservable<object> Track(INameScope scope, string name)
{
return new NeverEndingSynchronousCompletionAsyncResultObservable<object>(scope.FindAsync(name));
}
// This class is implemented in such weird way because for some reason
// our binding system doesn't expect OnCompleted to be ever called and
// seems to treat it as binding cancellation or something
private class NeverEndingSynchronousCompletionAsyncResultObservable<T> : IObservable<T>
{
private T _value;
private SynchronousCompletionAsyncResult<T>? _asyncResult;
public NeverEndingSynchronousCompletionAsyncResultObservable(SynchronousCompletionAsyncResult<T> task)
{
if (task.IsCompleted)
_value = task.GetResult();
else
_asyncResult = task;
}
public IDisposable Subscribe(IObserver<T> observer)
{
if (_asyncResult?.IsCompleted == true)
{
_value = _asyncResult.Value.GetResult();
_asyncResult = null;
}
if (_asyncResult != null)
_asyncResult.Value.OnCompleted(() =>
{
observer.OnNext(_asyncResult.Value.GetResult());
});
else
observer.OnNext(_value);
return Disposable.Empty;
}
}
}
}

74
src/Avalonia.Styling/LogicalTree/ControlLocator.cs

@ -15,18 +15,6 @@ namespace Avalonia.LogicalTree
/// </summary>
public static class ControlLocator
{
/// <summary>
/// Tracks a named control relative to another control.
/// </summary>
/// <param name="relativeTo">
/// The control relative from which the other control should be found.
/// </param>
/// <param name="name">The name of the control to find.</param>
public static IObservable<ILogical> Track(ILogical relativeTo, string name)
{
return new ControlTracker(relativeTo, name);
}
public static IObservable<ILogical> Track(ILogical relativeTo, int ancestorLevel, Type ancestorType = null)
{
return new ControlTracker(relativeTo, ancestorLevel, ancestorType);
@ -35,18 +23,10 @@ namespace Avalonia.LogicalTree
private class ControlTracker : LightweightObservableBase<ILogical>
{
private readonly ILogical _relativeTo;
private readonly string _name;
private readonly int _ancestorLevel;
private readonly Type _ancestorType;
INameScope _nameScope;
ILogical _value;
public ControlTracker(ILogical relativeTo, string name)
{
_relativeTo = relativeTo;
_name = name;
}
public ControlTracker(ILogical relativeTo, int ancestorLevel, Type ancestorType)
{
_relativeTo = relativeTo;
@ -66,12 +46,6 @@ namespace Avalonia.LogicalTree
_relativeTo.AttachedToLogicalTree -= Attached;
_relativeTo.DetachedFromLogicalTree -= Detached;
if (_nameScope != null)
{
_nameScope.Registered -= Registered;
_nameScope.Unregistered -= Unregistered;
}
_value = null;
}
@ -88,57 +62,15 @@ namespace Avalonia.LogicalTree
private void Detached(object sender, LogicalTreeAttachmentEventArgs e)
{
if (_nameScope != null)
{
_nameScope.Registered -= Registered;
_nameScope.Unregistered -= Unregistered;
}
_value = null;
PublishNext(null);
}
private void Registered(object sender, NameScopeEventArgs e)
{
if (e.Name == _name && e.Element is ILogical logical)
{
_value = logical;
PublishNext(logical);
}
}
private void Unregistered(object sender, NameScopeEventArgs e)
{
if (e.Name == _name)
{
_value = null;
PublishNext(null);
}
}
private void Update()
{
if (_name != null)
{
_nameScope = _relativeTo.FindNameScope();
if (_nameScope != null)
{
_nameScope.Registered += Registered;
_nameScope.Unregistered += Unregistered;
_value = _nameScope.Find<ILogical>(_name);
}
else
{
_value = null;
}
}
else
{
_value = _relativeTo.GetLogicalAncestors()
.Where(x => _ancestorType?.GetTypeInfo().IsAssignableFrom(x.GetType().GetTypeInfo()) ?? true)
.ElementAtOrDefault(_ancestorLevel);
}
_value = _relativeTo.GetLogicalAncestors()
.Where(x => _ancestorType?.GetTypeInfo().IsAssignableFrom(x.GetType().GetTypeInfo()) ?? true)
.ElementAtOrDefault(_ancestorLevel);
}
}
}

21
src/Avalonia.Styling/StyledElement.cs

@ -61,7 +61,6 @@ namespace Avalonia
private readonly Classes _classes = new Classes();
private bool _isAttachedToLogicalTree;
private IAvaloniaList<ILogical> _logicalChildren;
private INameScope _nameScope;
private IResourceDictionary _resources;
private Styles _styles;
private bool _styled;
@ -82,7 +81,6 @@ namespace Avalonia
/// </summary>
public StyledElement()
{
_nameScope = this as INameScope;
_isAttachedToLogicalTree = this is IStyleRoot;
}
@ -381,7 +379,6 @@ namespace Avalonia
{
if (_initCount == 0 && (!_styled || force))
{
RegisterWithNameScope();
ApplyStyling();
_styled = true;
}
@ -675,19 +672,6 @@ namespace Avalonia
AvaloniaLocator.Current.GetService<IStyler>()?.ApplyStyles(this);
}
private void RegisterWithNameScope()
{
if (_nameScope == null)
{
_nameScope = NameScope.GetNameScope(this) ?? ((StyledElement)Parent)?._nameScope;
}
if (Name != null)
{
_nameScope?.Register(Name, this);
}
}
private static void ValidateLogicalChild(ILogical c)
{
if (c == null)
@ -724,11 +708,6 @@ namespace Avalonia
{
if (_isAttachedToLogicalTree)
{
if (Name != null)
{
_nameScope?.Unregister(Name);
}
_isAttachedToLogicalTree = false;
_styleDetach.OnNext(this);
OnDetachedFromLogicalTree(e);

1
src/Avalonia.Styling/Styling/Setter.cs

@ -99,7 +99,6 @@ namespace Avalonia.Styling
if (template != null && !isPropertyOfTypeITemplate)
{
var materialized = template.Build();
NameScope.SetNameScope((StyledElement)materialized, new NameScope());
value = materialized;
}

2
src/Avalonia.Styling/Styling/Styles.cs

@ -180,7 +180,7 @@ namespace Avalonia.Styling
/// <inheritdoc/>
public bool TryGetResource(object key, out object value)
{
if (_resources != null && _resources.TryGetValue(key, out value))
if (_resources != null && _resources.TryGetResource(key, out value))
{
return true;
}

1
src/Avalonia.Themes.Default/Avalonia.Themes.Default.csproj

@ -18,6 +18,5 @@
<EmbeddedResource Include="**/*.xaml"/>
</ItemGroup>
<Import Project="..\..\build\BuildTargets.targets"/>
<Import Project="..\..\build\BuildTargets.targets" />
<Import Project="..\..\build\Rx.props" />
</Project>

5
src/Avalonia.Visuals/Rendering/RenderLayers.cs

@ -8,8 +8,8 @@ namespace Avalonia.Rendering
{
public class RenderLayers : IEnumerable<RenderLayer>
{
private List<RenderLayer> _inner = new List<RenderLayer>();
private Dictionary<IVisual, RenderLayer> _index = new Dictionary<IVisual, RenderLayer>();
private readonly List<RenderLayer> _inner = new List<RenderLayer>();
private readonly Dictionary<IVisual, RenderLayer> _index = new Dictionary<IVisual, RenderLayer>();
public int Count => _inner.Count;
public RenderLayer this[IVisual layerRoot] => _index[layerRoot];
@ -56,6 +56,7 @@ namespace Avalonia.Rendering
}
_index.Clear();
_inner.Clear();
}
public bool TryGetValue(IVisual layerRoot, out RenderLayer value)

13
src/Avalonia.X11/Glx/GlxDisplay.cs

@ -90,6 +90,19 @@ namespace Avalonia.X11.Glx
GlInterface = new GlInterface(GlxInterface.GlxGetProcAddress);
if (GlInterface.Version == null)
throw new OpenGlException("GL version string is null, aborting");
if (GlInterface.Renderer == null)
throw new OpenGlException("GL renderer string is null, aborting");
if (Environment.GetEnvironmentVariable("AVALONIA_GLX_IGNORE_RENDERER_BLACKLIST") != "1")
{
var blacklist = AvaloniaLocator.Current.GetService<X11PlatformOptions>()
?.GlxRendererBlacklist;
if (blacklist != null)
foreach(var item in blacklist)
if (GlInterface.Renderer.Contains(item))
throw new OpenGlException($"Renderer '{GlInterface.Renderer}' is blacklisted by '{item}'");
}
}
public void ClearContext() => Glx.MakeContextCurrent(_x11.Display,

10
src/Avalonia.X11/X11KeyTransform.cs

@ -221,12 +221,8 @@ namespace Avalonia.X11
//{ X11Key.?, Key.DeadCharProcessed }
};
public static Key ConvertKey(IntPtr key)
{
var ikey = key.ToInt32();
Key result;
return KeyDic.TryGetValue((X11Key)ikey, out result) ? result : Key.None;
}
}
public static Key ConvertKey(X11Key key)
=> KeyDic.TryGetValue(key, out var result) ? result : Key.None;
}
}

8
src/Avalonia.X11/X11Platform.cs

@ -96,6 +96,14 @@ namespace Avalonia
{
public bool UseEGL { get; set; }
public bool UseGpu { get; set; } = true;
public List<string> GlxRendererBlacklist { get; set; } = new List<string>
{
// llvmpipe is a software GL rasterizer. If it's returned by glGetString,
// that usually means that something in the system is horribly misconfigured
// and sometimes attempts to use GLX might cause a segfault
"llvmpipe"
};
public string WmClass { get; set; } = Assembly.GetEntryAssembly()?.GetName()?.Name ?? "AvaloniaApplication";
public bool? EnableMultiTouch { get; set; }
}

15
src/Avalonia.X11/X11Window.cs

@ -419,10 +419,21 @@ namespace Avalonia.X11
return;
var buffer = stackalloc byte[40];
var latinKeysym = XKeycodeToKeysym(_x11.Display, ev.KeyEvent.keycode, 0);
var index = ev.KeyEvent.state.HasFlag(XModifierMask.ShiftMask);
// We need the latin key, since it's mainly used for hotkeys, we use a different API for text anyway
var key = (X11Key)XKeycodeToKeysym(_x11.Display, ev.KeyEvent.keycode, index ? 1 : 0).ToInt32();
// Manually switch the Shift index for the keypad,
// there should be a proper way to do this
if (ev.KeyEvent.state.HasFlag(XModifierMask.Mod2Mask)
&& key > X11Key.Num_Lock && key <= X11Key.KP_9)
key = (X11Key)XKeycodeToKeysym(_x11.Display, ev.KeyEvent.keycode, index ? 0 : 1).ToInt32();
ScheduleInput(new RawKeyEventArgs(_keyboard, (ulong)ev.KeyEvent.time.ToInt64(),
ev.type == XEventName.KeyPress ? RawKeyEventType.KeyDown : RawKeyEventType.KeyUp,
X11KeyTransform.ConvertKey(latinKeysym), TranslateModifiers(ev.KeyEvent.state)), ref ev);
X11KeyTransform.ConvertKey(key), TranslateModifiers(ev.KeyEvent.state)), ref ev);
if (ev.type == XEventName.KeyPress)
{

3
src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/BindingExtension.cs

@ -40,7 +40,8 @@ namespace Avalonia.Markup.Xaml.MarkupExtensions
Source = Source,
StringFormat = StringFormat,
RelativeSource = RelativeSource,
DefaultAnchor = new WeakReference(GetDefaultAnchor(descriptorContext))
DefaultAnchor = new WeakReference(GetDefaultAnchor(descriptorContext)),
NameScope = new WeakReference<INameScope>(serviceProvider.GetService<INameScope>())
};
}

4
src/Markup/Avalonia.Markup.Xaml/Templates/ControlTemplate.cs

@ -17,6 +17,6 @@ namespace Avalonia.Markup.Xaml.Templates
public Type TargetType { get; set; }
public IControl Build(ITemplatedControl control) => TemplateContent.Load(Content);
public ControlTemplateResult Build(ITemplatedControl control) => TemplateContent.Load(Content);
}
}
}

4
src/Markup/Avalonia.Markup.Xaml/Templates/DataTemplate.cs

@ -32,6 +32,6 @@ namespace Avalonia.Markup.Xaml.Templates
}
}
public IControl Build(object data) => TemplateContent.Load(Content);
public IControl Build(object data) => TemplateContent.Load(Content).Control;
}
}
}

4
src/Markup/Avalonia.Markup.Xaml/Templates/ItemsPanelTemplate.cs

@ -14,8 +14,8 @@ namespace Avalonia.Markup.Xaml.Templates
public object Content { get; set; }
public IPanel Build()
=> (IPanel)TemplateContent.Load(Content);
=> (IPanel)TemplateContent.Load(Content).Control;
object ITemplate.Build() => Build();
}
}
}

4
src/Markup/Avalonia.Markup.Xaml/Templates/Template.cs

@ -13,8 +13,8 @@ namespace Avalonia.Markup.Xaml.Templates
[TemplateContent]
public object Content { get; set; }
public IControl Build() => TemplateContent.Load(Content);
public IControl Build() => TemplateContent.Load(Content).Control;
object ITemplate.Build() => Build();
}
}
}

5
src/Markup/Avalonia.Markup.Xaml/Templates/TemplateContent.cs

@ -4,17 +4,18 @@
using System;
using Avalonia.Controls;
using System.Collections.Generic;
using Avalonia.Controls.Templates;
namespace Avalonia.Markup.Xaml.Templates
{
public static class TemplateContent
{
public static IControl Load(object templateContent)
public static ControlTemplateResult Load(object templateContent)
{
if (templateContent is Func<IServiceProvider, object> direct)
{
return (IControl)direct(null);
return (ControlTemplateResult)direct(null);
}
throw new ArgumentException(nameof(templateContent));
}

4
src/Markup/Avalonia.Markup.Xaml/Templates/TreeDataTemplate.cs

@ -51,9 +51,9 @@ namespace Avalonia.Markup.Xaml.Templates
public IControl Build(object data)
{
var visualTreeForItem = TemplateContent.Load(Content);
var visualTreeForItem = TemplateContent.Load(Content).Control;
visualTreeForItem.DataContext = data;
return visualTreeForItem;
}
}
}
}

6
src/Markup/Avalonia.Markup.Xaml/XamlIl/AvaloniaXamlIlRuntimeCompiler.cs

@ -156,7 +156,7 @@ namespace Avalonia.Markup.Xaml.XamlIl
{
overrideField.SetValue(null,
new Action<object>(
target => { populateCb(XamlIlRuntimeHelpers.RootServiceProviderV1, target); }));
target => { populateCb(XamlIlRuntimeHelpers.CreateRootServiceProviderV2(), target); }));
try
{
return Activator.CreateInstance(targetType);
@ -170,11 +170,11 @@ namespace Avalonia.Markup.Xaml.XamlIl
var createCb = Expression.Lambda<Func<IServiceProvider, object>>(
Expression.Convert(Expression.Call(
created.GetMethod(AvaloniaXamlIlCompiler.BuildName), isp), typeof(object)), isp).Compile();
return createCb(XamlIlRuntimeHelpers.RootServiceProviderV1);
return createCb(XamlIlRuntimeHelpers.CreateRootServiceProviderV2());
}
else
{
populateCb(XamlIlRuntimeHelpers.RootServiceProviderV1, rootInstance);
populateCb(XamlIlRuntimeHelpers.CreateRootServiceProviderV2(), rootInstance);
return rootInstance;
}
}

22
src/Markup/Avalonia.Markup.Xaml/XamlIl/CompilerExtensions/AvaloniaXamlIlLanguage.cs

@ -35,6 +35,7 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions
},
ProvideValueTarget = typeSystem.GetType("Avalonia.Markup.Xaml.IProvideValueTarget"),
RootObjectProvider = typeSystem.GetType("Avalonia.Markup.Xaml.IRootObjectProvider"),
RootObjectProviderIntermediateRootPropertyName = "IntermediateRootObject",
UriContextProvider = typeSystem.GetType("Avalonia.Markup.Xaml.IUriContext"),
ParentStackProvider =
typeSystem.GetType("Avalonia.Markup.Xaml.XamlIl.Runtime.IAvaloniaXamlIlParentStackProvider"),
@ -53,9 +54,30 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions
ProvideValueTargetPropertyEmitter = XamlIlAvaloniaPropertyHelper.Emit,
};
rv.CustomAttributeResolver = new AttributeResolver(typeSystem, rv);
rv.ContextTypeBuilderCallback = (b, c) => EmitNameScopeField(rv, typeSystem, b, c);
return rv;
}
public const string ContextNameScopeFieldName = "AvaloniaNameScope";
private static void EmitNameScopeField(XamlIlLanguageTypeMappings mappings,
IXamlIlTypeSystem typeSystem,
IXamlIlTypeBuilder typebuilder, IXamlIlEmitter constructor)
{
var nameScopeType = typeSystem.FindType("Avalonia.Controls.INameScope");
var field = typebuilder.DefineField(nameScopeType,
ContextNameScopeFieldName, true, false);
constructor
.Ldarg_0()
.Ldarg(1)
.Ldtype(nameScopeType)
.EmitCall(mappings.ServiceProvider.GetMethod(new FindMethodMethodSignature("GetService",
typeSystem.FindType("System.Object"), typeSystem.FindType("System.Type"))))
.Stfld(field);
}
class AttributeResolver : IXamlIlCustomAttributeResolver
{
private readonly IXamlIlType _typeConverterAttribute;

107
src/Markup/Avalonia.Markup.Xaml/XamlIl/CompilerExtensions/Transformers/AddNameScopeRegistration.cs

@ -15,7 +15,7 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers
&& pa.Property.DeclaringType.FullName == "Avalonia.StyledElement")
{
if (context.ParentNodes().FirstOrDefault() is XamlIlManipulationGroupNode mg
&& mg.Children.OfType<ScopeRegistrationNode>().Any())
&& mg.Children.OfType<AvaloniaNameScopeRegistrationXamlIlNode>().Any())
return node;
IXamlIlAstValueNode value = null;
@ -41,54 +41,99 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers
Children =
{
pa,
new ScopeRegistrationNode(value)
new AvaloniaNameScopeRegistrationXamlIlNode(value, context.GetAvaloniaTypes())
}
};
}
if (!context.ParentNodes().Any()
&& node is XamlIlValueWithManipulationNode mnode)
{
mnode.Manipulation = new XamlIlManipulationGroupNode(mnode,
new[]
{
mnode.Manipulation,
new HandleRootObjectScopeNode(mnode, context.GetAvaloniaTypes())
});
}
return node;
}
class ScopeRegistrationNode : XamlIlAstNode, IXamlIlAstManipulationNode, IXamlIlAstEmitableNode
class HandleRootObjectScopeNode : XamlIlAstNode, IXamlIlAstManipulationNode, IXamlIlAstEmitableNode
{
public IXamlIlAstValueNode Value { get; set; }
public ScopeRegistrationNode(IXamlIlAstValueNode value) : base(value)
private readonly AvaloniaXamlIlWellKnownTypes _types;
public HandleRootObjectScopeNode(IXamlIlLineInfo lineInfo,
AvaloniaXamlIlWellKnownTypes types) : base(lineInfo)
{
Value = value;
_types = types;
}
public override void VisitChildren(IXamlIlAstVisitor visitor)
=> Value = (IXamlIlAstValueNode)Value.Visit(visitor);
public XamlIlNodeEmitResult Emit(XamlIlEmitContext context, IXamlIlEmitter codeGen)
{
var exts = context.Configuration.TypeSystem.GetType("Avalonia.Controls.NameScopeExtensions");
var findNameScope = exts.FindMethod(m => m.Name == "FindNameScope");
var registerMethod = findNameScope.ReturnType.FindMethod(m => m.Name == "Register");
using (var targetLoc = context.GetLocal(context.Configuration.WellKnownTypes.Object))
using (var nameScopeLoc = context.GetLocal(findNameScope.ReturnType))
var next = codeGen.DefineLabel();
var scopeField = context.RuntimeContext.ContextType.Fields.First(f =>
f.Name == AvaloniaXamlIlLanguage.ContextNameScopeFieldName);
using (var local = codeGen.LocalsPool.GetLocal(_types.StyledElement))
{
var exit = codeGen.DefineLabel();
codeGen
// var target = {pop}
.Stloc(targetLoc.Local)
// var scope = target.FindNameScope()
.Ldloc(targetLoc.Local)
.Castclass(findNameScope.Parameters[0])
.EmitCall(findNameScope)
.Stloc(nameScopeLoc.Local)
// if({scope} != null) goto call;
.Ldloc(nameScopeLoc.Local)
.Brfalse(exit)
.Ldloc(nameScopeLoc.Local);
context.Emit(Value, codeGen, Value.Type.GetClrType());
codeGen
.Ldloc(targetLoc.Local)
.EmitCall(registerMethod)
.MarkLabel(exit);
.Isinst(_types.StyledElement)
.Dup()
.Stloc(local.Local)
.Brfalse(next)
.Ldloc(local.Local)
.Ldloc(context.ContextLocal)
.Ldfld(scopeField)
.EmitCall(_types.NameScopeSetNameScope, true)
.MarkLabel(next)
.Ldloc(context.ContextLocal)
.Ldfld(scopeField)
.EmitCall(_types.INameScopeComplete, true);
}
return XamlIlNodeEmitResult.Void(1);
}
}
}
class AvaloniaNameScopeRegistrationXamlIlNode : XamlIlAstNode, IXamlIlAstManipulationNode, IXamlIlAstEmitableNode
{
private readonly AvaloniaXamlIlWellKnownTypes _types;
public IXamlIlAstValueNode Name { get; set; }
public AvaloniaNameScopeRegistrationXamlIlNode(IXamlIlAstValueNode name, AvaloniaXamlIlWellKnownTypes types) : base(name)
{
_types = types;
Name = name;
}
public override void VisitChildren(IXamlIlAstVisitor visitor)
=> Name = (IXamlIlAstValueNode)Name.Visit(visitor);
public XamlIlNodeEmitResult Emit(XamlIlEmitContext context, IXamlIlEmitter codeGen)
{
var scopeField = context.RuntimeContext.ContextType.Fields.First(f =>
f.Name == AvaloniaXamlIlLanguage.ContextNameScopeFieldName);
using (var targetLoc = context.GetLocal(context.Configuration.WellKnownTypes.Object))
{
codeGen
// var target = {pop}
.Stloc(targetLoc.Local)
// _context.NameScope.Register(Name, target)
.Ldloc(context.ContextLocal)
.Ldfld(scopeField);
context.Emit(Name, codeGen, Name.Type.GetClrType());
codeGen
.Ldloc(targetLoc.Local)
.EmitCall(_types.INameScopeRegister, true);
}
return XamlIlNodeEmitResult.Void(1);
}
}
}

26
src/Markup/Avalonia.Markup.Xaml/XamlIl/CompilerExtensions/Transformers/AvaloniaXamlIlWellKnownTypes.cs

@ -16,13 +16,21 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers
public IXamlIlMethod AvaloniaObjectSetValueMethod { get; }
public IXamlIlType IDisposable { get; }
public XamlIlTypeWellKnownTypes XamlIlTypes { get; }
public XamlIlLanguageTypeMappings XamlIlMappings { get; }
public IXamlIlType Transitions { get; }
public IXamlIlType AssignBindingAttribute { get; }
public IXamlIlType UnsetValueType { get; }
public IXamlIlType StyledElement { get; }
public IXamlIlType NameScope { get; }
public IXamlIlMethod NameScopeSetNameScope { get; }
public IXamlIlType INameScope { get; }
public IXamlIlMethod INameScopeRegister { get; }
public IXamlIlMethod INameScopeComplete { get; }
public AvaloniaXamlIlWellKnownTypes(XamlIlAstTransformationContext ctx)
{
XamlIlTypes = ctx.Configuration.WellKnownTypes;
XamlIlMappings = ctx.Configuration.TypeMappings;
AvaloniaObject = ctx.Configuration.TypeSystem.GetType("Avalonia.AvaloniaObject");
IAvaloniaObject = ctx.Configuration.TypeSystem.GetType("Avalonia.IAvaloniaObject");
AvaloniaObjectExtensions = ctx.Configuration.TypeSystem.GetType("Avalonia.AvaloniaObjectExtensions");
@ -37,8 +45,26 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers
AvaloniaProperty,
IBinding, ctx.Configuration.WellKnownTypes.Object);
UnsetValueType = ctx.Configuration.TypeSystem.GetType("Avalonia.UnsetValueType");
StyledElement = ctx.Configuration.TypeSystem.GetType("Avalonia.StyledElement");
INameScope = ctx.Configuration.TypeSystem.GetType("Avalonia.Controls.INameScope");
INameScopeRegister = INameScope.GetMethod(
new FindMethodMethodSignature("Register", XamlIlTypes.Void,
XamlIlTypes.String, XamlIlTypes.Object)
{
IsStatic = false, DeclaringOnly = true, IsExactMatch = true
});
INameScopeComplete = INameScope.GetMethod(
new FindMethodMethodSignature("Complete", XamlIlTypes.Void)
{
IsStatic = false, DeclaringOnly = true, IsExactMatch = true
});
NameScope = ctx.Configuration.TypeSystem.GetType("Avalonia.Controls.NameScope");
NameScopeSetNameScope = NameScope.GetMethod(new FindMethodMethodSignature("SetNameScope",
XamlIlTypes.Void, StyledElement, INameScope) {IsStatic = true});
AvaloniaObjectSetValueMethod = AvaloniaObject.FindMethod("SetValue", XamlIlTypes.Void,
false, AvaloniaProperty, XamlIlTypes.Object, BindingPriority);
}
}

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

@ -1,8 +1,10 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Reflection;
using Avalonia.Controls;
using Avalonia.Controls.Templates;
using Avalonia.Data;
// ReSharper disable UnusedMember.Global
// ReSharper disable UnusedParameter.Global
@ -17,7 +19,14 @@ namespace Avalonia.Markup.Xaml.XamlIl.Runtime
var resourceNodes = provider.GetService<IAvaloniaXamlIlParentStackProvider>().Parents
.OfType<IResourceNode>().ToList();
var rootObject = provider.GetService<IRootObjectProvider>().RootObject;
return sp => builder(new DeferredParentServiceProvider(sp, resourceNodes, rootObject));
var parentScope = provider.GetService<INameScope>();
return sp =>
{
var scope = parentScope != null ? new ChildNameScope(parentScope) : (INameScope)new NameScope();
var obj = builder(new DeferredParentServiceProvider(sp, resourceNodes, rootObject, scope));
scope.Complete();
return new ControlTemplateResult((IControl)obj, scope);
};
}
class DeferredParentServiceProvider :
@ -27,12 +36,14 @@ namespace Avalonia.Markup.Xaml.XamlIl.Runtime
{
private readonly IServiceProvider _parentProvider;
private readonly List<IResourceNode> _parentResourceNodes;
private readonly INameScope _nameScope;
public DeferredParentServiceProvider(IServiceProvider parentProvider, List<IResourceNode> parentResourceNodes,
object rootObject)
object rootObject, INameScope nameScope)
{
_parentProvider = parentProvider;
_parentResourceNodes = parentResourceNodes;
_nameScope = nameScope;
RootObject = rootObject;
}
@ -48,6 +59,8 @@ namespace Avalonia.Markup.Xaml.XamlIl.Runtime
public object GetService(Type serviceType)
{
if (serviceType == typeof(INameScope))
return _nameScope;
if (serviceType == typeof(IAvaloniaXamlIlParentStackProvider))
return this;
if (serviceType == typeof(IRootObjectProvider))
@ -56,6 +69,7 @@ namespace Avalonia.Markup.Xaml.XamlIl.Runtime
}
public object RootObject { get; }
public object IntermediateRootObject => RootObject;
}
@ -132,12 +146,28 @@ namespace Avalonia.Markup.Xaml.XamlIl.Runtime
}
}
public static readonly IServiceProvider RootServiceProviderV1 = new RootServiceProvider();
[Obsolete("Don't use", true)]
public static readonly IServiceProvider RootServiceProviderV1 = new RootServiceProvider(null);
[DebuggerStepThrough]
public static IServiceProvider CreateRootServiceProviderV2()
{
return new RootServiceProvider(new NameScope());
}
class RootServiceProvider : IServiceProvider, IAvaloniaXamlIlParentStackProvider
{
private readonly INameScope _nameScope;
public RootServiceProvider(INameScope nameScope)
{
_nameScope = nameScope;
}
public object GetService(Type serviceType)
{
if (serviceType == typeof(INameScope))
return _nameScope;
if (serviceType == typeof(IAvaloniaXamlIlParentStackProvider))
return this;
return null;

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

@ -1 +1 @@
Subproject commit 894b2c02827fd5eb16a338de5d5b6c9fbc60fef5
Subproject commit c2ec091f79fb4e1eea629bc823c9c24da7050022

8
src/Markup/Avalonia.Markup.Xaml/XamlTypes.cs

@ -10,7 +10,15 @@ namespace Avalonia.Markup.Xaml
public interface IRootObjectProvider
{
/// <summary>
/// The root object of the xaml file
/// </summary>
object RootObject { get; }
/// <summary>
/// The "current" root object, contains either the root of the xaml file
/// or the root object of the control/data template
/// </summary>
object IntermediateRootObject { get; }
}
public interface IUriContext

14
src/Markup/Avalonia.Markup/Data/Binding.cs

@ -5,6 +5,7 @@ using System;
using System.Linq;
using System.Reactive;
using System.Reactive.Linq;
using Avalonia.Controls;
using Avalonia.Data.Converters;
using Avalonia.Data.Core;
using Avalonia.LogicalTree;
@ -90,6 +91,8 @@ namespace Avalonia.Data
public string StringFormat { get; set; }
public WeakReference DefaultAnchor { get; set; }
public WeakReference<INameScope> NameScope { get; set; }
/// <summary>
/// Gets or sets a function used to resolve types from names in the binding path.
@ -110,7 +113,9 @@ namespace Avalonia.Data
ExpressionObserver observer;
var (node, mode) = ExpressionObserverBuilder.Parse(Path, enableDataValidation, TypeResolver);
INameScope nameScope = null;
NameScope?.TryGetTarget(out nameScope);
var (node, mode) = ExpressionObserverBuilder.Parse(Path, enableDataValidation, TypeResolver, nameScope);
if (ElementName != null)
{
@ -254,9 +259,12 @@ namespace Avalonia.Data
ExpressionNode node)
{
Contract.Requires<ArgumentNullException>(target != null);
NameScope.TryGetTarget(out var scope);
if (scope == null)
throw new InvalidOperationException("Name scope is null or was already collected");
var result = new ExpressionObserver(
ControlLocator.Track(target, elementName),
NameScopeLocator.Track(scope, elementName),
node,
null);
return result;

7
src/Markup/Avalonia.Markup/Data/MultiBinding.cs

@ -97,7 +97,12 @@ namespace Avalonia.Data
var culture = CultureInfo.CurrentCulture;
var converted = converter.Convert(values, targetType, ConverterParameter, culture);
if (converted == AvaloniaProperty.UnsetValue && FallbackValue != null)
if (converted == BindingOperations.DoNothing)
{
return converted;
}
if (converted == AvaloniaProperty.UnsetValue)
{
converted = FallbackValue;
}

6
src/Markup/Avalonia.Markup/Markup/Parsers/ExpressionObserverBuilder.cs

@ -1,5 +1,6 @@
using System;
using System.Reactive;
using Avalonia.Controls;
using Avalonia.Data.Core;
using Avalonia.Utilities;
@ -7,7 +8,8 @@ namespace Avalonia.Markup.Parsers
{
public static class ExpressionObserverBuilder
{
internal static (ExpressionNode Node, SourceMode Mode) Parse(string expression, bool enableValidation = false, Func<string, string, Type> typeResolver = null)
internal static (ExpressionNode Node, SourceMode Mode) Parse(string expression, bool enableValidation = false, Func<string, string, Type> typeResolver = null,
INameScope nameScope = null)
{
if (string.IsNullOrWhiteSpace(expression))
{
@ -15,7 +17,7 @@ namespace Avalonia.Markup.Parsers
}
var reader = new CharacterReader(expression.AsSpan());
var parser = new ExpressionParser(enableValidation, typeResolver);
var parser = new ExpressionParser(enableValidation, typeResolver, nameScope);
var node = parser.Parse(ref reader);
if (!reader.End)

7
src/Markup/Avalonia.Markup/Markup/Parsers/ExpressionParser.cs

@ -7,6 +7,7 @@ using Avalonia.Utilities;
using System;
using System.Collections.Generic;
using System.Linq;
using Avalonia.Controls;
namespace Avalonia.Markup.Parsers
{
@ -20,10 +21,12 @@ namespace Avalonia.Markup.Parsers
{
private readonly bool _enableValidation;
private readonly Func<string, string, Type> _typeResolver;
private readonly INameScope _nameScope;
public ExpressionParser(bool enableValidation, Func<string, string, Type> typeResolver)
public ExpressionParser(bool enableValidation, Func<string, string, Type> typeResolver, INameScope nameScope)
{
_typeResolver = typeResolver;
_nameScope = nameScope;
_enableValidation = enableValidation;
}
@ -213,7 +216,7 @@ namespace Avalonia.Markup.Parsers
throw new ExpressionParseException(r.Position, "Element name expected after '#'.");
}
nodes.Add(new ElementNameNode(name.ToString()));
nodes.Add(new ElementNameNode(_nameScope, name.ToString()));
return State.AfterMember;
}

13
src/Markup/Avalonia.Markup/Markup/Parsers/Nodes/ElementNameNode.cs

@ -1,4 +1,5 @@
using System;
using Avalonia.Controls;
using Avalonia.Data.Core;
using Avalonia.LogicalTree;
@ -6,11 +7,13 @@ namespace Avalonia.Markup.Parsers.Nodes
{
internal class ElementNameNode : ExpressionNode
{
private readonly WeakReference<INameScope> _nameScope;
private readonly string _name;
private IDisposable _subscription;
public ElementNameNode(string name)
public ElementNameNode(INameScope nameScope, string name)
{
_nameScope = new WeakReference<INameScope>(nameScope);
_name = name;
}
@ -18,14 +21,10 @@ namespace Avalonia.Markup.Parsers.Nodes
protected override void StartListeningCore(WeakReference reference)
{
if (reference.Target is ILogical logical)
{
_subscription = ControlLocator.Track(logical, _name).Subscribe(ValueChanged);
}
if (_nameScope.TryGetTarget(out var scope))
_subscription = NameScopeLocator.Track(scope, _name).Subscribe(ValueChanged);
else
{
_subscription = null;
}
}
protected override void StopListeningCore()

1
src/Skia/Avalonia.Skia/DrawingContextImpl.cs

@ -101,6 +101,7 @@ namespace Avalonia.Skia
public SKCanvas Canvas { get; }
SKCanvas ISkiaDrawingContextImpl.SkCanvas => Canvas;
GRContext ISkiaDrawingContextImpl.GrContext => _grContext;
/// <inheritdoc />
public void Clear(Color color)

1
src/Skia/Avalonia.Skia/ISkiaDrawingContextImpl.cs

@ -6,5 +6,6 @@ namespace Avalonia.Skia
public interface ISkiaDrawingContextImpl : IDrawingContextImpl
{
SKCanvas SkCanvas { get; }
GRContext GrContext { get; }
}
}

8
src/Windows/Avalonia.Win32/Interop/UnmanagedMethods.cs

@ -608,6 +608,14 @@ namespace Avalonia.Win32.Interop
GWL_USERDATA = -21
}
public enum MenuCharParam
{
MNC_IGNORE = 0,
MNC_CLOSE = 1,
MNC_EXECUTE = 2,
MNC_SELECT = 3
}
[StructLayout(LayoutKind.Sequential)]
public struct RGBQUAD
{

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

@ -514,6 +514,10 @@ namespace Avalonia.Win32
KeyInterop.KeyFromVirtualKey(ToInt32(wParam)), WindowsKeyboardDevice.Instance.Modifiers);
break;
case UnmanagedMethods.WindowsMessage.WM_MENUCHAR:
// mute the system beep
return (IntPtr)((Int32)UnmanagedMethods.MenuCharParam.MNC_CLOSE << 16);
case UnmanagedMethods.WindowsMessage.WM_KEYUP:
case UnmanagedMethods.WindowsMessage.WM_SYSKEYUP:
e = new RawKeyEventArgs(

78
tests/Avalonia.Base.UnitTests/AvaloniaObjectTests_DataValidation.cs

@ -3,6 +3,7 @@ using System.Collections.Generic;
using System.Linq;
using System.Reactive.Subjects;
using Avalonia.Data;
using Avalonia.UnitTests;
using Xunit;
namespace Avalonia.Base.UnitTests
@ -34,10 +35,10 @@ namespace Avalonia.Base.UnitTests
{
var target = new Class1();
target.SetValue(Class1.ValidatedDirectProperty, new BindingNotification(6));
target.SetValue(Class1.ValidatedDirectProperty, new BindingNotification(new Exception(), BindingErrorType.Error));
target.SetValue(Class1.ValidatedDirectProperty, new BindingNotification(new Exception(), BindingErrorType.DataValidationError));
target.SetValue(Class1.ValidatedDirectProperty, new BindingNotification(7));
target.SetValue(Class1.ValidatedDirectIntProperty, new BindingNotification(6));
target.SetValue(Class1.ValidatedDirectIntProperty, new BindingNotification(new Exception(), BindingErrorType.Error));
target.SetValue(Class1.ValidatedDirectIntProperty, new BindingNotification(new Exception(), BindingErrorType.DataValidationError));
target.SetValue(Class1.ValidatedDirectIntProperty, new BindingNotification(7));
Assert.Equal(
new[]
@ -73,7 +74,7 @@ namespace Avalonia.Base.UnitTests
var source = new Subject<object>();
var target = new Class1
{
[!Class1.ValidatedDirectProperty] = source.ToBinding(),
[!Class1.ValidatedDirectIntProperty] = source.ToBinding(),
};
source.OnNext(new BindingNotification(6));
@ -92,6 +93,30 @@ namespace Avalonia.Base.UnitTests
target.Notifications.AsEnumerable());
}
[Fact]
public void Bound_Validated_Direct_String_Property_Can_Be_Set_To_Null()
{
var source = new ViewModel
{
StringValue = "foo",
};
var target = new Class1
{
[!Class1.ValidatedDirectStringProperty] = new Binding
{
Path = nameof(ViewModel.StringValue),
Source = source,
},
};
Assert.Equal("foo", target.ValidatedDirectString);
source.StringValue = null;
Assert.Null(target.ValidatedDirectString);
}
private class Class1 : AvaloniaObject
{
public static readonly StyledProperty<int> NonValidatedProperty =
@ -104,15 +129,23 @@ namespace Avalonia.Base.UnitTests
o => o.NonValidatedDirect,
(o, v) => o.NonValidatedDirect = v);
public static readonly DirectProperty<Class1, int> ValidatedDirectProperty =
public static readonly DirectProperty<Class1, int> ValidatedDirectIntProperty =
AvaloniaProperty.RegisterDirect<Class1, int>(
nameof(ValidatedDirect),
o => o.ValidatedDirect,
(o, v) => o.ValidatedDirect = v,
nameof(ValidatedDirectInt),
o => o.ValidatedDirectInt,
(o, v) => o.ValidatedDirectInt = v,
enableDataValidation: true);
public static readonly DirectProperty<Class1, string> ValidatedDirectStringProperty =
AvaloniaProperty.RegisterDirect<Class1, string>(
nameof(ValidatedDirectString),
o => o.ValidatedDirectString,
(o, v) => o.ValidatedDirectString = v,
enableDataValidation: true);
private int _nonValidatedDirect;
private int _direct;
private int _directInt;
private string _directString;
public int NonValidated
{
@ -122,14 +155,20 @@ namespace Avalonia.Base.UnitTests
public int NonValidatedDirect
{
get { return _direct; }
get { return _directInt; }
set { SetAndRaise(NonValidatedDirectProperty, ref _nonValidatedDirect, value); }
}
public int ValidatedDirect
public int ValidatedDirectInt
{
get { return _directInt; }
set { SetAndRaise(ValidatedDirectIntProperty, ref _directInt, value); }
}
public string ValidatedDirectString
{
get { return _direct; }
set { SetAndRaise(ValidatedDirectProperty, ref _direct, value); }
get { return _directString; }
set { SetAndRaise(ValidatedDirectStringProperty, ref _directString, value); }
}
public IList<BindingNotification> Notifications { get; } = new List<BindingNotification>();
@ -139,5 +178,16 @@ namespace Avalonia.Base.UnitTests
Notifications.Add(notification);
}
}
public class ViewModel : NotifyingBase
{
private string _stringValue;
public string StringValue
{
get { return _stringValue; }
set { _stringValue = value; RaisePropertyChanged(); }
}
}
}
}

8
tests/Avalonia.Controls.UnitTests/AutoCompleteBoxTests.cs

@ -1012,23 +1012,23 @@ namespace Avalonia.Controls.UnitTests
}
private IControlTemplate CreateTemplate()
{
return new FuncControlTemplate<AutoCompleteBox>(control =>
return new FuncControlTemplate<AutoCompleteBox>((control, scope) =>
{
var textBox =
new TextBox
{
Name = "PART_TextBox"
};
}.RegisterInNameScope(scope);
var listbox =
new ListBox
{
Name = "PART_SelectingItemsControl"
};
}.RegisterInNameScope(scope);
var popup =
new Popup
{
Name = "PART_Popup"
};
}.RegisterInNameScope(scope);
var panel = new Panel();
panel.Children.Add(textBox);

4
tests/Avalonia.Controls.UnitTests/CarouselTests.cs

@ -302,7 +302,7 @@ namespace Avalonia.Controls.UnitTests
Assert.Equal("FooBar", target.SelectedItem);
}
private Control CreateTemplate(Carousel control)
private Control CreateTemplate(Carousel control, INameScope scope)
{
return new CarouselPresenter
{
@ -312,7 +312,7 @@ namespace Avalonia.Controls.UnitTests
[~CarouselPresenter.ItemsPanelProperty] = control[~Carousel.ItemsPanelProperty],
[~CarouselPresenter.SelectedIndexProperty] = control[~Carousel.SelectedIndexProperty],
[~CarouselPresenter.PageTransitionProperty] = control[~Carousel.PageTransitionProperty],
};
}.RegisterInNameScope(scope);
}
}
}

8
tests/Avalonia.Controls.UnitTests/ComboBoxTests.cs

@ -80,7 +80,7 @@ namespace Avalonia.Controls.UnitTests
private FuncControlTemplate GetTemplate()
{
return new FuncControlTemplate<ComboBox>(parent =>
return new FuncControlTemplate<ComboBox>((parent, scope) =>
{
return new Panel
{
@ -94,7 +94,7 @@ namespace Avalonia.Controls.UnitTests
new ToggleButton
{
Name = "toggle",
},
}.RegisterInNameScope(scope),
new Popup
{
Name = "PART_Popup",
@ -102,8 +102,8 @@ namespace Avalonia.Controls.UnitTests
{
Name = "PART_ItemsPresenter",
[!ItemsPresenter.ItemsProperty] = parent[!ComboBox.ItemsProperty],
}
}
}.RegisterInNameScope(scope)
}.RegisterInNameScope(scope)
}
};
});

10
tests/Avalonia.Controls.UnitTests/ContentControlTests.cs

@ -126,7 +126,7 @@ namespace Avalonia.Controls.UnitTests
var target = new ContentControl
{
Template = GetTemplate(),
ContentTemplate = new FuncDataTemplate<string>(_ => new Canvas()),
ContentTemplate = new FuncDataTemplate<string>((_, __) => new Canvas()),
};
target.Content = "Foo";
@ -302,8 +302,8 @@ namespace Avalonia.Controls.UnitTests
var target = new ContentControl
{
Template = new FuncControlTemplate<ContentControl>(_ => presenter),
ContentTemplate = new FuncDataTemplate<string>(x => new Canvas()),
Template = new FuncControlTemplate<ContentControl>((_, __) => presenter),
ContentTemplate = new FuncDataTemplate<string>((_, __) => new Canvas()),
Content = "foo",
};
@ -333,7 +333,7 @@ namespace Avalonia.Controls.UnitTests
private FuncControlTemplate GetTemplate()
{
return new FuncControlTemplate<ContentControl>(parent =>
return new FuncControlTemplate<ContentControl>((parent, scope) =>
{
return new Border
{
@ -343,7 +343,7 @@ namespace Avalonia.Controls.UnitTests
Name = "PART_ContentPresenter",
[~ContentPresenter.ContentProperty] = parent[~ContentControl.ContentProperty],
[~ContentPresenter.ContentTemplateProperty] = parent[~ContentControl.ContentTemplateProperty],
}
}.RegisterInNameScope(scope)
};
});
}

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

@ -16,6 +16,60 @@ namespace Avalonia.Controls.UnitTests
private Mock<IPopupImpl> popupImpl;
private MouseTestHelper _mouse = new MouseTestHelper();
[Fact]
public void Opening_Raises_Single_Opened_Event()
{
using (Application())
{
var sut = new ContextMenu();
var target = new Panel
{
ContextMenu = sut
};
new Window { Content = target };
int openedCount = 0;
sut.MenuOpened += (sender, args) =>
{
openedCount++;
};
sut.Open(null);
Assert.Equal(1, openedCount);
}
}
[Fact]
public void Closing_Raises_Single_Closed_Event()
{
using (Application())
{
var sut = new ContextMenu();
var target = new Panel
{
ContextMenu = sut
};
new Window { Content = target };
sut.Open(null);
int closedCount = 0;
sut.MenuClosed += (sender, args) =>
{
closedCount++;
};
sut.Close();
Assert.Equal(1, closedCount);
}
}
[Fact]
public void Clicking_On_Control_Toggles_ContextMenu()
{

10
tests/Avalonia.Controls.UnitTests/DatePickerTests.cs

@ -93,28 +93,28 @@ namespace Avalonia.Controls.UnitTests
private IControlTemplate CreateTemplate()
{
return new FuncControlTemplate<DatePicker>(control =>
return new FuncControlTemplate<DatePicker>((control, scope) =>
{
var textBox =
new TextBox
{
Name = "PART_TextBox"
};
}.RegisterInNameScope(scope);
var button =
new Button
{
Name = "PART_Button"
};
}.RegisterInNameScope(scope);
var calendar =
new Calendar
{
Name = "PART_Calendar"
};
}.RegisterInNameScope(scope);
var popup =
new Popup
{
Name = "PART_Popup"
};
}.RegisterInNameScope(scope);
var panel = new Panel();
panel.Children.Add(textBox);

21
tests/Avalonia.Controls.UnitTests/GridSplitterTests.cs

@ -20,6 +20,7 @@ namespace Avalonia.Controls.UnitTests
[Fact]
public void Detects_Horizontal_Orientation()
{
GridSplitter splitter;
var grid = new Grid()
{
RowDefinitions = new RowDefinitions("*,Auto,*"),
@ -27,7 +28,7 @@ namespace Avalonia.Controls.UnitTests
Children =
{
new Border { [Grid.RowProperty] = 0 },
new GridSplitter { [Grid.RowProperty] = 1, Name = "splitter" },
(splitter = new GridSplitter { [Grid.RowProperty] = 1 }),
new Border { [Grid.RowProperty] = 2 }
}
};
@ -35,12 +36,13 @@ namespace Avalonia.Controls.UnitTests
var root = new TestRoot { Child = grid };
root.Measure(new Size(100, 300));
root.Arrange(new Rect(0, 0, 100, 300));
Assert.Contains(grid.FindControl<GridSplitter>("splitter").Classes, ":horizontal".Equals);
Assert.Contains(splitter.Classes, ":horizontal".Equals);
}
[Fact]
public void Detects_Vertical_Orientation()
{
GridSplitter splitter;
var grid = new Grid()
{
ColumnDefinitions = new ColumnDefinitions("*,Auto,*"),
@ -48,7 +50,7 @@ namespace Avalonia.Controls.UnitTests
Children =
{
new Border { [Grid.ColumnProperty] = 0 },
new GridSplitter { [Grid.ColumnProperty] = 1, Name = "splitter" },
(splitter = new GridSplitter { [Grid.ColumnProperty] = 1}),
new Border { [Grid.ColumnProperty] = 2 },
}
};
@ -56,12 +58,13 @@ namespace Avalonia.Controls.UnitTests
var root = new TestRoot { Child = grid };
root.Measure(new Size(100, 300));
root.Arrange(new Rect(0, 0, 100, 300));
Assert.Contains(grid.FindControl<GridSplitter>("splitter").Classes, ":vertical".Equals);
Assert.Contains(splitter.Classes, ":vertical".Equals);
}
[Fact]
public void Detects_With_Both_Auto()
{
GridSplitter splitter;
var grid = new Grid()
{
ColumnDefinitions = new ColumnDefinitions("Auto,Auto,Auto"),
@ -69,7 +72,7 @@ namespace Avalonia.Controls.UnitTests
Children =
{
new Border { [Grid.ColumnProperty] = 0 },
new GridSplitter { [Grid.ColumnProperty] = 1, Name = "splitter" },
(splitter = new GridSplitter { [Grid.ColumnProperty] = 1}),
new Border { [Grid.ColumnProperty] = 2 },
}
};
@ -77,7 +80,7 @@ namespace Avalonia.Controls.UnitTests
var root = new TestRoot { Child = grid };
root.Measure(new Size(100, 300));
root.Arrange(new Rect(0, 0, 100, 300));
Assert.Contains(grid.FindControl<GridSplitter>("splitter").Classes, ":vertical".Equals);
Assert.Contains(splitter.Classes, ":vertical".Equals);
}
[Fact]
@ -129,13 +132,14 @@ namespace Avalonia.Controls.UnitTests
[Fact]
public void In_First_Position_Doesnt_Throw_Exception()
{
GridSplitter splitter;
var grid = new Grid()
{
ColumnDefinitions = new ColumnDefinitions("Auto,*,*"),
RowDefinitions = new RowDefinitions("*,*"),
Children =
{
new GridSplitter { [Grid.ColumnProperty] = 0, Name = "splitter" },
(splitter = new GridSplitter { [Grid.ColumnProperty] = 0} ),
new Border { [Grid.ColumnProperty] = 1 },
new Border { [Grid.ColumnProperty] = 2 },
}
@ -144,7 +148,6 @@ namespace Avalonia.Controls.UnitTests
var root = new TestRoot { Child = grid };
root.Measure(new Size(100, 300));
root.Arrange(new Rect(0, 0, 100, 300));
var splitter = grid.FindControl<GridSplitter>("splitter");
splitter.RaiseEvent(new VectorEventArgs
{
RoutedEvent = Thumb.DragDeltaEvent,
@ -199,4 +202,4 @@ namespace Avalonia.Controls.UnitTests
Assert.Equal(columnDefinitions[2].Width, new GridLength(80, GridUnitType.Star));
}
}
}
}

4
tests/Avalonia.Controls.UnitTests/HeaderedItemsControlTests .cs

@ -63,7 +63,7 @@ namespace Avalonia.Controls.UnitTests
private FuncControlTemplate GetTemplate()
{
return new FuncControlTemplate<HeaderedItemsControl>(parent =>
return new FuncControlTemplate<HeaderedItemsControl>((parent, scope) =>
{
return new Border
{
@ -71,7 +71,7 @@ namespace Avalonia.Controls.UnitTests
{
Name = "PART_HeaderPresenter",
[~ContentPresenter.ContentProperty] = parent[~HeaderedItemsControl.HeaderProperty],
}
}.RegisterInNameScope(scope)
};
});
}

31
tests/Avalonia.Controls.UnitTests/ItemsControlTests.cs

@ -23,7 +23,7 @@ namespace Avalonia.Controls.UnitTests
var target = new ItemsControl
{
Template = GetTemplate(),
ItemTemplate = new FuncDataTemplate<string>(_ => new Canvas()),
ItemTemplate = new FuncDataTemplate<string>((_, __) => new Canvas()),
};
target.Items = new[] { "Foo" };
@ -411,7 +411,7 @@ namespace Avalonia.Controls.UnitTests
DataContext = "Base",
DataTemplates =
{
new FuncDataTemplate<Item>(x => new Button { Content = x })
new FuncDataTemplate<Item>((x, __) => new Button { Content = x })
},
Items = items,
};
@ -451,29 +451,6 @@ namespace Avalonia.Controls.UnitTests
Assert.Null(NameScope.GetNameScope((TextBlock)item));
}
[Fact]
public void DataTemplate_Created_Content_Should_Be_NameScope()
{
var items = new object[]
{
"foo",
};
var target = new ItemsControl
{
Template = GetTemplate(),
Items = items,
};
target.ApplyTemplate();
target.Presenter.ApplyTemplate();
var container = (ContentPresenter)target.Presenter.Panel.LogicalChildren[0];
container.UpdateChild();
Assert.NotNull(NameScope.GetNameScope((TextBlock)container.Child));
}
[Fact]
public void Focuses_Next_Item_On_Key_Down()
{
@ -557,7 +534,7 @@ namespace Avalonia.Controls.UnitTests
private FuncControlTemplate GetTemplate()
{
return new FuncControlTemplate<ItemsControl>(parent =>
return new FuncControlTemplate<ItemsControl>((parent, scope) =>
{
return new Border
{
@ -566,7 +543,7 @@ namespace Avalonia.Controls.UnitTests
{
Name = "PART_ItemsPresenter",
[~ItemsPresenter.ItemsProperty] = parent[~ItemsControl.ItemsProperty],
}
}.RegisterInNameScope(scope)
};
});
}

30
tests/Avalonia.Controls.UnitTests/ListBoxTests.cs

@ -25,7 +25,7 @@ namespace Avalonia.Controls.UnitTests
{
Template = ListBoxTemplate(),
Items = new[] { "Foo" },
ItemTemplate = new FuncDataTemplate<string>(_ => new Canvas()),
ItemTemplate = new FuncDataTemplate<string>((_, __) => new Canvas()),
};
Prepare(target);
@ -113,7 +113,7 @@ namespace Avalonia.Controls.UnitTests
DataContext = "Base",
DataTemplates =
{
new FuncDataTemplate<Item>(x => new Button { Content = x })
new FuncDataTemplate<Item>((x, _) => new Button { Content = x })
},
Items = items,
};
@ -138,7 +138,7 @@ namespace Avalonia.Controls.UnitTests
{
Template = ListBoxTemplate(),
Items = Enumerable.Range(0, 20).Select(x => $"Item {x}").ToList(),
ItemTemplate = new FuncDataTemplate<string>(x => new TextBlock { Height = 10 }),
ItemTemplate = new FuncDataTemplate<string>((x, _) => new TextBlock { Height = 10 }),
SelectedIndex = 0,
};
@ -162,7 +162,7 @@ namespace Avalonia.Controls.UnitTests
{
Template = ListBoxTemplate(),
Items = Enumerable.Range(0, 20).Select(x => $"Item {x}").ToList(),
ItemTemplate = new FuncDataTemplate<string>(x => new TextBlock { Width = 20, Height = 10 }),
ItemTemplate = new FuncDataTemplate<string>((x, _) => new TextBlock { Width = 20, Height = 10 }),
SelectedIndex = 0,
};
@ -181,7 +181,7 @@ namespace Avalonia.Controls.UnitTests
{
Template = ListBoxTemplate(),
Items = items,
ItemTemplate = new FuncDataTemplate<string>(x => new TextBlock { Width = 20, Height = 10 }),
ItemTemplate = new FuncDataTemplate<string>((x, _) => new TextBlock { Width = 20, Height = 10 }),
SelectedIndex = 0,
};
@ -205,7 +205,7 @@ namespace Avalonia.Controls.UnitTests
Template = ListBoxTemplate(),
Items = items,
SelectionMode = SelectionMode.Toggle,
ItemTemplate = new FuncDataTemplate<string>(x => new TextBlock { Height = 10 })
ItemTemplate = new FuncDataTemplate<string>((x, _) => new TextBlock { Height = 10 })
};
Prepare(target);
@ -239,7 +239,7 @@ namespace Avalonia.Controls.UnitTests
{
Template = ListBoxTemplate(),
Items = items,
ItemTemplate = new FuncDataTemplate<string>(x => new TextBlock { Height = 11 })
ItemTemplate = new FuncDataTemplate<string>((x, _) => new TextBlock { Height = 11 })
};
Prepare(target);
@ -287,7 +287,7 @@ namespace Avalonia.Controls.UnitTests
target.DataContext = items;
target.VirtualizationMode = virtualizationMode;
target.ItemTemplate = new FuncDataTemplate<object>(c =>
target.ItemTemplate = new FuncDataTemplate<object>((c, _) =>
{
var tb = new TextBlock() { Height = 10, Width = 30 };
tb.Bind(TextBlock.TextProperty, new Data.Binding());
@ -334,7 +334,7 @@ namespace Avalonia.Controls.UnitTests
private FuncControlTemplate ListBoxTemplate()
{
return new FuncControlTemplate<ListBox>(parent =>
return new FuncControlTemplate<ListBox>((parent, scope) =>
new ScrollViewer
{
Name = "PART_ScrollViewer",
@ -345,24 +345,24 @@ namespace Avalonia.Controls.UnitTests
[~ItemsPresenter.ItemsProperty] = parent.GetObservable(ItemsControl.ItemsProperty).ToBinding(),
[~ItemsPresenter.ItemsPanelProperty] = parent.GetObservable(ItemsControl.ItemsPanelProperty).ToBinding(),
[~ItemsPresenter.VirtualizationModeProperty] = parent.GetObservable(ListBox.VirtualizationModeProperty).ToBinding(),
}
});
}.RegisterInNameScope(scope)
}.RegisterInNameScope(scope));
}
private FuncControlTemplate ListBoxItemTemplate()
{
return new FuncControlTemplate<ListBoxItem>(parent =>
return new FuncControlTemplate<ListBoxItem>((parent, scope) =>
new ContentPresenter
{
Name = "PART_ContentPresenter",
[!ContentPresenter.ContentProperty] = parent[!ListBoxItem.ContentProperty],
[!ContentPresenter.ContentTemplateProperty] = parent[!ListBoxItem.ContentTemplateProperty],
});
}.RegisterInNameScope(scope));
}
private FuncControlTemplate ScrollViewerTemplate()
{
return new FuncControlTemplate<ScrollViewer>(parent =>
return new FuncControlTemplate<ScrollViewer>((parent, scope) =>
new ScrollContentPresenter
{
Name = "PART_ContentPresenter",
@ -370,7 +370,7 @@ namespace Avalonia.Controls.UnitTests
[~~ScrollContentPresenter.ExtentProperty] = parent[~~ScrollViewer.ExtentProperty],
[~~ScrollContentPresenter.OffsetProperty] = parent[~~ScrollViewer.OffsetProperty],
[~~ScrollContentPresenter.ViewportProperty] = parent[~~ScrollViewer.ViewportProperty],
});
}.RegisterInNameScope(scope));
}
private void Prepare(ListBox target)

11
tests/Avalonia.Controls.UnitTests/ListBoxTests_Single.cs

@ -245,7 +245,7 @@ namespace Avalonia.Controls.UnitTests
}
}
private Control CreateListBoxTemplate(ITemplatedControl parent)
private Control CreateListBoxTemplate(ITemplatedControl parent, INameScope scope)
{
return new ScrollViewer
{
@ -254,17 +254,18 @@ namespace Avalonia.Controls.UnitTests
{
Name = "PART_ItemsPresenter",
[~ItemsPresenter.ItemsProperty] = parent.GetObservable(ItemsControl.ItemsProperty).ToBinding(),
}
}.RegisterInNameScope(scope)
};
}
private Control CreateScrollViewerTemplate(ITemplatedControl parent)
private Control CreateScrollViewerTemplate(ITemplatedControl parent, INameScope scope)
{
return new ScrollContentPresenter
{
Name = "PART_ContentPresenter",
[~ContentPresenter.ContentProperty] = parent.GetObservable(ContentControl.ContentProperty).ToBinding(),
};
[~ContentPresenter.ContentProperty] =
parent.GetObservable(ContentControl.ContentProperty).ToBinding(),
}.RegisterInNameScope(scope);
}
private void ApplyTemplate(ListBox target)

13
tests/Avalonia.Controls.UnitTests/Mixins/ContentControlMixinTests.cs

@ -21,15 +21,16 @@ namespace Avalonia.Controls.UnitTests.Mixins
{
var target = new TestControl()
{
Template = new FuncControlTemplate(_ => new Panel
Template = new FuncControlTemplate((_, scope) => new Panel
{
Children =
{
new ContentPresenter { Name = "Content_1_Presenter" },
new ContentPresenter { Name = "Content_2_Presenter" }
new ContentPresenter {Name = "Content_1_Presenter"}.RegisterInNameScope(scope),
new ContentPresenter {Name = "Content_2_Presenter"}.RegisterInNameScope(scope)
}
})
};
var ex = Record.Exception(() => target.ApplyTemplate());
@ -43,12 +44,12 @@ namespace Avalonia.Controls.UnitTests.Mixins
var p2 = new ContentPresenter { Name = "Content_2_Presenter" };
var target = new TestControl
{
Template = new FuncControlTemplate(_ => new Panel
Template = new FuncControlTemplate((_, scope) => new Panel
{
Children =
{
p1,
p2
p1.RegisterInNameScope(scope),
p2.RegisterInNameScope(scope)
}
})
};

132
tests/Avalonia.Controls.UnitTests/NameScopeTests.cs

@ -20,36 +20,146 @@ namespace Avalonia.Controls.UnitTests
}
[Fact]
public void Unregister_Unregisters_Element()
public void Cannot_Register_New_Element_With_Existing_Name()
{
var target = new NameScope();
target.Register("foo", new object());
Assert.Throws<ArgumentException>(() => target.Register("foo", new object()));
}
[Fact]
public void Can_Register_Same_Element_More_Than_Once()
{
var target = new NameScope();
var element = new object();
target.Register("foo", element);
target.Unregister("foo");
target.Register("foo", element);
Assert.Null(target.Find("foo"));
Assert.Same(element, target.Find("foo"));
}
[Fact]
public void Cannot_Register_New_Element_With_Existing_Name()
public void Cannot_Register_New_Element_For_Completed_Scope()
{
var target = new NameScope();
var element = new object();
target.Register("foo", new object());
Assert.Throws<ArgumentException>(() => target.Register("foo", new object()));
target.Register("foo", element);
target.Complete();
Assert.Throws<InvalidOperationException>(() => target.Register("bar", element));
}
/*
`async void` here is intentional since we expect the continuation to be
executed *synchronously* and behave more like an event handler to make sure that
that the object graph is completely ready to use after it's built
rather than have pending continuations queued by SynchronizationContext.
*/
object _found = null;
async void FindAsync(INameScope scope, string name)
{
_found = await scope.FindAsync(name);
}
[Fact]
public void Can_Register_Same_Element_More_Than_Once()
public void FindAsync_Should_Find_Controls_Added_Earlier()
{
var target = new NameScope();
var scope = new NameScope();
var element = new object();
scope.Register("foo", element);
FindAsync(scope, "foo");
Assert.Same(_found, element);
}
[Fact]
public void FindAsync_Should_Find_Controls_Added_Later()
{
var scope = new NameScope();
var element = new object();
FindAsync(scope, "foo");
Assert.Null(_found);
scope.Register("foo", element);
Assert.Same(_found, element);
}
[Fact]
public void FindAsync_Should_Return_Null_After_Scope_Completion()
{
var scope = new NameScope();
var element = new object();
bool finished = false;
async void Find(string name)
{
Assert.Null(await scope.FindAsync(name));
finished = true;
}
Find("foo");
Assert.False(finished);
scope.Register("bar", element);
Assert.False(finished);
scope.Complete();
Assert.True(finished);
}
target.Register("foo", element);
target.Register("foo", element);
[Fact]
public void Child_Scope_Should_Not_Find_Control_In_Parent_Scope_Unless_Completed()
{
var scope = new NameScope();
var childScope = new ChildNameScope(scope);
var element = new object();
scope.Register("foo", element);
Assert.Null(childScope.Find("foo"));
childScope.Complete();
Assert.Same(element, childScope.Find("foo"));
}
[Fact]
public void Child_Scope_Should_Prefer_Own_Elements()
{
var scope = new NameScope();
var childScope = new ChildNameScope(scope);
var element = new object();
var childElement = new object();
scope.Register("foo", element);
childScope.Register("foo", childElement);
childScope.Complete();
Assert.Same(childElement, childScope.Find("foo"));
}
Assert.Same(element, target.Find("foo"));
[Fact]
public void Child_Scope_FindAsync_Should_Find_Elements_In_Parent_Scope_When_Child_Is_Completed()
{
var scope = new NameScope();
var childScope = new ChildNameScope(scope);
var element = new object();
scope.Register("foo", element);
FindAsync(childScope, "foo");
Assert.Null(_found);
childScope.Complete();
Assert.Same(element, _found);
}
[Fact]
public void Child_Scope_FindAsync_Should_Prefer_Own_Elements()
{
var scope = new NameScope();
var childScope = new ChildNameScope(scope);
var element = new object();
var childElement = new object();
FindAsync(childScope, "foo");
scope.Register("foo", element);
Assert.Null(_found);
childScope.Register("foo", childElement);
Assert.Same(childElement, childScope.Find("foo"));
childScope.Complete();
FindAsync(childScope, "foo");
Assert.Same(childElement, childScope.Find("foo"));
}
}
}

29
tests/Avalonia.Controls.UnitTests/Presenters/ContentPresenterTests_InTemplate.cs

@ -136,17 +136,6 @@ namespace Avalonia.Controls.UnitTests.Presenters
Assert.Null(NameScope.GetNameScope((Control)target.Child));
}
[Fact]
public void DataTemplate_Created_Control_Should_Be_NameScope()
{
var (target, _) = CreateTarget();
target.Content = "Foo";
Assert.IsType<TextBlock>(target.Child);
Assert.NotNull(NameScope.GetNameScope((Control)target.Child));
}
[Fact]
public void Assigning_Control_To_Content_Should_Not_Set_DataContext()
{
@ -170,7 +159,7 @@ namespace Avalonia.Controls.UnitTests.Presenters
{
var (target, _) = CreateTarget();
target.ContentTemplate = new FuncDataTemplate<string>(_ => new Canvas());
target.ContentTemplate = new FuncDataTemplate<string>((_, __) => new Canvas());
target.Content = "Foo";
Assert.IsType<Canvas>(target.Child);
@ -184,7 +173,7 @@ namespace Avalonia.Controls.UnitTests.Presenters
target.Content = "Foo";
Assert.IsType<TextBlock>(target.Child);
target.ContentTemplate = new FuncDataTemplate<string>(_ => new Canvas());
target.ContentTemplate = new FuncDataTemplate<string>((_, __) => new Canvas());
Assert.IsType<Canvas>(target.Child);
target.ContentTemplate = null;
@ -209,7 +198,7 @@ namespace Avalonia.Controls.UnitTests.Presenters
public void Recycles_DataTemplate()
{
var (target, _) = CreateTarget();
target.DataTemplates.Add(new FuncDataTemplate<string>(_ => new Border(), true));
target.DataTemplates.Add(new FuncDataTemplate<string>((_, __) => new Border(), true));
target.Content = "foo";
@ -239,7 +228,7 @@ namespace Avalonia.Controls.UnitTests.Presenters
public void Detects_DataTemplate_Doesnt_Support_Recycling()
{
var (target, _) = CreateTarget();
target.DataTemplates.Add(new FuncDataTemplate<string>(_ => new Border(), false));
target.DataTemplates.Add(new FuncDataTemplate<string>((_, __) => new Border(), false));
target.Content = "foo";
@ -256,7 +245,7 @@ namespace Avalonia.Controls.UnitTests.Presenters
var (target, _) = CreateTarget();
target.DataTemplates.Add(new FuncDataTemplate<string>(x => x == "bar", _ => new Canvas(), true));
target.DataTemplates.Add(new FuncDataTemplate<string>(_ => new Border(), true));
target.DataTemplates.Add(new FuncDataTemplate<string>((_, __) => new Border(), true));
target.Content = "foo";
@ -278,8 +267,8 @@ namespace Avalonia.Controls.UnitTests.Presenters
};
var (target, host) = CreateTarget();
host.DataTemplates.Add(new FuncDataTemplate<string>(x => textBlock));
host.DataTemplates.Add(new FuncDataTemplate<int>(x => new Canvas()));
host.DataTemplates.Add(new FuncDataTemplate<string>((_, __) => textBlock));
host.DataTemplates.Add(new FuncDataTemplate<int>((_, __) => new Canvas()));
target.Content = "foo";
Assert.Same(textBlock, target.Child);
@ -296,11 +285,11 @@ namespace Avalonia.Controls.UnitTests.Presenters
{
var templatedParent = new ContentControl
{
Template = new FuncControlTemplate<ContentControl>(x =>
Template = new FuncControlTemplate<ContentControl>((_, s) =>
new ContentPresenter
{
Name = "PART_ContentPresenter",
}),
}.RegisterInNameScope(s)),
};
var root = new TestRoot { Child = templatedParent };

16
tests/Avalonia.Controls.UnitTests/Presenters/ContentPresenterTests_Standalone.cs

@ -54,7 +54,7 @@ namespace Avalonia.Controls.UnitTests.Presenters
var target = new ContentPresenter
{
ContentTemplate =
new FuncDataTemplate<string>(t => new ContentControl() { Content = t }, false)
new FuncDataTemplate<string>((t, _) => new ContentControl() { Content = t }, false)
};
var parentMock = new Mock<Control>();
@ -93,14 +93,14 @@ namespace Avalonia.Controls.UnitTests.Presenters
{
var contentControl = new ContentControl
{
Template = new FuncControlTemplate<ContentControl>(c => new ContentPresenter()
Template = new FuncControlTemplate<ContentControl>((c, scope) => new ContentPresenter()
{
Name = "PART_ContentPresenter",
[~ContentPresenter.ContentProperty] = c[~ContentControl.ContentProperty],
[~ContentPresenter.ContentTemplateProperty] = c[~ContentControl.ContentTemplateProperty]
}),
}.RegisterInNameScope(scope)),
ContentTemplate =
new FuncDataTemplate<string>(t => new ContentControl() { Content = t }, false)
new FuncDataTemplate<string>((t, _) => new ContentControl() { Content = t }, false)
};
var parentMock = new Mock<Control>();
@ -144,7 +144,7 @@ namespace Avalonia.Controls.UnitTests.Presenters
var target = new ContentPresenter
{
ContentTemplate =
new FuncDataTemplate<string>(t => new ContentControl() { Content = t }, false)
new FuncDataTemplate<string>((t, _) => new ContentControl() { Content = t }, false)
};
var parentMock = new Mock<Control>();
@ -179,7 +179,7 @@ namespace Avalonia.Controls.UnitTests.Presenters
var target = new ContentPresenter
{
ContentTemplate =
new FuncDataTemplate<string>(t => new ContentControl() { Content = t }, false)
new FuncDataTemplate<string>((t, _) => new ContentControl() { Content = t }, false)
};
target.Content = "foo";
@ -235,8 +235,8 @@ namespace Avalonia.Controls.UnitTests.Presenters
{
DataTemplates =
{
new FuncDataTemplate<string>(x => textBlock),
new FuncDataTemplate<int>(x => new Canvas()),
new FuncDataTemplate<string>((x, _) => textBlock),
new FuncDataTemplate<int>((x, _) => new Canvas()),
},
};

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

Loading…
Cancel
Save