From 792d9f1a0589fff578948345bfdb0197b5767d48 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Sat, 5 Jan 2019 17:46:29 +0100 Subject: [PATCH 01/14] Added failing test for #2191. Check that when content is created via `DataTemplate`, it is correctly added as a logical child. --- .../TabControlTests.cs | 50 +++++++++++++------ 1 file changed, 35 insertions(+), 15 deletions(-) diff --git a/tests/Avalonia.Controls.UnitTests/TabControlTests.cs b/tests/Avalonia.Controls.UnitTests/TabControlTests.cs index a5c3881d37..ddc573c74b 100644 --- a/tests/Avalonia.Controls.UnitTests/TabControlTests.cs +++ b/tests/Avalonia.Controls.UnitTests/TabControlTests.cs @@ -267,26 +267,46 @@ namespace Avalonia.Controls.UnitTests Assert.Null(page.Content); } + [Fact] + public void DataTemplate_Created_Content_Should_Be_Logical_Child_After_ApplyTemplate() + { + TabControl target = new TabControl + { + Template = TabControlTemplate(), + ContentTemplate = new FuncDataTemplate(x => + new TextBlock { Tag = "bar", Text = x }), + Items = new[] { "Foo" }, + }; + + ApplyTemplate(target); + target.ContentPart.UpdateChild(); + + var content = Assert.IsType(target.ContentPart.Child); + Assert.Equal("bar", content.Tag); + Assert.Same(target, content.GetLogicalParent()); + Assert.Single(target.GetLogicalChildren(), content); + } + private IControlTemplate TabControlTemplate() { return new FuncControlTemplate(parent => - new StackPanel { - Children = { - new ItemsPresenter - { - Name = "PART_ItemsPresenter", - [!TabStrip.ItemsProperty] = parent[!TabControl.ItemsProperty], - [!TabStrip.ItemTemplateProperty] = parent[!TabControl.ItemTemplateProperty], - }, - new ContentPresenter - { - Name = "PART_Content", - [!ContentPresenter.ContentProperty] = parent[!TabControl.SelectedContentProperty], - [!ContentPresenter.ContentTemplateProperty] = parent[!TabControl.SelectedContentTemplateProperty], - } - } + Children = + { + new ItemsPresenter + { + Name = "PART_ItemsPresenter", + [!TabStrip.ItemsProperty] = parent[!TabControl.ItemsProperty], + [!TabStrip.ItemTemplateProperty] = parent[!TabControl.ItemTemplateProperty], + }, + new ContentPresenter + { + Name = "PART_Content", + [!ContentPresenter.ContentProperty] = parent[!TabControl.SelectedContentProperty], + [!ContentPresenter.ContentTemplateProperty] = parent[!TabControl.SelectedContentTemplateProperty], + } + } }); } From 3d578464c60a91c2dd7a621966f3691f1fdcef72 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Sat, 5 Jan 2019 18:17:55 +0100 Subject: [PATCH 02/14] Register TabControl with ContentControlMixin. `ContentControlMixin` implements a number of behaviors related to logical children that controls that have a `ContentPresenter` need in order to function correctly. Also renamed `PART_Content` to `PART_SelectedContentHost` to match WPF Fixes #2191 --- samples/ControlCatalog/SideBar.xaml | 2 +- src/Avalonia.Controls/TabControl.cs | 7 ++++++- src/Avalonia.Themes.Default/TabControl.xaml | 2 +- tests/Avalonia.Controls.UnitTests/TabControlTests.cs | 2 +- 4 files changed, 9 insertions(+), 4 deletions(-) diff --git a/samples/ControlCatalog/SideBar.xaml b/samples/ControlCatalog/SideBar.xaml index f97a3064e7..625b344b8c 100644 --- a/samples/ControlCatalog/SideBar.xaml +++ b/samples/ControlCatalog/SideBar.xaml @@ -26,7 +26,7 @@ (SelectionMode.AlwaysSelected); ItemsPanelProperty.OverrideDefaultValue(DefaultPanel); AffectsMeasure(TabStripPlacementProperty); + ContentControlMixin.Attach( + SelectedContentProperty, + x => x.LogicalChildren, + "PART_SelectedContentHost"); } /// @@ -142,7 +147,7 @@ namespace Avalonia.Controls ItemsPresenterPart = e.NameScope.Get("PART_ItemsPresenter"); - ContentPart = e.NameScope.Get("PART_Content"); + ContentPart = e.NameScope.Get("PART_SelectedContentHost"); } /// diff --git a/src/Avalonia.Themes.Default/TabControl.xaml b/src/Avalonia.Themes.Default/TabControl.xaml index a492698feb..60c70ebfda 100644 --- a/src/Avalonia.Themes.Default/TabControl.xaml +++ b/src/Avalonia.Themes.Default/TabControl.xaml @@ -18,7 +18,7 @@ MemberSelector="{TemplateBinding MemberSelector}" > Date: Sat, 5 Jan 2019 18:19:17 +0100 Subject: [PATCH 03/14] Put TabControlPage in alphabetical order. --- samples/ControlCatalog/MainView.xaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/samples/ControlCatalog/MainView.xaml b/samples/ControlCatalog/MainView.xaml index 0f7f2e80a8..2ddb5887e5 100644 --- a/samples/ControlCatalog/MainView.xaml +++ b/samples/ControlCatalog/MainView.xaml @@ -32,10 +32,10 @@ + - From 985a3e7f2e62890ea252afbbefb8ccb719464539 Mon Sep 17 00:00:00 2001 From: Dmitry Zhelnin Date: Fri, 18 Jan 2019 02:04:50 +0300 Subject: [PATCH 04/14] fix ValueChanged event of NumericUpDown --- src/Avalonia.Controls/NumericUpDown/NumericUpDown.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Avalonia.Controls/NumericUpDown/NumericUpDown.cs b/src/Avalonia.Controls/NumericUpDown/NumericUpDown.cs index 57db1e19bb..1f8e991d2e 100644 --- a/src/Avalonia.Controls/NumericUpDown/NumericUpDown.cs +++ b/src/Avalonia.Controls/NumericUpDown/NumericUpDown.cs @@ -812,7 +812,7 @@ namespace Avalonia.Controls /// /// Raised when the changes. /// - public event EventHandler ValueChanged + public event EventHandler ValueChanged { add { AddHandler(ValueChangedEvent, value); } remove { RemoveHandler(ValueChangedEvent, value); } From 009b9a1ae7a96b100a461fd8745204d53dfcc46d Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Fri, 18 Jan 2019 10:59:19 +0100 Subject: [PATCH 05/14] Make PixelSize accept a scaling factor for conversions. For conversions between `PixelSize` and `Size` use a scaling factor by default; rename the DPI overloads to have a `WithDpi` suffix. --- src/Avalonia.Visuals/Media/Imaging/Bitmap.cs | 2 +- src/Avalonia.Visuals/Media/PixelSize.cs | 50 +++++++++++++++---- src/Skia/Avalonia.Skia/DrawingContextImpl.cs | 8 +-- .../Media/DrawingContextImpl.cs | 6 +-- .../Media/ImageBrushImpl.cs | 4 +- .../Imaging/D2DRenderTargetBitmapImpl.cs | 4 +- 6 files changed, 53 insertions(+), 21 deletions(-) diff --git a/src/Avalonia.Visuals/Media/Imaging/Bitmap.cs b/src/Avalonia.Visuals/Media/Imaging/Bitmap.cs index 0ad826f5a9..8dd75d2374 100644 --- a/src/Avalonia.Visuals/Media/Imaging/Bitmap.cs +++ b/src/Avalonia.Visuals/Media/Imaging/Bitmap.cs @@ -75,7 +75,7 @@ namespace Avalonia.Media.Imaging public Vector Dpi => PlatformImpl.Item.Dpi; /// - public Size Size => PlatformImpl.Item.PixelSize.ToSize(Dpi); + public Size Size => PlatformImpl.Item.PixelSize.ToSizeWithDpi(Dpi); /// public PixelSize PixelSize => PlatformImpl.Item.PixelSize; diff --git a/src/Avalonia.Visuals/Media/PixelSize.cs b/src/Avalonia.Visuals/Media/PixelSize.cs index 6785b51716..b903b804f9 100644 --- a/src/Avalonia.Visuals/Media/PixelSize.cs +++ b/src/Avalonia.Visuals/Media/PixelSize.cs @@ -72,7 +72,7 @@ namespace Avalonia /// The . public static PixelSize Parse(string s) { - using (var tokenizer = new StringTokenizer(s, CultureInfo.InvariantCulture, exceptionMessage: "Invalid Size")) + using (var tokenizer = new StringTokenizer(s, CultureInfo.InvariantCulture, exceptionMessage: "Invalid PixelSize")) { return new PixelSize( tokenizer.ReadInt32(), @@ -126,13 +126,29 @@ namespace Avalonia /// The new . public PixelSize WithHeight(int height) => new PixelSize(Width, height); + /// + /// Converts the to a device-independent using the + /// specified scaling factor. + /// + /// The scaling factor. + /// The device-independent size. + public Size ToSize(double scale) => new Size(Width / scale, Height / scale); + + /// + /// Converts the to a device-independent using the + /// specified scaling factor. + /// + /// The scaling factor. + /// The device-independent size. + public Size ToSize(Vector scale) => new Size(Width / scale.X, Height / scale.Y); + /// /// Converts the to a device-independent using the /// specified dots per inch (DPI). /// /// The dots per inch. /// The device-independent size. - public Size ToSize(double dpi) => new Size(Width / (dpi / 96), Height / (dpi / 96)); + public Size ToSizeWithDpi(double dpi) => ToSize(dpi / 96); /// /// Converts the to a device-independent using the @@ -140,7 +156,27 @@ namespace Avalonia /// /// The dots per inch. /// The device-independent size. - public Size ToSize(Vector dpi) => new Size(Width / (dpi.X / 96), Height / (dpi.Y / 96)); + public Size ToSizeWithDpi(Vector dpi) => ToSize(new Vector(dpi.X / 96, dpi.Y / 96)); + + /// + /// Converts a to device pixels using the specified scaling factor. + /// + /// The size. + /// The scaling factor. + /// The device-independent size. + public static PixelSize FromSize(Size size, double scale) => new PixelSize( + (int)Math.Ceiling(size.Width * scale), + (int)Math.Ceiling(size.Height * scale)); + + /// + /// Converts a to device pixels using the specified scaling factor. + /// + /// The size. + /// The scaling factor. + /// The device-independent size. + public static PixelSize FromSize(Size size, Vector scale) => new PixelSize( + (int)Math.Ceiling(size.Width * scale.X), + (int)Math.Ceiling(size.Height * scale.Y)); /// /// Converts a to device pixels using the specified dots per inch (DPI). @@ -148,9 +184,7 @@ namespace Avalonia /// The size. /// The dots per inch. /// The device-independent size. - public static PixelSize FromSize(Size size, double dpi) => new PixelSize( - (int)(size.Width * (dpi / 96)), - (int)(size.Height * (dpi / 96))); + public static PixelSize FromSizeWithDpi(Size size, double dpi) => FromSize(size, dpi / 96); /// /// Converts a to device pixels using the specified dots per inch (DPI). @@ -158,9 +192,7 @@ namespace Avalonia /// The size. /// The dots per inch. /// The device-independent size. - public static PixelSize FromSize(Size size, Vector dpi) => new PixelSize( - (int)Math.Ceiling(size.Width * (dpi.X / 96)), - (int)Math.Ceiling(size.Height * (dpi.Y / 96))); + public static PixelSize FromSizeWithDpi(Size size, Vector dpi) => FromSize(size, new Vector(dpi.X / 96, dpi.Y / 96)); /// /// Returns the string representation of the size. diff --git a/src/Skia/Avalonia.Skia/DrawingContextImpl.cs b/src/Skia/Avalonia.Skia/DrawingContextImpl.cs index fabfe2b225..353354da5e 100644 --- a/src/Skia/Avalonia.Skia/DrawingContextImpl.cs +++ b/src/Skia/Avalonia.Skia/DrawingContextImpl.cs @@ -401,15 +401,15 @@ namespace Avalonia.Skia /// Tile brush image. private void ConfigureTileBrush(ref PaintWrapper paintWrapper, Size targetSize, ITileBrush tileBrush, IDrawableBitmapImpl tileBrushImage) { - var calc = new TileBrushCalculator(tileBrush, tileBrushImage.PixelSize.ToSize(_dpi), targetSize); + var calc = new TileBrushCalculator(tileBrush, tileBrushImage.PixelSize.ToSizeWithDpi(_dpi), targetSize); var intermediate = CreateRenderTarget(calc.IntermediateSize); paintWrapper.AddDisposable(intermediate); using (var context = intermediate.CreateDrawingContext(null)) { - var sourceRect = new Rect(tileBrushImage.PixelSize.ToSize(96)); - var targetRect = new Rect(tileBrushImage.PixelSize.ToSize(_dpi)); + var sourceRect = new Rect(tileBrushImage.PixelSize.ToSizeWithDpi(96)); + var targetRect = new Rect(tileBrushImage.PixelSize.ToSizeWithDpi(_dpi)); context.Clear(Colors.Transparent); context.PushClip(calc.IntermediateClip); @@ -634,7 +634,7 @@ namespace Avalonia.Skia /// private SurfaceRenderTarget CreateRenderTarget(Size size, PixelFormat? format = null) { - var pixelSize = PixelSize.FromSize(size, _dpi); + var pixelSize = PixelSize.FromSizeWithDpi(size, _dpi); var createInfo = new SurfaceRenderTarget.CreateInfo { Width = pixelSize.Width, diff --git a/src/Windows/Avalonia.Direct2D1/Media/DrawingContextImpl.cs b/src/Windows/Avalonia.Direct2D1/Media/DrawingContextImpl.cs index aaca1a3b00..5e150ff647 100644 --- a/src/Windows/Avalonia.Direct2D1/Media/DrawingContextImpl.cs +++ b/src/Windows/Avalonia.Direct2D1/Media/DrawingContextImpl.cs @@ -330,7 +330,7 @@ namespace Avalonia.Direct2D1.Media { var platform = AvaloniaLocator.Current.GetService(); var dpi = new Vector(_deviceContext.DotsPerInch.Width, _deviceContext.DotsPerInch.Height); - var pixelSize = PixelSize.FromSize(size, dpi); + var pixelSize = PixelSize.FromSizeWithDpi(size, dpi); return platform.CreateRenderTargetBitmap(pixelSize, dpi); } } @@ -438,12 +438,12 @@ namespace Avalonia.Direct2D1.Media // D2D alters the DPI of the render target, which messes stuff up. PixelSize.FromSize // will do the rounding for us. var dpi = new Vector(_deviceContext.DotsPerInch.Width, _deviceContext.DotsPerInch.Height); - var pixelSize = PixelSize.FromSize(intermediateSize, dpi); + var pixelSize = PixelSize.FromSizeWithDpi(intermediateSize, dpi); using (var intermediate = new BitmapRenderTarget( _deviceContext, CompatibleRenderTargetOptions.None, - pixelSize.ToSize(dpi).ToSharpDX())) + pixelSize.ToSizeWithDpi(dpi).ToSharpDX())) { using (var ctx = new RenderTarget(intermediate).CreateDrawingContext(_visualBrushRenderer)) { diff --git a/src/Windows/Avalonia.Direct2D1/Media/ImageBrushImpl.cs b/src/Windows/Avalonia.Direct2D1/Media/ImageBrushImpl.cs index 19d320b783..fbc6d21cb7 100644 --- a/src/Windows/Avalonia.Direct2D1/Media/ImageBrushImpl.cs +++ b/src/Windows/Avalonia.Direct2D1/Media/ImageBrushImpl.cs @@ -21,7 +21,7 @@ namespace Avalonia.Direct2D1.Media Size targetSize) { var dpi = new Vector(target.DotsPerInch.Width, target.DotsPerInch.Height); - var calc = new TileBrushCalculator(brush, bitmap.PixelSize.ToSize(dpi), targetSize); + var calc = new TileBrushCalculator(brush, bitmap.PixelSize.ToSizeWithDpi(dpi), targetSize); if (!calc.NeedsIntermediate) { @@ -101,7 +101,7 @@ namespace Avalonia.Direct2D1.Media using (var context = new RenderTarget(result).CreateDrawingContext(null)) { var dpi = new Vector(target.DotsPerInch.Width, target.DotsPerInch.Height); - var rect = new Rect(bitmap.PixelSize.ToSize(dpi)); + var rect = new Rect(bitmap.PixelSize.ToSizeWithDpi(dpi)); context.Clear(Colors.Transparent); context.PushClip(calc.IntermediateClip); diff --git a/src/Windows/Avalonia.Direct2D1/Media/Imaging/D2DRenderTargetBitmapImpl.cs b/src/Windows/Avalonia.Direct2D1/Media/Imaging/D2DRenderTargetBitmapImpl.cs index fbe7f840d7..8ec368c999 100644 --- a/src/Windows/Avalonia.Direct2D1/Media/Imaging/D2DRenderTargetBitmapImpl.cs +++ b/src/Windows/Avalonia.Direct2D1/Media/Imaging/D2DRenderTargetBitmapImpl.cs @@ -61,8 +61,8 @@ namespace Avalonia.Direct2D1.Media.Imaging dc.DrawImage( RefCountable.CreateUnownedNotClonable(this), 1, - new Rect(PixelSize.ToSize(Dpi.X)), - new Rect(PixelSize.ToSize(Dpi.X))); + new Rect(PixelSize.ToSizeWithDpi(Dpi.X)), + new Rect(PixelSize.ToSizeWithDpi(Dpi.X))); } wic.Save(stream); From 580a7adbe42f5fda23842bf21ebb7081f41d792c Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Fri, 18 Jan 2019 11:02:50 +0100 Subject: [PATCH 06/14] Added PixelPoint and PixelRect. In addition to `PixelSize`. --- src/Avalonia.Visuals/Media/PixelPoint.cs | 201 +++++++++++ src/Avalonia.Visuals/Media/PixelRect.cs | 436 +++++++++++++++++++++++ 2 files changed, 637 insertions(+) create mode 100644 src/Avalonia.Visuals/Media/PixelPoint.cs create mode 100644 src/Avalonia.Visuals/Media/PixelRect.cs diff --git a/src/Avalonia.Visuals/Media/PixelPoint.cs b/src/Avalonia.Visuals/Media/PixelPoint.cs new file mode 100644 index 0000000000..995781ee9f --- /dev/null +++ b/src/Avalonia.Visuals/Media/PixelPoint.cs @@ -0,0 +1,201 @@ +// Copyright (c) The Avalonia Project. All rights reserved. +// Licensed under the MIT license. See licence.md file in the project root for full license information. + +using System; +using System.Globalization; +using Avalonia.Utilities; + +namespace Avalonia +{ + /// + /// Represents a point in device pixels. + /// + public readonly struct PixelPoint + { + /// + /// A point representing 0,0. + /// + public static readonly PixelPoint Origin = new PixelPoint(0, 0); + + /// + /// Initializes a new instance of the structure. + /// + /// The X co-ordinate. + /// The Y co-ordinate. + public PixelPoint(int x, int y) + { + X = x; + Y = y; + } + + /// + /// Gets the X co-ordinate. + /// + public int X { get; } + + /// + /// Gets the Y co-ordinate. + /// + public int Y { get; } + + /// + /// Checks for equality between two s. + /// + /// The first point. + /// The second point. + /// True if the points are equal; otherwise false. + public static bool operator ==(PixelPoint left, PixelPoint right) + { + return left.X == right.X && left.Y == right.Y; + } + + /// + /// Checks for inequality between two s. + /// + /// The first point. + /// The second point. + /// True if the points are unequal; otherwise false. + public static bool operator !=(PixelPoint left, PixelPoint right) + { + return !(left == right); + } + + /// + /// Parses a string. + /// + /// The string. + /// The . + public static PixelPoint Parse(string s) + { + using (var tokenizer = new StringTokenizer(s, CultureInfo.InvariantCulture, exceptionMessage: "Invalid PixelPoint")) + { + return new PixelPoint( + tokenizer.ReadInt32(), + tokenizer.ReadInt32()); + } + } + + /// + /// Checks for equality between a point and an object. + /// + /// The object. + /// + /// True if is a point that equals the current point. + /// + public override bool Equals(object obj) + { + if (obj is PixelPoint other) + { + return this == other; + } + + return false; + } + + /// + /// Returns a hash code for a . + /// + /// The hash code. + public override int GetHashCode() + { + unchecked + { + int hash = 17; + hash = (hash * 23) + X.GetHashCode(); + hash = (hash * 23) + Y.GetHashCode(); + return hash; + } + } + + /// + /// Returns a new with the same Y co-ordinate and the specified X co-ordinate. + /// + /// The X co-ordinate. + /// The new . + public PixelPoint WithX(int x) => new PixelPoint(x, Y); + + /// + /// Returns a new with the same X co-ordinate and the specified Y co-ordinate. + /// + /// The Y co-ordinate. + /// The new . + public PixelPoint WithY(int y) => new PixelPoint(X, y); + + /// + /// Converts the to a device-independent using the + /// specified scaling factor. + /// + /// The scaling factor. + /// The device-independent point. + public Point ToPoint(double scale) => new Point(X / scale, Y / scale); + + /// + /// Converts the to a device-independent using the + /// specified scaling factor. + /// + /// The scaling factor. + /// The device-independent point. + public Point ToPoint(Vector scale) => new Point(X / scale.X, Y / scale.Y); + + /// + /// Converts the to a device-independent using the + /// specified dots per inch (DPI). + /// + /// The dots per inch of the device. + /// The device-independent point. + public Point ToPointWithDpi(double dpi) => ToPoint(dpi / 96); + + /// + /// Converts the to a device-independent using the + /// specified dots per inch (DPI). + /// + /// The dots per inch of the device. + /// The device-independent point. + public Point ToPointWithDpi(Vector dpi) => ToPoint(new Vector(dpi.X / 96, dpi.Y / 96)); + + /// + /// Converts a to device pixels using the specified scaling factor. + /// + /// The point. + /// The scaling factor. + /// The device-independent point. + public static PixelPoint FromPoint(Point point, double scale) => new PixelPoint( + (int)(point.X * scale), + (int)(point.Y * scale)); + + /// + /// Converts a to device pixels using the specified scaling factor. + /// + /// The point. + /// The scaling factor. + /// The device-independent point. + public static PixelPoint FromPoint(Point point, Vector scale) => new PixelPoint( + (int)(point.X * scale.X), + (int)(point.Y * scale.Y)); + + /// + /// Converts a to device pixels using the specified dots per inch (DPI). + /// + /// The point. + /// The dots per inch of the device. + /// The device-independent point. + public static PixelPoint FromPointWithDpi(Point point, double dpi) => FromPoint(point, dpi / 96); + + /// + /// Converts a to device pixels using the specified dots per inch (DPI). + /// + /// The point. + /// The dots per inch of the device. + /// The device-independent point. + public static PixelPoint FromPointWithDpi(Point point, Vector dpi) => FromPoint(point, new Vector(dpi.X / 96, dpi.Y / 96)); + + /// + /// Returns the string representation of the point. + /// + /// The string representation of the point. + public override string ToString() + { + return string.Format(CultureInfo.InvariantCulture, "{0}, {1}", X, Y); + } + } +} diff --git a/src/Avalonia.Visuals/Media/PixelRect.cs b/src/Avalonia.Visuals/Media/PixelRect.cs new file mode 100644 index 0000000000..9c8e5ad1c4 --- /dev/null +++ b/src/Avalonia.Visuals/Media/PixelRect.cs @@ -0,0 +1,436 @@ +// Copyright (c) The Avalonia Project. All rights reserved. +// Licensed under the MIT license. See licence.md file in the project root for full license information. + +using System; +using System.Globalization; +using Avalonia.Utilities; + +namespace Avalonia +{ + /// + /// Represents a rectangle in device pixels. + /// + public readonly struct PixelRect + { + /// + /// An empty rectangle. + /// + public static readonly PixelRect Empty = default; + + /// + /// Initializes a new instance of the structure. + /// + /// The X position. + /// The Y position. + /// The width. + /// The height. + public PixelRect(int x, int y, int width, int height) + { + X = x; + Y = y; + Width = width; + Height = height; + } + + /// + /// Initializes a new instance of the structure. + /// + /// The size of the rectangle. + public PixelRect(PixelSize size) + { + X = 0; + Y = 0; + Width = size.Width; + Height = size.Height; + } + + /// + /// Initializes a new instance of the structure. + /// + /// The position of the rectangle. + /// The size of the rectangle. + public PixelRect(PixelPoint position, PixelSize size) + { + X = position.X; + Y = position.Y; + Width = size.Width; + Height = size.Height; + } + + /// + /// Initializes a new instance of the structure. + /// + /// The top left position of the rectangle. + /// The bottom right position of the rectangle. + public PixelRect(PixelPoint topLeft, PixelPoint bottomRight) + { + X = topLeft.X; + Y = topLeft.Y; + Width = bottomRight.X - topLeft.X; + Height = bottomRight.Y - topLeft.Y; + } + + /// + /// Gets the X position. + /// + public int X { get; } + + /// + /// Gets the Y position. + /// + public int Y { get; } + + /// + /// Gets the width. + /// + public int Width { get; } + + /// + /// Gets the height. + /// + public int Height { get; } + + /// + /// Gets the position of the rectangle. + /// + public PixelPoint Position => new PixelPoint(X, Y); + + /// + /// Gets the size of the rectangle. + /// + public PixelSize Size => new PixelSize(Width, Height); + + /// + /// Gets the right position of the rectangle. + /// + public int Right => X + Width; + + /// + /// Gets the bottom position of the rectangle. + /// + public int Bottom => Y + Height; + + /// + /// Gets the top left point of the rectangle. + /// + public PixelPoint TopLeft => new PixelPoint(X, Y); + + /// + /// Gets the top right point of the rectangle. + /// + public PixelPoint TopRight => new PixelPoint(Right, Y); + + /// + /// Gets the bottom left point of the rectangle. + /// + public PixelPoint BottomLeft => new PixelPoint(X, Bottom); + + /// + /// Gets the bottom right point of the rectangle. + /// + public PixelPoint BottomRight => new PixelPoint(Right, Bottom); + + /// + /// Gets the center point of the rectangle. + /// + public PixelPoint Center => new PixelPoint(X + (Width / 2), Y + (Height / 2)); + + /// + /// Gets a value that indicates whether the rectangle is empty. + /// + public bool IsEmpty => Width == 0 && Height == 0; + + /// + /// Checks for equality between two s. + /// + /// The first rect. + /// The second rect. + /// True if the rects are equal; otherwise false. + public static bool operator ==(PixelRect left, PixelRect right) + { + return left.Position == right.Position && left.Size == right.Size; + } + + /// + /// Checks for inequality between two s. + /// + /// The first rect. + /// The second rect. + /// True if the rects are unequal; otherwise false. + public static bool operator !=(PixelRect left, PixelRect right) + { + return !(left == right); + } + + /// + /// Determines whether a point in in the bounds of the rectangle. + /// + /// The point. + /// true if the point is in the bounds of the rectangle; otherwise false. + public bool Contains(PixelPoint p) + { + return p.X >= X && p.X <= Right && p.Y >= Y && p.Y <= Bottom; + } + + /// + /// Determines whether the rectangle fully contains another rectangle. + /// + /// The rectangle. + /// true if the rectangle is fully contained; otherwise false. + public bool Contains(PixelRect r) + { + return Contains(r.TopLeft) && Contains(r.BottomRight); + } + + /// + /// Centers another rectangle in this rectangle. + /// + /// The rectangle to center. + /// The centered rectangle. + public PixelRect CenterRect(PixelRect rect) + { + return new PixelRect( + X + ((Width - rect.Width) / 2), + Y + ((Height - rect.Height) / 2), + rect.Width, + rect.Height); + } + + /// + /// Returns a boolean indicating whether the given object is equal to this rectangle. + /// + /// The object to compare against. + /// True if the object is equal to this rectangle; false otherwise. + public override bool Equals(object obj) + { + if (obj is PixelRect other) + { + return this == other; + } + + return false; + } + + /// + /// Returns the hash code for this instance. + /// + /// The hash code. + public override int GetHashCode() + { + unchecked + { + int hash = 17; + hash = (hash * 23) + X.GetHashCode(); + hash = (hash * 23) + Y.GetHashCode(); + hash = (hash * 23) + Width.GetHashCode(); + hash = (hash * 23) + Height.GetHashCode(); + return hash; + } + } + + /// + /// Gets the intersection of two rectangles. + /// + /// The other rectangle. + /// The intersection. + public PixelRect Intersect(PixelRect rect) + { + var newLeft = (rect.X > X) ? rect.X : X; + var newTop = (rect.Y > Y) ? rect.Y : Y; + var newRight = (rect.Right < Right) ? rect.Right : Right; + var newBottom = (rect.Bottom < Bottom) ? rect.Bottom : Bottom; + + if ((newRight > newLeft) && (newBottom > newTop)) + { + return new PixelRect(newLeft, newTop, newRight - newLeft, newBottom - newTop); + } + else + { + return Empty; + } + } + + /// + /// Determines whether a rectangle intersects with this rectangle. + /// + /// The other rectangle. + /// + /// True if the specified rectangle intersects with this one; otherwise false. + /// + public bool Intersects(PixelRect rect) + { + return (rect.X < Right) && (X < rect.Right) && (rect.Y < Bottom) && (Y < rect.Bottom); + } + + /// + /// Gets the union of two rectangles. + /// + /// The other rectangle. + /// The union. + public PixelRect Union(PixelRect rect) + { + if (IsEmpty) + { + return rect; + } + else if (rect.IsEmpty) + { + return this; + } + else + { + var x1 = Math.Min(X, rect.X); + var x2 = Math.Max(Right, rect.Right); + var y1 = Math.Min(Y, rect.Y); + var y2 = Math.Max(Bottom, rect.Bottom); + + return new PixelRect(new PixelPoint(x1, y1), new PixelPoint(x2, y2)); + } + } + + /// + /// Returns a new with the specified X position. + /// + /// The x position. + /// The new . + public PixelRect WithX(int x) + { + return new PixelRect(x, Y, Width, Height); + } + + /// + /// Returns a new with the specified Y position. + /// + /// The y position. + /// The new . + public PixelRect WithY(int y) + { + return new PixelRect(X, y, Width, Height); + } + + /// + /// Returns a new with the specified width. + /// + /// The width. + /// The new . + public PixelRect WithWidth(int width) + { + return new PixelRect(X, Y, width, Height); + } + + /// + /// Returns a new with the specified height. + /// + /// The height. + /// The new . + public PixelRect WithHeight(int height) + { + return new PixelRect(X, Y, Width, Height); + } + + /// + /// Converts the to a device-independent using the + /// specified scaling factor. + /// + /// The scaling factor. + /// The device-independent rect. + public Rect ToRect(double scale) => new Rect(Position.ToPoint(scale), Size.ToSize(scale)); + + /// + /// Converts the to a device-independent using the + /// specified scaling factor. + /// + /// The scaling factor. + /// The device-independent rect. + public Rect ToRect(Vector scale) => new Rect(Position.ToPoint(scale), Size.ToSize(scale)); + + /// + /// Converts the to a device-independent using the + /// specified dots per inch (DPI). + /// + /// The dots per inch of the device. + /// The device-independent rect. + public Rect ToRectWithDpi(double dpi) => new Rect(Position.ToPointWithDpi(dpi), Size.ToSizeWithDpi(dpi)); + + /// + /// Converts the to a device-independent using the + /// specified dots per inch (DPI). + /// + /// The dots per inch of the device. + /// The device-independent rect. + public Rect ToRectWithDpi(Vector dpi) => new Rect(Position.ToPointWithDpi(dpi), Size.ToSizeWithDpi(dpi)); + + /// + /// Converts a to device pixels using the specified scaling factor. + /// + /// The rect. + /// The scaling factor. + /// The device-independent rect. + public static PixelRect FromRect(Rect rect, double scale) => new PixelRect( + PixelPoint.FromPoint(rect.Position, scale), + PixelSize.FromSize(rect.Size, scale)); + + /// + /// Converts a to device pixels using the specified scaling factor. + /// + /// The rect. + /// The scaling factor. + /// The device-independent point. + public static PixelRect FromRect(Rect rect, Vector scale) => new PixelRect( + PixelPoint.FromPoint(rect.Position, scale), + PixelSize.FromSize(rect.Size, scale)); + + /// + /// Converts a to device pixels using the specified dots per inch (DPI). + /// + /// The rect. + /// The dots per inch of the device. + /// The device-independent point. + public static PixelRect FromRectWithDpi(Rect rect, double dpi) => new PixelRect( + PixelPoint.FromPointWithDpi(rect.Position, dpi), + PixelSize.FromSizeWithDpi(rect.Size, dpi)); + + /// + /// Converts a to device pixels using the specified dots per inch (DPI). + /// + /// The rect. + /// The dots per inch of the device. + /// The device-independent point. + public static PixelRect FromRectWithDpi(Rect rect, Vector dpi) => new PixelRect( + PixelPoint.FromPointWithDpi(rect.Position, dpi), + PixelSize.FromSizeWithDpi(rect.Size, dpi)); + + /// + /// Returns the string representation of the rectangle. + /// + /// The string representation of the rectangle. + public override string ToString() + { + return string.Format( + CultureInfo.InvariantCulture, + "{0}, {1}, {2}, {3}", + X, + Y, + Width, + Height); + } + + /// + /// Parses a string. + /// + /// The string. + /// The parsed . + public static PixelRect Parse(string s) + { + using (var tokenizer = new StringTokenizer(s, CultureInfo.InvariantCulture, exceptionMessage: "Invalid PixelRect")) + { + return new PixelRect( + tokenizer.ReadInt32(), + tokenizer.ReadInt32(), + tokenizer.ReadInt32(), + tokenizer.ReadInt32() + ); + } + } + } +} From 4ec2b1c554d729fac656a1622be305ca390c291a Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Fri, 18 Jan 2019 11:06:16 +0100 Subject: [PATCH 07/14] Use PixelPoint and PixelRect for device points/rects. The affected members are: - `IWindowBaseImpl.Position` - `IWindowBaseImpl.PositionChanged` - `ITopLevelImpl.PointToClient` - `ITopLevelImpl.PointToScreen` - `IMouseDevice.Position` - `Screen.Bounds` - `Screen.WorkingArea` --- samples/ControlCatalog/Pages/ScreenPage.cs | 4 ++- .../Platform/SkiaPlatform/PopupImpl.cs | 6 ++-- .../Platform/SkiaPlatform/TopLevelImpl.cs | 8 ++--- .../Offscreen/OffscreenTopLevelImpl.cs | 4 +-- src/Avalonia.Controls/PixelPointEventArgs.cs | 27 +++++++++++++++++ .../Platform/ITopLevelImpl.cs | 4 +-- .../Platform/IWindowBaseImpl.cs | 6 ++-- src/Avalonia.Controls/Platform/Screen.cs | 8 ++--- src/Avalonia.Controls/PointEventArgs.cs | 27 ----------------- src/Avalonia.Controls/Primitives/Popup.cs | 30 ++++++++----------- src/Avalonia.Controls/Primitives/PopupRoot.cs | 10 +++---- src/Avalonia.Controls/Screens.cs | 14 ++++----- src/Avalonia.Controls/ToolTip.cs | 1 + src/Avalonia.Controls/TopLevel.cs | 8 ++--- src/Avalonia.Controls/Window.cs | 20 ++++++++++--- src/Avalonia.Controls/WindowBase.cs | 10 +++---- .../Remote/PreviewerWindowImpl.cs | 4 +-- src/Avalonia.DesignerSupport/Remote/Stubs.cs | 10 +++---- src/Avalonia.Input/IMouseDevice.cs | 2 +- src/Avalonia.Input/MouseDevice.cs | 2 +- src/Avalonia.Native/Helpers.cs | 15 ++++++++++ src/Avalonia.Native/ScreenImpl.cs | 5 +++- src/Avalonia.Native/WindowImplBase.cs | 14 ++++----- src/Avalonia.Visuals/Rendering/IRenderRoot.cs | 8 ++--- src/Avalonia.Visuals/VisualExtensions.cs | 12 +++++--- src/Avalonia.X11/X11Screens.cs | 16 +++++----- src/Avalonia.X11/X11Window.cs | 20 +++++++------ src/Gtk/Avalonia.Gtk3/GtkScreen.cs | 4 +-- src/Gtk/Avalonia.Gtk3/ScreenImpl.cs | 4 +-- src/Gtk/Avalonia.Gtk3/WindowBaseImpl.cs | 14 ++++----- .../FramebufferToplevelImpl.cs | 4 +-- .../Wpf/WpfInteropExtensions.cs | 2 ++ .../Wpf/WpfTopLevelImpl.cs | 4 +-- src/Windows/Avalonia.Win32/OleDropTarget.cs | 2 +- src/Windows/Avalonia.Win32/ScreenImpl.cs | 6 ++-- src/Windows/Avalonia.Win32/WinScreen.cs | 4 +-- src/Windows/Avalonia.Win32/WindowImpl.cs | 22 +++++++------- src/iOS/Avalonia.iOS/TopLevelImpl.cs | 4 +-- .../ButtonTests.cs | 4 +-- .../ContextMenuTests.cs | 2 +- .../ItemsPresenterTests_Virtualization.cs | 22 +++----------- ...emsPresenterTests_Virtualization_Simple.cs | 28 +++-------------- .../WindowTests.cs | 22 +++++++------- tests/Avalonia.UnitTests/TestRoot.cs | 4 +-- tests/Avalonia.UnitTests/TestTemplatedRoot.cs | 4 +-- 45 files changed, 227 insertions(+), 224 deletions(-) create mode 100644 src/Avalonia.Controls/PixelPointEventArgs.cs delete mode 100644 src/Avalonia.Controls/PointEventArgs.cs diff --git a/samples/ControlCatalog/Pages/ScreenPage.cs b/samples/ControlCatalog/Pages/ScreenPage.cs index fd66185832..b5c6892885 100644 --- a/samples/ControlCatalog/Pages/ScreenPage.cs +++ b/samples/ControlCatalog/Pages/ScreenPage.cs @@ -4,6 +4,7 @@ using Avalonia.Controls; using Avalonia.Markup.Xaml; using Avalonia.Media; using Avalonia.Platform; +using Avalonia.Rendering; namespace ControlCatalog.Pages { @@ -23,6 +24,7 @@ namespace ControlCatalog.Pages base.Render(context); Window w = (Window)VisualRoot; Screen[] screens = w.Screens.All; + var scaling = ((IRenderRoot)w).RenderScaling; Pen p = new Pen(Brushes.Black); if (screens != null) @@ -56,7 +58,7 @@ namespace ControlCatalog.Pages text.Text = $"Primary: {screen.Primary}"; context.DrawText(Brushes.Black, boundsRect.Position.WithY(boundsRect.Size.Height + 40), text); - text.Text = $"Current: {screen.Equals(w.Screens.ScreenFromBounds(new Rect(w.Position, w.Bounds.Size)))}"; + text.Text = $"Current: {screen.Equals(w.Screens.ScreenFromBounds(new PixelRect(w.Position, PixelSize.FromSize(w.Bounds.Size, scaling))))}"; context.DrawText(Brushes.Black, boundsRect.Position.WithY(boundsRect.Size.Height + 60), text); } diff --git a/src/Android/Avalonia.Android/Platform/SkiaPlatform/PopupImpl.cs b/src/Android/Avalonia.Android/Platform/SkiaPlatform/PopupImpl.cs index d55755d6e3..e89414d1f8 100644 --- a/src/Android/Avalonia.Android/Platform/SkiaPlatform/PopupImpl.cs +++ b/src/Android/Avalonia.Android/Platform/SkiaPlatform/PopupImpl.cs @@ -10,10 +10,10 @@ namespace Avalonia.Android.Platform.SkiaPlatform { class PopupImpl : TopLevelImpl, IPopupImpl { - private Point _position; + private PixelPoint _position; private bool _isAdded; Action IWindowBaseImpl.Activated { get; set; } - public Action PositionChanged { get; set; } + public Action PositionChanged { get; set; } public Action Deactivated { get; set; } public PopupImpl() : base(ActivityTracker.Current, true) @@ -36,7 +36,7 @@ namespace Avalonia.Android.Platform.SkiaPlatform public IScreenImpl Screen { get; } - public Point Position + public PixelPoint Position { get { return _position; } set diff --git a/src/Android/Avalonia.Android/Platform/SkiaPlatform/TopLevelImpl.cs b/src/Android/Avalonia.Android/Platform/SkiaPlatform/TopLevelImpl.cs index 8e8886269c..f42faeaa63 100644 --- a/src/Android/Avalonia.Android/Platform/SkiaPlatform/TopLevelImpl.cs +++ b/src/Android/Avalonia.Android/Platform/SkiaPlatform/TopLevelImpl.cs @@ -99,14 +99,14 @@ namespace Avalonia.Android.Platform.SkiaPlatform if (_view.Holder?.Surface?.IsValid == true) _view.Invalidate(); } - public Point PointToClient(Point point) + public Point PointToClient(PixelPoint point) { - return point; + return point.ToPoint(1); } - public Point PointToScreen(Point point) + public PixelPoint PointToScreen(Point point) { - return point; + return PixelPoint.FromPoint(point, 1); } public void SetCursor(IPlatformHandle cursor) diff --git a/src/Avalonia.Controls/Embedding/Offscreen/OffscreenTopLevelImpl.cs b/src/Avalonia.Controls/Embedding/Offscreen/OffscreenTopLevelImpl.cs index 37bb72e75a..d328e1ee88 100644 --- a/src/Avalonia.Controls/Embedding/Offscreen/OffscreenTopLevelImpl.cs +++ b/src/Avalonia.Controls/Embedding/Offscreen/OffscreenTopLevelImpl.cs @@ -49,9 +49,9 @@ namespace Avalonia.Controls.Embedding.Offscreen public Action ScalingChanged { get; set; } public void SetInputRoot(IInputRoot inputRoot) => InputRoot = inputRoot; - public virtual Point PointToClient(Point point) => point; + public virtual Point PointToClient(PixelPoint point) => point.ToPoint(1); - public virtual Point PointToScreen(Point point) => point; + public virtual PixelPoint PointToScreen(Point point) => PixelPoint.FromPoint(point, 1); public virtual void SetCursor(IPlatformHandle cursor) { diff --git a/src/Avalonia.Controls/PixelPointEventArgs.cs b/src/Avalonia.Controls/PixelPointEventArgs.cs new file mode 100644 index 0000000000..55a3d5601f --- /dev/null +++ b/src/Avalonia.Controls/PixelPointEventArgs.cs @@ -0,0 +1,27 @@ +// Copyright (c) The Avalonia Project. All rights reserved. +// Licensed under the MIT license. See licence.md file in the project root for full license information. + +using System; + +namespace Avalonia.Controls +{ + /// + /// Provides data for events. + /// + public class PixelPointEventArgs : EventArgs + { + /// + /// Initializes a new instance of the class. + /// + /// The data. + public PixelPointEventArgs(PixelPoint point) + { + Point = point; + } + + /// + /// Gets the data. + /// + public PixelPoint Point { get; } + } +} diff --git a/src/Avalonia.Controls/Platform/ITopLevelImpl.cs b/src/Avalonia.Controls/Platform/ITopLevelImpl.cs index 60e25d2be6..8d8ce35c38 100644 --- a/src/Avalonia.Controls/Platform/ITopLevelImpl.cs +++ b/src/Avalonia.Controls/Platform/ITopLevelImpl.cs @@ -82,14 +82,14 @@ namespace Avalonia.Platform /// /// The point in screen coordinates. /// The point in client coordinates. - Point PointToClient(Point point); + Point PointToClient(PixelPoint point); /// /// Converts a point from client to screen coordinates. /// /// The point in client coordinates. /// The point in screen coordinates. - Point PointToScreen(Point point); + PixelPoint PointToScreen(Point point); /// /// Sets the cursor associated with the toplevel. diff --git a/src/Avalonia.Controls/Platform/IWindowBaseImpl.cs b/src/Avalonia.Controls/Platform/IWindowBaseImpl.cs index e788b3c73e..817eb3a25b 100644 --- a/src/Avalonia.Controls/Platform/IWindowBaseImpl.cs +++ b/src/Avalonia.Controls/Platform/IWindowBaseImpl.cs @@ -27,14 +27,14 @@ namespace Avalonia.Platform void BeginResizeDrag(WindowEdge edge); /// - /// Gets position of the window relatively to the screen + /// Gets the position of the window relative in device pixels. /// - Point Position { get; set; } + PixelPoint Position { get; set; } /// /// Gets or sets a method called when the window's position changes. /// - Action PositionChanged { get; set; } + Action PositionChanged { get; set; } /// /// Activates the window. diff --git a/src/Avalonia.Controls/Platform/Screen.cs b/src/Avalonia.Controls/Platform/Screen.cs index 5aa8c2c392..e7c811235c 100644 --- a/src/Avalonia.Controls/Platform/Screen.cs +++ b/src/Avalonia.Controls/Platform/Screen.cs @@ -2,17 +2,17 @@ { public class Screen { - public Rect Bounds { get; } + public PixelRect Bounds { get; } - public Rect WorkingArea { get; } + public PixelRect WorkingArea { get; } public bool Primary { get; } - public Screen(Rect bounds, Rect workingArea, bool primary) + public Screen(PixelRect bounds, PixelRect workingArea, bool primary) { this.Bounds = bounds; this.WorkingArea = workingArea; this.Primary = primary; } } -} \ No newline at end of file +} diff --git a/src/Avalonia.Controls/PointEventArgs.cs b/src/Avalonia.Controls/PointEventArgs.cs deleted file mode 100644 index 4173d367d0..0000000000 --- a/src/Avalonia.Controls/PointEventArgs.cs +++ /dev/null @@ -1,27 +0,0 @@ -// Copyright (c) The Avalonia Project. All rights reserved. -// Licensed under the MIT license. See licence.md file in the project root for full license information. - -using System; - -namespace Avalonia.Controls -{ - /// - /// Provides data for events. - /// - public class PointEventArgs : EventArgs - { - /// - /// Initializes a new instance of the class. - /// - /// The data. - public PointEventArgs(Point point) - { - Point = point; - } - - /// - /// Gets the data. - /// - public Point Point { get; } - } -} diff --git a/src/Avalonia.Controls/Primitives/Popup.cs b/src/Avalonia.Controls/Primitives/Popup.cs index 5e79711f5a..d9070197b6 100644 --- a/src/Avalonia.Controls/Primitives/Popup.cs +++ b/src/Avalonia.Controls/Primitives/Popup.cs @@ -380,7 +380,7 @@ namespace Avalonia.Controls.Primitives /// Gets the position for the popup based on the placement properties. /// /// The popup's position in screen coordinates. - protected virtual Point GetPosition() + protected virtual PixelPoint GetPosition() { var result = GetPosition(PlacementTarget ?? this.GetVisualParent(), PlacementMode, PopupRoot, HorizontalOffset, VerticalOffset); @@ -388,35 +388,31 @@ namespace Avalonia.Controls.Primitives return result; } - internal static Point GetPosition(Control target, PlacementMode placement, PopupRoot popupRoot, double horizontalOffset, double verticalOffset) + internal static PixelPoint GetPosition(Control target, PlacementMode placement, PopupRoot popupRoot, double horizontalOffset, double verticalOffset) { - var zero = default(Point); - var mode = placement; - - if (target?.GetVisualRoot() == null) - { - mode = PlacementMode.Pointer; - } + var root = target?.GetVisualRoot(); + var mode = root != null ? placement : PlacementMode.Pointer; + var scaling = root?.RenderScaling ?? 1; switch (mode) { case PlacementMode.Pointer: if (popupRoot != null) { - // Scales the Horizontal and Vertical offset to screen co-ordinates. - var screenOffset = new Point(horizontalOffset * (popupRoot as ILayoutRoot).LayoutScaling, - verticalOffset * (popupRoot as ILayoutRoot).LayoutScaling); - return (((IInputRoot)popupRoot)?.MouseDevice?.Position ?? default(Point)) + screenOffset; + var screenOffset = PixelPoint.FromPoint(new Point(horizontalOffset, verticalOffset), scaling); + var mouseOffset = ((IInputRoot)popupRoot)?.MouseDevice?.Position ?? default; + return new PixelPoint( + screenOffset.X + mouseOffset.X, + screenOffset.Y + mouseOffset.Y); } - return default(Point); + return default; case PlacementMode.Bottom: - return target?.PointToScreen(new Point(0 + horizontalOffset, target.Bounds.Height + verticalOffset)) ?? - zero; + return target?.PointToScreen(new Point(0 + horizontalOffset, target.Bounds.Height + verticalOffset)) ?? default; case PlacementMode.Right: - return target?.PointToScreen(new Point(target.Bounds.Width + horizontalOffset, 0 + verticalOffset)) ?? zero; + return target?.PointToScreen(new Point(target.Bounds.Width + horizontalOffset, 0 + verticalOffset)) ?? default; default: throw new InvalidOperationException("Invalid value for Popup.PlacementMode"); diff --git a/src/Avalonia.Controls/Primitives/PopupRoot.cs b/src/Avalonia.Controls/Primitives/PopupRoot.cs index 286f7df3b9..59a8933b4b 100644 --- a/src/Avalonia.Controls/Primitives/PopupRoot.cs +++ b/src/Avalonia.Controls/Primitives/PopupRoot.cs @@ -85,18 +85,18 @@ namespace Avalonia.Controls.Primitives if (screen != null) { var scaling = VisualRoot.RenderScaling; - - var screenX = Position.X + (Bounds.Width * scaling) - screen.Bounds.X; - var screenY = Position.Y + (Bounds.Height * scaling) - screen.Bounds.Y; + var bounds = PixelRect.FromRect(Bounds, scaling); + var screenX = Position.X + bounds.Width - screen.Bounds.X; + var screenY = Position.Y + bounds.Height - screen.Bounds.Y; if (screenX > screen.Bounds.Width) { - Position = Position.WithX(Position.X - (screenX - screen.Bounds.Width)); + Position = Position.WithX(Position.X - screenX - bounds.Width); } if (screenY > screen.Bounds.Height) { - Position = Position.WithY(Position.Y - (screenY - screen.Bounds.Height)); + Position = Position.WithY(Position.Y - screenY - bounds.Height); } } } diff --git a/src/Avalonia.Controls/Screens.cs b/src/Avalonia.Controls/Screens.cs index 2bfddc048b..5a90adadbd 100644 --- a/src/Avalonia.Controls/Screens.cs +++ b/src/Avalonia.Controls/Screens.cs @@ -18,7 +18,7 @@ namespace Avalonia.Controls _iScreenImpl = iScreenImpl; } - public Screen ScreenFromBounds(Rect bounds){ + public Screen ScreenFromBounds(PixelRect bounds){ Screen currMaxScreen = null; double maxAreaSize = 0; @@ -39,16 +39,16 @@ namespace Avalonia.Controls return currMaxScreen; } - public Screen ScreenFromPoint(Point point) + public Screen ScreenFromPoint(PixelPoint point) { - return All.FirstOrDefault(x=>x.Bounds.Contains(point)); + return All.FirstOrDefault(x => x.Bounds.Contains(point)); } public Screen ScreenFromVisual(IVisual visual) { - Point tl = visual.PointToScreen(visual.Bounds.TopLeft); - Point br = visual.PointToScreen(visual.Bounds.BottomRight); - return ScreenFromBounds(new Rect(tl,br)); + var tl = visual.PointToScreen(visual.Bounds.TopLeft); + var br = visual.PointToScreen(visual.Bounds.BottomRight); + return ScreenFromBounds(new PixelRect(tl, br)); } } -} \ No newline at end of file +} diff --git a/src/Avalonia.Controls/ToolTip.cs b/src/Avalonia.Controls/ToolTip.cs index 28d1ba5e0f..018779c994 100644 --- a/src/Avalonia.Controls/ToolTip.cs +++ b/src/Avalonia.Controls/ToolTip.cs @@ -236,6 +236,7 @@ namespace Avalonia.Controls _popup = new PopupRoot { Content = this, }; ((ISetLogicalParent)_popup).SetParent(control); + _popup.Position = Popup.GetPosition(control, GetPlacement(control), _popup, GetHorizontalOffset(control), GetVerticalOffset(control)); _popup.Show(); diff --git a/src/Avalonia.Controls/TopLevel.cs b/src/Avalonia.Controls/TopLevel.cs index 5443baf225..9be6d2004a 100644 --- a/src/Avalonia.Controls/TopLevel.cs +++ b/src/Avalonia.Controls/TopLevel.cs @@ -233,15 +233,15 @@ namespace Avalonia.Controls } /// - Point IRenderRoot.PointToClient(Point p) + Point IRenderRoot.PointToClient(PixelPoint p) { - return PlatformImpl?.PointToClient(p) ?? default(Point); + return PlatformImpl?.PointToClient(p) ?? default; } /// - Point IRenderRoot.PointToScreen(Point p) + PixelPoint IRenderRoot.PointToScreen(Point p) { - return PlatformImpl?.PointToScreen(p) ?? default(Point); + return PlatformImpl?.PointToScreen(p) ?? default; } /// diff --git a/src/Avalonia.Controls/Window.cs b/src/Avalonia.Controls/Window.cs index 46916efc1d..4306ecf338 100644 --- a/src/Avalonia.Controls/Window.cs +++ b/src/Avalonia.Controls/Window.cs @@ -462,19 +462,31 @@ namespace Avalonia.Controls void SetWindowStartupLocation() { + var scaling = PlatformImpl?.Scaling ?? 1; + + // TODO: We really need non-client size here. + var rect = new PixelRect( + PixelPoint.Origin, + PixelSize.FromSize(ClientSize, scaling)); + if (WindowStartupLocation == WindowStartupLocation.CenterScreen) { - var screen = Screens.ScreenFromPoint(Bounds.Position); + var screen = Screens.ScreenFromPoint(Position); if (screen != null) - Position = screen.WorkingArea.CenterRect(new Rect(ClientSize)).Position; + { + Position = screen.WorkingArea.CenterRect(rect).Position; + } } else if (WindowStartupLocation == WindowStartupLocation.CenterOwner) { if (Owner != null) { - var positionAsSize = Owner.ClientSize / 2 - ClientSize / 2; - Position = Owner.Position + new Point(positionAsSize.Width, positionAsSize.Height); + // TODO: We really need non-client size here. + var ownerRect = new PixelRect( + Owner.Position, + PixelSize.FromSize(Owner.ClientSize, scaling)); + Position = ownerRect.CenterRect(rect).Position; } } } diff --git a/src/Avalonia.Controls/WindowBase.cs b/src/Avalonia.Controls/WindowBase.cs index f609432545..2fba8619c6 100644 --- a/src/Avalonia.Controls/WindowBase.cs +++ b/src/Avalonia.Controls/WindowBase.cs @@ -81,7 +81,7 @@ namespace Avalonia.Controls /// /// Fired when the window position is changed. /// - public event EventHandler PositionChanged; + public event EventHandler PositionChanged; [CanBeNull] public new IWindowBaseImpl PlatformImpl => (IWindowBaseImpl) base.PlatformImpl; @@ -98,9 +98,9 @@ namespace Avalonia.Controls /// /// Gets or sets the window position in screen coordinates. /// - public Point Position + public PixelPoint Position { - get { return PlatformImpl?.Position ?? default(Point); } + get { return PlatformImpl?.Position ?? PixelPoint.Origin; } set { if (PlatformImpl is IWindowBaseImpl impl) @@ -267,9 +267,9 @@ namespace Avalonia.Controls /// . /// /// The window position. - private void HandlePositionChanged(Point pos) + private void HandlePositionChanged(PixelPoint pos) { - PositionChanged?.Invoke(this, new PointEventArgs(pos)); + PositionChanged?.Invoke(this, new PixelPointEventArgs(pos)); } /// diff --git a/src/Avalonia.DesignerSupport/Remote/PreviewerWindowImpl.cs b/src/Avalonia.DesignerSupport/Remote/PreviewerWindowImpl.cs index cff59f2f8b..dc01bcb07e 100644 --- a/src/Avalonia.DesignerSupport/Remote/PreviewerWindowImpl.cs +++ b/src/Avalonia.DesignerSupport/Remote/PreviewerWindowImpl.cs @@ -35,8 +35,8 @@ namespace Avalonia.DesignerSupport.Remote { } - public Point Position { get; set; } - public Action PositionChanged { get; set; } + public PixelPoint Position { get; set; } + public Action PositionChanged { get; set; } public Action Deactivated { get; set; } public Action Activated { get; set; } public Func Closing { get; set; } diff --git a/src/Avalonia.DesignerSupport/Remote/Stubs.cs b/src/Avalonia.DesignerSupport/Remote/Stubs.cs index 8f5b97af41..d849bfc021 100644 --- a/src/Avalonia.DesignerSupport/Remote/Stubs.cs +++ b/src/Avalonia.DesignerSupport/Remote/Stubs.cs @@ -29,8 +29,8 @@ namespace Avalonia.DesignerSupport.Remote public Func Closing { get; set; } public Action Closed { get; set; } public IMouseDevice MouseDevice { get; } = new MouseDevice(); - public Point Position { get; set; } - public Action PositionChanged { get; set; } + public PixelPoint Position { get; set; } + public Action PositionChanged { get; set; } public WindowState WindowState { get; set; } public Action WindowStateChanged { get; set; } public IRenderer CreateRenderer(IRenderRoot root) => new ImmediateRenderer(root); @@ -45,9 +45,9 @@ namespace Avalonia.DesignerSupport.Remote { } - public Point PointToClient(Point point) => point; + public Point PointToClient(PixelPoint p) => p.ToPoint(1); - public Point PointToScreen(Point point) => point; + public PixelPoint PointToScreen(Point p) => PixelPoint.FromPoint(p, 1); public void SetCursor(IPlatformHandle cursor) { @@ -157,6 +157,6 @@ namespace Avalonia.DesignerSupport.Remote public int ScreenCount => 1; public Screen[] AllScreens { get; } = - {new Screen(new Rect(0, 0, 4000, 4000), new Rect(0, 0, 4000, 4000), true)}; + {new Screen(new PixelRect(0, 0, 4000, 4000), new PixelRect(0, 0, 4000, 4000), true)}; } } diff --git a/src/Avalonia.Input/IMouseDevice.cs b/src/Avalonia.Input/IMouseDevice.cs index 58e2222295..a1d1bb3eb8 100644 --- a/src/Avalonia.Input/IMouseDevice.cs +++ b/src/Avalonia.Input/IMouseDevice.cs @@ -11,6 +11,6 @@ namespace Avalonia.Input /// /// Gets the mouse position, in screen coordinates. /// - Point Position { get; } + PixelPoint Position { get; } } } diff --git a/src/Avalonia.Input/MouseDevice.cs b/src/Avalonia.Input/MouseDevice.cs index e01dedeede..4855dd0c56 100644 --- a/src/Avalonia.Input/MouseDevice.cs +++ b/src/Avalonia.Input/MouseDevice.cs @@ -54,7 +54,7 @@ namespace Avalonia.Input /// /// Gets the mouse position, in screen coordinates. /// - public Point Position + public PixelPoint Position { get; protected set; diff --git a/src/Avalonia.Native/Helpers.cs b/src/Avalonia.Native/Helpers.cs index c9d1256e91..a77e7d4ff8 100644 --- a/src/Avalonia.Native/Helpers.cs +++ b/src/Avalonia.Native/Helpers.cs @@ -12,11 +12,21 @@ namespace Avalonia.Native return new Point(pt.X, pt.Y); } + public static PixelPoint ToAvaloniaPixelPoint(this AvnPoint pt) + { + return new PixelPoint((int)pt.X, (int)pt.Y); + } + public static AvnPoint ToAvnPoint (this Point pt) { return new AvnPoint { X = pt.X, Y = pt.Y }; } + public static AvnPoint ToAvnPoint(this PixelPoint pt) + { + return new AvnPoint { X = pt.X, Y = pt.Y }; + } + public static AvnSize ToAvnSize (this Size size) { return new AvnSize { Height = size.Height, Width = size.Width }; @@ -31,5 +41,10 @@ namespace Avalonia.Native { return new Rect(rect.X, rect.Y, rect.Width, rect.Height); } + + public static PixelRect ToAvaloniaPixelRect(this AvnRect rect) + { + return new PixelRect((int)rect.X, (int)rect.Y, (int)rect.Width, (int)rect.Height); + } } } diff --git a/src/Avalonia.Native/ScreenImpl.cs b/src/Avalonia.Native/ScreenImpl.cs index 079062c254..c1edd6c846 100644 --- a/src/Avalonia.Native/ScreenImpl.cs +++ b/src/Avalonia.Native/ScreenImpl.cs @@ -29,7 +29,10 @@ namespace Avalonia.Native { var screen = _native.GetScreen(i); - result[i] = new Screen(screen.Bounds.ToAvaloniaRect(), screen.WorkingArea.ToAvaloniaRect(), screen.Primary); + result[i] = new Screen( + screen.Bounds.ToAvaloniaPixelRect(), + screen.WorkingArea.ToAvaloniaPixelRect(), + screen.Primary); } return result; diff --git a/src/Avalonia.Native/WindowImplBase.cs b/src/Avalonia.Native/WindowImplBase.cs index 629c91a2e8..3c8d8b597f 100644 --- a/src/Avalonia.Native/WindowImplBase.cs +++ b/src/Avalonia.Native/WindowImplBase.cs @@ -159,7 +159,7 @@ namespace Avalonia.Native void IAvnWindowBaseEvents.PositionChanged(AvnPoint position) { - _parent.PositionChanged?.Invoke(position.ToAvaloniaPoint()); + _parent.PositionChanged?.Invoke(position.ToAvaloniaPixelPoint()); } void IAvnWindowBaseEvents.RawMouseEvent(AvnRawMouseEventType type, uint timeStamp, AvnInputModifiers modifiers, AvnPoint point, AvnVector delta) @@ -275,20 +275,20 @@ namespace Avalonia.Native } - public Point Position + public PixelPoint Position { - get => _native.GetPosition().ToAvaloniaPoint(); + get => _native.GetPosition().ToAvaloniaPixelPoint(); set => _native.SetPosition(value.ToAvnPoint()); } - public Point PointToClient(Point point) + public Point PointToClient(PixelPoint point) { return _native.PointToClient(point.ToAvnPoint()).ToAvaloniaPoint(); } - public Point PointToScreen(Point point) + public PixelPoint PointToScreen(Point point) { - return _native.PointToScreen(point.ToAvnPoint()).ToAvaloniaPoint(); + return _native.PointToScreen(point.ToAvnPoint()).ToAvaloniaPixelPoint(); } public void Hide() @@ -320,7 +320,7 @@ namespace Avalonia.Native _native.Cursor = newCursor.Cursor; } - public Action PositionChanged { get; set; } + public Action PositionChanged { get; set; } public Action Input { get; set; } diff --git a/src/Avalonia.Visuals/Rendering/IRenderRoot.cs b/src/Avalonia.Visuals/Rendering/IRenderRoot.cs index 2b364e5d22..044911ca95 100644 --- a/src/Avalonia.Visuals/Rendering/IRenderRoot.cs +++ b/src/Avalonia.Visuals/Rendering/IRenderRoot.cs @@ -41,15 +41,15 @@ namespace Avalonia.Rendering /// /// Converts a point from screen to client coordinates. /// - /// The point in screen coordinates. + /// The point in screen device coordinates. /// The point in client coordinates. - Point PointToClient(Point point); + Point PointToClient(PixelPoint point); /// /// Converts a point from client to screen coordinates. /// /// The point in client coordinates. - /// The point in screen coordinates. - Point PointToScreen(Point point); + /// The point in screen device coordinates. + PixelPoint PointToScreen(Point point); } } diff --git a/src/Avalonia.Visuals/VisualExtensions.cs b/src/Avalonia.Visuals/VisualExtensions.cs index 7c5d011ce8..80ef41f5d4 100644 --- a/src/Avalonia.Visuals/VisualExtensions.cs +++ b/src/Avalonia.Visuals/VisualExtensions.cs @@ -18,10 +18,14 @@ namespace Avalonia /// The visual. /// The point in screen coordinates. /// The point in client coordinates. - public static Point PointToClient(this IVisual visual, Point point) + public static Point PointToClient(this IVisual visual, PixelPoint point) { - var p = GetRootAndPosition(visual); - return p.Item1.PointToClient(point - p.Item2); + var (root, offset) = GetRootAndPosition(visual); + var screenOffset = PixelPoint.FromPoint((Point)offset, root.RenderScaling); + var screenPoint = PixelPoint.FromPoint( + new Point(point.X - screenOffset.X, point.Y - screenOffset.Y), + root.RenderScaling); + return root.PointToClient(screenPoint); } /// @@ -30,7 +34,7 @@ namespace Avalonia /// The visual. /// The point in client coordinates. /// The point in screen coordinates. - public static Point PointToScreen(this IVisual visual, Point point) + public static PixelPoint PointToScreen(this IVisual visual, Point point) { var p = GetRootAndPosition(visual); return p.Item1.PointToScreen(point + p.Item2); diff --git a/src/Avalonia.X11/X11Screens.cs b/src/Avalonia.X11/X11Screens.cs index d775261de0..f2a0520c10 100644 --- a/src/Avalonia.X11/X11Screens.cs +++ b/src/Avalonia.X11/X11Screens.cs @@ -20,7 +20,7 @@ namespace Avalonia.X11 static unsafe X11Screen[] UpdateWorkArea(X11Info info, X11Screen[] screens) { - var rect = default(Rect); + var rect = default(PixelRect); foreach (var s in screens) { rect = rect.Union(s.Bounds); @@ -46,7 +46,7 @@ namespace Avalonia.X11 return screens; var pwa = (IntPtr*)prop; - var wa = new Rect(pwa[0].ToInt32(), pwa[1].ToInt32(), pwa[2].ToInt32(), pwa[3].ToInt32()); + var wa = new PixelRect(pwa[0].ToInt32(), pwa[1].ToInt32(), pwa[2].ToInt32(), pwa[3].ToInt32()); foreach (var s in screens) @@ -105,7 +105,7 @@ namespace Avalonia.X11 density *= _settings.GlobalScaleFactor; - var bounds = new Rect(mon.X, mon.Y, mon.Width, mon.Height); + var bounds = new PixelRect(mon.X, mon.Y, mon.Width, mon.Height); screens[c] = new X11Screen(bounds, mon.Primary != 0, name, @@ -130,12 +130,12 @@ namespace Avalonia.X11 Screens = UpdateWorkArea(info, new[] { - new X11Screen(new Rect(0, 0, geo.width, geo.height), true, "Default", null, + new X11Screen(new PixelRect(0, 0, geo.width, geo.height), true, "Default", null, settings.GlobalScaleFactor) }); } - Screens = new[] {new X11Screen(new Rect(0, 0, 1920, 1280), true, "Default", null, settings.GlobalScaleFactor)}; + Screens = new[] {new X11Screen(new PixelRect(0, 0, 1920, 1280), true, "Default", null, settings.GlobalScaleFactor)}; } public X11Screen[] Screens { get; } @@ -220,12 +220,12 @@ namespace Avalonia.X11 { public bool Primary { get; } public string Name { get; set; } - public Rect Bounds { get; set; } + public PixelRect Bounds { get; set; } public Size? PhysicalSize { get; set; } public double PixelDensity { get; set; } - public Rect WorkingArea { get; set; } + public PixelRect WorkingArea { get; set; } - public X11Screen(Rect bounds, bool primary, + public X11Screen(PixelRect bounds, bool primary, string name, Size? physicalSize, double? pixelDensity) { Primary = primary; diff --git a/src/Avalonia.X11/X11Window.cs b/src/Avalonia.X11/X11Window.cs index 710951fcab..14a9bc7c1a 100644 --- a/src/Avalonia.X11/X11Window.cs +++ b/src/Avalonia.X11/X11Window.cs @@ -24,12 +24,12 @@ namespace Avalonia.X11 private readonly X11Info _x11; private bool _invalidated; private XConfigureEvent? _configure; - private Point? _configurePoint; + private PixelPoint? _configurePoint; private bool _triggeredExpose; private IInputRoot _inputRoot; private readonly IMouseDevice _mouse; private readonly IKeyboardDevice _keyboard; - private Point? _position; + private PixelPoint? _position; private PixelSize _realSize; private IntPtr _handle; private IntPtr _xic; @@ -235,7 +235,7 @@ namespace Avalonia.X11 public Func Closing { get; set; } public Action WindowStateChanged { get; set; } public Action Closed { get; set; } - public Action PositionChanged { get; set; } + public Action PositionChanged { get; set; } public IRenderer CreateRenderer(IRenderRoot root) => new DeferredRenderer(root, AvaloniaLocator.Current.GetService()); @@ -321,13 +321,13 @@ namespace Avalonia.X11 var needEnqueue = (_configure == null); _configure = ev.ConfigureEvent; if (ev.ConfigureEvent.override_redirect || ev.ConfigureEvent.send_event) - _configurePoint = new Point(ev.ConfigureEvent.x, ev.ConfigureEvent.y); + _configurePoint = new PixelPoint(ev.ConfigureEvent.x, ev.ConfigureEvent.y); else { XTranslateCoordinates(_x11.Display, _handle, _x11.RootWindow, 0, 0, out var tx, out var ty, out _); - _configurePoint = new Point(tx, ty); + _configurePoint = new PixelPoint(tx, ty); } if (needEnqueue) Dispatcher.UIThread.Post(() => @@ -660,9 +660,11 @@ namespace Avalonia.X11 public void Hide() => XUnmapWindow(_x11.Display, _handle); - public Point PointToClient(Point point) => new Point((point.X - Position.X) / Scaling, (point.Y - Position.Y) / Scaling); + public Point PointToClient(PixelPoint point) => new Point((point.X - Position.X) / Scaling, (point.Y - Position.Y) / Scaling); - public Point PointToScreen(Point point) => new Point(point.X * Scaling + Position.X, point.Y * Scaling + Position.Y); + public PixelPoint PointToScreen(Point point) => new PixelPoint( + (int)(point.X * Scaling + Position.X), + (int)(point.Y * Scaling + Position.Y)); public void SetSystemDecorations(bool enabled) { @@ -716,7 +718,7 @@ namespace Avalonia.X11 public IPlatformHandle Handle { get; } - public Point Position + public PixelPoint Position { get => _position ?? default; set @@ -752,7 +754,7 @@ namespace Avalonia.X11 public IScreenImpl Screen => _platform.Screens; - public Size MaxClientSize => _platform.X11Screens.Screens.Select(s => s.Bounds.Size / s.PixelDensity) + public Size MaxClientSize => _platform.X11Screens.Screens.Select(s => s.Bounds.Size.ToSize(s.PixelDensity)) .OrderByDescending(x => x.Width + x.Height).FirstOrDefault(); diff --git a/src/Gtk/Avalonia.Gtk3/GtkScreen.cs b/src/Gtk/Avalonia.Gtk3/GtkScreen.cs index 7c28dd47b9..a2b4604130 100644 --- a/src/Gtk/Avalonia.Gtk3/GtkScreen.cs +++ b/src/Gtk/Avalonia.Gtk3/GtkScreen.cs @@ -6,7 +6,7 @@ namespace Avalonia.Gtk3 { private readonly int _screenId; - public GtkScreen(Rect bounds, Rect workingArea, bool primary, int screenId) : base(bounds, workingArea, primary) + public GtkScreen(PixelRect bounds, PixelRect workingArea, bool primary, int screenId) : base(bounds, workingArea, primary) { this._screenId = screenId; } @@ -21,4 +21,4 @@ namespace Avalonia.Gtk3 return (obj is GtkScreen screen) ? this._screenId == screen._screenId : base.Equals(obj); } } -} \ No newline at end of file +} diff --git a/src/Gtk/Avalonia.Gtk3/ScreenImpl.cs b/src/Gtk/Avalonia.Gtk3/ScreenImpl.cs index 97b5029219..9a19ed00e0 100644 --- a/src/Gtk/Avalonia.Gtk3/ScreenImpl.cs +++ b/src/Gtk/Avalonia.Gtk3/ScreenImpl.cs @@ -27,8 +27,8 @@ namespace Avalonia.Gtk3 GdkRectangle workArea = new GdkRectangle(), geometry = new GdkRectangle(); Native.GdkScreenGetMonitorGeometry(screen, i, ref geometry); Native.GdkScreenGetMonitorWorkarea(screen, i, ref workArea); - Rect workAreaRect = new Rect(workArea.X, workArea.Y, workArea.Width, workArea.Height); - Rect geometryRect = new Rect(geometry.X, geometry.Y, geometry.Width, geometry.Height); + PixelRect workAreaRect = new PixelRect(workArea.X, workArea.Y, workArea.Width, workArea.Height); + PixelRect geometryRect = new PixelRect(geometry.X, geometry.Y, geometry.Width, geometry.Height); GtkScreen s = new GtkScreen(geometryRect, workAreaRect, i == primary, i); screens[i] = s; } diff --git a/src/Gtk/Avalonia.Gtk3/WindowBaseImpl.cs b/src/Gtk/Avalonia.Gtk3/WindowBaseImpl.cs index 6d4544bfbd..c4697462d2 100644 --- a/src/Gtk/Avalonia.Gtk3/WindowBaseImpl.cs +++ b/src/Gtk/Avalonia.Gtk3/WindowBaseImpl.cs @@ -24,7 +24,7 @@ namespace Avalonia.Gtk3 private readonly EglGlPlatformSurface _egl; protected readonly List Disposables = new List(); private Size _lastSize; - private Point _lastPosition; + private PixelPoint _lastPosition; private double _lastScaling; private uint _lastKbdEvent; private uint _lastSmoothScrollEvent; @@ -383,7 +383,7 @@ namespace Avalonia.Gtk3 public Action Paint { get; set; } public Action Resized { get; set; } public Action ScalingChanged { get; set; } //TODO - public Action PositionChanged { get; set; } + public Action PositionChanged { get; set; } public void Activate() => Native.GtkWidgetActivate(GtkWidget); @@ -402,7 +402,7 @@ namespace Avalonia.Gtk3 Dispatcher.UIThread.Post(() => Input?.Invoke(args), DispatcherPriority.Input); } - public Point PointToClient(Point point) + public Point PointToClient(PixelPoint point) { int x, y; Native.GdkWindowGetOrigin(Native.GtkWidgetGetWindow(GtkWidget), out x, out y); @@ -410,11 +410,11 @@ namespace Avalonia.Gtk3 return new Point(point.X - x, point.Y - y); } - public Point PointToScreen(Point point) + public PixelPoint PointToScreen(Point point) { int x, y; Native.GdkWindowGetOrigin(Native.GtkWidgetGetWindow(GtkWidget), out x, out y); - return new Point(point.X + x, point.Y + y); + return new PixelPoint((int)(point.X + x), (int)(point.Y + y)); } public void SetCursor(IPlatformHandle cursor) @@ -490,13 +490,13 @@ namespace Avalonia.Gtk3 get; } = new ScreenImpl(); - public Point Position + public PixelPoint Position { get { int x, y; Native.GtkWindowGetPosition(GtkWidget, out x, out y); - return new Point(x, y); + return new PixelPoint(x, y); } set { Native.GtkWindowMove(GtkWidget, (int)value.X, (int)value.Y); } } diff --git a/src/Linux/Avalonia.LinuxFramebuffer/FramebufferToplevelImpl.cs b/src/Linux/Avalonia.LinuxFramebuffer/FramebufferToplevelImpl.cs index 968873abc0..6a63014e1a 100644 --- a/src/Linux/Avalonia.LinuxFramebuffer/FramebufferToplevelImpl.cs +++ b/src/Linux/Avalonia.LinuxFramebuffer/FramebufferToplevelImpl.cs @@ -51,9 +51,9 @@ namespace Avalonia.LinuxFramebuffer InputRoot = inputRoot; } - public Point PointToClient(Point point) => point; + public Point PointToClient(PixelPoint p) => p.ToPoint(1); - public Point PointToScreen(Point point) => point; + public PixelPoint PointToScreen(Point p) => PixelPoint.FromPoint(p, 1); public void SetCursor(IPlatformHandle cursor) { diff --git a/src/Windows/Avalonia.Win32.Interop/Wpf/WpfInteropExtensions.cs b/src/Windows/Avalonia.Win32.Interop/Wpf/WpfInteropExtensions.cs index 44c4c84cd6..cbfc259eda 100644 --- a/src/Windows/Avalonia.Win32.Interop/Wpf/WpfInteropExtensions.cs +++ b/src/Windows/Avalonia.Win32.Interop/Wpf/WpfInteropExtensions.cs @@ -3,7 +3,9 @@ static class WpfInteropExtensions { public static System.Windows.Point ToWpfPoint(this Point pt) => new System.Windows.Point(pt.X, pt.Y); + public static System.Windows.Point ToWpfPoint(this PixelPoint pt) => new System.Windows.Point(pt.X, pt.Y); public static Point ToAvaloniaPoint(this System.Windows.Point pt) => new Point(pt.X, pt.Y); + public static PixelPoint ToAvaloniaPixelPoint(this System.Windows.Point pt) => new PixelPoint((int)pt.X, (int)pt.Y); public static System.Windows.Size ToWpfSize(this Size pt) => new System.Windows.Size(pt.Width, pt.Height); public static Size ToAvaloniaSize(this System.Windows.Size pt) => new Size(pt.Width, pt.Height); } diff --git a/src/Windows/Avalonia.Win32.Interop/Wpf/WpfTopLevelImpl.cs b/src/Windows/Avalonia.Win32.Interop/Wpf/WpfTopLevelImpl.cs index b5fb158168..7005459487 100644 --- a/src/Windows/Avalonia.Win32.Interop/Wpf/WpfTopLevelImpl.cs +++ b/src/Windows/Avalonia.Win32.Interop/Wpf/WpfTopLevelImpl.cs @@ -135,9 +135,9 @@ namespace Avalonia.Win32.Interop.Wpf void ITopLevelImpl.SetInputRoot(IInputRoot inputRoot) => _inputRoot = inputRoot; - Point ITopLevelImpl.PointToClient(Point point) => PointFromScreen(point.ToWpfPoint()).ToAvaloniaPoint(); + Point ITopLevelImpl.PointToClient(PixelPoint point) => PointFromScreen(point.ToWpfPoint()).ToAvaloniaPoint(); - Point ITopLevelImpl.PointToScreen(Point point) => PointToScreen(point.ToWpfPoint()).ToAvaloniaPoint(); + PixelPoint ITopLevelImpl.PointToScreen(Point point) => PointToScreen(point.ToWpfPoint()).ToAvaloniaPixelPoint(); protected override void OnLostFocus(RoutedEventArgs e) => LostFocus?.Invoke(); diff --git a/src/Windows/Avalonia.Win32/OleDropTarget.cs b/src/Windows/Avalonia.Win32/OleDropTarget.cs index 7013931be9..28065566d0 100644 --- a/src/Windows/Avalonia.Win32/OleDropTarget.cs +++ b/src/Windows/Avalonia.Win32/OleDropTarget.cs @@ -177,7 +177,7 @@ namespace Avalonia.Win32 int x = (int)dragPoint; int y = (int)(dragPoint >> 32); - Point screenPt = new Point(x, y); + var screenPt = new PixelPoint(x, y); return _target.PointToClient(screenPt); } } diff --git a/src/Windows/Avalonia.Win32/ScreenImpl.cs b/src/Windows/Avalonia.Win32/ScreenImpl.cs index 5cfceb68b7..aa3b23eb50 100644 --- a/src/Windows/Avalonia.Win32/ScreenImpl.cs +++ b/src/Windows/Avalonia.Win32/ScreenImpl.cs @@ -31,10 +31,10 @@ namespace Avalonia.Win32 { RECT bounds = monitorInfo.rcMonitor; RECT workingArea = monitorInfo.rcWork; - Rect avaloniaBounds = new Rect(bounds.left, bounds.top, bounds.right - bounds.left, + PixelRect avaloniaBounds = new PixelRect(bounds.left, bounds.top, bounds.right - bounds.left, bounds.bottom - bounds.top); - Rect avaloniaWorkArea = - new Rect(workingArea.left, workingArea.top, workingArea.right - workingArea.left, + PixelRect avaloniaWorkArea = + new PixelRect(workingArea.left, workingArea.top, workingArea.right - workingArea.left, workingArea.bottom - workingArea.top); screens[index] = new WinScreen(avaloniaBounds, avaloniaWorkArea, monitorInfo.dwFlags == 1, diff --git a/src/Windows/Avalonia.Win32/WinScreen.cs b/src/Windows/Avalonia.Win32/WinScreen.cs index 2f8dcb0f3e..e849800e62 100644 --- a/src/Windows/Avalonia.Win32/WinScreen.cs +++ b/src/Windows/Avalonia.Win32/WinScreen.cs @@ -7,7 +7,7 @@ namespace Avalonia.Win32 { private readonly IntPtr _hMonitor; - public WinScreen(Rect bounds, Rect workingArea, bool primary, IntPtr hMonitor) : base(bounds, workingArea, primary) + public WinScreen(PixelRect bounds, PixelRect workingArea, bool primary, IntPtr hMonitor) : base(bounds, workingArea, primary) { this._hMonitor = hMonitor; } @@ -22,4 +22,4 @@ namespace Avalonia.Win32 return (obj is WinScreen screen) ? this._hMonitor == screen._hMonitor : base.Equals(obj); } } -} \ No newline at end of file +} diff --git a/src/Windows/Avalonia.Win32/WindowImpl.cs b/src/Windows/Avalonia.Win32/WindowImpl.cs index f4eb0e2ea8..081a713e95 100644 --- a/src/Windows/Avalonia.Win32/WindowImpl.cs +++ b/src/Windows/Avalonia.Win32/WindowImpl.cs @@ -87,7 +87,7 @@ namespace Avalonia.Win32 public Action ScalingChanged { get; set; } - public Action PositionChanged { get; set; } + public Action PositionChanged { get; set; } public Action WindowStateChanged { get; set; } @@ -297,19 +297,19 @@ namespace Avalonia.Win32 UnmanagedMethods.InvalidateRect(_hwnd, ref r, false); } - public Point PointToClient(Point point) + public Point PointToClient(PixelPoint point) { var p = new UnmanagedMethods.POINT { X = (int)point.X, Y = (int)point.Y }; UnmanagedMethods.ScreenToClient(_hwnd, ref p); return new Point(p.X, p.Y) / Scaling; } - public Point PointToScreen(Point point) + public PixelPoint PointToScreen(Point point) { point *= Scaling; var p = new UnmanagedMethods.POINT { X = (int)point.X, Y = (int)point.Y }; UnmanagedMethods.ClientToScreen(_hwnd, ref p); - return new Point(p.X, p.Y); + return new PixelPoint(p.X, p.Y); } public void SetInputRoot(IInputRoot inputRoot) @@ -357,21 +357,21 @@ namespace Avalonia.Win32 #endif } - public Point Position + public PixelPoint Position { get { UnmanagedMethods.RECT rc; UnmanagedMethods.GetWindowRect(_hwnd, out rc); - return new Point(rc.left, rc.top); + return new PixelPoint(rc.left, rc.top); } set { UnmanagedMethods.SetWindowPos( Handle.Handle, IntPtr.Zero, - (int)value.X, - (int)value.Y, + value.X, + value.Y, 0, 0, UnmanagedMethods.SetWindowPosFlags.SWP_NOSIZE | UnmanagedMethods.SetWindowPosFlags.SWP_NOACTIVATE); @@ -661,7 +661,7 @@ namespace Avalonia.Win32 return IntPtr.Zero; case UnmanagedMethods.WindowsMessage.WM_MOVE: - PositionChanged?.Invoke(new Point((short)(ToInt32(lParam) & 0xffff), (short)(ToInt32(lParam) >> 16))); + PositionChanged?.Invoke(new PixelPoint((short)(ToInt32(lParam) & 0xffff), (short)(ToInt32(lParam) >> 16))); return IntPtr.Zero; case UnmanagedMethods.WindowsMessage.WM_GETMINMAXINFO: @@ -785,9 +785,9 @@ namespace Avalonia.Win32 return new Point((short)(ToInt32(lParam) & 0xffff), (short)(ToInt32(lParam) >> 16)) / Scaling; } - private Point PointFromLParam(IntPtr lParam) + private PixelPoint PointFromLParam(IntPtr lParam) { - return new Point((short)(ToInt32(lParam) & 0xffff), (short)(ToInt32(lParam) >> 16)); + return new PixelPoint((short)(ToInt32(lParam) & 0xffff), (short)(ToInt32(lParam) >> 16)); } private Point ScreenToClient(Point point) diff --git a/src/iOS/Avalonia.iOS/TopLevelImpl.cs b/src/iOS/Avalonia.iOS/TopLevelImpl.cs index 1101f53222..83d10b8b44 100644 --- a/src/iOS/Avalonia.iOS/TopLevelImpl.cs +++ b/src/iOS/Avalonia.iOS/TopLevelImpl.cs @@ -68,9 +68,9 @@ namespace Avalonia.iOS public void SetInputRoot(IInputRoot inputRoot) => _inputRoot = inputRoot; - public Point PointToClient(Point point) => point; + public Point PointToClient(PixelPoint point) => point.ToPoint(1); - public Point PointToScreen(Point point) => point; + public PixelPoint PointToScreen(Point point) => PixelPoint.FromPoint(point, 1); public void SetCursor(IPlatformHandle cursor) { diff --git a/tests/Avalonia.Controls.UnitTests/ButtonTests.cs b/tests/Avalonia.Controls.UnitTests/ButtonTests.cs index 76f2898700..91b37596b7 100644 --- a/tests/Avalonia.Controls.UnitTests/ButtonTests.cs +++ b/tests/Avalonia.Controls.UnitTests/ButtonTests.cs @@ -238,9 +238,9 @@ namespace Avalonia.Controls.UnitTests public void Invalidate(Rect rect) => throw new NotImplementedException(); - public Point PointToClient(Point point) => throw new NotImplementedException(); + public Point PointToClient(PixelPoint p) => throw new NotImplementedException(); - public Point PointToScreen(Point point) => throw new NotImplementedException(); + public PixelPoint PointToScreen(Point p) => throw new NotImplementedException(); } private void RaisePointerPressed(Button button, IMouseDevice device, int clickCount, MouseButton mouseButton) diff --git a/tests/Avalonia.Controls.UnitTests/ContextMenuTests.cs b/tests/Avalonia.Controls.UnitTests/ContextMenuTests.cs index 66f0cc3a40..834c49ba6b 100644 --- a/tests/Avalonia.Controls.UnitTests/ContextMenuTests.cs +++ b/tests/Avalonia.Controls.UnitTests/ContextMenuTests.cs @@ -160,7 +160,7 @@ namespace Avalonia.Controls.UnitTests private IDisposable Application() { - var screen = new Rect(new Point(), new Size(100, 100)); + var screen = new PixelRect(new PixelPoint(), new PixelSize(100, 100)); var screenImpl = new Mock(); screenImpl.Setup(x => x.ScreenCount).Returns(1); screenImpl.Setup(X => X.AllScreens).Returns( new[] { new Screen(screen, screen, true) }); diff --git a/tests/Avalonia.Controls.UnitTests/Presenters/ItemsPresenterTests_Virtualization.cs b/tests/Avalonia.Controls.UnitTests/Presenters/ItemsPresenterTests_Virtualization.cs index ba4ccb0527..3ea32ed719 100644 --- a/tests/Avalonia.Controls.UnitTests/Presenters/ItemsPresenterTests_Virtualization.cs +++ b/tests/Avalonia.Controls.UnitTests/Presenters/ItemsPresenterTests_Virtualization.cs @@ -336,25 +336,11 @@ namespace Avalonia.Controls.UnitTests.Presenters public double LayoutScaling => 1; public ILayoutManager LayoutManager { get; } = new LayoutManager(); - public IRenderTarget CreateRenderTarget() - { - throw new NotImplementedException(); - } - - public void Invalidate(Rect rect) - { - throw new NotImplementedException(); - } - public Point PointToClient(Point point) - { - throw new NotImplementedException(); - } - - public Point PointToScreen(Point point) - { - throw new NotImplementedException(); - } + public IRenderTarget CreateRenderTarget() => throw new NotImplementedException(); + public void Invalidate(Rect rect) => throw new NotImplementedException(); + public Point PointToClient(PixelPoint p) => throw new NotImplementedException(); + public PixelPoint PointToScreen(Point p) => throw new NotImplementedException(); } private class TestItemsPresenter : ItemsPresenter diff --git a/tests/Avalonia.Controls.UnitTests/Presenters/ItemsPresenterTests_Virtualization_Simple.cs b/tests/Avalonia.Controls.UnitTests/Presenters/ItemsPresenterTests_Virtualization_Simple.cs index 7187ea16da..9921a8de6c 100644 --- a/tests/Avalonia.Controls.UnitTests/Presenters/ItemsPresenterTests_Virtualization_Simple.cs +++ b/tests/Avalonia.Controls.UnitTests/Presenters/ItemsPresenterTests_Virtualization_Simple.cs @@ -1077,30 +1077,10 @@ namespace Avalonia.Controls.UnitTests.Presenters public ILayoutManager LayoutManager { get; } = new LayoutManager(); - public IRenderTarget CreateRenderTarget() - { - throw new NotImplementedException(); - } - - public void Invalidate(Rect rect) - { - throw new NotImplementedException(); - } - - public Point PointToClient(Point point) - { - throw new NotImplementedException(); - } - - public Point PointToScreen(Point point) - { - throw new NotImplementedException(); - } - - protected override Size MeasureOverride(Size availableSize) - { - return base.MeasureOverride(availableSize); - } + public IRenderTarget CreateRenderTarget() => throw new NotImplementedException(); + public void Invalidate(Rect rect) => throw new NotImplementedException(); + public Point PointToClient(PixelPoint p) => throw new NotImplementedException(); + public PixelPoint PointToScreen(Point p) => throw new NotImplementedException(); } private class TestItemsPresenter : ItemsPresenter diff --git a/tests/Avalonia.Controls.UnitTests/WindowTests.cs b/tests/Avalonia.Controls.UnitTests/WindowTests.cs index adf9bd9cec..c0b5342934 100644 --- a/tests/Avalonia.Controls.UnitTests/WindowTests.cs +++ b/tests/Avalonia.Controls.UnitTests/WindowTests.cs @@ -278,8 +278,8 @@ namespace Avalonia.Controls.UnitTests [Fact] public void Window_Should_Be_Centered_When_WindowStartupLocation_Is_CenterScreen() { - var screen1 = new Mock(new Rect(new Size(1920, 1080)), new Rect(new Size(1920, 1040)), true); - var screen2 = new Mock(new Rect(new Size(1366, 768)), new Rect(new Size(1366, 728)), false); + var screen1 = new Mock(new PixelRect(new PixelSize(1920, 1080)), new PixelRect(new PixelSize(1920, 1040)), true); + var screen2 = new Mock(new PixelRect(new PixelSize(1366, 768)), new PixelRect(new PixelSize(1366, 728)), false); var screens = new Mock(); screens.Setup(x => x.AllScreens).Returns(new Screen[] { screen1.Object, screen2.Object }); @@ -294,13 +294,13 @@ namespace Avalonia.Controls.UnitTests { var window = new Window(windowImpl.Object); window.WindowStartupLocation = WindowStartupLocation.CenterScreen; - window.Position = new Point(60, 40); + window.Position = new PixelPoint(60, 40); window.Show(); - var expectedPosition = new Point( - screen1.Object.WorkingArea.Size.Width / 2 - window.ClientSize.Width / 2, - screen1.Object.WorkingArea.Size.Height / 2 - window.ClientSize.Height / 2); + var expectedPosition = new PixelPoint( + (int)(screen1.Object.WorkingArea.Size.Width / 2 - window.ClientSize.Width / 2), + (int)(screen1.Object.WorkingArea.Size.Height / 2 - window.ClientSize.Height / 2)); Assert.Equal(window.Position, expectedPosition); } @@ -330,7 +330,7 @@ namespace Avalonia.Controls.UnitTests using (UnitTestApplication.Start(parentWindowServices)) { var parentWindow = new Window(); - parentWindow.Position = new Point(60, 40); + parentWindow.Position = new PixelPoint(60, 40); parentWindow.Show(); @@ -338,14 +338,14 @@ namespace Avalonia.Controls.UnitTests { var window = new Window(); window.WindowStartupLocation = WindowStartupLocation.CenterOwner; - window.Position = new Point(60, 40); + window.Position = new PixelPoint(60, 40); window.Owner = parentWindow; window.Show(); - var expectedPosition = new Point( - parentWindow.Position.X + parentWindow.ClientSize.Width / 2 - window.ClientSize.Width / 2, - parentWindow.Position.Y + parentWindow.ClientSize.Height / 2 - window.ClientSize.Height / 2); + var expectedPosition = new PixelPoint( + (int)(parentWindow.Position.X + parentWindow.ClientSize.Width / 2 - window.ClientSize.Width / 2), + (int)(parentWindow.Position.Y + parentWindow.ClientSize.Height / 2 - window.ClientSize.Height / 2)); Assert.Equal(window.Position, expectedPosition); } diff --git a/tests/Avalonia.UnitTests/TestRoot.cs b/tests/Avalonia.UnitTests/TestRoot.cs index 972b1d78c0..370ddba410 100644 --- a/tests/Avalonia.UnitTests/TestRoot.cs +++ b/tests/Avalonia.UnitTests/TestRoot.cs @@ -90,9 +90,9 @@ namespace Avalonia.UnitTests { } - public Point PointToClient(Point p) => p; + public Point PointToClient(PixelPoint p) => p.ToPoint(1); - public Point PointToScreen(Point p) => p; + public PixelPoint PointToScreen(Point p) => PixelPoint.FromPoint(p, 1); void INameScope.Register(string name, object element) { diff --git a/tests/Avalonia.UnitTests/TestTemplatedRoot.cs b/tests/Avalonia.UnitTests/TestTemplatedRoot.cs index 2d896435a3..5d42699d3f 100644 --- a/tests/Avalonia.UnitTests/TestTemplatedRoot.cs +++ b/tests/Avalonia.UnitTests/TestTemplatedRoot.cs @@ -57,9 +57,9 @@ namespace Avalonia.UnitTests throw new NotImplementedException(); } - public Point PointToClient(Point p) => p; + public Point PointToClient(PixelPoint p) => p.ToPoint(1); - public Point PointToScreen(Point p) => p; + public PixelPoint PointToScreen(Point p) => PixelPoint.FromPoint(p, 1); void INameScope.Register(string name, object element) { From 9a2f828ba60d852f0aed9a3a50eb3645489b542f Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Fri, 18 Jan 2019 12:37:18 +0100 Subject: [PATCH 08/14] Modify DialogsPage to allow testing of startup location. --- samples/ControlCatalog/Pages/DialogsPage.xaml.cs | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/samples/ControlCatalog/Pages/DialogsPage.xaml.cs b/samples/ControlCatalog/Pages/DialogsPage.xaml.cs index 187250b6ea..55753b214f 100644 --- a/samples/ControlCatalog/Pages/DialogsPage.xaml.cs +++ b/samples/ControlCatalog/Pages/DialogsPage.xaml.cs @@ -40,10 +40,13 @@ namespace ControlCatalog.Pages }; this.FindControl