From 9d249b01cddb6a178bcce01447c96f8e89895452 Mon Sep 17 00:00:00 2001 From: rabbitism Date: Thu, 29 Jun 2023 01:45:58 +0800 Subject: [PATCH] feat: initialize tag input --- demo/Ursa.Demo/Pages/TagInputDemo.axaml | 12 ++ demo/Ursa.Demo/Pages/TagInputDemo.axaml.cs | 13 +++ demo/Ursa.Demo/Views/MainWindow.axaml | 3 + src/Ursa.Themes.Semi/Controls/TagInput.axaml | 87 +++++++++++++++ src/Ursa.Themes.Semi/Controls/_index.axaml | 1 + src/Ursa/AssemblyInfo.cs | 2 +- src/Ursa/Controls/TagInput/TagInput.cs | 76 +++++++++++++ src/Ursa/Controls/TagInput/TagInputPanel.cs | 109 +++++++++++++++++++ 8 files changed, 302 insertions(+), 1 deletion(-) create mode 100644 demo/Ursa.Demo/Pages/TagInputDemo.axaml create mode 100644 demo/Ursa.Demo/Pages/TagInputDemo.axaml.cs create mode 100644 src/Ursa.Themes.Semi/Controls/TagInput.axaml create mode 100644 src/Ursa/Controls/TagInput/TagInput.cs create mode 100644 src/Ursa/Controls/TagInput/TagInputPanel.cs diff --git a/demo/Ursa.Demo/Pages/TagInputDemo.axaml b/demo/Ursa.Demo/Pages/TagInputDemo.axaml new file mode 100644 index 0000000..4cc5c4a --- /dev/null +++ b/demo/Ursa.Demo/Pages/TagInputDemo.axaml @@ -0,0 +1,12 @@ + + + diff --git a/demo/Ursa.Demo/Pages/TagInputDemo.axaml.cs b/demo/Ursa.Demo/Pages/TagInputDemo.axaml.cs new file mode 100644 index 0000000..f19dc0a --- /dev/null +++ b/demo/Ursa.Demo/Pages/TagInputDemo.axaml.cs @@ -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(); + } +} \ No newline at end of file diff --git a/demo/Ursa.Demo/Views/MainWindow.axaml b/demo/Ursa.Demo/Views/MainWindow.axaml index d6da845..1b6c162 100644 --- a/demo/Ursa.Demo/Views/MainWindow.axaml +++ b/demo/Ursa.Demo/Views/MainWindow.axaml @@ -47,6 +47,9 @@ + + + diff --git a/src/Ursa.Themes.Semi/Controls/TagInput.axaml b/src/Ursa.Themes.Semi/Controls/TagInput.axaml new file mode 100644 index 0000000..9539c85 --- /dev/null +++ b/src/Ursa.Themes.Semi/Controls/TagInput.axaml @@ -0,0 +1,87 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/Ursa.Themes.Semi/Controls/_index.axaml b/src/Ursa.Themes.Semi/Controls/_index.axaml index 7c621a3..bbf2752 100644 --- a/src/Ursa.Themes.Semi/Controls/_index.axaml +++ b/src/Ursa.Themes.Semi/Controls/_index.axaml @@ -9,6 +9,7 @@ + diff --git a/src/Ursa/AssemblyInfo.cs b/src/Ursa/AssemblyInfo.cs index 0b0375e..af243bf 100644 --- a/src/Ursa/AssemblyInfo.cs +++ b/src/Ursa/AssemblyInfo.cs @@ -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")] \ No newline at end of file diff --git a/src/Ursa/Controls/TagInput/TagInput.cs b/src/Ursa/Controls/TagInput/TagInput.cs new file mode 100644 index 0000000..21bde20 --- /dev/null +++ b/src/Ursa/Controls/TagInput/TagInput.cs @@ -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> TagsProperty = AvaloniaProperty.Register>( + nameof(Tags)); + + private TextBox _textBox; + + public IList Tags + { + get => GetValue(TagsProperty); + set => SetValue(TagsProperty, value); + } + + public static readonly StyledProperty ItemsProperty = AvaloniaProperty.Register( + nameof(Items)); + + public IList Items + { + get => GetValue(ItemsProperty); + set => SetValue(ItemsProperty, value); + } + + public TagInput() + { + _textBox = new TextBox(); + } + + public static readonly StyledProperty InputThemeProperty = AvaloniaProperty.Register( + nameof(InputTheme)); + + public ControlTheme InputTheme + { + get => GetValue(InputThemeProperty); + set => SetValue(InputThemeProperty, value); + } + + public static readonly StyledProperty ItemTemplateProperty = AvaloniaProperty.Register( + nameof(ItemTemplate)); + + public IDataTemplate? ItemTemplate + { + get => GetValue(ItemTemplateProperty); + set => SetValue(ItemTemplateProperty, value); + } + + protected override void OnApplyTemplate(TemplateAppliedEventArgs e) + { + base.OnApplyTemplate(e); + Items = new AvaloniaList(); + 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); + } +} \ No newline at end of file diff --git a/src/Ursa/Controls/TagInput/TagInputPanel.cs b/src/Ursa/Controls/TagInput/TagInputPanel.cs new file mode 100644 index 0000000..afd19f4 --- /dev/null +++ b/src/Ursa/Controls/TagInput/TagInputPanel.cs @@ -0,0 +1,109 @@ +using Avalonia; +using Avalonia.Controls; +using Avalonia.Utilities; + +namespace Ursa.Controls; + +/// +/// TagInputPanel is a horizontal wrap with last item filling last row. +/// +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); + } +} \ No newline at end of file