Browse Source

Merge branch 'dpi-aware'

pull/447/head
Steven Kirk 10 years ago
parent
commit
e9611d8c23
  1. 4
      src/Android/Perspex.Android/Platform/SkiaPlatform/WindowImpl.cs
  2. 4
      src/Gtk/Perspex.Gtk/WindowImpl.cs
  3. 10
      src/Perspex.Controls/Platform/ITopLevelImpl.cs
  4. 32
      src/Perspex.Controls/Platform/PlatformManager.cs
  5. 18
      src/Perspex.Controls/TopLevel.cs
  6. 1
      src/Perspex.Input/Perspex.Input.csproj
  7. 5
      src/Perspex.Layout/ILayoutRoot.cs
  8. 40
      src/Perspex.Layout/Layoutable.cs
  9. 1
      src/Perspex.SceneGraph/Perspex.SceneGraph.csproj
  10. 4
      src/Perspex.SceneGraph/Platform/IPlatformSettings.cs
  11. 39
      src/Windows/Perspex.Win32/Interop/UnmanagedMethods.cs
  12. 8
      src/Windows/Perspex.Win32/Win32Platform.cs
  13. 31
      src/Windows/Perspex.Win32/WindowImpl.cs
  14. 4
      src/iOS/Perspex.iOS/PerspexView.cs
  15. 7
      tests/Perspex.Controls.UnitTests/TopLevelTests.cs
  16. 4
      tests/Perspex.Controls.UnitTests/WindowingPlatformMock.cs
  17. 1
      tests/Perspex.Layout.UnitTests/FullLayoutTests.cs
  18. 1
      tests/Perspex.Layout.UnitTests/TestLayoutRoot.cs
  19. 2
      tests/Perspex.UnitTests/MockWindowingPlatform.cs
  20. 2
      tests/Perspex.UnitTests/TestRoot.cs
  21. 2
      tests/Perspex.UnitTests/TestTemplatedRoot.cs

4
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
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
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>

32
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
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)
{

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
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; }
}
}

40
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
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" />

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; }
}
}

39
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,

8
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()
{

31
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)

4
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; }

7
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));
}
}

4
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
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
tests/Perspex.Layout.UnitTests/TestLayoutRoot.cs

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

2
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
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
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;

Loading…
Cancel
Save