feat: Add HoneycombPanel and demo.
This commit is contained in:
1 parent
d1944b01c3
commit
e7005d09b5
16 files changed
+263
-1
No files matched your search
@@ -1,4 +1,4 @@
|
|||||||
<Project Sdk="Microsoft.NET.Sdk">
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<OutputType>WinExe</OutputType>
|
<OutputType>WinExe</OutputType>
|
||||||
<!--If you are willing to use Windows/MacOS native APIs you will need to create 3 projects.
|
<!--If you are willing to use Windows/MacOS native APIs you will need to create 3 projects.
|
||||||
@@ -19,12 +19,14 @@
|
|||||||
<None Remove="Data\DemoInfo.json" />
|
<None Remove="Data\DemoInfo.json" />
|
||||||
<None Remove="Resources\Img\cloud.png" />
|
<None Remove="Resources\Img\cloud.png" />
|
||||||
<None Remove="Resources\Img\icon.ico" />
|
<None Remove="Resources\Img\icon.ico" />
|
||||||
|
<None Remove="Resources\Img\Avatar\*.png" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<EmbeddedResource Include="Data\DemoInfo.json" />
|
<EmbeddedResource Include="Data\DemoInfo.json" />
|
||||||
<AvaloniaResource Include="Resources\Img\cloud.png" />
|
<AvaloniaResource Include="Resources\Img\cloud.png" />
|
||||||
<AvaloniaResource Include="Resources\Img\icon.ico" />
|
<AvaloniaResource Include="Resources\Img\icon.ico" />
|
||||||
|
<AvaloniaResource Include="Resources\Img\Avatar\*.png" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
|||||||
Binary file not shown.
|
After Width: | Height: | Size: 693 B |
Binary file not shown.
|
After Width: | Height: | Size: 966 B |
Binary file not shown.
|
After Width: | Height: | Size: 878 B |
Binary file not shown.
|
After Width: | Height: | Size: 927 B |
Binary file not shown.
|
After Width: | Height: | Size: 995 B |
Binary file not shown.
|
After Width: | Height: | Size: 726 B |
File renamed without changes.
File renamed without changes.
@@ -0,0 +1,40 @@
|
|||||||
|
<UserControl xmlns="https://github.com/avaloniaui"
|
||||||
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||||
|
xmlns:hc="https://handyorg.github.io/handycontrol"
|
||||||
|
xmlns:vm="clr-namespace:HandyControlDemo.ViewModel"
|
||||||
|
x:Class="HandyControlDemo.UserControl.HoneycombPanelDemo"
|
||||||
|
DataContext="{Binding HoneycombPanelDemo, Source={x:Static vm:ViewModelLocator.Instance}}">
|
||||||
|
<Grid Margin="32">
|
||||||
|
<Grid.RowDefinitions>
|
||||||
|
<RowDefinition Height="Auto" />
|
||||||
|
<RowDefinition />
|
||||||
|
</Grid.RowDefinitions>
|
||||||
|
<StackPanel Orientation="Horizontal"
|
||||||
|
Spacing="8"
|
||||||
|
Margin="0,0,0,16">
|
||||||
|
<Button Command="{Binding AddItemCommand}" Content="Add" />
|
||||||
|
<Button Command="{Binding RemoveItemCommand}" Content="Remove" />
|
||||||
|
</StackPanel>
|
||||||
|
<ItemsControl Grid.Row="1"
|
||||||
|
HorizontalAlignment="Center"
|
||||||
|
VerticalAlignment="Center"
|
||||||
|
ItemsSource="{Binding DataList}">
|
||||||
|
<ItemsControl.ItemsPanel>
|
||||||
|
<ItemsPanelTemplate>
|
||||||
|
<hc:HoneycombPanel />
|
||||||
|
</ItemsPanelTemplate>
|
||||||
|
</ItemsControl.ItemsPanel>
|
||||||
|
<ItemsControl.ItemTemplate>
|
||||||
|
<DataTemplate>
|
||||||
|
<Border Width="60"
|
||||||
|
Height="60"
|
||||||
|
Margin="6"
|
||||||
|
CornerRadius="30"
|
||||||
|
ClipToBounds="True">
|
||||||
|
<Image Source="{Binding}" Stretch="UniformToFill" />
|
||||||
|
</Border>
|
||||||
|
</DataTemplate>
|
||||||
|
</ItemsControl.ItemTemplate>
|
||||||
|
</ItemsControl>
|
||||||
|
</Grid>
|
||||||
|
</UserControl>
|
||||||
+9
@@ -0,0 +1,9 @@
|
|||||||
|
namespace HandyControlDemo.UserControl;
|
||||||
|
|
||||||
|
public partial class HoneycombPanelDemo : Avalonia.Controls.UserControl
|
||||||
|
{
|
||||||
|
public HoneycombPanelDemo()
|
||||||
|
{
|
||||||
|
InitializeComponent();
|
||||||
|
}
|
||||||
|
}
|
||||||
File renamed without changes.
File renamed without changes.
+42
@@ -0,0 +1,42 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.ObjectModel;
|
||||||
|
using Avalonia.Media.Imaging;
|
||||||
|
using Avalonia.Platform;
|
||||||
|
using CommunityToolkit.Mvvm.ComponentModel;
|
||||||
|
using CommunityToolkit.Mvvm.Input;
|
||||||
|
|
||||||
|
namespace HandyControlDemo.ViewModel;
|
||||||
|
|
||||||
|
public partial class HoneycombPanelDemoViewModel : ObservableObject
|
||||||
|
{
|
||||||
|
private static readonly Random Random = new();
|
||||||
|
|
||||||
|
[ObservableProperty] private ObservableCollection<Bitmap> _dataList = [];
|
||||||
|
|
||||||
|
public HoneycombPanelDemoViewModel()
|
||||||
|
{
|
||||||
|
for (int i = 0; i < 7; i++)
|
||||||
|
{
|
||||||
|
DataList.Add(LoadRandomAvatar());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[RelayCommand]
|
||||||
|
private void AddItem() => DataList.Insert(0, LoadRandomAvatar());
|
||||||
|
|
||||||
|
[RelayCommand]
|
||||||
|
private void RemoveItem()
|
||||||
|
{
|
||||||
|
if (DataList.Count > 0)
|
||||||
|
{
|
||||||
|
DataList.RemoveAt(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Bitmap LoadRandomAvatar()
|
||||||
|
{
|
||||||
|
var uri = new Uri($"avares://HandyControlDemo/Resources/Img/Avatar/avatar{Random.Next(1, 7)}.png");
|
||||||
|
using var stream = AssetLoader.Open(uri);
|
||||||
|
return new Bitmap(stream);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -20,6 +20,7 @@ public class ViewModelLocator
|
|||||||
services.AddSingleton<DataService>();
|
services.AddSingleton<DataService>();
|
||||||
services.AddTransient<MainViewModel>();
|
services.AddTransient<MainViewModel>();
|
||||||
services.AddTransient<InputElementDemoViewModel>();
|
services.AddTransient<InputElementDemoViewModel>();
|
||||||
|
services.AddTransient<HoneycombPanelDemoViewModel>();
|
||||||
|
|
||||||
_serviceProvider = services.BuildServiceProvider();
|
_serviceProvider = services.BuildServiceProvider();
|
||||||
}
|
}
|
||||||
@@ -27,4 +28,6 @@ public class ViewModelLocator
|
|||||||
public MainViewModel Main => _serviceProvider.GetService<MainViewModel>()!;
|
public MainViewModel Main => _serviceProvider.GetService<MainViewModel>()!;
|
||||||
|
|
||||||
public InputElementDemoViewModel InputElementDemo => _serviceProvider.GetService<InputElementDemoViewModel>()!;
|
public InputElementDemoViewModel InputElementDemo => _serviceProvider.GetService<InputElementDemoViewModel>()!;
|
||||||
|
|
||||||
|
public HoneycombPanelDemoViewModel HoneycombPanelDemo => _serviceProvider.GetService<HoneycombPanelDemoViewModel>()!;
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,166 @@
|
|||||||
|
using System;
|
||||||
|
using System.Linq;
|
||||||
|
using Avalonia;
|
||||||
|
using Avalonia.Controls;
|
||||||
|
|
||||||
|
namespace HandyControl.Controls;
|
||||||
|
|
||||||
|
public class HoneycombPanel : Panel
|
||||||
|
{
|
||||||
|
private double _unitLength;
|
||||||
|
|
||||||
|
private HoneycombStuffer? _stuffer;
|
||||||
|
|
||||||
|
private static int GetXCount(int count)
|
||||||
|
{
|
||||||
|
if (count == 0) return 0;
|
||||||
|
|
||||||
|
count -= 1;
|
||||||
|
|
||||||
|
var index = (int) Math.Floor(Math.Pow((12.0 * count + 25) / 36, 0.5) - 5.0 / 6);
|
||||||
|
var valeIndex = 3 * index * index + 5 * index;
|
||||||
|
var centerValue = valeIndex + 2;
|
||||||
|
|
||||||
|
return count >= centerValue
|
||||||
|
? 4 * index + 6
|
||||||
|
: count > valeIndex
|
||||||
|
? 4 * index + 4
|
||||||
|
: 4 * index + 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static int GetYCount(int count)
|
||||||
|
{
|
||||||
|
if (count == 0) return 0;
|
||||||
|
|
||||||
|
count -= 1;
|
||||||
|
|
||||||
|
var index = (int) Math.Floor(Math.Pow(count / 3.0 + 0.25, 0.5) - 0.5);
|
||||||
|
var valeIndex = 3 * index * index + 3 * index;
|
||||||
|
|
||||||
|
return count > valeIndex
|
||||||
|
? 2 * index + 2
|
||||||
|
: 2 * index;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* layout order
|
||||||
|
*
|
||||||
|
* ● ●
|
||||||
|
* (7) (8)
|
||||||
|
*
|
||||||
|
* ● ● ●
|
||||||
|
* (6) (1) (9)
|
||||||
|
*
|
||||||
|
* ● ● ● ...
|
||||||
|
* (5) (0) (2)
|
||||||
|
*
|
||||||
|
* ● ● ...
|
||||||
|
* (4) (3)
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
protected override Size MeasureOverride(Size availableSize)
|
||||||
|
{
|
||||||
|
var maxSize = new Size();
|
||||||
|
var layoutChildren = Children.Where(child => child.IsVisible).ToList();
|
||||||
|
|
||||||
|
foreach (var child in layoutChildren)
|
||||||
|
{
|
||||||
|
child.Measure(availableSize);
|
||||||
|
maxSize = new Size(
|
||||||
|
Math.Max(maxSize.Width, child.DesiredSize.Width),
|
||||||
|
Math.Max(maxSize.Height, child.DesiredSize.Height));
|
||||||
|
}
|
||||||
|
|
||||||
|
_unitLength = Math.Max(maxSize.Width, maxSize.Height) / 2;
|
||||||
|
|
||||||
|
var xCount = GetXCount(layoutChildren.Count);
|
||||||
|
var yCount = GetYCount(layoutChildren.Count);
|
||||||
|
|
||||||
|
var availableWidth = xCount * _unitLength;
|
||||||
|
var availableHeight = yCount * Math.Pow(3, 0.5) * _unitLength + _unitLength * 2;
|
||||||
|
|
||||||
|
return new Size(availableWidth, availableHeight);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override Size ArrangeOverride(Size finalSize)
|
||||||
|
{
|
||||||
|
var childLength = _unitLength * 2;
|
||||||
|
_stuffer = new HoneycombStuffer(new Rect(finalSize.Width / 2 - _unitLength,
|
||||||
|
finalSize.Height / 2 - _unitLength, childLength, childLength));
|
||||||
|
|
||||||
|
foreach (var child in Children)
|
||||||
|
{
|
||||||
|
if (!child.IsVisible) continue;
|
||||||
|
child.Arrange(_stuffer.Move());
|
||||||
|
}
|
||||||
|
|
||||||
|
return finalSize;
|
||||||
|
}
|
||||||
|
|
||||||
|
private class HoneycombStuffer
|
||||||
|
{
|
||||||
|
private int _turns;
|
||||||
|
|
||||||
|
private int _maxIndex;
|
||||||
|
|
||||||
|
private int _currentIndex = -1;
|
||||||
|
|
||||||
|
private readonly double _offsetX;
|
||||||
|
|
||||||
|
private readonly double _offsetY;
|
||||||
|
|
||||||
|
private Rect _childBounds;
|
||||||
|
|
||||||
|
private readonly double[] _offsetXArr;
|
||||||
|
|
||||||
|
private readonly double[] _offsetYArr;
|
||||||
|
|
||||||
|
public HoneycombStuffer(Rect childBounds)
|
||||||
|
{
|
||||||
|
_childBounds = childBounds;
|
||||||
|
_offsetX = childBounds.Width / 2;
|
||||||
|
_offsetY = Math.Pow(3, 0.5) * _offsetX;
|
||||||
|
|
||||||
|
_offsetXArr = new[]
|
||||||
|
{
|
||||||
|
2 * _offsetX,
|
||||||
|
_offsetX,
|
||||||
|
-_offsetX,
|
||||||
|
-2 * _offsetX,
|
||||||
|
-_offsetX,
|
||||||
|
_offsetX
|
||||||
|
};
|
||||||
|
|
||||||
|
_offsetYArr = new[]
|
||||||
|
{
|
||||||
|
0,
|
||||||
|
_offsetY,
|
||||||
|
_offsetY,
|
||||||
|
0,
|
||||||
|
-_offsetY,
|
||||||
|
-_offsetY
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public Rect Move()
|
||||||
|
{
|
||||||
|
_currentIndex++;
|
||||||
|
if (_currentIndex > _maxIndex)
|
||||||
|
{
|
||||||
|
_turns++;
|
||||||
|
_maxIndex = _turns * 6 - 1;
|
||||||
|
_currentIndex = 0;
|
||||||
|
_childBounds = _childBounds.Translate(new Vector(_offsetX, -_offsetY));
|
||||||
|
return _childBounds;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_turns > 0)
|
||||||
|
{
|
||||||
|
var index = _currentIndex / _turns;
|
||||||
|
_childBounds = _childBounds.Translate(new Vector(_offsetXArr[index], _offsetYArr[index]));
|
||||||
|
}
|
||||||
|
|
||||||
|
return _childBounds;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in new issue
Block a user