From cfb5265a882b8b52d0494504b596f57d256815f1 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Tue, 2 Feb 2016 00:10:03 +0100 Subject: [PATCH 1/7] Merge branch 'master' of https://github.com/Arlorean/Perspex into dpi-aware --- .../Perspex.Win32/Interop/UnmanagedMethods.cs | 30 ++++++++++++++++++- src/Windows/Perspex.Win32/Win32Platform.cs | 23 ++++++++++++-- 2 files changed, 49 insertions(+), 4 deletions(-) diff --git a/src/Windows/Perspex.Win32/Interop/UnmanagedMethods.cs b/src/Windows/Perspex.Win32/Interop/UnmanagedMethods.cs index 02aefb4106..e8ba9bab16 100644 --- a/src/Windows/Perspex.Win32/Interop/UnmanagedMethods.cs +++ b/src/Windows/Perspex.Win32/Interop/UnmanagedMethods.cs @@ -718,7 +718,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); @@ -737,6 +736,35 @@ 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, uint dwFlags); + + public const uint MONITOR_DEFAULTTONULL = 0x00000000; + public const uint MONITOR_DEFAULTTOPRIMARY = 0x00000001; + public const uint 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..19f0795a56 100644 --- a/src/Windows/Perspex.Win32/Win32Platform.cs +++ b/src/Windows/Perspex.Win32/Win32Platform.cs @@ -26,21 +26,38 @@ namespace Perspex.Win32 private UnmanagedMethods.WndProc _wndProcDelegate; private IntPtr _hwnd; - + private double scale = 1.0; private readonly List _delegates = new List(); public Win32Platform() { + HandleDPI(); CreateMessageWindow(); } + private void HandleDPI() { + // Declare that this process is aware of per monitor DPI + UnmanagedMethods.SetProcessDpiAwareness(UnmanagedMethods.PROCESS_DPI_AWARENESS.PROCESS_PER_MONITOR_DPI_AWARE); + + // Get the DPI for the main monitor, and set the scaling factor + UnmanagedMethods.POINT pt = new UnmanagedMethods.POINT() { X = 1, Y = 1 }; + var hMonitor = UnmanagedMethods.MonitorFromPoint(pt, UnmanagedMethods.MONITOR_DEFAULTTONEAREST); + + // TODO: Check for failure + uint dpix, dpiy; + UnmanagedMethods.GetDpiForMonitor(hMonitor, UnmanagedMethods.MONITOR_DPI_TYPE.MDT_EFFECTIVE_DPI, out dpix, out dpiy); + + // Set scale based on x DPI + scale = dpix / 100.0; + } + public Size DoubleClickSize => new Size( UnmanagedMethods.GetSystemMetrics(UnmanagedMethods.SystemMetric.SM_CXDOUBLECLK), UnmanagedMethods.GetSystemMetrics(UnmanagedMethods.SystemMetric.SM_CYDOUBLECLK)); public TimeSpan DoubleClickTime => TimeSpan.FromMilliseconds(UnmanagedMethods.GetDoubleClickTime()); - public double RenderScalingFactor { get; } = 1; - public double LayoutScalingFactor { get; } = 1; + public double RenderScalingFactor { get { return scale; } } + public double LayoutScalingFactor { get { return scale; } } public static void Initialize() { From e23085cc2745aa40522582eb41d6853cd03ffa8b Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Tue, 2 Feb 2016 00:24:30 +0100 Subject: [PATCH 2/7] Fix naming/formatting. --- src/Windows/Perspex.Win32/Win32Platform.cs | 41 +++++++++++----------- 1 file changed, 21 insertions(+), 20 deletions(-) diff --git a/src/Windows/Perspex.Win32/Win32Platform.cs b/src/Windows/Perspex.Win32/Win32Platform.cs index 19f0795a56..987127631e 100644 --- a/src/Windows/Perspex.Win32/Win32Platform.cs +++ b/src/Windows/Perspex.Win32/Win32Platform.cs @@ -26,38 +26,22 @@ namespace Perspex.Win32 private UnmanagedMethods.WndProc _wndProcDelegate; private IntPtr _hwnd; - private double scale = 1.0; + private double _scale = 1.0; private readonly List _delegates = new List(); public Win32Platform() { - HandleDPI(); + HandleDpi(); CreateMessageWindow(); } - private void HandleDPI() { - // Declare that this process is aware of per monitor DPI - UnmanagedMethods.SetProcessDpiAwareness(UnmanagedMethods.PROCESS_DPI_AWARENESS.PROCESS_PER_MONITOR_DPI_AWARE); - - // Get the DPI for the main monitor, and set the scaling factor - UnmanagedMethods.POINT pt = new UnmanagedMethods.POINT() { X = 1, Y = 1 }; - var hMonitor = UnmanagedMethods.MonitorFromPoint(pt, UnmanagedMethods.MONITOR_DEFAULTTONEAREST); - - // TODO: Check for failure - uint dpix, dpiy; - UnmanagedMethods.GetDpiForMonitor(hMonitor, UnmanagedMethods.MONITOR_DPI_TYPE.MDT_EFFECTIVE_DPI, out dpix, out dpiy); - - // Set scale based on x DPI - scale = dpix / 100.0; - } - public Size DoubleClickSize => new Size( UnmanagedMethods.GetSystemMetrics(UnmanagedMethods.SystemMetric.SM_CXDOUBLECLK), UnmanagedMethods.GetSystemMetrics(UnmanagedMethods.SystemMetric.SM_CYDOUBLECLK)); public TimeSpan DoubleClickTime => TimeSpan.FromMilliseconds(UnmanagedMethods.GetDoubleClickTime()); - public double RenderScalingFactor { get { return scale; } } - public double LayoutScalingFactor { get { return scale; } } + public double RenderScalingFactor => _scale; + public double LayoutScalingFactor => _scale; public static void Initialize() { @@ -189,5 +173,22 @@ namespace Perspex.Win32 { return new PopupImpl(); } + + private void HandleDpi() + { + // Declare that this process is aware of per monitor DPI + UnmanagedMethods.SetProcessDpiAwareness(UnmanagedMethods.PROCESS_DPI_AWARENESS.PROCESS_PER_MONITOR_DPI_AWARE); + + // Get the DPI for the main monitor, and set the scaling factor + UnmanagedMethods.POINT pt = new UnmanagedMethods.POINT() { X = 1, Y = 1 }; + var hMonitor = UnmanagedMethods.MonitorFromPoint(pt, UnmanagedMethods.MONITOR_DEFAULTTONEAREST); + + // TODO: Check for failure + uint dpix, dpiy; + UnmanagedMethods.GetDpiForMonitor(hMonitor, UnmanagedMethods.MONITOR_DPI_TYPE.MDT_EFFECTIVE_DPI, out dpix, out dpiy); + + // Set scale based on x DPI + _scale = dpix / 100.0; + } } } From a8c89031628023ddd117fdf8f74c3b00302509d4 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Tue, 2 Feb 2016 00:26:29 +0100 Subject: [PATCH 3/7] Moved IPlatformSettings to SceneGraph. It's the lowest it can go in the stack as it depends on Size. --- .../Platform/IPlatformSettings.cs | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename src/{Perspex.Input => Perspex.SceneGraph}/Platform/IPlatformSettings.cs (100%) diff --git a/src/Perspex.Input/Platform/IPlatformSettings.cs b/src/Perspex.SceneGraph/Platform/IPlatformSettings.cs similarity index 100% rename from src/Perspex.Input/Platform/IPlatformSettings.cs rename to src/Perspex.SceneGraph/Platform/IPlatformSettings.cs From d460ec9fd0f646797821f72d5133c2fb7ec6ffd5 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Tue, 2 Feb 2016 01:07:31 +0100 Subject: [PATCH 4/7] Implemented basic layout scaling. Maybe works... I don't have a high DPI monitor to test on... --- src/Perspex.Input/Perspex.Input.csproj | 1 - src/Perspex.Layout/Layoutable.cs | 33 ++++++++++++------- .../Perspex.SceneGraph.csproj | 1 + src/Windows/Perspex.Win32/Win32Platform.cs | 2 +- 4 files changed, 23 insertions(+), 14 deletions(-) diff --git a/src/Perspex.Input/Perspex.Input.csproj b/src/Perspex.Input/Perspex.Input.csproj index babd0e034b..4cd3ce4363 100644 --- a/src/Perspex.Input/Perspex.Input.csproj +++ b/src/Perspex.Input/Perspex.Input.csproj @@ -100,7 +100,6 @@ - diff --git a/src/Perspex.Layout/Layoutable.cs b/src/Perspex.Layout/Layoutable.cs index 0866b05659..ce47d68e4c 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,10 @@ namespace Perspex.Layout { return new Size(Math.Max(size.Width, 0), Math.Max(size.Height, 0)); } + + private static double GetLayoutScale() + { + return PerspexLocator.Current.GetService()?.LayoutScalingFactor ?? 1.0; + } } } diff --git a/src/Perspex.SceneGraph/Perspex.SceneGraph.csproj b/src/Perspex.SceneGraph/Perspex.SceneGraph.csproj index 6578b9c7b5..af4e85061d 100644 --- a/src/Perspex.SceneGraph/Perspex.SceneGraph.csproj +++ b/src/Perspex.SceneGraph/Perspex.SceneGraph.csproj @@ -106,6 +106,7 @@ + diff --git a/src/Windows/Perspex.Win32/Win32Platform.cs b/src/Windows/Perspex.Win32/Win32Platform.cs index 987127631e..d23a74f911 100644 --- a/src/Windows/Perspex.Win32/Win32Platform.cs +++ b/src/Windows/Perspex.Win32/Win32Platform.cs @@ -188,7 +188,7 @@ namespace Perspex.Win32 UnmanagedMethods.GetDpiForMonitor(hMonitor, UnmanagedMethods.MONITOR_DPI_TYPE.MDT_EFFECTIVE_DPI, out dpix, out dpiy); // Set scale based on x DPI - _scale = dpix / 100.0; + _scale = dpix / 96.0; } } } From 93b4d810b9323ea50d10ebc62dfd896595bce718 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Tue, 2 Feb 2016 20:46:05 +0100 Subject: [PATCH 5/7] Moved scaling to per-window. Always returning a scaling factor of 1 currently. --- src/Gtk/Perspex.Gtk/WindowImpl.cs | 2 ++ .../Platform/ITopLevelImpl.cs | 5 ++++ .../Platform/PlatformManager.cs | 24 ++++++++----------- src/Perspex.Controls/TopLevel.cs | 3 +++ src/Perspex.Layout/ILayoutRoot.cs | 5 ++++ src/Perspex.Layout/Layoutable.cs | 4 ++-- .../Platform/IPlatformSettings.cs | 4 ---- src/Windows/Perspex.Win32/WindowImpl.cs | 2 ++ .../ControlTests.cs | 2 ++ tests/Perspex.Controls.UnitTests/TestRoot.cs | 2 ++ .../TopLevelTests.cs | 7 +++--- .../WindowingPlatformMock.cs | 4 ++-- .../FullLayoutTests.cs | 1 + .../TestLayoutRoot.cs | 1 + tests/Perspex.LeakTests/TestApp.cs | 4 ++-- tests/Perspex.Styling.UnitTests/TestRoot.cs | 2 ++ 16 files changed, 45 insertions(+), 27 deletions(-) diff --git a/src/Gtk/Perspex.Gtk/WindowImpl.cs b/src/Gtk/Perspex.Gtk/WindowImpl.cs index 0435a052ba..c9ed1bdfcd 100644 --- a/src/Gtk/Perspex.Gtk/WindowImpl.cs +++ b/src/Gtk/Perspex.Gtk/WindowImpl.cs @@ -75,6 +75,8 @@ namespace Perspex.Gtk } } + public double Scaling => 1; + IPlatformHandle ITopLevelImpl.Handle => this; [DllImport("libgdk-win32-2.0-0.dll", CallingConvention = CallingConvention.Cdecl)] diff --git a/src/Perspex.Controls/Platform/ITopLevelImpl.cs b/src/Perspex.Controls/Platform/ITopLevelImpl.cs index 7c80b951a5..2bfb1f4a9a 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. /// diff --git a/src/Perspex.Controls/Platform/PlatformManager.cs b/src/Perspex.Controls/Platform/PlatformManager.cs index 6cec6cfadf..400c77d559 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,31 +102,31 @@ 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 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; } diff --git a/src/Perspex.Controls/TopLevel.cs b/src/Perspex.Controls/TopLevel.cs index e019301609..92cc25f51a 100644 --- a/src/Perspex.Controls/TopLevel.cs +++ b/src/Perspex.Controls/TopLevel.cs @@ -198,6 +198,9 @@ namespace Perspex.Controls /// Size ILayoutRoot.MaxClientSize => Size.Infinity; + /// + double ILayoutRoot.LayoutScaling => PlatformImpl.Scaling; + IStyleHost IStyleHost.StylingParent { get { return PerspexLocator.Current.GetService(); } 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 ce47d68e4c..119622bf4b 100644 --- a/src/Perspex.Layout/Layoutable.cs +++ b/src/Perspex.Layout/Layoutable.cs @@ -671,9 +671,9 @@ namespace Perspex.Layout return new Size(Math.Max(size.Width, 0), Math.Max(size.Height, 0)); } - private static double GetLayoutScale() + private double GetLayoutScale() { - return PerspexLocator.Current.GetService()?.LayoutScalingFactor ?? 1.0; + return (VisualRoot as ILayoutRoot)?.LayoutScaling ?? 1.0; } } } diff --git a/src/Perspex.SceneGraph/Platform/IPlatformSettings.cs b/src/Perspex.SceneGraph/Platform/IPlatformSettings.cs index 9b680a388d..d269d01c1c 100644 --- a/src/Perspex.SceneGraph/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/WindowImpl.cs b/src/Windows/Perspex.Win32/WindowImpl.cs index 49383c10e3..f003c9a5a9 100644 --- a/src/Windows/Perspex.Win32/WindowImpl.cs +++ b/src/Windows/Perspex.Win32/WindowImpl.cs @@ -103,6 +103,8 @@ namespace Perspex.Win32 } } + public double Scaling => 1; + public IPlatformHandle Handle { get; diff --git a/tests/Perspex.Controls.UnitTests/ControlTests.cs b/tests/Perspex.Controls.UnitTests/ControlTests.cs index 30c1aafc38..2ef3cdd9d4 100644 --- a/tests/Perspex.Controls.UnitTests/ControlTests.cs +++ b/tests/Perspex.Controls.UnitTests/ControlTests.cs @@ -174,6 +174,8 @@ namespace Perspex.Controls.UnitTests get { throw new NotImplementedException(); } } + public double LayoutScaling => 1; + public Point TranslatePointToScreen(Point p) { throw new NotImplementedException(); diff --git a/tests/Perspex.Controls.UnitTests/TestRoot.cs b/tests/Perspex.Controls.UnitTests/TestRoot.cs index cc911c8b90..9b15e58f0d 100644 --- a/tests/Perspex.Controls.UnitTests/TestRoot.cs +++ b/tests/Perspex.Controls.UnitTests/TestRoot.cs @@ -23,6 +23,8 @@ namespace Perspex.Controls.UnitTests get { throw new NotImplementedException(); } } + public double LayoutScaling => 1; + public IRenderQueueManager RenderQueueManager => null; public Point TranslatePointToScreen(Point p) diff --git a/tests/Perspex.Controls.UnitTests/TopLevelTests.cs b/tests/Perspex.Controls.UnitTests/TopLevelTests.cs index 9999630b85..86b86f1708 100644 --- a/tests/Perspex.Controls.UnitTests/TopLevelTests.cs +++ b/tests/Perspex.Controls.UnitTests/TopLevelTests.cs @@ -96,6 +96,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) { @@ -121,9 +122,9 @@ namespace Perspex.Controls.UnitTests RegisterServices(); PerspexLocator.CurrentMutable.Bind().ToConstant(new LayoutManager()); - 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 @@ -135,7 +136,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.LeakTests/TestApp.cs b/tests/Perspex.LeakTests/TestApp.cs index d95d249f7d..0484af5065 100644 --- a/tests/Perspex.LeakTests/TestApp.cs +++ b/tests/Perspex.LeakTests/TestApp.cs @@ -19,7 +19,7 @@ namespace Perspex.LeakTests RegisterServices(); var fixture = new Fixture().Customize(new AutoMoqCustomization()); - var windowImpl = new Mock(); + var windowImpl = Mock.Of(x => x.Scaling == 1); var renderInterface = fixture.Create(); var threadingInterface = Mock.Of(x => x.CurrentThreadIsLoopThread == true); @@ -31,7 +31,7 @@ namespace Perspex.LeakTests .Bind().ToConstant(renderInterface) .Bind().ToConstant(threadingInterface) .Bind().ToConstant(new Mock().Object) - .Bind().ToConstant(new WindowingPlatformMock(() => windowImpl.Object)); + .Bind().ToConstant(new WindowingPlatformMock(() => windowImpl)); Styles = new DefaultTheme(); } diff --git a/tests/Perspex.Styling.UnitTests/TestRoot.cs b/tests/Perspex.Styling.UnitTests/TestRoot.cs index cc3deed060..6c906184c5 100644 --- a/tests/Perspex.Styling.UnitTests/TestRoot.cs +++ b/tests/Perspex.Styling.UnitTests/TestRoot.cs @@ -26,6 +26,8 @@ namespace Perspex.Styling.UnitTests get { throw new NotImplementedException(); } } + public double LayoutScaling => 1; + public Point TranslatePointToScreen(Point p) { return new Point(); From e742237d00803e0940cce96db9cbeb5de7caee3e Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Tue, 2 Feb 2016 20:57:37 +0100 Subject: [PATCH 6/7] Use actual DPI for scaling factor. Doesn't change when DPI changes yet though. --- .../Perspex.Win32/Interop/UnmanagedMethods.cs | 20 +++++++++----- src/Windows/Perspex.Win32/Win32Platform.cs | 26 +++---------------- src/Windows/Perspex.Win32/WindowImpl.cs | 23 +++++++++++----- 3 files changed, 33 insertions(+), 36 deletions(-) diff --git a/src/Windows/Perspex.Win32/Interop/UnmanagedMethods.cs b/src/Windows/Perspex.Win32/Interop/UnmanagedMethods.cs index e8ba9bab16..e75174012f 100644 --- a/src/Windows/Perspex.Win32/Interop/UnmanagedMethods.cs +++ b/src/Windows/Perspex.Win32/Interop/UnmanagedMethods.cs @@ -746,19 +746,27 @@ namespace Perspex.Win32.Interop public static extern void GetScaleFactorForMonitor(IntPtr hMon, out uint pScale); [DllImport("user32.dll")] - public static extern IntPtr MonitorFromPoint(POINT pt, uint dwFlags); + public static extern IntPtr MonitorFromPoint(POINT pt, MONITOR dwFlags); - public const uint MONITOR_DEFAULTTONULL = 0x00000000; - public const uint MONITOR_DEFAULTTOPRIMARY = 0x00000001; - public const uint MONITOR_DEFAULTTONEAREST = 0x00000002; + [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 { + 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 { + public enum MONITOR_DPI_TYPE + { MDT_EFFECTIVE_DPI = 0, MDT_ANGULAR_DPI = 1, MDT_RAW_DPI = 2, diff --git a/src/Windows/Perspex.Win32/Win32Platform.cs b/src/Windows/Perspex.Win32/Win32Platform.cs index d23a74f911..f8e4f7d35d 100644 --- a/src/Windows/Perspex.Win32/Win32Platform.cs +++ b/src/Windows/Perspex.Win32/Win32Platform.cs @@ -22,16 +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 double _scale = 1.0; private readonly List _delegates = new List(); public Win32Platform() { - HandleDpi(); + // Declare that this process is aware of per monitor DPI + UnmanagedMethods.SetProcessDpiAwareness(UnmanagedMethods.PROCESS_DPI_AWARENESS.PROCESS_PER_MONITOR_DPI_AWARE); + CreateMessageWindow(); } @@ -40,8 +39,6 @@ namespace Perspex.Win32 UnmanagedMethods.GetSystemMetrics(UnmanagedMethods.SystemMetric.SM_CYDOUBLECLK)); public TimeSpan DoubleClickTime => TimeSpan.FromMilliseconds(UnmanagedMethods.GetDoubleClickTime()); - public double RenderScalingFactor => _scale; - public double LayoutScalingFactor => _scale; public static void Initialize() { @@ -173,22 +170,5 @@ namespace Perspex.Win32 { return new PopupImpl(); } - - private void HandleDpi() - { - // Declare that this process is aware of per monitor DPI - UnmanagedMethods.SetProcessDpiAwareness(UnmanagedMethods.PROCESS_DPI_AWARENESS.PROCESS_PER_MONITOR_DPI_AWARE); - - // Get the DPI for the main monitor, and set the scaling factor - UnmanagedMethods.POINT pt = new UnmanagedMethods.POINT() { X = 1, Y = 1 }; - var hMonitor = UnmanagedMethods.MonitorFromPoint(pt, UnmanagedMethods.MONITOR_DEFAULTTONEAREST); - - // TODO: Check for failure - uint dpix, dpiy; - UnmanagedMethods.GetDpiForMonitor(hMonitor, UnmanagedMethods.MONITOR_DPI_TYPE.MDT_EFFECTIVE_DPI, out dpix, out dpiy); - - // Set scale based on x DPI - _scale = dpix / 96.0; - } } } diff --git a/src/Windows/Perspex.Win32/WindowImpl.cs b/src/Windows/Perspex.Win32/WindowImpl.cs index f003c9a5a9..e16c5991f8 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() { @@ -103,7 +98,7 @@ namespace Perspex.Win32 } } - public double Scaling => 1; + public double Scaling => _scaling; public IPlatformHandle Handle { @@ -562,6 +557,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) From e55ecb9960f0207f7d7b4a9c35f0f08801760a20 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Tue, 2 Feb 2016 21:14:45 +0100 Subject: [PATCH 7/7] Listen for DPI changes and update layout. --- src/Gtk/Perspex.Gtk/WindowImpl.cs | 2 ++ src/Perspex.Controls/Platform/ITopLevelImpl.cs | 5 +++++ src/Perspex.Controls/Platform/PlatformManager.cs | 8 ++++++-- src/Perspex.Controls/TopLevel.cs | 15 +++++++++++++++ .../Perspex.Win32/Interop/UnmanagedMethods.cs | 1 + src/Windows/Perspex.Win32/WindowImpl.cs | 8 ++++++++ 6 files changed, 37 insertions(+), 2 deletions(-) diff --git a/src/Gtk/Perspex.Gtk/WindowImpl.cs b/src/Gtk/Perspex.Gtk/WindowImpl.cs index c9ed1bdfcd..057379899d 100644 --- a/src/Gtk/Perspex.Gtk/WindowImpl.cs +++ b/src/Gtk/Perspex.Gtk/WindowImpl.cs @@ -129,6 +129,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 2bfb1f4a9a..57ffb3ef9d 100644 --- a/src/Perspex.Controls/Platform/ITopLevelImpl.cs +++ b/src/Perspex.Controls/Platform/ITopLevelImpl.cs @@ -62,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 400c77d559..55dc4cce48 100644 --- a/src/Perspex.Controls/Platform/PlatformManager.cs +++ b/src/Perspex.Controls/Platform/PlatformManager.cs @@ -131,8 +131,6 @@ namespace Perspex.Controls.Platform public Action Paint { get; set; } public Action Resized { get; set; } - - public Action Activated { get { return _tl.Activated; } @@ -151,6 +149,12 @@ namespace Perspex.Controls.Platform set { _tl.Deactivated = 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 92cc25f51a..3f02f73bc3 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); @@ -280,6 +282,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/Windows/Perspex.Win32/Interop/UnmanagedMethods.cs b/src/Windows/Perspex.Win32/Interop/UnmanagedMethods.cs index e75174012f..5e50f35feb 100644 --- a/src/Windows/Perspex.Win32/Interop/UnmanagedMethods.cs +++ b/src/Windows/Perspex.Win32/Interop/UnmanagedMethods.cs @@ -489,6 +489,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, diff --git a/src/Windows/Perspex.Win32/WindowImpl.cs b/src/Windows/Perspex.Win32/WindowImpl.cs index e16c5991f8..d0b7dd1158 100644 --- a/src/Windows/Perspex.Win32/WindowImpl.cs +++ b/src/Windows/Perspex.Win32/WindowImpl.cs @@ -52,6 +52,8 @@ namespace Perspex.Win32 public Action Resized { get; set; } + public Action ScalingChanged { get; set; } + public Thickness BorderThickness { get @@ -359,6 +361,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(