mirror of
https://github.com/Artemis-RGB/Artemis
synced 2025-12-12 13:28:33 +00:00
Shared UI - Added tags input control
This commit is contained in:
parent
3a6171726c
commit
e9f2b77fd6
78
src/Artemis.UI.Shared/Controls/TagsInput/TagsInput.cs
Normal file
78
src/Artemis.UI.Shared/Controls/TagsInput/TagsInput.cs
Normal file
@ -0,0 +1,78 @@
|
||||
using System.Text.RegularExpressions;
|
||||
using System.Windows.Input;
|
||||
using Avalonia.Controls;
|
||||
using Avalonia.Controls.Metadata;
|
||||
using Avalonia.Controls.Primitives;
|
||||
using Avalonia.Input;
|
||||
using ReactiveUI;
|
||||
|
||||
namespace Artemis.UI.Shared.TagsInput;
|
||||
|
||||
[TemplatePart("PART_TagInputBox", typeof(TextBox))]
|
||||
public partial class TagsInput : TemplatedControl
|
||||
{
|
||||
public TextBox? TagInputBox { get; set; }
|
||||
public ICommand RemoveTag { get; }
|
||||
|
||||
/// <inheritdoc />
|
||||
public TagsInput()
|
||||
{
|
||||
RemoveTag = ReactiveCommand.Create<string>(ExecuteRemoveTag);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void OnApplyTemplate(TemplateAppliedEventArgs e)
|
||||
{
|
||||
if (TagInputBox != null)
|
||||
{
|
||||
TagInputBox.KeyDown -= TagInputBoxOnKeyDown;
|
||||
TagInputBox.TextChanging -= TagInputBoxOnTextChanging;
|
||||
}
|
||||
|
||||
TagInputBox = e.NameScope.Find<TextBox>("PART_TagInputBox");
|
||||
|
||||
if (TagInputBox != null)
|
||||
{
|
||||
TagInputBox.KeyDown += TagInputBoxOnKeyDown;
|
||||
TagInputBox.TextChanging += TagInputBoxOnTextChanging;
|
||||
}
|
||||
}
|
||||
|
||||
private void ExecuteRemoveTag(string t)
|
||||
{
|
||||
Tags.Remove(t);
|
||||
|
||||
if (TagInputBox != null)
|
||||
TagInputBox.IsEnabled = Tags.Count < MaxLength;
|
||||
}
|
||||
|
||||
private void TagInputBoxOnTextChanging(object? sender, TextChangingEventArgs e)
|
||||
{
|
||||
if (TagInputBox?.Text == null)
|
||||
return;
|
||||
|
||||
TagInputBox.Text = CleanTagRegex().Replace(TagInputBox.Text.ToLower(), "");
|
||||
}
|
||||
|
||||
private void TagInputBoxOnKeyDown(object? sender, KeyEventArgs e)
|
||||
{
|
||||
if (TagInputBox == null)
|
||||
return;
|
||||
|
||||
if (e.Key == Key.Space)
|
||||
e.Handled = true;
|
||||
if (e.Key != Key.Enter)
|
||||
return;
|
||||
|
||||
if (string.IsNullOrWhiteSpace(TagInputBox.Text) || Tags.Contains(TagInputBox.Text) || Tags.Count >= MaxLength)
|
||||
return;
|
||||
|
||||
Tags.Add(CleanTagRegex().Replace(TagInputBox.Text.ToLower(), ""));
|
||||
|
||||
TagInputBox.Text = "";
|
||||
TagInputBox.IsEnabled = Tags.Count < MaxLength;
|
||||
}
|
||||
|
||||
[GeneratedRegex("[\\s\\-]+")]
|
||||
private static partial Regex CleanTagRegex();
|
||||
}
|
||||
@ -0,0 +1,54 @@
|
||||
using System.Collections.ObjectModel;
|
||||
using Avalonia;
|
||||
using Avalonia.Controls.Primitives;
|
||||
using Avalonia.Data;
|
||||
|
||||
namespace Artemis.UI.Shared.TagsInput;
|
||||
|
||||
public partial class TagsInput : TemplatedControl
|
||||
{
|
||||
/// <summary>
|
||||
/// Defines the <see cref="Tags" /> property
|
||||
/// </summary>
|
||||
public static readonly StyledProperty<ObservableCollection<string>> TagsProperty =
|
||||
AvaloniaProperty.Register<TagsInput, ObservableCollection<string>>(nameof(Tags), new ObservableCollection<string>());
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the selected tags.
|
||||
/// </summary>
|
||||
public ObservableCollection<string> Tags
|
||||
{
|
||||
get => GetValue(TagsProperty);
|
||||
set => SetValue(TagsProperty, value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Defines the <see cref="MaxLength" /> property
|
||||
/// </summary>
|
||||
public static readonly StyledProperty<int> MaxLengthProperty =
|
||||
AvaloniaProperty.Register<TagsInput, int>(nameof(MaxLength), 20);
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the max length of each tag
|
||||
/// </summary>
|
||||
public int MaxLength
|
||||
{
|
||||
get => GetValue(MaxLengthProperty);
|
||||
set => SetValue(MaxLengthProperty, value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Defines the <see cref="MaxTags" /> property
|
||||
/// </summary>
|
||||
public static readonly StyledProperty<int> MaxTagsProperty =
|
||||
AvaloniaProperty.Register<TagsInput, int>(nameof(MaxTags), 20);
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the max amount of tags to be added
|
||||
/// </summary>
|
||||
public int MaxTags
|
||||
{
|
||||
get => GetValue(MaxTagsProperty);
|
||||
set => SetValue(MaxTagsProperty, value);
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,49 @@
|
||||
<ResourceDictionary xmlns="https://github.com/avaloniaui"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:ui="using:FluentAvalonia.UI.Controls"
|
||||
xmlns:tagsInput="clr-namespace:Artemis.UI.Shared.TagsInput"
|
||||
xmlns:avalonia="clr-namespace:Material.Icons.Avalonia;assembly=Material.Icons.Avalonia"
|
||||
x:CompileBindings="True">
|
||||
<Design.PreviewWith>
|
||||
<Border Padding="30" Width="400">
|
||||
<StackPanel Spacing="20">
|
||||
<tagsInput:TagsInput Name="TagsInput"/>
|
||||
<ItemsControl ItemsSource="{CompiledBinding Path=Tags, ElementName=TagsInput}"/>
|
||||
</StackPanel>
|
||||
</Border>
|
||||
</Design.PreviewWith>
|
||||
|
||||
<ControlTheme x:Key="{x:Type tagsInput:TagsInput}" TargetType="tagsInput:TagsInput">
|
||||
<Setter Property="Template">
|
||||
<ControlTemplate>
|
||||
<StackPanel>
|
||||
<TextBox Watermark="Enter tags" Name="PART_TagInputBox" MaxLines="1" MaxLength="{TemplateBinding MaxLength}">
|
||||
<TextBox.InnerLeftContent>
|
||||
<avalonia:MaterialIcon Kind="Tags" Margin="8 0 -2 0"></avalonia:MaterialIcon>
|
||||
</TextBox.InnerLeftContent>
|
||||
</TextBox>
|
||||
|
||||
<ItemsControl ItemsSource="{CompiledBinding Tags, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type tagsInput:TagsInput}}}">
|
||||
<ItemsControl.ItemsPanel>
|
||||
<ItemsPanelTemplate>
|
||||
<WrapPanel/>
|
||||
</ItemsPanelTemplate>
|
||||
</ItemsControl.ItemsPanel>
|
||||
<ItemsControl.DataTemplates>
|
||||
<DataTemplate DataType="x:String">
|
||||
<Button Margin="0 5 5 0"
|
||||
Command="{CompiledBinding RemoveTag, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type tagsInput:TagsInput}}}"
|
||||
CommandParameter="{CompiledBinding}">
|
||||
<StackPanel Orientation="Horizontal" Spacing="5">
|
||||
<avalonia:MaterialIcon Kind="Close" Margin="-5 0 0 0" Foreground="Gray" />
|
||||
<TextBlock Text="{CompiledBinding}"></TextBlock>
|
||||
</StackPanel>
|
||||
</Button>
|
||||
</DataTemplate>
|
||||
</ItemsControl.DataTemplates>
|
||||
</ItemsControl>
|
||||
</StackPanel>
|
||||
</ControlTemplate>
|
||||
</Setter>
|
||||
</ControlTheme>
|
||||
</ResourceDictionary>
|
||||
Loading…
x
Reference in New Issue
Block a user