A cross-platform UI framework for .NET
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 

281 lines
9.6 KiB

// -----------------------------------------------------------------------
// <copyright file="DrawingContext.cs" company="Steven Kirk">
// Copyright 2013 MIT Licence. See licence.md for more information.
// </copyright>
// -----------------------------------------------------------------------
namespace Perspex.Cairo.Media
{
using System;
using System.Reactive.Disposables;
using Perspex.Cairo.Media.Imaging;
using Perspex.Media;
using Perspex.Platform;
using Splat;
using Cairo = global::Cairo;
using IBitmap = Perspex.Media.Imaging.IBitmap;
using System.Collections.Generic;
/// <summary>
/// Draws using Direct2D1.
/// </summary>
public class DrawingContext : IDrawingContext, IDisposable
{
/// <summary>
/// The cairo context.
/// </summary>
private Cairo.Context context;
/// <summary>
/// The cairo surface.
/// </summary>
private Cairo.Surface surface;
/// <summary>
/// Initializes a new instance of the <see cref="DrawingContext"/> class.
/// </summary>
/// <param name="surface">The target surface.</param>
public DrawingContext(Cairo.Surface surface)
{
this.surface = surface;
this.context = new Cairo.Context(surface);
this.CurrentTransform = Matrix.Identity;
}
/// <summary>
/// Initializes a new instance of the <see cref="DrawingContext"/> class.
/// </summary>
/// <param name="surface">The GDK drawable.</param>
public DrawingContext(Gdk.Drawable drawable)
{
this.Drawable = drawable;
this.context = Gdk.CairoHelper.Create(drawable);
this.CurrentTransform = Matrix.Identity;
}
public Matrix CurrentTransform
{
get;
private set;
}
public Gdk.Drawable Drawable
{
get;
private set;
}
/// <summary>
/// Ends a draw operation.
/// </summary>
public void Dispose()
{
this.context.Dispose();
// if (this.surface is Cairo.Win32Surface)
// {
if (this.surface != null)
this.surface.Dispose();
// }
}
public void DrawImage(IBitmap bitmap, double opacity, Rect sourceRect, Rect destRect)
{
var impl = bitmap.PlatformImpl as BitmapImpl;
var size = new Size(impl.PixelWidth, impl.PixelHeight);
var scaleX = destRect.Size.Width / sourceRect.Size.Width;
var scaleY = destRect.Size.Height / sourceRect.Size.Height;
this.context.Save();
this.context.Scale(scaleX, scaleY);
this.context.SetSourceSurface(impl.Surface, (int)sourceRect.X, (int)sourceRect.Y);
this.context.Rectangle(sourceRect.ToCairo());
this.context.Fill();
this.context.Restore();
}
/// <summary>
/// Draws a line.
/// </summary>
/// <param name="pen">The stroke pen.</param>
/// <param name="p1">The first point of the line.</param>
/// <param name="p1">The second point of the line.</param>
public void DrawLine(Pen pen, Perspex.Point p1, Perspex.Point p2)
{
var size = new Rect(p1, p2).Size;
this.SetBrush(pen.Brush, size);
this.context.LineWidth = pen.Thickness;
this.context.MoveTo(p1.ToCairo());
this.context.LineTo(p2.ToCairo());
this.context.Stroke();
}
/// <summary>
/// Draws a geometry.
/// </summary>
/// <param name="brush">The fill brush.</param>
/// <param name="pen">The stroke pen.</param>
/// <param name="geometry">The geometry.</param>
public void DrawGeometry(Perspex.Media.Brush brush, Perspex.Media.Pen pen, Perspex.Media.Geometry geometry)
{
var impl = geometry.PlatformImpl as StreamGeometryImpl;
var clone = new Queue<GeometryOp>(impl.Operations);
using (var pop = this.PushTransform(impl.Transform))
{
while (clone.Count > 0)
{
var current = clone.Dequeue();
if (current is BeginOp)
{
var bo = current as BeginOp;
this.context.MoveTo(bo.Point.ToCairo());
}
else if (current is LineToOp)
{
var lto = current as LineToOp;
this.context.LineTo(lto.Point.ToCairo());
}
else if (current is EndOp)
{
if (((EndOp)current).IsClosed)
this.context.ClosePath();
}
else if (current is CurveToOp)
{
var cto = current as CurveToOp;
this.context.CurveTo(cto.Point.ToCairo(), cto.Point2.ToCairo(), cto.Point3.ToCairo());
}
}
if (brush != null)
{
this.SetBrush(brush, geometry.Bounds.Size);
if (pen != null)
this.context.FillPreserve();
else
this.context.Fill();
}
if (pen != null)
{
this.SetPen(pen, geometry.Bounds.Size);
this.context.Stroke();
}
}
}
/// <summary>
/// Draws the outline of a rectangle.
/// </summary>
/// <param name="pen">The pen.</param>
/// <param name="rect">The rectangle bounds.</param>
public void DrawRectange(Pen pen, Rect rect, float cornerRadius)
{
this.SetPen(pen, rect.Size);
this.context.Rectangle(rect.ToCairo());
this.context.Stroke();
}
/// <summary>
/// Draws text.
/// </summary>
/// <param name="foreground">The foreground brush.</param>
/// <param name="origin">The upper-left corner of the text.</param>
/// <param name="text">The text.</param>
public void DrawText(Brush foreground, Point origin, FormattedText text)
{
var layout = ((FormattedTextImpl)text.PlatformImpl).Layout;
this.SetBrush(foreground, new Size(0, 0));
this.context.MoveTo(origin.X, origin.Y);
Pango.CairoHelper.ShowLayout(this.context, layout);
}
/// <summary>
/// Draws a filled rectangle.
/// </summary>
/// <param name="brush">The brush.</param>
/// <param name="rect">The rectangle bounds.</param>
public void FillRectange(Perspex.Media.Brush brush, Rect rect, float cornerRadius)
{
this.SetBrush(brush, rect.Size);
this.context.Rectangle(rect.ToCairo());
this.context.Fill();
}
/// <summary>
/// Pushes a clip rectange.
/// </summary>
/// <param name="clip">The clip rectangle.</param>
/// <returns>A disposable used to undo the clip rectangle.</returns>
public IDisposable PushClip(Rect clip)
{
this.context.Rectangle(clip.ToCairo());
this.context.Clip();
return Disposable.Create(() => this.context.ResetClip());
}
/// <summary>
/// Pushes an opacity value.
/// </summary>
/// <param name="opacity">The opacity.</param>
/// <returns>A disposable used to undo the opacity.</returns>
public IDisposable PushOpacity(double opacity)
{
// TODO: Implement
return Disposable.Empty;
}
/// <summary>
/// Pushes a matrix transformation.
/// </summary>
/// <param name="matrix">The matrix</param>
/// <returns>A disposable used to undo the transformation.</returns>
public IDisposable PushTransform(Matrix matrix)
{
this.context.Transform(matrix.ToCairo());
return Disposable.Create(() =>
{
this.context.Transform(matrix.Invert().ToCairo());
});
}
private void SetBrush(Brush brush, Size destinationSize)
{
var solid = brush as SolidColorBrush;
var linearGradientBrush = brush as LinearGradientBrush;
if (solid != null)
{
this.context.SetSourceRGBA(
solid.Color.R / 255.0,
solid.Color.G / 255.0,
solid.Color.B / 255.0,
solid.Color.A / 255.0);
}
else if (linearGradientBrush != null)
{
Cairo.LinearGradient g = new Cairo.LinearGradient(linearGradientBrush.StartPoint.X * destinationSize.Width, linearGradientBrush.StartPoint.Y * destinationSize.Height, linearGradientBrush.EndPoint.X * destinationSize.Width, linearGradientBrush.EndPoint.Y * destinationSize.Height);
foreach (var s in linearGradientBrush.GradientStops)
g.AddColorStopRgb(s.Offset, new Cairo.Color(s.Color.R, s.Color.G, s.Color.B, s.Color.A));
g.Extend = Cairo.Extend.Pad;
this.context.SetSource(g);
}
}
private void SetPen(Pen pen, Size destinationSize)
{
this.SetBrush(pen.Brush, destinationSize);
this.context.LineWidth = pen.Thickness;
}
}
}