Browse Source

pull/1235/head
Jeremy Koritzinsky 8 years ago
parent
commit
f17b104f4d
  1. 5
      build/Splat.props
  2. 4
      packages.cake
  3. 1
      samples/BindingTest/BindingTest.csproj
  4. 1
      samples/RenderTest/RenderTest.csproj
  5. 1
      samples/VirtualizationTest/VirtualizationTest.csproj
  6. 1
      samples/interop/Direct3DInteropSample/Direct3DInteropSample.csproj
  7. 1
      src/Android/Avalonia.AndroidTestApplication/Avalonia.AndroidTestApplication.csproj
  8. 6
      src/Avalonia.Base/Threading/Dispatcher.cs
  9. 4
      src/Avalonia.Controls/Control.cs
  10. 46
      src/Avalonia.Controls/Shapes/Shape.cs
  11. 1
      src/Avalonia.Diagnostics/Avalonia.Diagnostics.csproj
  12. 53
      src/Avalonia.Diagnostics/LogManager.cs
  13. 1
      src/Avalonia.ReactiveUI/Avalonia.ReactiveUI.csproj
  14. 3
      src/Markup/Avalonia.Markup.Xaml/Avalonia.Markup.Xaml.csproj
  15. 4
      src/Markup/Avalonia.Markup.Xaml/Data/Binding.cs
  16. 69
      src/Markup/Avalonia.Markup.Xaml/Data/StyleResourceBinding.cs
  17. 27
      src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/BindingExtension.cs
  18. 31
      src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/StyleResourceExtension.cs
  19. 12
      src/Markup/Avalonia.Markup.Xaml/PortableXaml/AvaloniaXamlType.cs
  20. 63
      src/Markup/Avalonia.Markup.Xaml/PortableXaml/XamlBinding.cs
  21. 88
      src/OSX/Avalonia.MonoMac/EmulatedFramebuffer.cs
  22. 46
      src/OSX/Avalonia.MonoMac/MonoMacPlatform.cs
  23. 32
      src/OSX/Avalonia.MonoMac/PlatformThreadingInterface.cs
  24. 33
      src/OSX/Avalonia.MonoMac/RenderLoop.cs
  25. 89
      src/OSX/Avalonia.MonoMac/TopLevelImpl.cs
  26. 44
      tests/Avalonia.Controls.UnitTests/Primitives/PopupTests.cs
  27. 113
      tests/Avalonia.Layout.UnitTests/ShapeLayoutTests.cs
  28. 1
      tests/Avalonia.Markup.Xaml.UnitTests/Avalonia.Markup.Xaml.UnitTests.csproj
  29. 19
      tests/Avalonia.Markup.Xaml.UnitTests/Xaml/BasicTests.cs
  30. 165
      tests/Avalonia.Markup.Xaml.UnitTests/Xaml/StyleTests.cs
  31. 23
      tests/Avalonia.UnitTests/TestWithServicesBase.cs

5
build/Splat.props

@ -1,5 +0,0 @@
<Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<ItemGroup>
<PackageReference Include="Splat" Version="2.0.0" />
</ItemGroup>
</Project>

4
packages.cake

@ -107,7 +107,6 @@ public class Packages
context.Information("Setting NuGet package dependencies versions:");
var SerilogVersion = packageVersions["Serilog"].FirstOrDefault().Item1;
var SplatVersion = packageVersions["Splat"].FirstOrDefault().Item1;
var SpracheVersion = packageVersions["Sprache"].FirstOrDefault().Item1;
var SystemReactiveVersion = packageVersions["System.Reactive"].FirstOrDefault().Item1;
var ReactiveUIVersion = packageVersions["reactiveui"].FirstOrDefault().Item1;
@ -121,7 +120,6 @@ public class Packages
var SharpDXDXGIVersion = packageVersions["SharpDX.DXGI"].FirstOrDefault().Item1;
context.Information("Package: Serilog, version: {0}", SerilogVersion);
context.Information("Package: Splat, version: {0}", SplatVersion);
context.Information("Package: Sprache, version: {0}", SpracheVersion);
context.Information("Package: System.Reactive, version: {0}", SystemReactiveVersion);
context.Information("Package: reactiveui, version: {0}", ReactiveUIVersion);
@ -245,7 +243,6 @@ public class Packages
Dependencies = new DependencyBuilder(this)
{
new NuSpecDependency() { Id = "Serilog", Version = SerilogVersion },
new NuSpecDependency() { Id = "Splat", Version = SplatVersion },
new NuSpecDependency() { Id = "Sprache", Version = SpracheVersion },
new NuSpecDependency() { Id = "System.Reactive", Version = SystemReactiveVersion },
new NuSpecDependency() { Id = "Avalonia.Remote.Protocol", Version = parameters.Version },
@ -253,7 +250,6 @@ public class Packages
new NuSpecDependency() { Id = "System.Threading.ThreadPool", TargetFramework = "netcoreapp2.0", Version = "4.3.0" },
new NuSpecDependency() { Id = "Microsoft.Extensions.DependencyModel", TargetFramework = "netcoreapp2.0", Version = "1.1.0" },
new NuSpecDependency() { Id = "NETStandard.Library", TargetFramework = "netcoreapp2.0", Version = "1.6.0" },
new NuSpecDependency() { Id = "Splat", TargetFramework = "netcoreapp2.0", Version = SplatVersion },
new NuSpecDependency() { Id = "Serilog", TargetFramework = "netcoreapp2.0", Version = SerilogVersion },
new NuSpecDependency() { Id = "Sprache", TargetFramework = "netcoreapp2.0", Version = SpracheVersion },
new NuSpecDependency() { Id = "System.Reactive", TargetFramework = "netcoreapp2.0", Version = SystemReactiveVersion },

1
samples/BindingTest/BindingTest.csproj

@ -152,7 +152,6 @@
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
<Import Project="..\..\build\Serilog.props" />
<Import Project="..\..\build\Serilog.Sinks.Trace.props" />
<Import Project="..\..\build\Splat.props" />
<Import Project="..\..\build\Rx.props" />
<Import Project="..\..\build\ReactiveUI.props" />
</Project>

1
samples/RenderTest/RenderTest.csproj

@ -181,7 +181,6 @@
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
<Import Project="..\..\build\Serilog.props" />
<Import Project="..\..\build\Serilog.Sinks.Trace.props" />
<Import Project="..\..\build\Splat.props" />
<Import Project="..\..\build\Rx.props" />
<Import Project="..\..\build\ReactiveUI.props" />
</Project>

1
samples/VirtualizationTest/VirtualizationTest.csproj

@ -148,7 +148,6 @@
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
<Import Project="..\..\build\Serilog.props" />
<Import Project="..\..\build\Serilog.Sinks.Trace.props" />
<Import Project="..\..\build\Splat.props" />
<Import Project="..\..\build\Rx.props" />
<Import Project="..\..\build\ReactiveUI.props" />
</Project>

1
samples/interop/Direct3DInteropSample/Direct3DInteropSample.csproj

@ -29,6 +29,5 @@
</ItemGroup>
<Import Project="..\..\..\build\Serilog.props" />
<Import Project="..\..\..\build\Serilog.Sinks.Trace.props" />
<Import Project="..\..\..\build\Splat.props" />
<Import Project="..\..\..\build\Rx.props" />
</Project>

1
src/Android/Avalonia.AndroidTestApplication/Avalonia.AndroidTestApplication.csproj

@ -154,7 +154,6 @@
</ItemGroup>
<Import Project="$(MSBuildExtensionsPath)\Xamarin\Android\Xamarin.Android.CSharp.targets" />
<Import Project="..\..\..\build\Serilog.props" />
<Import Project="..\..\..\build\Splat.props" />
<Import Project="..\..\..\build\Sprache.props" />
<Import Project="..\..\..\build\Rx.props" />
</Project>

6
src/Avalonia.Base/Threading/Dispatcher.cs

@ -72,6 +72,12 @@ namespace Avalonia.Threading
_jobRunner?.RunJobs(null);
}
/// <summary>
/// Use this method to ensure that more prioritized tasks are executed
/// </summary>
/// <param name="minimumPriority"></param>
public void RunJobs(DispatcherPriority minimumPriority) => _jobRunner.RunJobs(minimumPriority);
/// <inheritdoc/>
public Task InvokeTaskAsync(Action action, DispatcherPriority priority = DispatcherPriority.Normal)
{

4
src/Avalonia.Controls/Control.cs

@ -774,7 +774,9 @@ namespace Avalonia.Controls
foreach (var child in control.LogicalChildren)
{
if (child is Control c && !c.IsSet(DataContextProperty))
if (child is Control c &&
c.InheritanceParent == control &&
!c.IsSet(DataContextProperty))
{
DataContextNotifying(c, notifying);
}

46
src/Avalonia.Controls/Shapes/Shape.cs

@ -29,6 +29,8 @@ namespace Avalonia.Controls.Shapes
private Matrix _transform = Matrix.Identity;
private Geometry _definingGeometry;
private Geometry _renderedGeometry;
bool _calculateTransformOnArrange = false;
static Shape()
{
@ -150,13 +152,53 @@ namespace Avalonia.Controls.Shapes
this._definingGeometry = null;
InvalidateMeasure();
}
protected override Size MeasureOverride(Size availableSize)
{
bool deferCalculateTransform;
switch (Stretch)
{
case Stretch.Fill:
case Stretch.UniformToFill:
deferCalculateTransform = double.IsInfinity(availableSize.Width) || double.IsInfinity(availableSize.Height);
break;
case Stretch.Uniform:
deferCalculateTransform = double.IsInfinity(availableSize.Width) && double.IsInfinity(availableSize.Height);
break;
case Stretch.None:
default:
deferCalculateTransform = false;
break;
}
if (deferCalculateTransform)
{
_calculateTransformOnArrange = true;
return DefiningGeometry.Bounds.Size;
}
else
{
_calculateTransformOnArrange = false;
return CalculateShapeSizeAndSetTransform(availableSize);
}
}
protected override Size ArrangeOverride(Size finalSize)
{
if(_calculateTransformOnArrange)
{
_calculateTransformOnArrange = false;
CalculateShapeSizeAndSetTransform(finalSize);
}
return finalSize;
}
private Size CalculateShapeSizeAndSetTransform(Size availableSize)
{
// This should probably use GetRenderBounds(strokeThickness) but then the calculations
// will multiply the stroke thickness as well, which isn't correct.
var (size, transform) = CalculateSizeAndTransform(availableSize, DefiningGeometry.Bounds, Stretch);
if (_transform != transform)
{
_transform = transform;

1
src/Avalonia.Diagnostics/Avalonia.Diagnostics.csproj

@ -50,5 +50,4 @@
</EmbeddedResource>
</ItemGroup>
<Import Project="..\..\build\Rx.props" />
<Import Project="..\..\build\Splat.props" />
</Project>

53
src/Avalonia.Diagnostics/LogManager.cs

@ -1,53 +0,0 @@
// 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 Avalonia.Layout;
using Splat;
namespace Avalonia.Diagnostics
{
public class LogManager : ILogManager
{
private static LogManager s_instance;
public static LogManager Instance => s_instance ?? (s_instance = new LogManager());
public ILogger Logger
{
get;
set;
}
public bool LogPropertyMessages
{
get;
set;
}
public bool LogLayoutMessages
{
get;
set;
}
public static void Enable(ILogger logger)
{
Instance.Logger = logger;
Locator.CurrentMutable.Register(() => Instance, typeof(ILogManager));
}
public IFullLogger GetLogger(Type type)
{
if ((type == typeof(AvaloniaObject) && LogPropertyMessages) ||
(type == typeof(Layoutable) && LogLayoutMessages))
{
return new WrappingFullLogger(Logger, type);
}
else
{
return new WrappingFullLogger(new NullLogger(), type);
}
}
}
}

1
src/Avalonia.ReactiveUI/Avalonia.ReactiveUI.csproj

@ -16,5 +16,4 @@
</ItemGroup>
<Import Project="..\..\build\Rx.props" />
<Import Project="..\..\build\ReactiveUI.props" />
<Import Project="..\..\build\Splat.props" />
</Project>

3
src/Markup/Avalonia.Markup.Xaml/Avalonia.Markup.Xaml.csproj

@ -39,7 +39,6 @@
<Compile Include="MarkupExtensions\StaticResourceExtension.cs" />
<Compile Include="MarkupExtensions\StyleIncludeExtension.cs" />
<Compile Include="PortableXaml\AvaloniaXamlContext.cs" />
<Compile Include="PortableXaml\XamlBinding.cs" />
<Compile Include="PortableXaml\AttributeExtensions.cs" />
<Compile Include="PortableXaml\AvaloniaMemberAttributeProvider.cs" />
<Compile Include="PortableXaml\AvaloniaNameScope.cs" />
@ -73,8 +72,6 @@
<Compile Include="Data\DelayedBinding.cs" />
<Compile Include="Data\MultiBinding.cs" />
<Compile Include="Data\RelativeSource.cs" />
<Compile Include="Data\StyleResourceBinding.cs" />
<Compile Include="MarkupExtensions\StyleResourceExtension.cs" />
<Compile Include="MarkupExtensions\BindingExtension.cs" />
<Compile Include="MarkupExtensions\RelativeSourceExtension.cs" />
<Compile Include="MarkupExtensions\TemplateBindingExtension.cs" />

4
src/Markup/Avalonia.Markup.Xaml/Data/Binding.cs

@ -83,6 +83,8 @@ namespace Avalonia.Markup.Xaml.Data
/// </summary>
public object Source { get; set; }
internal WeakReference DefaultAnchor { get; set; }
/// <inheritdoc/>
public InstancedBinding Initiate(
IAvaloniaObject target,
@ -92,6 +94,8 @@ namespace Avalonia.Markup.Xaml.Data
{
Contract.Requires<ArgumentNullException>(target != null);
anchor = anchor ?? DefaultAnchor?.Target;
var pathInfo = ParsePath(Path);
ValidateState(pathInfo);
enableDataValidation = enableDataValidation && Priority == BindingPriority.LocalValue;

69
src/Markup/Avalonia.Markup.Xaml/Data/StyleResourceBinding.cs

@ -1,69 +0,0 @@
// 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.Disposables;
using System.Reactive.Linq;
using System.Reactive.Subjects;
using Avalonia.Controls;
using Avalonia.Data;
using Avalonia.Styling;
namespace Avalonia.Markup.Xaml.Data
{
public class StyleResourceBinding : IBinding
{
/// <summary>
/// Initializes a new instance of the <see cref="StyleResourceBinding"/> class.
/// </summary>
/// <param name="name">The resource name.</param>
public StyleResourceBinding(string name)
{
Name = name;
}
/// <inheritdoc/>
public BindingMode Mode => BindingMode.OneTime;
/// <summary>
/// Gets the resource name.
/// </summary>
public string Name { get; }
/// <inheritdoc/>
public BindingPriority Priority => BindingPriority.LocalValue;
/// <inheritdoc/>
public InstancedBinding Initiate(
IAvaloniaObject target,
AvaloniaProperty targetProperty,
object anchor = null,
bool enableDataValidation = false)
{
var host = (target as IControl) ?? (anchor as IControl);
var style = anchor as IStyle;
var resource = AvaloniaProperty.UnsetValue;
if (host != null)
{
resource = host.FindResource(Name);
}
else if (style != null)
{
if (!style.TryGetResource(Name, out resource))
{
resource = AvaloniaProperty.UnsetValue;
}
}
if (resource != AvaloniaProperty.UnsetValue)
{
return InstancedBinding.OneTime(resource, Priority);
}
else
{
return null;
}
}
}
}

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

@ -7,8 +7,13 @@ using System;
namespace Avalonia.Markup.Xaml.MarkupExtensions
{
using Avalonia.Controls;
using Avalonia.Styling;
using Portable.Xaml;
using Portable.Xaml.ComponentModel;
using Portable.Xaml.Markup;
using PortableXaml;
using System.ComponentModel;
[MarkupExtensionReturnType(typeof(IBinding))]
public class BindingExtension : MarkupExtension
@ -24,7 +29,7 @@ namespace Avalonia.Markup.Xaml.MarkupExtensions
public override object ProvideValue(IServiceProvider serviceProvider)
{
var b = new Binding
return new Binding
{
Converter = Converter,
ConverterParameter = ConverterParameter,
@ -33,10 +38,26 @@ namespace Avalonia.Markup.Xaml.MarkupExtensions
Mode = Mode,
Path = Path,
Priority = Priority,
RelativeSource = RelativeSource
RelativeSource = RelativeSource,
DefaultAnchor = new WeakReference(GetDefaultAnchor((ITypeDescriptorContext)serviceProvider))
};
}
private static object GetDefaultAnchor(ITypeDescriptorContext context)
{
object anchor = null;
// The target is not a control, so we need to find an anchor that will let us look
// up named controls and style resources. First look for the closest IControl in
// the context.
anchor = context.GetFirstAmbientValue<IControl>();
return XamlBinding.FromMarkupExtensionContext(b, serviceProvider);
// If a control was not found, then try to find the highest-level style as the XAML
// file could be a XAML file containing only styles.
return anchor ??
context.GetService<IRootObjectProvider>()?.RootObject as IStyle ??
context.GetLastOrDefaultAmbientValue<IStyle>();
}
public IValueConverter Converter { get; set; }

31
src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/StyleResourceExtension.cs

@ -1,31 +0,0 @@
// 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.Data;
using Avalonia.Markup.Xaml.Data;
using System;
namespace Avalonia.Markup.Xaml.MarkupExtensions
{
using Portable.Xaml.Markup;
using PortableXaml;
[MarkupExtensionReturnType(typeof(IBinding))]
public class StyleResourceExtension : MarkupExtension
{
public StyleResourceExtension(string name)
{
Name = name;
}
public override object ProvideValue(IServiceProvider serviceProvider)
{
return XamlBinding.FromMarkupExtensionContext(
new StyleResourceBinding(Name),
serviceProvider);
}
[ConstructorArgument("name")]
public string Name { get; set; }
}
}

12
src/Markup/Avalonia.Markup.Xaml/PortableXaml/AvaloniaXamlType.cs

@ -212,11 +212,6 @@ namespace Avalonia.Markup.Xaml.PortableXaml
value);
}
if (value is XamlBinding)
{
value = (value as XamlBinding).Value;
}
if (UpdateListInsteadSet &&
value != null &&
UpdateListInsteadSetValue(instance, value))
@ -317,9 +312,7 @@ namespace Avalonia.Markup.Xaml.PortableXaml
if (!Member.AssignBinding)
ApplyBinding(obj, (IBinding)value);
else
obj.SetValue(Property, value is XamlBinding ?
(value as XamlBinding).Value :
value);
obj.SetValue(Property, value);
}
else
{
@ -348,12 +341,9 @@ namespace Avalonia.Markup.Xaml.PortableXaml
{
var control = obj as IControl;
var property = Property;
var xamlBinding = binding as XamlBinding;
if (control != null && property != Control.DataContextProperty)
DelayedBinding.Add(control, property, binding);
else if (xamlBinding != null)
obj.Bind(property, xamlBinding.Value, xamlBinding.Anchor?.Target);
else
obj.Bind(property, binding);
}

63
src/Markup/Avalonia.Markup.Xaml/PortableXaml/XamlBinding.cs

@ -1,63 +0,0 @@
using Avalonia.Controls;
using Avalonia.Data;
using Avalonia.Styling;
using Portable.Xaml;
using Portable.Xaml.ComponentModel;
using System.ComponentModel;
using Portable.Xaml.Markup;
using System;
namespace Avalonia.Markup.Xaml.PortableXaml
{
internal class XamlBinding : IBinding
{
public static IBinding FromMarkupExtensionContext(
IBinding binding,
IServiceProvider serviceProvider)
{
var context = (ITypeDescriptorContext)serviceProvider;
var pvt = context.GetService<IProvideValueTarget>();
if (pvt.TargetObject is IControl) return binding;
object anchor = GetDefaultAnchor(context);
if (anchor == null) return binding;
return new XamlBinding(binding, anchor);
}
private static object GetDefaultAnchor(ITypeDescriptorContext context)
{
object anchor = null;
// The target is not a control, so we need to find an anchor that will let us look
// up named controls and style resources. First look for the closest IControl in
// the context.
anchor = context.GetFirstAmbientValue<IControl>();
// If a control was not found, then try to find the highest-level style as the XAML
// file could be a XAML file containing only styles.
return anchor ??
context.GetService<IRootObjectProvider>()?.RootObject as IStyle ??
context.GetLastOrDefaultAmbientValue<IStyle>();
}
private XamlBinding(IBinding binding, object anchor)
{
Value = binding;
Anchor = new WeakReference(anchor);
}
public WeakReference Anchor { get; }
public IBinding Value { get; }
public InstancedBinding Initiate(IAvaloniaObject target, AvaloniaProperty targetProperty, object anchor = null, bool enableDataValidation = false)
{
return Value.Initiate(target, targetProperty,
anchor ?? Anchor.Target, enableDataValidation);
}
}
}

88
src/OSX/Avalonia.MonoMac/EmulatedFramebuffer.cs

@ -2,44 +2,80 @@
using Avalonia.Platform;
using MonoMac.AppKit;
using System.Runtime.InteropServices;
using Avalonia.Threading;
using MonoMac.CoreGraphics;
namespace Avalonia.MonoMac
{
class EmulatedFramebuffer : ILockedFramebuffer
{
private readonly TopLevelImpl.TopLevelView _view;
private readonly CGSize _logicalSize;
public EmulatedFramebuffer(NSView view)
private readonly bool _isDeferred;
[DllImport("libc")]
static extern void memset(IntPtr p, int c, IntPtr size);
public EmulatedFramebuffer(TopLevelImpl.TopLevelView view)
{
_logicalSize = view.Frame.Size;
var pixelSize = view.ConvertSizeToBacking(_logicalSize);
_view = view;
_isDeferred = !Dispatcher.UIThread.CheckAccess();
_logicalSize = _view.LogicalSize;
var pixelSize = _view.PixelSize;
Width = (int)pixelSize.Width;
Height = (int)pixelSize.Height;
RowBytes = Width * 4;
Dpi = new Vector(96 * pixelSize.Width / _logicalSize.Width, 96 * pixelSize.Height / _logicalSize.Height);
Format = PixelFormat.Rgba8888;
Address = Marshal.AllocHGlobal(Height * RowBytes);
var size = Height * RowBytes;
Address = Marshal.AllocHGlobal(size);
memset(Address, 0, new IntPtr(size));
}
public void Dispose()
{
if (Address == IntPtr.Zero)
return;
var nfo = (int) CGBitmapFlags.ByteOrder32Big | (int) CGImageAlphaInfo.PremultipliedLast;
using (var colorSpace = CGColorSpace.CreateDeviceRGB())
using (var bContext = new CGBitmapContext(Address, Width, Height, 8, Width * 4,
colorSpace, (CGImageAlphaInfo) nfo))
using (var image = bContext.ToImage())
using (var nscontext = NSGraphicsContext.CurrentContext)
using (var context = nscontext.GraphicsPort)
CGImage image = null;
try
{
context.SetFillColor(255, 255, 255, 255);
context.FillRect(new CGRect(default(CGPoint), _logicalSize));
context.DrawImage(new CGRect(default(CGPoint), _logicalSize), image);
using (var colorSpace = CGColorSpace.CreateDeviceRGB())
using (var bContext = new CGBitmapContext(Address, Width, Height, 8, Width * 4,
colorSpace, (CGImageAlphaInfo)nfo))
image = bContext.ToImage();
lock (_view.SyncRoot)
{
if(!_isDeferred)
{
using (var nscontext = NSGraphicsContext.CurrentContext)
using (var context = nscontext.GraphicsPort)
{
context.SetFillColor(255, 255, 255, 255);
context.FillRect(new CGRect(default(CGPoint), _view.LogicalSize));
context.TranslateCTM(0, _view.LogicalSize.Height - _logicalSize.Height);
context.DrawImage(new CGRect(default(CGPoint), _logicalSize), image);
context.Flush();
nscontext.FlushGraphics();
}
}
}
}
Marshal.FreeHGlobal(Address);
Address = IntPtr.Zero;
finally
{
if (image != null)
{
if (!_isDeferred)
image.Dispose();
else
_view.SetBackBufferImage(new SavedImage(image, _logicalSize));
}
Marshal.FreeHGlobal(Address);
Address = IntPtr.Zero;
}
}
public IntPtr Address { get; private set; }
@ -49,4 +85,22 @@ namespace Avalonia.MonoMac
public Vector Dpi { get; }
public PixelFormat Format { get; }
}
class SavedImage : IDisposable
{
public CGImage Image { get; private set; }
public CGSize LogicalSize { get; }
public SavedImage(CGImage image, CGSize logicalSize)
{
Image = image;
LogicalSize = logicalSize;
}
public void Dispose()
{
Image?.Dispose();
Image = null;
}
}
}

46
src/OSX/Avalonia.MonoMac/MonoMacPlatform.cs

@ -1,10 +1,13 @@
using System;
using System.Threading;
using Avalonia.Controls;
using Avalonia.Controls.Platform;
using Avalonia.Input;
using Avalonia.Platform;
using Avalonia.Rendering;
using MonoMac.AppKit;
using MonoMac.Foundation;
using MonoMac.ObjCRuntime;
namespace Avalonia.MonoMac
{
@ -16,9 +19,11 @@ namespace Avalonia.MonoMac
internal static NSApplication App;
private static bool s_monoMacInitialized;
private static bool s_showInDock = true;
private static IRenderLoop s_renderLoop;
void DoInitialize()
{
InitializeMonoMac();
AvaloniaLocator.CurrentMutable
.Bind<IStandardCursorFactory>().ToTransient<CursorFactoryStub>()
.Bind<IPlatformIconLoader>().ToSingleton<IconLoader>()
@ -27,9 +32,8 @@ namespace Avalonia.MonoMac
.Bind<IPlatformSettings>().ToConstant(this)
.Bind<IWindowingPlatform>().ToConstant(this)
.Bind<ISystemDialogImpl>().ToSingleton<SystemDialogsImpl>()
.Bind<IRenderLoop>().ToConstant(s_renderLoop)
.Bind<IPlatformThreadingInterface>().ToConstant(PlatformThreadingInterface.Instance);
InitializeMonoMac();
}
public static void Initialize()
@ -39,13 +43,44 @@ namespace Avalonia.MonoMac
}
/// <summary>
/// See "Using POSIX Threads in a Cocoa Application" section here:
/// https://developer.apple.com/library/content/documentation/Cocoa/Conceptual/Multithreading/CreatingThreads/CreatingThreads.html#//apple_ref/doc/uid/20000738-125024
/// </summary>
class ThreadHelper : NSObject
{
private readonly AutoResetEvent _event = new AutoResetEvent(false);
private const string InitThreadingName = "initThreading";
[Export(InitThreadingName)]
public void DoNothing()
{
_event.Set();
}
public static void InitializeCocoaThreadingLocks()
{
var helper = new ThreadHelper();
var thread = new NSThread(helper, Selector.FromHandle(Selector.GetHandle(InitThreadingName)), new NSObject());
thread.Start();
helper._event.WaitOne();
helper._event.Dispose();
if (!NSThread.IsMultiThreaded)
{
throw new Exception("Unable to initialize Cocoa threading");
}
}
}
void InitializeMonoMac()
{
if(s_monoMacInitialized)
return;
NSApplication.Init();
ThreadHelper.InitializeCocoaThreadingLocks();
App = NSApplication.SharedApplication;
UpdateActivationPolicy();
s_renderLoop = new RenderLoop(); //TODO: use CVDisplayLink
s_monoMacInitialized = true;
}
@ -64,6 +99,7 @@ namespace Avalonia.MonoMac
}
}
public static bool UseDeferredRendering { get; set; } = true;
public Size DoubleClickSize => new Size(4, 4);
public TimeSpan DoubleClickTime => TimeSpan.FromSeconds(NSEvent.DoubleClickInterval);
@ -87,9 +123,11 @@ namespace Avalonia
{
public static class MonoMacPlatformExtensions
{
public static T UseMonoMac<T>(this T builder) where T : AppBuilderBase<T>, new()
public static T UseMonoMac<T>(this T builder, bool? useDeferredRendering = null) where T : AppBuilderBase<T>, new()
{
return builder.UseWindowingSubsystem(MonoMac.MonoMacPlatform.Initialize);
if (useDeferredRendering.HasValue)
MonoMac.MonoMacPlatform.UseDeferredRendering = useDeferredRendering.Value;
return builder.UseWindowingSubsystem(MonoMac.MonoMacPlatform.Initialize, "MonoMac");
}
}
}

32
src/OSX/Avalonia.MonoMac/PlatformThreadingInterface.cs

@ -3,14 +3,18 @@ using System.Threading;
using Avalonia.Platform;
using Avalonia.Threading;
using MonoMac.AppKit;
using MonoMac.CoreFoundation;
using MonoMac.CoreGraphics;
using MonoMac.Foundation;
using MonoMac.ObjCRuntime;
namespace Avalonia.MonoMac
{
class PlatformThreadingInterface : IPlatformThreadingInterface
class PlatformThreadingInterface : NSObject, IPlatformThreadingInterface
{
private bool _signaled;
private const string SignaledSelectorName = "avaloniauiSignaled";
private readonly Selector _signaledSelector = new Selector(SignaledSelectorName);
public static PlatformThreadingInterface Instance { get; } = new PlatformThreadingInterface();
public bool CurrentThreadIsLoopThread => NSThread.Current.IsMainThread;
@ -27,18 +31,25 @@ namespace Avalonia.MonoMac
return;
_signaled = true;
}
NSApplication.SharedApplication.BeginInvokeOnMainThread(() =>
{
lock (this)
PerformSelector(_signaledSelector, NSThread.MainThread, this, false,
new[]
{
if (!_signaled)
return;
_signaled = false;
}
Signaled?.Invoke(null);
});
NSRunLoop.NSDefaultRunLoopMode, NSRunLoop.NSRunLoopEventTracking, NSRunLoop.NSRunLoopModalPanelMode,
NSRunLoop.NSRunLoopCommonModes, NSRunLoop.NSRunLoopConnectionReplyMode
});
}
[Export(SignaledSelectorName)]
public void CallSignaled()
{
lock (this)
{
if (!_signaled)
return;
_signaled = false;
}
Signaled?.Invoke(null);
}
public void RunLoop(CancellationToken cancellationToken)
@ -55,6 +66,7 @@ namespace Avalonia.MonoMac
var ev = app.NextEvent(NSEventMask.AnyEvent, NSDate.DistantFuture, NSRunLoop.NSDefaultRunLoopMode, true);
if (ev != null)
{
Console.WriteLine("NSEVENT");
app.SendEvent(ev);
ev.Dispose();
}

33
src/OSX/Avalonia.MonoMac/RenderLoop.cs

@ -0,0 +1,33 @@
using System;
using System.Collections.Generic;
using System.Text;
using Avalonia.Platform;
using Avalonia.Rendering;
using MonoMac.Foundation;
namespace Avalonia.MonoMac
{
//TODO: Switch to using CVDisplayLink
public class RenderLoop : IRenderLoop
{
private readonly object _lock = new object();
private readonly IDisposable _timer;
public RenderLoop()
{
_timer = AvaloniaLocator.Current.GetService<IRuntimePlatform>().StartSystemTimer(new TimeSpan(0, 0, 0, 0, 1000 / 60),
() =>
{
lock (_lock)
{
using (new NSAutoreleasePool())
{
Tick?.Invoke(this, EventArgs.Empty);
}
}
});
}
public event EventHandler<EventArgs> Tick;
}
}

89
src/OSX/Avalonia.MonoMac/TopLevelImpl.cs

@ -5,8 +5,9 @@ using Avalonia.Input.Raw;
using Avalonia.Platform;
using Avalonia.Controls.Platform.Surfaces;
using Avalonia.Rendering;
using Avalonia.Threading;
using MonoMac.AppKit;
using MonoMac.CoreFoundation;
using MonoMac.CoreGraphics;
using MonoMac.Foundation;
using MonoMac.ObjCRuntime;
@ -24,6 +25,7 @@ namespace Avalonia.MonoMac
protected virtual void OnInput(RawInputEventArgs args)
{
Dispatcher.UIThread.RunJobs(DispatcherPriority.Input + 1);
Input?.Invoke(args);
}
@ -36,6 +38,14 @@ namespace Avalonia.MonoMac
private readonly IKeyboardDevice _keyboard;
private NSTrackingArea _area;
private NSCursor _cursor;
private bool _nonUiRedrawQueued;
public CGSize PixelSize { get; set; }
public CGSize LogicalSize { get; set; }
private SavedImage _backBuffer;
public object SyncRoot { get; } = new object();
public TopLevelView(TopLevelImpl tl)
{
@ -44,17 +54,75 @@ namespace Avalonia.MonoMac
_keyboard = AvaloniaLocator.Current.GetService<IKeyboardDevice>();
}
protected override void Dispose(bool disposing)
{
if (disposing)
{
_backBuffer?.Dispose();
_backBuffer = null;
}
base.Dispose(disposing);
}
public override bool ConformsToProtocol(IntPtr protocol)
{
var rv = base.ConformsToProtocol(protocol);
return rv;
}
public override bool IsOpaque => false;
public override void DrawRect(CGRect dirtyRect)
{
lock (SyncRoot)
_nonUiRedrawQueued = false;
Dispatcher.UIThread.RunJobs(DispatcherPriority.Render);
lock (SyncRoot)
{
if (_backBuffer != null)
{
using (var context = NSGraphicsContext.CurrentContext.GraphicsPort)
{
context.SetFillColor(255, 255, 255, 255);
context.FillRect(new CGRect(default(CGPoint), LogicalSize));
context.TranslateCTM(0, LogicalSize.Height - _backBuffer.LogicalSize.Height);
context.DrawImage(new CGRect(default(CGPoint), _backBuffer.LogicalSize), _backBuffer.Image);
context.Flush();
NSGraphicsContext.CurrentContext.FlushGraphics();
}
}
}
_tl.Paint?.Invoke(dirtyRect.ToAvaloniaRect());
}
public void SetBackBufferImage(SavedImage image)
{
lock (SyncRoot)
{
_backBuffer?.Dispose();
_backBuffer = image;
if (image == null)
return;
if (_nonUiRedrawQueued)
return;
_nonUiRedrawQueued = true;
Dispatcher.UIThread.InvokeAsync(
() =>
{
lock (SyncRoot)
{
if (!_nonUiRedrawQueued)
return;
_nonUiRedrawQueued = false;
}
SetNeedsDisplayInRect(Frame);
Display();
}, DispatcherPriority.Render);
}
}
[Export("viewDidChangeBackingProperties:")]
public void ViewDidChangeBackingProperties()
{
@ -78,7 +146,12 @@ namespace Avalonia.MonoMac
public override void SetFrameSize(CGSize newSize)
{
base.SetFrameSize(newSize);
lock (SyncRoot)
{
base.SetFrameSize(newSize);
LogicalSize = Frame.Size;
PixelSize = ConvertSizeToBacking(LogicalSize);
}
if (_area != null)
{
@ -92,6 +165,7 @@ namespace Avalonia.MonoMac
AddTrackingArea(_area);
UpdateCursor();
_tl?.Resized?.Invoke(_tl.ClientSize);
Dispatcher.UIThread.RunJobs(DispatcherPriority.Layout);
}
InputModifiers GetModifiers(NSEventModifierMask mod)
@ -348,9 +422,16 @@ namespace Avalonia.MonoMac
View.Dispose();
}
public IRenderer CreateRenderer(IRenderRoot root) => new ImmediateRenderer(root);
public IRenderer CreateRenderer(IRenderRoot root) =>
MonoMacPlatform.UseDeferredRendering
? new DeferredRenderer(root, AvaloniaLocator.Current.GetService<IRenderLoop>())
: (IRenderer) new ImmediateRenderer(root);
public void Invalidate(Rect rect) => View.SetNeedsDisplayInRect(View.Frame);
public void Invalidate(Rect rect)
{
if (!MonoMacPlatform.UseDeferredRendering)
View.SetNeedsDisplayInRect(View.Frame);
}
public abstract Point PointToClient(Point point);

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

@ -249,6 +249,37 @@ namespace Avalonia.Controls.UnitTests.Primitives
}
}
[Fact]
public void DataContextBeginUpdate_Should_Not_Be_Called_For_Controls_That_Dont_Inherit()
{
using (CreateServices())
{
TestControl child;
var popup = new Popup
{
Child = child = new TestControl(),
DataContext = "foo",
};
var beginCalled = false;
child.DataContextBeginUpdate += (s, e) => beginCalled = true;
// Test for #1245. Here, the child's logical parent is the popup but it's not yet
// attached to a visual tree because the popup hasn't been opened.
Assert.Same(popup, ((ILogical)child).LogicalParent);
Assert.Same(popup, child.InheritanceParent);
Assert.Null(child.GetVisualRoot());
popup.Open();
// #1245 was caused by the fact that DataContextBeginUpdate was called on `target`
// when the PopupRoot was created, even though PopupRoot isn't the
// InheritanceParent of child.
Assert.False(beginCalled);
}
}
private static IDisposable CreateServices()
{
var result = AvaloniaLocator.EnterScope();
@ -304,5 +335,18 @@ namespace Avalonia.Controls.UnitTests.Primitives
private class PopupContentControl : ContentControl
{
}
private class TestControl : Decorator
{
public event EventHandler DataContextBeginUpdate;
public new IAvaloniaObject InheritanceParent => base.InheritanceParent;
protected override void OnDataContextBeginUpdate()
{
DataContextBeginUpdate?.Invoke(this, EventArgs.Empty);
base.OnDataContextBeginUpdate();
}
}
}
}

113
tests/Avalonia.Layout.UnitTests/ShapeLayoutTests.cs

@ -0,0 +1,113 @@
using System.Collections.Generic;
using Avalonia.Controls;
using Avalonia.Controls.Shapes;
using Avalonia.Media;
using Avalonia.Platform;
using Avalonia.UnitTests;
using Xunit;
namespace Avalonia.Layout.UnitTests
{
public class ShapeLayoutTests : TestWithServicesBase
{
public ShapeLayoutTests()
{
AvaloniaLocator.CurrentMutable
.Bind<IPlatformRenderInterface>().ToSingleton<MockPlatformRenderInterface>();
}
[Fact]
public void Shape_Transformation_Calculation_Should_Be_Deferred_To_Arrange_When_Strech_Is_Fill_And_Aviable_Size_Is_Infinite()
{
var shape = new Polygon()
{
Points = new List<Point>
{
new Point(0, 0),
new Point(10, 5),
new Point(0, 10)
},
Stretch = Stretch.Fill
};
var availableSize = new Size(double.PositiveInfinity, double.PositiveInfinity);
shape.Measure(availableSize);
Geometry postMeasureGeometry = shape.RenderedGeometry;
Transform postMeasureTransform = postMeasureGeometry.Transform;
var finalSize = new Size(100, 50);
var finalRect = new Rect(finalSize);
shape.Arrange(finalRect);
Geometry postArrangeGeometry = shape.RenderedGeometry;
Transform postArrangeTransform = postArrangeGeometry.Transform;
Assert.NotEqual(postMeasureGeometry, postArrangeGeometry);
Assert.NotEqual(postMeasureTransform, postArrangeTransform);
Assert.Equal(finalSize, shape.Bounds.Size);
}
[Fact]
public void Shape_Transformation_Calculation_Should_Not_Be_Deferred_To_Arrange_When_Strech_Is_Fill_And_Aviable_Size_Is_Finite()
{
var shape = new Polygon()
{
Points = new List<Point>
{
new Point(0, 0),
new Point(10, 5),
new Point(0, 10)
},
Stretch = Stretch.Fill
};
var availableSize = new Size(100, 50);
shape.Measure(availableSize);
Geometry postMeasureGeometry = shape.RenderedGeometry;
Transform postMeasureTransform = postMeasureGeometry.Transform;
var finalRect = new Rect(availableSize);
shape.Arrange(finalRect);
Geometry postArrangeGeometry = shape.RenderedGeometry;
Transform postArrangeTransform = postArrangeGeometry.Transform;
Assert.Equal(postMeasureGeometry, postArrangeGeometry);
Assert.Equal(postMeasureTransform, postArrangeTransform);
Assert.Equal(availableSize, shape.Bounds.Size);
}
[Fact]
public void Shape_Transformation_Calculation_Should_Not_Be_Deferred_To_Arrange_When_Strech_Is_None()
{
var shape = new Polygon()
{
Points = new List<Point>
{
new Point(0, 0),
new Point(10, 5),
new Point(0, 10)
},
Stretch = Stretch.None
};
var availableSize = new Size(double.PositiveInfinity, double.PositiveInfinity);
shape.Measure(availableSize);
Geometry postMeasureGeometry = shape.RenderedGeometry;
Transform postMeasureTransform = postMeasureGeometry.Transform;
var finalSize = new Size(100, 50);
var finalRect = new Rect(finalSize);
shape.Arrange(finalRect);
Geometry postArrangeGeometry = shape.RenderedGeometry;
Transform postArrangeTransform = postArrangeGeometry.Transform;
Assert.Equal(postMeasureGeometry, postArrangeGeometry);
Assert.Equal(postMeasureTransform, postArrangeTransform);
Assert.Equal(finalSize, shape.Bounds.Size);
}
}
}

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

@ -8,7 +8,6 @@
<Import Project="..\..\build\XUnit.props" />
<Import Project="..\..\build\Rx.props" />
<Import Project="..\..\build\Microsoft.Reactive.Testing.props" />
<Import Project="..\..\build\Splat.props" />
<Import Project="..\..\build\Sprache.props" />
<ItemGroup>
<ProjectReference Include="..\..\src\Markup\Avalonia.Markup.Xaml\Avalonia.Markup.Xaml.csproj" />

19
tests/Avalonia.Markup.Xaml.UnitTests/Xaml/BasicTests.cs

@ -411,8 +411,8 @@ namespace Avalonia.Markup.Xaml.UnitTests.Xaml
var xaml = @"
<Styles xmlns='https://github.com/avaloniaui'>
<Style Selector='CheckBox'>
<Setter Property='BorderBrush' Value='{StyleResource ThemeBorderMidBrush}'/>
<Setter Property='BorderThickness' Value='{StyleResource ThemeBorderThickness}'/>
<Setter Property='BorderBrush' Value='{DynamicResource ThemeBorderMidBrush}'/>
<Setter Property='BorderThickness' Value='{DynamicResource ThemeBorderThickness}'/>
<Setter Property='Template'>
<ControlTemplate>
<Grid ColumnDefinitions='Auto,*'>
@ -423,7 +423,7 @@ namespace Avalonia.Markup.Xaml.UnitTests.Xaml
Height='18'
VerticalAlignment='Center'>
<Path Name='checkMark'
Fill='{StyleResource HighlightBrush}'
Fill='{StaticResource HighlightBrush}'
Width='11'
Height='10'
Stretch='Uniform'
@ -457,8 +457,6 @@ namespace Avalonia.Markup.Xaml.UnitTests.Xaml
Assert.Equal(CheckBox.BorderThicknessProperty, setters[1].Property);
Assert.Equal(CheckBox.TemplateProperty, setters[2].Property);
Assert.IsType<StyleResourceBinding>(setters[0].Value);
Assert.IsType<StyleResourceBinding>(setters[1].Value);
Assert.IsType<ControlTemplate>(setters[2].Value);
}
}
@ -772,15 +770,8 @@ do we need it?")]
<Window xmlns='https://github.com/avaloniaui'
xmlns:x='http://schemas.microsoft.com/winfx/2006/xaml'
xmlns:local='clr-namespace:Avalonia.Markup.Xaml.UnitTests.Xaml;assembly=Avalonia.Markup.Xaml.UnitTests'>
<Window.Styles>
<Style>
<Style.Resources>
<x:Double x:Key='Double'>100</x:Double>
</Style.Resources>
</Style>
</Window.Styles>
<local:InitializationOrderTracker Width='100' Height='{StyleResource Double}'
Tag='{Binding Height, RelativeSource={RelativeSource Self}}' />
<local:InitializationOrderTracker Width='100' Height='100'
Tag='{Binding Height, RelativeSource={RelativeSource Self}}' />
</Window>";

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

@ -61,171 +61,6 @@ namespace Avalonia.Markup.Xaml.UnitTests.Xaml
}
}
[Fact]
public void StyleResource_Can_Be_Assigned_To_Property()
{
var xaml = @"
<UserControl xmlns='https://github.com/avaloniaui'
xmlns:x='http://schemas.microsoft.com/winfx/2006/xaml'>
<UserControl.Styles>
<Style>
<Style.Resources>
<SolidColorBrush x:Key='brush'>#ff506070</SolidColorBrush>
</Style.Resources>
</Style>
</UserControl.Styles>
<Border Name='border' Background='{StyleResource brush}'/>
</UserControl>";
var loader = new AvaloniaXamlLoader();
var userControl = (UserControl)loader.Load(xaml);
var border = userControl.FindControl<Border>("border");
DelayedBinding.ApplyBindings(border);
var brush = (SolidColorBrush)border.Background;
Assert.Equal(0xff506070, brush.Color.ToUint32());
}
[Fact]
public void StyleResource_Can_Be_Assigned_To_Setter()
{
//skip default theme and styles, they are not needed
using (UnitTestApplication.Start(TestServices.StyledWindow
.With(theme: () => new Styles())))
{
var xaml = @"
<Window xmlns='https://github.com/avaloniaui'
xmlns:x='http://schemas.microsoft.com/winfx/2006/xaml'>
<Window.Styles>
<Style>
<Style.Resources>
<SolidColorBrush x:Key='brush'>#ff506070</SolidColorBrush>
</Style.Resources>
</Style>
<Style Selector='Button'>
<Setter Property='Background' Value='{StyleResource brush}'/>
</Style>
</Window.Styles>
<Button Name='button'/>
</Window>";
var loader = new AvaloniaXamlLoader();
var window = (Window)loader.Load(xaml);
var button = window.FindControl<Button>("button");
var brush = (SolidColorBrush)button.Background;
Assert.Equal(0xff506070, brush.Color.ToUint32());
}
}
[Fact]
public void StyleResource_Can_Be_Assigned_To_StyleResource_Property()
{
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>
<Style.Resources>
<Color x:Key='color'>#ff506070</Color>
<SolidColorBrush x:Key='brush' Color='{StyleResource color}'/>
</Style.Resources>
</Style>
</Window.Styles>
<Button Name='button' Background='{StyleResource brush}'/>
</Window>";
var loader = new AvaloniaXamlLoader();
var window = (Window)loader.Load(xaml);
var brush = (ISolidColorBrush)window.FindResource("brush");
var button = window.FindControl<Button>("button");
DelayedBinding.ApplyBindings(button);
var buttonBrush = (ISolidColorBrush)button.Background;
Assert.Equal(0xff506070, brush.Color.ToUint32());
Assert.Equal(0xff506070, buttonBrush.Color.ToUint32());
}
}
[Fact]
public void StyleResource_Can_Be_Found_In_TopLevel_Styles()
{
var xaml = @"
<Styles xmlns='https://github.com/avaloniaui'
xmlns:x='http://schemas.microsoft.com/winfx/2006/xaml'>
<Style>
<Style.Resources>
<Color x:Key='color'>#ff506070</Color>
<SolidColorBrush x:Key='brush' Color='{StyleResource color}'/>
</Style.Resources>
</Style>
</Styles>";
var loader = new AvaloniaXamlLoader();
var styles = (Styles)loader.Load(xaml);
styles.TryGetResource("brush", out var brush);
Assert.Equal(0xff506070, ((SolidColorBrush)brush).Color.ToUint32());
}
[Fact]
public void StyleResource_Can_Be_Found_In_Sibling_Styles()
{
var xaml = @"
<Styles xmlns='https://github.com/avaloniaui'
xmlns:x='http://schemas.microsoft.com/winfx/2006/xaml'>
<Style>
<Style.Resources>
<Color x:Key='color'>#ff506070</Color>
</Style.Resources>
</Style>
<Style>
<Style.Resources>
<SolidColorBrush x:Key='brush' Color='{StyleResource color}'/>
</Style.Resources>
</Style>
</Styles>";
var loader = new AvaloniaXamlLoader();
var styles = (Styles)loader.Load(xaml);
styles.TryGetResource("brush", out var brush);
Assert.Equal(0xff506070, ((SolidColorBrush)brush).Color.ToUint32());
}
[Fact(Skip = "TODO: Issue #492")]
public void StyleResource_Can_Be_Found_Across_Xaml_Files()
{
using (UnitTestApplication.Start(TestServices.StyledWindow))
{
var xaml = @"
<Window xmlns='https://github.com/avaloniaui'
xmlns:x='http://schemas.microsoft.com/winfx/2006/xaml'>
<Window.Styles>
<StyleInclude Source='resm:Avalonia.Markup.Xaml.UnitTests.Xaml.Style1.xaml?assembly=Avalonia.Markup.Xaml.UnitTests'/>
<StyleInclude Source='resm:Avalonia.Markup.Xaml.UnitTests.Xaml.Style2.xaml?assembly=Avalonia.Markup.Xaml.UnitTests'/>
</Window.Styles>
<Border Name='border' Background='{StyleResource RedBrush}'/>
</Window>";
var loader = new AvaloniaXamlLoader();
var window = (Window)loader.Load(xaml);
var border = window.FindControl<Border>("border");
var borderBrush = (ISolidColorBrush)border.Background;
Assert.NotNull(borderBrush);
Assert.Equal(0xffff0000, borderBrush.Color.ToUint32());
}
}
[Fact]
public void StyleInclude_Is_Built()
{

23
tests/Avalonia.UnitTests/TestWithServicesBase.cs

@ -0,0 +1,23 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Avalonia.UnitTests
{
public class TestWithServicesBase : IDisposable
{
private IDisposable _scope;
public TestWithServicesBase()
{
_scope = AvaloniaLocator.EnterScope();
}
public void Dispose()
{
_scope.Dispose();
}
}
}
Loading…
Cancel
Save