Browse Source

Merge pull request #4015 from AvaloniaUI/feature/convert-color-resource-to-brush

Convert Color resources to brushes.
pull/4017/head
danwalmsley 6 years ago
committed by GitHub
parent
commit
8573c28ee9
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 35
      src/Avalonia.Styling/Controls/ResourceNodeExtensions.cs
  2. 1
      src/Markup/Avalonia.Markup.Xaml/Avalonia.Markup.Xaml.csproj
  3. 88
      src/Markup/Avalonia.Markup.Xaml/Converters/ColorToBrushConverter.cs
  4. 18
      src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/DynamicResourceExtension.cs
  5. 23
      src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/StaticResourceExtension.cs
  6. 21
      tests/Avalonia.Markup.Xaml.UnitTests/MarkupExtensions/DynamicResourceExtensionTests.cs
  7. 21
      tests/Avalonia.Markup.Xaml.UnitTests/MarkupExtensions/StaticResourceExtensionTests.cs

35
src/Avalonia.Styling/Controls/ResourceNodeExtensions.cs

@ -1,4 +1,5 @@
using System;
using Avalonia.Data.Converters;
using Avalonia.LogicalTree;
using Avalonia.Reactive;
@ -58,31 +59,39 @@ namespace Avalonia.Controls
return false;
}
public static IObservable<object?> GetResourceObservable(this IStyledElement control, object key)
public static IObservable<object?> GetResourceObservable(
this IStyledElement control,
object key,
Func<object?, object?>? converter = null)
{
control = control ?? throw new ArgumentNullException(nameof(control));
key = key ?? throw new ArgumentNullException(nameof(key));
return new ResourceObservable(control, key);
return new ResourceObservable(control, key, converter);
}
public static IObservable<object?> GetResourceObservable(this IResourceProvider resourceProvider, object key)
public static IObservable<object?> GetResourceObservable(
this IResourceProvider resourceProvider,
object key,
Func<object?, object?>? converter = null)
{
resourceProvider = resourceProvider ?? throw new ArgumentNullException(nameof(resourceProvider));
key = key ?? throw new ArgumentNullException(nameof(key));
return new FloatingResourceObservable(resourceProvider, key);
return new FloatingResourceObservable(resourceProvider, key, converter);
}
private class ResourceObservable : LightweightObservableBase<object?>
{
private readonly IStyledElement _target;
private readonly object _key;
private readonly Func<object?, object?>? _converter;
public ResourceObservable(IStyledElement target, object key)
public ResourceObservable(IStyledElement target, object key, Func<object?, object?>? converter)
{
_target = target;
_key = key;
_converter = converter;
}
protected override void Initialize()
@ -97,25 +106,29 @@ namespace Avalonia.Controls
protected override void Subscribed(IObserver<object?> observer, bool first)
{
observer.OnNext(_target.FindResource(_key));
observer.OnNext(Convert(_target.FindResource(_key)));
}
private void ResourcesChanged(object sender, ResourcesChangedEventArgs e)
{
PublishNext(_target.FindResource(_key));
PublishNext(Convert(_target.FindResource(_key)));
}
private object? Convert(object? value) => _converter?.Invoke(value) ?? value;
}
private class FloatingResourceObservable : LightweightObservableBase<object?>
{
private readonly IResourceProvider _target;
private readonly object _key;
private readonly Func<object?, object?>? _converter;
private IResourceHost? _owner;
public FloatingResourceObservable(IResourceProvider target, object key)
public FloatingResourceObservable(IResourceProvider target, object key, Func<object?, object?>? converter)
{
_target = target;
_key = key;
_converter = converter;
}
protected override void Initialize()
@ -134,13 +147,13 @@ namespace Avalonia.Controls
{
if (_target.Owner is object)
{
observer.OnNext(_target.Owner?.FindResource(_key));
observer.OnNext(Convert(_target.Owner?.FindResource(_key)));
}
}
private void PublishNext()
{
PublishNext(_target.Owner?.FindResource(_key));
PublishNext(Convert(_target.Owner?.FindResource(_key)));
}
private void OwnerChanged(object sender, EventArgs e)
@ -164,6 +177,8 @@ namespace Avalonia.Controls
{
PublishNext();
}
private object? Convert(object? value) => _converter?.Invoke(value) ?? value;
}
}
}

1
src/Markup/Avalonia.Markup.Xaml/Avalonia.Markup.Xaml.csproj

@ -11,6 +11,7 @@
<ItemGroup>
<Compile Include="AvaloniaXamlLoader.cs" />
<Compile Include="Converters\AvaloniaUriTypeConverter.cs" />
<Compile Include="Converters\ColorToBrushConverter.cs" />
<Compile Include="Converters\FontFamilyTypeConverter.cs" />
<Compile Include="Converters\TimeSpanTypeConverter.cs" />
<Compile Include="Extensions.cs" />

88
src/Markup/Avalonia.Markup.Xaml/Converters/ColorToBrushConverter.cs

@ -0,0 +1,88 @@
using System;
using System.Globalization;
using Avalonia.Data.Converters;
using Avalonia.Media;
using Avalonia.Media.Immutable;
namespace Avalonia.Markup.Xaml.Converters
{
/// <summary>
/// Converts a <see cref="Color"/> to an <see cref="IBrush"/>.
/// </summary>
public class ColorToBrushConverter : IValueConverter
{
/// <summary>
/// Converts a <see cref="Color"/> to an <see cref="IBrush"/> if the arguments are of the
/// correct type.
/// </summary>
/// <param name="value">The value.</param>
/// <param name="targetType">The target type.</param>
/// <param name="parameter">Not used.</param>
/// <param name="culture">Not used.</param>
/// <returns>
/// If <paramref name="value"/> is a <see cref="Color"/> and <paramref name="targetType"/>
/// is <see cref="IBrush"/> then converts the color to a solid color brush.
/// </returns>
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
return Convert(value, targetType);
}
/// <summary>
/// Converts an <see cref="ISolidColorBrush"/> to a <see cref="Color"/> if the arguments are of the
/// correct type.
/// </summary>
/// <param name="value">The value.</param>
/// <param name="targetType">The target type.</param>
/// <param name="parameter">Not used.</param>
/// <param name="culture">Not used.</param>
/// <returns>
/// If <paramref name="value"/> is an <see cref="ISolidColorBrush"/> and <paramref name="targetType"/>
/// is <see cref="Color"/> then converts the solid color brush to a color.
/// </returns>
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
return ConvertBack(value, targetType);
}
/// <summary>
/// Converts a <see cref="Color"/> to an <see cref="IBrush"/> if the arguments are of the
/// correct type.
/// </summary>
/// <param name="value">The value.</param>
/// <param name="targetType">The target type.</param>
/// <returns>
/// If <paramref name="value"/> is a <see cref="Color"/> and <paramref name="targetType"/>
/// is <see cref="IBrush"/> then converts the color to a solid color brush.
/// </returns>
public static object Convert(object value, Type targetType)
{
if (targetType == typeof(IBrush) && value is Color c)
{
return new ImmutableSolidColorBrush(c);
}
return value;
}
/// <summary>
/// Converts an <see cref="ISolidColorBrush"/> to a <see cref="Color"/> if the arguments are of the
/// correct type.
/// </summary>
/// <param name="value">The value.</param>
/// <param name="targetType">The target type.</param>
/// <returns>
/// If <paramref name="value"/> is an <see cref="ISolidColorBrush"/> and <paramref name="targetType"/>
/// is <see cref="Color"/> then converts the solid color brush to a color.
/// </returns>
public static object ConvertBack(object value, Type targetType)
{
if (targetType == typeof(Color) && value is ISolidColorBrush brush)
{
return brush.Color;
}
return value;
}
}
}

18
src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/DynamicResourceExtension.cs

@ -1,6 +1,8 @@
using System;
using Avalonia.Controls;
using Avalonia.Data;
using Avalonia.Markup.Xaml.Converters;
using Avalonia.Media;
#nullable enable
@ -54,11 +56,23 @@ namespace Avalonia.Markup.Xaml.MarkupExtensions
if (control != null)
{
return InstancedBinding.OneWay(control.GetResourceObservable(ResourceKey));
var source = control.GetResourceObservable(ResourceKey, GetConverter(targetProperty));
return InstancedBinding.OneWay(source);
}
else if (_resourceProvider is object)
{
return InstancedBinding.OneWay(_resourceProvider.GetResourceObservable(ResourceKey));
var source = _resourceProvider.GetResourceObservable(ResourceKey, GetConverter(targetProperty));
return InstancedBinding.OneWay(source);
}
return null;
}
private Func<object?, object?>? GetConverter(AvaloniaProperty targetProperty)
{
if (targetProperty?.PropertyType == typeof(IBrush))
{
return x => ColorToBrushConverter.Convert(x, typeof(IBrush));
}
return null;

23
src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/StaticResourceExtension.cs

@ -1,9 +1,9 @@
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Reflection;
using Avalonia.Controls;
using Avalonia.Markup.Data;
using Avalonia.Markup.Xaml.Converters;
using Avalonia.Markup.Xaml.XamlIl.Runtime;
namespace Avalonia.Markup.Xaml.MarkupExtensions
@ -24,6 +24,14 @@ namespace Avalonia.Markup.Xaml.MarkupExtensions
public object ProvideValue(IServiceProvider serviceProvider)
{
var stack = serviceProvider.GetService<IAvaloniaXamlIlParentStackProvider>();
var provideTarget = serviceProvider.GetService<IProvideValueTarget>();
var targetType = provideTarget.TargetProperty switch
{
AvaloniaProperty ap => ap.PropertyType,
PropertyInfo pi => pi.PropertyType,
_ => null,
};
// Look upwards though the ambient context for IResourceHosts and IResourceProviders
// which might be able to give us the resource.
@ -33,30 +41,27 @@ namespace Avalonia.Markup.Xaml.MarkupExtensions
if (e is IResourceHost host && host.TryGetResource(ResourceKey, out value))
{
return value;
return ColorToBrushConverter.Convert(value, targetType);
}
else if (e is IResourceProvider provider && provider.TryGetResource(ResourceKey, out value))
{
return value;
return ColorToBrushConverter.Convert(value, targetType);
}
}
// The resource still hasn't been found, so add a delayed one-time binding.
var provideTarget = serviceProvider.GetService<IProvideValueTarget>();
if (provideTarget.TargetObject is IControl target &&
provideTarget.TargetProperty is PropertyInfo property)
{
DelayedBinding.Add(target, property, GetValue);
DelayedBinding.Add(target, property, x => GetValue(x, targetType));
return AvaloniaProperty.UnsetValue;
}
throw new KeyNotFoundException($"Static resource '{ResourceKey}' not found.");
}
private object GetValue(IStyledElement control)
private object GetValue(IStyledElement control, Type targetType)
{
return control.FindResource(ResourceKey);
return ColorToBrushConverter.Convert(control.FindResource(ResourceKey), targetType);
}
}
}

21
tests/Avalonia.Markup.Xaml.UnitTests/MarkupExtensions/DynamicResourceExtensionTests.cs

@ -797,6 +797,27 @@ namespace Avalonia.Markup.Xaml.UnitTests.MarkupExtensions
}
}
[Fact]
public void Automatically_Converts_Color_To_SolidColorBrush()
{
var xaml = @"
<UserControl xmlns='https://github.com/avaloniaui'
xmlns:x='http://schemas.microsoft.com/winfx/2006/xaml'>
<UserControl.Resources>
<Color x:Key='color'>#ff506070</Color>
</UserControl.Resources>
<Border Name='border' Background='{DynamicResource color}'/>
</UserControl>";
var loader = new AvaloniaXamlLoader();
var userControl = (UserControl)loader.Load(xaml);
var border = userControl.FindControl<Border>("border");
var brush = (ISolidColorBrush)border.Background;
Assert.Equal(0xff506070, brush.Color.ToUint32());
}
private IDisposable StyledWindow(params (string, string)[] assets)
{
var services = TestServices.StyledWindow.With(

21
tests/Avalonia.Markup.Xaml.UnitTests/MarkupExtensions/StaticResourceExtensionTests.cs

@ -511,6 +511,27 @@ namespace Avalonia.Markup.Xaml.UnitTests.MarkupExtensions
Assert.Equal(0xff506070, brush.Color.ToUint32());
}
[Fact]
public void Automatically_Converts_Color_To_SolidColorBrush()
{
var xaml = @"
<UserControl xmlns='https://github.com/avaloniaui'
xmlns:x='http://schemas.microsoft.com/winfx/2006/xaml'>
<UserControl.Resources>
<Color x:Key='color'>#ff506070</Color>
</UserControl.Resources>
<Border Name='border' Background='{StaticResource color}'/>
</UserControl>";
var loader = new AvaloniaXamlLoader();
var userControl = (UserControl)loader.Load(xaml);
var border = userControl.FindControl<Border>("border");
var brush = (ISolidColorBrush)border.Background;
Assert.Equal(0xff506070, brush.Color.ToUint32());
}
private IDisposable StyledWindow(params (string, string)[] assets)
{
var services = TestServices.StyledWindow.With(

Loading…
Cancel
Save