feat: Add Tag styles and demos.
This commit is contained in:
1 parent
5bfbbe1904
commit
4cd78a4b3f
7 files changed
+499
-1
No files matched your search
@@ -54,7 +54,8 @@
|
||||
[ "UniformSpacingPanel", "UniformSpacingPanelDemo", "Brush.IncreaseHorizontalSpacing", "", "Layout" ],
|
||||
[ "ProgressBar", "ProgressBarDemo", "Brush.ProgressBar", "", "Feedback" ],
|
||||
[ "Rate", "RateDemo", "Brush.Favorite", "", "Feedback" ],
|
||||
[ "Loading", "LoadingDemo", "Brush.Loading", "", "Feedback" ]
|
||||
[ "Loading", "LoadingDemo", "Brush.Loading", "", "Feedback" ],
|
||||
[ "Tag", "TagDemo", "Brush.Tag", "", "DataDisplay" ]
|
||||
]
|
||||
}
|
||||
]
|
||||
@@ -0,0 +1,79 @@
|
||||
<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.TagDemo"
|
||||
Background="{DynamicResource RegionBrush}">
|
||||
<UserControl.Resources>
|
||||
<ControlTheme x:Key="TagCustomStyle"
|
||||
BasedOn="{StaticResource {x:Type hc:Tag}}"
|
||||
TargetType="hc:Tag">
|
||||
<Setter Property="Content"
|
||||
Value="{Binding Name}" />
|
||||
<Setter Property="Margin"
|
||||
Value="5" />
|
||||
<Setter Property="IsSelected"
|
||||
Value="{Binding IsSelected}" />
|
||||
</ControlTheme>
|
||||
</UserControl.Resources>
|
||||
<ScrollViewer>
|
||||
<StackPanel Margin="11">
|
||||
<hc:TagContainer Margin="11"
|
||||
MaxWidth="420"
|
||||
Padding="5">
|
||||
<hc:Tag Header="H"
|
||||
hc:TitleElement.Background="{DynamicResource PrimaryBrush}"
|
||||
Margin="5"
|
||||
ShowCloseButton="False"
|
||||
Content="TextText" />
|
||||
<hc:Tag Margin="5"
|
||||
Selectable="True"
|
||||
Content="TextTextText" />
|
||||
<hc:Tag Margin="5"
|
||||
ShowCloseButton="False"
|
||||
Content="TextTextTextText" />
|
||||
<hc:Tag Margin="5"
|
||||
Content="TextTextTextTextText" />
|
||||
<hc:Tag Margin="5"
|
||||
IsSelected="True"
|
||||
Selectable="True"
|
||||
ShowCloseButton="False"
|
||||
Content="TextTextTextText" />
|
||||
<hc:Tag Margin="5"
|
||||
Content="TextTextText" />
|
||||
<hc:Tag Margin="5"
|
||||
ShowCloseButton="False"
|
||||
Content="TextText">
|
||||
<hc:Tag.Header>
|
||||
<Image Source="/Resources/Img/Album/1.jpg" />
|
||||
</hc:Tag.Header>
|
||||
</hc:Tag>
|
||||
<hc:Tag Margin="5"
|
||||
IsSelected="True"
|
||||
Selectable="True"
|
||||
Content="TextTextText">
|
||||
<hc:Tag.Header>
|
||||
<Image Source="/Resources/Img/Avatar/avatar2.png" />
|
||||
</hc:Tag.Header>
|
||||
</hc:Tag>
|
||||
<hc:Tag Margin="5"
|
||||
ShowCloseButton="False"
|
||||
Content="TextTextTextText" />
|
||||
<hc:Tag Margin="5"
|
||||
Content="TextTextTextTextText" />
|
||||
</hc:TagContainer>
|
||||
<hc:TagContainer Name="BoundContainer"
|
||||
Margin="11"
|
||||
MaxWidth="420"
|
||||
Padding="5"
|
||||
ItemContainerTheme="{StaticResource TagCustomStyle}" />
|
||||
<DockPanel LastChildFill="True"
|
||||
Margin="11,0,11,11">
|
||||
<Button Name="AddButton"
|
||||
Margin="10,0,0,0"
|
||||
DockPanel.Dock="Right"
|
||||
Content="Add an item" />
|
||||
<TextBox Name="TagNameBox" />
|
||||
</DockPanel>
|
||||
</StackPanel>
|
||||
</ScrollViewer>
|
||||
</UserControl>
|
||||
@@ -0,0 +1,63 @@
|
||||
using System.Collections.ObjectModel;
|
||||
using Avalonia.Interactivity;
|
||||
|
||||
namespace HandyControlDemo.UserControl;
|
||||
|
||||
public class TagDemoItem : Avalonia.AvaloniaObject
|
||||
{
|
||||
public static readonly Avalonia.StyledProperty<string?> NameProperty =
|
||||
Avalonia.AvaloniaProperty.Register<TagDemoItem, string?>(nameof(Name));
|
||||
|
||||
public static readonly Avalonia.StyledProperty<bool> IsSelectedProperty =
|
||||
Avalonia.AvaloniaProperty.Register<TagDemoItem, bool>(nameof(IsSelected));
|
||||
|
||||
public string? Name
|
||||
{
|
||||
get => GetValue(NameProperty);
|
||||
set => SetValue(NameProperty, value);
|
||||
}
|
||||
|
||||
public bool IsSelected
|
||||
{
|
||||
get => GetValue(IsSelectedProperty);
|
||||
set => SetValue(IsSelectedProperty, value);
|
||||
}
|
||||
}
|
||||
|
||||
public partial class TagDemo : Avalonia.Controls.UserControl
|
||||
{
|
||||
public ObservableCollection<TagDemoItem> DataList { get; } = new();
|
||||
|
||||
public TagDemo()
|
||||
{
|
||||
InitializeComponent();
|
||||
|
||||
for (var i = 1; i <= 10; i++)
|
||||
{
|
||||
DataList.Add(new TagDemoItem
|
||||
{
|
||||
Name = $"Name{i}",
|
||||
IsSelected = i % 2 == 0
|
||||
});
|
||||
}
|
||||
|
||||
BoundContainer.ItemsSource = DataList;
|
||||
AddButton.Click += AddButton_Click;
|
||||
}
|
||||
|
||||
private void AddButton_Click(object? sender, RoutedEventArgs e)
|
||||
{
|
||||
var name = TagNameBox.Text;
|
||||
if (string.IsNullOrWhiteSpace(name))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
DataList.Insert(0, new TagDemoItem
|
||||
{
|
||||
Name = name,
|
||||
IsSelected = DataList.Count % 2 == 0
|
||||
});
|
||||
TagNameBox.Text = string.Empty;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,131 @@
|
||||
using Avalonia;
|
||||
using Avalonia.Controls;
|
||||
using Avalonia.Controls.Metadata;
|
||||
using Avalonia.Controls.Primitives;
|
||||
using Avalonia.Input;
|
||||
using Avalonia.Interactivity;
|
||||
|
||||
namespace HandyControl.Controls;
|
||||
|
||||
[TemplatePart(PartCloseButton, typeof(Button))]
|
||||
public class Tag : HeaderedContentControl
|
||||
{
|
||||
public const string PartCloseButton = "PART_CloseButton";
|
||||
|
||||
private Button? _closeButton;
|
||||
|
||||
public static readonly StyledProperty<bool> ShowCloseButtonProperty =
|
||||
AvaloniaProperty.Register<Tag, bool>(nameof(ShowCloseButton), true);
|
||||
|
||||
public bool ShowCloseButton
|
||||
{
|
||||
get => GetValue(ShowCloseButtonProperty);
|
||||
set => SetValue(ShowCloseButtonProperty, value);
|
||||
}
|
||||
|
||||
public static readonly StyledProperty<bool> SelectableProperty =
|
||||
AvaloniaProperty.Register<Tag, bool>(nameof(Selectable));
|
||||
|
||||
public bool Selectable
|
||||
{
|
||||
get => GetValue(SelectableProperty);
|
||||
set => SetValue(SelectableProperty, value);
|
||||
}
|
||||
|
||||
public static readonly StyledProperty<bool> IsSelectedProperty =
|
||||
AvaloniaProperty.Register<Tag, bool>(nameof(IsSelected));
|
||||
|
||||
public bool IsSelected
|
||||
{
|
||||
get => GetValue(IsSelectedProperty);
|
||||
set => SetValue(IsSelectedProperty, value);
|
||||
}
|
||||
|
||||
public static readonly RoutedEvent<RoutedEventArgs> SelectedEvent =
|
||||
RoutedEvent.Register<Tag, RoutedEventArgs>(nameof(Selected), RoutingStrategies.Bubble);
|
||||
|
||||
public event System.EventHandler<RoutedEventArgs> Selected
|
||||
{
|
||||
add => AddHandler(SelectedEvent, value);
|
||||
remove => RemoveHandler(SelectedEvent, value);
|
||||
}
|
||||
|
||||
public static readonly RoutedEvent<CancelRoutedEventArgs> ClosingEvent =
|
||||
RoutedEvent.Register<Tag, CancelRoutedEventArgs>(nameof(Closing), RoutingStrategies.Bubble);
|
||||
|
||||
public event System.EventHandler<CancelRoutedEventArgs> Closing
|
||||
{
|
||||
add => AddHandler(ClosingEvent, value);
|
||||
remove => RemoveHandler(ClosingEvent, value);
|
||||
}
|
||||
|
||||
public static readonly RoutedEvent<RoutedEventArgs> ClosedEvent =
|
||||
RoutedEvent.Register<Tag, RoutedEventArgs>(nameof(Closed), RoutingStrategies.Bubble);
|
||||
|
||||
public event System.EventHandler<RoutedEventArgs> Closed
|
||||
{
|
||||
add => AddHandler(ClosedEvent, value);
|
||||
remove => RemoveHandler(ClosedEvent, value);
|
||||
}
|
||||
|
||||
static Tag()
|
||||
{
|
||||
IsSelectedProperty.Changed.AddClassHandler<Tag>((tag, e) => tag.OnIsSelectedChanged(e));
|
||||
}
|
||||
|
||||
private void OnIsSelectedChanged(AvaloniaPropertyChangedEventArgs e)
|
||||
{
|
||||
var newValue = e.GetNewValue<bool>();
|
||||
PseudoClasses.Set(":selected", newValue);
|
||||
RaiseEvent(new RoutedEventArgs(SelectedEvent, this));
|
||||
}
|
||||
|
||||
protected override void OnApplyTemplate(TemplateAppliedEventArgs e)
|
||||
{
|
||||
base.OnApplyTemplate(e);
|
||||
|
||||
if (_closeButton != null)
|
||||
{
|
||||
_closeButton.Click -= CloseButton_Click;
|
||||
}
|
||||
|
||||
_closeButton = e.NameScope.Find<Button>(PartCloseButton);
|
||||
|
||||
if (_closeButton != null)
|
||||
{
|
||||
_closeButton.Click += CloseButton_Click;
|
||||
}
|
||||
}
|
||||
|
||||
private void CloseButton_Click(object? sender, RoutedEventArgs e)
|
||||
{
|
||||
RequestClose();
|
||||
}
|
||||
|
||||
internal void RequestClose()
|
||||
{
|
||||
var argsClosing = new CancelRoutedEventArgs(ClosingEvent, this);
|
||||
RaiseEvent(argsClosing);
|
||||
if (argsClosing.Cancel)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
RaiseEvent(new RoutedEventArgs(ClosedEvent, this));
|
||||
}
|
||||
|
||||
protected override void OnPointerPressed(PointerPressedEventArgs e)
|
||||
{
|
||||
base.OnPointerPressed(e);
|
||||
|
||||
if (Selectable && e.GetCurrentPoint(this).Properties.IsLeftButtonPressed)
|
||||
{
|
||||
IsSelected = true;
|
||||
}
|
||||
}
|
||||
|
||||
public void Hide()
|
||||
{
|
||||
IsVisible = false;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,76 @@
|
||||
using System.Collections;
|
||||
using Avalonia;
|
||||
using Avalonia.Controls;
|
||||
using Avalonia.Interactivity;
|
||||
|
||||
namespace HandyControl.Controls;
|
||||
|
||||
public class TagContainer : ItemsControl
|
||||
{
|
||||
public static readonly AttachedProperty<bool> ShowCloseButtonProperty =
|
||||
AvaloniaProperty.RegisterAttached<TagContainer, AvaloniaObject, bool>(
|
||||
"ShowCloseButton", defaultValue: true, inherits: true);
|
||||
|
||||
public static void SetShowCloseButton(AvaloniaObject element, bool value) =>
|
||||
element.SetValue(ShowCloseButtonProperty, value);
|
||||
|
||||
public static bool GetShowCloseButton(AvaloniaObject element) =>
|
||||
element.GetValue(ShowCloseButtonProperty);
|
||||
|
||||
public TagContainer()
|
||||
{
|
||||
AddHandler(HandyControl.Controls.Tag.ClosedEvent, Tag_OnClosed);
|
||||
}
|
||||
|
||||
private void Tag_OnClosed(object? sender, RoutedEventArgs e)
|
||||
{
|
||||
if (e.Source is not Tag tag)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
tag.Hide();
|
||||
|
||||
if (ItemsSource == null)
|
||||
{
|
||||
Items.Remove(tag);
|
||||
}
|
||||
else if (ItemsSource is IList list)
|
||||
{
|
||||
var item = ItemFromContainer(tag);
|
||||
if (item != null && list.Contains(item))
|
||||
{
|
||||
list.Remove(item);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected override bool NeedsContainerOverride(object? item, int index, out object? recycleKey)
|
||||
{
|
||||
if (item is Tag)
|
||||
{
|
||||
recycleKey = null;
|
||||
return false;
|
||||
}
|
||||
|
||||
recycleKey = typeof(Tag);
|
||||
return true;
|
||||
}
|
||||
|
||||
protected override Control CreateContainerForItemOverride(object? item, int index, object? recycleKey) => new Tag();
|
||||
|
||||
protected override void PrepareContainerForItemOverride(Control container, object? item, int index)
|
||||
{
|
||||
base.PrepareContainerForItemOverride(container, item, index);
|
||||
|
||||
// ItemsControl auto-binds HeaderedContentControl.Header to the item.
|
||||
// Tag is a HeaderedContentControl but, in TagContainer, the Header is meant
|
||||
// for an explicit avatar/icon. Clear the auto-assigned Header to prevent the
|
||||
// item's ToString() bleeding into the header circle. Skip when the item IS
|
||||
// the container (Tag declared directly in XAML) so an explicit Header is kept.
|
||||
if (container is Tag tag && !ReferenceEquals(container, item))
|
||||
{
|
||||
tag.ClearValue(Avalonia.Controls.Primitives.HeaderedContentControl.HeaderProperty);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -70,6 +70,7 @@
|
||||
<MergeResourceInclude Source="avares://HandyControl/Themes/Styles/ElementGroup.axaml" />
|
||||
<MergeResourceInclude Source="avares://HandyControl/Themes/Styles/ButtonGroup.axaml" />
|
||||
<MergeResourceInclude Source="avares://HandyControl/Themes/Styles/Gravatar.axaml" />
|
||||
<MergeResourceInclude Source="avares://HandyControl/Themes/Styles/Tag.axaml" />
|
||||
</ResourceDictionary.MergedDictionaries>
|
||||
</ResourceDictionary>
|
||||
</Styles.Resources>
|
||||
|
||||
@@ -0,0 +1,147 @@
|
||||
<ResourceDictionary xmlns="https://github.com/avaloniaui"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
x:ClassModifier="internal"
|
||||
xmlns:hc="clr-namespace:HandyControl.Controls"
|
||||
xmlns:sys="using:System">
|
||||
|
||||
<ControlTheme x:Key="TagBaseStyle"
|
||||
TargetType="hc:Tag">
|
||||
<Setter Property="Background"
|
||||
Value="{DynamicResource SecondaryRegionBrush}" />
|
||||
<Setter Property="BorderBrush"
|
||||
Value="{DynamicResource BorderBrush}" />
|
||||
<Setter Property="Padding"
|
||||
Value="{StaticResource DefaultControlPadding}" />
|
||||
<Setter Property="HorizontalContentAlignment"
|
||||
Value="Center" />
|
||||
<Setter Property="VerticalContentAlignment"
|
||||
Value="Center" />
|
||||
<Setter Property="HorizontalAlignment"
|
||||
Value="Center" />
|
||||
<Setter Property="VerticalAlignment"
|
||||
Value="Center" />
|
||||
<Setter Property="Height"
|
||||
Value="{StaticResource DefaultControlHeight}" />
|
||||
<Setter Property="hc:TitleElement.Foreground"
|
||||
Value="{DynamicResource TextIconBrush}" />
|
||||
<Setter Property="ShowCloseButton"
|
||||
Value="{Binding Path=(hc:TagContainer.ShowCloseButton), RelativeSource={RelativeSource Self}}" />
|
||||
<Setter Property="Template">
|
||||
<ControlTemplate TargetType="hc:Tag">
|
||||
<Border Theme="{StaticResource BorderCircular}"
|
||||
BorderBrush="{TemplateBinding BorderBrush}"
|
||||
BorderThickness="{TemplateBinding BorderThickness}"
|
||||
Background="{TemplateBinding Background}">
|
||||
<Grid>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="Auto" />
|
||||
<ColumnDefinition />
|
||||
<ColumnDefinition Width="Auto" />
|
||||
</Grid.ColumnDefinitions>
|
||||
<Border IsVisible="{TemplateBinding Header, Converter={x:Static ObjectConverters.IsNotNull}}"
|
||||
Width="{Binding $self.Bounds.Height}"
|
||||
Theme="{StaticResource BorderCircularClip}"
|
||||
BorderBrush="{Binding Path=(hc:TitleElement.BorderBrush), RelativeSource={RelativeSource TemplatedParent}}"
|
||||
Background="{Binding Path=(hc:TitleElement.Background), RelativeSource={RelativeSource TemplatedParent}}">
|
||||
<ContentPresenter TextElement.Foreground="{Binding Path=(hc:TitleElement.Foreground), RelativeSource={RelativeSource TemplatedParent}}"
|
||||
Content="{TemplateBinding Header}"
|
||||
ContentTemplate="{TemplateBinding HeaderTemplate}"
|
||||
VerticalAlignment="Center"
|
||||
HorizontalAlignment="Center">
|
||||
<ContentPresenter.DataTemplates>
|
||||
<DataTemplate DataType="sys:String">
|
||||
<TextBlock Text="{Binding}"
|
||||
VerticalAlignment="Center"
|
||||
HorizontalAlignment="Center" />
|
||||
</DataTemplate>
|
||||
</ContentPresenter.DataTemplates>
|
||||
</ContentPresenter>
|
||||
</Border>
|
||||
<ContentPresenter Margin="{TemplateBinding Padding}"
|
||||
Grid.Column="1"
|
||||
HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}"
|
||||
VerticalAlignment="{TemplateBinding VerticalContentAlignment}"
|
||||
Content="{TemplateBinding Content}"
|
||||
ContentTemplate="{TemplateBinding ContentTemplate}"
|
||||
RecognizesAccessKey="True" />
|
||||
<Button Name="PART_CloseButton"
|
||||
Focusable="False"
|
||||
Padding="0"
|
||||
BorderThickness="0"
|
||||
hc:IconElement.Width="8"
|
||||
IsVisible="{TemplateBinding ShowCloseButton}"
|
||||
Margin="0,0,6,0"
|
||||
Width="16"
|
||||
Grid.Column="2"
|
||||
Theme="{StaticResource ButtonIcon}"
|
||||
hc:IconElement.Geometry="{StaticResource DeleteGeometry}"
|
||||
Foreground="{TemplateBinding Foreground}" />
|
||||
</Grid>
|
||||
</Border>
|
||||
</ControlTemplate>
|
||||
</Setter>
|
||||
|
||||
<Style Selector="^/template/ Button#PART_CloseButton:pointerover">
|
||||
<Setter Property="hc:IconElement.Geometry"
|
||||
Value="{StaticResource DeleteFillCircleGeometry}" />
|
||||
<Setter Property="hc:IconElement.Width"
|
||||
Value="14" />
|
||||
</Style>
|
||||
|
||||
<Style Selector="^:disabled">
|
||||
<Setter Property="Opacity"
|
||||
Value="0.4" />
|
||||
</Style>
|
||||
|
||||
<Style Selector="^:pointerover">
|
||||
<Setter Property="Background"
|
||||
Value="{DynamicResource DarkMaskBrush}" />
|
||||
</Style>
|
||||
|
||||
<Style Selector="^:selected">
|
||||
<Setter Property="Background"
|
||||
Value="{DynamicResource PrimaryBrush}" />
|
||||
<Setter Property="Foreground"
|
||||
Value="{DynamicResource TextIconBrush}" />
|
||||
</Style>
|
||||
</ControlTheme>
|
||||
|
||||
<ControlTheme x:Key="{x:Type hc:Tag}"
|
||||
BasedOn="{StaticResource TagBaseStyle}"
|
||||
TargetType="hc:Tag" />
|
||||
|
||||
<ControlTheme x:Key="TagContainerBaseStyle"
|
||||
TargetType="hc:TagContainer">
|
||||
<Setter Property="hc:BorderElement.CornerRadius"
|
||||
Value="{StaticResource DefaultCornerRadius}" />
|
||||
<Setter Property="ItemContainerTheme"
|
||||
Value="{StaticResource TagBaseStyle}" />
|
||||
<Setter Property="BorderThickness"
|
||||
Value="1" />
|
||||
<Setter Property="BorderBrush"
|
||||
Value="{DynamicResource BorderBrush}" />
|
||||
<Setter Property="ItemsPanel">
|
||||
<ItemsPanelTemplate>
|
||||
<WrapPanel />
|
||||
</ItemsPanelTemplate>
|
||||
</Setter>
|
||||
<Setter Property="Template">
|
||||
<ControlTemplate TargetType="hc:TagContainer">
|
||||
<Border CornerRadius="{Binding Path=(hc:BorderElement.CornerRadius), RelativeSource={RelativeSource TemplatedParent}}"
|
||||
BorderBrush="{TemplateBinding BorderBrush}"
|
||||
BorderThickness="{TemplateBinding BorderThickness}"
|
||||
Background="{TemplateBinding Background}">
|
||||
<ScrollViewer Focusable="False">
|
||||
<ItemsPresenter Margin="{TemplateBinding Padding}"
|
||||
ItemsPanel="{TemplateBinding ItemsPanel}" />
|
||||
</ScrollViewer>
|
||||
</Border>
|
||||
</ControlTemplate>
|
||||
</Setter>
|
||||
</ControlTheme>
|
||||
|
||||
<ControlTheme x:Key="{x:Type hc:TagContainer}"
|
||||
BasedOn="{StaticResource TagContainerBaseStyle}"
|
||||
TargetType="hc:TagContainer" />
|
||||
|
||||
</ResourceDictionary>
|
||||
Reference in new issue
Block a user