feat: Add Gravatar control and demo.
This commit is contained in:
1 parent
ee5c47a7cd
commit
6c307a6ccc
9 files changed
+329
-17
No files matched your search
@@ -0,0 +1,34 @@
|
||||
<UserControl xmlns="https://github.com/avaloniaui"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:hc="https://handyorg.github.io/handycontrol"
|
||||
xmlns:cv="clr-namespace:HandyControlDemo.Tools.Converter"
|
||||
x:Class="HandyControlDemo.UserControl.GravatarDemo"
|
||||
Background="{DynamicResource RegionBrush}">
|
||||
<UserControl.Resources>
|
||||
<cv:StringToBitmapConverter x:Key="StringToBitmap" />
|
||||
</UserControl.Resources>
|
||||
<TransitioningContentControl>
|
||||
<ScrollViewer>
|
||||
<hc:UniformSpacingPanel Margin="32" ChildWrapping="Wrap" Spacing="16" ItemHorizontalAlignment="Center">
|
||||
<UniformGrid Rows="3" Columns="3" Width="240" Height="240">
|
||||
<hc:Gravatar Id="User1" />
|
||||
<hc:Gravatar Id="User2" />
|
||||
<hc:Gravatar Theme="{StaticResource GravatarCircleImg}">
|
||||
<Image Source="{Binding Source='avares://HandyControlDemo/Resources/Img/Album/2.jpg', Converter={StaticResource StringToBitmap}}" />
|
||||
</hc:Gravatar>
|
||||
<hc:Gravatar Id="User4" />
|
||||
<hc:Gravatar Id="User5" Theme="{StaticResource GravatarCircle}" />
|
||||
<hc:Gravatar Id="User6" />
|
||||
<hc:Gravatar Theme="{StaticResource GravatarCircle}"
|
||||
Source="{Binding Source='avares://HandyControlDemo/Resources/Img/Album/1.jpg', Converter={StaticResource StringToBitmap}}" />
|
||||
<hc:Gravatar Id="User8" />
|
||||
<hc:Gravatar Id="User9" />
|
||||
</UniformGrid>
|
||||
<StackPanel Width="240" Height="220" VerticalAlignment="Center">
|
||||
<TextBox Text="User1" Name="TextBoxName" Width="180" />
|
||||
<hc:Gravatar Height="180" Width="180" Id="{Binding #TextBoxName.Text}" Margin="10" />
|
||||
</StackPanel>
|
||||
</hc:UniformSpacingPanel>
|
||||
</ScrollViewer>
|
||||
</TransitioningContentControl>
|
||||
</UserControl>
|
||||
@@ -0,0 +1,9 @@
|
||||
namespace HandyControlDemo.UserControl;
|
||||
|
||||
public partial class GravatarDemo : Avalonia.Controls.UserControl
|
||||
{
|
||||
public GravatarDemo()
|
||||
{
|
||||
InitializeComponent();
|
||||
}
|
||||
}
|
||||
+12
-16
@@ -23,27 +23,23 @@
|
||||
<Button Command="{Binding RemoveItemCommand}" Content="{x:Static langs:Lang.RemoveItem}" />
|
||||
</hc:ButtonGroup>
|
||||
<Border Grid.Row="1" Padding="0" Theme="{StaticResource BorderRegion}">
|
||||
<ItemsControl HorizontalAlignment="Center"
|
||||
VerticalAlignment="Center"
|
||||
ItemsSource="{Binding DataList}">
|
||||
<ItemsControl.ItemsPanel>
|
||||
<ListBox Background="Transparent"
|
||||
BorderThickness="0"
|
||||
Theme="{StaticResource ListBoxCustom}"
|
||||
ItemsSource="{Binding DataList}">
|
||||
<ListBox.ItemsPanel>
|
||||
<ItemsPanelTemplate>
|
||||
<hc:HoneycombPanel />
|
||||
</ItemsPanelTemplate>
|
||||
</ItemsControl.ItemsPanel>
|
||||
<ItemsControl.ItemTemplate>
|
||||
</ListBox.ItemsPanel>
|
||||
<ListBox.ItemTemplate>
|
||||
<DataTemplate DataType="data:CardModel">
|
||||
<Border Width="60"
|
||||
Height="60"
|
||||
Margin="6"
|
||||
CornerRadius="30"
|
||||
ClipToBounds="True">
|
||||
<Image Source="{Binding Content, Converter={StaticResource StringToBitmap}}"
|
||||
Stretch="UniformToFill" />
|
||||
</Border>
|
||||
<hc:Gravatar Theme="{StaticResource GravatarCircle}"
|
||||
Margin="10"
|
||||
Source="{Binding Content, Converter={StaticResource StringToBitmap}}" />
|
||||
</DataTemplate>
|
||||
</ItemsControl.ItemTemplate>
|
||||
</ItemsControl>
|
||||
</ListBox.ItemTemplate>
|
||||
</ListBox>
|
||||
</Border>
|
||||
</Grid>
|
||||
</TransitioningContentControl>
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
namespace HandyControl.Controls;
|
||||
|
||||
public interface IGravatarGenerator
|
||||
{
|
||||
object? GetGravatar(string id);
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
using Avalonia;
|
||||
using Avalonia.Controls;
|
||||
using Avalonia.Media;
|
||||
using HandyControl.Tools;
|
||||
|
||||
namespace HandyControl.Controls;
|
||||
|
||||
public class Gravatar : ContentControl
|
||||
{
|
||||
public static readonly StyledProperty<IGravatarGenerator> GeneratorProperty =
|
||||
AvaloniaProperty.Register<Gravatar, IGravatarGenerator>(
|
||||
nameof(Generator), defaultValue: new GithubGravatarGenerator());
|
||||
|
||||
public IGravatarGenerator Generator
|
||||
{
|
||||
get => GetValue(GeneratorProperty);
|
||||
set => SetValue(GeneratorProperty, value);
|
||||
}
|
||||
|
||||
public static readonly StyledProperty<string?> IdProperty =
|
||||
AvaloniaProperty.Register<Gravatar, string?>(nameof(Id));
|
||||
|
||||
public string? Id
|
||||
{
|
||||
get => GetValue(IdProperty);
|
||||
set => SetValue(IdProperty, value);
|
||||
}
|
||||
|
||||
public static readonly StyledProperty<IImage?> SourceProperty =
|
||||
AvaloniaProperty.Register<Gravatar, IImage?>(nameof(Source));
|
||||
|
||||
public IImage? Source
|
||||
{
|
||||
get => GetValue(SourceProperty);
|
||||
set => SetValue(SourceProperty, value);
|
||||
}
|
||||
|
||||
static Gravatar()
|
||||
{
|
||||
IdProperty.Changed.AddClassHandler<Gravatar>((o, e) => o.OnIdChanged(e));
|
||||
SourceProperty.Changed.AddClassHandler<Gravatar>((o, e) => o.OnSourceChanged(e));
|
||||
}
|
||||
|
||||
private void OnIdChanged(AvaloniaPropertyChangedEventArgs e)
|
||||
{
|
||||
if (Source != null) return;
|
||||
Content = Generator.GetGravatar((string?) e.NewValue ?? string.Empty);
|
||||
}
|
||||
|
||||
private void OnSourceChanged(AvaloniaPropertyChangedEventArgs e)
|
||||
{
|
||||
var v = (IImage?) e.NewValue;
|
||||
|
||||
Background = v is IImageBrushSource brushSource
|
||||
? new ImageBrush(brushSource) { Stretch = Stretch.UniformToFill }
|
||||
: ResourceHelper.GetResource<IBrush>(Data.ResourceToken.SecondaryRegionBrush);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
<ResourceDictionary xmlns="https://github.com/avaloniaui"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
x:ClassModifier="internal"
|
||||
xmlns:hc="clr-namespace:HandyControl.Controls">
|
||||
|
||||
<ControlTheme x:Key="GravatarBaseStyle"
|
||||
TargetType="hc:Gravatar">
|
||||
<Setter Property="Focusable" Value="False" />
|
||||
<Setter Property="Width" Value="72" />
|
||||
<Setter Property="Height" Value="72" />
|
||||
<Setter Property="Background" Value="{DynamicResource SecondaryRegionBrush}" />
|
||||
<Setter Property="CornerRadius" Value="{StaticResource DefaultCornerRadius}" />
|
||||
<Setter Property="Padding" Value="6" />
|
||||
<Setter Property="Template">
|
||||
<ControlTemplate TargetType="hc:Gravatar">
|
||||
<Border ClipToBounds="True"
|
||||
CornerRadius="{TemplateBinding CornerRadius}"
|
||||
Background="{TemplateBinding Background}"
|
||||
BorderBrush="{TemplateBinding BorderBrush}"
|
||||
BorderThickness="{TemplateBinding BorderThickness}">
|
||||
<ContentPresenter Margin="{TemplateBinding Padding}"
|
||||
Content="{TemplateBinding Content}"
|
||||
ContentTemplate="{TemplateBinding ContentTemplate}" />
|
||||
</Border>
|
||||
</ControlTemplate>
|
||||
</Setter>
|
||||
</ControlTheme>
|
||||
|
||||
<ControlTheme x:Key="{x:Type hc:Gravatar}"
|
||||
BasedOn="{StaticResource GravatarBaseStyle}"
|
||||
TargetType="hc:Gravatar" />
|
||||
|
||||
<ControlTheme x:Key="GravatarCircle"
|
||||
BasedOn="{StaticResource GravatarBaseStyle}"
|
||||
TargetType="hc:Gravatar">
|
||||
<Setter Property="Padding" Value="16" />
|
||||
<Setter Property="Template">
|
||||
<ControlTemplate TargetType="hc:Gravatar">
|
||||
<Border Theme="{StaticResource BorderCircularClip}"
|
||||
Background="{TemplateBinding Background}"
|
||||
BorderBrush="{TemplateBinding BorderBrush}"
|
||||
BorderThickness="{TemplateBinding BorderThickness}">
|
||||
<ContentPresenter Margin="{TemplateBinding Padding}"
|
||||
Content="{TemplateBinding Content}"
|
||||
ContentTemplate="{TemplateBinding ContentTemplate}" />
|
||||
</Border>
|
||||
</ControlTemplate>
|
||||
</Setter>
|
||||
</ControlTheme>
|
||||
|
||||
<ControlTheme x:Key="GravatarCircleImg"
|
||||
BasedOn="{StaticResource GravatarCircle}"
|
||||
TargetType="hc:Gravatar">
|
||||
<Setter Property="Padding" Value="0" />
|
||||
</ControlTheme>
|
||||
|
||||
</ResourceDictionary>
|
||||
@@ -1,4 +1,4 @@
|
||||
<ResourceDictionary xmlns="https://github.com/avaloniaui"
|
||||
<ResourceDictionary xmlns="https://github.com/avaloniaui"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
x:ClassModifier="internal"
|
||||
xmlns:hc="clr-namespace:HandyControl.Controls">
|
||||
@@ -118,4 +118,36 @@
|
||||
<Setter Property="ItemContainerTheme"
|
||||
Value="{StaticResource ListBoxItemBaseStyle.Small}" />
|
||||
</ControlTheme>
|
||||
|
||||
<ControlTheme x:Key="ListBoxItemCustom"
|
||||
TargetType="ListBoxItem">
|
||||
<Setter Property="Padding"
|
||||
Value="0" />
|
||||
<Setter Property="HorizontalContentAlignment"
|
||||
Value="{Binding $parent[ItemsControl].HorizontalContentAlignment}" />
|
||||
<Setter Property="VerticalContentAlignment"
|
||||
Value="Top" />
|
||||
<Setter Property="Background"
|
||||
Value="Transparent" />
|
||||
<Setter Property="BorderBrush"
|
||||
Value="Transparent" />
|
||||
<Setter Property="BorderThickness"
|
||||
Value="0" />
|
||||
<Setter Property="Template">
|
||||
<ControlTemplate>
|
||||
<ContentPresenter Padding="{TemplateBinding Padding}"
|
||||
HorizontalContentAlignment="{TemplateBinding HorizontalContentAlignment}"
|
||||
VerticalContentAlignment="{TemplateBinding VerticalContentAlignment}"
|
||||
Content="{TemplateBinding Content}"
|
||||
ContentTemplate="{TemplateBinding ContentTemplate}" />
|
||||
</ControlTemplate>
|
||||
</Setter>
|
||||
</ControlTheme>
|
||||
|
||||
<ControlTheme x:Key="ListBoxCustom"
|
||||
BasedOn="{StaticResource ListBoxBaseStyle}"
|
||||
TargetType="ListBox">
|
||||
<Setter Property="ItemContainerTheme"
|
||||
Value="{StaticResource ListBoxItemCustom}" />
|
||||
</ControlTheme>
|
||||
</ResourceDictionary>
|
||||
@@ -60,6 +60,7 @@
|
||||
<MergeResourceInclude Source="avares://HandyControl/Themes/Styles/Card.axaml" />
|
||||
<MergeResourceInclude Source="avares://HandyControl/Themes/Styles/ElementGroup.axaml" />
|
||||
<MergeResourceInclude Source="avares://HandyControl/Themes/Styles/ButtonGroup.axaml" />
|
||||
<MergeResourceInclude Source="avares://HandyControl/Themes/Styles/Gravatar.axaml" />
|
||||
</ResourceDictionary.MergedDictionaries>
|
||||
</ResourceDictionary>
|
||||
</Styles.Resources>
|
||||
|
||||
@@ -0,0 +1,119 @@
|
||||
using System;
|
||||
using System.Globalization;
|
||||
using System.Security.Cryptography;
|
||||
using System.Text;
|
||||
using Avalonia;
|
||||
using Avalonia.Controls.Shapes;
|
||||
using Avalonia.Layout;
|
||||
using Avalonia.Media;
|
||||
using HandyControl.Controls;
|
||||
|
||||
namespace HandyControl.Tools;
|
||||
|
||||
public class GithubGravatarGenerator : IGravatarGenerator
|
||||
{
|
||||
private const int RenderDataMaxLength = 15;
|
||||
|
||||
public object? GetGravatar(string id)
|
||||
{
|
||||
if (string.IsNullOrEmpty(id)) return null;
|
||||
|
||||
var hashcode = GetHashCode(id);
|
||||
var renderData = GetRenderData(hashcode);
|
||||
var renderBrush = GetRenderBrush(hashcode);
|
||||
var geometryGroup = new GeometryGroup();
|
||||
|
||||
void AddRec(int i, int j, bool hidden = false)
|
||||
{
|
||||
var rec = new RectangleGeometry(new Rect(new Point(i, j), hidden ? new Size() : new Size(1, 1)));
|
||||
geometryGroup.Children.Add(rec);
|
||||
}
|
||||
|
||||
var index = 0;
|
||||
for (var i = 0; i < 2; i++)
|
||||
{
|
||||
for (var j = 0; j < 5; j++, index++)
|
||||
{
|
||||
AddRec(i, j, renderData[index] == 0);
|
||||
}
|
||||
}
|
||||
|
||||
for (var j = 0; j < 5; j++, index++)
|
||||
{
|
||||
AddRec(2, j, renderData[index] == 0);
|
||||
}
|
||||
|
||||
index -= 10;
|
||||
for (var i = 3; i < 5; i++)
|
||||
{
|
||||
for (var j = 0; j < 5; j++, index++)
|
||||
{
|
||||
AddRec(i, j, renderData[index] == 0);
|
||||
}
|
||||
index -= 10;
|
||||
}
|
||||
|
||||
var path = new Path
|
||||
{
|
||||
Data = geometryGroup,
|
||||
Fill = renderBrush,
|
||||
VerticalAlignment = VerticalAlignment.Top,
|
||||
Stretch = Stretch.Uniform
|
||||
};
|
||||
|
||||
return path;
|
||||
}
|
||||
|
||||
private string GetHashCode(string id)
|
||||
{
|
||||
var bytes = Encoding.ASCII.GetBytes(id);
|
||||
var hash = MD5.HashData(bytes);
|
||||
var stringBuilder = new StringBuilder();
|
||||
foreach (var item in hash)
|
||||
{
|
||||
stringBuilder.Append(item.ToString("X2"));
|
||||
}
|
||||
return stringBuilder.ToString();
|
||||
}
|
||||
|
||||
private int[] GetRenderData(string hashcode)
|
||||
{
|
||||
var arr = new int[RenderDataMaxLength];
|
||||
for (var i = 0; i < RenderDataMaxLength; i++)
|
||||
{
|
||||
var c = hashcode[i];
|
||||
var v = (int) c;
|
||||
arr[i] = v % 2;
|
||||
}
|
||||
|
||||
return arr;
|
||||
}
|
||||
|
||||
private IBrush GetRenderBrush(string hashcode)
|
||||
{
|
||||
var v = (double) int.Parse(hashcode.Substring(hashcode.Length - 7), NumberStyles.HexNumber);
|
||||
var scale = v / 0xfffffff;
|
||||
var color = Hsl2Rgb(scale);
|
||||
return new SolidColorBrush(color);
|
||||
}
|
||||
|
||||
//adapted from https://github.com/stewartlord/identicon.js/blob/4fad7cafec1b7a4d896015e084e861625ef5d64f/identicon.js#L110
|
||||
private Color Hsl2Rgb(double h, double s = 0.7, double b = 0.5)
|
||||
{
|
||||
h *= 6;
|
||||
var arr = new[]
|
||||
{
|
||||
b += s *= b < .5 ? b : 1 - b,
|
||||
b - h % 1 * s * 2,
|
||||
b -= s *= 2,
|
||||
b,
|
||||
b + h % 1 * s,
|
||||
b + s
|
||||
};
|
||||
|
||||
var hValue = (int) Math.Floor(h);
|
||||
|
||||
return Color.FromRgb((byte) (arr[hValue % 6] * 255), (byte) (arr[(hValue | 16) % 6] * 255),
|
||||
(byte) (arr[(hValue | 8) % 6] * 255));
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user