Skip to content

Commit

Permalink
Add dragging methods to NodifyEditor (#159)
Browse files Browse the repository at this point in the history
* Add dragging methods to NodifyEditor
  • Loading branch information
miroiu authored Nov 27, 2024
1 parent edd3ff8 commit e4c5a50
Show file tree
Hide file tree
Showing 7 changed files with 223 additions and 154 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
> - Added BeginPanning, UpdatePanning, EndPanning, CancelPanning and AllowPanningCancellation to NodifyEditor
> - Added UpdateCuttingLine to NodifyEditor
> - Added BeginSelecting, UpdateSelection, EndSelecting, CancelSelecting and AllowSelectionCancellation to NodifyEditor
> - Added IsDragging, BeginDragging, UpdateDragging, EndDragging and CancelDragging to NodifyEditor
> - Bugfixes:
#### **Version 6.6.0**
Expand Down
22 changes: 8 additions & 14 deletions Nodify/Helpers/PushItemsStrategy.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Windows;

Expand All @@ -12,12 +11,11 @@ internal interface IPushStrategy
Rect Push(Vector amount);
Rect End();
Rect Cancel();
Rect OnViewportChanged();
Rect GetPushedArea();
}

internal abstract class BasePushStrategy : IPushStrategy
{
private IDraggingStrategy? _draggingStrategy;
private const double _minOffset = 2;
private double _actualOffset;
private double _initialPosition;
Expand All @@ -33,7 +31,7 @@ public BasePushStrategy(NodifyEditor editor)
public Rect Start(Point position)
{
var containers = GetFilteredContainers(position);
_draggingStrategy = Editor.CreateDraggingStrategy(containers);
Editor.BeginDragging(containers);

_initialPosition = GetInitialPosition(position);
_actualOffset = 0;
Expand All @@ -43,10 +41,8 @@ public Rect Start(Point position)

public Rect Push(Vector amount)
{
Debug.Assert(_draggingStrategy != null);

var offset = GetPushOffset(amount);
_draggingStrategy!.Update(offset);
Editor.UpdateDragging(offset);

_actualOffset += offset.X;
_actualOffset += offset.Y;
Expand All @@ -59,23 +55,21 @@ public Rect Push(Vector amount)

public Rect End()
{
Debug.Assert(_draggingStrategy != null);
_draggingStrategy!.End();
Editor.EndDragging();
return new Rect();
}

public Rect Cancel()
{
Debug.Assert(_draggingStrategy != null);
_draggingStrategy!.Abort();
Editor.CancelDragging();
return new Rect();
}

protected abstract IEnumerable<ItemContainer> GetFilteredContainers(Point position);
protected abstract double GetInitialPosition(Point position);
protected abstract Vector GetPushOffset(Vector offset);
protected abstract Rect CalculatePushedArea(double position, double offset);
public abstract Rect OnViewportChanged();
public abstract Rect GetPushedArea();
}

internal sealed class HorizontalPushStrategy : BasePushStrategy
Expand All @@ -96,7 +90,7 @@ protected override Vector GetPushOffset(Vector offset)
protected override Rect CalculatePushedArea(double position, double offset)
=> new Rect(position, Editor.ViewportLocation.Y - OffscreenOffset, offset, Editor.ViewportSize.Height + OffscreenOffset * 2);

public override Rect OnViewportChanged()
public override Rect GetPushedArea()
=> CalculatePushedArea(Editor.PushedArea.X, Editor.PushedArea.Width);
}

Expand All @@ -118,7 +112,7 @@ protected override Vector GetPushOffset(Vector offset)
protected override Rect CalculatePushedArea(double position, double offset)
=> new Rect(Editor.ViewportLocation.X - OffscreenOffset, position, Editor.ViewportSize.Width + OffscreenOffset * 2, offset);

public override Rect OnViewportChanged()
public override Rect GetPushedArea()
=> CalculatePushedArea(Editor.PushedArea.Y, Editor.PushedArea.Height);
}
}
4 changes: 2 additions & 2 deletions Nodify/ItemContainer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -25,12 +25,12 @@ public class ItemContainer : ContentControl, INodifyCanvasItem
public static readonly DependencyProperty SelectedBorderThicknessProperty = DependencyProperty.Register(nameof(SelectedBorderThickness), typeof(Thickness), typeof(ItemContainer), new FrameworkPropertyMetadata(BoxValue.Thickness2));
public static readonly DependencyProperty IsSelectableProperty = DependencyProperty.Register(nameof(IsSelectable), typeof(bool), typeof(ItemContainer), new FrameworkPropertyMetadata(BoxValue.True));
public static readonly DependencyProperty IsSelectedProperty = Selector.IsSelectedProperty.AddOwner(typeof(ItemContainer), new FrameworkPropertyMetadata(BoxValue.False, FrameworkPropertyMetadataOptions.BindsTwoWayByDefault, OnIsSelectedChanged));
public static readonly DependencyPropertyKey IsPreviewingSelectionPropertyKey = DependencyProperty.RegisterReadOnly(nameof(IsPreviewingSelection), typeof(bool?), typeof(ItemContainer), new FrameworkPropertyMetadata(null));
protected static readonly DependencyPropertyKey IsPreviewingSelectionPropertyKey = DependencyProperty.RegisterReadOnly(nameof(IsPreviewingSelection), typeof(bool?), typeof(ItemContainer), new FrameworkPropertyMetadata(null));
public static readonly DependencyProperty IsPreviewingSelectionProperty = IsPreviewingSelectionPropertyKey.DependencyProperty;
public static readonly DependencyProperty LocationProperty = DependencyProperty.Register(nameof(Location), typeof(Point), typeof(ItemContainer), new FrameworkPropertyMetadata(BoxValue.Point, FrameworkPropertyMetadataOptions.BindsTwoWayByDefault, OnLocationChanged));
public static readonly DependencyProperty ActualSizeProperty = DependencyProperty.Register(nameof(ActualSize), typeof(Size), typeof(ItemContainer), new FrameworkPropertyMetadata(BoxValue.Size));
public static readonly DependencyProperty DesiredSizeForSelectionProperty = DependencyProperty.Register(nameof(DesiredSizeForSelection), typeof(Size?), typeof(ItemContainer), new FrameworkPropertyMetadata(null, FrameworkPropertyMetadataOptions.NotDataBindable));
public static readonly DependencyPropertyKey IsPreviewingLocationPropertyKey = DependencyProperty.RegisterReadOnly(nameof(IsPreviewingLocation), typeof(bool), typeof(ItemContainer), new FrameworkPropertyMetadata(BoxValue.False));
private static readonly DependencyPropertyKey IsPreviewingLocationPropertyKey = DependencyProperty.RegisterReadOnly(nameof(IsPreviewingLocation), typeof(bool), typeof(ItemContainer), new FrameworkPropertyMetadata(BoxValue.False));
public static readonly DependencyProperty IsPreviewingLocationProperty = IsPreviewingLocationPropertyKey.DependencyProperty;
public static readonly DependencyProperty IsDraggableProperty = DependencyProperty.Register(nameof(IsDraggable), typeof(bool), typeof(ItemContainer), new FrameworkPropertyMetadata(BoxValue.True));

Expand Down
2 changes: 1 addition & 1 deletion Nodify/Nodes/Node.cs
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ public class Node : HeaderedContentControl
public static readonly DependencyProperty FooterProperty = DependencyProperty.Register(nameof(Footer), typeof(object), typeof(Node), new FrameworkPropertyMetadata(OnFooterChanged));
public static readonly DependencyProperty FooterTemplateProperty = DependencyProperty.Register(nameof(FooterTemplate), typeof(DataTemplate), typeof(Node));
public static readonly DependencyProperty InputConnectorTemplateProperty = DependencyProperty.Register(nameof(InputConnectorTemplate), typeof(DataTemplate), typeof(Node));
protected internal static readonly DependencyPropertyKey HasFooterPropertyKey = DependencyProperty.RegisterReadOnly(nameof(HasFooter), typeof(bool), typeof(Node), new FrameworkPropertyMetadata(BoxValue.False));
protected static readonly DependencyPropertyKey HasFooterPropertyKey = DependencyProperty.RegisterReadOnly(nameof(HasFooter), typeof(bool), typeof(Node), new FrameworkPropertyMetadata(BoxValue.False));
public static readonly DependencyProperty HasFooterProperty = HasFooterPropertyKey.DependencyProperty;
public static readonly DependencyProperty OutputConnectorTemplateProperty = DependencyProperty.Register(nameof(OutputConnectorTemplate), typeof(DataTemplate), typeof(Node));
public static readonly DependencyProperty InputProperty = DependencyProperty.Register(nameof(Input), typeof(IEnumerable), typeof(Node));
Expand Down
190 changes: 190 additions & 0 deletions Nodify/NodifyEditor.Dragging.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,190 @@
using System.Windows.Input;
using System.Windows;
using System.Collections.Generic;
using System.Windows.Controls.Primitives;
using System.Diagnostics;

namespace Nodify
{
public partial class NodifyEditor
{
public static readonly DependencyProperty ItemsDragStartedCommandProperty = DependencyProperty.Register(nameof(ItemsDragStartedCommand), typeof(ICommand), typeof(NodifyEditor));
public static readonly DependencyProperty ItemsDragCompletedCommandProperty = DependencyProperty.Register(nameof(ItemsDragCompletedCommand), typeof(ICommand), typeof(NodifyEditor));

protected static readonly DependencyPropertyKey IsDraggingPropertyKey = DependencyProperty.RegisterReadOnly(nameof(IsDragging), typeof(bool), typeof(NodifyEditor), new FrameworkPropertyMetadata(BoxValue.False, OnIsDraggingChanged));
public static readonly DependencyProperty IsDraggingProperty = IsDraggingPropertyKey.DependencyProperty;

private static void OnIsDraggingChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var editor = (NodifyEditor)d;

if ((bool)e.NewValue == true)
{
editor.OnItemsDragStarted();
}
else
{
editor.OnItemsDragCompleted();
}
}

private void OnItemsDragCompleted()
{
if (ItemsDragCompletedCommand?.CanExecute(DataContext) ?? false)
ItemsDragCompletedCommand.Execute(DataContext);
}

private void OnItemsDragStarted()
{
if (ItemsDragStartedCommand?.CanExecute(DataContext) ?? false)
ItemsDragStartedCommand.Execute(DataContext);
}

/// <summary>
/// Invoked when a drag operation starts for the <see cref="SelectedItems"/>, or when <see cref="IsPushingItems"/> is set to true.
/// </summary>
public ICommand? ItemsDragStartedCommand
{
get => (ICommand?)GetValue(ItemsDragStartedCommandProperty);
set => SetValue(ItemsDragStartedCommandProperty, value);
}

/// <summary>
/// Invoked when a drag operation is completed for the <see cref="SelectedItems"/>, or when <see cref="IsPushingItems"/> is set to false.
/// </summary>
public ICommand? ItemsDragCompletedCommand
{
get => (ICommand?)GetValue(ItemsDragCompletedCommandProperty);
set => SetValue(ItemsDragCompletedCommandProperty, value);
}

/// <summary>
/// Gets a value that indicates whether a dragging operation is in progress.
/// </summary>
public bool IsDragging
{
get => (bool)GetValue(IsDraggingProperty);
private set => SetValue(IsDraggingPropertyKey, value);
}

/// <summary>
/// Gets or sets if the current position of containers that are being dragged should not be committed until the end of the dragging operation.
/// </summary>
public static bool EnableDraggingContainersOptimizations { get; set; } = true;

private IDraggingStrategy? _draggingStrategy;

private void RegisterDragEvents()
{
AddHandler(ItemContainer.DragStartedEvent, new DragStartedEventHandler(OnItemsDragStarted));
AddHandler(ItemContainer.DragCompletedEvent, new DragCompletedEventHandler(OnItemsDragCompleted));
AddHandler(ItemContainer.DragDeltaEvent, new DragDeltaEventHandler(OnItemsDragDelta));
}

/// <summary>
/// Initiates the dragging operation using the currently selected <see cref="ItemContainer" />s.
/// </summary>
/// <remarks>This method has no effect if a dragging operation is already in progress.</remarks>
public void BeginDragging()
=> BeginDragging(SelectedContainers);

/// <summary>
/// Initiates the dragging operation for the specified <see cref="ItemContainer" />s.
/// </summary>
/// <param name="containers">The collection of item containers to be dragged.</param>
/// <remarks>This method has no effect if a dragging operation is already in progress.</remarks>
public void BeginDragging(IEnumerable<ItemContainer> containers)
{
if(IsDragging)
{
return;
}

IsDragging = true;
_draggingStrategy = CreateDraggingStrategy(containers);
}

/// <summary>
/// Updates the position of the items being dragged by a specified offset.
/// </summary>
/// <param name="amount">The vector by which to adjust the position of the dragged items.</param>
/// <remarks>
/// This method adjusts the items positions incrementally. It should only be called while a dragging operation is in progress (see <see cref="BeginDragging" />).
/// </remarks>
public void UpdateDragging(Vector amount)
{
Debug.Assert(IsDragging);
_draggingStrategy!.Update(amount);
}

/// <summary>
/// Completes the dragging operation, finalizing the position of the dragged items.
/// </summary>
/// <remarks>This method has no effect if there's no dragging operation in progress.</remarks>
public void EndDragging()
{
if (!IsDragging)
{
return;
}

IsBulkUpdatingItems = true;
_draggingStrategy!.End();
IsBulkUpdatingItems = false;

// Draw the containers at the new position.
ItemsHost.InvalidateArrange();

_draggingStrategy = null;
IsDragging = false;
}

/// <summary>
/// Cancels the ongoing dragging operation, reverting any changes made to the positions of the dragged items.
/// </summary>
/// <remarks>This method has no effect if there's no dragging operation in progress.</remarks>
public void CancelDragging()
{
if (!ItemContainer.AllowDraggingCancellation || !IsDragging)
{
return;
}

_draggingStrategy!.Abort();
IsDragging = false;
}

private IDraggingStrategy CreateDraggingStrategy(IEnumerable<ItemContainer> containers)
{
if (EnableDraggingContainersOptimizations)
{
return new DraggingOptimized(containers, GridCellSize);
}

return new DraggingSimple(containers, GridCellSize);
}

private void OnItemsDragStarted(object sender, DragStartedEventArgs e)
{
BeginDragging();
e.Handled = true;
}

private void OnItemsDragDelta(object sender, DragDeltaEventArgs e)
{
UpdateDragging(new Vector(e.HorizontalChange, e.VerticalChange));
}

private void OnItemsDragCompleted(object sender, DragCompletedEventArgs e)
{
if (e.Canceled)
{
CancelDragging();
}
else
{
EndDragging();
}
}
}
}
Loading

0 comments on commit e4c5a50

Please sign in to comment.