csharpc-sharpdotnetxamlavaloniauicross-platformcross-platform-xamlavaloniaguimulti-platformuser-interfacedotnetcore
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.
284 lines
8.7 KiB
284 lines
8.7 KiB
// Copyright (c) The Perspex Project. All rights reserved.
|
|
// Licensed under the MIT license. See licence.md file in the project root for full license information.
|
|
|
|
using System;
|
|
using System.Linq;
|
|
using System.Reactive.Disposables;
|
|
using Perspex.Cairo.Media.Imaging;
|
|
using Perspex.Media;
|
|
|
|
namespace Perspex.Cairo.Media
|
|
{
|
|
using Perspex.Media.Imaging;
|
|
using Cairo = global::Cairo;
|
|
|
|
/// <summary>
|
|
/// Draws using Direct2D1.
|
|
/// </summary>
|
|
public class DrawingContext : IDrawingContext, IDisposable
|
|
{
|
|
/// <summary>
|
|
/// The cairo context.
|
|
/// </summary>
|
|
private readonly Cairo.Context _context;
|
|
|
|
/// <summary>
|
|
/// Initializes a new instance of the <see cref="DrawingContext"/> class.
|
|
/// </summary>
|
|
/// <param name="surface">The target surface.</param>
|
|
public DrawingContext(Cairo.Surface surface)
|
|
{
|
|
_context = new Cairo.Context(surface);
|
|
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)
|
|
{
|
|
_context = Gdk.CairoHelper.Create(drawable);
|
|
CurrentTransform = Matrix.Identity;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets the current transform of the drawing context.
|
|
/// </summary>
|
|
public Matrix CurrentTransform
|
|
{
|
|
get; }
|
|
|
|
/// <summary>
|
|
/// Ends a draw operation.
|
|
/// </summary>
|
|
public void Dispose()
|
|
{
|
|
_context.Dispose();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Draws a bitmap image.
|
|
/// </summary>
|
|
/// <param name="source">The bitmap image.</param>
|
|
/// <param name="opacity">The opacity to draw with.</param>
|
|
/// <param name="sourceRect">The rect in the image to draw.</param>
|
|
/// <param name="destRect">The rect in the output to draw to.</param>
|
|
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 scale = new Vector(destRect.Width / sourceRect.Width, destRect.Height / sourceRect.Height);
|
|
|
|
_context.Save();
|
|
_context.Scale(scale.X, scale.Y);
|
|
destRect /= scale;
|
|
|
|
Gdk.CairoHelper.SetSourcePixbuf(
|
|
_context,
|
|
impl.Surface,
|
|
-sourceRect.X + destRect.X,
|
|
-sourceRect.Y + destRect.Y);
|
|
|
|
_context.Rectangle(destRect.ToCairo());
|
|
_context.Fill();
|
|
_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, Point p1, Point p2)
|
|
{
|
|
var size = new Rect(p1, p2).Size;
|
|
|
|
using (var p = SetPen(pen, size))
|
|
{
|
|
_context.MoveTo(p1.ToCairo());
|
|
_context.LineTo(p2.ToCairo());
|
|
_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(Brush brush, Pen pen, Geometry geometry)
|
|
{
|
|
var impl = geometry.PlatformImpl as StreamGeometryImpl;
|
|
|
|
using (var pop = PushTransform(impl.Transform))
|
|
{
|
|
_context.AppendPath(impl.Path);
|
|
|
|
if (brush != null)
|
|
{
|
|
using (var b = SetBrush(brush, geometry.Bounds.Size))
|
|
{
|
|
if (pen != null)
|
|
_context.FillPreserve();
|
|
else
|
|
_context.Fill();
|
|
}
|
|
}
|
|
}
|
|
|
|
if (pen != null)
|
|
{
|
|
using (var p = SetPen(pen, geometry.Bounds.Size))
|
|
{
|
|
_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)
|
|
{
|
|
using (var p = SetPen(pen, rect.Size))
|
|
{
|
|
_context.Rectangle(rect.ToCairo ());
|
|
_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;
|
|
_context.MoveTo(origin.X, origin.Y);
|
|
|
|
using (var b = SetBrush(foreground, new Size(0, 0)))
|
|
{
|
|
Pango.CairoHelper.ShowLayout(_context, layout);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Draws a filled rectangle.
|
|
/// </summary>
|
|
/// <param name="brush">The brush.</param>
|
|
/// <param name="rect">The rectangle bounds.</param>
|
|
public void FillRectange(Brush brush, Rect rect, float cornerRadius)
|
|
{
|
|
using (var b = SetBrush(brush, rect.Size))
|
|
{
|
|
_context.Rectangle(rect.ToCairo ());
|
|
_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)
|
|
{
|
|
_context.Rectangle(clip.ToCairo());
|
|
_context.Clip();
|
|
|
|
return Disposable.Create(() => _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)
|
|
{
|
|
_context.Transform(matrix.ToCairo());
|
|
|
|
return Disposable.Create(() =>
|
|
{
|
|
_context.Transform(matrix.Invert().ToCairo());
|
|
});
|
|
}
|
|
|
|
private double opacityOverride = 1.0f;
|
|
|
|
private BrushImpl SetBrush(Brush brush, Size destinationSize)
|
|
{
|
|
var solid = brush as SolidColorBrush;
|
|
var linearGradientBrush = brush as LinearGradientBrush;
|
|
var imageBrush = brush as ImageBrush;
|
|
var visualBrush = brush as VisualBrush;
|
|
BrushImpl impl = null;
|
|
|
|
if (solid != null)
|
|
{
|
|
impl = new SolidColorBrushImpl(solid, opacityOverride);
|
|
}
|
|
else if (linearGradientBrush != null)
|
|
{
|
|
impl = new LinearGradientBrushImpl(linearGradientBrush, destinationSize);
|
|
}
|
|
else if (imageBrush != null)
|
|
{
|
|
impl = new ImageBrushImpl(imageBrush, destinationSize);
|
|
}
|
|
else if (visualBrush != null)
|
|
{
|
|
impl = new VisualBrushImpl(visualBrush, destinationSize);
|
|
}
|
|
else
|
|
{
|
|
impl = new SolidColorBrushImpl(null, 1.0f);
|
|
}
|
|
|
|
_context.SetSource(impl.PlatformBrush);
|
|
|
|
return impl;
|
|
}
|
|
|
|
private BrushImpl SetPen(Pen pen, Size destinationSize)
|
|
{
|
|
if (pen.DashStyle != null)
|
|
{
|
|
if (pen.DashStyle.Dashes != null && pen.DashStyle.Dashes.Count > 0)
|
|
{
|
|
var cray = pen.DashStyle.Dashes.ToArray();
|
|
_context.SetDash(cray, pen.DashStyle.Offset);
|
|
}
|
|
}
|
|
|
|
_context.LineWidth = pen.Thickness;
|
|
_context.MiterLimit = pen.MiterLimit;
|
|
|
|
// Line caps and joins are currently broken on Cairo. I've defaulted them to sensible defaults for now.
|
|
// Cairo does not have StartLineCap, EndLineCap, and DashCap properties, whereas Direct2D does.
|
|
// TODO: Figure out a solution for this.
|
|
_context.LineJoin = Cairo.LineJoin.Miter;
|
|
_context.LineCap = Cairo.LineCap.Butt;
|
|
|
|
return SetBrush(pen.Brush, destinationSize);
|
|
}
|
|
}
|
|
}
|
|
|