Browse Source

Merge branch 'dpi-aware'

Steven Kirk 9 years ago
parent
commit
e9611d8c23

+ 4 - 0
src/Android/Perspex.Android/Platform/SkiaPlatform/WindowImpl.cs

@@ -85,6 +85,8 @@ namespace Perspex.Android.Platform.SkiaPlatform
 
         public Action<Size> Resized { get; set; }
 
+        public Action<double> 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();

+ 4 - 0
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<Size> Resized { get; set; }
 
+        public Action<double> ScalingChanged { get; set; }
+
         public IPopupImpl CreatePopup()
         {
             return new PopupImpl();

+ 10 - 0
src/Perspex.Controls/Platform/ITopLevelImpl.cs

@@ -22,6 +22,11 @@ namespace Perspex.Platform
         /// </summary>
         Size ClientSize { get; set; }
 
+        /// <summary>
+        /// Gets the scaling factor for the window.
+        /// </summary>
+        double Scaling { get; }
+
         /// <summary>
         /// Gets the platform window handle.
         /// </summary>
@@ -57,6 +62,11 @@ namespace Perspex.Platform
         /// </summary>
         Action<Size> Resized { get; set; }
 
+        /// <summary>
+        /// Gets or sets a method called when the window's scaling changes.
+        /// </summary>
+        Action<double> ScalingChanged { get; set; }
+
         /// <summary>
         /// Activates the window.
         /// </summary>

+ 17 - 15
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<RawInputEventArgs> Input { get; set; }
             public Action<Rect> Paint { get; set; }
             public Action<Size> Resized { get; set; }
@@ -163,6 +159,12 @@ namespace Perspex.Controls.Platform
                 set { _window.WindowState = value; }
             }
 
+            public Action<double> ScalingChanged
+            {
+                get { return _tl.ScalingChanged; }
+                set { _tl.ScalingChanged = value; }
+            }
+
             public void Dispose() => _tl.Dispose();
 
             public IPlatformHandle Handle => _tl.Handle;

+ 18 - 0
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
         /// <inheritdoc/>
         Size ILayoutRoot.MaxClientSize => Size.Infinity;
 
+        /// <inheritdoc/>
+        double ILayoutRoot.LayoutScaling => PlatformImpl.Scaling;
+
         IStyleHost IStyleHost.StylingParent
         {
             get { return PerspexLocator.Current.GetService<IGlobalStyles>(); }
@@ -279,6 +284,19 @@ namespace Perspex.Controls
             PlatformImpl.Invalidate(new Rect(clientSize));
         }
 
+        /// <summary>
+        /// Handles a window scaling change notification from 
+        /// <see cref="ITopLevelImpl.ScalingChanged"/>.
+        /// </summary>
+        /// <param name="scaling">The window scaling.</param>
+        protected virtual void HandleScalingChanged(double scaling)
+        {
+            foreach (ILayoutable control in this.GetSelfAndVisualDescendents())
+            {
+                control.InvalidateMeasure();
+            }
+        }
+
         /// <inheritdoc/>
         protected override void OnAttachedToVisualTree(VisualTreeAttachmentEventArgs e)
         {

+ 0 - 1
src/Perspex.Input/Perspex.Input.csproj

@@ -102,7 +102,6 @@
     <Compile Include="Navigation\FocusExtensions.cs" />
     <Compile Include="Navigation\DirectionalNavigation.cs" />
     <Compile Include="Navigation\TabNavigation.cs" />
-    <Compile Include="Platform\IPlatformSettings.cs" />
     <Compile Include="PointerWheelEventArgs.cs" />
     <Compile Include="Raw\RawMouseWheelEventArgs.cs" />
     <Compile Include="Raw\RawSizeEventArgs.cs" />

+ 5 - 0
src/Perspex.Layout/ILayoutRoot.cs

@@ -17,5 +17,10 @@ namespace Perspex.Layout
         /// The maximum client size available.
         /// </summary>
         Size MaxClientSize { get; }
+
+        /// <summary>
+        /// The scaling factor to use in layout.
+        /// </summary>
+        double LayoutScaling { get; }
     }
 }

+ 28 - 12
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;
+        }
     }
 }

+ 1 - 0
src/Perspex.SceneGraph/Perspex.SceneGraph.csproj

@@ -107,6 +107,7 @@
     <Compile Include="Media\TileBrush.cs" />
     <Compile Include="Media\ImageBush.cs" />
     <Compile Include="Media\VisualBrush.cs" />
+    <Compile Include="Platform\IPlatformSettings.cs" />
     <Compile Include="RelativePoint.cs" />
     <Compile Include="Platform\IFormattedTextImpl.cs" />
     <Compile Include="Platform\IBitmapImpl.cs" />

+ 0 - 4
src/Perspex.Input/Platform/IPlatformSettings.cs → 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; }
     }
 }

+ 38 - 1
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,

+ 3 - 5
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<Delegate> _delegates = new List<Delegate>();
 
         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()
         {

+ 25 - 6
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<Size> Resized { get; set; }
 
+        public Action<double> 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)

+ 3 - 1
src/iOS/Perspex.iOS/PerspexView.cs

@@ -70,10 +70,12 @@ namespace Perspex.iOS
         public Action<RawInputEventArgs> Input { get; set; }
         public Action<Rect> Paint { get; set; }
         public Action<Size> Resized { get; set; }
-
+        public Action<double> ScalingChanged { get; set; }
 
         public IPlatformHandle Handle => PerspexPlatformHandle;
 
+        public double Scaling => 1;
+
         public WindowState WindowState
         {
             get { return WindowState.Normal; }

+ 4 - 3
tests/Perspex.Controls.UnitTests/TopLevelTests.cs

@@ -88,6 +88,7 @@ namespace Perspex.Controls.UnitTests
                 var impl = new Mock<ITopLevelImpl>();
                 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<ITopLevelImpl>();
+                var impl = Mock.Of<ITopLevelImpl>(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));
             }
         }
 

+ 2 - 2
tests/Perspex.Controls.UnitTests/WindowingPlatformMock.cs

@@ -17,7 +17,7 @@ namespace Perspex.Controls.UnitTests
 
         public IWindowImpl CreateWindow()
         {
-            return _windowImpl?.Invoke() ?? new Mock<IWindowImpl>().Object;
+            return _windowImpl?.Invoke() ?? Mock.Of<IWindowImpl>(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<IPopupImpl>().Object;
+        public IPopupImpl CreatePopup() => _popupImpl?.Invoke() ?? Mock.Of<IPopupImpl>(x => x.Scaling == 1);
     }
 }

+ 1 - 0
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<IAssetLoader>().ToConstant(new AssetLoader())

+ 1 - 0
tests/Perspex.Layout.UnitTests/TestLayoutRoot.cs

@@ -19,5 +19,6 @@ namespace Perspex.Layout.UnitTests
         }
 
         public Size MaxClientSize => Size.Infinity;
+        public double LayoutScaling => 1;
     }
 }

+ 1 - 1
tests/Perspex.UnitTests/MockWindowingPlatform.cs

@@ -17,7 +17,7 @@ namespace Perspex.UnitTests
 
         public IWindowImpl CreateWindow()
         {
-            return _windowImpl?.Invoke() ?? Mock.Of<IWindowImpl>();
+            return _windowImpl?.Invoke() ?? Mock.Of<IWindowImpl>(x => x.Scaling == 1);
         }
 
         public IWindowImpl CreateEmbeddableWindow()

+ 2 - 0
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<ILayoutManager>();
 
         public IRenderTarget RenderTarget => null;

+ 2 - 0
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<ILayoutManager>();
 
         public IRenderTarget RenderTarget => null;