feat: initialize tag input
This commit is contained in:
12
demo/Ursa.Demo/Pages/TagInputDemo.axaml
Normal file
12
demo/Ursa.Demo/Pages/TagInputDemo.axaml
Normal 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>
|
||||||
13
demo/Ursa.Demo/Pages/TagInputDemo.axaml.cs
Normal file
13
demo/Ursa.Demo/Pages/TagInputDemo.axaml.cs
Normal 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();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -47,6 +47,9 @@
|
|||||||
<TabItem Header="Pagination">
|
<TabItem Header="Pagination">
|
||||||
<pages:PaginationDemo />
|
<pages:PaginationDemo />
|
||||||
</TabItem>
|
</TabItem>
|
||||||
|
<TabItem Header="TagInput">
|
||||||
|
<pages:TagInputDemo />
|
||||||
|
</TabItem>
|
||||||
<TabItem Header="Timeline">
|
<TabItem Header="Timeline">
|
||||||
<pages:TimelineDemo />
|
<pages:TimelineDemo />
|
||||||
</TabItem>
|
</TabItem>
|
||||||
|
|||||||
87
src/Ursa.Themes.Semi/Controls/TagInput.axaml
Normal file
87
src/Ursa.Themes.Semi/Controls/TagInput.axaml
Normal 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>
|
||||||
@@ -9,6 +9,7 @@
|
|||||||
<ResourceInclude Source="Loading.axaml" />
|
<ResourceInclude Source="Loading.axaml" />
|
||||||
<ResourceInclude Source="Navigation.axaml" />
|
<ResourceInclude Source="Navigation.axaml" />
|
||||||
<ResourceInclude Source="Pagination.axaml" />
|
<ResourceInclude Source="Pagination.axaml" />
|
||||||
|
<ResourceInclude Source="TagInput.axaml" />
|
||||||
<ResourceInclude Source="Timeline.axaml" />
|
<ResourceInclude Source="Timeline.axaml" />
|
||||||
</ResourceDictionary.MergedDictionaries>
|
</ResourceDictionary.MergedDictionaries>
|
||||||
</ResourceDictionary>
|
</ResourceDictionary>
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
using Avalonia.Metadata;
|
using Avalonia.Metadata;
|
||||||
|
|
||||||
|
[assembly:XmlnsPrefix("https://irihi.tech/ursa", "u")]
|
||||||
[assembly:XmlnsDefinition("https://irihi.tech/ursa", "Ursa")]
|
[assembly:XmlnsDefinition("https://irihi.tech/ursa", "Ursa")]
|
||||||
[assembly:XmlnsDefinition("https://irihi.tech/ursa", "Ursa.Controls")]
|
[assembly:XmlnsDefinition("https://irihi.tech/ursa", "Ursa.Controls")]
|
||||||
[assembly:XmlnsPrefix("https://irihi.tech/ursa", "u")]
|
|
||||||
76
src/Ursa/Controls/TagInput/TagInput.cs
Normal file
76
src/Ursa/Controls/TagInput/TagInput.cs
Normal 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
109
src/Ursa/Controls/TagInput/TagInputPanel.cs
Normal file
109
src/Ursa/Controls/TagInput/TagInputPanel.cs
Normal 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user