Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions Directory.Build.props
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
<Project>
<PropertyGroup>
<VersionPrefix>3.1.0</VersionPrefix>
<VersionPrefix>3.2.0</VersionPrefix>
<VersionSuffix></VersionSuffix>
<AvaloniaVersionPrefix>11.3.6</AvaloniaVersionPrefix>
<AvaloniaVersionPrefix>11.3.6.1</AvaloniaVersionPrefix>
<AvaloniaVersionSuffix>$(VersionSuffix)</AvaloniaVersionSuffix>
<Authors>Wiesław Šoltés</Authors>
<Company>Wiesław Šoltés</Company>
Expand Down
58 changes: 49 additions & 9 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -273,21 +273,28 @@ Install-Package Svg.Controls.Skia.Avalonia
</Image>
```

#### SvgBrush Markup Extension
#### SvgResourceExtension Markup Extension

Use `SvgBrush` when you want to place an SVG-backed `VisualBrush` in a resource dictionary and reuse it for backgrounds or other brush targets:
The former `SvgBrush` markup extension has been renamed to `SvgResourceExtension`. In XAML you can use the short `{SvgResource ...}` syntax to paint any brush property directly:

```XAML
<Border CornerRadius="12"
Background="{SvgResource /Assets/__tiger.svg}" />
```

To reuse the brush across your view, declare it in resources (the markup extension type named `SvgResourceExtension` still trims to `SvgResource` when used in XAML):

```XAML
<UserControl xmlns="https://github.com/avaloniaui"
xmlns:svg="clr-namespace:Avalonia.Svg.Skia;assembly=Svg.Controls.Skia.Avalonia">
<UserControl.Resources>
<svg:SvgBrush x:Key="TigerBrush"
Stretch="UniformToFill"
AlignmentX="Center"
AlignmentY="Center"
TileMode="Tile"
DestinationRect="0,0,1,1"
Opacity="0.85">/Assets/__tiger.svg</svg:SvgBrush>
<svg:SvgResource x:Key="TigerBrush"
Stretch="UniformToFill"
AlignmentX="Center"
AlignmentY="Center"
TileMode="Tile"
DestinationRect="0,0,1,1"
Opacity="0.85">/Assets/__tiger.svg</svg:SvgResource>
</UserControl.Resources>

<Border Background="{DynamicResource TigerBrush}" />
Expand All @@ -296,6 +303,39 @@ Use `SvgBrush` when you want to place an SVG-backed `VisualBrush` in a resource

The optional properties mirror those on `VisualBrush`, so you can tweak layout, tiling, opacity, and transforms directly in XAML while the control takes care of loading and rendering the SVG content.

When you need the brush from code-behind, the extension now exposes a `ToBrush(IServiceProvider? serviceProvider = null)` helper and supports implicit conversion to `Brush`, which can be assigned to any `IBrush` property:

```csharp
// Resolve relative paths by providing BaseUri when running outside of XAML.
var brush = new SvgResourceExtension("avares://MyAssembly/Assets/Icon.svg")
{
BaseUri = new Uri("avares://MyAssembly/")
}.ToBrush();

// Or rely on the implicit conversion to Brush/IBrush.
IBrush background = new SvgResourceExtension("avares://MyAssembly/Assets/Icon.svg");

// When you already have an SvgImage instance, create a brush directly.
var svgImage = new SvgImage
{
Source = SvgSource.Load("avares://MyAssembly/Assets/Icon.svg", baseUri: null)
};
var fromImage = SvgResourceExtension.CreateBrush(
svgImage,
stretch: Stretch.Uniform,
alignmentX: AlignmentX.Center,
alignmentY: AlignmentY.Center);

// Or skip creating the markup extension entirely and build a brush from the SVG path in one call.
var fromPath = SvgResourceExtension.CreateBrush(
"avares://MyAssembly/Assets/Icon.svg",
stretch: Stretch.Fill,
alignmentX: AlignmentX.Right,
alignmentY: AlignmentY.Bottom);
```

The Skia-backed controls also accept optional `css` and `currentCss` arguments on the static helper so you can apply styles while creating the brush from code.

#### Avalonia Previewer

To make controls work with `Avalonia Previewer` please add the following lines to `BuildAvaloniaApp()` method:
Expand Down
11 changes: 9 additions & 2 deletions samples/AvaloniaSvgSample/MainWindow.axaml
Original file line number Diff line number Diff line change
Expand Up @@ -111,10 +111,17 @@
Background="{SvgImage /Assets/__tiger.svg}" />
</Panel>
</TabItem>


<TabItem Header="Background (SvgResource)">
<Panel Margin="16" VerticalAlignment="Stretch" HorizontalAlignment="Stretch">
<Border CornerRadius="12"
Background="{SvgResource /Assets/__tiger.svg}" />
</Panel>
</TabItem>

<TabItem Header="Background (Resources)">
<TabItem.Resources>
<svg:SvgBrush x:Key="__tiger">/Assets/__tiger.svg</svg:SvgBrush>
<svg:SvgResource x:Key="__tiger">/Assets/__tiger.svg</svg:SvgResource>
</TabItem.Resources>
<Panel Margin="16" VerticalAlignment="Stretch" HorizontalAlignment="Stretch">
<Border CornerRadius="12"
Expand Down
9 changes: 8 additions & 1 deletion samples/AvaloniaSvgSkiaSample/MainWindow.axaml
Original file line number Diff line number Diff line change
Expand Up @@ -133,9 +133,16 @@
</Panel>
</TabItem>

<TabItem Header="Background (SvgResource)">
<Panel Margin="16" VerticalAlignment="Stretch" HorizontalAlignment="Stretch">
<Border CornerRadius="12"
Background="{SvgResource /Assets/__tiger.svg}" />
</Panel>
</TabItem>

<TabItem Header="Background (Resources)">
<TabItem.Resources>
<svg:SvgBrush x:Key="__tiger">/Assets/__tiger.svg</svg:SvgBrush>
<svg:SvgResource x:Key="__tiger">/Assets/__tiger.svg</svg:SvgResource>
</TabItem.Resources>
<Panel Margin="16" VerticalAlignment="Stretch" HorizontalAlignment="Stretch">
<Border CornerRadius="12"
Expand Down
2 changes: 1 addition & 1 deletion src/Svg.Controls.Avalonia/SvgImageExtension.cs
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ public override object ProvideValue(IServiceProvider serviceProvider)

if (typeof(IBrush).IsAssignableFrom(property.PropertyType))
{
return SvgBrush.CreateFromImage(image);
return SvgResourceExtension.CreateBrush(image);
}

return new Image { Source = image };
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,14 +11,36 @@ namespace Avalonia.Svg;
/// <summary>
/// Provides an SVG-backed brush that can be declared in XAML resources.
/// </summary>
public class SvgBrush : MarkupExtension
public class SvgResourceExtension : MarkupExtension
{
/// <summary>
/// Initializes a new instance of the <see cref="SvgResourceExtension" /> class.
/// </summary>
public SvgResourceExtension()
{
}

/// <summary>
/// Initializes a new instance of the <see cref="SvgResourceExtension" /> class, with the provided initial key.
/// </summary>
/// <param name="path">The path of the SVG resource that this markup extension references or file path.</param>
public SvgResourceExtension(string path)
{
Path = path;
}

/// <summary>
/// Gets or sets the SVG resource or file path.
/// </summary>
[ConstructorArgument("path")]
[Content]
public string? Path { get; set; }

/// <summary>
/// Gets or sets the base URI used when resolving <see cref="Path"/> outside of XAML.
/// </summary>
public Uri? BaseUri { get; set; }

/// <summary>
/// Gets or sets the stretch applied to the resulting brush.
/// </summary>
Expand Down Expand Up @@ -78,7 +100,7 @@ public class SvgBrush : MarkupExtension
/// <param name="transform">Optional transform applied to the brush.</param>
/// <param name="transformOrigin">Optional transform origin applied when <paramref name="transform"/> is set.</param>
/// <returns>A <see cref="VisualBrush"/> that renders <paramref name="image"/>.</returns>
internal static IBrush CreateFromImage(
public static IBrush CreateBrush(
IImage image,
Stretch? stretch = null,
AlignmentX? alignmentX = null,
Expand Down Expand Up @@ -146,28 +168,56 @@ internal static IBrush CreateFromImage(
return brush;
}

/// <inheritdoc/>
public override object ProvideValue(IServiceProvider serviceProvider)
/// <summary>
/// Creates a <see cref="IBrush"/> directly from an SVG path for convenient code usage.
/// </summary>
public static IBrush CreateBrush(
string path,
Uri? baseUri = null,
Stretch? stretch = null,
AlignmentX? alignmentX = null,
AlignmentY? alignmentY = null,
TileMode? tileMode = null,
RelativeRect? destinationRect = null,
RelativeRect? sourceRect = null,
double? opacity = null,
Transform? transform = null,
RelativePoint? transformOrigin = null)
{
if (serviceProvider is null)
if (string.IsNullOrWhiteSpace(path))
{
throw new ArgumentNullException(nameof(serviceProvider));
throw new ArgumentException("Path cannot be null or whitespace.", nameof(path));
}

return CreateBrushCore(
path,
baseUri,
stretch,
alignmentX,
alignmentY,
tileMode,
destinationRect,
sourceRect,
opacity,
transform,
transformOrigin);
}

/// <summary>
/// Creates an <see cref="IBrush"/> instance for use in code-behind.
/// </summary>
/// <param name="serviceProvider">Optional XAML service provider used to resolve relative URIs.</param>
/// <returns>The generated <see cref="IBrush"/>.</returns>
public IBrush ToBrush(IServiceProvider? serviceProvider = null)
{
if (Path is null)
{
throw new InvalidOperationException("SvgBrush requires a non-null Path.");
}

var baseUri = serviceProvider.GetContextBaseUri();
var source = SvgSource.Load(Path, baseUri);
var image = new SvgImage
{
Source = source
};

return CreateFromImage(
image,
return CreateBrushCore(
Path,
ResolveBaseUri(serviceProvider),
Stretch,
AlignmentX,
AlignmentY,
Expand All @@ -178,4 +228,72 @@ public override object ProvideValue(IServiceProvider serviceProvider)
Transform,
TransformOrigin);
}

/// <inheritdoc/>
public override object ProvideValue(IServiceProvider serviceProvider)
{
if (serviceProvider is null)
{
throw new ArgumentNullException(nameof(serviceProvider));
}

return ToBrush(serviceProvider);
}

private static IBrush CreateBrushCore(
string path,
Uri? baseUri,
Stretch? stretch,
AlignmentX? alignmentX,
AlignmentY? alignmentY,
TileMode? tileMode,
RelativeRect? destinationRect,
RelativeRect? sourceRect,
double? opacity,
Transform? transform,
RelativePoint? transformOrigin)
{
var source = SvgSource.Load(path, baseUri);
var image = new SvgImage
{
Source = source
};

return CreateBrush(
image,
stretch,
alignmentX,
alignmentY,
tileMode,
destinationRect,
sourceRect,
opacity,
transform,
transformOrigin);
}

private Uri? ResolveBaseUri(IServiceProvider? serviceProvider)
{
if (BaseUri is { } baseUri)
{
return baseUri;
}

if (serviceProvider is null)
{
return null;
}

return serviceProvider.GetContextBaseUri();
}

public static implicit operator Brush(SvgResourceExtension extension)
{
if (extension is null)
{
throw new ArgumentNullException(nameof(extension));
}

return (Brush)extension.ToBrush();
}
}
2 changes: 1 addition & 1 deletion src/Svg.Controls.Skia.Avalonia/SvgImageExtension.cs
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ public override object ProvideValue(IServiceProvider serviceProvider)

if (typeof(IBrush).IsAssignableFrom(property.PropertyType))
{
return SvgBrush.CreateFromImage(image);
return SvgResourceExtension.CreateBrush(image);
}

return new Image { Source = image };
Expand Down
Loading
Loading