1 //----------------------------------------------------------------
2 // <copyright company="Microsoft Corporation">
3 // Copyright (c) Microsoft Corporation. All rights reserved.
5 //----------------------------------------------------------------
7 namespace System.Activities.Presentation.FreeFormEditing
9 using System.Activities.Presentation.Model;
10 using System.Collections.Generic;
13 using System.Windows.Documents;
14 using System.Windows.Media;
16 internal sealed class AutoConnectHelper
18 private const double HitRegionOffset = 30;
19 private const double DropTargetWidth = 20;
20 private const double DropPointOffset = 50;
22 private FreeFormPanel panel = null;
23 private UIElement currentTarget = null;
25 public AutoConnectHelper(FreeFormPanel panel)
30 internal UIElement CurrentTarget
34 UIElement target = this.currentTarget;
36 // It is possible that currentTarget has been removed from the FreeFormPanel
37 if (target != null && VisualTreeHelper.GetParent(target) == null)
39 this.currentTarget = null;
48 this.currentTarget = value;
52 internal static Rect GetAutoConnectHitRect(DependencyObject target)
54 Size size = FreeFormPanel.GetChildSize(target);
55 Point location = FreeFormPanel.GetLocation(target);
56 Rect rect = new Rect(new Point(location.X - HitRegionOffset, location.Y - HitRegionOffset), new Size(size.Width + (HitRegionOffset * 2), size.Height + (HitRegionOffset * 2)));
61 // * Diagram: Hit test rect for auto connect (best view with Courier New)
70 // * ┌────────┴─┴────────┐ ┴ ┬
72 // * ┌────────┤ ├────────┐ ┴ a H
73 // * └────────┤ ├────────┘ ┬
75 // * └────────┬─┬────────┘ ┬ ┴
83 // * O: HitRegionOffset
84 // * a: DropTargetWidth
86 internal static List<Rect> CreateHitTestRects(Point targetLocation, Size targetSize)
88 List<Rect> rects = new List<Rect>();
90 // See the diagram above for these rects
91 rects.Add(new Rect(new Point(targetLocation.X - HitRegionOffset, targetLocation.Y + ((targetSize.Height - DropTargetWidth) / 2)), new Size(HitRegionOffset, DropTargetWidth)));
92 rects.Add(new Rect(new Point(targetLocation.X + targetSize.Width, targetLocation.Y + ((targetSize.Height - DropTargetWidth) / 2)), new Size(HitRegionOffset, DropTargetWidth)));
93 rects.Add(new Rect(new Point(targetLocation.X + ((targetSize.Width - DropTargetWidth) / 2), targetLocation.Y - HitRegionOffset), new Size(DropTargetWidth, HitRegionOffset)));
94 rects.Add(new Rect(new Point(targetLocation.X + ((targetSize.Width - DropTargetWidth) / 2), targetLocation.Y + targetSize.Height), new Size(DropTargetWidth, HitRegionOffset)));
98 internal static AutoConnectDirections GetAutoConnectDirection(int index)
103 return AutoConnectDirections.Left;
105 return AutoConnectDirections.Right;
107 return AutoConnectDirections.Top;
109 return AutoConnectDirections.Bottom;
111 return AutoConnectDirections.None;
115 internal static Point CalculateDropLocation(Size droppedSize, DependencyObject autoConnectTarget, AutoConnectDirections direction, HashSet<Point> shapeLocations)
117 Point dropPoint = new Point(-1, -1);
118 if (autoConnectTarget != null)
120 Point location = FreeFormPanel.GetLocation(autoConnectTarget);
121 Size size = FreeFormPanel.GetChildSize(autoConnectTarget);
124 case AutoConnectDirections.Left:
125 dropPoint = new Point(location.X - DropPointOffset - droppedSize.Width, location.Y + ((size.Height - droppedSize.Height) / 2));
127 case AutoConnectDirections.Right:
128 dropPoint = new Point(location.X + size.Width + DropPointOffset, location.Y + ((size.Height - droppedSize.Height) / 2));
130 case AutoConnectDirections.Top:
131 dropPoint = new Point(location.X + ((size.Width - droppedSize.Width) / 2), location.Y - DropPointOffset - droppedSize.Height);
133 case AutoConnectDirections.Bottom:
134 dropPoint = new Point(location.X + ((size.Width - droppedSize.Width) / 2), location.Y + DropPointOffset + size.Height);
137 Fx.Assert(false, "Should not be here");
141 dropPoint = new Point(dropPoint.X < 0 ? 0 : dropPoint.X, dropPoint.Y < 0 ? 0 : dropPoint.Y);
142 if (shapeLocations != null)
144 while (shapeLocations.Contains(dropPoint))
146 dropPoint.Offset(FreeFormPanel.GridSize, FreeFormPanel.GridSize);
154 internal static EdgeLocation AutoConnectDirection2EdgeLocation(AutoConnectDirections direction)
156 EdgeLocation edgeLocation = EdgeLocation.Right;
159 case AutoConnectDirections.Left:
160 edgeLocation = EdgeLocation.Left;
162 case AutoConnectDirections.Right:
163 edgeLocation = EdgeLocation.Right;
165 case AutoConnectDirections.Top:
166 edgeLocation = EdgeLocation.Top;
168 case AutoConnectDirections.Bottom:
169 edgeLocation = EdgeLocation.Bottom;
176 internal static DependencyObject GetShapeContainingPoint(Point point, List<DependencyObject> shapes)
178 DependencyObject result = null;
180 foreach (DependencyObject shape in shapes)
182 Rect rect = GetAutoConnectHitRect(shape);
183 if (rect.Contains(point))
185 // The design is that if the point is inside of multiple hit test regions, we do not
186 // show any drop targets to avoid confusion.
199 internal DependencyObject FindTarget(Point point, DependencyObject dragged, out AutoConnectDirections directions)
201 directions = AutoConnectDirections.None;
202 List<DependencyObject> childShapes = this.panel.GetChildShapes(dragged);
203 DependencyObject target = GetShapeContainingPoint(point, childShapes);
207 directions = this.GetAutoConnectDirections(directions, childShapes, target);
213 internal void OnPreviewDragOverPanel(DragEventArgs e)
215 // Do not do auto-connect if we are currently auto-splitting.
216 if (this.panel.CurrentAutoSplitTarget != null)
221 DependencyObject currentTarget = this.CurrentTarget;
222 if (currentTarget != null)
224 Rect rect = GetAutoConnectHitRect(currentTarget);
225 if (rect.Contains(e.GetPosition(this.panel)))
227 // Do not update the adorner if the cursor is still in the hit region of the current target
232 ModelItem draggedModelItem = DragDropHelper.GetDraggedModelItemInternal(e);
233 DependencyObject draggedView = draggedModelItem != null ? draggedModelItem.View : null;
234 AutoConnectDirections direction;
235 UIElement target = this.FindTarget(e.GetPosition(this.panel), draggedView, out direction) as UIElement;
236 this.RemoveDropTargets();
237 if (target != null && (direction & this.panel.AutoConnectContainer.GetDirectionsAllowed(e, target)) != AutoConnectDirections.None)
239 this.AddDropTargets(e, target, direction);
243 internal void RemoveDropTargets()
245 UIElement adornedElement = this.CurrentTarget;
246 if (adornedElement != null)
248 AdornerLayer adornerLayer = AdornerLayer.GetAdornerLayer(adornedElement);
249 Fx.Assert(adornerLayer != null, "AdornerLayer should not be null.");
250 Adorner[] adorners = adornerLayer.GetAdorners(adornedElement);
251 foreach (Adorner adorner in adorners)
253 if (adorner is AutoConnectAdorner)
255 adornerLayer.Remove(adorner);
256 this.CurrentTarget = null;
263 // Check if hit test rects collide with any children of the FreeFormPanel, and remove that direction in case a collision is found.
264 private static void RemoveDirectionsInCollision(List<DependencyObject> childShapes, DependencyObject target, List<Rect> hitTestRects, ref AutoConnectDirections directions)
266 foreach (DependencyObject shape in childShapes)
268 if (directions == AutoConnectDirections.None)
273 if (object.Equals(shape, target))
278 Point shapeLocation = FreeFormPanel.GetLocation(shape);
279 Size shapeSize = FreeFormPanel.GetChildSize(shape);
280 Rect shapeRect = new Rect(shapeLocation, shapeSize);
281 for (int i = 0; i < hitTestRects.Count; i++)
283 if (hitTestRects[i].IntersectsWith(shapeRect))
285 directions &= ~AutoConnectHelper.GetAutoConnectDirection(i);
291 // Check if hit test rects are completely within the FreeFormPanel rect, and remove that direction in case it's not.
292 private void RemoveDirectionsOutsideOfPanel(List<Rect> hitTestRects, ref AutoConnectDirections directions)
294 Rect panelRect = new Rect(0, 0, this.panel.Width, this.panel.Height);
295 for (int i = 0; i < hitTestRects.Count; i++)
297 if (!panelRect.Contains(hitTestRects[i]))
299 directions &= ~AutoConnectHelper.GetAutoConnectDirection(i);
304 private AutoConnectDirections GetAutoConnectDirections(AutoConnectDirections directions, List<DependencyObject> childShapes, DependencyObject target)
306 directions = AutoConnectDirections.Top | AutoConnectDirections.Bottom | AutoConnectDirections.Left | AutoConnectDirections.Right;
307 List<Rect> hitTestRects = CreateHitTestRects(FreeFormPanel.GetLocation(target), FreeFormPanel.GetChildSize(target));
308 this.RemoveDirectionsOutsideOfPanel(hitTestRects, ref directions);
309 RemoveDirectionsInCollision(childShapes, target, hitTestRects, ref directions);
313 private void AddDropTargets(DragEventArgs e, UIElement adornedElement, AutoConnectDirections directions)
315 AdornerLayer adornerLayer = AdornerLayer.GetAdornerLayer(adornedElement);
316 Fx.Assert(adornerLayer != null, "AdornerLayer should not be null.");
317 adornerLayer.Add(new AutoConnectAdorner(adornedElement, this.panel, (directions & this.panel.AutoConnectContainer.GetDirectionsAllowed(e, adornedElement))));
318 this.CurrentTarget = adornedElement;