Skip to content

Instantly share code, notes, and snippets.

@earthengine
Created October 28, 2016 05:19
Show Gist options
  • Save earthengine/c922e3ef73d27efc8ade6afb0c4edf46 to your computer and use it in GitHub Desktop.
Save earthengine/c922e3ef73d27efc8ade6afb0c4edf46 to your computer and use it in GitHub Desktop.
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