Skip to content

Commit

Permalink
Add SymbolImage to Avalonia
Browse files Browse the repository at this point in the history
  • Loading branch information
davidxuang committed Oct 26, 2024
1 parent 971fa2b commit 1a695e6
Show file tree
Hide file tree
Showing 2 changed files with 175 additions and 0 deletions.
28 changes: 28 additions & 0 deletions FluentIcons.Avalonia/MarkupExtensions/MarkupExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -33,3 +33,31 @@ public SymbolIcon ProvideValue(IServiceProvider serviceProvider)
return icon;
}
}

public class SymbolImageExtension
{
public Symbol? Symbol { get; set; }
public IconVariant? IconVariant { get; set; }
public bool? UseSegoeMetrics { get; set; }
public double? FontSize { get; set; }
public Brush? Foreground { get; set; }

public SymbolImage ProvideValue(IServiceProvider serviceProvider)
{
var image = new SymbolImage();

if (Symbol.HasValue) image.Symbol = Symbol.Value;
if (IconVariant.HasValue) image.IconVariant = IconVariant.Value;
if (UseSegoeMetrics.HasValue) image.UseSegoeMetrics = UseSegoeMetrics.Value;
if (FontSize.HasValue) image.FontSize = FontSize.Value;
if (Foreground is not null) image.Foreground = Foreground;

var service = serviceProvider.GetService(typeof(IProvideValueTarget)) as IProvideValueTarget;
if (service?.TargetObject is Visual source)
{
image.Bind(Visual.FlowDirectionProperty, source.GetBindingObservable(Visual.FlowDirectionProperty));
}

return image;
}
}
147 changes: 147 additions & 0 deletions FluentIcons.Avalonia/SymbolImage.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,147 @@
using System;
using System.ComponentModel;
using System.Globalization;
using Avalonia;
using Avalonia.Controls.Documents;
using Avalonia.Media;
using Avalonia.Media.TextFormatting;
using FluentIcons.Common;
using FluentIcons.Common.Internals;

namespace FluentIcons.Avalonia;

[TypeConverter(typeof(SymbolImageConverter))]
public class SymbolImage : AvaloniaObject, IDisposable, IImage
{
public static readonly StyledProperty<Symbol> SymbolProperty =
SymbolIcon.SymbolProperty.AddOwner<SymbolImage>();
public static readonly StyledProperty<IBrush?> ForegroundProperty =
TextElement.ForegroundProperty.AddOwner<SymbolImage>();
public static readonly StyledProperty<IconVariant> IconVariantProperty =
SymbolIcon.IconVariantProperty.AddOwner<SymbolImage>();
public static readonly StyledProperty<bool> UseSegoeMetricsProperty =
SymbolIcon.UseSegoeMetricsProperty.AddOwner<SymbolImage>();
public static readonly StyledProperty<FlowDirection> FlowDirectionProperty =
Visual.FlowDirectionProperty.AddOwner<SymbolImage>();
public static readonly StyledProperty<double> FontSizeProperty =
SymbolIcon.FontSizeProperty.AddOwner<SymbolImage>();

private TextLayout? _textLayout;

public Symbol Symbol
{
get => GetValue(SymbolProperty);
set => SetValue(SymbolProperty, value);
}

public IBrush? Foreground
{
get => GetValue(ForegroundProperty);
set => SetValue(ForegroundProperty, value);
}

public IconVariant IconVariant
{
get => GetValue(IconVariantProperty);
set => SetValue(IconVariantProperty, value);
}

public bool UseSegoeMetrics
{
get => GetValue(UseSegoeMetricsProperty);
set => SetValue(UseSegoeMetricsProperty, value);
}

public FlowDirection FlowDirection
{
get => GetValue(FlowDirectionProperty);
set => SetValue(FlowDirectionProperty, value);
}

public double FontSize
{
get => GetValue(FontSizeProperty);
set => SetValue(FontSizeProperty, value);
}

public Size Size => new(FontSize, FontSize);

public event EventHandler? VisualChanged;

public void Dispose()
{
_textLayout?.Dispose();
}

protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs change)
{
if (change.Property == FontSizeProperty ||
change.Property == ForegroundProperty ||
change.Property == SymbolProperty ||
change.Property == IconVariantProperty ||
change.Property == UseSegoeMetricsProperty ||
change.Property == FlowDirectionProperty)
{
_textLayout?.Dispose();
_textLayout = new TextLayout(
Symbol.ToString(IconVariant, FlowDirection == FlowDirection.RightToLeft),
UseSegoeMetrics ? SymbolIcon.Seagull : SymbolIcon.System,
FontSize,
Foreground,
TextAlignment.Center,
flowDirection: FlowDirection
);

VisualChanged?.Invoke(this, EventArgs.Empty);
}

base.OnPropertyChanged(change);
}

public void Draw(DrawingContext context, Rect sourceRect, Rect destRect)
{
if (_textLayout is null)
return;

var scale = Matrix.CreateScale(
destRect.Width / sourceRect.Width,
destRect.Height / sourceRect.Height);
var translate = Matrix.CreateTranslation(
-sourceRect.X + destRect.X,
-sourceRect.Y + destRect.Y);
var transform = translate * scale;
if (FlowDirection == FlowDirection.RightToLeft)
transform *= new Matrix(-1, 0, 0, 1, FontSize, 0);

using (context.PushClip(destRect))
using (context.PushTransform(transform))
{
_textLayout.Draw(context, new Point(0, 0));
}
}
}

public class SymbolImageConverter : TypeConverter
{
public override bool CanConvertFrom(ITypeDescriptorContext? context, Type sourceType)
{
if (sourceType == typeof(string) || sourceType == typeof(Symbol))
{
return true;
}
return base.CanConvertFrom(context, sourceType);
}

public override object? ConvertFrom(ITypeDescriptorContext? context, CultureInfo? culture, object value)
{
if (value is string val)
{
return new SymbolImage { Symbol = (Symbol)Enum.Parse(typeof(Symbol), val) };
}
else if (value is Symbol symbol)
{
return new SymbolImage { Symbol = symbol };
}
return base.ConvertFrom(context, culture, value);
}
}

0 comments on commit 1a695e6

Please sign in to comment.