feat: initialize tag input

This commit is contained in:
rabbitism
2023-06-29 01:45:58 +08:00
parent c2031dd05d
commit 9d249b01cd
8 changed files with 302 additions and 1 deletions

View File

@@ -0,0 +1,12 @@
<UserControl
x:Class="Ursa.Demo.Pages.TagInputDemo"
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"
d:DesignHeight="450"
d:DesignWidth="800"
mc:Ignorable="d">
<u:TagInput Margin="20" />
</UserControl>

View File

@@ -0,0 +1,13 @@
using Avalonia;
using Avalonia.Controls;
using Avalonia.Markup.Xaml;
namespace Ursa.Demo.Pages;
public partial class TagInputDemo : UserControl
{
public TagInputDemo()
{
InitializeComponent();
}
}

View File

@@ -47,6 +47,9 @@
<TabItem Header="Pagination">
<pages:PaginationDemo />
</TabItem>
<TabItem Header="TagInput">
<pages:TagInputDemo />
</TabItem>
<TabItem Header="Timeline">
<pages:TimelineDemo />
</TabItem>

View File

@@ -0,0 +1,87 @@
<ResourceDictionary
xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:u="https://irihi.tech/ursa">
<!-- Add Resources Here -->
<ControlTheme x:Key="{x:Type u:TagInput}" TargetType="u:TagInput">
<Setter Property="InputTheme" Value="{DynamicResource TagInputTextBoxTheme}" />
<Setter Property="HorizontalAlignment" Value="Stretch" />
<Setter Property="VerticalAlignment" Value="Top" />
<Setter Property="ItemTemplate">
<DataTemplate>
<Label
Classes="Solid"
Content="{Binding}"
Theme="{DynamicResource TagLabel}" />
</DataTemplate>
</Setter>
<Setter Property="Template">
<ControlTemplate TargetType="u:TagInput">
<Border
Padding="4"
VerticalAlignment="Top"
Background="LightGray"
CornerRadius="3">
<Panel HorizontalAlignment="Stretch">
<ItemsControl
HorizontalAlignment="Stretch"
VerticalAlignment="Top"
ItemTemplate="{TemplateBinding ItemTemplate}"
ItemsSource="{TemplateBinding Items}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<u:TagInputPanel VerticalAlignment="Top" />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
</ItemsControl>
</Panel>
</Border>
</ControlTemplate>
</Setter>
</ControlTheme>
<ControlTheme x:Key="TagInputTextBoxTheme" TargetType="TextBox">
<Setter Property="TextBox.Foreground" Value="{DynamicResource TextBoxForeground}" />
<Setter Property="TextBox.Background" Value="{DynamicResource TextBoxDefaultBackground}" />
<Setter Property="TextBox.BorderBrush" Value="{DynamicResource TextBoxDefaultBorderBrush}" />
<Setter Property="TextBox.SelectionBrush" Value="{DynamicResource TextBoxSelectionBackground}" />
<Setter Property="TextBox.SelectionForegroundBrush" Value="{DynamicResource TextBoxSelectionForeground}" />
<Setter Property="TextBox.Cursor" Value="Ibeam" />
<Setter Property="VerticalContentAlignment" Value="Stretch" />
<Setter Property="VerticalAlignment" Value="Stretch" />
<Setter Property="ScrollViewer.IsScrollChainingEnabled" Value="True" />
<Setter Property="HorizontalContentAlignment" Value="Stretch" />
<Setter Property="HorizontalAlignment" Value="Stretch" />
<Setter Property="Template">
<ControlTemplate TargetType="TextBox">
<Border>
<ScrollViewer
Width="{Binding $parent[TextBox].Bounds.Width}"
AllowAutoHide="{TemplateBinding (ScrollViewer.AllowAutoHide)}"
HorizontalScrollBarVisibility="{TemplateBinding (ScrollViewer.HorizontalScrollBarVisibility)}"
IsScrollChainingEnabled="{TemplateBinding (ScrollViewer.IsScrollChainingEnabled)}"
VerticalScrollBarVisibility="{TemplateBinding (ScrollViewer.VerticalScrollBarVisibility)}">
<TextPresenter
Name="PART_TextPresenter"
HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}"
VerticalAlignment="{TemplateBinding VerticalContentAlignment}"
CaretBrush="{TemplateBinding CaretBrush}"
CaretIndex="{TemplateBinding CaretIndex}"
LineHeight="{TemplateBinding LineHeight}"
PasswordChar="{TemplateBinding PasswordChar}"
RevealPassword="{TemplateBinding RevealPassword}"
SelectionBrush="{TemplateBinding SelectionBrush}"
SelectionEnd="{TemplateBinding SelectionEnd}"
SelectionForegroundBrush="{TemplateBinding SelectionForegroundBrush}"
SelectionStart="{TemplateBinding SelectionStart}"
Text="{TemplateBinding Text,
Mode=TwoWay}"
TextAlignment="{TemplateBinding TextAlignment}"
TextWrapping="{TemplateBinding TextWrapping}" />
</ScrollViewer>
</Border>
</ControlTemplate>
</Setter>
</ControlTheme>
</ResourceDictionary>

View File

@@ -9,6 +9,7 @@
<ResourceInclude Source="Loading.axaml" />
<ResourceInclude Source="Navigation.axaml" />
<ResourceInclude Source="Pagination.axaml" />
<ResourceInclude Source="TagInput.axaml" />
<ResourceInclude Source="Timeline.axaml" />
</ResourceDictionary.MergedDictionaries>
</ResourceDictionary>

View File

@@ -1,5 +1,5 @@
using Avalonia.Metadata;
[assembly:XmlnsPrefix("https://irihi.tech/ursa", "u")]
[assembly:XmlnsDefinition("https://irihi.tech/ursa", "Ursa")]
[assembly:XmlnsDefinition("https://irihi.tech/ursa", "Ursa.Controls")]
[assembly:XmlnsPrefix("https://irihi.tech/ursa", "u")]

View File

@@ -0,0 +1,76 @@
using System.Collections;
using Avalonia;
using Avalonia.Collections;
using Avalonia.Controls;
using Avalonia.Controls.Primitives;
using Avalonia.Controls.Templates;
using Avalonia.Layout;
using Avalonia.Styling;
namespace Ursa.Controls;
public class TagInput: TemplatedControl
{
public static readonly StyledProperty<IList<string>> TagsProperty = AvaloniaProperty.Register<TagInput, IList<string>>(
nameof(Tags));
private TextBox _textBox;
public IList<string> Tags
{
get => GetValue(TagsProperty);
set => SetValue(TagsProperty, value);
}
public static readonly StyledProperty<IList> ItemsProperty = AvaloniaProperty.Register<TagInput, IList>(
nameof(Items));
public IList Items
{
get => GetValue(ItemsProperty);
set => SetValue(ItemsProperty, value);
}
public TagInput()
{
_textBox = new TextBox();
}
public static readonly StyledProperty<ControlTheme> InputThemeProperty = AvaloniaProperty.Register<TagInput, ControlTheme>(
nameof(InputTheme));
public ControlTheme InputTheme
{
get => GetValue(InputThemeProperty);
set => SetValue(InputThemeProperty, value);
}
public static readonly StyledProperty<IDataTemplate?> ItemTemplateProperty = AvaloniaProperty.Register<TagInput, IDataTemplate?>(
nameof(ItemTemplate));
public IDataTemplate? ItemTemplate
{
get => GetValue(ItemTemplateProperty);
set => SetValue(ItemTemplateProperty, value);
}
protected override void OnApplyTemplate(TemplateAppliedEventArgs e)
{
base.OnApplyTemplate(e);
Items = new AvaloniaList<object>();
if (IsSet(InputThemeProperty) && InputTheme.TargetType == typeof(TextBox))
{
_textBox.Theme = InputTheme;
}
_textBox.KeyDown += (sender, args) =>
{
if (args.Key == Avalonia.Input.Key.Enter)
{
Items.Insert(Items.Count - 1, _textBox.Text);
// Tags.Insert(Items.Count - 1, _textBox.Text ?? string.Empty);
_textBox.Text = "";
}
};
Items.Add(_textBox);
}
}

View File

@@ -0,0 +1,109 @@
using Avalonia;
using Avalonia.Controls;
using Avalonia.Utilities;
namespace Ursa.Controls;
/// <summary>
/// TagInputPanel is a horizontal wrap with last item filling last row.
/// </summary>
public class TagInputPanel: Panel
{
protected override Size MeasureOverride(Size availableSize)
{
// return base.MeasureOverride(availableSize);
double currentLineX = 0;
double currentLineHeight = 0;
double totalHeight = 0;
var children = Children;
for (int i = 0; i < children.Count-1; i++)
{
var child = children[i];
child.Measure(availableSize);
double deltaX = availableSize.Width - currentLineX;
// Width is enough to place next child
if (MathUtilities.GreaterThan(deltaX, child.DesiredSize.Width))
{
currentLineX+=child.DesiredSize.Width;
currentLineHeight = Math.Max(currentLineHeight, child.DesiredSize.Height);
}
// Width is not enough to place next child
// reset currentLineX and currentLineHeight
// accumulate last line height to total height.
// Notice: last line height accumulation only happens when restarting a new line, so it needs to finally add one more time outside iteration.
else
{
currentLineX = child.DesiredSize.Width;
totalHeight += currentLineHeight;
currentLineHeight = child.DesiredSize.Height;
}
}
var last = children[children.Count - 1];
last.Measure(availableSize);
double lastDeltaX = availableSize.Width - currentLineX;
// If width is not enough, add a new line, and recalculate total height
if (lastDeltaX < 30)
{
totalHeight+=currentLineHeight;
totalHeight += last.DesiredSize.Height;
}
else
{
currentLineHeight = Math.Max(currentLineHeight, last.DesiredSize.Height);
totalHeight += currentLineHeight;
}
return new Size(availableSize.Width, totalHeight);
}
protected override Size ArrangeOverride(Size finalSize)
{
double currentLineX = 0;
double currentLineHeight = 0;
double totalHeight = 0;
var children = Children;
for (int i = 0; i < children.Count - 1; i++)
{
var child = children[i];
double deltaX = finalSize.Width - currentLineX;
// Width is enough to place next child
if (MathUtilities.GreaterThan(deltaX, child.DesiredSize.Width))
{
child.Arrange(new Rect(currentLineX, totalHeight, child.DesiredSize.Width, child.DesiredSize.Height));
currentLineX += child.DesiredSize.Width;
currentLineHeight = Math.Max(currentLineHeight, child.DesiredSize.Height);
}
// Width is not enough to place next child
// reset currentLineX and currentLineHeight
// accumulate last line height to total height.
// Notice: last line height accumulation only happens when restarting a new line, so it needs to finally add one more time outside iteration.
else
{
totalHeight += currentLineHeight;
child.Arrange(new Rect(0, totalHeight, finalSize.Width, child.DesiredSize.Height));
currentLineX = child.DesiredSize.Width;
currentLineHeight = child.DesiredSize.Height;
}
}
var last = children[children.Count - 1];
double lastDeltaX = finalSize.Width - currentLineX;
// If width is not enough, add a new line, and recalculate total height
if (lastDeltaX < 10)
{
totalHeight += currentLineHeight;
last.Arrange(new Rect(0, totalHeight, finalSize.Width, last.DesiredSize.Height));
totalHeight += last.DesiredSize.Height;
}
else
{
last.Arrange(new Rect(currentLineX, totalHeight,lastDeltaX, last.DesiredSize.Height));
currentLineHeight = Math.Max(currentLineHeight, last.DesiredSize.Height);
totalHeight += currentLineHeight;
}
return new Size(finalSize.Width, totalHeight);
}
}