diff --git a/Cairo/Perspex.Cairo/CairoExtensions.cs b/Cairo/Perspex.Cairo/CairoExtensions.cs index 3bbf4c04b6..364e330c09 100644 --- a/Cairo/Perspex.Cairo/CairoExtensions.cs +++ b/Cairo/Perspex.Cairo/CairoExtensions.cs @@ -37,24 +37,51 @@ namespace Perspex.Cairo public static Pango.Weight ToCairo(this Perspex.Media.FontWeight weight) { if (weight == Perspex.Media.FontWeight.Light) + { return Pango.Weight.Light; + } if (weight == Perspex.Media.FontWeight.Normal || weight == Perspex.Media.FontWeight.Regular) + { return Pango.Weight.Normal; + } if (weight == Perspex.Media.FontWeight.DemiBold || weight == Perspex.Media.FontWeight.Medium) + { return Pango.Weight.Semibold; + } if (weight == Perspex.Media.FontWeight.Bold) + { return Pango.Weight.Bold; + } if (weight == Perspex.Media.FontWeight.UltraBold || weight == Perspex.Media.FontWeight.ExtraBold) + { return Pango.Weight.Ultrabold; + } if (weight == Perspex.Media.FontWeight.Black || weight == Perspex.Media.FontWeight.Heavy || weight == Perspex.Media.FontWeight.UltraBlack) + { return Pango.Weight.Heavy; + } return Pango.Weight.Ultralight; } + + public static Pango.Alignment ToCairo(this Perspex.Media.TextAlignment alignment) + { + if (alignment == Perspex.Media.TextAlignment.Left) + { + return Pango.Alignment.Left; + } + + if (alignment == Perspex.Media.TextAlignment.Center) + { + return Pango.Alignment.Center; + } + + return Pango.Alignment.Right; + } } } \ No newline at end of file diff --git a/Cairo/Perspex.Cairo/CairoPlatform.cs b/Cairo/Perspex.Cairo/CairoPlatform.cs index f1841fe31a..827014afb4 100644 --- a/Cairo/Perspex.Cairo/CairoPlatform.cs +++ b/Cairo/Perspex.Cairo/CairoPlatform.cs @@ -35,9 +35,10 @@ namespace Perspex.Cairo string fontFamily, double fontSize, FontStyle fontStyle, + TextAlignment textAlignment, Perspex.Media.FontWeight fontWeight) { - return new FormattedTextImpl(text, fontFamily, fontSize, fontStyle, fontWeight); + return new FormattedTextImpl(text, fontFamily, fontSize, fontStyle, textAlignment, fontWeight); } public IRenderer CreateRenderer(IPlatformHandle handle, double width, double height) diff --git a/Cairo/Perspex.Cairo/Media/DrawingContext.cs b/Cairo/Perspex.Cairo/Media/DrawingContext.cs index e3929f1e69..d7a18ace50 100644 --- a/Cairo/Perspex.Cairo/Media/DrawingContext.cs +++ b/Cairo/Perspex.Cairo/Media/DrawingContext.cs @@ -118,53 +118,57 @@ namespace Perspex.Cairo.Media { var impl = geometry.PlatformImpl as StreamGeometryImpl; var clone = new Queue(impl.Operations); - bool useFill = false; - - var pop = this.PushTransform(impl.Transform); + bool useFill = false; + this.SetPen(pen); + this.SetBrush(brush); - while (clone.Count > 0) + using (var pop = this.PushTransform(impl.Transform)) { + while (clone.Count > 0) + { - var current = clone.Dequeue(); + var current = clone.Dequeue(); - if (current is BeginOp) - { - var bo = current as BeginOp; - this.context.MoveTo(bo.Point.ToCairo()); + if (current is BeginOp) + { + var bo = current as BeginOp; + this.context.MoveTo(bo.Point.ToCairo()); - useFill = bo.IsFilled; - } - else if (current is LineToOp) - { - var lto = current as LineToOp; - this.context.LineTo(lto.Point.ToCairo()); + useFill = bo.IsFilled; + + System.Diagnostics.Debug.WriteLine("Start"); + } + 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(); + + System.Diagnostics.Debug.WriteLine("End"); + } + else if (current is CurveToOp) + { + var cto = current as CurveToOp; + this.context.CurveTo(cto.Point.ToCairo(), cto.Point2.ToCairo(), cto.Point3.ToCairo()); + } } - else if (current is EndOp) + + System.Diagnostics.Debug.WriteLine("Render"); + if (useFill) { - if (((EndOp)current).IsClosed) - this.context.ClosePath(); + this.context.FillPreserve(); } - else if (current is CurveToOp) + else { - var cto = current as CurveToOp; - this.context.CurveTo(cto.Point.ToCairo(), cto.Point2.ToCairo(), cto.Point3.ToCairo()); + this.context.StrokePreserve(); } - } - - if (useFill) - { - this.context.StrokePreserve(); - this.context.Fill(); - } - else - { - this.context.Stroke(); - } - - pop.Dispose(); } /// @@ -220,6 +224,17 @@ namespace Perspex.Cairo.Media return Disposable.Create(() => this.context.ResetClip()); } + /// + /// Pushes an opacity value. + /// + /// The opacity. + /// A disposable used to undo the opacity. + public IDisposable PushOpacity(double opacity) + { + // TODO: Implement + return Disposable.Empty; + } + /// /// Pushes a matrix transformation. /// @@ -253,6 +268,7 @@ namespace Perspex.Cairo.Media { this.SetBrush(pen.Brush); this.context.LineWidth = pen.Thickness; + } } } diff --git a/Cairo/Perspex.Cairo/Media/FormattedTextImpl.cs b/Cairo/Perspex.Cairo/Media/FormattedTextImpl.cs index a465c9ac71..2925b2e741 100644 --- a/Cairo/Perspex.Cairo/Media/FormattedTextImpl.cs +++ b/Cairo/Perspex.Cairo/Media/FormattedTextImpl.cs @@ -20,6 +20,7 @@ namespace Perspex.Cairo.Media string fontFamily, double fontSize, FontStyle fontStyle, + TextAlignment textAlignment, FontWeight fontWeight) { var context = Locator.Current.GetService(); @@ -32,6 +33,8 @@ namespace Perspex.Cairo.Media Style = (Pango.Style)fontStyle, Weight = fontWeight.ToCairo() }; + + this.Layout.Alignment = textAlignment.ToCairo(); } public Size Constraint @@ -98,6 +101,12 @@ namespace Perspex.Cairo.Media int width; int height; this.Layout.GetPixelSize(out width, out height); + + if (this.Layout.Alignment == Pango.Alignment.Right) + { + return new Size(width, height); + } + return new Size(width, height); } diff --git a/Cairo/Perspex.Cairo/Media/Imaging/RenderTargetBitmapImpl.cs b/Cairo/Perspex.Cairo/Media/Imaging/RenderTargetBitmapImpl.cs index c0ccc372ab..68404cbf03 100644 --- a/Cairo/Perspex.Cairo/Media/Imaging/RenderTargetBitmapImpl.cs +++ b/Cairo/Perspex.Cairo/Media/Imaging/RenderTargetBitmapImpl.cs @@ -12,7 +12,6 @@ namespace Perspex.Cairo.Media.Imaging public class RenderTargetBitmapImpl : BitmapImpl, IRenderTargetBitmapImpl { - public RenderTargetBitmapImpl( Cairo.ImageSurface surface) : base(surface) diff --git a/Cairo/Perspex.Cairo/Media/StreamGeometryContextImpl.cs b/Cairo/Perspex.Cairo/Media/StreamGeometryContextImpl.cs index 1d53ef6ec4..8e629283fa 100644 --- a/Cairo/Perspex.Cairo/Media/StreamGeometryContextImpl.cs +++ b/Cairo/Perspex.Cairo/Media/StreamGeometryContextImpl.cs @@ -24,21 +24,17 @@ namespace Perspex.Cairo.Media public void ArcTo(Point point, Size size, double rotationAngle, bool isLargeArc, SweepDirection sweepDirection) { - // TODO: Implement - int i = 10; points.Add(point); } public void BeginFigure(Point startPoint, bool isFilled) { - System.Diagnostics.Debug.WriteLine("IS filled {0}", isFilled); this.impl.Operations.Enqueue(new BeginOp { Point = startPoint, IsFilled = isFilled }); points.Add(startPoint); } public void BezierTo(Point point1, Point point2, Point point3) { - // TODO: Implement this.impl.Operations.Enqueue(new CurveToOp { Point = point1, Point2 = point2, Point3 = point3 }); points.Add(point1); points.Add(point2); @@ -63,6 +59,7 @@ namespace Perspex.Cairo.Media maxX = System.Math.Max(p.X, maxX); maxY = System.Math.Max(p.Y, maxY); } + var context = new Cairo.Context(new Cairo.ImageSurface(Cairo.Format.Argb32, (int)maxX, (int)maxY)); var clone = new Queue(this.impl.Operations); @@ -95,6 +92,7 @@ namespace Perspex.Cairo.Media } context.StrokePreserve(); + context.FillPreserve(); } var test = context.StrokeExtents(); diff --git a/Cairo/Perspex.Cairo/Perspex.Cairo.csproj b/Cairo/Perspex.Cairo/Perspex.Cairo.csproj index eaad49e49d..042d9e370f 100644 --- a/Cairo/Perspex.Cairo/Perspex.Cairo.csproj +++ b/Cairo/Perspex.Cairo/Perspex.Cairo.csproj @@ -11,7 +11,8 @@ Perspex.Cairo v4.5 512 - b122e795 + + true @@ -43,9 +44,9 @@ gtk-sharp-2.0 - - False - ..\..\packages\Splat.1.6.1\lib\Net45\Splat.dll + + ..\..\packages\Splat.1.6.2\lib\Net45\Splat.dll + True @@ -95,13 +96,6 @@ - - - - This project references NuGet package(s) that are missing on this computer. Enable NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}. - - - + \ No newline at end of file diff --git a/Tests/Perspex.Direct2D1.UnitTests/Properties/AssemblyInfo.cs b/Tests/Perspex.Direct2D1.UnitTests/Properties/AssemblyInfo.cs new file mode 100644 index 0000000000..4b45a17ce2 --- /dev/null +++ b/Tests/Perspex.Direct2D1.UnitTests/Properties/AssemblyInfo.cs @@ -0,0 +1,36 @@ +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyTitle("Perspex.Direct2D1.UnitTests")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("Perspex.Direct2D1.UnitTests")] +[assembly: AssemblyCopyright("Copyright © 2015")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +// Setting ComVisible to false makes the types in this assembly not visible +// to COM components. If you need to access a type in this assembly from +// COM, set the ComVisible attribute to true on that type. +[assembly: ComVisible(false)] + +// The following GUID is for the ID of the typelib if this project is exposed to COM +[assembly: Guid("efb11458-9cdf-41c0-be4f-44af45a4cab8")] + +// Version information for an assembly consists of the following four values: +// +// Major Version +// Minor Version +// Build Number +// Revision +// +// You can specify all the values or you can default the Build and Revision Numbers +// by using the '*' as shown below: +// [assembly: AssemblyVersion("1.0.*")] +[assembly: AssemblyVersion("1.0.0.0")] +[assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/Tests/Perspex.Direct2D1.UnitTests/RectComparer.cs b/Tests/Perspex.Direct2D1.UnitTests/RectComparer.cs new file mode 100644 index 0000000000..6f8b62f4fa --- /dev/null +++ b/Tests/Perspex.Direct2D1.UnitTests/RectComparer.cs @@ -0,0 +1,24 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Perspex.Direct2D1.UnitTests +{ + public class RectComparer : IEqualityComparer + { + public bool Equals(Rect a, Rect b) + { + return Math.Round(a.X, 3) == Math.Round(b.X, 3) && + Math.Round(a.Y, 3) == Math.Round(b.Y, 3) && + Math.Round(a.Width, 3) == Math.Round(b.Width, 3) && + Math.Round(a.Height, 3) == Math.Round(b.Height, 3); + } + + public int GetHashCode(Rect obj) + { + throw new NotImplementedException(); + } + } +} diff --git a/Tests/Perspex.Direct2D1.UnitTests/packages.config b/Tests/Perspex.Direct2D1.UnitTests/packages.config new file mode 100644 index 0000000000..5f18eeea70 --- /dev/null +++ b/Tests/Perspex.Direct2D1.UnitTests/packages.config @@ -0,0 +1,11 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/Tests/Perspex.Input.UnitTests/KeyboardNavigationTests.cs b/Tests/Perspex.Input.UnitTests/KeyboardNavigationTests.cs new file mode 100644 index 0000000000..f7373454fc --- /dev/null +++ b/Tests/Perspex.Input.UnitTests/KeyboardNavigationTests.cs @@ -0,0 +1,834 @@ +// ----------------------------------------------------------------------- +// +// Copyright 2015 MIT Licence. See licence.md for more information. +// +// ----------------------------------------------------------------------- + +namespace Perspex.Input.UnitTests +{ + using Perspex.Controls; + using Xunit; + + public class KeyboardNavigationTests + { + [Fact] + public void GetNextInTabOrder_Continue_Returns_Next_Control_In_Container() + { + StackPanel container; + Button current; + Button next; + + var top = new StackPanel + { + Children = new Controls + { + (container = new StackPanel + { + Children = new Controls + { + new Button { Name = "Button1" }, + (current = new Button { Name = "Button2" }), + (next = new Button { Name = "Button3" }), + } + }), + new StackPanel + { + Children = new Controls + { + new Button { Name = "Button4" }, + new Button { Name = "Button5" }, + new Button { Name = "Button6" }, + } + }, + } + }; + + var result = KeyboardNavigationHandler.GetNextInTabOrder(current); + + Assert.Equal(next, result); + } + + [Fact] + public void GetNextInTabOrder_Continue_Returns_First_Control_In_Next_Sibling_Container() + { + StackPanel container; + Button current; + Button next; + + var top = new StackPanel + { + Children = new Controls + { + (container = new StackPanel + { + Children = new Controls + { + new Button { Name = "Button1" }, + new Button { Name = "Button2" }, + (current = new Button { Name = "Button3" }), + } + }), + new StackPanel + { + Children = new Controls + { + (next = new Button { Name = "Button4" }), + new Button { Name = "Button5" }, + new Button { Name = "Button6" }, + } + }, + } + }; + + var result = KeyboardNavigationHandler.GetNextInTabOrder(current); + + Assert.Equal(next, result); + } + + [Fact] + public void GetNextInTabOrder_Continue_Returns_Next_Sibling() + { + StackPanel container; + Button current; + Button next; + + var top = new StackPanel + { + Children = new Controls + { + (container = new StackPanel + { + Children = new Controls + { + new Button { Name = "Button1" }, + new Button { Name = "Button2" }, + (current = new Button { Name = "Button3" }), + } + }), + (next = new Button { Name = "Button4" }), + } + }; + + var result = KeyboardNavigationHandler.GetNextInTabOrder(current); + + Assert.Equal(next, result); + } + + [Fact] + public void GetNextInTabOrder_Continue_Returns_First_Control_In_Next_Uncle_Container() + { + StackPanel container; + Button current; + Button next; + + var top = new StackPanel + { + Children = new Controls + { + new StackPanel + { + Children = new Controls + { + (container = new StackPanel + { + Children = new Controls + { + new Button { Name = "Button1" }, + new Button { Name = "Button2" }, + (current = new Button { Name = "Button3" }), + } + }), + }, + }, + new StackPanel + { + Children = new Controls + { + (next = new Button { Name = "Button4" }), + new Button { Name = "Button5" }, + new Button { Name = "Button6" }, + } + }, + } + }; + + var result = KeyboardNavigationHandler.GetNextInTabOrder(current); + + Assert.Equal(next, result); + } + + [Fact] + public void GetNextInTabOrder_Continue_Returns_Child_Of_Top_Level() + { + Button next; + + var top = new StackPanel + { + Children = new Controls + { + (next = new Button { Name = "Button1" }), + } + }; + + var result = KeyboardNavigationHandler.GetNextInTabOrder(top); + + Assert.Equal(next, result); + } + + [Fact] + public void GetNextInTabOrder_Continue_Wraps() + { + StackPanel container; + Button current; + Button next; + + var top = new StackPanel + { + Children = new Controls + { + new StackPanel + { + Children = new Controls + { + (container = new StackPanel + { + Children = new Controls + { + (next = new Button { Name = "Button1" }), + new Button { Name = "Button2" }, + new Button { Name = "Button3" }, + } + }), + }, + }, + new StackPanel + { + Children = new Controls + { + new Button { Name = "Button4" }, + new Button { Name = "Button5" }, + (current = new Button { Name = "Button6" }), + } + }, + } + }; + + var result = KeyboardNavigationHandler.GetNextInTabOrder(current); + + Assert.Equal(next, result); + } + + [Fact] + public void GetNextInTabOrder_Cycle_Returns_Next_Control_In_Container() + { + StackPanel container; + Button current; + Button next; + + var top = new StackPanel + { + Children = new Controls + { + (container = new StackPanel + { + [KeyboardNavigation.TabNavigationProperty] = KeyboardNavigationMode.Cycle, + Children = new Controls + { + new Button { Name = "Button1" }, + (current = new Button { Name = "Button2" }), + (next = new Button { Name = "Button3" }), + } + }), + new StackPanel + { + Children = new Controls + { + new Button { Name = "Button4" }, + new Button { Name = "Button5" }, + new Button { Name = "Button6" }, + } + }, + } + }; + + var result = KeyboardNavigationHandler.GetNextInTabOrder(current); + + Assert.Equal(next, result); + } + + [Fact] + public void GetNextInTabOrder_Cycle_Wraps_To_First() + { + StackPanel container; + Button current; + Button next; + + var top = new StackPanel + { + Children = new Controls + { + (container = new StackPanel + { + [KeyboardNavigation.TabNavigationProperty] = KeyboardNavigationMode.Cycle, + Children = new Controls + { + (next = new Button { Name = "Button1" }), + new Button { Name = "Button2" }, + (current = new Button { Name = "Button3" }), + } + }), + new StackPanel + { + Children = new Controls + { + new Button { Name = "Button4" }, + new Button { Name = "Button5" }, + new Button { Name = "Button6" }, + } + }, + } + }; + + var result = KeyboardNavigationHandler.GetNextInTabOrder(current); + + Assert.Equal(next, result); + } + + [Fact] + public void GetNextInTabOrder_Once_Moves_To_Next_Container() + { + StackPanel container; + Button current; + Button next; + + var top = new StackPanel + { + Children = new Controls + { + (container = new StackPanel + { + [KeyboardNavigation.TabNavigationProperty] = KeyboardNavigationMode.Once, + Children = new Controls + { + new Button { Name = "Button1" }, + (current = new Button { Name = "Button2" }), + new Button { Name = "Button3" }, + } + }), + new StackPanel + { + Children = new Controls + { + (next = new Button { Name = "Button4" }), + new Button { Name = "Button5" }, + new Button { Name = "Button6" }, + } + }, + } + }; + + var result = KeyboardNavigationHandler.GetNextInTabOrder(current); + + Assert.Equal(next, result); + } + + [Fact] + public void GetNextInTabOrder_Once_Moves_To_Active_Element() + { + StackPanel container; + Button current; + Button next; + + var top = new StackPanel + { + Children = new Controls + { + (container = new StackPanel + { + [KeyboardNavigation.TabNavigationProperty] = KeyboardNavigationMode.Once, + Children = new Controls + { + new Button { Name = "Button1" }, + (next = new Button { Name = "Button2" }), + new Button { Name = "Button3" }, + } + }), + new StackPanel + { + Children = new Controls + { + new Button { Name = "Button4" }, + new Button { Name = "Button5" }, + (current = new Button { Name = "Button6" }), + } + }, + } + }; + + KeyboardNavigation.SetTabOnceActiveElement(container, next); + + var result = KeyboardNavigationHandler.GetNextInTabOrder(current); + + Assert.Equal(next, result); + } + + [Fact] + public void GetNextInTabOrder_Never_Moves_To_Next_Container() + { + StackPanel container; + Button current; + Button next; + + var top = new StackPanel + { + Children = new Controls + { + (container = new StackPanel + { + [KeyboardNavigation.TabNavigationProperty] = KeyboardNavigationMode.Never, + Children = new Controls + { + new Button { Name = "Button1" }, + (current = new Button { Name = "Button2" }), + new Button { Name = "Button3" }, + } + }), + new StackPanel + { + Children = new Controls + { + (next = new Button { Name = "Button4" }), + new Button { Name = "Button5" }, + new Button { Name = "Button6" }, + } + }, + } + }; + + var result = KeyboardNavigationHandler.GetNextInTabOrder(current); + + Assert.Equal(next, result); + } + + [Fact] + public void GetNextInTabOrder_Never_Skips_Container() + { + StackPanel container; + Button current; + Button next; + + var top = new StackPanel + { + Children = new Controls + { + (container = new StackPanel + { + [KeyboardNavigation.TabNavigationProperty] = KeyboardNavigationMode.Never, + Children = new Controls + { + new Button { Name = "Button1" }, + new Button { Name = "Button2" }, + new Button { Name = "Button3" }, + } + }), + new StackPanel + { + Children = new Controls + { + (next = new Button { Name = "Button4" }), + new Button { Name = "Button5" }, + (current = new Button { Name = "Button6" }), + } + }, + } + }; + + KeyboardNavigation.SetTabOnceActiveElement(container, next); + + var result = KeyboardNavigationHandler.GetNextInTabOrder(current); + + Assert.Equal(next, result); + } + + [Fact] + public void GetPreviousInTabOrder_Continue_Returns_Previous_Control_In_Container() + { + StackPanel container; + Button current; + Button next; + + var top = new StackPanel + { + Children = new Controls + { + (container = new StackPanel + { + Children = new Controls + { + new Button { Name = "Button1" }, + (next = new Button { Name = "Button2" }), + (current = new Button { Name = "Button3" }), + } + }), + new StackPanel + { + Children = new Controls + { + new Button { Name = "Button4" }, + new Button { Name = "Button5" }, + new Button { Name = "Button6" }, + } + }, + } + }; + + var result = KeyboardNavigationHandler.GetPreviousInTabOrder(current); + + Assert.Equal(next, result); + } + + [Fact] + public void GetPreviousInTabOrder_Continue_Returns_Last_Control_In_Previous_Sibling_Container() + { + StackPanel container; + Button current; + Button next; + + var top = new StackPanel + { + Children = new Controls + { + (container = new StackPanel + { + Children = new Controls + { + new Button { Name = "Button1" }, + new Button { Name = "Button2" }, + (next = new Button { Name = "Button3" }), + } + }), + new StackPanel + { + Children = new Controls + { + (current = new Button { Name = "Button4" }), + new Button { Name = "Button5" }, + new Button { Name = "Button6" }, + } + }, + } + }; + + var result = KeyboardNavigationHandler.GetPreviousInTabOrder(current); + + Assert.Equal(next, result); + } + + [Fact] + public void GetPreviousInTabOrder_Continue_Returns_Last_Child_Of_Sibling() + { + StackPanel container; + Button current; + Button next; + + var top = new StackPanel + { + Children = new Controls + { + (container = new StackPanel + { + Children = new Controls + { + new Button { Name = "Button1" }, + new Button { Name = "Button2" }, + (next = new Button { Name = "Button3" }), + } + }), + (current = new Button { Name = "Button4" }), + } + }; + + var result = KeyboardNavigationHandler.GetPreviousInTabOrder(current); + + Assert.Equal(next, result); + } + + [Fact] + public void GetPreviousInTabOrder_Continue_Returns_Last_Control_In_Previous_Nephew_Container() + { + StackPanel container; + Button current; + Button next; + + var top = new StackPanel + { + Children = new Controls + { + new StackPanel + { + Children = new Controls + { + (container = new StackPanel + { + Children = new Controls + { + new Button { Name = "Button1" }, + new Button { Name = "Button2" }, + (next = new Button { Name = "Button3" }), + } + }), + }, + }, + new StackPanel + { + Children = new Controls + { + (current = new Button { Name = "Button4" }), + new Button { Name = "Button5" }, + new Button { Name = "Button6" }, + } + }, + } + }; + + var result = KeyboardNavigationHandler.GetPreviousInTabOrder(current); + + Assert.Equal(next, result); + } + + [Fact] + public void GetPreviousInTabOrder_Continue_Wraps() + { + StackPanel container; + Button current; + Button next; + + var top = new StackPanel + { + Children = new Controls + { + new StackPanel + { + Children = new Controls + { + (container = new StackPanel + { + Children = new Controls + { + (current = new Button { Name = "Button1" }), + new Button { Name = "Button2" }, + new Button { Name = "Button3" }, + } + }), + }, + }, + new StackPanel + { + Children = new Controls + { + new Button { Name = "Button4" }, + new Button { Name = "Button5" }, + (next = new Button { Name = "Button6" }), + } + }, + } + }; + + var result = KeyboardNavigationHandler.GetPreviousInTabOrder(current); + + Assert.Equal(next, result); + } + + [Fact] + public void GetPreviousInTabOrder_Cycle_Returns_Previous_Control_In_Container() + { + StackPanel container; + Button current; + Button next; + + var top = new StackPanel + { + Children = new Controls + { + (container = new StackPanel + { + [KeyboardNavigation.TabNavigationProperty] = KeyboardNavigationMode.Cycle, + Children = new Controls + { + (next = new Button { Name = "Button1" }), + (current = new Button { Name = "Button2" }), + new Button { Name = "Button3" }, + } + }), + new StackPanel + { + Children = new Controls + { + new Button { Name = "Button4" }, + new Button { Name = "Button5" }, + new Button { Name = "Button6" }, + } + }, + } + }; + + var result = KeyboardNavigationHandler.GetPreviousInTabOrder(current); + + Assert.Equal(next, result); + } + + [Fact] + public void GetPreviousInTabOrder_Cycle_Wraps_To_Last() + { + StackPanel container; + Button current; + Button next; + + var top = new StackPanel + { + Children = new Controls + { + (container = new StackPanel + { + [KeyboardNavigation.TabNavigationProperty] = KeyboardNavigationMode.Cycle, + Children = new Controls + { + (current = new Button { Name = "Button1" }), + new Button { Name = "Button2" }, + (next = new Button { Name = "Button3" }), + } + }), + new StackPanel + { + Children = new Controls + { + new Button { Name = "Button4" }, + new Button { Name = "Button5" }, + new Button { Name = "Button6" }, + } + }, + } + }; + + var result = KeyboardNavigationHandler.GetPreviousInTabOrder(current); + + Assert.Equal(next, result); + } + + [Fact] + public void GetPreviousInTabOrder_Once_Moves_To_Previous_Container() + { + StackPanel container; + Button current; + Button next; + + var top = new StackPanel + { + Children = new Controls + { + (container = new StackPanel + { + Children = new Controls + { + new Button { Name = "Button1" }, + new Button { Name = "Button2" }, + (next = new Button { Name = "Button3" }), + } + }), + new StackPanel + { + [KeyboardNavigation.TabNavigationProperty] = KeyboardNavigationMode.Once, + Children = new Controls + { + new Button { Name = "Button4" }, + (current = new Button { Name = "Button5" }), + new Button { Name = "Button6" }, + } + }, + } + }; + + var result = KeyboardNavigationHandler.GetPreviousInTabOrder(current); + + Assert.Equal(next, result); + } + + [Fact] + public void GetPreviousInTabOrder_Once_Moves_To_Active_Element() + { + StackPanel container; + Button current; + Button next; + + var top = new StackPanel + { + Children = new Controls + { + (container = new StackPanel + { + [KeyboardNavigation.TabNavigationProperty] = KeyboardNavigationMode.Once, + Children = new Controls + { + new Button { Name = "Button1" }, + (next = new Button { Name = "Button2" }), + new Button { Name = "Button3" }, + } + }), + new StackPanel + { + Children = new Controls + { + (current = new Button { Name = "Button4" }), + new Button { Name = "Button5" }, + new Button { Name = "Button6" }, + } + }, + } + }; + + KeyboardNavigation.SetTabOnceActiveElement(container, next); + + var result = KeyboardNavigationHandler.GetPreviousInTabOrder(current); + + Assert.Equal(next, result); + } + + [Fact] + public void GetPreviousInTabOrder_Once_Moves_To_First_Element() + { + StackPanel container; + Button current; + Button next; + + var top = new StackPanel + { + Children = new Controls + { + (container = new StackPanel + { + [KeyboardNavigation.TabNavigationProperty] = KeyboardNavigationMode.Once, + Children = new Controls + { + (next = new Button { Name = "Button1" }), + new Button { Name = "Button2" }, + new Button { Name = "Button3" }, + } + }), + new StackPanel + { + Children = new Controls + { + (current = new Button { Name = "Button4" }), + new Button { Name = "Button5" }, + new Button { Name = "Button6" }, + } + }, + } + }; + + var result = KeyboardNavigationHandler.GetPreviousInTabOrder(current); + + Assert.Equal(next, result); + } + } +} diff --git a/Tests/Perspex.Input.UnitTests/Perspex.Input.UnitTests.csproj b/Tests/Perspex.Input.UnitTests/Perspex.Input.UnitTests.csproj new file mode 100644 index 0000000000..8c33cf739a --- /dev/null +++ b/Tests/Perspex.Input.UnitTests/Perspex.Input.UnitTests.csproj @@ -0,0 +1,129 @@ + + + + + + + + Debug + AnyCPU + {AC18926A-E784-40FE-B09D-BB0FE2B599F0} + Library + Properties + Perspex.Input.UnitTests + Perspex.Input.UnitTests + v4.5 + 512 + 90580c1a + + + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + + + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + + + + ..\..\packages\AutoFixture.3.30.4\lib\net40\Ploeh.AutoFixture.dll + True + + + ..\..\packages\Splat.1.6.2\lib\Net45\Splat.dll + True + + + + + + + + + + ..\..\packages\xunit.abstractions.2.0.0\lib\net35\xunit.abstractions.dll + True + + + ..\..\packages\xunit.assert.2.0.0\lib\portable-net45+win+wpa81+wp80+monotouch+monoandroid+Xamarin.iOS\xunit.assert.dll + True + + + ..\..\packages\xunit.extensibility.core.2.0.0\lib\portable-net45+win+wpa81+wp80+monotouch+monoandroid+Xamarin.iOS\xunit.core.dll + True + + + + + + + + + + + + + {d211e587-d8bc-45b9-95a4-f297c8fa5200} + Perspex.Animation + + + {b09b78d8-9b26-48b0-9149-d64a2f120f3f} + Perspex.Base + + + {d2221c82-4a25-4583-9b43-d791e3f6820c} + Perspex.Controls + + + {62024b2d-53eb-4638-b26b-85eeaa54866e} + Perspex.Input + + + {6b0ed19d-a08b-461c-a9d9-a9ee40b0c06b} + Perspex.Interactivity + + + {42472427-4774-4c81-8aff-9f27b8e31721} + Perspex.Layout + + + {eb582467-6abb-43a1-b052-e981ba910e3a} + Perspex.SceneGraph + + + {f1baa01a-f176-4c6a-b39d-5b40bb1b148f} + Perspex.Styling + + + + + + + + + + + + This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}. + + + + + + + \ No newline at end of file diff --git a/Tests/Perspex.Input.UnitTests/Properties/AssemblyInfo.cs b/Tests/Perspex.Input.UnitTests/Properties/AssemblyInfo.cs new file mode 100644 index 0000000000..0b681f4272 --- /dev/null +++ b/Tests/Perspex.Input.UnitTests/Properties/AssemblyInfo.cs @@ -0,0 +1,36 @@ +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyTitle("Perspex.Input.UnitTests")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("Perspex.Input.UnitTests")] +[assembly: AssemblyCopyright("Copyright © 2015")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +// Setting ComVisible to false makes the types in this assembly not visible +// to COM components. If you need to access a type in this assembly from +// COM, set the ComVisible attribute to true on that type. +[assembly: ComVisible(false)] + +// The following GUID is for the ID of the typelib if this project is exposed to COM +[assembly: Guid("ac18926a-e784-40fe-b09d-bb0fe2b599f0")] + +// Version information for an assembly consists of the following four values: +// +// Major Version +// Minor Version +// Build Number +// Revision +// +// You can specify all the values or you can default the Build and Revision Numbers +// by using the '*' as shown below: +// [assembly: AssemblyVersion("1.0.*")] +[assembly: AssemblyVersion("1.0.0.0")] +[assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/Tests/Perspex.Input.UnitTests/app.config b/Tests/Perspex.Input.UnitTests/app.config new file mode 100644 index 0000000000..133577da84 --- /dev/null +++ b/Tests/Perspex.Input.UnitTests/app.config @@ -0,0 +1,11 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/Tests/Perspex.Input.UnitTests/packages.config b/Tests/Perspex.Input.UnitTests/packages.config new file mode 100644 index 0000000000..78343e06f6 --- /dev/null +++ b/Tests/Perspex.Input.UnitTests/packages.config @@ -0,0 +1,12 @@ + + + + + + + + + + + + \ No newline at end of file diff --git a/Tests/Perspex.Layout.UnitTests/Perspex.Layout.UnitTests.csproj b/Tests/Perspex.Layout.UnitTests/Perspex.Layout.UnitTests.csproj index e84ef66cf9..a0a799b404 100644 --- a/Tests/Perspex.Layout.UnitTests/Perspex.Layout.UnitTests.csproj +++ b/Tests/Perspex.Layout.UnitTests/Perspex.Layout.UnitTests.csproj @@ -1,6 +1,7 @@  - + + Debug AnyCPU @@ -16,7 +17,7 @@ $(ProgramFiles)\Common Files\microsoft shared\VSTT\$(VisualStudioVersion)\UITestExtensionPackages False UnitTest - b0a273ef + 5e71f4dd true @@ -36,23 +37,31 @@ 4 - - False - ..\..\packages\Moq.4.2.1409.1722\lib\net40\Moq.dll + + ..\..\packages\Moq.4.2.1502.0911\lib\net40\Moq.dll + True - - ..\..\packages\AutoFixture.3.21.1\lib\net40\Ploeh.AutoFixture.dll + + ..\..\packages\AutoFixture.3.30.4\lib\net40\Ploeh.AutoFixture.dll + True - - ..\..\packages\AutoFixture.AutoMoq.3.21.1\lib\net40\Ploeh.AutoFixture.AutoMoq.dll + + ..\..\packages\AutoFixture.AutoMoq.3.30.4\lib\net40\Ploeh.AutoFixture.AutoMoq.dll + True - - False - ..\..\packages\Splat.1.6.1\lib\Net45\Splat.dll + + ..\..\packages\Splat.1.6.2\lib\Net45\Splat.dll + True - - ..\..\packages\xunit.1.9.2\lib\net20\xunit.dll + + ..\..\packages\xunit.abstractions.2.0.0\lib\net35\xunit.abstractions.dll + + + ..\..\packages\xunit.assert.2.0.0\lib\portable-net45+win+wpa81+wp80+monotouch+monoandroid+Xamarin.iOS\xunit.assert.dll + + + ..\..\packages\xunit.extensibility.core.2.0.0\lib\portable-net45+win+wpa81+wp80+monotouch+monoandroid+Xamarin.iOS\xunit.core.dll @@ -120,6 +129,9 @@ + + + @@ -144,7 +156,8 @@ This project references NuGet package(s) that are missing on this computer. Enable NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}. - + + - - ..\..\packages\StyleCop.MSBuild.4.7.46.0\tools\StyleCop.targets - - - - Failed to import StyleCop.MSBuild targets from '$(StyleCopMSBuildTargetsFile)'. The StyleCop.MSBuild package was either missing or incomplete when the project was loaded. Ensure that the package is present and then restart the build. If you are using an IDE (e.g. Visual Studio), reload the project before restarting the build. - Failed to import StyleCop.MSBuild targets from '$(StyleCopMSBuildTargetsFile)'. The StyleCop.MSBuild package was either missing or incomplete when the project was loaded (but is now present). To fix this, restart the build. If you are using an IDE (e.g. Visual Studio), reload the project before restarting the build. - Failed to import StyleCop.MSBuild targets from '$(StyleCopMSBuildTargetsFile)'. The StyleCop.MSBuild package was either missing or incomplete when the project was loaded. To fix this, restore the package and then restart the build. If you are using an IDE (e.g. Visual Studio), you may need to reload the project before restarting the build. Note that regular NuGet package restore (during build) does not work with this package because the package needs to be present before the project is loaded. If this is an automated build (e.g. CI server), you may want to ensure that the build process restores the StyleCop.MSBuild package before the project is built. - Failed to import StyleCop.MSBuild targets from '$(StyleCopMSBuildTargetsFile)'. The StyleCop.MSBuild package was either missing or incomplete when the project was loaded (but is now present). To fix this, restart the build. If you are using an IDE (e.g. Visual Studio), reload the project before restarting the build. Note that when using regular NuGet package restore (during build) the package will not be available for the initial build because the package needs to be present before the project is loaded. If package restore executes successfully in the intitial build then the package will be available for subsequent builds. If this is an automated build (e.g. CI server), you may want to ensure that the build process restores the StyleCop.MSBuild package before the initial build. - - - - - - - - - - - - - StyleCopMSBuildTargetsNotFound;$(PrepareForBuildDependsOn) - - - - - This project references NuGet package(s) that are missing on this computer. Enable NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}. - - - \ No newline at end of file diff --git a/Windows/Perspex.Win32/WindowImpl.cs b/Windows/Perspex.Win32/WindowImpl.cs index 25ffd322b4..17a6b95c78 100644 --- a/Windows/Perspex.Win32/WindowImpl.cs +++ b/Windows/Perspex.Win32/WindowImpl.cs @@ -6,6 +6,11 @@ namespace Perspex.Win32 { + using Perspex.Controls; + using Perspex.Input.Raw; + using Perspex.Platform; + using Perspex.Win32.Input; + using Perspex.Win32.Interop; using System; using System.Collections.Generic; using System.ComponentModel; @@ -14,12 +19,6 @@ namespace Perspex.Win32 using System.Reactive.Disposables; using System.Reactive.Linq; using System.Runtime.InteropServices; - using System.Threading.Tasks; - using Perspex.Controls; - using Perspex.Input.Raw; - using Perspex.Platform; - using Perspex.Win32.Input; - using Perspex.Win32.Interop; public class WindowImpl : IWindowImpl { @@ -247,6 +246,7 @@ namespace Perspex.Win32 return IntPtr.Zero; case UnmanagedMethods.WindowsMessage.WM_KEYDOWN: + case UnmanagedMethods.WindowsMessage.WM_SYSKEYDOWN: WindowsKeyboardDevice.Instance.UpdateKeyStates(); e = new RawKeyEventArgs( WindowsKeyboardDevice.Instance, @@ -256,6 +256,17 @@ namespace Perspex.Win32 WindowsKeyboardDevice.Instance.StringFromVirtualKey((uint)wParam)); break; + case UnmanagedMethods.WindowsMessage.WM_KEYUP: + case UnmanagedMethods.WindowsMessage.WM_SYSKEYUP: + WindowsKeyboardDevice.Instance.UpdateKeyStates(); + e = new RawKeyEventArgs( + WindowsKeyboardDevice.Instance, + timestamp, + RawKeyEventType.KeyUp, + KeyInterop.KeyFromVirtualKey((int)wParam), + WindowsKeyboardDevice.Instance.StringFromVirtualKey((uint)wParam)); + break; + case UnmanagedMethods.WindowsMessage.WM_LBUTTONDOWN: e = new RawMouseEventArgs( WindowsMouseDevice.Instance, diff --git a/Windows/Perspex.Win32/packages.config b/Windows/Perspex.Win32/packages.config index a0b6a6eb45..e9739b80a3 100644 --- a/Windows/Perspex.Win32/packages.config +++ b/Windows/Perspex.Win32/packages.config @@ -1,10 +1,10 @@  - - - - - - - + + + + + + + \ No newline at end of file diff --git a/readme.md b/readme.md index 6acb6c17f7..286a03a64a 100644 --- a/readme.md +++ b/readme.md @@ -1,6 +1,8 @@ # Perspex # [![Gitter](https://badges.gitter.im/Join Chat.svg)](https://gitter.im/grokys/Perspex?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) +[![Build status](https://ci.appveyor.com/api/projects/status/hubk3k0w9idyibfg/branch/master?svg=true)](https://ci.appveyor.com/project/grokys/perspex/branch/master) + ...a next generation WPF? ![](Docs/screen.png) @@ -37,10 +39,7 @@ Contributions are always welcome! ## Building and Using In order to build and use Perpex you need a compiler that supports the upcoming C# 6 features. -- **Visual Studio 2015 Preview**: The recommended way to compile C# 6 code is to use the new Visual Studio 2015 Preview version, which Microsoft has released later this year. It comes with the new Roslyn compiler and features like the new upcoming JIT (RyuJIT) and other improvements like extensible code analysis right out of the box. It can be downloaded [here](http://www.visualstudio.com/en-us/downloads/visual-studio-2015-downloads-vs) - -- **Visual Studio 2013**: With the introduction of the new Roslyn compiler platform earlier this year Microsoft has released an *April End User Preview* which is a small extension that brings supports to Visual Studio 2013.
- **NOTE**: This extension is **out of date** and will **no longer be updated**, according to the [Roslyn CodePlex](https://roslyn.codeplex.com/) main page. However, if you don't want to use a Preview IDE feel free to download the extension [over here](https://connect.microsoft.com/VisualStudio/Downloads/DownloadDetails.aspx?DownloadID=52793). It must be noted that it is not guaranteed that future versions of Perspex will compile and run when using this extension. +- **Visual Studio 2015 RC**: The recommended way to compile C# 6 code is to use the Visual Studio 2015 RC.It comes with the new Roslyn compiler and features like the new upcoming JIT (RyuJIT) and other improvements like extensible code analysis right out of the box. It can be downloaded [here](http://www.visualstudio.com/en-us/downloads/visual-studio-2015-downloads-vs) ## Contributing ##