Fix a bunch of templating issues

This commit is contained in:
TheXamlGuy
2024-07-17 20:43:39 +01:00
parent 60b784aa25
commit efd00ff81a
11 changed files with 315 additions and 116 deletions
+1 -1
View File
@@ -5,7 +5,7 @@
<Nullable>enable</Nullable>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Avalonia" Version="11.1.0-rc1" />
<PackageReference Include="Avalonia" Version="11.1.0-rc2" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\Toolkit.Foundation\Toolkit.Foundation.csproj" />
+1 -1
View File
@@ -9,7 +9,7 @@
</ItemGroup>
<ItemGroup>
<PackageReference Include="CommunityToolkit.Mvvm" Version="8.2.2" />
<PackageReference Include="Microsoft.Extensions.Hosting" Version="9.0.0-preview.5.24306.7" />
<PackageReference Include="Microsoft.Extensions.Hosting" Version="9.0.0-preview.6.24327.7" />
<PackageReference Include="System.Reactive" Version="6.0.1" />
</ItemGroup>
</Project>
@@ -5,8 +5,8 @@
<Nullable>enable</Nullable>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Avalonia" Version="11.1.0-rc1" />
<PackageReference Include="Avalonia.Xaml.Behaviors" Version="11.1.0-rc1" />
<PackageReference Include="Avalonia" Version="11.1.0-rc2" />
<PackageReference Include="Avalonia.Xaml.Behaviors" Version="11.1.0-rc2" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\Toolkit.Foundation\Toolkit.Foundation.csproj" />
@@ -8,47 +8,73 @@ namespace Toolkit.UI.Controls.Avalonia;
public class ContentBadge :
ContentControl
{
public static readonly StyledProperty<string?> BadgePathProperty =
AvaloniaProperty.Register<ContentBadge, string?>(nameof(BadgePath));
public static readonly StyledProperty<double> BadgeSizeProperty =
AvaloniaProperty.Register<ContentBadge, double>(nameof(BadgeSize), 14);
public static readonly StyledProperty<string> BadgePathProperty =
AvaloniaProperty.Register<ContentBadge, string>(nameof(BadgePath));
public static readonly StyledProperty<ContentBadgePlacement> BadgePlacementProperty =
AvaloniaProperty.Register<ContentBadge, ContentBadgePlacement>(nameof(BadgePlacement), ContentBadgePlacement.BottomRight);
public static readonly StyledProperty<double> BadgeSizeProperty =
AvaloniaProperty.Register<ContentBadge, double>(nameof(BadgeSize), 14);
public static readonly StyledProperty<bool> IsBadgeVisibleProperty =
AvaloniaProperty.Register<ContentBadge, bool>(nameof(IsBadgeVisible), true);
protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs change)
{
base.OnPropertyChanged(change);
if (change.Property == BadgePathProperty ||
change.Property == BadgeSizeProperty ||
change.Property == IsBadgeVisibleProperty)
{
UpdateBadge();
}
}
private ContentControl? badgeContent;
public string BadgePath
{
get => GetValue(BadgePathProperty);
set => SetValue(BadgePathProperty, value);
}
public ContentBadgePlacement BadgePlacement
{
get => GetValue(BadgePlacementProperty);
set => SetValue(BadgePlacementProperty, value);
}
public string? BadgePath
{
get => GetValue(BadgePathProperty);
set => SetValue(BadgePathProperty, value);
}
public double BadgeSize
{
get => GetValue(BadgeSizeProperty);
set => SetValue(BadgeSizeProperty, value);
}
public void UpdateClip()
public bool IsBadgeVisible
{
if (Content is Control content &&
badgeContent is not null &&
get => GetValue(IsBadgeVisibleProperty);
set => SetValue(IsBadgeVisibleProperty, value);
}
public void UpdateBadge()
{
if (Content is Control content && badgeContent is not null)
{
if (IsBadgeVisible &&
BadgePath is { Length: > 0 } &&
Geometry.Parse(BadgePath) is Geometry geometry)
{
double backgroundWidth = DesiredSize.Width;
double backgroundHeight = DesiredSize.Height;
double scaleX = BadgeSize / geometry.Bounds.Width;
double scaleY = BadgeSize / geometry.Bounds.Height;
double badgeWidth = geometry.Bounds.Width;
double badgeHeight = geometry.Bounds.Height;
double scale = BadgeSize / Math.Max(badgeWidth, badgeHeight);
double scaleX = scale;
double scaleY = scale;
double adjustedStrokeWidth = Math.Min(scaleX, scaleY) * 8;
@@ -109,6 +135,12 @@ public class ContentBadge :
Fill = Foreground
};
}
else
{
badgeContent.Content = null;
content.Clip = null;
}
}
}
protected override void OnApplyTemplate(TemplateAppliedEventArgs args)
@@ -120,7 +152,7 @@ public class ContentBadge :
protected override void OnSizeChanged(SizeChangedEventArgs args)
{
base.OnSizeChanged(args);
UpdateClip();
UpdateBadge();
}
}
@@ -13,7 +13,44 @@
<x:Double x:Key="OverflowItemSpacing">6</x:Double>
<x:Double x:Key="OverflowItemSize">40</x:Double>
<CornerRadius x:Key="OverflowItemCornerRadius">40</CornerRadius>
<ControlTheme x:Key="OverflowItemStyle" TargetType="ListBoxItem">
<ControlTheme x:Key="{x:Type OverflowList}" TargetType="OverflowList">
<Setter Property="Foreground" Value="{DynamicResource ListBoxForeground}" />
<Setter Property="Background" Value="{DynamicResource ListBoxBackground}" />
<Setter Property="BorderBrush" Value="{DynamicResource ListBoxBorder}" />
<Setter Property="BorderThickness" Value="{DynamicResource ListBoxBorderThemeThickness}" />
<Setter Property="ScrollViewer.HorizontalScrollBarVisibility" Value="Disabled" />
<Setter Property="ScrollViewer.VerticalScrollBarVisibility" Value="Auto" />
<Setter Property="ScrollViewer.IsScrollChainingEnabled" Value="True" />
<Setter Property="ScrollViewer.IsScrollInertiaEnabled" Value="True" />
<Setter Property="FontSize" Value="{DynamicResource ControlContentThemeFontSize}" />
<Setter Property="Template">
<ControlTemplate>
<Border
Name="border"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}">
<ScrollViewer
Name="PART_ScrollViewer"
AllowAutoHide="{TemplateBinding (ScrollViewer.AllowAutoHide)}"
Background="{TemplateBinding Background}"
BringIntoViewOnFocusChange="{TemplateBinding (ScrollViewer.BringIntoViewOnFocusChange)}"
HorizontalScrollBarVisibility="{TemplateBinding (ScrollViewer.HorizontalScrollBarVisibility)}"
HorizontalSnapPointsType="{TemplateBinding (ScrollViewer.HorizontalSnapPointsType)}"
IsDeferredScrollingEnabled="{TemplateBinding (ScrollViewer.IsDeferredScrollingEnabled)}"
IsScrollChainingEnabled="{TemplateBinding (ScrollViewer.IsScrollChainingEnabled)}"
IsScrollInertiaEnabled="{TemplateBinding (ScrollViewer.IsScrollInertiaEnabled)}"
VerticalScrollBarVisibility="{TemplateBinding (ScrollViewer.VerticalScrollBarVisibility)}"
VerticalSnapPointsType="{TemplateBinding (ScrollViewer.VerticalSnapPointsType)}">
<ItemsPresenter
Name="PART_ItemsPresenter"
Margin="{TemplateBinding Padding}"
ItemsPanel="{TemplateBinding ItemsPanel}" />
</ScrollViewer>
</Border>
</ControlTemplate>
</Setter>
</ControlTheme>
<ControlTheme x:Key="{x:Type controls:OverflowItem}" TargetType="controls:OverflowItem">
<Setter Property="FontFamily" Value="{DynamicResource ContentControlThemeFontFamily}" />
<Setter Property="FontSize" Value="{DynamicResource ControlContentThemeFontSize}" />
<Setter Property="Background" Value="{DynamicResource ListViewItemBackground}" />
@@ -34,13 +71,19 @@
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}"
CornerRadius="{TemplateBinding CornerRadius}">
<controls:ContentBadge
BadgePath="{TemplateBinding BadgePath}"
BadgePlacement="{TemplateBinding BadgePlacement}"
BadgeSize="{TemplateBinding BadgeSize}"
Foreground="Red"
IsBadgeVisible="{TemplateBinding IsBadgeVisible}">
<Grid>
<ContentPresenter
Name="PART_ContentPresenter"
HorizontalContentAlignment="{TemplateBinding HorizontalContentAlignment}"
VerticalContentAlignment="{TemplateBinding VerticalContentAlignment}"
Content="{TemplateBinding Content}"
ContentTemplate="{TemplateBinding ContentTemplate}" />
/>
<Border
Name="SelectionIndicator"
BorderBrush="{DynamicResource AccentFillColorDefaultBrush}"
@@ -49,6 +92,7 @@
IsVisible="False"
UseLayoutRounding="False" />
</Grid>
</controls:ContentBadge>
</Border>
</Panel>
</ControlTemplate>
@@ -116,18 +160,17 @@
<Setter Property="Template">
<ControlTemplate>
<StackPanel Margin="{TemplateBinding Margin}">
<ListBox
<controls:OverflowList
x:Name="PrimaryListBox"
Grid.Column="0"
ItemContainerTheme="{StaticResource OverflowItemStyle}"
ItemTemplate="{TemplateBinding ItemTemplate}"
SelectedItem="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=TemplateSettings.PrimarySelection, Mode=TwoWay}">
<ListBox.ItemsPanel>
<controls:OverflowList.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel Orientation="Horizontal" Spacing="{StaticResource OverflowItemSpacing}" />
</ItemsPanelTemplate>
</ListBox.ItemsPanel>
</ListBox>
</controls:OverflowList.ItemsPanel>
</controls:OverflowList>
<Grid
x:Name="Spacer"
Grid.Column="1"
@@ -153,17 +196,16 @@
</Viewbox>
<Button.Flyout>
<Flyout>
<ListBox
<controls:OverflowList
x:Name="SecondaryListBox"
ItemContainerTheme="{StaticResource OverflowItemStyle}"
ItemTemplate="{TemplateBinding ItemTemplate}"
SelectedItem="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=TemplateSettings.SecondarySelection, Mode=TwoWay}">
<ListBox.ItemsPanel>
<controls:OverflowList.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel Orientation="Horizontal" Spacing="{StaticResource OverflowItemSpacing}" />
</ItemsPanelTemplate>
</ListBox.ItemsPanel>
</ListBox>
</controls:OverflowList.ItemsPanel>
</controls:OverflowList>
</Flyout>
</Button.Flyout>
</Button>
@@ -29,13 +29,14 @@ public class Overflow :
private static readonly StyledProperty<OverflowTemplateSettings> TemplateSettingsProperty =
AvaloniaProperty.Register<Overflow, OverflowTemplateSettings>(nameof(TemplateSettings));
private readonly ObservableCollection<object> primaryCollection = [];
private readonly ObservableCollection<object> secondaryCollection = [];
private ListBox? primaryListBox;
private OverflowList? primaryListBox;
private ListBox? secondaryListBox;
private OverflowList? secondaryListBox;
public Overflow()
{
@@ -59,7 +60,6 @@ public class Overflow :
get => GetValue(ItemsSourceProperty);
set => SetValue(ItemsSourceProperty, value);
}
[InheritDataTypeFromItems(nameof(ItemsSource))]
public IDataTemplate? ItemTemplate
{
@@ -83,10 +83,10 @@ public class Overflow :
{
base.OnApplyTemplate(args);
primaryListBox = args.NameScope.Get<ListBox>("PrimaryListBox");
primaryListBox = args.NameScope.Get<OverflowList>("PrimaryListBox");
primaryListBox?.SetValue(ItemsControl.ItemsSourceProperty, primaryCollection);
secondaryListBox = args.NameScope.Get<ListBox>("SecondaryListBox");
secondaryListBox = args.NameScope.Get<OverflowList>("SecondaryListBox");
secondaryListBox?.SetValue(ItemsControl.ItemsSourceProperty, secondaryCollection);
InitializeCollections();
@@ -0,0 +1,45 @@
using Avalonia;
using Avalonia.Controls;
namespace Toolkit.UI.Controls.Avalonia;
public class OverflowItem :
ListBoxItem
{
public static readonly StyledProperty<double> BadgeSizeProperty =
AvaloniaProperty.Register<OverflowItem, double>(nameof(BadgeSize), 14);
public static readonly StyledProperty<bool> IsBadgeVisibleProperty =
AvaloniaProperty.Register<OverflowItem, bool>(nameof(IsBadgeVisible), true);
public static readonly StyledProperty<string> BadgePathProperty =
AvaloniaProperty.Register<OverflowItem, string>(nameof(BadgePath));
public static readonly StyledProperty<ContentBadgePlacement> BadgePlacementProperty =
AvaloniaProperty.Register<OverflowItem, ContentBadgePlacement>(nameof(BadgePlacement), ContentBadgePlacement.BottomRight);
public string BadgePath
{
get => GetValue(BadgePathProperty);
set => SetValue(BadgePathProperty, value);
}
public ContentBadgePlacement BadgePlacement
{
get => GetValue(BadgePlacementProperty);
set => SetValue(BadgePlacementProperty, value);
}
public double BadgeSize
{
get => GetValue(BadgeSizeProperty);
set => SetValue(BadgeSizeProperty, value);
}
public bool IsBadgeVisible
{
get => GetValue(IsBadgeVisibleProperty);
set => SetValue(IsBadgeVisibleProperty, value);
}
}
@@ -0,0 +1,39 @@
using Avalonia.Controls;
using Avalonia.Controls.Templates;
namespace Toolkit.UI.Controls.Avalonia;
public class OverflowList :
ListBox
{
protected override Control CreateContainerForItemOverride(object? item, int index, object? recycleKey)
{
if (recycleKey is IDataTemplate itemContainerTemplate)
{
if (itemContainerTemplate.Build(item) is OverflowItem container)
{
return container;
}
}
return new OverflowItem();
}
protected override bool NeedsContainerOverride(object? item, int index, out object? recycleKey)
{
if (item is OverflowItem)
{
recycleKey = null;
return false;
}
if (this.FindDataTemplate(item, ItemTemplate) is IDataTemplate itemContainerTemplate)
{
recycleKey = itemContainerTemplate;
return true;
}
recycleKey = DefaultRecycleKey;
return true;
}
}
@@ -0,0 +1,43 @@
using Avalonia.Controls;
using Avalonia.Controls.Templates;
namespace Toolkit.UI.Controls.Avalonia;
public class TemplateListBox :
ListBox
{
protected override Type StyleKeyOverride =>
typeof(ListBox);
protected override Control CreateContainerForItemOverride(object? item, int index, object? recycleKey)
{
if (recycleKey is IDataTemplate itemContainerTemplate)
{
if (itemContainerTemplate.Build(item) is ListBoxItem container)
{
return container;
}
}
return new ListBoxItem();
}
protected override bool NeedsContainerOverride(object? item, int index, out object? recycleKey)
{
if (item is ListBoxItem)
{
recycleKey = null;
return false;
}
if (this.FindDataTemplate(item, ItemTemplate) is IDataTemplate itemContainerTemplate)
{
recycleKey = itemContainerTemplate;
return true;
}
recycleKey = DefaultRecycleKey;
return true;
}
}
@@ -1,6 +1,4 @@
using Avalonia;
using Avalonia.Controls;
using Avalonia.Controls.Primitives;
using Avalonia.Controls.Templates;
namespace Toolkit.UI.Controls.Avalonia;
@@ -12,7 +12,7 @@
<AvaloniaResource Include="Fonts\FluentSystemIcons-Resizable.ttf" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Avalonia" Version="11.1.0-rc1" />
<PackageReference Include="Avalonia" Version="11.1.0-rc2" />
<PackageReference Include="Avalonia.Labs.Controls" Version="11.0.10.1" />
<PackageReference Include="FluentAvaloniaUI" Version="2.1.0-preview6" />
</ItemGroup>