// --------------------------------------------------------------------------------------------------------------------
//
// Copyright (c) 2014 OxyPlot contributors
//
//
// The tracker control.
//
// --------------------------------------------------------------------------------------------------------------------
using Avalonia;
namespace OxyPlot.Avalonia
{
using global::Avalonia.Controls;
using global::Avalonia.Controls.Presenters;
using global::Avalonia.Controls.Primitives;
using global::Avalonia.Controls.Shapes;
using global::Avalonia.Layout;
using global::Avalonia.Media;
using global::Avalonia.VisualTree;
using System.Collections.Generic;
using System.Linq;
///
/// The tracker control.
///
public class TrackerControl : ContentControl
{
///
/// Identifies the dependency property.
///
public static readonly StyledProperty HorizontalLineVisibilityProperty = AvaloniaProperty.Register(nameof(HorizontalLineVisibility), true);
///
/// Identifies the dependency property.
///
public static readonly StyledProperty VerticalLineVisibilityProperty = AvaloniaProperty.Register(nameof(VerticalLineVisibility), true);
///
/// Identifies the dependency property.
///
public static readonly StyledProperty LineStrokeProperty = AvaloniaProperty.Register(nameof(LineStroke));
///
/// Identifies the dependency property.
///
public static readonly StyledProperty LineExtentsProperty = AvaloniaProperty.Register(nameof(LineExtents), new OxyRect());
///
/// Identifies the dependency property.
///
public static readonly StyledProperty> LineDashArrayProperty = AvaloniaProperty.Register>(nameof(LineDashArray));
///
/// Identifies the dependency property.
///
public static readonly StyledProperty ShowPointerProperty = AvaloniaProperty.Register(nameof(ShowPointer), true);
///
/// Identifies the dependency property.
///
public static new readonly StyledProperty CornerRadiusProperty = AvaloniaProperty.Register(nameof(CornerRadius), 0.0);
///
/// Identifies the dependency property.
///
public static readonly StyledProperty DistanceProperty = AvaloniaProperty.Register(nameof(Distance), 7.0);
///
/// Identifies the dependency property.
///
public static readonly StyledProperty CanCenterHorizontallyProperty = AvaloniaProperty.Register(nameof(CanCenterHorizontally), true);
///
/// Identifies the dependency property.
///
public static readonly StyledProperty CanCenterVerticallyProperty = AvaloniaProperty.Register(nameof(CanCenterVertically), true);
///
/// Identifies the dependency property.
///
public static readonly StyledProperty PositionProperty = AvaloniaProperty.Register(nameof(Position), new ScreenPoint());
///
/// The path part string.
///
private const string PartPath = "PART_Path";
///
/// The content part string.
///
private const string PartContent = "PART_Content";
///
/// The content container part string.
///
private const string PartContentContainer = "PART_ContentContainer";
///
/// The horizontal line part string.
///
private const string PartHorizontalLine = "PART_HorizontalLine";
///
/// The vertical line part string.
///
private const string PartVerticalLine = "PART_VerticalLine";
///
/// The content.
///
private ContentPresenter content;
///
/// The horizontal line.
///
private Line horizontalLine;
///
/// The path.
///
private Path path;
///
/// The content container.
///
private Panel contentContainer;
///
/// The vertical line.
///
private Line verticalLine;
///
/// Initializes static members of the class.
///
static TrackerControl()
{
ClipToBoundsProperty.OverrideDefaultValue(false);
PositionProperty.Changed.AddClassHandler(PositionChanged);
}
///
/// Gets or sets HorizontalLineVisibility.
///
public bool HorizontalLineVisibility
{
get
{
return GetValue(HorizontalLineVisibilityProperty);
}
set
{
SetValue(HorizontalLineVisibilityProperty, value);
}
}
///
/// Gets or sets VerticalLineVisibility.
///
public bool VerticalLineVisibility
{
get
{
return GetValue(VerticalLineVisibilityProperty);
}
set
{
SetValue(VerticalLineVisibilityProperty, value);
}
}
///
/// Gets or sets LineStroke.
///
public IBrush LineStroke
{
get
{
return GetValue(LineStrokeProperty);
}
set
{
SetValue(LineStrokeProperty, value);
}
}
///
/// Gets or sets LineExtents.
///
public OxyRect LineExtents
{
get
{
return GetValue(LineExtentsProperty);
}
set
{
SetValue(LineExtentsProperty, value);
}
}
///
/// Gets or sets LineDashArray.
///
public List LineDashArray
{
get
{
return GetValue(LineDashArrayProperty);
}
set
{
SetValue(LineDashArrayProperty, value);
}
}
///
/// Gets or sets a value indicating whether to show a 'pointer' on the border.
///
public bool ShowPointer
{
get
{
return GetValue(ShowPointerProperty);
}
set
{
SetValue(ShowPointerProperty, value);
}
}
///
/// Gets or sets the corner radius (only used when ShowPoint=false).
///
public new double CornerRadius
{
get
{
return GetValue(CornerRadiusProperty);
}
set
{
SetValue(CornerRadiusProperty, value);
}
}
///
/// Gets or sets the distance of the content container from the trackers Position.
///
public double Distance
{
get
{
return GetValue(DistanceProperty);
}
set
{
SetValue(DistanceProperty, value);
}
}
///
/// Gets or sets a value indicating whether the tracker can center its content box horizontally.
///
public bool CanCenterHorizontally
{
get
{
return GetValue(CanCenterHorizontallyProperty);
}
set
{
SetValue(CanCenterHorizontallyProperty, value);
}
}
///
/// Gets or sets a value indicating whether the tracker can center its content box vertically.
///
public bool CanCenterVertically
{
get
{
return GetValue(CanCenterVerticallyProperty);
}
set
{
SetValue(CanCenterVerticallyProperty, value);
}
}
///
/// Gets or sets Position of the tracker.
///
public ScreenPoint Position
{
get
{
return GetValue(PositionProperty);
}
set
{
SetValue(PositionProperty, value);
}
}
protected override void OnApplyTemplate(TemplateAppliedEventArgs e)
{
base.OnApplyTemplate(e);
path = e.NameScope.Get(PartPath);
content = e.NameScope.Get(PartContent);
contentContainer = e.NameScope.Get(PartContentContainer);
horizontalLine = e.NameScope.Find(PartHorizontalLine);
verticalLine = e.NameScope.Find(PartVerticalLine);
UpdatePositionAndBorder();
}
///
/// Called when the position is changed.
///
/// The sender.
/// The instance containing the event data.
private static void PositionChanged(AvaloniaObject sender, AvaloniaPropertyChangedEventArgs e)
{
((TrackerControl)sender).OnPositionChanged(e);
}
///
/// Called when the position is changed.
///
/// The dependency property changed event args.
// ReSharper disable once UnusedParameter.Local
private void OnPositionChanged(AvaloniaPropertyChangedEventArgs dependencyPropertyChangedEventArgs)
{
UpdatePositionAndBorder();
}
///
/// Update the position and border of the tracker.
///
private void UpdatePositionAndBorder()
{
if (contentContainer == null)
{
return;
}
var pos = this.Position;
var lineExtents = this.LineExtents;
Canvas.SetLeft(this, pos.X);
Canvas.SetTop(this, pos.Y);
Control parent = this;
while (!(parent is Canvas) && parent != null)
{
parent = parent.GetVisualParent() as Control;
}
if (parent == null)
{
return;
}
// throw new InvalidOperationException("The TrackerControl must have a Canvas parent.");
var canvasWidth = parent.Bounds.Width;
var canvasHeight = parent.Bounds.Height;
content.Measure(new Size(canvasWidth, canvasHeight));
content.Arrange(new Rect(0, 0, content.DesiredSize.Width, content.DesiredSize.Height));
var contentWidth = content.DesiredSize.Width;
var contentHeight = content.DesiredSize.Height;
// Minimum allowed margins around the tracker
const double MarginLimit = 10;
var ha = HorizontalAlignment.Center;
if (CanCenterHorizontally)
{
if (pos.X - (contentWidth / 2) < MarginLimit)
{
ha = HorizontalAlignment.Left;
}
if (pos.X + (contentWidth / 2) > canvasWidth - MarginLimit)
{
ha = HorizontalAlignment.Right;
}
}
else
{
ha = pos.X < canvasWidth / 2 ? HorizontalAlignment.Left : HorizontalAlignment.Right;
}
var va = VerticalAlignment.Center;
if (CanCenterVertically)
{
if (pos.Y - (contentHeight / 2) < MarginLimit)
{
va = VerticalAlignment.Top;
}
if (ha == HorizontalAlignment.Center)
{
va = VerticalAlignment.Bottom;
if (pos.Y - contentHeight < MarginLimit)
{
va = VerticalAlignment.Top;
}
}
if (va == VerticalAlignment.Center && pos.Y + (contentHeight / 2) > canvasHeight - MarginLimit)
{
va = VerticalAlignment.Bottom;
}
if (va == VerticalAlignment.Top && pos.Y + contentHeight > canvasHeight - MarginLimit)
{
va = VerticalAlignment.Bottom;
}
}
else
{
va = pos.Y < canvasHeight / 2 ? VerticalAlignment.Top : VerticalAlignment.Bottom;
}
var dx = ha == HorizontalAlignment.Center ? -0.5 : ha == HorizontalAlignment.Left ? 0 : -1;
var dy = va == VerticalAlignment.Center ? -0.5 : va == VerticalAlignment.Top ? 0 : -1;
path.Data = ShowPointer ? CreatePointerBorderGeometry(ha, va, contentWidth, contentHeight, out var margin)
: CreateBorderGeometry(ha, va, contentWidth, contentHeight, out margin);
content.Margin = margin;
contentContainer.Measure(new Size(canvasWidth, canvasHeight));
var contentSize = contentContainer.DesiredSize;
contentContainer.RenderTransform = new TranslateTransform
{
X = dx * contentSize.Width,
Y = dy * contentSize.Height
};
if (horizontalLine != null)
{
horizontalLine.StartPoint = new Point(lineExtents.Left - pos.X, 0);
horizontalLine.EndPoint = new Point(lineExtents.Right - pos.X, 0);
}
if (verticalLine != null)
{
verticalLine.StartPoint = new Point(0, lineExtents.Top - pos.Y);
verticalLine.EndPoint = new Point(0, lineExtents.Bottom - pos.Y);
}
}
///
/// Create the border geometry.
///
/// The horizontal alignment.
/// The vertical alignment.
/// The width.
/// The height.
/// The margin.
/// The border geometry.
private Geometry CreateBorderGeometry(
HorizontalAlignment ha, VerticalAlignment va, double width, double height, out Thickness margin)
{
var m = Distance;
var rect = new Rect(
ha == HorizontalAlignment.Left ? m : 0, va == VerticalAlignment.Top ? m : 0, width, height);
margin = new Thickness(
ha == HorizontalAlignment.Left ? m : 0,
va == VerticalAlignment.Top ? m : 0,
ha == HorizontalAlignment.Right ? m : 0,
va == VerticalAlignment.Bottom ? m : 0);
return new RectangleGeometry(rect)/* { RadiusX = this.CornerRadius, RadiusY = this.CornerRadius }*/;
}
///
/// Create a border geometry with a 'pointer'.
///
/// The horizontal alignment.
/// The vertical alignment.
/// The width.
/// The height.
/// The margin.
/// The border geometry.
private Geometry CreatePointerBorderGeometry(
HorizontalAlignment ha, VerticalAlignment va, double width, double height, out Thickness margin)
{
Point[] points = null;
var m = Distance;
margin = new Thickness();
if (ha == HorizontalAlignment.Center && va == VerticalAlignment.Bottom)
{
var x1 = width;
var x2 = x1 / 2;
var y1 = height;
margin = new Thickness(0, 0, 0, m);
points = new[]
{
new Point(0, 0), new Point(x1, 0), new Point(x1, y1), new Point(x2 + (m / 2), y1),
new Point(x2, y1 + m), new Point(x2 - (m / 2), y1), new Point(0, y1)
};
}
else if (ha == HorizontalAlignment.Center && va == VerticalAlignment.Top)
{
var x1 = width;
var x2 = x1 / 2;
var y0 = m;
var y1 = m + height;
margin = new Thickness(0, m, 0, 0);
points = new[]
{
new Point(0, y0), new Point(x2 - (m / 2), y0), new Point(x2, 0), new Point(x2 + (m / 2), y0),
new Point(x1, y0), new Point(x1, y1), new Point(0, y1)
};
}
else if (ha == HorizontalAlignment.Left && va == VerticalAlignment.Center)
{
var x0 = m;
var x1 = m + width;
var y1 = height;
var y2 = y1 / 2;
margin = new Thickness(m, 0, 0, 0);
points = new[]
{
new Point(0, y2), new Point(x0, y2 - (m / 2)), new Point(x0, 0), new Point(x1, 0),
new Point(x1, y1), new Point(x0, y1), new Point(x0, y2 + (m / 2))
};
}
else if (ha == HorizontalAlignment.Right && va == VerticalAlignment.Center)
{
var x1 = width;
var y1 = height;
var y2 = y1 / 2;
margin = new Thickness(0, 0, m, 0);
points = new[]
{
new Point(x1 + m, y2), new Point(x1, y2 + (m / 2)), new Point(x1, y1), new Point(0, y1),
new Point(0, 0), new Point(x1, 0), new Point(x1, y2 - (m / 2))
};
}
else if (ha == HorizontalAlignment.Left && va == VerticalAlignment.Top)
{
m *= 0.67;
var x0 = m;
var x1 = m + width;
var y0 = m;
var y1 = m + height;
margin = new Thickness(m, m, 0, 0);
points = new[]
{
new Point(0, 0), new Point(m * 2, y0), new Point(x1, y0), new Point(x1, y1), new Point(x0, y1),
new Point(x0, m * 2)
};
}
else if (ha == HorizontalAlignment.Right && va == VerticalAlignment.Top)
{
m *= 0.67;
var x1 = width;
var y0 = m;
var y1 = m + height;
margin = new Thickness(0, m, m, 0);
points = new[]
{
new Point(x1 + m, 0), new Point(x1, y0 + m), new Point(x1, y1), new Point(0, y1),
new Point(0, y0), new Point(x1 - m, y0)
};
}
else if (ha == HorizontalAlignment.Left && va == VerticalAlignment.Bottom)
{
m *= 0.67;
var x0 = m;
var x1 = m + width;
var y1 = height;
margin = new Thickness(m, 0, 0, m);
points = new[]
{
new Point(0, y1 + m), new Point(x0, y1 - m), new Point(x0, 0), new Point(x1, 0),
new Point(x1, y1), new Point(x0 + m, y1)
};
}
else if (ha == HorizontalAlignment.Right && va == VerticalAlignment.Bottom)
{
m *= 0.67;
var x1 = width;
var y1 = height;
margin = new Thickness(0, 0, m, m);
points = new[]
{
new Point(x1 + m, y1 + m), new Point(x1 - m, y1), new Point(0, y1), new Point(0, 0),
new Point(x1, 0), new Point(x1, y1 - m)
};
}
if (points == null)
{
return null;
}
var pc = new List(points.Length);
foreach (var p in points)
{
pc.Add(p);
}
var segments = new PathSegments();
segments.AddRange(pc.Select(p => new LineSegment { Point = p }));
var pf = new PathFigure { StartPoint = points[0], Segments = segments, IsClosed = true };
return new PathGeometry { Figures = new PathFigures { pf } };
}
}
}