You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
337 lines
8.9 KiB
337 lines
8.9 KiB
using System; |
|
using System.Collections.Generic; |
|
using System.Linq; |
|
using System.Text; |
|
using System.Windows; |
|
using System.Windows.Controls; |
|
using System.Windows.Media; |
|
using System.Windows.Shapes; |
|
using SharpDevelop.XamlDesigner.Controls; |
|
using System.Diagnostics; |
|
using SharpDevelop.XamlDesigner.Dom; |
|
|
|
namespace SharpDevelop.XamlDesigner.Placement |
|
{ |
|
class SnapEngine |
|
{ |
|
public SnapEngine(DesignItem containerItem) |
|
{ |
|
this.containerItem = containerItem; |
|
} |
|
|
|
DesignItem containerItem; |
|
List<Snapline> map = new List<Snapline>(); |
|
|
|
public const double Accuracy = 7; |
|
public const double GlobalMargin = 11; |
|
public const double ControlMargin = 7; |
|
|
|
public static double? GetBaseline(DependencyObject obj) |
|
{ |
|
return (double?)obj.GetValue(BaselineProperty); |
|
} |
|
|
|
public static void SetBaseline(DependencyObject obj, double? value) |
|
{ |
|
obj.SetValue(BaselineProperty, value); |
|
} |
|
|
|
public static void UpdateBaseline(DesignItem item) |
|
{ |
|
SetBaseline(item.View, CalculateBaseline(item.View)); |
|
} |
|
|
|
public static readonly DependencyProperty BaselineProperty = |
|
DependencyProperty.RegisterAttached("Baseline", typeof(double?), typeof(SnapEngine)); |
|
|
|
public Canvas FeedbackLayer |
|
{ |
|
get { return containerItem.Context.DesignView.FeedbackLayer; } |
|
} |
|
|
|
public void BuildMap(MoveOperation op) |
|
{ |
|
var containerRect = new Rect(containerItem.View.RenderSize); |
|
AddLinesToMap(containerRect, 0, SnaplineKind.Bounds, false); |
|
AddLinesToMap(containerRect, -GlobalMargin, SnaplineKind.Bounds, false); |
|
|
|
var list = new List<DesignItem>(); |
|
|
|
foreach (var item in containerItem.Children().Except(op.Items)) { |
|
if (item.View.RenderTransform == Transform.Identity && |
|
item.View.LayoutTransform == Transform.Identity) { |
|
list.Add(item); |
|
} |
|
} |
|
|
|
foreach (var item in list) { |
|
var bounds = item.GetBounds(); |
|
AddLinesToMap(bounds, 0, SnaplineKind.Bounds, false); |
|
AddLinesToMap(bounds, ControlMargin, SnaplineKind.Margin, true); |
|
TryAddBaseline(map, bounds, CalculateBaseline(item.View)); |
|
} |
|
} |
|
|
|
public void SnapResize(ResizeOperation op) |
|
{ |
|
} |
|
|
|
public void SnapMove(MoveOperation op) |
|
{ |
|
HideSnaplines(); |
|
|
|
var input = BuildInput(op); |
|
|
|
Vector delta; |
|
if (TrySnap(input, out delta)) { |
|
foreach (var info in op.PlacementInfos) { |
|
var snappedBounds = info.NewBoundsInContainer; |
|
snappedBounds.Offset(delta); |
|
info.NewBoundsInContainer = snappedBounds; |
|
} |
|
|
|
var snappedInput = BuildInput(op); |
|
ShowSnaplines(snappedInput, Orientation.Horizontal); |
|
ShowSnaplines(snappedInput, Orientation.Vertical); |
|
} |
|
} |
|
|
|
List<Snapline> BuildInput(MoveOperation op) |
|
{ |
|
var result = new List<Snapline>(); |
|
var info = op.PlacementInfos[0]; |
|
|
|
AddLinesToInput(result, op.PlacementInfos[0].NewBoundsInContainer); |
|
TryAddBaseline(result, info.NewBoundsInContainer, CalculateBaseline(info.Item.View)); |
|
|
|
return result; |
|
} |
|
|
|
public void HideSnaplines() |
|
{ |
|
FeedbackLayer.Children.Clear(); |
|
} |
|
|
|
void ShowSnaplines(IEnumerable<Snapline> snappedInput, Orientation orient) |
|
{ |
|
var offsetDict = new Dictionary<double, Snapline>(); |
|
|
|
foreach (var inputLine in snappedInput) { |
|
if (inputLine.Orientation == orient) { |
|
foreach (var mapLine in map) { |
|
if (mapLine.Orientation == orient && |
|
mapLine.Offset == inputLine.Offset) { |
|
|
|
var offset = mapLine.Offset; |
|
Snapline drawLine; |
|
|
|
if (!offsetDict.TryGetValue(offset, out drawLine)) { |
|
drawLine = new Snapline(); |
|
drawLine.Orientation = mapLine.Orientation; |
|
drawLine.Offset = offset; |
|
drawLine.Start = double.MaxValue; |
|
drawLine.End = double.MinValue; |
|
offsetDict[offset] = drawLine; |
|
} |
|
drawLine.Start = Math.Min(drawLine.Start, Math.Min(inputLine.Start, mapLine.Start)); |
|
drawLine.End = Math.Max(drawLine.End, Math.Max(inputLine.End, mapLine.End)); |
|
} |
|
} |
|
} |
|
} |
|
|
|
var tr = containerItem.View.TransformToVisual(FeedbackLayer); |
|
var viewStyle = DesignResources.SnaplineStyle; |
|
var drawLines = offsetDict.Values.ToList(); |
|
|
|
foreach (var line in drawLines) { |
|
var view = new DashedLine(); |
|
if (line.Orientation == Orientation.Horizontal) { |
|
view.Point1 = new Point(line.Start, line.Offset); |
|
view.Point2 = new Point(line.End, line.Offset); |
|
} |
|
else { |
|
view.Point1 = new Point(line.Offset, line.Start); |
|
view.Point2 = new Point(line.Offset, line.End); |
|
} |
|
view.Point1 = tr.Transform(view.Point1); |
|
view.Point2 = tr.Transform(view.Point2); |
|
view.Style = viewStyle; |
|
|
|
FeedbackLayer.Children.Add(view); |
|
} |
|
} |
|
|
|
void AddLinesToMap(Rect r, double inflate, SnaplineKind kind, bool opposite) |
|
{ |
|
var r2 = r; |
|
r2.Inflate(0, inflate); |
|
AddLine(map, r2, ResizeDirection.Up, kind, opposite ? SnaplineSide.Bottom : SnaplineSide.Top); |
|
AddLine(map, r2, ResizeDirection.Down, kind, opposite ? SnaplineSide.Top : SnaplineSide.Bottom); |
|
|
|
r2 = r; |
|
r2.Inflate(inflate, 0); |
|
AddLine(map, r2, ResizeDirection.Left, kind, opposite ? SnaplineSide.Right : SnaplineSide.Left); |
|
AddLine(map, r2, ResizeDirection.Right, kind, opposite ? SnaplineSide.Left : SnaplineSide.Right); |
|
} |
|
|
|
void AddLinesToInput(List<Snapline> list, Rect r) |
|
{ |
|
AddLine(list, r, ResizeDirection.Up, SnaplineKind.Bounds, null); |
|
AddLine(list, r, ResizeDirection.Down, SnaplineKind.Bounds, null); |
|
AddLine(list, r, ResizeDirection.Left, SnaplineKind.Bounds, null); |
|
AddLine(list, r, ResizeDirection.Right, SnaplineKind.Bounds, null); |
|
} |
|
|
|
static void AddLine(List<Snapline> list, Rect r, ResizeDirection dir, |
|
SnaplineKind kind, SnaplineSide? side) |
|
{ |
|
switch (dir) { |
|
case ResizeDirection.Up: |
|
list.Add(new Snapline() { |
|
Kind = kind, |
|
Side = side ?? SnaplineSide.Top, |
|
Orientation = Orientation.Horizontal, |
|
Start = r.Left, |
|
End = r.Right, |
|
Offset = r.Top |
|
}); |
|
break; |
|
case ResizeDirection.Down: |
|
list.Add(new Snapline() { |
|
Kind = kind, |
|
Side = side ?? SnaplineSide.Bottom, |
|
Orientation = Orientation.Horizontal, |
|
Start = r.Left, |
|
End = r.Right, |
|
Offset = r.Bottom |
|
}); |
|
break; |
|
case ResizeDirection.Left: |
|
list.Add(new Snapline() { |
|
Kind = kind, |
|
Side = side ?? SnaplineSide.Left, |
|
Orientation = Orientation.Vertical, |
|
Start = r.Top, |
|
End = r.Bottom, |
|
Offset = r.Left |
|
}); |
|
break; |
|
case ResizeDirection.Right: |
|
list.Add(new Snapline() { |
|
Kind = kind, |
|
Side = side ?? SnaplineSide.Right, |
|
Orientation = Orientation.Vertical, |
|
Start = r.Top, |
|
End = r.Bottom, |
|
Offset = r.Right |
|
}); |
|
break; |
|
} |
|
} |
|
|
|
void TryAddBaseline(List<Snapline> list, Rect bounds, double? baseline) |
|
{ |
|
if (baseline.HasValue) { |
|
list.Add(new Snapline() { |
|
Kind = SnaplineKind.Baseline, |
|
Side = SnaplineSide.Any, |
|
Orientation = Orientation.Horizontal, |
|
Start = bounds.Left, |
|
End = bounds.Right, |
|
Offset = bounds.Top + baseline.Value |
|
}); |
|
} |
|
} |
|
|
|
bool TrySnap(List<Snapline> input, out Vector delta) |
|
{ |
|
double deltaX = 0; |
|
double deltaY = 0; |
|
var result = false; |
|
delta = new Vector(); |
|
|
|
if (TrySnap(input, Orientation.Horizontal, out deltaY)) { |
|
delta.Y = deltaY; |
|
result = true; |
|
} |
|
if (TrySnap(input, Orientation.Vertical, out deltaX)) { |
|
delta.X = deltaX; |
|
result = true; |
|
} |
|
|
|
return result; |
|
} |
|
|
|
bool TrySnap(IEnumerable<Snapline> input, Orientation orient, out double delta) |
|
{ |
|
delta = double.MaxValue; |
|
|
|
foreach (var inputLine in input) { |
|
if (inputLine.Orientation == orient) { |
|
foreach (var mapLine in map) { |
|
if (mapLine.Orientation == orient && |
|
(mapLine.Kind == inputLine.Kind || |
|
mapLine.Kind == SnaplineKind.Margin && inputLine.Kind == SnaplineKind.Bounds) && |
|
(mapLine.Side == SnaplineSide.Any || mapLine.Side == inputLine.Side)) { |
|
|
|
if (Math.Abs(mapLine.Offset - inputLine.Offset) <= Accuracy && |
|
|
|
(mapLine.Kind != SnaplineKind.Margin || |
|
Math.Max(mapLine.Start, inputLine.Start) < Math.Min(mapLine.End, inputLine.End))) { |
|
|
|
var newDelta = mapLine.Offset - inputLine.Offset; |
|
if (Math.Abs(newDelta) < Math.Abs(delta)) { |
|
delta = newDelta; |
|
} |
|
} |
|
} |
|
} |
|
} |
|
} |
|
|
|
return delta != double.MaxValue; |
|
} |
|
|
|
//TODO: GlyphRun must be used |
|
static double? CalculateBaseline(FrameworkElement element) |
|
{ |
|
var textBox = element.FindChild<TextBox>(); |
|
if (textBox != null) { |
|
var r = textBox.GetRectFromCharacterIndex(0).Bottom; |
|
return textBox.TranslatePoint(new Point(0, r), element).Y; |
|
} |
|
var textBlock = element.FindChild<TextBlock>(); |
|
if (textBlock != null) |
|
return textBlock.TranslatePoint(new Point(0, textBlock.BaselineOffset), element).Y; |
|
|
|
return null; |
|
} |
|
|
|
class Snapline |
|
{ |
|
public double Start; |
|
public double End; |
|
public double Offset; |
|
public SnaplineKind Kind; |
|
public SnaplineSide Side; |
|
public Orientation Orientation; |
|
} |
|
|
|
enum SnaplineKind |
|
{ |
|
Bounds, |
|
Margin, |
|
Baseline |
|
} |
|
|
|
enum SnaplineSide |
|
{ |
|
Left, |
|
Right, |
|
Top, |
|
Bottom, |
|
Any |
|
} |
|
} |
|
}
|
|
|