feat: Add CirclePanel control and demo.
Introduce a new CirclePanel control that arranges visible children evenly around a circle. The control exposes Diameter, KeepVertical and OffsetAngle styled properties and implements MeasureOverride/ArrangeOverride to position and optionally rotate items on a circular path. Also add a demo UserControl (CirclePanelDemo.axaml and .axaml.cs) with a themed button template to showcase the panel.
This commit is contained in:
1 parent
22cbc47156
commit
d1944b01c3
3 files changed
+158
No files matched your search
@@ -0,0 +1,49 @@
|
||||
<UserControl xmlns="https://github.com/avaloniaui"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:hc="https://handyorg.github.io/handycontrol"
|
||||
x:Class="HandyControlDemo.UserControl.CirclePanelDemo">
|
||||
<UserControl.Resources>
|
||||
<ControlTheme x:Key="CirclePanelButton" TargetType="Button">
|
||||
<Setter Property="UseLayoutRounding" Value="False" />
|
||||
<Setter Property="Height" Value="77" />
|
||||
<Setter Property="Width" Value="36.06" />
|
||||
<Setter Property="Background" Value="Transparent" />
|
||||
<Setter Property="BorderThickness" Value="0" />
|
||||
<Setter Property="Padding" Value="0" />
|
||||
<Setter Property="Template">
|
||||
<ControlTemplate TargetType="Button">
|
||||
<Panel Width="36.06" Height="77">
|
||||
<Path Data="{StaticResource CirclePanelDemoGeometry}"
|
||||
Fill="{DynamicResource PrimaryBrush}"
|
||||
Stretch="Fill" />
|
||||
<Path Data="{StaticResource CirclePanelRightGeometry}"
|
||||
Fill="White"
|
||||
Stretch="Uniform"
|
||||
Height="21"
|
||||
HorizontalAlignment="Center"
|
||||
VerticalAlignment="Center" />
|
||||
</Panel>
|
||||
</ControlTemplate>
|
||||
</Setter>
|
||||
<Style Selector="^:pointerover">
|
||||
<Setter Property="Opacity" Value=".9" />
|
||||
</Style>
|
||||
<Style Selector="^:pressed">
|
||||
<Setter Property="Opacity" Value=".6" />
|
||||
</Style>
|
||||
<Style Selector="^:disabled">
|
||||
<Setter Property="Opacity" Value="0.4" />
|
||||
</Style>
|
||||
</ControlTheme>
|
||||
</UserControl.Resources>
|
||||
<hc:CirclePanel Margin="64" Diameter="170">
|
||||
<Button Theme="{StaticResource CirclePanelButton}" />
|
||||
<Button Theme="{StaticResource CirclePanelButton}" />
|
||||
<Button Theme="{StaticResource CirclePanelButton}" />
|
||||
<Button Theme="{StaticResource CirclePanelButton}" />
|
||||
<Button Theme="{StaticResource CirclePanelButton}" />
|
||||
<Button Theme="{StaticResource CirclePanelButton}" />
|
||||
<Button Theme="{StaticResource CirclePanelButton}" />
|
||||
<Button Theme="{StaticResource CirclePanelButton}" />
|
||||
</hc:CirclePanel>
|
||||
</UserControl>
|
||||
@@ -0,0 +1,9 @@
|
||||
namespace HandyControlDemo.UserControl;
|
||||
|
||||
public partial class CirclePanelDemo : Avalonia.Controls.UserControl
|
||||
{
|
||||
public CirclePanelDemo()
|
||||
{
|
||||
InitializeComponent();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,100 @@
|
||||
using System;
|
||||
using System.Linq;
|
||||
using Avalonia;
|
||||
using Avalonia.Controls;
|
||||
using Avalonia.Layout;
|
||||
using Avalonia.Media;
|
||||
|
||||
namespace HandyControl.Controls;
|
||||
|
||||
public class CirclePanel : Panel
|
||||
{
|
||||
public static readonly StyledProperty<double> DiameterProperty =
|
||||
AvaloniaProperty.Register<CirclePanel, double>(nameof(Diameter), defaultValue: 170.0);
|
||||
|
||||
public double Diameter
|
||||
{
|
||||
get => GetValue(DiameterProperty);
|
||||
set => SetValue(DiameterProperty, value);
|
||||
}
|
||||
|
||||
public static readonly StyledProperty<bool> KeepVerticalProperty =
|
||||
AvaloniaProperty.Register<CirclePanel, bool>(nameof(KeepVertical));
|
||||
|
||||
public bool KeepVertical
|
||||
{
|
||||
get => GetValue(KeepVerticalProperty);
|
||||
set => SetValue(KeepVerticalProperty, value);
|
||||
}
|
||||
|
||||
public static readonly StyledProperty<double> OffsetAngleProperty =
|
||||
AvaloniaProperty.Register<CirclePanel, double>(nameof(OffsetAngle), defaultValue: 0d);
|
||||
|
||||
public double OffsetAngle
|
||||
{
|
||||
get => GetValue(OffsetAngleProperty);
|
||||
set => SetValue(OffsetAngleProperty, value);
|
||||
}
|
||||
|
||||
static CirclePanel()
|
||||
{
|
||||
AffectsMeasure<CirclePanel>(DiameterProperty, KeepVerticalProperty, OffsetAngleProperty);
|
||||
}
|
||||
|
||||
protected override Size MeasureOverride(Size availableSize)
|
||||
{
|
||||
var diameter = Diameter;
|
||||
var layoutChildren = Children.Where(element => element.IsVisible).ToList();
|
||||
|
||||
if (layoutChildren.Count == 0) return new Size(diameter, diameter);
|
||||
|
||||
var newSize = new Size(diameter, diameter);
|
||||
|
||||
foreach (var element in layoutChildren)
|
||||
{
|
||||
element.Measure(newSize);
|
||||
}
|
||||
|
||||
return newSize;
|
||||
}
|
||||
|
||||
protected override Size ArrangeOverride(Size finalSize)
|
||||
{
|
||||
var keepVertical = KeepVertical;
|
||||
var offsetAngle = OffsetAngle;
|
||||
var layoutChildren = Children.Where(element => element.IsVisible).ToList();
|
||||
|
||||
if (layoutChildren.Count == 0)
|
||||
{
|
||||
return finalSize;
|
||||
}
|
||||
|
||||
var i = 0;
|
||||
var perDeg = 360.0 / layoutChildren.Count;
|
||||
var radius = Diameter / 2;
|
||||
|
||||
foreach (var element in layoutChildren)
|
||||
{
|
||||
var centerX = element.DesiredSize.Width / 2.0;
|
||||
var centerY = element.DesiredSize.Height / 2.0;
|
||||
var angle = perDeg * i++ + offsetAngle;
|
||||
|
||||
element.RenderTransformOrigin = new RelativePoint(centerX, centerY, RelativeUnit.Absolute);
|
||||
element.RenderTransform = new RotateTransform
|
||||
{
|
||||
Angle = keepVertical ? 0 : angle
|
||||
};
|
||||
|
||||
var r = Math.PI * angle / 180.0;
|
||||
var x = radius * Math.Cos(r);
|
||||
var y = radius * Math.Sin(r);
|
||||
|
||||
var rectX = x + finalSize.Width / 2 - centerX;
|
||||
var rectY = y + finalSize.Height / 2 - centerY;
|
||||
|
||||
element.Arrange(new Rect(rectX, rectY, element.DesiredSize.Width, element.DesiredSize.Height));
|
||||
}
|
||||
|
||||
return finalSize;
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user