Merge pull request #261 from irihitech/ElasticWrapPanel

Add ElasticWrapPanel control
This commit is contained in:
Dong Bin
2024-06-15 19:59:18 +08:00
committed by GitHub
9 changed files with 874 additions and 0 deletions

View File

View File

@@ -0,0 +1,319 @@
<UserControl x:Class="Ursa.Demo.Pages.ElasticWrapPanelDemo"
xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:u="https://irihi.tech/ursa"
xmlns:viewModels="clr-namespace:Ursa.Demo.ViewModels"
d:DesignHeight="800"
d:DesignWidth="800"
x:CompileBindings="True"
x:DataType="viewModels:ElasticWrapPanelDemoViewModel"
mc:Ignorable="d">
<UserControl.Styles>
<Style Selector="u|FormItem">
<Setter Property="Width" Value="320" />
</Style>
<Style Selector="TabItem#FixToRBTabItem WrapPanel > Border, u|ElasticWrapPanel > Border">
<Setter Property="Width" Value="{Binding ItemSelfWidth}" />
<Setter Property="Height" Value="{Binding ItemSelfHeight}" />
</Style>
</UserControl.Styles>
<DockPanel>
<u:Form DockPanel.Dock="Left"
LabelAlignment="Left"
LabelPosition="Left"
LabelWidth="*">
<u:FormGroup>
<u:FormItem Label="Orientation">
<u:EnumSelector EnumType="Orientation" Value="{Binding SelectedOrientation}" />
</u:FormItem>
<u:FormItem Label="HorizontalScrollBar">
<u:EnumSelector EnumType="ScrollBarVisibility" Value="{Binding HorizontalVisibility}" />
</u:FormItem>
<u:FormItem Label="VerticalScrollBar">
<u:EnumSelector EnumType="ScrollBarVisibility" Value="{Binding VerticalVisibility}" />
</u:FormItem>
<u:FormItem>
<u:FormItem.Label>
<CheckBox Content="IsFillHorizontal" IsChecked="{Binding IsFillHorizontal}" />
</u:FormItem.Label>
</u:FormItem>
<u:FormItem Label="ItemWidth">
<u:NumericDoubleUpDown
Step="1"
AllowDrag="True"
Maximum="1000"
Minimum="0"
Value="{Binding ItemWidth}" />
</u:FormItem>
<u:FormItem>
<u:FormItem.Label>
<CheckBox Content="IsFillVertical" IsChecked="{Binding IsFillVertical}" />
</u:FormItem.Label>
</u:FormItem>
<u:FormItem Label="ItemHeight">
<u:NumericDoubleUpDown
AllowDrag="True"
Step="1"
Maximum="1000"
Minimum="0"
Value="{Binding ItemHeight}" />
</u:FormItem>
</u:FormGroup>
<u:FormGroup Header="FixToRB Extension" IsEnabled="{Binding #FixToRBTabItem.IsSelected}">
<u:FormItem>
<u:FormItem.Label>
<CheckBox Content="AutoWidth" IsChecked="{Binding AutoWidth}" />
</u:FormItem.Label>
</u:FormItem>
<u:FormItem Label="ItemSelfWidth">
<u:NumericDoubleUpDown
AllowDrag="True"
Maximum="1000"
Minimum="0"
IsEnabled="{Binding !AutoWidth}"
Value="{Binding ItemSelfWidth}" />
</u:FormItem>
<u:FormItem>
<u:FormItem.Label>
<CheckBox Content="AutoHeight" IsChecked="{Binding AutoHeight}" />
</u:FormItem.Label>
</u:FormItem>
<u:FormItem Label="ItemSelfHeight">
<u:NumericDoubleUpDown
AllowDrag="True"
Maximum="1000"
Minimum="0"
IsEnabled="{Binding !AutoHeight}"
Value="{Binding ItemSelfHeight}" />
</u:FormItem>
<u:FormItem Label="HorizontalAlignment">
<u:EnumSelector EnumType="HorizontalAlignment" Value="{Binding CmbHAlign}" />
</u:FormItem>
<u:FormItem Label="VerticalAlignment">
<u:EnumSelector EnumType="VerticalAlignment" Value="{Binding CmbVAlign}" />
</u:FormItem>
</u:FormGroup>
</u:Form>
<TabControl>
<TabItem Header="Flat">
<TabControl>
<TabItem Header="Common">
<u:ElasticWrapPanel IsFillHorizontal="{Binding IsFillHorizontal}"
IsFillVertical="{Binding IsFillVertical}"
ItemHeight="{Binding ItemHeight}"
ItemWidth="{Binding ItemWidth}"
Orientation="{Binding SelectedOrientation}">
<Border Background="{DynamicResource SemiRed5Color}" />
<Border Background="{DynamicResource SemiPink5Color}" />
<Border Background="{DynamicResource SemiPurple5Color}" />
<Border Background="{DynamicResource SemiViolet5Color}" />
<Border Background="{DynamicResource SemiIndigo5Color}" />
<Border Background="{DynamicResource SemiBlue5Color}" />
<Border Background="{DynamicResource SemiLightBlue5Color}" />
<Border Background="{DynamicResource SemiCyan5Color}" />
<Border Background="{DynamicResource SemiTeal5Color}" />
<Border Background="{DynamicResource SemiGreen5Color}" />
<Border Background="{DynamicResource SemiLightGreen5Color}" />
<Border Background="{DynamicResource SemiLime5Color}" />
<Border Background="{DynamicResource SemiYellow5Color}" />
<Border Background="{DynamicResource SemiAmber5Color}" />
<Border Background="{DynamicResource SemiOrange5Color}" />
<Border Background="{DynamicResource SemiGrey5Color}" />
</u:ElasticWrapPanel>
</TabItem>
<TabItem Header="Grid Auto">
<Grid RowDefinitions="auto,*">
<u:ElasticWrapPanel Grid.Row="0"
IsFillHorizontal="{Binding IsFillHorizontal}"
IsFillVertical="{Binding IsFillVertical}"
ItemHeight="{Binding ItemHeight}"
ItemWidth="{Binding ItemWidth}"
Orientation="{Binding SelectedOrientation}">
<Border Background="{DynamicResource SemiRed5Color}" />
<Border Background="{DynamicResource SemiPink5Color}" />
<Border Background="{DynamicResource SemiPurple5Color}" />
<Border Background="{DynamicResource SemiViolet5Color}" />
<Border Background="{DynamicResource SemiIndigo5Color}" />
<Border Background="{DynamicResource SemiBlue5Color}" />
<Border Background="{DynamicResource SemiLightBlue5Color}" />
<Border Background="{DynamicResource SemiCyan5Color}" />
<Border Background="{DynamicResource SemiTeal5Color}" />
<Border Background="{DynamicResource SemiGreen5Color}" />
<Border Background="{DynamicResource SemiLightGreen5Color}" />
<Border Background="{DynamicResource SemiLime5Color}" />
<Border Background="{DynamicResource SemiYellow5Color}" />
<Border Background="{DynamicResource SemiAmber5Color}" />
<Border Background="{DynamicResource SemiOrange5Color}" />
<Border Background="{DynamicResource SemiGrey5Color}" />
</u:ElasticWrapPanel>
<Border Grid.Row="1" Margin="0,4" Theme="{StaticResource CardBorder}">
<TextBlock Text="This is blank" />
</Border>
</Grid>
</TabItem>
<TabItem Header="ScrollViewer">
<Border Margin="0,4">
<ScrollViewer HorizontalScrollBarVisibility="{Binding HorizontalVisibility}"
VerticalScrollBarVisibility="{Binding VerticalVisibility}">
<u:ElasticWrapPanel IsFillHorizontal="{Binding IsFillHorizontal}"
IsFillVertical="{Binding IsFillVertical}"
ItemHeight="{Binding ItemHeight}"
ItemWidth="{Binding ItemWidth}"
Orientation="{Binding SelectedOrientation}">
<Border Background="{DynamicResource SemiRed5Color}" />
<Border Background="{DynamicResource SemiPink5Color}" />
<Border Background="{DynamicResource SemiPurple5Color}" />
<Border Background="{DynamicResource SemiViolet5Color}" />
<Border Background="{DynamicResource SemiIndigo5Color}" />
<Border Background="{DynamicResource SemiBlue5Color}" />
<Border Background="{DynamicResource SemiLightBlue5Color}" />
<Border Background="{DynamicResource SemiCyan5Color}" />
<Border Background="{DynamicResource SemiTeal5Color}" />
<Border Background="{DynamicResource SemiGreen5Color}" />
<Border Background="{DynamicResource SemiLightGreen5Color}" />
<Border Background="{DynamicResource SemiLime5Color}" />
<Border Background="{DynamicResource SemiYellow5Color}" />
<Border Background="{DynamicResource SemiAmber5Color}" />
<Border Background="{DynamicResource SemiOrange5Color}" />
<Border Background="{DynamicResource SemiGrey5Color}" />
</u:ElasticWrapPanel>
</ScrollViewer>
</Border>
</TabItem>
</TabControl>
</TabItem>
<TabItem Header="FixToRB" Name="FixToRBTabItem">
<DockPanel>
<TabControl>
<TabItem Header="Single">
<StackPanel>
<ScrollViewer HorizontalScrollBarVisibility="{Binding HorizontalVisibility}"
VerticalScrollBarVisibility="{Binding VerticalVisibility}">
<StackPanel>
<u:Divider HorizontalContentAlignment="Left" Content="WrapPanel" />
<WrapPanel ItemHeight="{Binding ItemHeight}"
ItemWidth="{Binding ItemWidth}"
Orientation="{Binding SelectedOrientation}">
<Border Background="{DynamicResource SemiRed5Color}" />
<Border Background="{DynamicResource SemiPink5Color}" />
<Border Background="{DynamicResource SemiPurple5Color}" />
<Border Background="{DynamicResource SemiViolet5Color}" />
<UniformGrid MinWidth="200"
HorizontalAlignment="{Binding CmbHAlign}"
VerticalAlignment="{Binding CmbVAlign}"
Rows="1">
<Button Content="Search" />
<Button Content="Export" />
</UniformGrid>
</WrapPanel>
</StackPanel>
</ScrollViewer>
<ScrollViewer HorizontalScrollBarVisibility="{Binding HorizontalVisibility}"
VerticalScrollBarVisibility="{Binding VerticalVisibility}">
<StackPanel>
<u:Divider HorizontalContentAlignment="Left" Content="ElasticWrapPanel" />
<u:ElasticWrapPanel IsFillHorizontal="{Binding IsFillHorizontal}"
IsFillVertical="{Binding IsFillVertical}"
ItemHeight="{Binding ItemHeight}"
ItemWidth="{Binding ItemWidth}"
Orientation="{Binding SelectedOrientation}">
<Border Background="{DynamicResource SemiRed5Color}" />
<Border Background="{DynamicResource SemiPink5Color}" />
<Border Background="{DynamicResource SemiPurple5Color}" />
<Border Background="{DynamicResource SemiViolet5Color}" />
<UniformGrid MinWidth="200"
HorizontalAlignment="{Binding CmbHAlign}"
VerticalAlignment="{Binding CmbVAlign}"
u:ElasticWrapPanel.FixToRB="True"
Rows="1">
<Button Content="Search" />
<Button Content="Export" />
</UniformGrid>
</u:ElasticWrapPanel>
</StackPanel>
</ScrollViewer>
</StackPanel>
</TabItem>
<TabItem Header="Multiply">
<StackPanel>
<ScrollViewer HorizontalScrollBarVisibility="{Binding HorizontalVisibility}"
VerticalScrollBarVisibility="{Binding VerticalVisibility}">
<StackPanel>
<u:Divider HorizontalContentAlignment="Left" Content="WrapPanel" />
<WrapPanel ItemHeight="{Binding ItemHeight}"
ItemWidth="{Binding ItemWidth}"
Orientation="{Binding SelectedOrientation}">
<Border Background="{DynamicResource SemiRed5Color}" />
<Border Background="{DynamicResource SemiPink5Color}" />
<Border Background="{DynamicResource SemiPurple5Color}" />
<Border Background="{DynamicResource SemiViolet5Color}" />
<UniformGrid MinWidth="200"
HorizontalAlignment="{Binding CmbHAlign}"
VerticalAlignment="{Binding CmbVAlign}"
u:ElasticWrapPanel.FixToRB="True"
Rows="1">
<Button Content="Search" />
<Button Content="Export" />
</UniformGrid>
<Border Background="{DynamicResource SemiIndigo5Color}" />
<Border Background="{DynamicResource SemiBlue5Color}" />
<Border Background="{DynamicResource SemiLightBlue5Color}" />
<Border Background="{DynamicResource SemiCyan5Color}" />
<UniformGrid MinWidth="200"
HorizontalAlignment="{Binding CmbHAlign}"
VerticalAlignment="{Binding CmbVAlign}"
u:ElasticWrapPanel.FixToRB="True"
Rows="1">
<Button Content="Search" />
<Button Content="Export" />
</UniformGrid>
</WrapPanel>
</StackPanel>
</ScrollViewer>
<ScrollViewer HorizontalScrollBarVisibility="{Binding HorizontalVisibility}"
VerticalScrollBarVisibility="{Binding VerticalVisibility}">
<StackPanel>
<u:Divider HorizontalContentAlignment="Left" Content="ElasticWrapPanel" />
<u:ElasticWrapPanel IsFillHorizontal="{Binding IsFillHorizontal}"
IsFillVertical="{Binding IsFillVertical}"
ItemHeight="{Binding ItemHeight}"
ItemWidth="{Binding ItemWidth}"
Orientation="{Binding SelectedOrientation}">
<Border Background="{DynamicResource SemiRed5Color}" />
<Border Background="{DynamicResource SemiPink5Color}" />
<Border Background="{DynamicResource SemiPurple5Color}" />
<Border Background="{DynamicResource SemiViolet5Color}" />
<UniformGrid MinWidth="200"
HorizontalAlignment="{Binding CmbHAlign}"
VerticalAlignment="{Binding CmbVAlign}"
u:ElasticWrapPanel.FixToRB="True"
Rows="1">
<Button Content="Search" />
<Button Content="Export" />
</UniformGrid>
<Border Background="{DynamicResource SemiIndigo5Color}" />
<Border Background="{DynamicResource SemiBlue5Color}" />
<Border Background="{DynamicResource SemiLightBlue5Color}" />
<Border Background="{DynamicResource SemiCyan5Color}" />
<UniformGrid MinWidth="200"
HorizontalAlignment="{Binding CmbHAlign}"
VerticalAlignment="{Binding CmbVAlign}"
u:ElasticWrapPanel.FixToRB="True"
Rows="1">
<Button Content="Search" />
<Button Content="Export" />
</UniformGrid>
</u:ElasticWrapPanel>
</StackPanel>
</ScrollViewer>
</StackPanel>
</TabItem>
</TabControl>
</DockPanel>
</TabItem>
</TabControl>
</DockPanel>
</UserControl>

View File

@@ -0,0 +1,13 @@
using Avalonia.Controls;
using Ursa.Demo.ViewModels;
namespace Ursa.Demo.Pages;
public partial class ElasticWrapPanelDemo : UserControl
{
public ElasticWrapPanelDemo()
{
InitializeComponent();
DataContext = new ElasticWrapPanelDemoViewModel();
}
}

View File

@@ -0,0 +1,54 @@
using Avalonia.Controls.Primitives;
using Avalonia.Layout;
using CommunityToolkit.Mvvm.ComponentModel;
namespace Ursa.Demo.ViewModels;
public partial class ElasticWrapPanelDemoViewModel : ObservableObject
{
[ObservableProperty] private Orientation _selectedOrientation = Orientation.Horizontal;
[ObservableProperty] private ScrollBarVisibility _horizontalVisibility = ScrollBarVisibility.Auto;
[ObservableProperty] private ScrollBarVisibility _verticalVisibility = ScrollBarVisibility.Auto;
[ObservableProperty] private bool _isFillHorizontal;
[ObservableProperty] private bool _isFillVertical;
[ObservableProperty] private double _itemWidth = 40d;
[ObservableProperty] private double _itemHeight = 40d;
[ObservableProperty] private bool _autoWidth = true;
[ObservableProperty] private bool _autoHeight = true;
[ObservableProperty] private double _itemSelfWidth = double.NaN;
[ObservableProperty] private double _itemSelfHeight = double.NaN;
[ObservableProperty] private HorizontalAlignment _cmbHAlign = HorizontalAlignment.Left;
[ObservableProperty] private VerticalAlignment _cmbVAlign = VerticalAlignment.Stretch;
private double _oldItemSelfWidth;
private double _oldItemSelfHeight;
partial void OnAutoWidthChanged(bool value)
{
if (value)
{
_oldItemSelfWidth = ItemSelfWidth;
ItemSelfWidth = double.NaN;
}
else
{
ItemSelfWidth = _oldItemSelfWidth;
}
}
partial void OnAutoHeightChanged(bool value)
{
if (value)
{
_oldItemSelfHeight = ItemSelfHeight;
ItemSelfHeight = double.NaN;
}
else
{
ItemSelfHeight = _oldItemSelfHeight;
}
}
}

View File

@@ -36,6 +36,7 @@ public class MainViewViewModel : ViewModelBase
MenuKeys.MenuKeyDisableContainer => new DisableContainerDemoViewModel(),
MenuKeys.MenuKeyDrawer => new DrawerDemoViewModel(),
MenuKeys.MenuKeyDualBadge => new DualBadgeDemoViewModel(),
MenuKeys.MenuKeyElasticWrapPanel => new ElasticWrapPanelDemoViewModel(),
MenuKeys.MenuKeyEnumSelector => new EnumSelectorDemoViewModel(),
MenuKeys.MenuKeyForm => new FormDemoViewModel(),
MenuKeys.MenuKeyImageViewer => new ImageViewerDemoViewModel(),

View File

@@ -23,6 +23,7 @@ public class MenuViewModel: ViewModelBase
new() { MenuHeader = "Divider", Key = MenuKeys.MenuKeyDivider },
new() { MenuHeader = "Drawer", Key = MenuKeys.MenuKeyDrawer },
new() { MenuHeader = "DualBadge", Key = MenuKeys.MenuKeyDualBadge },
new() { MenuHeader = "ElasticWrapPanel", Key = MenuKeys.MenuKeyElasticWrapPanel },
new() { MenuHeader = "Enum Selector", Key = MenuKeys.MenuKeyEnumSelector },
new() { MenuHeader = "Form", Key = MenuKeys.MenuKeyForm },
new() { MenuHeader = "Icon Button", Key = MenuKeys.MenuKeyIconButton },
@@ -69,6 +70,7 @@ public static class MenuKeys
public const string MenuKeyDisableContainer = "DisableContainer";
public const string MenuKeyDrawer = "Drawer";
public const string MenuKeyDualBadge = "DualBadge";
public const string MenuKeyElasticWrapPanel = "ElasticWrapPanel";
public const string MenuKeyEnumSelector = "EnumSelector";
public const string MenuKeyForm = "Form";
public const string MenuKeyImageViewer = "ImageViewer";

View File

@@ -0,0 +1,3 @@
<ResourceDictionary xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
</ResourceDictionary>

View File

@@ -13,6 +13,7 @@
<ResourceInclude Source="Divider.axaml" />
<ResourceInclude Source="Drawer.axaml" />
<ResourceInclude Source="DualBadge.axaml" />
<ResourceInclude Source="ElasticWrapPanel.axaml" />
<ResourceInclude Source="EnumSelector.axaml" />
<ResourceInclude Source="Form.axaml" />
<ResourceInclude Source="IconButton.axaml" />

View File

@@ -0,0 +1,481 @@
using Avalonia;
using Avalonia.Controls;
using Avalonia.Layout;
using Avalonia.Utilities;
using static System.Math;
namespace Ursa.Controls;
public class ElasticWrapPanel : WrapPanel
{
static ElasticWrapPanel()
{
IsFillHorizontalProperty.Changed.AddClassHandler<Control>(OnIsFillPropertyChanged);
IsFillVerticalProperty.Changed.AddClassHandler<Control>(OnIsFillPropertyChanged);
AffectsMeasure<ElasticWrapPanel>(IsFillHorizontalProperty, IsFillVerticalProperty);
}
#region AttachedProperty
public static void SetFixToRB(Control element, bool value)
{
_ = element ?? throw new ArgumentNullException(nameof(element));
element.SetValue(FixToRBProperty, value);
}
public static bool GetIsFixToRB(Control element)
{
_ = element ?? throw new ArgumentNullException(nameof(element));
return element.GetValue(FixToRBProperty);
}
/// <summary>
/// Fixed to [Right (Horizontal Mode) | Bottom (Vertical Mode)]
/// which will cause line breaks
/// </summary>
public static readonly AttachedProperty<bool> FixToRBProperty =
AvaloniaProperty.RegisterAttached<ElasticWrapPanel, Control, bool>("FixToRB");
#endregion
#region StyledProperty
public bool IsFillHorizontal
{
get => GetValue(IsFillHorizontalProperty);
set => SetValue(IsFillHorizontalProperty, value);
}
public static readonly StyledProperty<bool> IsFillHorizontalProperty =
AvaloniaProperty.Register<ElasticWrapPanel, bool>(nameof(IsFillHorizontal));
public bool IsFillVertical
{
get => GetValue(IsFillVerticalProperty);
set => SetValue(IsFillVerticalProperty, value);
}
public static readonly StyledProperty<bool> IsFillVerticalProperty =
AvaloniaProperty.Register<ElasticWrapPanel, bool>(nameof(IsFillVertical));
private static void OnIsFillPropertyChanged(AvaloniaObject d, AvaloniaPropertyChangedEventArgs e)
{
(d as ElasticWrapPanel)?.InvalidateMeasure();
}
#endregion
protected override Size MeasureOverride(Size constraint)
{
double itemWidth = ItemWidth;
double itemHeight = ItemHeight;
var orientation = Orientation;
var children = Children;
// Determine the space required for items in the same row/column based on horizontal/vertical arrangement
var curLineSize = new UVSize(orientation);
// Calculate the total space requirement for this ElasticWrapPanel
var panelSize = new UVSize(orientation);
// Measure UVSize with the given space constraint, used for measuring elements when ItemWidth and ItemHeight are not set
var uvConstraint = new UVSize(orientation, constraint.Width, constraint.Height);
bool itemWidthSet = !double.IsNaN(itemWidth);
bool itemHeightSet = !double.IsNaN(itemHeight);
var childConstraint = new Size(
itemWidthSet ? itemWidth : constraint.Width,
itemHeightSet ? itemHeight : constraint.Height);
// Measurement space for elements with FixToRB=True
Size childFixConstraint = new Size(constraint.Width, constraint.Height);
switch (orientation)
{
case Orientation.Horizontal when itemHeightSet:
childFixConstraint = new Size(constraint.Width, itemHeight);
break;
case Orientation.Vertical when itemWidthSet:
childFixConstraint = new Size(itemWidth, constraint.Height);
break;
}
// This is the size for non-space measurement
UVSize itemSetSize = new UVSize(orientation,
itemWidthSet ? itemWidth : 0,
itemHeightSet ? itemHeight : 0);
foreach (var child in children)
{
UVSize sz;
if (GetIsFixToRB(child))
{
// Measure the element when it needs to be fixed to the right/bottom
child.Measure(childFixConstraint);
sz = new UVSize(orientation, child.DesiredSize.Width, child.DesiredSize.Height);
// Ensure the width/height is within the constraint limits
if (sz.U > 0 && itemSetSize.U > 0)
{
if (sz.U < itemSetSize.U)
{
sz.U = itemSetSize.U;
}
else
{
sz.U = Min(sz.U, uvConstraint.U);
}
}
if (sz.V > 0 && itemSetSize.V > 0 && sz.V < itemSetSize.V)
{
sz.V = itemSetSize.V;
}
if (MathUtilities.GreaterThan(curLineSize.U + sz.U, uvConstraint.U))
{
panelSize.U = Max(curLineSize.U, panelSize.U);
panelSize.V += curLineSize.V;
curLineSize = sz;
}
else
{
curLineSize.U += sz.U;
curLineSize.V = Max(sz.V, curLineSize.V);
panelSize.U = Max(curLineSize.U, panelSize.U);
panelSize.V += curLineSize.V;
}
curLineSize = new UVSize(orientation);
}
else
{
// Flow passes its own constraint to children
child.Measure(childConstraint);
// This is the size of the child in UV space
sz = new UVSize(orientation,
itemWidthSet ? itemWidth : child.DesiredSize.Width,
itemHeightSet ? itemHeight : child.DesiredSize.Height);
if (MathUtilities.GreaterThan(curLineSize.U + sz.U, uvConstraint.U)) // Need to switch to another line
{
panelSize.U = Max(curLineSize.U, panelSize.U);
panelSize.V += curLineSize.V;
curLineSize = sz;
if (MathUtilities.GreaterThan(sz.U, uvConstraint.U)) // The element is wider than the constraint - give it a separate line
{
panelSize.U = Max(sz.U, panelSize.U);
panelSize.V += sz.V;
curLineSize = new UVSize(orientation);
}
}
else // Continue to accumulate a line
{
curLineSize.U += sz.U;
curLineSize.V = Max(sz.V, curLineSize.V);
}
}
}
// The last line size, if any should be added
panelSize.U = Max(curLineSize.U, panelSize.U);
panelSize.V += curLineSize.V;
// Go from UV space to W/H space
return new Size(panelSize.Width, panelSize.Height);
}
protected override Size ArrangeOverride(Size finalSize)
{
bool itemWidthSet = !double.IsNaN(ItemWidth);
bool itemHeightSet = !double.IsNaN(ItemHeight);
// This is the size for non-space measurement
UVSize itemSetSize = new UVSize(Orientation,
itemWidthSet ? ItemWidth : 0,
itemHeightSet ? ItemHeight : 0);
// Measure UVSize with the given space constraint, used for measuring elements when ItemWidth and ItemHeight are not set
UVSize uvFinalSize = new UVSize(Orientation, finalSize.Width, finalSize.Height);
// Collection of elements in the same direction (row/column)
List<UVCollection> lineUVCollection = new List<UVCollection>();
#region Get the collection of elements in the same direction
// Current collection of elements in a row/column
UVCollection curLineUIs = new UVCollection(Orientation, itemSetSize);
// Iterate over the child elements
var children = Children;
foreach (var child in children)
{
UVSize sz;
if (GetIsFixToRB(child))
{
// Measure the element when it needs to be fixed to the right/bottom
sz = new UVSize(Orientation, child.DesiredSize.Width, child.DesiredSize.Height);
double lengthCount = 1;
if (sz.U > 0 && itemSetSize.U > 0)
{
if (sz.U < itemSetSize.U)
{
sz.U = itemSetSize.U;
}
else
{
lengthCount = Ceiling(sz.U / itemSetSize.U);
sz.U = Min(sz.U, uvFinalSize.U);
}
}
if (sz.V > 0 && itemSetSize.V > 0 && sz.V < itemSetSize.V)
{
sz.V = itemSetSize.V;
}
if (MathUtilities.GreaterThan(curLineUIs.TotalU + sz.U, uvFinalSize.U))
{
if (curLineUIs.Count > 0)
{
lineUVCollection.Add(curLineUIs);
}
curLineUIs = new UVCollection(Orientation, itemSetSize);
curLineUIs.Add(child, sz, Convert.ToInt32(lengthCount));
}
else
{
curLineUIs.Add(child, sz, Convert.ToInt32(lengthCount));
}
lineUVCollection.Add(curLineUIs);
curLineUIs = new UVCollection(Orientation, itemSetSize);
}
else
{
sz = new UVSize(Orientation,
itemWidthSet ? ItemWidth : child.DesiredSize.Width,
itemHeightSet ? ItemHeight : child.DesiredSize.Height);
if (MathUtilities.GreaterThan(curLineUIs.TotalU + sz.U, uvFinalSize.U)) // Need to switch to another line
{
if (curLineUIs.Count > 0)
{
lineUVCollection.Add(curLineUIs);
}
curLineUIs = new UVCollection(Orientation, itemSetSize);
curLineUIs.Add(child, sz);
if (MathUtilities.GreaterThan(sz.U, uvFinalSize.U))
{
lineUVCollection.Add(curLineUIs);
curLineUIs = new UVCollection(Orientation, itemSetSize);
}
}
else
{
curLineUIs.Add(child, sz);
}
}
}
if (curLineUIs.Count > 0 && !lineUVCollection.Contains(curLineUIs))
{
lineUVCollection.Add(curLineUIs);
}
#endregion
bool isFillU = false;
bool isFillV = false;
switch (Orientation)
{
case Orientation.Horizontal:
isFillU = IsFillHorizontal;
isFillV = IsFillVertical;
break;
case Orientation.Vertical:
isFillU = IsFillVertical;
isFillV = IsFillHorizontal;
break;
}
if (lineUVCollection.Count > 0)
{
double accumulatedV = 0;
double adaptULength = 0;
bool isAdaptV = false;
double adaptVLength = 0;
if (isFillU)
{
if (itemSetSize.U > 0)
{
int maxElementCount = lineUVCollection
.Max(uiSet => uiSet.UICollection
.Sum(p => p.Value.ULengthCount));
adaptULength = (uvFinalSize.U - maxElementCount * itemSetSize.U) / maxElementCount;
adaptULength = Max(adaptULength, 0);
}
}
if (isFillV)
{
if (itemSetSize.V > 0)
{
isAdaptV = true;
adaptVLength = uvFinalSize.V / lineUVCollection.Count;
}
}
bool isHorizontal = Orientation == Orientation.Horizontal;
foreach (var uvCollection in lineUVCollection)
{
double u = 0;
var lineUIEles = uvCollection.UICollection.Keys.ToList();
double linevV = isAdaptV ? adaptVLength : uvCollection.LineV;
foreach (var child in lineUIEles)
{
UVLengthSize childSize = uvCollection.UICollection[child];
double layoutSlotU = childSize.UVSize.U + childSize.ULengthCount * adaptULength;
double layoutSlotV = isAdaptV ? linevV : childSize.UVSize.V;
if (ElasticWrapPanel.GetIsFixToRB(child) == false)
{
child.Arrange(new Rect(
isHorizontal ? u : accumulatedV,
isHorizontal ? accumulatedV : u,
isHorizontal ? layoutSlotU : layoutSlotV,
isHorizontal ? layoutSlotV : layoutSlotU));
}
else
{
if (itemSetSize.U > 0)
{
layoutSlotU = childSize.ULengthCount * itemSetSize.U +
childSize.ULengthCount * adaptULength;
double leaveULength = uvFinalSize.U - u;
layoutSlotU = Min(leaveULength, layoutSlotU);
}
child.Arrange(new Rect(
isHorizontal ? Max(0, uvFinalSize.U - layoutSlotU) : accumulatedV,
isHorizontal ? accumulatedV : Max(0, uvFinalSize.U - layoutSlotU),
isHorizontal ? layoutSlotU : layoutSlotV,
isHorizontal ? layoutSlotV : layoutSlotU));
}
u += layoutSlotU;
}
accumulatedV += linevV;
lineUIEles.Clear();
}
}
lineUVCollection.ForEach(col => col.Dispose());
lineUVCollection.Clear();
return finalSize;
}
#region Protected Methods
private struct UVSize
{
internal UVSize(Orientation orientation, double width, double height)
{
U = V = 0d;
_orientation = orientation;
Width = width;
Height = height;
}
internal UVSize(Orientation orientation)
{
U = V = 0d;
_orientation = orientation;
}
internal double U;
internal double V;
private Orientation _orientation;
internal double Width
{
get { return _orientation == Orientation.Horizontal ? U : V; }
set
{
if (_orientation == Orientation.Horizontal) U = value;
else V = value;
}
}
internal double Height
{
get { return _orientation == Orientation.Horizontal ? V : U; }
set
{
if (_orientation == Orientation.Horizontal) V = value;
else U = value;
}
}
}
private class UVLengthSize
{
public UVSize UVSize { get; set; }
public int ULengthCount { get; set; }
public UVLengthSize(UVSize uvSize, int uLengthCount)
{
this.UVSize = uvSize;
this.ULengthCount = uLengthCount;
}
}
/// <summary>
/// Elements used to store the same row/column
/// </summary>
private class UVCollection : IDisposable
{
public Dictionary<Control, UVLengthSize> UICollection { get; }
private UVSize LineDesireUVSize;
private UVSize ItemSetSize;
public UVCollection(Orientation orientation, UVSize itemSetSize)
{
UICollection = new Dictionary<Control, UVLengthSize>();
LineDesireUVSize = new UVSize(orientation);
ItemSetSize = itemSetSize;
}
public double TotalU => LineDesireUVSize.U;
public double LineV => LineDesireUVSize.V;
public void Add(Control element, UVSize childSize, int itemULength = 1)
{
if (UICollection.ContainsKey(element))
throw new InvalidOperationException("The element already exists and cannot be added repeatedly.");
UICollection[element] = new UVLengthSize(childSize, itemULength);
LineDesireUVSize.U += childSize.U;
LineDesireUVSize.V = Max(LineDesireUVSize.V, childSize.V);
}
public int Count => UICollection.Count;
public void Dispose()
{
UICollection.Clear();
}
}
#endregion
}