Procházet zdrojové kódy

Node Abbreviations concept

CPKreuz před 1 rokem
rodič
revize
5f884a89c5

+ 104 - 0
src/PixiEditor/Helpers/Nodes/NodeAbbreviation.cs

@@ -0,0 +1,104 @@
+using System.Buffers;
+using PixiEditor.Models.Nodes;
+
+namespace PixiEditor.Helpers.Nodes;
+
+public static class NodeAbbreviation
+{
+    private static readonly SearchValues<char> SearchFor = SearchValues.Create(['.']);
+
+    
+    public static bool IsAbbreviation(string value, out string? lastValue)
+    {
+        var span = value.AsSpan();
+
+        int i = span.LastIndexOfAny(SearchFor);
+
+        if (i == -1)
+        {
+            lastValue = null;
+            return false;
+        }
+
+        lastValue = span[(i + 1)..].ToString();
+        return true;
+    }
+    
+    public static List<NodeTypeInfo>? FromString(string value, ICollection<NodeTypeInfo> allNodes)
+    {
+        var span = value.AsSpan();
+
+        if (!span.ContainsAny(SearchFor))
+        {
+            return null;
+        }
+        
+        var list = new List<NodeTypeInfo>();
+
+        var enumerator = new PartEnumerator(span, SearchFor);
+
+        foreach (var name in enumerator)
+        {
+            var lookFor = name.Name.ToString();
+            var node = allNodes.First(SearchComparer);
+
+            list.Add(node);
+
+            continue;
+
+            bool SearchComparer(NodeTypeInfo x) =>
+                x.FinalPickerName.Value.Replace(" ", "").Contains(lookFor.Replace(" ", ""), StringComparison.OrdinalIgnoreCase);
+        }
+
+        return list;
+    }
+
+    private readonly ref struct PartResult(ReadOnlySpan<char> name, char? separator)
+    {
+        public ReadOnlySpan<char> Name { get; } = name;
+
+        public char? Separator { get; } = separator;
+    }
+
+    private ref struct PartEnumerator(ReadOnlySpan<char> name, SearchValues<char> searchFor)
+    {
+        private bool isDone;
+        private ReadOnlySpan<char> _remaining = name;
+        private PartResult _current;
+
+        public PartResult Current => _current;
+
+        /// <summary>
+        /// Returns this instance as an enumerator.
+        /// </summary>
+        public PartEnumerator GetEnumerator() => this;
+
+        public bool MoveNext()
+        {
+            if (isDone)
+                return false;
+
+            int i = _remaining.IndexOfAny(searchFor);
+
+            if (i == -1)
+            {
+                isDone = true;
+
+                if (_remaining.Length == 0)
+                {
+                    return false;
+                }
+
+                _current = new PartResult(_remaining, null);
+                return true;
+            }
+
+            _current = new PartResult(_remaining[..i], _remaining[i]);
+
+            i++;
+            _remaining = _remaining[i..];
+
+            return true;
+        }
+    }
+}

+ 2 - 1
src/PixiEditor/Styles/Templates/NodePicker.axaml

@@ -15,7 +15,8 @@
                     </Grid.RowDefinitions>
 
                     <input:InputBox
-                        Text="{Binding SearchQuery, RelativeSource={RelativeSource TemplatedParent}, Mode=TwoWay}" />
+                        Text="{Binding SearchQuery, RelativeSource={RelativeSource TemplatedParent}, Mode=TwoWay}"
+                        Name="PART_InputBox" />
                     <ItemsControl MinHeight="200" Grid.Row="1" ItemsSource="{TemplateBinding FilteredNodeTypeInfos}">
                         <ItemsControl.ItemTemplate>
                             <DataTemplate DataType="nodeModels:NodeTypeInfo">

+ 47 - 5
src/PixiEditor/Views/Nodes/NodePicker.cs

@@ -2,15 +2,20 @@
 using System.Windows.Input;
 using Avalonia;
 using Avalonia.Controls;
+using Avalonia.Controls.Metadata;
 using Avalonia.Controls.Primitives;
+using Avalonia.Input;
 using Avalonia.Interactivity;
 using Avalonia.Markup.Xaml;
 using CommunityToolkit.Mvvm.Input;
+using PixiEditor.Helpers.Nodes;
 using PixiEditor.Models.Nodes;
 using PixiEditor.Numerics;
+using PixiEditor.Views.Input;
 
 namespace PixiEditor.Views.Nodes;
 
+[TemplatePart("PART_InputBox", typeof(InputBox))]
 public partial class NodePicker : TemplatedControl
 {
     public static readonly StyledProperty<string> SearchQueryProperty = AvaloniaProperty.Register<NodePicker, string>(
@@ -49,13 +54,20 @@ public partial class NodePicker : TemplatedControl
         get => GetValue(SelectNodeCommandProperty);
         set => SetValue(SelectNodeCommandProperty, value);
     }
-    
+
     static NodePicker()
     {
         SearchQueryProperty.Changed.Subscribe(OnSearchQueryChanged);
         AllNodeTypeInfosProperty.Changed.Subscribe(OnAllNodeTypesChanged);
     }
 
+    protected override void OnApplyTemplate(TemplateAppliedEventArgs e)
+    {
+        var inputBox = e.NameScope.Find<InputBox>("PART_InputBox");
+        
+        inputBox.KeyDown += OnInputBoxKeyDown;
+    }
+
     private static void OnSearchQueryChanged(AvaloniaPropertyChangedEventArgs e)
     {
         if (e.Sender is not NodePicker nodePicker)
@@ -63,16 +75,46 @@ public partial class NodePicker : TemplatedControl
             return;
         }
 
-        nodePicker.FilteredNodeTypeInfos = new ObservableCollection<NodeTypeInfo>(nodePicker.AllNodeTypeInfos
-            .Where(SearchComparer));
+        if (NodeAbbreviation.IsAbbreviation(nodePicker.SearchQuery, out var abbreviationName))
+        {
+            nodePicker.FilteredNodeTypeInfos = new ObservableCollection<NodeTypeInfo>(nodePicker.AllNodeTypeInfos
+                .Where(x => SearchComparer(x, abbreviationName)));
+        }
+        else
+        {
+            nodePicker.FilteredNodeTypeInfos = new ObservableCollection<NodeTypeInfo>(nodePicker.AllNodeTypeInfos
+                .Where(x => SearchComparer(x, nodePicker.SearchQuery)));
+        }
 
         return;
 
-        bool SearchComparer(NodeTypeInfo x) =>
+        bool SearchComparer(NodeTypeInfo x, string lookFor) =>
             x.FinalPickerName.Value.Replace(" ", "")
-                .Contains(nodePicker.SearchQuery.Replace(" ", ""), StringComparison.OrdinalIgnoreCase);
+                .Contains(lookFor.Replace(" ", ""), StringComparison.OrdinalIgnoreCase);
     }
     
+    private void OnInputBoxKeyDown(object? sender, KeyEventArgs e)
+    {
+        if (e.Key != Key.Enter)
+        {
+            return;
+        }
+
+        var nodes = NodeAbbreviation.FromString(SearchQuery, AllNodeTypeInfos);
+
+        if (nodes == null && FilteredNodeTypeInfos.Count > 0)
+        {
+            SelectNodeCommand.Execute(FilteredNodeTypeInfos[0]);
+        }
+        else
+        {
+            foreach (var node in nodes)
+            {
+                SelectNodeCommand.Execute(node);
+            }
+        }
+    }
+
     private static void OnAllNodeTypesChanged(AvaloniaPropertyChangedEventArgs e)
     {
         if (e.Sender is NodePicker nodePicker)