123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608 |
- using System;
- using System.Collections.Generic;
- using System.ComponentModel;
- using System.Drawing;
- using System.Globalization;
- using System.Windows.Forms;
- namespace PaintDotNet
- {
- public sealed class SnapManager
- {
- private Dictionary<SnapObstacle, SnapDescription> obstacles =
- new Dictionary<SnapObstacle, SnapDescription>();
- private const string isSnappedValueName = "IsSnapped";
- private const string leftValueName = "Left";
- private const string topValueName = "Top";
- private const string widthValueName = "Width";
- private const string heightValueName = "Height";
- private const string nullName = "";
- private const string snappedToValueName = "SnappedTo";
- private const string horizontalEdgeValueName = "HorizontalEdge";
- private const string verticalEdgeValueName = "VerticalEdge";
- private const string xOffsetValueName = "XOffset";
- private const string yOffsetValueName = "YOffset";
- public void SaveSnapObstacleData(ISimpleCollection<string, string> saveTo, SnapObstacle so)
- {
- string prefix = so.Name + ".";
- SnapDescription sd = this.obstacles[so];
- bool isSnappedValue = (sd != null);
- saveTo.Set(prefix + isSnappedValueName, isSnappedValue.ToString(CultureInfo.InvariantCulture));
- if (isSnappedValue)
- {
- saveTo.Set(prefix + snappedToValueName, sd.SnappedTo.Name);
- saveTo.Set(prefix + horizontalEdgeValueName, sd.HorizontalEdge.ToString());
- saveTo.Set(prefix + verticalEdgeValueName, sd.VerticalEdge.ToString());
- saveTo.Set(prefix + xOffsetValueName, sd.XOffset.ToString(CultureInfo.InvariantCulture));
- saveTo.Set(prefix + yOffsetValueName, sd.YOffset.ToString(CultureInfo.InvariantCulture));
- }
- saveTo.Set(prefix + leftValueName, so.Bounds.Left.ToString(CultureInfo.InvariantCulture));
- saveTo.Set(prefix + topValueName, so.Bounds.Top.ToString(CultureInfo.InvariantCulture));
- saveTo.Set(prefix + widthValueName, so.Bounds.Width.ToString(CultureInfo.InvariantCulture));
- saveTo.Set(prefix + heightValueName, so.Bounds.Height.ToString(CultureInfo.InvariantCulture));
- }
- public void LoadSnapObstacleData(ISimpleCollection<string, string> loadFrom, SnapObstacle so)
- {
- string prefix = so.Name + ".";
- SnapDescription sd;
- string isSnappedString = loadFrom.Get(prefix + isSnappedValueName);
- bool isSnapped = bool.Parse(isSnappedString);
- if (isSnapped)
- {
- string snappedToString = loadFrom.Get(prefix + snappedToValueName);
- SnapObstacle snappedTo = FindObstacle(snappedToString);
- string horizontalEdgeString = loadFrom.Get(prefix + horizontalEdgeValueName);
- HorizontalSnapEdge horizontalEdge = (HorizontalSnapEdge)Enum.Parse(typeof(HorizontalSnapEdge), horizontalEdgeString, true);
- string verticalEdgeString = loadFrom.Get(prefix + verticalEdgeValueName);
- VerticalSnapEdge verticalEdge = (VerticalSnapEdge)Enum.Parse(typeof(VerticalSnapEdge), verticalEdgeString, true);
- string xOffsetString = loadFrom.Get(prefix + xOffsetValueName);
- int xOffset = int.Parse(xOffsetString, CultureInfo.InvariantCulture);
- string yOffsetString = loadFrom.Get(prefix + yOffsetValueName);
- int yOffset = int.Parse(yOffsetString, CultureInfo.InvariantCulture);
- sd = new SnapDescription(snappedTo, horizontalEdge, verticalEdge, xOffset, yOffset);
- }
- else
- {
- sd = null;
- }
- this.obstacles[so] = sd;
- string leftString = loadFrom.Get(prefix + leftValueName);
- int left = int.Parse(leftString, CultureInfo.InvariantCulture);
- string topString = loadFrom.Get(prefix + topValueName);
- int top = int.Parse(topString, CultureInfo.InvariantCulture);
- string widthString = loadFrom.Get(prefix + widthValueName);
- int width = int.Parse(widthString, CultureInfo.InvariantCulture);
- string heightString = loadFrom.Get(prefix + heightValueName);
- int height = int.Parse(heightString, CultureInfo.InvariantCulture);
- Rectangle newBounds = new Rectangle(left, top, width, height);
- so.RequestBoundsChange(newBounds);
- if (sd != null)
- {
- ParkObstacle(so, sd);
- }
- }
- // Requires that all SnapObstacles are already placed in this.obstacles
- public void Save(ISimpleCollection<string, string> saveTo)
- {
- foreach (SnapObstacle obstacle in this.obstacles.Keys)
- {
- if (obstacle.EnableSave)
- {
- SaveSnapObstacleData(saveTo, obstacle);
- }
- }
- }
- public void Load(ISimpleCollection<string, string> loadFrom)
- {
- SnapObstacle[] newObstacles = new SnapObstacle[this.obstacles.Count];
- this.obstacles.Keys.CopyTo(newObstacles, 0);
- foreach (SnapObstacle obstacle in newObstacles)
- {
- if (obstacle.EnableSave)
- {
- LoadSnapObstacleData(loadFrom, obstacle);
- }
- }
- }
- public void ParkObstacle(ISnapObstacleHost obstacle, ISnapObstacleHost snappedTo, HorizontalSnapEdge hEdge, VerticalSnapEdge vEdge)
- {
- ParkObstacle(obstacle.SnapObstacle, snappedTo.SnapObstacle, hEdge, vEdge);
- }
- public void ParkObstacle(SnapObstacle obstacle, SnapObstacle snappedTo, HorizontalSnapEdge hEdge, VerticalSnapEdge vEdge)
- {
- SnapDescription sd = new SnapDescription(snappedTo, hEdge, vEdge, obstacle.SnapDistance, obstacle.SnapDistance);
- this.obstacles[obstacle] = sd;
- ParkObstacle(obstacle, sd);
- }
- public void ReparkObstacle(ISnapObstacleHost obstacle)
- {
- ReparkObstacle(obstacle.SnapObstacle);
- }
- public void ReparkObstacle(SnapObstacle obstacle)
- {
- if (this.obstacles.ContainsKey(obstacle))
- {
- SnapDescription sd = this.obstacles[obstacle];
- if (sd != null)
- {
- ParkObstacle(obstacle, sd);
- }
- }
- }
- public void AddSnapObstacle(ISnapObstacleHost snapObstacleHost)
- {
- AddSnapObstacle(snapObstacleHost.SnapObstacle);
- }
- public void AddSnapObstacle(SnapObstacle snapObstacle)
- {
- if (!this.obstacles.ContainsKey(snapObstacle))
- {
- this.obstacles.Add(snapObstacle, null);
- if (snapObstacle.StickyEdges)
- {
- snapObstacle.BoundsChanging += SnapObstacle_BoundsChanging;
- snapObstacle.BoundsChanged += SnapObstacle_BoundsChanged;
- }
- }
- }
- private void SnapObstacle_BoundsChanging(object sender, EventArgs<Rectangle> e)
- {
- }
- private void SnapObstacle_BoundsChanged(object sender, EventArgs<Rectangle> e)
- {
- SnapObstacle senderSO = (SnapObstacle)sender;
- Rectangle fromRect = e.Data;
- Rectangle toRect = senderSO.Bounds;
- UpdateDependentObstacles(senderSO, fromRect, toRect);
- }
- private void UpdateDependentObstacles(SnapObstacle senderSO, Rectangle fromRect, Rectangle toRect)
- {
- int leftDelta = toRect.Left - fromRect.Left;
- int topDelta = toRect.Top - fromRect.Top;
- int rightDelta = toRect.Right - fromRect.Right;
- int bottomDelta = toRect.Bottom - fromRect.Bottom;
- foreach (SnapObstacle obstacle in this.obstacles.Keys)
- {
- if (!object.ReferenceEquals(senderSO, obstacle))
- {
- SnapDescription sd = this.obstacles[obstacle];
- if (sd != null && object.ReferenceEquals(sd.SnappedTo, senderSO))
- {
- int deltaX;
- if (sd.VerticalEdge == VerticalSnapEdge.Right)
- {
- deltaX = rightDelta;
- }
- else
- {
- deltaX = leftDelta;
- }
- int deltaY;
- if (sd.HorizontalEdge == HorizontalSnapEdge.Bottom)
- {
- deltaY = bottomDelta;
- }
- else
- {
- deltaY = topDelta;
- }
- Rectangle oldBounds = obstacle.Bounds;
- Point newLocation1 = new Point(oldBounds.Left + deltaX, oldBounds.Top + deltaY);
- Point newLocation2 = AdjustNewLocation(obstacle, newLocation1, sd);
- Rectangle newBounds = new Rectangle(newLocation2, oldBounds.Size);
- obstacle.RequestBoundsChange(newBounds);
- // Recursively update anything snapped to this obstacle
- UpdateDependentObstacles(obstacle, oldBounds, newBounds);
- }
- }
- }
- }
- public void RemoveSnapObstacle(ISnapObstacleHost snapObstacleHost)
- {
- RemoveSnapObstacle(snapObstacleHost.SnapObstacle);
- }
- public void RemoveSnapObstacle(SnapObstacle snapObstacle)
- {
- if (this.obstacles.ContainsKey(snapObstacle))
- {
- this.obstacles.Remove(snapObstacle);
- if (snapObstacle.StickyEdges)
- {
- snapObstacle.BoundsChanging -= SnapObstacle_BoundsChanging;
- snapObstacle.BoundsChanged -= SnapObstacle_BoundsChanged;
- }
- }
- }
- public bool ContainsSnapObstacle(ISnapObstacleHost snapObstacleHost)
- {
- return ContainsSnapObstacle(snapObstacleHost.SnapObstacle);
- }
- public bool ContainsSnapObstacle(SnapObstacle snapObstacle)
- {
- return this.obstacles.ContainsKey(snapObstacle);
- }
- private static bool AreEdgesClose(int l1, int r1, int l2, int r2)
- {
- if (r1 < l2)
- {
- return false;
- }
- else if (r2 < l1)
- {
- return false;
- }
- else if (l1 <= l2 && l2 <= r1 && r1 <= r2)
- {
- return true;
- }
- else if (l2 <= l1 && l1 <= r2 && r2 <= r1)
- {
- return true;
- }
- else if (l1 <= l2 && r2 <= r1)
- {
- return true;
- }
- else if (l2 <= l1 && l1 <= r2)
- {
- return true;
- }
- throw new InvalidOperationException();
- }
- private SnapDescription DetermineNewSnapDescription(
- SnapObstacle avoider,
- Point newLocation,
- SnapObstacle avoidee,
- SnapDescription currentSnapDescription)
- {
- int ourSnapProximity;
- if (currentSnapDescription != null &&
- (currentSnapDescription.HorizontalEdge != HorizontalSnapEdge.Neither ||
- currentSnapDescription.VerticalEdge != VerticalSnapEdge.Neither))
- {
- // the avoider is already snapped to the avoidee -- make it more difficult to un-snap
- ourSnapProximity = avoidee.SnapProximity * 2;
- }
- else
- {
- ourSnapProximity = avoidee.SnapProximity;
- }
- Rectangle avoiderRect = avoider.Bounds;
- avoiderRect.Location = newLocation;
- Rectangle avoideeRect = avoidee.Bounds;
- // Are the vertical edges close enough for snapping?
- bool vertProximity = AreEdgesClose(avoiderRect.Top, avoiderRect.Bottom, avoideeRect.Top, avoideeRect.Bottom);
- // Are the horizontal edges close enough for snapping?
- bool horizProximity = AreEdgesClose(avoiderRect.Left, avoiderRect.Right, avoideeRect.Left, avoideeRect.Right);
- // Compute distances from pertinent edges
- // (e.g. if SnapRegion.Interior, figure out distance from avoider's right edge to avoidee's right edge,
- // if SnapRegion.Exterior, figure out distance from avoider's right edge to avoidee's left edge)
- int leftDistance;
- int rightDistance;
- int topDistance;
- int bottomDistance;
- switch (avoidee.SnapRegion)
- {
- case SnapRegion.Interior:
- leftDistance = Math.Abs(avoiderRect.Left - avoideeRect.Left);
- rightDistance = Math.Abs(avoiderRect.Right - avoideeRect.Right);
- topDistance = Math.Abs(avoiderRect.Top - avoideeRect.Top);
- bottomDistance = Math.Abs(avoiderRect.Bottom - avoideeRect.Bottom);
- break;
- case SnapRegion.Exterior:
- leftDistance = Math.Abs(avoiderRect.Left - avoideeRect.Right);
- rightDistance = Math.Abs(avoiderRect.Right - avoideeRect.Left);
- topDistance = Math.Abs(avoiderRect.Top - avoideeRect.Bottom);
- bottomDistance = Math.Abs(avoiderRect.Bottom - avoideeRect.Top);
- break;
- default:
- throw new InvalidEnumArgumentException("avoidee.SnapRegion");
- }
- bool leftClose = (leftDistance < ourSnapProximity);
- bool rightClose = (rightDistance < ourSnapProximity);
- bool topClose = (topDistance < ourSnapProximity);
- bool bottomClose = (bottomDistance < ourSnapProximity);
- VerticalSnapEdge vEdge = VerticalSnapEdge.Neither;
- if (vertProximity)
- {
- if ((leftClose && avoidee.SnapRegion == SnapRegion.Exterior) ||
- (rightClose && avoidee.SnapRegion == SnapRegion.Interior))
- {
- vEdge = VerticalSnapEdge.Right;
- }
- else if ((rightClose && avoidee.SnapRegion == SnapRegion.Exterior) ||
- (leftClose && avoidee.SnapRegion == SnapRegion.Interior))
- {
- vEdge = VerticalSnapEdge.Left;
- }
- }
- HorizontalSnapEdge hEdge = HorizontalSnapEdge.Neither;
- if (horizProximity)
- {
- if ((topClose && avoidee.SnapRegion == SnapRegion.Exterior) ||
- (bottomClose && avoidee.SnapRegion == SnapRegion.Interior))
- {
- hEdge = HorizontalSnapEdge.Bottom;
- }
- else if ((bottomClose && avoidee.SnapRegion == SnapRegion.Exterior) ||
- (topClose && avoidee.SnapRegion == SnapRegion.Interior))
- {
- hEdge = HorizontalSnapEdge.Top;
- }
- }
- SnapDescription sd;
- if (hEdge != HorizontalSnapEdge.Neither || vEdge != VerticalSnapEdge.Neither)
- {
- int xOffset = avoider.SnapDistance;
- int yOffset = avoider.SnapDistance;
- if (hEdge == HorizontalSnapEdge.Neither)
- {
- if (avoidee.SnapRegion == SnapRegion.Interior)
- {
- yOffset = avoiderRect.Top - avoideeRect.Top;
- hEdge = HorizontalSnapEdge.Top;
- }
- }
- if (vEdge == VerticalSnapEdge.Neither)
- {
- if (avoidee.SnapRegion == SnapRegion.Interior)
- {
- xOffset = avoiderRect.Left - avoideeRect.Left;
- vEdge = VerticalSnapEdge.Left;
- }
- }
- sd = new SnapDescription(avoidee, hEdge, vEdge, xOffset, yOffset);
- }
- else
- {
- sd = null;
- }
- return sd;
- }
- private static void ParkObstacle(SnapObstacle avoider, SnapDescription snapDescription)
- {
- Point newLocation = avoider.Bounds.Location;
- Point adjustedLocation = AdjustNewLocation(avoider, newLocation, snapDescription);
- Rectangle newBounds = new Rectangle(adjustedLocation, avoider.Bounds.Size);
- avoider.RequestBoundsChange(newBounds);
- }
- private static Point AdjustNewLocation(SnapObstacle obstacle, Point newLocation, SnapDescription snapDescription)
- {
- if (snapDescription == null ||
- (snapDescription.HorizontalEdge == HorizontalSnapEdge.Neither &&
- snapDescription.VerticalEdge == VerticalSnapEdge.Neither))
- {
- return obstacle.Bounds.Location;
- }
- Rectangle obstacleRect = new Rectangle(newLocation, obstacle.Bounds.Size);
- Rectangle snappedToRect = snapDescription.SnappedTo.Bounds;
- HorizontalSnapEdge hEdge = snapDescription.HorizontalEdge;
- VerticalSnapEdge vEdge = snapDescription.VerticalEdge;
- SnapRegion region = snapDescription.SnappedTo.SnapRegion;
- int deltaY = 0;
- if (hEdge == HorizontalSnapEdge.Top && region == SnapRegion.Exterior)
- {
- int newBottomEdge = snappedToRect.Top - snapDescription.YOffset;
- deltaY = obstacleRect.Bottom - newBottomEdge;
- }
- else if (hEdge == HorizontalSnapEdge.Bottom && region == SnapRegion.Exterior)
- {
- int newTopEdge = snappedToRect.Bottom + snapDescription.YOffset;
- deltaY = obstacleRect.Top - newTopEdge;
- }
- else if (hEdge == HorizontalSnapEdge.Top && region == SnapRegion.Interior)
- {
- int newTopEdge = Math.Min(snappedToRect.Bottom, snappedToRect.Top + snapDescription.YOffset);
- deltaY = obstacleRect.Top - newTopEdge;
- }
- else if (hEdge == HorizontalSnapEdge.Bottom && region == SnapRegion.Interior)
- {
- int newBottomEdge = Math.Max(snappedToRect.Top, snappedToRect.Bottom - snapDescription.YOffset);
- deltaY = obstacleRect.Bottom - newBottomEdge;
- }
- int deltaX = 0;
- if (vEdge == VerticalSnapEdge.Left && region == SnapRegion.Exterior)
- {
- int newRightEdge = snappedToRect.Left - snapDescription.XOffset;
- deltaX = obstacleRect.Right - newRightEdge;
- }
- else if (vEdge == VerticalSnapEdge.Right && region == SnapRegion.Exterior)
- {
- int newLeftEdge = snappedToRect.Right + snapDescription.XOffset;
- deltaX = obstacleRect.Left - newLeftEdge;
- }
- else if (vEdge == VerticalSnapEdge.Left && region == SnapRegion.Interior)
- {
- int newLeftEdge = Math.Min(snappedToRect.Right, snappedToRect.Left + snapDescription.XOffset);
- deltaX = obstacleRect.Left - newLeftEdge;
- }
- else if (vEdge == VerticalSnapEdge.Right && region == SnapRegion.Interior)
- {
- int newRightEdge = Math.Max(snappedToRect.Left, snappedToRect.Right - snapDescription.XOffset);
- deltaX = obstacleRect.Right - newRightEdge;
- }
- Point adjustedLocation = new Point(obstacleRect.Left - deltaX, obstacleRect.Top - deltaY);
- return adjustedLocation;
- }
- /// <summary>
- /// Given an obstacle and its attempted destination, determines the correct landing
- /// spot for an obstacle.
- /// </summary>
- /// <param name="movingObstacle">The obstacle that is moving.</param>
- /// <param name="newLocation">The upper-left coordinate of the obstacle's original intended destination.</param>
- /// <returns>
- /// A Point that determines where the obstacle should be placed instead. If there are no adjustments
- /// required to the obstacle's desintation, then the return value will be equal to newLocation.
- /// </returns>
- /// <remarks>
- /// movingObstacle's SnapDescription will also be updated. The caller of this method is required
- /// to update the SnapObstacle with the new, adjusted location.
- /// </remarks>
- public Point AdjustObstacleDestination(SnapObstacle movingObstacle, Point newLocation)
- {
- Point adjusted1 = AdjustObstacleDestination(movingObstacle, newLocation, false);
- Point adjusted2 = AdjustObstacleDestination(movingObstacle, adjusted1, true);
- return adjusted2;
- }
- public Point AdjustObstacleDestination(SnapObstacle movingObstacle, Point newLocation, bool considerStickies)
- {
- Point adjustedLocation = newLocation;
- SnapDescription sd = this.obstacles[movingObstacle];
- SnapDescription newSD = null;
- foreach (SnapObstacle avoidee in this.obstacles.Keys)
- {
- if (avoidee.StickyEdges != considerStickies)
- {
- continue;
- }
- if (avoidee.Enabled && !object.ReferenceEquals(avoidee, movingObstacle))
- {
- SnapDescription newSD2 = DetermineNewSnapDescription(movingObstacle, adjustedLocation, avoidee, newSD);
- if (newSD2 != null)
- {
- Point adjustedLocation2 = AdjustNewLocation(movingObstacle, adjustedLocation, newSD2);
- newSD = newSD2;
- adjustedLocation = adjustedLocation2;
- Rectangle newBounds = new Rectangle(adjustedLocation, movingObstacle.Bounds.Size);
- }
- }
- }
- if (sd == null || !sd.SnappedTo.StickyEdges || newSD == null || newSD.SnappedTo.StickyEdges)
- {
- this.obstacles[movingObstacle] = newSD;
- }
- return adjustedLocation;
- }
- public SnapObstacle FindObstacle(string name)
- {
- foreach (SnapObstacle so in this.obstacles.Keys)
- {
- if (string.Compare(so.Name, name, true) == 0)
- {
- return so;
- }
- }
- return null;
- }
- public static SnapManager FindMySnapManager(Control me)
- {
- if (!(me is ISnapObstacleHost))
- {
- throw new ArgumentException("must be called with a Control that implements ISnapObstacleHost");
- }
- ISnapManagerHost ismh;
- ismh = me as ISnapManagerHost;
- if (ismh == null)
- {
- ismh = me.FindForm() as ISnapManagerHost;
- }
- SnapManager sm;
- if (ismh != null)
- {
- sm = ismh.SnapManager;
- }
- else
- {
- sm = null;
- }
- return sm;
- }
- public SnapManager()
- {
- }
- }
- }
|