diff --git a/Perspex.Direct2D1/Media/DrawingContext.cs b/Perspex.Direct2D1/Media/DrawingContext.cs
index a8c3f5b659..cb058dde6d 100644
--- a/Perspex.Direct2D1/Media/DrawingContext.cs
+++ b/Perspex.Direct2D1/Media/DrawingContext.cs
@@ -43,6 +43,12 @@ namespace Perspex.Direct2D1.Media
this.renderTarget.BeginDraw();
}
+ public Matrix CurrentTransform
+ {
+ get { return Convert(this.renderTarget.Transform); }
+ set { this.renderTarget.Transform = Convert(value); }
+ }
+
///
/// Ends a draw operation.
///
@@ -235,6 +241,17 @@ namespace Perspex.Direct2D1.Media
(float)matrix.OffsetY);
}
+ private Matrix Convert(Matrix3x2 matrix)
+ {
+ return new Matrix(
+ matrix.M11,
+ matrix.M12,
+ matrix.M21,
+ matrix.M22,
+ matrix.M31,
+ matrix.M32);
+ }
+
///
/// Converts a to a
///
diff --git a/Perspex.Direct2D1/Renderer.cs b/Perspex.Direct2D1/Renderer.cs
index 0e5689afdc..9817a27528 100644
--- a/Perspex.Direct2D1/Renderer.cs
+++ b/Perspex.Direct2D1/Renderer.cs
@@ -9,12 +9,14 @@ namespace Perspex.Direct2D1
using System;
using System.Linq;
using Perspex.Direct2D1.Media;
+ using Perspex.Media;
using Perspex.Platform;
using SharpDX;
using SharpDX.Direct2D1;
using Splat;
using DwFactory = SharpDX.DirectWrite.Factory;
using Matrix = Perspex.Media.Matrix;
+ using Point = Perspex.Point;
///
/// Renders a .
@@ -122,29 +124,30 @@ namespace Perspex.Direct2D1
{
if (visual.IsVisible && visual.Opacity > 0)
{
- if (visual.Opacity < 1)
+ Matrix transform = Matrix.Identity;
+
+ if (visual.RenderTransform != null)
{
- Layer layer = new Layer(this.renderTarget);
- LayerParameters p = new LayerParameters();
- p.Opacity = (float)visual.Opacity;
- this.renderTarget.PushLayer(ref p, layer);
+ Matrix current = context.CurrentTransform;
+ Matrix offset = Matrix.Translation(visual.TransformOrigin.ToPixels(visual.Bounds.Size));
+ transform = -current * -offset * visual.RenderTransform.Value * offset * current;
}
- visual.Render(context);
+ transform *= Matrix.Translation(visual.Bounds.Position);
- foreach (IVisual child in visual.VisualChildren)
+ using (context.PushTransform(transform))
{
- Matrix translate = Matrix.Translation(child.Bounds.X, child.Bounds.Y);
+ visual.Render(context);
- using (context.PushTransform(translate))
+ foreach (var child in visual.VisualChildren)
{
this.Render(child, context);
}
}
- if (visual.Opacity < 1)
+ if (visual.RenderTransform != null)
{
- this.renderTarget.PopLayer();
+ ((RotateTransform)visual.RenderTransform).Angle++;
}
}
}
diff --git a/Perspex/IVisual.cs b/Perspex/IVisual.cs
index 1f54d933f2..96fb13bf0d 100644
--- a/Perspex/IVisual.cs
+++ b/Perspex/IVisual.cs
@@ -20,6 +20,10 @@ namespace Perspex
double Opacity { get; }
+ Transform RenderTransform { get; }
+
+ Origin TransformOrigin { get; }
+
IEnumerable VisualChildren { get; }
IVisual VisualParent { get; set; }
diff --git a/Perspex/Media/IDrawingContext.cs b/Perspex/Media/IDrawingContext.cs
index e36d6be8d2..b7b81f1568 100644
--- a/Perspex/Media/IDrawingContext.cs
+++ b/Perspex/Media/IDrawingContext.cs
@@ -15,6 +15,8 @@ namespace Perspex.Media
///
public interface IDrawingContext : IDisposable
{
+ Matrix CurrentTransform { get; }
+
void DrawImage(Bitmap source, double opacity, Rect sourceRect, Rect destRect);
///
diff --git a/Perspex/Media/Matrix.cs b/Perspex/Media/Matrix.cs
index 47fbcf72c8..14d302a0bf 100644
--- a/Perspex/Media/Matrix.cs
+++ b/Perspex/Media/Matrix.cs
@@ -83,16 +83,49 @@ namespace Perspex.Media
get { return this.offsetY; }
}
+ public static Matrix operator *(Matrix left, Matrix right)
+ {
+ return new Matrix(
+ (left.M11 * right.M11) + (left.M12 * right.M21),
+ (left.M11 * right.M12) + (left.M12 * right.M22),
+ (left.M21 * right.M11) + (left.M22 * right.M21),
+ (left.M21 * right.M12) + (left.M22 * right.M22),
+ (left.offsetX * right.M11) + (left.offsetY * right.M21) + right.offsetX,
+ (left.offsetX * right.M12) + (left.offsetY * right.M22) + right.offsetY);
+ }
+
public static bool Equals(Matrix matrix1, Matrix matrix2)
{
return matrix1.Equals(matrix2);
}
+ public static Matrix Rotation(double angle)
+ {
+ double cos = Math.Cos(angle);
+ double sin = Math.Sin(angle);
+ return new Matrix(cos, sin, -sin, cos, 0, 0);
+ }
+
+ public static Matrix Translation(Vector v)
+ {
+ return Translation(v.X, v.Y);
+ }
+
public static Matrix Translation(double x, double y)
{
return new Matrix(1.0, 0.0, 0.0, 1.0, x, y);
}
+ public static double ToRadians(double angle)
+ {
+ return angle * 0.0174532925;
+ }
+
+ public static Matrix operator -(Matrix matrix)
+ {
+ return matrix.Invert();
+ }
+
public static bool operator ==(Matrix matrix1, Matrix matrix2)
{
return matrix1.Equals(matrix2);
@@ -127,5 +160,23 @@ namespace Perspex.Media
{
throw new NotImplementedException();
}
+
+ public Matrix Invert()
+ {
+ if (!this.HasInverse)
+ {
+ throw new InvalidOperationException("Transform is not invertible.");
+ }
+
+ double d = this.Determinant;
+
+ return new Matrix(
+ this.m22 / d,
+ -this.m12 / d,
+ -this.m21 / d,
+ this.m11 / d,
+ ((this.m21 * this.offsetY) - (this.m22 * this.offsetX)) / d,
+ ((this.m12 * this.offsetX) - (this.m11 * this.offsetY)) / d);
+ }
}
}
\ No newline at end of file
diff --git a/Perspex/Media/RotateTransform.cs b/Perspex/Media/RotateTransform.cs
new file mode 100644
index 0000000000..67fe38b7b8
--- /dev/null
+++ b/Perspex/Media/RotateTransform.cs
@@ -0,0 +1,34 @@
+// -----------------------------------------------------------------------
+//
+// Copyright 2013 MIT Licence. See licence.md for more information.
+//
+// -----------------------------------------------------------------------
+
+namespace Perspex.Media
+{
+ public class RotateTransform : Transform
+ {
+ public static readonly PerspexProperty AngleProperty =
+ PerspexProperty.Register("Angle");
+
+ public RotateTransform()
+ {
+ }
+
+ public RotateTransform(double angle)
+ {
+ this.Angle = angle;
+ }
+
+ public double Angle
+ {
+ get { return this.GetValue(AngleProperty); }
+ set { this.SetValue(AngleProperty, value); }
+ }
+
+ public override Matrix Value
+ {
+ get { return Matrix.Rotation(Matrix.ToRadians(this.Angle)); }
+ }
+ }
+}
diff --git a/Perspex/Media/Transform.cs b/Perspex/Media/Transform.cs
new file mode 100644
index 0000000000..7608b069b4
--- /dev/null
+++ b/Perspex/Media/Transform.cs
@@ -0,0 +1,13 @@
+// -----------------------------------------------------------------------
+//
+// Copyright 2014 MIT Licence. See licence.md for more information.
+//
+// -----------------------------------------------------------------------
+
+namespace Perspex.Media
+{
+ public abstract class Transform : PerspexObject
+ {
+ public abstract Matrix Value { get; }
+ }
+}
diff --git a/Perspex/Origin.cs b/Perspex/Origin.cs
new file mode 100644
index 0000000000..b4de4562fb
--- /dev/null
+++ b/Perspex/Origin.cs
@@ -0,0 +1,54 @@
+// -----------------------------------------------------------------------
+//
+// Copyright 2014 MIT Licence. See licence.md for more information.
+//
+// -----------------------------------------------------------------------
+
+namespace Perspex
+{
+ using System;
+ using System.Globalization;
+
+ public enum OriginUnit
+ {
+ Percent,
+ Pixels,
+ }
+
+ public struct Origin
+ {
+ public static readonly Origin Default = new Origin(0.5, 0.5, OriginUnit.Percent);
+
+ private Point point;
+
+ private OriginUnit unit;
+
+ public Origin(double x, double y, OriginUnit unit)
+ : this(new Point(x, y), unit)
+ {
+ }
+
+ public Origin(Point point, OriginUnit unit)
+ {
+ this.point = point;
+ this.unit = unit;
+ }
+
+ public Point Point
+ {
+ get { return this.point; }
+ }
+
+ public OriginUnit Unit
+ {
+ get { return this.unit; }
+ }
+
+ public Point ToPixels(Size size)
+ {
+ return this.unit == OriginUnit.Pixels ?
+ point :
+ new Point(point.X * size.Width, point.Y * size.Height);
+ }
+ }
+}
diff --git a/Perspex/Perspex.csproj b/Perspex/Perspex.csproj
index 831f718420..0f06f881dc 100644
--- a/Perspex/Perspex.csproj
+++ b/Perspex/Perspex.csproj
@@ -128,6 +128,8 @@
+
+
@@ -141,6 +143,7 @@
+
diff --git a/Perspex/Point.cs b/Perspex/Point.cs
index f5a83cdf76..8e000e9d34 100644
--- a/Perspex/Point.cs
+++ b/Perspex/Point.cs
@@ -60,6 +60,11 @@ namespace Perspex
return new Point(a.x - b.x, a.y - b.y);
}
+ public static implicit operator Vector(Point p)
+ {
+ return new Vector(p.x, p.y);
+ }
+
///
/// Returns the string representation of the point.
///
diff --git a/Perspex/Themes/Default/TreeViewItemStyle.cs b/Perspex/Themes/Default/TreeViewItemStyle.cs
index 7e361eb750..51143f6e45 100644
--- a/Perspex/Themes/Default/TreeViewItemStyle.cs
+++ b/Perspex/Themes/Default/TreeViewItemStyle.cs
@@ -48,6 +48,13 @@ namespace Perspex.Themes.Default
new Setter(ToggleButton.TemplateProperty, ControlTemplate.Create(this.ToggleButtonTemplate)),
},
},
+ new Style(x => x.OfType().Template().OfType().Class("expander").Class(":checked"))
+ {
+ Setters = new[]
+ {
+ new Setter(ToggleButton.RenderTransformProperty, new RotateTransform(1)),
+ },
+ },
new Style(x => x.OfType().Class(":empty").Template().OfType().Class("expander"))
{
Setters = new[]
@@ -105,6 +112,7 @@ namespace Perspex.Themes.Default
{
return new Border
{
+ Background = Brushes.Chartreuse,
Content = new Path
{
Fill = Brushes.Black,
diff --git a/Perspex/Vector.cs b/Perspex/Vector.cs
index 279f8004a4..7222ef7c51 100644
--- a/Perspex/Vector.cs
+++ b/Perspex/Vector.cs
@@ -50,6 +50,11 @@ namespace Perspex
get { return this.y; }
}
+ public static Vector operator -(Vector a)
+ {
+ return new Vector(-a.x, -a.y);
+ }
+
public static Vector operator +(Vector a, Vector b)
{
return new Vector(a.x + b.x, a.y + b.y);
diff --git a/Perspex/Visual.cs b/Perspex/Visual.cs
index d693de7c33..430e83baae 100644
--- a/Perspex/Visual.cs
+++ b/Perspex/Visual.cs
@@ -22,6 +22,12 @@ namespace Perspex
public static readonly PerspexProperty OpacityProperty =
PerspexProperty.Register("Opacity", 1);
+ public static readonly PerspexProperty RenderTransformProperty =
+ PerspexProperty.Register("RenderTransform");
+
+ public static readonly PerspexProperty TransformOriginProperty =
+ PerspexProperty.Register("TransformOrigin", defaultValue: Origin.Default);
+
private IVisual visualParent;
private Rect bounds;
@@ -43,6 +49,18 @@ namespace Perspex
set { this.SetValue(OpacityProperty, value); }
}
+ public Transform RenderTransform
+ {
+ get { return this.GetValue(RenderTransformProperty); }
+ set { this.SetValue(RenderTransformProperty, value); }
+ }
+
+ public Origin TransformOrigin
+ {
+ get { return this.GetValue(TransformOriginProperty); }
+ set { this.SetValue(TransformOriginProperty, value); }
+ }
+
Rect IVisual.Bounds
{
get { return this.bounds; }