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; }