484 lines
10 KiB
C#
484 lines
10 KiB
C#
using System;
|
|
using System.Collections.Generic;
|
|
using System.ComponentModel;
|
|
using System.Diagnostics;
|
|
using System.Globalization;
|
|
using System.Threading;
|
|
using System.Windows;
|
|
using System.Windows.Controls;
|
|
using System.Windows.Media;
|
|
using System.Windows.Media.Imaging;
|
|
using System.Windows.Threading;
|
|
using Microsoft.Research.DynamicDataDisplay.Common;
|
|
|
|
namespace Microsoft.Research.DynamicDataDisplay
|
|
{
|
|
/// <summary>ViewportElement2D is intended to be a child of Viewport2D. Specifics
|
|
/// of ViewportElement2D is Viewport2D attached property</summary>
|
|
public abstract class ViewportElement2D : PlotterElement, INotifyPropertyChanged
|
|
{
|
|
protected ViewportElement2D() { }
|
|
|
|
protected virtual Panel GetHostPanel(Plotter plotter)
|
|
{
|
|
return plotter.CentralGrid;
|
|
}
|
|
|
|
protected override void OnPlotterAttached(Plotter plotter)
|
|
{
|
|
base.OnPlotterAttached(plotter);
|
|
|
|
plotter2D = (Plotter2D)plotter;
|
|
GetHostPanel(plotter).Children.Add(this);
|
|
viewport = plotter2D.Viewport;
|
|
viewport.PropertyChanged += OnViewportPropertyChanged;
|
|
}
|
|
|
|
private void OnViewportPropertyChanged(object sender, ExtendedPropertyChangedEventArgs e)
|
|
{
|
|
if (e.PropertyName == "Visible")
|
|
{
|
|
OnVisibleChanged((DataRect)e.NewValue, (DataRect)e.OldValue);
|
|
}
|
|
else if (e.PropertyName == "Output")
|
|
{
|
|
OnOutputChanged((Rect)e.NewValue, (Rect)e.OldValue);
|
|
}
|
|
else if (e.PropertyName == "Transform")
|
|
{
|
|
Update();
|
|
}
|
|
else
|
|
{
|
|
// other properties changed are now not interesting for us
|
|
}
|
|
}
|
|
|
|
protected override void OnPlotterDetaching(Plotter plotter)
|
|
{
|
|
base.OnPlotterDetaching(plotter);
|
|
|
|
viewport.PropertyChanged -= OnViewportPropertyChanged;
|
|
viewport = null;
|
|
GetHostPanel(plotter).Children.Remove(this);
|
|
plotter2D = null;
|
|
}
|
|
|
|
private Plotter2D plotter2D;
|
|
protected Plotter2D Plotter2D
|
|
{
|
|
get { return plotter2D; }
|
|
}
|
|
|
|
public int ZIndex
|
|
{
|
|
get { return Panel.GetZIndex(this); }
|
|
set { Panel.SetZIndex(this, value); }
|
|
}
|
|
|
|
#region Viewport
|
|
|
|
private Viewport2D viewport;
|
|
protected Viewport2D Viewport
|
|
{
|
|
get { return viewport; }
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region Description
|
|
|
|
/// <summary>
|
|
/// Creates the default description.
|
|
/// </summary>
|
|
/// <returns></returns>
|
|
protected virtual Description CreateDefaultDescription()
|
|
{
|
|
return new StandardDescription();
|
|
}
|
|
|
|
private Description description;
|
|
/// <summary>
|
|
/// Gets or sets the description.
|
|
/// </summary>
|
|
/// <value>The description.</value>
|
|
public Description Description
|
|
{
|
|
get
|
|
{
|
|
if (description == null)
|
|
{
|
|
description = CreateDefaultDescription();
|
|
description.Attach(this);
|
|
}
|
|
return description;
|
|
}
|
|
set
|
|
{
|
|
if (description != null)
|
|
{
|
|
description.Detach();
|
|
}
|
|
description = value;
|
|
if (description != null)
|
|
{
|
|
description.Attach(this);
|
|
}
|
|
RaisePropertyChanged("Description");
|
|
}
|
|
}
|
|
|
|
public override string ToString()
|
|
{
|
|
return GetType().Name + ": " + Description.Brief;
|
|
}
|
|
|
|
#endregion
|
|
|
|
private Vector offset = new Vector();
|
|
protected internal Vector Offset
|
|
{
|
|
get { return offset; }
|
|
set { offset = value; }
|
|
}
|
|
|
|
//bool SizeEqual(Size s1, Size s2, double eps)
|
|
//{
|
|
// double width = Math.Min(s1.Width, s2.Width);
|
|
// double height = Math.Min(s1.Height, s2.Height);
|
|
// return Math.Abs(s1.Width - s2.Width) < width * eps &&
|
|
// Math.Abs(s1.Height - s2.Height) < height * eps;
|
|
//}
|
|
|
|
protected virtual void OnVisibleChanged(DataRect newRect, DataRect oldRect)
|
|
{
|
|
if (newRect.Size == oldRect.Size)
|
|
{
|
|
var transform = viewport.Transform;
|
|
offset += oldRect.Location.DataToScreen(transform) - newRect.Location.DataToScreen(transform);
|
|
if (ManualTranslate)
|
|
{
|
|
Update();
|
|
}
|
|
}
|
|
else
|
|
{
|
|
offset = new Vector();
|
|
Update();
|
|
}
|
|
}
|
|
|
|
protected virtual void OnOutputChanged(Rect newRect, Rect oldRect)
|
|
{
|
|
offset = new Vector();
|
|
Update();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets a value indicating whether this instance is translated.
|
|
/// </summary>
|
|
/// <value>
|
|
/// <c>true</c> if this instance is translated; otherwise, <c>false</c>.
|
|
/// </value>
|
|
protected bool IsTranslated
|
|
{
|
|
get { return offset.X != 0 || offset.Y != 0; }
|
|
}
|
|
|
|
#region IsLevel
|
|
|
|
public bool IsLayer
|
|
{
|
|
get { return (bool)GetValue(IsLayerProperty); }
|
|
set { SetValue(IsLayerProperty, value); }
|
|
}
|
|
|
|
public static readonly DependencyProperty IsLayerProperty =
|
|
DependencyProperty.Register(
|
|
"IsLayer",
|
|
typeof(bool),
|
|
typeof(ViewportElement2D),
|
|
new FrameworkPropertyMetadata(
|
|
false
|
|
));
|
|
|
|
#endregion
|
|
|
|
#region Rendering & caching options
|
|
|
|
protected object GetValueSync(DependencyProperty property)
|
|
{
|
|
return Dispatcher.Invoke(
|
|
DispatcherPriority.Send,
|
|
(DispatcherOperationCallback)delegate { return GetValue(property); },
|
|
property);
|
|
}
|
|
|
|
protected void SetValueAsync(DependencyProperty property, object value)
|
|
{
|
|
Dispatcher.BeginInvoke(DispatcherPriority.Send,
|
|
(SendOrPostCallback)delegate { SetValue(property, value); },
|
|
value);
|
|
}
|
|
|
|
private bool manualClip;
|
|
/// <summary>
|
|
/// Gets or sets a value indicating whether descendant graph class
|
|
/// relies on autotic clipping by Viewport.Output or
|
|
/// does its own clipping.
|
|
/// </summary>
|
|
public bool ManualClip
|
|
{
|
|
get { return manualClip; }
|
|
set { manualClip = value; }
|
|
}
|
|
|
|
private bool manualTranslate;
|
|
/// <summary>
|
|
/// Gets or sets a value indicating whether descendant graph class
|
|
/// relies on automatic translation of it, or does its own.
|
|
/// </summary>
|
|
public bool ManualTranslate
|
|
{
|
|
get { return manualTranslate; }
|
|
set { manualTranslate = value; }
|
|
}
|
|
|
|
private RenderTo renderTarget = RenderTo.Screen;
|
|
/// <summary>
|
|
/// Gets or sets a value indicating whether descendant graph class
|
|
/// uses cached rendering of its content to image, or not.
|
|
/// </summary>
|
|
public RenderTo RenderTarget
|
|
{
|
|
get { return renderTarget; }
|
|
set { renderTarget = value; }
|
|
}
|
|
|
|
private enum ImageKind
|
|
{
|
|
Real,
|
|
BeingRendered,
|
|
Empty
|
|
}
|
|
|
|
#endregion
|
|
|
|
private RenderState CreateRenderState(DataRect renderVisible, RenderTo renderingType)
|
|
{
|
|
Rect output = Viewport.Output;
|
|
|
|
return new RenderState(renderVisible, Viewport.Visible,
|
|
output,
|
|
renderingType);
|
|
}
|
|
|
|
protected override void OnPropertyChanged(DependencyPropertyChangedEventArgs e)
|
|
{
|
|
base.OnPropertyChanged(e);
|
|
|
|
if (e.Property == VisibilityProperty)
|
|
{
|
|
Update();
|
|
}
|
|
}
|
|
|
|
private bool updateCalled;
|
|
private bool beforeFirstUpdate = true;
|
|
protected void Update()
|
|
{
|
|
if (Viewport == null) return;
|
|
|
|
UpdateCore();
|
|
|
|
if (!beforeFirstUpdate)
|
|
{
|
|
updateCalled = true;
|
|
InvalidateVisual();
|
|
}
|
|
beforeFirstUpdate = false;
|
|
}
|
|
|
|
protected virtual void UpdateCore() { }
|
|
|
|
protected void TranslateVisual()
|
|
{
|
|
if (!ManualTranslate)
|
|
{
|
|
shouldReRender = false;
|
|
}
|
|
InvalidateVisual();
|
|
}
|
|
|
|
#region Thumbnail
|
|
|
|
private ImageSource thumbnail;
|
|
public ImageSource Thumbnail
|
|
{
|
|
get
|
|
{
|
|
if (!CreateThumbnail)
|
|
{
|
|
CreateThumbnail = true;
|
|
}
|
|
return thumbnail;
|
|
}
|
|
}
|
|
|
|
private bool createThumbnail;
|
|
public bool CreateThumbnail
|
|
{
|
|
get { return createThumbnail; }
|
|
set
|
|
{
|
|
if (createThumbnail != value)
|
|
{
|
|
createThumbnail = value;
|
|
if (value)
|
|
{
|
|
RenderThumbnail();
|
|
}
|
|
else
|
|
{
|
|
thumbnail = null;
|
|
RaisePropertyChanged("Thumbnail");
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
private bool ShouldCreateThumbnail
|
|
{
|
|
get { return IsLayer && createThumbnail; }
|
|
}
|
|
|
|
private void RenderThumbnail()
|
|
{
|
|
if (Viewport == null) return;
|
|
|
|
Rect output = Viewport.Output;
|
|
if (output.Width == 0 || output.Height == 0)
|
|
return;
|
|
|
|
DataRect visible = Viewport.Visible;
|
|
|
|
var transform = viewport.Transform;
|
|
|
|
DrawingVisual visual = new DrawingVisual();
|
|
using (DrawingContext dc = visual.RenderOpen())
|
|
{
|
|
Point outputStart = visible.Location.DataToScreen(transform);
|
|
double x = -outputStart.X + offset.X;
|
|
double y = -outputStart.Y + output.Bottom - output.Top + offset.Y;
|
|
bool translate = !manualTranslate && IsTranslated;
|
|
if (translate)
|
|
{
|
|
dc.PushTransform(new TranslateTransform(x, y));
|
|
}
|
|
|
|
const byte c = 240;
|
|
Brush brush = new SolidColorBrush(Color.FromArgb(120, c, c, c));
|
|
Pen pen = new Pen(Brushes.Black, 1);
|
|
dc.DrawRectangle(brush, pen, output);
|
|
dc.DrawDrawing(graphContents);
|
|
|
|
if (translate)
|
|
{
|
|
dc.Pop();
|
|
}
|
|
}
|
|
|
|
RenderTargetBitmap bmp = new RenderTargetBitmap((int)output.Width, (int)output.Height, 96, 96, PixelFormats.Pbgra32);
|
|
bmp.Render(visual);
|
|
thumbnail = bmp;
|
|
RaisePropertyChanged("Thumbnail");
|
|
}
|
|
|
|
#endregion
|
|
|
|
private bool shouldReRender = true;
|
|
private DrawingGroup graphContents;
|
|
protected sealed override void OnRender(DrawingContext drawingContext)
|
|
{
|
|
if (Viewport == null) return;
|
|
|
|
Rect output = Viewport.Output;
|
|
if (output.Width == 0 || output.Height == 0) return;
|
|
if (output.IsEmpty) return;
|
|
if (Visibility != Visibility.Visible) return;
|
|
|
|
if (shouldReRender || manualTranslate || renderTarget == RenderTo.Image || beforeFirstUpdate || updateCalled)
|
|
{
|
|
if (graphContents == null)
|
|
{
|
|
graphContents = new DrawingGroup();
|
|
}
|
|
if (beforeFirstUpdate)
|
|
{
|
|
Update();
|
|
}
|
|
|
|
using (DrawingContext context = graphContents.Open())
|
|
{
|
|
if (renderTarget == RenderTo.Screen)
|
|
{
|
|
RenderState state = CreateRenderState(Viewport.Visible, RenderTo.Screen);
|
|
OnRenderCore(context, state);
|
|
}
|
|
else
|
|
{
|
|
// for future use
|
|
}
|
|
}
|
|
updateCalled = false;
|
|
}
|
|
|
|
// thumbnail is not created, if
|
|
// 1) CreateThumbnail is false
|
|
// 2) this graph has IsLayer property, set to false
|
|
if (ShouldCreateThumbnail)
|
|
{
|
|
RenderThumbnail();
|
|
}
|
|
|
|
if (!manualClip)
|
|
{
|
|
drawingContext.PushClip(new RectangleGeometry(output));
|
|
}
|
|
bool translate = !manualTranslate && IsTranslated;
|
|
if (translate)
|
|
{
|
|
drawingContext.PushTransform(new TranslateTransform(offset.X, offset.Y));
|
|
}
|
|
|
|
drawingContext.DrawDrawing(graphContents);
|
|
|
|
if (translate)
|
|
{
|
|
drawingContext.Pop();
|
|
}
|
|
if (!manualClip)
|
|
{
|
|
drawingContext.Pop();
|
|
}
|
|
shouldReRender = true;
|
|
}
|
|
|
|
|
|
protected abstract void OnRenderCore(DrawingContext dc, RenderState state);
|
|
|
|
#region INotifyPropertyChanged Members
|
|
|
|
public event PropertyChangedEventHandler PropertyChanged;
|
|
|
|
protected void RaisePropertyChanged(string propertyName)
|
|
{
|
|
if (PropertyChanged != null)
|
|
{
|
|
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
|
|
}
|
|
}
|
|
|
|
#endregion
|
|
}
|
|
} |