Browse Source

Create Node

Krzysztof Krysiński 1 year ago
parent
commit
f41ca62b4b

+ 1 - 0
src/PixiEditor.AvaloniaUI/Styles/PixiEditor.Controls.axaml

@@ -17,6 +17,7 @@
                 <MergeResourceInclude Source="avares://PixiEditor.AvaloniaUI/Styles/Templates/NodeView.axaml"/>
                 <MergeResourceInclude Source="avares://PixiEditor.AvaloniaUI/Styles/Templates/NodeSocket.axaml"/>
                 <MergeResourceInclude Source="avares://PixiEditor.AvaloniaUI/Styles/Templates/ConnectionView.axaml"/>
+                <MergeResourceInclude Source="avares://PixiEditor.AvaloniaUI/Styles/Templates/NodePicker.axaml"/>
             </ResourceDictionary.MergedDictionaries>
         </ResourceDictionary>
     </Styles.Resources>

+ 77 - 70
src/PixiEditor.AvaloniaUI/Styles/Templates/NodeGraphView.axaml

@@ -6,78 +6,85 @@
         <Setter Property="Template">
             <ControlTemplate>
                 <Grid Background="Transparent">
-                    <ItemsControl ClipToBounds="False"
-                                  ItemsSource="{Binding NodeGraph.AllNodes, RelativeSource={RelativeSource TemplatedParent}}">
-                        <ItemsControl.ItemsPanel>
-                            <ItemsPanelTemplate>
-                                <Canvas RenderTransformOrigin="0, 0">
-                                    <Canvas.RenderTransform>
-                                        <TransformGroup>
-                                            <ScaleTransform
-                                                ScaleX="{Binding Scale, RelativeSource={RelativeSource FindAncestor, AncestorType=nodes:NodeGraphView}}"
-                                                ScaleY="{Binding Scale, RelativeSource={RelativeSource FindAncestor, AncestorType=nodes:NodeGraphView}}" />
-                                            <TranslateTransform
-                                                X="{Binding CanvasX, RelativeSource={RelativeSource FindAncestor, AncestorType=nodes:NodeGraphView}}"
-                                                Y="{Binding CanvasY, RelativeSource={RelativeSource FindAncestor, AncestorType=nodes:NodeGraphView}}" />
-                                        </TransformGroup>
-                                    </Canvas.RenderTransform>
-                                </Canvas>
-                            </ItemsPanelTemplate>
-                        </ItemsControl.ItemsPanel>
-                        <ItemsControl.ItemTemplate>
-                            <DataTemplate>
-                                <nodes:NodeView
-                                    Node="{Binding}"
-                                    DisplayName="{Binding NodeName}"
-                                    Inputs="{Binding Inputs}"
-                                    Outputs="{Binding Outputs}"
-                                    IsSelected="{Binding IsSelected}"
-                                    SelectNodeCommand="{Binding SelectNodeCommand, 
+                    <Grid.ContextFlyout>
+                        <Flyout>
+                            <nodes:NodePicker
+                                AllNodeTypes="{Binding AllNodeTypes, RelativeSource={RelativeSource TemplatedParent}}"
+                                SearchQuery="{Binding SearchQuery, RelativeSource={RelativeSource TemplatedParent}, Mode=TwoWay}"
+                                SelectNodeCommand="{Binding CreateNodeCommand, RelativeSource={RelativeSource TemplatedParent}}"
+                                />
+                        </Flyout>
+                        </Grid.ContextFlyout>
+                        <ItemsControl ClipToBounds="False"
+                                      ItemsSource="{Binding NodeGraph.AllNodes, RelativeSource={RelativeSource TemplatedParent}}">
+                            <ItemsControl.ItemsPanel>
+                                <ItemsPanelTemplate>
+                                    <Canvas RenderTransformOrigin="0, 0">
+                                        <Canvas.RenderTransform>
+                                            <TransformGroup>
+                                                <ScaleTransform
+                                                    ScaleX="{Binding Scale, RelativeSource={RelativeSource FindAncestor, AncestorType=nodes:NodeGraphView}}"
+                                                    ScaleY="{Binding Scale, RelativeSource={RelativeSource FindAncestor, AncestorType=nodes:NodeGraphView}}" />
+                                                <TranslateTransform
+                                                    X="{Binding CanvasX, RelativeSource={RelativeSource FindAncestor, AncestorType=nodes:NodeGraphView}}"
+                                                    Y="{Binding CanvasY, RelativeSource={RelativeSource FindAncestor, AncestorType=nodes:NodeGraphView}}" />
+                                            </TransformGroup>
+                                        </Canvas.RenderTransform>
+                                    </Canvas>
+                                </ItemsPanelTemplate>
+                            </ItemsControl.ItemsPanel>
+                            <ItemsControl.ItemTemplate>
+                                <DataTemplate>
+                                    <nodes:NodeView
+                                        Node="{Binding}"
+                                        DisplayName="{Binding NodeName}"
+                                        Inputs="{Binding Inputs}"
+                                        Outputs="{Binding Outputs}"
+                                        IsSelected="{Binding IsSelected}"
+                                        SelectNodeCommand="{Binding SelectNodeCommand,
                                     RelativeSource={RelativeSource FindAncestor, AncestorType=nodes:NodeGraphView}}"
-                                    StartDragCommand="{Binding StartDraggingCommand,
+                                        StartDragCommand="{Binding StartDraggingCommand, RelativeSource={RelativeSource FindAncestor, AncestorType=nodes:NodeGraphView}}"
+                                        DragCommand="{Binding DraggedCommand, RelativeSource={RelativeSource FindAncestor, AncestorType=nodes:NodeGraphView}}"
+                                        EndDragCommand="{Binding EndDragCommand,
                                         RelativeSource={RelativeSource FindAncestor, AncestorType=nodes:NodeGraphView}}"
-                                    DragCommand="{Binding DraggedCommand,
-                                        RelativeSource={RelativeSource FindAncestor, AncestorType=nodes:NodeGraphView}}"
-                                    EndDragCommand="{Binding EndDragCommand,
-                                        RelativeSource={RelativeSource FindAncestor, AncestorType=nodes:NodeGraphView}}"
-                                    ResultPreview="{Binding ResultPreview}" />
-                            </DataTemplate>
-                        </ItemsControl.ItemTemplate>
-                        <ItemsControl.ItemContainerTheme>
-                            <ControlTheme TargetType="ContentPresenter">
-                                <Setter Property="Canvas.Left" Value="{Binding PositionBindable.X}" />
-                                <Setter Property="Canvas.Top" Value="{Binding PositionBindable.Y}" />
-                            </ControlTheme>
-                        </ItemsControl.ItemContainerTheme>
-                    </ItemsControl>
-                    <ItemsControl Name="PART_Connections"
-                        ItemsSource="{Binding NodeGraph.Connections, RelativeSource={RelativeSource TemplatedParent}}">
-                        <ItemsControl.ItemsPanel>
-                            <ItemsPanelTemplate>
-                                <Canvas RenderTransformOrigin="0, 0">
-                                    <Canvas.RenderTransform>
-                                        <TransformGroup>
-                                            <ScaleTransform
-                                                ScaleX="{Binding Scale, RelativeSource={RelativeSource FindAncestor, AncestorType=nodes:NodeGraphView}}"
-                                                ScaleY="{Binding Scale, RelativeSource={RelativeSource FindAncestor, AncestorType=nodes:NodeGraphView}}" />
-                                            <TranslateTransform
-                                                X="{Binding CanvasX, RelativeSource={RelativeSource FindAncestor, AncestorType=nodes:NodeGraphView}}"
-                                                Y="{Binding CanvasY, RelativeSource={RelativeSource FindAncestor, AncestorType=nodes:NodeGraphView}}" />
-                                        </TransformGroup>
-                                    </Canvas.RenderTransform>
-                                </Canvas>
-                            </ItemsPanelTemplate>
-                        </ItemsControl.ItemsPanel>
-                        <ItemsControl.ItemTemplate>
-                            <DataTemplate>
-                                <nodes:ConnectionView
-                                    InputNodePosition="{Binding InputNode.PositionBindable}"
-                                    OutputNodePosition="{Binding OutputNode.PositionBindable}"
-                                    InputProperty="{Binding InputProperty}"
-                                    OutputProperty="{Binding OutputProperty}" />
-                            </DataTemplate>
-                        </ItemsControl.ItemTemplate>
-                    </ItemsControl>
+                                        ResultPreview="{Binding ResultPreview}" />
+                                </DataTemplate>
+                            </ItemsControl.ItemTemplate>
+                            <ItemsControl.ItemContainerTheme>
+                                <ControlTheme TargetType="ContentPresenter">
+                                    <Setter Property="Canvas.Left" Value="{Binding PositionBindable.X}" />
+                                    <Setter Property="Canvas.Top" Value="{Binding PositionBindable.Y}" />
+                                </ControlTheme>
+                            </ItemsControl.ItemContainerTheme>
+                        </ItemsControl>
+                        <ItemsControl Name="PART_Connections"
+                                      ItemsSource="{Binding NodeGraph.Connections, RelativeSource={RelativeSource TemplatedParent}}">
+                            <ItemsControl.ItemsPanel>
+                                <ItemsPanelTemplate>
+                                    <Canvas RenderTransformOrigin="0, 0">
+                                        <Canvas.RenderTransform>
+                                            <TransformGroup>
+                                                <ScaleTransform
+                                                    ScaleX="{Binding Scale, RelativeSource={RelativeSource FindAncestor, AncestorType=nodes:NodeGraphView}}"
+                                                    ScaleY="{Binding Scale, RelativeSource={RelativeSource FindAncestor, AncestorType=nodes:NodeGraphView}}" />
+                                                <TranslateTransform
+                                                    X="{Binding CanvasX, RelativeSource={RelativeSource FindAncestor, AncestorType=nodes:NodeGraphView}}"
+                                                    Y="{Binding CanvasY, RelativeSource={RelativeSource FindAncestor, AncestorType=nodes:NodeGraphView}}" />
+                                            </TransformGroup>
+                                        </Canvas.RenderTransform>
+                                    </Canvas>
+                                </ItemsPanelTemplate>
+                            </ItemsControl.ItemsPanel>
+                            <ItemsControl.ItemTemplate>
+                                <DataTemplate>
+                                    <nodes:ConnectionView
+                                        InputNodePosition="{Binding InputNode.PositionBindable}"
+                                        OutputNodePosition="{Binding OutputNode.PositionBindable}"
+                                        InputProperty="{Binding InputProperty}"
+                                        OutputProperty="{Binding OutputProperty}" />
+                                </DataTemplate>
+                            </ItemsControl.ItemTemplate>
+                        </ItemsControl>
                 </Grid>
             </ControlTemplate>
         </Setter>

+ 30 - 0
src/PixiEditor.AvaloniaUI/Styles/Templates/NodePicker.axaml

@@ -0,0 +1,30 @@
+<ResourceDictionary xmlns="https://github.com/avaloniaui"
+                    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
+                    xmlns:nodes="clr-namespace:PixiEditor.AvaloniaUI.Views.Nodes"
+                    xmlns:visuals="clr-namespace:PixiEditor.AvaloniaUI.Views.Visuals"
+                    xmlns:input="clr-namespace:PixiEditor.AvaloniaUI.Views.Input">
+    <ControlTheme TargetType="nodes:NodePicker" x:Key="{x:Type nodes:NodePicker}">
+        <Setter Property="Template">
+            <ControlTemplate>
+                <Grid MinWidth="200">
+                    <Grid.RowDefinitions>
+                        <RowDefinition Height="Auto" />
+                        <RowDefinition Height="*" />
+                    </Grid.RowDefinitions>
+
+                    <input:InputBox
+                        Text="{Binding SearchQuery, RelativeSource={RelativeSource TemplatedParent}, Mode=TwoWay}" />
+                    <ItemsControl MinHeight="200" Grid.Row="1" ItemsSource="{TemplateBinding FilteredNodeTypes}">
+                        <ItemsControl.ItemTemplate>
+                            <DataTemplate>
+                                <Button Command="{Binding SelectNodeCommand, RelativeSource={RelativeSource FindAncestor, AncestorType=nodes:NodePicker}}"
+                                        CommandParameter="{Binding}"
+                                        Content="{Binding Name}" />
+                            </DataTemplate>
+                        </ItemsControl.ItemTemplate>
+                    </ItemsControl>
+                </Grid>
+            </ControlTemplate>
+        </Setter>
+    </ControlTheme>
+</ResourceDictionary>

+ 5 - 0
src/PixiEditor.AvaloniaUI/ViewModels/Document/NodeGraphViewModel.cs

@@ -109,4 +109,9 @@ internal class NodeGraphViewModel : ViewModelBase, INodeGraphHandler
     {
         Internals.ActionAccumulator.AddFinishedActions(new EndNodePosition_Action());
     }
+
+    public void CreateNode(Type nodeType)
+    {
+        Internals.ActionAccumulator.AddFinishedActions(new CreateNode_Action(nodeType, Guid.NewGuid()));
+    }
 }

+ 6 - 0
src/PixiEditor.AvaloniaUI/ViewModels/SubViewModels/NodeGraphManagerViewModel.cs

@@ -10,6 +10,12 @@ internal class NodeGraphManagerViewModel : SubViewModel<ViewModelMain>
     {
     }
 
+    [Command.Internal("PixiEditor.NodeGraph.CreateNode")]
+    public void CreateNode(Type nodeType)
+    {
+        Owner.DocumentManagerSubViewModel.ActiveDocument?.NodeGraph.CreateNode(nodeType);
+    }
+
     [Command.Internal("PixiEditor.NodeGraph.ChangeNodePos")]
     public void ChangeNodePos((INodeHandler node, VecD newPos) args)
     {

+ 2 - 1
src/PixiEditor.AvaloniaUI/Views/Dock/NodeGraphDockView.axaml

@@ -11,7 +11,8 @@
     <Design.DataContext>
         <dock:NodeGraphDockViewModel/>
     </Design.DataContext>
-    <nodes:NodeGraphView 
+    <nodes:NodeGraphView
+        CreateNodeCommand="{xaml:Command PixiEditor.NodeGraph.CreateNode, UseProvided=True}"
         ChangeNodePosCommand="{xaml:Command PixiEditor.NodeGraph.ChangeNodePos, UseProvided=True}"
         EndChangeNodePosCommand="{xaml:Command PixiEditor.NodeGraph.EndChangeNodePos, UseProvided=True}"
         NodeGraph="{Binding DocumentManagerSubViewModel.ActiveDocument.NodeGraph}"/>

+ 36 - 4
src/PixiEditor.AvaloniaUI/Views/Nodes/NodeGraphView.cs

@@ -11,6 +11,7 @@ using PixiEditor.AvaloniaUI.Helpers;
 using PixiEditor.AvaloniaUI.Models.Handlers;
 using PixiEditor.AvaloniaUI.ViewModels.Document;
 using PixiEditor.AvaloniaUI.ViewModels.Nodes;
+using PixiEditor.ChangeableDocument.Changeables.Graph.Nodes;
 using PixiEditor.Numerics;
 using Point = Avalonia.Point;
 
@@ -46,6 +47,25 @@ internal class NodeGraphView : Zoombox.Zoombox
         AvaloniaProperty.Register<NodeGraphView, ICommand>(
             nameof(EndChangeNodePosCommand));
 
+    public static readonly StyledProperty<string> SearchQueryProperty = AvaloniaProperty.Register<NodeGraphView, string>(
+        nameof(SearchQuery));
+
+    public static readonly StyledProperty<ObservableCollection<Type>> AllNodeTypesProperty = AvaloniaProperty.Register<NodeGraphView, ObservableCollection<Type>>(
+        "AllNodeTypes");
+
+    public static readonly StyledProperty<ICommand> CreateNodeCommandProperty = AvaloniaProperty.Register<NodeGraphView, ICommand>("CreateNodeCommand");
+    public ObservableCollection<Type> AllNodeTypes
+    {
+        get => GetValue(AllNodeTypesProperty);
+        set => SetValue(AllNodeTypesProperty, value);
+    }
+
+    public string SearchQuery
+    {
+        get => GetValue(SearchQueryProperty);
+        set => SetValue(SearchQueryProperty, value);
+    }
+
     public ICommand EndChangeNodePosCommand
     {
         get => GetValue(EndChangeNodePosCommandProperty);
@@ -94,6 +114,12 @@ internal class NodeGraphView : Zoombox.Zoombox
 
     protected override Type StyleKeyOverride => typeof(NodeGraphView);
 
+    public ICommand CreateNodeCommand
+    {
+        get { return (ICommand)GetValue(CreateNodeCommandProperty); }
+        set { SetValue(CreateNodeCommandProperty, value); }
+    }
+
     private bool isDraggingNodes;
     private VecD clickPointOffset;
 
@@ -105,11 +131,8 @@ internal class NodeGraphView : Zoombox.Zoombox
         StartDraggingCommand = new RelayCommand<PointerPressedEventArgs>(StartDragging);
         DraggedCommand = new RelayCommand<PointerEventArgs>(Dragged);
         EndDragCommand = new RelayCommand<PointerCaptureLostEventArgs>(EndDrag);
-    }
 
-    protected override void OnApplyTemplate(TemplateAppliedEventArgs e)
-    {
-        base.OnApplyTemplate(e);
+        AllNodeTypes = new ObservableCollection<Type>(GatherAssemblyTypes<Node>());
     }
 
     protected override void OnPointerPressed(PointerPressedEventArgs e)
@@ -117,7 +140,16 @@ internal class NodeGraphView : Zoombox.Zoombox
         base.OnPointerPressed(e);
 
         if (e.GetMouseButton(this) == MouseButton.Left)
+        {
             ClearSelection();
+        }
+    }
+
+    private IEnumerable<Type> GatherAssemblyTypes<T>()
+    {
+        return AppDomain.CurrentDomain.GetAssemblies()
+            .SelectMany(x => x.GetTypes())
+            .Where(x => typeof(T).IsAssignableFrom(x) && !x.IsInterface && !x.IsAbstract);
     }
 
     private void StartDragging(PointerPressedEventArgs e)

+ 69 - 0
src/PixiEditor.AvaloniaUI/Views/Nodes/NodePicker.cs

@@ -0,0 +1,69 @@
+using System.Collections.ObjectModel;
+using System.Windows.Input;
+using Avalonia;
+using Avalonia.Controls;
+using Avalonia.Controls.Primitives;
+using Avalonia.Interactivity;
+using Avalonia.Markup.Xaml;
+
+namespace PixiEditor.AvaloniaUI.Views.Nodes;
+
+public partial class NodePicker : TemplatedControl
+{
+    public static readonly StyledProperty<string> SearchQueryProperty = AvaloniaProperty.Register<NodePicker, string>(
+        nameof(SearchQuery));
+
+    public string SearchQuery
+    {
+        get => GetValue(SearchQueryProperty);
+        set => SetValue(SearchQueryProperty, value);
+    }
+
+    public static readonly StyledProperty<ObservableCollection<Type>> AllNodeTypesProperty =
+        AvaloniaProperty.Register<NodePicker, ObservableCollection<Type>>(
+            "AllNodeTypes");
+
+    public static readonly StyledProperty<ObservableCollection<Type>> FilteredNodeTypesProperty =
+        AvaloniaProperty.Register<NodePicker, ObservableCollection<Type>>(nameof(FilteredNodeTypes));
+
+    public ObservableCollection<Type> AllNodeTypes
+    {
+        get => GetValue(AllNodeTypesProperty);
+        set => SetValue(AllNodeTypesProperty, value);
+    }
+
+    public ObservableCollection<Type> FilteredNodeTypes
+    {
+        get { return (ObservableCollection<Type>)GetValue(FilteredNodeTypesProperty); }
+        set { SetValue(FilteredNodeTypesProperty, value); }
+    }
+
+    public static readonly StyledProperty<ICommand> SelectNodeCommandProperty = AvaloniaProperty.Register<NodePicker, ICommand>(
+        nameof(SelectNodeCommand));
+
+    public ICommand SelectNodeCommand
+    {
+        get => GetValue(SelectNodeCommandProperty);
+        set => SetValue(SelectNodeCommandProperty, value);
+    }
+
+    static NodePicker()
+    {
+        SearchQueryProperty.Changed.Subscribe(OnSearchQueryChanged);
+    }
+
+    public NodePicker()
+    {
+
+    }
+
+    private static void OnSearchQueryChanged(AvaloniaPropertyChangedEventArgs e)
+    {
+        if (e.Sender is NodePicker nodePicker)
+        {
+            nodePicker.FilteredNodeTypes = new ObservableCollection<Type>(nodePicker.AllNodeTypes
+                .Where(x => x.Name.ToLower().Contains(nodePicker.SearchQuery.ToLower())));
+        }
+    }
+}
+

+ 12 - 0
src/PixiEditor.ChangeableDocument/Changeables/Graph/Factories/ImageLayerNodeFactory.cs

@@ -0,0 +1,12 @@
+using PixiEditor.ChangeableDocument.Changeables.Graph.Nodes;
+using PixiEditor.ChangeableDocument.Changeables.Interfaces;
+
+namespace PixiEditor.ChangeableDocument.Changeables.Graph.Factories;
+
+public class ImageLayerNodeFactory : NodeFactory<ImageLayerNode>
+{
+    public override T CreateNode<T>(IReadOnlyDocument document)
+    {
+        return (T)(object)new ImageLayerNode(document.Size);
+    }
+}

+ 30 - 0
src/PixiEditor.ChangeableDocument/Changeables/Graph/NodeFactory.cs

@@ -0,0 +1,30 @@
+using PixiEditor.ChangeableDocument.Changeables.Graph.Nodes;
+using PixiEditor.ChangeableDocument.Changeables.Interfaces;
+
+namespace PixiEditor.ChangeableDocument.Changeables.Graph;
+
+public abstract class NodeFactory
+{
+    public Type NodeType { get; }
+
+    public NodeFactory(Type nodeType)
+    {
+        NodeType = nodeType;
+    }
+
+    public abstract Node CreateNode(IReadOnlyDocument document);
+}
+
+public abstract class NodeFactory<T> : NodeFactory where T : Node
+{
+    public NodeFactory() : base(typeof(T))
+    {
+    }
+
+    public abstract T CreateNode<T>(IReadOnlyDocument document) where T : Node;
+
+    public override Node CreateNode(IReadOnlyDocument document)
+    {
+        return CreateNode<T>(document);
+    }
+}

+ 25 - 1
src/PixiEditor.ChangeableDocument/Changes/NodeGraph/CreateNode_Change.cs

@@ -1,8 +1,11 @@
 using System.Collections.Immutable;
+using System.Reflection;
+using PixiEditor.ChangeableDocument.Changeables.Graph;
 using PixiEditor.ChangeableDocument.Changeables.Graph.Interfaces;
 using PixiEditor.ChangeableDocument.Changeables.Graph.Nodes;
 using PixiEditor.ChangeableDocument.ChangeInfos.NodeGraph;
 using PixiEditor.Numerics;
+using Type = System.Type;
 
 namespace PixiEditor.ChangeableDocument.Changes.NodeGraph;
 
@@ -10,12 +13,24 @@ internal class CreateNode_Change : Change
 {
     private Type nodeType;
     private Guid id;
+    private static Dictionary<Type, NodeFactory> allFactories;
     
     [GenerateMakeChangeAction]
     public CreateNode_Change(Type nodeType, Guid id)
     {
         this.id = id;
         this.nodeType = nodeType;
+
+        if (allFactories == null)
+        {
+            allFactories = new Dictionary<Type, NodeFactory>();
+            var factoryTypes = Assembly.GetExecutingAssembly().GetTypes().Where(x => x.IsSubclassOf(typeof(NodeFactory)) && !x.IsAbstract && !x.IsInterface).ToImmutableArray();
+            foreach (var factoryType in factoryTypes)
+            {
+                NodeFactory factory = (NodeFactory)Activator.CreateInstance(factoryType);
+                allFactories.Add(factory.NodeType, factory);
+            }
+        }
     }
     
     public override bool InitializeAndValidate(Document target)
@@ -27,8 +42,17 @@ internal class CreateNode_Change : Change
     {
         if(id == Guid.Empty)
             id = Guid.NewGuid();
+
+        Node node = null;
+        if (allFactories.TryGetValue(nodeType, out NodeFactory factory))
+        {
+            node = factory.CreateNode(target);
+        }
+        else
+        {
+            node = (Node)Activator.CreateInstance(nodeType);
+        }
         
-        Node node = (Node)Activator.CreateInstance(nodeType);
         node.Position = new VecD(0, 0);
         node.Id = id;
         target.NodeGraph.AddNode(node);