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

View File

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

View File

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

View File

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

View File

@@ -5,6 +5,7 @@ using Avalonia.Controls.Presenters;
using Avalonia.Controls.Primitives; using Avalonia.Controls.Primitives;
using Avalonia.Controls.Templates; using Avalonia.Controls.Templates;
using Avalonia.Layout; using Avalonia.Layout;
using Avalonia.Markup.Xaml.Templates;
namespace Ursa.Controls; namespace Ursa.Controls;
@@ -59,6 +60,27 @@ public class ToolBar: HeaderedItemsControl
return new ContentPresenter(); 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) protected override void OnApplyTemplate(TemplateAppliedEventArgs e)
{ {
base.OnApplyTemplate(e); base.OnApplyTemplate(e);

View File

@@ -2,6 +2,7 @@
using Avalonia.Controls; using Avalonia.Controls;
using Avalonia.Layout; using Avalonia.Layout;
using Avalonia.LogicalTree; using Avalonia.LogicalTree;
using Avalonia.VisualTree;
namespace Ursa.Controls; namespace Ursa.Controls;
@@ -13,6 +14,15 @@ public class ToolBarPanel: StackPanel
internal Panel? OverflowPanel => _overflowPanel ??= _parent?.OverflowPanel; internal Panel? OverflowPanel => _overflowPanel ??= _parent?.OverflowPanel;
internal ToolBar? ParentToolBar => _parent ??= this.TemplatedParent as ToolBar; 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() static ToolBarPanel()
{ {
OrientationProperty.OverrideDefaultValue<ToolBarPanel>(Orientation.Horizontal); OrientationProperty.OverrideDefaultValue<ToolBarPanel>(Orientation.Horizontal);
@@ -28,24 +38,110 @@ public class ToolBarPanel: StackPanel
protected override Size MeasureOverride(Size availableSize) protected override Size MeasureOverride(Size availableSize)
{ {
var size = base.MeasureOverride(availableSize); var logicalChildren = _parent?.GetLogicalChildren().OfType<Control>().ToList();
var children = this.Children; var parent = this.GetLogicalParent();
var children2 = this.OverflowPanel?.Children; Size size = new Size();
var all = children.ToList(); double spacing = 0;
if (children2 != null) 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(); if (hasVisibleChildren)
for (int i = 0; i < all.Count - 1; i++)
{ {
this.Children.Add(all[i]); size = horizontal ? size.WithWidth(size.Width - spacing) : size.WithHeight(size.Height - spacing);
}
if (all.Count > 0)
{
OverflowPanel?.Children.Add(all.Last());
} }
return size; 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);
}
} }