From 65830195765fe812eba7dbbb050c7d08a3ef9979 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Wies=C5=82aw=20=C5=A0olt=C3=A9s?= Date: Thu, 5 Nov 2015 18:39:49 +0100 Subject: [PATCH 001/211] Renamed BezierTo and QuadTo method names --- src/Gtk/Perspex.Cairo/Media/StreamGeometryContextImpl.cs | 6 +++--- src/Perspex.SceneGraph/Media/EllipseGeometry.cs | 8 ++++---- src/Perspex.SceneGraph/Media/PathMarkupParser.cs | 2 +- src/Perspex.SceneGraph/Media/StreamGeometryContext.cs | 8 ++++---- .../Platform/IStreamGeometryContextImpl.cs | 4 ++-- src/Shared/RenderHelpers/ArcToHelper.cs | 4 ++-- src/Shared/RenderHelpers/QuadBezierHelper.cs | 4 ++-- .../Perspex.Direct2D1/Media/StreamGeometryContextImpl.cs | 4 ++-- 8 files changed, 20 insertions(+), 20 deletions(-) diff --git a/src/Gtk/Perspex.Cairo/Media/StreamGeometryContextImpl.cs b/src/Gtk/Perspex.Cairo/Media/StreamGeometryContextImpl.cs index 5e3e279deb..2c5cce2653 100644 --- a/src/Gtk/Perspex.Cairo/Media/StreamGeometryContextImpl.cs +++ b/src/Gtk/Perspex.Cairo/Media/StreamGeometryContextImpl.cs @@ -42,7 +42,7 @@ namespace Perspex.Cairo.Media } } - public void BezierTo(Point point1, Point point2, Point point3) + public void CubicBezierTo(Point point1, Point point2, Point point3) { if (this.Path == null) { @@ -51,11 +51,11 @@ namespace Perspex.Cairo.Media } } - public void QuadTo(Point control, Point endPoint) + public void QuadraticBezierTo(Point control, Point endPoint) { if (this.Path == null) { - QuadBezierHelper.QuadTo(this, _currentPoint, control, endPoint); + QuadBezierHelper.QuadraticBezierTo(this, _currentPoint, control, endPoint); _currentPoint = endPoint; } } diff --git a/src/Perspex.SceneGraph/Media/EllipseGeometry.cs b/src/Perspex.SceneGraph/Media/EllipseGeometry.cs index d8887ac035..3d6ae38909 100644 --- a/src/Perspex.SceneGraph/Media/EllipseGeometry.cs +++ b/src/Perspex.SceneGraph/Media/EllipseGeometry.cs @@ -39,10 +39,10 @@ namespace Perspex.Media var y4 = center.Y + radius.Y; ctx.BeginFigure(new Point(x2, y0), true); - ctx.BezierTo(new Point(x3, y0), new Point(x4, y1), new Point(x4, y2)); - ctx.BezierTo(new Point(x4, y3), new Point(x3, y4), new Point(x2, y4)); - ctx.BezierTo(new Point(x1, y4), new Point(x0, y3), new Point(x0, y2)); - ctx.BezierTo(new Point(x0, y1), new Point(x1, y0), new Point(x2, y0)); + ctx.CubicBezierTo(new Point(x3, y0), new Point(x4, y1), new Point(x4, y2)); + ctx.CubicBezierTo(new Point(x4, y3), new Point(x3, y4), new Point(x2, y4)); + ctx.CubicBezierTo(new Point(x1, y4), new Point(x0, y3), new Point(x0, y2)); + ctx.CubicBezierTo(new Point(x0, y1), new Point(x1, y0), new Point(x2, y0)); ctx.EndFigure(true); } diff --git a/src/Perspex.SceneGraph/Media/PathMarkupParser.cs b/src/Perspex.SceneGraph/Media/PathMarkupParser.cs index e02f65caba..b8b7d592e7 100644 --- a/src/Perspex.SceneGraph/Media/PathMarkupParser.cs +++ b/src/Perspex.SceneGraph/Media/PathMarkupParser.cs @@ -146,7 +146,7 @@ namespace Perspex.Media Point point1 = ReadPoint(reader); Point point2 = ReadPoint(reader); point = ReadPoint(reader); - _context.BezierTo(point1, point2, point); + _context.CubicBezierTo(point1, point2, point); break; } diff --git a/src/Perspex.SceneGraph/Media/StreamGeometryContext.cs b/src/Perspex.SceneGraph/Media/StreamGeometryContext.cs index 0d0c345389..dcb000544f 100644 --- a/src/Perspex.SceneGraph/Media/StreamGeometryContext.cs +++ b/src/Perspex.SceneGraph/Media/StreamGeometryContext.cs @@ -59,9 +59,9 @@ namespace Perspex.Media /// The first control point used to specify the shape of the curve. /// The second control point used to specify the shape of the curve. /// The destination point for the end of the curve. - public void BezierTo(Point point1, Point point2, Point point3) + public void CubicBezierTo(Point point1, Point point2, Point point3) { - _impl.BezierTo(point1, point2, point3); + _impl.CubicBezierTo(point1, point2, point3); } /// @@ -69,9 +69,9 @@ namespace Perspex.Media /// /// The control point used to specify the shape of the curve. /// The destination point for the end of the curve. - public void QuadTo(Point control, Point endPoint) + public void QuadraticBezierTo(Point control, Point endPoint) { - _impl.QuadTo(control, endPoint); + _impl.QuadraticBezierTo(control, endPoint); } /// diff --git a/src/Perspex.SceneGraph/Platform/IStreamGeometryContextImpl.cs b/src/Perspex.SceneGraph/Platform/IStreamGeometryContextImpl.cs index 88a5d0a7b9..182a3ec174 100644 --- a/src/Perspex.SceneGraph/Platform/IStreamGeometryContextImpl.cs +++ b/src/Perspex.SceneGraph/Platform/IStreamGeometryContextImpl.cs @@ -36,14 +36,14 @@ namespace Perspex.Platform /// The first control point used to specify the shape of the curve. /// The second control point used to specify the shape of the curve. /// The destination point for the end of the curve. - void BezierTo(Point point1, Point point2, Point point3); + void CubicBezierTo(Point point1, Point point2, Point point3); /// /// Draws a quadratic Bezier curve to the specified point /// /// Control point /// DestinationPoint - void QuadTo(Point control, Point endPoint); + void QuadraticBezierTo(Point control, Point endPoint); /// /// Draws a line to the specified point. diff --git a/src/Shared/RenderHelpers/ArcToHelper.cs b/src/Shared/RenderHelpers/ArcToHelper.cs index a708c09ee2..5c129a8c91 100644 --- a/src/Shared/RenderHelpers/ArcToHelper.cs +++ b/src/Shared/RenderHelpers/ArcToHelper.cs @@ -949,11 +949,11 @@ namespace Perspex.RenderHelpers else if (degree == 2) { double k = (yBDot * (xB - xA) - xBDot * (yB - yA)) / (xADot * yBDot - yADot * xBDot); - path.QuadTo(new Point(xA + k * xADot, yA + k * yADot), new Point(xB, yB)); + path.QuadraticBezierTo(new Point(xA + k * xADot, yA + k * yADot), new Point(xB, yB)); } else { - path.BezierTo( + path.CubicBezierTo( new Point(xA + alpha * xADot, yA + alpha * yADot), new Point(xB - alpha * xBDot, yB - alpha * yBDot), new Point(xB, yB) diff --git a/src/Shared/RenderHelpers/QuadBezierHelper.cs b/src/Shared/RenderHelpers/QuadBezierHelper.cs index 8c6c9f3d63..33b5042de7 100644 --- a/src/Shared/RenderHelpers/QuadBezierHelper.cs +++ b/src/Shared/RenderHelpers/QuadBezierHelper.cs @@ -4,10 +4,10 @@ namespace Perspex.RenderHelpers { static class QuadBezierHelper { - public static void QuadTo(IStreamGeometryContextImpl context, Point current, Point controlPoint, Point endPoint) + public static void QuadraticBezierTo(IStreamGeometryContextImpl context, Point current, Point controlPoint, Point endPoint) { //(s, (s + 2c)/ 3, (e + 2c)/ 3, e) - context.BezierTo((current + 2*controlPoint)/3, (endPoint + 2*controlPoint)/3, endPoint); + context.CubicBezierTo((current + 2*controlPoint)/3, (endPoint + 2*controlPoint)/3, endPoint); } } } diff --git a/src/Windows/Perspex.Direct2D1/Media/StreamGeometryContextImpl.cs b/src/Windows/Perspex.Direct2D1/Media/StreamGeometryContextImpl.cs index bf72373423..e18e61f94a 100644 --- a/src/Windows/Perspex.Direct2D1/Media/StreamGeometryContextImpl.cs +++ b/src/Windows/Perspex.Direct2D1/Media/StreamGeometryContextImpl.cs @@ -37,7 +37,7 @@ namespace Perspex.Direct2D1.Media _sink.BeginFigure(startPoint.ToSharpDX(), isFilled ? FigureBegin.Filled : FigureBegin.Hollow); } - public void BezierTo(Point point1, Point point2, Point point3) + public void CubicBezierTo(Point point1, Point point2, Point point3) { _sink.AddBezier(new BezierSegment { @@ -47,7 +47,7 @@ namespace Perspex.Direct2D1.Media }); } - public void QuadTo(Point control, Point dest) + public void QuadraticBezierTo(Point control, Point dest) { _sink.AddQuadraticBezier(new QuadraticBezierSegment { From c47b8fef5b71e5e8c9415aeba762ebc382548df3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Wies=C5=82aw=20=C5=A0olt=C3=A9s?= Date: Sat, 14 Nov 2015 23:00:03 +0100 Subject: [PATCH 002/211] Added TabStripPlacement property --- src/Perspex.Controls/TabControl.cs | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/src/Perspex.Controls/TabControl.cs b/src/Perspex.Controls/TabControl.cs index 3974fe2533..30f4bac01c 100644 --- a/src/Perspex.Controls/TabControl.cs +++ b/src/Perspex.Controls/TabControl.cs @@ -28,6 +28,12 @@ namespace Perspex.Controls private static readonly IMemberSelector s_contentSelector = new FuncMemberSelector(SelectContent); + /// + /// Defines the property. + /// + public static readonly PerspexProperty TabStripPlacementProperty = + PerspexProperty.Register(nameof(TabStripPlacement), defaultValue: Dock.Top); + /// /// Initializes static members of the class. /// @@ -36,6 +42,7 @@ namespace Perspex.Controls SelectionModeProperty.OverrideDefaultValue(SelectionMode.AlwaysSelected); FocusableProperty.OverrideDefaultValue(false); SelectedItemProperty.Changed.AddClassHandler(x => x.SelectedItemChanged); + AffectsMeasure(TabStripPlacementProperty); } /// @@ -64,6 +71,15 @@ namespace Perspex.Controls set { SetValue(TransitionProperty, value); } } + /// + /// Gets or sets the tabstrip placement of the tabcontrol. + /// + public Dock TabStripPlacement + { + get { return GetValue(TabStripPlacementProperty); } + set { SetValue(TabStripPlacementProperty, value); } + } + /// /// Asks the control whether it wants to reparent the logical children of the specified /// control. From e7fc9415aed8f5ba9d789d102a93f4d2eee749d7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Wies=C5=82aw=20=C5=A0olt=C3=A9s?= Date: Sat, 14 Nov 2015 23:00:27 +0100 Subject: [PATCH 003/211] Added TabStripPlacement style selectors --- src/Perspex.Themes.Default/TabControl.paml | 96 ++++++++++++++++++---- 1 file changed, 81 insertions(+), 15 deletions(-) diff --git a/src/Perspex.Themes.Default/TabControl.paml b/src/Perspex.Themes.Default/TabControl.paml index d5d7ba3c9f..0809be9077 100644 --- a/src/Perspex.Themes.Default/TabControl.paml +++ b/src/Perspex.Themes.Default/TabControl.paml @@ -1,15 +1,81 @@ - \ No newline at end of file + + + + + + \ No newline at end of file From 98a355df804da695493e472aa17245749b6c68be Mon Sep 17 00:00:00 2001 From: donandren Date: Tue, 24 Nov 2015 00:21:30 +0200 Subject: [PATCH 004/211] shadow should be disposed in TextRenderer according sharpdx docs http://sharpdx.org/documentation/api/t-sharpdx-icallbackable --- src/Windows/Perspex.Direct2D1/Media/PerspexTextRenderer.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Windows/Perspex.Direct2D1/Media/PerspexTextRenderer.cs b/src/Windows/Perspex.Direct2D1/Media/PerspexTextRenderer.cs index 297dcdb581..24faf13578 100644 --- a/src/Windows/Perspex.Direct2D1/Media/PerspexTextRenderer.cs +++ b/src/Windows/Perspex.Direct2D1/Media/PerspexTextRenderer.cs @@ -34,6 +34,7 @@ namespace Perspex.Direct2D1.Media public void Dispose() { + Shadow?.Dispose(); } public Result DrawGlyphRun( From a511eb57caa80f442cb5802a1b7bb042f2c0fcec Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Tue, 24 Nov 2015 01:52:06 +0100 Subject: [PATCH 005/211] Fixed skia backend broken by rename. --- src/Skia/Perspex.Skia/StreamGeometryImpl.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Skia/Perspex.Skia/StreamGeometryImpl.cs b/src/Skia/Perspex.Skia/StreamGeometryImpl.cs index 0a516c139e..d18a3c55c5 100644 --- a/src/Skia/Perspex.Skia/StreamGeometryImpl.cs +++ b/src/Skia/Perspex.Skia/StreamGeometryImpl.cs @@ -99,7 +99,7 @@ namespace Perspex.Skia _currentPoint = startPoint; } - public void BezierTo(Point point1, Point point2, Point point3) + public void CubicBezierTo(Point point1, Point point2, Point point3) { _elements.Add(new SkiaGeometryElement { @@ -111,7 +111,7 @@ namespace Perspex.Skia _currentPoint = point3; } - public void QuadTo(Point control, Point endPoint) + public void QuadraticBezierTo(Point control, Point endPoint) { _elements.Add(new SkiaGeometryElement { From 9efc30c12ed6a1ca319ca9a6aad56908b64a3cbe Mon Sep 17 00:00:00 2001 From: Nikita Tsukanov Date: Tue, 24 Nov 2015 20:33:50 +0300 Subject: [PATCH 006/211] Now using GLKView and CADisplayLink --- src/Skia/Perspex.Skia.iOS.TestApp/MainView.cs | 57 ++++++++--------- .../Perspex.Skia.iOS/Perspex.Skia.iOS.csproj | 1 + src/Skia/Perspex.Skia.iOS/SkiaView.cs | 64 +++++++++++++++++++ 3 files changed, 92 insertions(+), 30 deletions(-) create mode 100644 src/Skia/Perspex.Skia.iOS/SkiaView.cs diff --git a/src/Skia/Perspex.Skia.iOS.TestApp/MainView.cs b/src/Skia/Perspex.Skia.iOS.TestApp/MainView.cs index fe818d9d84..ff8f6995c9 100644 --- a/src/Skia/Perspex.Skia.iOS.TestApp/MainView.cs +++ b/src/Skia/Perspex.Skia.iOS.TestApp/MainView.cs @@ -1,7 +1,9 @@ using System; +using System.Diagnostics; using System.Drawing; using CoreAnimation; using CoreGraphics; +using CoreMedia; using Foundation; using Perspex.Media; using Perspex.Platform; @@ -10,54 +12,40 @@ using UIKit; namespace Perspex.Skia.iOS.TestApp { [Register("MainView")] - public class MainView : UIView + public class MainView : SkiaView { - CAEAGLLayer _layer = new CAEAGLLayer(); private IRenderTarget _target; - + FormattedText _text; public MainView() - { - Initialize(); - } - - public MainView(RectangleF bounds) : base(bounds) - { - Initialize(); - } - - - void Initialize() { AutoresizingMask = UIViewAutoresizing.All; - Layer.AddSublayer(_layer); - SkiaPlatform.Initialize(); _target = PerspexLocator.Current.GetService() - .CreateRenderer(new PlatformHandle(_layer.DangerousRetain().Handle, "Layer")); + .CreateRenderer(PerspexPlatformHandle); + UpdateText(0); } - double _radians = 0; - public override void TouchesMoved(NSSet touches, UIEvent evt) + + void UpdateText(int fps) { - foreach (var touch in touches) - { - var loc = ((UITouch) touch).LocationInView(this); - _radians = (loc.X + loc.Y)/100; - } - SetNeedsDisplay(); - base.TouchesMoved(touches, evt); + _text?.Dispose(); + _text = new FormattedText("FPS: " + fps, "Arial", 15, FontStyle.Normal, TextAlignment.Left, + FontWeight.Normal); } - public override void Draw(CGRect rect) + double _lastFps; + int _frames; + Stopwatch St = Stopwatch.StartNew(); + protected override void Draw() { - _layer.Bounds = new CGRect(0, 0, Bounds.Width, Bounds.Height); + _radians += 0.02; var scale = UIScreen.MainScreen.Scale; int width = (int) (Bounds.Width*scale), height = (int) (Bounds.Height*scale); using (var ctx = _target.CreateDrawingContext()) { ctx.FillRectangle(Brushes.Green, new Rect(0, 0, width, height)); - + ctx.DrawText(Brushes.Red, new Point(50, 50), _text); var rc = new Rect(0, 0, width/3, height/3); using (ctx.PushPostTransform( Perspex.Matrix.CreateTranslation(-width/6, -width/6)* @@ -74,7 +62,16 @@ namespace Perspex.Skia.iOS.TestApp }, rc, 5); } } - + _frames++; + var now = St.Elapsed.TotalSeconds; + var elapsed = now - _lastFps; + if (elapsed > 1) + { + UpdateText((int) (_frames/elapsed)); + _frames = 0; + _lastFps = now; + } + DrawOnNextFrame(); } } } \ No newline at end of file diff --git a/src/Skia/Perspex.Skia.iOS/Perspex.Skia.iOS.csproj b/src/Skia/Perspex.Skia.iOS/Perspex.Skia.iOS.csproj index 1d2fd23ae4..a583ceb5b2 100644 --- a/src/Skia/Perspex.Skia.iOS/Perspex.Skia.iOS.csproj +++ b/src/Skia/Perspex.Skia.iOS/Perspex.Skia.iOS.csproj @@ -37,6 +37,7 @@ + diff --git a/src/Skia/Perspex.Skia.iOS/SkiaView.cs b/src/Skia/Perspex.Skia.iOS/SkiaView.cs new file mode 100644 index 0000000000..e804dccdd9 --- /dev/null +++ b/src/Skia/Perspex.Skia.iOS/SkiaView.cs @@ -0,0 +1,64 @@ +using System; +using System.Collections.Generic; +using System.Runtime.InteropServices; +using System.Text; +using CoreAnimation; +using CoreGraphics; +using Foundation; +using GLKit; +using ObjCRuntime; +using OpenGLES; +using Perspex.Platform; +using UIKit; + +namespace Perspex.Skia.iOS +{ + public abstract class SkiaView : GLKView + { + [DllImport("__Internal")] + static extern IntPtr GetPerspexEAGLContext(); + + bool _drawQueued; + CADisplayLink _link; + static EAGLContext GetContext() + { + //Ensure initialization + MethodTable.Instance.SetOption((MethodTable.Option)0x10009999, IntPtr.Zero); + var ctx = GetPerspexEAGLContext(); + var rv = Runtime.GetNSObject(ctx); + rv.DangerousRetain(); + return rv; + } + + + protected SkiaView() : base(UIScreen.MainScreen.ApplicationFrame, GetContext()) + { + (_link = CADisplayLink.Create(() => OnFrame())).AddToRunLoop(NSRunLoop.Main, NSRunLoop.NSDefaultRunLoopMode); + } + + protected void OnFrame() + { + if (_drawQueued) + { + _drawQueued = false; + Display(); + } + } + + protected void DrawOnNextFrame() + { + _drawQueued = true; + } + + protected IPlatformHandle PerspexPlatformHandle { get; } + = new PlatformHandle(IntPtr.Zero, "Null (iOS-specific)"); + + + protected abstract void Draw(); + + public override void Draw(CGRect rect) + { + Draw(); + } + } +} From 85b530e36391b25d71c56665a80677b9336e541c Mon Sep 17 00:00:00 2001 From: Nikita Tsukanov Date: Tue, 24 Nov 2015 22:40:52 +0300 Subject: [PATCH 007/211] New ios backend --- Perspex.sln | 61 +++++- .../Perspex.Android/AndroidPlatform.cs | 2 +- src/Perspex.Application/Application.cs | 14 +- src/Perspex.Input/KeyboardDevice.cs | 2 +- src/Perspex.Input/MouseDevice.cs | 2 +- src/iOS/Perspex.iOS/Clipboard.cs | 26 +++ src/iOS/Perspex.iOS/CursorFactory.cs | 11 + src/iOS/Perspex.iOS/Extensions.cs | 15 ++ src/iOS/Perspex.iOS/Perspex.iOS.csproj | 101 +++++++++ src/iOS/Perspex.iOS/PerspexAppDelegate.cs | 50 +++++ src/iOS/Perspex.iOS/PerspexView.cs | 179 ++++++++++++++++ src/iOS/Perspex.iOS/PlatformSettings.cs | 16 ++ .../Perspex.iOS/PlatformThreadingInterface.cs | 56 +++++ .../Perspex.iOS/Properties/AssemblyInfo.cs | 36 ++++ .../Perspex.iOSTestApplication/AppDelegate.cs | 40 ++++ .../Entitlements.plist | 5 + .../GettingStarted.Xamarin | 4 + src/iOS/Perspex.iOSTestApplication/Info.plist | 34 +++ src/iOS/Perspex.iOSTestApplication/Main.cs | 20 ++ .../Perspex.iOSTestApplication.csproj | 197 ++++++++++++++++++ .../Properties/AssemblyInfo.cs | 36 ++++ .../Resources/Default-568h@2x.png | Bin 0 -> 2215 bytes .../packages.config | 6 + 23 files changed, 900 insertions(+), 13 deletions(-) create mode 100644 src/iOS/Perspex.iOS/Clipboard.cs create mode 100644 src/iOS/Perspex.iOS/CursorFactory.cs create mode 100644 src/iOS/Perspex.iOS/Extensions.cs create mode 100644 src/iOS/Perspex.iOS/Perspex.iOS.csproj create mode 100644 src/iOS/Perspex.iOS/PerspexAppDelegate.cs create mode 100644 src/iOS/Perspex.iOS/PerspexView.cs create mode 100644 src/iOS/Perspex.iOS/PlatformSettings.cs create mode 100644 src/iOS/Perspex.iOS/PlatformThreadingInterface.cs create mode 100644 src/iOS/Perspex.iOS/Properties/AssemblyInfo.cs create mode 100644 src/iOS/Perspex.iOSTestApplication/AppDelegate.cs create mode 100644 src/iOS/Perspex.iOSTestApplication/Entitlements.plist create mode 100644 src/iOS/Perspex.iOSTestApplication/GettingStarted.Xamarin create mode 100644 src/iOS/Perspex.iOSTestApplication/Info.plist create mode 100644 src/iOS/Perspex.iOSTestApplication/Main.cs create mode 100644 src/iOS/Perspex.iOSTestApplication/Perspex.iOSTestApplication.csproj create mode 100644 src/iOS/Perspex.iOSTestApplication/Properties/AssemblyInfo.cs create mode 100644 src/iOS/Perspex.iOSTestApplication/Resources/Default-568h@2x.png create mode 100644 src/iOS/Perspex.iOSTestApplication/packages.config diff --git a/Perspex.sln b/Perspex.sln index 1ddd288c70..24c017c309 100644 --- a/Perspex.sln +++ b/Perspex.sln @@ -130,25 +130,32 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Perspex.Android", "src\Andr EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Perspex.AndroidTestApplication", "src\Android\Perspex.AndroidTestApplication\Perspex.AndroidTestApplication.csproj", "{FF69B927-C545-49AE-8E16-3D14D621AA12}" EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "iOS", "iOS", "{0CB0B92E-6CFF-4240-80A5-CCAFE75D91E1}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Perspex.iOS", "src\iOS\Perspex.iOS\Perspex.iOS.csproj", "{4488AD85-1495-4809-9AA4-DDFE0A48527E}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Perspex.iOSTestApplication", "src\iOS\Perspex.iOSTestApplication\Perspex.iOSTestApplication.csproj", "{8C923867-8A8F-4F6B-8B80-47D9E8436166}" +EndProject Global GlobalSection(SharedMSBuildProjectFiles) = preSolution src\Shared\RenderHelpers\RenderHelpers.projitems*{fb05ac90-89ba-4f2f-a924-f37875fb547c}*SharedItemsImports = 4 + src\Shared\PlatformSupport\PlatformSupport.projitems*{4488ad85-1495-4809-9aa4-ddfe0a48527e}*SharedItemsImports = 4 src\Shared\PlatformSupport\PlatformSupport.projitems*{7b92af71-6287-4693-9dcb-bd5b6e927e23}*SharedItemsImports = 4 src\Shared\PlatformSupport\PlatformSupport.projitems*{e4d9629c-f168-4224-3f51-a5e482ffbc42}*SharedItemsImports = 13 src\Skia\Perspex.Skia\Perspex.Skia.projitems*{2f59f3d0-748d-4652-b01e-e0d954756308}*SharedItemsImports = 13 src\Shared\PlatformSupport\PlatformSupport.projitems*{db070a10-bf39-4752-8456-86e9d5928478}*SharedItemsImports = 4 - src\Skia\Perspex.Skia\Perspex.Skia.projitems*{925dd807-b651-475f-9f7c-cbeb974ce43d}*SharedItemsImports = 4 src\Shared\RenderHelpers\RenderHelpers.projitems*{925dd807-b651-475f-9f7c-cbeb974ce43d}*SharedItemsImports = 4 + src\Skia\Perspex.Skia\Perspex.Skia.projitems*{925dd807-b651-475f-9f7c-cbeb974ce43d}*SharedItemsImports = 4 samples\TestApplicationShared\TestApplicationShared.projitems*{78345174-5b52-4a14-b9fd-d5f2428137f0}*SharedItemsImports = 13 src\Shared\PlatformSupport\PlatformSupport.projitems*{54f237d5-a70a-4752-9656-0c70b1a7b047}*SharedItemsImports = 4 - samples\TestApplicationShared\TestApplicationShared.projitems*{ff69b927-c545-49ae-8e16-3d14d621aa12}*SharedItemsImports = 4 src\Shared\RenderHelpers\RenderHelpers.projitems*{3c4c0cb4-0c0f-4450-a37b-148c84ff905f}*SharedItemsImports = 13 src\Shared\PlatformSupport\PlatformSupport.projitems*{811a76cf-1cf6-440f-963b-bbe31bd72a82}*SharedItemsImports = 4 - src\Skia\Perspex.Skia\Perspex.Skia.projitems*{47be08a7-5985-410b-9ffc-2264b8ea595f}*SharedItemsImports = 4 src\Shared\RenderHelpers\RenderHelpers.projitems*{47be08a7-5985-410b-9ffc-2264b8ea595f}*SharedItemsImports = 4 + src\Skia\Perspex.Skia\Perspex.Skia.projitems*{47be08a7-5985-410b-9ffc-2264b8ea595f}*SharedItemsImports = 4 + samples\TestApplicationShared\TestApplicationShared.projitems*{8c923867-8a8f-4f6b-8b80-47d9e8436166}*SharedItemsImports = 4 samples\TestApplicationShared\TestApplicationShared.projitems*{e3a1060b-50d0-44e8-88b6-f44ef2e5bd72}*SharedItemsImports = 4 - src\Skia\Perspex.Skia\Perspex.Skia.projitems*{bd43f7c0-396b-4aa1-bad9-dfde54d51298}*SharedItemsImports = 4 src\Shared\RenderHelpers\RenderHelpers.projitems*{bd43f7c0-396b-4aa1-bad9-dfde54d51298}*SharedItemsImports = 4 + src\Skia\Perspex.Skia\Perspex.Skia.projitems*{bd43f7c0-396b-4aa1-bad9-dfde54d51298}*SharedItemsImports = 4 src\Shared\RenderHelpers\RenderHelpers.projitems*{3e908f67-5543-4879-a1dc-08eace79b3cd}*SharedItemsImports = 4 EndGlobalSection GlobalSection(SolutionConfigurationPlatforms) = preSolution @@ -1189,6 +1196,50 @@ Global {FF69B927-C545-49AE-8E16-3D14D621AA12}.Release|Any CPU.Deploy.0 = Release|Any CPU {FF69B927-C545-49AE-8E16-3D14D621AA12}.Release|iPhone.ActiveCfg = Release|Any CPU {FF69B927-C545-49AE-8E16-3D14D621AA12}.Release|iPhoneSimulator.ActiveCfg = Release|Any CPU + {4488AD85-1495-4809-9AA4-DDFE0A48527E}.Ad-Hoc|Any CPU.ActiveCfg = Release|Any CPU + {4488AD85-1495-4809-9AA4-DDFE0A48527E}.Ad-Hoc|Any CPU.Build.0 = Release|Any CPU + {4488AD85-1495-4809-9AA4-DDFE0A48527E}.Ad-Hoc|iPhone.ActiveCfg = Release|Any CPU + {4488AD85-1495-4809-9AA4-DDFE0A48527E}.Ad-Hoc|iPhone.Build.0 = Release|Any CPU + {4488AD85-1495-4809-9AA4-DDFE0A48527E}.Ad-Hoc|iPhoneSimulator.ActiveCfg = Release|Any CPU + {4488AD85-1495-4809-9AA4-DDFE0A48527E}.Ad-Hoc|iPhoneSimulator.Build.0 = Release|Any CPU + {4488AD85-1495-4809-9AA4-DDFE0A48527E}.AppStore|Any CPU.ActiveCfg = Release|Any CPU + {4488AD85-1495-4809-9AA4-DDFE0A48527E}.AppStore|Any CPU.Build.0 = Release|Any CPU + {4488AD85-1495-4809-9AA4-DDFE0A48527E}.AppStore|iPhone.ActiveCfg = Release|Any CPU + {4488AD85-1495-4809-9AA4-DDFE0A48527E}.AppStore|iPhone.Build.0 = Release|Any CPU + {4488AD85-1495-4809-9AA4-DDFE0A48527E}.AppStore|iPhoneSimulator.ActiveCfg = Release|Any CPU + {4488AD85-1495-4809-9AA4-DDFE0A48527E}.AppStore|iPhoneSimulator.Build.0 = Release|Any CPU + {4488AD85-1495-4809-9AA4-DDFE0A48527E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {4488AD85-1495-4809-9AA4-DDFE0A48527E}.Debug|Any CPU.Build.0 = Debug|Any CPU + {4488AD85-1495-4809-9AA4-DDFE0A48527E}.Debug|iPhone.ActiveCfg = Debug|Any CPU + {4488AD85-1495-4809-9AA4-DDFE0A48527E}.Debug|iPhone.Build.0 = Debug|Any CPU + {4488AD85-1495-4809-9AA4-DDFE0A48527E}.Debug|iPhoneSimulator.ActiveCfg = Debug|Any CPU + {4488AD85-1495-4809-9AA4-DDFE0A48527E}.Debug|iPhoneSimulator.Build.0 = Debug|Any CPU + {4488AD85-1495-4809-9AA4-DDFE0A48527E}.Release|Any CPU.ActiveCfg = Release|Any CPU + {4488AD85-1495-4809-9AA4-DDFE0A48527E}.Release|Any CPU.Build.0 = Release|Any CPU + {4488AD85-1495-4809-9AA4-DDFE0A48527E}.Release|iPhone.ActiveCfg = Release|Any CPU + {4488AD85-1495-4809-9AA4-DDFE0A48527E}.Release|iPhone.Build.0 = Release|Any CPU + {4488AD85-1495-4809-9AA4-DDFE0A48527E}.Release|iPhoneSimulator.ActiveCfg = Release|Any CPU + {4488AD85-1495-4809-9AA4-DDFE0A48527E}.Release|iPhoneSimulator.Build.0 = Release|Any CPU + {8C923867-8A8F-4F6B-8B80-47D9E8436166}.Ad-Hoc|Any CPU.ActiveCfg = Ad-Hoc|iPhone + {8C923867-8A8F-4F6B-8B80-47D9E8436166}.Ad-Hoc|iPhone.ActiveCfg = Ad-Hoc|iPhone + {8C923867-8A8F-4F6B-8B80-47D9E8436166}.Ad-Hoc|iPhone.Build.0 = Ad-Hoc|iPhone + {8C923867-8A8F-4F6B-8B80-47D9E8436166}.Ad-Hoc|iPhoneSimulator.ActiveCfg = Ad-Hoc|iPhoneSimulator + {8C923867-8A8F-4F6B-8B80-47D9E8436166}.Ad-Hoc|iPhoneSimulator.Build.0 = Ad-Hoc|iPhoneSimulator + {8C923867-8A8F-4F6B-8B80-47D9E8436166}.AppStore|Any CPU.ActiveCfg = AppStore|iPhone + {8C923867-8A8F-4F6B-8B80-47D9E8436166}.AppStore|iPhone.ActiveCfg = AppStore|iPhone + {8C923867-8A8F-4F6B-8B80-47D9E8436166}.AppStore|iPhone.Build.0 = AppStore|iPhone + {8C923867-8A8F-4F6B-8B80-47D9E8436166}.AppStore|iPhoneSimulator.ActiveCfg = AppStore|iPhoneSimulator + {8C923867-8A8F-4F6B-8B80-47D9E8436166}.AppStore|iPhoneSimulator.Build.0 = AppStore|iPhoneSimulator + {8C923867-8A8F-4F6B-8B80-47D9E8436166}.Debug|Any CPU.ActiveCfg = Debug|iPhone + {8C923867-8A8F-4F6B-8B80-47D9E8436166}.Debug|iPhone.ActiveCfg = Debug|iPhone + {8C923867-8A8F-4F6B-8B80-47D9E8436166}.Debug|iPhone.Build.0 = Debug|iPhone + {8C923867-8A8F-4F6B-8B80-47D9E8436166}.Debug|iPhoneSimulator.ActiveCfg = Debug|iPhoneSimulator + {8C923867-8A8F-4F6B-8B80-47D9E8436166}.Debug|iPhoneSimulator.Build.0 = Debug|iPhoneSimulator + {8C923867-8A8F-4F6B-8B80-47D9E8436166}.Release|Any CPU.ActiveCfg = Release|iPhone + {8C923867-8A8F-4F6B-8B80-47D9E8436166}.Release|iPhone.ActiveCfg = Release|iPhone + {8C923867-8A8F-4F6B-8B80-47D9E8436166}.Release|iPhone.Build.0 = Release|iPhone + {8C923867-8A8F-4F6B-8B80-47D9E8436166}.Release|iPhoneSimulator.ActiveCfg = Release|iPhoneSimulator + {8C923867-8A8F-4F6B-8B80-47D9E8436166}.Release|iPhoneSimulator.Build.0 = Release|iPhoneSimulator EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -1229,5 +1280,7 @@ Global {78345174-5B52-4A14-B9FD-D5F2428137F0} = {9B9E3891-2366-4253-A952-D08BCEB71098} {7B92AF71-6287-4693-9DCB-BD5B6E927E23} = {7CF9789C-F1D3-4D0E-90E5-F1DF67A2753F} {FF69B927-C545-49AE-8E16-3D14D621AA12} = {7CF9789C-F1D3-4D0E-90E5-F1DF67A2753F} + {4488AD85-1495-4809-9AA4-DDFE0A48527E} = {0CB0B92E-6CFF-4240-80A5-CCAFE75D91E1} + {8C923867-8A8F-4F6B-8B80-47D9E8436166} = {0CB0B92E-6CFF-4240-80A5-CCAFE75D91E1} EndGlobalSection EndGlobal diff --git a/src/Android/Perspex.Android/AndroidPlatform.cs b/src/Android/Perspex.Android/AndroidPlatform.cs index 8196571102..b3f4bdaa32 100644 --- a/src/Android/Perspex.Android/AndroidPlatform.cs +++ b/src/Android/Perspex.Android/AndroidPlatform.cs @@ -36,7 +36,7 @@ namespace Perspex.Android .Bind().ToTransient(); SkiaPlatform.Initialize(); - Application.SuppressPlatformInitialization(); + Application.RegisterPlatformCallback(() => { }); PerspexLocator.CurrentMutable.Bind().ToSingleton(); _scalingFactor = global::Android.App.Application.Context.Resources.DisplayMetrics.ScaledDensity; diff --git a/src/Perspex.Application/Application.cs b/src/Perspex.Application/Application.cs index d718d76e71..027ca0b759 100644 --- a/src/Perspex.Application/Application.cs +++ b/src/Perspex.Application/Application.cs @@ -34,7 +34,7 @@ namespace Perspex /// public class Application : IGlobalDataTemplates, IGlobalStyles { - static bool _suppressPlatformInitialization; + static Action _platformInitializationCallback; /// /// The application-global data templates. @@ -62,9 +62,9 @@ namespace Perspex Current = this; } - public static void SuppressPlatformInitialization() + public static void RegisterPlatformCallback(Action cb) { - _suppressPlatformInitialization = true; + _platformInitializationCallback = cb; } /// @@ -185,9 +185,11 @@ namespace Perspex /// The value of Environment.OSVersion.Platform. protected void InitializeSubsystems(int platformID) { - if(_suppressPlatformInitialization) - return; - if (platformID == 4 || platformID == 6) + if (_platformInitializationCallback != null) + { + _platformInitializationCallback(); + } + else if (platformID == 4 || platformID == 6) { InitializeSubsystem("Perspex.Cairo"); InitializeSubsystem("Perspex.Gtk"); diff --git a/src/Perspex.Input/KeyboardDevice.cs b/src/Perspex.Input/KeyboardDevice.cs index 3032a663bd..fc3edad4df 100644 --- a/src/Perspex.Input/KeyboardDevice.cs +++ b/src/Perspex.Input/KeyboardDevice.cs @@ -11,7 +11,7 @@ using Perspex.Interactivity; namespace Perspex.Input { - public abstract class KeyboardDevice : IKeyboardDevice, INotifyPropertyChanged + public class KeyboardDevice : IKeyboardDevice, INotifyPropertyChanged { private IInputElement _focusedElement; diff --git a/src/Perspex.Input/MouseDevice.cs b/src/Perspex.Input/MouseDevice.cs index e3da2caf9b..268b638def 100644 --- a/src/Perspex.Input/MouseDevice.cs +++ b/src/Perspex.Input/MouseDevice.cs @@ -12,7 +12,7 @@ using Perspex.VisualTree; namespace Perspex.Input { - public abstract class MouseDevice : IMouseDevice + public class MouseDevice : IMouseDevice { private int _clickCount; diff --git a/src/iOS/Perspex.iOS/Clipboard.cs b/src/iOS/Perspex.iOS/Clipboard.cs new file mode 100644 index 0000000000..f0a33136f9 --- /dev/null +++ b/src/iOS/Perspex.iOS/Clipboard.cs @@ -0,0 +1,26 @@ +using System.Threading.Tasks; +using Perspex.Input.Platform; +using UIKit; + +namespace Perspex.iOS +{ + public class Clipboard : IClipboard + { + public Task GetTextAsync() + { + return Task.FromResult(UIPasteboard.General.String); + } + + public Task SetTextAsync(string text) + { + UIPasteboard.General.String = text; + return Task.FromResult(0); + } + + public Task ClearAsync() + { + UIPasteboard.General.String = ""; + return Task.FromResult(0); + } + } +} \ No newline at end of file diff --git a/src/iOS/Perspex.iOS/CursorFactory.cs b/src/iOS/Perspex.iOS/CursorFactory.cs new file mode 100644 index 0000000000..415f4c79b9 --- /dev/null +++ b/src/iOS/Perspex.iOS/CursorFactory.cs @@ -0,0 +1,11 @@ +using System; +using Perspex.Input; +using Perspex.Platform; + +namespace Perspex.iOS +{ + class CursorFactory : IStandardCursorFactory + { + public IPlatformHandle GetCursor(StandardCursorType cursorType) => new PlatformHandle(IntPtr.Zero, "NULL"); + } +} \ No newline at end of file diff --git a/src/iOS/Perspex.iOS/Extensions.cs b/src/iOS/Perspex.iOS/Extensions.cs new file mode 100644 index 0000000000..8bf4ebeb29 --- /dev/null +++ b/src/iOS/Perspex.iOS/Extensions.cs @@ -0,0 +1,15 @@ +using System; +using System.Collections.Generic; +using System.Text; +using CoreGraphics; + +namespace Perspex.iOS +{ + static class Extensions + { + + public static Size ToPerspex(this CGSize size) => new Size(size.Width, size.Height); + + public static Point ToPerspex(this CGPoint point) => new Point(point.X, point.Y); + } +} diff --git a/src/iOS/Perspex.iOS/Perspex.iOS.csproj b/src/iOS/Perspex.iOS/Perspex.iOS.csproj new file mode 100644 index 0000000000..1a5866908b --- /dev/null +++ b/src/iOS/Perspex.iOS/Perspex.iOS.csproj @@ -0,0 +1,101 @@ + + + + Debug + iPhoneSimulator + 8.0.30703 + 2.0 + {4488AD85-1495-4809-9AA4-DDFE0A48527E} + {FEACFBD2-3405-455C-9665-78FE426C6842};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} + Library + Perspex.iOS + Resources + Perspex.iOS + + + true + full + false + bin\iPhone\Debug + DEBUG + prompt + 4 + false + true + iPhone Developer + + + none + true + bin\iPhone\Release + prompt + 4 + false + iPhone Developer + + + + + + + + + + + + + + {d211e587-d8bc-45b9-95a4-f297c8fa5200} + Perspex.Animation + + + {799a7bb5-3c2c-48b6-85a7-406a12c420da} + Perspex.Application + + + {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 + + + {47be08a7-5985-410b-9ffc-2264b8ea595f} + Perspex.Skia.iOS + false + false + + + + + + ..\..\..\packages\Rx-Core.2.2.5\lib\portable-windows8+net45+wp8\System.Reactive.Core.dll + + + + + + + + \ No newline at end of file diff --git a/src/iOS/Perspex.iOS/PerspexAppDelegate.cs b/src/iOS/Perspex.iOS/PerspexAppDelegate.cs new file mode 100644 index 0000000000..180d58a34e --- /dev/null +++ b/src/iOS/Perspex.iOS/PerspexAppDelegate.cs @@ -0,0 +1,50 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using Perspex.Controls.Platform; +using Perspex.Input; +using Perspex.Input.Platform; +using Perspex.Platform; +using Perspex.Shared.PlatformSupport; +using Perspex.Skia; +using UIKit; + +namespace Perspex.iOS +{ + public class PerspexAppDelegate : UIApplicationDelegate + { + static bool _initialized = false; + internal static MouseDevice MouseDevice; + internal static KeyboardDevice KeyboardDevice; + + protected void InitPerspex(Type appType) + { + if(_initialized) + return; + _initialized = true; + + var window = new UIWindow(UIScreen.MainScreen.Bounds); + var controller = new PerspexViewController(window); + window.RootViewController = controller; + window.MakeKeyAndVisible(); + + Application.RegisterPlatformCallback(() => + { + MouseDevice = new MouseDevice(); + KeyboardDevice = new KeyboardDevice(); + SharedPlatform.Register(appType.Assembly); + PerspexLocator.CurrentMutable + .Bind().ToTransient() + //.Bind().ToTransient() + .Bind().ToTransient() + .Bind().ToConstant(KeyboardDevice) + .Bind().ToConstant(MouseDevice) + .Bind().ToSingleton() + .Bind().ToConstant(new PlatformThreadingInterface()) + .Bind().ToConstant(controller.PerspexView); + SkiaPlatform.Initialize(); + }); + } + } +} diff --git a/src/iOS/Perspex.iOS/PerspexView.cs b/src/iOS/Perspex.iOS/PerspexView.cs new file mode 100644 index 0000000000..be70fefd13 --- /dev/null +++ b/src/iOS/Perspex.iOS/PerspexView.cs @@ -0,0 +1,179 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Linq; +using System.Reactive.Disposables; +using System.Text; +using CoreAnimation; +using CoreGraphics; +using Foundation; +using Perspex.Controls.Platform; +using Perspex.Input; +using Perspex.Input.Raw; +using Perspex.Media; +using Perspex.Platform; +using Perspex.Skia.iOS; +using UIKit; + +namespace Perspex.iOS +{ + class PerspexView : SkiaView, IWindowImpl + { + private readonly UIWindow _window; + private readonly UIViewController _controller; + private IInputRoot _inputRoot; + + public PerspexView(UIWindow window, UIViewController controller) + { + if (controller == null) throw new ArgumentNullException(nameof(controller)); + _window = window; + _controller = controller; + AutoresizingMask = UIViewAutoresizing.All; + AutoFit(); + UIApplication.Notifications.ObserveDidChangeStatusBarOrientation(delegate { AutoFit(); }); + UIApplication.Notifications.ObserveDidChangeStatusBarFrame(delegate { AutoFit(); }); + } + + + void AutoFit() + { + var needFlip = !UIDevice.CurrentDevice.CheckSystemVersion(8, 0) && + (_controller.InterfaceOrientation == UIInterfaceOrientation.LandscapeLeft + || _controller.InterfaceOrientation == UIInterfaceOrientation.LandscapeRight); + + var frame = UIScreen.MainScreen.Bounds; + if (needFlip) + Frame = new CGRect(frame.Y, frame.X, frame.Height, frame.Width); + else + Frame = frame; + } + + public Action Activated { get; set; } + public Action Closed { get; set; } + public Action Deactivated { get; set; } + public Action Input { get; set; } + public Action Paint { get; set; } + public Action Resized { get; set; } + + + public IPlatformHandle Handle => PerspexPlatformHandle; + + + public override void LayoutSubviews() => Resized?.Invoke(ClientSize); + + public Size ClientSize + { + get { return Bounds.Size.ToPerspex(); } + set { Resized?.Invoke(ClientSize); } + } + + public void Activate() + { + } + + protected override void Draw() + { + Paint?.Invoke(new Rect(new Point(), ClientSize)); + } + + public void Invalidate(Rect rect) => DrawOnNextFrame(); + + public void SetInputRoot(IInputRoot inputRoot) => _inputRoot = inputRoot; + + public Point PointToScreen(Point point) => point; + + public void SetCursor(IPlatformHandle cursor) + { + //Not supported + } + + public void Show() + { + } + + public Size MaxClientSize => Bounds.Size.ToPerspex(); + public void SetTitle(string title) + { + //Not supported + } + + public IDisposable ShowDialog() + { + //Not supported + return Disposable.Empty; + } + + public void Hide() + { + //Not supported + } + + public override void TouchesEnded(NSSet touches, UIEvent evt) + { + var touch = touches.AnyObject as UITouch; + if (touch != null) + { + var location = touch.LocationInView(this).ToPerspex(); + + Input?.Invoke(new RawMouseEventArgs( + PerspexAppDelegate.MouseDevice, + (uint) touch.Timestamp, + _inputRoot, + RawMouseEventType.LeftButtonUp, + location, + InputModifiers.None)); + } + } + + Point _touchLastPoint; + public override void TouchesBegan(NSSet touches, UIEvent evt) + { + var touch = touches.AnyObject as UITouch; + if (touch != null) + { + var location = touch.LocationInView(this).ToPerspex(); + _touchLastPoint = location; + Input?.Invoke(new RawMouseEventArgs(PerspexAppDelegate.MouseDevice, (uint) touch.Timestamp, _inputRoot, + RawMouseEventType.Move, location, InputModifiers.None)); + + Input?.Invoke(new RawMouseEventArgs(PerspexAppDelegate.MouseDevice, (uint) touch.Timestamp, _inputRoot, + RawMouseEventType.LeftButtonDown, location, InputModifiers.None)); + } + } + + public override void TouchesMoved(NSSet touches, UIEvent evt) + { + var touch = touches.AnyObject as UITouch; + if (touch != null) + { + var location = touch.LocationInView(this).ToPerspex(); + if (PerspexAppDelegate.MouseDevice.Captured != null) + Input?.Invoke(new RawMouseEventArgs(PerspexAppDelegate.MouseDevice, (uint) touch.Timestamp, _inputRoot, + RawMouseEventType.Move, location, InputModifiers.LeftMouseButton)); + else + { + Input?.Invoke(new RawMouseWheelEventArgs(PerspexAppDelegate.MouseDevice, (uint) touch.Timestamp, + _inputRoot, location, location - _touchLastPoint, InputModifiers.LeftMouseButton)); + } + _touchLastPoint = location; + } + } + + + } + + class PerspexViewController : UIViewController + { + public PerspexView PerspexView { get; } + + public PerspexViewController(UIWindow window) + { + PerspexView = new PerspexView(window, this); + } + + public override void LoadView() + { + View = PerspexView; + } + } +} diff --git a/src/iOS/Perspex.iOS/PlatformSettings.cs b/src/iOS/Perspex.iOS/PlatformSettings.cs new file mode 100644 index 0000000000..87b073bbe9 --- /dev/null +++ b/src/iOS/Perspex.iOS/PlatformSettings.cs @@ -0,0 +1,16 @@ +using System; +using System.Collections.Generic; +using System.Text; +using Perspex.Platform; +using UIKit; + +namespace Perspex.iOS +{ + class PlatformSettings : IPlatformSettings + { + public Size DoubleClickSize =>new Size(4, 4); + public TimeSpan DoubleClickTime => TimeSpan.FromMilliseconds(200); + public double RenderScalingFactor => UIScreen.MainScreen.Scale; + public double LayoutScalingFactor => 1; + } +} diff --git a/src/iOS/Perspex.iOS/PlatformThreadingInterface.cs b/src/iOS/Perspex.iOS/PlatformThreadingInterface.cs new file mode 100644 index 0000000000..00e2474238 --- /dev/null +++ b/src/iOS/Perspex.iOS/PlatformThreadingInterface.cs @@ -0,0 +1,56 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Reactive.Disposables; +using System.Text; +using System.Threading; +using CoreAnimation; +using Foundation; +using Perspex.Platform; +using Perspex.Shared.PlatformSupport; + +namespace Perspex.iOS +{ + class PlatformThreadingInterface : IPlatformThreadingInterface + { + readonly List _timers = new List(); + bool _signaled; + + public PlatformThreadingInterface() + { + CADisplayLink.Create(OnFrame).AddToRunLoop(NSRunLoop.Main, NSRunLoop.NSDefaultRunLoopMode); + } + + private void OnFrame() + { + foreach (var timer in _timers.ToList()) + { + timer(); + } + + if (_signaled) + { + _signaled = false; + Signaled?.Invoke(); + } + } + + public void RunLoop(CancellationToken cancellationToken) + { + } + + public IDisposable StartTimer(TimeSpan interval, Action tick) + { + _timers.Add(tick); + return Disposable.Create(() => _timers.Remove(tick)); + } + + public void Signal() + { + _signaled = true; + } + + public bool CurrentThreadIsLoopThread => NSThread.Current.IsMainThread; + public event Action Signaled; + } +} diff --git a/src/iOS/Perspex.iOS/Properties/AssemblyInfo.cs b/src/iOS/Perspex.iOS/Properties/AssemblyInfo.cs new file mode 100644 index 0000000000..d8e5078bdd --- /dev/null +++ b/src/iOS/Perspex.iOS/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.iOS")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("Perspex.iOS")] +[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("4488ad85-1495-4809-9aa4-ddfe0a48527e")] + +// 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/src/iOS/Perspex.iOSTestApplication/AppDelegate.cs b/src/iOS/Perspex.iOSTestApplication/AppDelegate.cs new file mode 100644 index 0000000000..9227062b13 --- /dev/null +++ b/src/iOS/Perspex.iOSTestApplication/AppDelegate.cs @@ -0,0 +1,40 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Linq; +using Foundation; +using Perspex.iOS; +using Perspex.Threading; +using TestApplication; +using UIKit; + +namespace Perspex.iOSTestApplication +{ + // The UIApplicationDelegate for the application. This class is responsible for launching the + // User Interface of the application, as well as listening (and optionally responding) to + // application events from iOS. + [Register("AppDelegate")] + public partial class AppDelegate : PerspexAppDelegate + { + // + // This method is invoked when the application has loaded and is ready to run. In this + // method you should instantiate the window, load the UI into it and then make the window + // visible. + // + // You have 17 seconds to return from this method, or iOS will terminate your application. + // + public override bool FinishedLaunching(UIApplication uiapp, NSDictionary options) + { + InitPerspex (typeof (App)); + + var app = new App(); + + MainWindow.RootNamespace = "Perspex.iOSTestApplication"; + var window = MainWindow.Create(); + window.Show(); + app.Run(window); + + return true; + } + } +} \ No newline at end of file diff --git a/src/iOS/Perspex.iOSTestApplication/Entitlements.plist b/src/iOS/Perspex.iOSTestApplication/Entitlements.plist new file mode 100644 index 0000000000..0c67376eba --- /dev/null +++ b/src/iOS/Perspex.iOSTestApplication/Entitlements.plist @@ -0,0 +1,5 @@ + + + + + diff --git a/src/iOS/Perspex.iOSTestApplication/GettingStarted.Xamarin b/src/iOS/Perspex.iOSTestApplication/GettingStarted.Xamarin new file mode 100644 index 0000000000..810f716685 --- /dev/null +++ b/src/iOS/Perspex.iOSTestApplication/GettingStarted.Xamarin @@ -0,0 +1,4 @@ + + GS\iOS\CS\iOSApp\GettingStarted.html + false + \ No newline at end of file diff --git a/src/iOS/Perspex.iOSTestApplication/Info.plist b/src/iOS/Perspex.iOSTestApplication/Info.plist new file mode 100644 index 0000000000..a6654de44c --- /dev/null +++ b/src/iOS/Perspex.iOSTestApplication/Info.plist @@ -0,0 +1,34 @@ + + + + + CFBundleDisplayName + Perspex.iOSTestApplication + CFBundleIdentifier + com.your-company.Perspex.iOSTestApplication + CFBundleShortVersionString + 1.0 + CFBundleVersion + 1.0 + UIDeviceFamily + + 1 + 2 + + UISupportedInterfaceOrientations + + UIInterfaceOrientationPortrait + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + + UISupportedInterfaceOrientations~ipad + + UIInterfaceOrientationPortrait + UIInterfaceOrientationPortraitUpsideDown + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + + MinimumOSVersion + 7.0 + + \ No newline at end of file diff --git a/src/iOS/Perspex.iOSTestApplication/Main.cs b/src/iOS/Perspex.iOSTestApplication/Main.cs new file mode 100644 index 0000000000..697e57c0d4 --- /dev/null +++ b/src/iOS/Perspex.iOSTestApplication/Main.cs @@ -0,0 +1,20 @@ +using System; +using System.Collections.Generic; +using System.Linq; + +using Foundation; +using UIKit; + +namespace Perspex.iOSTestApplication +{ + public class Application + { + // This is the main entry point of the application. + static void Main(string[] args) + { + // if you want to use a different Application Delegate class from "AppDelegate" + // you can specify it here. + UIApplication.Main(args, null, "AppDelegate"); + } + } +} \ No newline at end of file diff --git a/src/iOS/Perspex.iOSTestApplication/Perspex.iOSTestApplication.csproj b/src/iOS/Perspex.iOSTestApplication/Perspex.iOSTestApplication.csproj new file mode 100644 index 0000000000..42c0c2eccd --- /dev/null +++ b/src/iOS/Perspex.iOSTestApplication/Perspex.iOSTestApplication.csproj @@ -0,0 +1,197 @@ + + + + Debug + iPhoneSimulator + {8C923867-8A8F-4F6B-8B80-47D9E8436166} + {FEACFBD2-3405-455C-9665-78FE426C6842};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} + Exe + Perspex.iOSTestApplication + Resources + PerspexiOSTestApplication + + + true + full + false + bin\iPhoneSimulator\Debug + DEBUG + prompt + 4 + false + i386 + None + true + + + none + true + bin\iPhoneSimulator\Release + prompt + 4 + None + i386 + false + + + true + full + false + bin\iPhone\Debug + DEBUG + prompt + 4 + false + ARMv7, ARM64 + Entitlements.plist + iPhone Developer + True + 9.1 + SdkOnly + False + False + False + False + True + True + True + False + + + + none + true + bin\iPhone\Release + prompt + 4 + Entitlements.plist + ARMv7, ARM64 + false + iPhone Developer + + + none + True + bin\iPhone\Ad-Hoc + prompt + 4 + False + ARMv7, ARM64 + Entitlements.plist + True + Automatic:AdHoc + iPhone Distribution + + + none + True + bin\iPhone\AppStore + prompt + 4 + False + ARMv7, ARM64 + Entitlements.plist + Automatic:AppStore + iPhone Distribution + + + + + + + + + + + + + + + + ..\..\..\packages\Rx-Core.2.2.5\lib\portable-windows8+net45+wp8\System.Reactive.Core.dll + True + + + ..\..\..\packages\Rx-Interfaces.2.2.5\lib\portable-windows8+net45+wp8\System.Reactive.Interfaces.dll + True + + + ..\..\..\packages\Rx-Linq.2.2.5\lib\portable-windows8+net45+wp8\System.Reactive.Linq.dll + True + + + + + + + + + + + {3e53a01a-b331-47f3-b828-4a5717e77a24} + Perspex.Markup.Xaml + + + {6417e941-21bc-467b-a771-0de389353ce6} + Perspex.Markup + + + {d211e587-d8bc-45b9-95a4-f297c8fa5200} + Perspex.Animation + + + {799a7bb5-3c2c-48b6-85a7-406a12c420da} + Perspex.Application + + + {b09b78d8-9b26-48b0-9149-d64a2f120f3f} + Perspex.Base + + + {d2221c82-4a25-4583-9b43-d791e3f6820c} + Perspex.Controls + + + {5fb2b005-0a7f-4dad-add4-3ed01444e63d} + Perspex.HtmlRenderer + + + {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 + + + {3e10a5fa-e8da-48b1-ad44-6a5b6cb7750f} + Perspex.Themes.Default + + + {47be08a7-5985-410b-9ffc-2264b8ea595f} + Perspex.Skia.iOS + false + false + + + {4488ad85-1495-4809-9aa4-ddfe0a48527e} + Perspex.iOS + false + false + + + + + \ No newline at end of file diff --git a/src/iOS/Perspex.iOSTestApplication/Properties/AssemblyInfo.cs b/src/iOS/Perspex.iOSTestApplication/Properties/AssemblyInfo.cs new file mode 100644 index 0000000000..2505a45c78 --- /dev/null +++ b/src/iOS/Perspex.iOSTestApplication/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.iOSTestApplication")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("Perspex.iOSTestApplication")] +[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("8c923867-8a8f-4f6b-8b80-47d9e8436166")] + +// 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/src/iOS/Perspex.iOSTestApplication/Resources/Default-568h@2x.png b/src/iOS/Perspex.iOSTestApplication/Resources/Default-568h@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..29973dcbed50cb67f5f522d4fd9cb5caddda6cc4 GIT binary patch literal 2215 zcmeAS@N?(olHy`uVBq!ia0y~yU}|7sU@72W0*ZWnTNDnYI14-?iy0WWg+Z8+Vb&Z8 z1_q8uPZ!6Kid%0F8ZrWv99r$Ma}qo=E%%Q~loCIFNg8Dsze literal 0 HcmV?d00001 diff --git a/src/iOS/Perspex.iOSTestApplication/packages.config b/src/iOS/Perspex.iOSTestApplication/packages.config new file mode 100644 index 0000000000..4488cb8b7d --- /dev/null +++ b/src/iOS/Perspex.iOSTestApplication/packages.config @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file From d1badd0dbbd9a6fab9405dd99e1c057f75c6a604 Mon Sep 17 00:00:00 2001 From: Nikita Tsukanov Date: Tue, 24 Nov 2015 23:34:22 +0300 Subject: [PATCH 008/211] Reduced memory traffic --- src/Perspex.Base/Threading/DispatcherTimer.cs | 12 ++++--- .../Media/DrawingContext.cs | 17 ++++++++-- .../Rendering/RendererBase.cs | 33 ++++++++++++++++++- src/Skia/Perspex.Skia/FormattedTextImpl.cs | 2 ++ 4 files changed, 57 insertions(+), 7 deletions(-) diff --git a/src/Perspex.Base/Threading/DispatcherTimer.cs b/src/Perspex.Base/Threading/DispatcherTimer.cs index 159151dc79..d84bcf519e 100644 --- a/src/Perspex.Base/Threading/DispatcherTimer.cs +++ b/src/Perspex.Base/Threading/DispatcherTimer.cs @@ -18,12 +18,13 @@ namespace Perspex.Threading private TimeSpan _interval; + private readonly Action _raiseTickAction; + /// /// Initializes a new instance of the class. /// - public DispatcherTimer() + public DispatcherTimer() : this(DispatcherPriority.Normal) { - _priority = DispatcherPriority.Normal; } /// @@ -34,6 +35,7 @@ namespace Perspex.Threading public DispatcherTimer(DispatcherPriority priority) { _priority = priority; + _raiseTickAction = RaiseTick; } /// @@ -43,7 +45,7 @@ namespace Perspex.Threading /// The priority to use. /// The dispatcher to use. /// The event to call when the timer ticks. - public DispatcherTimer(TimeSpan interval, DispatcherPriority priority, EventHandler callback) + public DispatcherTimer(TimeSpan interval, DispatcherPriority priority, EventHandler callback) : this(priority) { _priority = priority; Interval = interval; @@ -172,12 +174,14 @@ namespace Perspex.Threading } } + + /// /// Raises the event on the dispatcher thread. /// private void InternalTick() { - Dispatcher.UIThread.InvokeAsync(RaiseTick, _priority); + Dispatcher.UIThread.InvokeAsync(_raiseTickAction, _priority); } /// diff --git a/src/Perspex.SceneGraph/Media/DrawingContext.cs b/src/Perspex.SceneGraph/Media/DrawingContext.cs index bd5b11a19f..28cfeb77a7 100644 --- a/src/Perspex.SceneGraph/Media/DrawingContext.cs +++ b/src/Perspex.SceneGraph/Media/DrawingContext.cs @@ -13,8 +13,17 @@ namespace Perspex.Media private readonly IDrawingContextImpl _impl; private int _currentLevel; - private Stack _transformContainers = new Stack(); - private Stack _states = new Stack(); + + + static readonly Stack> StateStackPool = new Stack>(); + static readonly Stack> TransformStackPool = new Stack>(); + + private Stack _states = StateStackPool.Count == 0 ? new Stack() : StateStackPool.Pop(); + + private Stack _transformContainers = TransformStackPool.Count == 0 + ? new Stack() + : TransformStackPool.Pop(); + struct TransformContainer { public Matrix LocalTransform; @@ -206,6 +215,10 @@ namespace Perspex.Media { while (_states.Count != 0) _states.Peek().Dispose(); + StateStackPool.Push(_states); + _states = null; + TransformStackPool.Push(_transformContainers); + _transformContainers = null; _impl.Dispose(); } } diff --git a/src/Perspex.SceneGraph/Rendering/RendererBase.cs b/src/Perspex.SceneGraph/Rendering/RendererBase.cs index 7a7db8f842..9ebe830f90 100644 --- a/src/Perspex.SceneGraph/Rendering/RendererBase.cs +++ b/src/Perspex.SceneGraph/Rendering/RendererBase.cs @@ -2,6 +2,8 @@ // Licensed under the MIT license. See licence.md file in the project root for full license information. using System; +using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; using System.Linq; using Perspex.Media; using Perspex.Platform; @@ -14,6 +16,8 @@ namespace Perspex.Rendering /// /// This class provides implements the platform-independent parts of . /// + [SuppressMessage("ReSharper", "LoopCanBeConvertedToQuery")] + [SuppressMessage("ReSharper", "ForCanBeConvertedToForeach")] public static class RendererMixin { /// @@ -58,12 +62,39 @@ namespace Perspex.Rendering using (context.PushTransformContainer()) { visual.Render(context); - foreach (var child in visual.VisualChildren.OrderBy(x => x.ZIndex)) + var lst = GetSortedVisualList(visual.VisualChildren); + foreach (var child in lst) { context.Render(child); } + ReturnListToPool(lst); } } } + + static readonly Stack> ListPool = new Stack>(); + static readonly ZIndexComparer VisualComparer = new ZIndexComparer(); + class ZIndexComparer : IComparer + { + public int Compare(IVisual x, IVisual y) => x.ZIndex.CompareTo(y.ZIndex); + } + + static void ReturnListToPool(List lst) + { + lst.Clear(); + ListPool.Push(lst); + } + + static List GetSortedVisualList(IReadOnlyList source) + { + var lst = ListPool.Count == 0 ? new List() : ListPool.Pop(); + for (var c = 0; c < source.Count; c++) + lst.Add(source[c]); + lst.Sort(VisualComparer); + return lst; + } + + + } } diff --git a/src/Skia/Perspex.Skia/FormattedTextImpl.cs b/src/Skia/Perspex.Skia/FormattedTextImpl.cs index 211da7bbc1..5e267099b5 100644 --- a/src/Skia/Perspex.Skia/FormattedTextImpl.cs +++ b/src/Skia/Perspex.Skia/FormattedTextImpl.cs @@ -114,6 +114,8 @@ namespace Perspex.Skia get { return _constraint; } set { + if(_constraint == value) + return; _constraint = value; _shared->WidthConstraint = (_constraint.Width != double.PositiveInfinity) From fb7ab444a3b53b819689cfd7a5b135e328226dcf Mon Sep 17 00:00:00 2001 From: Nikita Tsukanov Date: Wed, 25 Nov 2015 03:56:23 +0300 Subject: [PATCH 009/211] Updated testapp --- samples/TestApplicationShared/MainWindow.cs | 43 ++++++++++++++++++--- 1 file changed, 38 insertions(+), 5 deletions(-) diff --git a/samples/TestApplicationShared/MainWindow.cs b/samples/TestApplicationShared/MainWindow.cs index 6a412a95b5..2a0c736984 100644 --- a/samples/TestApplicationShared/MainWindow.cs +++ b/samples/TestApplicationShared/MainWindow.cs @@ -16,6 +16,7 @@ using Perspex.Layout; using Perspex.Media; using Perspex.Media.Imaging; using Perspex.Platform; +using Perspex.Threading; using TestApplication; namespace TestApplication @@ -747,12 +748,44 @@ namespace TestApplication VerticalAlignment = VerticalAlignment.Center, Background = Brushes.Crimson, RenderTransform = new RotateTransform(), - Child = new TextBox + Child = new Grid { - Background = Brushes.White, - Text = "Hello!", - HorizontalAlignment = HorizontalAlignment.Center, - VerticalAlignment = VerticalAlignment.Center, + Children = new Controls + { + new Ellipse() + { + Width = 100, + Height = 100, + Fill = + new RadialGradientBrush() + { + GradientStops = + { + new GradientStop(Colors.Blue, 0), + new GradientStop(Colors.Green, 1) + }, + Radius = 75 + } + }, + new Perspex.Controls.Shapes.Path + { + Data = + StreamGeometry.Parse( + "F1 M 16.6309,18.6563C 17.1309,8.15625 29.8809,14.1563 29.8809,14.1563C 30.8809,11.1563 34.1308,11.4063 34.1308,11.4063C 33.5,12 34.6309,13.1563 34.6309,13.1563C 32.1309,13.1562 31.1309,14.9062 31.1309,14.9062C 41.1309,23.9062 32.6309,27.9063 32.6309,27.9062C 24.6309,24.9063 21.1309,22.1562 16.6309,18.6563 Z M 16.6309,19.9063C 21.6309,24.1563 25.1309,26.1562 31.6309,28.6562C 31.6309,28.6562 26.3809,39.1562 18.3809,36.1563C 18.3809,36.1563 18,38 16.3809,36.9063C 15,36 16.3809,34.9063 16.3809,34.9063C 16.3809,34.9063 10.1309,30.9062 16.6309,19.9063 Z"), + Fill = + new LinearGradientBrush() + { + GradientStops = + { + new GradientStop(Colors.Green, 0), + new GradientStop(Colors.LightSeaGreen, 1) + } + }, + HorizontalAlignment = HorizontalAlignment.Center, + VerticalAlignment = VerticalAlignment.Center, + RenderTransform = new MatrixTransform(Matrix.CreateScale(2, 2)) + } + } }, [Canvas.LeftProperty] = 100, [Canvas.TopProperty] = 100, From 2462930a91a4690fee234a0abbf9e8e78ed8ecbd Mon Sep 17 00:00:00 2001 From: Nikita Tsukanov Date: Wed, 25 Nov 2015 00:57:03 +0300 Subject: [PATCH 010/211] Added /src/Skia/getnatives.sh --- src/Skia/getnatives.sh | 7 +++++++ 1 file changed, 7 insertions(+) create mode 100755 src/Skia/getnatives.sh diff --git a/src/Skia/getnatives.sh b/src/Skia/getnatives.sh new file mode 100755 index 0000000000..7ceace9db3 --- /dev/null +++ b/src/Skia/getnatives.sh @@ -0,0 +1,7 @@ +#!/bin/sh +rm -rf native +mkdir -p native +cd native +wget `cat ../native.url` -O native.zip +unzip native.zip + From 40b376591688bba053a57d035a1418c479567064 Mon Sep 17 00:00:00 2001 From: Nikita Tsukanov Date: Wed, 25 Nov 2015 01:12:07 +0300 Subject: [PATCH 011/211] Use single CFDisplayLink for everything --- src/Skia/Perspex.Skia.iOS/SkiaView.cs | 9 ++- src/iOS/Perspex.iOS/PerspexAppDelegate.cs | 2 +- src/iOS/Perspex.iOS/PerspexView.cs | 2 +- .../Perspex.iOS/PlatformThreadingInterface.cs | 74 +++++++++++++++---- 4 files changed, 71 insertions(+), 16 deletions(-) diff --git a/src/Skia/Perspex.Skia.iOS/SkiaView.cs b/src/Skia/Perspex.Skia.iOS/SkiaView.cs index e804dccdd9..7d4efea4aa 100644 --- a/src/Skia/Perspex.Skia.iOS/SkiaView.cs +++ b/src/Skia/Perspex.Skia.iOS/SkiaView.cs @@ -1,5 +1,7 @@ using System; using System.Collections.Generic; +using System.Diagnostics; +using System.IO; using System.Runtime.InteropServices; using System.Text; using CoreAnimation; @@ -31,11 +33,16 @@ namespace Perspex.Skia.iOS } + protected SkiaView(Action registerFrame) : base(UIScreen.MainScreen.ApplicationFrame, GetContext()) + { + registerFrame(OnFrame); + } + protected SkiaView() : base(UIScreen.MainScreen.ApplicationFrame, GetContext()) { (_link = CADisplayLink.Create(() => OnFrame())).AddToRunLoop(NSRunLoop.Main, NSRunLoop.NSDefaultRunLoopMode); } - + protected void OnFrame() { if (_drawQueued) diff --git a/src/iOS/Perspex.iOS/PerspexAppDelegate.cs b/src/iOS/Perspex.iOS/PerspexAppDelegate.cs index 180d58a34e..c0296d077c 100644 --- a/src/iOS/Perspex.iOS/PerspexAppDelegate.cs +++ b/src/iOS/Perspex.iOS/PerspexAppDelegate.cs @@ -41,7 +41,7 @@ namespace Perspex.iOS .Bind().ToConstant(KeyboardDevice) .Bind().ToConstant(MouseDevice) .Bind().ToSingleton() - .Bind().ToConstant(new PlatformThreadingInterface()) + .Bind().ToConstant(PlatformThreadingInterface.Instance) .Bind().ToConstant(controller.PerspexView); SkiaPlatform.Initialize(); }); diff --git a/src/iOS/Perspex.iOS/PerspexView.cs b/src/iOS/Perspex.iOS/PerspexView.cs index be70fefd13..1bea986c98 100644 --- a/src/iOS/Perspex.iOS/PerspexView.cs +++ b/src/iOS/Perspex.iOS/PerspexView.cs @@ -23,7 +23,7 @@ namespace Perspex.iOS private readonly UIViewController _controller; private IInputRoot _inputRoot; - public PerspexView(UIWindow window, UIViewController controller) + public PerspexView(UIWindow window, UIViewController controller) : base(onFrame => PlatformThreadingInterface.Instance.Render = onFrame) { if (controller == null) throw new ArgumentNullException(nameof(controller)); _window = window; diff --git a/src/iOS/Perspex.iOS/PlatformThreadingInterface.cs b/src/iOS/Perspex.iOS/PlatformThreadingInterface.cs index 00e2474238..8c6aa4b2de 100644 --- a/src/iOS/Perspex.iOS/PlatformThreadingInterface.cs +++ b/src/iOS/Perspex.iOS/PlatformThreadingInterface.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Diagnostics; using System.Linq; using System.Reactive.Disposables; using System.Text; @@ -13,26 +14,63 @@ namespace Perspex.iOS { class PlatformThreadingInterface : IPlatformThreadingInterface { - readonly List _timers = new List(); + static Stopwatch St = Stopwatch.StartNew(); + class Timer + { + readonly Action _tick; + readonly TimeSpan _interval; + TimeSpan _nextTick; + + public Timer(Action tick, TimeSpan interval) + { + _tick = tick; + _interval = interval; + _nextTick = St.Elapsed + _interval; + } + + public void Tick(TimeSpan now) + { + if (now > _nextTick) + { + _nextTick = now + _interval; + _tick(); + } + } + } + + readonly List _timers = new List(); bool _signaled; + readonly object _lock = new object(); + private CADisplayLink _link; + public Action Render { get; set; } - public PlatformThreadingInterface() + PlatformThreadingInterface() { - CADisplayLink.Create(OnFrame).AddToRunLoop(NSRunLoop.Main, NSRunLoop.NSDefaultRunLoopMode); + // For some reason it doesn't work when I specify OnFrame method directly + // ReSharper disable once ConvertClosureToMethodGroup + (_link = CADisplayLink.Create(() => OnFrame())).AddToRunLoop(NSRunLoop.Main, NSRunLoop.NSDefaultRunLoopMode); } private void OnFrame() { - foreach (var timer in _timers.ToList()) - { - timer(); - } + var now = St.Elapsed; + List timers; + lock (_lock) + timers = _timers.ToList(); + + foreach (var timer in timers) + timer.Tick(now); - if (_signaled) + do { - _signaled = false; + lock (_lock) + if (!_signaled) + break; + else + _signaled = false; Signaled?.Invoke(); - } + } while (false); + Render?.Invoke(); } public void RunLoop(CancellationToken cancellationToken) @@ -41,16 +79,26 @@ namespace Perspex.iOS public IDisposable StartTimer(TimeSpan interval, Action tick) { - _timers.Add(tick); - return Disposable.Create(() => _timers.Remove(tick)); + lock (_lock) + { + var timer = new Timer(tick, interval); + _timers.Add(timer); + return Disposable.Create(() => + { + lock (_lock) _timers.Remove(timer); + }); + } } public void Signal() { - _signaled = true; + lock (_lock) + _signaled = true; } public bool CurrentThreadIsLoopThread => NSThread.Current.IsMainThread; + public static PlatformThreadingInterface Instance { get; } = new PlatformThreadingInterface(); + public event Action Signaled; } } From c81d4cc599986cec4b1af45860a2d82cd709487e Mon Sep 17 00:00:00 2001 From: Nikita Tsukanov Date: Wed, 25 Nov 2015 02:08:20 +0300 Subject: [PATCH 012/211] FPS counter is back --- .../Rendering/RendererBase.cs | 33 ++++++++++++++++++- 1 file changed, 32 insertions(+), 1 deletion(-) diff --git a/src/Perspex.SceneGraph/Rendering/RendererBase.cs b/src/Perspex.SceneGraph/Rendering/RendererBase.cs index 9ebe830f90..8b0aa1aff8 100644 --- a/src/Perspex.SceneGraph/Rendering/RendererBase.cs +++ b/src/Perspex.SceneGraph/Rendering/RendererBase.cs @@ -3,6 +3,7 @@ using System; using System.Collections.Generic; +using System.Diagnostics; using System.Diagnostics.CodeAnalysis; using System.Linq; using Perspex.Media; @@ -20,6 +21,11 @@ namespace Perspex.Rendering [SuppressMessage("ReSharper", "ForCanBeConvertedToForeach")] public static class RendererMixin { + static int s_frameNum; + static int s_fps; + static int s_currentFrames; + static TimeSpan s_lastMeasure; + static Stopwatch s_stopwatch = Stopwatch.StartNew(); /// /// Renders the specified visual. /// @@ -28,10 +34,35 @@ namespace Perspex.Rendering public static void Render(this IRenderTarget renderTarget, IVisual visual) { using (var ctx = renderTarget.CreateDrawingContext()) + { ctx.Render(visual); + s_frameNum++; + if (DrawFpsCounter) + { + s_currentFrames++; + var now = s_stopwatch.Elapsed; + var elapsed = now - s_lastMeasure; + if (elapsed.TotalSeconds > 0) + { + s_fps = (int) (s_currentFrames/elapsed.TotalSeconds); + s_currentFrames = 0; + s_lastMeasure = now; + } + var pt = new Point(40, 40); + using ( + var txt = new FormattedText("Frame #" + s_frameNum + " FPS: " + s_fps, "Arial", 18, + FontStyle.Normal, + TextAlignment.Left, + FontWeight.Normal)) + { + ctx.FillRectangle(Brushes.White, new Rect(pt, txt.Measure())); + ctx.DrawText(Brushes.Black, pt, txt); + } + } + } } - + public static bool DrawFpsCounter { get; set; } /// /// Renders the specified visual. From aa7787f6897ff5a0bfed17dae3810c36edfa71b8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mitja=20Bezen=C5=A1ek?= Date: Tue, 24 Nov 2015 22:43:37 +0100 Subject: [PATCH 013/211] Update build.md Add information about downloading the Skia native libraries via the supplied script. --- docs/build.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/docs/build.md b/docs/build.md index 41d8f965f3..157af98fa2 100644 --- a/docs/build.md +++ b/docs/build.md @@ -18,6 +18,8 @@ We currently need to build our own private version of ReactiveUI as it doesn't w is linked as a submodule in the git repository, so run: git submodule update --init + +The next step is to download the Skia native libraries. Run ```getnatives.ps1``` PowerShell script which can be found under the folder ```Perspex\src\Skia\```. ## Linux @@ -43,6 +45,8 @@ We currently need to build our own private version of ReactiveUI as it doesn't w is linked as a submodule in the git repository, so run: git submodule update --init + +The next step is to download the Skia native libraries. Run ```getnatives.sh``` script which can be found under the folder ```Perspex\src\Skia\```. ### Load the Project in MonoDevelop From d360e61780df43aae8bd5cc0a7deb2229177b7b8 Mon Sep 17 00:00:00 2001 From: ReadmeCritic Date: Tue, 24 Nov 2015 23:37:45 -0800 Subject: [PATCH 014/211] Update README URLs based on HTTP redirects --- readme.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/readme.md b/readme.md index 4f15933968..ee5ec5bab7 100644 --- a/readme.md +++ b/readme.md @@ -1,5 +1,5 @@ # 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) +[![Gitter](https://badges.gitter.im/Join Chat.svg)](https://gitter.im/Perspex/Perspex?utm_campaign=pr-badge&utm_content=badge&utm_medium=badge&utm_source=badge) [![Build status](https://ci.appveyor.com/api/projects/status/hubk3k0w9idyibfg/branch/master?svg=true)](https://ci.appveyor.com/project/Perspex/Perspex/branch/master) @@ -39,7 +39,7 @@ framework. As mentioned above, Perspex is still in alpha and as such there's not much documentation yet. You can take a look at the [getting started page](docs/gettingstarted.md) for an overview of how to get started but probably the best thing to do for now is to already know a little bit -about WPF/Silverlight/UWP/XAML and ask questions in our [Gitter room](https://gitter.im/grokys/Perspex). +about WPF/Silverlight/UWP/XAML and ask questions in our [Gitter room](https://gitter.im/Perspex/Perspex). There's also a high-level [architecture document](docs/architecture.md) that is currently a little bit out of date, and I've also started writing blog posts on Perspex at http://grokys.github.io/. From 1380f39cfd2d6fa41f8289501187538c7df0f82e Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Wed, 25 Nov 2015 00:07:16 +0100 Subject: [PATCH 015/211] Don't get caret position unless needed. --- src/Perspex.Controls/Presenters/TextPresenter.cs | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/Perspex.Controls/Presenters/TextPresenter.cs b/src/Perspex.Controls/Presenters/TextPresenter.cs index 4b68d160e5..6aabc6836f 100644 --- a/src/Perspex.Controls/Presenters/TextPresenter.cs +++ b/src/Perspex.Controls/Presenters/TextPresenter.cs @@ -91,9 +91,7 @@ namespace Perspex.Controls.Presenters base.Render(context); if (selectionStart == selectionEnd) - { - var charPos = FormattedText.HitTestTextPosition(CaretIndex); - + { var backgroundColor = (((Control)TemplatedParent).GetValue(BackgroundProperty) as SolidColorBrush)?.Color; var caretBrush = Brushes.Black; @@ -108,6 +106,7 @@ namespace Perspex.Controls.Presenters if (_caretBlink) { + var charPos = FormattedText.HitTestTextPosition(CaretIndex); var x = Math.Floor(charPos.X) + 0.5; var y = Math.Floor(charPos.Y) + 0.5; var b = Math.Ceiling(charPos.Bottom) - 0.5; From 8ed00230addfec01b470bd32a443a268e82d8600 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Wed, 25 Nov 2015 00:16:21 +0100 Subject: [PATCH 016/211] Fixed wonky formatting. --- src/Windows/Perspex.Direct2D1/Media/FormattedTextImpl.cs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/Windows/Perspex.Direct2D1/Media/FormattedTextImpl.cs b/src/Windows/Perspex.Direct2D1/Media/FormattedTextImpl.cs index 6ace607107..1368a574fd 100644 --- a/src/Windows/Perspex.Direct2D1/Media/FormattedTextImpl.cs +++ b/src/Windows/Perspex.Direct2D1/Media/FormattedTextImpl.cs @@ -53,9 +53,7 @@ namespace Perspex.Direct2D1.Media } } - public DWrite.TextLayout TextLayout - { - get; } + public DWrite.TextLayout TextLayout { get; } public void Dispose() { From 14cf0319bd490d59a2b1eb663b95ff7292b2f2f4 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Wed, 25 Nov 2015 00:41:06 +0100 Subject: [PATCH 017/211] Added ScrollViewer offset coercion unit test. --- tests/Perspex.Controls.UnitTests/ScrollViewerTests.cs | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/tests/Perspex.Controls.UnitTests/ScrollViewerTests.cs b/tests/Perspex.Controls.UnitTests/ScrollViewerTests.cs index 4774a47706..563b270605 100644 --- a/tests/Perspex.Controls.UnitTests/ScrollViewerTests.cs +++ b/tests/Perspex.Controls.UnitTests/ScrollViewerTests.cs @@ -45,6 +45,17 @@ namespace Perspex.Controls.UnitTests Assert.Equal(target, presenter.TemplatedParent); } + [Fact] + public void Offset_Should_Be_Coerced_To_Viewport() + { + var target = new ScrollViewer(); + target.SetValue(ScrollViewer.ExtentProperty, new Size(20, 20)); + target.SetValue(ScrollViewer.ViewportProperty, new Size(10, 10)); + target.Offset = new Vector(12, 12); + + Assert.Equal(new Vector(10, 10), target.Offset); + } + private Control CreateTemplate(ScrollViewer control) { return new Grid From fa872389c39bed88078d328a7b60496d83fe09e6 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Wed, 25 Nov 2015 00:51:02 +0100 Subject: [PATCH 018/211] Make ScrollContentViewer.Offset get coerced. Use the same logic as for ScrollViewer. --- .../Presenters/ScrollContentPresenter.cs | 9 +++++++++ src/Perspex.Controls/ScrollViewer.cs | 11 ++++++++--- .../Presenters/ScrollContentPresenterTests.cs | 13 ++++++++++++- 3 files changed, 29 insertions(+), 4 deletions(-) diff --git a/src/Perspex.Controls/Presenters/ScrollContentPresenter.cs b/src/Perspex.Controls/Presenters/ScrollContentPresenter.cs index f9e870ea1d..f2ff1e3d5d 100644 --- a/src/Perspex.Controls/Presenters/ScrollContentPresenter.cs +++ b/src/Perspex.Controls/Presenters/ScrollContentPresenter.cs @@ -28,6 +28,7 @@ namespace Perspex.Controls.Presenters static ScrollContentPresenter() { ClipToBoundsProperty.OverrideDefaultValue(typeof(ScrollContentPresenter), true); + OffsetProperty.OverrideValidation(ValidateOffset); AffectsArrange(OffsetProperty); } @@ -143,5 +144,13 @@ namespace Perspex.Controls.Presenters Offset = offset; } + + private static Vector ValidateOffset(ScrollContentPresenter o, Vector value) + { + return ScrollViewer.CoerceOffset( + o.GetValue(ExtentProperty), + o.GetValue(ViewportProperty), + value); + } } } diff --git a/src/Perspex.Controls/ScrollViewer.cs b/src/Perspex.Controls/ScrollViewer.cs index 4bc46dcb8b..2d7da90ac1 100644 --- a/src/Perspex.Controls/ScrollViewer.cs +++ b/src/Perspex.Controls/ScrollViewer.cs @@ -124,6 +124,13 @@ namespace Perspex.Controls set { SetValue(VerticalScrollBarVisibilityProperty, value); } } + internal static Vector CoerceOffset(Size extent, Size viewport, Vector offset) + { + var maxX = Math.Max(extent.Width - viewport.Width, 0); + var maxY = Math.Max(extent.Height - viewport.Height, 0); + return new Vector(Clamp(offset.X, 0, maxX), Clamp(offset.Y, 0, maxY)); + } + protected override Size MeasureOverride(Size availableSize) { return base.MeasureOverride(availableSize); @@ -148,9 +155,7 @@ namespace Perspex.Controls { var extent = scrollViewer.Extent; var viewport = scrollViewer.Viewport; - var maxX = Math.Max(extent.Width - viewport.Width, 0); - var maxY = Math.Max(extent.Height - viewport.Height, 0); - return new Vector(Clamp(value.X, 0, maxX), Clamp(value.Y, 0, maxY)); + return CoerceOffset(extent, viewport, value); } else { diff --git a/tests/Perspex.Controls.UnitTests/Presenters/ScrollContentPresenterTests.cs b/tests/Perspex.Controls.UnitTests/Presenters/ScrollContentPresenterTests.cs index 706790bab0..38d6ca5e1c 100644 --- a/tests/Perspex.Controls.UnitTests/Presenters/ScrollContentPresenterTests.cs +++ b/tests/Perspex.Controls.UnitTests/Presenters/ScrollContentPresenterTests.cs @@ -167,7 +167,7 @@ namespace Perspex.Controls.UnitTests.Presenters { var target = new ScrollContentPresenter { - Content = new Border { Width = 40, Height = 50 } + Content = new Border { Width = 140, Height = 150 } }; target.Measure(new Size(100, 100)); @@ -178,6 +178,17 @@ namespace Perspex.Controls.UnitTests.Presenters Assert.False(target.IsArrangeValid); } + [Fact] + public void Offset_Should_Be_Coerced_To_Viewport() + { + var target = new ScrollContentPresenter(); + target.SetValue(ScrollContentPresenter.ExtentProperty, new Size(20, 20)); + target.SetValue(ScrollContentPresenter.ViewportProperty, new Size(10, 10)); + target.Offset = new Vector(12, 12); + + Assert.Equal(new Vector(10, 10), target.Offset); + } + private class TestControl : Control { protected override Size MeasureOverride(Size availableSize) From 96290423c64851ec62bfbee0175647dce89ebeb8 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Wed, 25 Nov 2015 01:43:39 +0100 Subject: [PATCH 019/211] Respect padding in ScrollViewer. --- src/Perspex.Themes.Default/ScrollViewer.paml | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Perspex.Themes.Default/ScrollViewer.paml b/src/Perspex.Themes.Default/ScrollViewer.paml index a459b11536..becdaab70e 100644 --- a/src/Perspex.Themes.Default/ScrollViewer.paml +++ b/src/Perspex.Themes.Default/ScrollViewer.paml @@ -5,6 +5,7 @@ From 4ca4238dfe87a9bc137bbb81852df2e02bad3edb Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Wed, 25 Nov 2015 02:15:15 +0100 Subject: [PATCH 020/211] Delay changing TextBox caret pos til after measure. --- .../Presenters/TextPresenter.cs | 20 +++++++++++++++++-- 1 file changed, 18 insertions(+), 2 deletions(-) diff --git a/src/Perspex.Controls/Presenters/TextPresenter.cs b/src/Perspex.Controls/Presenters/TextPresenter.cs index 6aabc6836f..8c25fc5a4f 100644 --- a/src/Perspex.Controls/Presenters/TextPresenter.cs +++ b/src/Perspex.Controls/Presenters/TextPresenter.cs @@ -142,8 +142,24 @@ namespace Perspex.Controls.Presenters _caretTimer.Start(); InvalidateVisual(); - var rect = FormattedText.HitTestTextPosition(caretIndex); - this.BringIntoView(rect); + if (IsMeasureValid) + { + var rect = FormattedText.HitTestTextPosition(caretIndex); + this.BringIntoView(rect); + } + else + { + // The measure is currently invalid so there's no point trying to bring the + // current char into view until a measure has been carried out as the scroll + // viewer extents may not be up-to-date. + Dispatcher.UIThread.InvokeAsync( + () => + { + var rect = FormattedText.HitTestTextPosition(caretIndex); + this.BringIntoView(rect); + }, + DispatcherPriority.Normal); + } } } From 2fbcb222c1a09030d53daec8dcb7307e4aaf5407 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Wed, 25 Nov 2015 20:02:03 +0100 Subject: [PATCH 021/211] Added doc comments for ScrollContentPresenter. And added BringDescendentIntoView method. --- .../Presenters/ScrollContentPresenter.cs | 107 +++++++++++++----- 1 file changed, 78 insertions(+), 29 deletions(-) diff --git a/src/Perspex.Controls/Presenters/ScrollContentPresenter.cs b/src/Perspex.Controls/Presenters/ScrollContentPresenter.cs index f2ff1e3d5d..5ee9cc160d 100644 --- a/src/Perspex.Controls/Presenters/ScrollContentPresenter.cs +++ b/src/Perspex.Controls/Presenters/ScrollContentPresenter.cs @@ -9,22 +9,40 @@ using Perspex.VisualTree; namespace Perspex.Controls.Presenters { + /// + /// Presents a scrolling view of content inside a . + /// public class ScrollContentPresenter : ContentPresenter, IPresenter { + /// + /// Defines the property. + /// public static readonly PerspexProperty ExtentProperty = ScrollViewer.ExtentProperty.AddOwner(); + /// + /// Defines the property. + /// public static readonly PerspexProperty OffsetProperty = ScrollViewer.OffsetProperty.AddOwner(); + /// + /// Defines the property. + /// public static readonly PerspexProperty ViewportProperty = ScrollViewer.ViewportProperty.AddOwner(); + /// + /// Defines the property. + /// public static readonly PerspexProperty CanScrollHorizontallyProperty = PerspexProperty.Register("CanScrollHorizontally", true); private Size _measuredExtent; + /// + /// Initializes static members of the class. + /// static ScrollContentPresenter() { ClipToBoundsProperty.OverrideDefaultValue(typeof(ScrollContentPresenter), true); @@ -32,31 +50,88 @@ namespace Perspex.Controls.Presenters AffectsArrange(OffsetProperty); } + /// + /// Initializes a new instance of the class. + /// public ScrollContentPresenter() { AddHandler(RequestBringIntoViewEvent, BringIntoViewRequested); } + /// + /// Gets the extent of the scrollable content. + /// public Size Extent { get { return GetValue(ExtentProperty); } private set { SetValue(ExtentProperty, value); } } + /// + /// Gets or sets the current scroll offset. + /// public Vector Offset { get { return GetValue(OffsetProperty); } set { SetValue(OffsetProperty, value); } } + /// + /// Gets the size of the viewport on the scrollable content. + /// public Size Viewport { get { return GetValue(ViewportProperty); } private set { SetValue(ViewportProperty, value); } } + /// + /// Gets a value indicating whether the content can be scrolled horizontally. + /// public bool CanScrollHorizontally => GetValue(CanScrollHorizontallyProperty); + /// + /// Attempts to bring a portion of the target visual into view by scrolling the content. + /// + /// The target visual. + /// The portion of the target visual to bring into view. + /// True if the scroll offset was changed; otherwise false. + public bool BringDescendentIntoView(IVisual target, Rect targetRect) + { + var transform = target.TransformToVisual(this.GetVisualChildren().Single()); + var rect = targetRect * transform; + var offset = Offset; + var result = false; + + if (rect.Bottom > offset.Y + Viewport.Height) + { + offset = offset.WithY(rect.Bottom - Viewport.Height); + result = true; + } + + if (rect.Y < offset.Y) + { + offset = offset.WithY(rect.Y); + result = true; + } + + if (rect.Right > offset.X + Viewport.Width) + { + offset = offset.WithX(rect.Right - Viewport.Width); + result = true; + } + + if (rect.X < offset.X) + { + offset = offset.WithX(rect.X); + result = true; + } + + Offset = offset; + return result; + } + + /// protected override Size MeasureOverride(Size availableSize) { var content = Content as ILayoutable; @@ -81,6 +156,7 @@ namespace Perspex.Controls.Presenters } } + /// protected override Size ArrangeOverride(Size finalSize) { var child = this.GetVisualChildren().SingleOrDefault() as ILayoutable; @@ -100,6 +176,7 @@ namespace Perspex.Controls.Presenters return new Size(); } + /// protected override void OnPointerWheelChanged(PointerWheelEventArgs e) { if (Extent.Height > Viewport.Height) @@ -114,35 +191,7 @@ namespace Perspex.Controls.Presenters private void BringIntoViewRequested(object sender, RequestBringIntoViewEventArgs e) { - var transform = e.TargetObject.TransformToVisual(this.GetVisualChildren().Single()); - var rect = e.TargetRect * transform; - var offset = Offset; - - if (rect.Bottom > offset.Y + Viewport.Height) - { - offset = offset.WithY(rect.Bottom - Viewport.Height); - e.Handled = true; - } - - if (rect.Y < offset.Y) - { - offset = offset.WithY(rect.Y); - e.Handled = true; - } - - if (rect.Right > offset.X + Viewport.Width) - { - offset = offset.WithX(rect.Right - Viewport.Width); - e.Handled = true; - } - - if (rect.X < offset.X) - { - offset = offset.WithX(rect.X); - e.Handled = true; - } - - Offset = offset; + e.Handled = BringDescendentIntoView(e.TargetObject, e.TargetRect); } private static Vector ValidateOffset(ScrollContentPresenter o, Vector value) From 6dc9ee31e094bf7b604ee7183d0b699ba0e28bb3 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Wed, 25 Nov 2015 20:10:28 +0100 Subject: [PATCH 022/211] Added doc comments for ScrollViewer. --- src/Perspex.Controls/ScrollViewer.cs | 118 ++++++++++++++++++++++++--- 1 file changed, 107 insertions(+), 11 deletions(-) diff --git a/src/Perspex.Controls/ScrollViewer.cs b/src/Perspex.Controls/ScrollViewer.cs index 2d7da90ac1..5af724a614 100644 --- a/src/Perspex.Controls/ScrollViewer.cs +++ b/src/Perspex.Controls/ScrollViewer.cs @@ -8,50 +8,127 @@ using Perspex.Controls.Primitives; namespace Perspex.Controls { + /// + /// A control scrolls its content if the content is bigger than the space available. + /// public class ScrollViewer : ContentControl { + /// + /// Defines the property. + /// + public static readonly PerspexProperty CanScrollHorizontallyProperty = + PerspexProperty.RegisterAttached(nameof(CanScrollHorizontally), true); + + /// + /// Defines the property. + /// public static readonly PerspexProperty ExtentProperty = - PerspexProperty.Register("Extent"); + PerspexProperty.Register(nameof(Extent)); + /// + /// Defines the property. + /// public static readonly PerspexProperty OffsetProperty = - PerspexProperty.Register("Offset", validate: ValidateOffset); + PerspexProperty.Register(nameof(Offset), validate: ValidateOffset); + /// + /// Defines the property. + /// public static readonly PerspexProperty ViewportProperty = - PerspexProperty.Register("Viewport"); - + PerspexProperty.Register(nameof(Viewport)); + + /// + /// Defines the HorizontalScrollBarMaximum property. + /// + /// + /// There is no C# accessor for this property as it is intended to be bound to by a + /// in the control's template. + /// public static readonly PerspexProperty HorizontalScrollBarMaximumProperty = PerspexProperty.Register("HorizontalScrollBarMaximum"); + /// + /// Defines the HorizontalScrollBarValue property. + /// + /// + /// There is no C# accessor for this property as it is intended to be bound to by a + /// in the control's template. + /// public static readonly PerspexProperty HorizontalScrollBarValueProperty = PerspexProperty.Register("HorizontalScrollBarValue"); + /// + /// Defines the HorizontalScrollBarViewportSize property. + /// + /// + /// There is no C# accessor for this property as it is intended to be bound to by a + /// in the control's template. + /// public static readonly PerspexProperty HorizontalScrollBarViewportSizeProperty = PerspexProperty.Register("HorizontalScrollBarViewportSize"); + /// + /// Defines the property. + /// + /// + /// There is no C# accessor for this property as it is intended to be bound to by a + /// in the control's template. + /// + public static readonly PerspexProperty HorizontalScrollBarVisibilityProperty = + PerspexProperty.RegisterAttached( + nameof(HorizontalScrollBarVisibility), + ScrollBarVisibility.Auto); + + /// + /// Defines the VerticalScrollBarMaximum property. + /// + /// + /// There is no C# accessor for this property as it is intended to be bound to by a + /// in the control's template. + /// public static readonly PerspexProperty VerticalScrollBarMaximumProperty = PerspexProperty.Register("VerticalScrollBarMaximum"); + /// + /// Defines the VerticalScrollBarValue property. + /// + /// + /// There is no C# accessor for this property as it is intended to be bound to by a + /// in the control's template. + /// public static readonly PerspexProperty VerticalScrollBarValueProperty = PerspexProperty.Register("VerticalScrollBarValue"); + /// + /// Defines the VerticalScrollBarViewportSize property. + /// + /// + /// There is no C# accessor for this property as it is intended to be bound to by a + /// in the control's template. + /// public static readonly PerspexProperty VerticalScrollBarViewportSizeProperty = PerspexProperty.Register("VerticalScrollBarViewportSize"); - public static readonly PerspexProperty CanScrollHorizontallyProperty = - PerspexProperty.RegisterAttached("CanScrollHorizontally", true); - - public static readonly PerspexProperty HorizontalScrollBarVisibilityProperty = - PerspexProperty.RegisterAttached("HorizontalScrollBarVisibility", ScrollBarVisibility.Auto); - + /// + /// Defines the property. + /// public static readonly PerspexProperty VerticalScrollBarVisibilityProperty = - PerspexProperty.RegisterAttached("VerticalScrollBarVisibility", ScrollBarVisibility.Auto); + PerspexProperty.RegisterAttached( + nameof(VerticalScrollBarVisibility), + ScrollBarVisibility.Auto); + /// + /// Initializes static members of the class. + /// static ScrollViewer() { AffectsValidation(ExtentProperty, OffsetProperty); AffectsValidation(ViewportProperty, OffsetProperty); } + /// + /// Initializes a new instance of the class. + /// public ScrollViewer() { var extentAndViewport = Observable.CombineLatest( @@ -88,36 +165,54 @@ namespace Perspex.Controls .Subscribe(x => Offset = x); } + /// + /// Gets the extent of the scrollable content. + /// public Size Extent { get { return GetValue(ExtentProperty); } private set { SetValue(ExtentProperty, value); } } + /// + /// Gets or sets the current scroll offset. + /// public Vector Offset { get { return GetValue(OffsetProperty); } set { SetValue(OffsetProperty, value); } } + /// + /// Gets the size of the viewport on the scrollable content. + /// public Size Viewport { get { return GetValue(ViewportProperty); } private set { SetValue(ViewportProperty, value); } } + /// + /// Gets a value indicating whether the content can be scrolled horizontally. + /// public bool CanScrollHorizontally { get { return GetValue(CanScrollHorizontallyProperty); } set { SetValue(CanScrollHorizontallyProperty, value); } } + /// + /// Gets or sets the horizontal scrollbar visibility. + /// public ScrollBarVisibility HorizontalScrollBarVisibility { get { return GetValue(HorizontalScrollBarVisibilityProperty); } set { SetValue(HorizontalScrollBarVisibilityProperty, value); } } + /// + /// Gets or sets the vertical scrollbar visibility. + /// public ScrollBarVisibility VerticalScrollBarVisibility { get { return GetValue(VerticalScrollBarVisibilityProperty); } @@ -131,6 +226,7 @@ namespace Perspex.Controls return new Vector(Clamp(offset.X, 0, maxX), Clamp(offset.Y, 0, maxY)); } + /// protected override Size MeasureOverride(Size availableSize) { return base.MeasureOverride(availableSize); From bd6b6bad67cf1cb0cb38c3358d12f85a1cd9f6cf Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Wed, 25 Nov 2015 20:31:28 +0100 Subject: [PATCH 023/211] Added test for SCV.BringDescendentIntoView --- src/Perspex.SceneGraph/Visual.cs | 29 +++++++++++++++++-- .../VisualTree/VisualExtensions.cs | 14 ++++++++- .../Presenters/ScrollContentPresenterTests.cs | 22 ++++++++++++++ 3 files changed, 62 insertions(+), 3 deletions(-) diff --git a/src/Perspex.SceneGraph/Visual.cs b/src/Perspex.SceneGraph/Visual.cs index 53c6b8e66d..f559fd0f6e 100644 --- a/src/Perspex.SceneGraph/Visual.cs +++ b/src/Perspex.SceneGraph/Visual.cs @@ -307,8 +307,9 @@ namespace Perspex /// A containing the transform. public Matrix TransformToVisual(IVisual visual) { - var thisOffset = GetOffsetFromRoot(this).Item2; - var thatOffset = GetOffsetFromRoot(visual).Item2; + var common = this.FindCommonVisualAncestor(visual); + var thisOffset = GetOffsetFrom(common, this); + var thatOffset = GetOffsetFrom(common, visual); return Matrix.CreateTranslation(-thatOffset) * Matrix.CreateTranslation(thisOffset); } @@ -468,6 +469,30 @@ namespace Perspex } } + /// + /// Gets the visual offset from the specified ancestor. + /// + /// The ancestor visual. + /// The visual. + /// The visual offset. + private static Vector GetOffsetFrom(IVisual ancestor, IVisual visual) + { + var result = new Vector(); + + while (visual != ancestor) + { + result = new Vector(result.X + visual.Bounds.X, result.Y + visual.Bounds.Y); + visual = visual.VisualParent; + + if (visual == null) + { + throw new ArgumentException("'visual' is not a descendent of 'ancestor'."); + } + } + + return result; + } + /// /// Gets the root of the controls visual tree and the distance from the root. /// diff --git a/src/Perspex.SceneGraph/VisualTree/VisualExtensions.cs b/src/Perspex.SceneGraph/VisualTree/VisualExtensions.cs index 901bf678f1..d08682c829 100644 --- a/src/Perspex.SceneGraph/VisualTree/VisualExtensions.cs +++ b/src/Perspex.SceneGraph/VisualTree/VisualExtensions.cs @@ -8,10 +8,22 @@ using System.Linq; namespace Perspex.VisualTree { /// - /// Provides extension methods for working with visual tree. + /// Provides extension methods for working with the visual tree. /// public static class VisualExtensions { + /// + /// Tries to get the first common ancestor of two visuals. + /// + /// The first visual. + /// The second visual. + /// The common ancestor, or null if not found. + public static IVisual FindCommonVisualAncestor(this IVisual visual, IVisual target) + { + return visual.GetSelfAndVisualAncestors().Intersect(target.GetSelfAndVisualAncestors()) + .FirstOrDefault(); + } + /// /// Enumerates the ancestors of an in the visual tree. /// diff --git a/tests/Perspex.Controls.UnitTests/Presenters/ScrollContentPresenterTests.cs b/tests/Perspex.Controls.UnitTests/Presenters/ScrollContentPresenterTests.cs index 38d6ca5e1c..9da287f4b9 100644 --- a/tests/Perspex.Controls.UnitTests/Presenters/ScrollContentPresenterTests.cs +++ b/tests/Perspex.Controls.UnitTests/Presenters/ScrollContentPresenterTests.cs @@ -189,6 +189,28 @@ namespace Perspex.Controls.UnitTests.Presenters Assert.Equal(new Vector(10, 10), target.Offset); } + [Fact] + public void BringDescendentIntoView_Should_Work() + { + var target = new ScrollContentPresenter + { + Width = 100, + Height = 100, + Content = new Border + { + Width = 200, + Height = 200, + } + }; + + target.ApplyTemplate(); + target.Measure(Size.Infinity); + target.Arrange(new Rect(0, 0, 100, 100)); + target.BringDescendentIntoView(target.Child, new Rect(200, 200, 0, 0)); + + Assert.Equal(new Vector(100, 100), target.Offset); + } + private class TestControl : Control { protected override Size MeasureOverride(Size availableSize) From fc90527fc851611fb5345c334d45fa39ac49bfbe Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Wed, 25 Nov 2015 20:58:54 +0100 Subject: [PATCH 024/211] Removed debug messagebox. --- src/Windows/Perspex.Designer/AppHost/PerspexAppHost.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/src/Windows/Perspex.Designer/AppHost/PerspexAppHost.cs b/src/Windows/Perspex.Designer/AppHost/PerspexAppHost.cs index a26808a507..8cc1dc5096 100644 --- a/src/Windows/Perspex.Designer/AppHost/PerspexAppHost.cs +++ b/src/Windows/Perspex.Designer/AppHost/PerspexAppHost.cs @@ -177,7 +177,6 @@ namespace Perspex.Designer.AppHost private void DoInit(string targetExe, StringBuilder logger) { - MessageBox.Show("WAT"); _appDir = Path.GetFullPath(Path.GetDirectoryName(targetExe)); Directory.SetCurrentDirectory(_appDir); Action log = s => From 1ac814e534cdecf2ef06542a23b5a46f32d0f061 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Wed, 25 Nov 2015 22:34:52 +0100 Subject: [PATCH 025/211] Make ScrollViewer handle child margin. --- .../Presenters/ScrollContentPresenter.cs | 63 +++++++++++-------- .../Presenters/ScrollContentPresenterTests.cs | 29 ++++++++- 2 files changed, 66 insertions(+), 26 deletions(-) diff --git a/src/Perspex.Controls/Presenters/ScrollContentPresenter.cs b/src/Perspex.Controls/Presenters/ScrollContentPresenter.cs index 5ee9cc160d..76ba8c1545 100644 --- a/src/Perspex.Controls/Presenters/ScrollContentPresenter.cs +++ b/src/Perspex.Controls/Presenters/ScrollContentPresenter.cs @@ -98,37 +98,50 @@ namespace Perspex.Controls.Presenters /// True if the scroll offset was changed; otherwise false. public bool BringDescendentIntoView(IVisual target, Rect targetRect) { - var transform = target.TransformToVisual(this.GetVisualChildren().Single()); - var rect = targetRect * transform; - var offset = Offset; - var result = false; - - if (rect.Bottom > offset.Y + Viewport.Height) + if (Child != null) { - offset = offset.WithY(rect.Bottom - Viewport.Height); - result = true; - } + var transform = target.TransformToVisual(Child); + var rect = targetRect * transform; + var offset = Offset; + var result = false; - if (rect.Y < offset.Y) - { - offset = offset.WithY(rect.Y); - result = true; - } + if (rect.Bottom > offset.Y + Viewport.Height) + { + offset = offset.WithY((rect.Bottom - Viewport.Height) + Child.Margin.Top); + result = true; + } - if (rect.Right > offset.X + Viewport.Width) - { - offset = offset.WithX(rect.Right - Viewport.Width); - result = true; - } + if (rect.Y < offset.Y) + { + offset = offset.WithY(rect.Y); + result = true; + } - if (rect.X < offset.X) + if (rect.Right > offset.X + Viewport.Width) + { + offset = offset.WithX((rect.Right - Viewport.Width) + Child.Margin.Left); + result = true; + } + + if (rect.X < offset.X) + { + offset = offset.WithX(rect.X); + result = true; + } + + if (result) + { + Offset = offset; + } + + System.Diagnostics.Debug.WriteLine($"{targetRect} {offset}"); + + return result; + } + else { - offset = offset.WithX(rect.X); - result = true; + return false; } - - Offset = offset; - return result; } /// diff --git a/tests/Perspex.Controls.UnitTests/Presenters/ScrollContentPresenterTests.cs b/tests/Perspex.Controls.UnitTests/Presenters/ScrollContentPresenterTests.cs index 9da287f4b9..eb913b3410 100644 --- a/tests/Perspex.Controls.UnitTests/Presenters/ScrollContentPresenterTests.cs +++ b/tests/Perspex.Controls.UnitTests/Presenters/ScrollContentPresenterTests.cs @@ -190,7 +190,7 @@ namespace Perspex.Controls.UnitTests.Presenters } [Fact] - public void BringDescendentIntoView_Should_Work() + public void BringDescendentIntoView_Should_Update_Offset() { var target = new ScrollContentPresenter { @@ -211,6 +211,33 @@ namespace Perspex.Controls.UnitTests.Presenters Assert.Equal(new Vector(100, 100), target.Offset); } + [Fact] + public void BringDescendentIntoView_Should_Handle_Child_Margin() + { + Border border; + var target = new ScrollContentPresenter + { + Width = 100, + Height = 100, + Content = new Decorator + { + Margin = new Thickness(50), + Child = border = new Border + { + Width = 200, + Height = 200, + } + } + }; + + target.ApplyTemplate(); + target.Measure(Size.Infinity); + target.Arrange(new Rect(0, 0, 100, 100)); + target.BringDescendentIntoView(border, new Rect(200, 200, 0, 0)); + + Assert.Equal(new Vector(150, 150), target.Offset); + } + private class TestControl : Control { protected override Size MeasureOverride(Size availableSize) From a697349fe0903c23cff4acc0bdf5b42d17fbeae9 Mon Sep 17 00:00:00 2001 From: Nikita Tsukanov Date: Wed, 25 Nov 2015 04:54:25 +0300 Subject: [PATCH 026/211] GTK's RadialGradientBrushImpl doesn't crash anymore but doesn't seem to work properly --- src/Gtk/Perspex.Cairo/Media/RadialGradientBrushImpl.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Gtk/Perspex.Cairo/Media/RadialGradientBrushImpl.cs b/src/Gtk/Perspex.Cairo/Media/RadialGradientBrushImpl.cs index a345f1869d..2d54882324 100644 --- a/src/Gtk/Perspex.Cairo/Media/RadialGradientBrushImpl.cs +++ b/src/Gtk/Perspex.Cairo/Media/RadialGradientBrushImpl.cs @@ -15,10 +15,10 @@ namespace Perspex.Cairo foreach (var stop in brush.GradientStops) { - ((LinearGradient)this.PlatformBrush).AddColorStop(stop.Offset, stop.Color.ToCairo()); + ((RadialGradient)this.PlatformBrush).AddColorStop(stop.Offset, stop.Color.ToCairo()); } - ((LinearGradient)this.PlatformBrush).Extend = Extend.Pad; + ((RadialGradient)this.PlatformBrush).Extend = Extend.Pad; } } } From de8b407be9daac93c5482318b65536d900386bdb Mon Sep 17 00:00:00 2001 From: Nikita Tsukanov Date: Thu, 26 Nov 2015 02:22:41 +0300 Subject: [PATCH 027/211] Packaging scripts refactoring --- nuget/build-appveyor.ps1 | 9 +++++---- nuget/build-version.ps1 | 35 +++++++++++++++++++---------------- nuget/include.ps1 | 1 + 3 files changed, 25 insertions(+), 20 deletions(-) create mode 100644 nuget/include.ps1 diff --git a/nuget/build-appveyor.ps1 b/nuget/build-appveyor.ps1 index ee38e950b7..fafafdc2bd 100644 --- a/nuget/build-appveyor.ps1 +++ b/nuget/build-appveyor.ps1 @@ -9,6 +9,7 @@ sv version $env:APPVEYOR_BUILD_NUMBER sv version 9999.0.$version-nightly sv key $env:myget_key +. "include.ps1" .\build-version.ps1 $version sv reponame $env:APPVEYOR_REPO_NAME @@ -23,10 +24,10 @@ if ([string]::IsNullOrWhiteSpace($pullreq)) if($repobranch -eq "master") { echo "Repo branch matched" - nuget.exe push Perspex.$version.nupkg $key -Source https://www.myget.org/F/perspex-nightly/api/v2/package - nuget.exe push Perspex.Desktop.$version.nupkg $key -Source https://www.myget.org/F/perspex-nightly/api/v2/package - nuget.exe push Perspex.Skia.Desktop.$version.nupkg $key -Source https://www.myget.org/F/perspex-nightly/api/v2/package - nuget.exe push Perspex.Android.$version.nupkg $key -Source https://www.myget.org/F/perspex-nightly/api/v2/package + foreach($pkg in $Packages) + { + nuget.exe push $pkg.$version.nupkg $key -Source https://www.myget.org/F/perspex-nightly/api/v2/package + } } } diff --git a/nuget/build-version.ps1 b/nuget/build-version.ps1 index 65b673385e..49bf6c6947 100644 --- a/nuget/build-version.ps1 +++ b/nuget/build-version.ps1 @@ -1,9 +1,11 @@ $ErrorActionPreference = "Stop" -rm -Force -Recurse .\Perspex -ErrorAction SilentlyContinue -rm -Force -Recurse .\Perspex.Desktop -ErrorAction SilentlyContinue -rm -Force -Recurse .\Perspex.Skia.Desktop -ErrorAction SilentlyContinue -rm -Force -Recurse .\Perspex.Android -ErrorAction SilentlyContinue +. ".\include.ps1" + +foreach($pkg in $Packages) +{ + rm -Force -Recurse .\$pkg -ErrorAction SilentlyContinue +} rm -Force -Recurse *.nupkg -ErrorAction SilentlyContinue Copy-Item template Perspex -Recurse @@ -65,17 +67,18 @@ Copy-Item ..\src\Skia\Perspex.Skia.Desktop\bin\Release\Perspex.Skia.Desktop.dll Copy-Item ..\src\Android\Perspex.Android\bin\Release\Perspex.Android.dll $android Copy-Item ..\src\Skia\Perspex.Skia.Android\bin\Release\Perspex.Skia.Android.dll $android -(gc Perspex\Perspex.nuspec).replace('#VERSION#', $args[0]) | sc Perspex\Perspex.nuspec -(gc Perspex\Perspex.Desktop.nuspec).replace('#VERSION#', $args[0]) | sc Perspex.Desktop\Perspex.Desktop.nuspec -(gc Perspex\Perspex.Skia.Desktop.nuspec).replace('#VERSION#', $args[0]) | sc Perspex.Skia.Desktop\Perspex.Skia.Desktop.nuspec -(gc Perspex\Perspex.Android.nuspec).replace('#VERSION#', $args[0]) | sc Perspex.Android\Perspex.Android.nuspec -nuget.exe pack Perspex\Perspex.nuspec -nuget.exe pack Perspex.Desktop\Perspex.Desktop.nuspec -nuget.exe pack Perspex.Skia.Desktop\Perspex.Skia.Desktop.nuspec -nuget.exe pack Perspex.Android\Perspex.Android.nuspec +foreach($pkg in $Packages) +{ + (gc Perspex\$pkg.nuspec).replace('#VERSION#', $args[0]) | sc $pkg\$pkg.nuspec +} + +foreach($pkg in $Packages) +{ + nuget.exe pack $pkg\$pkg.nuspec +} -rm -Force -Recurse .\Perspex -rm -Force -Recurse .\Perspex.Desktop -rm -Force -Recurse .\Perspex.Skia.Desktop -rm -Force -Recurse .\Perspex.Android +foreach($pkg in $Packages) +{ + rm -Force -Recurse .\$pkg +} \ No newline at end of file diff --git a/nuget/include.ps1 b/nuget/include.ps1 new file mode 100644 index 0000000000..1898105862 --- /dev/null +++ b/nuget/include.ps1 @@ -0,0 +1 @@ +$Packages = @("Perspex", "Perspex.Desktop", "Perspex.Skia.Desktop", "Perspex.Android") \ No newline at end of file From 043e1d5eaf0648d04f69348ea931c83d3d6ceebc Mon Sep 17 00:00:00 2001 From: Nikita Tsukanov Date: Thu, 26 Nov 2015 02:49:21 +0300 Subject: [PATCH 028/211] Added nuget package for iOS --- nuget/build-version.ps1 | 4 ++++ nuget/include.ps1 | 2 +- nuget/template/Perspex.iOS.nuspec | 27 +++++++++++++++++++++++++++ 3 files changed, 32 insertions(+), 1 deletion(-) create mode 100644 nuget/template/Perspex.iOS.nuspec diff --git a/nuget/build-version.ps1 b/nuget/build-version.ps1 index 49bf6c6947..59031527d5 100644 --- a/nuget/build-version.ps1 +++ b/nuget/build-version.ps1 @@ -16,11 +16,13 @@ sv skia_root "Perspex.Skia.Desktop" sv skia_lib "Perspex.Skia.Desktop\lib\net45" sv skia_native "Perspex.Skia.Desktop\build\net45\native" sv android "Perspex.Android\lib\MonoAndroid10" +sv ios "Perspex.iOS\lib\Xamarin.iOS10" mkdir $lib -ErrorAction SilentlyContinue mkdir $build -ErrorAction SilentlyContinue mkdir $skia_lib mkdir $android +mkdir $ios Copy-Item ..\src\Perspex.Animation\bin\Release\Perspex.Animation.dll $lib @@ -67,6 +69,8 @@ Copy-Item ..\src\Skia\Perspex.Skia.Desktop\bin\Release\Perspex.Skia.Desktop.dll Copy-Item ..\src\Android\Perspex.Android\bin\Release\Perspex.Android.dll $android Copy-Item ..\src\Skia\Perspex.Skia.Android\bin\Release\Perspex.Skia.Android.dll $android +Copy-Item ..\src\iOS\Perspex.iOS\bin\iPhone\Release\Perspex.iOS.dll $ios +Copy-Item ..\src\Skia\Perspex.Skia.iOS\bin\iPhone\Release\Perspex.Skia.iOS.dll $ios foreach($pkg in $Packages) { diff --git a/nuget/include.ps1 b/nuget/include.ps1 index 1898105862..f1e6f5d523 100644 --- a/nuget/include.ps1 +++ b/nuget/include.ps1 @@ -1 +1 @@ -$Packages = @("Perspex", "Perspex.Desktop", "Perspex.Skia.Desktop", "Perspex.Android") \ No newline at end of file +$Packages = @("Perspex", "Perspex.Desktop", "Perspex.Skia.Desktop", "Perspex.Android", "Perspex.iOS") \ No newline at end of file diff --git a/nuget/template/Perspex.iOS.nuspec b/nuget/template/Perspex.iOS.nuspec new file mode 100644 index 0000000000..568ee2bae1 --- /dev/null +++ b/nuget/template/Perspex.iOS.nuspec @@ -0,0 +1,27 @@ + + + + Perspex.iOS + #VERSION# + Perspex Team + stevenk + http://opensource.org/licenses/MIT + https://github.com/Perspex/Perspex/ + false + The Perspex UI framework + + Copyright 2015 + Perspex + + + + + + + + + + + + + \ No newline at end of file From aefe4bbbe55dcc8a73651862588fd054288789b1 Mon Sep 17 00:00:00 2001 From: Serg2DFX Date: Thu, 26 Nov 2015 14:06:02 +0300 Subject: [PATCH 029/211] Update build.md Correct Url for repository. --- docs/build.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/build.md b/docs/build.md index 157af98fa2..b3be7581c4 100644 --- a/docs/build.md +++ b/docs/build.md @@ -12,7 +12,7 @@ Perspex.Gtk project in Visual Studio. ### Clone the Perspex repository - git clone https://github.com/grokys/Perspex.git + git clone https://github.com/Perspex/Perspex.git We currently need to build our own private version of ReactiveUI as it doesn't work on mono. This is linked as a submodule in the git repository, so run: From 627e24c073fddada61e6fb45b8769d7b4da0c5a3 Mon Sep 17 00:00:00 2001 From: Nikita Tsukanov Date: Thu, 26 Nov 2015 18:01:42 +0300 Subject: [PATCH 030/211] Fixed #236 --- src/Perspex.HtmlRenderer/external | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Perspex.HtmlRenderer/external b/src/Perspex.HtmlRenderer/external index 9b58697e1c..b1050f5708 160000 --- a/src/Perspex.HtmlRenderer/external +++ b/src/Perspex.HtmlRenderer/external @@ -1 +1 @@ -Subproject commit 9b58697e1cba93bb86a40eb33b7d8b8fc743e3c0 +Subproject commit b1050f5708fb4f41d9dd40ef23311737befe4455 From 991e42143783beda359295f39695a16c52fe787d Mon Sep 17 00:00:00 2001 From: Nikita Tsukanov Date: Thu, 26 Nov 2015 23:31:16 +0300 Subject: [PATCH 031/211] Now using built-in designer API instead of dynamic calls --- .../Perspex.Android/AndroidPlatform.cs | 22 +++++- src/Gtk/Perspex.Gtk/GtkPlatform.cs | 19 ++++- .../Perspex.Markup.Xaml.csproj | 4 - src/Perspex.Application/Designer/Designer.cs | 58 ++++++++++++++ .../Designer/DesignerApi.cs | 51 ++++++++++++ .../Perspex.Application.csproj | 19 ++++- src/Perspex.Base/Perspex.Base.csproj | 1 + .../Platform/IAssetLoader.cs | 0 src/Perspex.Controls/Perspex.Controls.csproj | 1 + .../Platform/IWindowingPlatform.cs | 15 ++++ .../Platform/PlatformManager.cs | 14 +++- .../AppHost/PerspexAppHost.cs | 77 ++++++------------- .../Perspex.Designer/Perspex.Designer.csproj | 3 + src/Windows/Perspex.Win32/Win32Platform.cs | 35 +++++---- src/iOS/Perspex.iOS/PerspexAppDelegate.cs | 27 ++++++- 15 files changed, 261 insertions(+), 85 deletions(-) create mode 100644 src/Perspex.Application/Designer/Designer.cs create mode 100644 src/Perspex.Application/Designer/DesignerApi.cs rename src/{Perspex.Application => Perspex.Base}/Platform/IAssetLoader.cs (100%) create mode 100644 src/Perspex.Controls/Platform/IWindowingPlatform.cs diff --git a/src/Android/Perspex.Android/AndroidPlatform.cs b/src/Android/Perspex.Android/AndroidPlatform.cs index b3f4bdaa32..9276cba150 100644 --- a/src/Android/Perspex.Android/AndroidPlatform.cs +++ b/src/Android/Perspex.Android/AndroidPlatform.cs @@ -10,10 +10,11 @@ using Perspex.Shared.PlatformSupport; using Perspex.Skia; using System; using System.Collections.Generic; +using Perspex.Android.Platform.SkiaPlatform; namespace Perspex.Android { - public class AndroidPlatform : IPlatformSettings + public class AndroidPlatform : IPlatformSettings, IWindowingPlatform { public static readonly AndroidPlatform Instance = new AndroidPlatform(); public Size DoubleClickSize => new Size(4, 4); @@ -33,11 +34,11 @@ namespace Perspex.Android .Bind().ToConstant(this) .Bind().ToConstant(new AndroidThreadingInterface()) .Bind().ToTransient() - .Bind().ToTransient(); + .Bind().ToTransient() + .Bind().ToConstant(this); SkiaPlatform.Initialize(); Application.RegisterPlatformCallback(() => { }); - PerspexLocator.CurrentMutable.Bind().ToSingleton(); _scalingFactor = global::Android.App.Application.Context.Resources.DisplayMetrics.ScaledDensity; @@ -50,5 +51,20 @@ namespace Perspex.Android { SharedPlatform.Register(applicationType.Assembly); } + + public IWindowImpl CreateWindow() + { + return new WindowImpl(); + } + + public IWindowImpl CreateDesignerFriendlyWindow() + { + throw new NotImplementedException(); + } + + public IPopupImpl CreatePopup() + { + throw new NotImplementedException(); + } } } \ No newline at end of file diff --git a/src/Gtk/Perspex.Gtk/GtkPlatform.cs b/src/Gtk/Perspex.Gtk/GtkPlatform.cs index 16ba905589..014048854d 100644 --- a/src/Gtk/Perspex.Gtk/GtkPlatform.cs +++ b/src/Gtk/Perspex.Gtk/GtkPlatform.cs @@ -14,7 +14,7 @@ namespace Perspex.Gtk { using Gtk = global::Gtk; - public class GtkPlatform : IPlatformThreadingInterface, IPlatformSettings + public class GtkPlatform : IPlatformThreadingInterface, IPlatformSettings, IWindowingPlatform { private static readonly GtkPlatform s_instance = new GtkPlatform(); private static Thread _uiThread; @@ -33,8 +33,7 @@ namespace Perspex.Gtk public static void Initialize() { PerspexLocator.CurrentMutable - .Bind().ToTransient() - .Bind().ToTransient() + .Bind().ToConstant(s_instance) .Bind().ToSingleton() .Bind().ToConstant(CursorFactory.Instance) .Bind().ToConstant(GtkKeyboardDevice.Instance) @@ -86,5 +85,19 @@ namespace Perspex.Gtk public bool CurrentThreadIsLoopThread => Thread.CurrentThread == _uiThread; public event Action Signaled; + public IWindowImpl CreateWindow() + { + return new WindowImpl(); + } + + public IWindowImpl CreateDesignerFriendlyWindow() + { + throw new NotSupportedException(); + } + + public IPopupImpl CreatePopup() + { + return new PopupImpl(); + } } } \ No newline at end of file diff --git a/src/Markup/Perspex.Markup.Xaml/Perspex.Markup.Xaml.csproj b/src/Markup/Perspex.Markup.Xaml/Perspex.Markup.Xaml.csproj index 35fde7d0e4..9b8cb35162 100644 --- a/src/Markup/Perspex.Markup.Xaml/Perspex.Markup.Xaml.csproj +++ b/src/Markup/Perspex.Markup.Xaml/Perspex.Markup.Xaml.csproj @@ -272,10 +272,6 @@ {D211E587-D8BC-45B9-95A4-F297C8FA5200} Perspex.Animation - - {799A7BB5-3C2C-48B6-85A7-406A12C420DA} - Perspex.Application - {B09B78D8-9B26-48B0-9149-D64A2F120F3F} Perspex.Base diff --git a/src/Perspex.Application/Designer/Designer.cs b/src/Perspex.Application/Designer/Designer.cs new file mode 100644 index 0000000000..665eb3447f --- /dev/null +++ b/src/Perspex.Application/Designer/Designer.cs @@ -0,0 +1,58 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using OmniXaml; +using Perspex.Controls; +using Perspex.Controls.Platform; +using Perspex.Markup.Xaml; +using Perspex.Themes.Default; + +namespace Perspex.Designer +{ + class Designer + { + class DesignerApp : Application + { + public DesignerApp() + { + RegisterServices(); + //For now we only support windows + InitializeSubsystems(2); + Styles = new DefaultTheme(); + } + } + + public static DesignerApi Api { get; set; } + + public static void Init(Dictionary shared) + { + Api = new DesignerApi(shared) {UpdateXaml = UpdateXaml}; + new DesignerApp(); + } + + static Window s_currentWindow; + + private static void UpdateXaml(string xaml) + { + + Window window; + using (PlatformManager.DesignerMode()) + { + var obj = ((XamlXmlLoader)new PerspexXamlLoader()).Load(new MemoryStream(Encoding.UTF8.GetBytes(xaml))); + window = obj as Window; + if (window == null) + { + window = new Window() {Content = obj}; + } + } + s_currentWindow?.Close(); + s_currentWindow = window; + window.Show(); + Api.OnWindowCreated?.Invoke(window.PlatformImpl.Handle.Handle); + Api.OnResize?.Invoke(); + } + } +} diff --git a/src/Perspex.Application/Designer/DesignerApi.cs b/src/Perspex.Application/Designer/DesignerApi.cs new file mode 100644 index 0000000000..944bd86705 --- /dev/null +++ b/src/Perspex.Application/Designer/DesignerApi.cs @@ -0,0 +1,51 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Runtime.CompilerServices; +using System.Text; +using System.Threading.Tasks; + +namespace Perspex.Designer +{ + class DesignerApi + { + private readonly Dictionary _inner; + + public DesignerApi(Dictionary inner) + { + _inner = inner; + } + + object Get([CallerMemberName] string name = null) + { + object rv; + _inner.TryGetValue(name, out rv); + return rv; + } + + void Set(object value, [CallerMemberName] string name = null) + { + _inner[name] = value; + } + + public Action UpdateXaml + { + get { return (Action) Get(); } + set {Set(value); } + } + + public Action OnResize + { + get { return (Action) Get(); } + set { Set(value);} + } + + public Action OnWindowCreated + { + set { Set(value); } + get { return (Action) Get(); } + } + + + } +} diff --git a/src/Perspex.Application/Perspex.Application.csproj b/src/Perspex.Application/Perspex.Application.csproj index c5fe995636..08ecf9912e 100644 --- a/src/Perspex.Application/Perspex.Application.csproj +++ b/src/Perspex.Application/Perspex.Application.csproj @@ -39,6 +39,18 @@ + + {3e53a01a-b331-47f3-b828-4a5717e77a24} + Perspex.Markup.Xaml + + + {6417e941-21bc-467b-a771-0de389353ce6} + Perspex.Markup + + + {d211e587-d8bc-45b9-95a4-f297c8fa5200} + Perspex.Animation + {B09B78D8-9B26-48B0-9149-D64A2F120F3F} Perspex.Base @@ -67,13 +79,18 @@ {F1BAA01A-F176-4C6A-B39D-5B40BB1B148F} Perspex.Styling + + {3E10A5FA-E8DA-48B1-AD44-6A5B6CB7750F} + Perspex.Themes.Default + Properties\SharedAssemblyInfo.cs - + + diff --git a/src/Perspex.Base/Perspex.Base.csproj b/src/Perspex.Base/Perspex.Base.csproj index 890fa666bf..deeb85a600 100644 --- a/src/Perspex.Base/Perspex.Base.csproj +++ b/src/Perspex.Base/Perspex.Base.csproj @@ -53,6 +53,7 @@ + diff --git a/src/Perspex.Application/Platform/IAssetLoader.cs b/src/Perspex.Base/Platform/IAssetLoader.cs similarity index 100% rename from src/Perspex.Application/Platform/IAssetLoader.cs rename to src/Perspex.Base/Platform/IAssetLoader.cs diff --git a/src/Perspex.Controls/Perspex.Controls.csproj b/src/Perspex.Controls/Perspex.Controls.csproj index a99b3fbbfd..80b1aba585 100644 --- a/src/Perspex.Controls/Perspex.Controls.csproj +++ b/src/Perspex.Controls/Perspex.Controls.csproj @@ -44,6 +44,7 @@ + diff --git a/src/Perspex.Controls/Platform/IWindowingPlatform.cs b/src/Perspex.Controls/Platform/IWindowingPlatform.cs new file mode 100644 index 0000000000..99176b720e --- /dev/null +++ b/src/Perspex.Controls/Platform/IWindowingPlatform.cs @@ -0,0 +1,15 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Perspex.Platform +{ + public interface IWindowingPlatform + { + IWindowImpl CreateWindow(); + IWindowImpl CreateDesignerFriendlyWindow(); + IPopupImpl CreatePopup(); + } +} diff --git a/src/Perspex.Controls/Platform/PlatformManager.cs b/src/Perspex.Controls/Platform/PlatformManager.cs index ba239d1a46..4983fc0483 100644 --- a/src/Perspex.Controls/Platform/PlatformManager.cs +++ b/src/Perspex.Controls/Platform/PlatformManager.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.Linq; +using System.Reactive.Disposables; using System.Text; using System.Threading.Tasks; using Perspex.Input; @@ -17,6 +18,8 @@ namespace Perspex.Controls.Platform static IPlatformSettings GetSettings() => PerspexLocator.Current.GetService(); + static bool s_designerMode; + public static IRenderTarget CreateRenderTarget(ITopLevelImpl window) { return @@ -24,6 +27,11 @@ namespace Perspex.Controls.Platform PerspexLocator.Current.GetService().CreateRenderer(window.Handle), window); } + public static IDisposable DesignerMode() + { + s_designerMode = true; + return Disposable.Create(() => s_designerMode = false); + } class RenderTargetDecorator : IRenderTarget { @@ -165,12 +173,14 @@ namespace Perspex.Controls.Platform public static IWindowImpl CreateWindow() { - return new WindowDecorator(PerspexLocator.Current.GetService()); + var platform = PerspexLocator.Current.GetService(); + return + new WindowDecorator(s_designerMode ? platform.CreateDesignerFriendlyWindow() : platform.CreateWindow()); } public static IPopupImpl CreatePopup() { - return new WindowDecorator(PerspexLocator.Current.GetService()); + return new WindowDecorator(PerspexLocator.Current.GetService().CreatePopup()); } } } diff --git a/src/Windows/Perspex.Designer/AppHost/PerspexAppHost.cs b/src/Windows/Perspex.Designer/AppHost/PerspexAppHost.cs index 8cc1dc5096..0def6e0f3e 100644 --- a/src/Windows/Perspex.Designer/AppHost/PerspexAppHost.cs +++ b/src/Windows/Perspex.Designer/AppHost/PerspexAppHost.cs @@ -18,12 +18,11 @@ namespace Perspex.Designer.AppHost class PerspexAppHost { private string _appDir; - private CommChannel _comm; + private readonly CommChannel _comm; private string _lastXaml; private string _currentXaml; - private Func _xamlReader; private bool _initSuccess; - private HostedAppModel _appModel = new HostedAppModel(); + private readonly HostedAppModel _appModel = new HostedAppModel(); private Control _window; public PerspexAppHost(CommChannel channel) @@ -92,8 +91,6 @@ namespace Perspex.Designer.AppHost PerspexDesignerMetadata BuildMetadata(List asms, Type xmlNsAttr) { - - var rv = new PerspexDesignerMetadata() { @@ -200,34 +197,10 @@ namespace Perspex.Designer.AppHost log("Looking up Perspex types"); BuildMetadataAndSendMessageAsync(asms); - var syncContext = LookupType("Perspex.Threading.PerspexSynchronizationContext"); - syncContext.GetProperty("AutoInstall", BindingFlags.Public | BindingFlags.Static).SetValue(null, false); - - var app = Activator.CreateInstance(LookupType("Perspex.Application")); - app.GetType() - .GetMethod("RegisterServices", BindingFlags.NonPublic | BindingFlags.Instance) - .Invoke(app, null); - - LookupStaticMethod("Perspex.Direct2D1.Direct2D1Platform", "Initialize").Invoke(null, null); - LookupStaticMethod("Perspex.Win32.Win32Platform", "InitializeEmbedded").Invoke(null, null); - - app.GetType().GetProperty("Styles").GetSetMethod(true) - .Invoke(app, new[] {Activator.CreateInstance(LookupType("Perspex.Themes.Default.DefaultTheme"))}); - - - dynamic dispatcher = - LookupType("Perspex.Threading.Dispatcher") - .GetProperty("UIThread", BindingFlags.Static | BindingFlags.Public) - .GetValue(null); - - - - var xamlFactory = Activator.CreateInstance(LookupType("Perspex.Markup.Xaml.Context.PerspexParserFactory")); - - dynamic xamlLoader = - LookupType("OmniXaml.XamlLoader", "OmniXaml.XamlXmlLoader").GetConstructors().First().Invoke(new object[] {xamlFactory}); - - _xamlReader = (stream) => xamlLoader.Load(stream); + log("Initializing built-in designer"); + var dic = new Dictionary(); + Api = new DesignerApi(dic) {OnResize = OnResize, OnWindowCreated = OnWindowCreated}; + LookupStaticMethod("Perspex.Designer.Designer", "Init").Invoke(null, new object[] {dic}); _window = new Control { @@ -242,15 +215,19 @@ namespace Perspex.Designer.AppHost }; _window.CreateControl(); - new Timer {Interval = 10, Enabled = true}.Tick += delegate - { - dispatcher.RunJobs(); - }; new Timer {Interval = 200, Enabled = true}.Tick += delegate { XamlUpdater(); }; _comm.SendMessage(new WindowCreatedMessage(_window.Handle)); _initSuccess = true; } + private void OnWindowCreated(IntPtr hWnd) + { + _appModel.NativeWindowHandle = hWnd; + } + + + public DesignerApi Api { get; set; } + bool ValidateXml(string xml) { @@ -269,6 +246,11 @@ namespace Perspex.Designer.AppHost return true; } + private void OnResize() + { + + } + void XamlUpdater() { if (!_initSuccess) @@ -282,27 +264,10 @@ namespace Perspex.Designer.AppHost _appModel.SetError("Invalid markup"); return; } - - try { - const string windowType = "Perspex.Controls.Window"; - - var root = _xamlReader(new MemoryStream(Encoding.UTF8.GetBytes(_currentXaml))); - dynamic window = root; - if (root.GetType().FullName != windowType) - { - window = Activator.CreateInstance(LookupType(windowType)); - window.Content = root; - } + Api.UpdateXaml(_currentXaml); - var w = ((object) (window.PlatformImpl)).Prop("Handle"); - if (!(w is IntPtr)) - w = w.Prop("Handle"); - - var hWnd = (IntPtr) w; - _appModel.NativeWindowHandle = hWnd; - window.Show(); _appModel.SetError(null); } catch (Exception e) @@ -310,6 +275,8 @@ namespace Perspex.Designer.AppHost _appModel.SetError("XAML load error", e.ToString()); } } + + private Assembly CurrentDomain_AssemblyResolve(object sender, ResolveEventArgs args) { diff --git a/src/Windows/Perspex.Designer/Perspex.Designer.csproj b/src/Windows/Perspex.Designer/Perspex.Designer.csproj index f5dd8bd8ac..abb50a5279 100644 --- a/src/Windows/Perspex.Designer/Perspex.Designer.csproj +++ b/src/Windows/Perspex.Designer/Perspex.Designer.csproj @@ -67,6 +67,9 @@ + + AppHost\DesignerApi.cs + App.xaml diff --git a/src/Windows/Perspex.Win32/Win32Platform.cs b/src/Windows/Perspex.Win32/Win32Platform.cs index e461e95de3..5c8c475f17 100644 --- a/src/Windows/Perspex.Win32/Win32Platform.cs +++ b/src/Windows/Perspex.Win32/Win32Platform.cs @@ -18,7 +18,7 @@ using Perspex.Win32.Interop; namespace Perspex.Win32 { - public class Win32Platform : IPlatformThreadingInterface, IPlatformSettings + public class Win32Platform : IPlatformThreadingInterface, IPlatformSettings, IWindowingPlatform { private static readonly Win32Platform s_instance = new Win32Platform(); private static Thread _uiThread; @@ -42,34 +42,22 @@ namespace Perspex.Win32 public double RenderScalingFactor { get; } = 1; public double LayoutScalingFactor { get; } = 1; - private static void InitializeInternal() + public static void Initialize() { PerspexLocator.CurrentMutable - .Bind().ToTransient() .Bind().ToSingleton() .Bind().ToConstant(CursorFactory.Instance) .Bind().ToConstant(WindowsKeyboardDevice.Instance) .Bind().ToConstant(WindowsMouseDevice.Instance) .Bind().ToConstant(s_instance) .Bind().ToConstant(s_instance) - .Bind().ToSingleton(); + .Bind().ToSingleton() + .Bind().ToConstant(s_instance); SharedPlatform.Register(); _uiThread = Thread.CurrentThread; } - public static void Initialize() - { - PerspexLocator.CurrentMutable.Bind().ToTransient(); - InitializeInternal(); - } - - public static void InitializeEmbedded() - { - PerspexLocator.CurrentMutable.Bind().ToTransient(); - InitializeInternal(); - } - public bool HasMessages() { UnmanagedMethods.MSG msg; @@ -169,5 +157,20 @@ namespace Perspex.Win32 throw new Win32Exception(); } } + + public IWindowImpl CreateWindow() + { + return new WindowImpl(); + } + + public IWindowImpl CreateDesignerFriendlyWindow() + { + return new EmbeddedWindowImpl(); + } + + public IPopupImpl CreatePopup() + { + return new PopupImpl(); + } } } diff --git a/src/iOS/Perspex.iOS/PerspexAppDelegate.cs b/src/iOS/Perspex.iOS/PerspexAppDelegate.cs index c0296d077c..e526f3a69c 100644 --- a/src/iOS/Perspex.iOS/PerspexAppDelegate.cs +++ b/src/iOS/Perspex.iOS/PerspexAppDelegate.cs @@ -42,9 +42,34 @@ namespace Perspex.iOS .Bind().ToConstant(MouseDevice) .Bind().ToSingleton() .Bind().ToConstant(PlatformThreadingInterface.Instance) - .Bind().ToConstant(controller.PerspexView); + .Bind().ToConstant(new WindowingPlatform(controller.PerspexView)); SkiaPlatform.Initialize(); }); } + + class WindowingPlatform : IWindowingPlatform + { + private readonly IWindowImpl _window; + + public WindowingPlatform(IWindowImpl window) + { + _window = window; + } + + public IWindowImpl CreateWindow() + { + return _window; + } + + public IWindowImpl CreateDesignerFriendlyWindow() + { + throw new NotImplementedException(); + } + + public IPopupImpl CreatePopup() + { + throw new NotImplementedException(); + } + } } } From a96149842080ce4215f01eda8f2613449b695db0 Mon Sep 17 00:00:00 2001 From: Nikita Tsukanov Date: Fri, 27 Nov 2015 00:01:00 +0300 Subject: [PATCH 032/211] Implemented designer scaling --- src/Perspex.Application/Designer/Designer.cs | 9 ++++++- .../Designer/DesignerApi.cs | 5 ++++ .../Platform/PlatformManager.cs | 13 ++++++++-- .../AppHost/HostedAppModel.cs | 20 ++++++++++++++++ .../AppHost/PerspexAppHost.cs | 3 ++- .../Perspex.Designer/Comm/CommChannel.cs | 2 +- .../InProcDesigner/InProcDesignerView.xaml | 24 +++++++++++++------ 7 files changed, 64 insertions(+), 12 deletions(-) diff --git a/src/Perspex.Application/Designer/Designer.cs b/src/Perspex.Application/Designer/Designer.cs index 665eb3447f..a04e1d1fef 100644 --- a/src/Perspex.Application/Designer/Designer.cs +++ b/src/Perspex.Application/Designer/Designer.cs @@ -29,10 +29,17 @@ namespace Perspex.Designer public static void Init(Dictionary shared) { - Api = new DesignerApi(shared) {UpdateXaml = UpdateXaml}; + Api = new DesignerApi(shared) {UpdateXaml = UpdateXaml, SetScalingFactor = SetScalingFactor}; new DesignerApp(); } + private static void SetScalingFactor(double factor) + { + PlatformManager.SetDesignerScalingFactor(factor); + if (s_currentWindow != null) + s_currentWindow.PlatformImpl.ClientSize = s_currentWindow.ClientSize; + } + static Window s_currentWindow; private static void UpdateXaml(string xaml) diff --git a/src/Perspex.Application/Designer/DesignerApi.cs b/src/Perspex.Application/Designer/DesignerApi.cs index 944bd86705..aebc621786 100644 --- a/src/Perspex.Application/Designer/DesignerApi.cs +++ b/src/Perspex.Application/Designer/DesignerApi.cs @@ -46,6 +46,11 @@ namespace Perspex.Designer get { return (Action) Get(); } } + public Action SetScalingFactor + { + set { Set(value);} + get { return (Action) Get(); } + } } } diff --git a/src/Perspex.Controls/Platform/PlatformManager.cs b/src/Perspex.Controls/Platform/PlatformManager.cs index 4983fc0483..383eecd5c7 100644 --- a/src/Perspex.Controls/Platform/PlatformManager.cs +++ b/src/Perspex.Controls/Platform/PlatformManager.cs @@ -19,6 +19,7 @@ namespace Perspex.Controls.Platform => PerspexLocator.Current.GetService(); static bool s_designerMode; + private static double _designerScalingFactor = 1; public static IRenderTarget CreateRenderTarget(ITopLevelImpl window) { @@ -33,6 +34,14 @@ namespace Perspex.Controls.Platform return Disposable.Create(() => s_designerMode = false); } + public static void SetDesignerScalingFactor(double factor) + { + _designerScalingFactor = factor; + } + + static double RenderScalingFactor => (GetSettings()?.RenderScalingFactor ?? 1)*_designerScalingFactor; + static double LayoutScalingFactor => (GetSettings()?.LayoutScalingFactor ?? 1) * _designerScalingFactor; + class RenderTargetDecorator : IRenderTarget { private readonly IRenderTarget _target; @@ -53,7 +62,7 @@ namespace Perspex.Controls.Platform { var cs = _window.ClientSize; var ctx = _target.CreateDrawingContext(); - var factor = GetSettings()?.RenderScalingFactor ?? 1; + var factor = RenderScalingFactor; if (factor != 1) { ctx.PushPostTransform(Matrix.CreateScale(factor, factor)); @@ -70,7 +79,7 @@ namespace Perspex.Controls.Platform private readonly IPopupImpl _popup; public ITopLevelImpl TopLevel => _tl; - double ScalingFactor => GetSettings()?.LayoutScalingFactor ?? 1; + double ScalingFactor => LayoutScalingFactor; public WindowDecorator(ITopLevelImpl tl) { diff --git a/src/Windows/Perspex.Designer/AppHost/HostedAppModel.cs b/src/Windows/Perspex.Designer/AppHost/HostedAppModel.cs index 649b5d48ee..1be709216a 100644 --- a/src/Windows/Perspex.Designer/AppHost/HostedAppModel.cs +++ b/src/Windows/Perspex.Designer/AppHost/HostedAppModel.cs @@ -11,10 +11,16 @@ namespace Perspex.Designer.AppHost { public class HostedAppModel : INotifyPropertyChanged { + private readonly PerspexAppHost _host; private IntPtr _nativeWindowHandle; private string _error; private string _errorDetails; + internal HostedAppModel(PerspexAppHost host) + { + _host = host; + } + public IntPtr NativeWindowHandle { get { return _nativeWindowHandle; } @@ -48,12 +54,26 @@ namespace Perspex.Designer.AppHost } } + public IReadOnlyList AvailableScalingFactors => new List() {1, 2, 4, 8}; + + public double CurrentScalingFactor + { + get { return _currentScalingFactor; } + set + { + _currentScalingFactor = value; + _host.Api.SetScalingFactor(value); + } + } + public void SetError(string error, string details = null) { Error = error; ErrorDetails = details; } + double _currentScalingFactor = 1; + public event PropertyChangedEventHandler PropertyChanged; [NotifyPropertyChangedInvocator] diff --git a/src/Windows/Perspex.Designer/AppHost/PerspexAppHost.cs b/src/Windows/Perspex.Designer/AppHost/PerspexAppHost.cs index 0def6e0f3e..b855019f4c 100644 --- a/src/Windows/Perspex.Designer/AppHost/PerspexAppHost.cs +++ b/src/Windows/Perspex.Designer/AppHost/PerspexAppHost.cs @@ -22,12 +22,13 @@ namespace Perspex.Designer.AppHost private string _lastXaml; private string _currentXaml; private bool _initSuccess; - private readonly HostedAppModel _appModel = new HostedAppModel(); + private HostedAppModel _appModel; private Control _window; public PerspexAppHost(CommChannel channel) { _comm = channel; + _appModel = new HostedAppModel(this); } public void Start() diff --git a/src/Windows/Perspex.Designer/Comm/CommChannel.cs b/src/Windows/Perspex.Designer/Comm/CommChannel.cs index 4e7bc641c9..a2608c88db 100644 --- a/src/Windows/Perspex.Designer/Comm/CommChannel.cs +++ b/src/Windows/Perspex.Designer/Comm/CommChannel.cs @@ -13,7 +13,7 @@ using System.Windows.Threading; namespace Perspex.Designer.Comm { - internal class CommChannel : IDisposable + class CommChannel : IDisposable { private readonly BinaryReader _input; private readonly BinaryWriter _output; diff --git a/src/Windows/Perspex.Designer/InProcDesigner/InProcDesignerView.xaml b/src/Windows/Perspex.Designer/InProcDesigner/InProcDesignerView.xaml index 38e09688cb..795749e6f6 100644 --- a/src/Windows/Perspex.Designer/InProcDesigner/InProcDesignerView.xaml +++ b/src/Windows/Perspex.Designer/InProcDesigner/InProcDesignerView.xaml @@ -4,17 +4,27 @@ xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:appHost="clr-namespace:Perspex.Designer.AppHost" + xmlns:system="clr-namespace:System;assembly=mscorlib" mc:Ignorable="d" d:DesignHeight="300" d:DesignWidth="300" d:DataContext="{d:DesignInstance appHost:HostedAppModel}"> - - - - + + + + + 00% + + + + + + + (View details) - - - + + + + From 3c78704f557cc87f4cd5bf28f592df30da7e2974 Mon Sep 17 00:00:00 2001 From: Nikita Tsukanov Date: Fri, 27 Nov 2015 00:15:19 +0300 Subject: [PATCH 033/211] Fix for tests --- .../Perspex.Controls.UnitTests.csproj | 1 + .../Primitives/PopupTests.cs | 2 +- .../Primitives/WindowingPlatformMock.cs | 21 +++++++++++++++++++ .../Utils/HotKeyManagerTests.cs | 4 ++-- 4 files changed, 25 insertions(+), 3 deletions(-) create mode 100644 tests/Perspex.Controls.UnitTests/Primitives/WindowingPlatformMock.cs diff --git a/tests/Perspex.Controls.UnitTests/Perspex.Controls.UnitTests.csproj b/tests/Perspex.Controls.UnitTests/Perspex.Controls.UnitTests.csproj index f299a47e6d..c441eedf59 100644 --- a/tests/Perspex.Controls.UnitTests/Perspex.Controls.UnitTests.csproj +++ b/tests/Perspex.Controls.UnitTests/Perspex.Controls.UnitTests.csproj @@ -88,6 +88,7 @@ + diff --git a/tests/Perspex.Controls.UnitTests/Primitives/PopupTests.cs b/tests/Perspex.Controls.UnitTests/Primitives/PopupTests.cs index 18d7867b0f..172d2320f1 100644 --- a/tests/Perspex.Controls.UnitTests/Primitives/PopupTests.cs +++ b/tests/Perspex.Controls.UnitTests/Primitives/PopupTests.cs @@ -267,7 +267,7 @@ namespace Perspex.Controls.UnitTests.Primitives PerspexLocator.CurrentMutable .Bind().ToTransient() .Bind().ToFunc(() => globalStyles.Object) - .Bind().ToConstant(new Mock().Object) + .Bind().ToConstant(new WindowingPlatformMock()) .Bind().ToTransient(); return result; diff --git a/tests/Perspex.Controls.UnitTests/Primitives/WindowingPlatformMock.cs b/tests/Perspex.Controls.UnitTests/Primitives/WindowingPlatformMock.cs new file mode 100644 index 0000000000..bb879929a5 --- /dev/null +++ b/tests/Perspex.Controls.UnitTests/Primitives/WindowingPlatformMock.cs @@ -0,0 +1,21 @@ +using System; +using Moq; +using Perspex.Platform; + +namespace Perspex.Controls.UnitTests.Primitives +{ + class WindowingPlatformMock : IWindowingPlatform + { + public IWindowImpl CreateWindow() + { + return new Mock().Object; + } + + public IWindowImpl CreateDesignerFriendlyWindow() + { + throw new NotImplementedException(); + } + + public IPopupImpl CreatePopup() => new Mock().Object; + } +} \ No newline at end of file diff --git a/tests/Perspex.Controls.UnitTests/Utils/HotKeyManagerTests.cs b/tests/Perspex.Controls.UnitTests/Utils/HotKeyManagerTests.cs index 40949e624a..6cdcd81539 100644 --- a/tests/Perspex.Controls.UnitTests/Utils/HotKeyManagerTests.cs +++ b/tests/Perspex.Controls.UnitTests/Utils/HotKeyManagerTests.cs @@ -6,6 +6,7 @@ using System.Threading.Tasks; using Moq; using Perspex.Controls.Presenters; using Perspex.Controls.Templates; +using Perspex.Controls.UnitTests.Primitives; using Perspex.Input; using Perspex.Platform; using Perspex.Styling; @@ -20,11 +21,10 @@ namespace Perspex.Controls.UnitTests.Utils { using (PerspexLocator.EnterScope()) { - var windowImpl = new Mock(); var styler = new Mock(); PerspexLocator.CurrentMutable - .Bind().ToConstant(windowImpl.Object) + .Bind().ToConstant(new WindowingPlatformMock()) .Bind().ToConstant(styler.Object); var gesture1 = new KeyGesture {Key = Key.A, Modifiers = InputModifiers.Control}; From 27e1d4accbea0a41a0206d126c532dd3952adb06 Mon Sep 17 00:00:00 2001 From: Nikita Tsukanov Date: Fri, 27 Nov 2015 00:25:06 +0300 Subject: [PATCH 034/211] Fix for FullLayoutTests --- .../Primitives/WindowingPlatformMock.cs | 15 ++++++++++++--- tests/Perspex.Layout.UnitTests/FullLayoutTests.cs | 3 ++- .../Perspex.Layout.UnitTests.csproj | 4 ++++ 3 files changed, 18 insertions(+), 4 deletions(-) diff --git a/tests/Perspex.Controls.UnitTests/Primitives/WindowingPlatformMock.cs b/tests/Perspex.Controls.UnitTests/Primitives/WindowingPlatformMock.cs index bb879929a5..6dde6ef4d1 100644 --- a/tests/Perspex.Controls.UnitTests/Primitives/WindowingPlatformMock.cs +++ b/tests/Perspex.Controls.UnitTests/Primitives/WindowingPlatformMock.cs @@ -4,11 +4,20 @@ using Perspex.Platform; namespace Perspex.Controls.UnitTests.Primitives { - class WindowingPlatformMock : IWindowingPlatform + public class WindowingPlatformMock : IWindowingPlatform { + private readonly Func _windowImpl; + private readonly Func _popupImpl; + + public WindowingPlatformMock(Func windowImpl = null, Func popupImpl = null ) + { + _windowImpl = windowImpl; + _popupImpl = popupImpl; + } + public IWindowImpl CreateWindow() { - return new Mock().Object; + return _windowImpl?.Invoke() ?? new Mock().Object; } public IWindowImpl CreateDesignerFriendlyWindow() @@ -16,6 +25,6 @@ namespace Perspex.Controls.UnitTests.Primitives throw new NotImplementedException(); } - public IPopupImpl CreatePopup() => new Mock().Object; + public IPopupImpl CreatePopup() => _popupImpl?.Invoke() ?? new Mock().Object; } } \ No newline at end of file diff --git a/tests/Perspex.Layout.UnitTests/FullLayoutTests.cs b/tests/Perspex.Layout.UnitTests/FullLayoutTests.cs index 53a74dec19..88c0e53827 100644 --- a/tests/Perspex.Layout.UnitTests/FullLayoutTests.cs +++ b/tests/Perspex.Layout.UnitTests/FullLayoutTests.cs @@ -9,6 +9,7 @@ using Perspex.Controls; using Perspex.Controls.Presenters; using Perspex.Controls.Primitives; using Perspex.Controls.Templates; +using Perspex.Controls.UnitTests.Primitives; using Perspex.Diagnostics; using Perspex.Input; using Perspex.Platform; @@ -149,7 +150,7 @@ namespace Perspex.Layout.UnitTests .Bind().ToConstant(renderInterface) .Bind().ToConstant(renderManager) .Bind().ToConstant(new Styler()) - .Bind().ToConstant(windowImpl.Object); + .Bind().ToConstant(new WindowingPlatformMock(() => windowImpl.Object)); var theme = new DefaultTheme(); globalStyles.Setup(x => x.Styles).Returns(theme); diff --git a/tests/Perspex.Layout.UnitTests/Perspex.Layout.UnitTests.csproj b/tests/Perspex.Layout.UnitTests/Perspex.Layout.UnitTests.csproj index 15f1a5cc25..08e0e2c7a0 100644 --- a/tests/Perspex.Layout.UnitTests/Perspex.Layout.UnitTests.csproj +++ b/tests/Perspex.Layout.UnitTests/Perspex.Layout.UnitTests.csproj @@ -137,6 +137,10 @@ {3E10A5FA-E8DA-48B1-AD44-6A5B6CB7750F} Perspex.Themes.Default + + {5ccb5571-7c30-4e7d-967d-0e2158ebd91f} + Perspex.Controls.UnitTests + From 6e33686ff6bfb6a6fba0ad9d1e0c7bc695ccf5ef Mon Sep 17 00:00:00 2001 From: Nikita Tsukanov Date: Fri, 27 Nov 2015 14:44:44 +0300 Subject: [PATCH 035/211] CreateDesignerFriendlyWindow -> CreateEmbeddableWindow --- src/Android/Perspex.Android/AndroidPlatform.cs | 2 +- src/Gtk/Perspex.Gtk/GtkPlatform.cs | 2 +- src/Perspex.Controls/Platform/IWindowingPlatform.cs | 2 +- src/Perspex.Controls/Platform/PlatformManager.cs | 2 +- src/Windows/Perspex.Win32/Win32Platform.cs | 2 +- src/iOS/Perspex.iOS/PerspexAppDelegate.cs | 2 +- .../Primitives/WindowingPlatformMock.cs | 2 +- 7 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/Android/Perspex.Android/AndroidPlatform.cs b/src/Android/Perspex.Android/AndroidPlatform.cs index 9276cba150..4b02a14aa0 100644 --- a/src/Android/Perspex.Android/AndroidPlatform.cs +++ b/src/Android/Perspex.Android/AndroidPlatform.cs @@ -57,7 +57,7 @@ namespace Perspex.Android return new WindowImpl(); } - public IWindowImpl CreateDesignerFriendlyWindow() + public IWindowImpl CreateEmbeddableWindow() { throw new NotImplementedException(); } diff --git a/src/Gtk/Perspex.Gtk/GtkPlatform.cs b/src/Gtk/Perspex.Gtk/GtkPlatform.cs index 014048854d..b8a6d5c511 100644 --- a/src/Gtk/Perspex.Gtk/GtkPlatform.cs +++ b/src/Gtk/Perspex.Gtk/GtkPlatform.cs @@ -90,7 +90,7 @@ namespace Perspex.Gtk return new WindowImpl(); } - public IWindowImpl CreateDesignerFriendlyWindow() + public IWindowImpl CreateEmbeddableWindow() { throw new NotSupportedException(); } diff --git a/src/Perspex.Controls/Platform/IWindowingPlatform.cs b/src/Perspex.Controls/Platform/IWindowingPlatform.cs index 99176b720e..5a12fbdeb2 100644 --- a/src/Perspex.Controls/Platform/IWindowingPlatform.cs +++ b/src/Perspex.Controls/Platform/IWindowingPlatform.cs @@ -9,7 +9,7 @@ namespace Perspex.Platform public interface IWindowingPlatform { IWindowImpl CreateWindow(); - IWindowImpl CreateDesignerFriendlyWindow(); + IWindowImpl CreateEmbeddableWindow(); IPopupImpl CreatePopup(); } } diff --git a/src/Perspex.Controls/Platform/PlatformManager.cs b/src/Perspex.Controls/Platform/PlatformManager.cs index 383eecd5c7..1e13b926c0 100644 --- a/src/Perspex.Controls/Platform/PlatformManager.cs +++ b/src/Perspex.Controls/Platform/PlatformManager.cs @@ -184,7 +184,7 @@ namespace Perspex.Controls.Platform { var platform = PerspexLocator.Current.GetService(); return - new WindowDecorator(s_designerMode ? platform.CreateDesignerFriendlyWindow() : platform.CreateWindow()); + new WindowDecorator(s_designerMode ? platform.CreateEmbeddableWindow() : platform.CreateWindow()); } public static IPopupImpl CreatePopup() diff --git a/src/Windows/Perspex.Win32/Win32Platform.cs b/src/Windows/Perspex.Win32/Win32Platform.cs index 5c8c475f17..89c6646b0c 100644 --- a/src/Windows/Perspex.Win32/Win32Platform.cs +++ b/src/Windows/Perspex.Win32/Win32Platform.cs @@ -163,7 +163,7 @@ namespace Perspex.Win32 return new WindowImpl(); } - public IWindowImpl CreateDesignerFriendlyWindow() + public IWindowImpl CreateEmbeddableWindow() { return new EmbeddedWindowImpl(); } diff --git a/src/iOS/Perspex.iOS/PerspexAppDelegate.cs b/src/iOS/Perspex.iOS/PerspexAppDelegate.cs index e526f3a69c..3a8c2e00df 100644 --- a/src/iOS/Perspex.iOS/PerspexAppDelegate.cs +++ b/src/iOS/Perspex.iOS/PerspexAppDelegate.cs @@ -61,7 +61,7 @@ namespace Perspex.iOS return _window; } - public IWindowImpl CreateDesignerFriendlyWindow() + public IWindowImpl CreateEmbeddableWindow() { throw new NotImplementedException(); } diff --git a/tests/Perspex.Controls.UnitTests/Primitives/WindowingPlatformMock.cs b/tests/Perspex.Controls.UnitTests/Primitives/WindowingPlatformMock.cs index 6dde6ef4d1..5169ebfa65 100644 --- a/tests/Perspex.Controls.UnitTests/Primitives/WindowingPlatformMock.cs +++ b/tests/Perspex.Controls.UnitTests/Primitives/WindowingPlatformMock.cs @@ -20,7 +20,7 @@ namespace Perspex.Controls.UnitTests.Primitives return _windowImpl?.Invoke() ?? new Mock().Object; } - public IWindowImpl CreateDesignerFriendlyWindow() + public IWindowImpl CreateEmbeddableWindow() { throw new NotImplementedException(); } From abd54620cd83c34b5084d0cb1a172eca41ff46b6 Mon Sep 17 00:00:00 2001 From: Nikita Tsukanov Date: Fri, 27 Nov 2015 14:45:37 +0300 Subject: [PATCH 036/211] Moved WindowingPlatformMock --- .../Perspex.Controls.UnitTests.csproj | 2 +- .../{Primitives => }/WindowingPlatformMock.cs | 2 +- tests/Perspex.Layout.UnitTests/FullLayoutTests.cs | 1 + 3 files changed, 3 insertions(+), 2 deletions(-) rename tests/Perspex.Controls.UnitTests/{Primitives => }/WindowingPlatformMock.cs (94%) diff --git a/tests/Perspex.Controls.UnitTests/Perspex.Controls.UnitTests.csproj b/tests/Perspex.Controls.UnitTests/Perspex.Controls.UnitTests.csproj index c441eedf59..c19e639895 100644 --- a/tests/Perspex.Controls.UnitTests/Perspex.Controls.UnitTests.csproj +++ b/tests/Perspex.Controls.UnitTests/Perspex.Controls.UnitTests.csproj @@ -88,7 +88,7 @@ - + diff --git a/tests/Perspex.Controls.UnitTests/Primitives/WindowingPlatformMock.cs b/tests/Perspex.Controls.UnitTests/WindowingPlatformMock.cs similarity index 94% rename from tests/Perspex.Controls.UnitTests/Primitives/WindowingPlatformMock.cs rename to tests/Perspex.Controls.UnitTests/WindowingPlatformMock.cs index 5169ebfa65..989cb5f784 100644 --- a/tests/Perspex.Controls.UnitTests/Primitives/WindowingPlatformMock.cs +++ b/tests/Perspex.Controls.UnitTests/WindowingPlatformMock.cs @@ -2,7 +2,7 @@ using System; using Moq; using Perspex.Platform; -namespace Perspex.Controls.UnitTests.Primitives +namespace Perspex.Controls.UnitTests { public class WindowingPlatformMock : IWindowingPlatform { diff --git a/tests/Perspex.Layout.UnitTests/FullLayoutTests.cs b/tests/Perspex.Layout.UnitTests/FullLayoutTests.cs index 88c0e53827..3ca03c43b7 100644 --- a/tests/Perspex.Layout.UnitTests/FullLayoutTests.cs +++ b/tests/Perspex.Layout.UnitTests/FullLayoutTests.cs @@ -9,6 +9,7 @@ using Perspex.Controls; using Perspex.Controls.Presenters; using Perspex.Controls.Primitives; using Perspex.Controls.Templates; +using Perspex.Controls.UnitTests; using Perspex.Controls.UnitTests.Primitives; using Perspex.Diagnostics; using Perspex.Input; From 87b3386e760440522052882ffc3eed15e698624e Mon Sep 17 00:00:00 2001 From: Nikita Tsukanov Date: Fri, 27 Nov 2015 15:35:45 +0300 Subject: [PATCH 037/211] Added background color for designer Fixed #183 --- .../AppHost/HostedAppModel.cs | 15 ++++++++++++++ .../Perspex.Designer/AppHost/WindowHost.cs | 20 ++++++++++++++++++- .../InProcDesigner/InProcDesignerView.xaml | 4 ++++ .../InProcDesigner/InProcDesignerView.xaml.cs | 16 ++++++++++++++- .../Perspex.Designer/Perspex.Designer.csproj | 1 + src/Windows/Perspex.Designer/Settings.cs | 19 ++++++++++++++++++ 6 files changed, 73 insertions(+), 2 deletions(-) create mode 100644 src/Windows/Perspex.Designer/Settings.cs diff --git a/src/Windows/Perspex.Designer/AppHost/HostedAppModel.cs b/src/Windows/Perspex.Designer/AppHost/HostedAppModel.cs index 1be709216a..31fe2beeeb 100644 --- a/src/Windows/Perspex.Designer/AppHost/HostedAppModel.cs +++ b/src/Windows/Perspex.Designer/AppHost/HostedAppModel.cs @@ -5,6 +5,7 @@ using System.Linq; using System.Runtime.CompilerServices; using System.Text; using System.Threading.Tasks; +using System.Windows.Media; using JetBrains.Annotations; namespace Perspex.Designer.AppHost @@ -19,6 +20,7 @@ namespace Perspex.Designer.AppHost internal HostedAppModel(PerspexAppHost host) { _host = host; + Background = Settings.Background; } public IntPtr NativeWindowHandle @@ -54,6 +56,17 @@ namespace Perspex.Designer.AppHost } } + public string Background + { + get { return _background; } + set + { + if (value == _background) return; + _background = value; + OnPropertyChanged(); + } + } + public IReadOnlyList AvailableScalingFactors => new List() {1, 2, 4, 8}; public double CurrentScalingFactor @@ -73,6 +86,8 @@ namespace Perspex.Designer.AppHost } double _currentScalingFactor = 1; + private string _color; + private string _background; public event PropertyChangedEventHandler PropertyChanged; diff --git a/src/Windows/Perspex.Designer/AppHost/WindowHost.cs b/src/Windows/Perspex.Designer/AppHost/WindowHost.cs index 549e8a3e79..80b0fe8e9b 100644 --- a/src/Windows/Perspex.Designer/AppHost/WindowHost.cs +++ b/src/Windows/Perspex.Designer/AppHost/WindowHost.cs @@ -20,7 +20,19 @@ namespace Perspex.Designer.AppHost Text = "ScrollableArea"; Controls.Add(_windowHost); _windowHost.Anchor = AnchorStyles.None; - _timer.Tick += delegate { FixWindow(); }; + _timer.Tick += delegate + { + ReloadSettings(); + FixWindow(); + }; + } + + private void ReloadSettings() + { + var bkg = Settings.Background; + var color = System.Drawing.ColorTranslator.FromHtml(bkg); + if (BackColor != color) + BackColor = color; } private Control _windowHost = new Control() {Text = "WindowWrapper"}; @@ -43,6 +55,12 @@ namespace Perspex.Designer.AppHost base.WndProc(ref m); } + protected override void OnPaint(PaintEventArgs e) + { + using (var b = new SolidBrush(BackColor)) + e.Graphics.FillRectangle(b, e.ClipRectangle); + } + void FixPosition() { var newScrollSize = new Size(_desiredWidth, _desiredHeight); diff --git a/src/Windows/Perspex.Designer/InProcDesigner/InProcDesignerView.xaml b/src/Windows/Perspex.Designer/InProcDesigner/InProcDesignerView.xaml index 795749e6f6..721cb83e04 100644 --- a/src/Windows/Perspex.Designer/InProcDesigner/InProcDesignerView.xaml +++ b/src/Windows/Perspex.Designer/InProcDesigner/InProcDesignerView.xaml @@ -16,6 +16,10 @@ + + + diff --git a/src/Windows/Perspex.Designer/InProcDesigner/InProcDesignerView.xaml.cs b/src/Windows/Perspex.Designer/InProcDesigner/InProcDesignerView.xaml.cs index 801b088730..9e617f1560 100644 --- a/src/Windows/Perspex.Designer/InProcDesigner/InProcDesignerView.xaml.cs +++ b/src/Windows/Perspex.Designer/InProcDesigner/InProcDesignerView.xaml.cs @@ -1,18 +1,21 @@ using System; using System.Collections.Generic; +using System.Drawing; using System.Linq; using System.Text; using System.Threading.Tasks; using System.Windows; -using System.Windows.Controls; using System.Windows.Data; using System.Windows.Documents; +using System.Windows.Forms; using System.Windows.Input; using System.Windows.Media; using System.Windows.Media.Imaging; using System.Windows.Navigation; using System.Windows.Shapes; using Perspex.Designer.AppHost; +using TextBox = System.Windows.Controls.TextBox; +using UserControl = System.Windows.Controls.UserControl; namespace Perspex.Designer.InProcDesigner { @@ -80,5 +83,16 @@ namespace Perspex.Designer.InProcDesigner }; wnd.ShowDialog(); } + + private void ColorPicker_OnClick(object sender, MouseButtonEventArgs e) + { + var dlg = new ColorDialog() {Color = ColorTranslator.FromHtml(Settings.Background)}; + if (dlg.ShowDialog(_host) == DialogResult.OK) + { + var color = ColorTranslator.ToHtml(dlg.Color); + _appModel.Background = color; + Settings.Background = color; + } + } } } diff --git a/src/Windows/Perspex.Designer/Perspex.Designer.csproj b/src/Windows/Perspex.Designer/Perspex.Designer.csproj index abb50a5279..42131b06fd 100644 --- a/src/Windows/Perspex.Designer/Perspex.Designer.csproj +++ b/src/Windows/Perspex.Designer/Perspex.Designer.csproj @@ -97,6 +97,7 @@ + diff --git a/src/Windows/Perspex.Designer/Settings.cs b/src/Windows/Perspex.Designer/Settings.cs new file mode 100644 index 0000000000..534dd2d5f1 --- /dev/null +++ b/src/Windows/Perspex.Designer/Settings.cs @@ -0,0 +1,19 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Microsoft.Win32; + +namespace Perspex.Designer +{ + static class Settings + { + static readonly string Root = @"HKEY_CURRENT_USER\Software\PerspexUI\Designer"; + public static string Background + { + get { return Registry.GetValue(Root, "Background", "#f3f3f3")?.ToString() ?? "#f3f3f3"; } + set { Registry.SetValue(Root, "Background", value); } + } + } +} From 81ca82b32806de92dd3a2a6826067bbc8a9fe4d4 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Fri, 27 Nov 2015 19:22:12 +0100 Subject: [PATCH 038/211] Removed debug code. --- src/Perspex.Controls/Presenters/ScrollContentPresenter.cs | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/Perspex.Controls/Presenters/ScrollContentPresenter.cs b/src/Perspex.Controls/Presenters/ScrollContentPresenter.cs index 76ba8c1545..ac82076637 100644 --- a/src/Perspex.Controls/Presenters/ScrollContentPresenter.cs +++ b/src/Perspex.Controls/Presenters/ScrollContentPresenter.cs @@ -134,8 +134,6 @@ namespace Perspex.Controls.Presenters Offset = offset; } - System.Diagnostics.Debug.WriteLine($"{targetRect} {offset}"); - return result; } else From 944f97684fd438a181d0a230b86ca2bb32e5a5c2 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Fri, 27 Nov 2015 19:57:50 +0100 Subject: [PATCH 039/211] Make Carousel transitions more intelligent. In the face of multiple rapid changes. Fixes #212. --- .../Presenters/CarouselPresenter.cs | 80 +++++++++++++------ 1 file changed, 56 insertions(+), 24 deletions(-) diff --git a/src/Perspex.Controls/Presenters/CarouselPresenter.cs b/src/Perspex.Controls/Presenters/CarouselPresenter.cs index 7af2416b9a..e130936d29 100644 --- a/src/Perspex.Controls/Presenters/CarouselPresenter.cs +++ b/src/Perspex.Controls/Presenters/CarouselPresenter.cs @@ -55,6 +55,8 @@ namespace Perspex.Controls.Presenters private int _selectedIndex = -1; private bool _createdPanel; private IItemContainerGenerator _generator; + private Task _currentTransition; + private int _queuedTransitionIndex = -1; /// /// Initializes static members of the class. @@ -187,35 +189,39 @@ namespace Perspex.Controls.Presenters /// A task tracking the animation. private async Task MoveToPage(int fromIndex, int toIndex) { - var generator = ItemContainerGenerator; - IControl from = null; - IControl to = null; - - if (fromIndex != -1) + if (fromIndex != toIndex) { - from = generator.ContainerFromIndex(fromIndex); - } + var generator = ItemContainerGenerator; + IControl from = null; + IControl to = null; - if (toIndex != -1) - { - var item = Items.Cast().ElementAt(toIndex); - to = generator.Materialize(toIndex, new[] { item }, MemberSelector).FirstOrDefault(); + if (fromIndex != -1) + { + from = generator.ContainerFromIndex(fromIndex); + } - if (to != null) + if (toIndex != -1) { - Panel.Children.Add(to); + var item = Items.Cast().ElementAt(toIndex); + to = generator.ContainerFromIndex(toIndex) ?? + generator.Materialize(toIndex, new[] { item }, MemberSelector).FirstOrDefault(); + + if (to != null) + { + Panel.Children.Add(to); + } } - } - if (Transition != null && (from != null || to != null)) - { - await Transition.Start((Visual)from, (Visual)to, fromIndex < toIndex); - } + if (Transition != null && (from != null || to != null)) + { + await Transition.Start((Visual)from, (Visual)to, fromIndex < toIndex); + } - if (from != null) - { - Panel.Children.Remove(from); - generator.Dematerialize(fromIndex, 1); + if (from != null) + { + Panel.Children.Remove(from); + generator.Dematerialize(fromIndex, 1); + } } } @@ -223,11 +229,37 @@ namespace Perspex.Controls.Presenters /// Called when the property changes. /// /// The event args. - private void SelectedIndexChanged(PerspexPropertyChangedEventArgs e) + private async void SelectedIndexChanged(PerspexPropertyChangedEventArgs e) { if (Panel != null) { - var task = MoveToPage((int)e.OldValue, (int)e.NewValue); + if (_currentTransition == null) + { + int fromIndex = (int)e.OldValue; + int toIndex = (int)e.NewValue; + + for (;;) + { + _currentTransition = MoveToPage(fromIndex, toIndex); + await _currentTransition; + + if (_queuedTransitionIndex != -1) + { + fromIndex = toIndex; + toIndex = _queuedTransitionIndex; + _queuedTransitionIndex = -1; + } + else + { + _currentTransition = null; + break; + } + } + } + else + { + _queuedTransitionIndex = (int)e.NewValue; + } } } } From b9992c582c7d9e36a810e3b1c633c00af075f4a9 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Fri, 27 Nov 2015 20:42:06 +0100 Subject: [PATCH 040/211] Added binding ConverterParameter. Closes #317. --- .../Perspex.Markup.Xaml/Data/Binding.cs | 8 +++- .../MarkupExtensions/BindingExtension.cs | 2 + .../Perspex.Markup/Data/ExpressionSubject.cs | 25 +++++++++-- .../Data/ExpressionSubjectTests.cs | 45 ++++++++++++++++++- .../Perspex.Markup.UnitTests.csproj | 4 ++ .../Perspex.Markup.UnitTests/packages.config | 1 + .../Data/BindingTests.cs | 18 ++++++++ 7 files changed, 98 insertions(+), 5 deletions(-) diff --git a/src/Markup/Perspex.Markup.Xaml/Data/Binding.cs b/src/Markup/Perspex.Markup.Xaml/Data/Binding.cs index 08d7e82387..b8ac6c8ac3 100644 --- a/src/Markup/Perspex.Markup.Xaml/Data/Binding.cs +++ b/src/Markup/Perspex.Markup.Xaml/Data/Binding.cs @@ -19,6 +19,11 @@ namespace Perspex.Markup.Xaml.Data /// public IValueConverter Converter { get; set; } + /// + /// Gets or sets a parameter to pass to . + /// + public object ConverterParameter { get; set; } + /// /// Gets or sets the name of the element to use as the binding source. /// @@ -115,7 +120,8 @@ namespace Perspex.Markup.Xaml.Data return new ExpressionSubject( observer, targetType, - Converter ?? DefaultValueConverter.Instance); + Converter ?? DefaultValueConverter.Instance, + ConverterParameter); } /// diff --git a/src/Markup/Perspex.Markup.Xaml/MarkupExtensions/BindingExtension.cs b/src/Markup/Perspex.Markup.Xaml/MarkupExtensions/BindingExtension.cs index c2cdc5efd3..0bf319c913 100644 --- a/src/Markup/Perspex.Markup.Xaml/MarkupExtensions/BindingExtension.cs +++ b/src/Markup/Perspex.Markup.Xaml/MarkupExtensions/BindingExtension.cs @@ -22,6 +22,7 @@ namespace Perspex.Markup.Xaml.MarkupExtensions return new Binding { Converter = Converter, + ConverterParameter = ConverterParameter, ElementName = ElementName, Mode = Mode, Path = Path, @@ -29,6 +30,7 @@ namespace Perspex.Markup.Xaml.MarkupExtensions } public IValueConverter Converter { get; set; } + public object ConverterParameter { get; set; } public string ElementName { get; set; } public BindingMode Mode { get; set; } public string Path { get; set; } diff --git a/src/Markup/Perspex.Markup/Data/ExpressionSubject.cs b/src/Markup/Perspex.Markup/Data/ExpressionSubject.cs index a39033ec59..686cb3b04f 100644 --- a/src/Markup/Perspex.Markup/Data/ExpressionSubject.cs +++ b/src/Markup/Perspex.Markup/Data/ExpressionSubject.cs @@ -34,7 +34,12 @@ namespace Perspex.Markup.Data /// The . /// The type to convert the value to. /// The value converter to use. - public ExpressionSubject(ExpressionObserver inner, Type targetType, IValueConverter converter) + /// A parameter to pass to . + public ExpressionSubject( + ExpressionObserver inner, + Type targetType, + IValueConverter converter, + object converterParameter = null) { Contract.Requires(inner != null); Contract.Requires(targetType != null); @@ -43,6 +48,7 @@ namespace Perspex.Markup.Data _inner = inner; _targetType = targetType; Converter = converter; + ConverterParameter = converterParameter; } /// @@ -50,6 +56,11 @@ namespace Perspex.Markup.Data /// public IValueConverter Converter { get; } + /// + /// Gets a parameter to pass to . + /// + public object ConverterParameter { get; } + /// string IDescription.Description => _inner.Expression; @@ -70,7 +81,11 @@ namespace Perspex.Markup.Data if (type != null) { - var converted = Converter.ConvertBack(value, type, null, CultureInfo.CurrentUICulture); + var converted = Converter.ConvertBack( + value, + type, + ConverterParameter, + CultureInfo.CurrentUICulture); if (converted == PerspexProperty.UnsetValue) { @@ -85,7 +100,11 @@ namespace Perspex.Markup.Data public IDisposable Subscribe(IObserver observer) { return _inner - .Select(x => Converter.Convert(x, _targetType, null, CultureInfo.CurrentUICulture)) + .Select(x => Converter.Convert( + x, + _targetType, + ConverterParameter, + CultureInfo.CurrentUICulture)) .Subscribe(observer); } } diff --git a/tests/Perspex.Markup.UnitTests/Data/ExpressionSubjectTests.cs b/tests/Perspex.Markup.UnitTests/Data/ExpressionSubjectTests.cs index c38085ef5f..7209d0bd4b 100644 --- a/tests/Perspex.Markup.UnitTests/Data/ExpressionSubjectTests.cs +++ b/tests/Perspex.Markup.UnitTests/Data/ExpressionSubjectTests.cs @@ -1,7 +1,11 @@ // 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.ComponentModel; +using System.Globalization; using System.Reactive.Linq; +using Moq; using Perspex.Markup.Data; using Xunit; @@ -125,11 +129,50 @@ namespace Perspex.Markup.UnitTests.Data Assert.Equal(0, data.DoubleValue); } - private class Class1 + [Fact] + public void Should_Pass_ConverterParameter_To_Convert() { + var data = new Class1 { DoubleValue = 5.6 }; + var converter = new Mock(); + var target = new ExpressionSubject( + new ExpressionObserver(data, "DoubleValue"), + typeof(string), + converter.Object, + "foo"); + + target.Subscribe(_ => { }); + + converter.Verify(x => x.Convert(5.6, typeof(string), "foo", CultureInfo.CurrentUICulture)); + } + + [Fact] + public void Should_Pass_ConverterParameter_To_ConvertBack() + { + var data = new Class1 { DoubleValue = 5.6 }; + var converter = new Mock(); + var target = new ExpressionSubject( + new ExpressionObserver(data, "DoubleValue"), + typeof(string), + converter.Object, + "foo"); + + target.OnNext("bar"); + + converter.Verify(x => x.ConvertBack("bar", typeof(double), "foo", CultureInfo.CurrentUICulture)); + } + + private class Class1 : INotifyPropertyChanged + { + public event PropertyChangedEventHandler PropertyChanged; + public string StringValue { get; set; } public double DoubleValue { get; set; } + + public void RaisePropertyChanged(string propertyName) + { + PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); + } } } } diff --git a/tests/Perspex.Markup.UnitTests/Perspex.Markup.UnitTests.csproj b/tests/Perspex.Markup.UnitTests/Perspex.Markup.UnitTests.csproj index 1bc9e09112..ad9843a103 100644 --- a/tests/Perspex.Markup.UnitTests/Perspex.Markup.UnitTests.csproj +++ b/tests/Perspex.Markup.UnitTests/Perspex.Markup.UnitTests.csproj @@ -39,6 +39,10 @@ True + + ..\..\packages\Moq.4.2.1507.0118\lib\net40\Moq.dll + True + diff --git a/tests/Perspex.Markup.UnitTests/packages.config b/tests/Perspex.Markup.UnitTests/packages.config index 2a659c426f..4e0bc991f5 100644 --- a/tests/Perspex.Markup.UnitTests/packages.config +++ b/tests/Perspex.Markup.UnitTests/packages.config @@ -1,5 +1,6 @@  + diff --git a/tests/Perspex.Markup.Xaml.UnitTests/Data/BindingTests.cs b/tests/Perspex.Markup.Xaml.UnitTests/Data/BindingTests.cs index 9184b485b7..02438baefd 100644 --- a/tests/Perspex.Markup.Xaml.UnitTests/Data/BindingTests.cs +++ b/tests/Perspex.Markup.Xaml.UnitTests/Data/BindingTests.cs @@ -2,6 +2,7 @@ // Licensed under the MIT license. See licence.md file in the project root for full license information. using System; +using System.Globalization; using System.Reactive.Linq; using System.Reactive.Subjects; using Moq; @@ -169,6 +170,23 @@ namespace Perspex.Markup.Xaml.UnitTests.Data Assert.Same(converter.Object, ((ExpressionSubject)result).Converter); } + [Fact] + public void Should_Pass_ConverterParameter_To_Supplied_Converter() + { + var target = CreateTarget(); + var converter = new Mock(); + var binding = new Binding + { + Converter = converter.Object, + ConverterParameter = "foo", + Path = "Bar", + }; + + var result = binding.CreateSubject(target.Object, TextBox.TextProperty.PropertyType); + + Assert.Same("foo", ((ExpressionSubject)result).ConverterParameter); + } + /// /// Tests a problem discovered with ListBox with selection. /// From e64e2f1c47edfe4881bb9c1c4f10ae76b4d51ef9 Mon Sep 17 00:00:00 2001 From: Nikita Tsukanov Date: Sat, 28 Nov 2015 04:56:24 +0300 Subject: [PATCH 041/211] Nuget --- nuget/build-appveyor.ps1 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nuget/build-appveyor.ps1 b/nuget/build-appveyor.ps1 index fafafdc2bd..1cf5f5a108 100644 --- a/nuget/build-appveyor.ps1 +++ b/nuget/build-appveyor.ps1 @@ -9,7 +9,7 @@ sv version $env:APPVEYOR_BUILD_NUMBER sv version 9999.0.$version-nightly sv key $env:myget_key -. "include.ps1" +. ".\include.ps1" .\build-version.ps1 $version sv reponame $env:APPVEYOR_REPO_NAME From b363ba8204d13e10e018ec46bf57fbebf1ec3280 Mon Sep 17 00:00:00 2001 From: Nikita Tsukanov Date: Sat, 28 Nov 2015 05:14:35 +0300 Subject: [PATCH 042/211] Powershell sucks --- nuget/build-appveyor.ps1 | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/nuget/build-appveyor.ps1 b/nuget/build-appveyor.ps1 index 1cf5f5a108..cbe6980648 100644 --- a/nuget/build-appveyor.ps1 +++ b/nuget/build-appveyor.ps1 @@ -1,3 +1,4 @@ +$ErrorActionPreference = "Stop" $scriptpath = $MyInvocation.MyCommand.Path $dir = Split-Path $scriptpath Push-Location $dir @@ -26,7 +27,7 @@ if ([string]::IsNullOrWhiteSpace($pullreq)) echo "Repo branch matched" foreach($pkg in $Packages) { - nuget.exe push $pkg.$version.nupkg $key -Source https://www.myget.org/F/perspex-nightly/api/v2/package + nuget.exe push "$($pkg).$($version).nupkg" $key -Source https://www.myget.org/F/perspex-nightly/api/v2/package } } } From 0de8b0a6ea24a2adb016cc013c75c37528cdb628 Mon Sep 17 00:00:00 2001 From: Nikita Tsukanov Date: Sat, 28 Nov 2015 05:16:50 +0300 Subject: [PATCH 043/211] File renaming --- .../Designer/{Designer.cs => DesignerAssist.cs} | 2 +- src/Perspex.Application/Perspex.Application.csproj | 2 +- src/Windows/Perspex.Designer/AppHost/PerspexAppHost.cs | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) rename src/Perspex.Application/Designer/{Designer.cs => DesignerAssist.cs} (98%) diff --git a/src/Perspex.Application/Designer/Designer.cs b/src/Perspex.Application/Designer/DesignerAssist.cs similarity index 98% rename from src/Perspex.Application/Designer/Designer.cs rename to src/Perspex.Application/Designer/DesignerAssist.cs index a04e1d1fef..778905058e 100644 --- a/src/Perspex.Application/Designer/Designer.cs +++ b/src/Perspex.Application/Designer/DesignerAssist.cs @@ -12,7 +12,7 @@ using Perspex.Themes.Default; namespace Perspex.Designer { - class Designer + class DesignerAssist { class DesignerApp : Application { diff --git a/src/Perspex.Application/Perspex.Application.csproj b/src/Perspex.Application/Perspex.Application.csproj index 08ecf9912e..5b1cc42c4a 100644 --- a/src/Perspex.Application/Perspex.Application.csproj +++ b/src/Perspex.Application/Perspex.Application.csproj @@ -89,7 +89,7 @@ Properties\SharedAssemblyInfo.cs - + diff --git a/src/Windows/Perspex.Designer/AppHost/PerspexAppHost.cs b/src/Windows/Perspex.Designer/AppHost/PerspexAppHost.cs index b855019f4c..24eef4ad33 100644 --- a/src/Windows/Perspex.Designer/AppHost/PerspexAppHost.cs +++ b/src/Windows/Perspex.Designer/AppHost/PerspexAppHost.cs @@ -201,7 +201,7 @@ namespace Perspex.Designer.AppHost log("Initializing built-in designer"); var dic = new Dictionary(); Api = new DesignerApi(dic) {OnResize = OnResize, OnWindowCreated = OnWindowCreated}; - LookupStaticMethod("Perspex.Designer.Designer", "Init").Invoke(null, new object[] {dic}); + LookupStaticMethod("Perspex.Designer.DesignerAssist", "Init").Invoke(null, new object[] {dic}); _window = new Control { From ffe0fc61e0abc6d0aacc7d97e82d42fa023a11fe Mon Sep 17 00:00:00 2001 From: Nikita Tsukanov Date: Sat, 28 Nov 2015 05:19:33 +0300 Subject: [PATCH 044/211] File rename --- src/Perspex.Application/Designer/DesignerApi.cs | 2 +- src/Perspex.Application/Designer/DesignerAssist.cs | 2 +- src/Windows/Perspex.Designer/AppHost/PerspexAppHost.cs | 3 ++- 3 files changed, 4 insertions(+), 3 deletions(-) diff --git a/src/Perspex.Application/Designer/DesignerApi.cs b/src/Perspex.Application/Designer/DesignerApi.cs index aebc621786..4a172af87b 100644 --- a/src/Perspex.Application/Designer/DesignerApi.cs +++ b/src/Perspex.Application/Designer/DesignerApi.cs @@ -5,7 +5,7 @@ using System.Runtime.CompilerServices; using System.Text; using System.Threading.Tasks; -namespace Perspex.Designer +namespace Perspex.DesignerSupport { class DesignerApi { diff --git a/src/Perspex.Application/Designer/DesignerAssist.cs b/src/Perspex.Application/Designer/DesignerAssist.cs index 778905058e..3228408087 100644 --- a/src/Perspex.Application/Designer/DesignerAssist.cs +++ b/src/Perspex.Application/Designer/DesignerAssist.cs @@ -10,7 +10,7 @@ using Perspex.Controls.Platform; using Perspex.Markup.Xaml; using Perspex.Themes.Default; -namespace Perspex.Designer +namespace Perspex.DesignerSupport { class DesignerAssist { diff --git a/src/Windows/Perspex.Designer/AppHost/PerspexAppHost.cs b/src/Windows/Perspex.Designer/AppHost/PerspexAppHost.cs index 24eef4ad33..7cd020e249 100644 --- a/src/Windows/Perspex.Designer/AppHost/PerspexAppHost.cs +++ b/src/Windows/Perspex.Designer/AppHost/PerspexAppHost.cs @@ -12,6 +12,7 @@ using Perspex.Designer.Comm; using Perspex.Designer.InProcDesigner; using Perspex.Designer.Metadata; using Timer = System.Windows.Forms.Timer; +using Perspex.DesignerSupport; namespace Perspex.Designer.AppHost { @@ -201,7 +202,7 @@ namespace Perspex.Designer.AppHost log("Initializing built-in designer"); var dic = new Dictionary(); Api = new DesignerApi(dic) {OnResize = OnResize, OnWindowCreated = OnWindowCreated}; - LookupStaticMethod("Perspex.Designer.DesignerAssist", "Init").Invoke(null, new object[] {dic}); + LookupStaticMethod("Perspex.DesignerSupport.DesignerAssist", "Init").Invoke(null, new object[] {dic}); _window = new Control { From 1c36606fa6b7ce8b9bec3197d8aa8b54cc18dec0 Mon Sep 17 00:00:00 2001 From: Nikita Tsukanov Date: Sat, 28 Nov 2015 05:33:21 +0300 Subject: [PATCH 045/211] Added Design.Width, Design.Height and Design.DataContext attached properties Fixes #152 Fixes #153 --- src/Perspex.Application/Designer/Design.cs | 56 +++++++++++++++++++ .../Designer/DesignerAssist.cs | 1 + .../Perspex.Application.csproj | 1 + 3 files changed, 58 insertions(+) create mode 100644 src/Perspex.Application/Designer/Design.cs diff --git a/src/Perspex.Application/Designer/Design.cs b/src/Perspex.Application/Designer/Design.cs new file mode 100644 index 0000000000..ef371c4f60 --- /dev/null +++ b/src/Perspex.Application/Designer/Design.cs @@ -0,0 +1,56 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Perspex.Controls; + +namespace Perspex +{ + public static class Design + { + public static bool IsDesignMode { get; internal set; } + + public static readonly PerspexProperty HeightProperty = PerspexProperty + .RegisterAttached("Height", typeof (Design)); + + public static void SetHeight(TopLevel topLevel, double value) + { + topLevel.SetValue(HeightProperty, value); + } + + public static double GetHeight(TopLevel topLevel) + { + return (double) topLevel.GetValue(HeightProperty); + } + + public static readonly PerspexProperty WidthProperty = PerspexProperty + .RegisterAttached("Width", typeof (Design)); + + public static void SetWidth(TopLevel topLevel, double value) + { + topLevel.SetValue(WidthProperty, value); + } + + public static double GetWidth(TopLevel topLevel) + { + return (double) topLevel.GetValue(WidthProperty); + } + + static Design() + { + WidthProperty.Changed.Subscribe(OnChanged); + HeightProperty.Changed.Subscribe(OnChanged); + } + + static void OnChanged(PerspexPropertyChangedEventArgs args) + { + if(!IsDesignMode) + return; + if (args.Property == WidthProperty) + ((TopLevel) args.Sender).Width = (double) args.NewValue; + if (args.Property == HeightProperty) + ((TopLevel)args.Sender).Height = (double)args.NewValue; + } + } +} diff --git a/src/Perspex.Application/Designer/DesignerAssist.cs b/src/Perspex.Application/Designer/DesignerAssist.cs index 3228408087..52c1e57930 100644 --- a/src/Perspex.Application/Designer/DesignerAssist.cs +++ b/src/Perspex.Application/Designer/DesignerAssist.cs @@ -29,6 +29,7 @@ namespace Perspex.DesignerSupport public static void Init(Dictionary shared) { + Design.IsDesignMode = true; Api = new DesignerApi(shared) {UpdateXaml = UpdateXaml, SetScalingFactor = SetScalingFactor}; new DesignerApp(); } diff --git a/src/Perspex.Application/Perspex.Application.csproj b/src/Perspex.Application/Perspex.Application.csproj index 5b1cc42c4a..be9c29e1c7 100644 --- a/src/Perspex.Application/Perspex.Application.csproj +++ b/src/Perspex.Application/Perspex.Application.csproj @@ -89,6 +89,7 @@ Properties\SharedAssemblyInfo.cs + From 99780d8495c4c307b42b6d7dcca8d86e7a234fb3 Mon Sep 17 00:00:00 2001 From: Nikita Tsukanov Date: Sat, 28 Nov 2015 05:48:49 +0300 Subject: [PATCH 046/211] Added Design.DataContext --- src/Perspex.Application/Designer/Design.cs | 20 ++++++++++++++++++-- 1 file changed, 18 insertions(+), 2 deletions(-) diff --git a/src/Perspex.Application/Designer/Design.cs b/src/Perspex.Application/Designer/Design.cs index ef371c4f60..a31f791f9d 100644 --- a/src/Perspex.Application/Designer/Design.cs +++ b/src/Perspex.Application/Designer/Design.cs @@ -25,7 +25,7 @@ namespace Perspex } public static readonly PerspexProperty WidthProperty = PerspexProperty - .RegisterAttached("Width", typeof (Design)); + .RegisterAttached("Width", typeof(Design)); public static void SetWidth(TopLevel topLevel, double value) { @@ -34,13 +34,27 @@ namespace Perspex public static double GetWidth(TopLevel topLevel) { - return (double) topLevel.GetValue(WidthProperty); + return (double)topLevel.GetValue(WidthProperty); + } + + public static readonly PerspexProperty DataContextProperty = PerspexProperty + .RegisterAttached("DataContext", typeof (Design)); + + public static void SetDataContext(TopLevel topLevel, object value) + { + topLevel.SetValue(DataContextProperty, value); + } + + public static object GetDataContext(TopLevel topLevel) + { + return topLevel.GetValue(DataContextProperty); } static Design() { WidthProperty.Changed.Subscribe(OnChanged); HeightProperty.Changed.Subscribe(OnChanged); + DataContextProperty.Changed.Subscribe(OnChanged); } static void OnChanged(PerspexPropertyChangedEventArgs args) @@ -51,6 +65,8 @@ namespace Perspex ((TopLevel) args.Sender).Width = (double) args.NewValue; if (args.Property == HeightProperty) ((TopLevel)args.Sender).Height = (double)args.NewValue; + if (args.Property == DataContextProperty) + ((TopLevel) args.Sender).DataContext = args.NewValue; } } } From 2678bd26654979193001b1be177867dd918d0c62 Mon Sep 17 00:00:00 2001 From: Nikita Tsukanov Date: Sat, 28 Nov 2015 19:35:23 +0300 Subject: [PATCH 047/211] Load user's application class in Designer --- src/Perspex.Application/Application.cs | 8 +++++++- .../Designer/DesignerAssist.cs | 15 ++++++++++++++- 2 files changed, 21 insertions(+), 2 deletions(-) diff --git a/src/Perspex.Application/Application.cs b/src/Perspex.Application/Application.cs index 027ca0b759..84f40d12fb 100644 --- a/src/Perspex.Application/Application.cs +++ b/src/Perspex.Application/Application.cs @@ -205,7 +205,7 @@ namespace Perspex /// Initializes the rendering or windowing subsystem defined by the specified assemblt. /// /// The name of the assembly. - protected void InitializeSubsystem(string assemblyName) + protected static void InitializeSubsystem(string assemblyName) { var assembly = Assembly.Load(new AssemblyName(assemblyName)); var platformClassName = assemblyName.Replace("Perspex.", string.Empty) + "Platform"; @@ -214,5 +214,11 @@ namespace Perspex var init = platformClass.GetRuntimeMethod("Initialize", new Type[0]); init.Invoke(null, null); } + + internal static void InitializeWin32Subsystem() + { + InitializeSubsystem("Perspex.Direct2D1"); + InitializeSubsystem("Perspex.Win32"); + } } } diff --git a/src/Perspex.Application/Designer/DesignerAssist.cs b/src/Perspex.Application/Designer/DesignerAssist.cs index 52c1e57930..8a0bbc2449 100644 --- a/src/Perspex.Application/Designer/DesignerAssist.cs +++ b/src/Perspex.Application/Designer/DesignerAssist.cs @@ -1,13 +1,16 @@ using System; using System.Collections.Generic; +using System.Diagnostics; using System.IO; using System.Linq; +using System.Reflection; using System.Text; using System.Threading.Tasks; using OmniXaml; using Perspex.Controls; using Perspex.Controls.Platform; using Perspex.Markup.Xaml; +using Perspex.Platform; using Perspex.Themes.Default; namespace Perspex.DesignerSupport @@ -31,7 +34,17 @@ namespace Perspex.DesignerSupport { Design.IsDesignMode = true; Api = new DesignerApi(shared) {UpdateXaml = UpdateXaml, SetScalingFactor = SetScalingFactor}; - new DesignerApp(); + Application.RegisterPlatformCallback(Application.InitializeWin32Subsystem); + + var plat = (IPclPlatformWrapper) Activator.CreateInstance(Assembly.Load(new AssemblyName("Perspex.Win32")) + .DefinedTypes.First(typeof (IPclPlatformWrapper).GetTypeInfo().IsAssignableFrom).AsType()); + var app = plat.GetLoadedAssemblies() + .SelectMany(a => a.DefinedTypes) + .Where(typeof (Application).GetTypeInfo().IsAssignableFrom).FirstOrDefault(t => t.Assembly != typeof (Application).GetTypeInfo().Assembly); + if (app == null) + new DesignerApp(); + else + Activator.CreateInstance(app.AsType()); } private static void SetScalingFactor(double factor) From 4f282cd0a067a53abd2c35a000f9e6b201aab301 Mon Sep 17 00:00:00 2001 From: Nikita Tsukanov Date: Sat, 28 Nov 2015 20:06:15 +0300 Subject: [PATCH 048/211] Design properties now can be applied to Control --- src/Perspex.Application/Designer/Design.cs | 36 +++++++++++----------- 1 file changed, 18 insertions(+), 18 deletions(-) diff --git a/src/Perspex.Application/Designer/Design.cs b/src/Perspex.Application/Designer/Design.cs index a31f791f9d..72bb23c067 100644 --- a/src/Perspex.Application/Designer/Design.cs +++ b/src/Perspex.Application/Designer/Design.cs @@ -12,42 +12,42 @@ namespace Perspex public static bool IsDesignMode { get; internal set; } public static readonly PerspexProperty HeightProperty = PerspexProperty - .RegisterAttached("Height", typeof (Design)); + .RegisterAttached("Height", typeof (Design)); - public static void SetHeight(TopLevel topLevel, double value) + public static void SetHeight(Control control, double value) { - topLevel.SetValue(HeightProperty, value); + control.SetValue(HeightProperty, value); } - public static double GetHeight(TopLevel topLevel) + public static double GetHeight(Control control) { - return (double) topLevel.GetValue(HeightProperty); + return control.GetValue(HeightProperty); } public static readonly PerspexProperty WidthProperty = PerspexProperty - .RegisterAttached("Width", typeof(Design)); + .RegisterAttached("Width", typeof(Design)); - public static void SetWidth(TopLevel topLevel, double value) + public static void SetWidth(Control control, double value) { - topLevel.SetValue(WidthProperty, value); + control.SetValue(WidthProperty, value); } - public static double GetWidth(TopLevel topLevel) + public static double GetWidth(Control control) { - return (double)topLevel.GetValue(WidthProperty); + return control.GetValue(WidthProperty); } public static readonly PerspexProperty DataContextProperty = PerspexProperty - .RegisterAttached("DataContext", typeof (Design)); + .RegisterAttached("DataContext", typeof (Design)); - public static void SetDataContext(TopLevel topLevel, object value) + public static void SetDataContext(Control control, object value) { - topLevel.SetValue(DataContextProperty, value); + control.SetValue(DataContextProperty, value); } - public static object GetDataContext(TopLevel topLevel) + public static object GetDataContext(Control control) { - return topLevel.GetValue(DataContextProperty); + return control.GetValue(DataContextProperty); } static Design() @@ -62,11 +62,11 @@ namespace Perspex if(!IsDesignMode) return; if (args.Property == WidthProperty) - ((TopLevel) args.Sender).Width = (double) args.NewValue; + ((Control) args.Sender).Width = (double) args.NewValue; if (args.Property == HeightProperty) - ((TopLevel)args.Sender).Height = (double)args.NewValue; + ((Control)args.Sender).Height = (double)args.NewValue; if (args.Property == DataContextProperty) - ((TopLevel) args.Sender).DataContext = args.NewValue; + ((Control) args.Sender).DataContext = args.NewValue; } } } From 2c39da715312cc66e9777e22ff5d1a472dd2ee04 Mon Sep 17 00:00:00 2001 From: Nikita Tsukanov Date: Sat, 28 Nov 2015 20:38:45 +0300 Subject: [PATCH 049/211] Apply design properties to tollevel --- src/Perspex.Application/Designer/Design.cs | 27 +++++++------------ .../Designer/DesignerAssist.cs | 8 +++--- 2 files changed, 14 insertions(+), 21 deletions(-) diff --git a/src/Perspex.Application/Designer/Design.cs b/src/Perspex.Application/Designer/Design.cs index 72bb23c067..d27646d6c7 100644 --- a/src/Perspex.Application/Designer/Design.cs +++ b/src/Perspex.Application/Designer/Design.cs @@ -49,24 +49,15 @@ namespace Perspex { return control.GetValue(DataContextProperty); } - - static Design() - { - WidthProperty.Changed.Subscribe(OnChanged); - HeightProperty.Changed.Subscribe(OnChanged); - DataContextProperty.Changed.Subscribe(OnChanged); - } - - static void OnChanged(PerspexPropertyChangedEventArgs args) - { - if(!IsDesignMode) - return; - if (args.Property == WidthProperty) - ((Control) args.Sender).Width = (double) args.NewValue; - if (args.Property == HeightProperty) - ((Control)args.Sender).Height = (double)args.NewValue; - if (args.Property == DataContextProperty) - ((Control) args.Sender).DataContext = args.NewValue; + + internal static void ApplyDesignerProperties(Control target, Control source) + { + if (source.IsSet(WidthProperty)) + target.Width = source.GetValue(WidthProperty); + if (source.IsSet(HeightProperty)) + target.Height = source.GetValue(HeightProperty); + if (source.IsSet(DataContextProperty)) + target.DataContext = source.GetValue(DataContextProperty); } } } diff --git a/src/Perspex.Application/Designer/DesignerAssist.cs b/src/Perspex.Application/Designer/DesignerAssist.cs index 8a0bbc2449..31f716b8be 100644 --- a/src/Perspex.Application/Designer/DesignerAssist.cs +++ b/src/Perspex.Application/Designer/DesignerAssist.cs @@ -60,18 +60,20 @@ namespace Perspex.DesignerSupport { Window window; + Control original; using (PlatformManager.DesignerMode()) { - var obj = ((XamlXmlLoader)new PerspexXamlLoader()).Load(new MemoryStream(Encoding.UTF8.GetBytes(xaml))); - window = obj as Window; + original =(Control)((XamlXmlLoader)new PerspexXamlLoader()).Load(new MemoryStream(Encoding.UTF8.GetBytes(xaml))); + window = original as Window; if (window == null) { - window = new Window() {Content = obj}; + window = new Window() {Content = original}; } } s_currentWindow?.Close(); s_currentWindow = window; window.Show(); + Design.ApplyDesignerProperties(window, original); Api.OnWindowCreated?.Invoke(window.PlatformImpl.Handle.Handle); Api.OnResize?.Invoke(); } From 9eb54f59d240b2ff5744b061ca370a5b3547bdb0 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Sat, 28 Nov 2015 21:48:27 +0100 Subject: [PATCH 050/211] Fixed name scopes. Controls were being registered with the nearest name scope on the visual tree, whereas they should be registered by traversing the logical tree. --- .../Context/NameScopeWrapper.cs | 4 +- .../Context/PerspexXamlType.cs | 10 +- src/Markup/Perspex.Markup/ControlLocator.cs | 2 +- src/Perspex.Base/PerspexProperty.cs | 2 +- src/Perspex.Controls/Button.cs | 12 -- src/Perspex.Controls/Control.cs | 31 ++++- src/Perspex.Controls/ControlExtensions.cs | 4 +- src/Perspex.Controls/Decorator.cs | 2 +- .../INameScope.cs | 2 +- .../NameScope.cs | 8 +- .../NameScopeEventArgs.cs | 2 +- .../NameScopeExtensions.cs | 2 +- src/Perspex.Controls/Perspex.Controls.csproj | 4 + .../Presenters/ContentPresenter.cs | 5 +- .../Primitives/TemplatedControl.cs | 2 +- .../Perspex.SceneGraph.csproj | 4 - src/Perspex.SceneGraph/Visual.cs | 87 +++--------- .../VisualTreeAttachmentEventArgs.cs | 9 +- .../ControlTests_NameScope.cs | 125 ++++++++++++++++++ .../NameScopeTests.cs | 2 +- .../Perspex.Controls.UnitTests.csproj | 2 + .../Primitives/TemplatedControlTests.cs | 2 +- .../Perspex.SceneGraph.UnitTests.csproj | 3 +- .../Perspex.SceneGraph.UnitTests/TestRoot.cs | 31 +---- .../VisualTests.cs | 50 ------- 25 files changed, 208 insertions(+), 199 deletions(-) rename src/{Perspex.SceneGraph => Perspex.Controls}/INameScope.cs (98%) rename src/{Perspex.SceneGraph => Perspex.Controls}/NameScope.cs (93%) rename src/{Perspex.SceneGraph => Perspex.Controls}/NameScopeEventArgs.cs (94%) rename src/{Perspex.SceneGraph => Perspex.Controls}/NameScopeExtensions.cs (98%) create mode 100644 tests/Perspex.Controls.UnitTests/ControlTests_NameScope.cs rename tests/{Perspex.SceneGraph.UnitTests => Perspex.Controls.UnitTests}/NameScopeTests.cs (97%) diff --git a/src/Markup/Perspex.Markup.Xaml/Context/NameScopeWrapper.cs b/src/Markup/Perspex.Markup.Xaml/Context/NameScopeWrapper.cs index 4cb67ce912..2a5e46825a 100644 --- a/src/Markup/Perspex.Markup.Xaml/Context/NameScopeWrapper.cs +++ b/src/Markup/Perspex.Markup.Xaml/Context/NameScopeWrapper.cs @@ -5,9 +5,9 @@ namespace Perspex.Markup.Xaml.Context { internal class NameScopeWrapper : OmniXaml.INameScope { - private Perspex.INameScope _inner; + private readonly Perspex.Controls.INameScope _inner; - public NameScopeWrapper(Perspex.INameScope inner) + public NameScopeWrapper(Perspex.Controls.INameScope inner) { _inner = inner; } diff --git a/src/Markup/Perspex.Markup.Xaml/Context/PerspexXamlType.cs b/src/Markup/Perspex.Markup.Xaml/Context/PerspexXamlType.cs index c771b30509..413df6c3ea 100644 --- a/src/Markup/Perspex.Markup.Xaml/Context/PerspexXamlType.cs +++ b/src/Markup/Perspex.Markup.Xaml/Context/PerspexXamlType.cs @@ -5,7 +5,7 @@ using System; using System.Reflection; using OmniXaml; using OmniXaml.Typing; -using Perspex.Markup.Xaml.Data; +using Perspex.Controls; namespace Perspex.Markup.Xaml.Context { @@ -20,15 +20,15 @@ namespace Perspex.Markup.Xaml.Context public override OmniXaml.INameScope GetNamescope(object instance) { - var result = this.UnderlyingType as OmniXaml.INameScope; + var result = instance as OmniXaml.INameScope; if (result == null) { - var visual = instance as Visual; + var control = instance as Control; - if (visual != null) + if (control != null) { - var perspexNs = (instance as Perspex.INameScope) ?? NameScope.GetNameScope(visual); + var perspexNs = (instance as Perspex.Controls.INameScope) ?? NameScope.GetNameScope(control); if (perspexNs != null) { diff --git a/src/Markup/Perspex.Markup/ControlLocator.cs b/src/Markup/Perspex.Markup/ControlLocator.cs index 36330d89c5..c2c080137d 100644 --- a/src/Markup/Perspex.Markup/ControlLocator.cs +++ b/src/Markup/Perspex.Markup/ControlLocator.cs @@ -25,7 +25,7 @@ namespace Perspex.Markup var attached = Observable.FromEventPattern( x => relativeTo.AttachedToVisualTree += x, x => relativeTo.DetachedFromVisualTree += x) - .Select(x => x.EventArgs.NameScope) + .Select(x => ((IControl)x.Sender).FindNameScope()) .StartWith(relativeTo.FindNameScope()); var detached = Observable.FromEventPattern( diff --git a/src/Perspex.Base/PerspexProperty.cs b/src/Perspex.Base/PerspexProperty.cs index 94f9baf5ba..ef06bf4326 100644 --- a/src/Perspex.Base/PerspexProperty.cs +++ b/src/Perspex.Base/PerspexProperty.cs @@ -56,7 +56,7 @@ namespace Perspex /// /// Gets the ID of the property. /// - private int _id; + private readonly int _id; /// /// Initializes a new instance of the class. diff --git a/src/Perspex.Controls/Button.cs b/src/Perspex.Controls/Button.cs index a1ca0e11a6..0bd29dbb64 100644 --- a/src/Perspex.Controls/Button.cs +++ b/src/Perspex.Controls/Button.cs @@ -134,18 +134,6 @@ namespace Perspex.Controls set { SetValue(IsDefaultProperty, value); } } - /// - protected override Size MeasureOverride(Size availableSize) - { - return base.MeasureOverride(availableSize); - } - - /// - protected override Size ArrangeOverride(Size finalSize) - { - return base.ArrangeOverride(finalSize); - } - /// protected override void OnAttachedToVisualTree(VisualTreeAttachmentEventArgs e) { diff --git a/src/Perspex.Controls/Control.cs b/src/Perspex.Controls/Control.cs index b382e82675..f9d04faa73 100644 --- a/src/Perspex.Controls/Control.cs +++ b/src/Perspex.Controls/Control.cs @@ -71,6 +71,7 @@ namespace Perspex.Controls private DataTemplates _dataTemplates; private IControl _focusAdorner; private IPerspexList _logicalChildren; + private INameScope _nameScope; private Styles _styles; /// @@ -84,6 +85,14 @@ namespace Perspex.Controls PseudoClass(IsPointerOverProperty, ":pointerover"); } + /// + /// Initializes a new instance of the class. + /// + public Control() + { + _nameScope = this as INameScope; + } + /// /// Gets or sets the control's classes. /// @@ -376,11 +385,27 @@ namespace Perspex.Controls { base.OnAttachedToVisualTree(e); - IStyler styler = PerspexLocator.Current.GetService(); + if (_nameScope == null) + { + _nameScope = NameScope.GetNameScope(this) ?? ((Control)Parent)?._nameScope; + } + + if (Name != null) + { + _nameScope?.Register(Name, this); + } + + PerspexLocator.Current.GetService()?.ApplyStyles(this); + } + + /// + protected override void OnDetachedFromVisualTree(VisualTreeAttachmentEventArgs e) + { + base.OnDetachedFromVisualTree(e); - if (styler != null) + if (Name != null) { - styler.ApplyStyles(this); + _nameScope?.Unregister(Name); } } diff --git a/src/Perspex.Controls/ControlExtensions.cs b/src/Perspex.Controls/ControlExtensions.cs index 08514258f7..6667810f24 100644 --- a/src/Perspex.Controls/ControlExtensions.cs +++ b/src/Perspex.Controls/ControlExtensions.cs @@ -59,14 +59,14 @@ namespace Perspex.Controls } /// - /// Finds the name scope for a control. + /// Finds the name scope for a control by searching up the logical tree. /// /// The control. /// The control's name scope, or null if not found. public static INameScope FindNameScope(this IControl control) { return control.GetSelfAndLogicalAncestors() - .OfType() + .OfType() .Select(x => (x as INameScope) ?? NameScope.GetNameScope(x)) .FirstOrDefault(x => x != null); } diff --git a/src/Perspex.Controls/Decorator.cs b/src/Perspex.Controls/Decorator.cs index a32e4364f5..1e1733a814 100644 --- a/src/Perspex.Controls/Decorator.cs +++ b/src/Perspex.Controls/Decorator.cs @@ -98,9 +98,9 @@ namespace Perspex.Controls if (newChild != null) { + ((ISetLogicalParent)newChild).SetParent(this); AddVisualChild(newChild); LogicalChildren.Add(newChild); - ((ISetLogicalParent)newChild).SetParent(this); } } } diff --git a/src/Perspex.SceneGraph/INameScope.cs b/src/Perspex.Controls/INameScope.cs similarity index 98% rename from src/Perspex.SceneGraph/INameScope.cs rename to src/Perspex.Controls/INameScope.cs index 6e54c90afd..a6c115a4ec 100644 --- a/src/Perspex.SceneGraph/INameScope.cs +++ b/src/Perspex.Controls/INameScope.cs @@ -3,7 +3,7 @@ using System; -namespace Perspex +namespace Perspex.Controls { /// /// Defines a name scope. diff --git a/src/Perspex.SceneGraph/NameScope.cs b/src/Perspex.Controls/NameScope.cs similarity index 93% rename from src/Perspex.SceneGraph/NameScope.cs rename to src/Perspex.Controls/NameScope.cs index f4d0addc82..768e9de9dc 100644 --- a/src/Perspex.SceneGraph/NameScope.cs +++ b/src/Perspex.Controls/NameScope.cs @@ -4,7 +4,7 @@ using System; using System.Collections.Generic; -namespace Perspex +namespace Perspex.Controls { /// /// Implements a name scope. @@ -15,7 +15,7 @@ namespace Perspex /// Defines the NameScope attached property. /// public static readonly PerspexProperty NameScopeProperty = - PerspexProperty.RegisterAttached("NameScope"); + PerspexProperty.RegisterAttached("NameScope"); private readonly Dictionary _inner = new Dictionary(); @@ -34,7 +34,7 @@ namespace Perspex /// /// The visual. /// The value of the NameScope attached property. - public static INameScope GetNameScope(Visual visual) + public static INameScope GetNameScope(Control visual) { return visual.GetValue(NameScopeProperty); } @@ -44,7 +44,7 @@ namespace Perspex /// /// The visual. /// The value to set. - public static void SetNameScope(Visual visual, INameScope value) + public static void SetNameScope(Control visual, INameScope value) { visual.SetValue(NameScopeProperty, value); } diff --git a/src/Perspex.SceneGraph/NameScopeEventArgs.cs b/src/Perspex.Controls/NameScopeEventArgs.cs similarity index 94% rename from src/Perspex.SceneGraph/NameScopeEventArgs.cs rename to src/Perspex.Controls/NameScopeEventArgs.cs index 4d19082a24..254b13c4bd 100644 --- a/src/Perspex.SceneGraph/NameScopeEventArgs.cs +++ b/src/Perspex.Controls/NameScopeEventArgs.cs @@ -3,7 +3,7 @@ using System; -namespace Perspex +namespace Perspex.Controls { public class NameScopeEventArgs : EventArgs { diff --git a/src/Perspex.SceneGraph/NameScopeExtensions.cs b/src/Perspex.Controls/NameScopeExtensions.cs similarity index 98% rename from src/Perspex.SceneGraph/NameScopeExtensions.cs rename to src/Perspex.Controls/NameScopeExtensions.cs index 3e80f8e838..9053adebee 100644 --- a/src/Perspex.SceneGraph/NameScopeExtensions.cs +++ b/src/Perspex.Controls/NameScopeExtensions.cs @@ -4,7 +4,7 @@ using System; using System.Collections.Generic; -namespace Perspex +namespace Perspex.Controls { /// /// Extension methods for . diff --git a/src/Perspex.Controls/Perspex.Controls.csproj b/src/Perspex.Controls/Perspex.Controls.csproj index 80b1aba585..2682d76d16 100644 --- a/src/Perspex.Controls/Perspex.Controls.csproj +++ b/src/Perspex.Controls/Perspex.Controls.csproj @@ -43,6 +43,10 @@ + + + + diff --git a/src/Perspex.Controls/Presenters/ContentPresenter.cs b/src/Perspex.Controls/Presenters/ContentPresenter.cs index b2b2e6ff9f..46a57dd613 100644 --- a/src/Perspex.Controls/Presenters/ContentPresenter.cs +++ b/src/Perspex.Controls/Presenters/ContentPresenter.cs @@ -106,8 +106,8 @@ namespace Perspex.Controls.Presenters if (old != null) { - logicalChildren.Remove(old); ((ISetLogicalParent)old).SetParent(null); + logicalChildren.Remove(old); ClearVisualChildren(); } @@ -120,13 +120,12 @@ namespace Perspex.Controls.Presenters result.DataContext = content; } - AddVisualChild(result); - if (result.Parent == null) { ((ISetLogicalParent)result).SetParent((ILogical)logicalHost ?? this); } + AddVisualChild(result); logicalChildren.Remove(old); logicalChildren.Add(result); } diff --git a/src/Perspex.Controls/Primitives/TemplatedControl.cs b/src/Perspex.Controls/Primitives/TemplatedControl.cs index 170bfa6fa8..9e98ad154c 100644 --- a/src/Perspex.Controls/Primitives/TemplatedControl.cs +++ b/src/Perspex.Controls/Primitives/TemplatedControl.cs @@ -196,7 +196,7 @@ namespace Perspex.Controls.Primitives var child = Template.Build(this); var nameScope = new NameScope(); - NameScope.SetNameScope((Visual)child, nameScope); + NameScope.SetNameScope((Control)child, nameScope); // We need to call SetTemplatedParentAndApplyChildTemplates twice - once // before the controls are added to the visual tree so that the logical diff --git a/src/Perspex.SceneGraph/Perspex.SceneGraph.csproj b/src/Perspex.SceneGraph/Perspex.SceneGraph.csproj index 2121be7bfc..776589de81 100644 --- a/src/Perspex.SceneGraph/Perspex.SceneGraph.csproj +++ b/src/Perspex.SceneGraph/Perspex.SceneGraph.csproj @@ -56,7 +56,6 @@ - @@ -103,9 +102,6 @@ - - - diff --git a/src/Perspex.SceneGraph/Visual.cs b/src/Perspex.SceneGraph/Visual.cs index f559fd0f6e..14267ef132 100644 --- a/src/Perspex.SceneGraph/Visual.cs +++ b/src/Perspex.SceneGraph/Visual.cs @@ -407,12 +407,7 @@ namespace Perspex /// The event args. private static void AffectsRenderInvalidate(PerspexPropertyChangedEventArgs e) { - Visual visual = e.Sender as Visual; - - if (visual != null) - { - visual.InvalidateVisual(); - } + (e.Sender as Visual)?.InvalidateVisual(); } /// @@ -425,48 +420,8 @@ namespace Perspex /// private VisualTreeAttachmentEventArgs GetAttachmentEventArgs() { - var e = (IVisual)this; - IRenderRoot root = null; - INameScope nameScope = null; - - while (e != null) - { - if (nameScope == null) - { - nameScope = e as INameScope ?? NameScope.GetNameScope((Visual)e); - } - - root = e as IRenderRoot; - - if (root != null) - { - return new VisualTreeAttachmentEventArgs(root, nameScope); - } - - e = e.VisualParent; - } - - return null; - } - - /// - /// Gets the for this element based on the - /// parent's args. - /// - /// The parent args. - /// The args for this element. - private VisualTreeAttachmentEventArgs GetAttachmentEventArgs(VisualTreeAttachmentEventArgs e) - { - var childNameScope = (this as INameScope) ?? NameScope.GetNameScope(this); - - if (childNameScope != null) - { - return new VisualTreeAttachmentEventArgs(e.Root, childNameScope); - } - else - { - return e; - } + var root = this.GetSelfAndVisualAncestors().OfType().FirstOrDefault(); + return root != null ? new VisualTreeAttachmentEventArgs(root) : null; } /// @@ -560,7 +515,14 @@ namespace Perspex /// The visual parent. private void SetVisualParent(Visual value) { - if (_visualParent != value) + if (_visualParent == value) + { + return; + } + + var old = _visualParent; + + if (_isAttachedToVisualTree) { var oldArgs = GetAttachmentEventArgs(); @@ -570,16 +532,23 @@ namespace Perspex { NotifyDetachedFromVisualTree(oldArgs); } + } + else + { + _visualParent = value; + } + if (_visualParent is IRenderRoot || _visualParent?.IsAttachedToVisualTree == true) + { var newArgs = GetAttachmentEventArgs(); if (newArgs != null) { NotifyAttachedToVisualTree(newArgs); } - - RaisePropertyChanged(VisualParentProperty, oldArgs, value, BindingPriority.LocalValue); } + + RaisePropertyChanged(VisualParentProperty, old, value, BindingPriority.LocalValue); } /// @@ -622,19 +591,13 @@ namespace Perspex _isAttachedToVisualTree = true; - if (Name != null && e.NameScope != null) - { - e.NameScope.Register(Name, this); - } - OnAttachedToVisualTree(e); if (_visualChildren != null) { foreach (Visual child in _visualChildren.OfType()) { - var ce = child.GetAttachmentEventArgs(e); - child.NotifyAttachedToVisualTree(ce); + child.NotifyAttachedToVisualTree(e); } } } @@ -648,11 +611,6 @@ namespace Perspex { _visualLogger.Verbose("Detached from visual tree"); - if (Name != null && e.NameScope != null) - { - e.NameScope.Unregister(Name); - } - _isAttachedToVisualTree = false; OnDetachedFromVisualTree(e); @@ -660,8 +618,7 @@ namespace Perspex { foreach (Visual child in _visualChildren.OfType()) { - var ce = child.GetAttachmentEventArgs(e); - child.NotifyDetachedFromVisualTree(ce); + child.NotifyDetachedFromVisualTree(e); } } } diff --git a/src/Perspex.SceneGraph/VisualTreeAttachmentEventArgs.cs b/src/Perspex.SceneGraph/VisualTreeAttachmentEventArgs.cs index 41f37e26fc..b2df5a9a34 100644 --- a/src/Perspex.SceneGraph/VisualTreeAttachmentEventArgs.cs +++ b/src/Perspex.SceneGraph/VisualTreeAttachmentEventArgs.cs @@ -16,21 +16,14 @@ namespace Perspex /// Initializes a new instance of the class. /// /// The root visual. - /// The name scope. - public VisualTreeAttachmentEventArgs(IRenderRoot root, INameScope nameScope) + public VisualTreeAttachmentEventArgs(IRenderRoot root) { Root = root; - NameScope = nameScope; } /// /// Gets the root of the visual tree that the visual is being attached to or detached from. /// public IRenderRoot Root { get; } - - /// - /// Gets the element's name scope. - /// - public INameScope NameScope { get; } } } diff --git a/tests/Perspex.Controls.UnitTests/ControlTests_NameScope.cs b/tests/Perspex.Controls.UnitTests/ControlTests_NameScope.cs new file mode 100644 index 0000000000..2aa911b61a --- /dev/null +++ b/tests/Perspex.Controls.UnitTests/ControlTests_NameScope.cs @@ -0,0 +1,125 @@ +// 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 Perspex.Controls.Presenters; +using Perspex.Controls.Templates; +using Perspex.Rendering; +using Xunit; + +namespace Perspex.Controls.UnitTests +{ + public class ControlTests_NameScope + { + [Fact] + public void Controls_Should_Register_With_NameScope() + { + var root = new TestRoot + { + Content = new Border + { + Name = "foo", + Child = new Border + { + Name = "bar", + } + } + }; + + root.ApplyTemplate(); + + Assert.Same(root.Find("foo"), root.Content); + Assert.Same(root.Find("bar"), ((Border)root.Content).Child); + } + + [Fact] + public void Control_Should_Unregister_With_NameScope() + { + var root = new TestRoot + { + Content = new Border + { + Name = "foo", + Child = new Border + { + Name = "bar", + } + } + }; + + root.ApplyTemplate(); + root.Content = null; + root.Presenter.ApplyTemplate(); + + Assert.Null(root.Find("foo")); + Assert.Null(root.Find("bar")); + } + + [Fact] + public void Control_Should_Not_Register_With_Template_NameScope() + { + var root = new TestRoot + { + Content = new Border + { + Name = "foo", + } + }; + + root.ApplyTemplate(); + + Assert.Null(NameScope.GetNameScope(root.Presenter).Find("foo")); + } + + private class TestRoot : ContentControl, IRenderRoot, INameScope + { + private readonly NameScope _nameScope = new NameScope(); + + public TestRoot() + { + Template = new FuncControlTemplate(x => new ContentPresenter + { + Name = "PART_ContentPresenter", + [!ContentPresenter.ContentProperty] = x[!ContentControl.ContentProperty], + }); + } + + public event EventHandler Registered + { + add { _nameScope.Registered += value; } + remove { _nameScope.Registered -= value; } + } + + public event EventHandler Unregistered + { + add { _nameScope.Unregistered += value; } + remove { _nameScope.Unregistered -= value; } + } + + public IRenderQueueManager RenderQueueManager + { + get { throw new NotImplementedException(); } + } + + public Point TranslatePointToScreen(Point p) + { + throw new NotImplementedException(); + } + + public void Register(string name, object element) + { + _nameScope.Register(name, element); + } + + public object Find(string name) + { + return _nameScope.Find(name); + } + + public void Unregister(string name) + { + _nameScope.Unregister(name); + } + } + } +} diff --git a/tests/Perspex.SceneGraph.UnitTests/NameScopeTests.cs b/tests/Perspex.Controls.UnitTests/NameScopeTests.cs similarity index 97% rename from tests/Perspex.SceneGraph.UnitTests/NameScopeTests.cs rename to tests/Perspex.Controls.UnitTests/NameScopeTests.cs index a774e6884d..da4079f3c1 100644 --- a/tests/Perspex.SceneGraph.UnitTests/NameScopeTests.cs +++ b/tests/Perspex.Controls.UnitTests/NameScopeTests.cs @@ -4,7 +4,7 @@ using System; using Xunit; -namespace Perspex.SceneGraph.UnitTests +namespace Perspex.Controls.UnitTests { public class NameScopeTests { diff --git a/tests/Perspex.Controls.UnitTests/Perspex.Controls.UnitTests.csproj b/tests/Perspex.Controls.UnitTests/Perspex.Controls.UnitTests.csproj index c19e639895..745556a791 100644 --- a/tests/Perspex.Controls.UnitTests/Perspex.Controls.UnitTests.csproj +++ b/tests/Perspex.Controls.UnitTests/Perspex.Controls.UnitTests.csproj @@ -81,12 +81,14 @@ + + diff --git a/tests/Perspex.Controls.UnitTests/Primitives/TemplatedControlTests.cs b/tests/Perspex.Controls.UnitTests/Primitives/TemplatedControlTests.cs index 1d39e8d443..d0f0547854 100644 --- a/tests/Perspex.Controls.UnitTests/Primitives/TemplatedControlTests.cs +++ b/tests/Perspex.Controls.UnitTests/Primitives/TemplatedControlTests.cs @@ -70,7 +70,7 @@ namespace Perspex.Controls.UnitTests.Primitives target.ApplyTemplate(); - Assert.NotNull(NameScope.GetNameScope((Visual)target.GetVisualChildren().Single())); + Assert.NotNull(NameScope.GetNameScope((Control)target.GetVisualChildren().Single())); } [Fact] diff --git a/tests/Perspex.SceneGraph.UnitTests/Perspex.SceneGraph.UnitTests.csproj b/tests/Perspex.SceneGraph.UnitTests/Perspex.SceneGraph.UnitTests.csproj index f5cfe531c9..d161b1264f 100644 --- a/tests/Perspex.SceneGraph.UnitTests/Perspex.SceneGraph.UnitTests.csproj +++ b/tests/Perspex.SceneGraph.UnitTests/Perspex.SceneGraph.UnitTests.csproj @@ -72,7 +72,6 @@ - @@ -160,4 +159,4 @@ --> - + \ No newline at end of file diff --git a/tests/Perspex.SceneGraph.UnitTests/TestRoot.cs b/tests/Perspex.SceneGraph.UnitTests/TestRoot.cs index 62bc782b78..99a1fd0d4a 100644 --- a/tests/Perspex.SceneGraph.UnitTests/TestRoot.cs +++ b/tests/Perspex.SceneGraph.UnitTests/TestRoot.cs @@ -7,22 +7,8 @@ using Perspex.Rendering; namespace Perspex.SceneGraph.UnitTests { - public class TestRoot : TestVisual, IRenderRoot, INameScope + public class TestRoot : TestVisual, IRenderRoot { - private NameScope _nameScope = new NameScope(); - - event EventHandler INameScope.Registered - { - add { _nameScope.Registered += value; } - remove { _nameScope.Registered -= value; } - } - - public event EventHandler Unregistered - { - add { _nameScope.Unregistered += value; } - remove { _nameScope.Unregistered -= value; } - } - public IRenderTarget RenderTarget { get { throw new NotImplementedException(); } @@ -37,20 +23,5 @@ namespace Perspex.SceneGraph.UnitTests { throw new NotImplementedException(); } - - public void Register(string name, object element) - { - _nameScope.Register(name, element); - } - - public object Find(string name) - { - return _nameScope.Find(name); - } - - public void Unregister(string name) - { - _nameScope.Unregister(name); - } } } diff --git a/tests/Perspex.SceneGraph.UnitTests/VisualTests.cs b/tests/Perspex.SceneGraph.UnitTests/VisualTests.cs index 7756130c20..f4785b66c1 100644 --- a/tests/Perspex.SceneGraph.UnitTests/VisualTests.cs +++ b/tests/Perspex.SceneGraph.UnitTests/VisualTests.cs @@ -121,55 +121,5 @@ namespace Perspex.SceneGraph.UnitTests Assert.True(called1); Assert.True(called2); } - - [Fact] - public void Controls_Should_Add_Themselves_To_Root_NameScope() - { - var child2 = new TestVisual { Name = "bar" }; - var child1 = new TestVisual { Name = "foo", Child = child2 }; - var root = new TestRoot { Child = child1 }; - - Assert.Same(child1, root.Find("foo")); - Assert.Same(child2, root.Find("bar")); - } - - [Fact] - public void Controls_Should_Add_Themselves_To_NameScopes_In_Attached_Property() - { - var child2 = new TestVisual { Name = "bar", [NameScope.NameScopeProperty] = new NameScope() }; - var child1 = new TestVisual { Name = "foo", Child = child2}; - var root = new TestRoot { Child = child1 }; - - Assert.Same(child1, root.Find("foo")); - Assert.Null(root.Find("bar")); - Assert.Same(child2, NameScope.GetNameScope(child2).Find("bar")); - } - - [Fact] - public void Controls_Should_Remove_Themselves_From_Root_NameScope() - { - var child2 = new TestVisual { Name = "bar" }; - var child1 = new TestVisual { Name = "foo", Child = child2 }; - var root = new TestRoot { Child = child1 }; - - root.Child = null; - - Assert.Null(root.Find("foo")); - Assert.Null(root.Find("bar")); - } - - [Fact] - public void Controls_Should_Remove_Themselves_From_NameScopes_In_Attached_Property() - { - var child2 = new TestVisual { Name = "bar" }; - var child1 = new TestVisual { Name = "foo", Child = child2,[NameScope.NameScopeProperty] = new NameScope() }; - var root = new TestRoot { Child = child1 }; - - root.Child = null; - - Assert.Null(root.Find("foo")); - Assert.Null(root.Find("bar")); - Assert.Null(NameScope.GetNameScope(child1).Find("bar")); - } } } From 55d867ba546206b46b1475227da1570c562ae306 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Sat, 28 Nov 2015 22:02:09 +0100 Subject: [PATCH 051/211] Suppress XML doc comment warnings. --- src/Perspex.Application/Perspex.Application.csproj | 1 + src/Perspex.Base/Perspex.Base.csproj | 1 + src/Perspex.Controls/Perspex.Controls.csproj | 1 + src/Perspex.Diagnostics/Perspex.Diagnostics.csproj | 1 + src/Perspex.Input/Perspex.Input.csproj | 1 + src/Perspex.Interactivity/Perspex.Interactivity.csproj | 1 + src/Perspex.SceneGraph/Perspex.SceneGraph.csproj | 1 + src/Perspex.Styling/Perspex.Styling.csproj | 1 + src/Windows/Perspex.Direct2D1/Perspex.Direct2D1.csproj | 1 + src/Windows/Perspex.Win32/Perspex.Win32.csproj | 1 + 10 files changed, 10 insertions(+) diff --git a/src/Perspex.Application/Perspex.Application.csproj b/src/Perspex.Application/Perspex.Application.csproj index be9c29e1c7..bbafdcf1a7 100644 --- a/src/Perspex.Application/Perspex.Application.csproj +++ b/src/Perspex.Application/Perspex.Application.csproj @@ -27,6 +27,7 @@ prompt 4 bin\Debug\Perspex.Application.XML + CS1591 pdbonly diff --git a/src/Perspex.Base/Perspex.Base.csproj b/src/Perspex.Base/Perspex.Base.csproj index deeb85a600..733d8cc69e 100644 --- a/src/Perspex.Base/Perspex.Base.csproj +++ b/src/Perspex.Base/Perspex.Base.csproj @@ -27,6 +27,7 @@ prompt 4 bin\Debug\Perspex.Base.XML + CS1591 pdbonly diff --git a/src/Perspex.Controls/Perspex.Controls.csproj b/src/Perspex.Controls/Perspex.Controls.csproj index 2682d76d16..9270658e51 100644 --- a/src/Perspex.Controls/Perspex.Controls.csproj +++ b/src/Perspex.Controls/Perspex.Controls.csproj @@ -27,6 +27,7 @@ prompt 4 bin\Debug\Perspex.Controls.XML + CS1591 pdbonly diff --git a/src/Perspex.Diagnostics/Perspex.Diagnostics.csproj b/src/Perspex.Diagnostics/Perspex.Diagnostics.csproj index 98fff81ad5..17db490d9b 100644 --- a/src/Perspex.Diagnostics/Perspex.Diagnostics.csproj +++ b/src/Perspex.Diagnostics/Perspex.Diagnostics.csproj @@ -27,6 +27,7 @@ prompt 4 bin\Debug\Perspex.Diagnostics.XML + CS1591 pdbonly diff --git a/src/Perspex.Input/Perspex.Input.csproj b/src/Perspex.Input/Perspex.Input.csproj index c853ea9bb1..babd0e034b 100644 --- a/src/Perspex.Input/Perspex.Input.csproj +++ b/src/Perspex.Input/Perspex.Input.csproj @@ -27,6 +27,7 @@ prompt 4 bin\Debug\Perspex.Input.XML + CS1591 pdbonly diff --git a/src/Perspex.Interactivity/Perspex.Interactivity.csproj b/src/Perspex.Interactivity/Perspex.Interactivity.csproj index c67efb6c11..79e4820236 100644 --- a/src/Perspex.Interactivity/Perspex.Interactivity.csproj +++ b/src/Perspex.Interactivity/Perspex.Interactivity.csproj @@ -27,6 +27,7 @@ prompt 4 bin\Debug\Perspex.Interactivity.XML + CS1591 pdbonly diff --git a/src/Perspex.SceneGraph/Perspex.SceneGraph.csproj b/src/Perspex.SceneGraph/Perspex.SceneGraph.csproj index 776589de81..a6c4f6da14 100644 --- a/src/Perspex.SceneGraph/Perspex.SceneGraph.csproj +++ b/src/Perspex.SceneGraph/Perspex.SceneGraph.csproj @@ -27,6 +27,7 @@ prompt 4 bin\Debug\Perspex.SceneGraph.XML + CS1591 pdbonly diff --git a/src/Perspex.Styling/Perspex.Styling.csproj b/src/Perspex.Styling/Perspex.Styling.csproj index a8e997821e..c741543430 100644 --- a/src/Perspex.Styling/Perspex.Styling.csproj +++ b/src/Perspex.Styling/Perspex.Styling.csproj @@ -27,6 +27,7 @@ prompt 4 bin\Debug\Perspex.Styling.XML + CS1591 pdbonly diff --git a/src/Windows/Perspex.Direct2D1/Perspex.Direct2D1.csproj b/src/Windows/Perspex.Direct2D1/Perspex.Direct2D1.csproj index 38e9b9ed8e..924d90927b 100644 --- a/src/Windows/Perspex.Direct2D1/Perspex.Direct2D1.csproj +++ b/src/Windows/Perspex.Direct2D1/Perspex.Direct2D1.csproj @@ -23,6 +23,7 @@ prompt 4 bin\Debug\Perspex.Direct2D1.XML + CS1591 pdbonly diff --git a/src/Windows/Perspex.Win32/Perspex.Win32.csproj b/src/Windows/Perspex.Win32/Perspex.Win32.csproj index 29adac0267..fab4e69617 100644 --- a/src/Windows/Perspex.Win32/Perspex.Win32.csproj +++ b/src/Windows/Perspex.Win32/Perspex.Win32.csproj @@ -24,6 +24,7 @@ 4 bin\Debug\Perspex.Win32.XML true + CS1591 pdbonly From ca4c21256a18e3e919a37ed6860014eb93bdc6d8 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Sat, 28 Nov 2015 22:12:38 +0100 Subject: [PATCH 052/211] Fixed some warnings. --- .../Perspex.Gtk/Input/GtkKeyboardDevice.cs | 2 +- .../Data/ExpressionNodeBuilder.cs | 2 +- .../Data/ExpressionParseException.cs | 19 +++++++++++++------ .../Data/Parsers/ArgumentListParser.cs | 8 ++++---- .../Data/Parsers/ExpressionParser.cs | 4 ++-- src/Perspex.Base/Threading/DispatcherTimer.cs | 2 -- .../Platform/IPlatformRenderInterface.cs | 2 -- src/Perspex.Styling/Styling/Selectors.cs | 1 - src/Skia/Perspex.Skia/BitmapImpl.cs | 3 --- .../Perspex.Direct2D1/Media/TileBrushImpl.cs | 8 ++++---- src/Windows/Perspex.Direct2D1/RenderTarget.cs | 2 -- .../Input/WindowsKeyboardDevice.cs | 4 +--- .../Perspex.Win32/Interop/UnmanagedMethods.cs | 2 +- .../InteractiveTests.cs | 2 -- 14 files changed, 27 insertions(+), 34 deletions(-) diff --git a/src/Gtk/Perspex.Gtk/Input/GtkKeyboardDevice.cs b/src/Gtk/Perspex.Gtk/Input/GtkKeyboardDevice.cs index 3ccec97656..b0b9016ff6 100644 --- a/src/Gtk/Perspex.Gtk/Input/GtkKeyboardDevice.cs +++ b/src/Gtk/Perspex.Gtk/Input/GtkKeyboardDevice.cs @@ -214,7 +214,7 @@ namespace Perspex.Gtk //{ Gdk.Key.?, Key.DeadCharProcessed } }; - public static GtkKeyboardDevice Instance { get; } = new GtkKeyboardDevice(); + public new static GtkKeyboardDevice Instance { get; } = new GtkKeyboardDevice(); public static Key ConvertKey(Gdk.Key key) { diff --git a/src/Markup/Perspex.Markup/Data/ExpressionNodeBuilder.cs b/src/Markup/Perspex.Markup/Data/ExpressionNodeBuilder.cs index af70bc717e..93b38a438a 100644 --- a/src/Markup/Perspex.Markup/Data/ExpressionNodeBuilder.cs +++ b/src/Markup/Perspex.Markup/Data/ExpressionNodeBuilder.cs @@ -20,7 +20,7 @@ namespace Perspex.Markup.Data if (!reader.End) { - throw new ExpressionParseException(reader, "Expected end of expression."); + throw new ExpressionParseException(reader.Position, "Expected end of expression."); } return node; diff --git a/src/Markup/Perspex.Markup/Data/ExpressionParseException.cs b/src/Markup/Perspex.Markup/Data/ExpressionParseException.cs index fffeac8a77..d92f42cab7 100644 --- a/src/Markup/Perspex.Markup/Data/ExpressionParseException.cs +++ b/src/Markup/Perspex.Markup/Data/ExpressionParseException.cs @@ -6,19 +6,26 @@ using Perspex.Markup.Data.Parsers; namespace Perspex.Markup.Data { + /// + /// Exception thrown when could not parse the provided + /// expression string. + /// public class ExpressionParseException : Exception { - internal ExpressionParseException(int column, string message) + /// + /// Initializes a new instance of the class. + /// + /// The column position of the error. + /// The exception message. + public ExpressionParseException(int column, string message) : base(message) { Column = column; } - internal ExpressionParseException(Reader r, string message) - : this(r.Position, message) - { - } - + /// + /// Gets the column position at which the error occurred. + /// public int Column { get; } } } diff --git a/src/Markup/Perspex.Markup/Data/Parsers/ArgumentListParser.cs b/src/Markup/Perspex.Markup/Data/Parsers/ArgumentListParser.cs index 588e3257b5..5b67166739 100644 --- a/src/Markup/Perspex.Markup/Data/Parsers/ArgumentListParser.cs +++ b/src/Markup/Perspex.Markup/Data/Parsers/ArgumentListParser.cs @@ -26,14 +26,14 @@ namespace Perspex.Markup.Data.Parsers } else { - throw new ExpressionParseException(r, "Expected integer."); + throw new ExpressionParseException(r.Position, "Expected integer."); } r.SkipWhitespace(); if (r.End) { - throw new ExpressionParseException(r, "Expected ','."); + throw new ExpressionParseException(r.Position, "Expected ','."); } else if (r.TakeIf(close)) { @@ -43,7 +43,7 @@ namespace Perspex.Markup.Data.Parsers { if (r.Take() != ',') { - throw new ExpressionParseException(r, "Expected ','."); + throw new ExpressionParseException(r.Position, "Expected ','."); } r.SkipWhitespace(); @@ -57,7 +57,7 @@ namespace Perspex.Markup.Data.Parsers } else { - throw new ExpressionParseException(r, "Expected ']'."); + throw new ExpressionParseException(r.Position, "Expected ']'."); } } diff --git a/src/Markup/Perspex.Markup/Data/Parsers/ExpressionParser.cs b/src/Markup/Perspex.Markup/Data/Parsers/ExpressionParser.cs index 6f743175c9..2ec7f49f5c 100644 --- a/src/Markup/Perspex.Markup/Data/Parsers/ExpressionParser.cs +++ b/src/Markup/Perspex.Markup/Data/Parsers/ExpressionParser.cs @@ -34,7 +34,7 @@ namespace Perspex.Markup.Data.Parsers if (state == State.BeforeMember) { - throw new ExpressionParseException(r, "Unexpected end of expression."); + throw new ExpressionParseException(r.Position, "Unexpected end of expression."); } for (int n = 0; n < nodes.Count - 1; ++n) @@ -80,7 +80,7 @@ namespace Perspex.Markup.Data.Parsers { if (args.Count == 0) { - throw new ExpressionParseException(r, "Indexer may not be empty."); + throw new ExpressionParseException(r.Position, "Indexer may not be empty."); } nodes.Add(new IndexerNode(args)); diff --git a/src/Perspex.Base/Threading/DispatcherTimer.cs b/src/Perspex.Base/Threading/DispatcherTimer.cs index d84bcf519e..18eedff08f 100644 --- a/src/Perspex.Base/Threading/DispatcherTimer.cs +++ b/src/Perspex.Base/Threading/DispatcherTimer.cs @@ -31,7 +31,6 @@ namespace Perspex.Threading /// Initializes a new instance of the class. /// /// The priority to use. - /// The dispatcher to use. public DispatcherTimer(DispatcherPriority priority) { _priority = priority; @@ -43,7 +42,6 @@ namespace Perspex.Threading /// /// The interval at which to tick. /// The priority to use. - /// The dispatcher to use. /// The event to call when the timer ticks. public DispatcherTimer(TimeSpan interval, DispatcherPriority priority, EventHandler callback) : this(priority) { diff --git a/src/Perspex.SceneGraph/Platform/IPlatformRenderInterface.cs b/src/Perspex.SceneGraph/Platform/IPlatformRenderInterface.cs index ccf55104a8..244c3bc335 100644 --- a/src/Perspex.SceneGraph/Platform/IPlatformRenderInterface.cs +++ b/src/Perspex.SceneGraph/Platform/IPlatformRenderInterface.cs @@ -39,8 +39,6 @@ namespace Perspex.Platform /// Creates a renderer. /// /// The platform handle for the renderer. - /// The initial width of the render. - /// The initial height of the render. /// An . IRenderTarget CreateRenderer(IPlatformHandle handle); diff --git a/src/Perspex.Styling/Styling/Selectors.cs b/src/Perspex.Styling/Styling/Selectors.cs index d291bcd3f9..65f0d6dee0 100644 --- a/src/Perspex.Styling/Styling/Selectors.cs +++ b/src/Perspex.Styling/Styling/Selectors.cs @@ -131,7 +131,6 @@ namespace Perspex.Styling /// /// Returns a selector which matches a control with the specified property value. /// - /// The property type. /// The previous selector. /// The property. /// The property value. diff --git a/src/Skia/Perspex.Skia/BitmapImpl.cs b/src/Skia/Perspex.Skia/BitmapImpl.cs index 75c2ae5213..83d89feac0 100644 --- a/src/Skia/Perspex.Skia/BitmapImpl.cs +++ b/src/Skia/Perspex.Skia/BitmapImpl.cs @@ -10,9 +10,6 @@ namespace Perspex.Skia { class BitmapImpl : PerspexHandleHolder, IRenderTargetBitmapImpl { - private int width; - private int height; - public void Save(string fileName) { var ext = Path.GetExtension(fileName)?.ToLower(); diff --git a/src/Windows/Perspex.Direct2D1/Media/TileBrushImpl.cs b/src/Windows/Perspex.Direct2D1/Media/TileBrushImpl.cs index 0f0d929b9e..413035f5e9 100644 --- a/src/Windows/Perspex.Direct2D1/Media/TileBrushImpl.cs +++ b/src/Windows/Perspex.Direct2D1/Media/TileBrushImpl.cs @@ -36,7 +36,7 @@ namespace Perspex.Direct2D1.Media } - protected static BrushProperties GetBrushProperties(TileBrush brush, Rect destinationRect) + private static BrushProperties GetBrushProperties(TileBrush brush, Rect destinationRect) { return new BrushProperties { @@ -49,7 +49,7 @@ namespace Perspex.Direct2D1.Media }; } - protected static BitmapBrushProperties GetBitmapBrushProperties(TileBrush brush) + private static BitmapBrushProperties GetBitmapBrushProperties(TileBrush brush) { var tileMode = brush.TileMode; @@ -60,12 +60,12 @@ namespace Perspex.Direct2D1.Media }; } - protected static ExtendMode GetExtendModeX(TileMode tileMode) + private static ExtendMode GetExtendModeX(TileMode tileMode) { return (tileMode & TileMode.FlipX) != 0 ? ExtendMode.Mirror : ExtendMode.Wrap; } - protected static ExtendMode GetExtendModeY(TileMode tileMode) + private static ExtendMode GetExtendModeY(TileMode tileMode) { return (tileMode & TileMode.FlipY) != 0 ? ExtendMode.Mirror : ExtendMode.Wrap; } diff --git a/src/Windows/Perspex.Direct2D1/RenderTarget.cs b/src/Windows/Perspex.Direct2D1/RenderTarget.cs index 53a04683d2..9627f25053 100644 --- a/src/Windows/Perspex.Direct2D1/RenderTarget.cs +++ b/src/Windows/Perspex.Direct2D1/RenderTarget.cs @@ -30,8 +30,6 @@ namespace Perspex.Direct2D1 /// Initializes a new instance of the class. /// /// The window handle. - /// The width of the window. - /// The height of the window. public RenderTarget(IntPtr hwnd) { _hwnd = hwnd; diff --git a/src/Windows/Perspex.Win32/Input/WindowsKeyboardDevice.cs b/src/Windows/Perspex.Win32/Input/WindowsKeyboardDevice.cs index 10598c4e00..34935f5e82 100644 --- a/src/Windows/Perspex.Win32/Input/WindowsKeyboardDevice.cs +++ b/src/Windows/Perspex.Win32/Input/WindowsKeyboardDevice.cs @@ -10,11 +10,9 @@ namespace Perspex.Win32.Input { public class WindowsKeyboardDevice : KeyboardDevice { - private static readonly WindowsKeyboardDevice s_instance = new WindowsKeyboardDevice(); - private readonly byte[] _keyStates = new byte[256]; - public static new WindowsKeyboardDevice Instance => s_instance; + public new static WindowsKeyboardDevice Instance { get; } = new WindowsKeyboardDevice(); public InputModifiers Modifiers { diff --git a/src/Windows/Perspex.Win32/Interop/UnmanagedMethods.cs b/src/Windows/Perspex.Win32/Interop/UnmanagedMethods.cs index 80dd0cd672..3c7154e480 100644 --- a/src/Windows/Perspex.Win32/Interop/UnmanagedMethods.cs +++ b/src/Windows/Perspex.Win32/Interop/UnmanagedMethods.cs @@ -7,7 +7,7 @@ using System.Runtime.InteropServices; using System.Text; // ReSharper disable InconsistentNaming -#pragma warning disable 169 +#pragma warning disable 169, 649 namespace Perspex.Win32.Interop { diff --git a/tests/Perspex.Interactivity.UnitTests/InteractiveTests.cs b/tests/Perspex.Interactivity.UnitTests/InteractiveTests.cs index 156fa05fce..3a3d47947a 100644 --- a/tests/Perspex.Interactivity.UnitTests/InteractiveTests.cs +++ b/tests/Perspex.Interactivity.UnitTests/InteractiveTests.cs @@ -338,8 +338,6 @@ namespace Perspex.Interactivity.UnitTests private class TestInteractive : Interactive { - public string Name { get; set; } - public bool ClassHandlerInvoked { get; private set; } public IEnumerable Children From 0ec58456e105725b83c26ab144f8d03da8a9bb0f Mon Sep 17 00:00:00 2001 From: Nikita Tsukanov Date: Sun, 29 Nov 2015 00:19:31 +0300 Subject: [PATCH 053/211] Removed unused property --- src/Windows/Perspex.Win32/Embedding/EmbeddedWindowImpl.cs | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/Windows/Perspex.Win32/Embedding/EmbeddedWindowImpl.cs b/src/Windows/Perspex.Win32/Embedding/EmbeddedWindowImpl.cs index 052e83a28f..2dd8573bf6 100644 --- a/src/Windows/Perspex.Win32/Embedding/EmbeddedWindowImpl.cs +++ b/src/Windows/Perspex.Win32/Embedding/EmbeddedWindowImpl.cs @@ -10,8 +10,6 @@ namespace Perspex.Win32 { private static readonly System.Windows.Forms.UserControl WinFormsControl = new System.Windows.Forms.UserControl(); - public IntPtr Handle { get; private set; } - protected override IntPtr CreateWindowOverride(ushort atom) { var hWnd = UnmanagedMethods.CreateWindowEx( @@ -27,7 +25,6 @@ namespace Perspex.Win32 IntPtr.Zero, IntPtr.Zero, IntPtr.Zero); - Handle = hWnd; return hWnd; } } From 49a89ba0364025f46fbfc1bdea341a5902818820 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Sat, 28 Nov 2015 22:23:04 +0100 Subject: [PATCH 054/211] Fixed more warnings. --- .../Parsers/SelectorGrammar.cs | 12 +++++--- src/Perspex.SceneGraph/Media/Pen.cs | 30 +++++++++++++++---- 2 files changed, 32 insertions(+), 10 deletions(-) diff --git a/src/Markup/Perspex.Markup.Xaml/Parsers/SelectorGrammar.cs b/src/Markup/Perspex.Markup.Xaml/Parsers/SelectorGrammar.cs index ba3e2fa5ac..1823995321 100644 --- a/src/Markup/Perspex.Markup.Xaml/Parsers/SelectorGrammar.cs +++ b/src/Markup/Perspex.Markup.Xaml/Parsers/SelectorGrammar.cs @@ -3,10 +3,12 @@ using System.Collections.Generic; using System.Globalization; -using System.Linq; -using Perspex.Styling; using Sprache; +// Don't need to override GetHashCode as the ISyntax objects will not be stored in a hash; the +// only reason they have overridden Equals methods is for unit testing. +#pragma warning disable 659 + namespace Perspex.Markup.Xaml.Parsers { internal class SelectorGrammar @@ -129,7 +131,8 @@ namespace Perspex.Markup.Xaml.Parsers public override bool Equals(object obj) { - return obj is OfTypeSyntax && ((OfTypeSyntax)obj).TypeName == TypeName; + var other = obj as OfTypeSyntax; + return other != null && other.TypeName == TypeName && other.Xmlns == Xmlns; } } @@ -141,7 +144,8 @@ namespace Perspex.Markup.Xaml.Parsers public override bool Equals(object obj) { - return obj is IsSyntax && ((IsSyntax)obj).TypeName == TypeName; + var other = obj as IsSyntax; + return other != null && other.TypeName == TypeName && other.Xmlns == Xmlns; } } diff --git a/src/Perspex.SceneGraph/Media/Pen.cs b/src/Perspex.SceneGraph/Media/Pen.cs index b813392ccd..cc14ae29d6 100644 --- a/src/Perspex.SceneGraph/Media/Pen.cs +++ b/src/Perspex.SceneGraph/Media/Pen.cs @@ -1,8 +1,6 @@ // 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.Collections.Generic; - namespace Perspex.Media { /// @@ -15,11 +13,21 @@ namespace Perspex.Media /// /// The brush used to draw. /// The stroke thickness. + /// The dash style. + /// The dash cap. + /// The start line cap. + /// The end line cap. + /// The line join. + /// The miter limit. public Pen( Brush brush, double thickness = 1.0, - DashStyle dashStyle = null, PenLineCap dashCap = PenLineCap.Flat, PenLineCap startLineCap = PenLineCap.Flat, - PenLineCap endLineCap = PenLineCap.Flat, PenLineJoin lineJoin = PenLineJoin.Miter, double miterLimit = 10.0) + DashStyle dashStyle = null, + PenLineCap dashCap = PenLineCap.Flat, + PenLineCap startLineCap = PenLineCap.Flat, + PenLineCap endLineCap = PenLineCap.Flat, + PenLineJoin lineJoin = PenLineJoin.Miter, + double miterLimit = 10.0) { Brush = brush; Thickness = thickness; @@ -36,11 +44,21 @@ namespace Perspex.Media /// /// The stroke color. /// The stroke thickness. + /// The dash style. + /// The dash cap. + /// The start line cap. + /// The end line cap. + /// The line join. + /// The miter limit. public Pen( uint color, double thickness = 1.0, - DashStyle dashStyle = null, PenLineCap dashCap = PenLineCap.Flat, PenLineCap startLineCap = PenLineCap.Flat, - PenLineCap endLineCap = PenLineCap.Flat, PenLineJoin lineJoin = PenLineJoin.Miter, double miterLimit = 10.0) + DashStyle dashStyle = null, + PenLineCap dashCap = PenLineCap.Flat, + PenLineCap startLineCap = PenLineCap.Flat, + PenLineCap endLineCap = PenLineCap.Flat, + PenLineJoin lineJoin = PenLineJoin.Miter, + double miterLimit = 10.0) { Brush = new SolidColorBrush(color); Thickness = thickness; From 67835cc09b3ebbd7c1ed6f56b1910f880408887f Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Sat, 28 Nov 2015 23:05:25 +0100 Subject: [PATCH 055/211] Applied a bunch of resharper suggestions. --- src/Gtk/Perspex.Cairo/CairoPlatform.cs | 2 +- .../Perspex.Cairo/Media/FormattedTextImpl.cs | 2 +- src/Gtk/Perspex.Cairo/RenderTarget.cs | 2 +- .../Context/PerspexContentPropertyProvider.cs | 2 +- .../PerspexPropertyTypeConverter.cs | 10 ++--- .../Perspex.Markup.Xaml/Data/MultiBinding.cs | 2 +- .../Parsers/SelectorParser.cs | 2 +- .../Templates/MemberSelector.cs | 10 +---- .../Perspex.Markup/Data/ExpressionNode.cs | 5 +-- .../Perspex.Markup/Data/ExpressionObserver.cs | 4 +- .../Perspex.Markup/Data/ExpressionSubject.cs | 4 +- src/Markup/Perspex.Markup/Data/IndexerNode.cs | 2 +- .../Perspex.Markup/Data/LogicalNotNode.cs | 4 +- .../Perspex.Markup/Data/Parsers/Reader.cs | 2 +- .../Plugins/InpcPropertyAccessorPlugin.cs | 16 +++----- .../Plugins/PerspexPropertyAccessorPlugin.cs | 14 ++----- .../Perspex.Markup/FuncMultiValueConverter.cs | 2 +- .../Perspex.Markup/FuncValueConverter.cs | 2 +- src/Markup/Perspex.Markup/StringConverters.cs | 2 +- src/Perspex.Animation/Animatable.cs | 7 +--- src/Perspex.Animation/LinearEasing.cs | 5 +-- src/Perspex.Application/Application.cs | 16 +------- src/Perspex.Base/BindingDescriptor.cs | 2 +- .../Collections/PerspexDictionary.cs | 5 +-- src/Perspex.Base/Collections/PerspexList.cs | 16 ++------ .../Metadata/XmlnsDefinitionAttribute.cs | 4 +- src/Perspex.Base/PerspexLocator.cs | 2 +- src/Perspex.Base/PerspexObject.cs | 17 ++------- src/Perspex.Base/PerspexProperty.cs | 2 +- src/Perspex.Base/PriorityBindingEntry.cs | 5 +-- .../Reactive/PerspexObservable.cs | 5 +-- src/Perspex.Base/Threading/DispatcherTimer.cs | 8 +--- .../Threading/SingleThreadDispatcher.cs | 4 +- src/Perspex.Controls/Button.cs | 5 +-- src/Perspex.Controls/Control.cs | 34 +++-------------- src/Perspex.Controls/Decorator.cs | 7 +--- src/Perspex.Controls/DockPanel.cs | 11 ++---- .../Generators/TreeItemContainerGenerator.cs | 21 +++------- src/Perspex.Controls/HotkeyManager.cs | 6 +-- src/Perspex.Controls/ListBox.cs | 5 +-- src/Perspex.Controls/Menu.cs | 2 +- src/Perspex.Controls/MenuItem.cs | 11 +----- .../MenuItemAccessKeyHandler.cs | 8 +--- .../Primitives/AdornerLayer.cs | 6 +-- src/Perspex.Controls/Primitives/Popup.cs | 8 ++-- src/Perspex.Controls/Primitives/PopupRoot.cs | 9 +---- .../Primitives/SelectingItemsControl.cs | 2 +- src/Perspex.Controls/TabControl.cs | 5 +-- .../Templates/FuncDataTemplate.cs | 4 +- .../Templates/FuncDataTemplate`1.cs | 2 +- .../Templates/FuncMemberSelector.cs | 2 +- .../Templates/FuncTreeDataTemplate.cs | 4 +- .../Templates/FuncTreeDataTemplate`1.cs | 2 +- src/Perspex.Controls/TextBox.cs | 2 +- src/Perspex.Controls/ToolTip.cs | 2 +- src/Perspex.Controls/TopLevel.cs | 38 +++++-------------- src/Perspex.Controls/UserControl.cs | 2 +- src/Perspex.Controls/Window.cs | 4 +- src/Perspex.Diagnostics/LogManager.cs | 13 +------ .../ViewModels/PropertyDetails.cs | 2 +- .../ViewModels/VisualTreeNode.cs | 2 +- .../Adapters/GraphicsAdapter.cs | 2 +- src/Perspex.Input/FocusManager.cs | 2 +- src/Perspex.Input/KeyBinding.cs | 4 +- src/Perspex.Input/KeyGesture.cs | 2 +- src/Perspex.Input/KeyboardDevice.cs | 22 ++++------- src/Perspex.Input/MouseDevice.cs | 34 +++++++---------- .../Navigation/DirectionalNavigation.cs | 13 ++----- src/Perspex.Input/Navigation/TabNavigation.cs | 13 ++----- src/Perspex.Layout/Layoutable.cs | 24 ++---------- src/Perspex.SceneGraph/Media/Color.cs | 2 +- src/Perspex.SceneGraph/Media/DashStyle.cs | 10 +---- .../Media/DrawingContext.cs | 4 +- .../Media/PathMarkupParser.cs | 11 ++---- src/Perspex.SceneGraph/Media/Transform.cs | 5 +-- src/Perspex.SceneGraph/RelativePoint.cs | 2 +- src/Perspex.SceneGraph/RelativeRect.cs | 2 +- .../Rendering/RendererBase.cs | 2 +- src/Perspex.SceneGraph/Thickness.cs | 2 +- src/Perspex.SceneGraph/Visual.cs | 6 +-- .../VisualTree/BoundsTracker.cs | 4 +- src/Perspex.Styling/Styling/Classes.cs | 6 +-- src/Perspex.Styling/Styling/Selector.cs | 5 +-- src/Perspex.Styling/Styling/StyleBinding.cs | 4 +- src/Perspex.Styling/Styling/Styler.cs | 21 ++++------ src/Shared/PlatformSupport/AssetLoader.cs | 2 +- .../RenderHelpers/TileBrushImplHelper.cs | 4 +- src/Skia/Perspex.Skia/DrawingContextImpl.cs | 2 +- src/Skia/Perspex.Skia/FormattedTextImpl.cs | 6 +-- src/Skia/Perspex.Skia/NativeBrush.cs | 4 +- src/Skia/Perspex.Skia/PerspexHandleHolder.cs | 2 +- .../AppHost/PerspexAppHost.cs | 2 +- .../Perspex.Designer/AppHost/WindowHost.cs | 4 +- .../Perspex.Designer/Comm/CommChannel.cs | 6 +-- .../Perspex.Designer/PerspexDesigner.xaml | 2 +- .../Perspex.Direct2D1/Media/DrawingContext.cs | 2 +- .../Perspex.Win32/Input/WindowsMouseDevice.cs | 4 +- .../Perspex.Win32/Interop/UnmanagedMethods.cs | 4 +- src/Windows/Perspex.Win32/WindowImpl.cs | 17 ++------- .../PerspexObjectTests_Direct.cs | 2 +- tests/Perspex.Markup.UnitTests/TestRoot.cs | 2 +- .../UnitTestSynchronizationContext.cs | 4 +- 102 files changed, 202 insertions(+), 475 deletions(-) diff --git a/src/Gtk/Perspex.Cairo/CairoPlatform.cs b/src/Gtk/Perspex.Cairo/CairoPlatform.cs index 5d9a003a93..2c72811bbb 100644 --- a/src/Gtk/Perspex.Cairo/CairoPlatform.cs +++ b/src/Gtk/Perspex.Cairo/CairoPlatform.cs @@ -16,7 +16,7 @@ namespace Perspex.Cairo { private static readonly CairoPlatform s_instance = new CairoPlatform(); - private static Pango.Context s_pangoContext = CreatePangoContext(); + private static readonly Pango.Context s_pangoContext = CreatePangoContext(); public static void Initialize() => PerspexLocator.CurrentMutable.Bind().ToConstant(s_instance); diff --git a/src/Gtk/Perspex.Cairo/Media/FormattedTextImpl.cs b/src/Gtk/Perspex.Cairo/Media/FormattedTextImpl.cs index 3b08b06812..956a522002 100644 --- a/src/Gtk/Perspex.Cairo/Media/FormattedTextImpl.cs +++ b/src/Gtk/Perspex.Cairo/Media/FormattedTextImpl.cs @@ -13,7 +13,7 @@ namespace Perspex.Cairo.Media public class FormattedTextImpl : IFormattedTextImpl { private Size _size; - private string _text; + private readonly string _text; public FormattedTextImpl( Pango.Context context, diff --git a/src/Gtk/Perspex.Cairo/RenderTarget.cs b/src/Gtk/Perspex.Cairo/RenderTarget.cs index ee02a279bd..e562ccfded 100644 --- a/src/Gtk/Perspex.Cairo/RenderTarget.cs +++ b/src/Gtk/Perspex.Cairo/RenderTarget.cs @@ -19,7 +19,7 @@ namespace Perspex.Cairo public class RenderTarget : IRenderTarget { private readonly Surface _surface; - private Gtk.Window _window; + private readonly Gtk.Window _window; /// /// Initializes a new instance of the class. diff --git a/src/Markup/Perspex.Markup.Xaml/Context/PerspexContentPropertyProvider.cs b/src/Markup/Perspex.Markup.Xaml/Context/PerspexContentPropertyProvider.cs index bff1db29b5..590ffde841 100644 --- a/src/Markup/Perspex.Markup.Xaml/Context/PerspexContentPropertyProvider.cs +++ b/src/Markup/Perspex.Markup.Xaml/Context/PerspexContentPropertyProvider.cs @@ -15,7 +15,7 @@ namespace Perspex.Markup.Xaml.Context { public class PerspexContentPropertyProvider : IContentPropertyProvider { - private Dictionary _values = new Dictionary(); + private readonly Dictionary _values = new Dictionary(); public string GetContentPropertyName(Type type) { diff --git a/src/Markup/Perspex.Markup.Xaml/Converters/PerspexPropertyTypeConverter.cs b/src/Markup/Perspex.Markup.Xaml/Converters/PerspexPropertyTypeConverter.cs index b46364d4c4..7191cf9f63 100644 --- a/src/Markup/Perspex.Markup.Xaml/Converters/PerspexPropertyTypeConverter.cs +++ b/src/Markup/Perspex.Markup.Xaml/Converters/PerspexPropertyTypeConverter.cs @@ -55,13 +55,9 @@ namespace Perspex.Markup.Xaml.Converters } // First look for non-attached property on the type and then look for an attached property. - var property = PerspexPropertyRegistry.Instance.FindRegistered(type, s); - - if (property == null) - { - property = PerspexPropertyRegistry.Instance.GetAttached(type) - .FirstOrDefault(x => x.Name == propertyName); - } + var property = PerspexPropertyRegistry.Instance.FindRegistered(type, s) ?? + PerspexPropertyRegistry.Instance.GetAttached(type) + .FirstOrDefault(x => x.Name == propertyName); if (property == null) { diff --git a/src/Markup/Perspex.Markup.Xaml/Data/MultiBinding.cs b/src/Markup/Perspex.Markup.Xaml/Data/MultiBinding.cs index 46a01a166c..1e27cc230a 100644 --- a/src/Markup/Perspex.Markup.Xaml/Data/MultiBinding.cs +++ b/src/Markup/Perspex.Markup.Xaml/Data/MultiBinding.cs @@ -79,7 +79,7 @@ namespace Perspex.Markup.Xaml.Data var result = new BehaviorSubject(PerspexProperty.UnsetValue); var children = Bindings.Select(x => x.CreateSubject(target, typeof(object))); - var input = Observable.CombineLatest(children).Select(x => + var input = children.CombineLatest().Select(x => Converter.Convert(x, targetType, null, CultureInfo.CurrentUICulture)); input.Subscribe(result); return result; diff --git a/src/Markup/Perspex.Markup.Xaml/Parsers/SelectorParser.cs b/src/Markup/Perspex.Markup.Xaml/Parsers/SelectorParser.cs index 8e727c3ff7..dd1341996e 100644 --- a/src/Markup/Perspex.Markup.Xaml/Parsers/SelectorParser.cs +++ b/src/Markup/Perspex.Markup.Xaml/Parsers/SelectorParser.cs @@ -14,7 +14,7 @@ namespace Perspex.Markup.Xaml.Parsers /// public class SelectorParser { - private Func _typeResolver; + private readonly Func _typeResolver; /// /// Initializes a new instance of the class. diff --git a/src/Markup/Perspex.Markup.Xaml/Templates/MemberSelector.cs b/src/Markup/Perspex.Markup.Xaml/Templates/MemberSelector.cs index 3f1d660cb6..1231fd2a28 100644 --- a/src/Markup/Perspex.Markup.Xaml/Templates/MemberSelector.cs +++ b/src/Markup/Perspex.Markup.Xaml/Templates/MemberSelector.cs @@ -14,15 +14,7 @@ namespace Perspex.Markup.Xaml.Templates { // TODO: Handle nested property paths, changing values etc. var property = o.GetType().GetRuntimeProperty(MemberName); - - if (property != null) - { - return property.GetValue(o); - } - else - { - return null; - } + return property?.GetValue(o); } } } diff --git a/src/Markup/Perspex.Markup/Data/ExpressionNode.cs b/src/Markup/Perspex.Markup/Data/ExpressionNode.cs index 5af8576882..c138050cb4 100644 --- a/src/Markup/Perspex.Markup/Data/ExpressionNode.cs +++ b/src/Markup/Perspex.Markup/Data/ExpressionNode.cs @@ -63,10 +63,7 @@ namespace Perspex.Markup.Data Next.Target = value; } - if (_subject != null) - { - _subject.OnNext(value); - } + _subject?.OnNext(value); } } diff --git a/src/Markup/Perspex.Markup/Data/ExpressionObserver.cs b/src/Markup/Perspex.Markup/Data/ExpressionObserver.cs index d669713156..81c670ba3e 100644 --- a/src/Markup/Perspex.Markup/Data/ExpressionObserver.cs +++ b/src/Markup/Perspex.Markup/Data/ExpressionObserver.cs @@ -180,9 +180,9 @@ namespace Perspex.Markup.Data { _node.Target = _rootGetter(); } - else if (_empty != null) + else { - _empty.OnNext(_rootGetter()); + _empty?.OnNext(_rootGetter()); } } } diff --git a/src/Markup/Perspex.Markup/Data/ExpressionSubject.cs b/src/Markup/Perspex.Markup/Data/ExpressionSubject.cs index 686cb3b04f..c69865bacc 100644 --- a/src/Markup/Perspex.Markup/Data/ExpressionSubject.cs +++ b/src/Markup/Perspex.Markup/Data/ExpressionSubject.cs @@ -15,8 +15,8 @@ namespace Perspex.Markup.Data /// public class ExpressionSubject : ISubject, IDescription { - private ExpressionObserver _inner; - private Type _targetType; + private readonly ExpressionObserver _inner; + private readonly Type _targetType; /// /// Initializes a new instance of the class. diff --git a/src/Markup/Perspex.Markup/Data/IndexerNode.cs b/src/Markup/Perspex.Markup/Data/IndexerNode.cs index 7279175730..b791e15eaa 100644 --- a/src/Markup/Perspex.Markup/Data/IndexerNode.cs +++ b/src/Markup/Perspex.Markup/Data/IndexerNode.cs @@ -12,7 +12,7 @@ namespace Perspex.Markup.Data { internal class IndexerNode : ExpressionNode { - private int[] _intArgs; + private readonly int[] _intArgs; public IndexerNode(IList arguments) { diff --git a/src/Markup/Perspex.Markup/Data/LogicalNotNode.cs b/src/Markup/Perspex.Markup/Data/LogicalNotNode.cs index fac0d19b4c..2447aee4f2 100644 --- a/src/Markup/Perspex.Markup/Data/LogicalNotNode.cs +++ b/src/Markup/Perspex.Markup/Data/LogicalNotNode.cs @@ -16,10 +16,10 @@ namespace Perspex.Markup.Data public override IDisposable Subscribe(IObserver observer) { - return Next.Select(x => Negate(x)).Subscribe(observer); + return Next.Select(Negate).Subscribe(observer); } - private object Negate(object v) + private static object Negate(object v) { if (v != PerspexProperty.UnsetValue) { diff --git a/src/Markup/Perspex.Markup/Data/Parsers/Reader.cs b/src/Markup/Perspex.Markup/Data/Parsers/Reader.cs index f82cb34ded..6c86dbf733 100644 --- a/src/Markup/Perspex.Markup/Data/Parsers/Reader.cs +++ b/src/Markup/Perspex.Markup/Data/Parsers/Reader.cs @@ -7,7 +7,7 @@ namespace Perspex.Markup.Data.Parsers { internal class Reader { - private string _s; + private readonly string _s; private int _i; public Reader(string s) diff --git a/src/Markup/Perspex.Markup/Data/Plugins/InpcPropertyAccessorPlugin.cs b/src/Markup/Perspex.Markup/Data/Plugins/InpcPropertyAccessorPlugin.cs index f6907fddfd..044730f643 100644 --- a/src/Markup/Perspex.Markup/Data/Plugins/InpcPropertyAccessorPlugin.cs +++ b/src/Markup/Perspex.Markup/Data/Plugins/InpcPropertyAccessorPlugin.cs @@ -56,9 +56,9 @@ namespace Perspex.Markup.Data.Plugins private class Accessor : IPropertyAccessor { - private object _instance; - private PropertyInfo _property; - private Action _changed; + private readonly object _instance; + private readonly PropertyInfo _property; + private readonly Action _changed; public Accessor(object instance, PropertyInfo property, Action changed) { @@ -77,15 +77,9 @@ namespace Perspex.Markup.Data.Plugins } } - public Type PropertyType - { - get { return _property.PropertyType; } - } + public Type PropertyType => _property.PropertyType; - public object Value - { - get { return _property.GetValue(_instance); } - } + public object Value => _property.GetValue(_instance); public void Dispose() { diff --git a/src/Markup/Perspex.Markup/Data/Plugins/PerspexPropertyAccessorPlugin.cs b/src/Markup/Perspex.Markup/Data/Plugins/PerspexPropertyAccessorPlugin.cs index 8e8fba45cb..a5b9f00371 100644 --- a/src/Markup/Perspex.Markup/Data/Plugins/PerspexPropertyAccessorPlugin.cs +++ b/src/Markup/Perspex.Markup/Data/Plugins/PerspexPropertyAccessorPlugin.cs @@ -56,8 +56,8 @@ namespace Perspex.Markup.Data.Plugins private class Accessor : IPropertyAccessor { - private PerspexObject _instance; - private PerspexProperty _property; + private readonly PerspexObject _instance; + private readonly PerspexProperty _property; private IDisposable _subscription; public Accessor(PerspexObject instance, PerspexProperty property, Action changed) @@ -70,15 +70,9 @@ namespace Perspex.Markup.Data.Plugins _subscription = instance.GetObservable(property).Skip(1).Subscribe(changed); } - public Type PropertyType - { - get { return _property.PropertyType; } - } + public Type PropertyType => _property.PropertyType; - public object Value - { - get { return _instance.GetValue(_property); } - } + public object Value => _instance.GetValue(_property); public void Dispose() { diff --git a/src/Markup/Perspex.Markup/FuncMultiValueConverter.cs b/src/Markup/Perspex.Markup/FuncMultiValueConverter.cs index b956512357..66165e5876 100644 --- a/src/Markup/Perspex.Markup/FuncMultiValueConverter.cs +++ b/src/Markup/Perspex.Markup/FuncMultiValueConverter.cs @@ -16,7 +16,7 @@ namespace Perspex.Markup /// The output type. public class FuncMultiValueConverter : IMultiValueConverter { - private Func, TOut> _convert; + private readonly Func, TOut> _convert; /// /// Initializes a new instance of the class. diff --git a/src/Markup/Perspex.Markup/FuncValueConverter.cs b/src/Markup/Perspex.Markup/FuncValueConverter.cs index 74059a3c3e..2dec0c43a4 100644 --- a/src/Markup/Perspex.Markup/FuncValueConverter.cs +++ b/src/Markup/Perspex.Markup/FuncValueConverter.cs @@ -15,7 +15,7 @@ namespace Perspex.Markup /// The output type. public class FuncValueConverter : IValueConverter { - private Func _convert; + private readonly Func _convert; /// /// Initializes a new instance of the class. diff --git a/src/Markup/Perspex.Markup/StringConverters.cs b/src/Markup/Perspex.Markup/StringConverters.cs index da4be76a90..950617a52d 100644 --- a/src/Markup/Perspex.Markup/StringConverters.cs +++ b/src/Markup/Perspex.Markup/StringConverters.cs @@ -16,7 +16,7 @@ namespace Perspex.Markup /// A value converter that returns true if the input string is null or an empty string. /// public static readonly IValueConverter NullOrEmpty = - new FuncValueConverter(x => string.IsNullOrEmpty(x)); + new FuncValueConverter(string.IsNullOrEmpty); /// /// A value converter that returns true if the input string is not null or empty. diff --git a/src/Perspex.Animation/Animatable.cs b/src/Perspex.Animation/Animatable.cs index 33c7f611ad..c00755f4bc 100644 --- a/src/Perspex.Animation/Animatable.cs +++ b/src/Perspex.Animation/Animatable.cs @@ -25,12 +25,7 @@ namespace Perspex.Animation { get { - if (_propertyTransitions == null) - { - _propertyTransitions = new PropertyTransitions(); - } - - return _propertyTransitions; + return _propertyTransitions ?? (_propertyTransitions = new PropertyTransitions()); } set diff --git a/src/Perspex.Animation/LinearEasing.cs b/src/Perspex.Animation/LinearEasing.cs index 716ed2e6f5..57583a505c 100644 --- a/src/Perspex.Animation/LinearEasing.cs +++ b/src/Perspex.Animation/LinearEasing.cs @@ -27,9 +27,8 @@ namespace Perspex.Animation } else { - throw new NotSupportedException(string.Format( - "Don't know how to create a LinearEasing for type '{0}'.", - typeof(T).FullName)); + throw new NotSupportedException( + $"Don't know how to create a LinearEasing for type '{typeof(T).FullName}'."); } } } diff --git a/src/Perspex.Application/Application.cs b/src/Perspex.Application/Application.cs index 84f40d12fb..52d2574c70 100644 --- a/src/Perspex.Application/Application.cs +++ b/src/Perspex.Application/Application.cs @@ -87,20 +87,8 @@ namespace Perspex /// public DataTemplates DataTemplates { - get - { - if (_dataTemplates == null) - { - _dataTemplates = new DataTemplates(); - } - - return _dataTemplates; - } - - set - { - _dataTemplates = value; - } + get { return _dataTemplates ?? (_dataTemplates = new DataTemplates()); } + set { _dataTemplates = value; } } /// diff --git a/src/Perspex.Base/BindingDescriptor.cs b/src/Perspex.Base/BindingDescriptor.cs index 0006843ae3..4ee4966a98 100644 --- a/src/Perspex.Base/BindingDescriptor.cs +++ b/src/Perspex.Base/BindingDescriptor.cs @@ -81,7 +81,7 @@ namespace Perspex /// /// Gets a description of the binding. /// - public string Description => string.Format("{0}.{1}", Source?.GetType().Name, Property.Name); + public string Description => $"{Source?.GetType().Name}.{Property.Name}"; /// /// Makes a two-way binding. diff --git a/src/Perspex.Base/Collections/PerspexDictionary.cs b/src/Perspex.Base/Collections/PerspexDictionary.cs index 7324e653ea..d3da0c031d 100644 --- a/src/Perspex.Base/Collections/PerspexDictionary.cs +++ b/src/Perspex.Base/Collections/PerspexDictionary.cs @@ -71,10 +71,7 @@ namespace Perspex.Collections if (replace) { - if (PropertyChanged != null) - { - PropertyChanged(this, new PropertyChangedEventArgs($"Item[{key}]")); - } + PropertyChanged?.Invoke(this, new PropertyChangedEventArgs($"Item[{key}]")); if (CollectionChanged != null) { diff --git a/src/Perspex.Base/Collections/PerspexList.cs b/src/Perspex.Base/Collections/PerspexList.cs index 9c200aedaf..25424461db 100644 --- a/src/Perspex.Base/Collections/PerspexList.cs +++ b/src/Perspex.Base/Collections/PerspexList.cs @@ -437,10 +437,7 @@ namespace Perspex.Collections /// private void NotifyCountChanged() { - if (PropertyChanged != null) - { - PropertyChanged(this, new PropertyChangedEventArgs("Count")); - } + PropertyChanged?.Invoke(this, new PropertyChangedEventArgs("Count")); } /// @@ -469,14 +466,9 @@ namespace Perspex.Collections { NotifyCollectionChangedEventArgs e; - if (ResetBehavior == ResetBehavior.Reset) - { - e = new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset); - } - else - { - e = new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Remove, t, 0); - } + e = ResetBehavior == ResetBehavior.Reset ? + new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset) : + new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Remove, t, 0); CollectionChanged(this, e); } diff --git a/src/Perspex.Base/Metadata/XmlnsDefinitionAttribute.cs b/src/Perspex.Base/Metadata/XmlnsDefinitionAttribute.cs index 8e9fddc50b..ad30517dcd 100644 --- a/src/Perspex.Base/Metadata/XmlnsDefinitionAttribute.cs +++ b/src/Perspex.Base/Metadata/XmlnsDefinitionAttribute.cs @@ -25,11 +25,11 @@ namespace Perspex.Metadata /// /// Gets or sets the URL of the XML namespace. /// - public string XmlNamespace { get; set; } + public string XmlNamespace { get; } /// /// Gets or sets the CLR namespace. /// - public string ClrNamespace { get; set; } + public string ClrNamespace { get; } } } diff --git a/src/Perspex.Base/PerspexLocator.cs b/src/Perspex.Base/PerspexLocator.cs index 8b1129f867..142e78dd8c 100644 --- a/src/Perspex.Base/PerspexLocator.cs +++ b/src/Perspex.Base/PerspexLocator.cs @@ -36,7 +36,7 @@ namespace Perspex public class RegistrationHelper { - private PerspexLocator _locator; + private readonly PerspexLocator _locator; public RegistrationHelper(PerspexLocator locator) { diff --git a/src/Perspex.Base/PerspexObject.cs b/src/Perspex.Base/PerspexObject.cs index a61e04a8ac..70dff41dda 100644 --- a/src/Perspex.Base/PerspexObject.cs +++ b/src/Perspex.Base/PerspexObject.cs @@ -694,20 +694,14 @@ namespace Perspex newValue, priority); - if (property.Notifying != null) - { - property.Notifying(this, true); - } + property.Notifying?.Invoke(this, true); try { OnPropertyChanged(e); property.NotifyChanged(e); - if (PropertyChanged != null) - { - PropertyChanged(this, e); - } + PropertyChanged?.Invoke(this, e); if (_inpcChanged != null) { @@ -717,10 +711,7 @@ namespace Perspex } finally { - if (property.Notifying != null) - { - property.Notifying(this, false); - } + property.Notifying?.Invoke(this, false); } } @@ -866,7 +857,7 @@ namespace Perspex /// The description. private string GetDescription(PerspexProperty property) { - return string.Format("{0}.{1}", GetType().Name, property.Name); + return $"{GetType().Name}.{property.Name}"; } /// diff --git a/src/Perspex.Base/PerspexProperty.cs b/src/Perspex.Base/PerspexProperty.cs index ef06bf4326..0a00b21219 100644 --- a/src/Perspex.Base/PerspexProperty.cs +++ b/src/Perspex.Base/PerspexProperty.cs @@ -538,7 +538,7 @@ namespace Perspex public override bool Equals(object obj) { var p = obj as PerspexProperty; - return p != null ? Equals(p) : false; + return p != null && Equals(p); } /// diff --git a/src/Perspex.Base/PriorityBindingEntry.cs b/src/Perspex.Base/PriorityBindingEntry.cs index 63f906d450..a17080f0f4 100644 --- a/src/Perspex.Base/PriorityBindingEntry.cs +++ b/src/Perspex.Base/PriorityBindingEntry.cs @@ -99,10 +99,7 @@ namespace Perspex /// public void Dispose() { - if (_subscription != null) - { - _subscription.Dispose(); - } + _subscription?.Dispose(); } } } diff --git a/src/Perspex.Base/Reactive/PerspexObservable.cs b/src/Perspex.Base/Reactive/PerspexObservable.cs index 48193aba74..bd76f544d4 100644 --- a/src/Perspex.Base/Reactive/PerspexObservable.cs +++ b/src/Perspex.Base/Reactive/PerspexObservable.cs @@ -22,10 +22,7 @@ namespace Perspex.Reactive /// The description of the observable. public PerspexObservable(Func, IDisposable> subscribe, string description) { - if (subscribe == null) - { - throw new ArgumentNullException("subscribe"); - } + Contract.Requires(subscribe != null); _subscribe = subscribe; Description = description; diff --git a/src/Perspex.Base/Threading/DispatcherTimer.cs b/src/Perspex.Base/Threading/DispatcherTimer.cs index 18eedff08f..376ca5c0c9 100644 --- a/src/Perspex.Base/Threading/DispatcherTimer.cs +++ b/src/Perspex.Base/Threading/DispatcherTimer.cs @@ -131,9 +131,8 @@ namespace Perspex.Threading /// An used to cancel the timer. public static IDisposable Run(Func action, TimeSpan interval, DispatcherPriority priority = DispatcherPriority.Normal) { - var timer = new DispatcherTimer(priority); + var timer = new DispatcherTimer(priority) { Interval = interval }; - timer.Interval = interval; timer.Tick += (s, e) => { if (!action()) @@ -187,10 +186,7 @@ namespace Perspex.Threading /// private void RaiseTick() { - if (Tick != null) - { - Tick(this, EventArgs.Empty); - } + Tick?.Invoke(this, EventArgs.Empty); } } } \ No newline at end of file diff --git a/src/Perspex.Base/Threading/SingleThreadDispatcher.cs b/src/Perspex.Base/Threading/SingleThreadDispatcher.cs index 5e751833f9..bcf858db3a 100644 --- a/src/Perspex.Base/Threading/SingleThreadDispatcher.cs +++ b/src/Perspex.Base/Threading/SingleThreadDispatcher.cs @@ -12,8 +12,8 @@ namespace Perspex.Threading { class ThreadingInterface : IPlatformThreadingInterface { - private AutoResetEvent _evnt = new AutoResetEvent(false); - private JobRunner _timerJobRunner; + private readonly AutoResetEvent _evnt = new AutoResetEvent(false); + private readonly JobRunner _timerJobRunner; public ThreadingInterface() { diff --git a/src/Perspex.Controls/Button.cs b/src/Perspex.Controls/Button.cs index 0bd29dbb64..c334007b70 100644 --- a/src/Perspex.Controls/Button.cs +++ b/src/Perspex.Controls/Button.cs @@ -207,10 +207,7 @@ namespace Perspex.Controls /// The event args. protected virtual void OnClick(RoutedEventArgs e) { - if (Command != null) - { - Command.Execute(CommandParameter); - } + Command?.Execute(CommandParameter); } /// diff --git a/src/Perspex.Controls/Control.cs b/src/Perspex.Controls/Control.cs index f9d04faa73..1787742352 100644 --- a/src/Perspex.Controls/Control.cs +++ b/src/Perspex.Controls/Control.cs @@ -156,20 +156,8 @@ namespace Perspex.Controls /// public DataTemplates DataTemplates { - get - { - if (_dataTemplates == null) - { - _dataTemplates = new DataTemplates(); - } - - return _dataTemplates; - } - - set - { - _dataTemplates = value; - } + get { return _dataTemplates ?? (_dataTemplates = new DataTemplates()); } + set { _dataTemplates = value; } } /// @@ -182,20 +170,8 @@ namespace Perspex.Controls /// public Styles Styles { - get - { - if (_styles == null) - { - _styles = new Styles(); - } - - return _styles; - } - - set - { - _styles = value; - } + get { return _styles ?? (_styles = new Styles()); } + set { _styles = value; } } /// @@ -321,7 +297,7 @@ namespace Perspex.Controls throw new ArgumentException("Cannot supply an empty className."); } - Observable.Merge(property.Changed, property.Initialized) + property.Changed.Merge(property.Initialized) .Subscribe(e => { if (selector((T)e.NewValue)) diff --git a/src/Perspex.Controls/Decorator.cs b/src/Perspex.Controls/Decorator.cs index 1e1733a814..f5da5a4114 100644 --- a/src/Perspex.Controls/Decorator.cs +++ b/src/Perspex.Controls/Decorator.cs @@ -71,12 +71,7 @@ namespace Perspex.Controls protected override Size ArrangeOverride(Size finalSize) { Control content = Child; - - if (content != null) - { - content.Arrange(new Rect(finalSize).Deflate(Padding)); - } - + content?.Arrange(new Rect(finalSize).Deflate(Padding)); return finalSize; } diff --git a/src/Perspex.Controls/DockPanel.cs b/src/Perspex.Controls/DockPanel.cs index efded15d40..a1224a5272 100644 --- a/src/Perspex.Controls/DockPanel.cs +++ b/src/Perspex.Controls/DockPanel.cs @@ -251,18 +251,15 @@ public struct Alignments { - private readonly Alignment _horizontal; - private readonly Alignment _vertical; - public Alignments(Alignment horizontal, Alignment vertical) { - _horizontal = horizontal; - _vertical = vertical; + Horizontal = horizontal; + Vertical = vertical; } - public Alignment Horizontal => _horizontal; + public Alignment Horizontal { get; } - public Alignment Vertical => _vertical; + public Alignment Vertical { get; } } public static class CoordinateMixin diff --git a/src/Perspex.Controls/Generators/TreeItemContainerGenerator.cs b/src/Perspex.Controls/Generators/TreeItemContainerGenerator.cs index 18df4c4183..802cd3866b 100644 --- a/src/Perspex.Controls/Generators/TreeItemContainerGenerator.cs +++ b/src/Perspex.Controls/Generators/TreeItemContainerGenerator.cs @@ -14,8 +14,8 @@ namespace Perspex.Controls.Generators public class TreeItemContainerGenerator : ItemContainerGenerator, ITreeItemContainerGenerator where T : class, IControl, new() { - private Dictionary _itemToContainer; - private Dictionary _containerToItem; + private readonly Dictionary _itemToContainer; + private readonly Dictionary _containerToItem; /// /// Initializes a new instance of the class. @@ -182,20 +182,9 @@ namespace Perspex.Controls.Generators /// The template. private ITreeDataTemplate GetTreeDataTemplate(object item) { - IDataTemplate template = Owner.FindDataTemplate(item); - - if (template == null) - { - template = FuncDataTemplate.Default; - } - - var treeTemplate = template as ITreeDataTemplate; - - if (treeTemplate == null) - { - treeTemplate = new FuncTreeDataTemplate(typeof(object), template.Build, x => null); - } - + var template = Owner.FindDataTemplate(item) ?? FuncDataTemplate.Default; + var treeTemplate = template as ITreeDataTemplate ?? + new FuncTreeDataTemplate(typeof(object), template.Build, x => null); return treeTemplate; } } diff --git a/src/Perspex.Controls/HotkeyManager.cs b/src/Perspex.Controls/HotkeyManager.cs index 6b7fb117c1..d407a7433c 100644 --- a/src/Perspex.Controls/HotkeyManager.cs +++ b/src/Perspex.Controls/HotkeyManager.cs @@ -12,7 +12,7 @@ namespace Perspex.Controls { public class HotKeyManager { - public static PerspexProperty HotKeyProperty + public static readonly PerspexProperty HotKeyProperty = PerspexProperty.RegisterAttached("HotKey", typeof (HotKeyManager)); class HotkeyCommandWrapper : ICommand @@ -22,7 +22,7 @@ namespace Perspex.Controls Control = control; } - public IControl Control; + public readonly IControl Control; private ICommand GetCommand() => Control.GetValue(Button.CommandProperty); @@ -42,7 +42,7 @@ namespace Perspex.Controls private IDisposable _parentSub; private IDisposable _hotkeySub; private KeyGesture _hotkey; - private HotkeyCommandWrapper _wrapper; + private readonly HotkeyCommandWrapper _wrapper; private KeyBinding _binding; public Manager(IControl control) diff --git a/src/Perspex.Controls/ListBox.cs b/src/Perspex.Controls/ListBox.cs index 7641e2e54a..3cb66641ac 100644 --- a/src/Perspex.Controls/ListBox.cs +++ b/src/Perspex.Controls/ListBox.cs @@ -29,10 +29,7 @@ namespace Perspex.Controls SelectingItemsControl.SelectionModeProperty; /// - public new IList SelectedItems - { - get { return base.SelectedItems; } - } + public new IList SelectedItems => base.SelectedItems; /// public new SelectionMode SelectionMode diff --git a/src/Perspex.Controls/Menu.cs b/src/Perspex.Controls/Menu.cs index 9e9ee451e8..2abe65ae48 100644 --- a/src/Perspex.Controls/Menu.cs +++ b/src/Perspex.Controls/Menu.cs @@ -116,7 +116,7 @@ namespace Perspex.Controls var inputRoot = e.Root as IInputRoot; - if (inputRoot != null && inputRoot.AccessKeyHandler != null) + if (inputRoot?.AccessKeyHandler != null) { inputRoot.AccessKeyHandler.MainMenu = this; } diff --git a/src/Perspex.Controls/MenuItem.cs b/src/Perspex.Controls/MenuItem.cs index 4d73240604..7207bfa808 100644 --- a/src/Perspex.Controls/MenuItem.cs +++ b/src/Perspex.Controls/MenuItem.cs @@ -209,10 +209,7 @@ namespace Perspex.Controls /// The click event args. protected virtual void OnClick(RoutedEventArgs e) { - if (Command != null) - { - Command.Execute(CommandParameter); - } + Command?.Execute(CommandParameter); } /// @@ -483,11 +480,7 @@ namespace Perspex.Controls if (selected != -1) { var container = ItemContainerGenerator.ContainerFromIndex(selected); - - if (container != null) - { - container.Focus(); - } + container?.Focus(); } } diff --git a/src/Perspex.Controls/MenuItemAccessKeyHandler.cs b/src/Perspex.Controls/MenuItemAccessKeyHandler.cs index 7dbf429696..37ae5ee4c4 100644 --- a/src/Perspex.Controls/MenuItemAccessKeyHandler.cs +++ b/src/Perspex.Controls/MenuItemAccessKeyHandler.cs @@ -93,13 +93,9 @@ namespace Perspex.Controls { var text = e.Text.ToUpper(); var focus = _registered - .Where(x => x.Item1 == text && x.Item2.IsEffectivelyVisible) - .FirstOrDefault()?.Item2; + .FirstOrDefault(x => x.Item1 == text && x.Item2.IsEffectivelyVisible)?.Item2; - if (focus != null) - { - focus.RaiseEvent(new RoutedEventArgs(AccessKeyHandler.AccessKeyPressedEvent)); - } + focus?.RaiseEvent(new RoutedEventArgs(AccessKeyHandler.AccessKeyPressedEvent)); e.Handled = true; } diff --git a/src/Perspex.Controls/Primitives/AdornerLayer.cs b/src/Perspex.Controls/Primitives/AdornerLayer.cs index 67ef2e71da..138df92f4b 100644 --- a/src/Perspex.Controls/Primitives/AdornerLayer.cs +++ b/src/Perspex.Controls/Primitives/AdornerLayer.cs @@ -74,11 +74,7 @@ namespace Perspex.Controls.Primitives var adorner = (Visual)e.Sender; var adorned = (Visual)e.NewValue; var layer = adorner.GetVisualParent(); - - if (layer != null) - { - layer.UpdateAdornedElement(adorner, adorned); - } + layer?.UpdateAdornedElement(adorner, adorned); } private void ChildrenCollectionChanged(object sender, NotifyCollectionChangedEventArgs e) diff --git a/src/Perspex.Controls/Primitives/Popup.cs b/src/Perspex.Controls/Primitives/Popup.cs index 138c5b586f..e3f4183a1a 100644 --- a/src/Perspex.Controls/Primitives/Popup.cs +++ b/src/Perspex.Controls/Primitives/Popup.cs @@ -250,10 +250,7 @@ namespace Perspex.Controls.Primitives { LogicalChildren.Clear(); - if (e.OldValue != null) - { - ((ISetLogicalParent)e.OldValue).SetParent(null); - } + ((ISetLogicalParent)e.OldValue)?.SetParent(null); if (e.NewValue != null) { @@ -269,10 +266,11 @@ namespace Perspex.Controls.Primitives private Point GetPosition() { var target = PlacementTarget ?? this.GetVisualParent(); - Point point; if (target != null) { + Point point; + switch (PlacementMode) { case PlacementMode.Bottom: diff --git a/src/Perspex.Controls/Primitives/PopupRoot.cs b/src/Perspex.Controls/Primitives/PopupRoot.cs index 3b52f27cbd..b69af20c3f 100644 --- a/src/Perspex.Controls/Primitives/PopupRoot.cs +++ b/src/Perspex.Controls/Primitives/PopupRoot.cs @@ -106,13 +106,8 @@ namespace Perspex.Controls.Primitives _presenterSubscription = null; } - var presenter = Presenter; - - if (presenter != null) - { - presenter.GetObservable(ContentPresenter.ChildProperty) - .Subscribe(SetTemplatedParentAndApplyChildTemplates); - } + Presenter?.GetObservable(ContentPresenter.ChildProperty) + .Subscribe(SetTemplatedParentAndApplyChildTemplates); } } diff --git a/src/Perspex.Controls/Primitives/SelectingItemsControl.cs b/src/Perspex.Controls/Primitives/SelectingItemsControl.cs index 55ff57e6be..42886a0f41 100644 --- a/src/Perspex.Controls/Primitives/SelectingItemsControl.cs +++ b/src/Perspex.Controls/Primitives/SelectingItemsControl.cs @@ -317,7 +317,7 @@ namespace Perspex.Controls.Primitives var mode = SelectionMode; var toggle = toggleModifier || (mode & SelectionMode.Toggle) != 0; var multi = (mode & SelectionMode.Multiple) != 0; - var range = multi && SelectedIndex != -1 ? rangeModifier : false; + var range = multi && SelectedIndex != -1 && rangeModifier; if (!toggle && !range) { diff --git a/src/Perspex.Controls/TabControl.cs b/src/Perspex.Controls/TabControl.cs index 30f4bac01c..bff505497f 100644 --- a/src/Perspex.Controls/TabControl.cs +++ b/src/Perspex.Controls/TabControl.cs @@ -48,10 +48,7 @@ namespace Perspex.Controls /// /// Gets an that selects the content of a . /// - public IMemberSelector ContentSelector - { - get { return s_contentSelector; } - } + public IMemberSelector ContentSelector => s_contentSelector; /// /// Gets the as a . diff --git a/src/Perspex.Controls/Templates/FuncDataTemplate.cs b/src/Perspex.Controls/Templates/FuncDataTemplate.cs index ab2e203d4c..a283297d31 100644 --- a/src/Perspex.Controls/Templates/FuncDataTemplate.cs +++ b/src/Perspex.Controls/Templates/FuncDataTemplate.cs @@ -73,9 +73,7 @@ namespace Perspex.Controls.Templates /// private static bool IsInstance(object o, Type t) { - return (o != null) ? - t.GetTypeInfo().IsAssignableFrom(o.GetType().GetTypeInfo()) : - false; + return (o != null) && t.GetTypeInfo().IsAssignableFrom(o.GetType().GetTypeInfo()); } } } \ No newline at end of file diff --git a/src/Perspex.Controls/Templates/FuncDataTemplate`1.cs b/src/Perspex.Controls/Templates/FuncDataTemplate`1.cs index b0021d093d..aa264ac2e9 100644 --- a/src/Perspex.Controls/Templates/FuncDataTemplate`1.cs +++ b/src/Perspex.Controls/Templates/FuncDataTemplate`1.cs @@ -43,7 +43,7 @@ namespace Perspex.Controls.Templates /// The weakly typed function. private static Func CastMatch(Func f) { - return o => (o is T) ? f((T)o) : false; + return o => (o is T) && f((T)o); } /// diff --git a/src/Perspex.Controls/Templates/FuncMemberSelector.cs b/src/Perspex.Controls/Templates/FuncMemberSelector.cs index 9ceab082b1..04d6e7618c 100644 --- a/src/Perspex.Controls/Templates/FuncMemberSelector.cs +++ b/src/Perspex.Controls/Templates/FuncMemberSelector.cs @@ -10,7 +10,7 @@ namespace Perspex.Controls.Templates /// public class FuncMemberSelector : IMemberSelector { - private Func _selector; + private readonly Func _selector; /// /// Initializes a new instance of the diff --git a/src/Perspex.Controls/Templates/FuncTreeDataTemplate.cs b/src/Perspex.Controls/Templates/FuncTreeDataTemplate.cs index 0442f9a649..d7ed589135 100644 --- a/src/Perspex.Controls/Templates/FuncTreeDataTemplate.cs +++ b/src/Perspex.Controls/Templates/FuncTreeDataTemplate.cs @@ -137,9 +137,7 @@ namespace Perspex.Controls.Templates /// private static bool IsInstance(object o, Type t) { - return (o != null) ? - t.GetTypeInfo().IsAssignableFrom(o.GetType().GetTypeInfo()) : - false; + return (o != null) && t.GetTypeInfo().IsAssignableFrom(o.GetType().GetTypeInfo()); } } } diff --git a/src/Perspex.Controls/Templates/FuncTreeDataTemplate`1.cs b/src/Perspex.Controls/Templates/FuncTreeDataTemplate`1.cs index 18de28feb6..d55cfcf095 100644 --- a/src/Perspex.Controls/Templates/FuncTreeDataTemplate`1.cs +++ b/src/Perspex.Controls/Templates/FuncTreeDataTemplate`1.cs @@ -117,7 +117,7 @@ namespace Perspex.Controls.Templates /// The untyped function. private static Func CastMatch(Func f) { - return o => (o is T) ? f((T)o) : false; + return o => (o is T) && f((T)o); } /// diff --git a/src/Perspex.Controls/TextBox.cs b/src/Perspex.Controls/TextBox.cs index 36fd8c0cb9..34ad050d0a 100644 --- a/src/Perspex.Controls/TextBox.cs +++ b/src/Perspex.Controls/TextBox.cs @@ -360,7 +360,7 @@ namespace Perspex.Controls private static int ValidateCaretIndex(PerspexObject o, int value) { var text = o.GetValue(TextProperty); - var length = (text != null) ? text.Length : 0; + var length = text?.Length ?? 0; return Math.Max(0, Math.Min(length, value)); } diff --git a/src/Perspex.Controls/ToolTip.cs b/src/Perspex.Controls/ToolTip.cs index f2c6e76ffc..8be91f237b 100644 --- a/src/Perspex.Controls/ToolTip.cs +++ b/src/Perspex.Controls/ToolTip.cs @@ -115,7 +115,7 @@ namespace Perspex.Controls } var cp = MouseDevice.Instance?.GetPosition(control); - var position = control.PointToScreen(cp.HasValue ? cp.Value : new Point(0, 0)) + new Vector(0, 22); + var position = control.PointToScreen(cp ?? new Point(0, 0)) + new Vector(0, 22); ((ToolTip)s_popup.Content).Content = GetTip(control); s_popup.SetPosition(position); diff --git a/src/Perspex.Controls/TopLevel.cs b/src/Perspex.Controls/TopLevel.cs index 6aacac85ff..ca84f67de1 100644 --- a/src/Perspex.Controls/TopLevel.cs +++ b/src/Perspex.Controls/TopLevel.cs @@ -102,7 +102,7 @@ namespace Perspex.Controls PlatformImpl.Input = HandleInput; PlatformImpl.Resized = HandleResized; - Size clientSize = ClientSize = PlatformImpl.ClientSize; + var clientSize = ClientSize = PlatformImpl.ClientSize; if (LayoutManager != null) { @@ -111,16 +111,8 @@ namespace Perspex.Controls LayoutManager.LayoutCompleted.Subscribe(_ => HandleLayoutCompleted()); } - if (_keyboardNavigationHandler != null) - { - _keyboardNavigationHandler.SetOwner(this); - } - - if (_accessKeyHandler != null) - { - _accessKeyHandler.SetOwner(this); - } - + _keyboardNavigationHandler?.SetOwner(this); + _accessKeyHandler?.SetOwner(this); styler?.ApplyStyles(this); GetObservable(ClientSizeProperty).Skip(1).Subscribe(x => PlatformImpl.ClientSize = x); @@ -306,12 +298,9 @@ namespace Perspex.Controls { var result = resolver.GetService(); - if (result == null) - { - System.Diagnostics.Debug.WriteLineIf( - result == null, - $"Could not create {typeof(T).Name} : maybe Application.RegisterServices() wasn't called?"); - } + System.Diagnostics.Debug.WriteLineIf( + result == null, + $"Could not create {typeof(T).Name} : maybe Application.RegisterServices() wasn't called?"); return result; } @@ -321,10 +310,7 @@ namespace Perspex.Controls /// private void HandleActivated() { - if (Activated != null) - { - Activated(this, EventArgs.Empty); - } + Activated?.Invoke(this, EventArgs.Empty); var scope = this as IFocusScope; @@ -341,10 +327,7 @@ namespace Perspex.Controls /// private void HandleClosed() { - if (Closed != null) - { - Closed(this, EventArgs.Empty); - } + Closed?.Invoke(this, EventArgs.Empty); } /// @@ -354,10 +337,7 @@ namespace Perspex.Controls { IsActive = false; - if (Deactivated != null) - { - Deactivated(this, EventArgs.Empty); - } + Deactivated?.Invoke(this, EventArgs.Empty); } /// diff --git a/src/Perspex.Controls/UserControl.cs b/src/Perspex.Controls/UserControl.cs index 05c1c998b9..6578595817 100644 --- a/src/Perspex.Controls/UserControl.cs +++ b/src/Perspex.Controls/UserControl.cs @@ -8,7 +8,7 @@ namespace Perspex.Controls { public class UserControl : ContentControl, IStyleable, INameScope { - private NameScope _nameScope = new NameScope(); + private readonly NameScope _nameScope = new NameScope(); /// event EventHandler INameScope.Registered diff --git a/src/Perspex.Controls/Window.cs b/src/Perspex.Controls/Window.cs index 3f6c5f344c..1e7855e204 100644 --- a/src/Perspex.Controls/Window.cs +++ b/src/Perspex.Controls/Window.cs @@ -55,9 +55,9 @@ namespace Perspex.Controls public static readonly PerspexProperty TitleProperty = PerspexProperty.Register(nameof(Title), "Window"); - private NameScope _nameScope = new NameScope(); + private readonly NameScope _nameScope = new NameScope(); private object _dialogResult; - private Size _maxPlatformClientSize; + private readonly Size _maxPlatformClientSize; /// /// Initializes static members of the class. diff --git a/src/Perspex.Diagnostics/LogManager.cs b/src/Perspex.Diagnostics/LogManager.cs index 89f0ca3ecd..8773025da3 100644 --- a/src/Perspex.Diagnostics/LogManager.cs +++ b/src/Perspex.Diagnostics/LogManager.cs @@ -11,18 +11,7 @@ namespace Perspex.Diagnostics { private static LogManager s_instance; - public static LogManager Instance - { - get - { - if (s_instance == null) - { - s_instance = new LogManager(); - } - - return s_instance; - } - } + public static LogManager Instance => s_instance ?? (s_instance = new LogManager()); public ILogger Logger { diff --git a/src/Perspex.Diagnostics/ViewModels/PropertyDetails.cs b/src/Perspex.Diagnostics/ViewModels/PropertyDetails.cs index 078488453b..171a8df25e 100644 --- a/src/Perspex.Diagnostics/ViewModels/PropertyDetails.cs +++ b/src/Perspex.Diagnostics/ViewModels/PropertyDetails.cs @@ -17,7 +17,7 @@ namespace Perspex.Diagnostics.ViewModels public PropertyDetails(PerspexObject o, PerspexProperty property) { Name = property.IsAttached ? - string.Format("[{0}.{1}]", property.OwnerType.Name, property.Name) : + $"[{property.OwnerType.Name}.{property.Name}]" : property.Name; IsAttached = property.IsAttached; diff --git a/src/Perspex.Diagnostics/ViewModels/VisualTreeNode.cs b/src/Perspex.Diagnostics/ViewModels/VisualTreeNode.cs index b3ddc315b3..64064df20a 100644 --- a/src/Perspex.Diagnostics/ViewModels/VisualTreeNode.cs +++ b/src/Perspex.Diagnostics/ViewModels/VisualTreeNode.cs @@ -14,7 +14,7 @@ namespace Perspex.Diagnostics.ViewModels { var host = visual as IVisualTreeHost; - if (host == null || host.Root == null) + if (host?.Root == null) { Children = visual.VisualChildren.CreateDerivedCollection(x => new VisualTreeNode(x)); } diff --git a/src/Perspex.HtmlRenderer/Adapters/GraphicsAdapter.cs b/src/Perspex.HtmlRenderer/Adapters/GraphicsAdapter.cs index 6fd918a69b..730f50fb02 100644 --- a/src/Perspex.HtmlRenderer/Adapters/GraphicsAdapter.cs +++ b/src/Perspex.HtmlRenderer/Adapters/GraphicsAdapter.cs @@ -42,7 +42,7 @@ namespace TheArtOfDev.HtmlRenderer.Perspex.Adapters #endregion - private Stack _clipStack = new Stack(); + private readonly Stack _clipStack = new Stack(); /// diff --git a/src/Perspex.Input/FocusManager.cs b/src/Perspex.Input/FocusManager.cs index 67fef2588c..530f8c05bd 100644 --- a/src/Perspex.Input/FocusManager.cs +++ b/src/Perspex.Input/FocusManager.cs @@ -182,7 +182,7 @@ namespace Perspex.Input { element = element.GetSelfAndVisualAncestors() .OfType() - .FirstOrDefault(x => CanFocus(x)); + .FirstOrDefault(CanFocus); } if (element != null) diff --git a/src/Perspex.Input/KeyBinding.cs b/src/Perspex.Input/KeyBinding.cs index 48b0eb3712..33a41e7fee 100644 --- a/src/Perspex.Input/KeyBinding.cs +++ b/src/Perspex.Input/KeyBinding.cs @@ -9,7 +9,7 @@ namespace Perspex.Input { public class KeyBinding : PerspexObject { - public static PerspexProperty CommandProperty = + public static readonly PerspexProperty CommandProperty = PerspexProperty.Register("Command"); public ICommand Command @@ -18,7 +18,7 @@ namespace Perspex.Input set { SetValue(CommandProperty, value); } } - public static PerspexProperty GestureProperty = + public static readonly PerspexProperty GestureProperty = PerspexProperty.Register("Gesture"); public KeyGesture Gesture diff --git a/src/Perspex.Input/KeyGesture.cs b/src/Perspex.Input/KeyGesture.cs index e298d63bcf..6a05280684 100644 --- a/src/Perspex.Input/KeyGesture.cs +++ b/src/Perspex.Input/KeyGesture.cs @@ -45,7 +45,7 @@ namespace Perspex.Input public InputModifiers Modifiers { get; set; } - static Dictionary KeySynonims = new Dictionary + static readonly Dictionary KeySynonims = new Dictionary { {"+", Key.OemPlus }, {"-", Key.OemMinus}, diff --git a/src/Perspex.Input/KeyboardDevice.cs b/src/Perspex.Input/KeyboardDevice.cs index fc3edad4df..421a09eaba 100644 --- a/src/Perspex.Input/KeyboardDevice.cs +++ b/src/Perspex.Input/KeyboardDevice.cs @@ -54,26 +54,20 @@ namespace Perspex.Input { var interactive = FocusedElement as IInteractive; - if (interactive != null) + interactive?.RaiseEvent(new RoutedEventArgs { - interactive.RaiseEvent(new RoutedEventArgs - { - RoutedEvent = InputElement.LostFocusEvent, - }); - } + RoutedEvent = InputElement.LostFocusEvent, + }); FocusedElement = element; interactive = element as IInteractive; - if (interactive != null) + interactive?.RaiseEvent(new GotFocusEventArgs { - interactive.RaiseEvent(new GotFocusEventArgs - { - RoutedEvent = InputElement.GotFocusEvent, - NavigationMethod = method, - InputModifiers = modifiers, - }); - } + RoutedEvent = InputElement.GotFocusEvent, + NavigationMethod = method, + InputModifiers = modifiers, + }); } } diff --git a/src/Perspex.Input/MouseDevice.cs b/src/Perspex.Input/MouseDevice.cs index 268b638def..84c723db36 100644 --- a/src/Perspex.Input/MouseDevice.cs +++ b/src/Perspex.Input/MouseDevice.cs @@ -178,17 +178,14 @@ namespace Perspex.Input { IInteractive source = GetSource(hit); - if (source != null) + source?.RaiseEvent(new PointerReleasedEventArgs { - source.RaiseEvent(new PointerReleasedEventArgs - { - Device = this, - RoutedEvent = InputElement.PointerReleasedEvent, - Source = source, - MouseButton = button, - InputModifiers = inputModifiers - }); - } + Device = this, + RoutedEvent = InputElement.PointerReleasedEvent, + Source = source, + MouseButton = button, + InputModifiers = inputModifiers + }); } } @@ -200,17 +197,14 @@ namespace Perspex.Input { IInteractive source = GetSource(hit); - if (source != null) + source?.RaiseEvent(new PointerWheelEventArgs { - source.RaiseEvent(new PointerWheelEventArgs - { - Device = this, - RoutedEvent = InputElement.PointerWheelChangedEvent, - Source = source, - Delta = delta, - InputModifiers = inputModifiers - }); - } + Device = this, + RoutedEvent = InputElement.PointerWheelChangedEvent, + Source = source, + Delta = delta, + InputModifiers = inputModifiers + }); } } diff --git a/src/Perspex.Input/Navigation/DirectionalNavigation.cs b/src/Perspex.Input/Navigation/DirectionalNavigation.cs index 3a3e1354b6..145b178c3f 100644 --- a/src/Perspex.Input/Navigation/DirectionalNavigation.cs +++ b/src/Perspex.Input/Navigation/DirectionalNavigation.cs @@ -196,16 +196,9 @@ namespace Perspex.Input.Navigation var siblings = parent.GetVisualChildren() .OfType() .Where(FocusExtensions.CanFocusDescendents); - IInputElement sibling; - - if (isForward) - { - sibling = siblings.SkipWhile(x => x != container).Skip(1).FirstOrDefault(); - } - else - { - sibling = siblings.TakeWhile(x => x != container).LastOrDefault(); - } + var sibling = isForward ? + siblings.SkipWhile(x => x != container).Skip(1).FirstOrDefault() : + siblings.TakeWhile(x => x != container).LastOrDefault(); if (sibling != null) { diff --git a/src/Perspex.Input/Navigation/TabNavigation.cs b/src/Perspex.Input/Navigation/TabNavigation.cs index 97091fffde..2cc2c54c09 100644 --- a/src/Perspex.Input/Navigation/TabNavigation.cs +++ b/src/Perspex.Input/Navigation/TabNavigation.cs @@ -204,16 +204,9 @@ namespace Perspex.Input.Navigation var siblings = parent.GetVisualChildren() .OfType() .Where(FocusExtensions.CanFocusDescendents); - IInputElement sibling; - - if (direction == FocusNavigationDirection.Next) - { - sibling = siblings.SkipWhile(x => x != container).Skip(1).FirstOrDefault(); - } - else - { - sibling = siblings.TakeWhile(x => x != container).LastOrDefault(); - } + var sibling = direction == FocusNavigationDirection.Next ? + siblings.SkipWhile(x => x != container).Skip(1).FirstOrDefault() : + siblings.TakeWhile(x => x != container).LastOrDefault(); if (sibling != null) { diff --git a/src/Perspex.Layout/Layoutable.cs b/src/Perspex.Layout/Layoutable.cs index ead1ef88bc..018cc6a16c 100644 --- a/src/Perspex.Layout/Layoutable.cs +++ b/src/Perspex.Layout/Layoutable.cs @@ -386,11 +386,7 @@ namespace Perspex.Layout else { var root = GetLayoutRoot(); - - if (root != null && root.Item1.LayoutManager != null) - { - root.Item1.LayoutManager.InvalidateMeasure(this, root.Item2); - } + root?.Item1.LayoutManager?.InvalidateMeasure(this, root.Item2); } } @@ -408,11 +404,7 @@ namespace Perspex.Layout IsArrangeValid = false; _previousArrange = null; - - if (root != null && root.Item1.LayoutManager != null) - { - root.Item1.LayoutManager.InvalidateArrange(this, root.Item2); - } + root?.Item1.LayoutManager?.InvalidateArrange(this, root.Item2); } /// @@ -606,11 +598,7 @@ namespace Perspex.Layout private static void AffectsMeasureInvalidate(PerspexPropertyChangedEventArgs e) { ILayoutable control = e.Sender as ILayoutable; - - if (control != null) - { - control.InvalidateMeasure(); - } + control?.InvalidateMeasure(); } /// @@ -620,11 +608,7 @@ namespace Perspex.Layout private static void AffectsArrangeInvalidate(PerspexPropertyChangedEventArgs e) { ILayoutable control = e.Sender as ILayoutable; - - if (control != null) - { - control.InvalidateArrange(); - } + control?.InvalidateArrange(); } /// diff --git a/src/Perspex.SceneGraph/Media/Color.cs b/src/Perspex.SceneGraph/Media/Color.cs index 35856ddb63..dbc5a108ac 100644 --- a/src/Perspex.SceneGraph/Media/Color.cs +++ b/src/Perspex.SceneGraph/Media/Color.cs @@ -134,7 +134,7 @@ namespace Perspex.Media public override string ToString() { uint rgb = ((uint)A << 24) | ((uint)R << 16) | ((uint)G << 8) | (uint)B; - return string.Format("#{0:x8}", rgb); + return $"#{rgb:x8}"; } /// diff --git a/src/Perspex.SceneGraph/Media/DashStyle.cs b/src/Perspex.SceneGraph/Media/DashStyle.cs index 8ec4d67e32..b16321e199 100644 --- a/src/Perspex.SceneGraph/Media/DashStyle.cs +++ b/src/Perspex.SceneGraph/Media/DashStyle.cs @@ -28,15 +28,7 @@ private static DashStyle dot; public static DashStyle Dot { - get - { - if (dot == null) - { - dot = new DashStyle(new double[] { 0, 2 }, 0); - } - - return dot; - } + get { return dot ?? (dot = new DashStyle(new double[] {0, 2}, 0)); } } private static DashStyle dashDot; diff --git a/src/Perspex.SceneGraph/Media/DrawingContext.cs b/src/Perspex.SceneGraph/Media/DrawingContext.cs index 28cfeb77a7..41a599858e 100644 --- a/src/Perspex.SceneGraph/Media/DrawingContext.cs +++ b/src/Perspex.SceneGraph/Media/DrawingContext.cs @@ -26,8 +26,8 @@ namespace Perspex.Media struct TransformContainer { - public Matrix LocalTransform; - public Matrix ContainerTransform; + public readonly Matrix LocalTransform; + public readonly Matrix ContainerTransform; public TransformContainer(Matrix localTransform, Matrix containerTransform) { diff --git a/src/Perspex.SceneGraph/Media/PathMarkupParser.cs b/src/Perspex.SceneGraph/Media/PathMarkupParser.cs index b8b7d592e7..0caa53dc7c 100644 --- a/src/Perspex.SceneGraph/Media/PathMarkupParser.cs +++ b/src/Perspex.SceneGraph/Media/PathMarkupParser.cs @@ -98,14 +98,9 @@ namespace Perspex.Media _context.EndFigure(false); } - if (command == Command.Move) - { - point = ReadPoint(reader); - } - else - { - point = ReadRelativePoint(reader, point); - } + point = command == Command.Move ? + ReadPoint(reader) : + ReadRelativePoint(reader, point); _context.BeginFigure(point, true); openFigure = true; diff --git a/src/Perspex.SceneGraph/Media/Transform.cs b/src/Perspex.SceneGraph/Media/Transform.cs index a91ffae56a..b7a8e8cef3 100644 --- a/src/Perspex.SceneGraph/Media/Transform.cs +++ b/src/Perspex.SceneGraph/Media/Transform.cs @@ -26,10 +26,7 @@ namespace Perspex.Media /// protected void RaiseChanged() { - if (Changed != null) - { - Changed(this, EventArgs.Empty); - } + Changed?.Invoke(this, EventArgs.Empty); } } } diff --git a/src/Perspex.SceneGraph/RelativePoint.cs b/src/Perspex.SceneGraph/RelativePoint.cs index 23c9f49590..30ee86fc4c 100644 --- a/src/Perspex.SceneGraph/RelativePoint.cs +++ b/src/Perspex.SceneGraph/RelativePoint.cs @@ -109,7 +109,7 @@ namespace Perspex /// True if the objects are equal, otherwise false. public override bool Equals(object obj) { - return (obj is RelativePoint) ? Equals((RelativePoint)obj) : false; + return (obj is RelativePoint) && Equals((RelativePoint)obj); } /// diff --git a/src/Perspex.SceneGraph/RelativeRect.cs b/src/Perspex.SceneGraph/RelativeRect.cs index 5ededd2e52..c7aba43c80 100644 --- a/src/Perspex.SceneGraph/RelativeRect.cs +++ b/src/Perspex.SceneGraph/RelativeRect.cs @@ -116,7 +116,7 @@ namespace Perspex /// True if the objects are equal, otherwise false. public override bool Equals(object obj) { - return (obj is RelativeRect) ? Equals((RelativeRect)obj) : false; + return (obj is RelativeRect) && Equals((RelativeRect)obj); } /// diff --git a/src/Perspex.SceneGraph/Rendering/RendererBase.cs b/src/Perspex.SceneGraph/Rendering/RendererBase.cs index 8b0aa1aff8..24dbab3622 100644 --- a/src/Perspex.SceneGraph/Rendering/RendererBase.cs +++ b/src/Perspex.SceneGraph/Rendering/RendererBase.cs @@ -25,7 +25,7 @@ namespace Perspex.Rendering static int s_fps; static int s_currentFrames; static TimeSpan s_lastMeasure; - static Stopwatch s_stopwatch = Stopwatch.StartNew(); + static readonly Stopwatch s_stopwatch = Stopwatch.StartNew(); /// /// Renders the specified visual. /// diff --git a/src/Perspex.SceneGraph/Thickness.cs b/src/Perspex.SceneGraph/Thickness.cs index b7847e6687..dada7c4a3e 100644 --- a/src/Perspex.SceneGraph/Thickness.cs +++ b/src/Perspex.SceneGraph/Thickness.cs @@ -231,7 +231,7 @@ namespace Perspex /// The string representation of the thickness. public override string ToString() { - return string.Format("{0},{1},{2},{3}", _left, _top, _right, _bottom); + return $"{_left},{_top},{_right},{_bottom}"; } } } diff --git a/src/Perspex.SceneGraph/Visual.cs b/src/Perspex.SceneGraph/Visual.cs index 14267ef132..8d164de630 100644 --- a/src/Perspex.SceneGraph/Visual.cs +++ b/src/Perspex.SceneGraph/Visual.cs @@ -272,11 +272,7 @@ namespace Perspex IRenderRoot root = this.GetSelfAndVisualAncestors() .OfType() .FirstOrDefault(); - - if (root != null && root.RenderQueueManager != null) - { - root.RenderQueueManager.InvalidateRender(this); - } + root?.RenderQueueManager?.InvalidateRender(this); } /// diff --git a/src/Perspex.SceneGraph/VisualTree/BoundsTracker.cs b/src/Perspex.SceneGraph/VisualTree/BoundsTracker.cs index e7abbc4105..d0bc9d763a 100644 --- a/src/Perspex.SceneGraph/VisualTree/BoundsTracker.cs +++ b/src/Perspex.SceneGraph/VisualTree/BoundsTracker.cs @@ -44,10 +44,10 @@ namespace Perspex.VisualTree boundsSubscriptions.Add(v.GetObservable(Visual.BoundsProperty)); } - var bounds = Observable.CombineLatest(boundsSubscriptions).Select(ExtractBounds); + var bounds = boundsSubscriptions.CombineLatest().Select(ExtractBounds); // TODO: Track transform and clip rectangle. - return Observable.Select(bounds, x => new TransformedBounds((Rect)x, (Rect)new Rect(), (Matrix)Matrix.Identity)); + return bounds.Select(x => new TransformedBounds((Rect)x, (Rect)new Rect(), (Matrix)Matrix.Identity)); } /// diff --git a/src/Perspex.Styling/Styling/Classes.cs b/src/Perspex.Styling/Styling/Classes.cs index 8cb5be11a6..1517b06018 100644 --- a/src/Perspex.Styling/Styling/Classes.cs +++ b/src/Perspex.Styling/Styling/Classes.cs @@ -147,11 +147,7 @@ namespace Perspex.Styling private void RaiseChanged(NotifyCollectionChangedEventArgs e) { - if (CollectionChanged != null) - { - CollectionChanged(this, e); - } - + CollectionChanged?.Invoke(this, e); _changed.OnNext(e); _afterChanged.OnNext(e); } diff --git a/src/Perspex.Styling/Styling/Selector.cs b/src/Perspex.Styling/Styling/Selector.cs index 889b343cfb..06999744c1 100644 --- a/src/Perspex.Styling/Styling/Selector.cs +++ b/src/Perspex.Styling/Styling/Selector.cs @@ -97,10 +97,7 @@ namespace Perspex.Styling /// /// Gets the target type of the selector, if available. /// - public Type TargetType - { - get { return _targetType ?? MovePrevious()?.TargetType; } - } + public Type TargetType => _targetType ?? MovePrevious()?.TargetType; /// /// Returns the previous selector if traversal is not stopped. diff --git a/src/Perspex.Styling/Styling/StyleBinding.cs b/src/Perspex.Styling/Styling/StyleBinding.cs index c0235161db..9eb6f00235 100644 --- a/src/Perspex.Styling/Styling/StyleBinding.cs +++ b/src/Perspex.Styling/Styling/StyleBinding.cs @@ -97,8 +97,8 @@ namespace Perspex.Styling } else { - return Observable - .CombineLatest(_activator, Source, (x, y) => new { Active = x, Value = y }) + return _activator + .CombineLatest(Source, (x, y) => new { Active = x, Value = y }) .Subscribe(x => observer.OnNext(x.Active ? x.Value : PerspexProperty.UnsetValue)); } } diff --git a/src/Perspex.Styling/Styling/Styler.cs b/src/Perspex.Styling/Styling/Styler.cs index d9780de6e6..f1933d5f2e 100644 --- a/src/Perspex.Styling/Styling/Styler.cs +++ b/src/Perspex.Styling/Styling/Styler.cs @@ -18,10 +18,7 @@ namespace Perspex.Styling .FirstOrDefault(); IGlobalStyles global = PerspexLocator.Current.GetService(); - if (global != null) - { - global.Styles.Attach(control, null); - } + global?.Styles.Attach(control, null); if (styleContainer != null) { @@ -36,17 +33,13 @@ namespace Perspex.Styling IVisual visual = container as IVisual; - if (visual != null) + IStyleHost parentContainer = visual?.GetVisualAncestors() + .OfType() + .FirstOrDefault(); + + if (parentContainer != null) { - IStyleHost parentContainer = visual - .GetVisualAncestors() - .OfType() - .FirstOrDefault(); - - if (parentContainer != null) - { - ApplyStyles(control, parentContainer); - } + ApplyStyles(control, parentContainer); } container.Styles.Attach(control, container); diff --git a/src/Shared/PlatformSupport/AssetLoader.cs b/src/Shared/PlatformSupport/AssetLoader.cs index 407579b55f..dc3838b18a 100644 --- a/src/Shared/PlatformSupport/AssetLoader.cs +++ b/src/Shared/PlatformSupport/AssetLoader.cs @@ -18,7 +18,7 @@ namespace Perspex.Shared.PlatformSupport private static readonly Dictionary AssemblyNameCache = new Dictionary(); - private Assembly _defaultAssembly; + private readonly Assembly _defaultAssembly; public AssetLoader(Assembly assembly = null) { diff --git a/src/Shared/RenderHelpers/TileBrushImplHelper.cs b/src/Shared/RenderHelpers/TileBrushImplHelper.cs index e0d21a3d7b..9ca4a7554c 100644 --- a/src/Shared/RenderHelpers/TileBrushImplHelper.cs +++ b/src/Shared/RenderHelpers/TileBrushImplHelper.cs @@ -23,8 +23,8 @@ namespace Perspex.RenderHelpers private readonly Size _imageSize; private readonly VisualBrush _visualBrush; private readonly ImageBrush _imageBrush; - private Matrix _transform; - private Rect _drawRect; + private readonly Matrix _transform; + private readonly Rect _drawRect; public bool IsValid { get; } diff --git a/src/Skia/Perspex.Skia/DrawingContextImpl.cs b/src/Skia/Perspex.Skia/DrawingContextImpl.cs index eec1e44e10..fee6499f01 100644 --- a/src/Skia/Perspex.Skia/DrawingContextImpl.cs +++ b/src/Skia/Perspex.Skia/DrawingContextImpl.cs @@ -187,7 +187,7 @@ namespace Perspex.Skia public void PopOpacity() => _settings->Opacity = _opacityStack.Pop(); private Matrix _currentTransform = Matrix.Identity; - private float[] _fmatrix = new float[6]; + private readonly float[] _fmatrix = new float[6]; public Matrix Transform { get { return _currentTransform; } diff --git a/src/Skia/Perspex.Skia/FormattedTextImpl.cs b/src/Skia/Perspex.Skia/FormattedTextImpl.cs index 5e267099b5..808935e99e 100644 --- a/src/Skia/Perspex.Skia/FormattedTextImpl.cs +++ b/src/Skia/Perspex.Skia/FormattedTextImpl.cs @@ -24,9 +24,9 @@ namespace Perspex.Skia return new FormattedTextImpl(handle, pShared, text); } } - - List _lines = new List(); - List _rects = new List(); + + readonly List _lines = new List(); + readonly List _rects = new List(); Size _size; public IEnumerable GetLines() diff --git a/src/Skia/Perspex.Skia/NativeBrush.cs b/src/Skia/Perspex.Skia/NativeBrush.cs index 885425d768..9d667f0c51 100644 --- a/src/Skia/Perspex.Skia/NativeBrush.cs +++ b/src/Skia/Perspex.Skia/NativeBrush.cs @@ -69,7 +69,7 @@ namespace Perspex.Skia private readonly NativeBrushPool _pool; public NativeBrush* Brush; - List _disposables = new List(); + readonly List _disposables = new List(); public NativeBrushContainer(NativeBrushPool pool) { @@ -96,7 +96,7 @@ namespace Perspex.Skia class NativeBrushPool { public static NativeBrushPool Instance { get; } = new NativeBrushPool(); - Stack _pool = new Stack(); + readonly Stack _pool = new Stack(); public void Return(NativeBrushContainer c) { diff --git a/src/Skia/Perspex.Skia/PerspexHandleHolder.cs b/src/Skia/Perspex.Skia/PerspexHandleHolder.cs index 20b6ca929f..d8f52e8f61 100644 --- a/src/Skia/Perspex.Skia/PerspexHandleHolder.cs +++ b/src/Skia/Perspex.Skia/PerspexHandleHolder.cs @@ -4,7 +4,7 @@ namespace Perspex.Skia { abstract class PerspexHandleHolder : IDisposable { - private IntPtr _handle; + private readonly IntPtr _handle; public IntPtr Handle { diff --git a/src/Windows/Perspex.Designer/AppHost/PerspexAppHost.cs b/src/Windows/Perspex.Designer/AppHost/PerspexAppHost.cs index 7cd020e249..c3ca657baf 100644 --- a/src/Windows/Perspex.Designer/AppHost/PerspexAppHost.cs +++ b/src/Windows/Perspex.Designer/AppHost/PerspexAppHost.cs @@ -23,7 +23,7 @@ namespace Perspex.Designer.AppHost private string _lastXaml; private string _currentXaml; private bool _initSuccess; - private HostedAppModel _appModel; + private readonly HostedAppModel _appModel; private Control _window; public PerspexAppHost(CommChannel channel) diff --git a/src/Windows/Perspex.Designer/AppHost/WindowHost.cs b/src/Windows/Perspex.Designer/AppHost/WindowHost.cs index 80b0fe8e9b..2ca7ad8c3a 100644 --- a/src/Windows/Perspex.Designer/AppHost/WindowHost.cs +++ b/src/Windows/Perspex.Designer/AppHost/WindowHost.cs @@ -35,8 +35,8 @@ namespace Perspex.Designer.AppHost BackColor = color; } - private Control _windowHost = new Control() {Text = "WindowWrapper"}; - private Timer _timer = new Timer {Enabled = true, Interval = 50}; + private readonly Control _windowHost = new Control() {Text = "WindowWrapper"}; + private readonly Timer _timer = new Timer {Enabled = true, Interval = 50}; private IntPtr _hWnd; private int _desiredWidth; private int _desiredHeight; diff --git a/src/Windows/Perspex.Designer/Comm/CommChannel.cs b/src/Windows/Perspex.Designer/Comm/CommChannel.cs index a2608c88db..41d613d4d6 100644 --- a/src/Windows/Perspex.Designer/Comm/CommChannel.cs +++ b/src/Windows/Perspex.Designer/Comm/CommChannel.cs @@ -17,9 +17,9 @@ namespace Perspex.Designer.Comm { private readonly BinaryReader _input; private readonly BinaryWriter _output; - private SynchronizationContext _dispatcher; - TaskCompletionSource _terminating = new TaskCompletionSource(); - private BlockingCollection _outputQueue = new BlockingCollection(); + private readonly SynchronizationContext _dispatcher; + readonly TaskCompletionSource _terminating = new TaskCompletionSource(); + private readonly BlockingCollection _outputQueue = new BlockingCollection(); public event Action OnMessage; public event Action Disposed; public event Action Exception; diff --git a/src/Windows/Perspex.Designer/PerspexDesigner.xaml b/src/Windows/Perspex.Designer/PerspexDesigner.xaml index 673b4ca52c..a5a14d170e 100644 --- a/src/Windows/Perspex.Designer/PerspexDesigner.xaml +++ b/src/Windows/Perspex.Designer/PerspexDesigner.xaml @@ -11,7 +11,7 @@ - + diff --git a/src/Windows/Perspex.Direct2D1/Media/DrawingContext.cs b/src/Windows/Perspex.Direct2D1/Media/DrawingContext.cs index fc4418f682..77f6702533 100644 --- a/src/Windows/Perspex.Direct2D1/Media/DrawingContext.cs +++ b/src/Windows/Perspex.Direct2D1/Media/DrawingContext.cs @@ -247,7 +247,7 @@ namespace Perspex.Direct2D1.Media _renderTarget.PopAxisAlignedClip(); } - Stack _layers = new Stack(); + readonly Stack _layers = new Stack(); private readonly Stack _layerPool = new Stack(); /// /// Pushes an opacity value. diff --git a/src/Windows/Perspex.Win32/Input/WindowsMouseDevice.cs b/src/Windows/Perspex.Win32/Input/WindowsMouseDevice.cs index dd8aafdaa4..aa68a95958 100644 --- a/src/Windows/Perspex.Win32/Input/WindowsMouseDevice.cs +++ b/src/Windows/Perspex.Win32/Input/WindowsMouseDevice.cs @@ -10,9 +10,7 @@ namespace Perspex.Win32.Input { public class WindowsMouseDevice : MouseDevice { - private static readonly WindowsMouseDevice s_instance = new WindowsMouseDevice(); - - public static new WindowsMouseDevice Instance => s_instance; + public new static WindowsMouseDevice Instance { get; } = new WindowsMouseDevice(); public WindowImpl CurrentWindow { diff --git a/src/Windows/Perspex.Win32/Interop/UnmanagedMethods.cs b/src/Windows/Perspex.Win32/Interop/UnmanagedMethods.cs index 3c7154e480..5e281b5678 100644 --- a/src/Windows/Perspex.Win32/Interop/UnmanagedMethods.cs +++ b/src/Windows/Perspex.Win32/Interop/UnmanagedMethods.cs @@ -806,8 +806,8 @@ namespace Perspex.Win32.Interop public IntPtr lpstrInitialDir; public IntPtr lpstrTitle; public OpenFileNameFlags Flags; - private ushort Unused; - private ushort Unused2; + private readonly ushort Unused; + private readonly ushort Unused2; public IntPtr lpstrDefExt; public IntPtr lCustData; public IntPtr lpfnHook; diff --git a/src/Windows/Perspex.Win32/WindowImpl.cs b/src/Windows/Perspex.Win32/WindowImpl.cs index 5204c737a2..3ae9dc26cb 100644 --- a/src/Windows/Perspex.Win32/WindowImpl.cs +++ b/src/Windows/Perspex.Win32/WindowImpl.cs @@ -204,10 +204,7 @@ namespace Perspex.Win32 window.IsEnabled = true; } - if (activated != null) - { - activated.Activate(); - } + activated?.Activate(); }); } @@ -256,20 +253,12 @@ namespace Perspex.Win32 case UnmanagedMethods.WindowActivate.WA_ACTIVE: case UnmanagedMethods.WindowActivate.WA_CLICKACTIVE: _isActive = true; - if (Activated != null) - { - Activated(); - } - + Activated?.Invoke(); break; case UnmanagedMethods.WindowActivate.WA_INACTIVE: _isActive = false; - if (Deactivated != null) - { - Deactivated(); - } - + Deactivated?.Invoke(); break; } diff --git a/tests/Perspex.Base.UnitTests/PerspexObjectTests_Direct.cs b/tests/Perspex.Base.UnitTests/PerspexObjectTests_Direct.cs index 42fcf39f3c..491c31b9ad 100644 --- a/tests/Perspex.Base.UnitTests/PerspexObjectTests_Direct.cs +++ b/tests/Perspex.Base.UnitTests/PerspexObjectTests_Direct.cs @@ -350,7 +350,7 @@ namespace Perspex.Base.UnitTests PerspexProperty.RegisterDirect("Bar", o => o.Baz, (o,v) => o.Baz = v); private string _foo = "initial"; - private string _bar = "bar"; + private readonly string _bar = "bar"; private int _baz = 5; public string Foo diff --git a/tests/Perspex.Markup.UnitTests/TestRoot.cs b/tests/Perspex.Markup.UnitTests/TestRoot.cs index fa63095210..3649493f0c 100644 --- a/tests/Perspex.Markup.UnitTests/TestRoot.cs +++ b/tests/Perspex.Markup.UnitTests/TestRoot.cs @@ -10,7 +10,7 @@ namespace Perspex.Markup.UnitTests { public class TestRoot : Decorator, IRenderRoot, INameScope { - private NameScope _nameScope = new NameScope(); + private readonly NameScope _nameScope = new NameScope(); event EventHandler INameScope.Registered { diff --git a/tests/Perspex.Markup.UnitTests/UnitTestSynchronizationContext.cs b/tests/Perspex.Markup.UnitTests/UnitTestSynchronizationContext.cs index 1a1eaf4ecf..20dfec9168 100644 --- a/tests/Perspex.Markup.UnitTests/UnitTestSynchronizationContext.cs +++ b/tests/Perspex.Markup.UnitTests/UnitTestSynchronizationContext.cs @@ -45,8 +45,8 @@ namespace Perspex.Markup.UnitTests public class Scope : IDisposable { - private SynchronizationContext _old; - private UnitTestSynchronizationContext _new; + private readonly SynchronizationContext _old; + private readonly UnitTestSynchronizationContext _new; public Scope(SynchronizationContext old, UnitTestSynchronizationContext n) { From ceece5802bb0f2e4e2153653b7403c0c73979060 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Sun, 29 Nov 2015 10:22:38 +0100 Subject: [PATCH 056/211] Correctly track removal in ControlLocator. Fixes #323. --- src/Markup/Perspex.Markup/ControlLocator.cs | 6 ++-- src/Perspex.Controls/NameScope.cs | 2 +- .../ControlLocatorTests.cs | 30 +++++++++++++++++++ 3 files changed, 35 insertions(+), 3 deletions(-) diff --git a/src/Markup/Perspex.Markup/ControlLocator.cs b/src/Markup/Perspex.Markup/ControlLocator.cs index c2c080137d..4887de8a15 100644 --- a/src/Markup/Perspex.Markup/ControlLocator.cs +++ b/src/Markup/Perspex.Markup/ControlLocator.cs @@ -45,10 +45,12 @@ namespace Perspex.Markup .OfType(); var unregistered = Observable.FromEventPattern( x => nameScope.Unregistered += x, - x => nameScope.Unregistered -= x); + x => nameScope.Unregistered -= x) + .Where(x => x.EventArgs.Name == name) + .Select(_ => (IControl)null); return registered .StartWith(nameScope.Find(name)) - .TakeUntil(unregistered); + .Merge(unregistered); } else { diff --git a/src/Perspex.Controls/NameScope.cs b/src/Perspex.Controls/NameScope.cs index 768e9de9dc..ab64733a4b 100644 --- a/src/Perspex.Controls/NameScope.cs +++ b/src/Perspex.Controls/NameScope.cs @@ -102,7 +102,7 @@ namespace Perspex.Controls if (_inner.TryGetValue(name, out element)) { _inner.Remove(name); - Registered?.Invoke(this, new NameScopeEventArgs(name, element)); + Unregistered?.Invoke(this, new NameScopeEventArgs(name, element)); } } } diff --git a/tests/Perspex.Markup.UnitTests/ControlLocatorTests.cs b/tests/Perspex.Markup.UnitTests/ControlLocatorTests.cs index 14286479c2..19dbe5200b 100644 --- a/tests/Perspex.Markup.UnitTests/ControlLocatorTests.cs +++ b/tests/Perspex.Markup.UnitTests/ControlLocatorTests.cs @@ -71,6 +71,36 @@ namespace Perspex.Markup.UnitTests Assert.Equal(0, root.NameScopeUnregisteredSubscribers); } + [Fact] + public void Track_By_Name_Should_Track_Removal_And_Readd() + { + StackPanel panel; + TextBlock target; + TextBlock relativeTo; + + var root = new TestRoot + { + Child = panel = new StackPanel + { + Children = new Controls.Controls + { + (target = new TextBlock { Name = "target" }), + (relativeTo = new TextBlock { Name = "start" }), + } + } + }; + + var locator = ControlLocator.Track(relativeTo, "target"); + var result = new List(); + locator.Subscribe(x => result.Add(x)); + + var other = new TextBlock { Name = "target" }; + panel.Children.Remove(target); + panel.Children.Add(other); + + Assert.Equal(new[] { target, null, other }, result); + } + [Fact] public void Track_By_Name_Should_Find_Control_When_Tree_Changed() { From 3aa72e0e787553d940cba6cdb30d2f5c9f672f0d Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Sun, 29 Nov 2015 10:40:10 +0100 Subject: [PATCH 057/211] Set MultiBinding default mode to OneWay. Fixes #299. --- src/Markup/Perspex.Markup.Xaml/Data/MultiBinding.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Markup/Perspex.Markup.Xaml/Data/MultiBinding.cs b/src/Markup/Perspex.Markup.Xaml/Data/MultiBinding.cs index 1e27cc230a..4ea1feb9a2 100644 --- a/src/Markup/Perspex.Markup.Xaml/Data/MultiBinding.cs +++ b/src/Markup/Perspex.Markup.Xaml/Data/MultiBinding.cs @@ -31,7 +31,7 @@ namespace Perspex.Markup.Xaml.Data /// /// Gets or sets the binding mode. /// - public BindingMode Mode { get; set; } + public BindingMode Mode { get; set; } = BindingMode.OneWay; /// /// Gets or sets the binding priority. From 9199bb48cee08b91bd2a2d05cf3088f2f094a5ec Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Sun, 29 Nov 2015 10:48:30 +0100 Subject: [PATCH 058/211] Use invariant locale in tests. Fixes #304. --- .../Data/ExpressionSubjectTests.cs | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/tests/Perspex.Markup.UnitTests/Data/ExpressionSubjectTests.cs b/tests/Perspex.Markup.UnitTests/Data/ExpressionSubjectTests.cs index 7209d0bd4b..2f11bf1551 100644 --- a/tests/Perspex.Markup.UnitTests/Data/ExpressionSubjectTests.cs +++ b/tests/Perspex.Markup.UnitTests/Data/ExpressionSubjectTests.cs @@ -37,6 +37,8 @@ namespace Perspex.Markup.UnitTests.Data [Fact] public async void Should_Convert_Get_String_To_Double() { + CultureInfo.CurrentUICulture = CultureInfo.InvariantCulture; + var data = new Class1 { StringValue = "5.6" }; var target = new ExpressionSubject(new ExpressionObserver(data, "StringValue"), typeof(double)); var result = await target.Take(1); @@ -67,6 +69,8 @@ namespace Perspex.Markup.UnitTests.Data [Fact] public void Should_Convert_Set_String_To_Double() { + CultureInfo.CurrentUICulture = CultureInfo.InvariantCulture; + var data = new Class1 { StringValue = "5.6" }; var target = new ExpressionSubject(new ExpressionObserver(data, "StringValue"), typeof(double)); @@ -78,6 +82,8 @@ namespace Perspex.Markup.UnitTests.Data [Fact] public async void Should_Convert_Get_Double_To_String() { + CultureInfo.CurrentUICulture = CultureInfo.InvariantCulture; + var data = new Class1 { DoubleValue = 5.6 }; var target = new ExpressionSubject(new ExpressionObserver(data, "DoubleValue"), typeof(string)); var result = await target.Take(1); @@ -88,6 +94,8 @@ namespace Perspex.Markup.UnitTests.Data [Fact] public void Should_Convert_Set_Double_To_String() { + CultureInfo.CurrentUICulture = CultureInfo.InvariantCulture; + var data = new Class1 { DoubleValue = 5.6 }; var target = new ExpressionSubject(new ExpressionObserver(data, "DoubleValue"), typeof(string)); From 6eadf12f9615cf81c23bdb4747ab51a752ecc9fd Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Sun, 29 Nov 2015 11:22:30 +0100 Subject: [PATCH 059/211] Add ContractAnnotation to Contract.Requires. To tell resharper that an exception will be thrown when condition == false. --- src/Perspex.Base/Contract.cs | 5 +++-- src/Perspex.Base/packages.config | 1 + 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/src/Perspex.Base/Contract.cs b/src/Perspex.Base/Contract.cs index 95e2976474..ffc47a2123 100644 --- a/src/Perspex.Base/Contract.cs +++ b/src/Perspex.Base/Contract.cs @@ -1,12 +1,12 @@ // 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.Runtime.CompilerServices; +using JetBrains.Annotations; namespace Perspex { - using System; - /// /// A stub of Code Contract's Contract class. /// @@ -25,6 +25,7 @@ namespace Perspex /// /// The precondition. [MethodImpl(MethodImplOptions.AggressiveInlining)] + [ContractAnnotation("condition:false=>stop")] public static void Requires(bool condition) where TException : Exception, new() { if (!condition) diff --git a/src/Perspex.Base/packages.config b/src/Perspex.Base/packages.config index 7d2a813bf7..26d3282b7a 100644 --- a/src/Perspex.Base/packages.config +++ b/src/Perspex.Base/packages.config @@ -1,5 +1,6 @@  + From d004426b28168cd463a529a8067c0c2043f03abe Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Sun, 29 Nov 2015 11:39:27 +0100 Subject: [PATCH 060/211] Allow styles to apply across popup contexts. By defining a StylingParent property and tying it to InheritanceParent. Fixes #312. --- src/Perspex.Base/Perspex.Base.csproj | 4 ++++ src/Perspex.Controls/Control.cs | 5 +++++ src/Perspex.Styling/Styling/IStyleHost.cs | 12 ++++++++++++ src/Perspex.Styling/Styling/Styler.cs | 6 +----- 4 files changed, 22 insertions(+), 5 deletions(-) diff --git a/src/Perspex.Base/Perspex.Base.csproj b/src/Perspex.Base/Perspex.Base.csproj index 733d8cc69e..4d6504d520 100644 --- a/src/Perspex.Base/Perspex.Base.csproj +++ b/src/Perspex.Base/Perspex.Base.csproj @@ -87,6 +87,10 @@ + + ..\..\packages\JetBrains.Annotations.9.2.0\lib\portable-net4+sl5+netcore45+wpa81+wp8+MonoAndroid1+MonoTouch1\JetBrains.Annotations.PCL328.dll + True + ..\..\packages\Rx-Core.2.2.5\lib\portable-windows8+net45+wp8\System.Reactive.Core.dll diff --git a/src/Perspex.Controls/Control.cs b/src/Perspex.Controls/Control.cs index 1787742352..861192202b 100644 --- a/src/Perspex.Controls/Control.cs +++ b/src/Perspex.Controls/Control.cs @@ -218,6 +218,11 @@ namespace Perspex.Controls /// Type IStyleable.StyleKey => GetType(); + /// + /// Gets the parent style host element. + /// + IStyleHost IStyleHost.StylingParent => (IStyleHost)InheritanceParent; + /// /// Gets a value which indicates whether a change to the is in /// the process of being notified. diff --git a/src/Perspex.Styling/Styling/IStyleHost.cs b/src/Perspex.Styling/Styling/IStyleHost.cs index cb0da117fb..40cad25f63 100644 --- a/src/Perspex.Styling/Styling/IStyleHost.cs +++ b/src/Perspex.Styling/Styling/IStyleHost.cs @@ -3,8 +3,20 @@ namespace Perspex.Styling { + /// + /// Defines an element that has a collection. + /// public interface IStyleHost : IVisual { + /// + /// Gets the styles for the element. + /// Styles Styles { get; } + + /// + /// Gets the parent style host element. + /// + IStyleHost StylingParent { get; } + } } diff --git a/src/Perspex.Styling/Styling/Styler.cs b/src/Perspex.Styling/Styling/Styler.cs index f1933d5f2e..f2f8f56380 100644 --- a/src/Perspex.Styling/Styling/Styler.cs +++ b/src/Perspex.Styling/Styling/Styler.cs @@ -31,11 +31,7 @@ namespace Perspex.Styling Contract.Requires(control != null); Contract.Requires(container != null); - IVisual visual = container as IVisual; - - IStyleHost parentContainer = visual?.GetVisualAncestors() - .OfType() - .FirstOrDefault(); + var parentContainer = container.StylingParent; if (parentContainer != null) { From 7111572a2f23d87a8b63ed1ebd1e14e7c8b23d5a Mon Sep 17 00:00:00 2001 From: Nikita Tsukanov Date: Sun, 29 Nov 2015 18:53:57 +0300 Subject: [PATCH 061/211] Implemented undo/redo for TextBox #204 --- src/Perspex.Base/Perspex.Base.csproj | 1 + src/Perspex.Base/Utilities/WeakTimer.cs | 55 ++++++++++++ src/Perspex.Controls/Perspex.Controls.csproj | 5 ++ src/Perspex.Controls/TextBox.cs | 48 ++++++++++- src/Perspex.Controls/Utils/UndoRedoHelper.cs | 89 ++++++++++++++++++++ src/Perspex.Controls/packages.config | 1 + 6 files changed, 196 insertions(+), 3 deletions(-) create mode 100644 src/Perspex.Base/Utilities/WeakTimer.cs create mode 100644 src/Perspex.Controls/Utils/UndoRedoHelper.cs diff --git a/src/Perspex.Base/Perspex.Base.csproj b/src/Perspex.Base/Perspex.Base.csproj index 4d6504d520..b0f6d928ff 100644 --- a/src/Perspex.Base/Perspex.Base.csproj +++ b/src/Perspex.Base/Perspex.Base.csproj @@ -85,6 +85,7 @@ + diff --git a/src/Perspex.Base/Utilities/WeakTimer.cs b/src/Perspex.Base/Utilities/WeakTimer.cs new file mode 100644 index 0000000000..f8901a1d74 --- /dev/null +++ b/src/Perspex.Base/Utilities/WeakTimer.cs @@ -0,0 +1,55 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Perspex.Threading; + +namespace Perspex.Utilities +{ + public class WeakTimer + { + public interface IWeakTimerSubscriber + { + bool Tick(); + } + + private readonly WeakReference _subscriber; + private DispatcherTimer _timer; + + public WeakTimer(IWeakTimerSubscriber subscriber) + { + _subscriber = new WeakReference(subscriber); + _timer = new DispatcherTimer(); + + _timer.Tick += delegate { OnTick(); }; + _timer.Start(); + } + + private void OnTick() + { + IWeakTimerSubscriber subscriber; + if (!_subscriber.TryGetTarget(out subscriber) || !subscriber.Tick()) + Stop(); + } + + public TimeSpan Interval + { + get { return _timer.Interval; } + set { _timer.Interval = value; } + } + + public void Start() => _timer.Start(); + + public void Stop() => _timer.Stop(); + + + public static WeakTimer StartWeakTimer(IWeakTimerSubscriber subscriber, TimeSpan interval) + { + var timer = new WeakTimer(subscriber) {Interval = interval}; + timer.Start(); + return timer; + } + + } +} diff --git a/src/Perspex.Controls/Perspex.Controls.csproj b/src/Perspex.Controls/Perspex.Controls.csproj index 9270658e51..f9a4a3ad84 100644 --- a/src/Perspex.Controls/Perspex.Controls.csproj +++ b/src/Perspex.Controls/Perspex.Controls.csproj @@ -148,6 +148,7 @@ + @@ -173,6 +174,10 @@ + + ..\..\packages\JetBrains.Annotations.9.2.0\lib\portable-net4+sl5+netcore45+wpa81+wp8+MonoAndroid1+MonoTouch1\JetBrains.Annotations.PCL328.dll + True + ..\..\packages\Rx-Core.2.2.5\lib\portable-windows8+net45+wp8\System.Reactive.Core.dll diff --git a/src/Perspex.Controls/TextBox.cs b/src/Perspex.Controls/TextBox.cs index 34ad050d0a..7eddecf0f7 100644 --- a/src/Perspex.Controls/TextBox.cs +++ b/src/Perspex.Controls/TextBox.cs @@ -17,7 +17,7 @@ using Perspex.Metadata; namespace Perspex.Controls { - public class TextBox : TemplatedControl + public class TextBox : TemplatedControl, UndoRedoHelper.IUndoRedoHost { public static readonly PerspexProperty AcceptsReturnProperty = PerspexProperty.Register("AcceptsReturn"); @@ -49,7 +49,22 @@ namespace Perspex.Controls public static readonly PerspexProperty UseFloatingWatermarkProperty = PerspexProperty.Register("UseFloatingWatermark"); + struct UndoRedoState : IEquatable + { + public string Text { get; } + public int CaretPosition { get; } + + public UndoRedoState(string text, int caretPosition) + { + Text = text; + CaretPosition = caretPosition; + } + + public bool Equals(UndoRedoState other) => ReferenceEquals(Text, other.Text) || Equals(Text, other.Text); + } + private TextPresenter _presenter; + private UndoRedoHelper _undoRedoHelper; static TextBox() { @@ -73,6 +88,7 @@ namespace Perspex.Controls ScrollViewer.HorizontalScrollBarVisibilityProperty, horizontalScrollBarVisibility, BindingPriority.Style); + _undoRedoHelper = new UndoRedoHelper(this); } public bool AcceptsReturn @@ -90,7 +106,12 @@ namespace Perspex.Controls public int CaretIndex { get { return GetValue(CaretIndexProperty); } - set { SetValue(CaretIndexProperty, value); } + set + { + SetValue(CaretIndexProperty, value); + if (_undoRedoHelper.IsLastState && _undoRedoHelper.LastState.Text == Text) + _undoRedoHelper.UpdateLastState(); + } } public int SelectionStart @@ -173,6 +194,7 @@ namespace Perspex.Controls Text = text.Substring(0, caretIndex) + input + text.Substring(caretIndex); CaretIndex += input.Length; SelectionStart = SelectionEnd = CaretIndex; + _undoRedoHelper.DiscardRedo(); } } @@ -189,7 +211,7 @@ namespace Perspex.Controls { return; } - + _undoRedoHelper.Snapshot(); HandleTextInput(text); } @@ -223,6 +245,16 @@ namespace Perspex.Controls Paste(); } + break; + case Key.Z: + if (modifiers == InputModifiers.Control) + _undoRedoHelper.Undo(); + + break; + case Key.Y: + if (modifiers == InputModifiers.Control) + _undoRedoHelper.Redo(); + break; case Key.Left: MoveHorizontal(-1, modifiers); @@ -524,5 +556,15 @@ namespace Perspex.Controls return i; } + + UndoRedoState UndoRedoHelper.IUndoRedoHost.UndoRedoState + { + get { return new UndoRedoState(Text, CaretIndex); } + set + { + Text = value.Text; + SelectionStart = SelectionEnd = CaretIndex = value.CaretPosition; + } + } } } diff --git a/src/Perspex.Controls/Utils/UndoRedoHelper.cs b/src/Perspex.Controls/Utils/UndoRedoHelper.cs new file mode 100644 index 0000000000..9de2ca9b35 --- /dev/null +++ b/src/Perspex.Controls/Utils/UndoRedoHelper.cs @@ -0,0 +1,89 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using JetBrains.Annotations; +using Perspex.Utilities; + +namespace Perspex.Controls.Utils +{ + class UndoRedoHelper : WeakTimer.IWeakTimerSubscriber where TState : IEquatable + { + private readonly IUndoRedoHost _host; + + public interface IUndoRedoHost + { + TState UndoRedoState { get; set; } + } + + + + private readonly LinkedList _states = new LinkedList(); + + [NotNull] + private LinkedListNode _currentNode; + + public int Limit { get; set; } = 10; + + public UndoRedoHelper(IUndoRedoHost host) + { + _host = host; + _states.AddFirst(_host.UndoRedoState); + _currentNode = _states.First; + WeakTimer.StartWeakTimer(this, new TimeSpan(0, 0, 1)); + + } + + public void Undo() + { + _host.UndoRedoState= (_currentNode = _currentNode?.Previous ?? _currentNode).Value; + } + + public bool IsLastState => _currentNode.Next == null; + + public void UpdateLastState(TState state) + { + _states.Last.Value = state; + } + + public void UpdateLastState() + { + _states.Last.Value = _host.UndoRedoState; + } + + public TState LastState => _currentNode.Value; + + public void DiscardRedo() + { + //Linked list sucks, so we are doing this + while (_currentNode.Next != null) + _states.Remove(_currentNode.Next); + } + + public void Redo() + { + _host.UndoRedoState = (_currentNode = _currentNode?.Next ?? _currentNode).Value; + } + + public void Snapshot() + { + var current = _host.UndoRedoState; + if (!_currentNode.Value.Equals(current)) + { + if(_currentNode.Next != null) + DiscardRedo(); + _states.AddLast(current); + _currentNode = _states.Last; + if(_states.Count > Limit) + _states.RemoveFirst(); + } + } + + bool WeakTimer.IWeakTimerSubscriber.Tick() + { + Snapshot(); + return true; + } + } +} diff --git a/src/Perspex.Controls/packages.config b/src/Perspex.Controls/packages.config index 7d2a813bf7..26d3282b7a 100644 --- a/src/Perspex.Controls/packages.config +++ b/src/Perspex.Controls/packages.config @@ -1,5 +1,6 @@  + From 4d13d1313c39c3aa2f4395e9eb26bd92cb96635d Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Sun, 29 Nov 2015 17:11:15 +0100 Subject: [PATCH 062/211] Added some memory leak unit tests. Using JetBrains' dotMemory Unit testing framework. Currently shows that TreeView does not get freed. --- Perspex.sln | 28 +++ tests/Perspex.LeakTests/ControlTests.cs | 106 +++++++++++ .../Perspex.LeakTests.csproj | 174 ++++++++++++++++++ .../Properties/AssemblyInfo.cs | 36 ++++ tests/Perspex.LeakTests/Readme.txt | 8 + tests/Perspex.LeakTests/TestApp.cs | 41 +++++ tests/Perspex.LeakTests/app.config | 15 ++ tests/Perspex.LeakTests/packages.config | 17 ++ 8 files changed, 425 insertions(+) create mode 100644 tests/Perspex.LeakTests/ControlTests.cs create mode 100644 tests/Perspex.LeakTests/Perspex.LeakTests.csproj create mode 100644 tests/Perspex.LeakTests/Properties/AssemblyInfo.cs create mode 100644 tests/Perspex.LeakTests/Readme.txt create mode 100644 tests/Perspex.LeakTests/TestApp.cs create mode 100644 tests/Perspex.LeakTests/app.config create mode 100644 tests/Perspex.LeakTests/packages.config diff --git a/Perspex.sln b/Perspex.sln index 24c017c309..8e836d8e35 100644 --- a/Perspex.sln +++ b/Perspex.sln @@ -136,6 +136,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Perspex.iOS", "src\iOS\Pers EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Perspex.iOSTestApplication", "src\iOS\Perspex.iOSTestApplication\Perspex.iOSTestApplication.csproj", "{8C923867-8A8F-4F6B-8B80-47D9E8436166}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Perspex.LeakTests", "tests\Perspex.LeakTests\Perspex.LeakTests.csproj", "{E1AA3DBF-9056-4530-9376-18119A7A3FFE}" +EndProject Global GlobalSection(SharedMSBuildProjectFiles) = preSolution src\Shared\RenderHelpers\RenderHelpers.projitems*{fb05ac90-89ba-4f2f-a924-f37875fb547c}*SharedItemsImports = 4 @@ -157,6 +159,7 @@ Global src\Shared\RenderHelpers\RenderHelpers.projitems*{bd43f7c0-396b-4aa1-bad9-dfde54d51298}*SharedItemsImports = 4 src\Skia\Perspex.Skia\Perspex.Skia.projitems*{bd43f7c0-396b-4aa1-bad9-dfde54d51298}*SharedItemsImports = 4 src\Shared\RenderHelpers\RenderHelpers.projitems*{3e908f67-5543-4879-a1dc-08eace79b3cd}*SharedItemsImports = 4 + src\Shared\PlatformSupport\PlatformSupport.projitems*{e1aa3dbf-9056-4530-9376-18119a7a3ffe}*SharedItemsImports = 4 EndGlobalSection GlobalSection(SolutionConfigurationPlatforms) = preSolution Ad-Hoc|Any CPU = Ad-Hoc|Any CPU @@ -1240,6 +1243,30 @@ Global {8C923867-8A8F-4F6B-8B80-47D9E8436166}.Release|iPhone.Build.0 = Release|iPhone {8C923867-8A8F-4F6B-8B80-47D9E8436166}.Release|iPhoneSimulator.ActiveCfg = Release|iPhoneSimulator {8C923867-8A8F-4F6B-8B80-47D9E8436166}.Release|iPhoneSimulator.Build.0 = Release|iPhoneSimulator + {E1AA3DBF-9056-4530-9376-18119A7A3FFE}.Ad-Hoc|Any CPU.ActiveCfg = Release|Any CPU + {E1AA3DBF-9056-4530-9376-18119A7A3FFE}.Ad-Hoc|Any CPU.Build.0 = Release|Any CPU + {E1AA3DBF-9056-4530-9376-18119A7A3FFE}.Ad-Hoc|iPhone.ActiveCfg = Release|Any CPU + {E1AA3DBF-9056-4530-9376-18119A7A3FFE}.Ad-Hoc|iPhone.Build.0 = Release|Any CPU + {E1AA3DBF-9056-4530-9376-18119A7A3FFE}.Ad-Hoc|iPhoneSimulator.ActiveCfg = Release|Any CPU + {E1AA3DBF-9056-4530-9376-18119A7A3FFE}.Ad-Hoc|iPhoneSimulator.Build.0 = Release|Any CPU + {E1AA3DBF-9056-4530-9376-18119A7A3FFE}.AppStore|Any CPU.ActiveCfg = Release|Any CPU + {E1AA3DBF-9056-4530-9376-18119A7A3FFE}.AppStore|Any CPU.Build.0 = Release|Any CPU + {E1AA3DBF-9056-4530-9376-18119A7A3FFE}.AppStore|iPhone.ActiveCfg = Release|Any CPU + {E1AA3DBF-9056-4530-9376-18119A7A3FFE}.AppStore|iPhone.Build.0 = Release|Any CPU + {E1AA3DBF-9056-4530-9376-18119A7A3FFE}.AppStore|iPhoneSimulator.ActiveCfg = Release|Any CPU + {E1AA3DBF-9056-4530-9376-18119A7A3FFE}.AppStore|iPhoneSimulator.Build.0 = Release|Any CPU + {E1AA3DBF-9056-4530-9376-18119A7A3FFE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {E1AA3DBF-9056-4530-9376-18119A7A3FFE}.Debug|Any CPU.Build.0 = Debug|Any CPU + {E1AA3DBF-9056-4530-9376-18119A7A3FFE}.Debug|iPhone.ActiveCfg = Debug|Any CPU + {E1AA3DBF-9056-4530-9376-18119A7A3FFE}.Debug|iPhone.Build.0 = Debug|Any CPU + {E1AA3DBF-9056-4530-9376-18119A7A3FFE}.Debug|iPhoneSimulator.ActiveCfg = Debug|Any CPU + {E1AA3DBF-9056-4530-9376-18119A7A3FFE}.Debug|iPhoneSimulator.Build.0 = Debug|Any CPU + {E1AA3DBF-9056-4530-9376-18119A7A3FFE}.Release|Any CPU.ActiveCfg = Release|Any CPU + {E1AA3DBF-9056-4530-9376-18119A7A3FFE}.Release|Any CPU.Build.0 = Release|Any CPU + {E1AA3DBF-9056-4530-9376-18119A7A3FFE}.Release|iPhone.ActiveCfg = Release|Any CPU + {E1AA3DBF-9056-4530-9376-18119A7A3FFE}.Release|iPhone.Build.0 = Release|Any CPU + {E1AA3DBF-9056-4530-9376-18119A7A3FFE}.Release|iPhoneSimulator.ActiveCfg = Release|Any CPU + {E1AA3DBF-9056-4530-9376-18119A7A3FFE}.Release|iPhoneSimulator.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -1282,5 +1309,6 @@ Global {FF69B927-C545-49AE-8E16-3D14D621AA12} = {7CF9789C-F1D3-4D0E-90E5-F1DF67A2753F} {4488AD85-1495-4809-9AA4-DDFE0A48527E} = {0CB0B92E-6CFF-4240-80A5-CCAFE75D91E1} {8C923867-8A8F-4F6B-8B80-47D9E8436166} = {0CB0B92E-6CFF-4240-80A5-CCAFE75D91E1} + {E1AA3DBF-9056-4530-9376-18119A7A3FFE} = {C5A00AC3-B34C-4564-9BDD-2DA473EF4D8B} EndGlobalSection EndGlobal diff --git a/tests/Perspex.LeakTests/ControlTests.cs b/tests/Perspex.LeakTests/ControlTests.cs new file mode 100644 index 0000000000..557f04f794 --- /dev/null +++ b/tests/Perspex.LeakTests/ControlTests.cs @@ -0,0 +1,106 @@ +// 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.Collections.Generic; +using System.Linq; +using JetBrains.dotMemoryUnit; +using Perspex.Controls; +using Perspex.Controls.Templates; +using Xunit; +using Xunit.Abstractions; + +namespace Perspex.LeakTests +{ + [DotMemoryUnit(FailIfRunWithoutSupport = false)] + public class ControlTests + { + public ControlTests(ITestOutputHelper atr) + { + TestApp.Initialize(); + DotMemoryUnitTestOutput.SetOutputMethod(atr.WriteLine); + } + + [Fact] + public void Canvas_Is_Freed() + { + Func run = () => + { + var window = new Window + { + Content = new Canvas() + }; + + // Do a layout and make sure that Canvas gets added to visual tree. + window.LayoutManager.ExecuteLayoutPass(); + Assert.IsType(window.Presenter.Child); + + // Clear the content and ensure the Canvas is removed. + window.Content = null; + window.LayoutManager.ExecuteLayoutPass(); + Assert.Null(window.Presenter.Child); + + return window; + }; + + var result = run(); + + dotMemory.Check(memory => + Assert.Equal(0, memory.GetObjects(where => where.Type.Is()).ObjectsCount)); + } + + [Fact] + public void TreeView_Is_Freed() + { + Func run = () => + { + var nodes = new[] + { + new Node + { + Children = new[] { new Node() }, + } + }; + + TreeView target; + + var window = new Window + { + Content = target = new TreeView + { + DataTemplates = new DataTemplates + { + new FuncTreeDataTemplate( + x => new TextBlock { Text = x.Name }, + x => x.Children, + x => true) + }, + Items = nodes + } + }; + + // Do a layout and make sure that TreeViewItems get realized. + window.LayoutManager.ExecuteLayoutPass(); + Assert.Equal(1, target.ItemContainerGenerator.Containers.Count()); + + // Clear the content and ensure the TreeView is removed. + window.Content = null; + window.LayoutManager.ExecuteLayoutPass(); + Assert.Null(window.Presenter.Child); + + return window; + }; + + var result = run(); + + dotMemory.Check(memory => + Assert.Equal(0, memory.GetObjects(where => where.Type.Is()).ObjectsCount)); + } + + private class Node + { + public string Name { get; set; } + public IEnumerable Children { get; set; } + } + } +} diff --git a/tests/Perspex.LeakTests/Perspex.LeakTests.csproj b/tests/Perspex.LeakTests/Perspex.LeakTests.csproj new file mode 100644 index 0000000000..b2d67554c3 --- /dev/null +++ b/tests/Perspex.LeakTests/Perspex.LeakTests.csproj @@ -0,0 +1,174 @@ + + + + + + Debug + AnyCPU + {E1AA3DBF-9056-4530-9376-18119A7A3FFE} + Library + Properties + Perspex.LeakTests + Perspex.LeakTests + v4.5 + 512 + + + + + + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + + + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + + + + ..\..\packages\JetBrains.dotMemoryUnit.2.1.20150828.125449\lib\dotMemory.Unit.dll + True + + + ..\..\packages\Moq.4.2.1507.0118\lib\net40\Moq.dll + True + + + ..\..\packages\AutoFixture.3.31.3\lib\net40\Ploeh.AutoFixture.dll + True + + + ..\..\packages\AutoFixture.AutoMoq.3.31.1\lib\net40\Ploeh.AutoFixture.AutoMoq.dll + True + + + + + ..\..\packages\Rx-Core.2.2.5\lib\net45\System.Reactive.Core.dll + True + + + ..\..\packages\Rx-Interfaces.2.2.5\lib\net45\System.Reactive.Interfaces.dll + True + + + ..\..\packages\Rx-Linq.2.2.5\lib\net45\System.Reactive.Linq.dll + True + + + ..\..\packages\Rx-PlatformServices.2.2.5\lib\net45\System.Reactive.PlatformServices.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 + + + + + + + + + + + + + + {3e53a01a-b331-47f3-b828-4a5717e77a24} + Perspex.Markup.Xaml + + + {6417e941-21bc-467b-a771-0de389353ce6} + Perspex.Markup + + + {d211e587-d8bc-45b9-95a4-f297c8fa5200} + Perspex.Animation + + + {799a7bb5-3c2c-48b6-85a7-406a12c420da} + Perspex.Application + + + {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 + + + {3e10a5fa-e8da-48b1-ad44-6a5b6cb7750f} + Perspex.Themes.Default + + + {5ccb5571-7c30-4e7d-967d-0e2158ebd91f} + Perspex.Controls.UnitTests + + + + + + + + + + + + + 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.LeakTests/Properties/AssemblyInfo.cs b/tests/Perspex.LeakTests/Properties/AssemblyInfo.cs new file mode 100644 index 0000000000..cb943198bc --- /dev/null +++ b/tests/Perspex.LeakTests/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.LeakTests")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("Perspex.LeakTests")] +[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("e1aa3dbf-9056-4530-9376-18119a7a3ffe")] + +// 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.LeakTests/Readme.txt b/tests/Perspex.LeakTests/Readme.txt new file mode 100644 index 0000000000..2a6087b5b3 --- /dev/null +++ b/tests/Perspex.LeakTests/Readme.txt @@ -0,0 +1,8 @@ +Memory Leak Tests +----------------- + +These tests use JetBrains' dotMemory Unit. When run in a normal test runner, they will always pass. + +To run the tests, you need to have dotMemory/ReSharper and install the XUnit plugin. You should +then be able to run the tests using Resharper -> Unit Tests -> Run all tests from solution under +dotMemory Unit. \ No newline at end of file diff --git a/tests/Perspex.LeakTests/TestApp.cs b/tests/Perspex.LeakTests/TestApp.cs new file mode 100644 index 0000000000..d0df7c18fc --- /dev/null +++ b/tests/Perspex.LeakTests/TestApp.cs @@ -0,0 +1,41 @@ +// 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 Moq; +using Perspex.Controls.UnitTests; +using Perspex.Platform; +using Perspex.Shared.PlatformSupport; +using Perspex.Themes.Default; +using Ploeh.AutoFixture; +using Ploeh.AutoFixture.AutoMoq; + +namespace Perspex.LeakTests +{ + internal class TestApp : Application + { + private TestApp() + { + RegisterServices(); + + var fixture = new Fixture().Customize(new AutoMoqCustomization()); + var windowImpl = new Mock(); + var renderInterface = fixture.Create(); + + PerspexLocator.CurrentMutable + .Bind().ToConstant(new AssetLoader()) + .Bind().ToConstant(new PclPlatformWrapper()) + .Bind().ToConstant(renderInterface) + .Bind().ToConstant(new WindowingPlatformMock(() => windowImpl.Object)); + + Styles = new DefaultTheme(); + } + + public static void Initialize() + { + if (Current == null) + { + new TestApp(); + } + } + } +} diff --git a/tests/Perspex.LeakTests/app.config b/tests/Perspex.LeakTests/app.config new file mode 100644 index 0000000000..4ecf794192 --- /dev/null +++ b/tests/Perspex.LeakTests/app.config @@ -0,0 +1,15 @@ + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/tests/Perspex.LeakTests/packages.config b/tests/Perspex.LeakTests/packages.config new file mode 100644 index 0000000000..35e293bdbc --- /dev/null +++ b/tests/Perspex.LeakTests/packages.config @@ -0,0 +1,17 @@ + + + + + + + + + + + + + + + + + \ No newline at end of file From 6f10775ec42eb1a1340abac569f852437b345c82 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Sun, 29 Nov 2015 17:23:28 +0100 Subject: [PATCH 063/211] Unsubscribe from RenderTransform.Changed When control is not attached to a visual tree. This fixes a memory leak with TreeView. --- src/Perspex.SceneGraph/Visual.cs | 20 +++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/src/Perspex.SceneGraph/Visual.cs b/src/Perspex.SceneGraph/Visual.cs index 8d164de630..6779cd7269 100644 --- a/src/Perspex.SceneGraph/Visual.cs +++ b/src/Perspex.SceneGraph/Visual.cs @@ -383,8 +383,17 @@ namespace Perspex /// Called when the control is added to a visual tree. /// /// The event args. + /// + /// It is vital that if you override this method you call the base implementation; + /// failing to do so will cause numerous features to not work as expected. + /// protected virtual void OnAttachedToVisualTree(VisualTreeAttachmentEventArgs e) { + if (RenderTransform != null) + { + RenderTransform.Changed += RenderTransformChanged; + } + AttachedToVisualTree?.Invoke(this, e); } @@ -392,8 +401,17 @@ namespace Perspex /// Called when the control is removed from a visual tree. /// /// The event args. + /// + /// It is vital that if you override this method you call the base implementation; + /// failing to do so will cause numerous features to not work as expected. + /// protected virtual void OnDetachedFromVisualTree(VisualTreeAttachmentEventArgs e) { + if (RenderTransform != null) + { + RenderTransform.Changed -= RenderTransformChanged; + } + DetachedFromVisualTree?.Invoke(this, e); } @@ -475,7 +493,7 @@ namespace Perspex { var sender = e.Sender as Visual; - if (sender != null) + if (sender?._isAttachedToVisualTree == true) { var oldValue = e.OldValue as Transform; var newValue = e.NewValue as Transform; From 9b8bff1ba4dc462ff3eaeb2409b12c9b7bbfd41c Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Sun, 29 Nov 2015 17:28:14 +0100 Subject: [PATCH 064/211] Added leak test for named controls. As previously they were leaking before recent name scope fixes. --- tests/Perspex.LeakTests/ControlTests.cs | 32 +++++++++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/tests/Perspex.LeakTests/ControlTests.cs b/tests/Perspex.LeakTests/ControlTests.cs index 557f04f794..a44532dc5c 100644 --- a/tests/Perspex.LeakTests/ControlTests.cs +++ b/tests/Perspex.LeakTests/ControlTests.cs @@ -49,6 +49,38 @@ namespace Perspex.LeakTests Assert.Equal(0, memory.GetObjects(where => where.Type.Is()).ObjectsCount)); } + [Fact] + public void Named_Canvas_Is_Freed() + { + Func run = () => + { + var window = new Window + { + Content = new Canvas + { + Name = "foo" + } + }; + + // Do a layout and make sure that Canvas gets added to visual tree. + window.LayoutManager.ExecuteLayoutPass(); + Assert.IsType(window.Find("foo")); + Assert.IsType(window.Presenter.Child); + + // Clear the content and ensure the Canvas is removed. + window.Content = null; + window.LayoutManager.ExecuteLayoutPass(); + Assert.Null(window.Presenter.Child); + + return window; + }; + + var result = run(); + + dotMemory.Check(memory => + Assert.Equal(0, memory.GetObjects(where => where.Type.Is()).ObjectsCount)); + } + [Fact] public void TreeView_Is_Freed() { From d10176b70cdfa903423556bc9076a5155615ce69 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Sun, 29 Nov 2015 19:09:14 +0100 Subject: [PATCH 065/211] Update Linear/RadialGradientBrushImpl Fixed formatting, dispose of GradientStopCollection; however they're still leaking massively. --- .../Media/LinearGradientBrushImpl.cs | 37 ++++++++++++---- .../Media/RadialGradientBrushImpl.cs | 44 ++++++++++++++----- 2 files changed, 61 insertions(+), 20 deletions(-) diff --git a/src/Windows/Perspex.Direct2D1/Media/LinearGradientBrushImpl.cs b/src/Windows/Perspex.Direct2D1/Media/LinearGradientBrushImpl.cs index b67378990c..4c2c3c0779 100644 --- a/src/Windows/Perspex.Direct2D1/Media/LinearGradientBrushImpl.cs +++ b/src/Windows/Perspex.Direct2D1/Media/LinearGradientBrushImpl.cs @@ -1,11 +1,7 @@ // 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.Collections.Generic; using System.Linq; -using System.Text; -using System.Threading.Tasks; namespace Perspex.Direct2D1.Media { @@ -16,17 +12,40 @@ namespace Perspex.Direct2D1.Media SharpDX.Direct2D1.RenderTarget target, Size destinationSize) { - var gradientStops = brush.GradientStops.Select(s => new SharpDX.Direct2D1.GradientStop { Color = s.Color.ToDirect2D(), Position = (float)s.Offset }).ToArray(); + if (brush.GradientStops.Count == 0) + { + return; + } - Point startPoint = brush.StartPoint.ToPixels(destinationSize); - Point endPoint = brush.EndPoint.ToPixels(destinationSize); + var gradientStops = brush.GradientStops.Select(s => new SharpDX.Direct2D1.GradientStop + { + Color = s.Color.ToDirect2D(), + Position = (float)s.Offset + }).ToArray(); + + var startPoint = brush.StartPoint.ToPixels(destinationSize); + var endPoint = brush.EndPoint.ToPixels(destinationSize); PlatformBrush = new SharpDX.Direct2D1.LinearGradientBrush( target, - new SharpDX.Direct2D1.LinearGradientBrushProperties { StartPoint = startPoint.ToSharpDX(), EndPoint = endPoint.ToSharpDX() }, - new SharpDX.Direct2D1.BrushProperties { Opacity = (float)brush.Opacity, Transform = target.Transform }, + new SharpDX.Direct2D1.LinearGradientBrushProperties + { + StartPoint = startPoint.ToSharpDX(), + EndPoint = endPoint.ToSharpDX() + }, + new SharpDX.Direct2D1.BrushProperties + { + Opacity = (float)brush.Opacity, + Transform = target.Transform + }, new SharpDX.Direct2D1.GradientStopCollection(target, gradientStops, brush.SpreadMethod.ToDirect2D()) ); } + + public override void Dispose() + { + ((SharpDX.Direct2D1.LinearGradientBrush)PlatformBrush)?.GradientStopCollection.Dispose(); + base.Dispose(); + } } } diff --git a/src/Windows/Perspex.Direct2D1/Media/RadialGradientBrushImpl.cs b/src/Windows/Perspex.Direct2D1/Media/RadialGradientBrushImpl.cs index f0b21c7ae7..db45994da9 100644 --- a/src/Windows/Perspex.Direct2D1/Media/RadialGradientBrushImpl.cs +++ b/src/Windows/Perspex.Direct2D1/Media/RadialGradientBrushImpl.cs @@ -1,11 +1,7 @@ // 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.Collections.Generic; using System.Linq; -using System.Text; -using System.Threading.Tasks; namespace Perspex.Direct2D1.Media { @@ -16,20 +12,46 @@ namespace Perspex.Direct2D1.Media SharpDX.Direct2D1.RenderTarget target, Size destinationSize) { - var gradientStops = brush.GradientStops.Select(s => new SharpDX.Direct2D1.GradientStop { Color = s.Color.ToDirect2D(), Position = (float)s.Offset }).ToArray(); + if (brush.GradientStops.Count == 0) + { + return; + } - Point centerPoint = brush.Center.ToPixels(destinationSize); - Point GradientOriginOffset = brush.GradientOrigin.ToPixels(destinationSize); + var gradientStops = brush.GradientStops.Select(s => new SharpDX.Direct2D1.GradientStop + { + Color = s.Color.ToDirect2D(), + Position = (float)s.Offset + }).ToArray(); + + var centerPoint = brush.Center.ToPixels(destinationSize); + var GradientOriginOffset = brush.GradientOrigin.ToPixels(destinationSize); + // Note: Direct2D supports RadiusX and RadiusY but Cairo backend supports only Radius property - double radiusX = brush.Radius; - double radiusY = brush.Radius; + var radiusX = brush.Radius; + var radiusY = brush.Radius; PlatformBrush = new SharpDX.Direct2D1.RadialGradientBrush( target, - new SharpDX.Direct2D1.RadialGradientBrushProperties { Center = centerPoint.ToSharpDX(), GradientOriginOffset = GradientOriginOffset.ToSharpDX(), RadiusX = (float)radiusX, RadiusY = (float)radiusY }, - new SharpDX.Direct2D1.BrushProperties { Opacity = (float)brush.Opacity, Transform = target.Transform }, + new SharpDX.Direct2D1.RadialGradientBrushProperties + { + Center = centerPoint.ToSharpDX(), + GradientOriginOffset = GradientOriginOffset.ToSharpDX(), + RadiusX = (float)radiusX, + RadiusY = (float)radiusY + }, + new SharpDX.Direct2D1.BrushProperties + { + Opacity = (float)brush.Opacity, + Transform = target.Transform + }, new SharpDX.Direct2D1.GradientStopCollection(target, gradientStops, brush.SpreadMethod.ToDirect2D()) ); } + + public override void Dispose() + { + ((SharpDX.Direct2D1.RadialGradientBrush)PlatformBrush)?.GradientStopCollection.Dispose(); + base.Dispose(); + } } } From b5c69fd151cf5e7e4cfd7db7feeea4fc0ce1cdb8 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Sun, 29 Nov 2015 19:33:01 +0100 Subject: [PATCH 066/211] Use correct transform in Linear/Radial gradient. --- src/Windows/Perspex.Direct2D1/Media/LinearGradientBrushImpl.cs | 2 +- src/Windows/Perspex.Direct2D1/Media/RadialGradientBrushImpl.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Windows/Perspex.Direct2D1/Media/LinearGradientBrushImpl.cs b/src/Windows/Perspex.Direct2D1/Media/LinearGradientBrushImpl.cs index 4c2c3c0779..e402b456c3 100644 --- a/src/Windows/Perspex.Direct2D1/Media/LinearGradientBrushImpl.cs +++ b/src/Windows/Perspex.Direct2D1/Media/LinearGradientBrushImpl.cs @@ -36,7 +36,7 @@ namespace Perspex.Direct2D1.Media new SharpDX.Direct2D1.BrushProperties { Opacity = (float)brush.Opacity, - Transform = target.Transform + Transform = SharpDX.Matrix3x2.Identity, }, new SharpDX.Direct2D1.GradientStopCollection(target, gradientStops, brush.SpreadMethod.ToDirect2D()) ); diff --git a/src/Windows/Perspex.Direct2D1/Media/RadialGradientBrushImpl.cs b/src/Windows/Perspex.Direct2D1/Media/RadialGradientBrushImpl.cs index db45994da9..453166de6b 100644 --- a/src/Windows/Perspex.Direct2D1/Media/RadialGradientBrushImpl.cs +++ b/src/Windows/Perspex.Direct2D1/Media/RadialGradientBrushImpl.cs @@ -42,7 +42,7 @@ namespace Perspex.Direct2D1.Media new SharpDX.Direct2D1.BrushProperties { Opacity = (float)brush.Opacity, - Transform = target.Transform + Transform = SharpDX.Matrix3x2.Identity, }, new SharpDX.Direct2D1.GradientStopCollection(target, gradientStops, brush.SpreadMethod.ToDirect2D()) ); From b3e90d01ea936e13aab9e8b45f72e70329decb1f Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Sun, 29 Nov 2015 19:38:53 +0100 Subject: [PATCH 067/211] Updated LinearGradientBrush expected output on D2D. --- ...Brush_RedBlue_Horizontal_Fill.expected.png | Bin 652 -> 642 bytes ...ntBrush_RedBlue_Vertical_Fill.expected.png | Bin 1959 -> 1998 bytes 2 files changed, 0 insertions(+), 0 deletions(-) diff --git a/tests/TestFiles/Direct2D1/Media/LinearGradientBrush/LinearGradientBrush_RedBlue_Horizontal_Fill.expected.png b/tests/TestFiles/Direct2D1/Media/LinearGradientBrush/LinearGradientBrush_RedBlue_Horizontal_Fill.expected.png index 744a314256d8d42397f9d111ab4b83364af647d0..89668d19f40847604c17c2ed55e9f75562e9602b 100644 GIT binary patch literal 642 zcmeAS@N?(olHy`uVBq!ia0vp^CqS5k4M?tyST_$yF%}28J29*~C-V}>VM%xNb!1@J z*w6hZk(GggNypR0F{C2y?PW*4CI=DLgA;$xm%P%V`fWzy@=(r$KPpel0W|@E{j0)# z_P35dzH!{F@A~rh&tHAt{{QaY^*QhEg_~{ueJZUi{^pr?+Ov0mpL+Jqz1h0bH_n+C oI4TIRFpUyJJq*C{&rqMzopr03^|LYXATM literal 652 zcmeAS@N?(olHy`uVBq!ia0vp^CqS5k4M?tyST_$yF%}28J29*~C-V}>VM%xNb!1@J z*w6hZk(Ggg$;{KmF{C2y?PYJSLk1!a7hf>l`R^qA+tDgugSk+86`!tV<(_lzfEt0| zM_~KgJEgTYzxBSy$87&@)TjUa-uLC@_s@lwrG1_{_hwD%^V>g9oy*y`$$0kqH)o!m zFMnQmimmT9=N?4?4yHzhQDSI^0V6mSH0(D%#+!a^OTm^)OpPEBPgg&ebxsLQ0G|$z AQUCw| diff --git a/tests/TestFiles/Direct2D1/Media/LinearGradientBrush/LinearGradientBrush_RedBlue_Vertical_Fill.expected.png b/tests/TestFiles/Direct2D1/Media/LinearGradientBrush/LinearGradientBrush_RedBlue_Vertical_Fill.expected.png index 3a3d7df5d47bd2009a1d5613de4af07739416a7b..2b882874dcb11d7c79f3c1b73cc29a68952378ff 100644 GIT binary patch literal 1998 zcmYk74^R_V9>>$rP1$hB!vdU{K}g z{0@Y{VCO}KiQmslnHWgs4$OF(r}wQXS$&*t>Syh(Zh^-cH;;!G1HUR>f8$2*#5Gew z*c;)`noBseIF~_j|*ZJ#O}vOS*r;?Ro7!+(CFPFPJtQA1lYI!zbi zgK`H?U1pW2nlc0qagqnKK^#laJvVj#iM}}kON|>6RSt|bl_D)wzORSL18(;aSk|uX zSk%ckvt+7#aVU)0*0PFY$vne%*8iaH1Gs594O=1TuV?%KR&SQ6nsk(Eb@QsG62$&$ z3CUKg>Wp3BwTfW=H-3@o4VEynpRwcv{WZUdfV_#fLFT#8gMPrqP|JPu?Yf0 z<$J*0u~e!$7w!Q;-?hHOu`nOL1DaK9WvbrBX3+c;S0i@T1dN%uJWpUVH+sP6vy^hg z?mhvU6Iad=m}1ZjkezZ6;TJoBW)OcZ$KvyeKWOq4QdMty2&C8L2|1R(eFBwf3u%S) zVFnHN`*AGMK_2SgY4&4W^nD5K6yze9cSlB32MIS2$_xk9Ah}~s9XnI;J39LeR2{d{2kgiDq zO{D;g7XKFLtj}FhAyq|>tcIY^^Pt4C-yl8Le=!8z=F)J#KgW_c=MI|Ye-K#t)jrUq z%E}SG_6}%1xcdu%$!s5EQCW;TplPIU%`6DySh%rJ%>a@OnioX?z4@>_cEv&w0@ROX zsxm1FnjK|YB*&`|H0yf5Ca~o~x(Lpb2a%k=ghF~kCt{kcO}PehF;Elg-elYW41 z3muON#FYd4W*bwT@197IVupsq`26g3u1e{e=F-`d-c^bZeEggfuluYy|Ls4$@@RUt zYjF=mLYi?UJy<_K$Wy=2P1O@jRSt~KUwhtdUVC&FiIaqk{$3!iJt@(szO6}#obGB% zvT5|E4Z}g3@fT(exL`NNOA=?Y{-p8w(`dmR*D!fgJzj&%aoO>EwkmFkG-A^@R0v~P0D>b12@ts{Jg@^#lfw6FFfz%Tn;+ozX|qc`&Y*(QPJLVc;1I<-O&-3 zO|Y8UzkHJtu@pxE#?>xG)13be&--BdUYxAp!rmM&V0AIwEDrFzO?#y{xtJ>hC}x?M zZWlvon3Io_3uOVYnzS!bBCV_zf{JZksLrb#rgZP4DA64M7A%~X=en4}qE}(eGX2Lm zxgj_m*6LdkW~Q)2L&L_+IC(302f#%UCZ?@x1-Mt(JkgO?R>9s>9!{Y|N_!=wuUWbb z)v>pNW{f@qCp)WQZ;s2AMyAawfS`u#T{sz94e8?_wwswYlY)i{xFJI{oLT{eA5kJF zX%Ph7wYj4@ln0tMmsOM~NN^oA@pdCq`1cQCOo~o{ldDkuKEufYoIbVGft2V|*&!J7AGs&0TQUWjl3oMA9rV$? zPrd}zt(<~%$t5c#lAJPvd%VQN9MtF`=vHGmPLAb6dY0QdNH_68vq!!RO=DLpQ~)0gb_LethIK>A#D1HfN30FUhUKy`jTpxJyp5-0Z%J3(`+yO}9BWxOXw-7FU1+r*`5bs|il6wyGjZbY?q!bo1oSsBS|lt!W;kME59o!we|zK{d-B yz?kz5DL5IF4@)Y`A-B}=y=U*k!z)G|hL~r=y2Tx&{XV>t8Ij?!VTO&{5BvcFr+!@k literal 1959 zcmY+F2~-nj5XWQM#nwf|YXbpgDjqj#{W92IB`}i^QpZ`qe`&RMkD&O(r z_~SSn&Uk-6@oIQ7?9T^g@Vl5SQNd$O>}ua-oRTJ+ZusNs$Mtx z%HK6oDrn7n!IED}=VOfIiVHY6 zG$f^}0$o^Gwg_YRI~M}XjgwME=dVF|tO#l@ng>Epn8zeuc=whlbA8~isYvOOEGQ{a z&&8OWa@M_zlZ-0b%mbm~csq{*s0C<=O>iOru z<|-l;F?pO~Z8qE_arAC8*bG&rAtvI~c!16ONW9;*79dZhK%ABH!F#3iQjBT&RtQ4r z@oG_5%oz~s^_Yf+Ot=p@gPWrX+Myc;Z^XMa-0a35ASb~jCg}R^aDWzD>$%z5vn=c* z2|9a#wVCs|S~R2JDhSn`utG!J4qD6gkLwPRc*~zMXl+4@jPh8v3A%W70z*UQ`ocIK zH`hT9Wi-GLosMf&HwWM++jkT6okOq_!XpBqbyx+v;|usP^Nj(i3!?uh zO!m$+v{ke&kjJib5?FlJ<}|0eBD=*?ooTQdX`IqsKd!WB=O_npd7&yDr%~w@{*> zDk?Yn_h}uId)Tmt&i{r9mJYIEI{SCR{u(ZD$kM-=;us_?l{YHC@YUpzWDWL+FDqTLa8Bt%J~Ba+9qGG-CUFzeCpl?JZFTvh6u0KTvc+ zEQB$g>=dSrFmF#`$?-xqOqkgGW(v8DJJ_(n+=o03jKxx3XoT4$9?fBEoF2}4r$$Om zf^<%Cj%&-C@7_ z+1|^3bXi9MYDj=_&K?-EuoqTUm@!30nSaG@OP_ff62pB6i*dj4ClX&?Q4eePeuff> zv6%uV>V5-F;wjhQL>a^x>4>vj0gEwP0H;u}0)%!fO_5UO9b-WKsH+>saAw0Up1fI0 z;=Tec4v2sy+sI5FC9Q zngK%B2INQ#9t%RAK0_qFA-)}odsJ$<-8VfTr@xzzc5u4DaKeH4YLQ!i5#%5}wrI#w z3=U61MiaokDuDZYtkHZs6olsGts&?nG6WU|ano_TYtvvHSXmrF-ykCa+Pi7E;mr*I zvo67j+DX7obvg5yTJ&u5_aO9H7!S>#cM&deiLO^IYQOX&IErc4aeq$lgq)4%RuZ(K z3Ql1Javk9QX#jscvxA`5SFuB|cdzG$-wFWlg<)`0Ijo1vA|WkHEs{LF0_v?jmT10f zA=oTF>qF4fNw_SG(@#Q Date: Mon, 30 Nov 2015 00:19:15 +0100 Subject: [PATCH 068/211] Fixed leak in Linear/RadialGradientBrush. --- .../Media/LinearGradientBrushImpl.cs | 37 ++++++++--------- .../Media/RadialGradientBrushImpl.cs | 41 +++++++++---------- 2 files changed, 38 insertions(+), 40 deletions(-) diff --git a/src/Windows/Perspex.Direct2D1/Media/LinearGradientBrushImpl.cs b/src/Windows/Perspex.Direct2D1/Media/LinearGradientBrushImpl.cs index e402b456c3..436826fbaa 100644 --- a/src/Windows/Perspex.Direct2D1/Media/LinearGradientBrushImpl.cs +++ b/src/Windows/Perspex.Direct2D1/Media/LinearGradientBrushImpl.cs @@ -26,26 +26,25 @@ namespace Perspex.Direct2D1.Media var startPoint = brush.StartPoint.ToPixels(destinationSize); var endPoint = brush.EndPoint.ToPixels(destinationSize); - PlatformBrush = new SharpDX.Direct2D1.LinearGradientBrush( + using (var stops = new SharpDX.Direct2D1.GradientStopCollection( target, - new SharpDX.Direct2D1.LinearGradientBrushProperties - { - StartPoint = startPoint.ToSharpDX(), - EndPoint = endPoint.ToSharpDX() - }, - new SharpDX.Direct2D1.BrushProperties - { - Opacity = (float)brush.Opacity, - Transform = SharpDX.Matrix3x2.Identity, - }, - new SharpDX.Direct2D1.GradientStopCollection(target, gradientStops, brush.SpreadMethod.ToDirect2D()) - ); - } - - public override void Dispose() - { - ((SharpDX.Direct2D1.LinearGradientBrush)PlatformBrush)?.GradientStopCollection.Dispose(); - base.Dispose(); + gradientStops, + brush.SpreadMethod.ToDirect2D())) + { + PlatformBrush = new SharpDX.Direct2D1.LinearGradientBrush( + target, + new SharpDX.Direct2D1.LinearGradientBrushProperties + { + StartPoint = startPoint.ToSharpDX(), + EndPoint = endPoint.ToSharpDX() + }, + new SharpDX.Direct2D1.BrushProperties + { + Opacity = (float)brush.Opacity, + Transform = SharpDX.Matrix3x2.Identity, + }, + stops); + } } } } diff --git a/src/Windows/Perspex.Direct2D1/Media/RadialGradientBrushImpl.cs b/src/Windows/Perspex.Direct2D1/Media/RadialGradientBrushImpl.cs index 453166de6b..356f5d8533 100644 --- a/src/Windows/Perspex.Direct2D1/Media/RadialGradientBrushImpl.cs +++ b/src/Windows/Perspex.Direct2D1/Media/RadialGradientBrushImpl.cs @@ -30,28 +30,27 @@ namespace Perspex.Direct2D1.Media var radiusX = brush.Radius; var radiusY = brush.Radius; - PlatformBrush = new SharpDX.Direct2D1.RadialGradientBrush( + using (var stops = new SharpDX.Direct2D1.GradientStopCollection( target, - new SharpDX.Direct2D1.RadialGradientBrushProperties - { - Center = centerPoint.ToSharpDX(), - GradientOriginOffset = GradientOriginOffset.ToSharpDX(), - RadiusX = (float)radiusX, - RadiusY = (float)radiusY - }, - new SharpDX.Direct2D1.BrushProperties - { - Opacity = (float)brush.Opacity, - Transform = SharpDX.Matrix3x2.Identity, - }, - new SharpDX.Direct2D1.GradientStopCollection(target, gradientStops, brush.SpreadMethod.ToDirect2D()) - ); - } - - public override void Dispose() - { - ((SharpDX.Direct2D1.RadialGradientBrush)PlatformBrush)?.GradientStopCollection.Dispose(); - base.Dispose(); + gradientStops, + brush.SpreadMethod.ToDirect2D())) + { + PlatformBrush = new SharpDX.Direct2D1.RadialGradientBrush( + target, + new SharpDX.Direct2D1.RadialGradientBrushProperties + { + Center = centerPoint.ToSharpDX(), + GradientOriginOffset = GradientOriginOffset.ToSharpDX(), + RadiusX = (float)radiusX, + RadiusY = (float)radiusY + }, + new SharpDX.Direct2D1.BrushProperties + { + Opacity = (float)brush.Opacity, + Transform = SharpDX.Matrix3x2.Identity, + }, + stops); + } } } } From 4fa3c98ca381f1f47706bf9ec3743c18a7f7637a Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Mon, 30 Nov 2015 21:06:24 +0100 Subject: [PATCH 069/211] Detach styles when control removed from visual tree. --- src/Perspex.Controls/ItemsControl.cs | 7 ++ .../Primitives/TemplatedControl.cs | 17 ++-- src/Perspex.Layout/LayoutManager.cs | 4 +- .../Properties/AssemblyInfo.cs | 4 +- src/Perspex.Styling/Styling/Style.cs | 14 +++- src/Perspex.Styling/Styling/StyleBinding.cs | 13 +-- .../Perspex.Styling.UnitTests.csproj | 2 + .../StyleBindingTests.cs | 79 +++++++++++++++++++ tests/Perspex.Styling.UnitTests/StyleTests.cs | 27 ++++++- tests/Perspex.Styling.UnitTests/TestRoot.cs | 34 ++++++++ 10 files changed, 184 insertions(+), 17 deletions(-) create mode 100644 tests/Perspex.Styling.UnitTests/StyleBindingTests.cs create mode 100644 tests/Perspex.Styling.UnitTests/TestRoot.cs diff --git a/src/Perspex.Controls/ItemsControl.cs b/src/Perspex.Controls/ItemsControl.cs index 5c06dbc04e..02357c6f3f 100644 --- a/src/Perspex.Controls/ItemsControl.cs +++ b/src/Perspex.Controls/ItemsControl.cs @@ -152,6 +152,13 @@ namespace Perspex.Controls Presenter = nameScope.Find("PART_ItemsPresenter"); } + /// + protected override void OnTemplateChanged(PerspexPropertyChangedEventArgs e) + { + base.OnTemplateChanged(e); + ItemContainerGenerator.Clear(); + } + /// /// Caled when the property changes. /// diff --git a/src/Perspex.Controls/Primitives/TemplatedControl.cs b/src/Perspex.Controls/Primitives/TemplatedControl.cs index 9e98ad154c..92e1c4af88 100644 --- a/src/Perspex.Controls/Primitives/TemplatedControl.cs +++ b/src/Perspex.Controls/Primitives/TemplatedControl.cs @@ -81,12 +81,7 @@ namespace Perspex.Controls.Primitives /// static TemplatedControl() { - TemplateProperty.Changed.Subscribe(e => - { - var templatedControl = (TemplatedControl)e.Sender; - templatedControl._templateApplied = false; - templatedControl.InvalidateMeasure(); - }); + TemplateProperty.Changed.AddClassHandler(x => x.OnTemplateChanged); } /// @@ -224,6 +219,16 @@ namespace Perspex.Controls.Primitives { } + /// + /// Called when the property changes. + /// + /// The event args. + protected virtual void OnTemplateChanged(PerspexPropertyChangedEventArgs e) + { + _templateApplied = false; + InvalidateMeasure(); + } + /// /// Sets the TemplatedParent property for a control created from the control template and /// applies the templates of nested templated controls. Also adds each control to its name diff --git a/src/Perspex.Layout/LayoutManager.cs b/src/Perspex.Layout/LayoutManager.cs index f1c45693ab..326549ccae 100644 --- a/src/Perspex.Layout/LayoutManager.cs +++ b/src/Perspex.Layout/LayoutManager.cs @@ -226,12 +226,12 @@ namespace Perspex.Layout { var parent = item.Control.GetVisualParent(); - while (parent.PreviousMeasure == null) + while (parent != null && parent.PreviousMeasure == null) { parent = parent.GetVisualParent(); } - if (parent.GetVisualRoot() == Root) + if (parent != null && parent.GetVisualRoot() == Root) { parent.Measure(parent.PreviousMeasure.Value, true); } diff --git a/src/Perspex.Styling/Properties/AssemblyInfo.cs b/src/Perspex.Styling/Properties/AssemblyInfo.cs index f06c17710e..21034a0753 100644 --- a/src/Perspex.Styling/Properties/AssemblyInfo.cs +++ b/src/Perspex.Styling/Properties/AssemblyInfo.cs @@ -2,7 +2,9 @@ // Licensed under the MIT license. See licence.md file in the project root for full license information. using System.Reflection; +using System.Runtime.CompilerServices; using Perspex.Metadata; [assembly: AssemblyTitle("Perspex.Styling")] -[assembly: XmlnsDefinition("https://github.com/perspex", "Perspex.Styling")] \ No newline at end of file +[assembly: XmlnsDefinition("https://github.com/perspex", "Perspex.Styling")] +[assembly: InternalsVisibleTo("Perspex.Styling.UnitTests")] \ No newline at end of file diff --git a/src/Perspex.Styling/Styling/Style.cs b/src/Perspex.Styling/Styling/Style.cs index 61b05d1412..112bc817a0 100644 --- a/src/Perspex.Styling/Styling/Style.cs +++ b/src/Perspex.Styling/Styling/Style.cs @@ -56,9 +56,21 @@ namespace Perspex.Styling if (match.ImmediateResult != false) { + var visual = control as IVisual; + var activator = match.ObservableResult ?? + Observable.Never().StartWith(true); + + if (visual != null) + { + var detached = Observable.FromEventPattern( + x => visual.DetachedFromVisualTree += x, + x => visual.DetachedFromVisualTree -= x); + activator = activator.TakeUntil(detached); + } + foreach (var setter in Setters) { - setter.Apply(this, control, match.ObservableResult); + setter.Apply(this, control, activator); } } } diff --git a/src/Perspex.Styling/Styling/StyleBinding.cs b/src/Perspex.Styling/Styling/StyleBinding.cs index 9eb6f00235..b2ddbeee1b 100644 --- a/src/Perspex.Styling/Styling/StyleBinding.cs +++ b/src/Perspex.Styling/Styling/StyleBinding.cs @@ -61,7 +61,8 @@ namespace Perspex.Styling /// public object ActivatedValue { - get; } + get; + } /// /// Gets a description of the binding. @@ -90,16 +91,16 @@ namespace Perspex.Styling if (Source == null) { - return _activator.Subscribe( - active => observer.OnNext(active ? ActivatedValue : PerspexProperty.UnsetValue), - observer.OnError, - observer.OnCompleted); + return _activator + .Select(active => active ? ActivatedValue : PerspexProperty.UnsetValue) + .Subscribe(observer); } else { return _activator .CombineLatest(Source, (x, y) => new { Active = x, Value = y }) - .Subscribe(x => observer.OnNext(x.Active ? x.Value : PerspexProperty.UnsetValue)); + .Select(x => x.Active ? x.Value : PerspexProperty.UnsetValue) + .Subscribe(observer); } } } diff --git a/tests/Perspex.Styling.UnitTests/Perspex.Styling.UnitTests.csproj b/tests/Perspex.Styling.UnitTests/Perspex.Styling.UnitTests.csproj index 4099319b5a..95cb831480 100644 --- a/tests/Perspex.Styling.UnitTests/Perspex.Styling.UnitTests.csproj +++ b/tests/Perspex.Styling.UnitTests/Perspex.Styling.UnitTests.csproj @@ -85,10 +85,12 @@ + + diff --git a/tests/Perspex.Styling.UnitTests/StyleBindingTests.cs b/tests/Perspex.Styling.UnitTests/StyleBindingTests.cs new file mode 100644 index 0000000000..82a7f66d07 --- /dev/null +++ b/tests/Perspex.Styling.UnitTests/StyleBindingTests.cs @@ -0,0 +1,79 @@ +// 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.Collections.Generic; +using System.Reactive.Linq; +using System.Reactive.Subjects; +using Xunit; + +namespace Perspex.Styling.UnitTests +{ + public class StyleBindingTests + { + [Fact] + public async void Should_Produce_UnsetValue_On_Activator_False() + { + var activator = new BehaviorSubject(false); + var target = new StyleBinding(activator, 1, string.Empty); + var result = await target.Take(1); + + Assert.Equal(PerspexProperty.UnsetValue, result); + } + + [Fact] + public async void Should_Produce_Value_On_Activator_True() + { + var activator = new BehaviorSubject(true); + var target = new StyleBinding(activator, 1, string.Empty); + var result = await target.Take(1); + + Assert.Equal(1, result); + } + + [Fact] + public void Should_Change_Value_On_Activator_Change() + { + var activator = new BehaviorSubject(false); + var target = new StyleBinding(activator, 1, string.Empty); + var result = new List(); + + target.Subscribe(x => result.Add(x)); + + activator.OnNext(true); + activator.OnNext(false); + + Assert.Equal(new[] { PerspexProperty.UnsetValue, 1, PerspexProperty.UnsetValue }, result); + } + + [Fact] + public void Should_Change_Value_With_Source_Observable() + { + var activator = new BehaviorSubject(false); + var source = new BehaviorSubject(1); + var target = new StyleBinding(activator, source, string.Empty); + var result = new List(); + + target.Subscribe(x => result.Add(x)); + + activator.OnNext(true); + source.OnNext(2); + activator.OnNext(false); + + Assert.Equal(new[] { PerspexProperty.UnsetValue, 1, 2, PerspexProperty.UnsetValue }, result); + } + + [Fact] + public void Should_Complete_When_Activator_Completes() + { + var activator = new BehaviorSubject(false); + var target = new StyleBinding(activator, 1, string.Empty); + var completed = false; + + target.Subscribe(_ => { }, () => completed = true); + activator.OnCompleted(); + + Assert.True(completed); + } + } +} diff --git a/tests/Perspex.Styling.UnitTests/StyleTests.cs b/tests/Perspex.Styling.UnitTests/StyleTests.cs index cda975b597..c484389cbf 100644 --- a/tests/Perspex.Styling.UnitTests/StyleTests.cs +++ b/tests/Perspex.Styling.UnitTests/StyleTests.cs @@ -166,7 +166,7 @@ namespace Perspex.Styling.UnitTests { var source = new BehaviorSubject("Foo"); - Style style = new Style(x => x.OfType().Class("foo")) + var style = new Style(x => x.OfType().Class("foo")) { Setters = new[] { @@ -187,6 +187,31 @@ namespace Perspex.Styling.UnitTests Assert.Equal("foodefault", target.Foo); } + [Fact] + public void Style_Should_Detach_When_Removed_From_Visual_Tree() + { + Border border; + + var style = new Style(x => x.OfType()) + { + Setters = new[] + { + new Setter(Border.BorderThicknessProperty, 4), + } + }; + + var root = new TestRoot + { + Child = border = new Border(), + }; + + style.Attach(border, null); + + Assert.Equal(4, border.BorderThickness); + root.Child = null; + Assert.Equal(0, border.BorderThickness); + } + private class Class1 : Control { public static readonly PerspexProperty FooProperty = diff --git a/tests/Perspex.Styling.UnitTests/TestRoot.cs b/tests/Perspex.Styling.UnitTests/TestRoot.cs new file mode 100644 index 0000000000..2f613e4e51 --- /dev/null +++ b/tests/Perspex.Styling.UnitTests/TestRoot.cs @@ -0,0 +1,34 @@ +// 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 Moq; +using Perspex.Controls; +using Perspex.Layout; +using Perspex.Platform; +using Perspex.Rendering; + +namespace Perspex.Styling.UnitTests +{ + internal class TestRoot : Decorator, ILayoutRoot, IRenderRoot + { + public Size ClientSize => new Size(100, 100); + + public ILayoutManager LayoutManager => new Mock().Object; + + public IRenderTarget RenderTarget + { + get { throw new NotImplementedException(); } + } + + public IRenderQueueManager RenderQueueManager + { + get { throw new NotImplementedException(); } + } + + public Point TranslatePointToScreen(Point p) + { + return new Point(); + } + } +} From 2bcaef3c7ecb02ceb6bdca2aa82c8372515e1e37 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Mon, 30 Nov 2015 21:32:50 +0100 Subject: [PATCH 070/211] Fixed failing TreeView tests. --- src/Perspex.Controls/ItemsControl.cs | 6 +++++- tests/Perspex.Controls.UnitTests/TreeViewTests.cs | 5 +++++ 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/src/Perspex.Controls/ItemsControl.cs b/src/Perspex.Controls/ItemsControl.cs index 02357c6f3f..1fc232870e 100644 --- a/src/Perspex.Controls/ItemsControl.cs +++ b/src/Perspex.Controls/ItemsControl.cs @@ -156,7 +156,11 @@ namespace Perspex.Controls protected override void OnTemplateChanged(PerspexPropertyChangedEventArgs e) { base.OnTemplateChanged(e); - ItemContainerGenerator.Clear(); + + if (e.NewValue == null) + { + ItemContainerGenerator.Clear(); + } } /// diff --git a/tests/Perspex.Controls.UnitTests/TreeViewTests.cs b/tests/Perspex.Controls.UnitTests/TreeViewTests.cs index 2db62dd619..1e8ffc7a48 100644 --- a/tests/Perspex.Controls.UnitTests/TreeViewTests.cs +++ b/tests/Perspex.Controls.UnitTests/TreeViewTests.cs @@ -67,6 +67,9 @@ namespace Perspex.Controls.UnitTests var container = target.ItemContainerGenerator.TreeContainerFromItem( tree[0].Children[1].Children[0]); + + Assert.NotNull(container); + var header = ((TreeViewItem)container).Header; var headerContent = ((TextBlock)header).Text; @@ -91,6 +94,8 @@ namespace Perspex.Controls.UnitTests var item = tree[0].Children[1].Children[0]; var container = (TreeViewItem)target.ItemContainerGenerator.TreeContainerFromItem(item); + Assert.NotNull(container); + container.RaiseEvent(new PointerPressEventArgs { RoutedEvent = InputElement.PointerPressedEvent, From 260f2a4cc4b7221cee0060dab167179b639151dd Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Mon, 30 Nov 2015 22:25:04 +0100 Subject: [PATCH 071/211] Added some more leak tests. All passing. --- tests/Perspex.LeakTests/ControlTests.cs | 107 ++++++++++++++++++++++++ tests/Perspex.LeakTests/TestApp.cs | 4 + 2 files changed, 111 insertions(+) diff --git a/tests/Perspex.LeakTests/ControlTests.cs b/tests/Perspex.LeakTests/ControlTests.cs index a44532dc5c..abc4280357 100644 --- a/tests/Perspex.LeakTests/ControlTests.cs +++ b/tests/Perspex.LeakTests/ControlTests.cs @@ -7,6 +7,7 @@ using System.Linq; using JetBrains.dotMemoryUnit; using Perspex.Controls; using Perspex.Controls.Templates; +using Perspex.VisualTree; using Xunit; using Xunit.Abstractions; @@ -81,6 +82,112 @@ namespace Perspex.LeakTests Assert.Equal(0, memory.GetObjects(where => where.Type.Is()).ObjectsCount)); } + [Fact] + public void ScrollViewer_With_Content_Is_Freed() + { + Func run = () => + { + var window = new Window + { + Content = new ScrollViewer + { + Content = new Canvas() + } + }; + + // Do a layout and make sure that ScrollViewer gets added to visual tree and its + // template applied. + window.LayoutManager.ExecuteLayoutPass(); + Assert.IsType(window.Presenter.Child); + Assert.IsType(((ScrollViewer)window.Presenter.Child).Presenter.Child); + + // Clear the content and ensure the ScrollViewer is removed. + window.Content = null; + window.LayoutManager.ExecuteLayoutPass(); + Assert.Null(window.Presenter.Child); + + return window; + }; + + var result = run(); + + dotMemory.Check(memory => + Assert.Equal(0, memory.GetObjects(where => where.Type.Is()).ObjectsCount)); + dotMemory.Check(memory => + Assert.Equal(0, memory.GetObjects(where => where.Type.Is()).ObjectsCount)); + } + + [Fact] + public void TextBox_Is_Freed() + { + Func run = () => + { + var window = new Window + { + Content = new TextBox() + }; + + // Do a layout and make sure that TextBox gets added to visual tree and its + // template applied. + window.LayoutManager.ExecuteLayoutPass(); + Assert.IsType(window.Presenter.Child); + Assert.NotEqual(0, window.Presenter.Child.GetVisualChildren().Count()); + + // Clear the content and ensure the TextBox is removed. + window.Content = null; + window.LayoutManager.ExecuteLayoutPass(); + Assert.Null(window.Presenter.Child); + + return window; + }; + + var result = run(); + + dotMemory.Check(memory => + Assert.Equal(0, memory.GetObjects(where => where.Type.Is()).ObjectsCount)); + } + + [Fact] + public void TextBox_With_Xaml_Binding_Is_Freed() + { + Func run = () => + { + var window = new Window + { + DataContext = new Node { Name = "foo" }, + Content = new TextBox() + }; + + var binding = new Perspex.Markup.Xaml.Data.Binding + { + Path = "Name" + }; + + binding.Bind((TextBox)window.Content, TextBox.TextProperty); + + // Do a layout and make sure that TextBox gets added to visual tree and its + // Text property set. + window.LayoutManager.ExecuteLayoutPass(); + Assert.IsType(window.Presenter.Child); + Assert.Equal("foo", ((TextBox)window.Presenter.Child).Text); + + // Clear the content and DataContext and ensure the TextBox is removed. + window.Content = null; + window.DataContext = null; + window.LayoutManager.ExecuteLayoutPass(); + Assert.Null(window.Presenter.Child); + + return window; + }; + + var result = run(); + + dotMemory.Check(memory => + Assert.Equal(0, memory.GetObjects(where => where.Type.Is()).ObjectsCount)); + dotMemory.Check(memory => + Assert.Equal(0, memory.GetObjects(where => where.Type.Is()).ObjectsCount)); + } + [Fact] public void TreeView_Is_Freed() { diff --git a/tests/Perspex.LeakTests/TestApp.cs b/tests/Perspex.LeakTests/TestApp.cs index d0df7c18fc..8c08c57fc3 100644 --- a/tests/Perspex.LeakTests/TestApp.cs +++ b/tests/Perspex.LeakTests/TestApp.cs @@ -20,11 +20,15 @@ namespace Perspex.LeakTests var fixture = new Fixture().Customize(new AutoMoqCustomization()); var windowImpl = new Mock(); var renderInterface = fixture.Create(); + var threadingInterface = Mock.Of(x => + x.CurrentThreadIsLoopThread == true); PerspexLocator.CurrentMutable .Bind().ToConstant(new AssetLoader()) .Bind().ToConstant(new PclPlatformWrapper()) .Bind().ToConstant(renderInterface) + .Bind().ToConstant(threadingInterface) + .Bind().ToConstant(new Mock().Object) .Bind().ToConstant(new WindowingPlatformMock(() => windowImpl.Object)); Styles = new DefaultTheme(); From f20e26ea38282ab9713d6bb47be7cbb9c09819ab Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Mon, 30 Nov 2015 22:47:23 +0100 Subject: [PATCH 072/211] TextBox ScrollViewer doesn't get released When TextBox.Template is cleared. This is showing up as a leak when a TextBox is contained in a TabControl and tabs get changed. --- tests/Perspex.LeakTests/ControlTests.cs | 30 +++++++++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/tests/Perspex.LeakTests/ControlTests.cs b/tests/Perspex.LeakTests/ControlTests.cs index abc4280357..c770fb1c16 100644 --- a/tests/Perspex.LeakTests/ControlTests.cs +++ b/tests/Perspex.LeakTests/ControlTests.cs @@ -188,6 +188,36 @@ namespace Perspex.LeakTests Assert.Equal(0, memory.GetObjects(where => where.Type.Is()).ObjectsCount)); } + [Fact] + public void TextBox_ScrollViewer_Is_Freed_When_Template_Cleared() + { + Func run = () => + { + var window = new Window + { + Content = new TextBox() + }; + + // Do a layout and make sure that TextBox gets added to visual tree and its + // template applied. + window.LayoutManager.ExecuteLayoutPass(); + Assert.IsType(window.Presenter.Child); + Assert.NotEqual(0, window.Presenter.Child.GetVisualChildren().Count()); + + // Clear the template and ensure the TextBox template gets removed + ((TextBox)window.Content).Template = null; + window.LayoutManager.ExecuteLayoutPass(); + Assert.Equal(0, window.Presenter.Child.GetVisualChildren().Count()); + + return window; + }; + + var result = run(); + + dotMemory.Check(memory => + Assert.Equal(0, memory.GetObjects(where => where.Type.Is()).ObjectsCount)); + } + [Fact] public void TreeView_Is_Freed() { From b094e06859a5ed4884adabdcd73b12957e5cb39f Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Mon, 30 Nov 2015 23:17:51 +0100 Subject: [PATCH 073/211] Added tooltip to default theme. --- src/Perspex.Themes.Default/DefaultTheme.paml | 1 + .../Perspex.Themes.Default.csproj | 3 +++ src/Perspex.Themes.Default/ToolTip.paml | 16 ++++++++++++++++ 3 files changed, 20 insertions(+) create mode 100644 src/Perspex.Themes.Default/ToolTip.paml diff --git a/src/Perspex.Themes.Default/DefaultTheme.paml b/src/Perspex.Themes.Default/DefaultTheme.paml index 534dad5a07..5902e568b3 100644 --- a/src/Perspex.Themes.Default/DefaultTheme.paml +++ b/src/Perspex.Themes.Default/DefaultTheme.paml @@ -23,6 +23,7 @@ + diff --git a/src/Perspex.Themes.Default/Perspex.Themes.Default.csproj b/src/Perspex.Themes.Default/Perspex.Themes.Default.csproj index cd60cda586..2ec676592d 100644 --- a/src/Perspex.Themes.Default/Perspex.Themes.Default.csproj +++ b/src/Perspex.Themes.Default/Perspex.Themes.Default.csproj @@ -140,6 +140,9 @@ Designer + + Designer + Designer diff --git a/src/Perspex.Themes.Default/ToolTip.paml b/src/Perspex.Themes.Default/ToolTip.paml new file mode 100644 index 0000000000..ed59c08a70 --- /dev/null +++ b/src/Perspex.Themes.Default/ToolTip.paml @@ -0,0 +1,16 @@ + \ No newline at end of file From 811e28b191dd5cc94e5bfddc9933bfb9bf119189 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Tue, 1 Dec 2015 21:13:50 +0100 Subject: [PATCH 074/211] Updated OmniXAML with fix for attached property on root instance. --- src/Markup/Perspex.Markup.Xaml/OmniXAML | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Markup/Perspex.Markup.Xaml/OmniXAML b/src/Markup/Perspex.Markup.Xaml/OmniXAML index 3e3b46ba66..f2673838c0 160000 --- a/src/Markup/Perspex.Markup.Xaml/OmniXAML +++ b/src/Markup/Perspex.Markup.Xaml/OmniXAML @@ -1 +1 @@ -Subproject commit 3e3b46ba66941da925092e2977003d0553cfc907 +Subproject commit f2673838c0422ff0f6fdb3e4b34a5302971f59b5 From 40c559c41d937ef2df495a721f9d4ab09f2fc6fc Mon Sep 17 00:00:00 2001 From: Nikita Tsukanov Date: Wed, 2 Dec 2015 01:26:10 +0300 Subject: [PATCH 075/211] WndHost is unreliable --- .../Perspex.Designer/AppHost/WindowHost.cs | 48 ++++++++++++------- .../InProcDesigner/InProcDesignerView.xaml.cs | 2 +- .../Perspex.Designer/PerspexDesigner.xaml | 2 +- .../Perspex.Designer/PerspexDesigner.xaml.cs | 32 ++----------- 4 files changed, 39 insertions(+), 45 deletions(-) diff --git a/src/Windows/Perspex.Designer/AppHost/WindowHost.cs b/src/Windows/Perspex.Designer/AppHost/WindowHost.cs index 2ca7ad8c3a..a98850bcf3 100644 --- a/src/Windows/Perspex.Designer/AppHost/WindowHost.cs +++ b/src/Windows/Perspex.Designer/AppHost/WindowHost.cs @@ -11,13 +11,18 @@ namespace Perspex.Designer.AppHost { class WindowHost : UserControl { - public WindowHost() + private readonly bool _supportScroll; + + public WindowHost(bool supportScroll) { - AutoScroll = true; - VerticalScroll.Enabled = true; - HorizontalScroll.Enabled = true; + _supportScroll = supportScroll; + if (_supportScroll) + { + AutoScroll = true; + VerticalScroll.Enabled = true; + HorizontalScroll.Enabled = true; + } SetStyle(ControlStyles.AllPaintingInWmPaint, true); - Text = "ScrollableArea"; Controls.Add(_windowHost); _windowHost.Anchor = AnchorStyles.None; _timer.Tick += delegate @@ -102,25 +107,36 @@ namespace Perspex.Designer.AppHost { if (_hWnd != IntPtr.Zero) { - WinApi.RECT rc; - WinApi.GetWindowRect(_hWnd, out rc); - _desiredWidth = rc.Right - rc.Left; - _desiredHeight = rc.Bottom - rc.Top; - var pt = _windowHost.PointToClient(new Point(rc.Left, rc.Top)); + if (_supportScroll) + { + WinApi.RECT rc; + WinApi.GetWindowRect(_hWnd, out rc); + _desiredWidth = rc.Right - rc.Left; + _desiredHeight = rc.Bottom - rc.Top; + var pt = _windowHost.PointToClient(new Point(rc.Left, rc.Top)); - if (!(pt.Y == 0 && pt.X == 0 && _desiredWidth == _windowHost.Width && _desiredHeight == _windowHost.Height)) + if ( + !(pt.Y == 0 && pt.X == 0 && _desiredWidth == _windowHost.Width && + _desiredHeight == _windowHost.Height)) + { + _windowHost.Width = _desiredWidth; + _windowHost.Height = _desiredHeight; + WinApi.MoveWindow(_hWnd, 0, 0, _desiredWidth, _desiredHeight, true); + } + FixPosition(); + } + else { - _windowHost.Width = _desiredWidth; - _windowHost.Height = _desiredHeight; - WinApi.MoveWindow(_hWnd, 0, 0, _desiredWidth, _desiredHeight, true); + _windowHost.Width = Width; + _windowHost.Height = Height; + WinApi.MoveWindow(_hWnd, 0, 0, Width, Height, true); } - FixPosition(); } } protected override void OnResize(EventArgs e) { - FixPosition(); + FixWindow(); base.OnResize(e); } } diff --git a/src/Windows/Perspex.Designer/InProcDesigner/InProcDesignerView.xaml.cs b/src/Windows/Perspex.Designer/InProcDesigner/InProcDesignerView.xaml.cs index 9e617f1560..c936b2faa8 100644 --- a/src/Windows/Perspex.Designer/InProcDesigner/InProcDesignerView.xaml.cs +++ b/src/Windows/Perspex.Designer/InProcDesigner/InProcDesignerView.xaml.cs @@ -33,7 +33,7 @@ namespace Perspex.Designer.InProcDesigner InitializeComponent(); DataContext = _appModel; _appModel.PropertyChanged += ModelPropertyChanged; - WindowHostControl.Child = _host = new WindowHost(); + WindowHostControl.Child = _host = new WindowHost(true); HandleVisibility(); HandleWindow(); diff --git a/src/Windows/Perspex.Designer/PerspexDesigner.xaml b/src/Windows/Perspex.Designer/PerspexDesigner.xaml index a5a14d170e..96679be499 100644 --- a/src/Windows/Perspex.Designer/PerspexDesigner.xaml +++ b/src/Windows/Perspex.Designer/PerspexDesigner.xaml @@ -6,7 +6,7 @@ mc:Ignorable="d" d:DesignHeight="300" d:DesignWidth="300"> - + diff --git a/src/Windows/Perspex.Designer/PerspexDesigner.xaml.cs b/src/Windows/Perspex.Designer/PerspexDesigner.xaml.cs index 2f944d7251..ad4488d5c6 100644 --- a/src/Windows/Perspex.Designer/PerspexDesigner.xaml.cs +++ b/src/Windows/Perspex.Designer/PerspexDesigner.xaml.cs @@ -73,41 +73,19 @@ namespace Perspex.Designer { if (e.PropertyName == nameof(ProcessHost.WindowHandle)) { - if (NativeContainer.Content != null) + if (NativeContainer.Child == null) { - var wndHost = ((HwndHost) NativeContainer.Content); - NativeContainer.Content = null; - wndHost?.Dispose(); + NativeContainer.Child = new WindowHost(false); } - if (_host.WindowHandle != IntPtr.Zero) { - var host = new NativeWindowHost(_host.WindowHandle); - NativeContainer.Content = host; + var wndHost = ((WindowHost) NativeContainer.Child); + wndHost.SetWindow(_host.WindowHandle); } - } - } - - class NativeWindowHost :HwndHost - { - private readonly IntPtr _hWnd; - - public NativeWindowHost(IntPtr hWnd) - { - _hWnd = hWnd; - } - - protected override HandleRef BuildWindowCore(HandleRef hwndParent) - { - WinApi.SetParent(_hWnd, hwndParent.Handle); - return new HandleRef(this, _hWnd); - } - protected override void DestroyWindowCore(HandleRef hwnd) - { - WinApi.SendMessage(hwnd.Handle, WinApi.WM_CLOSE, IntPtr.Zero, IntPtr.Zero); } } + public void KillProcess() { _host.Kill(); From 2e7f3091c8126dc60aeff8d6be10a6b7075bef47 Mon Sep 17 00:00:00 2001 From: Nikita Tsukanov Date: Wed, 2 Dec 2015 01:38:46 +0300 Subject: [PATCH 076/211] Designer positioning fixes --- src/Windows/Perspex.Designer/AppHost/WindowHost.cs | 6 +++--- .../Perspex.Designer/PerspexDesigner.xaml.cs | 14 ++++++++------ 2 files changed, 11 insertions(+), 9 deletions(-) diff --git a/src/Windows/Perspex.Designer/AppHost/WindowHost.cs b/src/Windows/Perspex.Designer/AppHost/WindowHost.cs index a98850bcf3..e598e4cb6e 100644 --- a/src/Windows/Perspex.Designer/AppHost/WindowHost.cs +++ b/src/Windows/Perspex.Designer/AppHost/WindowHost.cs @@ -25,6 +25,8 @@ namespace Perspex.Designer.AppHost SetStyle(ControlStyles.AllPaintingInWmPaint, true); Controls.Add(_windowHost); _windowHost.Anchor = AnchorStyles.None; + if (!supportScroll) + _windowHost.Visible = false; _timer.Tick += delegate { ReloadSettings(); @@ -98,7 +100,7 @@ namespace Perspex.Designer.AppHost _hWnd = hWnd; if (_hWnd != IntPtr.Zero) { - WinApi.SetParent(hWnd, _windowHost.Handle); + WinApi.SetParent(hWnd, _supportScroll ? _windowHost.Handle : Handle); FixWindow(); } } @@ -127,8 +129,6 @@ namespace Perspex.Designer.AppHost } else { - _windowHost.Width = Width; - _windowHost.Height = Height; WinApi.MoveWindow(_hWnd, 0, 0, Width, Height, true); } } diff --git a/src/Windows/Perspex.Designer/PerspexDesigner.xaml.cs b/src/Windows/Perspex.Designer/PerspexDesigner.xaml.cs index ad4488d5c6..1b394a0f7d 100644 --- a/src/Windows/Perspex.Designer/PerspexDesigner.xaml.cs +++ b/src/Windows/Perspex.Designer/PerspexDesigner.xaml.cs @@ -73,14 +73,16 @@ namespace Perspex.Designer { if (e.PropertyName == nameof(ProcessHost.WindowHandle)) { - if (NativeContainer.Child == null) + if (NativeContainer.Child != null) { - NativeContainer.Child = new WindowHost(false); - } - { - var wndHost = ((WindowHost) NativeContainer.Child); - wndHost.SetWindow(_host.WindowHandle); + var child = NativeContainer.Child; + NativeContainer.Child = null; + child.Dispose(); } + NativeContainer.Child = new WindowHost(false); + var wndHost = ((WindowHost) NativeContainer.Child); + wndHost.SetWindow(_host.WindowHandle); + } } From e4c566738d3b052c885c8ab3ed27cb0b88f47d46 Mon Sep 17 00:00:00 2001 From: Nikita Tsukanov Date: Wed, 2 Dec 2015 17:12:39 +0300 Subject: [PATCH 077/211] Bump NuGet package version --- nuget/build.ps1 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nuget/build.ps1 b/nuget/build.ps1 index a26fe74706..2663012664 100644 --- a/nuget/build.ps1 +++ b/nuget/build.ps1 @@ -1 +1 @@ -.\build-version.ps1 0.1.1-alpha2 \ No newline at end of file +.\build-version.ps1 0.2.0-alpha3 \ No newline at end of file From 9892b1ea1c370c5647a4d98df4c5f9e32c2efd42 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Wed, 2 Dec 2015 16:32:41 +0100 Subject: [PATCH 078/211] Fix crash when clicking menu item. --- .../Presenters/ScrollContentPresenter.cs | 68 ++++++++++--------- src/Perspex.SceneGraph/IVisual.cs | 7 +- src/Perspex.SceneGraph/Visual.cs | 19 ++++-- 3 files changed, 55 insertions(+), 39 deletions(-) diff --git a/src/Perspex.Controls/Presenters/ScrollContentPresenter.cs b/src/Perspex.Controls/Presenters/ScrollContentPresenter.cs index ac82076637..65aa4ae4f9 100644 --- a/src/Perspex.Controls/Presenters/ScrollContentPresenter.cs +++ b/src/Perspex.Controls/Presenters/ScrollContentPresenter.cs @@ -98,48 +98,52 @@ namespace Perspex.Controls.Presenters /// True if the scroll offset was changed; otherwise false. public bool BringDescendentIntoView(IVisual target, Rect targetRect) { - if (Child != null) + if (Child == null) { - var transform = target.TransformToVisual(Child); - var rect = targetRect * transform; - var offset = Offset; - var result = false; + return false; + } - if (rect.Bottom > offset.Y + Viewport.Height) - { - offset = offset.WithY((rect.Bottom - Viewport.Height) + Child.Margin.Top); - result = true; - } + var transform = target.TransformToVisual(Child); - if (rect.Y < offset.Y) - { - offset = offset.WithY(rect.Y); - result = true; - } + if (transform == null) + { + return false; + } - if (rect.Right > offset.X + Viewport.Width) - { - offset = offset.WithX((rect.Right - Viewport.Width) + Child.Margin.Left); - result = true; - } + var rect = targetRect * transform.Value; + var offset = Offset; + var result = false; - if (rect.X < offset.X) - { - offset = offset.WithX(rect.X); - result = true; - } + if (rect.Bottom > offset.Y + Viewport.Height) + { + offset = offset.WithY((rect.Bottom - Viewport.Height) + Child.Margin.Top); + result = true; + } - if (result) - { - Offset = offset; - } + if (rect.Y < offset.Y) + { + offset = offset.WithY(rect.Y); + result = true; + } - return result; + if (rect.Right > offset.X + Viewport.Width) + { + offset = offset.WithX((rect.Right - Viewport.Width) + Child.Margin.Left); + result = true; } - else + + if (rect.X < offset.X) { - return false; + offset = offset.WithX(rect.X); + result = true; + } + + if (result) + { + Offset = offset; } + + return result; } /// diff --git a/src/Perspex.SceneGraph/IVisual.cs b/src/Perspex.SceneGraph/IVisual.cs index 3acb3ac27c..de14703144 100644 --- a/src/Perspex.SceneGraph/IVisual.cs +++ b/src/Perspex.SceneGraph/IVisual.cs @@ -97,7 +97,10 @@ namespace Perspex /// of the specified . /// /// The visual to translate the coordinates to. - /// A containing the transform. - Matrix TransformToVisual(IVisual visual); + /// + /// A containing the transform or null if the visuals don't share a + /// common ancestor. + /// + Matrix? TransformToVisual(IVisual visual); } } diff --git a/src/Perspex.SceneGraph/Visual.cs b/src/Perspex.SceneGraph/Visual.cs index 6779cd7269..597528d0a7 100644 --- a/src/Perspex.SceneGraph/Visual.cs +++ b/src/Perspex.SceneGraph/Visual.cs @@ -300,13 +300,22 @@ namespace Perspex /// of the specified . /// /// The visual to translate the coordinates to. - /// A containing the transform. - public Matrix TransformToVisual(IVisual visual) + /// + /// A containing the transform or null if the visuals don't share a + /// common ancestor. + /// + public Matrix? TransformToVisual(IVisual visual) { var common = this.FindCommonVisualAncestor(visual); - var thisOffset = GetOffsetFrom(common, this); - var thatOffset = GetOffsetFrom(common, visual); - return Matrix.CreateTranslation(-thatOffset) * Matrix.CreateTranslation(thisOffset); + + if (common != null) + { + var thisOffset = GetOffsetFrom(common, this); + var thatOffset = GetOffsetFrom(common, visual); + return Matrix.CreateTranslation(-thatOffset) * Matrix.CreateTranslation(thisOffset); + } + + return null; } /// From 8fb9c5c8e709b69019a9799fc03c3e6dabd97ed7 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Wed, 2 Dec 2015 19:44:14 +0100 Subject: [PATCH 079/211] Fixed problem with binding DataContext. Bindings such as were not working. --- samples/BindingTest/BindingTest.csproj | 5 +++++ samples/BindingTest/MainWindow.paml | 8 ++++++- samples/BindingTest/TestUserControl.paml | 3 +++ samples/BindingTest/TestUserControl.paml.cs | 18 +++++++++++++++ .../ViewModels/MainWindowViewModel.cs | 1 + .../ViewModels/TestUserControlViewModel.cs | 9 ++++++++ .../Perspex.Markup.Xaml/Data/Binding.cs | 22 ++++++++++++------- .../Perspex.Markup/Data/ExpressionObserver.cs | 2 ++ .../Data/BindingTests.cs | 21 ++++++++++++++++++ 9 files changed, 80 insertions(+), 9 deletions(-) create mode 100644 samples/BindingTest/TestUserControl.paml create mode 100644 samples/BindingTest/TestUserControl.paml.cs create mode 100644 samples/BindingTest/ViewModels/TestUserControlViewModel.cs diff --git a/samples/BindingTest/BindingTest.csproj b/samples/BindingTest/BindingTest.csproj index 62914fdc13..0ba9e86d67 100644 --- a/samples/BindingTest/BindingTest.csproj +++ b/samples/BindingTest/BindingTest.csproj @@ -79,6 +79,10 @@ MainWindow.paml + + TestUserControl.paml + + @@ -89,6 +93,7 @@ MSBuild:Compile + diff --git a/samples/BindingTest/MainWindow.paml b/samples/BindingTest/MainWindow.paml index 903d1f1050..c782786e42 100644 --- a/samples/BindingTest/MainWindow.paml +++ b/samples/BindingTest/MainWindow.paml @@ -1,5 +1,6 @@  + xmlns:vm="clr-namespace:BindingTest.ViewModels;assembly=BindingTest" + xmlns:local="clr-namespace:BindingTest;assembly=BindingTest"> @@ -55,5 +56,10 @@ + + + \ No newline at end of file diff --git a/samples/BindingTest/TestUserControl.paml b/samples/BindingTest/TestUserControl.paml new file mode 100644 index 0000000000..2c5609f8c9 --- /dev/null +++ b/samples/BindingTest/TestUserControl.paml @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/samples/BindingTest/TestUserControl.paml.cs b/samples/BindingTest/TestUserControl.paml.cs new file mode 100644 index 0000000000..1cf4342f19 --- /dev/null +++ b/samples/BindingTest/TestUserControl.paml.cs @@ -0,0 +1,18 @@ +using Perspex.Controls; +using Perspex.Markup.Xaml; + +namespace BindingTest +{ + public class TestUserControl : UserControl + { + public TestUserControl() + { + this.InitializeComponent(); + } + + private void InitializeComponent() + { + PerspexXamlLoader.Load(this); + } + } +} diff --git a/samples/BindingTest/ViewModels/MainWindowViewModel.cs b/samples/BindingTest/ViewModels/MainWindowViewModel.cs index 057666d5c4..650f69bc36 100644 --- a/samples/BindingTest/ViewModels/MainWindowViewModel.cs +++ b/samples/BindingTest/ViewModels/MainWindowViewModel.cs @@ -32,6 +32,7 @@ namespace BindingTest.ViewModels public ObservableCollection Items { get; } public ObservableCollection SelectedItems { get; } public ReactiveCommand ShuffleItems { get; } + public TestUserControlViewModel UserControl { get; } = new TestUserControlViewModel(); public string BooleanString { diff --git a/samples/BindingTest/ViewModels/TestUserControlViewModel.cs b/samples/BindingTest/ViewModels/TestUserControlViewModel.cs new file mode 100644 index 0000000000..9e5879e1c1 --- /dev/null +++ b/samples/BindingTest/ViewModels/TestUserControlViewModel.cs @@ -0,0 +1,9 @@ +using ReactiveUI; + +namespace BindingTest.ViewModels +{ + public class TestUserControlViewModel : ReactiveObject + { + public string Content { get; } = "User Control Content"; + } +} diff --git a/src/Markup/Perspex.Markup.Xaml/Data/Binding.cs b/src/Markup/Perspex.Markup.Xaml/Data/Binding.cs index b8ac6c8ac3..b220fb4e46 100644 --- a/src/Markup/Perspex.Markup.Xaml/Data/Binding.cs +++ b/src/Markup/Perspex.Markup.Xaml/Data/Binding.cs @@ -214,22 +214,28 @@ namespace Perspex.Markup.Xaml.Data { Contract.Requires(target != null); - var dataContextHost = targetIsDataContext ? - target.InheritanceParent as IObservablePropertyBag : target; - - if (dataContextHost != null) + if (!targetIsDataContext) { var result = new ExpressionObserver( - () => dataContextHost.GetValue(Control.DataContextProperty), + () => target.GetValue(Control.DataContextProperty), path); - dataContextHost.GetObservable(Control.DataContextProperty).Subscribe(x => + + /// TODO: Instead of doing this, make the ExpressionObserver accept an "update" + /// observable as doing it this way can will cause a leak in Binding as this + /// observable is never unsubscribed. + target.GetObservable(Control.DataContextProperty).Subscribe(x => result.UpdateRoot()); + return result; } else { - throw new InvalidOperationException( - "Cannot bind to DataContext of object with no parent."); + return new ExpressionObserver( + target.GetObservable(Visual.VisualParentProperty) + .OfType() + .Select(x => x.GetObservable(Control.DataContextProperty)) + .Switch(), + path); } } diff --git a/src/Markup/Perspex.Markup/Data/ExpressionObserver.cs b/src/Markup/Perspex.Markup/Data/ExpressionObserver.cs index 81c670ba3e..e8df013d50 100644 --- a/src/Markup/Perspex.Markup/Data/ExpressionObserver.cs +++ b/src/Markup/Perspex.Markup/Data/ExpressionObserver.cs @@ -172,6 +172,8 @@ namespace Perspex.Markup.Data /// /// Causes the root object to be re-read from the root getter. /// + /// TODO: Instead of doing this, make the object accept an "update" observable + /// as doing it this way can cause a leak in Binding. public void UpdateRoot() { if (_count > 0 && _rootGetter != null) diff --git a/tests/Perspex.Markup.Xaml.UnitTests/Data/BindingTests.cs b/tests/Perspex.Markup.Xaml.UnitTests/Data/BindingTests.cs index 02438baefd..95675fef4b 100644 --- a/tests/Perspex.Markup.Xaml.UnitTests/Data/BindingTests.cs +++ b/tests/Perspex.Markup.Xaml.UnitTests/Data/BindingTests.cs @@ -140,6 +140,27 @@ namespace Perspex.Markup.Xaml.UnitTests.Data Assert.Equal("Bar", parent.Child.DataContext); } + [Fact] + public void DataContext_Binding_Should_Track_Parent() + { + var parent = new Decorator + { + DataContext = new { Foo = "foo" }, + }; + + var child = new Control(); + + var binding = new Binding + { + Path = "Foo", + }; + + binding.Bind(child, Control.DataContextProperty); + Assert.Null(child.DataContext); + parent.Child = child; + Assert.Equal("foo", child.DataContext); + } + [Fact] public void Should_Use_DefaultValueConverter_When_No_Converter_Specified() { From 1fddedd65d576a8dee8ce18f00855f846aaab687 Mon Sep 17 00:00:00 2001 From: Nikita Tsukanov Date: Thu, 3 Dec 2015 22:33:52 +0300 Subject: [PATCH 080/211] updated readme --- readme.md | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/readme.md b/readme.md index ee5ec5bab7..c57bb7ba4e 100644 --- a/readme.md +++ b/readme.md @@ -3,12 +3,18 @@ [![Build status](https://ci.appveyor.com/api/projects/status/hubk3k0w9idyibfg/branch/master?svg=true)](https://ci.appveyor.com/project/Perspex/Perspex/branch/master) -A multi-platform .NET UI framework. +A multi-platform .NET UI framework. It can run on Windows, Linux, Mac OS X, iOS and Android. ![](docs/screen.png) +Desktop platforms: + ![](docs/perspex-video.png) +Mobile platforms: + +![](https://i.ytimg.com/vi/NJ9-hnmUbBM/hqdefault.jpg) + ## NuGet Perspex is delivered as a NuGet package. From aa6b9045bcb5a2a0259f14caae8c3133a153b599 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Thu, 3 Dec 2015 21:34:07 +0100 Subject: [PATCH 081/211] Added new leak test. --- tests/Perspex.LeakTests/ControlTests.cs | 39 +++++++++++++++++++++++++ 1 file changed, 39 insertions(+) diff --git a/tests/Perspex.LeakTests/ControlTests.cs b/tests/Perspex.LeakTests/ControlTests.cs index c770fb1c16..3a68e55f5c 100644 --- a/tests/Perspex.LeakTests/ControlTests.cs +++ b/tests/Perspex.LeakTests/ControlTests.cs @@ -6,6 +6,7 @@ using System.Collections.Generic; using System.Linq; using JetBrains.dotMemoryUnit; using Perspex.Controls; +using Perspex.Controls.Primitives; using Perspex.Controls.Templates; using Perspex.VisualTree; using Xunit; @@ -82,6 +83,36 @@ namespace Perspex.LeakTests Assert.Equal(0, memory.GetObjects(where => where.Type.Is()).ObjectsCount)); } + [Fact] + public void Templated_Child_Is_Freed_When_Template_Cleared() + { + Func run = () => + { + var window = new Window + { + Content = new TestTemplatedControl() + }; + + // Do a layout and make sure that the control gets added to visual tree and its + // template applied. + window.LayoutManager.ExecuteLayoutPass(); + Assert.IsType(window.Presenter.Child); + Assert.IsType(window.Presenter.Child.GetVisualChildren().SingleOrDefault()); + + // Clear the template and ensure the control template gets removed + ((TestTemplatedControl)window.Content).Template = null; + window.LayoutManager.ExecuteLayoutPass(); + Assert.Equal(0, window.Presenter.Child.GetVisualChildren().Count()); + + return window; + }; + + var result = run(); + + dotMemory.Check(memory => + Assert.Equal(0, memory.GetObjects(where => where.Type.Is()).ObjectsCount)); + } + [Fact] public void ScrollViewer_With_Content_Is_Freed() { @@ -266,6 +297,14 @@ namespace Perspex.LeakTests Assert.Equal(0, memory.GetObjects(where => where.Type.Is()).ObjectsCount)); } + private class TestTemplatedControl : TemplatedControl + { + public TestTemplatedControl() + { + Template = new FuncControlTemplate(_ => new Canvas()); + } + } + private class Node { public string Name { get; set; } From 21bd841258d680bed335d0236611226be9c41949 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Thu, 3 Dec 2015 21:57:42 +0100 Subject: [PATCH 082/211] Made templated child leak test fail. --- tests/Perspex.LeakTests/ControlTests.cs | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/tests/Perspex.LeakTests/ControlTests.cs b/tests/Perspex.LeakTests/ControlTests.cs index 3a68e55f5c..76351ac635 100644 --- a/tests/Perspex.LeakTests/ControlTests.cs +++ b/tests/Perspex.LeakTests/ControlTests.cs @@ -299,9 +299,16 @@ namespace Perspex.LeakTests private class TestTemplatedControl : TemplatedControl { + public static readonly PerspexProperty IsCanvasVisibleProperty = + PerspexProperty.Register("IsCanvasVisible"); + public TestTemplatedControl() { - Template = new FuncControlTemplate(_ => new Canvas()); + Template = new FuncControlTemplate(parent => + new Canvas + { + [~IsVisibleProperty] = parent[~IsCanvasVisibleProperty] + }); } } From e402eb8cdd3f7748e52005f395f0ecb8a3c73bdc Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Thu, 3 Dec 2015 22:36:27 +0100 Subject: [PATCH 083/211] Make template bindings complete on Template change. This fixes a leak - needs to be done in XAML as well though. --- src/Perspex.Base/BindingDescriptor.cs | 14 +++++++++++++- src/Perspex.Base/PerspexObject.cs | 19 ++++++++++++------- .../Primitives/TemplatedControl.cs | 17 +++++++++++++++++ 3 files changed, 42 insertions(+), 8 deletions(-) diff --git a/src/Perspex.Base/BindingDescriptor.cs b/src/Perspex.Base/BindingDescriptor.cs index 4ee4966a98..c0c8f3406f 100644 --- a/src/Perspex.Base/BindingDescriptor.cs +++ b/src/Perspex.Base/BindingDescriptor.cs @@ -78,6 +78,18 @@ namespace Perspex set; } + /// + /// Gets or sets the source observable. + /// + /// + /// If null, then . will be used. + /// + public IObservable SourceObservable + { + get; + set; + } + /// /// Gets a description of the binding. /// @@ -128,7 +140,7 @@ namespace Perspex /// protected override IDisposable SubscribeCore(IObserver observer) { - return Source.GetObservable(Property).Subscribe(observer); + return (SourceObservable ?? Source.GetObservable(Property)).Subscribe(observer); } } } diff --git a/src/Perspex.Base/PerspexObject.cs b/src/Perspex.Base/PerspexObject.cs index 70dff41dda..25e0ff48be 100644 --- a/src/Perspex.Base/PerspexObject.cs +++ b/src/Perspex.Base/PerspexObject.cs @@ -163,13 +163,7 @@ namespace Perspex { get { - return new BindingDescriptor - { - Mode = binding.Mode, - Priority = binding.Priority, - Property = binding.Property, - Source = this, - }; + return CreateBindingDescriptor(binding); } set @@ -203,6 +197,17 @@ namespace Perspex } } + protected virtual BindingDescriptor CreateBindingDescriptor(BindingDescriptor source) + { + return new BindingDescriptor + { + Mode = source.Mode, + Priority = source.Priority, + Property = source.Property, + Source = this, + }; + } + public bool CheckAccess() => Dispatcher.UIThread.CheckAccess(); public void VerifyAccess() => Dispatcher.UIThread.VerifyAccess(); diff --git a/src/Perspex.Controls/Primitives/TemplatedControl.cs b/src/Perspex.Controls/Primitives/TemplatedControl.cs index 92e1c4af88..2955c733b8 100644 --- a/src/Perspex.Controls/Primitives/TemplatedControl.cs +++ b/src/Perspex.Controls/Primitives/TemplatedControl.cs @@ -3,6 +3,7 @@ using System; using System.Linq; +using System.Reactive.Linq; using Perspex.Controls.Presenters; using Perspex.Controls.Templates; using Perspex.Media; @@ -211,6 +212,22 @@ namespace Perspex.Controls.Primitives } } + protected sealed override BindingDescriptor CreateBindingDescriptor(BindingDescriptor source) + { + var result = base.CreateBindingDescriptor(source); + + // If the binding is a template binding, then complete when the Template changes. + if (source.Priority == BindingPriority.TemplatedParent) + { + var templateChanged = GetObservable(TemplateProperty).Skip(1); + + result.SourceObservable = result.Source.GetObservable(result.Property) + .TakeUntil(templateChanged); + } + + return result; + } + /// /// Called when the control's template is applied. /// From 318be5196d31922611b5ec9ca02a345efd64f8df Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Fri, 4 Dec 2015 00:24:56 +0100 Subject: [PATCH 084/211] Added some ExpressionObserver lifetime tests. --- .../Perspex.Markup/Data/ExpressionObserver.cs | 5 ++ .../Data/ExpressionObserverTests_Lifetime.cs | 70 +++++++++++++++++++ .../Data/ExpressionObserverTests_Property.cs | 2 +- .../Perspex.Markup.UnitTests.csproj | 1 + 4 files changed, 77 insertions(+), 1 deletion(-) create mode 100644 tests/Perspex.Markup.UnitTests/Data/ExpressionObserverTests_Lifetime.cs diff --git a/src/Markup/Perspex.Markup/Data/ExpressionObserver.cs b/src/Markup/Perspex.Markup/Data/ExpressionObserver.cs index e8df013d50..791a788df5 100644 --- a/src/Markup/Perspex.Markup/Data/ExpressionObserver.cs +++ b/src/Markup/Perspex.Markup/Data/ExpressionObserver.cs @@ -156,6 +156,11 @@ namespace Perspex.Markup.Data /// string IDescription.Description => Expression; + /// + /// Gets the root expression node. Used for testing. + /// + internal ExpressionNode Node => _node; + /// /// Gets the leaf node. /// diff --git a/tests/Perspex.Markup.UnitTests/Data/ExpressionObserverTests_Lifetime.cs b/tests/Perspex.Markup.UnitTests/Data/ExpressionObserverTests_Lifetime.cs new file mode 100644 index 0000000000..22373193c3 --- /dev/null +++ b/tests/Perspex.Markup.UnitTests/Data/ExpressionObserverTests_Lifetime.cs @@ -0,0 +1,70 @@ +// 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.Collections.Generic; +using System.Reactive; +using System.Reactive.Subjects; +using Microsoft.Reactive.Testing; +using Perspex.Markup.Data; +using Xunit; + +namespace Perspex.Markup.UnitTests.Data +{ + public class ExpressionObserverTests_Lifetime + { + [Fact(Skip = "Not working yet")] + public void Should_Complete_When_Source_Observable_Completes() + { + var source = new BehaviorSubject(1); + var target = new ExpressionObserver(source, "Foo"); + var completed = false; + + target.Subscribe(_ => { }, () => completed = true); + source.OnCompleted(); + + Assert.True(completed); + } + + [Fact] + public void Should_Unsubscribe_From_Source_Observable() + { + var scheduler = new TestScheduler(); + var source = scheduler.CreateColdObservable( + OnNext(1, new { Foo = "foo" })); + var target = new ExpressionObserver(source, "Foo"); + var result = new List(); + + using (target.Subscribe(x => result.Add(x))) + using (target.Subscribe(_ => { })) + { + scheduler.Start(); + } + + Assert.Equal(new[] { PerspexProperty.UnsetValue, "foo" }, result); + Assert.Equal(1, source.Subscriptions.Count); + Assert.NotEqual(Subscription.Infinite, source.Subscriptions[0].Unsubscribe); + } + + [Fact] + public void Should_Set_Node_Target_To_Null_On_Unsubscribe() + { + var target = new ExpressionObserver(new { Foo = "foo" }, "Foo"); + var result = new List(); + + using (target.Subscribe(x => result.Add(x))) + using (target.Subscribe(_ => { })) + { + Assert.NotNull(target.Node.Target); + } + + Assert.Equal(new[] { "foo" }, result); + Assert.Null(target.Node.Target); + } + + private Recorded> OnNext(long time, object value) + { + return new Recorded>(time, Notification.CreateOnNext(value)); + } + } +} diff --git a/tests/Perspex.Markup.UnitTests/Data/ExpressionObserverTests_Property.cs b/tests/Perspex.Markup.UnitTests/Data/ExpressionObserverTests_Property.cs index fd1583106c..0d917b15b4 100644 --- a/tests/Perspex.Markup.UnitTests/Data/ExpressionObserverTests_Property.cs +++ b/tests/Perspex.Markup.UnitTests/Data/ExpressionObserverTests_Property.cs @@ -355,7 +355,7 @@ namespace Perspex.Markup.UnitTests.Data { } - public Recorded> OnNext(long time, object value) + private Recorded> OnNext(long time, object value) { return new Recorded>(time, Notification.CreateOnNext(value)); } diff --git a/tests/Perspex.Markup.UnitTests/Perspex.Markup.UnitTests.csproj b/tests/Perspex.Markup.UnitTests/Perspex.Markup.UnitTests.csproj index ad9843a103..639142b00e 100644 --- a/tests/Perspex.Markup.UnitTests/Perspex.Markup.UnitTests.csproj +++ b/tests/Perspex.Markup.UnitTests/Perspex.Markup.UnitTests.csproj @@ -84,6 +84,7 @@ + From 5f0a6c9d72bec413f1d0b3964632deadf77e604a Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Fri, 4 Dec 2015 00:54:06 +0100 Subject: [PATCH 085/211] Use observable to signal update... ...in ExpressionObserver, instead of calling UpdateRoot. This should avoid a leak. --- .../Perspex.Markup.Xaml/Data/Binding.cs | 32 +++++----- .../Perspex.Markup/Data/ExpressionObserver.cs | 62 +++++++++++-------- .../Data/ExpressionObserverTests_Lifetime.cs | 19 ++++++ .../Data/ExpressionObserverTests_Property.cs | 13 ++-- .../Perspex.Markup.UnitTests.csproj | 2 +- 5 files changed, 77 insertions(+), 51 deletions(-) diff --git a/src/Markup/Perspex.Markup.Xaml/Data/Binding.cs b/src/Markup/Perspex.Markup.Xaml/Data/Binding.cs index b220fb4e46..20cab81984 100644 --- a/src/Markup/Perspex.Markup.Xaml/Data/Binding.cs +++ b/src/Markup/Perspex.Markup.Xaml/Data/Binding.cs @@ -2,6 +2,7 @@ // Licensed under the MIT license. See licence.md file in the project root for full license information. using System; +using System.Reactive; using System.Reactive.Linq; using System.Reactive.Subjects; using Perspex.Controls; @@ -216,15 +217,13 @@ namespace Perspex.Markup.Xaml.Data if (!targetIsDataContext) { + var update = target.GetObservable(Control.DataContextProperty) + .Skip(1) + .Select(_ => Unit.Default); var result = new ExpressionObserver( () => target.GetValue(Control.DataContextProperty), - path); - - /// TODO: Instead of doing this, make the ExpressionObserver accept an "update" - /// observable as doing it this way can will cause a leak in Binding as this - /// observable is never unsubscribed. - target.GetObservable(Control.DataContextProperty).Subscribe(x => - result.UpdateRoot()); + path, + update); return result; } @@ -245,19 +244,16 @@ namespace Perspex.Markup.Xaml.Data { Contract.Requires(target != null); + var update = target.GetObservable(Control.TemplatedParentProperty) + .Skip(1) + .Where(x => x != null) + .Take(1) + .Select(_ => Unit.Default); + var result = new ExpressionObserver( () => target.GetValue(Control.TemplatedParentProperty), - path); - - if (target.GetValue(Control.TemplatedParentProperty) == null) - { - // TemplatedParent should only be set once, so only listen for the first non-null - // value. - target.GetObservable(Control.TemplatedParentProperty) - .Where(x => x != null) - .Take(1) - .Subscribe(x => result.UpdateRoot()); - } + path, + update); return result; } diff --git a/src/Markup/Perspex.Markup/Data/ExpressionObserver.cs b/src/Markup/Perspex.Markup/Data/ExpressionObserver.cs index 791a788df5..d3c2146bf2 100644 --- a/src/Markup/Perspex.Markup/Data/ExpressionObserver.cs +++ b/src/Markup/Perspex.Markup/Data/ExpressionObserver.cs @@ -5,6 +5,7 @@ using System; using System.Collections.Generic; using System.Reactive; using System.Reactive.Disposables; +using System.Reactive.Linq; using System.Reactive.Subjects; using Perspex.Markup.Data.Plugins; @@ -29,10 +30,11 @@ namespace Perspex.Markup.Data private readonly object _root; private readonly Func _rootGetter; private readonly IObservable _rootObservable; + private readonly IObservable _update; private IDisposable _rootObserverSubscription; + private IDisposable _updateSubscription; private int _count; private readonly ExpressionNode _node; - private ISubject _empty; /// /// Initializes a new instance of the class. @@ -78,12 +80,18 @@ namespace Perspex.Markup.Data /// /// A function which gets the root object. /// The expression. - public ExpressionObserver(Func rootGetter, string expression) + /// An observable which triggers a re-read of the getter. + public ExpressionObserver( + Func rootGetter, + string expression, + IObservable update) { Contract.Requires(rootGetter != null); Contract.Requires(expression != null); + Contract.Requires(update != null); _rootGetter = rootGetter; + _update = update; if (!string.IsNullOrWhiteSpace(expression)) { @@ -104,7 +112,11 @@ namespace Perspex.Markup.Data public bool SetValue(object value) { IncrementCount(); - UpdateRoot(); + + if (_rootGetter != null && _node != null) + { + _node.Target = _rootGetter(); + } try { @@ -174,26 +186,6 @@ namespace Perspex.Markup.Data } } - /// - /// Causes the root object to be re-read from the root getter. - /// - /// TODO: Instead of doing this, make the object accept an "update" observable - /// as doing it this way can cause a leak in Binding. - public void UpdateRoot() - { - if (_count > 0 && _rootGetter != null) - { - if (_node != null) - { - _node.Target = _rootGetter(); - } - else - { - _empty?.OnNext(_rootGetter()); - } - } - } - /// protected override IDisposable SubscribeCore(IObserver observer) { @@ -211,12 +203,17 @@ namespace Perspex.Markup.Data } else { - if (_empty == null) + if (_update == null) { - _empty = new BehaviorSubject(_rootGetter()); + return Observable.Never().StartWith(_root).Subscribe(observer); + } + else + { + return _update + .Select(_ => _rootGetter()) + .StartWith(_rootGetter()) + .Subscribe(observer); } - - return _empty.Subscribe(observer); } } @@ -227,6 +224,11 @@ namespace Perspex.Markup.Data if (_rootGetter != null) { _node.Target = _rootGetter(); + + if (_update != null) + { + _updateSubscription = _update.Subscribe(x => _node.Target = _rootGetter()); + } } else if (_rootObservable != null) { @@ -249,6 +251,12 @@ namespace Perspex.Markup.Data _rootObserverSubscription = null; } + if (_updateSubscription != null) + { + _updateSubscription.Dispose(); + _updateSubscription = null; + } + _node.Target = null; } } diff --git a/tests/Perspex.Markup.UnitTests/Data/ExpressionObserverTests_Lifetime.cs b/tests/Perspex.Markup.UnitTests/Data/ExpressionObserverTests_Lifetime.cs index 22373193c3..cfbc4bc29f 100644 --- a/tests/Perspex.Markup.UnitTests/Data/ExpressionObserverTests_Lifetime.cs +++ b/tests/Perspex.Markup.UnitTests/Data/ExpressionObserverTests_Lifetime.cs @@ -46,6 +46,25 @@ namespace Perspex.Markup.UnitTests.Data Assert.NotEqual(Subscription.Infinite, source.Subscriptions[0].Unsubscribe); } + [Fact] + public void Should_Unsubscribe_From_Update_Observable() + { + var scheduler = new TestScheduler(); + var update = scheduler.CreateColdObservable(); + var target = new ExpressionObserver(() => new { Foo = "foo" }, "Foo", update); + var result = new List(); + + using (target.Subscribe(x => result.Add(x))) + using (target.Subscribe(_ => { })) + { + scheduler.Start(); + } + + Assert.Equal(new[] { "foo" }, result); + Assert.Equal(1, update.Subscriptions.Count); + Assert.NotEqual(Subscription.Infinite, update.Subscriptions[0].Unsubscribe); + } + [Fact] public void Should_Set_Node_Target_To_Null_On_Unsubscribe() { diff --git a/tests/Perspex.Markup.UnitTests/Data/ExpressionObserverTests_Property.cs b/tests/Perspex.Markup.UnitTests/Data/ExpressionObserverTests_Property.cs index 0d917b15b4..fe9336ebe6 100644 --- a/tests/Perspex.Markup.UnitTests/Data/ExpressionObserverTests_Property.cs +++ b/tests/Perspex.Markup.UnitTests/Data/ExpressionObserverTests_Property.cs @@ -5,6 +5,7 @@ using System; using System.Collections.Generic; using System.Reactive; using System.Reactive.Linq; +using System.Reactive.Subjects; using Microsoft.Reactive.Testing; using Perspex.Markup.Data; using Xunit; @@ -193,13 +194,14 @@ namespace Perspex.Markup.UnitTests.Data public void Empty_Expression_Should_Track_Root() { var data = new Class1 { Foo = "foo" }; - var target = new ExpressionObserver(() => data.Foo, ""); + var update = new Subject(); + var target = new ExpressionObserver(() => data.Foo, "", update); var result = new List(); target.Subscribe(x => result.Add(x)); data.Foo = "bar"; - target.UpdateRoot(); + update.OnNext(Unit.Default); Assert.Equal(new[] { "foo", "bar" }, result); } @@ -286,14 +288,15 @@ namespace Perspex.Markup.UnitTests.Data var first = new Class1 { Foo = "foo" }; var second = new Class1 { Foo = "bar" }; var root = first; - var target = new ExpressionObserver(() => root, "Foo"); + var update = new Subject(); + var target = new ExpressionObserver(() => root, "Foo", update); var result = new List(); var sub = target.Subscribe(x => result.Add(x)); root = second; - target.UpdateRoot(); + update.OnNext(Unit.Default); root = null; - target.UpdateRoot(); + update.OnNext(Unit.Default); Assert.Equal(new[] { "foo", "bar", PerspexProperty.UnsetValue }, result); diff --git a/tests/Perspex.Markup.UnitTests/Perspex.Markup.UnitTests.csproj b/tests/Perspex.Markup.UnitTests/Perspex.Markup.UnitTests.csproj index 639142b00e..161e79f6f6 100644 --- a/tests/Perspex.Markup.UnitTests/Perspex.Markup.UnitTests.csproj +++ b/tests/Perspex.Markup.UnitTests/Perspex.Markup.UnitTests.csproj @@ -84,7 +84,7 @@ - + From 84fd1936b35030e409650d054d2f8ac2b34f91d5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Manuel=20Nieto?= Date: Sat, 5 Dec 2015 12:11:52 +0100 Subject: [PATCH 086/211] Fixed link in readme This is de link to the video for Mobile Devices :) https://www.youtube.com/watch?v=NJ9-hnmUbBM --- readme.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/readme.md b/readme.md index c57bb7ba4e..6eec66c9ae 100644 --- a/readme.md +++ b/readme.md @@ -13,7 +13,7 @@ Desktop platforms: Mobile platforms: -![](https://i.ytimg.com/vi/NJ9-hnmUbBM/hqdefault.jpg) +![](https://i.ytimg.com/vi/NJ9-hnmUbBM/hqdefault.jpg) ## NuGet From 5ac1f7c0c5dbfc511e822a61dd08e9791fb123d0 Mon Sep 17 00:00:00 2001 From: donandren Date: Sat, 5 Dec 2015 15:46:44 +0200 Subject: [PATCH 087/211] ios keyboard support --- src/iOS/Perspex.iOS/Perspex.iOS.csproj | 1 + src/iOS/Perspex.iOS/PerspexView.cs | 16 ++ .../Specific/KeyboardEventsHelper.cs | 147 ++++++++++++++++++ 3 files changed, 164 insertions(+) create mode 100644 src/iOS/Perspex.iOS/Specific/KeyboardEventsHelper.cs diff --git a/src/iOS/Perspex.iOS/Perspex.iOS.csproj b/src/iOS/Perspex.iOS/Perspex.iOS.csproj index 1a5866908b..0d670ac5ed 100644 --- a/src/iOS/Perspex.iOS/Perspex.iOS.csproj +++ b/src/iOS/Perspex.iOS/Perspex.iOS.csproj @@ -42,6 +42,7 @@ + diff --git a/src/iOS/Perspex.iOS/PerspexView.cs b/src/iOS/Perspex.iOS/PerspexView.cs index 1bea986c98..43d941fba3 100644 --- a/src/iOS/Perspex.iOS/PerspexView.cs +++ b/src/iOS/Perspex.iOS/PerspexView.cs @@ -14,26 +14,41 @@ using Perspex.Media; using Perspex.Platform; using Perspex.Skia.iOS; using UIKit; +using Perspex.iOS.Specific; +using ObjCRuntime; namespace Perspex.iOS { + [Adopts("UIKeyInput")] class PerspexView : SkiaView, IWindowImpl { private readonly UIWindow _window; private readonly UIViewController _controller; private IInputRoot _inputRoot; + private readonly KeyboardEventsHelper _keyboardHelper; public PerspexView(UIWindow window, UIViewController controller) : base(onFrame => PlatformThreadingInterface.Instance.Render = onFrame) { if (controller == null) throw new ArgumentNullException(nameof(controller)); _window = window; _controller = controller; + _keyboardHelper = new KeyboardEventsHelper(this); AutoresizingMask = UIViewAutoresizing.All; AutoFit(); UIApplication.Notifications.ObserveDidChangeStatusBarOrientation(delegate { AutoFit(); }); UIApplication.Notifications.ObserveDidChangeStatusBarFrame(delegate { AutoFit(); }); } + [Export("hasText")] + bool HasText => _keyboardHelper.HasText(); + + [Export("insertText:")] + void InsertText(string text) => _keyboardHelper.InsertText(text); + + [Export("deleteBackward")] + void DeleteBackward() => _keyboardHelper.DeleteBackward(); + + public override bool CanBecomeFirstResponder => _keyboardHelper.CanBecomeFirstResponder(); void AutoFit() { @@ -89,6 +104,7 @@ namespace Perspex.iOS public void Show() { + _keyboardHelper.ActivateAutoShowKeybord(); } public Size MaxClientSize => Bounds.Size.ToPerspex(); diff --git a/src/iOS/Perspex.iOS/Specific/KeyboardEventsHelper.cs b/src/iOS/Perspex.iOS/Specific/KeyboardEventsHelper.cs new file mode 100644 index 0000000000..872f9cd37d --- /dev/null +++ b/src/iOS/Perspex.iOS/Specific/KeyboardEventsHelper.cs @@ -0,0 +1,147 @@ +using ObjCRuntime; +using Perspex.Controls; +using Perspex.Input; +using Perspex.Input.Raw; +using Perspex.Platform; +using System; +using System.ComponentModel; +using System.Linq; +using UIKit; + +namespace Perspex.iOS.Specific +{ + /// + /// In order to have properly handle of keyboard event in iOS View should already made some things in the View: + /// 1. Adopt the UIKeyInput protocol - add [Adopts("UIKeyInput")] to your view class + /// 2. Implement all the methods required by UIKeyInput: + /// 2.1 Implement HasText + /// example: + /// [Export("hasText")] + /// bool HasText => _keyboardHelper.HasText() + /// 2.2 Implement InsertText + /// example: + /// [Export("insertText:")] + /// void InsertText(string text) => _keyboardHelper.InsertText(text); + /// 2.3 Implement InsertText + /// example: + /// [Export("deleteBackward")] + /// void DeleteBackward() => _keyboardHelper.DeleteBackward(); + /// 3.Let iOS know that this can become a first responder: + /// public override bool CanBecomeFirstResponder => _keyboardHelper.CanBecomeFirstResponder(); + /// or + /// public override bool CanBecomeFirstResponder { get { return true; } } + /// + /// 4. To show keyboard: + /// view.BecomeFirstResponder(); + /// 5. To hide keyboard + /// view.ResignFirstResponder(); + /// + /// View that needs keyboard events and show/hide keyboard + internal class KeyboardEventsHelper where TView : UIView, IWindowImpl + { + private TView _view; + private IInputElement _lastFocusedElement; + + public KeyboardEventsHelper(TView view) + { + _view = view; + + var uiKeyInputAttribute = view.GetType().GetCustomAttributes(typeof(AdoptsAttribute), true).OfType().Where(a => a.ProtocolType == "UIKeyInput").FirstOrDefault(); + + if (uiKeyInputAttribute == null) throw new NotSupportedException($"View class {typeof(TView).Name} should have class attribute - [Adopts(\"UIKeyInput\")] in order to access keyboard events!"); + + HandleEvents = true; + } + + /// + /// HandleEvents in order to suspend keyboard notifications or resume it + /// + public bool HandleEvents { get; set; } + + public bool HasText() => false; + + public bool CanBecomeFirstResponder() => true; + + public void DeleteBackward() + { + HandleKey(Key.Back, RawKeyEventType.KeyDown); + HandleKey(Key.Back, RawKeyEventType.KeyUp); + } + + public void InsertText(string text) + { + var rawTextEvent = new RawTextInputEventArgs(KeyboardDevice.Instance, (uint)DateTime.Now.Ticks, text); + _view.Input(rawTextEvent); + } + + private void HandleKey(Key key, RawKeyEventType type) + { + var rawKeyEvent = new RawKeyEventArgs(KeyboardDevice.Instance, (uint)DateTime.Now.Ticks, type, key, InputModifiers.None); + _view.Input(rawKeyEvent); + } + + //currently not found a way to get InputModifiers state + //private static InputModifiers GetModifierKeys(object e) + //{ + // var im = InputModifiers.None; + // //if (IsCtrlPressed) rv |= InputModifiers.Control; + // //if (IsShiftPressed) rv |= InputModifiers.Shift; + + // return im; + //} + + private bool NeedsKeyboard(IInputElement element) + { + //may be some other elements + return element is TextBox; + } + + private void TryShowHideKeyboard(IInputElement element, bool value) + { + if (value) + { + _view.BecomeFirstResponder(); + } + else + { + _view.ResignFirstResponder(); + } + } + + public void UpdateKeyboardState(IInputElement element) + { + var focusedElement = element; + bool oldValue = NeedsKeyboard(_lastFocusedElement); + bool newValue = NeedsKeyboard(focusedElement); + + if (newValue != oldValue || newValue) + { + TryShowHideKeyboard(focusedElement, newValue); + } + + _lastFocusedElement = element; + } + + public void ActivateAutoShowKeybord() + { + var kbDevice = (KeyboardDevice.Instance as INotifyPropertyChanged); + + //just in case we've called more than once the method + kbDevice.PropertyChanged -= KeyboardDevice_PropertyChanged; + kbDevice.PropertyChanged += KeyboardDevice_PropertyChanged; + } + + private void KeyboardDevice_PropertyChanged(object sender, PropertyChangedEventArgs e) + { + if (e.PropertyName == nameof(KeyboardDevice.FocusedElement)) + { + UpdateKeyboardState(KeyboardDevice.Instance.FocusedElement); + } + } + + public void Dispose() + { + HandleEvents = false; + } + } +} \ No newline at end of file From fcf79c45eba904a966c9abd11ebaaf37bf74ce4d Mon Sep 17 00:00:00 2001 From: donandren Date: Sat, 5 Dec 2015 17:36:17 +0200 Subject: [PATCH 088/211] enhanced emulation of mouse scroll event from touch in iOS --- src/iOS/Perspex.iOS/PerspexView.cs | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/src/iOS/Perspex.iOS/PerspexView.cs b/src/iOS/Perspex.iOS/PerspexView.cs index 43d941fba3..4adfb98790 100644 --- a/src/iOS/Perspex.iOS/PerspexView.cs +++ b/src/iOS/Perspex.iOS/PerspexView.cs @@ -168,8 +168,14 @@ namespace Perspex.iOS RawMouseEventType.Move, location, InputModifiers.LeftMouseButton)); else { - Input?.Invoke(new RawMouseWheelEventArgs(PerspexAppDelegate.MouseDevice, (uint) touch.Timestamp, - _inputRoot, location, location - _touchLastPoint, InputModifiers.LeftMouseButton)); + double x = location.X - _touchLastPoint.X; + double y = location.Y - _touchLastPoint.Y; + double correction = 0.02; + var scale = PerspexLocator.Current.GetService().RenderScalingFactor; + scale = 1; + + Input?.Invoke(new RawMouseWheelEventArgs(PerspexAppDelegate.MouseDevice, (uint)touch.Timestamp, + _inputRoot, location, new Vector(x * correction / scale, y * correction / scale), InputModifiers.LeftMouseButton)); } _touchLastPoint = location; } From 2248a4b5a8d52def240b9485076c93215550266d Mon Sep 17 00:00:00 2001 From: donandren Date: Sat, 5 Dec 2015 21:09:53 +0200 Subject: [PATCH 089/211] no need for layout scale to be taken into account because touch events are already scaled --- src/iOS/Perspex.iOS/PerspexView.cs | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/src/iOS/Perspex.iOS/PerspexView.cs b/src/iOS/Perspex.iOS/PerspexView.cs index 4adfb98790..5a87b38e96 100644 --- a/src/iOS/Perspex.iOS/PerspexView.cs +++ b/src/iOS/Perspex.iOS/PerspexView.cs @@ -168,14 +168,11 @@ namespace Perspex.iOS RawMouseEventType.Move, location, InputModifiers.LeftMouseButton)); else { - double x = location.X - _touchLastPoint.X; - double y = location.Y - _touchLastPoint.Y; + //magic number based on test - correction of 0.02 is working perfect double correction = 0.02; - var scale = PerspexLocator.Current.GetService().RenderScalingFactor; - scale = 1; Input?.Invoke(new RawMouseWheelEventArgs(PerspexAppDelegate.MouseDevice, (uint)touch.Timestamp, - _inputRoot, location, new Vector(x * correction / scale, y * correction / scale), InputModifiers.LeftMouseButton)); + _inputRoot, location, (location - _touchLastPoint)* correction, InputModifiers.LeftMouseButton)); } _touchLastPoint = location; } From 4d6572b74e14025c5d0bdf0ba1f6d1f437f76ada Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Mon, 7 Dec 2015 20:45:36 +0100 Subject: [PATCH 090/211] Added StyleActivator leak test. Trying to locate source of StyleActivator leaks - this test doesn't find it. --- src/Perspex.Styling/Styling/Style.cs | 14 ++-- .../Perspex.LeakTests.csproj | 1 + tests/Perspex.LeakTests/StyleTests.cs | 69 +++++++++++++++++++ 3 files changed, 77 insertions(+), 7 deletions(-) create mode 100644 tests/Perspex.LeakTests/StyleTests.cs diff --git a/src/Perspex.Styling/Styling/Style.cs b/src/Perspex.Styling/Styling/Style.cs index 112bc817a0..5c5f97417d 100644 --- a/src/Perspex.Styling/Styling/Style.cs +++ b/src/Perspex.Styling/Styling/Style.cs @@ -60,13 +60,13 @@ namespace Perspex.Styling var activator = match.ObservableResult ?? Observable.Never().StartWith(true); - if (visual != null) - { - var detached = Observable.FromEventPattern( - x => visual.DetachedFromVisualTree += x, - x => visual.DetachedFromVisualTree -= x); - activator = activator.TakeUntil(detached); - } + //if (visual != null) + //{ + // var detached = Observable.FromEventPattern( + // x => visual.DetachedFromVisualTree += x, + // x => visual.DetachedFromVisualTree -= x); + // activator = activator.TakeUntil(detached); + //} foreach (var setter in Setters) { diff --git a/tests/Perspex.LeakTests/Perspex.LeakTests.csproj b/tests/Perspex.LeakTests/Perspex.LeakTests.csproj index b2d67554c3..ba2450f8f9 100644 --- a/tests/Perspex.LeakTests/Perspex.LeakTests.csproj +++ b/tests/Perspex.LeakTests/Perspex.LeakTests.csproj @@ -88,6 +88,7 @@ + diff --git a/tests/Perspex.LeakTests/StyleTests.cs b/tests/Perspex.LeakTests/StyleTests.cs new file mode 100644 index 0000000000..82f8ebf1d5 --- /dev/null +++ b/tests/Perspex.LeakTests/StyleTests.cs @@ -0,0 +1,69 @@ +// 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.Collections.Generic; +using System.Linq; +using JetBrains.dotMemoryUnit; +using Perspex.Controls; +using Perspex.Controls.Primitives; +using Perspex.Controls.Templates; +using Perspex.Styling; +using Perspex.VisualTree; +using Xunit; +using Xunit.Abstractions; + +namespace Perspex.LeakTests +{ + [DotMemoryUnit(FailIfRunWithoutSupport = false)] + public class StyleTests + { + public StyleTests(ITestOutputHelper atr) + { + TestApp.Initialize(); + DotMemoryUnitTestOutput.SetOutputMethod(atr.WriteLine); + } + + [Fact] + public void StyleActivator_Should_Be_Released() + { + Func run = () => + { + var window = new Window + { + Styles = new Styles + { + new Style(x => x.OfType().Class("foo")) + { + Setters = new[] + { + new Setter(Canvas.WidthProperty, 100), + } + } + }, + Content = new Canvas + { + Classes = new Classes("foo"), + } + }; + + // Do a layout and make sure that styled Canvas gets added to visual tree. + window.LayoutManager.ExecuteLayoutPass(); + Assert.IsType(window.Presenter.Child); + Assert.Equal(100, (window.Presenter.Child).Width); + + // Clear the content and ensure the Canvas is removed. + window.Content = null; + window.LayoutManager.ExecuteLayoutPass(); + Assert.Null(window.Presenter.Child); + + return window; + }; + + var result = run(); + + dotMemory.Check(memory => + Assert.Equal(0, memory.GetObjects(where => where.Type.Is()).ObjectsCount)); + } + } +} From da39751faf43419d694a32a9bad54942cb04487c Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Mon, 7 Dec 2015 21:02:59 +0100 Subject: [PATCH 091/211] Undo commented out code. --- src/Perspex.Styling/Styling/Style.cs | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/Perspex.Styling/Styling/Style.cs b/src/Perspex.Styling/Styling/Style.cs index 5c5f97417d..112bc817a0 100644 --- a/src/Perspex.Styling/Styling/Style.cs +++ b/src/Perspex.Styling/Styling/Style.cs @@ -60,13 +60,13 @@ namespace Perspex.Styling var activator = match.ObservableResult ?? Observable.Never().StartWith(true); - //if (visual != null) - //{ - // var detached = Observable.FromEventPattern( - // x => visual.DetachedFromVisualTree += x, - // x => visual.DetachedFromVisualTree -= x); - // activator = activator.TakeUntil(detached); - //} + if (visual != null) + { + var detached = Observable.FromEventPattern( + x => visual.DetachedFromVisualTree += x, + x => visual.DetachedFromVisualTree -= x); + activator = activator.TakeUntil(detached); + } foreach (var setter in Setters) { From fe88eae319e34e541043ded57b0b053ac7991e18 Mon Sep 17 00:00:00 2001 From: susloparov Date: Tue, 8 Dec 2015 03:10:57 +0600 Subject: [PATCH 092/211] Somehow PathTests.cs was treated as a binary file by git. Fixed that. --- tests/Perspex.RenderTests/Shapes/PathTests.cs | Bin 10128 -> 4930 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/tests/Perspex.RenderTests/Shapes/PathTests.cs b/tests/Perspex.RenderTests/Shapes/PathTests.cs index 54cdf7e1cf79334d3f45afc531ab3223239b8aed..6a2c96feb533b42d197f3b6d71bbbcb273097e8a 100644 GIT binary patch literal 4930 zcmeHK-EW&n5P#>dSh+8@6m7s51Fb4u;v}urL}$gmYjk%N+QQiE1p`{vy`-o7?>7tB zhJssL=ZD-WJn%95o6nsYc1};pC`lg$Uxbo${T`WxKqerv6duS#B!7Z0DLIQGl0z~g zAOrCQ0;-*!l56fmoIyZVaR7oycsNK}w1nF16aKG0=A<~#zz;~bY3vWZ9%N#=>9dtSkSqhEiYxbC|D^e>DmdaEi9_+0y2BY35&&lo-8M=Z<8gvl_sio7Q3dCpL}LKWBw zeuM1xa)4S=*K6__dAdJ!ifI0!aGfn=E9Xhu@cn+peEDhfSz7cZ;X!VYXXtwR;LQYQ z@gf3m1hE7GK}GXctg_(({6sK`$sw6ofFy5841aCO8%+vaXH}GsJdh#s^mYBD233pi zAy4@otD}%dL8(r8-e7NPQ$nve=I~o`E>>9x8GR?%V^gyNPBDY;p#r^y;Cskf$o=~m zaA(orcHsL&@UK`JEINz$B3@!}5!^zhMcmt^{0c&Hzb>Oe`>uowCK)P54}mS;L9zrX z9_fS$%%;u_(N8c7$Th)x{$}YXI{B^BYnt;^3;Gk*rHb78Dl0F+kVCgfkKUUaVw5aX zCSbf|3&^^aMaoqk}luqFELj?ie~btO=-#f=U5SjF|g=Rw+-92&4FoKCj(O_qjoY< z4LTUOu4Ndu;pAY8>bBjtbxXG$qi>PpVN93y&3@G|lj^SP+Lq;5E}DwEj)rm2?*X+e z(=knbA6`xe2enS8y%)Z;&Lg(PeOmC*+TuPf_@ExSqocmG&hGDT%4nwzSol$eUt>NGQIuoD zmhajhV%8V*b^)Kq$dVl5@;2VIKfOGPDgo`rop&e>_f|-;h zz)TrDVC?%)J!xW22Q&JYf z*i%qD0oALiZ31fx+&5!j->GFf7kDV^J;3g%oNKZT&bDMt-bn*;F2Q2k@=E3F17A=1 z9pH`jiPKCIxTu#r=FuOV5A5i?5oC=@?x?rbI&mD-+N=&~lU6p$h^?)9|fW0JA zt*T6JeLR#^DWsH+TA+qW?}t7;=#JL_*7)rErU#uKLbJ!Xs9AdD0oDsWgqri@5pL@* zE2NXbutjm(Xq3ifyg5V%a%S-3EvbI?spaIU%td_n;{~U46Y#`yb*?ny=zfrApvGAA zDVDFOJYV5!9gPwuSp)W2z%@X0Yd~L&g-u<_j3M-D`sE1EA?_C9W4!$Fe4BPwF?ScV zhrm73mT{?x{yx4i20g*u$NM+5m!ab%H{8=H8`N8sn?B-bJyfah$Lh@HSi26sf($`U z)68B0O-5wK=CG7zfoZZC2gzhK=x0igah&v)p=Cx}W)()1=W2yhNhZ7Dtiwu-R9#?+ z$IA%Tx&~HeTxPni`hi>5PS@!;U046=$QfFCZWFp^j;00s8k0HdWJm_f92TsCblkkb z%pvGevT2-L4R;+bR(0JqeW{%-+ZgM?8`{tlGZpQ73jd&Avf^oDtbrz9BD(F@8Dq^&Vw=yimH|CZVd`%ARVEOM+ z!d1gn*7>AS4Php2Qm&w;tKf~ftOAGv`en^^q{1tIe(pXXDqTDseQ^r-r zUGufd9A3ctZNpRb?Og)*UfQgWwmPDzRlm;1xb@}vRc(@=i66f&Z_3Ux#}}r zWt-Ki{6oxYRm&{aW;>{VZwGY^H7IwK?L@aBJb&WpQv>HQ+O F>fZ{lwf_JB From eef1704fda2e15a6baca6cdc0846e807466bcde2 Mon Sep 17 00:00:00 2001 From: susloparov Date: Tue, 8 Dec 2015 03:13:26 +0600 Subject: [PATCH 093/211] Added DashLineCap, StartLineCap, EndLineCap to the Shape. Fixed properties assignment order in Pen constructor. --- src/Perspex.Controls/Shapes/Shape.cs | 9 ++++++++- src/Perspex.SceneGraph/Media/Pen.cs | 2 +- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/src/Perspex.Controls/Shapes/Shape.cs b/src/Perspex.Controls/Shapes/Shape.cs index 58dc24a738..fcd1fc20fe 100644 --- a/src/Perspex.Controls/Shapes/Shape.cs +++ b/src/Perspex.Controls/Shapes/Shape.cs @@ -90,13 +90,20 @@ namespace Perspex.Controls.Shapes set { SetValue(StrokeThicknessProperty, value); } } + public PenLineCap DashCap { get; set; } = PenLineCap.Flat; + + public PenLineCap StartLineCap { get; set; } = PenLineCap.Flat; + + public PenLineCap EndLineCap { get; set; } = PenLineCap.Flat; + public override void Render(DrawingContext context) { var geometry = RenderedGeometry; if (geometry != null) { - var pen = new Pen(Stroke, StrokeThickness, new DashStyle(StrokeDashArray)); + var pen = new Pen(Stroke, StrokeThickness, new DashStyle(StrokeDashArray), + DashCap, StartLineCap, EndLineCap); context.DrawGeometry(Fill, pen, geometry); } } diff --git a/src/Perspex.SceneGraph/Media/Pen.cs b/src/Perspex.SceneGraph/Media/Pen.cs index cc14ae29d6..489860a87a 100644 --- a/src/Perspex.SceneGraph/Media/Pen.cs +++ b/src/Perspex.SceneGraph/Media/Pen.cs @@ -31,12 +31,12 @@ namespace Perspex.Media { Brush = brush; Thickness = thickness; + DashCap = dashCap; StartLineCap = startLineCap; EndLineCap = endLineCap; LineJoin = lineJoin; MiterLimit = miterLimit; DashStyle = dashStyle; - DashCap = dashCap; } /// From 42bddea9f2c90af3e59fc081523bcafe7f5bd896 Mon Sep 17 00:00:00 2001 From: susloparov Date: Tue, 8 Dec 2015 03:14:02 +0600 Subject: [PATCH 094/211] Direct2D render test for the Path using DashLineCap, StartLineCap, EndLineCap. --- tests/Perspex.RenderTests/Shapes/PathTests.cs | 31 ++++++++++++++++++ .../Path/Path_With_PenLineCap.expected.png | Bin 0 -> 1155 bytes 2 files changed, 31 insertions(+) create mode 100644 tests/TestFiles/Direct2D1/Shapes/Path/Path_With_PenLineCap.expected.png diff --git a/tests/Perspex.RenderTests/Shapes/PathTests.cs b/tests/Perspex.RenderTests/Shapes/PathTests.cs index 6a2c96feb5..bc70aa1088 100644 --- a/tests/Perspex.RenderTests/Shapes/PathTests.cs +++ b/tests/Perspex.RenderTests/Shapes/PathTests.cs @@ -15,6 +15,8 @@ namespace Perspex.Skia.RenderTests namespace Perspex.Direct2D1.RenderTests.Shapes #endif { + using Perspex.Collections; + public class PathTests : TestBase { public PathTests() @@ -126,6 +128,35 @@ namespace Perspex.Direct2D1.RenderTests.Shapes } }; + RenderToFile(target); + CompareImages(); + } + +#if PERSPEX_SKIA + [Fact(Skip = "FIXME")] +#else + [Fact] +#endif + public void Path_With_PenLineCap() + { + Decorator target = new Decorator + { + Width = 200, + Height = 200, + Child = new Path + { + Stroke = Brushes.Black, + StrokeThickness = 10, + HorizontalAlignment = HorizontalAlignment.Center, + VerticalAlignment = VerticalAlignment.Center, + DashCap = PenLineCap.Triangle, + StrokeDashArray = new PerspexList(3, 1), + StartLineCap = PenLineCap.Round, + EndLineCap = PenLineCap.Square, + Data = StreamGeometry.Parse("M 20,20 L 180,180"), + } + }; + RenderToFile(target); CompareImages(); } diff --git a/tests/TestFiles/Direct2D1/Shapes/Path/Path_With_PenLineCap.expected.png b/tests/TestFiles/Direct2D1/Shapes/Path/Path_With_PenLineCap.expected.png new file mode 100644 index 0000000000000000000000000000000000000000..d33068d62c9deb097896fc29422ed4f765f2daf4 GIT binary patch literal 1155 zcmeAS@N?(olHy`uVBq!ia0vp^CqS5k4M?tyST_$yF%}28J29*~C-V}>VM%xNb!1@J z*w6hZkrl|*^>lFzsfc@fcVS*?s6^XCKHq>2FBQJ-H3}`=2Odm_kdbq4YOq#d;WQ6u zeaIKUa*%^XsIi&l+Wq2LCU3L$Z#AF0F(MRJMfd&p*>N!&ha? z=hHWI+IFi6Mg7dn5j#{X%$a+|edo*RUv9b_T5M$-7r*uXu{ina_n)rr;(DF)=Fuln zo8nTXGk#qr>YTY9EKCy}6ofcjm>NA41UOlg8XZ&w5P}EqdDw6?nSFElbXa1`MpL22 z3+JYJusC_Ip2*R%Elf#}W9?E8mP7U59##C^zpB6JzU`fRpNihhKKxd>bhT%4r8rp;nX#c1#@*LIshHz5UoAYAzM@Ef!yYdbzy~$*_#d4`KxR(>wfeM zX!mA?G+|ENvq?L$l@`v82*=qvRM@vPM5M&m=>;`3Jffudm8s7>Gy@t zTlY)jdoM6-^85Ez?%4bE`Ns47+b>+cp1gVT)2vb^$7sz7vph1gRY7E?DTqw>Rxq6i z^q@nQ(!yT<<7t9RI$O+MowWnGdW#~?Sc5pRT6yJuTOQpvolE}x<-`?vw^$$D+;b;H z{^>R2`t&;a)#?Av{jvP^_n}zTs^uro`%PPYm6IiRDaih{Q$gg?s~~c&E+_)EqnUvb zX!dG<^K5XGtuX(s(_}L7LL0V}gcNLVdY^O~W+eKn?C*1wj#d`rc$?D+47M#&oen_X z1H&$=>&Zptk2!I2(X~^e`I~2~OlPdG*fF=Na*vaDe(zbLT=k75sCt8kUw?Q-*FOns zY3uzpTi5N|x9>et^0&{sNXV%+9xCf)*MGQEWAyFnL$MvFr%gW6AF*YNP^U%4+kGBK v7!T_R6&^6Xt}Mvz6C&}5fuVv&UHw1P$(b*Hr Date: Tue, 8 Dec 2015 03:14:11 +0600 Subject: [PATCH 095/211] Fixes #313 --- .../Perspex.Direct2D1/PrimitiveExtensions.cs | 33 +++++++++---------- 1 file changed, 15 insertions(+), 18 deletions(-) diff --git a/src/Windows/Perspex.Direct2D1/PrimitiveExtensions.cs b/src/Windows/Perspex.Direct2D1/PrimitiveExtensions.cs index fac270dd7c..65d3ce2dad 100644 --- a/src/Windows/Perspex.Direct2D1/PrimitiveExtensions.cs +++ b/src/Windows/Perspex.Direct2D1/PrimitiveExtensions.cs @@ -71,26 +71,23 @@ namespace Perspex.Direct2D1 /// The Direct2D brush. public static StrokeStyle ToDirect2DStrokeStyle(this Perspex.Media.Pen pen, SharpDX.Direct2D1.RenderTarget target) { - if (pen.DashStyle != null) + var properties = new StrokeStyleProperties { - if (pen.DashStyle.Dashes != null && pen.DashStyle.Dashes.Count > 0) - { - var properties = new StrokeStyleProperties - { - DashStyle = DashStyle.Custom, - DashOffset = (float)pen.DashStyle.Offset, - MiterLimit = (float)pen.MiterLimit, - LineJoin = pen.LineJoin.ToDirect2D(), - StartCap = pen.StartLineCap.ToDirect2D(), - EndCap = pen.EndLineCap.ToDirect2D(), - DashCap = pen.DashCap.ToDirect2D() - }; - - return new StrokeStyle(target.Factory, properties, pen.DashStyle?.Dashes.Select(x => (float)x).ToArray()); - } + DashStyle = DashStyle.Solid, + MiterLimit = (float)pen.MiterLimit, + LineJoin = pen.LineJoin.ToDirect2D(), + StartCap = pen.StartLineCap.ToDirect2D(), + EndCap = pen.EndLineCap.ToDirect2D(), + DashCap = pen.DashCap.ToDirect2D() + }; + var dashes = new float[0]; + if (pen.DashStyle?.Dashes != null && pen.DashStyle.Dashes.Count > 0) + { + properties.DashStyle = DashStyle.Custom; + properties.DashOffset = (float)pen.DashStyle.Offset; + dashes = pen.DashStyle?.Dashes.Select(x => (float)x).ToArray(); } - - return null; + return new StrokeStyle(target.Factory, properties, dashes); } /// From 40c01b73fbb001021a5f97671199ac45a55e47e3 Mon Sep 17 00:00:00 2001 From: susloparov Date: Tue, 8 Dec 2015 23:43:13 +0600 Subject: [PATCH 096/211] Renamed stroke Shape properties according to WPF naming --- src/Perspex.Controls/Shapes/Shape.cs | 8 ++++---- tests/Perspex.RenderTests/Shapes/PathTests.cs | 6 +++--- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/Perspex.Controls/Shapes/Shape.cs b/src/Perspex.Controls/Shapes/Shape.cs index fcd1fc20fe..6e7934b0e7 100644 --- a/src/Perspex.Controls/Shapes/Shape.cs +++ b/src/Perspex.Controls/Shapes/Shape.cs @@ -90,11 +90,11 @@ namespace Perspex.Controls.Shapes set { SetValue(StrokeThicknessProperty, value); } } - public PenLineCap DashCap { get; set; } = PenLineCap.Flat; + public PenLineCap StrokeDashCap { get; set; } = PenLineCap.Flat; - public PenLineCap StartLineCap { get; set; } = PenLineCap.Flat; + public PenLineCap StrokeStartLineCap { get; set; } = PenLineCap.Flat; - public PenLineCap EndLineCap { get; set; } = PenLineCap.Flat; + public PenLineCap StrokeEndLineCap { get; set; } = PenLineCap.Flat; public override void Render(DrawingContext context) { @@ -103,7 +103,7 @@ namespace Perspex.Controls.Shapes if (geometry != null) { var pen = new Pen(Stroke, StrokeThickness, new DashStyle(StrokeDashArray), - DashCap, StartLineCap, EndLineCap); + StrokeDashCap, StrokeStartLineCap, StrokeEndLineCap); context.DrawGeometry(Fill, pen, geometry); } } diff --git a/tests/Perspex.RenderTests/Shapes/PathTests.cs b/tests/Perspex.RenderTests/Shapes/PathTests.cs index bc70aa1088..9736f86d1c 100644 --- a/tests/Perspex.RenderTests/Shapes/PathTests.cs +++ b/tests/Perspex.RenderTests/Shapes/PathTests.cs @@ -149,10 +149,10 @@ namespace Perspex.Direct2D1.RenderTests.Shapes StrokeThickness = 10, HorizontalAlignment = HorizontalAlignment.Center, VerticalAlignment = VerticalAlignment.Center, - DashCap = PenLineCap.Triangle, + StrokeDashCap = PenLineCap.Triangle, StrokeDashArray = new PerspexList(3, 1), - StartLineCap = PenLineCap.Round, - EndLineCap = PenLineCap.Square, + StrokeStartLineCap = PenLineCap.Round, + StrokeEndLineCap = PenLineCap.Square, Data = StreamGeometry.Parse("M 20,20 L 180,180"), } }; From ef7dc640c5dc3363f9a146becb310865166dff37 Mon Sep 17 00:00:00 2001 From: susloparov Date: Wed, 9 Dec 2015 00:36:55 +0600 Subject: [PATCH 097/211] Skipped Path_With_PenLineCap render test for Cairo --- tests/Perspex.RenderTests/Shapes/PathTests.cs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/tests/Perspex.RenderTests/Shapes/PathTests.cs b/tests/Perspex.RenderTests/Shapes/PathTests.cs index 9736f86d1c..68b5956234 100644 --- a/tests/Perspex.RenderTests/Shapes/PathTests.cs +++ b/tests/Perspex.RenderTests/Shapes/PathTests.cs @@ -132,7 +132,9 @@ namespace Perspex.Direct2D1.RenderTests.Shapes CompareImages(); } -#if PERSPEX_SKIA +#if PERSPEX_CAIRO + [Fact(Skip = "Path with StrokeDashCap, StrokeStartLineCap, StrokeEndLineCap rendering is not implemented in Cairo yet")] +#elif PERSPEX_SKIA [Fact(Skip = "FIXME")] #else [Fact] From 05da19937f259dd3151fa3cb49b685bc2cab150d Mon Sep 17 00:00:00 2001 From: donandren Date: Wed, 9 Dec 2015 20:34:15 +0200 Subject: [PATCH 098/211] Path properly parse Arc implemented - issue #354 --- .../Media/PathMarkupParser.cs | 39 +++++++++++++++++-- 1 file changed, 36 insertions(+), 3 deletions(-) diff --git a/src/Perspex.SceneGraph/Media/PathMarkupParser.cs b/src/Perspex.SceneGraph/Media/PathMarkupParser.cs index 0caa53dc7c..17539c924b 100644 --- a/src/Perspex.SceneGraph/Media/PathMarkupParser.cs +++ b/src/Perspex.SceneGraph/Media/PathMarkupParser.cs @@ -28,6 +28,8 @@ namespace Perspex.Media { 'v', Command.VerticalLineRelative }, { 'C', Command.CubicBezierCurve }, { 'c', Command.CubicBezierCurveRelative }, + { 'A', Command.Arc }, + { 'a', Command.Arc }, { 'Z', Command.Close }, { 'z', Command.Close }, }; @@ -64,6 +66,7 @@ namespace Perspex.Media VerticalLineRelative, CubicBezierCurve, CubicBezierCurveRelative, + Arc, Close, Eof, } @@ -98,8 +101,8 @@ namespace Perspex.Media _context.EndFigure(false); } - point = command == Command.Move ? - ReadPoint(reader) : + point = command == Command.Move ? + ReadPoint(reader) : ReadRelativePoint(reader, point); _context.BeginFigure(point, true); @@ -144,6 +147,22 @@ namespace Perspex.Media _context.CubicBezierTo(point1, point2, point); break; } + case Command.Arc: + { + //example: A10,10 0 0,0 10,20 + //format - size rotationAngle isLargeArcFlag sweepDirectionFlag endPoint + Size size = ReadSize(reader); + ReadSeparator(reader); + double rotationAngle = ReadDouble(reader); + ReadSeparator(reader); + bool isLargeArc = ReadBool(reader); + ReadSeparator(reader); + SweepDirection sweepDirection = ReadBool(reader) ? SweepDirection.Clockwise : SweepDirection.CounterClockwise; + point = ReadPoint(reader); + + _context.ArcTo(point, size, rotationAngle, isLargeArc, sweepDirection); + break; + } case Command.Close: _context.EndFigure(true); @@ -244,6 +263,20 @@ namespace Perspex.Media return new Point(x, y); } + private static Size ReadSize(StringReader reader) + { + ReadWhitespace(reader); + double x = ReadDouble(reader); + ReadSeparator(reader); + double y = ReadDouble(reader); + return new Size(x, y); + } + + private static bool ReadBool(StringReader reader) + { + return ReadDouble(reader) != 0; + } + private static Point ReadRelativePoint(StringReader reader, Point lastPoint) { ReadWhitespace(reader); @@ -302,4 +335,4 @@ namespace Perspex.Media } } } -} +} \ No newline at end of file From 626aa9cd19008366e223babe9d63e59664c07923 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Wed, 9 Dec 2015 20:09:26 +0100 Subject: [PATCH 099/211] Fix SelectingItemsControl.SelectedItems bug. Make sure unbound SelectedItems cleared when DataContext is cleared. --- src/Perspex.Controls/Control.cs | 5 ++++ .../SelectingItemsControlTests_Multiple.cs | 28 +++++++++++++++++++ 2 files changed, 33 insertions(+) diff --git a/src/Perspex.Controls/Control.cs b/src/Perspex.Controls/Control.cs index 861192202b..25cabadb0a 100644 --- a/src/Perspex.Controls/Control.cs +++ b/src/Perspex.Controls/Control.cs @@ -419,6 +419,11 @@ namespace Perspex.Controls if (control != null) { control.IsDataContextChanging = notifying; + + if (!notifying) + { + control.OnDataContextFinishedChanging(); + } } } } diff --git a/tests/Perspex.Controls.UnitTests/Primitives/SelectingItemsControlTests_Multiple.cs b/tests/Perspex.Controls.UnitTests/Primitives/SelectingItemsControlTests_Multiple.cs index d651216ca3..3d19e353ad 100644 --- a/tests/Perspex.Controls.UnitTests/Primitives/SelectingItemsControlTests_Multiple.cs +++ b/tests/Perspex.Controls.UnitTests/Primitives/SelectingItemsControlTests_Multiple.cs @@ -427,6 +427,34 @@ namespace Perspex.Controls.UnitTests.Primitives // Clear DataContext and ensure that SelectedItems is still set in the VM. target.DataContext = null; Assert.Equal(new[] { "bar" }, vm.SelectedItems); + + // Ensure target's SelectedItems is now clear. + Assert.Empty(target.SelectedItems); + } + + [Fact] + public void Unbound_SelectedItems_Should_Be_Cleared_When_DataContext_Cleared() + { + var data = new + { + Items = new[] { "foo", "bar", "baz" }, + }; + + var target = new TestSelector + { + DataContext = data, + Template = Template(), + }; + + var itemsBinding = new Binding { Path = "Items" }; + itemsBinding.Bind(target, TestSelector.ItemsProperty); + + Assert.Same(data.Items, target.Items); + + target.SelectedItems.Add("bar"); + target.DataContext = null; + + Assert.Empty(target.SelectedItems); } private FuncControlTemplate Template() From 195fb6df3b57a0f43b5ca08452590adc538ae19d Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Wed, 9 Dec 2015 20:15:20 +0100 Subject: [PATCH 100/211] Renamed OnDataContextFinishedChanging To OnDataContextChanged and added DataContextChanged event. --- src/Perspex.Controls/Control.cs | 14 ++++++++++++-- .../Primitives/SelectingItemsControl.cs | 2 +- 2 files changed, 13 insertions(+), 3 deletions(-) diff --git a/src/Perspex.Controls/Control.cs b/src/Perspex.Controls/Control.cs index 25cabadb0a..ad959d7f8e 100644 --- a/src/Perspex.Controls/Control.cs +++ b/src/Perspex.Controls/Control.cs @@ -93,6 +93,15 @@ namespace Perspex.Controls _nameScope = this as INameScope; } + /// + /// Occurs when the property changes. + /// + /// + /// This event will be raised when the property has changed and + /// all subscribers to that change have been notified. + /// + public event EventHandler DataContextChanged; + /// /// Gets or sets the control's classes. /// @@ -394,8 +403,9 @@ namespace Perspex.Controls /// Called when the is changed and all subscribers to that change /// have been notified. /// - protected virtual void OnDataContextFinishedChanging() + protected virtual void OnDataContextChanged() { + DataContextChanged?.Invoke(this, EventArgs.Empty); } /// @@ -422,7 +432,7 @@ namespace Perspex.Controls if (!notifying) { - control.OnDataContextFinishedChanging(); + control.OnDataContextChanged(); } } } diff --git a/src/Perspex.Controls/Primitives/SelectingItemsControl.cs b/src/Perspex.Controls/Primitives/SelectingItemsControl.cs index 42886a0f41..b60af28fda 100644 --- a/src/Perspex.Controls/Primitives/SelectingItemsControl.cs +++ b/src/Perspex.Controls/Primitives/SelectingItemsControl.cs @@ -287,7 +287,7 @@ namespace Perspex.Controls.Primitives } /// - protected override void OnDataContextFinishedChanging() + protected override void OnDataContextChanged() { if (_clearSelectedItemsAfterDataContextChanged == SelectedItems) { From 8f24ccf44181db5617048cc0779cf284f0349a6e Mon Sep 17 00:00:00 2001 From: Nikita Tsukanov Date: Fri, 11 Dec 2015 04:11:57 +0300 Subject: [PATCH 101/211] Added Window::HasSystemDecorations property --- .../Platform/SkiaPlatform/WindowImpl.cs | 3 ++ src/Gtk/Perspex.Gtk/WindowImpl.cs | 2 + src/Perspex.Controls/Platform/IWindowImpl.cs | 5 +++ .../Platform/PlatformManager.cs | 1 + src/Perspex.Controls/Window.cs | 18 ++++++++ .../Perspex.Win32/Interop/UnmanagedMethods.cs | 3 ++ src/Windows/Perspex.Win32/WindowImpl.cs | 43 +++++++++++++++++++ src/iOS/Perspex.iOS/PerspexView.cs | 5 +++ 8 files changed, 80 insertions(+) diff --git a/src/Android/Perspex.Android/Platform/SkiaPlatform/WindowImpl.cs b/src/Android/Perspex.Android/Platform/SkiaPlatform/WindowImpl.cs index b7125a56ed..09aa7a165c 100644 --- a/src/Android/Perspex.Android/Platform/SkiaPlatform/WindowImpl.cs +++ b/src/Android/Perspex.Android/Platform/SkiaPlatform/WindowImpl.cs @@ -94,6 +94,9 @@ namespace Perspex.Android.Platform.SkiaPlatform this.Visibility = ViewStates.Invisible; } + public void SetSystemDecorations(bool enabled) + { + } public void Invalidate(Rect rect) { if (Holder?.Surface?.IsValid == true) base.Invalidate(); diff --git a/src/Gtk/Perspex.Gtk/WindowImpl.cs b/src/Gtk/Perspex.Gtk/WindowImpl.cs index 64b1beb779..afeaaf9ff0 100644 --- a/src/Gtk/Perspex.Gtk/WindowImpl.cs +++ b/src/Gtk/Perspex.Gtk/WindowImpl.cs @@ -170,6 +170,8 @@ namespace Perspex.Gtk return Disposable.Empty; } + public void SetSystemDecorations(bool enabled) => Decorated = enabled; + void ITopLevelImpl.Activate() { Activate(); diff --git a/src/Perspex.Controls/Platform/IWindowImpl.cs b/src/Perspex.Controls/Platform/IWindowImpl.cs index f8bd6588cd..e27d94b41e 100644 --- a/src/Perspex.Controls/Platform/IWindowImpl.cs +++ b/src/Perspex.Controls/Platform/IWindowImpl.cs @@ -33,5 +33,10 @@ namespace Perspex.Platform /// Hides the window. /// void Hide(); + + /// + /// Enables of disables system window decorations (title bar, buttons, etc) + /// + void SetSystemDecorations(bool enabled); } } diff --git a/src/Perspex.Controls/Platform/PlatformManager.cs b/src/Perspex.Controls/Platform/PlatformManager.cs index 1e13b926c0..735b810d91 100644 --- a/src/Perspex.Controls/Platform/PlatformManager.cs +++ b/src/Perspex.Controls/Platform/PlatformManager.cs @@ -178,6 +178,7 @@ namespace Perspex.Controls.Platform public IDisposable ShowDialog() => _window.ShowDialog(); public void Hide() => _popup.Hide(); + public void SetSystemDecorations(bool enabled) => _window.SetSystemDecorations(enabled); } public static IWindowImpl CreateWindow() diff --git a/src/Perspex.Controls/Window.cs b/src/Perspex.Controls/Window.cs index 1e7855e204..7798bde1f4 100644 --- a/src/Perspex.Controls/Window.cs +++ b/src/Perspex.Controls/Window.cs @@ -49,6 +49,12 @@ namespace Perspex.Controls public static readonly PerspexProperty SizeToContentProperty = PerspexProperty.Register(nameof(SizeToContent)); + /// + /// Enables of disables system window decorations (title bar, buttons, etc) + /// + public static readonly PerspexProperty HasSystemDecorationsProperty = + PerspexProperty.Register(nameof(HasSystemDecorations), true); + /// /// Defines the property. /// @@ -66,6 +72,8 @@ namespace Perspex.Controls { BackgroundProperty.OverrideDefaultValue(typeof(Window), Brushes.White); TitleProperty.Changed.AddClassHandler((s, e) => s.PlatformImpl.SetTitle((string)e.NewValue)); + HasSystemDecorationsProperty.Changed.AddClassHandler( + (s, e) => s.PlatformImpl.SetSystemDecorations((bool) e.NewValue)); } /// @@ -114,6 +122,16 @@ namespace Perspex.Controls set { SetValue(TitleProperty, value); } } + /// + /// Enables of disables system window decorations (title bar, buttons, etc) + /// + /// + public bool HasSystemDecorations + { + get { return GetValue(HasSystemDecorationsProperty); } + set { SetValue(HasSystemDecorationsProperty, value); } + } + /// Type IStyleable.StyleKey => typeof(Window); diff --git a/src/Windows/Perspex.Win32/Interop/UnmanagedMethods.cs b/src/Windows/Perspex.Win32/Interop/UnmanagedMethods.cs index 5e281b5678..b19d771512 100644 --- a/src/Windows/Perspex.Win32/Interop/UnmanagedMethods.cs +++ b/src/Windows/Perspex.Win32/Interop/UnmanagedMethods.cs @@ -571,6 +571,9 @@ namespace Perspex.Win32.Interop [DllImport("user32.dll", SetLastError = true)] public static extern uint GetWindowLong(IntPtr hWnd, int nIndex); + [DllImport("user32.dll", SetLastError = true)] + public static extern uint SetWindowLong(IntPtr hWnd, int nIndex, uint value); + [DllImport("user32.dll")] public static extern bool GetWindowRect(IntPtr hwnd, out RECT lpRect); diff --git a/src/Windows/Perspex.Win32/WindowImpl.cs b/src/Windows/Perspex.Win32/WindowImpl.cs index 3ae9dc26cb..03fcd7cc6d 100644 --- a/src/Windows/Perspex.Win32/WindowImpl.cs +++ b/src/Windows/Perspex.Win32/WindowImpl.cs @@ -37,6 +37,8 @@ namespace Perspex.Win32 private bool _isActive; + private bool _decorated = true; + public WindowImpl() { CreateWindow(); @@ -145,6 +147,47 @@ namespace Perspex.Win32 UnmanagedMethods.ShowWindow(_hwnd, UnmanagedMethods.ShowWindowCommand.Hide); } + public void SetSystemDecorations(bool value) + { + if (value == _decorated) + return; + var style = (UnmanagedMethods.WindowStyles) UnmanagedMethods.GetWindowLong(_hwnd, -16); + style |= UnmanagedMethods.WindowStyles.WS_OVERLAPPEDWINDOW; + if (!value) + style ^= UnmanagedMethods.WindowStyles.WS_OVERLAPPEDWINDOW; + + UnmanagedMethods.RECT windowRect; + + UnmanagedMethods.GetWindowRect(_hwnd, out windowRect); + Rect newRect; + var oldThickness = BorderThickness; + + UnmanagedMethods.SetWindowLong(_hwnd, -16, (uint) style); + if (value) + { + var thickness = BorderThickness; + newRect = new Rect( + windowRect.left - thickness.Left, + windowRect.top - thickness.Top, + (windowRect.right - windowRect.left) + (thickness.Left + thickness.Right), + (windowRect.bottom - windowRect.top) + (thickness.Top + thickness.Bottom)); + } + else + newRect = new Rect( + windowRect.left + oldThickness.Left, + windowRect.top + oldThickness.Top, + (windowRect.right - windowRect.left) - (oldThickness.Left + oldThickness.Right), + (windowRect.bottom - windowRect.top) - (oldThickness.Top + oldThickness.Bottom)); + UnmanagedMethods.SetWindowPos(_hwnd, IntPtr.Zero, (int) newRect.X, (int) newRect.Y, (int) newRect.Width, + (int) newRect.Height, + UnmanagedMethods.SetWindowPosFlags.SWP_NOZORDER | UnmanagedMethods.SetWindowPosFlags.SWP_NOACTIVATE); + + + _decorated = value; + + + } + public void Invalidate(Rect rect) { var r = new UnmanagedMethods.RECT diff --git a/src/iOS/Perspex.iOS/PerspexView.cs b/src/iOS/Perspex.iOS/PerspexView.cs index 4adfb98790..52fe69d8d1 100644 --- a/src/iOS/Perspex.iOS/PerspexView.cs +++ b/src/iOS/Perspex.iOS/PerspexView.cs @@ -124,6 +124,11 @@ namespace Perspex.iOS //Not supported } + public void SetSystemDecorations(bool enabled) + { + //Not supported + } + public override void TouchesEnded(NSSet touches, UIEvent evt) { var touch = touches.AnyObject as UITouch; From 0b0875970c5dba043761b3267ed8da577472d8b0 Mon Sep 17 00:00:00 2001 From: Nikita Tsukanov Date: Fri, 11 Dec 2015 05:43:58 +0300 Subject: [PATCH 102/211] Added TopLevel::BeginMoveDrag() --- .../Perspex.Android/Platform/SkiaPlatform/WindowImpl.cs | 5 +++++ src/Gtk/Perspex.Gtk/WindowImpl.cs | 8 ++++++++ src/Perspex.Controls/Platform/ITopLevelImpl.cs | 5 +++++ src/Perspex.Controls/Platform/PlatformManager.cs | 1 + src/Perspex.Controls/TopLevel.cs | 5 +++++ src/Windows/Perspex.Win32/WindowImpl.cs | 6 ++++++ src/iOS/Perspex.iOS/PerspexView.cs | 5 +++++ 7 files changed, 35 insertions(+) diff --git a/src/Android/Perspex.Android/Platform/SkiaPlatform/WindowImpl.cs b/src/Android/Perspex.Android/Platform/SkiaPlatform/WindowImpl.cs index 09aa7a165c..1697295198 100644 --- a/src/Android/Perspex.Android/Platform/SkiaPlatform/WindowImpl.cs +++ b/src/Android/Perspex.Android/Platform/SkiaPlatform/WindowImpl.cs @@ -126,6 +126,11 @@ namespace Perspex.Android.Platform.SkiaPlatform this.Visibility = ViewStates.Visible; } + public void BeginMoveDrag() + { + //Not supported + } + public IDisposable ShowDialog() { throw new NotImplementedException(); diff --git a/src/Gtk/Perspex.Gtk/WindowImpl.cs b/src/Gtk/Perspex.Gtk/WindowImpl.cs index afeaaf9ff0..735150f9b2 100644 --- a/src/Gtk/Perspex.Gtk/WindowImpl.cs +++ b/src/Gtk/Perspex.Gtk/WindowImpl.cs @@ -162,6 +162,14 @@ namespace Perspex.Gtk GdkWindow.Cursor = cursor != null ? new Gdk.Cursor(cursor.Handle) : DefaultCursor; } + public void BeginMoveDrag() + { + int x, y; + ModifierType mod; + Screen.RootWindow.GetPointer(out x, out y, out mod); + BeginMoveDrag(0, x, y, 0); + } + public IDisposable ShowDialog() { Modal = true; diff --git a/src/Perspex.Controls/Platform/ITopLevelImpl.cs b/src/Perspex.Controls/Platform/ITopLevelImpl.cs index f826ec5990..ade7aab3bd 100644 --- a/src/Perspex.Controls/Platform/ITopLevelImpl.cs +++ b/src/Perspex.Controls/Platform/ITopLevelImpl.cs @@ -88,5 +88,10 @@ namespace Perspex.Platform /// Shows the toplevel. /// void Show(); + + /// + /// Starts moving a window with left button being held. Should be called from left mouse button press event handler + /// + void BeginMoveDrag(); } } diff --git a/src/Perspex.Controls/Platform/PlatformManager.cs b/src/Perspex.Controls/Platform/PlatformManager.cs index 735b810d91..1e7b5cc755 100644 --- a/src/Perspex.Controls/Platform/PlatformManager.cs +++ b/src/Perspex.Controls/Platform/PlatformManager.cs @@ -174,6 +174,7 @@ namespace Perspex.Controls.Platform public void SetTitle(string title) => _window.SetTitle(title); public void Show() => _tl.Show(); + public void BeginMoveDrag() => _tl.BeginMoveDrag(); public IDisposable ShowDialog() => _window.ShowDialog(); diff --git a/src/Perspex.Controls/TopLevel.cs b/src/Perspex.Controls/TopLevel.cs index ca84f67de1..dffedefbe2 100644 --- a/src/Perspex.Controls/TopLevel.cs +++ b/src/Perspex.Controls/TopLevel.cs @@ -364,5 +364,10 @@ namespace Perspex.Controls { _renderQueueManager?.InvalidateRender(this); } + + /// + /// Starts moving a window with left button being held. Should be called from left mouse button press event handler + /// + public void BeginMoveDrag() => PlatformImpl.BeginMoveDrag(); } } diff --git a/src/Windows/Perspex.Win32/WindowImpl.cs b/src/Windows/Perspex.Win32/WindowImpl.cs index 03fcd7cc6d..063be91873 100644 --- a/src/Windows/Perspex.Win32/WindowImpl.cs +++ b/src/Windows/Perspex.Win32/WindowImpl.cs @@ -223,6 +223,12 @@ namespace Perspex.Win32 UnmanagedMethods.ShowWindow(_hwnd, UnmanagedMethods.ShowWindowCommand.Normal); } + public void BeginMoveDrag() + { + UnmanagedMethods.DefWindowProc(_hwnd, (int) UnmanagedMethods.WindowsMessage.WM_NCLBUTTONDOWN, + new IntPtr(2), IntPtr.Zero); + } + public virtual IDisposable ShowDialog() { var disabled = s_instances.Where(x => x != this && x.IsEnabled).ToList(); diff --git a/src/iOS/Perspex.iOS/PerspexView.cs b/src/iOS/Perspex.iOS/PerspexView.cs index 52fe69d8d1..1ccf78330f 100644 --- a/src/iOS/Perspex.iOS/PerspexView.cs +++ b/src/iOS/Perspex.iOS/PerspexView.cs @@ -107,6 +107,11 @@ namespace Perspex.iOS _keyboardHelper.ActivateAutoShowKeybord(); } + public void BeginMoveDrag() + { + //Not supported + } + public Size MaxClientSize => Bounds.Size.ToPerspex(); public void SetTitle(string title) { From 7b56292655cd971580f4de77dd6e679178890769 Mon Sep 17 00:00:00 2001 From: Nikita Tsukanov Date: Fri, 11 Dec 2015 05:46:49 +0300 Subject: [PATCH 103/211] GtkWindow::BeginMoveDrag should receive Button1 as the first argument --- src/Gtk/Perspex.Gtk/WindowImpl.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Gtk/Perspex.Gtk/WindowImpl.cs b/src/Gtk/Perspex.Gtk/WindowImpl.cs index 735150f9b2..67d075add8 100644 --- a/src/Gtk/Perspex.Gtk/WindowImpl.cs +++ b/src/Gtk/Perspex.Gtk/WindowImpl.cs @@ -167,7 +167,7 @@ namespace Perspex.Gtk int x, y; ModifierType mod; Screen.RootWindow.GetPointer(out x, out y, out mod); - BeginMoveDrag(0, x, y, 0); + BeginMoveDrag(1, x, y, 0); } public IDisposable ShowDialog() From 53b909282660a81c5dd7c8d983f385b0159280ac Mon Sep 17 00:00:00 2001 From: Nikita Tsukanov Date: Sat, 12 Dec 2015 07:09:41 +0300 Subject: [PATCH 104/211] Added cursor type converter --- .../Context/PerspexWiringContext.cs | 1 + .../Converters/CursorTypeConverter.cs | 36 +++++++++++++++++++ .../Perspex.Markup.Xaml.csproj | 1 + 3 files changed, 38 insertions(+) create mode 100644 src/Markup/Perspex.Markup.Xaml/Converters/CursorTypeConverter.cs diff --git a/src/Markup/Perspex.Markup.Xaml/Context/PerspexWiringContext.cs b/src/Markup/Perspex.Markup.Xaml/Context/PerspexWiringContext.cs index 89203022a9..ab16826dc6 100644 --- a/src/Markup/Perspex.Markup.Xaml/Context/PerspexWiringContext.cs +++ b/src/Markup/Perspex.Markup.Xaml/Context/PerspexWiringContext.cs @@ -107,6 +107,7 @@ namespace Perspex.Markup.Xaml.Context new TypeConverterRegistration(typeof(Thickness), new ThicknessTypeConverter()), new TypeConverterRegistration(typeof(TimeSpan), new TimeSpanTypeConverter()), new TypeConverterRegistration(typeof(Uri), new UriTypeConverter()), + new TypeConverterRegistration(typeof(Cursor), new CursorTypeConverter()) }; typeConverterProvider.AddAll(converters); diff --git a/src/Markup/Perspex.Markup.Xaml/Converters/CursorTypeConverter.cs b/src/Markup/Perspex.Markup.Xaml/Converters/CursorTypeConverter.cs new file mode 100644 index 0000000000..2310d563c5 --- /dev/null +++ b/src/Markup/Perspex.Markup.Xaml/Converters/CursorTypeConverter.cs @@ -0,0 +1,36 @@ +// 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.Globalization; +using OmniXaml.TypeConversion; +using Perspex.Input; +using Perspex.Media.Imaging; +using Perspex.Platform; + +namespace Perspex.Markup.Xaml.Converters +{ + public class CursorTypeConverter : ITypeConverter + { + public bool CanConvertFrom(IXamlTypeConverterContext context, Type sourceType) + { + return sourceType == typeof(string); + } + + public bool CanConvertTo(IXamlTypeConverterContext context, Type destinationType) + { + return false; + } + + public object ConvertFrom(IXamlTypeConverterContext context, CultureInfo culture, object value) + { + var cursor = (StandardCursorType)Enum.Parse(typeof (StandardCursorType), ((string) value).Trim(), true); + return new Cursor(cursor); + } + + public object ConvertTo(IXamlTypeConverterContext context, CultureInfo culture, object value, Type destinationType) + { + throw new NotImplementedException(); + } + } +} \ No newline at end of file diff --git a/src/Markup/Perspex.Markup.Xaml/Perspex.Markup.Xaml.csproj b/src/Markup/Perspex.Markup.Xaml/Perspex.Markup.Xaml.csproj index 9b8cb35162..484363cd85 100644 --- a/src/Markup/Perspex.Markup.Xaml/Perspex.Markup.Xaml.csproj +++ b/src/Markup/Perspex.Markup.Xaml/Perspex.Markup.Xaml.csproj @@ -39,6 +39,7 @@ Properties\SharedAssemblyInfo.cs + From ec2a319049c8f033fc38c4122f1778b58dd52aee Mon Sep 17 00:00:00 2001 From: Nikita Tsukanov Date: Sat, 12 Dec 2015 07:38:29 +0300 Subject: [PATCH 105/211] Implemented BeginResizeDrag --- .../Platform/SkiaPlatform/WindowImpl.cs | 6 ++++ src/Gtk/Perspex.Gtk/WindowImpl.cs | 9 ++++++ src/Perspex.Controls/Perspex.Controls.csproj | 1 + .../Platform/ITopLevelImpl.cs | 9 +++++- .../Platform/PlatformManager.cs | 1 + src/Perspex.Controls/TopLevel.cs | 6 ++++ src/Perspex.Controls/WindowEdge.cs | 17 +++++++++++ .../Perspex.Win32/Interop/UnmanagedMethods.cs | 28 +++++++++++++++++++ src/Windows/Perspex.Win32/WindowImpl.cs | 20 ++++++++++++- src/iOS/Perspex.iOS/PerspexView.cs | 6 ++++ 10 files changed, 101 insertions(+), 2 deletions(-) create mode 100644 src/Perspex.Controls/WindowEdge.cs diff --git a/src/Android/Perspex.Android/Platform/SkiaPlatform/WindowImpl.cs b/src/Android/Perspex.Android/Platform/SkiaPlatform/WindowImpl.cs index 1697295198..14bd084a37 100644 --- a/src/Android/Perspex.Android/Platform/SkiaPlatform/WindowImpl.cs +++ b/src/Android/Perspex.Android/Platform/SkiaPlatform/WindowImpl.cs @@ -9,6 +9,7 @@ using Perspex.Input.Raw; using Perspex.Platform; using Perspex.Skia.Android; using System; +using Perspex.Controls; namespace Perspex.Android.Platform.SkiaPlatform { @@ -131,6 +132,11 @@ namespace Perspex.Android.Platform.SkiaPlatform //Not supported } + public void BeginResizeDrag(WindowEdge edge) + { + //Not supported + } + public IDisposable ShowDialog() { throw new NotImplementedException(); diff --git a/src/Gtk/Perspex.Gtk/WindowImpl.cs b/src/Gtk/Perspex.Gtk/WindowImpl.cs index 67d075add8..01054ae50b 100644 --- a/src/Gtk/Perspex.Gtk/WindowImpl.cs +++ b/src/Gtk/Perspex.Gtk/WindowImpl.cs @@ -11,6 +11,7 @@ using Perspex.Platform; using Perspex.Input; using Perspex.Threading; using Action = System.Action; +using WindowEdge = Perspex.Controls.WindowEdge; namespace Perspex.Gtk { @@ -170,6 +171,14 @@ namespace Perspex.Gtk BeginMoveDrag(1, x, y, 0); } + public void BeginResizeDrag(WindowEdge edge) + { + int x, y; + ModifierType mod; + Screen.RootWindow.GetPointer(out x, out y, out mod); + BeginResizeDrag((Gdk.WindowEdge) (int) edge, 1, x, y, 0); + } + public IDisposable ShowDialog() { Modal = true; diff --git a/src/Perspex.Controls/Perspex.Controls.csproj b/src/Perspex.Controls/Perspex.Controls.csproj index f9a4a3ad84..5a8557da6b 100644 --- a/src/Perspex.Controls/Perspex.Controls.csproj +++ b/src/Perspex.Controls/Perspex.Controls.csproj @@ -171,6 +171,7 @@ + diff --git a/src/Perspex.Controls/Platform/ITopLevelImpl.cs b/src/Perspex.Controls/Platform/ITopLevelImpl.cs index ade7aab3bd..0a11566c5a 100644 --- a/src/Perspex.Controls/Platform/ITopLevelImpl.cs +++ b/src/Perspex.Controls/Platform/ITopLevelImpl.cs @@ -2,6 +2,7 @@ // Licensed under the MIT license. See licence.md file in the project root for full license information. using System; +using Perspex.Controls; using Perspex.Input; using Perspex.Input.Raw; @@ -90,8 +91,14 @@ namespace Perspex.Platform void Show(); /// - /// Starts moving a window with left button being held. Should be called from left mouse button press event handler + /// Starts moving a window with left button being held. Should be called from left mouse button press event handler. /// void BeginMoveDrag(); + + /// + /// Starts resizing a window. This function is used if an application has window resizing controls. + /// Should be called from left mouse button press event handler + /// + void BeginResizeDrag(WindowEdge edge); } } diff --git a/src/Perspex.Controls/Platform/PlatformManager.cs b/src/Perspex.Controls/Platform/PlatformManager.cs index 1e7b5cc755..fc582ab45d 100644 --- a/src/Perspex.Controls/Platform/PlatformManager.cs +++ b/src/Perspex.Controls/Platform/PlatformManager.cs @@ -175,6 +175,7 @@ namespace Perspex.Controls.Platform public void Show() => _tl.Show(); public void BeginMoveDrag() => _tl.BeginMoveDrag(); + public void BeginResizeDrag(WindowEdge edge) => _tl.BeginResizeDrag(edge); public IDisposable ShowDialog() => _window.ShowDialog(); diff --git a/src/Perspex.Controls/TopLevel.cs b/src/Perspex.Controls/TopLevel.cs index dffedefbe2..12311df1c9 100644 --- a/src/Perspex.Controls/TopLevel.cs +++ b/src/Perspex.Controls/TopLevel.cs @@ -369,5 +369,11 @@ namespace Perspex.Controls /// Starts moving a window with left button being held. Should be called from left mouse button press event handler /// public void BeginMoveDrag() => PlatformImpl.BeginMoveDrag(); + + /// + /// Starts resizing a window. This function is used if an application has window resizing controls. + /// Should be called from left mouse button press event handler + /// + public void BeginResizeDrag(WindowEdge edge) => PlatformImpl.BeginResizeDrag(edge); } } diff --git a/src/Perspex.Controls/WindowEdge.cs b/src/Perspex.Controls/WindowEdge.cs new file mode 100644 index 0000000000..356f882899 --- /dev/null +++ b/src/Perspex.Controls/WindowEdge.cs @@ -0,0 +1,17 @@ +namespace Perspex.Controls +{ + + public enum WindowEdge + { + //Please don't reorder stuff here, I was lazy to write proper conversion code + //so the order of values is matching one from GTK + NorthWest = 0, + North, + NorthEast, + West, + East, + SouthWest, + South, + SouthEast, + } +} \ No newline at end of file diff --git a/src/Windows/Perspex.Win32/Interop/UnmanagedMethods.cs b/src/Windows/Perspex.Win32/Interop/UnmanagedMethods.cs index b19d771512..02aefb4106 100644 --- a/src/Windows/Perspex.Win32/Interop/UnmanagedMethods.cs +++ b/src/Windows/Perspex.Win32/Interop/UnmanagedMethods.cs @@ -217,6 +217,34 @@ namespace Perspex.Win32.Interop WA_CLICKACTIVE, } + public enum HitTestValues + { + HTERROR = -2, + HTTRANSPARENT = -1, + HTNOWHERE = 0, + HTCLIENT = 1, + HTCAPTION = 2, + HTSYSMENU = 3, + HTGROWBOX = 4, + HTMENU = 5, + HTHSCROLL = 6, + HTVSCROLL = 7, + HTMINBUTTON = 8, + HTMAXBUTTON = 9, + HTLEFT = 10, + HTRIGHT = 11, + HTTOP = 12, + HTTOPLEFT = 13, + HTTOPRIGHT = 14, + HTBOTTOM = 15, + HTBOTTOMLEFT = 16, + HTBOTTOMRIGHT = 17, + HTBORDER = 18, + HTOBJECT = 19, + HTCLOSE = 20, + HTHELP = 21 + } + [Flags] public enum WindowStyles : uint { diff --git a/src/Windows/Perspex.Win32/WindowImpl.cs b/src/Windows/Perspex.Win32/WindowImpl.cs index 063be91873..ca1a2ae9c6 100644 --- a/src/Windows/Perspex.Win32/WindowImpl.cs +++ b/src/Windows/Perspex.Win32/WindowImpl.cs @@ -226,7 +226,25 @@ namespace Perspex.Win32 public void BeginMoveDrag() { UnmanagedMethods.DefWindowProc(_hwnd, (int) UnmanagedMethods.WindowsMessage.WM_NCLBUTTONDOWN, - new IntPtr(2), IntPtr.Zero); + new IntPtr((int)UnmanagedMethods.HitTestValues.HTCAPTION), IntPtr.Zero); + } + + static readonly Dictionary EdgeDic = new Dictionary + { + {WindowEdge.East, UnmanagedMethods.HitTestValues.HTRIGHT}, + {WindowEdge.North, UnmanagedMethods.HitTestValues.HTTOP }, + {WindowEdge.NorthEast, UnmanagedMethods.HitTestValues.HTTOPRIGHT }, + {WindowEdge.NorthWest, UnmanagedMethods.HitTestValues.HTTOPLEFT }, + {WindowEdge.South, UnmanagedMethods.HitTestValues.HTBOTTOM }, + {WindowEdge.SouthEast, UnmanagedMethods.HitTestValues.HTBOTTOMRIGHT }, + {WindowEdge.SouthWest, UnmanagedMethods.HitTestValues.HTBOTTOMLEFT }, + {WindowEdge.West, UnmanagedMethods.HitTestValues.HTLEFT} + }; + + public void BeginResizeDrag(WindowEdge edge) + { + UnmanagedMethods.DefWindowProc(_hwnd, (int) UnmanagedMethods.WindowsMessage.WM_NCLBUTTONDOWN, + new IntPtr((int) EdgeDic[edge]), IntPtr.Zero); } public virtual IDisposable ShowDialog() diff --git a/src/iOS/Perspex.iOS/PerspexView.cs b/src/iOS/Perspex.iOS/PerspexView.cs index 1ccf78330f..92b2fac730 100644 --- a/src/iOS/Perspex.iOS/PerspexView.cs +++ b/src/iOS/Perspex.iOS/PerspexView.cs @@ -16,6 +16,7 @@ using Perspex.Skia.iOS; using UIKit; using Perspex.iOS.Specific; using ObjCRuntime; +using Perspex.Controls; namespace Perspex.iOS { @@ -112,6 +113,11 @@ namespace Perspex.iOS //Not supported } + public void BeginResizeDrag(WindowEdge edge) + { + //Not supported + } + public Size MaxClientSize => Bounds.Size.ToPerspex(); public void SetTitle(string title) { From f85d68d58047726587a85b0ba513b88a71fbb0b2 Mon Sep 17 00:00:00 2001 From: Nikita Tsukanov Date: Sun, 13 Dec 2015 02:23:16 +0300 Subject: [PATCH 106/211] Added some sizing cursors --- src/Gtk/Perspex.Gtk/CursorFactory.cs | 32 ++++++++++------- src/Perspex.Input/Cursors.cs | 10 +++++- src/Windows/Perspex.Win32/CursorFactory.cs | 40 +++++++++++++--------- 3 files changed, 53 insertions(+), 29 deletions(-) diff --git a/src/Gtk/Perspex.Gtk/CursorFactory.cs b/src/Gtk/Perspex.Gtk/CursorFactory.cs index 80e6b7b1e6..6a250024bf 100644 --- a/src/Gtk/Perspex.Gtk/CursorFactory.cs +++ b/src/Gtk/Perspex.Gtk/CursorFactory.cs @@ -21,20 +21,28 @@ namespace Perspex.Gtk private static readonly Dictionary CursorTypeMapping = new Dictionary { - { StandardCursorType.AppStarting, CursorType.Watch }, - { StandardCursorType.Arrow, CursorType.LeftPtr }, - { StandardCursorType.Cross, CursorType.Cross }, - { StandardCursorType.Hand, CursorType.Hand1 }, - { StandardCursorType.Ibeam, CursorType.Xterm }, - { StandardCursorType.No, Gtk.Stock.Cancel}, - { StandardCursorType.SizeAll, CursorType.Sizing }, + {StandardCursorType.AppStarting, CursorType.Watch}, + {StandardCursorType.Arrow, CursorType.LeftPtr}, + {StandardCursorType.Cross, CursorType.Cross}, + {StandardCursorType.Hand, CursorType.Hand1}, + {StandardCursorType.Ibeam, CursorType.Xterm}, + {StandardCursorType.No, Gtk.Stock.Cancel}, + {StandardCursorType.SizeAll, CursorType.Sizing}, //{ StandardCursorType.SizeNorthEastSouthWest, 32643 }, - { StandardCursorType.SizeNorthSouth, CursorType.SbVDoubleArrow}, + {StandardCursorType.SizeNorthSouth, CursorType.SbVDoubleArrow}, //{ StandardCursorType.SizeNorthWestSouthEast, 32642 }, - { StandardCursorType.SizeWestEast, CursorType.SbHDoubleArrow }, - { StandardCursorType.UpArrow, CursorType.BasedArrowUp }, - { StandardCursorType.Wait, CursorType.Watch }, - { StandardCursorType.Help, Gtk.Stock.Help } + {StandardCursorType.SizeWestEast, CursorType.SbHDoubleArrow}, + {StandardCursorType.UpArrow, CursorType.BasedArrowUp}, + {StandardCursorType.Wait, CursorType.Watch}, + {StandardCursorType.Help, Gtk.Stock.Help}, + {StandardCursorType.TopSide, CursorType.TopSide}, + {StandardCursorType.BottomSize, CursorType.BottomSide}, + {StandardCursorType.LeftSide, CursorType.LeftSide}, + {StandardCursorType.RightSide, CursorType.RightSide}, + {StandardCursorType.TopLeftCorner, CursorType.TopLeftCorner}, + {StandardCursorType.TopRightCorner, CursorType.TopRightCorner}, + {StandardCursorType.BottomLeftCorner, CursorType.BottomLeftCorner}, + {StandardCursorType.BottomRightCorner, CursorType.BottomRightCorner} }; private static readonly Dictionary Cache = diff --git a/src/Perspex.Input/Cursors.cs b/src/Perspex.Input/Cursors.cs index 889cc68774..34f97fe0a6 100644 --- a/src/Perspex.Input/Cursors.cs +++ b/src/Perspex.Input/Cursors.cs @@ -30,7 +30,15 @@ namespace Perspex.Input No, Hand, AppStarting, - Help + Help, + TopSide, + BottomSize, + LeftSide, + RightSide, + TopLeftCorner, + TopRightCorner, + BottomLeftCorner, + BottomRightCorner // Not available in GTK directly, see http://www.pixelbeat.org/programming/x_cursors/ // We might enable them later, preferably, by loading pixmax direclty from theme with fallback image diff --git a/src/Windows/Perspex.Win32/CursorFactory.cs b/src/Windows/Perspex.Win32/CursorFactory.cs index ad2defac35..fd117f6814 100644 --- a/src/Windows/Perspex.Win32/CursorFactory.cs +++ b/src/Windows/Perspex.Win32/CursorFactory.cs @@ -23,22 +23,30 @@ namespace Perspex.Win32 private static readonly Dictionary CursorTypeMapping = new Dictionary { - { StandardCursorType.AppStarting, 32650 }, - { StandardCursorType.Arrow, 32512 }, - { StandardCursorType.Cross, 32515 }, - { StandardCursorType.Hand, 32649 }, - { StandardCursorType.Help, 32651 }, - { StandardCursorType.Ibeam, 32513 }, - { StandardCursorType.No, 32648 }, - { StandardCursorType.SizeAll, 32646 }, - - // { StandardCursorType.SizeNorthEastSouthWest, 32643 }, - { StandardCursorType.SizeNorthSouth, 32645 }, - - // { StandardCursorType.SizeNorthWestSouthEast, 32642 }, - { StandardCursorType.SizeWestEast, 32644 }, - { StandardCursorType.UpArrow, 32516 }, - { StandardCursorType.Wait, 32514 } + {StandardCursorType.AppStarting, 32650}, + {StandardCursorType.Arrow, 32512}, + {StandardCursorType.Cross, 32515}, + {StandardCursorType.Hand, 32649}, + {StandardCursorType.Help, 32651}, + {StandardCursorType.Ibeam, 32513}, + {StandardCursorType.No, 32648}, + {StandardCursorType.SizeAll, 32646}, + {StandardCursorType.UpArrow, 32516}, + {StandardCursorType.SizeNorthSouth, 32645}, + {StandardCursorType.SizeWestEast, 32644}, + {StandardCursorType.Wait, 32514}, + //Same as SizeNorthSouth + {StandardCursorType.TopSide, 32645}, + {StandardCursorType.BottomSize, 32645}, + //Same as SizeWestEast + {StandardCursorType.LeftSide, 32644}, + {StandardCursorType.RightSide, 32644}, + //Using SizeNorthWestSouthEast + {StandardCursorType.TopLeftCorner, 32642}, + {StandardCursorType.BottomRightCorner, 32642}, + //Using SizeNorthEastSouthWest + {StandardCursorType.TopRightCorner, 32643}, + {StandardCursorType.BottomLeftCorner, 32643}, }; private static readonly Dictionary Cache = From 84af63ce1e4cf944c266fe8a880ee712eb0b3385 Mon Sep 17 00:00:00 2001 From: Nikita Tsukanov Date: Sun, 13 Dec 2015 05:19:24 +0300 Subject: [PATCH 107/211] Added Position property, moved Hide/Show to ITopLevelImpl --- .../Platform/SkiaPlatform/WindowImpl.cs | 2 ++ src/Gtk/Perspex.Gtk/PopupImpl.cs | 5 ----- src/Gtk/Perspex.Gtk/WindowImpl.cs | 14 ++++++++++++ src/Perspex.Controls/Platform/IPopupImpl.cs | 9 -------- .../Platform/ITopLevelImpl.cs | 7 ++++++ src/Perspex.Controls/Platform/IWindowImpl.cs | 5 ----- .../Platform/PlatformManager.cs | 11 +++++----- src/Perspex.Controls/Primitives/Popup.cs | 2 +- src/Perspex.Controls/Primitives/PopupRoot.cs | 9 -------- src/Perspex.Controls/ToolTip.cs | 2 +- src/Perspex.Controls/TopLevel.cs | 6 +++++ src/Windows/Perspex.Win32/PopupImpl.cs | 12 ---------- src/Windows/Perspex.Win32/WindowImpl.cs | 22 +++++++++++++++++++ src/iOS/Perspex.iOS/PerspexView.cs | 2 ++ 14 files changed, 61 insertions(+), 47 deletions(-) diff --git a/src/Android/Perspex.Android/Platform/SkiaPlatform/WindowImpl.cs b/src/Android/Perspex.Android/Platform/SkiaPlatform/WindowImpl.cs index 14bd084a37..ed853a4353 100644 --- a/src/Android/Perspex.Android/Platform/SkiaPlatform/WindowImpl.cs +++ b/src/Android/Perspex.Android/Platform/SkiaPlatform/WindowImpl.cs @@ -137,6 +137,8 @@ namespace Perspex.Android.Platform.SkiaPlatform //Not supported } + public Point Position { get; set; } + public IDisposable ShowDialog() { throw new NotImplementedException(); diff --git a/src/Gtk/Perspex.Gtk/PopupImpl.cs b/src/Gtk/Perspex.Gtk/PopupImpl.cs index 8e432e2448..63e34460b4 100644 --- a/src/Gtk/Perspex.Gtk/PopupImpl.cs +++ b/src/Gtk/Perspex.Gtk/PopupImpl.cs @@ -12,10 +12,5 @@ namespace Perspex.Gtk : base(WindowType.Popup) { } - - public void SetPosition(Point p) - { - Move((int)p.X, (int)p.Y); - } } } diff --git a/src/Gtk/Perspex.Gtk/WindowImpl.cs b/src/Gtk/Perspex.Gtk/WindowImpl.cs index 01054ae50b..0435a052ba 100644 --- a/src/Gtk/Perspex.Gtk/WindowImpl.cs +++ b/src/Gtk/Perspex.Gtk/WindowImpl.cs @@ -179,6 +179,20 @@ namespace Perspex.Gtk BeginResizeDrag((Gdk.WindowEdge) (int) edge, 1, x, y, 0); } + public Point Position + { + get + { + int x, y; + GetPosition(out x, out y); + return new Point(x, y); + } + set + { + Move((int)value.X, (int)value.Y); + } + } + public IDisposable ShowDialog() { Modal = true; diff --git a/src/Perspex.Controls/Platform/IPopupImpl.cs b/src/Perspex.Controls/Platform/IPopupImpl.cs index 495b16409c..5cbdafb9f9 100644 --- a/src/Perspex.Controls/Platform/IPopupImpl.cs +++ b/src/Perspex.Controls/Platform/IPopupImpl.cs @@ -8,15 +8,6 @@ namespace Perspex.Platform /// public interface IPopupImpl : ITopLevelImpl { - /// - /// Sets the position of the popup. - /// - /// The position, in screen coordinates. - void SetPosition(Point p); - /// - /// Hides the popup. - /// - void Hide(); } } diff --git a/src/Perspex.Controls/Platform/ITopLevelImpl.cs b/src/Perspex.Controls/Platform/ITopLevelImpl.cs index 0a11566c5a..7c80b951a5 100644 --- a/src/Perspex.Controls/Platform/ITopLevelImpl.cs +++ b/src/Perspex.Controls/Platform/ITopLevelImpl.cs @@ -89,6 +89,11 @@ namespace Perspex.Platform /// Shows the toplevel. /// void Show(); + + /// + /// Hides the window. + /// + void Hide(); /// /// Starts moving a window with left button being held. Should be called from left mouse button press event handler. @@ -100,5 +105,7 @@ namespace Perspex.Platform /// Should be called from left mouse button press event handler /// void BeginResizeDrag(WindowEdge edge); + + Point Position { get; set; } } } diff --git a/src/Perspex.Controls/Platform/IWindowImpl.cs b/src/Perspex.Controls/Platform/IWindowImpl.cs index e27d94b41e..cb266a9153 100644 --- a/src/Perspex.Controls/Platform/IWindowImpl.cs +++ b/src/Perspex.Controls/Platform/IWindowImpl.cs @@ -29,11 +29,6 @@ namespace Perspex.Platform /// IDisposable ShowDialog(); - /// - /// Hides the window. - /// - void Hide(); - /// /// Enables of disables system window decorations (title bar, buttons, etc) /// diff --git a/src/Perspex.Controls/Platform/PlatformManager.cs b/src/Perspex.Controls/Platform/PlatformManager.cs index fc582ab45d..6cec6cfadf 100644 --- a/src/Perspex.Controls/Platform/PlatformManager.cs +++ b/src/Perspex.Controls/Platform/PlatformManager.cs @@ -155,11 +155,6 @@ namespace Perspex.Controls.Platform set { _tl.Deactivated = value; } } - public void SetPosition(Point p) - { - _popup.SetPosition(p*ScalingFactor); - } - public void Dispose() => _tl.Dispose(); public IPlatformHandle Handle => _tl.Handle; @@ -177,6 +172,12 @@ namespace Perspex.Controls.Platform public void BeginMoveDrag() => _tl.BeginMoveDrag(); public void BeginResizeDrag(WindowEdge edge) => _tl.BeginResizeDrag(edge); + public Point Position + { + get { return _tl.Position; } + set { _tl.Position = value; } + } + public IDisposable ShowDialog() => _window.ShowDialog(); public void Hide() => _popup.Hide(); diff --git a/src/Perspex.Controls/Primitives/Popup.cs b/src/Perspex.Controls/Primitives/Popup.cs index e3f4183a1a..c074d26874 100644 --- a/src/Perspex.Controls/Primitives/Popup.cs +++ b/src/Perspex.Controls/Primitives/Popup.cs @@ -169,7 +169,7 @@ namespace Perspex.Controls.Primitives ((ISetLogicalParent)_popupRoot).SetParent(this); } - _popupRoot.SetPosition(GetPosition()); + _popupRoot.Position = GetPosition(); _popupRoot.AddHandler(PointerPressedEvent, MaybeClose, RoutingStrategies.Bubble, true); if (_topLevel != null) diff --git a/src/Perspex.Controls/Primitives/PopupRoot.cs b/src/Perspex.Controls/Primitives/PopupRoot.cs index b69af20c3f..9abf981642 100644 --- a/src/Perspex.Controls/Primitives/PopupRoot.cs +++ b/src/Perspex.Controls/Primitives/PopupRoot.cs @@ -65,15 +65,6 @@ namespace Perspex.Controls.Primitives /// IVisual IHostedVisualTreeRoot.Host => Parent; - /// - /// Sets the position of the popup in screen coordinates. - /// - /// The position. - public void SetPosition(Point p) - { - PlatformImpl.SetPosition(p); - } - /// /// Hides the popup. /// diff --git a/src/Perspex.Controls/ToolTip.cs b/src/Perspex.Controls/ToolTip.cs index 8be91f237b..559db90ff3 100644 --- a/src/Perspex.Controls/ToolTip.cs +++ b/src/Perspex.Controls/ToolTip.cs @@ -118,7 +118,7 @@ namespace Perspex.Controls var position = control.PointToScreen(cp ?? new Point(0, 0)) + new Vector(0, 22); ((ToolTip)s_popup.Content).Content = GetTip(control); - s_popup.SetPosition(position); + s_popup.Position = position; s_popup.Show(); s_current = control; diff --git a/src/Perspex.Controls/TopLevel.cs b/src/Perspex.Controls/TopLevel.cs index 12311df1c9..f167c62770 100644 --- a/src/Perspex.Controls/TopLevel.cs +++ b/src/Perspex.Controls/TopLevel.cs @@ -375,5 +375,11 @@ namespace Perspex.Controls /// Should be called from left mouse button press event handler /// public void BeginResizeDrag(WindowEdge edge) => PlatformImpl.BeginResizeDrag(edge); + + public Point Position + { + get { return PlatformImpl.Position; } + set { PlatformImpl.Position = value; } + } } } diff --git a/src/Windows/Perspex.Win32/PopupImpl.cs b/src/Windows/Perspex.Win32/PopupImpl.cs index 8161df4c65..2d73da88b0 100644 --- a/src/Windows/Perspex.Win32/PopupImpl.cs +++ b/src/Windows/Perspex.Win32/PopupImpl.cs @@ -9,18 +9,6 @@ namespace Perspex.Win32 { public class PopupImpl : WindowImpl, IPopupImpl { - public void SetPosition(Point p) - { - UnmanagedMethods.SetWindowPos( - Handle.Handle, - IntPtr.Zero, - (int)p.X, - (int)p.Y, - 0, - 0, - UnmanagedMethods.SetWindowPosFlags.SWP_NOSIZE | UnmanagedMethods.SetWindowPosFlags.SWP_NOACTIVATE); - } - public override void Show() { UnmanagedMethods.ShowWindow(Handle.Handle, UnmanagedMethods.ShowWindowCommand.ShowNoActivate); diff --git a/src/Windows/Perspex.Win32/WindowImpl.cs b/src/Windows/Perspex.Win32/WindowImpl.cs index ca1a2ae9c6..49383c10e3 100644 --- a/src/Windows/Perspex.Win32/WindowImpl.cs +++ b/src/Windows/Perspex.Win32/WindowImpl.cs @@ -247,6 +247,28 @@ namespace Perspex.Win32 new IntPtr((int) EdgeDic[edge]), IntPtr.Zero); } + public Point Position + { + get + { + UnmanagedMethods.RECT rc; + UnmanagedMethods.GetWindowRect(_hwnd, out rc); + return new Point(rc.left, rc.top); + } + set + { + UnmanagedMethods.SetWindowPos( + Handle.Handle, + IntPtr.Zero, + (int) value.X, + (int) value.Y, + 0, + 0, + UnmanagedMethods.SetWindowPosFlags.SWP_NOSIZE | UnmanagedMethods.SetWindowPosFlags.SWP_NOACTIVATE); + + } + } + public virtual IDisposable ShowDialog() { var disabled = s_instances.Where(x => x != this && x.IsEnabled).ToList(); diff --git a/src/iOS/Perspex.iOS/PerspexView.cs b/src/iOS/Perspex.iOS/PerspexView.cs index 92b2fac730..fca1e28a1c 100644 --- a/src/iOS/Perspex.iOS/PerspexView.cs +++ b/src/iOS/Perspex.iOS/PerspexView.cs @@ -118,6 +118,8 @@ namespace Perspex.iOS //Not supported } + public Point Position { get; set; } + public Size MaxClientSize => Bounds.Size.ToPerspex(); public void SetTitle(string title) { From 6d8263b778a44309cb52b0426894bb620b5c4630 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Manuel=20Nieto=20S=C3=A1nchez?= Date: Sun, 13 Dec 2015 22:12:23 +0100 Subject: [PATCH 108/211] Adaptation to newest changes in OmniXAML --- .../Perspex.Markup.Xaml/Context/PerspexObjectAssembler.cs | 2 ++ src/Markup/Perspex.Markup.Xaml/OmniXAML | 2 +- src/Markup/Perspex.Markup.Xaml/Perspex.Markup.Xaml.csproj | 4 ++++ 3 files changed, 7 insertions(+), 1 deletion(-) diff --git a/src/Markup/Perspex.Markup.Xaml/Context/PerspexObjectAssembler.cs b/src/Markup/Perspex.Markup.Xaml/Context/PerspexObjectAssembler.cs index 2a9a88219f..788bfd3ced 100644 --- a/src/Markup/Perspex.Markup.Xaml/Context/PerspexObjectAssembler.cs +++ b/src/Markup/Perspex.Markup.Xaml/Context/PerspexObjectAssembler.cs @@ -29,6 +29,8 @@ namespace Perspex.Markup.Xaml.Context public object Result => _objectAssembler.Result; + public InstanceLifeCycleHandler InstanceLifeCycleHandler { get; set; } = new InstanceLifeCycleHandler(); + public EventHandler XamlSetValueHandler { get; set; } public IWiringContext WiringContext => _objectAssembler.WiringContext; diff --git a/src/Markup/Perspex.Markup.Xaml/OmniXAML b/src/Markup/Perspex.Markup.Xaml/OmniXAML index f2673838c0..0904dcb071 160000 --- a/src/Markup/Perspex.Markup.Xaml/OmniXAML +++ b/src/Markup/Perspex.Markup.Xaml/OmniXAML @@ -1 +1 @@ -Subproject commit f2673838c0422ff0f6fdb3e4b34a5302971f59b5 +Subproject commit 0904dcb07175ca0cdf2ae1fda1434c0f1425a53e diff --git a/src/Markup/Perspex.Markup.Xaml/Perspex.Markup.Xaml.csproj b/src/Markup/Perspex.Markup.Xaml/Perspex.Markup.Xaml.csproj index 484363cd85..69b6143bbc 100644 --- a/src/Markup/Perspex.Markup.Xaml/Perspex.Markup.Xaml.csproj +++ b/src/Markup/Perspex.Markup.Xaml/Perspex.Markup.Xaml.csproj @@ -109,6 +109,7 @@ + @@ -119,6 +120,7 @@ + @@ -130,6 +132,7 @@ + @@ -141,6 +144,7 @@ + From d2bbe8e08e8fe71e7078cad6c0dabc9a547df38b Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Mon, 14 Dec 2015 22:22:20 +0000 Subject: [PATCH 109/211] Test event handling works as expected. --- .../InteractiveTests.cs | 46 +++++++++++++++++++ 1 file changed, 46 insertions(+) diff --git a/tests/Perspex.Interactivity.UnitTests/InteractiveTests.cs b/tests/Perspex.Interactivity.UnitTests/InteractiveTests.cs index 3a3d47947a..c26ecf0ecb 100644 --- a/tests/Perspex.Interactivity.UnitTests/InteractiveTests.cs +++ b/tests/Perspex.Interactivity.UnitTests/InteractiveTests.cs @@ -117,6 +117,52 @@ namespace Perspex.Interactivity.UnitTests invoked); } + [Fact] + public void Handled_Bubbled_Event_Should_Not_Propogate_Further() + { + var ev = new RoutedEvent("test", RoutingStrategies.Bubble, typeof(RoutedEventArgs), typeof(TestInteractive)); + var invoked = new List(); + + EventHandler handler = (s, e) => + { + var t = (TestInteractive)s; + invoked.Add(t.Name); + e.Handled = t.Name == "2b"; + }; + + var target = CreateTree(ev, handler, RoutingStrategies.Bubble); + + var args = new RoutedEventArgs(ev, target); + target.RaiseEvent(args); + + Assert.Equal(new[] { "2b" }, invoked); + } + + [Fact] + public void Handled_Tunnelled_Event_Should_Not_Propogate_Further() + { + var ev = new RoutedEvent( + "test", + RoutingStrategies.Bubble | RoutingStrategies.Tunnel, + typeof(RoutedEventArgs), + typeof(TestInteractive)); + var invoked = new List(); + + EventHandler handler = (s, e) => + { + var t = (TestInteractive)s; + invoked.Add(t.Name); + e.Handled = t.Name == "2b"; + }; + + var target = CreateTree(ev, handler, RoutingStrategies.Bubble | RoutingStrategies.Tunnel); + + var args = new RoutedEventArgs(ev, target); + target.RaiseEvent(args); + + Assert.Equal(new[] { "1", "2b" }, invoked); + } + [Fact] public void Direct_Subscription_Should_Not_Catch_Tunneling_Or_Bubbling() { From ef06e796674b206bcacdd38fb70de9028cae8d0e Mon Sep 17 00:00:00 2001 From: susloparov Date: Thu, 10 Dec 2015 18:57:25 +0600 Subject: [PATCH 110/211] First grid splitter implementation --- src/Perspex.Controls/GridSplitter.cs | 130 ++++++++++++++++++++++++--- 1 file changed, 120 insertions(+), 10 deletions(-) diff --git a/src/Perspex.Controls/GridSplitter.cs b/src/Perspex.Controls/GridSplitter.cs index 9a0ef0b988..cb77cb6aad 100644 --- a/src/Perspex.Controls/GridSplitter.cs +++ b/src/Perspex.Controls/GridSplitter.cs @@ -2,42 +2,152 @@ // Licensed under the MIT license. See licence.md file in the project root for full license information. using System; +using System.Collections.Generic; +using System.Linq; using Perspex.Controls.Primitives; using Perspex.Input; -using Perspex.Rendering; using Perspex.VisualTree; namespace Perspex.Controls { + /// + /// Unlike WPF GridSplitter, Perspex GridSplitter has only one Behavior. It's GridResizeBehavior.PreviousAndNext + /// public class GridSplitter : Thumb { private Grid _grid; + private DefinitionBase _prevDefinition; + + private DefinitionBase _nextDefinition; + + private bool _isResizingColumns; + + private List _definitions; + /// - /// Initializes a new instance of the class. + /// Decide depending on set size /// - public GridSplitter() + /// + private bool IsResizingColumns() + { + if (!double.IsNaN(Width)) + { + return true; + } + if (!double.IsNaN(Height)) + { + return false; + } + throw new InvalidOperationException("GridSpliter Should have width or height set. It affects whether columns or rows it resizes"); + } + + private double GetActualLength(DefinitionBase definition) { - Cursor = new Cursor(StandardCursorType.SizeWestEast); + var columnDefinition = definition as ColumnDefinition; + return columnDefinition?.ActualWidth ?? ((RowDefinition)definition).ActualHeight; } + private double GetMinLength(DefinitionBase definition) + { + var columnDefinition = definition as ColumnDefinition; + return columnDefinition?.MinWidth ?? ((RowDefinition)definition).MinHeight; + } + + private double GetMaxLength(DefinitionBase definition) + { + var columnDefinition = definition as ColumnDefinition; + return columnDefinition?.MaxWidth ?? ((RowDefinition)definition).MaxHeight; + } + + private bool IsStar(DefinitionBase definition) + { + var columnDefinition = definition as ColumnDefinition; + return columnDefinition?.Width.IsStar ?? ((RowDefinition)definition).Height.IsStar; + } + + private void SetLengthInStars(DefinitionBase definition, double value) + { + var columnDefinition = definition as ColumnDefinition; + if (columnDefinition != null) + { + columnDefinition.Width = new GridLength(value, GridUnitType.Star); + } + else + { + ((RowDefinition)definition).Height = new GridLength(value, GridUnitType.Star); + } + } + + + + + private void GetDeltaConstraints(out double min, out double max) + { + double prevDefinitionLen = GetActualLength(_prevDefinition); + double prevDefinitionMin = GetMinLength(_prevDefinition); + double prevDefinitionMax = GetMaxLength(_prevDefinition); + + double nextDefinitionLen = GetActualLength(_nextDefinition); + double nextDefinitionMin = GetMinLength(_nextDefinition); + double nextDefinitionMax = GetMaxLength(_nextDefinition); + + + // Determine the minimum and maximum the columns can be resized + min = -Math.Min(prevDefinitionLen - prevDefinitionMin, nextDefinitionMax - nextDefinitionLen); + max = Math.Min(prevDefinitionMax - prevDefinitionLen, nextDefinitionLen - nextDefinitionMin); + } + + protected override void OnDragDelta(VectorEventArgs e) { - int col = GetValue(Grid.ColumnProperty); + var delta = _isResizingColumns ? e.Vector.X : e.Vector.Y; + + double max; + double min; + GetDeltaConstraints(out min, out max); + delta = Math.Min(Math.Max(delta, min), max); - if (_grid != null && col > 0) + + foreach (var definition in _definitions) { - var size = _grid.ColumnDefinitions[col - 1].ActualWidth + e.Vector.X; - _grid.ColumnDefinitions[col - 1].Width = new GridLength( - Math.Max(0, size), - GridUnitType.Pixel); + if (definition == _prevDefinition) + { + SetLengthInStars(_prevDefinition, GetActualLength(_prevDefinition) + delta); + } + else if (definition == _nextDefinition) + { + SetLengthInStars(_nextDefinition, GetActualLength(_nextDefinition) - delta); + } + else if(IsStar(definition)) + { + SetLengthInStars(definition, GetActualLength(definition)); // same size but in stars + } } } protected override void OnAttachedToVisualTree(VisualTreeAttachmentEventArgs e) { + //new Range base.OnAttachedToVisualTree(e); _grid = this.GetVisualParent(); + _isResizingColumns = IsResizingColumns(); + if (_isResizingColumns) + { + Cursor = new Cursor(StandardCursorType.SizeWestEast); + var col = GetValue(Grid.ColumnProperty); + _definitions = _grid.ColumnDefinitions.Cast().ToList(); + _prevDefinition = _definitions[col - 1]; + _nextDefinition = _definitions[col + 1]; + } + else + { + Cursor = new Cursor(StandardCursorType.SizeNorthSouth); + var row = GetValue(Grid.RowProperty); + _definitions = _grid.RowDefinitions.Cast().ToList(); + _prevDefinition = _definitions[row - 1]; + _nextDefinition = _definitions[row + 1]; + } } } } From 981ec53d074c12a0d6dd2afb64ae6f04e789971f Mon Sep 17 00:00:00 2001 From: susloparov Date: Thu, 10 Dec 2015 20:28:38 +0600 Subject: [PATCH 111/211] Seperated GridSplitter into HorizontalGridSplitter and VerticalGridSplitter --- src/Perspex.Controls/GridSplitter.cs | 194 +++++++++++------- .../Views/LogicalTreeView.cs | 2 +- .../Views/VisualTreeView.cs | 2 +- src/Perspex.Themes.Default/DefaultTheme.paml | 4 +- ...itter.paml => HorizontalGridSplitter.paml} | 4 +- .../Perspex.Themes.Default.csproj | 5 +- .../VerticalGridSplitter.paml | 8 + 7 files changed, 138 insertions(+), 81 deletions(-) rename src/Perspex.Themes.Default/{GridSplitter.paml => HorizontalGridSplitter.paml} (53%) create mode 100644 src/Perspex.Themes.Default/VerticalGridSplitter.paml diff --git a/src/Perspex.Controls/GridSplitter.cs b/src/Perspex.Controls/GridSplitter.cs index cb77cb6aad..b4e76ad41e 100644 --- a/src/Perspex.Controls/GridSplitter.cs +++ b/src/Perspex.Controls/GridSplitter.cs @@ -2,8 +2,8 @@ // Licensed under the MIT license. See licence.md file in the project root for full license information. using System; -using System.Collections.Generic; -using System.Linq; + +using Perspex.Collections; using Perspex.Controls.Primitives; using Perspex.Input; using Perspex.VisualTree; @@ -13,17 +13,16 @@ namespace Perspex.Controls /// /// Unlike WPF GridSplitter, Perspex GridSplitter has only one Behavior. It's GridResizeBehavior.PreviousAndNext /// - public class GridSplitter : Thumb + public abstract class GridSplitterBase : Thumb + where T : DefinitionBase { - private Grid _grid; - - private DefinitionBase _prevDefinition; + protected PerspexList _definitions; - private DefinitionBase _nextDefinition; + protected Grid _grid; - private bool _isResizingColumns; + protected T _nextDefinition; - private List _definitions; + protected T _prevDefinition; /// /// Decide depending on set size @@ -42,73 +41,40 @@ namespace Perspex.Controls throw new InvalidOperationException("GridSpliter Should have width or height set. It affects whether columns or rows it resizes"); } - private double GetActualLength(DefinitionBase definition) - { - var columnDefinition = definition as ColumnDefinition; - return columnDefinition?.ActualWidth ?? ((RowDefinition)definition).ActualHeight; - } - - private double GetMinLength(DefinitionBase definition) - { - var columnDefinition = definition as ColumnDefinition; - return columnDefinition?.MinWidth ?? ((RowDefinition)definition).MinHeight; - } - - private double GetMaxLength(DefinitionBase definition) - { - var columnDefinition = definition as ColumnDefinition; - return columnDefinition?.MaxWidth ?? ((RowDefinition)definition).MaxHeight; - } - - private bool IsStar(DefinitionBase definition) - { - var columnDefinition = definition as ColumnDefinition; - return columnDefinition?.Width.IsStar ?? ((RowDefinition)definition).Height.IsStar; - } + protected abstract double GetActualLength(T definition); - private void SetLengthInStars(DefinitionBase definition, double value) - { - var columnDefinition = definition as ColumnDefinition; - if (columnDefinition != null) - { - columnDefinition.Width = new GridLength(value, GridUnitType.Star); - } - else - { - ((RowDefinition)definition).Height = new GridLength(value, GridUnitType.Star); - } - } + protected abstract double GetMinLength(T definition); + protected abstract double GetMaxLength(T definition); + protected abstract bool IsStar(T definition); + protected abstract void SetLengthInStars(T definition, double value); private void GetDeltaConstraints(out double min, out double max) { - double prevDefinitionLen = GetActualLength(_prevDefinition); - double prevDefinitionMin = GetMinLength(_prevDefinition); - double prevDefinitionMax = GetMaxLength(_prevDefinition); - - double nextDefinitionLen = GetActualLength(_nextDefinition); - double nextDefinitionMin = GetMinLength(_nextDefinition); - double nextDefinitionMax = GetMaxLength(_nextDefinition); + var prevDefinitionLen = GetActualLength(_prevDefinition); + var prevDefinitionMin = GetMinLength(_prevDefinition); + var prevDefinitionMax = GetMaxLength(_prevDefinition); + var nextDefinitionLen = GetActualLength(_nextDefinition); + var nextDefinitionMin = GetMinLength(_nextDefinition); + var nextDefinitionMax = GetMaxLength(_nextDefinition); // Determine the minimum and maximum the columns can be resized min = -Math.Min(prevDefinitionLen - prevDefinitionMin, nextDefinitionMax - nextDefinitionLen); max = Math.Min(prevDefinitionMax - prevDefinitionLen, nextDefinitionLen - nextDefinitionMin); } - protected override void OnDragDelta(VectorEventArgs e) { - var delta = _isResizingColumns ? e.Vector.X : e.Vector.Y; + var delta = GetDelta(e); double max; double min; GetDeltaConstraints(out min, out max); delta = Math.Min(Math.Max(delta, min), max); - foreach (var definition in _definitions) { if (definition == _prevDefinition) @@ -119,35 +85,115 @@ namespace Perspex.Controls { SetLengthInStars(_nextDefinition, GetActualLength(_nextDefinition) - delta); } - else if(IsStar(definition)) + else if (IsStar(definition)) { SetLengthInStars(definition, GetActualLength(definition)); // same size but in stars } } } + protected abstract double GetDelta(VectorEventArgs vectorEventArgs); + protected override void OnAttachedToVisualTree(VisualTreeAttachmentEventArgs e) { - //new Range base.OnAttachedToVisualTree(e); _grid = this.GetVisualParent(); - _isResizingColumns = IsResizingColumns(); - if (_isResizingColumns) - { - Cursor = new Cursor(StandardCursorType.SizeWestEast); - var col = GetValue(Grid.ColumnProperty); - _definitions = _grid.ColumnDefinitions.Cast().ToList(); - _prevDefinition = _definitions[col - 1]; - _nextDefinition = _definitions[col + 1]; - } - else - { - Cursor = new Cursor(StandardCursorType.SizeNorthSouth); - var row = GetValue(Grid.RowProperty); - _definitions = _grid.RowDefinitions.Cast().ToList(); - _prevDefinition = _definitions[row - 1]; - _nextDefinition = _definitions[row + 1]; - } } } -} + + public class HorizontalGridSplitter : GridSplitterBase + { + public HorizontalGridSplitter() + { + Cursor = new Cursor(StandardCursorType.SizeNorthSouth); + } + + protected override double GetActualLength(RowDefinition definition) + { + return definition.ActualHeight; + } + + protected override double GetMaxLength(RowDefinition definition) + { + return definition.MaxHeight; + } + + protected override double GetMinLength(RowDefinition definition) + { + return definition.MinHeight; + } + + protected override bool IsStar(RowDefinition definition) + { + return definition.Height.IsStar; + } + + protected override double GetDelta(VectorEventArgs vectorEventArgs) + { + return vectorEventArgs.Vector.Y; + } + + protected override void OnAttachedToVisualTree(VisualTreeAttachmentEventArgs e) + { + base.OnAttachedToVisualTree(e); + var row = GetValue(Grid.RowProperty); + _definitions = _grid.RowDefinitions; + _prevDefinition = _definitions[row - 1]; + _nextDefinition = _definitions[row + 1]; + } + + protected override void SetLengthInStars(RowDefinition definition, double value) + { + definition.Height = new GridLength(value, GridUnitType.Star); + } + } + + public class VerticalGridSplitter : GridSplitterBase + { + + + + public VerticalGridSplitter() + { + Cursor = new Cursor(StandardCursorType.SizeWestEast); + } + + protected override double GetActualLength(ColumnDefinition definition) + { + return definition.ActualWidth; + } + + protected override double GetMaxLength(ColumnDefinition definition) + { + return definition.MaxWidth; + } + + protected override double GetMinLength(ColumnDefinition definition) + { + return definition.MinWidth; + } + + protected override bool IsStar(ColumnDefinition definition) + { + return definition.Width.IsStar; + } + protected override double GetDelta(VectorEventArgs vectorEventArgs) + { + return vectorEventArgs.Vector.X; + } + + protected override void SetLengthInStars(ColumnDefinition definition, double value) + { + definition.Width = new GridLength(value, GridUnitType.Star); + } + + protected override void OnAttachedToVisualTree(VisualTreeAttachmentEventArgs e) + { + base.OnAttachedToVisualTree(e); + var col = GetValue(Grid.ColumnProperty); + _definitions = _grid.ColumnDefinitions; + _prevDefinition = _definitions[col - 1]; + _nextDefinition = _definitions[col + 1]; + } + } +} \ No newline at end of file diff --git a/src/Perspex.Diagnostics/Views/LogicalTreeView.cs b/src/Perspex.Diagnostics/Views/LogicalTreeView.cs index 276506bf09..434b7a574f 100644 --- a/src/Perspex.Diagnostics/Views/LogicalTreeView.cs +++ b/src/Perspex.Diagnostics/Views/LogicalTreeView.cs @@ -52,7 +52,7 @@ namespace Perspex.Diagnostics.Views }, [!ItemsControl.ItemsProperty] = this.WhenAnyValue(x => x.ViewModel.Nodes), }), - new GridSplitter + new VerticalGridSplitter { [Grid.ColumnProperty] = 1, Width = 4, diff --git a/src/Perspex.Diagnostics/Views/VisualTreeView.cs b/src/Perspex.Diagnostics/Views/VisualTreeView.cs index df7277e396..0ef992ead3 100644 --- a/src/Perspex.Diagnostics/Views/VisualTreeView.cs +++ b/src/Perspex.Diagnostics/Views/VisualTreeView.cs @@ -53,7 +53,7 @@ namespace Perspex.Diagnostics.Views }, [!ItemsControl.ItemsProperty] = this.WhenAnyValue(x => x.ViewModel.Nodes), }), - new GridSplitter + new VerticalGridSplitter { [Grid.ColumnProperty] = 1, Width = 4, diff --git a/src/Perspex.Themes.Default/DefaultTheme.paml b/src/Perspex.Themes.Default/DefaultTheme.paml index 5902e568b3..4dd9df8d32 100644 --- a/src/Perspex.Themes.Default/DefaultTheme.paml +++ b/src/Perspex.Themes.Default/DefaultTheme.paml @@ -1,12 +1,12 @@  - - + + diff --git a/src/Perspex.Themes.Default/GridSplitter.paml b/src/Perspex.Themes.Default/HorizontalGridSplitter.paml similarity index 53% rename from src/Perspex.Themes.Default/GridSplitter.paml rename to src/Perspex.Themes.Default/HorizontalGridSplitter.paml index ee0757a3f5..7cdd47fc30 100644 --- a/src/Perspex.Themes.Default/GridSplitter.paml +++ b/src/Perspex.Themes.Default/HorizontalGridSplitter.paml @@ -1,5 +1,5 @@ - From e7872e1a0c20e93c02fa9e7d707b2b2adc9455b7 Mon Sep 17 00:00:00 2001 From: susloparov Date: Thu, 10 Dec 2015 20:39:49 +0600 Subject: [PATCH 112/211] Added GridSplitter example to XamlTestApplication, Layout tab. --- .../XamlTestApplicationPcl/Views/MainWindow.paml | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/samples/XamlTestApplicationPcl/Views/MainWindow.paml b/samples/XamlTestApplicationPcl/Views/MainWindow.paml index c7680382d4..c815a4eb98 100644 --- a/samples/XamlTestApplicationPcl/Views/MainWindow.paml +++ b/samples/XamlTestApplicationPcl/Views/MainWindow.paml @@ -220,6 +220,19 @@ + + + + + + + + + + + + + From 0a4730d9ba21fa407b2cbd18a0cbf13883457653 Mon Sep 17 00:00:00 2001 From: susloparov Date: Thu, 10 Dec 2015 20:40:06 +0600 Subject: [PATCH 113/211] Styled GridSplitters --- .../HorizontalGridSplitter.paml | 18 +++++++++++++++++- .../VerticalGridSplitter.paml | 18 +++++++++++++++++- 2 files changed, 34 insertions(+), 2 deletions(-) diff --git a/src/Perspex.Themes.Default/HorizontalGridSplitter.paml b/src/Perspex.Themes.Default/HorizontalGridSplitter.paml index 7cdd47fc30..99236635ae 100644 --- a/src/Perspex.Themes.Default/HorizontalGridSplitter.paml +++ b/src/Perspex.Themes.Default/HorizontalGridSplitter.paml @@ -1,8 +1,24 @@  + + + + + + \ No newline at end of file diff --git a/src/Perspex.Themes.Default/VerticalGridSplitter.paml b/src/Perspex.Themes.Default/VerticalGridSplitter.paml index 1132840de1..0b61ad0fae 100644 --- a/src/Perspex.Themes.Default/VerticalGridSplitter.paml +++ b/src/Perspex.Themes.Default/VerticalGridSplitter.paml @@ -1,8 +1,24 @@  + + + + + + From dc757851c11bcf9b3e3befafc90b7a4c85886fa1 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Wed, 16 Dec 2015 00:05:28 +0000 Subject: [PATCH 114/211] Added failing Carousel leak test. As found by @donandren. --- tests/Perspex.LeakTests/ControlTests.cs | 12 ++--- tests/Perspex.LeakTests/StyleTests.cs | 59 +++++++++++++++++++++++++ 2 files changed, 65 insertions(+), 6 deletions(-) diff --git a/tests/Perspex.LeakTests/ControlTests.cs b/tests/Perspex.LeakTests/ControlTests.cs index 76351ac635..78dc8d764e 100644 --- a/tests/Perspex.LeakTests/ControlTests.cs +++ b/tests/Perspex.LeakTests/ControlTests.cs @@ -269,12 +269,12 @@ namespace Perspex.LeakTests Content = target = new TreeView { DataTemplates = new DataTemplates - { - new FuncTreeDataTemplate( - x => new TextBlock { Text = x.Name }, - x => x.Children, - x => true) - }, + { + new FuncTreeDataTemplate( + x => new TextBlock { Text = x.Name }, + x => x.Children, + x => true) + }, Items = nodes } }; diff --git a/tests/Perspex.LeakTests/StyleTests.cs b/tests/Perspex.LeakTests/StyleTests.cs index 82f8ebf1d5..30a4aef525 100644 --- a/tests/Perspex.LeakTests/StyleTests.cs +++ b/tests/Perspex.LeakTests/StyleTests.cs @@ -65,5 +65,64 @@ namespace Perspex.LeakTests dotMemory.Check(memory => Assert.Equal(0, memory.GetObjects(where => where.Type.Is()).ObjectsCount)); } + + [Fact] + public void Changing_Carousel_SelectedIndex_Should_Not_Leak_StyleActivators() + { + Func run = () => + { + Carousel target; + + var window = new Window + { + Styles = new Styles + { + new Style(x => x.OfType().Class("foo")) + { + Setters = new[] + { + new Setter(Visual.OpacityProperty, 0.5), + } + } + }, + Content = target = new Carousel + { + Items = new[] + { + new ContentControl + { + Name = "item1", + Classes = new Classes("foo"), + Content = "item1", + }, + new ContentControl + { + Name = "item2", + Classes = new Classes("foo"), + Content = "item2", + }, + } + } + }; + + // Do a layout and make sure that Carousel gets added to visual tree. + window.LayoutManager.ExecuteLayoutPass(); + Assert.IsType(window.Presenter.Child); + + target.SelectedIndex = 1; + window.LayoutManager.ExecuteLayoutPass(); + target.SelectedIndex = 0; + window.LayoutManager.ExecuteLayoutPass(); + target.SelectedIndex = 1; + window.LayoutManager.ExecuteLayoutPass(); + + return window; + }; + + var result = run(); + + dotMemory.Check(memory => + Assert.Equal(1, memory.GetObjects(where => where.Type.Is()).ObjectsCount)); + } } } From 7070ad51ed238a649229c7ef4682a482fda72534 Mon Sep 17 00:00:00 2001 From: susloparov Date: Wed, 16 Dec 2015 13:58:23 +0600 Subject: [PATCH 115/211] Merged Horizontal and Vertical Grid splitter back into single class --- .../Views/MainWindow.paml | 8 +- src/Perspex.Controls/GridSplitter.cs | 168 +++++++++++------- .../Views/LogicalTreeView.cs | 2 +- .../Views/VisualTreeView.cs | 2 +- src/Perspex.Themes.Default/DefaultTheme.paml | 3 +- src/Perspex.Themes.Default/GridSplitter.paml | 51 ++++++ .../HorizontalGridSplitter.paml | 24 --- .../Perspex.Themes.Default.csproj | 5 +- .../VerticalGridSplitter.paml | 24 --- 9 files changed, 164 insertions(+), 123 deletions(-) create mode 100644 src/Perspex.Themes.Default/GridSplitter.paml delete mode 100644 src/Perspex.Themes.Default/HorizontalGridSplitter.paml delete mode 100644 src/Perspex.Themes.Default/VerticalGridSplitter.paml diff --git a/samples/XamlTestApplicationPcl/Views/MainWindow.paml b/samples/XamlTestApplicationPcl/Views/MainWindow.paml index c815a4eb98..afc2d592e9 100644 --- a/samples/XamlTestApplicationPcl/Views/MainWindow.paml +++ b/samples/XamlTestApplicationPcl/Views/MainWindow.paml @@ -224,13 +224,13 @@ - + - + - + - + diff --git a/src/Perspex.Controls/GridSplitter.cs b/src/Perspex.Controls/GridSplitter.cs index b4e76ad41e..a5228d3862 100644 --- a/src/Perspex.Controls/GridSplitter.cs +++ b/src/Perspex.Controls/GridSplitter.cs @@ -11,62 +11,97 @@ using Perspex.VisualTree; namespace Perspex.Controls { /// - /// Unlike WPF GridSplitter, Perspex GridSplitter has only one Behavior. It's GridResizeBehavior.PreviousAndNext + /// Unlike WPF GridSplitter, Perspex GridSplitter has only one Behavior, GridResizeBehavior.PreviousAndNext /// - public abstract class GridSplitterBase : Thumb - where T : DefinitionBase + public class GridSplitter : Thumb { - protected PerspexList _definitions; + /// + /// Defines the property. + /// + public static readonly PerspexProperty OrientationProperty = + PerspexProperty.Register(nameof(Orientation)); protected Grid _grid; - protected T _nextDefinition; + private IGridColumnsResizer _resizer; + + /// + /// Gets or sets the orientation of the GridsSlitter, if null, it's inferred from column/row defenition(should be auto). + /// + public Orientation? Orientation { get { return GetValue(OrientationProperty); } set { SetValue(OrientationProperty, value); } } - protected T _prevDefinition; + + static GridSplitter() + { + PseudoClass(OrientationProperty, o => o == Perspex.Controls.Orientation.Vertical, ":vertical"); + PseudoClass(OrientationProperty, o => o == Perspex.Controls.Orientation.Horizontal, ":horizontal"); + } + + + protected override void OnDragDelta(VectorEventArgs e) + { + _resizer.DragDelta(e); + } /// - /// Decide depending on set size + /// If orientation is not set, method automatically calculates orientation based column/row auto size /// /// - private bool IsResizingColumns() + private void AutoSetOrientation() { - if (!double.IsNaN(Width)) + if (Orientation.HasValue) + { + return; + } + if (_grid.ColumnDefinitions[GetValue(Grid.ColumnProperty)].Width.IsAuto) { - return true; + Orientation = Perspex.Controls.Orientation.Vertical; + return; } - if (!double.IsNaN(Height)) + if (_grid.RowDefinitions[GetValue(Grid.RowProperty)].Height.IsAuto) { - return false; + Orientation = Perspex.Controls.Orientation.Horizontal; + return; } - throw new InvalidOperationException("GridSpliter Should have width or height set. It affects whether columns or rows it resizes"); + throw new InvalidOperationException("GridSpliter Should have Orientation, width or height set."); } - protected abstract double GetActualLength(T definition); - - protected abstract double GetMinLength(T definition); - - protected abstract double GetMaxLength(T definition); + protected override void OnAttachedToVisualTree(VisualTreeAttachmentEventArgs e) + { + _grid = this.GetVisualParent(); + AutoSetOrientation(); + switch (Orientation) + { + case Perspex.Controls.Orientation.Vertical: + _resizer = new VerticalColumnsResizer(_grid, GetValue(Grid.ColumnProperty)); + break; + case Perspex.Controls.Orientation.Horizontal: + _resizer = new HorizontalGridColumnsResizer(_grid, GetValue(Grid.RowProperty)); + break; + default: + throw new ArgumentOutOfRangeException(); + } + base.OnAttachedToVisualTree(e); + } + } - protected abstract bool IsStar(T definition); + internal interface IGridColumnsResizer + { + void DragDelta(VectorEventArgs e); + } - protected abstract void SetLengthInStars(T definition, double value); + internal abstract class GridColumnsResizer : IGridColumnsResizer + where T : DefinitionBase + { + protected PerspexList _definitions; - private void GetDeltaConstraints(out double min, out double max) - { - var prevDefinitionLen = GetActualLength(_prevDefinition); - var prevDefinitionMin = GetMinLength(_prevDefinition); - var prevDefinitionMax = GetMaxLength(_prevDefinition); + protected T _nextDefinition; - var nextDefinitionLen = GetActualLength(_nextDefinition); - var nextDefinitionMin = GetMinLength(_nextDefinition); - var nextDefinitionMax = GetMaxLength(_nextDefinition); + protected T _prevDefinition; - // Determine the minimum and maximum the columns can be resized - min = -Math.Min(prevDefinitionLen - prevDefinitionMin, nextDefinitionMax - nextDefinitionLen); - max = Math.Min(prevDefinitionMax - prevDefinitionLen, nextDefinitionLen - nextDefinitionMin); - } + public abstract Cursor Cursor { get; } - protected override void OnDragDelta(VectorEventArgs e) + public void DragDelta(VectorEventArgs e) { var delta = GetDelta(e); @@ -92,22 +127,45 @@ namespace Perspex.Controls } } + protected abstract double GetActualLength(T definition); + + protected abstract double GetMinLength(T definition); + + protected abstract double GetMaxLength(T definition); + + protected abstract bool IsStar(T definition); + + protected abstract void SetLengthInStars(T definition, double value); + protected abstract double GetDelta(VectorEventArgs vectorEventArgs); - protected override void OnAttachedToVisualTree(VisualTreeAttachmentEventArgs e) + protected void GetDeltaConstraints(out double min, out double max) { - base.OnAttachedToVisualTree(e); - _grid = this.GetVisualParent(); + var _prevDefinitionLen = GetActualLength(_prevDefinition); + var _prevDefinitionMin = GetMinLength(_prevDefinition); + var _prevDefinitionMax = GetMaxLength(_prevDefinition); + + var _nextDefinitionLen = GetActualLength(_nextDefinition); + var _nextDefinitionMin = GetMinLength(_nextDefinition); + var _nextDefinitionMax = GetMaxLength(_nextDefinition); + + // Determine the minimum and maximum the columns can be resized + min = -Math.Min(_prevDefinitionLen - _prevDefinitionMin, _nextDefinitionMax - _nextDefinitionLen); + max = Math.Min(_prevDefinitionMax - _prevDefinitionLen, _nextDefinitionLen - _nextDefinitionMin); } } - public class HorizontalGridSplitter : GridSplitterBase + internal class HorizontalGridColumnsResizer : GridColumnsResizer { - public HorizontalGridSplitter() + public HorizontalGridColumnsResizer(Grid _grid, int splitterRow) { - Cursor = new Cursor(StandardCursorType.SizeNorthSouth); + _definitions = _grid.RowDefinitions; + _nextDefinition = _definitions[splitterRow + 1]; + _prevDefinition = _definitions[splitterRow - 1]; } + public override Cursor Cursor => new Cursor(StandardCursorType.SizeNorthSouth); + protected override double GetActualLength(RowDefinition definition) { return definition.ActualHeight; @@ -133,31 +191,23 @@ namespace Perspex.Controls return vectorEventArgs.Vector.Y; } - protected override void OnAttachedToVisualTree(VisualTreeAttachmentEventArgs e) - { - base.OnAttachedToVisualTree(e); - var row = GetValue(Grid.RowProperty); - _definitions = _grid.RowDefinitions; - _prevDefinition = _definitions[row - 1]; - _nextDefinition = _definitions[row + 1]; - } - protected override void SetLengthInStars(RowDefinition definition, double value) { definition.Height = new GridLength(value, GridUnitType.Star); } } - public class VerticalGridSplitter : GridSplitterBase + internal class VerticalColumnsResizer : GridColumnsResizer { - - - - public VerticalGridSplitter() + public VerticalColumnsResizer(Grid _grid, int splitterColumn) { - Cursor = new Cursor(StandardCursorType.SizeWestEast); + _definitions = _grid.ColumnDefinitions; + _nextDefinition = _definitions[splitterColumn + 1]; + _prevDefinition = _definitions[splitterColumn - 1]; } + public override Cursor Cursor => new Cursor(StandardCursorType.SizeWestEast); + protected override double GetActualLength(ColumnDefinition definition) { return definition.ActualWidth; @@ -177,6 +227,7 @@ namespace Perspex.Controls { return definition.Width.IsStar; } + protected override double GetDelta(VectorEventArgs vectorEventArgs) { return vectorEventArgs.Vector.X; @@ -186,14 +237,5 @@ namespace Perspex.Controls { definition.Width = new GridLength(value, GridUnitType.Star); } - - protected override void OnAttachedToVisualTree(VisualTreeAttachmentEventArgs e) - { - base.OnAttachedToVisualTree(e); - var col = GetValue(Grid.ColumnProperty); - _definitions = _grid.ColumnDefinitions; - _prevDefinition = _definitions[col - 1]; - _nextDefinition = _definitions[col + 1]; - } } } \ No newline at end of file diff --git a/src/Perspex.Diagnostics/Views/LogicalTreeView.cs b/src/Perspex.Diagnostics/Views/LogicalTreeView.cs index 434b7a574f..276506bf09 100644 --- a/src/Perspex.Diagnostics/Views/LogicalTreeView.cs +++ b/src/Perspex.Diagnostics/Views/LogicalTreeView.cs @@ -52,7 +52,7 @@ namespace Perspex.Diagnostics.Views }, [!ItemsControl.ItemsProperty] = this.WhenAnyValue(x => x.ViewModel.Nodes), }), - new VerticalGridSplitter + new GridSplitter { [Grid.ColumnProperty] = 1, Width = 4, diff --git a/src/Perspex.Diagnostics/Views/VisualTreeView.cs b/src/Perspex.Diagnostics/Views/VisualTreeView.cs index 0ef992ead3..df7277e396 100644 --- a/src/Perspex.Diagnostics/Views/VisualTreeView.cs +++ b/src/Perspex.Diagnostics/Views/VisualTreeView.cs @@ -53,7 +53,7 @@ namespace Perspex.Diagnostics.Views }, [!ItemsControl.ItemsProperty] = this.WhenAnyValue(x => x.ViewModel.Nodes), }), - new VerticalGridSplitter + new GridSplitter { [Grid.ColumnProperty] = 1, Width = 4, diff --git a/src/Perspex.Themes.Default/DefaultTheme.paml b/src/Perspex.Themes.Default/DefaultTheme.paml index 4dd9df8d32..121cf86879 100644 --- a/src/Perspex.Themes.Default/DefaultTheme.paml +++ b/src/Perspex.Themes.Default/DefaultTheme.paml @@ -5,8 +5,7 @@ - - + diff --git a/src/Perspex.Themes.Default/GridSplitter.paml b/src/Perspex.Themes.Default/GridSplitter.paml new file mode 100644 index 0000000000..6c5f936ec2 --- /dev/null +++ b/src/Perspex.Themes.Default/GridSplitter.paml @@ -0,0 +1,51 @@ + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/Perspex.Themes.Default/HorizontalGridSplitter.paml b/src/Perspex.Themes.Default/HorizontalGridSplitter.paml deleted file mode 100644 index 99236635ae..0000000000 --- a/src/Perspex.Themes.Default/HorizontalGridSplitter.paml +++ /dev/null @@ -1,24 +0,0 @@ - - - - - - - - - - \ No newline at end of file diff --git a/src/Perspex.Themes.Default/Perspex.Themes.Default.csproj b/src/Perspex.Themes.Default/Perspex.Themes.Default.csproj index 700f41a2a2..2ec676592d 100644 --- a/src/Perspex.Themes.Default/Perspex.Themes.Default.csproj +++ b/src/Perspex.Themes.Default/Perspex.Themes.Default.csproj @@ -119,7 +119,7 @@ Designer - + Designer @@ -187,9 +187,6 @@ Designer - - - m>Pwva?2@cuT)_+Q{ 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 literal 0 HcmV?d00001 diff --git a/samples/ControlCatalog/Assets/hirsch-899118_640.jpg b/samples/ControlCatalog/Assets/hirsch-899118_640.jpg new file mode 100644 index 0000000000000000000000000000000000000000..af0eff6b47935353f0433e05ec65f94440a8cba4 GIT binary patch literal 216662 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

ay68^qTdCHAtjD>EBQK&4|1w=u`M zw25bZ6&M-(lEcfkDaRWO7lRB zN8EdCZQ2cW{wlK`HTZF-X;%wt;nndYKZvJ-(mysEEe_Q_#iz#PFjn7*$|zyDJ-Ym= zxg0gBIYZgY?K{6ti_cAceMzSudh^|_{{R(k+8#aOI~#TIhNL5= zTUVTk7}~*>IsNCABR19%q8H9YD*pgRd?$ dCK!FBSNIMvgxQ6pKW)ljVW6%a#*b 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 GKmXYYe*!oF literal 0 HcmV?d00001 diff --git a/samples/ControlCatalog/Assets/maple-leaf-888807_640.jpg b/samples/ControlCatalog/Assets/maple-leaf-888807_640.jpg new file mode 100644 index 0000000000000000000000000000000000000000..72bf0a61ae107229d39acc3e6da7b057c9c8635f GIT binary patch literal 113622 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 literal 0 HcmV?d00001 diff --git a/samples/ControlCatalog/ControlCatalog.csproj b/samples/ControlCatalog/ControlCatalog.csproj new file mode 100644 index 0000000000..189660101e --- /dev/null +++ b/samples/ControlCatalog/ControlCatalog.csproj @@ -0,0 +1,186 @@ + + + + + Debug + AnyCPU + {61BEC86C-F307-4295-B5B8-9428610D7D55} + WinExe + Properties + ControlCatalog + ControlCatalog + v4.6 + 512 + true + + + AnyCPU + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + + + AnyCPU + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + + + + + + + + + + + + + + + + + App.paml + + + MainWindow.paml + + + BorderPage.paml + + + CarouselPage.paml + + + CanvasPage.paml + + + ButtonPage.paml + + + + + + + Designer + + + Designer + + + Designer + + + Designer + + + Designer + + + Designer + + + Designer + + + + + {fb05ac90-89ba-4f2f-a924-f37875fb547c} + Perspex.Cairo + + + {54f237d5-a70a-4752-9656-0c70b1a7b047} + Perspex.Gtk + + + {3e53a01a-b331-47f3-b828-4a5717e77a24} + Perspex.Markup.Xaml + + + {6417e941-21bc-467b-a771-0de389353ce6} + Perspex.Markup + + + {d211e587-d8bc-45b9-95a4-f297c8fa5200} + Perspex.Animation + + + {799a7bb5-3c2c-48b6-85a7-406a12c420da} + Perspex.Application + + + {b09b78d8-9b26-48b0-9149-d64a2f120f3f} + Perspex.Base + + + {d2221c82-4a25-4583-9b43-d791e3f6820c} + Perspex.Controls + + + {7062ae20-5dcc-4442-9645-8195bdece63e} + Perspex.Diagnostics + + + {5fb2b005-0a7f-4dad-add4-3ed01444e63d} + Perspex.HtmlRenderer + + + {62024b2d-53eb-4638-b26b-85eeaa54866e} + Perspex.Input + + + {6b0ed19d-a08b-461c-a9d9-a9ee40b0c06b} + Perspex.Interactivity + + + {42472427-4774-4c81-8aff-9f27b8e31721} + Perspex.Layout + + + {6417b24e-49c2-4985-8db2-3ab9d898ec91} + Perspex.ReactiveUI + + + {eb582467-6abb-43a1-b052-e981ba910e3a} + Perspex.SceneGraph + + + {f1baa01a-f176-4c6a-b39d-5b40bb1b148f} + Perspex.Styling + + + {3e10a5fa-e8da-48b1-ad44-6a5b6cb7750f} + Perspex.Themes.Default + + + {3e908f67-5543-4879-a1dc-08eace79b3cd} + Perspex.Direct2D1 + + + {811a76cf-1cf6-440f-963b-bbe31bd72a82} + Perspex.Win32 + + + + + + + + + + + + \ No newline at end of file diff --git a/samples/ControlCatalog/MainWindow.paml b/samples/ControlCatalog/MainWindow.paml new file mode 100644 index 0000000000..b83321dd4d --- /dev/null +++ b/samples/ControlCatalog/MainWindow.paml @@ -0,0 +1,13 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/samples/ControlCatalog/MainWindow.paml.cs b/samples/ControlCatalog/MainWindow.paml.cs new file mode 100644 index 0000000000..5a5b7d4303 --- /dev/null +++ b/samples/ControlCatalog/MainWindow.paml.cs @@ -0,0 +1,19 @@ +using Perspex.Controls; +using Perspex.Markup.Xaml; + +namespace ControlCatalog +{ + public class MainWindow : Window + { + public MainWindow() + { + this.InitializeComponent(); + App.AttachDevTools(this); + } + + private void InitializeComponent() + { + PerspexXamlLoader.Load(this); + } + } +} diff --git a/samples/ControlCatalog/Pages/BorderPage.paml b/samples/ControlCatalog/Pages/BorderPage.paml new file mode 100644 index 0000000000..5ce9eb80f1 --- /dev/null +++ b/samples/ControlCatalog/Pages/BorderPage.paml @@ -0,0 +1,29 @@ + + + Border + A control which decorates a child with a border and background + + + + Border + + + Border and Background + + + Rounded Corners + + + Rounded Corners + + + + \ No newline at end of file diff --git a/samples/ControlCatalog/Pages/BorderPage.paml.cs b/samples/ControlCatalog/Pages/BorderPage.paml.cs new file mode 100644 index 0000000000..9cc6629599 --- /dev/null +++ b/samples/ControlCatalog/Pages/BorderPage.paml.cs @@ -0,0 +1,18 @@ +using Perspex.Controls; +using Perspex.Markup.Xaml; + +namespace ControlCatalog.Pages +{ + public class BorderPage : UserControl + { + public BorderPage() + { + this.InitializeComponent(); + } + + private void InitializeComponent() + { + PerspexXamlLoader.Load(this); + } + } +} diff --git a/samples/ControlCatalog/Pages/ButtonPage.paml b/samples/ControlCatalog/Pages/ButtonPage.paml new file mode 100644 index 0000000000..a5f99634aa --- /dev/null +++ b/samples/ControlCatalog/Pages/ButtonPage.paml @@ -0,0 +1,26 @@ + + + Button + A button control + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/samples/ControlCatalog/Pages/ButtonPage.paml.cs b/samples/ControlCatalog/Pages/ButtonPage.paml.cs new file mode 100644 index 0000000000..a0c424570b --- /dev/null +++ b/samples/ControlCatalog/Pages/ButtonPage.paml.cs @@ -0,0 +1,18 @@ +using Perspex.Controls; +using Perspex.Markup.Xaml; + +namespace ControlCatalog.Pages +{ + public class ButtonPage : UserControl + { + public ButtonPage() + { + this.InitializeComponent(); + } + + private void InitializeComponent() + { + PerspexXamlLoader.Load(this); + } + } +} diff --git a/samples/ControlCatalog/Pages/CanvasPage.paml b/samples/ControlCatalog/Pages/CanvasPage.paml new file mode 100644 index 0000000000..4a3f682d17 --- /dev/null +++ b/samples/ControlCatalog/Pages/CanvasPage.paml @@ -0,0 +1,13 @@ + + + Canvas + A panel which lays out its children by explicit coordinates + + + + + + + + + \ No newline at end of file diff --git a/samples/ControlCatalog/Pages/CanvasPage.paml.cs b/samples/ControlCatalog/Pages/CanvasPage.paml.cs new file mode 100644 index 0000000000..4264b11071 --- /dev/null +++ b/samples/ControlCatalog/Pages/CanvasPage.paml.cs @@ -0,0 +1,18 @@ +using Perspex.Controls; +using Perspex.Markup.Xaml; + +namespace ControlCatalog.Pages +{ + public class CanvasPage : UserControl + { + public CanvasPage() + { + this.InitializeComponent(); + } + + private void InitializeComponent() + { + PerspexXamlLoader.Load(this); + } + } +} diff --git a/samples/ControlCatalog/Pages/CarouselPage.paml b/samples/ControlCatalog/Pages/CarouselPage.paml new file mode 100644 index 0000000000..6f1243a005 --- /dev/null +++ b/samples/ControlCatalog/Pages/CarouselPage.paml @@ -0,0 +1,33 @@ + + + Carousel + An items control that displays its items as pages that fill the control. + + + + + + + + + + + + + + + + Transition + + None + Slide + Crossfade + + + + + \ No newline at end of file diff --git a/samples/ControlCatalog/Pages/CarouselPage.paml.cs b/samples/ControlCatalog/Pages/CarouselPage.paml.cs new file mode 100644 index 0000000000..540cb5cc87 --- /dev/null +++ b/samples/ControlCatalog/Pages/CarouselPage.paml.cs @@ -0,0 +1,22 @@ +using Perspex.Controls; +using Perspex.Markup.Xaml; + +namespace ControlCatalog.Pages +{ + public class CarouselPage : UserControl + { + private Carousel carousel; + private Button left; + private Button right; + + public CarouselPage() + { + this.InitializeComponent(); + } + + private void InitializeComponent() + { + PerspexXamlLoader.Load(this); + } + } +} diff --git a/samples/ControlCatalog/Properties/AssemblyInfo.cs b/samples/ControlCatalog/Properties/AssemblyInfo.cs new file mode 100644 index 0000000000..31f8a6272b --- /dev/null +++ b/samples/ControlCatalog/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("ControlCatalog")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("ControlCatalog")] +[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("61bec86c-f307-4295-b5b8-9428610d7d55")] + +// 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/samples/ControlCatalog/SideBar.paml b/samples/ControlCatalog/SideBar.paml new file mode 100644 index 0000000000..d9636cc36a --- /dev/null +++ b/samples/ControlCatalog/SideBar.paml @@ -0,0 +1,39 @@ + + + + + + + From 1eb0fbb3859f294dbb76eb4b8357542216323b9c Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Tue, 22 Dec 2015 23:07:39 +0000 Subject: [PATCH 143/211] Further simplify StyleActivator. The clever stuff was causing styles to fail and probably isn't needed anyhow. Fixes #371. --- src/Perspex.Styling/Styling/StyleActivator.cs | 14 +++---------- .../StyleActivatorTests.cs | 20 ------------------- 2 files changed, 3 insertions(+), 31 deletions(-) diff --git a/src/Perspex.Styling/Styling/StyleActivator.cs b/src/Perspex.Styling/Styling/StyleActivator.cs index 463fc5bb78..dd2601e0bb 100644 --- a/src/Perspex.Styling/Styling/StyleActivator.cs +++ b/src/Perspex.Styling/Styling/StyleActivator.cs @@ -19,17 +19,9 @@ namespace Perspex.Styling { public static IObservable And(IEnumerable> inputs) { - var sourceArray = inputs.Select(s => s.Publish().RefCount()).ToArray(); - - var terminate = sourceArray - .ToObservable() - .SelectMany(x => x.LastAsync() - .Where(y => y == false)); - - return sourceArray - .CombineLatest(values => values.All(x => x)) - .DistinctUntilChanged() - .TakeUntil(terminate); + return inputs.CombineLatest() + .Select(values => values.All(x => x)) + .DistinctUntilChanged(); } public static IObservable Or(IEnumerable> inputs) diff --git a/tests/Perspex.Styling.UnitTests/StyleActivatorTests.cs b/tests/Perspex.Styling.UnitTests/StyleActivatorTests.cs index 9669904af6..afd573dd9d 100644 --- a/tests/Perspex.Styling.UnitTests/StyleActivatorTests.cs +++ b/tests/Perspex.Styling.UnitTests/StyleActivatorTests.cs @@ -84,26 +84,6 @@ namespace Perspex.Styling.UnitTests Assert.Equal(1, inputs[2].SubscriberCount); } - [Fact] - public void Activator_And_Should_Complete_When_Input_Completes_On_False() - { - var inputs = new[] - { - new TestSubject(false), - new TestSubject(false), - new TestSubject(true), - }; - var target = StyleActivator.And(inputs); - var result = new TestObserver(); - var completed = false; - - target.Subscribe(_ => { }, () => completed = true); - inputs[0].OnNext(false); - inputs[0].OnCompleted(); - - Assert.True(completed); - } - [Fact] public void Activator_Or_Should_Follow_Single_Input() { From 9efbfc182efc08da2013461d45536daee52755af Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Tue, 22 Dec 2015 23:51:50 +0000 Subject: [PATCH 144/211] Use IBitmap for Image.Source And register converter for that interface. This is to conform with ImageBrush. --- .../Perspex.Markup.Xaml/Context/PerspexWiringContext.cs | 2 +- src/Perspex.Controls/Image.cs | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Markup/Perspex.Markup.Xaml/Context/PerspexWiringContext.cs b/src/Markup/Perspex.Markup.Xaml/Context/PerspexWiringContext.cs index ab16826dc6..040ef313ba 100644 --- a/src/Markup/Perspex.Markup.Xaml/Context/PerspexWiringContext.cs +++ b/src/Markup/Perspex.Markup.Xaml/Context/PerspexWiringContext.cs @@ -88,7 +88,7 @@ namespace Perspex.Markup.Xaml.Context var typeConverterProvider = new TypeConverterProvider(); var converters = new[] { - new TypeConverterRegistration(typeof(Bitmap), new BitmapTypeConverter()), + new TypeConverterRegistration(typeof(IBitmap), new BitmapTypeConverter()), new TypeConverterRegistration(typeof(Brush), new BrushTypeConverter()), new TypeConverterRegistration(typeof(Color), new ColorTypeConverter()), new TypeConverterRegistration(typeof(Classes), new ClassesTypeConverter()), diff --git a/src/Perspex.Controls/Image.cs b/src/Perspex.Controls/Image.cs index 0b316689bd..26dc930a06 100644 --- a/src/Perspex.Controls/Image.cs +++ b/src/Perspex.Controls/Image.cs @@ -27,7 +27,7 @@ namespace Perspex.Controls ///

/// Gets or sets the bitmap image that will be displayed. /// - public Bitmap Source + public IBitmap Source { get { return GetValue(SourceProperty); } set { SetValue(SourceProperty, value); } @@ -48,7 +48,7 @@ namespace Perspex.Controls /// The drawing context. public override void Render(DrawingContext context) { - Bitmap source = Source; + var source = Source; if (source != null) { From 9ad94743c089053492147723d75bd1386abadad4 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Wed, 23 Dec 2015 00:29:27 +0000 Subject: [PATCH 145/211] Fix small layout bug with UseLayoutRounding. --- src/Perspex.Layout/Layoutable.cs | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/Perspex.Layout/Layoutable.cs b/src/Perspex.Layout/Layoutable.cs index e09917b12b..c751e80cdc 100644 --- a/src/Perspex.Layout/Layoutable.cs +++ b/src/Perspex.Layout/Layoutable.cs @@ -539,7 +539,12 @@ namespace Perspex.Layout if (UseLayoutRounding) { - size = new Size(Math.Ceiling(size.Width), Math.Ceiling(size.Height)); + size = new Size( + Math.Ceiling(size.Width), + Math.Ceiling(size.Height)); + sizeMinusMargins = new Size( + Math.Ceiling(sizeMinusMargins.Width), + Math.Ceiling(sizeMinusMargins.Height)); } size = ArrangeOverride(size).Constrain(size); From fe7fc66cc5ad95fc9ae418804e722f6efe5409cf Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Wed, 23 Dec 2015 01:01:45 +0000 Subject: [PATCH 146/211] Fixed GridSplitter in DevTools. --- src/Perspex.Diagnostics/Views/LogicalTreeView.cs | 3 ++- src/Perspex.Diagnostics/Views/VisualTreeView.cs | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/src/Perspex.Diagnostics/Views/LogicalTreeView.cs b/src/Perspex.Diagnostics/Views/LogicalTreeView.cs index 276506bf09..21038ad50f 100644 --- a/src/Perspex.Diagnostics/Views/LogicalTreeView.cs +++ b/src/Perspex.Diagnostics/Views/LogicalTreeView.cs @@ -54,8 +54,9 @@ namespace Perspex.Diagnostics.Views }), new GridSplitter { - [Grid.ColumnProperty] = 1, Width = 4, + Orientation = Orientation.Vertical, + [Grid.ColumnProperty] = 1, }, new ContentControl { diff --git a/src/Perspex.Diagnostics/Views/VisualTreeView.cs b/src/Perspex.Diagnostics/Views/VisualTreeView.cs index df7277e396..4b187d00a0 100644 --- a/src/Perspex.Diagnostics/Views/VisualTreeView.cs +++ b/src/Perspex.Diagnostics/Views/VisualTreeView.cs @@ -55,8 +55,9 @@ namespace Perspex.Diagnostics.Views }), new GridSplitter { - [Grid.ColumnProperty] = 1, Width = 4, + Orientation = Orientation.Vertical, + [Grid.ColumnProperty] = 1, }, new ContentControl { From c98254025f7b0dadac5791d851253e55807837cf Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Wed, 23 Dec 2015 01:19:15 +0000 Subject: [PATCH 147/211] Simplified TabControl style. --- src/Perspex.Themes.Default/DefaultTheme.paml | 3 +- src/Perspex.Themes.Default/TabControl.paml | 119 ++++++------------- 2 files changed, 40 insertions(+), 82 deletions(-) diff --git a/src/Perspex.Themes.Default/DefaultTheme.paml b/src/Perspex.Themes.Default/DefaultTheme.paml index 9d456d4af9..4c5b221206 100644 --- a/src/Perspex.Themes.Default/DefaultTheme.paml +++ b/src/Perspex.Themes.Default/DefaultTheme.paml @@ -17,9 +17,10 @@ - + + diff --git a/src/Perspex.Themes.Default/TabControl.paml b/src/Perspex.Themes.Default/TabControl.paml index b678304a87..e02c385127 100644 --- a/src/Perspex.Themes.Default/TabControl.paml +++ b/src/Perspex.Themes.Default/TabControl.paml @@ -1,93 +1,50 @@  - - - + - \ No newline at end of file From 2e161e5fcb9f6887cd7da183ef5ecf75a9bcb43a Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Wed, 23 Dec 2015 01:41:23 +0000 Subject: [PATCH 148/211] Fixed up control catalog sidebar. --- samples/ControlCatalog/SideBar.paml | 31 ++++++++++---------- src/Perspex.Themes.Default/TabStripItem.paml | 6 +++- 2 files changed, 21 insertions(+), 16 deletions(-) diff --git a/samples/ControlCatalog/SideBar.paml b/samples/ControlCatalog/SideBar.paml index d9636cc36a..9c71fbeaa0 100644 --- a/samples/ControlCatalog/SideBar.paml +++ b/samples/ControlCatalog/SideBar.paml @@ -2,38 +2,39 @@ - -
diff --git a/src/Perspex.Themes.Default/TabStripItem.paml b/src/Perspex.Themes.Default/TabStripItem.paml index 08dd0bb932..38ad8d139d 100644 --- a/src/Perspex.Themes.Default/TabStripItem.paml +++ b/src/Perspex.Themes.Default/TabStripItem.paml @@ -4,9 +4,13 @@ - + + From 7ab944c0a5b7db49d605706cba9dd0b924c33978 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Thu, 24 Dec 2015 00:26:33 +0000 Subject: [PATCH 149/211] Make GridSplitter.Orientation non-nullable. As OmniXAML can't handle it. --- .../Views/MainWindow.paml | 8 ++--- src/Perspex.Controls/GridSplitter.cs | 36 ++++--------------- 2 files changed, 10 insertions(+), 34 deletions(-) diff --git a/samples/XamlTestApplicationPcl/Views/MainWindow.paml b/samples/XamlTestApplicationPcl/Views/MainWindow.paml index 945b9cb1b0..98633354b1 100644 --- a/samples/XamlTestApplicationPcl/Views/MainWindow.paml +++ b/samples/XamlTestApplicationPcl/Views/MainWindow.paml @@ -225,13 +225,13 @@ - + - + - + - + diff --git a/src/Perspex.Controls/GridSplitter.cs b/src/Perspex.Controls/GridSplitter.cs index 39e235cda3..57d77e8f8d 100644 --- a/src/Perspex.Controls/GridSplitter.cs +++ b/src/Perspex.Controls/GridSplitter.cs @@ -23,8 +23,8 @@ namespace Perspex.Controls /// /// Defines the property. /// - public static readonly PerspexProperty OrientationProperty = - PerspexProperty.Register(nameof(Orientation)); + public static readonly PerspexProperty OrientationProperty = + PerspexProperty.Register(nameof(Orientation)); protected Grid _grid; @@ -42,7 +42,7 @@ namespace Perspex.Controls /// /// if null, it's inferred from column/row definition (should be auto). /// - public Orientation? Orientation { + public Orientation Orientation { get { return GetValue(OrientationProperty); @@ -75,7 +75,7 @@ namespace Perspex.Controls protected override void OnDragDelta(VectorEventArgs e) { - var delta = Orientation.Value == Perspex.Controls.Orientation.Vertical ? e.Vector.X : e.Vector.Y; + var delta = Orientation == Orientation.Vertical ? e.Vector.X : e.Vector.Y; double max; double min; GetDeltaConstraints(out min, out max); @@ -97,30 +97,6 @@ namespace Perspex.Controls } } - /// - /// If orientation is not set, method automatically calculates orientation based column/row auto size. - /// - private void AutoSetOrientation() - { - if (Orientation.HasValue) - { - return; - } - var column = GetValue(Grid.ColumnProperty); - if (column < _grid.ColumnDefinitions.Count && _grid.ColumnDefinitions[column].Width.IsAuto) - { - Orientation = Perspex.Controls.Orientation.Vertical; - return; - } - var row = GetValue(Grid.RowProperty); - if (row < _grid.RowDefinitions.Count && _grid.RowDefinitions[row].Height.IsAuto) - { - Orientation = Perspex.Controls.Orientation.Horizontal; - return; - } - throw new InvalidOperationException("GridSpliter Should have Orientation, width or height set."); - } - private double GetActualLength(DefinitionBase definition) { var columnDefinition = definition as ColumnDefinition; @@ -162,8 +138,8 @@ namespace Perspex.Controls { base.OnAttachedToVisualTree(e); _grid = this.GetVisualParent(); - AutoSetOrientation(); - if (Orientation.Value == Perspex.Controls.Orientation.Vertical) + + if (Orientation == Orientation.Vertical) { Cursor = new Cursor(StandardCursorType.SizeWestEast); var col = GetValue(Grid.ColumnProperty); From ce1ef2fd892a7133123a01b83af3806952418f4c Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Thu, 24 Dec 2015 00:55:41 +0000 Subject: [PATCH 150/211] Make Carousel demo buttons work. --- .../ControlCatalog/Pages/CarouselPage.paml.cs | 11 +++++++--- src/Perspex.Controls/Carousel.cs | 22 +++++++++++++++++++ 2 files changed, 30 insertions(+), 3 deletions(-) diff --git a/samples/ControlCatalog/Pages/CarouselPage.paml.cs b/samples/ControlCatalog/Pages/CarouselPage.paml.cs index 540cb5cc87..b5ec05f51e 100644 --- a/samples/ControlCatalog/Pages/CarouselPage.paml.cs +++ b/samples/ControlCatalog/Pages/CarouselPage.paml.cs @@ -5,18 +5,23 @@ namespace ControlCatalog.Pages { public class CarouselPage : UserControl { - private Carousel carousel; - private Button left; - private Button right; + private Carousel _carousel; + private Button _left; + private Button _right; public CarouselPage() { this.InitializeComponent(); + _left.Click += (s, e) => _carousel.Previous(); + _right.Click += (s, e) => _carousel.Next(); } private void InitializeComponent() { PerspexXamlLoader.Load(this); + _carousel = this.FindControl("carousel"); + _left = this.FindControl
public class AssetLoader : IAssetLoader { - private static readonly Dictionary AssemblyNameCache - = new Dictionary(); + class AssemblyDescriptor + { + public AssemblyDescriptor(Assembly assembly) + { + Assembly = assembly; + Resources = assembly.GetManifestResourceNames() + .ToDictionary(n => n, n => (IAssetDescriptor)new AssemblyResourceDescriptor(assembly, n)); + Name = assembly.GetName().Name; + } + + public Assembly Assembly { get; } + public Dictionary Resources { get; } + public string Name { get; } + } + - private readonly Assembly _defaultAssembly; + private static readonly Dictionary AssemblyNameCache + = new Dictionary(); + + private readonly AssemblyDescriptor _defaultAssembly; public AssetLoader(Assembly assembly = null) { - _defaultAssembly = assembly; + if (assembly == null) + assembly = Assembly.GetEntryAssembly(); + _defaultAssembly = new AssemblyDescriptor(assembly); } + + - static Assembly GetAssembly(string name) + AssemblyDescriptor GetAssembly(string name) { - Assembly rv; + if (name == null) + return _defaultAssembly; + AssemblyDescriptor rv; if (!AssemblyNameCache.TryGetValue(name, out rv)) AssemblyNameCache[name] = rv = - AppDomain.CurrentDomain.GetAssemblies().FirstOrDefault(a => a.GetName().Name == name) - ?? Assembly.Load(name); + new AssemblyDescriptor(AppDomain.CurrentDomain.GetAssemblies() + .FirstOrDefault(a => a.GetName().Name == name) + ?? Assembly.Load(name)); return rv; } + interface IAssetDescriptor + { + Stream GetStream(); + } + + + class AssemblyResourceDescriptor : IAssetDescriptor + { + private readonly Assembly _asm; + private readonly string _name; + + public AssemblyResourceDescriptor(Assembly asm, string name) + { + _asm = asm; + _name = name; + } + + public Stream GetStream() + { + return _asm.GetManifestResourceStream(_name); + } + } + + + IAssetDescriptor GetAsset(Uri uri) + { + if (!uri.IsAbsoluteUri || uri.Scheme == "resm") + { + var qs = uri.Query.TrimStart('?') + .Split('&') + .Select(p => p.Split('=')) + .ToDictionary(p => p[0], p => p[1]); + //TODO: Replace _defaultAssembly by current one (need support from OmniXAML) + var asm = _defaultAssembly; + if (qs.ContainsKey("assembly")) + asm = GetAssembly(qs["assembly"]); + + IAssetDescriptor rv; + asm.Resources.TryGetValue(uri.AbsolutePath, out rv); + return rv; + } + throw new ArgumentException($"Invalid uri, see https://github.com/Perspex/Perspex/issues/282#issuecomment-166982104", nameof(uri)); + } + /// /// Checks if an asset with the specified URI exists. /// @@ -42,11 +109,7 @@ namespace Perspex.Shared.PlatformSupport /// True if the asset could be found; otherwise false. public bool Exists(Uri uri) { - var parts = uri.AbsolutePath.Split(new[] { '/' }, StringSplitOptions.RemoveEmptyEntries); - var asm = parts.Length == 1 ? (_defaultAssembly ?? Assembly.GetEntryAssembly()) : GetAssembly(parts[0]); - var typeName = parts[parts.Length == 1 ? 0 : 1]; - var rv = asm.GetManifestResourceStream(typeName); - return rv != null; + return GetAsset(uri) != null; } /// @@ -59,18 +122,10 @@ namespace Perspex.Shared.PlatformSupport /// public Stream Open(Uri uri) { - var parts = uri.AbsolutePath.Split(new[] {'/'}, StringSplitOptions.RemoveEmptyEntries); - var asm = parts.Length == 1 ? (_defaultAssembly ?? Assembly.GetEntryAssembly()) : GetAssembly(parts[0]); - var typeName = parts[parts.Length == 1 ? 0 : 1]; - var rv = asm.GetManifestResourceStream(typeName); - if (rv == null) - { -#if DEBUG - var names = asm.GetManifestResourceNames().ToList(); -#endif + var asset = GetAsset(uri); + if (asset == null) throw new FileNotFoundException($"The resource {uri} could not be found."); - } - return rv; + return asset.GetStream(); } } } From c6582d83c31963008b03f77d0b740e222bf1180c Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Sat, 26 Dec 2015 17:53:13 +0000 Subject: [PATCH 153/211] Merged the two separate TemplatedControlTests. --- .../Perspex.Controls.UnitTests.csproj | 1 - .../Primitives/TemplatedControlTests.cs | 145 +++++++--- .../TemplatedControlTests.cs | 272 ------------------ 3 files changed, 107 insertions(+), 311 deletions(-) delete mode 100644 tests/Perspex.Controls.UnitTests/TemplatedControlTests.cs diff --git a/tests/Perspex.Controls.UnitTests/Perspex.Controls.UnitTests.csproj b/tests/Perspex.Controls.UnitTests/Perspex.Controls.UnitTests.csproj index 5b33890f2a..c2535d887e 100644 --- a/tests/Perspex.Controls.UnitTests/Perspex.Controls.UnitTests.csproj +++ b/tests/Perspex.Controls.UnitTests/Perspex.Controls.UnitTests.csproj @@ -122,7 +122,6 @@ - diff --git a/tests/Perspex.Controls.UnitTests/Primitives/TemplatedControlTests.cs b/tests/Perspex.Controls.UnitTests/Primitives/TemplatedControlTests.cs index 40d8e1c0c0..d4d20d3d9d 100644 --- a/tests/Perspex.Controls.UnitTests/Primitives/TemplatedControlTests.cs +++ b/tests/Perspex.Controls.UnitTests/Primitives/TemplatedControlTests.cs @@ -4,11 +4,12 @@ using System; using System.Collections.Generic; using System.Linq; -using Perspex.Collections; +using Moq; using Perspex.Controls.Presenters; using Perspex.Controls.Primitives; using Perspex.Controls.Templates; using Perspex.LogicalTree; +using Perspex.Styling; using Perspex.VisualTree; using Xunit; @@ -17,7 +18,47 @@ namespace Perspex.Controls.UnitTests.Primitives public class TemplatedControlTests { [Fact] - public void ApplyTemplate_Should_Create_Visual_Child() + public void Template_Doesnt_Get_Executed_On_Set() + { + bool executed = false; + + var template = new FuncControlTemplate(_ => + { + executed = true; + return new Control(); + }); + + var target = new TemplatedControl + { + Template = template, + }; + + Assert.False(executed); + } + + [Fact] + public void Template_Gets_Executed_On_Measure() + { + bool executed = false; + + var template = new FuncControlTemplate(_ => + { + executed = true; + return new Control(); + }); + + var target = new TemplatedControl + { + Template = template, + }; + + target.Measure(new Size(100, 100)); + + Assert.True(executed); + } + + [Fact] + public void ApplyTemplate_Should_Create_Visual_Children() { var target = new TemplatedControl { @@ -119,53 +160,81 @@ namespace Perspex.Controls.UnitTests.Primitives } [Fact] - public void Nested_TemplatedControls_Should_Be_Expanded_And_Have_Correct_TemplatedParent() + public void Nested_Templated_Controls_Have_Correct_TemplatedParent() { - var target = new ItemsControl + var target = new TestTemplatedControl { - Template = new FuncControlTemplate(ItemsControlTemplate), - Items = new[] { "Foo", } + Template = new FuncControlTemplate(_ => + { + return new ContentControl + { + Template = new FuncControlTemplate(parent => + { + return new Border + { + Child = new ContentPresenter + { + [~ContentPresenter.ContentProperty] = parent.GetObservable(ContentControl.ContentProperty), + } + }; + }), + Content = new TextBlock + { + } + }; + }), }; target.ApplyTemplate(); - var scrollViewer = target.GetVisualDescendents() - .OfType() - .Single(); - var types = target.GetVisualDescendents() - .Select(x => x.GetType()) - .ToList(); - var templatedParents = target.GetVisualDescendents() - .OfType() - .Select(x => x.TemplatedParent) - .ToList(); + var contentControl = target.GetTemplateChildren().OfType().Single(); + var border = contentControl.GetTemplateChildren().OfType().Single(); + var presenter = contentControl.GetTemplateChildren().OfType().Single(); + var textBlock = (TextBlock)presenter.Content; - Assert.Equal( - new[] - { - typeof(Border), - typeof(ScrollViewer), - typeof(ScrollContentPresenter), - typeof(ItemsPresenter), - typeof(StackPanel), - typeof(TextBlock), - }, - types); + Assert.Equal(target, contentControl.TemplatedParent); + Assert.Equal(contentControl, border.TemplatedParent); + Assert.Equal(contentControl, presenter.TemplatedParent); + Assert.Equal(target, textBlock.TemplatedParent); + } - Assert.Equal( - new object[] + [Fact] + public void Templated_Children_Should_Be_Styled() + { + using (PerspexLocator.EnterScope()) + { + var styler = new Mock(); + + PerspexLocator.CurrentMutable.Bind().ToConstant(styler.Object); + + TestTemplatedControl target; + + var root = new TestRoot { - target, - target, - scrollViewer, - target, - target, - null - }, - templatedParents); - } + Child = target = new TestTemplatedControl + { + Template = new FuncControlTemplate(_ => + { + return new StackPanel + { + Children = new Controls + { + new TextBlock + { + } + } + }; + }), + } + }; + target.ApplyTemplate(); + styler.Verify(x => x.ApplyStyles(It.IsAny()), Times.Once()); + styler.Verify(x => x.ApplyStyles(It.IsAny()), Times.Once()); + styler.Verify(x => x.ApplyStyles(It.IsAny()), Times.Once()); + } + } [Fact] public void Nested_TemplatedControls_Should_Register_With_Correct_NameScope() diff --git a/tests/Perspex.Controls.UnitTests/TemplatedControlTests.cs b/tests/Perspex.Controls.UnitTests/TemplatedControlTests.cs deleted file mode 100644 index dd6ef2e858..0000000000 --- a/tests/Perspex.Controls.UnitTests/TemplatedControlTests.cs +++ /dev/null @@ -1,272 +0,0 @@ -// 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.Linq; -using Moq; -using Perspex.Controls; -using Perspex.Controls.Presenters; -using Perspex.Controls.Primitives; -using Perspex.Controls.Templates; -using Perspex.Styling; -using Perspex.VisualTree; -using Xunit; - -namespace Perspex.Controls.UnitTests -{ - public class TemplatedControlTests - { - [Fact] - public void Template_Doesnt_Get_Executed_On_Set() - { - bool executed = false; - - var template = new FuncControlTemplate(_ => - { - executed = true; - return new Control(); - }); - - var target = new TemplatedControl - { - Template = template, - }; - - Assert.False(executed); - } - - [Fact] - public void Template_Gets_Executed_On_Measure() - { - bool executed = false; - - var template = new FuncControlTemplate(_ => - { - executed = true; - return new Control(); - }); - - var target = new TemplatedControl - { - Template = template, - }; - - target.Measure(new Size(100, 100)); - - Assert.True(executed); - } - - [Fact] - public void Template_Result_Becomes_Visual_Child() - { - Control templateResult = new Control(); - - var template = new FuncControlTemplate(_ => - { - return templateResult; - }); - - var target = new TemplatedControl - { - Template = template, - }; - - target.Measure(new Size(100, 100)); - var children = target.GetVisualChildren().ToList(); - - Assert.Equal(new[] { templateResult }, children); - } - - [Fact] - public void TemplatedParent_Is_Set_On_Generated_Template() - { - Control templateResult = new Control(); - - var template = new FuncControlTemplate(_ => - { - return templateResult; - }); - - var target = new TemplatedControl - { - Template = template, - }; - - target.Measure(new Size(100, 100)); - - Assert.Equal(target, templateResult.TemplatedParent); - } - - [Fact] - public void OnTemplateApplied_Is_Called() - { - var target = new TestTemplatedControl - { - Template = new FuncControlTemplate(_ => - { - return new Control(); - }) - }; - - target.Measure(new Size(100, 100)); - - Assert.True(target.OnTemplateAppliedCalled); - } - - [Fact] - public void Template_Should_Be_Instantated() - { - var target = new TestTemplatedControl - { - Template = new FuncControlTemplate(_ => - { - return new StackPanel - { - Children = new Controls - { - new TextBlock - { - } - } - }; - }), - }; - - target.ApplyTemplate(); - - var child = target.GetVisualChildren().Single(); - Assert.IsType(child); - child = child.VisualChildren.Single(); - Assert.IsType(child); - } - - [Fact] - public void Templated_Children_Should_Be_Styled() - { - using (PerspexLocator.EnterScope()) - { - var styler = new Mock(); - - PerspexLocator.CurrentMutable.Bind().ToConstant(styler.Object); - - TestTemplatedControl target; - - var root = new TestRoot - { - Child = target = new TestTemplatedControl - { - Template = new FuncControlTemplate(_ => - { - return new StackPanel - { - Children = new Controls - { - new TextBlock - { - } - } - }; - }), - } - }; - - target.ApplyTemplate(); - - styler.Verify(x => x.ApplyStyles(It.IsAny()), Times.Once()); - styler.Verify(x => x.ApplyStyles(It.IsAny()), Times.Once()); - styler.Verify(x => x.ApplyStyles(It.IsAny()), Times.Once()); - } - } - - [Fact] - public void Templated_Children_Should_Have_TemplatedParent_Set() - { - var target = new TestTemplatedControl - { - Template = new FuncControlTemplate(_ => - { - return new StackPanel - { - Children = new Controls - { - new TextBlock - { - } - } - }; - }), - }; - - target.ApplyTemplate(); - - var panel = target.GetTemplateChildren().OfType().Single(); - var textBlock = target.GetTemplateChildren().OfType().Single(); - - Assert.Equal(target, panel.TemplatedParent); - Assert.Equal(target, textBlock.TemplatedParent); - } - - [Fact] - public void Presenter_Children_Should_Not_Have_TemplatedParent_Set() - { - var target = new TestTemplatedControl - { - Template = new FuncControlTemplate(_ => - { - return new ContentPresenter - { - Content = new TextBlock - { - } - }; - }), - }; - - target.ApplyTemplate(); - - var presenter = target.GetTemplateChildren().OfType().Single(); - var textBlock = (TextBlock)presenter.Child; - - Assert.Equal(target, presenter.TemplatedParent); - Assert.Null(textBlock.TemplatedParent); - } - - [Fact] - public void Nested_Templated_Controls_Have_Correct_TemplatedParent() - { - var target = new TestTemplatedControl - { - Template = new FuncControlTemplate(_ => - { - return new ContentControl - { - Template = new FuncControlTemplate(parent => - { - return new Border - { - Child = new ContentPresenter - { - [~ContentPresenter.ContentProperty] = parent.GetObservable(ContentControl.ContentProperty), - } - }; - }), - Content = new TextBlock - { - } - }; - }), - }; - - target.ApplyTemplate(); - - var contentControl = target.GetTemplateChildren().OfType().Single(); - var border = contentControl.GetTemplateChildren().OfType().Single(); - var presenter = contentControl.GetTemplateChildren().OfType().Single(); - var textBlock = (TextBlock)presenter.Content; - - Assert.Equal(target, contentControl.TemplatedParent); - Assert.Equal(contentControl, border.TemplatedParent); - Assert.Equal(contentControl, presenter.TemplatedParent); - Assert.Equal(target, textBlock.TemplatedParent); - } - } -} From a7089515c5cd41eb0b12076586c72c5ac1ad4c76 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Sat, 26 Dec 2015 17:53:36 +0000 Subject: [PATCH 154/211] Fixed culling bug with transforms. --- src/Perspex.SceneGraph/Rendering/RendererMixin.cs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/Perspex.SceneGraph/Rendering/RendererMixin.cs b/src/Perspex.SceneGraph/Rendering/RendererMixin.cs index a820eaf65e..6acb10e6e9 100644 --- a/src/Perspex.SceneGraph/Rendering/RendererMixin.cs +++ b/src/Perspex.SceneGraph/Rendering/RendererMixin.cs @@ -162,7 +162,10 @@ namespace Perspex.Rendering } else { - return visual.Bounds.TransformToAABB(visual.RenderTransform.Value); + var origin = visual.TransformOrigin.ToPixels(new Size(visual.Bounds.Width, visual.Bounds.Height)); + var offset = Matrix.CreateTranslation(visual.Bounds.Position + origin); + var m = (-offset) * visual.RenderTransform.Value * (offset); + return visual.Bounds.TransformToAABB(m); } } From 630be0c6bfdaa97e5910427075004aa70cb2d6b9 Mon Sep 17 00:00:00 2001 From: Nikita Tsukanov Date: Sat, 26 Dec 2015 21:07:30 +0300 Subject: [PATCH 155/211] Test app is running again --- samples/TestApplicationShared/MainWindow.cs | 2 +- src/Shared/PlatformSupport/AssetLoader.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/samples/TestApplicationShared/MainWindow.cs b/samples/TestApplicationShared/MainWindow.cs index e702b6f4a5..4c27018c6f 100644 --- a/samples/TestApplicationShared/MainWindow.cs +++ b/samples/TestApplicationShared/MainWindow.cs @@ -358,7 +358,7 @@ namespace TestApplication static Stream GetImage(string path) { - return PerspexLocator.Current.GetService().Open(new Uri("res:///" + RootNamespace + "." + path)); + return PerspexLocator.Current.GetService().Open(new Uri("resm:" + RootNamespace + "." + path)); } private static TabItem ListsTab() diff --git a/src/Shared/PlatformSupport/AssetLoader.cs b/src/Shared/PlatformSupport/AssetLoader.cs index 48378dfe31..a954bd0022 100644 --- a/src/Shared/PlatformSupport/AssetLoader.cs +++ b/src/Shared/PlatformSupport/AssetLoader.cs @@ -87,7 +87,7 @@ namespace Perspex.Shared.PlatformSupport if (!uri.IsAbsoluteUri || uri.Scheme == "resm") { var qs = uri.Query.TrimStart('?') - .Split('&') + .Split(new[] {'&'}, StringSplitOptions.RemoveEmptyEntries) .Select(p => p.Split('=')) .ToDictionary(p => p[0], p => p[1]); //TODO: Replace _defaultAssembly by current one (need support from OmniXAML) From 4ef9639f3e07b1dbf5b31acf628b9d459fe27857 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Sat, 26 Dec 2015 20:51:36 +0000 Subject: [PATCH 156/211] Removed LINQ from MeasureOverride. And removed empty MeasureOverride methods. --- src/Perspex.Controls/Primitives/ScrollBar.cs | 6 ------ src/Perspex.Controls/ScrollViewer.cs | 6 ------ src/Perspex.Layout/Layoutable.cs | 2 +- 3 files changed, 1 insertion(+), 13 deletions(-) diff --git a/src/Perspex.Controls/Primitives/ScrollBar.cs b/src/Perspex.Controls/Primitives/ScrollBar.cs index adaad0c2d6..06961d569f 100644 --- a/src/Perspex.Controls/Primitives/ScrollBar.cs +++ b/src/Perspex.Controls/Primitives/ScrollBar.cs @@ -72,12 +72,6 @@ namespace Perspex.Controls.Primitives set { SetValue(OrientationProperty, value); } } - /// - protected override Size MeasureOverride(Size availableSize) - { - return base.MeasureOverride(availableSize); - } - /// /// Calculates whether the scrollbar should be visible. /// diff --git a/src/Perspex.Controls/ScrollViewer.cs b/src/Perspex.Controls/ScrollViewer.cs index b454bc6d5e..f8a712cd6a 100644 --- a/src/Perspex.Controls/ScrollViewer.cs +++ b/src/Perspex.Controls/ScrollViewer.cs @@ -231,12 +231,6 @@ namespace Perspex.Controls return new Vector(Clamp(offset.X, 0, maxX), Clamp(offset.Y, 0, maxY)); } - /// - protected override Size MeasureOverride(Size availableSize) - { - return base.MeasureOverride(availableSize); - } - private static double Clamp(double value, double min, double max) { return (value < min) ? min : (value > max) ? max : value; diff --git a/src/Perspex.Layout/Layoutable.cs b/src/Perspex.Layout/Layoutable.cs index c751e80cdc..329719187c 100644 --- a/src/Perspex.Layout/Layoutable.cs +++ b/src/Perspex.Layout/Layoutable.cs @@ -490,7 +490,7 @@ namespace Perspex.Layout double width = 0; double height = 0; - foreach (ILayoutable child in this.GetVisualChildren().OfType()) + foreach (ILayoutable child in this.GetVisualChildren()) { child.Measure(availableSize); width = Math.Max(width, child.DesiredSize.Width); From 7925fa8c659ce961ff691d934827726a18a71e44 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Sat, 26 Dec 2015 20:57:30 +0000 Subject: [PATCH 157/211] Cache Margin in MeasureCore. --- src/Perspex.Layout/Layoutable.cs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/Perspex.Layout/Layoutable.cs b/src/Perspex.Layout/Layoutable.cs index 329719187c..9930c24614 100644 --- a/src/Perspex.Layout/Layoutable.cs +++ b/src/Perspex.Layout/Layoutable.cs @@ -446,11 +446,13 @@ namespace Perspex.Layout { if (IsVisible) { + var margin = Margin; + ApplyTemplate(); var constrained = LayoutHelper .ApplyLayoutConstraints(this, availableSize) - .Deflate(Margin); + .Deflate(margin); var measured = MeasureOverride(constrained); var width = measured.Width; @@ -472,7 +474,7 @@ namespace Perspex.Layout height = Math.Min(height, MaxHeight); height = Math.Max(height, MinHeight); - return NonNegative(new Size(width, height).Inflate(Margin)); + return NonNegative(new Size(width, height).Inflate(margin)); } else { From 072cb0128e328dac1d946c3694e1c2590e287fc0 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Sat, 26 Dec 2015 22:27:56 +0000 Subject: [PATCH 158/211] Fixed failing tests. --- Perspex.sln | 34 +++++++++++++++++++++-- src/Shared/PlatformSupport/AssetLoader.cs | 10 +++++-- 2 files changed, 38 insertions(+), 6 deletions(-) diff --git a/Perspex.sln b/Perspex.sln index ce49a336e6..fded8df134 100644 --- a/Perspex.sln +++ b/Perspex.sln @@ -140,6 +140,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Perspex.LeakTests", "tests\ EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ControlCatalog", "samples\ControlCatalog\ControlCatalog.csproj", "{61BEC86C-F307-4295-B5B8-9428610D7D55}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Perspex.PerfTests", "tests\Perspex.PerfTests\Perspex.PerfTests.csproj", "{2136D059-4BB5-4CA8-A59C-244722F8610C}" +EndProject Global GlobalSection(SharedMSBuildProjectFiles) = preSolution src\Shared\RenderHelpers\RenderHelpers.projitems*{fb05ac90-89ba-4f2f-a924-f37875fb547c}*SharedItemsImports = 4 @@ -148,18 +150,19 @@ Global src\Shared\PlatformSupport\PlatformSupport.projitems*{e4d9629c-f168-4224-3f51-a5e482ffbc42}*SharedItemsImports = 13 src\Skia\Perspex.Skia\Perspex.Skia.projitems*{2f59f3d0-748d-4652-b01e-e0d954756308}*SharedItemsImports = 13 src\Shared\PlatformSupport\PlatformSupport.projitems*{db070a10-bf39-4752-8456-86e9d5928478}*SharedItemsImports = 4 - src\Skia\Perspex.Skia\Perspex.Skia.projitems*{925dd807-b651-475f-9f7c-cbeb974ce43d}*SharedItemsImports = 4 src\Shared\RenderHelpers\RenderHelpers.projitems*{925dd807-b651-475f-9f7c-cbeb974ce43d}*SharedItemsImports = 4 + src\Skia\Perspex.Skia\Perspex.Skia.projitems*{925dd807-b651-475f-9f7c-cbeb974ce43d}*SharedItemsImports = 4 samples\TestApplicationShared\TestApplicationShared.projitems*{78345174-5b52-4a14-b9fd-d5f2428137f0}*SharedItemsImports = 13 src\Shared\PlatformSupport\PlatformSupport.projitems*{54f237d5-a70a-4752-9656-0c70b1a7b047}*SharedItemsImports = 4 src\Shared\RenderHelpers\RenderHelpers.projitems*{3c4c0cb4-0c0f-4450-a37b-148c84ff905f}*SharedItemsImports = 13 src\Shared\PlatformSupport\PlatformSupport.projitems*{811a76cf-1cf6-440f-963b-bbe31bd72a82}*SharedItemsImports = 4 - src\Skia\Perspex.Skia\Perspex.Skia.projitems*{47be08a7-5985-410b-9ffc-2264b8ea595f}*SharedItemsImports = 4 src\Shared\RenderHelpers\RenderHelpers.projitems*{47be08a7-5985-410b-9ffc-2264b8ea595f}*SharedItemsImports = 4 + src\Skia\Perspex.Skia\Perspex.Skia.projitems*{47be08a7-5985-410b-9ffc-2264b8ea595f}*SharedItemsImports = 4 samples\TestApplicationShared\TestApplicationShared.projitems*{8c923867-8a8f-4f6b-8b80-47d9e8436166}*SharedItemsImports = 4 samples\TestApplicationShared\TestApplicationShared.projitems*{e3a1060b-50d0-44e8-88b6-f44ef2e5bd72}*SharedItemsImports = 4 - src\Skia\Perspex.Skia\Perspex.Skia.projitems*{bd43f7c0-396b-4aa1-bad9-dfde54d51298}*SharedItemsImports = 4 + src\Shared\PlatformSupport\PlatformSupport.projitems*{2136d059-4bb5-4ca8-a59c-244722f8610c}*SharedItemsImports = 4 src\Shared\RenderHelpers\RenderHelpers.projitems*{bd43f7c0-396b-4aa1-bad9-dfde54d51298}*SharedItemsImports = 4 + src\Skia\Perspex.Skia\Perspex.Skia.projitems*{bd43f7c0-396b-4aa1-bad9-dfde54d51298}*SharedItemsImports = 4 src\Shared\RenderHelpers\RenderHelpers.projitems*{3e908f67-5543-4879-a1dc-08eace79b3cd}*SharedItemsImports = 4 src\Shared\PlatformSupport\PlatformSupport.projitems*{e1aa3dbf-9056-4530-9376-18119a7a3ffe}*SharedItemsImports = 4 EndGlobalSection @@ -1293,6 +1296,30 @@ Global {61BEC86C-F307-4295-B5B8-9428610D7D55}.Release|iPhone.Build.0 = Release|Any CPU {61BEC86C-F307-4295-B5B8-9428610D7D55}.Release|iPhoneSimulator.ActiveCfg = Release|Any CPU {61BEC86C-F307-4295-B5B8-9428610D7D55}.Release|iPhoneSimulator.Build.0 = Release|Any CPU + {2136D059-4BB5-4CA8-A59C-244722F8610C}.Ad-Hoc|Any CPU.ActiveCfg = Release|Any CPU + {2136D059-4BB5-4CA8-A59C-244722F8610C}.Ad-Hoc|Any CPU.Build.0 = Release|Any CPU + {2136D059-4BB5-4CA8-A59C-244722F8610C}.Ad-Hoc|iPhone.ActiveCfg = Release|Any CPU + {2136D059-4BB5-4CA8-A59C-244722F8610C}.Ad-Hoc|iPhone.Build.0 = Release|Any CPU + {2136D059-4BB5-4CA8-A59C-244722F8610C}.Ad-Hoc|iPhoneSimulator.ActiveCfg = Release|Any CPU + {2136D059-4BB5-4CA8-A59C-244722F8610C}.Ad-Hoc|iPhoneSimulator.Build.0 = Release|Any CPU + {2136D059-4BB5-4CA8-A59C-244722F8610C}.AppStore|Any CPU.ActiveCfg = Release|Any CPU + {2136D059-4BB5-4CA8-A59C-244722F8610C}.AppStore|Any CPU.Build.0 = Release|Any CPU + {2136D059-4BB5-4CA8-A59C-244722F8610C}.AppStore|iPhone.ActiveCfg = Release|Any CPU + {2136D059-4BB5-4CA8-A59C-244722F8610C}.AppStore|iPhone.Build.0 = Release|Any CPU + {2136D059-4BB5-4CA8-A59C-244722F8610C}.AppStore|iPhoneSimulator.ActiveCfg = Release|Any CPU + {2136D059-4BB5-4CA8-A59C-244722F8610C}.AppStore|iPhoneSimulator.Build.0 = Release|Any CPU + {2136D059-4BB5-4CA8-A59C-244722F8610C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {2136D059-4BB5-4CA8-A59C-244722F8610C}.Debug|Any CPU.Build.0 = Debug|Any CPU + {2136D059-4BB5-4CA8-A59C-244722F8610C}.Debug|iPhone.ActiveCfg = Debug|Any CPU + {2136D059-4BB5-4CA8-A59C-244722F8610C}.Debug|iPhone.Build.0 = Debug|Any CPU + {2136D059-4BB5-4CA8-A59C-244722F8610C}.Debug|iPhoneSimulator.ActiveCfg = Debug|Any CPU + {2136D059-4BB5-4CA8-A59C-244722F8610C}.Debug|iPhoneSimulator.Build.0 = Debug|Any CPU + {2136D059-4BB5-4CA8-A59C-244722F8610C}.Release|Any CPU.ActiveCfg = Release|Any CPU + {2136D059-4BB5-4CA8-A59C-244722F8610C}.Release|Any CPU.Build.0 = Release|Any CPU + {2136D059-4BB5-4CA8-A59C-244722F8610C}.Release|iPhone.ActiveCfg = Release|Any CPU + {2136D059-4BB5-4CA8-A59C-244722F8610C}.Release|iPhone.Build.0 = Release|Any CPU + {2136D059-4BB5-4CA8-A59C-244722F8610C}.Release|iPhoneSimulator.ActiveCfg = Release|Any CPU + {2136D059-4BB5-4CA8-A59C-244722F8610C}.Release|iPhoneSimulator.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -1337,5 +1364,6 @@ Global {8C923867-8A8F-4F6B-8B80-47D9E8436166} = {0CB0B92E-6CFF-4240-80A5-CCAFE75D91E1} {E1AA3DBF-9056-4530-9376-18119A7A3FFE} = {C5A00AC3-B34C-4564-9BDD-2DA473EF4D8B} {61BEC86C-F307-4295-B5B8-9428610D7D55} = {9B9E3891-2366-4253-A952-D08BCEB71098} + {2136D059-4BB5-4CA8-A59C-244722F8610C} = {C5A00AC3-B34C-4564-9BDD-2DA473EF4D8B} EndGlobalSection EndGlobal diff --git a/src/Shared/PlatformSupport/AssetLoader.cs b/src/Shared/PlatformSupport/AssetLoader.cs index a954bd0022..0dfc055833 100644 --- a/src/Shared/PlatformSupport/AssetLoader.cs +++ b/src/Shared/PlatformSupport/AssetLoader.cs @@ -20,9 +20,13 @@ namespace Perspex.Shared.PlatformSupport public AssemblyDescriptor(Assembly assembly) { Assembly = assembly; - Resources = assembly.GetManifestResourceNames() - .ToDictionary(n => n, n => (IAssetDescriptor)new AssemblyResourceDescriptor(assembly, n)); - Name = assembly.GetName().Name; + + if (assembly != null) + { + Resources = assembly.GetManifestResourceNames() + .ToDictionary(n => n, n => (IAssetDescriptor)new AssemblyResourceDescriptor(assembly, n)); + Name = assembly.GetName().Name; + } } public Assembly Assembly { get; } From 516a701e6b474fd73f16a8828935d4723f3959ff Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Sat, 26 Dec 2015 22:32:01 +0000 Subject: [PATCH 159/211] Fixed ControlCatalog resources. --- samples/ControlCatalog/App.paml | 2 +- samples/ControlCatalog/Pages/CarouselPage.paml | 6 +++--- .../Perspex.Markup.Xaml/Converters/BitmapTypeConverter.cs | 4 +--- 3 files changed, 5 insertions(+), 7 deletions(-) diff --git a/samples/ControlCatalog/App.paml b/samples/ControlCatalog/App.paml index ba8bc0ca0c..b09160d0a8 100644 --- a/samples/ControlCatalog/App.paml +++ b/samples/ControlCatalog/App.paml @@ -11,6 +11,6 @@ - + \ No newline at end of file diff --git a/samples/ControlCatalog/Pages/CarouselPage.paml b/samples/ControlCatalog/Pages/CarouselPage.paml index 6f1243a005..3a38e0abd4 100644 --- a/samples/ControlCatalog/Pages/CarouselPage.paml +++ b/samples/ControlCatalog/Pages/CarouselPage.paml @@ -11,9 +11,9 @@ - - - + + +