Browse Source

Merge branch 'master' into fixes/2260-image-sizing

pull/2262/head
danwalmsley 8 years ago
committed by GitHub
parent
commit
7a6474180d
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 31
      Avalonia.sln
  2. 2
      build/SkiaSharp.props
  3. 2
      nukebuild/BuildParameters.cs
  4. 3
      scripts/ReplaceNugetCache.ps1
  5. 2
      src/Avalonia.Base/AvaloniaProperty.cs
  6. 22
      src/Avalonia.Base/AvaloniaPropertyRegistry.cs
  7. 76
      src/Avalonia.Base/Data/Core/Plugins/ObservableStreamPlugin.cs
  8. 20
      src/Avalonia.Base/Utilities/AvaloniaResourcesIndex.cs
  9. 145
      src/Avalonia.Base/ValueStore.cs
  10. 11
      src/Avalonia.Controls/TopLevel.cs
  11. 2
      src/Avalonia.Controls/Window.cs
  12. 3
      src/Avalonia.Controls/WindowBase.cs
  13. 1
      src/Avalonia.OpenGL/GlEntryPointAttribute.cs
  14. 21
      src/Avalonia.OpenGL/GlInterface.cs
  15. 25
      src/Avalonia.OpenGL/GlInterfaceBase.cs
  16. 6
      src/Avalonia.ReactiveUI/AvaloniaActivationForViewFetcher.cs
  17. 4
      src/Avalonia.ReactiveUI/ReactiveUserControl.cs
  18. 7
      src/Avalonia.Remote.Protocol/MetsysBson.cs
  19. 6
      src/Avalonia.Remote.Protocol/TcpTransportBase.cs
  20. 7
      src/Avalonia.Remote.Protocol/TransportConnectionWrapper.cs
  21. 72
      src/Avalonia.Styling/Styling/NotSelector.cs
  22. 11
      src/Avalonia.Styling/Styling/Selectors.cs
  23. 1
      src/Avalonia.X11/Avalonia.X11.csproj
  24. 84
      src/Avalonia.X11/Glx/Glx.cs
  25. 9
      src/Avalonia.X11/Glx/GlxConsts.cs
  26. 17
      src/Avalonia.X11/Glx/GlxContext.cs
  27. 74
      src/Avalonia.X11/Glx/GlxDisplay.cs
  28. 4
      src/Avalonia.X11/Glx/GlxGlPlatformSurface.cs
  29. 263
      src/Avalonia.X11/NativeDialogs/Gtk.cs
  30. 122
      src/Avalonia.X11/NativeDialogs/GtkNativeFileDialogs.cs
  31. 4
      src/Avalonia.X11/X11Platform.cs
  32. 5
      src/Avalonia.X11/X11Window.cs
  33. 16
      src/Markup/Avalonia.Markup.Xaml/AvaloniaXamlLoader.cs
  34. 2
      src/Markup/Avalonia.Markup.Xaml/PortableXaml/AvaloniaXamlSchemaContext.cs
  35. 2
      src/Markup/Avalonia.Markup.Xaml/PortableXaml/portable.xaml.github
  36. 51
      src/Markup/Avalonia.Markup/Markup/Parsers/SelectorGrammar.cs
  37. 13
      src/Markup/Avalonia.Markup/Markup/Parsers/SelectorParser.cs
  38. 12
      tests/Avalonia.Base.UnitTests/AvaloniaPropertyRegistryTests.cs
  39. 20
      tests/Avalonia.Base.UnitTests/Data/Core/ExpressionObserverTests_Observable.cs
  40. 16
      tests/Avalonia.Controls.UnitTests/WindowBaseTests.cs
  41. 21
      tests/Avalonia.Controls.UnitTests/WindowTests.cs
  42. 62
      tests/Avalonia.DesignerSupport.Tests/Helpers.cs
  43. 171
      tests/Avalonia.DesignerSupport.Tests/RemoteProtocolTests.cs
  44. 73
      tests/Avalonia.Markup.UnitTests/Parsers/SelectorGrammarTests.cs
  45. 28
      tests/Avalonia.Markup.Xaml.UnitTests/Xaml/StyleTests.cs
  46. 35
      tests/Avalonia.ReactiveUI.UnitTests/AvaloniaActivationForViewFetcherTest.cs
  47. 114
      tests/Avalonia.Styling.UnitTests/SelectorTests_Not.cs

31
Avalonia.sln

@ -196,9 +196,11 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "_build", "nukebuild\_build.
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Avalonia.Animation.UnitTests", "tests\Avalonia.Animation.UnitTests\Avalonia.Animation.UnitTests.csproj", "{AF227847-E65C-4BE9-BCE9-B551357788E0}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Avalonia.X11", "src\Avalonia.X11\Avalonia.X11.csproj", "{41B02319-965D-4945-8005-C1A3D1224165}"
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Avalonia.X11", "src\Avalonia.X11\Avalonia.X11.csproj", "{41B02319-965D-4945-8005-C1A3D1224165}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PlatformSanityChecks", "samples\PlatformSanityChecks\PlatformSanityChecks.csproj", "{D775DECB-4E00-4ED5-A75A-5FCE58ADFF0B}"
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "PlatformSanityChecks", "samples\PlatformSanityChecks\PlatformSanityChecks.csproj", "{D775DECB-4E00-4ED5-A75A-5FCE58ADFF0B}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Avalonia.ReactiveUI.UnitTests", "tests\Avalonia.ReactiveUI.UnitTests\Avalonia.ReactiveUI.UnitTests.csproj", "{AF915D5C-AB00-4EA0-B5E6-001F4AE84E68}"
EndProject
Global
GlobalSection(SharedMSBuildProjectFiles) = preSolution
@ -1819,6 +1821,30 @@ Global
{D775DECB-4E00-4ED5-A75A-5FCE58ADFF0B}.Release|iPhone.Build.0 = Release|Any CPU
{D775DECB-4E00-4ED5-A75A-5FCE58ADFF0B}.Release|iPhoneSimulator.ActiveCfg = Release|Any CPU
{D775DECB-4E00-4ED5-A75A-5FCE58ADFF0B}.Release|iPhoneSimulator.Build.0 = Release|Any CPU
{AF915D5C-AB00-4EA0-B5E6-001F4AE84E68}.Ad-Hoc|Any CPU.ActiveCfg = Debug|Any CPU
{AF915D5C-AB00-4EA0-B5E6-001F4AE84E68}.Ad-Hoc|Any CPU.Build.0 = Debug|Any CPU
{AF915D5C-AB00-4EA0-B5E6-001F4AE84E68}.Ad-Hoc|iPhone.ActiveCfg = Debug|Any CPU
{AF915D5C-AB00-4EA0-B5E6-001F4AE84E68}.Ad-Hoc|iPhone.Build.0 = Debug|Any CPU
{AF915D5C-AB00-4EA0-B5E6-001F4AE84E68}.Ad-Hoc|iPhoneSimulator.ActiveCfg = Debug|Any CPU
{AF915D5C-AB00-4EA0-B5E6-001F4AE84E68}.Ad-Hoc|iPhoneSimulator.Build.0 = Debug|Any CPU
{AF915D5C-AB00-4EA0-B5E6-001F4AE84E68}.AppStore|Any CPU.ActiveCfg = Debug|Any CPU
{AF915D5C-AB00-4EA0-B5E6-001F4AE84E68}.AppStore|Any CPU.Build.0 = Debug|Any CPU
{AF915D5C-AB00-4EA0-B5E6-001F4AE84E68}.AppStore|iPhone.ActiveCfg = Debug|Any CPU
{AF915D5C-AB00-4EA0-B5E6-001F4AE84E68}.AppStore|iPhone.Build.0 = Debug|Any CPU
{AF915D5C-AB00-4EA0-B5E6-001F4AE84E68}.AppStore|iPhoneSimulator.ActiveCfg = Debug|Any CPU
{AF915D5C-AB00-4EA0-B5E6-001F4AE84E68}.AppStore|iPhoneSimulator.Build.0 = Debug|Any CPU
{AF915D5C-AB00-4EA0-B5E6-001F4AE84E68}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{AF915D5C-AB00-4EA0-B5E6-001F4AE84E68}.Debug|Any CPU.Build.0 = Debug|Any CPU
{AF915D5C-AB00-4EA0-B5E6-001F4AE84E68}.Debug|iPhone.ActiveCfg = Debug|Any CPU
{AF915D5C-AB00-4EA0-B5E6-001F4AE84E68}.Debug|iPhone.Build.0 = Debug|Any CPU
{AF915D5C-AB00-4EA0-B5E6-001F4AE84E68}.Debug|iPhoneSimulator.ActiveCfg = Debug|Any CPU
{AF915D5C-AB00-4EA0-B5E6-001F4AE84E68}.Debug|iPhoneSimulator.Build.0 = Debug|Any CPU
{AF915D5C-AB00-4EA0-B5E6-001F4AE84E68}.Release|Any CPU.ActiveCfg = Release|Any CPU
{AF915D5C-AB00-4EA0-B5E6-001F4AE84E68}.Release|Any CPU.Build.0 = Release|Any CPU
{AF915D5C-AB00-4EA0-B5E6-001F4AE84E68}.Release|iPhone.ActiveCfg = Release|Any CPU
{AF915D5C-AB00-4EA0-B5E6-001F4AE84E68}.Release|iPhone.Build.0 = Release|Any CPU
{AF915D5C-AB00-4EA0-B5E6-001F4AE84E68}.Release|iPhoneSimulator.ActiveCfg = Release|Any CPU
{AF915D5C-AB00-4EA0-B5E6-001F4AE84E68}.Release|iPhoneSimulator.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
@ -1875,6 +1901,7 @@ Global
{AF227847-E65C-4BE9-BCE9-B551357788E0} = {C5A00AC3-B34C-4564-9BDD-2DA473EF4D8B}
{41B02319-965D-4945-8005-C1A3D1224165} = {86C53C40-57AA-45B8-AD42-FAE0EFDF0F2B}
{D775DECB-4E00-4ED5-A75A-5FCE58ADFF0B} = {9B9E3891-2366-4253-A952-D08BCEB71098}
{AF915D5C-AB00-4EA0-B5E6-001F4AE84E68} = {C5A00AC3-B34C-4564-9BDD-2DA473EF4D8B}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {87366D66-1391-4D90-8999-95A620AD786A}

2
build/SkiaSharp.props

@ -1,6 +1,6 @@
<Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<ItemGroup>
<PackageReference Include="SkiaSharp" Version="1.68.0" />
<PackageReference Condition="'$(IncludeLinuxSkia)' == 'true'" Include="SkiaSharp.NativeAssets.Linux" Version="1.68.0" />
<PackageReference Condition="'$(IncludeLinuxSkia)' == 'true'" Include="Avalonia.Skia.Linux.Natives" Version="1.68.0.1" />
</ItemGroup>
</Project>

2
nukebuild/BuildParameters.cs

@ -109,7 +109,7 @@ public partial class Build
if (!IsNuGetRelease)
{
// Use AssemblyVersion with Build as version
Version += "-build" + Environment.GetEnvironmentVariable("BUILD_BUILDID") + "-beta";
Version += "-cibuild" + int.Parse(Environment.GetEnvironmentVariable("BUILD_BUILDID")).ToString("0000000") + "-beta";
}
PublishTestResults = true;

3
scripts/ReplaceNugetCache.ps1

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

2
src/Avalonia.Base/AvaloniaProperty.cs

@ -21,7 +21,7 @@ namespace Avalonia
/// </summary>
public static readonly object UnsetValue = new Unset();
private static int s_nextId = 1;
private static int s_nextId;
private readonly Subject<AvaloniaPropertyChangedEventArgs> _initialized;
private readonly Subject<AvaloniaPropertyChangedEventArgs> _changed;
private readonly PropertyMetadata _defaultMetadata;

22
src/Avalonia.Base/AvaloniaPropertyRegistry.cs

@ -13,6 +13,8 @@ namespace Avalonia
/// </summary>
public class AvaloniaPropertyRegistry
{
private readonly List<AvaloniaProperty> _properties =
new List<AvaloniaProperty>();
private readonly Dictionary<Type, Dictionary<int, AvaloniaProperty>> _registered =
new Dictionary<Type, Dictionary<int, AvaloniaProperty>>();
private readonly Dictionary<Type, Dictionary<int, AvaloniaProperty>> _attached =
@ -28,6 +30,11 @@ namespace Avalonia
public static AvaloniaPropertyRegistry Instance { get; }
= new AvaloniaPropertyRegistry();
/// <summary>
/// Gets a list of all registered properties.
/// </summary>
internal IReadOnlyList<AvaloniaProperty> Properties => _properties;
/// <summary>
/// Gets all non-attached <see cref="AvaloniaProperty"/>s registered on a type.
/// </summary>
@ -148,6 +155,16 @@ namespace Avalonia
return FindRegistered(o.GetType(), name);
}
/// <summary>
/// Finds a registered property by Id.
/// </summary>
/// <param name="id">The property Id.</param>
/// <returns>The registered property or null if no matching property found.</returns>
internal AvaloniaProperty FindRegistered(int id)
{
return id < _properties.Count ? _properties[id] : null;
}
/// <summary>
/// Checks whether a <see cref="AvaloniaProperty"/> is registered on a type.
/// </summary>
@ -202,7 +219,8 @@ namespace Avalonia
{
inner.Add(property.Id, property);
}
_properties.Add(property);
_registeredCache.Clear();
}
@ -237,7 +255,7 @@ namespace Avalonia
{
inner.Add(property.Id, property);
}
_attachedCache.Clear();
}
}

76
src/Avalonia.Base/Data/Core/Plugins/ObservableStreamPlugin.cs

@ -2,6 +2,9 @@
// Licensed under the MIT license. See licence.md file in the project root for full license information.
using System;
using System.Linq;
using System.Reactive.Linq;
using System.Reflection;
namespace Avalonia.Data.Core.Plugins
{
@ -10,12 +13,19 @@ namespace Avalonia.Data.Core.Plugins
/// </summary>
public class ObservableStreamPlugin : IStreamPlugin
{
static MethodInfo observableSelect;
/// <summary>
/// Checks whether this plugin handles the specified value.
/// </summary>
/// <param name="reference">A weak reference to the value.</param>
/// <returns>True if the plugin can handle the value; otherwise false.</returns>
public virtual bool Match(WeakReference reference) => reference.Target is IObservable<object>;
public virtual bool Match(WeakReference reference)
{
return reference.Target.GetType().GetInterfaces().Any(x =>
x.IsGenericType &&
x.GetGenericTypeDefinition() == typeof(IObservable<>));
}
/// <summary>
/// Starts producing output based on the specified value.
@ -26,7 +36,69 @@ namespace Avalonia.Data.Core.Plugins
/// </returns>
public virtual IObservable<object> Start(WeakReference reference)
{
return reference.Target as IObservable<object>;
var target = reference.Target;
// If the observable returns a reference type then we can cast it.
if (target is IObservable<object> result)
{
return result;
};
// If the observable returns a value type then we need to call Observable.Select on it.
// First get the type of T in `IObservable<T>`.
var sourceType = reference.Target.GetType().GetInterfaces().First(x =>
x.IsGenericType &&
x.GetGenericTypeDefinition() == typeof(IObservable<>)).GetGenericArguments()[0];
// Get the Observable.Select method.
var select = GetObservableSelect(sourceType);
// Make a Box<> delegate of the correct type.
var funcType = typeof(Func<,>).MakeGenericType(sourceType, typeof(object));
var box = GetType().GetMethod(nameof(Box), BindingFlags.Static | BindingFlags.NonPublic)
.MakeGenericMethod(sourceType)
.CreateDelegate(funcType);
// Call Observable.Select(target, box);
return (IObservable<object>)select.Invoke(
null,
new object[] { target, box });
}
private static MethodInfo GetObservableSelect(Type source)
{
return GetObservableSelect().MakeGenericMethod(source, typeof(object));
}
private static MethodInfo GetObservableSelect()
{
if (observableSelect == null)
{
observableSelect = typeof(Observable).GetRuntimeMethods().First(x =>
{
if (x.Name == nameof(Observable.Select) &&
x.ContainsGenericParameters &&
x.GetGenericArguments().Length == 2)
{
var parameters = x.GetParameters();
if (parameters.Length == 2 &&
parameters[0].ParameterType.IsConstructedGenericType &&
parameters[0].ParameterType.GetGenericTypeDefinition() == typeof(IObservable<>) &&
parameters[1].ParameterType.IsConstructedGenericType &&
parameters[1].ParameterType.GetGenericTypeDefinition() == typeof(Func<,>))
{
return true;
}
}
return false;
});
}
return observableSelect;
}
private static object Box<T>(T value) => (object)value;
}
}

20
src/Avalonia.Base/Utilities/AvaloniaResourcesIndex.cs

@ -4,6 +4,8 @@ using System.IO;
using System.Runtime.CompilerServices;
using System.Runtime.Serialization;
using System.Runtime.Serialization.Json;
using System.Xml.Linq;
using System.Linq;
// ReSharper disable AssignNullToNotNullAttribute
@ -19,10 +21,20 @@ namespace Avalonia.Utilities
{
var ver = new BinaryReader(stream).ReadInt32();
if (ver > LastKnownVersion)
throw new Exception("Resources index format version is not known");
var index = (AvaloniaResourcesIndex)
new DataContractSerializer(typeof(AvaloniaResourcesIndex)).ReadObject(stream);
return index.Entries;
throw new Exception("Resources index format version is not known");
var assetDoc = XDocument.Load(stream);
XNamespace assetNs = assetDoc.Root.Attribute("xmlns").Value;
List<AvaloniaResourcesIndexEntry> entries=
(from entry in assetDoc.Root.Element(assetNs + "Entries").Elements(assetNs + "AvaloniaResourcesIndexEntry")
select new AvaloniaResourcesIndexEntry
{
Path = entry.Element(assetNs + "Path").Value,
Offset = int.Parse(entry.Element(assetNs + "Offset").Value),
Size = int.Parse(entry.Element(assetNs + "Size").Value)
}).ToList();
return entries;
}
public static void Write(Stream stream, List<AvaloniaResourcesIndexEntry> entries)

145
src/Avalonia.Base/ValueStore.cs

@ -7,13 +7,21 @@ namespace Avalonia
{
internal class ValueStore : IPriorityValueOwner
{
private struct Entry
{
internal int PropertyId;
internal object Value;
}
private readonly AvaloniaObject _owner;
private readonly Dictionary<AvaloniaProperty, object> _values =
new Dictionary<AvaloniaProperty, object>();
private Entry[] _entries;
public ValueStore(AvaloniaObject owner)
{
_owner = owner;
// The last item in the list is always int.MaxValue
_entries = new[] { new Entry { PropertyId = int.MaxValue, Value = null } };
}
public IDisposable AddBinding(
@ -23,7 +31,7 @@ namespace Avalonia
{
PriorityValue priorityValue;
if (_values.TryGetValue(property, out var v))
if (TryGetValue(property, out var v))
{
priorityValue = v as PriorityValue;
@ -31,13 +39,13 @@ namespace Avalonia
{
priorityValue = CreatePriorityValue(property);
priorityValue.SetValue(v, (int)BindingPriority.LocalValue);
_values[property] = priorityValue;
SetValueInternal(property, priorityValue);
}
}
else
{
priorityValue = CreatePriorityValue(property);
_values.Add(property, priorityValue);
AddValueInternal(property, priorityValue);
}
return priorityValue.Add(source, (int)priority);
@ -47,7 +55,7 @@ namespace Avalonia
{
PriorityValue priorityValue;
if (_values.TryGetValue(property, out var v))
if (TryGetValue(property, out var v))
{
priorityValue = v as PriorityValue;
@ -55,7 +63,7 @@ namespace Avalonia
{
if (priority == (int)BindingPriority.LocalValue)
{
_values[property] = Validate(property, value);
SetValueInternal(property, Validate(property, value));
Changed(property, priority, v, value);
return;
}
@ -63,7 +71,7 @@ namespace Avalonia
{
priorityValue = CreatePriorityValue(property);
priorityValue.SetValue(v, (int)BindingPriority.LocalValue);
_values[property] = priorityValue;
SetValueInternal(property, priorityValue);
}
}
}
@ -76,14 +84,14 @@ namespace Avalonia
if (priority == (int)BindingPriority.LocalValue)
{
_values.Add(property, Validate(property, value));
AddValueInternal(property, Validate(property, value));
Changed(property, priority, AvaloniaProperty.UnsetValue, value);
return;
}
else
{
priorityValue = CreatePriorityValue(property);
_values.Add(property, priorityValue);
AddValueInternal(property, priorityValue);
}
}
@ -100,13 +108,22 @@ namespace Avalonia
_owner.PriorityValueChanged(property, priority, oldValue, newValue);
}
public IDictionary<AvaloniaProperty, object> GetSetValues() => _values;
public IDictionary<AvaloniaProperty, object> GetSetValues()
{
var dict = new Dictionary<AvaloniaProperty, object>(_entries.Length - 1);
for (int i = 0; i < _entries.Length - 1; ++i)
{
dict.Add(AvaloniaPropertyRegistry.Instance.FindRegistered(_entries[i].PropertyId), _entries[i].Value);
}
return dict;
}
public object GetValue(AvaloniaProperty property)
{
var result = AvaloniaProperty.UnsetValue;
if (_values.TryGetValue(property, out var value))
if (TryGetValue(property, out var value))
{
result = (value is PriorityValue priorityValue) ? priorityValue.Value : value;
}
@ -116,12 +133,12 @@ namespace Avalonia
public bool IsAnimating(AvaloniaProperty property)
{
return _values.TryGetValue(property, out var value) && value is PriorityValue priority && priority.IsAnimating;
return TryGetValue(property, out var value) && value is PriorityValue priority && priority.IsAnimating;
}
public bool IsSet(AvaloniaProperty property)
{
if (_values.TryGetValue(property, out var value))
if (TryGetValue(property, out var value))
{
return ((value as PriorityValue)?.Value ?? value) != AvaloniaProperty.UnsetValue;
}
@ -131,7 +148,7 @@ namespace Avalonia
public void Revalidate(AvaloniaProperty property)
{
if (_values.TryGetValue(property, out var value))
if (TryGetValue(property, out var value))
{
(value as PriorityValue)?.Revalidate();
}
@ -178,5 +195,103 @@ namespace Avalonia
(_deferredSetter = new DeferredSetter<object>());
}
}
private bool TryGetValue(AvaloniaProperty property, out object value)
{
(int index, bool found) = TryFindEntry(property.Id);
if (!found)
{
value = null;
return false;
}
value = _entries[index].Value;
return true;
}
private void AddValueInternal(AvaloniaProperty property, object value)
{
Entry[] entries = new Entry[_entries.Length + 1];
for (int i = 0; i < _entries.Length; ++i)
{
if (_entries[i].PropertyId > property.Id)
{
if (i > 0)
{
Array.Copy(_entries, 0, entries, 0, i);
}
entries[i] = new Entry { PropertyId = property.Id, Value = value };
Array.Copy(_entries, i, entries, i + 1, _entries.Length - i);
break;
}
}
_entries = entries;
}
private void SetValueInternal(AvaloniaProperty property, object value)
{
_entries[TryFindEntry(property.Id).Item1].Value = value;
}
private (int, bool) TryFindEntry(int propertyId)
{
if (_entries.Length <= 12)
{
// For small lists, we use an optimized linear search. Since the last item in the list
// is always int.MaxValue, we can skip a conditional branch in each iteration.
// By unrolling the loop, we can skip another unconditional branch in each iteration.
if (_entries[0].PropertyId >= propertyId) return (0, _entries[0].PropertyId == propertyId);
if (_entries[1].PropertyId >= propertyId) return (1, _entries[1].PropertyId == propertyId);
if (_entries[2].PropertyId >= propertyId) return (2, _entries[2].PropertyId == propertyId);
if (_entries[3].PropertyId >= propertyId) return (3, _entries[3].PropertyId == propertyId);
if (_entries[4].PropertyId >= propertyId) return (4, _entries[4].PropertyId == propertyId);
if (_entries[5].PropertyId >= propertyId) return (5, _entries[5].PropertyId == propertyId);
if (_entries[6].PropertyId >= propertyId) return (6, _entries[6].PropertyId == propertyId);
if (_entries[7].PropertyId >= propertyId) return (7, _entries[7].PropertyId == propertyId);
if (_entries[8].PropertyId >= propertyId) return (8, _entries[8].PropertyId == propertyId);
if (_entries[9].PropertyId >= propertyId) return (9, _entries[9].PropertyId == propertyId);
if (_entries[10].PropertyId >= propertyId) return (10, _entries[10].PropertyId == propertyId);
}
else
{
int low = 0;
int high = _entries.Length;
int id;
while (high - low > 3)
{
int pivot = (high + low) / 2;
id = _entries[pivot].PropertyId;
if (propertyId == id)
return (pivot, true);
if (propertyId <= id)
high = pivot;
else
low = pivot + 1;
}
do
{
id = _entries[low].PropertyId;
if (id == propertyId)
return (low, true);
if (id > propertyId)
break;
++low;
}
while (low < high);
}
return (0, false);
}
}
}

11
src/Avalonia.Controls/TopLevel.cs

@ -136,6 +136,11 @@ namespace Avalonia.Controls
}
}
/// <summary>
/// Fired when the window is opened.
/// </summary>
public event EventHandler Opened;
/// <summary>
/// Fired when the window is closed.
/// </summary>
@ -311,6 +316,12 @@ namespace Avalonia.Controls
$"Control '{GetType().Name}' is a top level control and cannot be added as a child.");
}
/// <summary>
/// Raises the <see cref="Opened"/> event.
/// </summary>
/// <param name="e">The event args.</param>
protected virtual void OnOpened(EventArgs e) => Opened?.Invoke(this, e);
/// <summary>
/// Tries to get a service from an <see cref="IAvaloniaDependencyResolver"/>, logging a
/// warning if not found.

2
src/Avalonia.Controls/Window.cs

@ -389,6 +389,7 @@ namespace Avalonia.Controls
Renderer?.Start();
}
SetWindowStartupLocation(Owner?.PlatformImpl);
OnOpened(EventArgs.Empty);
}
/// <summary>
@ -458,6 +459,7 @@ namespace Avalonia.Controls
owner.Activate();
result.SetResult((TResult)(_dialogResult ?? default(TResult)));
});
OnOpened(EventArgs.Empty);
}
SetWindowStartupLocation(owner);

3
src/Avalonia.Controls/WindowBase.cs

@ -163,7 +163,7 @@ namespace Avalonia.Controls
}
/// <summary>
/// Shows the popup.
/// Shows the window.
/// </summary>
public virtual void Show()
{
@ -181,6 +181,7 @@ namespace Avalonia.Controls
}
PlatformImpl?.Show();
Renderer?.Start();
OnOpened(EventArgs.Empty);
}
finally
{

1
src/Avalonia.OpenGL/GlEntryPointAttribute.cs

@ -2,6 +2,7 @@ using System;
namespace Avalonia.OpenGL
{
[AttributeUsage(AttributeTargets.Property)]
public class GlEntryPointAttribute : Attribute
{
public string EntryPoint { get; }

21
src/Avalonia.OpenGL/GlInterface.cs

@ -8,31 +8,24 @@ namespace Avalonia.OpenGL
public class GlInterface : GlInterfaceBase
{
private readonly Func<string, bool, IntPtr> _getProcAddress;
public string Version { get; }
public GlInterface(Func<string, bool, IntPtr> getProcAddress) : base(getProcAddress)
{
_getProcAddress = getProcAddress;
var versionPtr = GetString(GlConsts.GL_VERSION);
if (versionPtr != IntPtr.Zero)
Version = Marshal.PtrToStringAnsi(versionPtr);
}
public static GlInterface FromNativeUtf8GetProcAddress(Func<Utf8Buffer, IntPtr> getProcAddress) =>
new GlInterface((proc, optional) =>
{
using (var u = new Utf8Buffer(proc))
{
var rv = getProcAddress(u);
if (rv == IntPtr.Zero && !optional)
throw new OpenGlException("Missing function " + proc);
return rv;
}
});
public GlInterface(Func<Utf8Buffer, IntPtr> n) : this(ConvertNative(n))
{
}
public IntPtr GetProcAddress(string proc) => _getProcAddress(proc, true);
public static GlInterface FromNativeUtf8GetProcAddress(Func<Utf8Buffer, IntPtr> getProcAddress) =>
new GlInterface(getProcAddress);
public T GetProcAddress<T>(string proc) => Marshal.GetDelegateForFunctionPointer<T>(GetProcAddress(proc));
// ReSharper disable UnassignedGetOnlyAutoProperty

25
src/Avalonia.OpenGL/GlInterfaceBase.cs

@ -1,13 +1,17 @@
using System;
using System.Reflection;
using System.Runtime.InteropServices;
using Avalonia.Platform.Interop;
namespace Avalonia.OpenGL
{
public class GlInterfaceBase
{
private readonly Func<string, bool, IntPtr> _getProcAddress;
public GlInterfaceBase(Func<string, bool, IntPtr> getProcAddress)
{
_getProcAddress = getProcAddress;
foreach (var prop in this.GetType().GetProperties())
{
var a = prop.GetCustomAttribute<GlEntryPointAttribute>();
@ -24,5 +28,26 @@ namespace Avalonia.OpenGL
}
}
}
protected static Func<string, bool, IntPtr> ConvertNative(Func<Utf8Buffer, IntPtr> func) =>
(proc, optional) =>
{
using (var u = new Utf8Buffer(proc))
{
var rv = func(u);
if (rv == IntPtr.Zero && !optional)
throw new OpenGlException("Missing function " + proc);
return rv;
}
};
public GlInterfaceBase(Func<Utf8Buffer, IntPtr> nativeGetProcAddress) : this(ConvertNative(nativeGetProcAddress))
{
}
public IntPtr GetProcAddress(string proc) => _getProcAddress(proc, true);
public IntPtr GetProcAddress(string proc, bool optional) => _getProcAddress(proc, optional);
}
}

6
src/Avalonia.ReactiveUI/AvaloniaActivationForViewFetcher.cs

@ -29,8 +29,8 @@ namespace Avalonia
{
var windowLoaded = Observable
.FromEventPattern(
x => window.Initialized += x,
x => window.Initialized -= x)
x => window.Opened += x,
x => window.Opened -= x)
.Select(args => true);
var windowUnloaded = Observable
.FromEventPattern(
@ -59,4 +59,4 @@ namespace Avalonia
.DistinctUntilChanged();
}
}
}
}

4
src/Avalonia.ReactiveUI/ReactiveUserControl.cs

@ -16,7 +16,7 @@ namespace Avalonia
public class ReactiveUserControl<TViewModel> : UserControl, IViewFor<TViewModel> where TViewModel : class
{
public static readonly AvaloniaProperty<TViewModel> ViewModelProperty = AvaloniaProperty
.Register<ReactiveWindow<TViewModel>, TViewModel>(nameof(ViewModel));
.Register<ReactiveUserControl<TViewModel>, TViewModel>(nameof(ViewModel));
/// <summary>
/// Initializes a new instance of the <see cref="ReactiveUserControl{TViewModel}"/> class.
@ -41,4 +41,4 @@ namespace Avalonia
set => ViewModel = (TViewModel)value;
}
}
}
}

7
src/Avalonia.Remote.Protocol/MetsysBson.cs

@ -1190,10 +1190,6 @@ namespace Metsys.Bson
object container = null;
var property = typeHelper.FindProperty(name);
var propertyType = property != null ? property.Type : _typeMap.ContainsKey(storageType) ? _typeMap[storageType] : typeof(object);
if (property == null && typeHelper.Expando == null)
{
throw new BsonException(string.Format("Deserialization failed: type {0} does not have a property named {1}", type.FullName, name));
}
if (property != null && property.Setter == null)
{
container = property.Getter(instance);
@ -1201,7 +1197,8 @@ namespace Metsys.Bson
var value = isNull ? null : DeserializeValue(propertyType, storageType, container, options);
if (property == null)
{
((IDictionary<string, object>)typeHelper.Expando.Getter(instance))[name] = value;
if (typeHelper.Expando != null)
((IDictionary<string, object>)typeHelper.Expando.Getter(instance))[name] = value;
}
else if (container == null && value != null && !property.Ignored)
{

6
src/Avalonia.Remote.Protocol/TcpTransportBase.cs

@ -46,7 +46,7 @@ namespace Avalonia.Remote.Protocol
{
try
{
var cl = await server.AcceptTcpClientAsync();
var cl = await server.AcceptTcpClientAsync().ConfigureAwait(false);
AcceptNew();
await Task.Run(async () =>
{
@ -54,7 +54,7 @@ namespace Avalonia.Remote.Protocol
var t = CreateTransport(_resolver, cl.GetStream(), () => tcs.TrySetResult(0));
cb(t);
await tcs.Task;
});
}).ConfigureAwait(false);
}
catch
{
@ -69,7 +69,7 @@ namespace Avalonia.Remote.Protocol
public async Task<IAvaloniaRemoteTransportConnection> Connect(IPAddress address, int port)
{
var c = new TcpClient();
await c.ConnectAsync(address, port);
await c.ConnectAsync(address, port).ConfigureAwait(false);
return CreateTransport(_resolver, c.GetStream(), ((IDisposable)c).Dispose);
}
}

7
src/Avalonia.Remote.Protocol/TransportConnectionWrapper.cs

@ -64,7 +64,7 @@ namespace Avalonia.Remote.Protocol
public Task Send(object data)
{
var tcs = new TaskCompletionSource<int>();
var tcs = new TaskCompletionSource<int>(TaskCreationOptions.RunContinuationsAsynchronously);
lock (_lock)
{
if (!_workerIsAlive)
@ -79,8 +79,9 @@ namespace Avalonia.Remote.Protocol
});
if (_signal != null)
{
_signal.SetResult(0);
var signal = _signal;
_signal = null;
signal.SetResult(0);
}
}
return tcs.Task;
@ -98,4 +99,4 @@ namespace Avalonia.Remote.Protocol
remove => _onException.Remove(value);
}
}
}
}

72
src/Avalonia.Styling/Styling/NotSelector.cs

@ -0,0 +1,72 @@
// 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;
using System.Reactive.Linq;
namespace Avalonia.Styling
{
/// <summary>
/// The `:not()` style selector.
/// </summary>
internal class NotSelector : Selector
{
private readonly Selector _previous;
private readonly Selector _argument;
private string _selectorString;
/// <summary>
/// Initializes a new instance of the <see cref="NotSelector"/> class.
/// </summary>
/// <param name="previous">The previous selector.</param>
/// <param name="argument">The selector to be not-ed.</param>
public NotSelector(Selector previous, Selector argument)
{
_previous = previous;
_argument = argument ?? throw new InvalidOperationException("Not selector must have a selector argument.");
}
/// <inheritdoc/>
public override bool InTemplate => _argument.InTemplate;
/// <inheritdoc/>
public override bool IsCombinator => false;
/// <inheritdoc/>
public override Type TargetType => _previous?.TargetType;
/// <inheritdoc/>
public override string ToString()
{
if (_selectorString == null)
{
_selectorString = ":not(" + _argument.ToString() + ")";
}
return _selectorString;
}
protected override SelectorMatch Evaluate(IStyleable control, bool subscribe)
{
var innerResult = _argument.Match(control, subscribe);
switch (innerResult.Result)
{
case SelectorMatchResult.AlwaysThisInstance:
return SelectorMatch.NeverThisInstance;
case SelectorMatchResult.AlwaysThisType:
return SelectorMatch.NeverThisType;
case SelectorMatchResult.NeverThisInstance:
return SelectorMatch.AlwaysThisInstance;
case SelectorMatchResult.NeverThisType:
return SelectorMatch.AlwaysThisType;
case SelectorMatchResult.Sometimes:
return new SelectorMatch(innerResult.Activator.Select(x => !x));
default:
throw new InvalidOperationException("Invalid SelectorMatchResult.");
}
}
protected override Selector MovePrevious() => _previous;
}
}

11
src/Avalonia.Styling/Styling/Selectors.cs

@ -94,6 +94,17 @@ namespace Avalonia.Styling
}
}
/// <summary>
/// Returns a selector which inverts the results of selector argument.
/// </summary>
/// <param name="previous">The previous selector.</param>
/// <param name="argument">The selector to be not-ed.</param>
/// <returns>The selector.</returns>
public static Selector Not(this Selector previous, Func<Selector, Selector> argument)
{
return new NotSelector(previous, argument(null));
}
/// <summary>
/// Returns a selector which matches a type.
/// </summary>

1
src/Avalonia.X11/Avalonia.X11.csproj

@ -7,7 +7,6 @@
<ItemGroup>
<ProjectReference Include="..\..\packages\Avalonia\Avalonia.csproj" />
<ProjectReference Include="..\Gtk\Avalonia.Gtk3\Avalonia.Gtk3.csproj" />
<ProjectReference Include="..\Skia\Avalonia.Skia\Avalonia.Skia.csproj" />
</ItemGroup>

84
src/Avalonia.X11/Glx/Glx.cs

@ -2,54 +2,90 @@ using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.InteropServices;
using Avalonia.OpenGL;
using Avalonia.Platform.Interop;
// ReSharper disable UnassignedGetOnlyAutoProperty
namespace Avalonia.X11.Glx
{
static unsafe class Glx
unsafe class GlxInterface : GlInterfaceBase
{
private const string libGL = "libGL.so.1";
[DllImport(libGL, EntryPoint = "glXMakeCurrent")]
public static extern bool GlxMakeCurrent(IntPtr display, IntPtr drawable, IntPtr context);
[GlEntryPointAttribute("glXMakeContextCurrent")]
public GlxMakeContextCurrent MakeContextCurrent { get; }
public delegate bool GlxMakeContextCurrent(IntPtr display, IntPtr draw, IntPtr read, IntPtr context);
[GlEntryPoint("glXCreatePbuffer")]
public GlxCreatePbuffer CreatePbuffer { get; }
public delegate IntPtr GlxCreatePbuffer(IntPtr dpy, IntPtr fbc, int[] attrib_list);
[GlEntryPointAttribute("glXChooseVisual")]
public GlxChooseVisual ChooseVisual { get; }
public delegate XVisualInfo* GlxChooseVisual(IntPtr dpy, int screen, int[] attribList);
[DllImport(libGL, EntryPoint = "glXChooseVisual")]
public static extern XVisualInfo* GlxChooseVisual(IntPtr dpy, int screen, int[] attribList);
[DllImport(libGL, EntryPoint = "glXCreateContext")]
public static extern IntPtr GlxCreateContext(IntPtr dpy, XVisualInfo* vis, IntPtr shareList, bool direct);
[GlEntryPointAttribute("glXCreateContext")]
public GlxCreateContext CreateContext { get; }
public delegate IntPtr GlxCreateContext(IntPtr dpy, XVisualInfo* vis, IntPtr shareList, bool direct);
[GlEntryPointAttribute("glXCreateContextAttribsARB")]
public GlxCreateContextAttribsARB CreateContextAttribsARB { get; }
public delegate IntPtr GlxCreateContextAttribsARB(IntPtr dpy, IntPtr fbconfig, IntPtr shareList,
bool direct, int[] attribs);
[DllImport(libGL, EntryPoint = "glXGetProcAddress")]
public static extern IntPtr GlxGetProcAddress(Utf8Buffer buffer);
public static extern IntPtr GlxGetProcAddress(Utf8Buffer buffer);
[GlEntryPointAttribute("glXDestroyContext")]
public GlxDestroyContext DestroyContext { get; }
public delegate void GlxDestroyContext(IntPtr dpy, IntPtr ctx);
[DllImport(libGL, EntryPoint = "glXDestroyContext")]
public static extern void GlxDestroyContext(IntPtr dpy, IntPtr ctx);
[GlEntryPointAttribute("glXChooseFBConfig")]
public GlxChooseFBConfig ChooseFBConfig { get; }
public delegate IntPtr* GlxChooseFBConfig(IntPtr dpy, int screen, int[] attrib_list, out int nelements);
[DllImport(libGL, EntryPoint = "glXChooseFBConfig")]
public static extern IntPtr* GlxChooseFBConfig(IntPtr dpy, int screen, int[] attrib_list, out int nelements);
public static IntPtr* GlxChooseFbConfig(IntPtr dpy, int screen, IEnumerable<int> attribs, out int nelements)
public IntPtr* GlxChooseFbConfig(IntPtr dpy, int screen, IEnumerable<int> attribs, out int nelements)
{
var arr = attribs.Concat(new[]{0}).ToArray();
return GlxChooseFBConfig(dpy, screen, arr, out nelements);
return ChooseFBConfig(dpy, screen, arr, out nelements);
}
[DllImport(libGL, EntryPoint = "glXGetVisualFromFBConfig")]
public static extern XVisualInfo * GlxGetVisualFromFBConfig(IntPtr dpy, IntPtr config);
[GlEntryPointAttribute("glXGetVisualFromFBConfig")]
public GlxGetVisualFromFBConfig GetVisualFromFBConfig { get; }
public delegate XVisualInfo * GlxGetVisualFromFBConfig(IntPtr dpy, IntPtr config);
[DllImport(libGL, EntryPoint = "glXGetFBConfigAttrib")]
public static extern int GlxGetFBConfigAttrib(IntPtr dpy, IntPtr config, int attribute, out int value);
[GlEntryPointAttribute("glXGetFBConfigAttrib")]
public GlxGetFBConfigAttrib GetFBConfigAttrib { get; }
public delegate int GlxGetFBConfigAttrib(IntPtr dpy, IntPtr config, int attribute, out int value);
[DllImport(libGL, EntryPoint = "glXSwapBuffers")]
public static extern void GlxSwapBuffers(IntPtr dpy, IntPtr drawable);
[GlEntryPointAttribute("glXSwapBuffers")]
public GlxSwapBuffers SwapBuffers { get; }
public delegate void GlxSwapBuffers(IntPtr dpy, IntPtr drawable);
[DllImport(libGL, EntryPoint = "glXWaitX")]
public static extern void GlxWaitX();
[DllImport(libGL, EntryPoint = "glXWaitGL")]
public static extern void GlxWaitGL();
[GlEntryPointAttribute("glXWaitX")]
public GlxWaitX WaitX { get; }
public delegate void GlxWaitX();
[GlEntryPointAttribute("glXWaitGL")]
public GlxWaitGL WaitGL { get; }
public delegate void GlxWaitGL();
public delegate int GlGetError();
[GlEntryPoint("glGetError")]
public GlGetError GetError { get; }
public GlxInterface() : base(GlxGetProcAddress)
{
}
}
}

9
src/Avalonia.X11/Glx/GlxConsts.cs

@ -93,5 +93,14 @@ namespace Avalonia.X11.Glx
public const int GLX_SAMPLES = 0x186a1 /*100001*/;
public const int GLX_PbufferClobber = 0;
public const int GLX_BufferSwapComplete = 1;
public const int GLX_CONTEXT_DEBUG_BIT_ARB = 0x00000001;
public const int GLX_CONTEXT_FORWARD_COMPATIBLE_BIT_ARB = 0x00000002;
public const int GLX_CONTEXT_MAJOR_VERSION_ARB = 0x2091;
public const int GLX_CONTEXT_MINOR_VERSION_ARB = 0x2092;
public const int GLX_CONTEXT_FLAGS_ARB = 0x2094;
public const int GLX_CONTEXT_CORE_PROFILE_BIT_ARB = 0x00000001;
public const int GLX_CONTEXT_COMPATIBILITY_PROFILE_BIT_ARB = 0x00000002;
public const int GLX_CONTEXT_PROFILE_MASK_ARB = 0x9126;
}
}

17
src/Avalonia.X11/Glx/GlxContext.cs

@ -2,19 +2,22 @@ using System;
using System.Reactive.Disposables;
using System.Threading;
using Avalonia.OpenGL;
using static Avalonia.X11.Glx.Glx;
namespace Avalonia.X11.Glx
{
class GlxContext : IGlContext
{
public IntPtr Handle { get; }
public GlxInterface Glx { get; }
private readonly X11Info _x11;
private readonly IntPtr _defaultXid;
private readonly object _lock = new object();
public GlxContext(IntPtr handle, GlxDisplay display, X11Info x11)
public GlxContext(GlxInterface glx, IntPtr handle, GlxDisplay display, X11Info x11, IntPtr defaultXid)
{
Handle = handle;
Glx = glx;
_x11 = x11;
_defaultXid = defaultXid;
Display = display;
}
@ -27,8 +30,12 @@ namespace Avalonia.X11.Glx
return Disposable.Create(() => Monitor.Exit(_lock));
}
public void MakeCurrent() => MakeCurrent(IntPtr.Zero);
public void MakeCurrent(IntPtr xid) => GlxMakeCurrent(_x11.Display, xid, Handle);
public void MakeCurrent() => MakeCurrent(_defaultXid);
public void MakeCurrent(IntPtr xid)
{
if (!Glx.MakeContextCurrent(_x11.Display, xid, xid, Handle))
throw new OpenGlException("glXMakeContextCurrent failed ");
}
}
}

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

@ -2,7 +2,7 @@ using System;
using System.Linq;
using Avalonia.OpenGL;
using static Avalonia.X11.Glx.GlxConsts;
using static Avalonia.X11.Glx.Glx;
namespace Avalonia.X11.Glx
{
unsafe class GlxDisplay : IGlDisplay
@ -19,7 +19,7 @@ namespace Avalonia.X11.Glx
public GlxContext ImmediateContext { get; }
public GlxContext DeferredContext { get; }
public GlxInterface Glx { get; } = new GlxInterface();
public GlxDisplay(X11Info x11)
{
_x11 = x11;
@ -28,7 +28,7 @@ namespace Avalonia.X11.Glx
{
GLX_X_RENDERABLE, 1,
GLX_RENDER_TYPE, GLX_RGBA_BIT,
GLX_DRAWABLE_TYPE, GLX_WINDOW_BIT,
GLX_DRAWABLE_TYPE, GLX_WINDOW_BIT | GLX_PBUFFER_BIT,
GLX_DOUBLEBUFFER, 1,
GLX_RED_SIZE, 8,
GLX_GREEN_SIZE, 8,
@ -45,12 +45,12 @@ namespace Avalonia.X11.Glx
baseAttribs,
})
{
var ptr = GlxChooseFBConfig(_x11.Display, x11.DefaultScreen,
var ptr = Glx.ChooseFBConfig(_x11.Display, x11.DefaultScreen,
attribs, out var count);
for (var c = 0 ; c < count; c++)
{
var visual = GlxGetVisualFromFBConfig(_x11.Display, ptr[c]);
var visual = Glx.GetVisualFromFBConfig(_x11.Display, ptr[c]);
// We prefer 32 bit visuals
if (_fbconfig == IntPtr.Zero || visual->depth == 32)
{
@ -70,29 +70,67 @@ namespace Avalonia.X11.Glx
if (_visual == null)
throw new OpenGlException("Unable to get visual info from FBConfig");
if (GlxGetFBConfigAttrib(_x11.Display, _fbconfig, GLX_SAMPLES, out var samples) == 0)
if (Glx.GetFBConfigAttrib(_x11.Display, _fbconfig, GLX_SAMPLES, out var samples) == 0)
SampleCount = samples;
if (GlxGetFBConfigAttrib(_x11.Display, _fbconfig, GLX_STENCIL_SIZE, out var stencil) == 0)
if (Glx.GetFBConfigAttrib(_x11.Display, _fbconfig, GLX_STENCIL_SIZE, out var stencil) == 0)
StencilSize = stencil;
ImmediateContext = CreateContext(null);
DeferredContext = CreateContext(ImmediateContext);
var pbuffers = Enumerable.Range(0, 2).Select(_ => Glx.CreatePbuffer(_x11.Display, _fbconfig, new[]
{
GLX_PBUFFER_WIDTH, 1, GLX_PBUFFER_HEIGHT, 1, 0
})).ToList();
XLib.XFlush(_x11.Display);
ImmediateContext = CreateContext(pbuffers[0],null);
DeferredContext = CreateContext(pbuffers[1], ImmediateContext);
ImmediateContext.MakeCurrent();
GlInterface = GlInterface.FromNativeUtf8GetProcAddress(p => GlxGetProcAddress(p));
var err = Glx.GetError();
GlInterface = new GlInterface(GlxInterface.GlxGetProcAddress);
if (GlInterface.Version == null)
throw new OpenGlException("GL version string is null, aborting");
}
public void ClearContext() => GlxMakeCurrent(_x11.Display, IntPtr.Zero, IntPtr.Zero);
public GlxContext CreateContext(IGlContext share)
public void ClearContext() => Glx.MakeContextCurrent(_x11.Display,
IntPtr.Zero, IntPtr.Zero, IntPtr.Zero);
public GlxContext CreateContext(IGlContext share) => CreateContext(IntPtr.Zero, share);
public GlxContext CreateContext(IntPtr defaultXid, IGlContext share)
{
var sharelist = ((GlxContext)share)?.Handle ?? IntPtr.Zero;
var h = GlxCreateContext(_x11.Display, _visual, sharelist, true);
if (h == IntPtr.Zero)
IntPtr handle = default;
foreach (var ver in new[]
{
new Version(4, 0), new Version(3, 2),
new Version(3, 0), new Version(2, 0)
})
{
var attrs = new[]
{
GLX_CONTEXT_MAJOR_VERSION_ARB, ver.Major,
GLX_CONTEXT_MINOR_VERSION_ARB, ver.Minor,
GLX_CONTEXT_PROFILE_MASK_ARB, GLX_CONTEXT_CORE_PROFILE_BIT_ARB,
0
};
try
{
handle = Glx.CreateContextAttribsARB(_x11.Display, _fbconfig, sharelist, true, attrs);
if (handle != IntPtr.Zero)
break;
}
catch
{
break;
}
}
if (handle == IntPtr.Zero)
throw new OpenGlException("Unable to create direct GLX context");
return new GlxContext(h, this, _x11);
return new GlxContext(new GlxInterface(), handle, this, _x11, defaultXid);
}
public void SwapBuffers(IntPtr xid) => GlxSwapBuffers(_x11.Display, xid);
public void SwapBuffers(IntPtr xid) => Glx.SwapBuffers(_x11.Display, xid);
}
}

4
src/Avalonia.X11/Glx/GlxGlPlatformSurface.cs

@ -70,9 +70,9 @@ namespace Avalonia.X11.Glx
public void Dispose()
{
_context.Display.GlInterface.Flush();
Glx.GlxWaitGL();
_context.Glx.WaitGL();
_context.Display.SwapBuffers(_info.Handle);
Glx.GlxWaitX();
_context.Glx.WaitX();
_context.Display.ClearContext();
_lock.Dispose();
}

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

@ -0,0 +1,263 @@
using System;
using System.Runtime.InteropServices;
using System.Threading;
using System.Threading.Tasks;
using Avalonia.Platform.Interop;
// ReSharper disable IdentifierTypo
namespace Avalonia.X11.NativeDialogs
{
static unsafe class Glib
{
private const string GlibName = "libglib-2.0.so.0";
private const string GObjectName = "libgobject-2.0.so.0";
[DllImport(GlibName)]
public static extern void g_slist_free(GSList* data);
[DllImport(GObjectName)]
private static extern void g_object_ref(IntPtr instance);
[DllImport(GObjectName)]
private static extern ulong g_signal_connect_object(IntPtr instance, Utf8Buffer signal,
IntPtr handler, IntPtr userData, int flags);
[DllImport(GObjectName)]
private static extern void g_object_unref(IntPtr instance);
[DllImport(GObjectName)]
private static extern ulong g_signal_handler_disconnect(IntPtr instance, ulong connectionId);
private delegate bool timeout_callback(IntPtr data);
[DllImport(GlibName)]
private static extern ulong g_timeout_add_full(int prio, uint interval, timeout_callback callback, IntPtr data,
IntPtr destroy);
class ConnectedSignal : IDisposable
{
private readonly IntPtr _instance;
private GCHandle _handle;
private readonly ulong _id;
public ConnectedSignal(IntPtr instance, GCHandle handle, ulong id)
{
_instance = instance;
g_object_ref(instance);
_handle = handle;
_id = id;
}
public void Dispose()
{
if (_handle.IsAllocated)
{
g_signal_handler_disconnect(_instance, _id);
g_object_unref(_instance);
_handle.Free();
}
}
}
public static IDisposable ConnectSignal<T>(IntPtr obj, string name, T handler)
{
var handle = GCHandle.Alloc(handler);
var ptr = Marshal.GetFunctionPointerForDelegate((Delegate)(object)handler);
using (var utf = new Utf8Buffer(name))
{
var id = g_signal_connect_object(obj, utf, ptr, IntPtr.Zero, 0);
if (id == 0)
throw new ArgumentException("Unable to connect to signal " + name);
return new ConnectedSignal(obj, handle, id);
}
}
static bool TimeoutHandler(IntPtr data)
{
var handle = GCHandle.FromIntPtr(data);
var cb = (Func<bool>)handle.Target;
if (!cb())
{
handle.Free();
return false;
}
return true;
}
private static readonly timeout_callback s_pinnedHandler;
static Glib()
{
s_pinnedHandler = TimeoutHandler;
}
static void AddTimeout(int priority, uint interval, Func<bool> callback)
{
var handle = GCHandle.Alloc(callback);
g_timeout_add_full(priority, interval, s_pinnedHandler, GCHandle.ToIntPtr(handle), IntPtr.Zero);
}
public static Task<T> RunOnGlibThread<T>(Func<T> action)
{
var tcs = new TaskCompletionSource<T>();
AddTimeout(0, 0, () =>
{
try
{
tcs.SetResult(action());
}
catch (Exception e)
{
tcs.TrySetException(e);
}
return false;
});
return tcs.Task;
}
}
[StructLayout(LayoutKind.Sequential)]
unsafe struct GSList
{
public readonly IntPtr Data;
public readonly GSList* Next;
}
enum GtkFileChooserAction
{
Open,
Save,
SelectFolder,
}
// ReSharper disable UnusedMember.Global
enum GtkResponseType
{
Help = -11,
Apply = -10,
No = -9,
Yes = -8,
Close = -7,
Cancel = -6,
Ok = -5,
DeleteEvent = -4,
Accept = -3,
Reject = -2,
None = -1,
}
// ReSharper restore UnusedMember.Global
static unsafe class Gtk
{
private static IntPtr s_display;
private const string GdkName = "libgdk-3.so.0";
private const string GtkName = "libgtk-3.so.0";
[DllImport(GtkName)]
static extern void gtk_main_iteration();
[DllImport(GtkName)]
public static extern void gtk_window_set_modal(IntPtr window, bool modal);
[DllImport(GtkName)]
public static extern void gtk_window_present(IntPtr gtkWindow);
public delegate bool signal_generic(IntPtr gtkWidget, IntPtr userData);
public delegate bool signal_dialog_response(IntPtr gtkWidget, GtkResponseType response, IntPtr userData);
[DllImport(GtkName)]
public static extern IntPtr gtk_file_chooser_dialog_new(Utf8Buffer title, IntPtr parent,
GtkFileChooserAction action, IntPtr ignore);
[DllImport(GtkName)]
public static extern void gtk_file_chooser_set_select_multiple(IntPtr chooser, bool allow);
[DllImport(GtkName)]
public static extern void
gtk_dialog_add_button(IntPtr raw, Utf8Buffer button_text, GtkResponseType response_id);
[DllImport(GtkName)]
public static extern GSList* gtk_file_chooser_get_filenames(IntPtr chooser);
[DllImport(GtkName)]
public static extern void gtk_file_chooser_set_filename(IntPtr chooser, Utf8Buffer file);
[DllImport(GtkName)]
public static extern void gtk_widget_realize(IntPtr gtkWidget);
[DllImport(GtkName)]
public static extern IntPtr gtk_widget_get_window(IntPtr gtkWidget);
[DllImport(GtkName)]
public static extern void gtk_widget_hide(IntPtr gtkWidget);
[DllImport(GtkName)]
static extern bool gtk_init_check(int argc, IntPtr argv);
[DllImport(GdkName)]
static extern IntPtr gdk_x11_window_foreign_new_for_display(IntPtr display, IntPtr xid);
[DllImport(GdkName)]
static extern IntPtr gdk_set_allowed_backends(Utf8Buffer backends);
[DllImport(GdkName)]
static extern IntPtr gdk_display_get_default();
[DllImport(GtkName)]
static extern IntPtr gtk_application_new(Utf8Buffer appId, int flags);
[DllImport(GdkName)]
public static extern void gdk_window_set_transient_for(IntPtr window, IntPtr parent);
public static IntPtr GetForeignWindow(IntPtr xid) => gdk_x11_window_foreign_new_for_display(s_display, xid);
public static Task<bool> StartGtk()
{
var tcs = new TaskCompletionSource<bool>();
new Thread(() =>
{
try
{
using (var backends = new Utf8Buffer("x11"))
gdk_set_allowed_backends(backends);
}
catch
{
//Ignore
}
Environment.SetEnvironmentVariable("WAYLAND_DISPLAY",
"/proc/fake-display-to-prevent-wayland-initialization-by-gtk3");
if (!gtk_init_check(0, IntPtr.Zero))
{
tcs.SetResult(false);
return;
}
IntPtr app;
using (var utf = new Utf8Buffer($"avalonia.app.a{Guid.NewGuid():N}"))
app = gtk_application_new(utf, 0);
if (app == IntPtr.Zero)
{
tcs.SetResult(false);
return;
}
s_display = gdk_display_get_default();
tcs.SetResult(true);
while (true)
gtk_main_iteration();
}) {Name = "GTK3THREAD", IsBackground = true}.Start();
return tcs.Task;
}
}
}

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

@ -0,0 +1,122 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Threading.Tasks;
using Avalonia.Controls;
using Avalonia.Controls.Platform;
using Avalonia.Platform;
using Avalonia.Platform.Interop;
using static Avalonia.X11.NativeDialogs.Glib;
using static Avalonia.X11.NativeDialogs.Gtk;
// ReSharper disable AccessToModifiedClosure
namespace Avalonia.X11.NativeDialogs
{
class GtkSystemDialog : ISystemDialogImpl
{
private Task<bool> _initialized;
private unsafe Task<string[]> ShowDialog(string title, IWindowImpl parent, GtkFileChooserAction action,
bool multiSelect, string initialFileName)
{
IntPtr dlg;
using (var name = new Utf8Buffer(title))
dlg = gtk_file_chooser_dialog_new(name, IntPtr.Zero, action, IntPtr.Zero);
UpdateParent(dlg, parent);
if (multiSelect)
gtk_file_chooser_set_select_multiple(dlg, true);
gtk_window_set_modal(dlg, true);
var tcs = new TaskCompletionSource<string[]>();
List<IDisposable> disposables = null;
void Dispose()
{
// ReSharper disable once PossibleNullReferenceException
foreach (var d in disposables) d.Dispose();
disposables.Clear();
}
disposables = new List<IDisposable>
{
ConnectSignal<signal_generic>(dlg, "close", delegate
{
tcs.TrySetResult(null);
Dispose();
return false;
}),
ConnectSignal<signal_dialog_response>(dlg, "response", (_, resp, __) =>
{
string[] result = null;
if (resp == GtkResponseType.Accept)
{
var resultList = new List<string>();
var gs = gtk_file_chooser_get_filenames(dlg);
var cgs = gs;
while (cgs != null)
{
if (cgs->Data != IntPtr.Zero)
resultList.Add(Utf8Buffer.StringFromPtr(cgs->Data));
cgs = cgs->Next;
}
g_slist_free(gs);
result = resultList.ToArray();
}
gtk_widget_hide(dlg);
Dispose();
tcs.TrySetResult(result);
return false;
})
};
using (var open = new Utf8Buffer("Open"))
gtk_dialog_add_button(dlg, open, GtkResponseType.Accept);
using (var open = new Utf8Buffer("Cancel"))
gtk_dialog_add_button(dlg, open, GtkResponseType.Cancel);
if (initialFileName != null)
using (var fn = new Utf8Buffer(initialFileName))
gtk_file_chooser_set_filename(dlg, fn);
gtk_window_present(dlg);
return tcs.Task;
}
public async Task<string[]> ShowFileDialogAsync(FileDialog dialog, IWindowImpl parent)
{
await EnsureInitialized();
return await await RunOnGlibThread(
() => ShowDialog(dialog.Title, parent,
dialog is OpenFileDialog ? GtkFileChooserAction.Open : GtkFileChooserAction.Save,
(dialog as OpenFileDialog)?.AllowMultiple ?? false,
Path.Combine(string.IsNullOrEmpty(dialog.InitialDirectory) ? "" : dialog.InitialDirectory,
string.IsNullOrEmpty(dialog.InitialFileName) ? "" : dialog.InitialFileName)));
}
public async Task<string> ShowFolderDialogAsync(OpenFolderDialog dialog, IWindowImpl parent)
{
await EnsureInitialized();
return await await RunOnGlibThread(async () =>
{
var res = await ShowDialog(dialog.Title, parent,
GtkFileChooserAction.SelectFolder, false, dialog.InitialDirectory);
return res?.FirstOrDefault();
});
}
async Task EnsureInitialized()
{
if (_initialized == null) _initialized = StartGtk();
if (!(await _initialized))
throw new Exception("Unable to initialize GTK on separate thread");
}
void UpdateParent(IntPtr chooser, IWindowImpl parentWindow)
{
var xid = parentWindow.Handle.Handle;
gtk_widget_realize(chooser);
var window = gtk_widget_get_window(chooser);
var parent = GetForeignWindow(xid);
if (window != IntPtr.Zero && parent != IntPtr.Zero)
gdk_window_set_transient_for(window, parent);
}
}
}

4
src/Avalonia.X11/X11Platform.cs

@ -2,7 +2,6 @@
using System.Collections.Generic;
using Avalonia.Controls;
using Avalonia.Controls.Platform;
using Avalonia.Gtk3;
using Avalonia.Input;
using Avalonia.Input.Platform;
using Avalonia.OpenGL;
@ -10,6 +9,7 @@ using Avalonia.Platform;
using Avalonia.Rendering;
using Avalonia.X11;
using Avalonia.X11.Glx;
using Avalonia.X11.NativeDialogs;
using static Avalonia.X11.XLib;
namespace Avalonia.X11
{
@ -45,7 +45,7 @@ namespace Avalonia.X11
.Bind<IClipboard>().ToConstant(new X11Clipboard(this))
.Bind<IPlatformSettings>().ToConstant(new PlatformSettingsStub())
.Bind<IPlatformIconLoader>().ToConstant(new X11IconLoader(Info))
.Bind<ISystemDialogImpl>().ToConstant(new Gtk3ForeignX11SystemDialog());
.Bind<ISystemDialogImpl>().ToConstant(new GtkSystemDialog());
X11Screens = Avalonia.X11.X11Screens.Init(this);
Screens = new X11Screens(X11Screens);

5
src/Avalonia.X11/X11Window.cs

@ -54,8 +54,7 @@ namespace Avalonia.X11
_x11 = platform.Info;
_mouse = platform.MouseDevice;
_keyboard = platform.KeyboardDevice;
_xic = XCreateIC(_x11.Xim, XNames.XNInputStyle, XIMProperties.XIMPreeditNothing | XIMProperties.XIMStatusNothing,
XNames.XNClientWindow, _handle, IntPtr.Zero);
var glfeature = AvaloniaLocator.Current.GetService<IWindowingPlatformGlFeature>();
XSetWindowAttributes attr = new XSetWindowAttributes();
var valueMask = default(SetWindowValuemask);
@ -146,6 +145,8 @@ namespace Avalonia.X11
Surfaces = surfaces.ToArray();
UpdateMotifHints();
_xic = XCreateIC(_x11.Xim, XNames.XNInputStyle, XIMProperties.XIMPreeditNothing | XIMProperties.XIMStatusNothing,
XNames.XNClientWindow, _handle, IntPtr.Zero);
XFlush(_x11.Display);
}

16
src/Markup/Avalonia.Markup.Xaml/AvaloniaXamlLoader.cs

@ -13,6 +13,8 @@ using System.Reflection;
using System.Runtime.Serialization;
using System.Runtime.Serialization.Json;
using System.Text;
using System.Xml.Linq;
using System.Linq;
namespace Avalonia.Markup.Xaml
{
@ -240,15 +242,21 @@ namespace Avalonia.Markup.Xaml
{
using (var xamlInfoStream = assetLocator.Open(xamlInfoUri))
{
var xamlInfo = (AvaloniaResourceXamlInfo)s_xamlInfoSerializer.ReadObject(xamlInfoStream);
if (xamlInfo.ClassToResourcePathIndex.TryGetValue(typeName, out var rv) == true)
var assetDoc = XDocument.Load(xamlInfoStream);
XNamespace assetNs = assetDoc.Root.Attribute("xmlns").Value;
XNamespace arrayNs = "http://schemas.microsoft.com/2003/10/Serialization/Arrays";
Dictionary<string,string> xamlInfo =
assetDoc.Root.Element(assetNs + "ClassToResourcePathIndex").Elements(arrayNs + "KeyValueOfstringstring")
.ToDictionary(entry =>entry.Element(arrayNs + "Key").Value,
entry => entry.Element(arrayNs + "Value").Value);
if (xamlInfo.TryGetValue(typeName, out var rv) == true)
{
yield return new Uri($"avares://{asm}{rv}");
yield break;
}
}
}
}
yield return new Uri("resm:" + typeName + ".xaml?assembly=" + asm);
yield return new Uri("resm:" + typeName + ".paml?assembly=" + asm);

2
src/Markup/Avalonia.Markup.Xaml/PortableXaml/AvaloniaXamlSchemaContext.cs

@ -33,7 +33,7 @@ namespace Avalonia.Markup.Xaml.PortableXaml
private IRuntimeTypeProvider _avaloniaTypeProvider;
protected internal override XamlType GetXamlType(string xamlNamespace, string name, params XamlType[] typeArguments)
protected override XamlType GetXamlType(string xamlNamespace, string name, params XamlType[] typeArguments)
{
XamlType type = null;
try

2
src/Markup/Avalonia.Markup.Xaml/PortableXaml/portable.xaml.github

@ -1 +1 @@
Subproject commit 8abbe09592668efb573ac4d5548ba2d7e464ba78
Subproject commit ab5526173722b8988bc5ca3c03c8752ce89c0975

51
src/Markup/Avalonia.Markup/Markup/Parsers/SelectorGrammar.cs

@ -3,6 +3,7 @@
using System;
using System.Collections.Generic;
using System.Linq;
using Avalonia.Data.Core;
using Avalonia.Utilities;
@ -32,6 +33,11 @@ namespace Avalonia.Markup.Parsers
public static IEnumerable<ISyntax> Parse(string s)
{
var r = new CharacterReader(s.AsSpan());
return Parse(ref r, null);
}
private static IEnumerable<ISyntax> Parse(ref CharacterReader r, char? end)
{
var state = State.Start;
var selector = new List<ISyntax>();
while (!r.End && state != State.End)
@ -43,7 +49,7 @@ namespace Avalonia.Markup.Parsers
state = ParseStart(ref r);
break;
case State.Middle:
state = ParseMiddle(ref r);
state = ParseMiddle(ref r, end);
break;
case State.CanHaveType:
state = ParseCanHaveType(ref r);
@ -107,7 +113,7 @@ namespace Avalonia.Markup.Parsers
return State.TypeName;
}
private static State ParseMiddle(ref CharacterReader r)
private static State ParseMiddle(ref CharacterReader r, char? end)
{
if (r.TakeIf(':'))
{
@ -129,6 +135,10 @@ namespace Avalonia.Markup.Parsers
{
return State.Name;
}
else if (end.HasValue && !r.End && r.Peek == end.Value)
{
return State.End;
}
return State.TypeName;
}
@ -151,16 +161,23 @@ namespace Avalonia.Markup.Parsers
}
const string IsKeyword = "is";
const string NotKeyword = "not";
if (identifier.SequenceEqual(IsKeyword.AsSpan()) && r.TakeIf('('))
{
var syntax = ParseType(ref r, new IsSyntax());
if (r.End || !r.TakeIf(')'))
{
throw new ExpressionParseException(r.Position, $"Expected ')', got {r.Peek}");
}
Expect(ref r, ')');
return (State.CanHaveType, syntax);
}
if (identifier.SequenceEqual(NotKeyword.AsSpan()) && r.TakeIf('('))
{
var argument = Parse(ref r, ')');
Expect(ref r, ')');
var syntax = new NotSyntax { Argument = argument };
return (State.Middle, syntax);
}
else
{
return (
@ -282,6 +299,18 @@ namespace Avalonia.Markup.Parsers
return syntax;
}
private static void Expect(ref CharacterReader r, char c)
{
if (r.End)
{
throw new ExpressionParseException(r.Position, $"Expected '{c}', got end of selector.");
}
else if (!r.TakeIf(')'))
{
throw new ExpressionParseException(r.Position, $"Expected '{c}', got '{r.Peek}'.");
}
}
public interface ISyntax
{
}
@ -376,5 +405,15 @@ namespace Avalonia.Markup.Parsers
return obj is TemplateSyntax;
}
}
public class NotSyntax : ISyntax
{
public IEnumerable<ISyntax> Argument { get; set; }
public override bool Equals(object obj)
{
return (obj is NotSyntax not) && Argument.SequenceEqual(not.Argument);
}
}
}
}

13
src/Markup/Avalonia.Markup/Markup/Parsers/SelectorParser.cs

@ -2,6 +2,7 @@
// Licensed under the MIT license. See licence.md file in the project root for full license information.
using System;
using System.Collections.Generic;
using System.Globalization;
using Avalonia.Styling;
using Avalonia.Utilities;
@ -25,7 +26,7 @@ namespace Avalonia.Markup.Parsers
/// </param>
public SelectorParser(Func<string, string, Type> typeResolver)
{
this._typeResolver = typeResolver;
_typeResolver = typeResolver;
}
/// <summary>
@ -36,6 +37,11 @@ namespace Avalonia.Markup.Parsers
public Selector Parse(string s)
{
var syntax = SelectorGrammar.Parse(s);
return Create(syntax);
}
private Selector Create(IEnumerable<SelectorGrammar.ISyntax> syntax)
{
var result = default(Selector);
foreach (var i in syntax)
@ -97,6 +103,11 @@ namespace Avalonia.Markup.Parsers
case SelectorGrammar.TemplateSyntax template:
result = result.Template();
break;
case SelectorGrammar.NotSyntax not:
result = result.Not(x => Create(not.Argument));
break;
default:
throw new NotSupportedException($"Unsupported selector grammar '{i.GetType()}'.");
}
}

12
tests/Avalonia.Base.UnitTests/AvaloniaPropertyRegistryTests.cs

@ -19,6 +19,18 @@ namespace Avalonia.Base.UnitTests
p = AttachedOwner.AttachedProperty;
}
[Fact]
public void Registered_Properties_Count_Reflects_Newly_Added_Attached_Property()
{
var registry = new AvaloniaPropertyRegistry();
var metadata = new StyledPropertyMetadata<int>();
var property = new AttachedProperty<int>("test", typeof(object), metadata, true);
registry.Register(typeof(object), property);
registry.RegisterAttached(typeof(AvaloniaPropertyRegistryTests), property);
Assert.Equal(1, registry.Properties.Count);
}
[Fact]
public void GetRegistered_Returns_Registered_Properties()
{

20
tests/Avalonia.Base.UnitTests/Data/Core/ExpressionObserverTests_Observable.cs

@ -150,6 +150,26 @@ namespace Avalonia.Base.UnitTests.Data.Core
}
}
[Fact]
public void Should_Work_With_Value_Type()
{
using (var sync = UnitTestSynchronizationContext.Begin())
{
var source = new BehaviorSubject<int>(1);
var data = new { Foo = source };
var target = ExpressionObserver.Create(data, o => o.Foo.StreamBinding());
var result = new List<int>();
var sub = target.Subscribe(x => result.Add((int)x));
source.OnNext(42);
sync.ExecutePostedCallbacks();
Assert.Equal(new[] { 1, 42 }, result);
GC.KeepAlive(data);
}
}
private class Class1 : NotifyingBase
{
public Subject<Class2> Next { get; } = new Subject<Class2>();

16
tests/Avalonia.Controls.UnitTests/WindowBaseTests.cs

@ -199,6 +199,22 @@ namespace Avalonia.Controls.UnitTests
}
}
[Fact]
public void Showing_Should_Raise_Opened()
{
using (UnitTestApplication.Start(TestServices.StyledWindow))
{
var target = new TestWindowBase();
var raised = false;
target.Opened += (s, e) => raised = true;
target.Show();
Assert.True(raised);
}
}
[Fact]
public void Hiding_Should_Stop_Renderer()
{

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

@ -228,18 +228,35 @@ namespace Avalonia.Controls.UnitTests
[Fact]
public void ShowDialog_Should_Start_Renderer()
{
using (UnitTestApplication.Start(TestServices.StyledWindow))
{
var parent = Mock.Of<IWindowImpl>();
var renderer = new Mock<IRenderer>();
var target = new Window(CreateImpl(renderer));
target.Show();
target.ShowDialog<object>(parent);
renderer.Verify(x => x.Start(), Times.Once);
}
}
[Fact]
public void ShowDialog_Should_Raise_Opened()
{
using (UnitTestApplication.Start(TestServices.StyledWindow))
{
var parent = Mock.Of<IWindowImpl>();
var target = new Window();
var raised = false;
target.Opened += (s, e) => raised = true;
target.ShowDialog<object>(parent);
Assert.True(raised);
}
}
[Fact]
public void Hiding_Should_Stop_Renderer()
{

62
tests/Avalonia.DesignerSupport.Tests/Helpers.cs

@ -0,0 +1,62 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
namespace Avalonia.DesignerSupport.Tests
{
static class Helpers
{
public static void StructDiff(object parsed, object expected) => StructDiff(parsed, expected, "{root}");
static void StructDiff(object parsed, object expected, string path)
{
if (parsed == null && expected == null)
return;
if ((parsed == null && expected != null) || (parsed != null && expected == null))
throw new Exception(
$"{path}: Null mismatch: {(parsed == null ? "null" : "not-null")} {(expected == null ? "null" : "not-null")}");
if (parsed.GetType() != expected.GetType())
throw new Exception($"{path}: Type mismatch: {parsed.GetType()} {expected.GetType()}");
if (parsed is string || parsed.GetType().IsPrimitive)
{
if (!parsed.Equals(expected))
throw new Exception($"{path}: Not equal {parsed} {expected}");
}
else if (parsed is IDictionary dic)
{
var dic2 = (IDictionary) expected;
if (dic.Count != dic2.Count)
throw new Exception($"{path}: Dictionary count mismatch: {dic.Count} {dic2.Count}");
foreach (var k in dic.Keys.Cast<object>().OrderBy(o => o.ToString()))
{
var v1 = dic[k];
var v2 = dic2[k];
StructDiff(v1, v2, path + "['" + k + "']");
}
}
else if (parsed is IList col)
{
var col2 = (IList) expected;
if (col.Count != col2.Count)
throw new Exception($"{path}: Collection count mismatch: {col.Count} {col2.Count}");
for (var c = 0; c < col.Count; c++)
StructDiff(col[c], col2[c], path + "[" + c + "]");
}
else
{
foreach (var prop in parsed.GetType().GetProperties()
.Where(p => p.GetMethod != null && p.GetMethod.IsPublic))
{
StructDiff(prop.GetValue(parsed), prop.GetValue(expected), path + "." + prop.Name);
}
}
}
}
}

171
tests/Avalonia.DesignerSupport.Tests/RemoteProtocolTests.cs

@ -0,0 +1,171 @@
using System;
using System.Collections;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Net.Sockets;
using System.Reflection;
using System.Threading;
using System.Threading.Tasks;
using Avalonia.Remote.Protocol;
using Avalonia.Remote.Protocol.Viewport;
using Xunit;
namespace Avalonia.DesignerSupport.Tests
{
public class RemoteProtocolTests : IDisposable
{
private readonly List<IDisposable> _disposables = new List<IDisposable>();
private IAvaloniaRemoteTransportConnection _server;
private IAvaloniaRemoteTransportConnection _client;
private BlockingCollection<object> _serverMessages = new BlockingCollection<object>();
private BlockingCollection<object> _clientMessages = new BlockingCollection<object>();
private SynchronizationContext _originalContext;
class DisabledSyncContext : SynchronizationContext
{
public override void Post(SendOrPostCallback d, object state)
{
throw new InvalidCastException("Not allowed");
}
public override void Send(SendOrPostCallback d, object state)
{
throw new InvalidCastException("Not allowed");
}
}
void Init(IMessageTypeResolver clientResolver = null, IMessageTypeResolver serverResolver = null)
{
_originalContext = SynchronizationContext.Current;
SynchronizationContext.SetSynchronizationContext(new DisabledSyncContext());
var clientTransport = new BsonTcpTransport(clientResolver ?? new DefaultMessageTypeResolver());
var serverTransport = new BsonTcpTransport(serverResolver ?? new DefaultMessageTypeResolver());
var tcpListener = new TcpListener(IPAddress.Loopback, 0);
tcpListener.Start();
var port = ((IPEndPoint)tcpListener.LocalEndpoint).Port;
tcpListener.Stop();
var tcs = new TaskCompletionSource<int>();
serverTransport.Listen(IPAddress.Loopback, port, connected =>
{
_server = connected;
tcs.SetResult(0);
});
_client = clientTransport.Connect(IPAddress.Loopback, port).Result;
_disposables.Add(_client);
_client.OnMessage += (_, m) => _clientMessages.Add(m);
tcs.Task.Wait();
_disposables.Add(_server);
_server.OnMessage += (_, m) => _serverMessages.Add(m);
}
object TakeServer()
{
var src = new CancellationTokenSource(200);
try
{
return _serverMessages.Take(src.Token);
}
finally
{
src.Dispose();
}
}
[Fact]
void EntitiesAreProperlySerializedAndDeserialized()
{
Init();
var rnd = new Random();
_server.OnMessage += (_, message) => { };
object GetRandomValue(Type t, string pathInfo)
{
if (t.IsArray)
{
var arr = Array.CreateInstance(t.GetElementType(), 1);
((IList)arr)[0] = GetRandomValue(t.GetElementType(), pathInfo);
return arr;
}
if (t == typeof(bool))
return true;
if (t == typeof(int) || t == typeof(long))
return rnd.Next();
if (t == typeof(byte))
return (byte)rnd.Next(255);
if (t == typeof(double))
return rnd.NextDouble();
if (t.IsEnum)
return ((IList)Enum.GetValues(t)).Cast<object>().Last();
if (t == typeof(string))
return Guid.NewGuid().ToString();
if (t == typeof(Guid))
return Guid.NewGuid();
throw new Exception($"Doesn't know how to fabricate a random value for {t}, path {pathInfo}");
}
foreach (var t in typeof(MeasureViewportMessage).Assembly.GetTypes().Where(t =>
t.GetCustomAttribute(typeof(AvaloniaRemoteMessageGuidAttribute)) != null))
{
var o = Activator.CreateInstance(t);
foreach (var p in t.GetProperties())
p.SetValue(o, GetRandomValue(p.PropertyType, $"{t.FullName}.{p.Name}"));
_client.Send(o).Wait(200);
var received = TakeServer();
Helpers.StructDiff(received, o);
}
}
[Fact]
void RemoteProtocolShouldBeBackwardsCompatible()
{
Init(new DefaultMessageTypeResolver(typeof(ExtendedMeasureViewportMessage).Assembly));
_client.Send(new ExtendedMeasureViewportMessage()
{
Width = 100, Height = 200, SomeNewProperty = 300,
SomeArrayProperty = new[]{1,2,3},
SubObjectProperty = new ExtendedMeasureViewportMessage.SubObject()
{
Foo = 543
}
});
var received = (MeasureViewportMessage)TakeServer();
Assert.Equal(100, received.Width);
Assert.Equal(200, received.Height);
}
public void Dispose()
{
_disposables.ForEach(d => d.Dispose());
SynchronizationContext.SetSynchronizationContext(_originalContext);
}
}
[AvaloniaRemoteMessageGuid("6E3C5310-E2B1-4C3D-8688-01183AA48C5B")]
public class ExtendedMeasureViewportMessage
{
public double Width { get; set; }
public int SomeNewProperty { get; set; }
public int[] SomeArrayProperty { get; set; }
public class SubObject
{
public int Foo { get; set; }
}
public SubObject SubObjectProperty { get; set; }
public double Height { get; set; }
}
}

73
tests/Avalonia.Markup.UnitTests/Parsers/SelectorGrammarTests.cs

@ -200,6 +200,67 @@ namespace Avalonia.Markup.UnitTests.Parsers
result);
}
[Fact]
public void Not_OfType()
{
var result = SelectorGrammar.Parse(":not(Button)");
Assert.Equal(
new SelectorGrammar.ISyntax[]
{
new SelectorGrammar.NotSyntax
{
Argument = new SelectorGrammar.ISyntax[]
{
new SelectorGrammar.OfTypeSyntax { TypeName = "Button" },
},
}
},
result);
}
[Fact]
public void OfType_Not_Class()
{
var result = SelectorGrammar.Parse("Button:not(.foo)");
Assert.Equal(
new SelectorGrammar.ISyntax[]
{
new SelectorGrammar.OfTypeSyntax { TypeName = "Button" },
new SelectorGrammar.NotSyntax
{
Argument = new SelectorGrammar.ISyntax[]
{
new SelectorGrammar.ClassSyntax { Class = "foo" },
},
}
},
result);
}
[Fact]
public void Is_Descendent_Not_OfType_Class()
{
var result = SelectorGrammar.Parse(":is(Control) :not(Button.foo)");
Assert.Equal(
new SelectorGrammar.ISyntax[]
{
new SelectorGrammar.IsSyntax { TypeName = "Control" },
new SelectorGrammar.DescendantSyntax { },
new SelectorGrammar.NotSyntax
{
Argument = new SelectorGrammar.ISyntax[]
{
new SelectorGrammar.OfTypeSyntax { TypeName = "Button" },
new SelectorGrammar.ClassSyntax { Class = "foo" },
},
}
},
result);
}
[Fact]
public void Namespace_Alone_Fails()
{
@ -223,5 +284,17 @@ namespace Avalonia.Markup.UnitTests.Parsers
{
Assert.Throws<ExpressionParseException>(() => SelectorGrammar.Parse(".%foo"));
}
[Fact]
public void Not_Without_Argument_Fails()
{
Assert.Throws<ExpressionParseException>(() => SelectorGrammar.Parse(":not()"));
}
[Fact]
public void Not_Without_Closing_Parenthesis_Fails()
{
Assert.Throws<ExpressionParseException>(() => SelectorGrammar.Parse(":not(Button"));
}
}
}

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

@ -198,5 +198,33 @@ namespace Avalonia.Markup.Xaml.UnitTests.Xaml
ex.InnerException.Message);
}
}
[Fact]
public void Style_Can_Use_Not_Selector()
{
using (UnitTestApplication.Start(TestServices.StyledWindow))
{
var xaml = @"
<Window xmlns='https://github.com/avaloniaui'
xmlns:x='http://schemas.microsoft.com/winfx/2006/xaml'>
<Window.Styles>
<Style Selector='Border:not(.foo)'>
<Setter Property='Background' Value='Red'/>
</Style>
</Window.Styles>
<StackPanel>
<Border Name='foo' Classes='foo bar'/>
<Border Name='notFoo' Classes='bar'/>
</StackPanel>
</Window>";
var loader = new AvaloniaXamlLoader();
var window = (Window)loader.Load(xaml);
var foo = window.FindControl<Border>("foo");
var notFoo = window.FindControl<Border>("notFoo");
Assert.Null(foo.Background);
Assert.Equal(Colors.Red, ((ISolidColorBrush)notFoo.Background).Color);
}
}
}
}

35
tests/Avalonia.ReactiveUI.UnitTests/AvaloniaActivationForViewFetcherTest.cs

@ -10,6 +10,7 @@ using ReactiveUI;
using DynamicData;
using Xunit;
using Splat;
using Avalonia.Markup.Xaml;
namespace Avalonia
{
@ -70,12 +71,40 @@ namespace Avalonia
public class ActivatableWindow : ReactiveWindow<ActivatableViewModel>
{
public ActivatableWindow() => this.WhenActivated(disposables => { });
public ActivatableWindow()
{
InitializeComponent();
Assert.IsType<Border>(Content);
this.WhenActivated(disposables => { });
}
private void InitializeComponent()
{
var loader = new AvaloniaXamlLoader();
loader.Load(@"
<Window xmlns='https://github.com/avaloniaui'>
<Border/>
</Window>", null, this);
}
}
public class ActivatableUserControl : ReactiveUserControl<ActivatableViewModel>
{
public ActivatableUserControl() => this.WhenActivated(disposables => { });
public ActivatableUserControl()
{
InitializeComponent();
Assert.IsType<Border>(Content);
this.WhenActivated(disposables => { });
}
private void InitializeComponent()
{
var loader = new AvaloniaXamlLoader();
loader.Load(@"
<UserControl xmlns='https://github.com/avaloniaui'>
<Border/>
</UserControl>", null, this);
}
}
public AvaloniaActivationForViewFetcherTest()
@ -183,4 +212,4 @@ namespace Avalonia
Assert.False(viewModel.IsActivated);
}
}
}
}

114
tests/Avalonia.Styling.UnitTests/SelectorTests_Not.cs

@ -0,0 +1,114 @@
// 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.Reactive.Linq;
using System.Threading.Tasks;
using Avalonia.Controls;
using Xunit;
namespace Avalonia.Styling.UnitTests
{
public class SelectorTests_Not
{
[Fact]
public void Not_Selector_Should_Have_Correct_String_Representation()
{
var target = default(Selector).Not(x => x.Class("foo"));
Assert.Equal(":not(.foo)", target.ToString());
}
[Fact]
public void Not_OfType_Matches_Control_Of_Incorrect_Type()
{
var control = new Control1();
var target = default(Selector).Not(x => x.OfType<Control1>());
Assert.Equal(SelectorMatchResult.NeverThisType, target.Match(control).Result);
}
[Fact]
public void Not_OfType_Doesnt_Match_Control_Of_Correct_Type()
{
var control = new Control2();
var target = default(Selector).Not(x => x.OfType<Control1>());
Assert.Equal(SelectorMatchResult.AlwaysThisType, target.Match(control).Result);
}
[Fact]
public async Task Not_Class_Doesnt_Match_Control_With_Class()
{
var control = new Control1
{
Classes = new Classes { "foo" },
};
var target = default(Selector).Not(x => x.Class("foo"));
var match = target.Match(control);
Assert.Equal(SelectorMatchResult.Sometimes, match.Result);
Assert.False(await match.Activator.Take(1));
}
[Fact]
public async Task Not_Class_Matches_Control_Without_Class()
{
var control = new Control1
{
Classes = new Classes { "bar" },
};
var target = default(Selector).Not(x => x.Class("foo"));
var match = target.Match(control);
Assert.Equal(SelectorMatchResult.Sometimes, match.Result);
Assert.True(await match.Activator.Take(1));
}
[Fact]
public async Task OfType_Not_Class_Matches_Control_Without_Class()
{
var control = new Control1
{
Classes = new Classes { "bar" },
};
var target = default(Selector).OfType<Control1>().Not(x => x.Class("foo"));
var match = target.Match(control);
Assert.Equal(SelectorMatchResult.Sometimes, match.Result);
Assert.True(await match.Activator.Take(1));
}
[Fact]
public void OfType_Not_Class_Doesnt_Match_Control_Of_Wrong_Type()
{
var control = new Control2
{
Classes = new Classes { "foo" },
};
var target = default(Selector).OfType<Control1>().Not(x => x.Class("foo"));
var match = target.Match(control);
Assert.Equal(SelectorMatchResult.NeverThisType, match.Result);
}
[Fact]
public void Returns_Correct_TargetType()
{
var target = default(Selector).OfType<Control1>().Not(x => x.Class("foo"));
Assert.Equal(typeof(Control1), target.TargetType);
}
public class Control1 : TestControlBase
{
}
public class Control2 : TestControlBase
{
}
}
}
Loading…
Cancel
Save