feat: Add WaterfallPanel control and demo.
Introduce a new WaterfallPanel control for Avalonia and a demo UserControl showcasing its usage. The panel supports Groups, AutoGroup, DesiredLength (coerced to >= 0) and Orientation; AutoGroup computes columns/rows based on DesiredLength. Measure/Arrange logic distributes children into the shortest group (waterfall layout) and sizes items by dividing the primary axis by group count. Also add WaterfallPanelDemo.axaml and code-behind demonstrating horizontal/vertical examples with a NumericUpDown to adjust groups.
This commit is contained in:
1 parent
3f384cb0c2
commit
22cbc47156
3 files changed
+299
No files matched your search
@@ -0,0 +1,132 @@
|
||||
<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.WaterfallPanelDemo">
|
||||
<Grid Margin="32">
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto"/>
|
||||
<RowDefinition/>
|
||||
</Grid.RowDefinitions>
|
||||
<NumericUpDown x:Name="UpDownGroups"
|
||||
HorizontalAlignment="Center"
|
||||
Value="2"
|
||||
Minimum="1"
|
||||
Maximum="3"
|
||||
Width="200"
|
||||
Theme="{StaticResource NumericUpDownExtend}"
|
||||
hc:TitleElement.Title="Groups"
|
||||
hc:TitleElement.TitleWidth="50"
|
||||
hc:TitleElement.TitlePlacement="Left"/>
|
||||
<hc:UniformSpacingPanel Grid.Row="1"
|
||||
Spacing="16"
|
||||
ChildWrapping="Wrap"
|
||||
Margin="0,16,0,0">
|
||||
<ScrollViewer MaxHeight="300">
|
||||
<hc:WaterfallPanel VerticalAlignment="Center"
|
||||
Width="300"
|
||||
Groups="{Binding #UpDownGroups.Value}">
|
||||
<Border Height="100"
|
||||
Background="{DynamicResource PrimaryBrush}"
|
||||
Effect="{StaticResource EffectShadow1}"
|
||||
Margin="5">
|
||||
<TextBlock Text="Content"
|
||||
VerticalAlignment="Center"
|
||||
HorizontalAlignment="Center"
|
||||
Foreground="White"/>
|
||||
</Border>
|
||||
<Border Height="140"
|
||||
Background="{DynamicResource DangerBrush}"
|
||||
Effect="{StaticResource EffectShadow1}"
|
||||
Margin="5">
|
||||
<TextBlock Text="Content"
|
||||
VerticalAlignment="Center"
|
||||
HorizontalAlignment="Center"
|
||||
Foreground="White"/>
|
||||
</Border>
|
||||
<Border Height="100"
|
||||
Background="{DynamicResource SuccessBrush}"
|
||||
Effect="{StaticResource EffectShadow1}"
|
||||
Margin="5">
|
||||
<TextBlock Text="Content"
|
||||
VerticalAlignment="Center"
|
||||
HorizontalAlignment="Center"
|
||||
Foreground="White"/>
|
||||
</Border>
|
||||
<Border Height="170"
|
||||
Background="{DynamicResource InfoBrush}"
|
||||
Effect="{StaticResource EffectShadow1}"
|
||||
Margin="5">
|
||||
<TextBlock Text="Content"
|
||||
VerticalAlignment="Center"
|
||||
HorizontalAlignment="Center"
|
||||
Foreground="White"/>
|
||||
</Border>
|
||||
<Border Height="100"
|
||||
Background="{DynamicResource WarningBrush}"
|
||||
Effect="{StaticResource EffectShadow1}"
|
||||
Margin="5">
|
||||
<TextBlock Text="Content"
|
||||
VerticalAlignment="Center"
|
||||
HorizontalAlignment="Center"
|
||||
Foreground="White"/>
|
||||
</Border>
|
||||
</hc:WaterfallPanel>
|
||||
</ScrollViewer>
|
||||
<ScrollViewer HorizontalScrollBarVisibility="Auto"
|
||||
VerticalScrollBarVisibility="Hidden"
|
||||
hc:ScrollViewerAttach.Orientation="Horizontal"
|
||||
MaxWidth="300">
|
||||
<hc:WaterfallPanel VerticalAlignment="Center"
|
||||
Height="300"
|
||||
Orientation="Vertical"
|
||||
Groups="{Binding #UpDownGroups.Value}">
|
||||
<Border Width="100"
|
||||
Background="{DynamicResource PrimaryBrush}"
|
||||
Effect="{StaticResource EffectShadow1}"
|
||||
Margin="5">
|
||||
<TextBlock Text="Content"
|
||||
VerticalAlignment="Center"
|
||||
HorizontalAlignment="Center"
|
||||
Foreground="White"/>
|
||||
</Border>
|
||||
<Border Width="140"
|
||||
Background="{DynamicResource DangerBrush}"
|
||||
Effect="{StaticResource EffectShadow1}"
|
||||
Margin="5">
|
||||
<TextBlock Text="Content"
|
||||
VerticalAlignment="Center"
|
||||
HorizontalAlignment="Center"
|
||||
Foreground="White"/>
|
||||
</Border>
|
||||
<Border Width="100"
|
||||
Background="{DynamicResource SuccessBrush}"
|
||||
Effect="{StaticResource EffectShadow1}"
|
||||
Margin="5">
|
||||
<TextBlock Text="Content"
|
||||
VerticalAlignment="Center"
|
||||
HorizontalAlignment="Center"
|
||||
Foreground="White"/>
|
||||
</Border>
|
||||
<Border Width="170"
|
||||
Background="{DynamicResource InfoBrush}"
|
||||
Effect="{StaticResource EffectShadow1}"
|
||||
Margin="5">
|
||||
<TextBlock Text="Content"
|
||||
VerticalAlignment="Center"
|
||||
HorizontalAlignment="Center"
|
||||
Foreground="White"/>
|
||||
</Border>
|
||||
<Border Width="100"
|
||||
Background="{DynamicResource WarningBrush}"
|
||||
Effect="{StaticResource EffectShadow1}"
|
||||
Margin="5">
|
||||
<TextBlock Text="Content"
|
||||
VerticalAlignment="Center"
|
||||
HorizontalAlignment="Center"
|
||||
Foreground="White"/>
|
||||
</Border>
|
||||
</hc:WaterfallPanel>
|
||||
</ScrollViewer>
|
||||
</hc:UniformSpacingPanel>
|
||||
</Grid>
|
||||
</UserControl>
|
||||
@@ -0,0 +1,9 @@
|
||||
namespace HandyControlDemo.UserControl;
|
||||
|
||||
public partial class WaterfallPanelDemo : Avalonia.Controls.UserControl
|
||||
{
|
||||
public WaterfallPanelDemo()
|
||||
{
|
||||
InitializeComponent();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,158 @@
|
||||
using System.Linq;
|
||||
using Avalonia;
|
||||
using Avalonia.Controls;
|
||||
using Avalonia.Layout;
|
||||
using HandyControl.Data;
|
||||
using HandyControl.Tools;
|
||||
|
||||
namespace HandyControl.Controls;
|
||||
|
||||
public class WaterfallPanel : Panel
|
||||
{
|
||||
public static readonly StyledProperty<int> GroupsProperty =
|
||||
AvaloniaProperty.Register<WaterfallPanel, int>(nameof(Groups), defaultValue: 2,
|
||||
validate: IsGroupsValid);
|
||||
|
||||
public int Groups
|
||||
{
|
||||
get => GetValue(GroupsProperty);
|
||||
set => SetValue(GroupsProperty, value);
|
||||
}
|
||||
|
||||
private static bool IsGroupsValid(int value) => value >= 1;
|
||||
|
||||
public static readonly StyledProperty<bool> AutoGroupProperty =
|
||||
AvaloniaProperty.Register<WaterfallPanel, bool>(nameof(AutoGroup));
|
||||
|
||||
public bool AutoGroup
|
||||
{
|
||||
get => GetValue(AutoGroupProperty);
|
||||
set => SetValue(AutoGroupProperty, value);
|
||||
}
|
||||
|
||||
public static readonly StyledProperty<double> DesiredLengthProperty =
|
||||
AvaloniaProperty.Register<WaterfallPanel, double>(nameof(DesiredLength), defaultValue: 0d,
|
||||
coerce: CoerceLength);
|
||||
|
||||
public double DesiredLength
|
||||
{
|
||||
get => GetValue(DesiredLengthProperty);
|
||||
set => SetValue(DesiredLengthProperty, value);
|
||||
}
|
||||
|
||||
public static readonly StyledProperty<Orientation> OrientationProperty =
|
||||
StackPanel.OrientationProperty.AddOwner<WaterfallPanel>(new StyledPropertyMetadata<Orientation>(
|
||||
defaultValue: Orientation.Horizontal));
|
||||
|
||||
public Orientation Orientation
|
||||
{
|
||||
get => GetValue(OrientationProperty);
|
||||
set => SetValue(OrientationProperty, value);
|
||||
}
|
||||
|
||||
static WaterfallPanel()
|
||||
{
|
||||
AffectsMeasure<WaterfallPanel>(
|
||||
GroupsProperty,
|
||||
AutoGroupProperty,
|
||||
DesiredLengthProperty,
|
||||
OrientationProperty
|
||||
);
|
||||
}
|
||||
|
||||
private static double CoerceLength(AvaloniaObject _, double length) => length < 0 ? 0 : length;
|
||||
|
||||
private int CalculateGroupCount(PanelUvSize size)
|
||||
{
|
||||
if (!AutoGroup)
|
||||
{
|
||||
return Groups;
|
||||
}
|
||||
|
||||
var itemLength = DesiredLength;
|
||||
|
||||
if (MathHelper.IsVerySmall(itemLength))
|
||||
{
|
||||
return Groups;
|
||||
}
|
||||
|
||||
return (int) (size.U / itemLength);
|
||||
}
|
||||
|
||||
protected override Size MeasureOverride(Size constraint)
|
||||
{
|
||||
var orientation = Orientation;
|
||||
var uvConstraint = new PanelUvSize(orientation, constraint);
|
||||
|
||||
var groups = CalculateGroupCount(uvConstraint);
|
||||
if (groups < 1)
|
||||
{
|
||||
return constraint;
|
||||
}
|
||||
|
||||
var vArr = new double[groups].ToList();
|
||||
var itemU = uvConstraint.U / groups;
|
||||
if (double.IsNaN(itemU) || double.IsInfinity(itemU))
|
||||
{
|
||||
return constraint;
|
||||
}
|
||||
|
||||
var children = Children;
|
||||
for (int i = 0, count = children.Count; i < count; i++)
|
||||
{
|
||||
var child = children[i];
|
||||
if (child == null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
child.Measure(constraint);
|
||||
|
||||
var sz = new PanelUvSize(orientation, child.DesiredSize);
|
||||
var minIndex = vArr.IndexOf(vArr.Min());
|
||||
var minV = vArr[minIndex];
|
||||
|
||||
vArr[minIndex] = minV + sz.V;
|
||||
}
|
||||
|
||||
uvConstraint = new PanelUvSize(orientation, new Size(uvConstraint.ScreenSize.Width, vArr.Max()));
|
||||
|
||||
return uvConstraint.ScreenSize;
|
||||
}
|
||||
|
||||
protected override Size ArrangeOverride(Size finalSize)
|
||||
{
|
||||
var orientation = Orientation;
|
||||
var uvConstraint = new PanelUvSize(orientation, finalSize);
|
||||
|
||||
var groups = CalculateGroupCount(uvConstraint);
|
||||
if (groups < 1)
|
||||
{
|
||||
return finalSize;
|
||||
}
|
||||
|
||||
var vArr = new double[groups].ToList();
|
||||
var itemU = uvConstraint.U / groups;
|
||||
|
||||
var children = Children;
|
||||
for (int i = 0, count = children.Count; i < count; i++)
|
||||
{
|
||||
var child = children[i];
|
||||
if (child == null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
var minIndex = vArr.IndexOf(vArr.Min());
|
||||
var minV = vArr[minIndex];
|
||||
var childUvSize = new PanelUvSize(orientation, child.DesiredSize);
|
||||
var childSize = new PanelUvSize(orientation, itemU, childUvSize.V);
|
||||
var childRectSize = new PanelUvSize(orientation, minIndex * itemU, minV);
|
||||
|
||||
child.Arrange(new Rect(new Point(childRectSize.U, childRectSize.V), childSize.ScreenSize));
|
||||
vArr[minIndex] = minV + childUvSize.V;
|
||||
}
|
||||
|
||||
return finalSize;
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user