-
Notifications
You must be signed in to change notification settings - Fork 1.8k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
[VisualDiagnostics] Fix RectangleAdorner with updated math (#8728)
- Loading branch information
1 parent
951f65e
commit 0779d0f
Showing
2 changed files
with
263 additions
and
12 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,212 @@ | ||
using System; | ||
using System.Collections.Generic; | ||
using System.Linq; | ||
using System.Numerics; | ||
using Microsoft.Maui.Graphics; | ||
using Microsoft.Maui.Platform; | ||
|
||
namespace Microsoft.Maui | ||
{ | ||
internal class AdornerModel | ||
{ | ||
Rect boundingBox = new Rect(); | ||
List<Rect> marginZones = new List<Rect>(); | ||
|
||
public Rect BoundingBox => boundingBox; | ||
public IReadOnlyList<Rect> MarginZones => marginZones; | ||
|
||
|
||
public void Update(Rect rect, Thickness margin, Matrix4x4 transformToRoot, double density) | ||
{ | ||
double unitsPerPixel = 1 / density; | ||
boundingBox = DpiHelper.RoundToPixel(rect, unitsPerPixel); | ||
|
||
marginZones.Clear(); | ||
|
||
if (margin == new Thickness()) | ||
{ | ||
return; | ||
} | ||
|
||
// If transform to root is more than just an offset (i.e. element or | ||
// some of its ancestors are scaled/rotated/etc.) then no margins to | ||
// render. This matches WPF, UWP and WinUI adorners. | ||
if (!Tolerances.AreClose(transformToRoot.M11, 1) || | ||
!Tolerances.AreClose(transformToRoot.M22, 1) || | ||
!Tolerances.AreClose(transformToRoot.M12, 0) || | ||
!Tolerances.AreClose(transformToRoot.M21, 0)) | ||
{ | ||
return; | ||
} | ||
|
||
// Create up to 4 rectangles for margins. Keep in mind that some of | ||
// margin values can be negative, e.g. Margin="-10, 20, 30, -40". | ||
|
||
// Left | ||
if (!Tolerances.NearZero(margin.Left)) | ||
{ | ||
Rect rc = new Rect(); | ||
rc.Left = Math.Min(rect.Left - margin.Left, rect.Left); | ||
rc.Width = Math.Abs(margin.Left); | ||
rc.Top = rect.Top; | ||
rc.Bottom = rect.Bottom; | ||
TryAddMarginZone(rc, unitsPerPixel); | ||
} | ||
|
||
// Right | ||
if (!Tolerances.NearZero(margin.Right)) | ||
{ | ||
Rect rc = new Rect(); | ||
rc.Left = Math.Min(rect.Right, rect.Right + margin.Right); | ||
rc.Width = Math.Abs(margin.Right); | ||
rc.Top = rect.Top; | ||
rc.Bottom = rect.Bottom; | ||
TryAddMarginZone(rc, unitsPerPixel); | ||
} | ||
|
||
// Top | ||
if (!Tolerances.NearZero(margin.Top)) | ||
{ | ||
Rect rc = new Rect(); | ||
rc.Left = rect.Left - Math.Max(0, margin.Left); | ||
rc.Right = rect.Right + Math.Max(0, margin.Right); | ||
rc.Top = Math.Min(rect.Top - margin.Top, rect.Top); | ||
rc.Height = Math.Abs(margin.Top); | ||
TryAddMarginZone(rc, unitsPerPixel); | ||
} | ||
|
||
// Bottom | ||
if (!Tolerances.NearZero(margin.Bottom)) | ||
{ | ||
Rect rc = new Rect(); | ||
rc.Left = rect.Left - Math.Max(0, margin.Left); | ||
rc.Right = rect.Right + Math.Max(0, margin.Right); | ||
rc.Top = Math.Min(rect.Bottom + margin.Bottom, rect.Bottom); | ||
rc.Height = Math.Abs(margin.Bottom); | ||
TryAddMarginZone(rc, unitsPerPixel); | ||
} | ||
} | ||
|
||
void TryAddMarginZone(Rect rect, double unitsPerPixel) | ||
{ | ||
Rect rc = DpiHelper.RoundToPixel(rect, unitsPerPixel); | ||
if (!rc.IsEmpty) | ||
marginZones.Add(rc); | ||
} | ||
|
||
/// <summary> | ||
/// DPI related utilities. | ||
/// </summary> | ||
private static class DpiHelper | ||
{ | ||
/// <summary> | ||
/// Rounds unit value to nearest pixel. | ||
/// </summary> | ||
/// <returns> | ||
/// Rounded value in units. | ||
/// </returns> | ||
public static double RoundToPixel(double units, double unitsPerPixel) | ||
{ | ||
double pixels = units / unitsPerPixel; | ||
double floorPixels = (double)Math.Floor(pixels); | ||
pixels = Tolerances.LessThan(pixels, floorPixels + 0.5) ? | ||
floorPixels : (double)Math.Ceiling(pixels); | ||
return pixels * unitsPerPixel; | ||
} | ||
|
||
/// <summary> | ||
/// Rounds point X and Y coordinates to nearest pixel. | ||
/// </summary> | ||
public static Point RoundToPixel(Point point, double unitsPerPixel) | ||
{ | ||
Point pixelPoint = new Point( | ||
DpiHelper.RoundToPixel(point.X, unitsPerPixel), | ||
DpiHelper.RoundToPixel(point.Y, unitsPerPixel)); | ||
return pixelPoint; | ||
} | ||
|
||
/// <summary> | ||
/// Rounds rectangle corner coordinates to nearest pixel. | ||
/// </summary> | ||
public static Rect RoundToPixel(Rect rect, double unitsPerPixel) | ||
{ | ||
double left = DpiHelper.RoundToPixel(rect.Left, unitsPerPixel); | ||
double top = DpiHelper.RoundToPixel(rect.Top, unitsPerPixel); | ||
double right = DpiHelper.RoundToPixel(rect.Right, unitsPerPixel); | ||
double bottom = DpiHelper.RoundToPixel(rect.Bottom, unitsPerPixel); | ||
return new Rect(left, top, right - left, bottom - top); | ||
} | ||
} | ||
|
||
/// <summary> | ||
/// Helper utilities to compare double values. | ||
/// </summary> | ||
private static class Tolerances | ||
{ | ||
const double Epsilon = 2.2204460492503131e-016; | ||
const double ZeroThreshold = 2.2204460492503131e-015; | ||
|
||
public static bool AreClose(Point point1, Point point2) | ||
{ | ||
if (Tolerances.AreClose(point1.X, point2.X) && Tolerances.AreClose(point1.Y, point2.Y)) | ||
{ | ||
return true; | ||
} | ||
|
||
return false; | ||
} | ||
|
||
public static bool NearZero(double value) | ||
{ | ||
return Math.Abs(value) < Tolerances.ZeroThreshold; | ||
} | ||
|
||
public static bool AreClose(double value1, double value2) | ||
{ | ||
//in case they are Infinities (then epsilon check does not work) | ||
if (value1 == value2) return true; | ||
// This computes (|value1-value2| / (|value1| + |value2| + 10.0)) < Tolerances.Epsilon | ||
double eps = (Math.Abs(value1) + Math.Abs(value2) + 10.0) * Tolerances.Epsilon; | ||
double delta = value1 - value2; | ||
return (-eps < delta) && (eps > delta); | ||
} | ||
|
||
public static bool GreaterThan(double value1, double value2) | ||
{ | ||
if (value1 > value2) | ||
{ | ||
return !Tolerances.AreClose(value1, value2); | ||
} | ||
return false; | ||
} | ||
|
||
public static bool GreaterThanOrClose(double value1, double value2) | ||
{ | ||
if (value1 <= value2) | ||
{ | ||
return Tolerances.AreClose(value1, value2); | ||
} | ||
return true; | ||
} | ||
|
||
public static bool LessThan(double value1, double value2) | ||
{ | ||
if (value1 < value2) | ||
{ | ||
return !Tolerances.AreClose(value1, value2); | ||
} | ||
return false; | ||
} | ||
|
||
public static bool LessThanOrClose(double value1, double value2) | ||
{ | ||
if (value1 >= value2) | ||
{ | ||
return Tolerances.AreClose(value1, value2); | ||
} | ||
return true; | ||
} | ||
} | ||
} | ||
} | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters