feat: layout calculation.

This commit is contained in:
rabbitism
2024-02-21 22:48:11 +08:00
parent ab4b74d055
commit 6f4e11c7f5
6 changed files with 166 additions and 34 deletions

View File

@@ -28,7 +28,6 @@ public class ToolBarItemTemplateSelector: IDataTemplate
{
[!ContentControl.ContentProperty] = new Binding() { Path = "Content" },
[!ToggleButton.IsCheckedProperty] = new Binding() { Path = "IsChecked" },
[!Button.CommandProperty] = new Binding() { Path = "Command" },
[!ToolBar.OverflowModeProperty] = new Binding(){ Path = "OverflowMode" }
};
}
@@ -39,7 +38,7 @@ public class ToolBarItemTemplateSelector: IDataTemplate
[!ContentControl.ContentProperty] = new Binding() { Path = "Content" },
[!SelectingItemsControl.SelectedItemProperty] = new Binding() { Path = "SelectedItem" },
[!ItemsControl.ItemsSourceProperty] = new Binding() { Path = "Items" },
[!ToolBar.OverflowModeProperty] = new Binding(){ Path = "OverflowMode" }
[ToolBar.OverflowModeProperty] = OverflowMode.Always,
};
}
return new Button() { Content = "Undefined Item" };

View File

@@ -10,15 +10,16 @@
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
x:Class="Ursa.Demo.Pages.ToolBarDemo">
<StackPanel>
<u:ToolBar>
<u:ToolBar HorizontalAlignment="Left">
<Button Content="Button 1" />
<Button Content="Button 2" />
<Button Content="Button 3" />
<Button Content="Button 3" u:ToolBar.OverflowMode="Always" />
</u:ToolBar>
<u:ToolBar ItemsSource="{Binding Items}">
<u:ToolBar ItemsSource="{Binding Items}" HorizontalAlignment="Left">
<u:ToolBar.ItemTemplate>
<template:ToolBarItemTemplateSelector/>
</u:ToolBar.ItemTemplate>
</u:ToolBar>
<CheckBox Content="Check"></CheckBox>
</StackPanel>
</UserControl>

View File

@@ -1,4 +1,5 @@
using Avalonia.Controls;
using Avalonia.Interactivity;
namespace Ursa.Demo.Views;

View File

@@ -6,30 +6,43 @@
<ControlTheme x:Key="{x:Type u:ToolBar}" TargetType="u:ToolBar">
<Setter Property="ItemsPanel">
<ItemsPanelTemplate>
<u:ToolBarPanel/>
<u:ToolBarPanel />
</ItemsPanelTemplate>
</Setter>
<Setter Property="Template">
<ControlTemplate TargetType="u:ToolBar">
<Border
Padding="2"
Theme="{DynamicResource CardBorder}"
CornerRadius="4">
<StackPanel Orientation="{TemplateBinding Orientation}">
CornerRadius="4"
Theme="{DynamicResource CardBorder}">
<DockPanel LastChildFill="True">
<Rectangle
Width="1"
Margin="4,0"
VerticalAlignment="Stretch"
Margin="4 0"
DockPanel.Dock="Left"
Fill="Gray" />
<ContentPresenter Margin="4 0" Content="{TemplateBinding Header}" ContentTemplate="{TemplateBinding HeaderTemplate}" />
<ItemsPresenter VerticalAlignment="Center" HorizontalAlignment="Left" ItemsPanel="{TemplateBinding ItemsPanel}"/>
<ToggleButton Name="button" Content="More"></ToggleButton>
<Popup IsOpen="{Binding #button.IsChecked, Mode=TwoWay}" PlacementTarget="{Binding #button}" IsLightDismissEnabled="True">
<Border Theme="{DynamicResource CardBorder}">
<StackPanel Name="{x:Static u:ToolBar.PART_OverflowPanel}"></StackPanel>
</Border>
</Popup>
</StackPanel>
<ContentPresenter
Margin="4,0"
Content="{TemplateBinding Header}"
ContentTemplate="{TemplateBinding HeaderTemplate}"
DockPanel.Dock="Left" />
<Panel DockPanel.Dock="Right">
<ToggleButton Name="button" Content="More" />
<Popup
IsLightDismissEnabled="True"
IsOpen="{Binding #button.IsChecked, Mode=TwoWay}"
PlacementTarget="{Binding #button}">
<Border Theme="{DynamicResource CardBorder}">
<StackPanel Name="{x:Static u:ToolBar.PART_OverflowPanel}" />
</Border>
</Popup>
</Panel>
<ItemsPresenter
HorizontalAlignment="Left"
VerticalAlignment="Center"
ItemsPanel="{TemplateBinding ItemsPanel}" />
</DockPanel>
</Border>
</ControlTemplate>
</Setter>

View File

@@ -5,6 +5,7 @@ using Avalonia.Controls.Presenters;
using Avalonia.Controls.Primitives;
using Avalonia.Controls.Templates;
using Avalonia.Layout;
using Avalonia.Markup.Xaml.Templates;
namespace Ursa.Controls;
@@ -59,6 +60,27 @@ public class ToolBar: HeaderedItemsControl
return new ContentPresenter();
}
protected override void ContainerForItemPreparedOverride(Control container, object? item, int index)
{
base.ContainerForItemPreparedOverride(container, item, index);
if (item is Control s)
{
container[!ToolBar.OverflowModeProperty] = s[!ToolBar.OverflowModeProperty];
}
else
{
if (container is ContentPresenter p)
{
p.ApplyTemplate();
var c = p.Child;
if (c != null)
{
container[!ToolBar.OverflowModeProperty] = c[!ToolBar.OverflowModeProperty];
}
}
}
}
protected override void OnApplyTemplate(TemplateAppliedEventArgs e)
{
base.OnApplyTemplate(e);

View File

@@ -2,6 +2,7 @@
using Avalonia.Controls;
using Avalonia.Layout;
using Avalonia.LogicalTree;
using Avalonia.VisualTree;
namespace Ursa.Controls;
@@ -13,6 +14,15 @@ public class ToolBarPanel: StackPanel
internal Panel? OverflowPanel => _overflowPanel ??= _parent?.OverflowPanel;
internal ToolBar? ParentToolBar => _parent ??= this.TemplatedParent as ToolBar;
public static readonly StyledProperty<Orientation> OrientationProperty =
StackPanel.OrientationProperty.AddOwner<ToolBar>();
public Orientation Orientation
{
get => GetValue(OrientationProperty);
set => SetValue(OrientationProperty, value);
}
static ToolBarPanel()
{
OrientationProperty.OverrideDefaultValue<ToolBarPanel>(Orientation.Horizontal);
@@ -25,27 +35,113 @@ public class ToolBarPanel: StackPanel
if (_parent is null) return;
this[!OrientationProperty] = _parent[!OrientationProperty];
}
protected override Size MeasureOverride(Size availableSize)
{
var size = base.MeasureOverride(availableSize);
var children = this.Children;
var children2 = this.OverflowPanel?.Children;
var all = children.ToList();
if (children2 != null)
var logicalChildren = _parent?.GetLogicalChildren().OfType<Control>().ToList();
var parent = this.GetLogicalParent();
Size size = new Size();
double spacing = 0;
Size measureSize = availableSize;
bool horizontal = Orientation == Orientation.Horizontal;
bool hasVisibleChildren = false;
measureSize = horizontal
? measureSize.WithWidth(double.PositiveInfinity)
: measureSize.WithHeight(double.PositiveInfinity);
int index = 0;
if (logicalChildren is null) return size;
for (int count = logicalChildren.Count; index < count; ++index)
{
all.AddRange(children2);
Control control = logicalChildren[index];
var mode = ToolBar.GetOverflowMode(control);
if(mode == OverflowMode.Always) continue;
bool isVisible = control.IsVisible;
if (isVisible)
{
hasVisibleChildren = true;
}
control.Measure(measureSize);
Size desiredSize = control.DesiredSize;
if (horizontal)
{
size = size.WithWidth(size.Width + desiredSize.Width + (isVisible ? spacing : 0));
size = size.WithHeight(Math.Max(size.Height, desiredSize.Height));
}
else
{
size = size.WithHeight(size.Height + desiredSize.Height + (isVisible ? spacing : 0));
size = size.WithWidth(Math.Max(size.Width, desiredSize.Width));
}
}
this.Children.Clear();
OverflowPanel?.Children.Clear();
for (int i = 0; i < all.Count - 1; i++)
if (hasVisibleChildren)
{
this.Children.Add(all[i]);
}
if (all.Count > 0)
{
OverflowPanel?.Children.Add(all.Last());
size = horizontal ? size.WithWidth(size.Width - spacing) : size.WithHeight(size.Height - spacing);
}
return size;
}
protected override Size ArrangeOverride(Size finalSize)
{
if (_parent is null)
{
return finalSize;
}
var all = _parent.GetLogicalChildren().OfType<Control>().ToList();
Children.Clear();
OverflowPanel?.Children.Clear();
Size currentSize = new Size();
bool horizontal = Orientation == Orientation.Horizontal;
double spacing = 0;
Rect arrangeRect = new Rect(finalSize);
double previousChildSize = 0.0;
for (var i = 0; i < all.Count; i++)
{
var control = all[i];
if (!control.IsVisible) continue;
var desiredSize = control.DesiredSize;
var mode = ToolBar.GetOverflowMode(control);
if (mode == OverflowMode.Always)
{
OverflowPanel?.Children.Add(control);
}
else
{
Children.Add(control);
if (horizontal)
{
arrangeRect = arrangeRect.WithX(arrangeRect.X + previousChildSize);
previousChildSize = control.DesiredSize.Width;
arrangeRect = arrangeRect.WithWidth(previousChildSize);
arrangeRect = arrangeRect.WithHeight(Math.Max(finalSize.Height, control.DesiredSize.Height));
previousChildSize += spacing;
currentSize = currentSize.WithWidth(currentSize.Width + desiredSize.Width + spacing);
currentSize = currentSize.WithHeight(Math.Max(currentSize.Height, desiredSize.Height));
}
else
{
arrangeRect = arrangeRect.WithY(arrangeRect.Y + previousChildSize);
previousChildSize = control.DesiredSize.Height;
arrangeRect = arrangeRect.WithHeight(previousChildSize);
arrangeRect = arrangeRect.WithWidth(Math.Max(finalSize.Width, control.DesiredSize.Width));
previousChildSize += spacing;
currentSize = currentSize.WithHeight(currentSize.Height + desiredSize.Height + spacing);
currentSize = currentSize.WithWidth(Math.Max(currentSize.Width, desiredSize.Width));
}
control.Arrange(arrangeRect);
}
}
return currentSize;
}
protected override void OnDetachedFromVisualTree(VisualTreeAttachmentEventArgs e)
{
var list = OverflowPanel?.Children?.ToList();
if (list is not null)
{
OverflowPanel?.Children?.Clear();
this.Children.AddRange(list);
}
base.OnDetachedFromVisualTree(e);
}
}