From aa9c5d2b41f4979eb367fcb0f8cb03ecf4a73239 Mon Sep 17 00:00:00 2001 From: shcherban Date: Wed, 30 Sep 2015 17:59:44 +0300 Subject: [PATCH 01/22] Added file extension append to selected path in SaveFileDialog if it wasn't specified by user --- src/Windows/Perspex.Win32/SystemDialogImpl.cs | 29 ++++++++++++++----- 1 file changed, 21 insertions(+), 8 deletions(-) diff --git a/src/Windows/Perspex.Win32/SystemDialogImpl.cs b/src/Windows/Perspex.Win32/SystemDialogImpl.cs index 7ba493b3d3..f237665299 100644 --- a/src/Windows/Perspex.Win32/SystemDialogImpl.cs +++ b/src/Windows/Perspex.Win32/SystemDialogImpl.cs @@ -40,10 +40,12 @@ namespace Perspex.Win32 filters.CopyTo(0, filterBuffer, 0, filterBuffer.Length); var defExt = (dialog as SaveFileDialog)?.DefaultExtension; - var buffer = new char[256]; - dialog.InitialFileName?.CopyTo(0, buffer, 0, dialog.InitialFileName.Length); + var fileBuffer = new char[256]; + dialog.InitialFileName?.CopyTo(0, fileBuffer, 0, dialog.InitialFileName.Length); - fixed (char* pBuffer = buffer) + string userSelectedExt = null; + + fixed (char* pFileBuffer = fileBuffer) fixed (char* pFilterBuffer = filterBuffer) fixed (char* pDefExt = defExt) fixed (char* pInitDir = dialog.InitialDirectory) @@ -60,13 +62,13 @@ namespace Perspex.Win32 UnmanagedMethods.OpenFileNameFlags.OFN_EXPLORER | UnmanagedMethods.OpenFileNameFlags.OFN_HIDEREADONLY, nMaxCustFilter = 0, - nMaxFile = buffer.Length - 1, + nMaxFile = fileBuffer.Length - 1, nMaxFileTitle = 0, lpTemplateName = IntPtr.Zero, lpfnHook = IntPtr.Zero, lpstrCustomFilter = IntPtr.Zero, lpstrDefExt = new IntPtr(pDefExt), - lpstrFile = new IntPtr(pBuffer), + lpstrFile = new IntPtr(pFileBuffer), lpstrFileTitle = IntPtr.Zero, lpstrFilter = new IntPtr(pFilterBuffer), lpstrInitialDir = new IntPtr(pInitDir), @@ -89,19 +91,20 @@ namespace Perspex.Win32 if (!res) return null; + userSelectedExt = dialog.Filters[ofn.nFilterIndex - 1].Extensions[0]; } var cStart = 0; string dir = null; var files = new List(); - for (var c = 0; c < buffer.Length; c++) + for (var c = 0; c < fileBuffer.Length; c++) { - if (buffer[c] == 0) + if (fileBuffer[c] == 0) { //Encountered double zero char if (cStart == c) break; - var s = new string(buffer, cStart, c - cStart); + var s = new string(fileBuffer, cStart, c - cStart); if (dir == null) dir = s; else @@ -110,7 +113,17 @@ namespace Perspex.Win32 } } if (files.Count == 0) + { + if (dialog is SaveFileDialog) + { + if (string.IsNullOrWhiteSpace(Path.GetExtension(dir)) && + !string.IsNullOrWhiteSpace(userSelectedExt) && + !userSelectedExt.Contains("*")) + dir = Path.ChangeExtension(dir, userSelectedExt); + } + return new[] {dir}; + } return files.Select(f => Path.Combine(dir, f)).ToArray(); }); From 3fc62506df5ea7d5918183614af8417b70924fc7 Mon Sep 17 00:00:00 2001 From: Nikita Tsukanov Date: Sat, 3 Oct 2015 10:31:14 +0300 Subject: [PATCH 02/22] Introduced DrawingContext class responsible for matrix transformation and push/pop order validation --- src/Gtk/Perspex.Cairo/Media/DrawingContext.cs | 74 ++++---- .../Media/Imaging/RenderTargetBitmapImpl.cs | 2 +- src/Gtk/Perspex.Cairo/RenderTarget.cs | 12 +- src/Perspex.Application/Application.cs | 2 +- src/Perspex.Controls/Border.cs | 2 +- src/Perspex.Controls/Image.cs | 2 +- src/Perspex.Controls/Panel.cs | 4 +- .../Presenters/TextPresenter.cs | 2 +- src/Perspex.Controls/Primitives/AccessText.cs | 2 +- src/Perspex.Controls/Shapes/Shape.cs | 2 +- src/Perspex.Controls/TextBlock.cs | 2 +- .../Adapters/GraphicsAdapter.cs | 4 +- src/Perspex.HtmlRenderer/HtmlContainer.cs | 2 +- src/Perspex.HtmlRenderer/HtmlControl.cs | 2 +- src/Perspex.SceneGraph/IVisual.cs | 4 +- .../Media/DrawingContext.cs | 160 ++++++++++++++++++ .../Media/IDrawingContext.cs | 19 +-- .../Media/Imaging/RenderTargetBitmap.cs | 2 +- .../Media/ValidatingDrawingContext.cs | 76 --------- .../Perspex.SceneGraph.csproj | 2 +- .../Platform/IRenderTarget.cs | 4 +- .../Rendering/RendererBase.cs | 4 +- src/Perspex.SceneGraph/Visual.cs | 4 +- .../Perspex.Direct2D1/Media/DrawingContext.cs | 59 +++---- .../Media/Imaging/RenderTargetBitmapImpl.cs | 2 +- src/Windows/Perspex.Direct2D1/RenderTarget.cs | 19 +-- 26 files changed, 274 insertions(+), 195 deletions(-) create mode 100644 src/Perspex.SceneGraph/Media/DrawingContext.cs delete mode 100644 src/Perspex.SceneGraph/Media/ValidatingDrawingContext.cs diff --git a/src/Gtk/Perspex.Cairo/Media/DrawingContext.cs b/src/Gtk/Perspex.Cairo/Media/DrawingContext.cs index f8c74f3f8f..3efb60e5c5 100644 --- a/src/Gtk/Perspex.Cairo/Media/DrawingContext.cs +++ b/src/Gtk/Perspex.Cairo/Media/DrawingContext.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.Collections.Generic; using System.Linq; using System.Reactive.Disposables; using System.Runtime.InteropServices; @@ -16,7 +17,7 @@ namespace Perspex.Cairo.Media /// /// Draws using Direct2D1. /// - public class DrawingContext : IDrawingContext, IDisposable + public class DrawingContext : IDrawingContextImpl, IDisposable { /// /// The cairo context. @@ -30,7 +31,6 @@ namespace Perspex.Cairo.Media public DrawingContext(Cairo.Surface surface) { _context = new Cairo.Context(surface); - CurrentTransform = Matrix.Identity; } /// @@ -40,15 +40,23 @@ namespace Perspex.Cairo.Media public DrawingContext(Gdk.Drawable drawable) { _context = Gdk.CairoHelper.Create(drawable); - CurrentTransform = Matrix.Identity; } + + private Matrix _transform = Matrix.Identity; /// /// Gets the current transform of the drawing context. /// - public Matrix CurrentTransform + public Matrix Transform { - get; } + get { return _transform; } + set + { + _transform = value; + _context.Matrix = value.ToCairo(); + + } + } /// /// Ends a draw operation. @@ -131,29 +139,31 @@ namespace Perspex.Cairo.Media { var impl = geometry.PlatformImpl as StreamGeometryImpl; - using (var pop = PushTransform(impl.Transform)) + var oldMatrix = Transform; + Transform *= impl.Transform; + + + + if (brush != null) { _context.AppendPath(impl.Path); - - if (brush != null) + using (var b = SetBrush(brush, geometry.Bounds.Size)) { - using (var b = SetBrush(brush, geometry.Bounds.Size)) - { - if (pen != null) - _context.FillPreserve(); - else - _context.Fill(); - } + if (pen != null) + _context.FillPreserve(); + else + _context.Fill(); } } - if (pen != null) { - using (var p = SetPen(pen, geometry.Bounds.Size)) - { - _context.Stroke(); - } + _context.AppendPath(impl.Path); + using (var p = SetPen(pen, geometry.Bounds.Size)) + { + _context.Stroke(); + } } + Transform = oldMatrix; } /// @@ -208,31 +218,37 @@ namespace Perspex.Cairo.Media /// /// The clip rectangle. /// A disposable used to undo the clip rectangle. - public IDisposable PushClip(Rect clip) + public void PushClip(Rect clip) { _context.Save(); _context.Rectangle(clip.ToCairo()); _context.Clip(); + } - return Disposable.Create(() => _context.Restore()); + public void PopClip() + { + _context.Restore(); } + readonly Stack _opacityStack = new Stack(); + /// /// Pushes an opacity value. /// /// The opacity. /// A disposable used to undo the opacity. - public IDisposable PushOpacity(double opacity) + public void PushOpacity(double opacity) { - var tmp = opacityOverride; + _opacityStack.Push(opacityOverride); if (opacity < 1.0f) - opacityOverride = opacity; + opacityOverride *= opacity; - return Disposable.Create(() => - { - opacityOverride = tmp; - }); + } + + public void PopOpacity() + { + opacityOverride = _opacityStack.Pop(); } /// diff --git a/src/Gtk/Perspex.Cairo/Media/Imaging/RenderTargetBitmapImpl.cs b/src/Gtk/Perspex.Cairo/Media/Imaging/RenderTargetBitmapImpl.cs index 0b9183d928..4d8f05848a 100644 --- a/src/Gtk/Perspex.Cairo/Media/Imaging/RenderTargetBitmapImpl.cs +++ b/src/Gtk/Perspex.Cairo/Media/Imaging/RenderTargetBitmapImpl.cs @@ -39,7 +39,7 @@ namespace Perspex.Cairo.Media.Imaging Surface.WriteToPng(fileName); } - public IDrawingContext CreateDrawingContext() + public Perspex.Media.DrawingContext CreateDrawingContext() { return _renderTarget.CreateDrawingContext(); } diff --git a/src/Gtk/Perspex.Cairo/RenderTarget.cs b/src/Gtk/Perspex.Cairo/RenderTarget.cs index 333c558e8a..2daab33fbe 100644 --- a/src/Gtk/Perspex.Cairo/RenderTarget.cs +++ b/src/Gtk/Perspex.Cairo/RenderTarget.cs @@ -7,6 +7,7 @@ using Perspex.Cairo.Media; using Perspex.Media; using Perspex.Platform; using Perspex.Rendering; +using DrawingContext = Perspex.Media.DrawingContext; namespace Perspex.Cairo { @@ -50,12 +51,13 @@ namespace Perspex.Cairo /// /// Creates a cairo surface that targets a platform-specific resource. /// - /// A surface wrapped in an . - public IDrawingContext CreateDrawingContext() + /// A surface wrapped in an . + public DrawingContext CreateDrawingContext() { - if(_surface != null) - return new DrawingContext(_surface); - return new DrawingContext(_window.GdkWindow); + var ctx = _surface != null + ? new Media.DrawingContext(_surface) + : new Media.DrawingContext(_window.GdkWindow); + return new DrawingContext(ctx); } public void Dispose() => _surface?.Dispose(); diff --git a/src/Perspex.Application/Application.cs b/src/Perspex.Application/Application.cs index 88f3a23ffe..1e19736154 100644 --- a/src/Perspex.Application/Application.cs +++ b/src/Perspex.Application/Application.cs @@ -177,7 +177,7 @@ namespace Perspex /// The value of Environment.OSVersion.Platform. protected void InitializeSubsystems(int platformID) { - if (platformID == 4 || platformID == 6) + if (true)//platformID == 4 || platformID == 6) { InitializeSubsystem("Perspex.Cairo"); InitializeSubsystem("Perspex.Gtk"); diff --git a/src/Perspex.Controls/Border.cs b/src/Perspex.Controls/Border.cs index 8a70987cb0..bcb1235f25 100644 --- a/src/Perspex.Controls/Border.cs +++ b/src/Perspex.Controls/Border.cs @@ -83,7 +83,7 @@ namespace Perspex.Controls /// Renders the control. /// /// The drawing context. - public override void Render(IDrawingContext context) + public override void Render(DrawingContext context) { var background = Background; var borderBrush = BorderBrush; diff --git a/src/Perspex.Controls/Image.cs b/src/Perspex.Controls/Image.cs index 79b79c6141..0b316689bd 100644 --- a/src/Perspex.Controls/Image.cs +++ b/src/Perspex.Controls/Image.cs @@ -46,7 +46,7 @@ namespace Perspex.Controls /// Renders the control. /// /// The drawing context. - public override void Render(IDrawingContext context) + public override void Render(DrawingContext context) { Bitmap source = Source; diff --git a/src/Perspex.Controls/Panel.cs b/src/Perspex.Controls/Panel.cs index 08e6621adb..bd45bf1e3a 100644 --- a/src/Perspex.Controls/Panel.cs +++ b/src/Perspex.Controls/Panel.cs @@ -172,10 +172,10 @@ namespace Perspex.Controls } /// - /// Renders the visual to a . + /// Renders the visual to a . /// /// The drawing context. - public override void Render(IDrawingContext context) + public override void Render(DrawingContext context) { Brush background = Background; if (background != null) diff --git a/src/Perspex.Controls/Presenters/TextPresenter.cs b/src/Perspex.Controls/Presenters/TextPresenter.cs index 1e6068fd7d..2948cd541a 100644 --- a/src/Perspex.Controls/Presenters/TextPresenter.cs +++ b/src/Perspex.Controls/Presenters/TextPresenter.cs @@ -69,7 +69,7 @@ namespace Perspex.Controls.Presenters return hit.TextPosition + (hit.IsTrailing ? 1 : 0); } - public override void Render(IDrawingContext context) + public override void Render(DrawingContext context) { var selectionStart = SelectionStart; var selectionEnd = SelectionEnd; diff --git a/src/Perspex.Controls/Primitives/AccessText.cs b/src/Perspex.Controls/Primitives/AccessText.cs index 33da32037f..0168974248 100644 --- a/src/Perspex.Controls/Primitives/AccessText.cs +++ b/src/Perspex.Controls/Primitives/AccessText.cs @@ -62,7 +62,7 @@ namespace Perspex.Controls.Primitives /// Renders the to a drawing context. /// /// The drawing context. - public override void Render(IDrawingContext context) + public override void Render(DrawingContext context) { base.Render(context); diff --git a/src/Perspex.Controls/Shapes/Shape.cs b/src/Perspex.Controls/Shapes/Shape.cs index 5f5b989602..58dc24a738 100644 --- a/src/Perspex.Controls/Shapes/Shape.cs +++ b/src/Perspex.Controls/Shapes/Shape.cs @@ -90,7 +90,7 @@ namespace Perspex.Controls.Shapes set { SetValue(StrokeThicknessProperty, value); } } - public override void Render(IDrawingContext context) + public override void Render(DrawingContext context) { var geometry = RenderedGeometry; diff --git a/src/Perspex.Controls/TextBlock.cs b/src/Perspex.Controls/TextBlock.cs index e621369679..b6bec05240 100644 --- a/src/Perspex.Controls/TextBlock.cs +++ b/src/Perspex.Controls/TextBlock.cs @@ -214,7 +214,7 @@ namespace Perspex.Controls /// Renders the to a drawing context. /// /// The drawing context. - public override void Render(IDrawingContext context) + public override void Render(DrawingContext context) { Brush background = Background; diff --git a/src/Perspex.HtmlRenderer/Adapters/GraphicsAdapter.cs b/src/Perspex.HtmlRenderer/Adapters/GraphicsAdapter.cs index 8b52a1b573..6fd918a69b 100644 --- a/src/Perspex.HtmlRenderer/Adapters/GraphicsAdapter.cs +++ b/src/Perspex.HtmlRenderer/Adapters/GraphicsAdapter.cs @@ -32,7 +32,7 @@ namespace TheArtOfDev.HtmlRenderer.Perspex.Adapters /// /// The wrapped Perspex graphics object /// - private readonly IDrawingContext _g; + private readonly DrawingContext _g; /// /// if to release the graphics object on dispose @@ -51,7 +51,7 @@ namespace TheArtOfDev.HtmlRenderer.Perspex.Adapters /// the Perspex graphics object to use /// the initial clip of the graphics /// optional: if to release the graphics object on dispose (default - false) - public GraphicsAdapter(IDrawingContext g, RRect initialClip, bool releaseGraphics = false) + public GraphicsAdapter(DrawingContext g, RRect initialClip, bool releaseGraphics = false) : base(PerspexAdapter.Instance, initialClip) { ArgChecker.AssertArgNotNull(g, "g"); diff --git a/src/Perspex.HtmlRenderer/HtmlContainer.cs b/src/Perspex.HtmlRenderer/HtmlContainer.cs index 9ada42d5ed..e30036b343 100644 --- a/src/Perspex.HtmlRenderer/HtmlContainer.cs +++ b/src/Perspex.HtmlRenderer/HtmlContainer.cs @@ -360,7 +360,7 @@ namespace TheArtOfDev.HtmlRenderer.Perspex /// /// the device to use to render /// the clip rectangle of the html container - public void PerformPaint(IDrawingContext g, Rect clip) + public void PerformPaint(DrawingContext g, Rect clip) { ArgChecker.AssertArgNotNull(g, "g"); diff --git a/src/Perspex.HtmlRenderer/HtmlControl.cs b/src/Perspex.HtmlRenderer/HtmlControl.cs index b720b17bc0..ddd7541923 100644 --- a/src/Perspex.HtmlRenderer/HtmlControl.cs +++ b/src/Perspex.HtmlRenderer/HtmlControl.cs @@ -327,7 +327,7 @@ namespace Perspex.Controls.Html private Size RenderSize => new Size(Bounds.Width, Bounds.Height); - public override void Render(IDrawingContext context) + public override void Render(DrawingContext context) { context.FillRectangle(Background, new Rect(RenderSize)); diff --git a/src/Perspex.SceneGraph/IVisual.cs b/src/Perspex.SceneGraph/IVisual.cs index 7126558483..0491d63fad 100644 --- a/src/Perspex.SceneGraph/IVisual.cs +++ b/src/Perspex.SceneGraph/IVisual.cs @@ -76,10 +76,10 @@ namespace Perspex int ZIndex { get; set; } /// - /// Renders the scene graph node to a . + /// Renders the scene graph node to a . /// /// The context. - void Render(IDrawingContext context); + void Render(DrawingContext context); /// /// Returns a transform that transforms the visual's coordinates into the coordinates diff --git a/src/Perspex.SceneGraph/Media/DrawingContext.cs b/src/Perspex.SceneGraph/Media/DrawingContext.cs new file mode 100644 index 0000000000..5f95ba021b --- /dev/null +++ b/src/Perspex.SceneGraph/Media/DrawingContext.cs @@ -0,0 +1,160 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Perspex.Media.Imaging; + +namespace Perspex.Media +{ + public sealed class DrawingContext : IDisposable + { + private readonly IDrawingContextImpl _impl; + private int _currentLevel; + + public DrawingContext(IDrawingContextImpl impl) + { + _impl = impl; + } + + /// + /// Gets the current transform of the drawing context. + /// + public Matrix CurrentTransform => _impl.Transform; + + /// + /// Draws a bitmap image. + /// + /// The bitmap image. + /// The opacity to draw with. + /// The rect in the image to draw. + /// The rect in the output to draw to. + public void DrawImage(IBitmap source, double opacity, Rect sourceRect, Rect destRect) + => _impl.DrawImage(source, opacity, sourceRect, destRect); + + /// + /// Draws a line. + /// + /// The stroke pen. + /// The first point of the line. + /// The second point of the line. + public void DrawLine(Pen pen, Point p1, Point p2) => _impl.DrawLine(pen, p1, p2); + + /// + /// Draws a geometry. + /// + /// The fill brush. + /// The stroke pen. + /// The geometry. + public void DrawGeometry(Brush brush, Pen pen, Geometry geometry) => _impl.DrawGeometry(brush, pen, geometry); + + /// + /// Draws the outline of a rectangle. + /// + /// The pen. + /// The rectangle bounds. + /// The corner radius. + public void DrawRectangle(Pen pen, Rect rect, float cornerRadius = 0.0f) + => _impl.DrawRectangle(pen, rect, cornerRadius); + + /// + /// Draws text. + /// + /// The foreground brush. + /// The upper-left corner of the text. + /// The text. + public void DrawText(Brush foreground, Point origin, FormattedText text) + => _impl.DrawText(foreground, origin, text); + + /// + /// Draws a filled rectangle. + /// + /// The brush. + /// The rectangle bounds. + /// The corner radius. + public void FillRectangle(Brush brush, Rect rect, float cornerRadius = 0.0f) + => _impl.FillRectangle(brush, rect, cornerRadius); + + public struct PushedState : IDisposable + { + private readonly int _level; + private readonly DrawingContext _context; + private readonly Matrix _matrix; + private readonly PushedStateType _type; + + public enum PushedStateType + { + None, + Matrix, + Opacity, + Clip + } + + public PushedState(DrawingContext context, PushedStateType type, Matrix matrix = default(Matrix)) + { + _level = context._currentLevel += 1; + _context = context; + _type = type; + _matrix = matrix; + + } + + public void Dispose() + { + if(_type == PushedStateType.None) + return; + if (_context._currentLevel != _level) + throw new InvalidOperationException("Wrong Push/Pop state order"); + _context._currentLevel--; + if (_type == PushedStateType.Matrix) + _context._impl.Transform = _matrix; + else if(_type == PushedStateType.Clip) + _context._impl.PopClip(); + + else if(_type == PushedStateType.Opacity) + _context._impl.PopOpacity(); + } + + + } + + + /// + /// Pushes a clip rectange. + /// + /// The clip rectangle. + /// A disposable used to undo the clip rectangle. + public PushedState PushClip(Rect clip) + { + _impl.PushClip(clip); + return new PushedState(this, PushedState.PushedStateType.Clip); + } + + /// + /// Pushes an opacity value. + /// + /// The opacity. + /// A disposable used to undo the opacity. + public PushedState PushOpacity(double opacity) + //TODO: Elimintate platform-specific push opacity call + { + _impl.PushOpacity(opacity); + return new PushedState(this, PushedState.PushedStateType.Opacity); + } + + /// + /// Pushes a matrix transformation. + /// + /// The matrix + /// A disposable used to undo the transformation. + public PushedState PushTransform(Matrix matrix) + { + var oldMatrix = CurrentTransform; + matrix = oldMatrix*matrix; + _impl.Transform = matrix; + return new PushedState(this, PushedState.PushedStateType.Matrix, oldMatrix); + } + + public void Dispose() => _impl.Dispose(); + } +} diff --git a/src/Perspex.SceneGraph/Media/IDrawingContext.cs b/src/Perspex.SceneGraph/Media/IDrawingContext.cs index 92d1fc4e98..4d144b33e0 100644 --- a/src/Perspex.SceneGraph/Media/IDrawingContext.cs +++ b/src/Perspex.SceneGraph/Media/IDrawingContext.cs @@ -9,12 +9,12 @@ namespace Perspex.Media /// /// Defines the interface through which drawing occurs. /// - public interface IDrawingContext : IDisposable + public interface IDrawingContextImpl : IDisposable { /// - /// Gets the current transform of the drawing context. + /// Gets or sets the current transform of the drawing context. /// - Matrix CurrentTransform { get; } + Matrix Transform { get; set; } /// /// Draws a bitmap image. @@ -70,20 +70,17 @@ namespace Perspex.Media /// /// The clip rectangle. /// A disposable used to undo the clip rectangle. - IDisposable PushClip(Rect clip); + void PushClip(Rect clip); + + void PopClip(); /// /// Pushes an opacity value. /// /// The opacity. /// A disposable used to undo the opacity. - IDisposable PushOpacity(double opacity); + void PushOpacity(double opacity); - /// - /// Pushes a matrix transformation. - /// - /// The matrix - /// A disposable used to undo the transformation. - IDisposable PushTransform(Matrix matrix); + void PopOpacity(); } } diff --git a/src/Perspex.SceneGraph/Media/Imaging/RenderTargetBitmap.cs b/src/Perspex.SceneGraph/Media/Imaging/RenderTargetBitmap.cs index 5b8bbea6da..20fc55ebd8 100644 --- a/src/Perspex.SceneGraph/Media/Imaging/RenderTargetBitmap.cs +++ b/src/Perspex.SceneGraph/Media/Imaging/RenderTargetBitmap.cs @@ -47,7 +47,7 @@ namespace Perspex.Media.Imaging return factory.CreateRenderTargetBitmap(width, height); } - public IDrawingContext CreateDrawingContext() => PlatformImpl.CreateDrawingContext(); + public DrawingContext CreateDrawingContext() => PlatformImpl.CreateDrawingContext(); void IRenderTarget.Resize(int width, int height) { diff --git a/src/Perspex.SceneGraph/Media/ValidatingDrawingContext.cs b/src/Perspex.SceneGraph/Media/ValidatingDrawingContext.cs deleted file mode 100644 index 86ae342894..0000000000 --- a/src/Perspex.SceneGraph/Media/ValidatingDrawingContext.cs +++ /dev/null @@ -1,76 +0,0 @@ -using System; -using System.Collections.Generic; -using System.ComponentModel.DataAnnotations; -using System.Reactive.Disposables; -using Perspex.Media.Imaging; - -namespace Perspex.Media -{ - public class ValidatingDrawingContext : IDrawingContext - { - private readonly IDrawingContext _base; - - public ValidatingDrawingContext(IDrawingContext @base) - { - _base = @base; - } - - public void Dispose() - { - _base.Dispose(); - } - - public Matrix CurrentTransform => _base.CurrentTransform; - public void DrawImage(IBitmap source, double opacity, Rect sourceRect, Rect destRect) - { - _base.DrawImage(source, opacity, sourceRect, destRect); - } - - public void DrawLine(Pen pen, Point p1, Point p2) - { - _base.DrawLine(pen, p1, p2); - } - - public void DrawGeometry(Brush brush, Pen pen, Geometry geometry) - { - _base.DrawGeometry(brush, pen, geometry); - } - - public void DrawRectangle(Pen pen, Rect rect, float cornerRadius = 0) - { - _base.DrawRectangle(pen, rect, cornerRadius); - } - - public void DrawText(Brush foreground, Point origin, FormattedText text) - { - _base.DrawText(foreground, origin, text); - } - - public void FillRectangle(Brush brush, Rect rect, float cornerRadius = 0) - { - _base.FillRectangle(brush, rect, cornerRadius); - } - - - Stack _stateStack = new Stack(); - - IDisposable Transform(IDisposable disposable) - { - _stateStack.Push(disposable); - return Disposable.Create(() => - { - var current = _stateStack.Peek(); - if (current != disposable) - throw new InvalidOperationException("Invalid push/pop order"); - current.Dispose(); - _stateStack.Pop(); - }); - } - - public IDisposable PushClip(Rect clip) => Transform(_base.PushClip(clip)); - - public IDisposable PushOpacity(double opacity) => Transform(_base.PushOpacity(opacity)); - - public IDisposable PushTransform(Matrix matrix) => Transform(_base.PushTransform(matrix)); - } -} \ No newline at end of file diff --git a/src/Perspex.SceneGraph/Perspex.SceneGraph.csproj b/src/Perspex.SceneGraph/Perspex.SceneGraph.csproj index e09bed7d90..d2d897142f 100644 --- a/src/Perspex.SceneGraph/Perspex.SceneGraph.csproj +++ b/src/Perspex.SceneGraph/Perspex.SceneGraph.csproj @@ -64,6 +64,7 @@ + @@ -99,7 +100,6 @@ - diff --git a/src/Perspex.SceneGraph/Platform/IRenderTarget.cs b/src/Perspex.SceneGraph/Platform/IRenderTarget.cs index fdbf391a05..a5c8f71d79 100644 --- a/src/Perspex.SceneGraph/Platform/IRenderTarget.cs +++ b/src/Perspex.SceneGraph/Platform/IRenderTarget.cs @@ -15,9 +15,9 @@ namespace Perspex.Platform public interface IRenderTarget : IDisposable { /// - /// Creates an for a rendering session. + /// Creates an for a rendering session. /// - IDrawingContext CreateDrawingContext(); + DrawingContext CreateDrawingContext(); /// /// Resizes the rendered viewport. diff --git a/src/Perspex.SceneGraph/Rendering/RendererBase.cs b/src/Perspex.SceneGraph/Rendering/RendererBase.cs index ba4a0d35c1..679407f7bf 100644 --- a/src/Perspex.SceneGraph/Rendering/RendererBase.cs +++ b/src/Perspex.SceneGraph/Rendering/RendererBase.cs @@ -35,7 +35,7 @@ namespace Perspex.Rendering /// The visual to render. /// /// The drawing context. - public static void Render(this IDrawingContext context, IVisual visual) + public static void Render(this DrawingContext context, IVisual visual) { var opacity = visual.Opacity; if (visual.IsVisible && opacity > 0) @@ -54,7 +54,7 @@ namespace Perspex.Rendering using (context.PushTransform(m)) using (context.PushOpacity(opacity)) - using (visual.ClipToBounds ? context.PushClip(new Rect(visual.Bounds.Size)) : null) + using (visual.ClipToBounds ? context.PushClip(new Rect(visual.Bounds.Size)) : default(DrawingContext.PushedState)) { visual.Render(context); foreach (var child in visual.VisualChildren.OrderBy(x => x.ZIndex)) diff --git a/src/Perspex.SceneGraph/Visual.cs b/src/Perspex.SceneGraph/Visual.cs index e835c6a76c..2bbc290685 100644 --- a/src/Perspex.SceneGraph/Visual.cs +++ b/src/Perspex.SceneGraph/Visual.cs @@ -229,10 +229,10 @@ namespace Perspex } /// - /// Renders the visual to a . + /// Renders the visual to a . /// /// The drawing context. - public virtual void Render(IDrawingContext context) + public virtual void Render(DrawingContext context) { Contract.Requires(context != null); } diff --git a/src/Windows/Perspex.Direct2D1/Media/DrawingContext.cs b/src/Windows/Perspex.Direct2D1/Media/DrawingContext.cs index 0b6abe9353..b10208d7e0 100644 --- a/src/Windows/Perspex.Direct2D1/Media/DrawingContext.cs +++ b/src/Windows/Perspex.Direct2D1/Media/DrawingContext.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; +using System.Collections.Generic; using System.Reactive.Disposables; using Perspex.Media; using SharpDX; @@ -13,7 +15,7 @@ namespace Perspex.Direct2D1.Media /// /// Draws using Direct2D1. /// - public class DrawingContext : IDrawingContext, IDisposable + public class DrawingContext : IDrawingContextImpl, IDisposable { /// /// The Direct2D1 render target. @@ -42,10 +44,10 @@ namespace Perspex.Direct2D1.Media /// /// Gets the current transform of the drawing context. /// - public Matrix CurrentTransform + public Matrix Transform { get { return _renderTarget.Transform.ToPerspex(); } - private set { _renderTarget.Transform = value.ToDirect2D(); } + set { _renderTarget.Transform = value.ToDirect2D(); } } /// @@ -53,6 +55,8 @@ namespace Perspex.Direct2D1.Media /// public void Dispose() { + foreach (var layer in _layerPool) + layer.Dispose(); _renderTarget.EndDraw(); } @@ -233,22 +237,24 @@ namespace Perspex.Direct2D1.Media /// /// The clip rectangle. /// A disposable used to undo the clip rectangle. - public IDisposable PushClip(Rect clip) + public void PushClip(Rect clip) { _renderTarget.PushAxisAlignedClip(clip.ToSharpDX(), AntialiasMode.PerPrimitive); + } - return Disposable.Create(() => - { - _renderTarget.PopAxisAlignedClip(); - }); + public void PopClip() + { + _renderTarget.PopAxisAlignedClip(); } + Stack _layers = new Stack(); + private readonly Stack _layerPool = new Stack(); /// /// Pushes an opacity value. /// /// The opacity. /// A disposable used to undo the opacity. - public IDisposable PushOpacity(double opacity) + public void PushOpacity(double opacity) { if (opacity < 1) { @@ -256,41 +262,26 @@ namespace Perspex.Direct2D1.Media { ContentBounds = RectangleF.Infinite, MaskTransform = Matrix3x2.Identity, - Opacity = (float)opacity, + Opacity = (float) opacity, }; - var layer = new Layer(_renderTarget); - + var layer = _layerPool.Count != 0 ? _layerPool.Pop() : new Layer(_renderTarget); _renderTarget.PushLayer(ref parameters, layer); - return Disposable.Create(() => - { - _renderTarget.PopLayer(); - layer.Dispose(); - }); + _layers.Push(layer); } else - { - return Disposable.Empty; - } + _layers.Push(null); } - /// - /// Pushes a matrix transformation. - /// - /// The matrix - /// A disposable used to undo the transformation. - public IDisposable PushTransform(Matrix matrix) + public void PopOpacity() { - Matrix3x2 m3x2 = matrix.ToDirect2D(); - Matrix3x2 transform = _renderTarget.Transform * m3x2; - _renderTarget.Transform = transform; - - return Disposable.Create(() => + var layer = _layers.Pop(); + if (layer != null) { - m3x2.Invert(); - _renderTarget.Transform = transform * m3x2; - }); + _renderTarget.PopLayer(); + _layerPool.Push(layer); + } } /// diff --git a/src/Windows/Perspex.Direct2D1/Media/Imaging/RenderTargetBitmapImpl.cs b/src/Windows/Perspex.Direct2D1/Media/Imaging/RenderTargetBitmapImpl.cs index 97efa00d47..cf785aa444 100644 --- a/src/Windows/Perspex.Direct2D1/Media/Imaging/RenderTargetBitmapImpl.cs +++ b/src/Windows/Perspex.Direct2D1/Media/Imaging/RenderTargetBitmapImpl.cs @@ -38,7 +38,7 @@ namespace Perspex.Direct2D1.Media // TODO: } - public IDrawingContext CreateDrawingContext() => new RenderTarget(_target).CreateDrawingContext(); + public Perspex.Media.DrawingContext CreateDrawingContext() => new RenderTarget(_target).CreateDrawingContext(); void IRenderTarget.Resize(int width, int height) { diff --git a/src/Windows/Perspex.Direct2D1/RenderTarget.cs b/src/Windows/Perspex.Direct2D1/RenderTarget.cs index 5feabdde87..66ffe80448 100644 --- a/src/Windows/Perspex.Direct2D1/RenderTarget.cs +++ b/src/Windows/Perspex.Direct2D1/RenderTarget.cs @@ -8,6 +8,7 @@ using Perspex.Platform; using Perspex.Rendering; using SharpDX; using SharpDX.Direct2D1; +using DrawingContext = Perspex.Media.DrawingContext; using DwFactory = SharpDX.DirectWrite.Factory; namespace Perspex.Direct2D1 @@ -91,24 +92,12 @@ namespace Perspex.Direct2D1 window.Resize(new Size2(width, height)); } - IDrawingContext Wrap(IDrawingContext ctx) - { -#if DEBUG - return new ValidatingDrawingContext(ctx); -#endif -#pragma warning disable 162 - return ctx; -#pragma warning restore 162 - } - /// /// Creates a drawing context for a rendering session. /// - /// An . - public IDrawingContext CreateDrawingContext() - { - return Wrap(new DrawingContext(_renderTarget, DirectWriteFactory)); - } + /// An . + public DrawingContext CreateDrawingContext() + => new DrawingContext(new Media.DrawingContext(_renderTarget, DirectWriteFactory)); public void Dispose() { From 50f0865a646ded52547f8cf0ad9fe55e96d3a7aa Mon Sep 17 00:00:00 2001 From: Nikita Tsukanov Date: Sat, 3 Oct 2015 10:37:04 +0300 Subject: [PATCH 03/22] Replaced PushTransform by PushPreTransform and PushPostTransform --- src/Gtk/Perspex.Cairo/Media/TileBrushes.cs | 2 +- src/Perspex.Application/Application.cs | 2 +- src/Perspex.SceneGraph/Media/DrawingContext.cs | 8 ++++++-- src/Perspex.SceneGraph/Rendering/RendererBase.cs | 5 +++-- src/Windows/Perspex.Direct2D1/Media/VisualBrushImpl.cs | 2 +- 5 files changed, 12 insertions(+), 7 deletions(-) diff --git a/src/Gtk/Perspex.Cairo/Media/TileBrushes.cs b/src/Gtk/Perspex.Cairo/Media/TileBrushes.cs index 94fb19ea0b..98361427f0 100644 --- a/src/Gtk/Perspex.Cairo/Media/TileBrushes.cs +++ b/src/Gtk/Perspex.Cairo/Media/TileBrushes.cs @@ -113,7 +113,7 @@ namespace Perspex.Cairo.Media out drawRect); using (ctx.PushClip(drawRect)) - using (ctx.PushTransform(transform)) + using (ctx.PushPostTransform(transform)) { ctx.Render(visual); } diff --git a/src/Perspex.Application/Application.cs b/src/Perspex.Application/Application.cs index 1e19736154..88f3a23ffe 100644 --- a/src/Perspex.Application/Application.cs +++ b/src/Perspex.Application/Application.cs @@ -177,7 +177,7 @@ namespace Perspex /// The value of Environment.OSVersion.Platform. protected void InitializeSubsystems(int platformID) { - if (true)//platformID == 4 || platformID == 6) + if (platformID == 4 || platformID == 6) { InitializeSubsystem("Perspex.Cairo"); InitializeSubsystem("Perspex.Gtk"); diff --git a/src/Perspex.SceneGraph/Media/DrawingContext.cs b/src/Perspex.SceneGraph/Media/DrawingContext.cs index 5f95ba021b..32edc8d8fa 100644 --- a/src/Perspex.SceneGraph/Media/DrawingContext.cs +++ b/src/Perspex.SceneGraph/Media/DrawingContext.cs @@ -147,10 +147,14 @@ namespace Perspex.Media /// /// The matrix /// A disposable used to undo the transformation. - public PushedState PushTransform(Matrix matrix) + public PushedState PushPostTransform(Matrix matrix) => PushSetTransform(CurrentTransform*matrix); + + public PushedState PushPreTransform(Matrix matrix) => PushSetTransform(matrix*CurrentTransform); + + + PushedState PushSetTransform(Matrix matrix) { var oldMatrix = CurrentTransform; - matrix = oldMatrix*matrix; _impl.Transform = matrix; return new PushedState(this, PushedState.PushedStateType.Matrix, oldMatrix); } diff --git a/src/Perspex.SceneGraph/Rendering/RendererBase.cs b/src/Perspex.SceneGraph/Rendering/RendererBase.cs index 679407f7bf..88601e7ef8 100644 --- a/src/Perspex.SceneGraph/Rendering/RendererBase.cs +++ b/src/Perspex.SceneGraph/Rendering/RendererBase.cs @@ -50,9 +50,10 @@ namespace Perspex.Rendering var offset = Matrix.CreateTranslation(origin); renderTransform = (-offset)*visual.RenderTransform.Value*(offset); } - m = context.CurrentTransform.Invert()*renderTransform*m*context.CurrentTransform; + m = renderTransform*m; - using (context.PushTransform(m)) + + using (context.PushPreTransform(m)) using (context.PushOpacity(opacity)) using (visual.ClipToBounds ? context.PushClip(new Rect(visual.Bounds.Size)) : default(DrawingContext.PushedState)) { diff --git a/src/Windows/Perspex.Direct2D1/Media/VisualBrushImpl.cs b/src/Windows/Perspex.Direct2D1/Media/VisualBrushImpl.cs index ddb82fba19..74377b67e6 100644 --- a/src/Windows/Perspex.Direct2D1/Media/VisualBrushImpl.cs +++ b/src/Windows/Perspex.Direct2D1/Media/VisualBrushImpl.cs @@ -55,7 +55,7 @@ namespace Perspex.Direct2D1.Media using (var ctx = renderer.CreateDrawingContext()) using (ctx.PushClip(drawRect)) - using (ctx.PushTransform(transform)) + using (ctx.PushPostTransform(transform)) { ctx.Render(visual); } From 8a2578a98576d622d864b6852a36b63108b2d77d Mon Sep 17 00:00:00 2001 From: Nikita Tsukanov Date: Sat, 3 Oct 2015 11:06:26 +0300 Subject: [PATCH 04/22] Implemented transform containers --- .../Media/DrawingContext.cs | 56 ++++++++++++++++--- .../Rendering/RendererBase.cs | 4 +- 2 files changed, 51 insertions(+), 9 deletions(-) diff --git a/src/Perspex.SceneGraph/Media/DrawingContext.cs b/src/Perspex.SceneGraph/Media/DrawingContext.cs index 32edc8d8fa..a191cb1fce 100644 --- a/src/Perspex.SceneGraph/Media/DrawingContext.cs +++ b/src/Perspex.SceneGraph/Media/DrawingContext.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Diagnostics; using System.Linq; using System.Text; using System.Threading.Tasks; @@ -12,15 +13,42 @@ namespace Perspex.Media private readonly IDrawingContextImpl _impl; private int _currentLevel; + private Stack _transformContainers = new Stack(); + + struct TransformContainer + { + public Matrix LocalTransform; + public Matrix ContainerTransform; + + public TransformContainer(Matrix localTransform, Matrix containerTransform) + { + LocalTransform = localTransform; + ContainerTransform = containerTransform; + } + } + public DrawingContext(IDrawingContextImpl impl) { _impl = impl; } + + private Matrix _currentTransform = Matrix.Identity; + /// /// Gets the current transform of the drawing context. /// - public Matrix CurrentTransform => _impl.Transform; + public Matrix CurrentTransform + { + get { return _currentTransform; } + private set + { + _currentTransform = value; + _impl.Transform = _currentTransform*_currentContainerTransform; + } + } + + private Matrix _currentContainerTransform = Matrix.Identity; /// /// Draws a bitmap image. @@ -87,7 +115,8 @@ namespace Perspex.Media None, Matrix, Opacity, - Clip + Clip, + MatrixContainer } public PushedState(DrawingContext context, PushedStateType type, Matrix matrix = default(Matrix)) @@ -107,15 +136,18 @@ namespace Perspex.Media throw new InvalidOperationException("Wrong Push/Pop state order"); _context._currentLevel--; if (_type == PushedStateType.Matrix) - _context._impl.Transform = _matrix; + _context.CurrentTransform = _matrix; else if(_type == PushedStateType.Clip) _context._impl.PopClip(); - else if(_type == PushedStateType.Opacity) _context._impl.PopOpacity(); + else if (_type == PushedStateType.MatrixContainer) + { + var cont = _context._transformContainers.Pop(); + _context._currentContainerTransform = cont.ContainerTransform; + _context.CurrentTransform = cont.LocalTransform; + } } - - } @@ -155,10 +187,20 @@ namespace Perspex.Media PushedState PushSetTransform(Matrix matrix) { var oldMatrix = CurrentTransform; - _impl.Transform = matrix; + CurrentTransform = matrix; + return new PushedState(this, PushedState.PushedStateType.Matrix, oldMatrix); } + + public PushedState PushTransformContainer() + { + _transformContainers.Push(new TransformContainer(CurrentTransform, _currentContainerTransform)); + _currentContainerTransform = CurrentTransform*_currentContainerTransform; + _currentTransform = Matrix.Identity; + return new PushedState(this, PushedState.PushedStateType.MatrixContainer); + } + public void Dispose() => _impl.Dispose(); } } diff --git a/src/Perspex.SceneGraph/Rendering/RendererBase.cs b/src/Perspex.SceneGraph/Rendering/RendererBase.cs index 88601e7ef8..7a7db8f842 100644 --- a/src/Perspex.SceneGraph/Rendering/RendererBase.cs +++ b/src/Perspex.SceneGraph/Rendering/RendererBase.cs @@ -52,10 +52,10 @@ namespace Perspex.Rendering } m = renderTransform*m; - - using (context.PushPreTransform(m)) + using (context.PushPostTransform(m)) using (context.PushOpacity(opacity)) using (visual.ClipToBounds ? context.PushClip(new Rect(visual.Bounds.Size)) : default(DrawingContext.PushedState)) + using (context.PushTransformContainer()) { visual.Render(context); foreach (var child in visual.VisualChildren.OrderBy(x => x.ZIndex)) From ac5485d24935ba3efd53c2a783075568eb496312 Mon Sep 17 00:00:00 2001 From: Nikita Tsukanov Date: Sat, 3 Oct 2015 11:25:32 +0300 Subject: [PATCH 05/22] Fixed cairo unit tests, however that change doesn't have any sense --- src/Gtk/Perspex.Cairo/Media/DrawingContext.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Gtk/Perspex.Cairo/Media/DrawingContext.cs b/src/Gtk/Perspex.Cairo/Media/DrawingContext.cs index 3efb60e5c5..f1255b9ce1 100644 --- a/src/Gtk/Perspex.Cairo/Media/DrawingContext.cs +++ b/src/Gtk/Perspex.Cairo/Media/DrawingContext.cs @@ -143,7 +143,6 @@ namespace Perspex.Cairo.Media Transform *= impl.Transform; - if (brush != null) { _context.AppendPath(impl.Path); @@ -155,6 +154,8 @@ namespace Perspex.Cairo.Media _context.Fill(); } } + Transform = oldMatrix; + if (pen != null) { _context.AppendPath(impl.Path); @@ -163,7 +164,6 @@ namespace Perspex.Cairo.Media _context.Stroke(); } } - Transform = oldMatrix; } /// From 5b65becdef8e03b0c768e4933432c4274a2d44c8 Mon Sep 17 00:00:00 2001 From: Nikita Tsukanov Date: Mon, 5 Oct 2015 14:21:12 +0300 Subject: [PATCH 06/22] Changed matrix multiplication order --- src/Gtk/Perspex.Cairo/Media/DrawingContext.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Gtk/Perspex.Cairo/Media/DrawingContext.cs b/src/Gtk/Perspex.Cairo/Media/DrawingContext.cs index f1255b9ce1..a1fee1a2e3 100644 --- a/src/Gtk/Perspex.Cairo/Media/DrawingContext.cs +++ b/src/Gtk/Perspex.Cairo/Media/DrawingContext.cs @@ -140,7 +140,7 @@ namespace Perspex.Cairo.Media var impl = geometry.PlatformImpl as StreamGeometryImpl; var oldMatrix = Transform; - Transform *= impl.Transform; + Transform = impl.Transform * Transform; if (brush != null) From d1ae3ba50b4bdf664946db85406bffd347aa193a Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Mon, 5 Oct 2015 19:29:03 +0200 Subject: [PATCH 07/22] Fix ListBox item DataContexts. --- .../Generators/ItemContainerGenerator.cs | 9 +++- .../Generators/ItemContainerGenerator`1.cs | 6 +++ .../Presenters/ContentPresenter.cs | 5 +++ .../Templates/DataTemplateExtensions.cs | 2 - .../ListBoxTests.cs | 44 +++++++++++++++++++ 5 files changed, 63 insertions(+), 3 deletions(-) diff --git a/src/Perspex.Controls/Generators/ItemContainerGenerator.cs b/src/Perspex.Controls/Generators/ItemContainerGenerator.cs index f2b2ff131f..4d47f91a36 100644 --- a/src/Perspex.Controls/Generators/ItemContainerGenerator.cs +++ b/src/Perspex.Controls/Generators/ItemContainerGenerator.cs @@ -145,7 +145,14 @@ namespace Perspex.Controls.Generators /// The created container control. protected virtual IControl CreateContainer(object item) { - return Owner.MaterializeDataTemplate(item); + var result = Owner.MaterializeDataTemplate(item); + + if (result != null && !(item is IControl)) + { + result.DataContext = item; + } + + return result; } /// diff --git a/src/Perspex.Controls/Generators/ItemContainerGenerator`1.cs b/src/Perspex.Controls/Generators/ItemContainerGenerator`1.cs index 561e3f61c5..fca5d1b4f8 100644 --- a/src/Perspex.Controls/Generators/ItemContainerGenerator`1.cs +++ b/src/Perspex.Controls/Generators/ItemContainerGenerator`1.cs @@ -37,6 +37,12 @@ namespace Perspex.Controls.Generators { var result = new T(); result.Content = Owner.MaterializeDataTemplate(item); + + if (!(item is IControl)) + { + result.DataContext = item; + } + return result; } } diff --git a/src/Perspex.Controls/Presenters/ContentPresenter.cs b/src/Perspex.Controls/Presenters/ContentPresenter.cs index 492279e25f..b2b2e6ff9f 100644 --- a/src/Perspex.Controls/Presenters/ContentPresenter.cs +++ b/src/Perspex.Controls/Presenters/ContentPresenter.cs @@ -115,6 +115,11 @@ namespace Perspex.Controls.Presenters if (result != null) { + if (!(content is IControl)) + { + result.DataContext = content; + } + AddVisualChild(result); if (result.Parent == null) diff --git a/src/Perspex.Controls/Templates/DataTemplateExtensions.cs b/src/Perspex.Controls/Templates/DataTemplateExtensions.cs index fbd4086ed5..d95c1185df 100644 --- a/src/Perspex.Controls/Templates/DataTemplateExtensions.cs +++ b/src/Perspex.Controls/Templates/DataTemplateExtensions.cs @@ -45,8 +45,6 @@ namespace Perspex.Controls.Templates result = FuncDataTemplate.Default.Build(data); } - result.DataContext = data; - return result; } } diff --git a/tests/Perspex.Controls.UnitTests/ListBoxTests.cs b/tests/Perspex.Controls.UnitTests/ListBoxTests.cs index 1d3b885361..bea8be733d 100644 --- a/tests/Perspex.Controls.UnitTests/ListBoxTests.cs +++ b/tests/Perspex.Controls.UnitTests/ListBoxTests.cs @@ -50,6 +50,40 @@ namespace Perspex.Controls.UnitTests Assert.Equal(1, target.SelectedIndex); } + [Fact] + public void DataContexts_Should_Be_Correctly_Set() + { + var items = new object[] + { + "Foo", + new Item("Bar"), + new TextBlock { Text = "Baz" }, + new ListBoxItem { Content = "Qux" }, + }; + + var target = new ListBox + { + Template = new ControlTemplate(CreateListBoxTemplate), + DataContext = "Base", + DataTemplates = new DataTemplates + { + new FuncDataTemplate(x => new Button { Content = x }) + }, + Items = items, + }; + + target.ApplyTemplate(); + + var dataContexts = target.Presenter.Panel.Children + .Cast() + .Select(x => x.DataContext) + .ToList(); + + Assert.Equal( + new object[] { items[0], items[1], "Base", "Base" }, + dataContexts); + } + private Control CreateListBoxTemplate(ITemplatedControl parent) { return new ScrollViewer @@ -70,5 +104,15 @@ namespace Perspex.Controls.UnitTests [~ContentPresenter.ContentProperty] = parent.GetObservable(ContentControl.ContentProperty), }; } + + private class Item + { + public Item(string value) + { + Value = value; + } + + public string Value { get; } + } } } From 3945d9d07555dd2888235f7f6a791de5ca396e6a Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Tue, 6 Oct 2015 11:28:40 +0200 Subject: [PATCH 08/22] Make DataTemplate match derived types. Closes #251. --- .../Templates/DataTemplate.cs | 3 +- .../Templates/DataTemplateTests.cs | 37 +++++++++++++++++++ 2 files changed, 39 insertions(+), 1 deletion(-) create mode 100644 tests/Perspex.Markup.Xaml.UnitTests/Templates/DataTemplateTests.cs diff --git a/src/Markup/Perspex.Markup.Xaml/Templates/DataTemplate.cs b/src/Markup/Perspex.Markup.Xaml/Templates/DataTemplate.cs index da22cafae2..243ef9ef96 100644 --- a/src/Markup/Perspex.Markup.Xaml/Templates/DataTemplate.cs +++ b/src/Markup/Perspex.Markup.Xaml/Templates/DataTemplate.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.Reflection; using OmniXaml.Attributes; using Perspex.Controls; using Perspex.Controls.Templates; @@ -20,7 +21,7 @@ namespace Perspex.Markup.Xaml.Templates throw new InvalidOperationException("DataTemplate must have a DataType."); } - return DataType == data.GetType(); + return DataType.GetTypeInfo().IsAssignableFrom(data.GetType().GetTypeInfo()); } public IControl Build(object data) diff --git a/tests/Perspex.Markup.Xaml.UnitTests/Templates/DataTemplateTests.cs b/tests/Perspex.Markup.Xaml.UnitTests/Templates/DataTemplateTests.cs new file mode 100644 index 0000000000..390331f941 --- /dev/null +++ b/tests/Perspex.Markup.Xaml.UnitTests/Templates/DataTemplateTests.cs @@ -0,0 +1,37 @@ +// 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; +using Perspex.Markup.Xaml.Templates; +using Xunit; + +namespace Perspex.Markup.Xaml.UnitTests.Templates +{ + public class DataTemplateTests + { + [Fact] + public void DataTemplate_Should_Match_Data_Of_Type() + { + var target = new DataTemplate { DataType = typeof(Class1) }; + var data = new Class1(); + + Assert.True(target.Match(data)); + } + + [Fact] + public void DataTemplate_Should_Match_Data_Of_Derived_Type() + { + var target = new DataTemplate { DataType = typeof(Class1) }; + var data = new Class2(); + + Assert.True(target.Match(data)); + } + + private class Class1 { } + private class Class2 : Class1 { } + } +} From 98e3d4ba326ff805d3b0d362fa03087a309b9a92 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Tue, 6 Oct 2015 11:46:08 +0200 Subject: [PATCH 09/22] Added file missed from last commit. --- .../Perspex.Markup.Xaml.UnitTests.csproj | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/Perspex.Markup.Xaml.UnitTests/Perspex.Markup.Xaml.UnitTests.csproj b/tests/Perspex.Markup.Xaml.UnitTests/Perspex.Markup.Xaml.UnitTests.csproj index 2ac180603f..b6c7933775 100644 --- a/tests/Perspex.Markup.Xaml.UnitTests/Perspex.Markup.Xaml.UnitTests.csproj +++ b/tests/Perspex.Markup.Xaml.UnitTests/Perspex.Markup.Xaml.UnitTests.csproj @@ -101,6 +101,7 @@ + From d68131c488356ee714223fd38bf2166dcc33241e Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Tue, 6 Oct 2015 12:39:13 +0200 Subject: [PATCH 10/22] Added getters/setters for TextBlock attached props Closes #252. --- src/Perspex.Controls/TextBlock.cs | 84 +++++++++++++++++++++++++++++++ 1 file changed, 84 insertions(+) diff --git a/src/Perspex.Controls/TextBlock.cs b/src/Perspex.Controls/TextBlock.cs index b6bec05240..a1df8c77ed 100644 --- a/src/Perspex.Controls/TextBlock.cs +++ b/src/Perspex.Controls/TextBlock.cs @@ -210,6 +210,90 @@ namespace Perspex.Controls set { SetValue(TextAlignmentProperty, value); } } + /// + /// Gets the value of the attached on a control. + /// + /// The control. + /// The font family. + public static string GetFontFamily(Control control) + { + return control.GetValue(FontFamilyProperty); + } + + /// + /// Gets the value of the attached on a control. + /// + /// The control. + /// The font family. + public static double GetFontSize(Control control) + { + return control.GetValue(FontSizeProperty); + } + + /// + /// Gets the value of the attached on a control. + /// + /// The control. + /// The font family. + public static FontStyle GetFontStyle(Control control) + { + return control.GetValue(FontStyleProperty); + } + + /// + /// Gets the value of the attached on a control. + /// + /// The control. + /// The font family. + public static FontWeight GetFontWeight(Control control) + { + return control.GetValue(FontWeightProperty); + } + + /// + /// Sets the value of the attached on a control. + /// + /// The control. + /// The property value to set. + /// The font family. + public static void SetFontFamily(Control control, string value) + { + control.SetValue(FontFamilyProperty, value); + } + + /// + /// Sets the value of the attached on a control. + /// + /// The control. + /// The property value to set. + /// The font family. + public static void SetFontSize(Control control, double value) + { + control.SetValue(FontSizeProperty, value); + } + + /// + /// Sets the value of the attached on a control. + /// + /// The control. + /// The property value to set. + /// The font family. + public static void SetFontStyle(Control control, FontStyle value) + { + control.SetValue(FontStyleProperty, value); + } + + /// + /// Sets the value of the attached on a control. + /// + /// The control. + /// The property value to set. + /// The font family. + public static void SetFontWeight(Control control, FontWeight value) + { + control.SetValue(FontWeightProperty, value); + } + /// /// Renders the to a drawing context. /// From f1ca851833840138fcb7a49559134fa95d827a21 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Tue, 6 Oct 2015 20:21:19 +0200 Subject: [PATCH 11/22] Fix setting expression value on broken chain. Closes #254. --- .../Binding/PropertyAccessorNode.cs | 2 + .../ExpressionObserverTests_SetValue.cs | 80 +++++++++++++++++++ .../Perspex.Markup.UnitTests.csproj | 1 + 3 files changed, 83 insertions(+) create mode 100644 tests/Perspex.Markup.UnitTests/Binding/ExpressionObserverTests_SetValue.cs diff --git a/src/Markup/Perspex.Markup/Binding/PropertyAccessorNode.cs b/src/Markup/Perspex.Markup/Binding/PropertyAccessorNode.cs index 1015bcfa0a..b4efbc324c 100644 --- a/src/Markup/Perspex.Markup/Binding/PropertyAccessorNode.cs +++ b/src/Markup/Perspex.Markup/Binding/PropertyAccessorNode.cs @@ -81,6 +81,8 @@ namespace Perspex.Markup.Binding { inpc.PropertyChanged -= PropertyChanged; } + + _propertyInfo = null; } private static PropertyInfo FindProperty(object target, string propertyName) diff --git a/tests/Perspex.Markup.UnitTests/Binding/ExpressionObserverTests_SetValue.cs b/tests/Perspex.Markup.UnitTests/Binding/ExpressionObserverTests_SetValue.cs new file mode 100644 index 0000000000..7be47702b8 --- /dev/null +++ b/tests/Perspex.Markup.UnitTests/Binding/ExpressionObserverTests_SetValue.cs @@ -0,0 +1,80 @@ +// 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 Perspex.Markup.Binding; +using Xunit; + +namespace Perspex.Markup.UnitTests.Binding +{ + public class ExpressionObserverTests_SetValue + { + [Fact] + public void Should_Set_Simple_Property_Value() + { + var data = new { Foo = "foo" }; + var target = new ExpressionObserver(data, "Foo"); + + target.SetValue("bar"); + + Assert.Equal("foo", data.Foo); + } + + [Fact] + public void Should_Set_Value_On_Simple_Property_Chain() + { + var data = new Class1 { Foo = new Class2 { Bar = "bar" } }; + var target = new ExpressionObserver(data, "Foo.Bar"); + + target.SetValue("foo"); + + Assert.Equal("foo", data.Foo.Bar); + } + + [Fact] + public void Should_Not_Try_To_Set_Value_On_Broken_Chain() + { + var data = new Class1 { Foo = new Class2 { Bar = "bar" } }; + var target = new ExpressionObserver(data, "Foo.Bar"); + + // Ensure the ExpressionObserver's subscriptions are kept active. + target.OfType().Subscribe(x => { }); + + data.Foo = null; + + Assert.False(target.SetValue("foo")); + } + + private class Class1 : NotifyingBase + { + private Class2 _foo; + + public Class2 Foo + { + get { return _foo; } + set + { + _foo = value; + RaisePropertyChanged(nameof(Foo)); + } + } + } + + private class Class2 : NotifyingBase + { + private string _bar; + + public string Bar + { + get { return _bar; } + set + { + _bar = value; + RaisePropertyChanged(nameof(Bar)); + } + } + } + } +} diff --git a/tests/Perspex.Markup.UnitTests/Perspex.Markup.UnitTests.csproj b/tests/Perspex.Markup.UnitTests/Perspex.Markup.UnitTests.csproj index 36ff242096..71cc52aef1 100644 --- a/tests/Perspex.Markup.UnitTests/Perspex.Markup.UnitTests.csproj +++ b/tests/Perspex.Markup.UnitTests/Perspex.Markup.UnitTests.csproj @@ -74,6 +74,7 @@ + From 19498ab03d01830106bc1c1eb4d967b054636e1f Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Tue, 6 Oct 2015 20:30:24 +0200 Subject: [PATCH 12/22] Correctly set TreeView item DataContexts. Also added a few tests for TreeView. --- .../Generators/TreeItemContainerGenerator.cs | 25 +++-- .../TreeViewTests.cs | 101 ++++++++++++++++++ 2 files changed, 119 insertions(+), 7 deletions(-) create mode 100644 tests/Perspex.Controls.UnitTests/TreeViewTests.cs diff --git a/src/Perspex.Controls/Generators/TreeItemContainerGenerator.cs b/src/Perspex.Controls/Generators/TreeItemContainerGenerator.cs index d4aad6236e..66a2db8a4f 100644 --- a/src/Perspex.Controls/Generators/TreeItemContainerGenerator.cs +++ b/src/Perspex.Controls/Generators/TreeItemContainerGenerator.cs @@ -165,22 +165,33 @@ namespace Perspex.Controls.Generators /// The created container control. protected virtual T CreateContainer(object item) { - T result = item as T; + var container = item as T; - if (result == null) + if (item == null) + { + return null; + } + else if (container != null) + { + return container; + } + else { var template = GetTreeDataTemplate(item); - - result = new T + var result = new T { Header = template.Build(item), Items = template.ItemsSelector(item), IsExpanded = template.IsExpanded(item), - DataContext = item, }; - } - return result; + if (!(item is IControl)) + { + result.DataContext = item; + } + + return result; + } } /// diff --git a/tests/Perspex.Controls.UnitTests/TreeViewTests.cs b/tests/Perspex.Controls.UnitTests/TreeViewTests.cs new file mode 100644 index 0000000000..b3cb7dbbe3 --- /dev/null +++ b/tests/Perspex.Controls.UnitTests/TreeViewTests.cs @@ -0,0 +1,101 @@ +// Copyright (c) The Perspex Project. All rights reserved. +// Licensed under the MIT license. See licence.md file in the project root for full license information. + +using System; +using System.Linq; +using Perspex.Controls; +using Perspex.Controls.Presenters; +using Perspex.Controls.Templates; +using Perspex.LogicalTree; +using Perspex.Styling; +using Xunit; + +namespace Perspex.Controls.UnitTests +{ + public class TreeViewTests + { + [Fact] + public void LogicalChildren_Should_Be_Set() + { + var target = new TreeView + { + Template = new ControlTemplate(CreateTreeViewTemplate), + Items = new[] { "Foo", "Bar", "Baz " }, + }; + + target.ApplyTemplate(); + + Assert.Equal(3, target.GetLogicalChildren().Count()); + + foreach (var child in target.GetLogicalChildren()) + { + Assert.IsType(child); + } + } + + [Fact] + public void DataContexts_Should_Be_Correctly_Set() + { + var items = new object[] + { + "Foo", + new Item("Bar"), + new TextBlock { Text = "Baz" }, + new TreeViewItem { Header = "Qux" }, + }; + + var target = new TreeView + { + Template = new ControlTemplate(CreateTreeViewTemplate), + DataContext = "Base", + DataTemplates = new DataTemplates + { + new FuncDataTemplate(x => new Button { Content = x }) + }, + Items = items, + }; + + target.ApplyTemplate(); + + var dataContexts = target.Presenter.Panel.Children + .Cast() + .Select(x => x.DataContext) + .ToList(); + + Assert.Equal( + new object[] { items[0], items[1], "Base", "Base" }, + dataContexts); + } + + private Control CreateTreeViewTemplate(ITemplatedControl parent) + { + return new ScrollViewer + { + Template = new ControlTemplate(CreateScrollViewerTemplate), + Content = new ItemsPresenter + { + Name = "itemsPresenter", + [~ItemsPresenter.ItemsProperty] = parent.GetObservable(ItemsControl.ItemsProperty), + } + }; + } + + private Control CreateScrollViewerTemplate(ITemplatedControl parent) + { + return new ScrollContentPresenter + { + [~ContentPresenter.ContentProperty] = parent.GetObservable(ContentControl.ContentProperty), + }; + } + + private class Item + { + public Item(string value) + { + Value = value; + } + + public string Value { get; } + } + } +} From 24e18d023618a3736a7877d27ed9eb9127554084 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Tue, 6 Oct 2015 20:47:25 +0200 Subject: [PATCH 13/22] Documentation. --- src/Perspex.Controls/Button.cs | 3 ++ src/Perspex.Controls/Canvas.cs | 17 +++++-- src/Perspex.Controls/ListBox.cs | 4 ++ src/Perspex.Controls/MenuItem.cs | 4 +- src/Perspex.Controls/PlacementMode.cs | 12 +++++ src/Perspex.Controls/Platform/IPopupImpl.cs | 17 +++++-- .../Platform/ISystemDialogImpl.cs | 16 +++++-- .../Platform/ITopLevelImpl.cs | 46 ++++++++++++++++++- .../Perspex.Controls.UnitTests.csproj | 1 + 9 files changed, 106 insertions(+), 14 deletions(-) diff --git a/src/Perspex.Controls/Button.cs b/src/Perspex.Controls/Button.cs index 41736206c9..29eb4c3628 100644 --- a/src/Perspex.Controls/Button.cs +++ b/src/Perspex.Controls/Button.cs @@ -44,6 +44,9 @@ namespace Perspex.Controls public static readonly PerspexProperty CommandProperty = PerspexProperty.Register(nameof(Command)); + /// + /// Defines the property. + /// public static readonly PerspexProperty HotKeyProperty = HotKeyManager.HotKeyProperty.AddOwner /// The service type. - /// An optional contract. /// A service of the requested type. public object GetService(Type serviceType) { diff --git a/src/Perspex.Controls/PlacementMode.cs b/src/Perspex.Controls/PlacementMode.cs index cf65f29c63..e623f31674 100644 --- a/src/Perspex.Controls/PlacementMode.cs +++ b/src/Perspex.Controls/PlacementMode.cs @@ -1,11 +1,23 @@ // 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 Perspex.Controls.Primitives; + namespace Perspex.Controls { + /// + /// Defines the placement for a control. + /// public enum PlacementMode { + /// + /// The popup is placed at the bottom left of its target. + /// Bottom, + + /// + /// The popup is placed at the top right of its target. + /// Right, } } diff --git a/src/Perspex.Controls/Platform/IPopupImpl.cs b/src/Perspex.Controls/Platform/IPopupImpl.cs index 087177ea0c..fc7aeb6e2a 100644 --- a/src/Perspex.Controls/Platform/IPopupImpl.cs +++ b/src/Perspex.Controls/Platform/IPopupImpl.cs @@ -1,18 +1,27 @@ // 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; -using Perspex.Input.Raw; - namespace Perspex.Platform { + /// + /// Defines a platform-specific popup window implementation. + /// public interface IPopupImpl : ITopLevelImpl { + /// + /// Sets the position of the popup. + /// + /// The position, in screen coordinates. void SetPosition(Point p); + /// + /// Shows the popup. + /// void Show(); + /// + /// Hides the popup. + /// void Hide(); } } diff --git a/src/Perspex.Controls/Platform/ISystemDialogImpl.cs b/src/Perspex.Controls/Platform/ISystemDialogImpl.cs index 4e8f51427e..d3af982f95 100644 --- a/src/Perspex.Controls/Platform/ISystemDialogImpl.cs +++ b/src/Perspex.Controls/Platform/ISystemDialogImpl.cs @@ -1,14 +1,22 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; +// 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.Threading.Tasks; using Perspex.Platform; namespace Perspex.Controls.Platform { + /// + /// Defines a platform-specific system dialog implementation. + /// public interface ISystemDialogImpl { + /// + /// Shows a file dialog. + /// + /// The details of the file dialog to show. + /// The parent window. + /// A task returning the selected filenames. Task ShowFileDialogAsync(FileDialog dialog, IWindowImpl parent); } } diff --git a/src/Perspex.Controls/Platform/ITopLevelImpl.cs b/src/Perspex.Controls/Platform/ITopLevelImpl.cs index 053c340b27..5b0d738e29 100644 --- a/src/Perspex.Controls/Platform/ITopLevelImpl.cs +++ b/src/Perspex.Controls/Platform/ITopLevelImpl.cs @@ -2,36 +2,80 @@ // 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; namespace Perspex.Platform { + /// + /// Defines a platform-specific top-level window implementation. + /// + /// + /// This interface is the common interface to and + /// . + /// public interface ITopLevelImpl : IDisposable { + /// + /// Gets or sets the client size of the window. + /// Size ClientSize { get; set; } + /// + /// Gets the platform window handle. + /// IPlatformHandle Handle { get; } + /// + /// Gets or sets a method called when the window is activated (receives focus). + /// Action Activated { get; set; } + /// + /// Gets or sets a method called when the window is closed. + /// Action Closed { get; set; } + /// + /// Gets or sets a method called when the window is deactivated (loses focus). + /// Action Deactivated { get; set; } + /// + /// Gets or sets a method called when the window receives input. + /// Action Input { get; set; } + /// + /// Gets or sets a method called when the window requires painting. + /// Action Paint { get; set; } + /// + /// Gets or sets a method called when the window is resized. + /// Action Resized { get; set; } + /// + /// Activates the window. + /// void Activate(); + /// + /// Invalidates a rect on the window. + /// void Invalidate(Rect rect); + /// + /// Sets the for the window. + /// void SetInputRoot(IInputRoot inputRoot); + /// + /// Converts a point from client to screen coordinates. + /// + /// The point in client coordinates. + /// The point in screen coordinates. Point PointToScreen(Point point); /// diff --git a/tests/Perspex.Controls.UnitTests/Perspex.Controls.UnitTests.csproj b/tests/Perspex.Controls.UnitTests/Perspex.Controls.UnitTests.csproj index 29cd92b135..43854919c4 100644 --- a/tests/Perspex.Controls.UnitTests/Perspex.Controls.UnitTests.csproj +++ b/tests/Perspex.Controls.UnitTests/Perspex.Controls.UnitTests.csproj @@ -84,6 +84,7 @@ + From c7ace7b6a7b20c1702a806b5298d9847957b20c1 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Tue, 6 Oct 2015 21:26:26 +0200 Subject: [PATCH 14/22] Allow binding to attached properties. Closes #255. --- .../Context/PerspexAttachableXamlMember.cs | 33 +++++++++++++++++++ .../Context/PerspexXamlMemberValuePlugin.cs | 30 ++++++++++++++--- .../Context/PerspexXamlType.cs | 9 +++++ .../Perspex.Markup.Xaml.csproj | 1 + 4 files changed, 68 insertions(+), 5 deletions(-) create mode 100644 src/Markup/Perspex.Markup.Xaml/Context/PerspexAttachableXamlMember.cs diff --git a/src/Markup/Perspex.Markup.Xaml/Context/PerspexAttachableXamlMember.cs b/src/Markup/Perspex.Markup.Xaml/Context/PerspexAttachableXamlMember.cs new file mode 100644 index 0000000000..117f7768f1 --- /dev/null +++ b/src/Markup/Perspex.Markup.Xaml/Context/PerspexAttachableXamlMember.cs @@ -0,0 +1,33 @@ +// 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 Perspex.Markup.Xaml.Binding; +using OmniXaml; +using OmniXaml.Typing; +using System.Reflection; + +namespace Perspex.Markup.Xaml.Context +{ + public class PerspexAttachableXamlMember : AttachableXamlMember + { + public PerspexAttachableXamlMember(string name, + XamlType owner, + MethodInfo getter, + MethodInfo setter, + IXamlTypeRepository xamlTypeRepository, + ITypeFeatureProvider featureProvider) + : base(name, getter, setter, xamlTypeRepository, featureProvider) + { + } + + protected override IXamlMemberValuePlugin LookupXamlMemberValueConnector() + { + return new PerspexXamlMemberValuePlugin(this); + } + + public override string ToString() + { + return "Perspex Attachable XAML Member " + base.ToString(); + } + } +} \ No newline at end of file diff --git a/src/Markup/Perspex.Markup.Xaml/Context/PerspexXamlMemberValuePlugin.cs b/src/Markup/Perspex.Markup.Xaml/Context/PerspexXamlMemberValuePlugin.cs index fd7a786649..36e1d03ea8 100644 --- a/src/Markup/Perspex.Markup.Xaml/Context/PerspexXamlMemberValuePlugin.cs +++ b/src/Markup/Perspex.Markup.Xaml/Context/PerspexXamlMemberValuePlugin.cs @@ -5,6 +5,7 @@ using System; using System.Linq; using System.Reactive.Linq; using System.Reflection; +using System.Runtime.CompilerServices; using Glass; using OmniXaml.ObjectAssembler; using OmniXaml.Typing; @@ -16,9 +17,9 @@ namespace Perspex.Markup.Xaml.Context { public class PerspexXamlMemberValuePlugin : MemberValuePlugin { - private readonly XamlMember _xamlMember; + private readonly MutableXamlMember _xamlMember; - public PerspexXamlMemberValuePlugin(XamlMember xamlMember) + public PerspexXamlMemberValuePlugin(MutableXamlMember xamlMember) : base(xamlMember) { _xamlMember = xamlMember; @@ -73,6 +74,7 @@ namespace Perspex.Markup.Xaml.Context else { var perspexObject = instance as PerspexObject; + var attached = _xamlMember as PerspexAttachableXamlMember; if (perspexObject == null) { @@ -80,13 +82,31 @@ namespace Perspex.Markup.Xaml.Context $"Cannot bind to an object of type '{instance.GetType()}"); } - var property = perspexObject.GetRegisteredProperties() - .FirstOrDefault(x => x.Name == _xamlMember.Name); + PerspexProperty property; + string propertyName; + + if (attached == null) + { + propertyName = _xamlMember.Name; + property = perspexObject.GetRegisteredProperties() + .FirstOrDefault(x => x.Name == propertyName); + } + else + { + // Ensure the OwnerType's static ctor has been run. + RuntimeHelpers.RunClassConstructor(attached.DeclaringType.UnderlyingType.TypeHandle); + + propertyName = attached.DeclaringType.UnderlyingType.Name + '.' + _xamlMember.Name; + + property = perspexObject.GetRegisteredProperties() + .Where(x => x.IsAttached && x.OwnerType == attached.DeclaringType.UnderlyingType) + .FirstOrDefault(x => x.Name == _xamlMember.Name); + } if (property == null) { throw new InvalidOperationException( - $"Cannot find '{_xamlMember.Name}' on '{instance.GetType()}"); + $"Cannot find '{propertyName}' on '{instance.GetType()}"); } var binding = new XamlBinding diff --git a/src/Markup/Perspex.Markup.Xaml/Context/PerspexXamlType.cs b/src/Markup/Perspex.Markup.Xaml/Context/PerspexXamlType.cs index 0d1fb218b1..dc6b7398f4 100644 --- a/src/Markup/Perspex.Markup.Xaml/Context/PerspexXamlType.cs +++ b/src/Markup/Perspex.Markup.Xaml/Context/PerspexXamlType.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.Reflection; using OmniXaml; using OmniXaml.Typing; using Perspex.Markup.Xaml.Binding; @@ -22,6 +23,14 @@ namespace Perspex.Markup.Xaml.Context return new PerspexXamlMember(name, this, TypeRepository, FeatureProvider); } + protected override AttachableXamlMember LookupAttachableMember(string name) + { + // OmniXAML seems to require a getter and setter even though we don't use them. + var getter = UnderlyingType.GetTypeInfo().GetDeclaredMethod("Get" + name); + var setter = UnderlyingType.GetTypeInfo().GetDeclaredMethod("Set" + name); + return new PerspexAttachableXamlMember(name, this, getter, setter, TypeRepository, FeatureProvider); + } + public override string ToString() { return "Perspex XAML Type " + base.ToString(); diff --git a/src/Markup/Perspex.Markup.Xaml/Perspex.Markup.Xaml.csproj b/src/Markup/Perspex.Markup.Xaml/Perspex.Markup.Xaml.csproj index 987b630907..d34c97c50f 100644 --- a/src/Markup/Perspex.Markup.Xaml/Perspex.Markup.Xaml.csproj +++ b/src/Markup/Perspex.Markup.Xaml/Perspex.Markup.Xaml.csproj @@ -42,6 +42,7 @@ + From 66aedf3d67fdecd28fc7d8a98dfac5f36e3af0dc Mon Sep 17 00:00:00 2001 From: Nikita Tsukanov Date: Wed, 7 Oct 2015 11:50:10 +0300 Subject: [PATCH 15/22] Fixed file dialog crash --- src/Windows/Perspex.Win32/SystemDialogImpl.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/Windows/Perspex.Win32/SystemDialogImpl.cs b/src/Windows/Perspex.Win32/SystemDialogImpl.cs index f237665299..69f394c75c 100644 --- a/src/Windows/Perspex.Win32/SystemDialogImpl.cs +++ b/src/Windows/Perspex.Win32/SystemDialogImpl.cs @@ -90,8 +90,8 @@ namespace Perspex.Win32 : UnmanagedMethods.GetSaveFileName(new IntPtr(pofn)); if (!res) return null; - - userSelectedExt = dialog.Filters[ofn.nFilterIndex - 1].Extensions[0]; + if (dialog?.Filters.Count > 0) + userSelectedExt = dialog.Filters[ofn.nFilterIndex - 1].Extensions.FirstOrDefault(); } var cStart = 0; string dir = null; @@ -117,8 +117,8 @@ namespace Perspex.Win32 if (dialog is SaveFileDialog) { if (string.IsNullOrWhiteSpace(Path.GetExtension(dir)) && - !string.IsNullOrWhiteSpace(userSelectedExt) && - !userSelectedExt.Contains("*")) + !string.IsNullOrWhiteSpace(userSelectedExt) && + !userSelectedExt.Contains("*")) dir = Path.ChangeExtension(dir, userSelectedExt); } From 303f473ffe9bc2d4e1fff82ea32f28d4a8c3e658 Mon Sep 17 00:00:00 2001 From: Nikita Tsukanov Date: Wed, 7 Oct 2015 13:28:57 +0300 Subject: [PATCH 16/22] Added CheckDisposed logic to FormattedText --- src/Perspex.Base/Perspex.Base.csproj | 1 + src/Perspex.Base/PerspexDisposable.cs | 27 +++++++++++++++++++ src/Perspex.SceneGraph/Media/FormattedText.cs | 22 ++++++++++++--- 3 files changed, 46 insertions(+), 4 deletions(-) create mode 100644 src/Perspex.Base/PerspexDisposable.cs diff --git a/src/Perspex.Base/Perspex.Base.csproj b/src/Perspex.Base/Perspex.Base.csproj index abe7582049..b7227d831b 100644 --- a/src/Perspex.Base/Perspex.Base.csproj +++ b/src/Perspex.Base/Perspex.Base.csproj @@ -46,6 +46,7 @@ + diff --git a/src/Perspex.Base/PerspexDisposable.cs b/src/Perspex.Base/PerspexDisposable.cs new file mode 100644 index 0000000000..28a06bc12d --- /dev/null +++ b/src/Perspex.Base/PerspexDisposable.cs @@ -0,0 +1,27 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Perspex +{ + public abstract class PerspexDisposable : IDisposable + { + public bool IsDisposed { get; private set; } + + public void Dispose() + { + IsDisposed = true; + DoDispose(); + } + + protected void CheckDisposed() + { + if (IsDisposed) + throw new ObjectDisposedException(GetType().FullName); + } + + protected abstract void DoDispose(); + } +} diff --git a/src/Perspex.SceneGraph/Media/FormattedText.cs b/src/Perspex.SceneGraph/Media/FormattedText.cs index 245db90eb6..25ef16cd7e 100644 --- a/src/Perspex.SceneGraph/Media/FormattedText.cs +++ b/src/Perspex.SceneGraph/Media/FormattedText.cs @@ -10,7 +10,7 @@ namespace Perspex.Media /// /// Represents a piece of text with formatting. /// - public class FormattedText : IDisposable + public class FormattedText : PerspexDisposable { /// /// Initializes a new instance of the class. @@ -56,8 +56,16 @@ namespace Perspex.Media /// public Size Constraint { - get { return PlatformImpl.Constraint; } - set { PlatformImpl.Constraint = value; } + get + { + CheckDisposed(); + return PlatformImpl.Constraint; + } + set + { + CheckDisposed(); + PlatformImpl.Constraint = value; + } } /// @@ -124,7 +132,7 @@ namespace Perspex.Media /// /// Disposes of unmanaged resources associated with the formatted text. /// - public void Dispose() + protected override void DoDispose() { PlatformImpl.Dispose(); } @@ -137,6 +145,7 @@ namespace Perspex.Media /// public IEnumerable GetLines() { + CheckDisposed(); return PlatformImpl.GetLines(); } @@ -149,6 +158,7 @@ namespace Perspex.Media /// public TextHitTestResult HitTestPoint(Point point) { + CheckDisposed(); return PlatformImpl.HitTestPoint(point); } @@ -159,6 +169,7 @@ namespace Perspex.Media /// The character bounds. public Rect HitTestTextPosition(int index) { + CheckDisposed(); return PlatformImpl.HitTestTextPosition(index); } @@ -170,6 +181,7 @@ namespace Perspex.Media /// The character bounds. public IEnumerable HitTestTextRange(int index, int length) { + CheckDisposed(); return PlatformImpl.HitTestTextRange(index, length); } @@ -179,6 +191,7 @@ namespace Perspex.Media /// The bounds box of the text. public Size Measure() { + CheckDisposed(); return PlatformImpl.Measure(); } @@ -190,6 +203,7 @@ namespace Perspex.Media /// The length of the text range. public void SetForegroundBrush(Brush brush, int startIndex, int length) { + CheckDisposed(); PlatformImpl.SetForegroundBrush(brush, startIndex, length); } } From 21f977ac9da19e219d9a281b8f0b3a2260834489 Mon Sep 17 00:00:00 2001 From: Nikita Tsukanov Date: Wed, 7 Oct 2015 13:35:16 +0300 Subject: [PATCH 17/22] Replaced InvokeAsync with Invoke and InvokeTask to avoid exception swallowing and misunderstanding --- src/Perspex.Base/Threading/Dispatcher.cs | 4 ++-- src/Perspex.Base/Threading/DispatcherTimer.cs | 2 +- src/Perspex.Base/Threading/PerspexSynchronizationContext.cs | 4 ++-- src/Perspex.Controls/Platform/ITopLevelRenderer.cs | 2 +- src/Perspex.Controls/TopLevel.cs | 2 +- 5 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/Perspex.Base/Threading/Dispatcher.cs b/src/Perspex.Base/Threading/Dispatcher.cs index ee9555552c..75ffb5a047 100644 --- a/src/Perspex.Base/Threading/Dispatcher.cs +++ b/src/Perspex.Base/Threading/Dispatcher.cs @@ -58,7 +58,7 @@ namespace Perspex.Threading /// The method. /// The priority with which to invoke the method. /// A task that can be used to track the method's execution. - public Task InvokeAsync(Action action, DispatcherPriority priority = DispatcherPriority.Normal) + public Task InvokeTask(Action action, DispatcherPriority priority = DispatcherPriority.Normal) { return _jobRunner.InvokeAsync(action, priority); } @@ -68,7 +68,7 @@ namespace Perspex.Threading /// /// The method. /// The priority with which to invoke the method. - internal void Post(Action action, DispatcherPriority priority = DispatcherPriority.Normal) + public void Invoke(Action action, DispatcherPriority priority = DispatcherPriority.Normal) { _jobRunner.Post(action, priority); } diff --git a/src/Perspex.Base/Threading/DispatcherTimer.cs b/src/Perspex.Base/Threading/DispatcherTimer.cs index 688cf57605..3547efc20a 100644 --- a/src/Perspex.Base/Threading/DispatcherTimer.cs +++ b/src/Perspex.Base/Threading/DispatcherTimer.cs @@ -177,7 +177,7 @@ namespace Perspex.Threading /// private void InternalTick() { - Dispatcher.UIThread.Post(RaiseTick, _priority); + Dispatcher.UIThread.Invoke(RaiseTick, _priority); } /// diff --git a/src/Perspex.Base/Threading/PerspexSynchronizationContext.cs b/src/Perspex.Base/Threading/PerspexSynchronizationContext.cs index ac7b509740..d6728fb102 100644 --- a/src/Perspex.Base/Threading/PerspexSynchronizationContext.cs +++ b/src/Perspex.Base/Threading/PerspexSynchronizationContext.cs @@ -36,14 +36,14 @@ namespace Perspex.Threading /// public override void Post(SendOrPostCallback d, object state) { - Dispatcher.UIThread.Post(() => d(state)); + Dispatcher.UIThread.Invoke(() => d(state)); } /// public override void Send(SendOrPostCallback d, object state) { // TODO: Add check for being on the main thread, we should invoke the method immediately in this case - Dispatcher.UIThread.InvokeAsync(() => d(state)).Wait(); + Dispatcher.UIThread.InvokeTask(() => d(state)).Wait(); } } } \ No newline at end of file diff --git a/src/Perspex.Controls/Platform/ITopLevelRenderer.cs b/src/Perspex.Controls/Platform/ITopLevelRenderer.cs index 0f58dac76e..c7d0ad891b 100644 --- a/src/Perspex.Controls/Platform/ITopLevelRenderer.cs +++ b/src/Perspex.Controls/Platform/ITopLevelRenderer.cs @@ -44,7 +44,7 @@ namespace Perspex.Controls.Platform })); resources.Add(queueManager.RenderNeeded.Subscribe(_ => - Dispatcher.UIThread.InvokeAsync(() => topLevel.PlatformImpl.Invalidate(new Rect(topLevel.ClientSize))))); + Dispatcher.UIThread.Invoke(() => topLevel.PlatformImpl.Invalidate(new Rect(topLevel.ClientSize))))); topLevel.PlatformImpl.Paint = rect => { diff --git a/src/Perspex.Controls/TopLevel.cs b/src/Perspex.Controls/TopLevel.cs index 25572b588d..a75fbd6bc9 100644 --- a/src/Perspex.Controls/TopLevel.cs +++ b/src/Perspex.Controls/TopLevel.cs @@ -365,7 +365,7 @@ namespace Perspex.Controls /// private void HandleLayoutNeeded() { - Dispatcher.UIThread.InvokeAsync(LayoutManager.ExecuteLayoutPass, DispatcherPriority.Render); + Dispatcher.UIThread.Invoke(LayoutManager.ExecuteLayoutPass, DispatcherPriority.Render); } /// From adede3c171b7204214a2f9fe7747c3e79c053c23 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Wed, 7 Oct 2015 13:31:25 +0200 Subject: [PATCH 18/22] Don't run input tests in parallel. --- tests/Perspex.Input.UnitTests/Properties/AssemblyInfo.cs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/tests/Perspex.Input.UnitTests/Properties/AssemblyInfo.cs b/tests/Perspex.Input.UnitTests/Properties/AssemblyInfo.cs index d9d846e53b..2e99d7b15f 100644 --- a/tests/Perspex.Input.UnitTests/Properties/AssemblyInfo.cs +++ b/tests/Perspex.Input.UnitTests/Properties/AssemblyInfo.cs @@ -2,5 +2,9 @@ // Licensed under the MIT license. See licence.md file in the project root for full license information. using System.Reflection; +using Xunit; [assembly: AssemblyTitle("Perspex.Input.UnitTests")] + +// Don't run tests in parallel. +[assembly: CollectionBehavior(DisableTestParallelization = true)] \ No newline at end of file From 17f90128c9b1407b5a6e66ca856b09ef39d51902 Mon Sep 17 00:00:00 2001 From: Nikita Tsukanov Date: Wed, 7 Oct 2015 14:36:50 +0300 Subject: [PATCH 19/22] Method renaming --- src/Perspex.Base/Threading/Dispatcher.cs | 4 ++-- src/Perspex.Base/Threading/DispatcherTimer.cs | 2 +- src/Perspex.Base/Threading/PerspexSynchronizationContext.cs | 4 ++-- src/Perspex.Controls/Platform/ITopLevelRenderer.cs | 2 +- src/Perspex.Controls/TopLevel.cs | 2 +- 5 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/Perspex.Base/Threading/Dispatcher.cs b/src/Perspex.Base/Threading/Dispatcher.cs index 75ffb5a047..78ec3bdb48 100644 --- a/src/Perspex.Base/Threading/Dispatcher.cs +++ b/src/Perspex.Base/Threading/Dispatcher.cs @@ -58,7 +58,7 @@ namespace Perspex.Threading /// The method. /// The priority with which to invoke the method. /// A task that can be used to track the method's execution. - public Task InvokeTask(Action action, DispatcherPriority priority = DispatcherPriority.Normal) + public Task InvokeTaskAsync(Action action, DispatcherPriority priority = DispatcherPriority.Normal) { return _jobRunner.InvokeAsync(action, priority); } @@ -68,7 +68,7 @@ namespace Perspex.Threading /// /// The method. /// The priority with which to invoke the method. - public void Invoke(Action action, DispatcherPriority priority = DispatcherPriority.Normal) + public void InvokeAsync(Action action, DispatcherPriority priority = DispatcherPriority.Normal) { _jobRunner.Post(action, priority); } diff --git a/src/Perspex.Base/Threading/DispatcherTimer.cs b/src/Perspex.Base/Threading/DispatcherTimer.cs index 3547efc20a..159151dc79 100644 --- a/src/Perspex.Base/Threading/DispatcherTimer.cs +++ b/src/Perspex.Base/Threading/DispatcherTimer.cs @@ -177,7 +177,7 @@ namespace Perspex.Threading /// private void InternalTick() { - Dispatcher.UIThread.Invoke(RaiseTick, _priority); + Dispatcher.UIThread.InvokeAsync(RaiseTick, _priority); } /// diff --git a/src/Perspex.Base/Threading/PerspexSynchronizationContext.cs b/src/Perspex.Base/Threading/PerspexSynchronizationContext.cs index d6728fb102..15941557d4 100644 --- a/src/Perspex.Base/Threading/PerspexSynchronizationContext.cs +++ b/src/Perspex.Base/Threading/PerspexSynchronizationContext.cs @@ -36,14 +36,14 @@ namespace Perspex.Threading /// public override void Post(SendOrPostCallback d, object state) { - Dispatcher.UIThread.Invoke(() => d(state)); + Dispatcher.UIThread.InvokeAsync(() => d(state)); } /// public override void Send(SendOrPostCallback d, object state) { // TODO: Add check for being on the main thread, we should invoke the method immediately in this case - Dispatcher.UIThread.InvokeTask(() => d(state)).Wait(); + Dispatcher.UIThread.InvokeTaskAsync(() => d(state)).Wait(); } } } \ No newline at end of file diff --git a/src/Perspex.Controls/Platform/ITopLevelRenderer.cs b/src/Perspex.Controls/Platform/ITopLevelRenderer.cs index c7d0ad891b..0f58dac76e 100644 --- a/src/Perspex.Controls/Platform/ITopLevelRenderer.cs +++ b/src/Perspex.Controls/Platform/ITopLevelRenderer.cs @@ -44,7 +44,7 @@ namespace Perspex.Controls.Platform })); resources.Add(queueManager.RenderNeeded.Subscribe(_ => - Dispatcher.UIThread.Invoke(() => topLevel.PlatformImpl.Invalidate(new Rect(topLevel.ClientSize))))); + Dispatcher.UIThread.InvokeAsync(() => topLevel.PlatformImpl.Invalidate(new Rect(topLevel.ClientSize))))); topLevel.PlatformImpl.Paint = rect => { diff --git a/src/Perspex.Controls/TopLevel.cs b/src/Perspex.Controls/TopLevel.cs index a75fbd6bc9..25572b588d 100644 --- a/src/Perspex.Controls/TopLevel.cs +++ b/src/Perspex.Controls/TopLevel.cs @@ -365,7 +365,7 @@ namespace Perspex.Controls /// private void HandleLayoutNeeded() { - Dispatcher.UIThread.Invoke(LayoutManager.ExecuteLayoutPass, DispatcherPriority.Render); + Dispatcher.UIThread.InvokeAsync(LayoutManager.ExecuteLayoutPass, DispatcherPriority.Render); } /// From 6c77e2b236169c93b4ea29939c535f83419388ba Mon Sep 17 00:00:00 2001 From: Nikita Tsukanov Date: Wed, 7 Oct 2015 15:26:23 +0300 Subject: [PATCH 20/22] Added code to debug disposables --- src/Perspex.Base/PerspexDisposable.cs | 16 +++++++++++++++- .../Platform/IPclPlatformWrapper.cs | 1 + .../PlatformSupport/PclPlatformWrapper.cs | Bin 1818 -> 1954 bytes 3 files changed, 16 insertions(+), 1 deletion(-) diff --git a/src/Perspex.Base/PerspexDisposable.cs b/src/Perspex.Base/PerspexDisposable.cs index 28a06bc12d..d3017e4b44 100644 --- a/src/Perspex.Base/PerspexDisposable.cs +++ b/src/Perspex.Base/PerspexDisposable.cs @@ -3,23 +3,37 @@ using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; +using Perspex.Platform; namespace Perspex { public abstract class PerspexDisposable : IDisposable { +#if DEBUG_DISPOSE + public string DisposedAt { get; private set; } +#endif + + public bool IsDisposed { get; private set; } public void Dispose() { IsDisposed = true; +#if DEBUG_DISPOSE + DisposedAt = PerspexLocator.Current.GetService().GetStackTrace(); +#endif DoDispose(); } protected void CheckDisposed() { if (IsDisposed) - throw new ObjectDisposedException(GetType().FullName); + throw new ObjectDisposedException(GetType().FullName +#if DEBUG_DISPOSE + , "Disposed at: \n" + DisposedAt +#endif + + ); } protected abstract void DoDispose(); diff --git a/src/Perspex.Base/Platform/IPclPlatformWrapper.cs b/src/Perspex.Base/Platform/IPclPlatformWrapper.cs index 20c836bdaf..68a2a379bf 100644 --- a/src/Perspex.Base/Platform/IPclPlatformWrapper.cs +++ b/src/Perspex.Base/Platform/IPclPlatformWrapper.cs @@ -12,5 +12,6 @@ namespace Perspex.Platform Assembly[] GetLoadedAssemblies(); void PostThreadPoolItem(Action cb); IDisposable StartSystemTimer(TimeSpan interval, Action tick); + string GetStackTrace(); } } diff --git a/src/Shared/PlatformSupport/PclPlatformWrapper.cs b/src/Shared/PlatformSupport/PclPlatformWrapper.cs index 7ef28393543a639d07803a4b261c0685dd8e0dfd..0c7e80fddbc2facc71397d47b61dd9c6131e508b 100644 GIT binary patch delta 134 zcmbQmw}^iO7yIN7Y$B5@nEA?z8A=$67&0017}6OO7~C0Bf&5?~PGm@C$Yuxu%7Iv^ w3>plYKzUmRJ0R%_R9yztQ^b%DWaR>7^ME?^uVd#oFt3&Y0EK55od5s; delta 24 bcmZ3)KZ|bz7rTH00|eGG@G@{Q)G`17I_Cqt From d1c6b50911b9caf62021ed50612d96fb007d8f5c Mon Sep 17 00:00:00 2001 From: Nikita Tsukanov Date: Wed, 7 Oct 2015 16:13:43 +0300 Subject: [PATCH 21/22] Added VerifyAccess call to PerspexObject. We need to do something about Dispatcher and unit tests --- src/Gtk/Perspex.Gtk/GtkPlatform.cs | 4 ++++ src/Perspex.Base/PerspexObject.cs | 18 ++++++++++++++---- .../Platform/IPlatformThreadingInterface.cs | 2 ++ src/Perspex.Base/Threading/Dispatcher.cs | 17 ++++++++++++++--- .../Threading/SingleThreadDispatcher.cs | 5 +++++ src/Windows/Perspex.Win32/Win32Platform.cs | 4 ++++ 6 files changed, 43 insertions(+), 7 deletions(-) diff --git a/src/Gtk/Perspex.Gtk/GtkPlatform.cs b/src/Gtk/Perspex.Gtk/GtkPlatform.cs index 5b52ac84d1..276c0b49f3 100644 --- a/src/Gtk/Perspex.Gtk/GtkPlatform.cs +++ b/src/Gtk/Perspex.Gtk/GtkPlatform.cs @@ -17,6 +17,7 @@ namespace Perspex.Gtk public class GtkPlatform : IPlatformThreadingInterface, IPlatformSettings { private static readonly GtkPlatform s_instance = new GtkPlatform(); + private static Thread _uiThread; public GtkPlatform() { @@ -40,6 +41,7 @@ namespace Perspex.Gtk .Bind().ToConstant(s_instance) .Bind().ToSingleton(); SharedPlatform.Register(); + _uiThread = Thread.CurrentThread; } public bool HasMessages() @@ -79,6 +81,8 @@ namespace Perspex.Gtk Gtk.Application.Invoke(delegate { Signaled?.Invoke(); }); } + public bool CheckForLoopThread() => Thread.CurrentThread == _uiThread; + public event Action Signaled; } } \ No newline at end of file diff --git a/src/Perspex.Base/PerspexObject.cs b/src/Perspex.Base/PerspexObject.cs index 696d01893a..27476c881d 100644 --- a/src/Perspex.Base/PerspexObject.cs +++ b/src/Perspex.Base/PerspexObject.cs @@ -10,6 +10,7 @@ using System.Reactive.Linq; using System.Reactive.Subjects; using System.Reflection; using Perspex.Reactive; +using Perspex.Threading; using Perspex.Utilities; using Serilog; using Serilog.Core.Enrichers; @@ -300,6 +301,10 @@ namespace Perspex } } + public bool CheckAccess() => Dispatcher.UIThread.CheckAccess(); + + public void VerifyAccess() => Dispatcher.UIThread.VerifyAccess(); + /// /// Clears a 's local value. /// @@ -495,7 +500,7 @@ namespace Perspex BindingPriority priority = BindingPriority.LocalValue) { Contract.Requires(property != null); - + VerifyAccess(); if (property.IsDirect) { property = GetRegistered(property); @@ -556,7 +561,7 @@ namespace Perspex BindingPriority priority = BindingPriority.LocalValue) { Contract.Requires(property != null); - + VerifyAccess(); if (property.IsDirect) { property = (PerspexProperty)GetRegistered(property); @@ -590,7 +595,7 @@ namespace Perspex BindingPriority priority = BindingPriority.LocalValue) { Contract.Requires(property != null); - + VerifyAccess(); if (property.IsDirect) { property = GetRegistered(property); @@ -650,7 +655,7 @@ namespace Perspex BindingPriority priority = BindingPriority.LocalValue) { Contract.Requires(property != null); - + VerifyAccess(); if (property.IsDirect) { property = (PerspexProperty)GetRegistered(property); @@ -687,6 +692,7 @@ namespace Perspex PerspexProperty sourceProperty, BindingPriority priority = BindingPriority.LocalValue) { + VerifyAccess(); _propertyLog.Verbose( "Bound two way {Property} to {Binding} with priority {Priority}", property, @@ -716,6 +722,7 @@ namespace Perspex ISubject source, BindingPriority priority = BindingPriority.LocalValue) { + VerifyAccess(); _propertyLog.Verbose( "Bound two way {Property} to {Binding} with priority {Priority}", property, @@ -733,6 +740,7 @@ namespace Perspex /// The property. public void Revalidate(PerspexProperty property) { + VerifyAccess(); PriorityValue value; if (_values.TryGetValue(property, out value)) @@ -788,6 +796,7 @@ namespace Perspex BindingPriority priority) { Contract.Requires(property != null); + VerifyAccess(); PerspexPropertyChangedEventArgs e = new PerspexPropertyChangedEventArgs( this, @@ -824,6 +833,7 @@ namespace Perspex /// protected bool SetAndRaise(PerspexProperty property, ref T field, T value) { + VerifyAccess(); if (!object.Equals(field, value)) { var old = field; diff --git a/src/Perspex.Base/Platform/IPlatformThreadingInterface.cs b/src/Perspex.Base/Platform/IPlatformThreadingInterface.cs index 04cdd5aaba..897cdc1af4 100644 --- a/src/Perspex.Base/Platform/IPlatformThreadingInterface.cs +++ b/src/Perspex.Base/Platform/IPlatformThreadingInterface.cs @@ -26,6 +26,8 @@ namespace Perspex.Platform /// void Signal(); + bool CheckForLoopThread(); + event Action Signaled; } diff --git a/src/Perspex.Base/Threading/Dispatcher.cs b/src/Perspex.Base/Threading/Dispatcher.cs index 78ec3bdb48..de5024fc0b 100644 --- a/src/Perspex.Base/Threading/Dispatcher.cs +++ b/src/Perspex.Base/Threading/Dispatcher.cs @@ -26,10 +26,21 @@ namespace Perspex.Threading public Dispatcher(IPlatformThreadingInterface platform) { _platform = platform; + if(_platform == null) + //TODO: Unit test mode, fix that somehow + return; _jobRunner = new JobRunner(platform); _platform.Signaled += _jobRunner.RunJobs; } + public bool CheckAccess() => _platform?.CheckForLoopThread() ?? true; + + public void VerifyAccess() + { + if (!CheckAccess()) + throw new InvalidOperationException("Call from invalid thread"); + } + /// /// Runs the dispatcher's main loop. @@ -49,7 +60,7 @@ namespace Perspex.Threading /// public void RunJobs() { - _jobRunner.RunJobs(); + _jobRunner?.RunJobs(); } /// @@ -60,7 +71,7 @@ namespace Perspex.Threading /// A task that can be used to track the method's execution. public Task InvokeTaskAsync(Action action, DispatcherPriority priority = DispatcherPriority.Normal) { - return _jobRunner.InvokeAsync(action, priority); + return _jobRunner?.InvokeAsync(action, priority); } /// @@ -70,7 +81,7 @@ namespace Perspex.Threading /// The priority with which to invoke the method. public void InvokeAsync(Action action, DispatcherPriority priority = DispatcherPriority.Normal) { - _jobRunner.Post(action, priority); + _jobRunner?.Post(action, priority); } } } \ No newline at end of file diff --git a/src/Perspex.Base/Threading/SingleThreadDispatcher.cs b/src/Perspex.Base/Threading/SingleThreadDispatcher.cs index 7f1ca48593..027c3a4b28 100644 --- a/src/Perspex.Base/Threading/SingleThreadDispatcher.cs +++ b/src/Perspex.Base/Threading/SingleThreadDispatcher.cs @@ -37,6 +37,11 @@ namespace Perspex.Threading () => _timerJobRunner.Post(tick, DispatcherPriority.Normal)); public void Signal() => _evnt.Set(); + public bool CheckForLoopThread() + { + //TODO: Actually perform check + return true; + } public event Action Signaled; } diff --git a/src/Windows/Perspex.Win32/Win32Platform.cs b/src/Windows/Perspex.Win32/Win32Platform.cs index 896bbcda65..5a79fadfe0 100644 --- a/src/Windows/Perspex.Win32/Win32Platform.cs +++ b/src/Windows/Perspex.Win32/Win32Platform.cs @@ -21,6 +21,7 @@ namespace Perspex.Win32 public class Win32Platform : IPlatformThreadingInterface, IPlatformSettings { private static readonly Win32Platform s_instance = new Win32Platform(); + private static Thread _uiThread; private UnmanagedMethods.WndProc _wndProcDelegate; @@ -52,6 +53,7 @@ namespace Perspex.Win32 .Bind().ToSingleton(); SharedPlatform.Register(); + _uiThread = Thread.CurrentThread; } public static void Initialize() @@ -124,6 +126,8 @@ namespace Perspex.Win32 new IntPtr(SignalL)); } + public bool CheckForLoopThread() => _uiThread == Thread.CurrentThread; + public event Action Signaled; [SuppressMessage("Microsoft.StyleCop.CSharp.NamingRules", "SA1305:FieldNamesMustNotUseHungarianNotation", Justification = "Using Win32 naming for consistency.")] From 45da23550724b0cf3f3ccbf364eb62252843b261 Mon Sep 17 00:00:00 2001 From: Nikita Tsukanov Date: Wed, 7 Oct 2015 16:20:03 +0300 Subject: [PATCH 22/22] Renaming stuff --- src/Gtk/Perspex.Gtk/GtkPlatform.cs | 2 +- src/Perspex.Base/Platform/IPlatformThreadingInterface.cs | 2 +- src/Perspex.Base/Threading/Dispatcher.cs | 2 +- src/Perspex.Base/Threading/SingleThreadDispatcher.cs | 7 ++----- src/Windows/Perspex.Win32/Win32Platform.cs | 2 +- 5 files changed, 6 insertions(+), 9 deletions(-) diff --git a/src/Gtk/Perspex.Gtk/GtkPlatform.cs b/src/Gtk/Perspex.Gtk/GtkPlatform.cs index 276c0b49f3..f6190b3330 100644 --- a/src/Gtk/Perspex.Gtk/GtkPlatform.cs +++ b/src/Gtk/Perspex.Gtk/GtkPlatform.cs @@ -81,7 +81,7 @@ namespace Perspex.Gtk Gtk.Application.Invoke(delegate { Signaled?.Invoke(); }); } - public bool CheckForLoopThread() => Thread.CurrentThread == _uiThread; + public bool CurrentThreadIsLoopThread => Thread.CurrentThread == _uiThread; public event Action Signaled; } diff --git a/src/Perspex.Base/Platform/IPlatformThreadingInterface.cs b/src/Perspex.Base/Platform/IPlatformThreadingInterface.cs index 897cdc1af4..7acbc30fec 100644 --- a/src/Perspex.Base/Platform/IPlatformThreadingInterface.cs +++ b/src/Perspex.Base/Platform/IPlatformThreadingInterface.cs @@ -26,7 +26,7 @@ namespace Perspex.Platform /// void Signal(); - bool CheckForLoopThread(); + bool CurrentThreadIsLoopThread { get; } event Action Signaled; diff --git a/src/Perspex.Base/Threading/Dispatcher.cs b/src/Perspex.Base/Threading/Dispatcher.cs index de5024fc0b..9634eeb4a4 100644 --- a/src/Perspex.Base/Threading/Dispatcher.cs +++ b/src/Perspex.Base/Threading/Dispatcher.cs @@ -33,7 +33,7 @@ namespace Perspex.Threading _platform.Signaled += _jobRunner.RunJobs; } - public bool CheckAccess() => _platform?.CheckForLoopThread() ?? true; + public bool CheckAccess() => _platform?.CurrentThreadIsLoopThread() ?? true; public void VerifyAccess() { diff --git a/src/Perspex.Base/Threading/SingleThreadDispatcher.cs b/src/Perspex.Base/Threading/SingleThreadDispatcher.cs index 027c3a4b28..5e751833f9 100644 --- a/src/Perspex.Base/Threading/SingleThreadDispatcher.cs +++ b/src/Perspex.Base/Threading/SingleThreadDispatcher.cs @@ -37,11 +37,8 @@ namespace Perspex.Threading () => _timerJobRunner.Post(tick, DispatcherPriority.Normal)); public void Signal() => _evnt.Set(); - public bool CheckForLoopThread() - { - //TODO: Actually perform check - return true; - } + //TODO: Actually perform a check + public bool CurrentThreadIsLoopThread => true; public event Action Signaled; } diff --git a/src/Windows/Perspex.Win32/Win32Platform.cs b/src/Windows/Perspex.Win32/Win32Platform.cs index 5a79fadfe0..6ce58e5018 100644 --- a/src/Windows/Perspex.Win32/Win32Platform.cs +++ b/src/Windows/Perspex.Win32/Win32Platform.cs @@ -126,7 +126,7 @@ namespace Perspex.Win32 new IntPtr(SignalL)); } - public bool CheckForLoopThread() => _uiThread == Thread.CurrentThread; + public bool CurrentThreadIsLoopThread => _uiThread == Thread.CurrentThread; public event Action Signaled;