using System; using System.Collections.Generic; using System.Collections.ObjectModel; using System.ComponentModel; using System.Globalization; using System.Linq; using System.Windows; using System.Windows.Controls; using System.Windows.Documents; using System.Windows.Input; using System.Windows.Markup; using System.Windows.Media; using System.Windows.Shapes; using Tango.Graphics2D.VisualBinders; namespace Tango.Graphics2D { [ContentProperty(nameof(Elements))] public abstract class Drawing2DHost : FrameworkElement { private readonly VisualCollection _children; private Dictionary _elements; private List _containers; private FrameworkElement _mouseEnterElement; private Dictionary _binders; #region Properties public ObservableCollection Elements { get { return (ObservableCollection)GetValue(ElementsProperty); } set { SetValue(ElementsProperty, value); } } public static readonly DependencyProperty ElementsProperty = DependencyProperty.Register("Elements", typeof(ObservableCollection), typeof(Drawing2DHost), new PropertyMetadata(null, (d, e) => (d as Drawing2DHost).OnElementsChanged())); public Brush Foreground { get { return (Brush)this.GetValue(ForegroundProperty); } set { this.SetValue(ForegroundProperty, value); } } public static readonly DependencyProperty ForegroundProperty = TextBlock.ForegroundProperty.AddOwner(typeof(Drawing2DHost)); public bool CanResize { get { return (bool)GetValue(CanResizeProperty); } set { SetValue(CanResizeProperty, value); } } public static readonly DependencyProperty CanResizeProperty = DependencyProperty.Register("CanResize", typeof(bool), typeof(Drawing2DHost), new PropertyMetadata(false)); #endregion #region Constructors public Drawing2DHost() { Elements = new ObservableCollection(); _elements = new Dictionary(); _containers = new List(); _binders = new Dictionary(); _children = new VisualCollection(this); RegisterVisualBinder(new TextBlockBinder()); RegisterVisualBinder(new RectangleBinder()); RegisterVisualBinder(new EllipseBinder()); RegisterVisualBinder(new BorderBinder()); RegisterVisualBinder(new ImageBinder()); RegisterVisualBinder(new LineBinder()); RegisterVisualBinder(new PathBinder()); Loaded += Drawing2DHost_Loaded; SizeChanged += Drawing2DHost_SizeChanged; } #endregion #region Host Events private void Drawing2DHost_Loaded(object sender, RoutedEventArgs e) { DrawElements(); var size = OnMeasure(); if (double.IsNaN(Width)) { Width = size.Width; } if (double.IsNaN(Height)) { Height = size.Height; } } private void Drawing2DHost_SizeChanged(object sender, SizeChangedEventArgs e) { foreach (var item in _elements) { InvalidateElement(item.Key, item.Value); } } #endregion #region Mouse Interactions protected override void OnMouseDown(MouseButtonEventArgs e) { base.OnMouseDown(e); if (e.ChangedButton == MouseButton.Left) { Point pt = e.GetPosition(this); VisualTreeHelper.HitTest(this, null, MouseDownCallback, new PointHitTestParameters(pt)); } } private HitTestResultBehavior MouseDownCallback(HitTestResult result) { if (result.VisualHit.GetType() == typeof(DrawingVisual)) { var visual = result.VisualHit as DrawingVisual; if (visual != null) { var element = _elements[visual]; if (element != null) { element.RaiseEvent(new MouseButtonEventArgs(Mouse.PrimaryDevice, 0, MouseButton.Left) { RoutedEvent = Mouse.MouseDownEvent, Source = element, }); } } } return HitTestResultBehavior.Stop; } protected override void OnMouseUp(MouseButtonEventArgs e) { base.OnMouseUp(e); if (e.ChangedButton == MouseButton.Left) { Point pt = e.GetPosition(this); VisualTreeHelper.HitTest(this, null, MouseUpCallback, new PointHitTestParameters(pt)); } } private HitTestResultBehavior MouseUpCallback(HitTestResult result) { if (result.VisualHit.GetType() == typeof(DrawingVisual)) { var visual = result.VisualHit as DrawingVisual; if (visual != null) { var element = _elements[visual]; if (element != null) { element.RaiseEvent(new MouseButtonEventArgs(Mouse.PrimaryDevice, 0, MouseButton.Left) { RoutedEvent = Mouse.MouseUpEvent, Source = element, }); } } } return HitTestResultBehavior.Stop; } protected override void OnMouseMove(MouseEventArgs e) { base.OnMouseMove(e); Point pt = e.GetPosition(this); VisualTreeHelper.HitTest(this, null, MouseMoveCallback, new PointHitTestParameters(pt)); } private HitTestResultBehavior MouseMoveCallback(HitTestResult result) { if (result.VisualHit.GetType() == typeof(DrawingVisual)) { var visual = result.VisualHit as DrawingVisual; if (visual != null) { var element = _elements[visual]; if (element != null) { element.RaiseEvent(new MouseEventArgs(Mouse.PrimaryDevice, 0) { RoutedEvent = Mouse.MouseMoveEvent, Source = element, }); if (_mouseEnterElement != null && _mouseEnterElement != element) { _mouseEnterElement.RaiseEvent(new MouseEventArgs(Mouse.PrimaryDevice, 0) { RoutedEvent = Mouse.MouseLeaveEvent, Source = _mouseEnterElement, }); } if (_mouseEnterElement != element) { _mouseEnterElement = element; element.RaiseEvent(new MouseEventArgs(Mouse.PrimaryDevice, 0) { RoutedEvent = Mouse.MouseEnterEvent, Source = element, }); } } } } return HitTestResultBehavior.Stop; } #endregion #region Elements Changed private void Elements_CollectionChanged(object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e) { if (!IsDesignMode) { foreach (var element in e.NewItems) { (element as FrameworkElement).DataContext = DataContext; } } } private void OnElementsChanged() { if (Elements != null) { Elements.CollectionChanged += Elements_CollectionChanged; } } #endregion #region Drawing Executers private void DrawElements() { foreach (var element in Elements) { DrawElement(element); } } private void DrawElement(FrameworkElement element) { DrawingVisual visual = new DrawingVisual(); IVisualBinder binder = InvalidateElement(visual, element); AddVisual(visual, element); foreach (var dp in binder.GetRenderProperties()) { var container = BindingEventContainer.Generate(visual, element, dp); container.ValueChanged += (sender, e) => { InvalidateElement(e.Visual, e.Element); }; _containers.Add(container); } } private IVisualBinder InvalidateElement(DrawingVisual visual, FrameworkElement element) { var context = visual.RenderOpen(); if (!_binders.TryGetValue(element.GetType(), out IVisualBinder binder)) { throw new InvalidOperationException($"No visual binder is registered for element type '{element.GetType()}'"); } binder.DrawVisual(element, this, context); visual.Opacity = element.Opacity; if (element.Visibility != Visibility.Visible) { visual.Opacity = 0; } context.Close(); return binder; } private void AddVisual(DrawingVisual visual, FrameworkElement element) { visual.Opacity = element.Opacity; visual.Transform = element.RenderTransform; _children.Add(visual); _elements.Add(visual, element); } #endregion #region Abstract and Virtual Methods protected virtual Size OnMeasure() { return new Size(double.NaN, double.NaN); } public abstract Point GetElementLocation(FrameworkElement element); public abstract Size GetElementSize(FrameworkElement element); #endregion #region Binders public void RegisterVisualBinder(IVisualBinder instance) { _binders.Add(typeof(T), instance); } #endregion #region Child Get/Count protected override int VisualChildrenCount => _children != null ? _children.Count : 0; protected override Visual GetVisualChild(int index) { if (index < 0 || index >= _children.Count) { throw new ArgumentOutOfRangeException(); } return _children[index]; } #endregion #region Helper Methods protected bool IsDesignMode { get { return (DesignerProperties.GetIsInDesignMode(new DependencyObject())); } } #endregion } }