Created
October 28, 2016 05:19
-
-
Save earthengine/c922e3ef73d27efc8ade6afb0c4edf46 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
public class UIThreadDrawingCanvas : FrameworkElement, IZoomFit | |
{ | |
public static readonly DependencyProperty ContentDomainProperty = DependencyProperty.Register( | |
"ContentDomain", typeof(Rect), typeof(UIThreadDrawingCanvas)); | |
public static readonly DependencyProperty RenderRequestProperty = DependencyProperty.Register( | |
"RenderRequest", typeof(ContextDrawer), typeof(UIThreadDrawingCanvas), | |
new FrameworkPropertyMetadata(null, RenderRequestChanged)); | |
public static readonly DependencyProperty BackgroundProperty = DependencyProperty.Register( | |
"Background", typeof(Brush), typeof(UIThreadDrawingCanvas), | |
new FrameworkPropertyMetadata(Brushes.Transparent)); | |
private static readonly DependencyProperty TransformMatrixProperty = DependencyProperty.Register( | |
"TransformMatrix", typeof(Matrix), typeof(UIThreadDrawingCanvas), | |
new FrameworkPropertyMetadata(Matrix.Identity, FrameworkPropertyMetadataOptions.None)); | |
private static DependencyProperty RenderCompleteCommandProperty = DependencyProperty.Register( | |
"RenderCompleteCommand", typeof(ICommand), typeof(UIThreadDrawingCanvas)); | |
public static readonly RoutedEvent RenderStartEvent = EventManager.RegisterRoutedEvent( | |
"RenderStart", RoutingStrategy.Bubble, typeof(RoutedEventHandler), typeof(UIThreadDrawingCanvas)); | |
public static readonly RoutedEvent RenderFinishEvent = EventManager.RegisterRoutedEvent( | |
"RenderFinish", RoutingStrategy.Bubble, typeof(RoutedEventHandler), typeof(UIThreadDrawingCanvas)); | |
public event RoutedEventHandler RenderStart | |
{ | |
add { AddHandler(RenderStartEvent, value); } | |
remove { RemoveHandler(RenderStartEvent, value); } | |
} | |
public event RoutedEventHandler RenderFinish | |
{ | |
add { AddHandler(RenderFinishEvent, value); } | |
remove { RemoveHandler(RenderFinishEvent, value); } | |
} | |
public ICommand RenderCompleteCommand | |
{ | |
get { return (ICommand)GetValue(RenderCompleteCommandProperty); } | |
set { SetValue(RenderCompleteCommandProperty, value); } | |
} | |
public Matrix TransformMatrix | |
{ | |
get { | |
var v = visual.Transform.Value; | |
SetValue(TransformMatrixProperty, v); | |
return v; | |
} | |
set | |
{ | |
} | |
} | |
public Rect ContentDomain | |
{ | |
get { return (Rect)GetValue(ContentDomainProperty); } | |
set { SetValue(ContentDomainProperty, value); } | |
} | |
public ContextDrawer RenderRequest | |
{ | |
get { return (ContextDrawer)GetValue(RenderRequestProperty); } | |
set { SetValue(RenderRequestProperty, value); } | |
} | |
public Brush Background | |
{ | |
get { return (Brush)GetValue(BackgroundProperty); } | |
set { SetValue(BackgroundProperty, value); } | |
} | |
private void SetTransformMatrix(Matrix m) | |
{ | |
visual.Transform = new MatrixTransform(m); | |
SetValue(TransformMatrixProperty, m); | |
} | |
public void ZoomFit() | |
{ | |
if (ContentDomain.Width <= 0 || ContentDomain.Height <= 0) | |
{ | |
SetTransformMatrix(Matrix.Identity); | |
return; | |
} | |
var s1 = ActualWidth / ContentDomain.Width; | |
var s2 = ActualHeight / ContentDomain.Height; | |
if (s1 == 0 || s2 == 0) | |
{ | |
SetTransformMatrix(Matrix.Identity); | |
return; | |
} | |
var m = new Matrix(); | |
var sc = s1 > s2 ? s2 : s1; | |
m.Scale(sc, sc); | |
var porig = m.Transform(new Point(ContentDomain.Width / 2 + ContentDomain.Left, ContentDomain.Height / 2 + ContentDomain.Top)); | |
var pdest = new Point(ActualWidth / 2, ActualHeight / 2); | |
m.Translate(pdest.X - porig.X, pdest.Y - porig.Y); | |
SetTransformMatrix(m); | |
} | |
public UIThreadDrawingCanvas() | |
{ | |
visual = new HostVisual(); | |
renderTask = Task.Delay(0); | |
cts = new CancellationTokenSource(); | |
var tcs = new TaskCompletionSource<VisualTarget>(); | |
var thread = new Thread(() => { | |
renderDispatcher = Dispatcher.CurrentDispatcher; | |
renderDispatcher.Invoke(() => | |
{ | |
tcs.SetResult(new VisualTarget(visual)); | |
}); | |
Dispatcher.Run(); | |
}) { | |
IsBackground = true, | |
Name = "DrawingCanvas" | |
}; | |
thread.SetApartmentState(ApartmentState.STA); | |
thread.Start(); | |
vt = tcs.Task.Result; | |
AddVisualChild(visual); | |
SizeChanged += UIThreadDrawingCanvas_SizeChanged; | |
} | |
protected override int VisualChildrenCount | |
{ | |
get { return background == null ? 1 : 2; } | |
} | |
protected override Visual GetVisualChild(int index) | |
{ | |
if (index<0 || index>1 || (index==1 && background== null)) | |
{ | |
throw new ArgumentOutOfRangeException("index"); | |
} | |
return index==0 ? (background==null ? visual : background) : visual; | |
} | |
protected override HitTestResult HitTestCore(PointHitTestParameters hitTestParameters) | |
{ | |
var pt = hitTestParameters.HitPoint; | |
return new PointHitTestResult(visual, pt); | |
} | |
protected override void OnMouseDown(MouseButtonEventArgs e) | |
{ | |
if (e.ChangedButton == MouseButton.Left) | |
{ | |
if (e.ClickCount == 2) | |
{ | |
ZoomFit(); | |
} | |
else | |
{ | |
draggingPoint = e.GetPosition(this); | |
CaptureMouse(); | |
} | |
} | |
} | |
protected override void OnMouseUp(MouseButtonEventArgs e) | |
{ | |
if (e.ChangedButton == MouseButton.Left && draggingPoint.HasValue) | |
{ | |
var pos = e.GetPosition(this); | |
ReleaseMouseCapture(); | |
var v = draggingPoint.Value - pos; | |
var m = TransformMatrix; | |
m.Translate(-v.X, -v.Y); | |
SetTransformMatrix(m); | |
draggingPoint = null; | |
} | |
} | |
protected override void OnMouseWheel(MouseWheelEventArgs e) | |
{ | |
var s = e.Delta > 0 ? 1.04 : 0.96; | |
var m = TransformMatrix; | |
var porig = e.GetPosition(this); | |
m.ScaleAt(s, s, porig.X, porig.Y); | |
SetTransformMatrix(m); | |
} | |
private Visual background; | |
private HostVisual visual; | |
private VisualTarget vt; | |
private Dispatcher renderDispatcher; | |
private CancellationTokenSource cts; | |
private Task renderTask; | |
private Point? draggingPoint; | |
private void UIThreadDrawingCanvas_SizeChanged(object sender, SizeChangedEventArgs e) | |
{ | |
if (background != null) | |
RemoveVisualChild(background); | |
RemoveVisualChild(visual); | |
var dv = new DrawingVisual(); | |
using(var ctx = dv.RenderOpen()) | |
{ | |
ctx.DrawRectangle(Background, new Pen(Brushes.Transparent, 0), new Rect(new Point(0, 0), e.NewSize)); | |
} | |
background = dv; | |
AddVisualChild(background); | |
AddVisualChild(visual); | |
ZoomFit(); | |
} | |
private static async void RenderRequestChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) | |
{ | |
var that = d as UIThreadDrawingCanvas; | |
if (that == null || e.NewValue == null | |
|| !(e.NewValue is ContextDrawer)) return; | |
var cd = e.NewValue as ContextDrawer; | |
switch (cd.Priority) | |
{ | |
case ContextDrawingPriority.CancelPrevious: | |
{ | |
using (that.cts) that.cts.Cancel(); | |
that.cts = new CancellationTokenSource(); | |
} | |
break; | |
case ContextDrawingPriority.IgnogeCurrent: | |
{ | |
if (!that.renderTask.IsCompleted) return; | |
} | |
break; | |
case ContextDrawingPriority.PlaceInQueue: | |
break; | |
} | |
await that.Draw((ContextDrawer)(e.NewValue), that.cts.Token); | |
} | |
private async Task Draw(ContextDrawer drawingAction, CancellationToken token) | |
{ | |
await renderDispatcher.InvokeAsync(() => | |
{ | |
var current = renderTask; | |
renderTask = renderTask.ContinueWith(t => | |
{ | |
return DrawRaw(token, drawingAction); | |
}).Unwrap(); | |
}); | |
} | |
private async Task DrawRaw(CancellationToken token, ContextDrawer drawingAction) | |
{ | |
try { | |
await renderDispatcher.InvokeAsync(() => { | |
Dispatcher.Invoke(() => RaiseRenderStartEvent()); | |
var dv = new DrawingVisual(); | |
using (var ctx = dv.RenderOpen()) | |
{ | |
drawingAction.Draw(ctx, token); | |
} | |
vt.RootVisual = dv; | |
Dispatcher.InvokeAsync(() => { | |
RaiseRenderFinishEvent(); | |
if (RenderCompleteCommand!=null && RenderCompleteCommand.CanExecute(this)) | |
RenderCompleteCommand.Execute(this); | |
}); | |
}); | |
} | |
catch (AggregateException ex) | |
{ | |
ex.Handle(e => | |
{ | |
var c = e is OperationCanceledException; | |
return false; | |
}); | |
} | |
catch (OperationCanceledException) | |
{ | |
throw; | |
} | |
} | |
private void RaiseRenderStartEvent() | |
{ | |
var ea = new RoutedEventArgs(RenderStartEvent); | |
RaiseEvent(ea); | |
} | |
private void RaiseRenderFinishEvent() | |
{ | |
var ea = new RoutedEventArgs(RenderFinishEvent); | |
RaiseEvent(ea); | |
} | |
public Point GetEventPosition(MouseEventArgs e) | |
{ | |
return e.GetPosition(this); | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment