Skip to content

Commit 10220cc

Browse files
authored
Merge pull request #54 from sn4k3/HybridIcon
Implements IImage on MaterialIcon
2 parents 0831029 + 616bc97 commit 10220cc

11 files changed

Lines changed: 274 additions & 69 deletions

File tree

Material.Icons.Avalonia.Demo/Views/MainWindow.axaml

Lines changed: 29 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
11
<Window x:Class="Material.Icons.Avalonia.Demo.Views.MainWindow"
22
xmlns="https://github.com/avaloniaui"
33
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
4-
xmlns:avalonia="clr-namespace:Material.Icons.Avalonia;assembly=Material.Icons.Avalonia"
54
xmlns:controls="clr-namespace:Material.Icons.Avalonia.Demo.Controls"
65
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
6+
xmlns:icon="clr-namespace:Material.Icons.Avalonia;assembly=Material.Icons.Avalonia"
77
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
88
xmlns:models="clr-namespace:Material.Icons.Avalonia.Demo.Models"
99
xmlns:system="clr-namespace:System;assembly=System.Runtime.Extensions"
@@ -34,9 +34,9 @@
3434
<Design.DataContext>
3535
<vm:MainWindowViewModel />
3636
</Design.DataContext>
37-
<Grid ColumnDefinitions="*, *, 100"
37+
<Grid ColumnDefinitions="*, *, 100,Auto,Auto"
3838
RowDefinitions="*, Auto">
39-
<ScrollViewer Grid.ColumnSpan="3"
39+
<ScrollViewer Grid.ColumnSpan="5"
4040
HorizontalScrollBarVisibility="Disabled"
4141
VerticalScrollBarVisibility="Visible">
4242
<ItemsRepeater HorizontalAlignment="Center"
@@ -59,12 +59,12 @@
5959
FontSize="10"
6060
Text="{Binding Kind}" />
6161

62-
<avalonia:MaterialIcon Width="32"
63-
Height="32"
64-
HorizontalAlignment="Center"
65-
VerticalAlignment="Center"
66-
Animation="{Binding $parent[Window].DataContext.Animation}"
67-
Kind="{Binding Kind}" />
62+
<icon:MaterialIcon Width="32"
63+
Height="32"
64+
HorizontalAlignment="Center"
65+
VerticalAlignment="Center"
66+
Animation="{Binding $parent[Window].DataContext.Animation}"
67+
Kind="{Binding Kind}" />
6868
</DockPanel>
6969
</Border>
7070
</controls:SelectionWrapper>
@@ -85,6 +85,25 @@
8585
<ComboBox Grid.Row="1"
8686
Grid.Column="2"
8787
ItemsSource="{Binding Animations, Mode=OneTime}"
88-
SelectedItem="{Binding Animation}"/>
88+
SelectedItem="{Binding Animation}" />
89+
90+
<icon:MaterialIcon Name="RandomIcon"
91+
Grid.Row="1"
92+
Grid.Column="3"
93+
Margin="10,5"
94+
ToolTip.Tip="Random icon" />
95+
96+
<Image Name="RandomImage"
97+
Grid.Row="1"
98+
Grid.Column="4"
99+
Width="16"
100+
Height="16"
101+
Margin="0,5,10,5"
102+
ToolTip.Tip="Random icon image">
103+
<icon:MaterialIcon Name="RandomImageIcon"
104+
Foreground="DeepPink"
105+
Kind="Heart" />
106+
</Image>
107+
89108
</Grid>
90109
</Window>
Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,24 @@
1+
using System;
12
using Avalonia.Controls;
2-
using Avalonia.Diagnostics;
3-
using Avalonia.Input;
4-
using Avalonia.Markup.Xaml;
3+
using Avalonia.Threading;
54

65
namespace Material.Icons.Avalonia.Demo.Views {
76
public partial class MainWindow : Window {
87
public MainWindow() {
98
InitializeComponent();
9+
10+
var values = Enum.GetValues<MaterialIconKind>();
11+
12+
DispatcherTimer.Run(() => {
13+
14+
RandomIcon.Kind = values[Random.Shared.Next(0, values.Length)];
15+
RandomImageIcon.Kind = values[Random.Shared.Next(0, values.Length)];
16+
#if RELEASE
17+
// Without this line, image will not be updated with the new icon
18+
RandomImage.InvalidateVisual();
19+
#endif
20+
return true;
21+
}, TimeSpan.FromSeconds(1));
1022
}
1123
}
1224
}

Material.Icons.Avalonia/MaterialIcon.axaml

Lines changed: 59 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,17 @@
2020
<Setter Property="CornerRadius" Value="10" />
2121
<Setter Property="BorderThickness" Value="2" />
2222
</Style>
23+
<Style Selector=".Images Border">
24+
<Setter Property="Margin" Value="10,0,10,10" />
25+
<Setter Property="Padding" Value="10" />
26+
<Setter Property="BorderBrush" Value="Gray" />
27+
<Setter Property="CornerRadius" Value="10" />
28+
<Setter Property="BorderThickness" Value="2" />
29+
</Style>
30+
<Style Selector=".Images Image">
31+
<Setter Property="Width" Value="24" />
32+
<Setter Property="Height" Value="24" />
33+
</Style>
2334
</Border.Styles>
2435
<StackPanel Orientation="Vertical"
2536
Spacing="10">
@@ -36,8 +47,9 @@
3647
<icon:MaterialIcon Animation="Pulse"
3748
Kind="DotsVertical" />
3849

39-
<icon:MaterialIcon Animation="PulseCcw"
40-
Kind="ProgressHelper" />
50+
<icon:MaterialIconExt Animation="PulseCcw"
51+
IconForeground="Gold"
52+
Kind="ProgressHelper" />
4153

4254
<icon:MaterialIcon Animation="FadeOutIn"
4355
Foreground="DeepPink"
@@ -47,31 +59,53 @@
4759
Kind="Heart" />
4860
</WrapPanel>
4961

62+
<WrapPanel Classes="Images"
63+
Orientation="Horizontal">
64+
<Border>
65+
<Image>
66+
<icon:MaterialIcon Kind="Image" />
67+
</Image>
68+
</Border>
69+
70+
<Border>
71+
<Image Source="{icon:MaterialIconExt Kind=Image}" />
72+
</Border>
73+
74+
<Border>
75+
<Image>
76+
<icon:MaterialIcon Foreground="WhiteSmoke"
77+
Kind="AddAlarm" />
78+
</Image>
79+
</Border>
80+
81+
<Border>
82+
<Image Source="{icon:MaterialIconTextExt Kind=AddAlarm, IconForeground=WhiteSmoke, Text='DEBUG, no effect!', IconSize=512}" />
83+
</Border>
84+
85+
<Border>
86+
<Image Source="{icon:MaterialIconExt Kind=ProgressHelper, IconForeground=Gold, Animation=PulseCcw}" />
87+
</Border>
88+
89+
<Border>
90+
<Image Width="92"
91+
Source="{icon:MaterialIconExt Kind=Heart,
92+
IconForeground=DeepPink}"
93+
Stretch="Fill" />
94+
</Border>
95+
96+
</WrapPanel>
97+
5098
<StackPanel Orientation="Horizontal"
5199
Spacing="20">
52-
<TextBlock>
53-
<StackPanel Orientation="Horizontal"
54-
Spacing="5">
55-
<icon:MaterialIcon Kind="Network" />
56-
<TextBlock Text="127.0.0.1" />
57-
</StackPanel>
58-
</TextBlock>
59-
60-
<TextBlock FontSize="16">
61-
<StackPanel Orientation="Horizontal"
62-
Spacing="5">
63-
<icon:MaterialIcon Kind="Network" />
64-
<TextBlock Text="127.0.0.1 (16px)" />
65-
</StackPanel>
66-
</TextBlock>
100+
<icon:MaterialIconText Kind="Network"
101+
Text="127.0.0.1" />
67102

68-
<TextBlock FontSize="24">
69-
<StackPanel Orientation="Horizontal"
70-
Spacing="5">
71-
<icon:MaterialIcon Kind="Network" />
72-
<TextBlock Text="127.0.0.1 (24px)" />
73-
</StackPanel>
74-
</TextBlock>
103+
<icon:MaterialIconText FontSize="16"
104+
Kind="Network"
105+
Text="127.0.0.1 (16px)" />
106+
<icon:MaterialIconText FontSize="24"
107+
Kind="Network"
108+
Text="127.0.0.1 (24px)" />
75109
</StackPanel>
76110

77111

@@ -178,7 +212,7 @@
178212
BorderThickness="{TemplateBinding BorderThickness}"
179213
CornerRadius="{TemplateBinding CornerRadius}">
180214
<Viewbox Name="PART_IconViewbox">
181-
<Path Data="{TemplateBinding Kind, Converter={StaticResource GeometryConverter}}"
215+
<Path Data="{Binding Drawing.Geometry, RelativeSource={RelativeSource TemplatedParent}}"
182216
Fill="{TemplateBinding Foreground}"
183217
Stretch="Uniform" />
184218
</Viewbox>

Material.Icons.Avalonia/MaterialIcon.axaml.cs

Lines changed: 96 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,13 @@
11
using Avalonia;
22
using Avalonia.Controls.Primitives;
3+
using Avalonia.Interactivity;
4+
using Avalonia.Media;
35

46
namespace Material.Icons.Avalonia {
5-
public class MaterialIcon : TemplatedControl {
7+
8+
public class MaterialIcon : TemplatedControl, IImage {
9+
#region Properties
10+
611
public static readonly StyledProperty<MaterialIconKind> KindProperty
712
= AvaloniaProperty.Register<MaterialIcon, MaterialIconKind>(nameof(Kind));
813

@@ -35,5 +40,95 @@ public MaterialIconAnimation Animation {
3540
get => GetValue(AnimationProperty);
3641
set => SetValue(AnimationProperty, value);
3742
}
43+
44+
public static readonly DirectProperty<MaterialIcon, GeometryDrawing> DrawingProperty =
45+
AvaloniaProperty.RegisterDirect<MaterialIcon, GeometryDrawing>(
46+
nameof(Drawing),
47+
o => o.Drawing);
48+
49+
/// <summary>
50+
/// Gets the <see cref="GeometryDrawing"/> of the icon.
51+
/// </summary>
52+
public GeometryDrawing Drawing { get; } = new();
53+
54+
// Default size for Material Icons
55+
private static readonly Rect DefaultIconBounds = new(0, 0, 24, 24);
56+
57+
#endregion
58+
59+
#region Constructor
60+
61+
public MaterialIcon() {
62+
Drawing.Brush = Foreground;
63+
}
64+
65+
#endregion
66+
67+
#region Overrides
68+
69+
/// <inheritdoc />
70+
protected override void OnLoaded(RoutedEventArgs e) {
71+
if (Drawing.Geometry is null)
72+
SetGeometry();
73+
base.OnLoaded(e);
74+
}
75+
76+
/// <inheritdoc />
77+
protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs e) {
78+
base.OnPropertyChanged(e);
79+
80+
if (e.Property == KindProperty) {
81+
SetGeometry();
82+
}
83+
else if (e.Property == ForegroundProperty) {
84+
Drawing.Brush = Foreground;
85+
}
86+
}
87+
88+
#endregion
89+
90+
#region Methods
91+
92+
/// <summary>
93+
/// Sets the geometry for the drawing based on the specified material icon kind.
94+
/// </summary>
95+
/// <remarks>This method updates the <see cref="Drawing"/> Geometry property by parsing the
96+
/// geometry data associated with the current <see cref="Kind"/> value.
97+
/// </remarks>
98+
private void SetGeometry() {
99+
// TODO: Implement future cache here
100+
Drawing.Geometry = Geometry.Parse(MaterialIconDataProvider.GetData(Kind));
101+
}
102+
103+
#endregion
104+
105+
#region IImage Implementation
106+
107+
/// <inheritdoc/>
108+
Size IImage.Size => DefaultIconBounds.Size;
109+
110+
/// <inheritdoc/>
111+
void IImage.Draw(DrawingContext context, Rect sourceRect, Rect destRect) {
112+
if (Drawing.Geometry is null)
113+
SetGeometry();
114+
115+
var bounds = DefaultIconBounds;
116+
var scale = Matrix.CreateScale(
117+
destRect.Width / sourceRect.Width,
118+
destRect.Height / sourceRect.Height
119+
);
120+
var translate = Matrix.CreateTranslation(
121+
-sourceRect.X + destRect.X - bounds.X,
122+
-sourceRect.Y + destRect.Y - bounds.Y
123+
);
124+
125+
using (context.PushClip(destRect))
126+
using (context.PushTransform(translate * scale)) {
127+
Drawing.Draw(context);
128+
}
129+
}
130+
131+
#endregion
132+
38133
}
39134
}

Material.Icons.Avalonia/MaterialIconExt.cs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
using System;
22
using Avalonia.Markup.Xaml;
3+
using Avalonia.Media;
34

45
namespace Material.Icons.Avalonia {
56
public class MaterialIconExt : MarkupExtension {
@@ -23,6 +24,9 @@ public MaterialIconExt(MaterialIconKind kind, double? iconSize, MaterialIconAnim
2324
[ConstructorArgument("iconSize")]
2425
public double? IconSize { get; set; }
2526

27+
[ConstructorArgument("iconForeground")]
28+
public IBrush? IconForeground { get; set; }
29+
2630
[ConstructorArgument("animation")]
2731
public MaterialIconAnimation Animation { get; set; }
2832

@@ -39,6 +43,10 @@ public override object ProvideValue(IServiceProvider serviceProvider) {
3943
result.IconSize = IconSize.Value;
4044
}
4145

46+
if (IconForeground is not null) {
47+
result.Foreground = IconForeground;
48+
}
49+
4250
if (!string.IsNullOrWhiteSpace(Classes)) {
4351
result.Classes.AddRange(global::Avalonia.Controls.Classes.Parse(Classes!));
4452
}

Material.Icons.Avalonia/MaterialIconTextExt.cs

Lines changed: 11 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -39,20 +39,24 @@ public override object ProvideValue(IServiceProvider serviceProvider) {
3939
if (string.IsNullOrWhiteSpace(Text))
4040
return base.ProvideValue(serviceProvider);
4141

42-
var result = new MaterialIconText();
42+
var result = new MaterialIconText {
43+
Kind = Kind,
44+
Text = Text,
45+
Animation = Animation
46+
};
47+
48+
if (IconSize.HasValue) result.IconSize = IconSize.Value;
49+
if (IconForeground is not null) result.Foreground = IconForeground;
50+
4351
if (Spacing.HasValue) result.Spacing = Spacing.Value;
4452
if (Orientation.HasValue) result.Orientation = Orientation.Value;
4553
if (TextFirst.HasValue) result.TextFirst = TextFirst.Value;
4654
if (IsTextSelectable.HasValue) result.IsTextSelectable = IsTextSelectable.Value;
47-
if (IconSize.HasValue) {
48-
result.IconSize = IconSize.Value;
49-
}
55+
5056
if (!string.IsNullOrWhiteSpace(Classes)) {
5157
result.Classes.AddRange(global::Avalonia.Controls.Classes.Parse(Classes!));
5258
}
53-
result.Kind = Kind;
54-
result.Text = Text;
55-
result.Animation = Animation;
59+
5660
return result;
5761
}
5862
}

0 commit comments

Comments
 (0)