Browse Source

Added nodes multi selection with a rectangle drag

flabbet 7 months ago
parent
commit
b201b452c5

+ 136 - 128
src/PixiEditor/Styles/Templates/NodeGraphView.axaml

@@ -7,147 +7,155 @@
         <Setter Property="Template">
             <ControlTemplate>
                 <Grid Background="Transparent">
+
+                    <Rectangle Name="PART_SelectionRectangle" HorizontalAlignment="Left"
+                               VerticalAlignment="Top"
+                               IsVisible="False" ZIndex="100"
+                               Fill="{DynamicResource SelectionFillBrush}" Opacity="1" />
                     <Grid.ContextFlyout>
                         <Flyout>
                             <nodes:NodePicker
                                 AllNodeTypeInfos="{Binding AllNodeTypeInfos, RelativeSource={RelativeSource TemplatedParent}}"
                                 SearchQuery="{Binding SearchQuery, RelativeSource={RelativeSource TemplatedParent}, Mode=TwoWay}"
-                                SelectNodeCommand="{Binding CreateNodeFromContextCommand, RelativeSource={RelativeSource TemplatedParent}}"
-                                />
+                                SelectNodeCommand="{Binding CreateNodeFromContextCommand, RelativeSource={RelativeSource TemplatedParent}}" />
                         </Flyout>
-                        </Grid.ContextFlyout>
-                        <ItemsControl ZIndex="1" ClipToBounds="False"
-                                      Name="PART_Nodes"
-                                      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 NodeNameBindable}"
-                                        CategoryBackgroundBrush="{Binding CategoryBackgroundBrush}"
-                                        Inputs="{Binding Inputs}"
-                                        ActiveFrame="{Binding ActiveFrame, RelativeSource={RelativeSource FindAncestor, AncestorType=nodes:NodeGraphView}}"
-                                        BorderBrush="{Binding InternalName, Converter={converters:NodeInternalNameToStyleConverter}, ConverterParameter='BorderBrush'}"
-                                        BorderThickness="2"
-                                        Outputs="{Binding Outputs}"
-                                        IsSelected="{Binding IsNodeSelected}"
-                                        SelectNodeCommand="{Binding SelectNodeCommand,
+                    </Grid.ContextFlyout>
+                    <ItemsControl ZIndex="1" ClipToBounds="False"
+                                  Name="PART_Nodes"
+                                  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 NodeNameBindable}"
+                                    CategoryBackgroundBrush="{Binding CategoryBackgroundBrush}"
+                                    Inputs="{Binding Inputs}"
+                                    ActiveFrame="{Binding ActiveFrame, RelativeSource={RelativeSource FindAncestor, AncestorType=nodes:NodeGraphView}}"
+                                    BorderBrush="{Binding InternalName, Converter={converters:NodeInternalNameToStyleConverter}, ConverterParameter='BorderBrush'}"
+                                    BorderThickness="2"
+                                    Outputs="{Binding Outputs}"
+                                    IsSelected="{Binding IsNodeSelected}"
+                                    SelectNodeCommand="{Binding SelectNodeCommand,
                                     RelativeSource={RelativeSource FindAncestor, AncestorType=nodes:NodeGraphView}}"
-                                        StartDragCommand="{Binding StartDraggingCommand, RelativeSource={RelativeSource FindAncestor, AncestorType=nodes:NodeGraphView}}"
-                                        DragCommand="{Binding DraggedCommand, RelativeSource={RelativeSource FindAncestor, AncestorType=nodes:NodeGraphView}}"
-                                        EndDragCommand="{Binding EndDragCommand,
+                                    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}}"
-                                        SocketDropCommand="{Binding SocketDropCommand,
+                                    SocketDropCommand="{Binding SocketDropCommand,
                                         RelativeSource={RelativeSource FindAncestor, AncestorType=nodes:NodeGraphView}}"
-                                        ResultPreview="{Binding ResultPainter}" />
-                                </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}">
-                                        <nodes:ConnectionView.IsVisible>
-                                            <MultiBinding Converter="{x:Static BoolConverters.And}">
-                                                <Binding Path="InputProperty.IsVisible" />
-                                                <Binding Path="OutputProperty.IsVisible" />
-                                            </MultiBinding>
-                                        </nodes:ConnectionView.IsVisible>
-                                    </nodes:ConnectionView>
-                                </DataTemplate>
-                            </ItemsControl.ItemTemplate>
-                        </ItemsControl>
+                                    ResultPreview="{Binding ResultPainter}" />
+                            </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}">
+                                    <nodes:ConnectionView.IsVisible>
+                                        <MultiBinding Converter="{x:Static BoolConverters.And}">
+                                            <Binding Path="InputProperty.IsVisible" />
+                                            <Binding Path="OutputProperty.IsVisible" />
+                                        </MultiBinding>
+                                    </nodes:ConnectionView.IsVisible>
+                                </nodes:ConnectionView>
+                            </DataTemplate>
+                        </ItemsControl.ItemTemplate>
+                    </ItemsControl>
                     <ItemsControl
                         ZIndex="-1"
                         Name="PART_Frames"
                         ItemsSource="{Binding NodeGraph.Frames, 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:NodeFrameView
-                                        TopLeft="{Binding TopLeft}"
-                                        BottomRight="{Binding BottomRight}"
-                                        Size="{Binding Size}">
-                                        <nodes:NodeFrameView.Background>
-                                            <MultiBinding Converter="{converters:UnsetSkipMultiConverter}">
-                                                 <Binding Path="InternalName" Converter="{converters:NodeInternalNameToStyleConverter}" ConverterParameter="BackgroundBrush" />
-                                                 <DynamicResource ResourceKey="NodeFrameBackgroundBrush"/>
-                                            </MultiBinding>
-                                        </nodes:NodeFrameView.Background>
-                                        <nodes:NodeFrameView.BorderBrush>
-                                            <MultiBinding Converter="{converters:UnsetSkipMultiConverter}">
-                                                <Binding Path="InternalName" Converter="{converters:NodeInternalNameToStyleConverter}" ConverterParameter="BorderBrush" />
-                                                <DynamicResource ResourceKey="NodeFrameBorderBrush"/>
-                                            </MultiBinding>
-                                        </nodes:NodeFrameView.BorderBrush>
-                                    </nodes:NodeFrameView>
-                                </DataTemplate>
-                            </ItemsControl.ItemTemplate>
-                            <ItemsControl.ItemContainerTheme>
-                                <ControlTheme TargetType="ContentPresenter">
-                                    <Setter Property="Canvas.Left" Value="{Binding TopLeft.X}" />
-                                    <Setter Property="Canvas.Top" Value="{Binding TopLeft.Y}" />
-                                </ControlTheme>
-                            </ItemsControl.ItemContainerTheme>
-                        </ItemsControl>
+                        <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:NodeFrameView
+                                    TopLeft="{Binding TopLeft}"
+                                    BottomRight="{Binding BottomRight}"
+                                    Size="{Binding Size}">
+                                    <nodes:NodeFrameView.Background>
+                                        <MultiBinding Converter="{converters:UnsetSkipMultiConverter}">
+                                            <Binding Path="InternalName"
+                                                     Converter="{converters:NodeInternalNameToStyleConverter}"
+                                                     ConverterParameter="BackgroundBrush" />
+                                            <DynamicResource ResourceKey="NodeFrameBackgroundBrush" />
+                                        </MultiBinding>
+                                    </nodes:NodeFrameView.Background>
+                                    <nodes:NodeFrameView.BorderBrush>
+                                        <MultiBinding Converter="{converters:UnsetSkipMultiConverter}">
+                                            <Binding Path="InternalName"
+                                                     Converter="{converters:NodeInternalNameToStyleConverter}"
+                                                     ConverterParameter="BorderBrush" />
+                                            <DynamicResource ResourceKey="NodeFrameBorderBrush" />
+                                        </MultiBinding>
+                                    </nodes:NodeFrameView.BorderBrush>
+                                </nodes:NodeFrameView>
+                            </DataTemplate>
+                        </ItemsControl.ItemTemplate>
+                        <ItemsControl.ItemContainerTheme>
+                            <ControlTheme TargetType="ContentPresenter">
+                                <Setter Property="Canvas.Left" Value="{Binding TopLeft.X}" />
+                                <Setter Property="Canvas.Top" Value="{Binding TopLeft.Y}" />
+                            </ControlTheme>
+                        </ItemsControl.ItemContainerTheme>
+                    </ItemsControl>
                 </Grid>
             </ControlTemplate>
         </Setter>

+ 77 - 5
src/PixiEditor/Views/Nodes/NodeGraphView.cs

@@ -3,13 +3,16 @@ using System.Collections.Specialized;
 using System.Windows.Input;
 using Avalonia;
 using Avalonia.Controls;
+using Avalonia.Controls.Metadata;
 using Avalonia.Controls.Presenters;
 using Avalonia.Controls.Primitives;
+using Avalonia.Controls.Shapes;
 using Avalonia.Input;
 using Avalonia.Media;
 using Avalonia.Threading;
 using Avalonia.VisualTree;
 using CommunityToolkit.Mvvm.Input;
+using Drawie.Backend.Core.Numerics;
 using PixiEditor.Helpers;
 using PixiEditor.ViewModels.Document;
 using PixiEditor.ChangeableDocument.Changeables.Graph.Nodes;
@@ -22,6 +25,7 @@ using Point = Avalonia.Point;
 
 namespace PixiEditor.Views.Nodes;
 
+[TemplatePart("PART_SelectionRectangle", typeof(Rectangle))]
 internal class NodeGraphView : Zoombox.Zoombox
 {
     public static readonly StyledProperty<INodeGraphHandler> NodeGraphProperty =
@@ -190,9 +194,15 @@ internal class NodeGraphView : Zoombox.Zoombox
     private NodeConnectionViewModel? _hiddenConnection;
     private Color _startingPropColor;
     private VecD _lastMouseClickPos;
+    private Point _lastMousePos;
 
     private ItemsControl nodeItemsControl;
     private ItemsControl connectionItemsControl;
+    private Rectangle selectionRectangle;
+    
+    private List<Control> nodeViewsOnPress = new();
+
+    private bool isSelecting;
 
     public static readonly StyledProperty<int> ActiveFrameProperty =
         AvaloniaProperty.Register<NodeGraphView, int>("ActiveFrame");
@@ -215,6 +225,7 @@ internal class NodeGraphView : Zoombox.Zoombox
         base.OnApplyTemplate(e);
         nodeItemsControl = e.NameScope.Find<ItemsControl>("PART_Nodes");
         connectionItemsControl = e.NameScope.Find<ItemsControl>("PART_Connections");
+        selectionRectangle = e.NameScope.Find<Rectangle>("PART_SelectionRectangle");
 
         Dispatcher.UIThread.Post(() =>
         {
@@ -254,16 +265,16 @@ internal class NodeGraphView : Zoombox.Zoombox
                 nodeView.PropertyChanged += NodeView_PropertyChanged;
             }
         }
-        
-        if(e.Property == Canvas.LeftProperty || e.Property == Canvas.TopProperty)
+
+        if (e.Property == Canvas.LeftProperty || e.Property == Canvas.TopProperty)
         {
             if (e.Sender is ContentPresenter presenter && presenter.Child is NodeView nodeView)
             {
                 Dispatcher.UIThread.Post(
                     () =>
-                {
-                    UpdateConnections(nodeView);
-                }, DispatcherPriority.Render);
+                    {
+                        UpdateConnections(nodeView);
+                    }, DispatcherPriority.Render);
             }
         }
     }
@@ -285,9 +296,19 @@ internal class NodeGraphView : Zoombox.Zoombox
         if (e.GetMouseButton(this) == MouseButton.Left)
         {
             ClearSelection();
+            isSelecting = true;
+            selectionRectangle.IsVisible = true;
+            nodeViewsOnPress = nodeItemsControl.ItemsPanelRoot.Children.ToList();
+            e.Handled = true;
+        }
+        else
+        {
+            isSelecting = false;
+            selectionRectangle.IsVisible = false;
         }
 
         Point pos = e.GetPosition(this);
+        _lastMousePos = pos;
         _lastMouseClickPos = ToZoomboxSpace(new VecD(pos.X, pos.Y));
     }
 
@@ -297,6 +318,35 @@ internal class NodeGraphView : Zoombox.Zoombox
         {
             UpdateConnectionEnd(e);
         }
+        else if (isSelecting)
+        {
+            var pos = e.GetPosition(this);
+            Point currentPoint = new Point(pos.X, pos.Y);
+
+            float x = (float)Math.Min(_lastMousePos.X, currentPoint.X);
+            float y = (float)Math.Min(_lastMousePos.Y, currentPoint.Y);
+            float width = (float)Math.Abs(_lastMousePos.X - currentPoint.X);
+            float height = (float)Math.Abs(_lastMousePos.Y - currentPoint.Y);
+
+            selectionRectangle.Width = width;
+            selectionRectangle.Height = height;
+            Thickness margin = new Thickness(x, y, 0, 0);
+
+            selectionRectangle.Margin = margin;
+
+            
+            VecD zoomboxSpacePos = ToZoomboxSpace(new VecD(x, y));
+            VecD zoomboxSpaceSize = ToZoomboxSpace(new VecD(x + width, y + height));
+            
+            x = (float)zoomboxSpacePos.X;
+            y = (float)zoomboxSpacePos.Y;
+            width = (float)(zoomboxSpaceSize.X - zoomboxSpacePos.X);
+            height = (float)(zoomboxSpaceSize.Y - zoomboxSpacePos.Y);
+
+            Rect zoomboxSpaceRect = new Rect(x, y, width, height);
+            ClearSelection();
+            SelectWithinBounds(zoomboxSpaceRect);
+        }
     }
 
     private void UpdateConnectionEnd(PointerEventArgs e)
@@ -347,6 +397,20 @@ internal class NodeGraphView : Zoombox.Zoombox
         }
     }
 
+    private void SelectWithinBounds(Rect rect)
+    {
+        foreach (var control in nodeViewsOnPress)
+        {
+            if(control.Bounds.Intersects(rect))
+            {
+                if (control is ContentPresenter { Child: NodeView nodeView })
+                {
+                    nodeView.Node.IsNodeSelected = true;
+                }
+            }
+        }
+    }
+
     private static Color? GetSocketColor(NodeSocket? nodeSocket)
     {
         if (nodeSocket == null)
@@ -386,6 +450,14 @@ internal class NodeGraphView : Zoombox.Zoombox
             _hiddenConnection = null;
         }
 
+        if (isSelecting)
+        {
+            isSelecting = false;
+            selectionRectangle.IsVisible = false;
+            selectionRectangle.Width = 0;
+            selectionRectangle.Height = 0;
+        }
+
         if (e.Source is NodeView nodeView)
         {
             UpdateConnections(nodeView);