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>
|
||||
<OutputType>WinExe</OutputType>
|
||||
<!--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="Resources\Img\cloud.png" />
|
||||
<None Remove="Resources\Img\icon.ico" />
|
||||
<None Remove="Resources\Img\Avatar\*.png" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<EmbeddedResource Include="Data\DemoInfo.json" />
|
||||
<AvaloniaResource Include="Resources\Img\cloud.png" />
|
||||
<AvaloniaResource Include="Resources\Img\icon.ico" />
|
||||
<AvaloniaResource Include="Resources\Img\Avatar\*.png" />
|
||||
</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.AddTransient<MainViewModel>();
|
||||
services.AddTransient<InputElementDemoViewModel>();
|
||||
services.AddTransient<HoneycombPanelDemoViewModel>();
|
||||
|
||||
_serviceProvider = services.BuildServiceProvider();
|
||||
}
|
||||
@@ -27,4 +28,6 @@ public class ViewModelLocator
|
||||
public MainViewModel Main => _serviceProvider.GetService<MainViewModel>()!;
|
||||
|
||||
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