Browse Source

Merge branch 'master' into fixes/1701-textbox-null-binding

pull/2685/head
Steven Kirk 7 years ago
committed by GitHub
parent
commit
d9a74daa99
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. 46
      src/Avalonia.Base/Data/Converters/StringFormatMultiValueConverter.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. 173
      src/Avalonia.Controls/DockPanel.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. 14
      src/Avalonia.Controls/ItemsControl.cs
  25. 13
      src/Avalonia.Controls/ListBox.cs
  26. 6
      src/Avalonia.Controls/Presenters/ContentPresenter.cs
  27. 644
      src/Avalonia.Controls/Primitives/SelectingItemsControl.cs
  28. 30
      src/Avalonia.Controls/Primitives/TemplatedControl.cs
  29. 4
      src/Avalonia.Controls/ShutdownMode.cs
  30. 10
      src/Avalonia.Controls/Templates/FuncControlTemplate.cs
  31. 6
      src/Avalonia.Controls/Templates/FuncControlTemplate`2.cs
  32. 8
      src/Avalonia.Controls/Templates/FuncDataTemplate.cs
  33. 26
      src/Avalonia.Controls/Templates/FuncDataTemplate`1.cs
  34. 14
      src/Avalonia.Controls/Templates/FuncTemplateNameScopeExtensions.cs
  35. 15
      src/Avalonia.Controls/Templates/FuncTemplate`2.cs
  36. 4
      src/Avalonia.Controls/Templates/FuncTreeDataTemplate.cs
  37. 15
      src/Avalonia.Controls/Templates/FuncTreeDataTemplate`1.cs
  38. 22
      src/Avalonia.Controls/Templates/IControlTemplate.cs
  39. 4
      src/Avalonia.Controls/Templates/ITemplate`2.cs
  40. 26
      src/Avalonia.Controls/TreeView.cs
  41. 34
      src/Avalonia.Controls/UserControl.cs
  42. 34
      src/Avalonia.Controls/Window.cs
  43. 10
      src/Avalonia.Controls/WrapPanel.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. 31
      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. 8
      tests/Avalonia.Controls.UnitTests/AutoCompleteBoxTests.cs
  86. 4
      tests/Avalonia.Controls.UnitTests/CarouselTests.cs
  87. 8
      tests/Avalonia.Controls.UnitTests/ComboBoxTests.cs
  88. 10
      tests/Avalonia.Controls.UnitTests/ContentControlTests.cs
  89. 10
      tests/Avalonia.Controls.UnitTests/DatePickerTests.cs
  90. 21
      tests/Avalonia.Controls.UnitTests/GridSplitterTests.cs
  91. 4
      tests/Avalonia.Controls.UnitTests/HeaderedItemsControlTests .cs
  92. 31
      tests/Avalonia.Controls.UnitTests/ItemsControlTests.cs
  93. 30
      tests/Avalonia.Controls.UnitTests/ListBoxTests.cs
  94. 11
      tests/Avalonia.Controls.UnitTests/ListBoxTests_Single.cs
  95. 13
      tests/Avalonia.Controls.UnitTests/Mixins/ContentControlMixinTests.cs
  96. 132
      tests/Avalonia.Controls.UnitTests/NameScopeTests.cs
  97. 29
      tests/Avalonia.Controls.UnitTests/Presenters/ContentPresenterTests_InTemplate.cs
  98. 16
      tests/Avalonia.Controls.UnitTests/Presenters/ContentPresenterTests_Standalone.cs
  99. 4
      tests/Avalonia.Controls.UnitTests/Presenters/ContentPresenterTests_Unrooted.cs
  100. 2
      tests/Avalonia.Controls.UnitTests/Presenters/ItemsPresenterTests.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>

46
src/Avalonia.Base/Data/Converters/StringFormatMultiValueConverter.cs

@ -0,0 +1,46 @@
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
namespace Avalonia.Data.Converters
{
/// <summary>
/// A multi-value converter which calls <see cref="string.Format(string, object)"/>
/// </summary>
public class StringFormatMultiValueConverter : IMultiValueConverter
{
/// <summary>
/// Initializes a new instance of the <see cref="StringFormatMultiValueConverter"/> class.
/// </summary>
/// <param name="format">The format string.</param>
/// <param name="inner">
/// An optional inner converter to be called before the format takes place.
/// </param>
public StringFormatMultiValueConverter(string format, IMultiValueConverter inner)
{
Contract.Requires<ArgumentNullException>(format != null);
Format = format;
Inner = inner;
}
/// <summary>
/// Gets an inner value converter which will be called before the string format takes place.
/// </summary>
public IMultiValueConverter Inner { get; }
/// <summary>
/// Gets the format string.
/// </summary>
public string Format { get; }
/// <inheritdoc/>
public object Convert(IList<object> values, Type targetType, object parameter, CultureInfo culture)
{
return Inner == null
? string.Format(culture, Format, values.ToArray())
: string.Format(culture, Format, Inner.Convert(values, targetType, parameter, culture));
}
}
}

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

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

173
src/Avalonia.Controls/DockPanel.cs

@ -1,7 +1,12 @@
// This source file is adapted from the Windows Presentation Foundation project.
// (https://github.com/dotnet/wpf/)
//
// Licensed to The Avalonia Project under MIT License, courtesy of The .NET Foundation.
using System;
namespace Avalonia.Controls
{
using System;
/// <summary>
/// Defines the available docking modes for a control in a <see cref="DockPanel"/>.
/// </summary>
@ -70,107 +75,137 @@ namespace Avalonia.Controls
set { SetValue(LastChildFillProperty, value); }
}
/// <inheritdoc/>
/// <summary>
/// Updates DesiredSize of the DockPanel. Called by parent Control. This is the first pass of layout.
/// </summary>
/// <remarks>
/// Children are measured based on their sizing properties and <see cref="Dock" />.
/// Each child is allowed to consume all of the space on the side on which it is docked; Left/Right docked
/// children are granted all vertical space for their entire width, and Top/Bottom docked children are
/// granted all horizontal space for their entire height.
/// </remarks>
/// <param name="constraint">Constraint size is an "upper limit" that the return value should not exceed.</param>
/// <returns>The Panel's desired size.</returns>
protected override Size MeasureOverride(Size constraint)
{
double usedWidth = 0.0;
double usedHeight = 0.0;
double maximumWidth = 0.0;
double maximumHeight = 0.0;
var children = Children;
double parentWidth = 0; // Our current required width due to children thus far.
double parentHeight = 0; // Our current required height due to children thus far.
double accumulatedWidth = 0; // Total width consumed by children.
double accumulatedHeight = 0; // Total height consumed by children.
// Measure each of the Children
foreach (Control element in Children)
for (int i = 0, count = children.Count; i < count; ++i)
{
// Get the child's desired size
Size remainingSize = new Size(
Math.Max(0.0, constraint.Width - usedWidth),
Math.Max(0.0, constraint.Height - usedHeight));
element.Measure(remainingSize);
Size desiredSize = element.DesiredSize;
// Decrease the remaining space for the rest of the children
switch (GetDock(element))
var child = children[i];
Size childConstraint; // Contains the suggested input constraint for this child.
Size childDesiredSize; // Contains the return size from child measure.
if (child == null)
{ continue; }
// Child constraint is the remaining size; this is total size minus size consumed by previous children.
childConstraint = new Size(Math.Max(0.0, constraint.Width - accumulatedWidth),
Math.Max(0.0, constraint.Height - accumulatedHeight));
// Measure child.
child.Measure(childConstraint);
childDesiredSize = child.DesiredSize;
// Now, we adjust:
// 1. Size consumed by children (accumulatedSize). This will be used when computing subsequent
// children to determine how much space is remaining for them.
// 2. Parent size implied by this child (parentSize) when added to the current children (accumulatedSize).
// This is different from the size above in one respect: A Dock.Left child implies a height, but does
// not actually consume any height for subsequent children.
// If we accumulate size in a given dimension, the next child (or the end conditions after the child loop)
// will deal with computing our minimum size (parentSize) due to that accumulation.
// Therefore, we only need to compute our minimum size (parentSize) in dimensions that this child does
// not accumulate: Width for Top/Bottom, Height for Left/Right.
switch (DockPanel.GetDock((Control)child))
{
case Dock.Left:
case Dock.Right:
maximumHeight = Math.Max(maximumHeight, usedHeight + desiredSize.Height);
usedWidth += desiredSize.Width;
parentHeight = Math.Max(parentHeight, accumulatedHeight + childDesiredSize.Height);
accumulatedWidth += childDesiredSize.Width;
break;
case Dock.Top:
case Dock.Bottom:
maximumWidth = Math.Max(maximumWidth, usedWidth + desiredSize.Width);
usedHeight += desiredSize.Height;
parentWidth = Math.Max(parentWidth, accumulatedWidth + childDesiredSize.Width);
accumulatedHeight += childDesiredSize.Height;
break;
}
}
maximumWidth = Math.Max(maximumWidth, usedWidth);
maximumHeight = Math.Max(maximumHeight, usedHeight);
return new Size(maximumWidth, maximumHeight);
// Make sure the final accumulated size is reflected in parentSize.
parentWidth = Math.Max(parentWidth, accumulatedWidth);
parentHeight = Math.Max(parentHeight, accumulatedHeight);
return (new Size(parentWidth, parentHeight));
}
/// <inheritdoc/>
/// <summary>
/// DockPanel computes a position and final size for each of its children based upon their
/// <see cref="Dock" /> enum and sizing properties.
/// </summary>
/// <param name="arrangeSize">Size that DockPanel will assume to position children.</param>
protected override Size ArrangeOverride(Size arrangeSize)
{
double left = 0.0;
double top = 0.0;
double right = 0.0;
double bottom = 0.0;
// Arrange each of the Children
var children = Children;
int dockedCount = children.Count - (LastChildFill ? 1 : 0);
int index = 0;
int totalChildrenCount = children.Count;
int nonFillChildrenCount = totalChildrenCount - (LastChildFill ? 1 : 0);
double accumulatedLeft = 0;
double accumulatedTop = 0;
double accumulatedRight = 0;
double accumulatedBottom = 0;
foreach (Control element in children)
for (int i = 0; i < totalChildrenCount; ++i)
{
// Determine the remaining space left to arrange the element
Rect remainingRect = new Rect(
left,
top,
Math.Max(0.0, arrangeSize.Width - left - right),
Math.Max(0.0, arrangeSize.Height - top - bottom));
// Trim the remaining Rect to the docked size of the element
// (unless the element should fill the remaining space because
// of LastChildFill)
if (index < dockedCount)
var child = children[i];
if (child == null)
{ continue; }
Size childDesiredSize = child.DesiredSize;
Rect rcChild = new Rect(
accumulatedLeft,
accumulatedTop,
Math.Max(0.0, arrangeSize.Width - (accumulatedLeft + accumulatedRight)),
Math.Max(0.0, arrangeSize.Height - (accumulatedTop + accumulatedBottom)));
if (i < nonFillChildrenCount)
{
Size desiredSize = element.DesiredSize;
switch (GetDock(element))
switch (DockPanel.GetDock((Control)child))
{
case Dock.Left:
left += desiredSize.Width;
remainingRect = remainingRect.WithWidth(desiredSize.Width);
break;
case Dock.Top:
top += desiredSize.Height;
remainingRect = remainingRect.WithHeight(desiredSize.Height);
accumulatedLeft += childDesiredSize.Width;
rcChild = rcChild.WithWidth(childDesiredSize.Width);
break;
case Dock.Right:
right += desiredSize.Width;
remainingRect = new Rect(
Math.Max(0.0, arrangeSize.Width - right),
remainingRect.Y,
desiredSize.Width,
remainingRect.Height);
accumulatedRight += childDesiredSize.Width;
rcChild = rcChild.WithX(Math.Max(0.0, arrangeSize.Width - accumulatedRight));
rcChild = rcChild.WithWidth(childDesiredSize.Width);
break;
case Dock.Top:
accumulatedTop += childDesiredSize.Height;
rcChild = rcChild.WithHeight(childDesiredSize.Height);
break;
case Dock.Bottom:
bottom += desiredSize.Height;
remainingRect = new Rect(
remainingRect.X,
Math.Max(0.0, arrangeSize.Height - bottom),
remainingRect.Width,
desiredSize.Height);
accumulatedBottom += childDesiredSize.Height;
rcChild = rcChild.WithY(Math.Max(0.0, arrangeSize.Height - accumulatedBottom));
rcChild = rcChild.WithHeight(childDesiredSize.Height);
break;
}
}
element.Arrange(remainingRect);
index++;
child.Arrange(rcChild);
}
return arrangeSize;
return (arrangeSize);
}
}
}

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;
@ -123,11 +122,20 @@ namespace Avalonia.Controls.Generators
return 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;
}
}

14
src/Avalonia.Controls/ItemsControl.cs

@ -36,6 +36,12 @@ namespace Avalonia.Controls
public static readonly DirectProperty<ItemsControl, IEnumerable> ItemsProperty =
AvaloniaProperty.RegisterDirect<ItemsControl, IEnumerable>(nameof(Items), o => o.Items, (o, v) => o.Items = v);
/// <summary>
/// Defines the <see cref="ItemCount"/> property.
/// </summary>
public static readonly DirectProperty<ItemsControl, int> ItemCountProperty =
AvaloniaProperty.RegisterDirect<ItemsControl, int>(nameof(ItemCount), o => o.ItemCount);
/// <summary>
/// Defines the <see cref="ItemsPanel"/> property.
/// </summary>
@ -55,6 +61,7 @@ namespace Avalonia.Controls
AvaloniaProperty.Register<ItemsControl, IMemberSelector>(nameof(MemberSelector));
private IEnumerable _items = new AvaloniaList<object>();
private int _itemCount;
private IItemContainerGenerator _itemContainerGenerator;
private IDisposable _itemsCollectionChangedSubscription;
@ -110,10 +117,13 @@ namespace Avalonia.Controls
set { SetAndRaise(ItemsProperty, ref _items, value); }
}
/// <summary>
/// Gets the number of items in <see cref="Items"/>.
/// </summary>
public int ItemCount
{
get;
private set;
get => _itemCount;
private set => SetAndRaise(ItemCountProperty, ref _itemCount, value);
}
/// <summary>

13
src/Avalonia.Controls/ListBox.cs

@ -84,6 +84,16 @@ namespace Avalonia.Controls
set { SetValue(VirtualizationModeProperty, value); }
}
/// <summary>
/// Selects all items in the <see cref="ListBox"/>.
/// </summary>
public new void SelectAll() => base.SelectAll();
/// <summary>
/// Deselects all items in the <see cref="ListBox"/>.
/// </summary>
public new void UnselectAll() => base.UnselectAll();
/// <inheritdoc/>
protected override IItemContainerGenerator CreateItemContainerGenerator()
{
@ -118,7 +128,8 @@ namespace Avalonia.Controls
e.Source,
true,
(e.InputModifiers & InputModifiers.Shift) != 0,
(e.InputModifiers & InputModifiers.Control) != 0);
(e.InputModifiers & InputModifiers.Control) != 0,
e.MouseButton == MouseButton.Right);
}
}

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

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

@ -12,6 +12,7 @@ using Avalonia.Data;
using Avalonia.Input;
using Avalonia.Input.Platform;
using Avalonia.Interactivity;
using Avalonia.Logging;
using Avalonia.Styling;
using Avalonia.VisualTree;
@ -103,6 +104,7 @@ namespace Avalonia.Controls.Primitives
private static readonly IList Empty = Array.Empty<object>();
private readonly Selection _selection = new Selection();
private int _selectedIndex = -1;
private object _selectedItem;
private IList _selectedItems;
@ -152,23 +154,8 @@ namespace Avalonia.Controls.Primitives
{
if (_updateCount == 0)
{
SetAndRaise(SelectedIndexProperty, ref _selectedIndex, (int val, ref int backing, Action<Action> notifyWrapper) =>
{
var old = backing;
var effective = (val >= 0 && val < Items?.Cast<object>().Count()) ? val : -1;
if (old != effective)
{
backing = effective;
notifyWrapper(() =>
RaisePropertyChanged(
SelectedIndexProperty,
old,
effective,
BindingPriority.LocalValue));
SelectedItem = ElementAt(Items, effective);
}
}, value);
var effective = (value >= 0 && value < ItemCount) ? value : -1;
UpdateSelectedItem(effective);
}
else
{
@ -192,41 +179,7 @@ namespace Avalonia.Controls.Primitives
{
if (_updateCount == 0)
{
SetAndRaise(SelectedItemProperty, ref _selectedItem, (object val, ref object backing, Action<Action> notifyWrapper) =>
{
var old = backing;
var index = IndexOf(Items, val);
var effective = index != -1 ? val : null;
if (!object.Equals(effective, old))
{
backing = effective;
notifyWrapper(() =>
RaisePropertyChanged(
SelectedItemProperty,
old,
effective,
BindingPriority.LocalValue));
SelectedIndex = index;
if (effective != null)
{
if (SelectedItems.Count != 1 || SelectedItems[0] != effective)
{
_syncingSelectedItems = true;
SelectedItems.Clear();
SelectedItems.Add(effective);
_syncingSelectedItems = false;
}
}
else if (SelectedItems.Count > 0)
{
SelectedItems.Clear();
}
}
}, value);
UpdateSelectedItem(IndexOf(Items, value));
}
else
{
@ -354,31 +307,23 @@ namespace Avalonia.Controls.Primitives
{
SelectedIndex = 0;
}
else
{
_selection.ItemsInserted(e.NewStartingIndex, e.NewItems.Count);
UpdateSelectedItem(_selection.First(), false);
}
break;
case NotifyCollectionChangedAction.Remove:
case NotifyCollectionChangedAction.Replace:
var selectedIndex = SelectedIndex;
if (selectedIndex >= e.OldStartingIndex &&
selectedIndex < e.OldStartingIndex + e.OldItems.Count)
{
if (!AlwaysSelected)
{
selectedIndex = SelectedIndex = -1;
}
else
{
LostSelection();
}
}
_selection.ItemsRemoved(e.OldStartingIndex, e.OldItems.Count);
UpdateSelectedItem(_selection.First(), false);
ResetSelectedItems();
break;
var items = Items?.Cast<object>();
if (selectedIndex >= items.Count())
{
selectedIndex = SelectedIndex = items.Count() - 1;
}
case NotifyCollectionChangedAction.Replace:
UpdateSelectedItem(SelectedIndex, false);
ResetSelectedItems();
break;
case NotifyCollectionChangedAction.Move:
@ -439,11 +384,7 @@ namespace Avalonia.Controls.Primitives
{
if (i.ContainerControl != null && i.Item != null)
{
var ms = MemberSelector;
bool selected = ms == null ?
SelectedItems.Contains(i.Item) :
SelectedItems.OfType<object>().Any(v => Equals(ms.Select(v), i.Item));
bool selected = _selection.Contains(i.Index);
MarkContainerSelected(i.ContainerControl, selected);
}
}
@ -476,9 +417,12 @@ namespace Avalonia.Controls.Primitives
var keymap = AvaloniaLocator.Current.GetService<PlatformHotkeyConfiguration>();
bool Match(List<KeyGesture> gestures) => gestures.Any(g => g.Matches(e));
if (this.SelectionMode == SelectionMode.Multiple && Match(keymap.SelectAll))
if (ItemCount > 0 &&
Match(keymap.SelectAll) &&
(((SelectionMode & SelectionMode.Multiple) != 0) ||
(SelectionMode & SelectionMode.Toggle) != 0))
{
SynchronizeItems(SelectedItems, Items?.Cast<object>());
SelectAll();
e.Handled = true;
}
}
@ -520,6 +464,41 @@ namespace Avalonia.Controls.Primitives
return false;
}
/// <summary>
/// Selects all items in the control.
/// </summary>
protected void SelectAll()
{
if ((SelectionMode & (SelectionMode.Multiple | SelectionMode.Toggle)) == 0)
{
throw new NotSupportedException("Multiple selection is not enabled on this control.");
}
UpdateSelectedItems(() =>
{
_selection.Clear();
for (var i = 0; i < ItemCount; ++i)
{
_selection.Add(i);
}
UpdateSelectedItem(0, false);
foreach (var container in ItemContainerGenerator.Containers)
{
MarkItemSelected(container.Index, true);
}
ResetSelectedItems();
});
}
/// <summary>
/// Deselects all items in the control.
/// </summary>
protected void UnselectAll() => UpdateSelectedItem(-1);
/// <summary>
/// Updates the selection for an item based on user interaction.
/// </summary>
@ -527,51 +506,83 @@ namespace Avalonia.Controls.Primitives
/// <param name="select">Whether the item should be selected or unselected.</param>
/// <param name="rangeModifier">Whether the range modifier is enabled (i.e. shift key).</param>
/// <param name="toggleModifier">Whether the toggle modifier is enabled (i.e. ctrl key).</param>
/// <param name="rightButton">Whether the event is a right-click.</param>
protected void UpdateSelection(
int index,
bool select = true,
bool rangeModifier = false,
bool toggleModifier = false)
bool toggleModifier = false,
bool rightButton = false)
{
if (index != -1)
{
if (select)
{
var mode = SelectionMode;
var toggle = toggleModifier || (mode & SelectionMode.Toggle) != 0;
var multi = (mode & SelectionMode.Multiple) != 0;
var range = multi && SelectedIndex != -1 && rangeModifier;
var toggle = (toggleModifier || (mode & SelectionMode.Toggle) != 0);
var range = multi && rangeModifier;
if (!toggle && !range)
if (range)
{
SelectedIndex = index;
}
else if (multi && range)
{
SynchronizeItems(
SelectedItems,
GetRange(Items, SelectedIndex, index));
UpdateSelectedItems(() =>
{
var start = SelectedIndex != -1 ? SelectedIndex : 0;
var step = start < index ? 1 : -1;
_selection.Clear();
for (var i = start; i != index; i += step)
{
_selection.Add(i);
}
_selection.Add(index);
var first = Math.Min(start, index);
var last = Math.Max(start, index);
foreach (var container in ItemContainerGenerator.Containers)
{
MarkItemSelected(
container.Index,
container.Index >= first && container.Index <= last);
}
ResetSelectedItems();
});
}
else
else if (multi && toggle)
{
var item = ElementAt(Items, index);
var i = SelectedItems.IndexOf(item);
if (i != -1 && (!AlwaysSelected || SelectedItems.Count > 1))
{
SelectedItems.Remove(item);
}
else
UpdateSelectedItems(() =>
{
if (multi)
if (!_selection.Contains(index))
{
SelectedItems.Add(item);
_selection.Add(index);
MarkItemSelected(index, true);
SelectedItems.Add(ElementAt(Items, index));
}
else
{
SelectedIndex = index;
_selection.Remove(index);
MarkItemSelected(index, false);
if (index == _selectedIndex)
{
UpdateSelectedItem(_selection.First(), false);
}
SelectedItems.Remove(ElementAt(Items, index));
}
}
});
}
else if (toggle)
{
SelectedIndex = (SelectedIndex == index) ? -1 : index;
}
else
{
UpdateSelectedItem(index, !(rightButton && _selection.Contains(index)));
}
if (Presenter?.Panel != null)
@ -596,17 +607,19 @@ namespace Avalonia.Controls.Primitives
/// <param name="select">Whether the container should be selected or unselected.</param>
/// <param name="rangeModifier">Whether the range modifier is enabled (i.e. shift key).</param>
/// <param name="toggleModifier">Whether the toggle modifier is enabled (i.e. ctrl key).</param>
/// <param name="rightButton">Whether the event is a right-click.</param>
protected void UpdateSelection(
IControl container,
bool select = true,
bool rangeModifier = false,
bool toggleModifier = false)
bool toggleModifier = false,
bool rightButton = false)
{
var index = ItemContainerGenerator?.IndexFromContainer(container) ?? -1;
if (index != -1)
{
UpdateSelection(index, select, rangeModifier, toggleModifier);
UpdateSelection(index, select, rangeModifier, toggleModifier, rightButton);
}
}
@ -618,6 +631,7 @@ namespace Avalonia.Controls.Primitives
/// <param name="select">Whether the container should be selected or unselected.</param>
/// <param name="rangeModifier">Whether the range modifier is enabled (i.e. shift key).</param>
/// <param name="toggleModifier">Whether the toggle modifier is enabled (i.e. ctrl key).</param>
/// <param name="rightButton">Whether the event is a right-click.</param>
/// <returns>
/// True if the event originated from a container that belongs to the control; otherwise
/// false.
@ -626,51 +640,20 @@ namespace Avalonia.Controls.Primitives
IInteractive eventSource,
bool select = true,
bool rangeModifier = false,
bool toggleModifier = false)
bool toggleModifier = false,
bool rightButton = false)
{
var container = GetContainerFromEventSource(eventSource);
if (container != null)
{
UpdateSelection(container, select, rangeModifier, toggleModifier);
UpdateSelection(container, select, rangeModifier, toggleModifier, rightButton);
return true;
}
return false;
}
/// <summary>
/// Makes a list of objects equal another.
/// </summary>
/// <param name="items">The items collection.</param>
/// <param name="desired">The desired items.</param>
internal static void SynchronizeItems(IList items, IEnumerable<object> desired)
{
var index = 0;
foreach (object item in desired)
{
int itemIndex = items.IndexOf(item);
if (itemIndex == -1)
{
items.Insert(index, item);
}
else if(itemIndex != index)
{
items.RemoveAt(itemIndex);
items.Insert(index, item);
}
++index;
}
while (index < items.Count)
{
items.RemoveAt(items.Count - 1);
}
}
/// <summary>
/// Gets a range of items from an IEnumerable.
/// </summary>
@ -678,17 +661,19 @@ namespace Avalonia.Controls.Primitives
/// <param name="first">The index of the first item.</param>
/// <param name="last">The index of the last item.</param>
/// <returns>The items.</returns>
private static IEnumerable<object> GetRange(IEnumerable items, int first, int last)
private static List<object> GetRange(IEnumerable items, int first, int last)
{
var list = (items as IList) ?? items.Cast<object>().ToList();
int step = first > last ? -1 : 1;
var step = first > last ? -1 : 1;
var result = new List<object>();
for (int i = first; i != last; i += step)
{
yield return list[i];
result.Add(list[i]);
}
yield return list[last];
result.Add(list[last]);
return result;
}
/// <summary>
@ -724,19 +709,14 @@ namespace Avalonia.Controls.Primitives
private void LostSelection()
{
var items = Items?.Cast<object>();
var index = -1;
if (items != null && AlwaysSelected)
{
var index = Math.Min(SelectedIndex, items.Count() - 1);
if (index > -1)
{
SelectedItem = items.ElementAt(index);
return;
}
index = Math.Min(SelectedIndex, items.Count() - 1);
}
SelectedIndex = -1;
SelectedIndex = index;
}
/// <summary>
@ -793,7 +773,7 @@ namespace Avalonia.Controls.Primitives
/// </summary>
/// <param name="item">The item.</param>
/// <param name="selected">Whether the item should be selected or deselected.</param>
private void MarkItemSelected(object item, bool selected)
private int MarkItemSelected(object item, bool selected)
{
var index = IndexOf(Items, item);
@ -801,6 +781,21 @@ namespace Avalonia.Controls.Primitives
{
MarkItemSelected(index, selected);
}
return index;
}
private void ResetSelectedItems()
{
UpdateSelectedItems(() =>
{
SelectedItems.Clear();
foreach (var i in _selection)
{
SelectedItems.Add(ElementAt(Items, i));
}
});
}
/// <summary>
@ -810,95 +805,97 @@ namespace Avalonia.Controls.Primitives
/// <param name="e">The event args.</param>
private void SelectedItemsCollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
{
var generator = ItemContainerGenerator;
if (_syncingSelectedItems)
{
return;
}
void Add(IList newItems, IList addedItems = null)
{
foreach (var item in newItems)
{
var index = MarkItemSelected(item, true);
if (index != -1 && _selection.Add(index) && addedItems != null)
{
addedItems.Add(item);
}
}
}
void UpdateSelection()
{
if ((SelectedIndex != -1 && !_selection.Contains(SelectedIndex)) ||
(SelectedIndex == -1 && _selection.HasItems))
{
_selectedIndex = _selection.First();
_selectedItem = ElementAt(Items, _selectedIndex);
RaisePropertyChanged(SelectedIndexProperty, -1, _selectedIndex, BindingPriority.LocalValue);
RaisePropertyChanged(SelectedItemProperty, null, _selectedItem, BindingPriority.LocalValue);
if (AutoScrollToSelectedItem)
{
ScrollIntoView(_selectedIndex);
}
}
}
IList added = null;
IList removed = null;
switch (e.Action)
{
case NotifyCollectionChangedAction.Add:
SelectedItemsAdded(e.NewItems.Cast<object>().ToList());
if (AutoScrollToSelectedItem)
{
ScrollIntoView(e.NewItems[0]);
Add(e.NewItems);
UpdateSelection();
added = e.NewItems;
}
added = e.NewItems;
break;
case NotifyCollectionChangedAction.Remove:
if (SelectedItems.Count == 0)
{
if (!_syncingSelectedItems)
{
SelectedIndex = -1;
}
SelectedIndex = -1;
}
foreach (var item in e.OldItems)
{
MarkItemSelected(item, false);
var index = MarkItemSelected(item, false);
_selection.Remove(index);
}
removed = e.OldItems;
break;
case NotifyCollectionChangedAction.Replace:
throw new NotSupportedException("Replacing items in a SelectedItems collection is not supported.");
case NotifyCollectionChangedAction.Move:
throw new NotSupportedException("Moving items in a SelectedItems collection is not supported.");
case NotifyCollectionChangedAction.Reset:
if (generator != null)
{
removed = new List<object>();
added = new List<object>();
foreach (var item in generator.Containers)
foreach (var index in _selection.ToList())
{
if (item?.ContainerControl != null)
var item = ElementAt(Items, index);
if (!SelectedItems.Contains(item))
{
if (MarkContainerSelected(item.ContainerControl, false))
{
removed.Add(item.Item);
}
MarkItemSelected(index, false);
removed.Add(item);
_selection.Remove(index);
}
}
}
if (SelectedItems.Count > 0)
{
_selectedItem = null;
SelectedItemsAdded(SelectedItems);
added = SelectedItems;
}
else if (!_syncingSelectedItems)
{
SelectedIndex = -1;
}
break;
case NotifyCollectionChangedAction.Replace:
foreach (var item in e.OldItems)
{
MarkItemSelected(item, false);
}
foreach (var item in e.NewItems)
{
MarkItemSelected(item, true);
Add(SelectedItems, added);
UpdateSelection();
}
if (SelectedItem != SelectedItems[0] && !_syncingSelectedItems)
{
var oldItem = SelectedItem;
var oldIndex = SelectedIndex;
var item = SelectedItems[0];
var index = IndexOf(Items, item);
_selectedIndex = index;
_selectedItem = item;
RaisePropertyChanged(SelectedIndexProperty, oldIndex, index, BindingPriority.LocalValue);
RaisePropertyChanged(SelectedItemProperty, oldItem, item, BindingPriority.LocalValue);
}
added = e.NewItems;
removed = e.OldItems;
break;
}
@ -912,34 +909,6 @@ namespace Avalonia.Controls.Primitives
}
}
/// <summary>
/// Called when items are added to the <see cref="SelectedItems"/> collection.
/// </summary>
/// <param name="items">The added items.</param>
private void SelectedItemsAdded(IList items)
{
if (items.Count > 0)
{
foreach (var item in items)
{
MarkItemSelected(item, true);
}
if (SelectedItem == null && !_syncingSelectedItems)
{
var index = IndexOf(Items, items[0]);
if (index != -1)
{
_selectedItem = items[0];
_selectedIndex = index;
RaisePropertyChanged(SelectedIndexProperty, -1, index, BindingPriority.LocalValue);
RaisePropertyChanged(SelectedItemProperty, null, items[0], BindingPriority.LocalValue);
}
}
}
}
/// <summary>
/// Subscribes to the <see cref="SelectedItems"/> CollectionChanged event, if any.
/// </summary>
@ -970,6 +939,112 @@ namespace Avalonia.Controls.Primitives
}
}
/// <summary>
/// Updates the selection due to a change to <see cref="SelectedIndex"/> or
/// <see cref="SelectedItem"/>.
/// </summary>
/// <param name="index">The new selected index.</param>
/// <param name="clear">Whether to clear existing selection.</param>
private void UpdateSelectedItem(int index, bool clear = true)
{
var oldIndex = _selectedIndex;
var oldItem = _selectedItem;
if (index == -1 && AlwaysSelected)
{
index = Math.Min(SelectedIndex, ItemCount - 1);
}
var item = ElementAt(Items, index);
var added = -1;
HashSet<int> removed = null;
_selectedIndex = index;
_selectedItem = item;
if (oldIndex != index || _selection.HasMultiple)
{
if (clear)
{
removed = _selection.Clear();
}
if (index != -1)
{
if (_selection.Add(index))
{
added = index;
}
if (removed?.Contains(index) == true)
{
removed.Remove(index);
added = -1;
}
}
if (removed != null)
{
foreach (var i in removed)
{
MarkItemSelected(i, false);
}
}
MarkItemSelected(index, true);
RaisePropertyChanged(
SelectedIndexProperty,
oldIndex,
index);
}
if (!Equals(item, oldItem))
{
RaisePropertyChanged(
SelectedItemProperty,
oldItem,
item);
}
if (removed != null && index != -1)
{
removed.Remove(index);
}
if (added != -1 || removed?.Count > 0)
{
ResetSelectedItems();
var e = new SelectionChangedEventArgs(
SelectionChangedEvent,
added != -1 ? new[] { ElementAt(Items, added) } : Array.Empty<object>(),
removed?.Select(x => ElementAt(Items, x)).ToArray() ?? Array.Empty<object>());
RaiseEvent(e);
}
}
private void UpdateSelectedItems(Action action)
{
try
{
_syncingSelectedItems = true;
action();
}
catch (Exception ex)
{
Logger.Error(
LogArea.Property,
this,
"Error thrown updating SelectedItems: {Error}",
ex);
}
finally
{
_syncingSelectedItems = false;
}
}
private void UpdateFinished()
{
if (_updateSelectedIndex != int.MinValue)
@ -981,5 +1056,104 @@ namespace Avalonia.Controls.Primitives
SelectedItems = _updateSelectedItems;
}
}
private class Selection : IEnumerable<int>
{
private readonly List<int> _list = new List<int>();
private HashSet<int> _set = new HashSet<int>();
public bool HasItems => _set.Count > 0;
public bool HasMultiple => _set.Count > 1;
public bool Add(int index)
{
if (index == -1)
{
throw new ArgumentException("Invalid index", "index");
}
if (_set.Add(index))
{
_list.Add(index);
return true;
}
return false;
}
public bool Remove(int index)
{
if (_set.Remove(index))
{
_list.RemoveAll(x => x == index);
return true;
}
return false;
}
public HashSet<int> Clear()
{
var result = _set;
_list.Clear();
_set = new HashSet<int>();
return result;
}
public void ItemsInserted(int index, int count)
{
_set = new HashSet<int>();
for (var i = 0; i < _list.Count; ++i)
{
var ix = _list[i];
if (ix >= index)
{
var newIndex = ix + count;
_list[i] = newIndex;
_set.Add(newIndex);
}
else
{
_set.Add(ix);
}
}
}
public void ItemsRemoved(int index, int count)
{
var last = (index + count) - 1;
_set = new HashSet<int>();
for (var i = 0; i < _list.Count; ++i)
{
var ix = _list[i];
if (ix >= index && ix <= last)
{
_list.RemoveAt(i--);
}
else if (ix > last)
{
var newIndex = ix - count;
_list[i] = newIndex;
_set.Add(newIndex);
}
else
{
_set.Add(ix);
}
}
}
public bool Contains(int index) => _set.Contains(index);
public int First() => HasItems ? _list[0] : -1;
public IEnumerator<int> GetEnumerator() => _set.GetEnumerator();
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
}
}
}

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);
}
}
}

26
src/Avalonia.Controls/TreeView.cs

@ -409,7 +409,7 @@ namespace Avalonia.Controls
if (this.SelectionMode == SelectionMode.Multiple && Match(keymap.SelectAll))
{
SelectingItemsControl.SynchronizeItems(SelectedItems, ItemContainerGenerator.Index.Items);
SynchronizeItems(SelectedItems, ItemContainerGenerator.Index.Items);
e.Handled = true;
}
}
@ -521,7 +521,7 @@ namespace Avalonia.Controls
}
else if (multi && range)
{
SelectingItemsControl.SynchronizeItems(
SynchronizeItems(
SelectedItems,
GetItemsInRange(selectedContainer as TreeViewItem, container as TreeViewItem));
}
@ -778,5 +778,27 @@ namespace Avalonia.Controls
container.Classes.Set(":selected", selected);
}
}
/// <summary>
/// Makes a list of objects equal another (though doesn't preserve order).
/// </summary>
/// <param name="items">The items collection.</param>
/// <param name="desired">The desired items.</param>
private static void SynchronizeItems(IList items, IEnumerable<object> desired)
{
var list = items.Cast<object>().ToList();
var toRemove = list.Except(desired).ToList();
var toAdd = desired.Except(list).ToList();
foreach (var i in toRemove)
{
items.Remove(i);
}
foreach (var i in toAdd)
{
items.Add(i);
}
}
}
}

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)
{

10
src/Avalonia.Controls/WrapPanel.cs

@ -1,9 +1,7 @@
// Copyright (c) The Avalonia Project. All rights reserved.
// Licensed under the MIT license. See licence.md file in the project root for full license information.
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
// This source file is adapted from the Windows Presentation Foundation project.
// (https://github.com/dotnet/wpf/)
//
// Licensed to The Avalonia Project under MIT License, courtesy of The .NET Foundation.
using Avalonia.Input;
using Avalonia.Utilities;

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;

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

@ -64,14 +64,19 @@ namespace Avalonia.Data
object anchor = null,
bool enableDataValidation = false)
{
if (Converter == null)
var targetType = targetProperty?.PropertyType ?? typeof(object);
var converter = Converter;
// We only respect `StringFormat` if the type of the property we're assigning to will
// accept a string. Note that this is slightly different to WPF in that WPF only applies
// `StringFormat` for target type `string` (not `object`).
if (!string.IsNullOrWhiteSpace(StringFormat) &&
(targetType == typeof(string) || targetType == typeof(object)))
{
throw new NotSupportedException("MultiBinding without Converter not currently supported.");
converter = new StringFormatMultiValueConverter(StringFormat, converter);
}
var targetType = targetProperty?.PropertyType ?? typeof(object);
var children = Bindings.Select(x => x.Initiate(target, null));
var input = children.Select(x => x.Observable).CombineLatest().Select(x => ConvertValue(x, targetType));
var input = children.Select(x => x.Observable).CombineLatest().Select(x => ConvertValue(x, targetType, converter));
var mode = Mode == BindingMode.Default ?
targetProperty?.GetMetadata(target.GetType()).DefaultBindingMode : Mode;
@ -87,23 +92,19 @@ namespace Avalonia.Data
}
}
private object ConvertValue(IList<object> values, Type targetType)
private object ConvertValue(IList<object> values, Type targetType, IMultiValueConverter converter)
{
var culture = CultureInfo.CurrentCulture;
var converted = Converter.Convert(values, targetType, ConverterParameter, culture);
var converted = converter.Convert(values, targetType, ConverterParameter, culture);
if (converted == AvaloniaProperty.UnsetValue && FallbackValue != null)
if (converted == BindingOperations.DoNothing)
{
converted = FallbackValue;
return converted;
}
// We only respect `StringFormat` if the type of the property we're assigning to will
// accept a string. Note that this is slightly different to WPF in that WPF only applies
// `StringFormat` for target type `string` (not `object`).
if (!string.IsNullOrWhiteSpace(StringFormat) &&
(targetType == typeof(string) || targetType == typeof(object)))
if (converted == AvaloniaProperty.UnsetValue)
{
converted = string.Format(culture, StringFormat, converted);
converted = FallbackValue;
}
return converted;

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(

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)
};
});
}

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,
};
@ -472,29 +472,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()
{
@ -578,7 +555,7 @@ namespace Avalonia.Controls.UnitTests
private FuncControlTemplate GetTemplate()
{
return new FuncControlTemplate<ItemsControl>(parent =>
return new FuncControlTemplate<ItemsControl>((parent, scope) =>
{
return new Border
{
@ -588,7 +565,7 @@ namespace Avalonia.Controls.UnitTests
Name = "PART_ItemsPresenter",
MemberSelector = parent.MemberSelector,
[~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()),
},
};

4
tests/Avalonia.Controls.UnitTests/Presenters/ContentPresenterTests_Unrooted.cs

@ -90,7 +90,7 @@ namespace Avalonia.Controls.UnitTests.Presenters
{
DataTemplates =
{
new FuncDataTemplate<string>(x => new Decorator()),
new FuncDataTemplate<string>((x, _) => new Decorator()),
},
};
@ -99,4 +99,4 @@ namespace Avalonia.Controls.UnitTests.Presenters
Assert.IsType<Decorator>(target.Child);
}
}
}
}

2
tests/Avalonia.Controls.UnitTests/Presenters/ItemsPresenterTests.cs

@ -220,7 +220,7 @@ namespace Avalonia.Controls.UnitTests.Presenters
{
VirtualizationMode = ItemVirtualizationMode.None,
Items = items,
ItemTemplate = new FuncDataTemplate<string>(x => new TextBlock { Height = 10 }),
ItemTemplate = new FuncDataTemplate<string>((x, _) => new TextBlock { Height = 10 }),
};
target.ApplyTemplate();

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

Loading…
Cancel
Save