diff --git a/src/Android/Perspex.Android/Platform/SkiaPlatform/WindowImpl.cs b/src/Android/Perspex.Android/Platform/SkiaPlatform/WindowImpl.cs index 6eb9b6464c..13619286c3 100644 --- a/src/Android/Perspex.Android/Platform/SkiaPlatform/WindowImpl.cs +++ b/src/Android/Perspex.Android/Platform/SkiaPlatform/WindowImpl.cs @@ -85,6 +85,8 @@ namespace Perspex.Android.Platform.SkiaPlatform public Action Resized { get; set; } + public Action ScalingChanged { get; set; } + public View View => this; Action ITopLevelImpl.Activated { get; set; } @@ -149,6 +151,8 @@ namespace Perspex.Android.Platform.SkiaPlatform public Point Position { get; set; } + public double Scaling => 1; + public IDisposable ShowDialog() { throw new NotImplementedException(); diff --git a/src/Gtk/Perspex.Gtk/WindowImpl.cs b/src/Gtk/Perspex.Gtk/WindowImpl.cs index d19f3a120c..cf0278d133 100644 --- a/src/Gtk/Perspex.Gtk/WindowImpl.cs +++ b/src/Gtk/Perspex.Gtk/WindowImpl.cs @@ -108,6 +108,8 @@ namespace Perspex.Gtk } } + public double Scaling => 1; + IPlatformHandle ITopLevelImpl.Handle => this; [DllImport("libgdk-win32-2.0-0.dll", CallingConvention = CallingConvention.Cdecl)] @@ -160,6 +162,8 @@ namespace Perspex.Gtk public Action Resized { get; set; } + public Action ScalingChanged { get; set; } + public IPopupImpl CreatePopup() { return new PopupImpl(); diff --git a/src/Perspex.Controls/Platform/ITopLevelImpl.cs b/src/Perspex.Controls/Platform/ITopLevelImpl.cs index ffaf4dfbf9..59105a571f 100644 --- a/src/Perspex.Controls/Platform/ITopLevelImpl.cs +++ b/src/Perspex.Controls/Platform/ITopLevelImpl.cs @@ -22,6 +22,11 @@ namespace Perspex.Platform /// Size ClientSize { get; set; } + /// + /// Gets the scaling factor for the window. + /// + double Scaling { get; } + /// /// Gets the platform window handle. /// @@ -57,6 +62,11 @@ namespace Perspex.Platform /// Action Resized { get; set; } + /// + /// Gets or sets a method called when the window's scaling changes. + /// + Action ScalingChanged { get; set; } + /// /// Activates the window. /// diff --git a/src/Perspex.Controls/Platform/PlatformManager.cs b/src/Perspex.Controls/Platform/PlatformManager.cs index 17a3ff691f..3c3737cd97 100644 --- a/src/Perspex.Controls/Platform/PlatformManager.cs +++ b/src/Perspex.Controls/Platform/PlatformManager.cs @@ -39,9 +39,6 @@ namespace Perspex.Controls.Platform _designerScalingFactor = factor; } - static double RenderScalingFactor => (GetSettings()?.RenderScalingFactor ?? 1)*_designerScalingFactor; - static double LayoutScalingFactor => (GetSettings()?.LayoutScalingFactor ?? 1) * _designerScalingFactor; - class RenderTargetDecorator : IRenderTarget { private readonly IRenderTarget _target; @@ -62,7 +59,7 @@ namespace Perspex.Controls.Platform { var cs = _window.ClientSize; var ctx = _target.CreateDrawingContext(); - var factor = RenderScalingFactor; + var factor = _window.Scaling; if (factor != 1) { ctx.PushPostTransform(Matrix.CreateScale(factor, factor)); @@ -79,7 +76,6 @@ namespace Perspex.Controls.Platform private readonly IPopupImpl _popup; public ITopLevelImpl TopLevel => _tl; - double ScalingFactor => LayoutScalingFactor; public WindowDecorator(ITopLevelImpl tl) { @@ -93,12 +89,12 @@ namespace Perspex.Controls.Platform private void OnResized(Size size) { - Resized?.Invoke(size/ScalingFactor); + Resized?.Invoke(size/Scaling); } private void OnPaint(Rect rc) { - var f = ScalingFactor; + var f = Scaling; Paint?.Invoke(new Rect(rc.X/f, rc.Y/f, rc.Width/f, rc.Height/f)); } @@ -106,35 +102,35 @@ namespace Perspex.Controls.Platform { var mouseEvent = obj as RawMouseEventArgs; if (mouseEvent != null) - mouseEvent.Position /= ScalingFactor; + mouseEvent.Position /= Scaling; //TODO: Transform event coordinates Input?.Invoke(obj); } public Point PointToClient(Point point) { - return _tl.PointToClient(point / ScalingFactor) * ScalingFactor; + return _tl.PointToClient(point / Scaling) * Scaling; } public Point PointToScreen(Point point) { - return _tl.PointToScreen(point * ScalingFactor) / ScalingFactor; + return _tl.PointToScreen(point * Scaling) / Scaling; } public void Invalidate(Rect rc) { - var f = ScalingFactor; + var f = Scaling; _tl.Invalidate(new Rect(rc.X*f, rc.Y*f, (rc.Width + 1)*f, (rc.Height + 1)*f)); } public Size ClientSize { - get { return _tl.ClientSize/ScalingFactor; } - set { _tl.ClientSize = value*ScalingFactor; } + get { return _tl.ClientSize/Scaling; } + set { _tl.ClientSize = value*Scaling; } } - public Size MaxClientSize => _window.MaxClientSize/ScalingFactor; - + public Size MaxClientSize => _window.MaxClientSize/Scaling; + public double Scaling => _tl.Scaling; public Action Input { get; set; } public Action Paint { get; set; } public Action Resized { get; set; } @@ -163,6 +159,12 @@ namespace Perspex.Controls.Platform set { _window.WindowState = value; } } + public Action ScalingChanged + { + get { return _tl.ScalingChanged; } + set { _tl.ScalingChanged = value; } + } + public void Dispose() => _tl.Dispose(); public IPlatformHandle Handle => _tl.Handle; diff --git a/src/Perspex.Controls/TopLevel.cs b/src/Perspex.Controls/TopLevel.cs index 1b290b3fbc..b82d3da078 100644 --- a/src/Perspex.Controls/TopLevel.cs +++ b/src/Perspex.Controls/TopLevel.cs @@ -12,6 +12,7 @@ using Perspex.Layout; using Perspex.Platform; using Perspex.Rendering; using Perspex.Styling; +using Perspex.VisualTree; namespace Perspex.Controls { @@ -99,6 +100,7 @@ namespace Perspex.Controls PlatformImpl.Closed = HandleClosed; PlatformImpl.Input = HandleInput; PlatformImpl.Resized = HandleResized; + PlatformImpl.ScalingChanged = HandleScalingChanged; _keyboardNavigationHandler?.SetOwner(this); _accessKeyHandler?.SetOwner(this); @@ -198,6 +200,9 @@ namespace Perspex.Controls /// Size ILayoutRoot.MaxClientSize => Size.Infinity; + /// + double ILayoutRoot.LayoutScaling => PlatformImpl.Scaling; + IStyleHost IStyleHost.StylingParent { get { return PerspexLocator.Current.GetService(); } @@ -279,6 +284,19 @@ namespace Perspex.Controls PlatformImpl.Invalidate(new Rect(clientSize)); } + /// + /// Handles a window scaling change notification from + /// . + /// + /// The window scaling. + protected virtual void HandleScalingChanged(double scaling) + { + foreach (ILayoutable control in this.GetSelfAndVisualDescendents()) + { + control.InvalidateMeasure(); + } + } + /// protected override void OnAttachedToVisualTree(VisualTreeAttachmentEventArgs e) { diff --git a/src/Perspex.Input/Perspex.Input.csproj b/src/Perspex.Input/Perspex.Input.csproj index 80e1566f1c..9d9a9080a6 100644 --- a/src/Perspex.Input/Perspex.Input.csproj +++ b/src/Perspex.Input/Perspex.Input.csproj @@ -102,7 +102,6 @@ - diff --git a/src/Perspex.Layout/ILayoutRoot.cs b/src/Perspex.Layout/ILayoutRoot.cs index ea2083b0d0..e1a6ebe727 100644 --- a/src/Perspex.Layout/ILayoutRoot.cs +++ b/src/Perspex.Layout/ILayoutRoot.cs @@ -17,5 +17,10 @@ namespace Perspex.Layout /// The maximum client size available. /// Size MaxClientSize { get; } + + /// + /// The scaling factor to use in layout. + /// + double LayoutScaling { get; } } } diff --git a/src/Perspex.Layout/Layoutable.cs b/src/Perspex.Layout/Layoutable.cs index 0866b05659..763cbb41d9 100644 --- a/src/Perspex.Layout/Layoutable.cs +++ b/src/Perspex.Layout/Layoutable.cs @@ -3,6 +3,7 @@ using System; using System.Linq; +using Perspex.Platform; using Perspex.VisualTree; using Serilog; using Serilog.Core.Enrichers; @@ -466,6 +467,7 @@ namespace Perspex.Layout .Deflate(margin); var measured = MeasureOverride(constrained); + var width = measured.Width; var height = measured.Height; @@ -485,6 +487,13 @@ namespace Perspex.Layout height = Math.Min(height, MaxHeight); height = Math.Max(height, MinHeight); + if (UseLayoutRounding) + { + var scale = GetLayoutScale(); + width = Math.Ceiling(width * scale) / scale; + height = Math.Ceiling(height * scale) / scale; + } + return NonNegative(new Size(width, height).Inflate(margin)); } else @@ -510,12 +519,6 @@ namespace Perspex.Layout height = Math.Max(height, child.DesiredSize.Height); } - if (UseLayoutRounding) - { - width = Math.Ceiling(width); - height = Math.Ceiling(height); - } - return new Size(width, height); } @@ -537,6 +540,7 @@ namespace Perspex.Layout Math.Max(0, finalRect.Width - Margin.Left - Margin.Right), Math.Max(0, finalRect.Height - Margin.Top - Margin.Bottom)); var size = sizeMinusMargins; + var scale = GetLayoutScale(); if (HorizontalAlignment != HorizontalAlignment.Stretch) { @@ -553,11 +557,11 @@ namespace Perspex.Layout if (UseLayoutRounding) { size = new Size( - Math.Ceiling(size.Width), - Math.Ceiling(size.Height)); + Math.Ceiling(size.Width * scale) / scale, + Math.Ceiling(size.Height * scale) / scale); sizeMinusMargins = new Size( - Math.Ceiling(sizeMinusMargins.Width), - Math.Ceiling(sizeMinusMargins.Height)); + Math.Ceiling(sizeMinusMargins.Width * scale) / scale, + Math.Ceiling(sizeMinusMargins.Height * scale) / scale); } size = ArrangeOverride(size).Constrain(size); @@ -586,8 +590,8 @@ namespace Perspex.Layout if (UseLayoutRounding) { - originX = Math.Floor(originX); - originY = Math.Floor(originY); + originX = Math.Floor(originX * scale) / scale; + originY = Math.Floor(originY * scale) / scale; } Bounds = new Rect(originX, originY, size.Width, size.Height); @@ -666,5 +670,17 @@ namespace Perspex.Layout { return new Size(Math.Max(size.Width, 0), Math.Max(size.Height, 0)); } + + private double GetLayoutScale() + { + var result = (VisualRoot as ILayoutRoot)?.LayoutScaling ?? 1.0; + + if (result == 0 || double.IsNaN(result) || double.IsInfinity(result)) + { + throw new Exception($"Invalid LayoutScaling returned from {VisualRoot.GetType()}"); + } + + return result; + } } } diff --git a/src/Perspex.SceneGraph/Perspex.SceneGraph.csproj b/src/Perspex.SceneGraph/Perspex.SceneGraph.csproj index 9f6aa6216e..f4a1428837 100644 --- a/src/Perspex.SceneGraph/Perspex.SceneGraph.csproj +++ b/src/Perspex.SceneGraph/Perspex.SceneGraph.csproj @@ -107,6 +107,7 @@ + diff --git a/src/Perspex.Input/Platform/IPlatformSettings.cs b/src/Perspex.SceneGraph/Platform/IPlatformSettings.cs similarity index 79% rename from src/Perspex.Input/Platform/IPlatformSettings.cs rename to src/Perspex.SceneGraph/Platform/IPlatformSettings.cs index 9b680a388d..d269d01c1c 100644 --- a/src/Perspex.Input/Platform/IPlatformSettings.cs +++ b/src/Perspex.SceneGraph/Platform/IPlatformSettings.cs @@ -10,9 +10,5 @@ namespace Perspex.Platform Size DoubleClickSize { get; } TimeSpan DoubleClickTime { get; } - - double RenderScalingFactor { get; } - - double LayoutScalingFactor { get; } } } diff --git a/src/Windows/Perspex.Win32/Interop/UnmanagedMethods.cs b/src/Windows/Perspex.Win32/Interop/UnmanagedMethods.cs index 4d87d0e033..1809f28f10 100644 --- a/src/Windows/Perspex.Win32/Interop/UnmanagedMethods.cs +++ b/src/Windows/Perspex.Win32/Interop/UnmanagedMethods.cs @@ -490,6 +490,7 @@ namespace Perspex.Win32.Interop WM_WTSSESSION_CHANGE = 0x02B1, WM_TABLET_FIRST = 0x02c0, WM_TABLET_LAST = 0x02df, + WM_DPICHANGED = 0x02E0, WM_CUT = 0x0300, WM_COPY = 0x0301, WM_PASTE = 0x0302, @@ -729,7 +730,6 @@ namespace Perspex.Win32.Interop [DllImport("kernel32.dll", CharSet = CharSet.Auto, ExactSpelling = true)] public static extern IntPtr GlobalLock(IntPtr handle); - [DllImport("kernel32.dll", CharSet = CharSet.Auto, ExactSpelling = true)] public static extern bool GlobalUnlock(IntPtr handle); @@ -748,6 +748,43 @@ namespace Perspex.Win32.Interop [DllImport("comdlg32.dll")] public static extern int CommDlgExtendedError(); + [DllImport("shcore.dll")] + public static extern void SetProcessDpiAwareness(PROCESS_DPI_AWARENESS value); + + [DllImport("shcore.dll")] + public static extern long GetDpiForMonitor(IntPtr hmonitor, MONITOR_DPI_TYPE dpiType, out uint dpiX, out uint dpiY); + + [DllImport("shcore.dll")] + public static extern void GetScaleFactorForMonitor(IntPtr hMon, out uint pScale); + + [DllImport("user32.dll")] + public static extern IntPtr MonitorFromPoint(POINT pt, MONITOR dwFlags); + + [DllImport("user32.dll")] + public static extern IntPtr MonitorFromWindow(IntPtr hwnd, MONITOR dwFlags); + + public enum MONITOR + { + MONITOR_DEFAULTTONULL = 0x00000000, + MONITOR_DEFAULTTOPRIMARY = 0x00000001, + MONITOR_DEFAULTTONEAREST = 0x00000002, + } + + public enum PROCESS_DPI_AWARENESS + { + PROCESS_DPI_UNAWARE = 0, + PROCESS_SYSTEM_DPI_AWARE = 1, + PROCESS_PER_MONITOR_DPI_AWARE = 2 + } + + public enum MONITOR_DPI_TYPE + { + MDT_EFFECTIVE_DPI = 0, + MDT_ANGULAR_DPI = 1, + MDT_RAW_DPI = 2, + MDT_DEFAULT = MDT_EFFECTIVE_DPI + } + public enum ClipboardFormat { CF_TEXT = 1, diff --git a/src/Windows/Perspex.Win32/Win32Platform.cs b/src/Windows/Perspex.Win32/Win32Platform.cs index 89c6646b0c..f8e4f7d35d 100644 --- a/src/Windows/Perspex.Win32/Win32Platform.cs +++ b/src/Windows/Perspex.Win32/Win32Platform.cs @@ -22,15 +22,15 @@ namespace Perspex.Win32 { private static readonly Win32Platform s_instance = new Win32Platform(); private static Thread _uiThread; - private UnmanagedMethods.WndProc _wndProcDelegate; - private IntPtr _hwnd; - private readonly List _delegates = new List(); public Win32Platform() { + // Declare that this process is aware of per monitor DPI + UnmanagedMethods.SetProcessDpiAwareness(UnmanagedMethods.PROCESS_DPI_AWARENESS.PROCESS_PER_MONITOR_DPI_AWARE); + CreateMessageWindow(); } @@ -39,8 +39,6 @@ namespace Perspex.Win32 UnmanagedMethods.GetSystemMetrics(UnmanagedMethods.SystemMetric.SM_CYDOUBLECLK)); public TimeSpan DoubleClickTime => TimeSpan.FromMilliseconds(UnmanagedMethods.GetDoubleClickTime()); - public double RenderScalingFactor { get; } = 1; - public double LayoutScalingFactor { get; } = 1; public static void Initialize() { diff --git a/src/Windows/Perspex.Win32/WindowImpl.cs b/src/Windows/Perspex.Win32/WindowImpl.cs index 35be38c06d..a14748375c 100644 --- a/src/Windows/Perspex.Win32/WindowImpl.cs +++ b/src/Windows/Perspex.Win32/WindowImpl.cs @@ -26,18 +26,13 @@ namespace Perspex.Win32 IntPtr.Zero, new IntPtr((int)UnmanagedMethods.Cursor.IDC_ARROW)); private UnmanagedMethods.WndProc _wndProcDelegate; - private string _className; - private IntPtr _hwnd; - private IInputRoot _owner; - private bool _trackingMouse; - private bool _isActive; - private bool _decorated = true; + private double _scaling = 1; public WindowImpl() { @@ -57,6 +52,8 @@ namespace Perspex.Win32 public Action Resized { get; set; } + public Action ScalingChanged { get; set; } + public Thickness BorderThickness { get @@ -103,6 +100,8 @@ namespace Perspex.Win32 } } + public double Scaling => _scaling; + public IPlatformHandle Handle { get; @@ -410,6 +409,12 @@ namespace Perspex.Win32 return IntPtr.Zero; + case UnmanagedMethods.WindowsMessage.WM_DPICHANGED: + var dpi = (int)wParam & 0xffff; + _scaling = dpi / 96.0; + ScalingChanged?.Invoke(_scaling); + break; + case UnmanagedMethods.WindowsMessage.WM_KEYDOWN: case UnmanagedMethods.WindowsMessage.WM_SYSKEYDOWN: e = new RawKeyEventArgs( @@ -608,6 +613,20 @@ namespace Perspex.Win32 } Handle = new PlatformHandle(_hwnd, PlatformConstants.WindowHandleType); + + var monitor = UnmanagedMethods.MonitorFromWindow( + _hwnd, + UnmanagedMethods.MONITOR.MONITOR_DEFAULTTONEAREST); + + uint dpix, dpiy; + if (UnmanagedMethods.GetDpiForMonitor( + monitor, + UnmanagedMethods.MONITOR_DPI_TYPE.MDT_EFFECTIVE_DPI, + out dpix, + out dpiy) == 0) + { + _scaling = dpix / 96.0; + } } private Point PointFromLParam(IntPtr lParam) diff --git a/src/iOS/Perspex.iOS/PerspexView.cs b/src/iOS/Perspex.iOS/PerspexView.cs index 9c5e5bbe6a..e1df45192d 100644 --- a/src/iOS/Perspex.iOS/PerspexView.cs +++ b/src/iOS/Perspex.iOS/PerspexView.cs @@ -70,10 +70,12 @@ namespace Perspex.iOS public Action Input { get; set; } public Action Paint { get; set; } public Action Resized { get; set; } - + public Action ScalingChanged { get; set; } public IPlatformHandle Handle => PerspexPlatformHandle; + public double Scaling => 1; + public WindowState WindowState { get { return WindowState.Normal; } diff --git a/tests/Perspex.Controls.UnitTests/TopLevelTests.cs b/tests/Perspex.Controls.UnitTests/TopLevelTests.cs index 6a6769d4c1..f03737c728 100644 --- a/tests/Perspex.Controls.UnitTests/TopLevelTests.cs +++ b/tests/Perspex.Controls.UnitTests/TopLevelTests.cs @@ -88,6 +88,7 @@ namespace Perspex.Controls.UnitTests var impl = new Mock(); impl.SetupProperty(x => x.ClientSize); impl.SetupProperty(x => x.Resized); + impl.SetupGet(x => x.Scaling).Returns(1); var target = new TestTopLevel(impl.Object) { @@ -110,9 +111,9 @@ namespace Perspex.Controls.UnitTests { using (UnitTestApplication.Start(TestServices.StyledWindow)) { - var impl = new Mock(); + var impl = Mock.Of(x => x.Scaling == 1); - var target = new TestTopLevel(impl.Object) + var target = new TestTopLevel(impl) { Template = CreateTemplate(), Content = new TextBlock @@ -124,7 +125,7 @@ namespace Perspex.Controls.UnitTests LayoutManager.Instance.ExecuteInitialLayoutPass(target); - impl.VerifySet(x => x.ClientSize = new Size(321, 432)); + Mock.Get(impl).VerifySet(x => x.ClientSize = new Size(321, 432)); } } diff --git a/tests/Perspex.Controls.UnitTests/WindowingPlatformMock.cs b/tests/Perspex.Controls.UnitTests/WindowingPlatformMock.cs index 989cb5f784..6e021fdae8 100644 --- a/tests/Perspex.Controls.UnitTests/WindowingPlatformMock.cs +++ b/tests/Perspex.Controls.UnitTests/WindowingPlatformMock.cs @@ -17,7 +17,7 @@ namespace Perspex.Controls.UnitTests public IWindowImpl CreateWindow() { - return _windowImpl?.Invoke() ?? new Mock().Object; + return _windowImpl?.Invoke() ?? Mock.Of(x => x.Scaling == 1); } public IWindowImpl CreateEmbeddableWindow() @@ -25,6 +25,6 @@ namespace Perspex.Controls.UnitTests throw new NotImplementedException(); } - public IPopupImpl CreatePopup() => _popupImpl?.Invoke() ?? new Mock().Object; + public IPopupImpl CreatePopup() => _popupImpl?.Invoke() ?? Mock.Of(x => x.Scaling == 1); } } \ No newline at end of file diff --git a/tests/Perspex.Layout.UnitTests/FullLayoutTests.cs b/tests/Perspex.Layout.UnitTests/FullLayoutTests.cs index b3ec813f75..6df4fa1f89 100644 --- a/tests/Perspex.Layout.UnitTests/FullLayoutTests.cs +++ b/tests/Perspex.Layout.UnitTests/FullLayoutTests.cs @@ -141,6 +141,7 @@ namespace Perspex.Layout.UnitTests windowImpl.SetupProperty(x => x.ClientSize); windowImpl.Setup(x => x.MaxClientSize).Returns(new Size(1024, 1024)); + windowImpl.SetupGet(x => x.Scaling).Returns(1); PerspexLocator.CurrentMutable .Bind().ToConstant(new AssetLoader()) diff --git a/tests/Perspex.Layout.UnitTests/TestLayoutRoot.cs b/tests/Perspex.Layout.UnitTests/TestLayoutRoot.cs index d186b07169..2c0c1b3b8b 100644 --- a/tests/Perspex.Layout.UnitTests/TestLayoutRoot.cs +++ b/tests/Perspex.Layout.UnitTests/TestLayoutRoot.cs @@ -19,5 +19,6 @@ namespace Perspex.Layout.UnitTests } public Size MaxClientSize => Size.Infinity; + public double LayoutScaling => 1; } } diff --git a/tests/Perspex.UnitTests/MockWindowingPlatform.cs b/tests/Perspex.UnitTests/MockWindowingPlatform.cs index 9f1f8c9c6b..1e8b825baa 100644 --- a/tests/Perspex.UnitTests/MockWindowingPlatform.cs +++ b/tests/Perspex.UnitTests/MockWindowingPlatform.cs @@ -17,7 +17,7 @@ namespace Perspex.UnitTests public IWindowImpl CreateWindow() { - return _windowImpl?.Invoke() ?? Mock.Of(); + return _windowImpl?.Invoke() ?? Mock.Of(x => x.Scaling == 1); } public IWindowImpl CreateEmbeddableWindow() diff --git a/tests/Perspex.UnitTests/TestRoot.cs b/tests/Perspex.UnitTests/TestRoot.cs index 3e41c4cfd6..84a4a15aa7 100644 --- a/tests/Perspex.UnitTests/TestRoot.cs +++ b/tests/Perspex.UnitTests/TestRoot.cs @@ -34,6 +34,8 @@ namespace Perspex.UnitTests public Size MaxClientSize => Size.Infinity; + public double LayoutScaling => 1; + public ILayoutManager LayoutManager => PerspexLocator.Current.GetService(); public IRenderTarget RenderTarget => null; diff --git a/tests/Perspex.UnitTests/TestTemplatedRoot.cs b/tests/Perspex.UnitTests/TestTemplatedRoot.cs index c28b4c02a4..61f619b23c 100644 --- a/tests/Perspex.UnitTests/TestTemplatedRoot.cs +++ b/tests/Perspex.UnitTests/TestTemplatedRoot.cs @@ -37,6 +37,8 @@ namespace Perspex.UnitTests public Size MaxClientSize => Size.Infinity; + public double LayoutScaling => 1; + public ILayoutManager LayoutManager => PerspexLocator.Current.GetService(); public IRenderTarget RenderTarget => null;