Browse Source

Merge pull request #4104 from rstm-sf/refactor/remove_duplicate_math_utils

Remove duplicate code for MathUtilities
pull/4134/head
Dariusz Komosiński 6 years ago
committed by GitHub
parent
commit
c8c4d1bf0a
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 1
      src/Avalonia.Base/Properties/AssemblyInfo.cs
  2. 17
      src/Avalonia.Base/Utilities/MathUtilities.cs
  3. 38
      src/Avalonia.Controls.DataGrid/DataGrid.cs
  4. 2
      src/Avalonia.Controls.DataGrid/DataGridColumnHeader.cs
  5. 16
      src/Avalonia.Controls.DataGrid/DataGridColumns.cs
  6. 2
      src/Avalonia.Controls.DataGrid/DataGridLength.cs
  7. 2
      src/Avalonia.Controls.DataGrid/DataGridRow.cs
  8. 60
      src/Avalonia.Controls.DataGrid/DataGridRows.cs
  9. 6
      src/Avalonia.Controls.DataGrid/Primitives/DataGridCellsPresenter.cs
  10. 136
      src/Avalonia.Controls.DataGrid/Utils/DoubleUtil.cs
  11. 109
      tests/Avalonia.Base.UnitTests/Utilities/MathUtilitiesTests.cs

1
src/Avalonia.Base/Properties/AssemblyInfo.cs

@ -8,3 +8,4 @@ using Avalonia.Metadata;
[assembly: InternalsVisibleTo("Avalonia.Base.UnitTests")]
[assembly: InternalsVisibleTo("Avalonia.UnitTests")]
[assembly: InternalsVisibleTo("DynamicProxyGenAssembly2")]
[assembly: InternalsVisibleTo("Avalonia.Controls.DataGrid")]

17
src/Avalonia.Base/Utilities/MathUtilities.cs

@ -1,5 +1,4 @@
using System;
using System.Runtime.InteropServices;
namespace Avalonia.Utilities
{
@ -9,7 +8,7 @@ namespace Avalonia.Utilities
public static class MathUtilities
{
// smallest such that 1.0+DoubleEpsilon != 1.0
private const double DoubleEpsilon = 2.2204460492503131e-016;
internal static readonly double DoubleEpsilon = 2.2204460492503131e-016;
private const float FloatEpsilon = 1.192092896e-07F;
@ -188,6 +187,11 @@ namespace Avalonia.Utilities
/// <returns>The clamped value.</returns>
public static double Clamp(double val, double min, double max)
{
if (min > max)
{
ThrowCannotBeGreaterThanException(min, max);
}
if (val < min)
{
return min;
@ -216,7 +220,7 @@ namespace Avalonia.Utilities
double newValue;
// If DPI == 1, don't use DPI-aware rounding.
if (!MathUtilities.AreClose(dpiScale, 1.0))
if (!MathUtilities.IsOne(dpiScale))
{
newValue = Math.Round(value * dpiScale) / dpiScale;
// If rounding produces a value unacceptable to layout (NaN, Infinity or MaxValue), use the original value.
@ -246,7 +250,7 @@ namespace Avalonia.Utilities
{
if (min > max)
{
throw new ArgumentException($"{min} cannot be greater than {max}.");
ThrowCannotBeGreaterThanException(min, max);
}
if (val < min)
@ -262,5 +266,10 @@ namespace Avalonia.Utilities
return val;
}
}
private static void ThrowCannotBeGreaterThanException(double min, double max)
{
throw new ArgumentException($"{min} cannot be greater than {max}.");
}
}
}

38
src/Avalonia.Controls.DataGrid/DataGrid.cs

@ -2681,7 +2681,7 @@ namespace Avalonia.Controls
{
return;
}
Debug.Assert(DoubleUtil.LessThanOrClose(_vScrollBar.Value, _vScrollBar.Maximum));
Debug.Assert(MathUtilities.LessThanOrClose(_vScrollBar.Value, _vScrollBar.Maximum));
_verticalScrollChangesIgnored++;
try
@ -2698,7 +2698,7 @@ namespace Avalonia.Controls
}
else if (scrollEventType == ScrollEventType.SmallDecrement)
{
if (DoubleUtil.GreaterThan(NegVerticalOffset, 0))
if (MathUtilities.GreaterThan(NegVerticalOffset, 0))
{
DisplayData.PendingVerticalScrollHeight -= NegVerticalOffset;
}
@ -2717,7 +2717,7 @@ namespace Avalonia.Controls
DisplayData.PendingVerticalScrollHeight = _vScrollBar.Value - _verticalOffset;
}
if (!DoubleUtil.IsZero(DisplayData.PendingVerticalScrollHeight))
if (!MathUtilities.IsZero(DisplayData.PendingVerticalScrollHeight))
{
// Invalidate so the scroll happens on idle
InvalidateRowsMeasure(invalidateIndividualElements: false);
@ -3346,22 +3346,22 @@ namespace Avalonia.Controls
bool needHorizScrollbarWithoutVertScrollbar = false;
if (allowHorizScrollbar &&
DoubleUtil.GreaterThan(totalVisibleWidth, cellsWidth) &&
DoubleUtil.LessThan(totalVisibleFrozenWidth, cellsWidth) &&
DoubleUtil.LessThanOrClose(horizScrollBarHeight, cellsHeight))
MathUtilities.GreaterThan(totalVisibleWidth, cellsWidth) &&
MathUtilities.LessThan(totalVisibleFrozenWidth, cellsWidth) &&
MathUtilities.LessThanOrClose(horizScrollBarHeight, cellsHeight))
{
double oldDataHeight = cellsHeight;
cellsHeight -= horizScrollBarHeight;
Debug.Assert(cellsHeight >= 0);
needHorizScrollbarWithoutVertScrollbar = needHorizScrollbar = true;
if (allowVertScrollbar && (DoubleUtil.LessThanOrClose(totalVisibleWidth - cellsWidth, vertScrollBarWidth) ||
DoubleUtil.LessThanOrClose(cellsWidth - totalVisibleFrozenWidth, vertScrollBarWidth)))
if (allowVertScrollbar && (MathUtilities.LessThanOrClose(totalVisibleWidth - cellsWidth, vertScrollBarWidth) ||
MathUtilities.LessThanOrClose(cellsWidth - totalVisibleFrozenWidth, vertScrollBarWidth)))
{
// Would we still need a horizontal scrollbar without the vertical one?
UpdateDisplayedRows(DisplayData.FirstScrollingSlot, cellsHeight);
if (DisplayData.NumTotallyDisplayedScrollingElements != VisibleSlotCount)
{
needHorizScrollbar = DoubleUtil.LessThan(totalVisibleFrozenWidth, cellsWidth - vertScrollBarWidth);
needHorizScrollbar = MathUtilities.LessThan(totalVisibleFrozenWidth, cellsWidth - vertScrollBarWidth);
}
}
@ -3374,8 +3374,8 @@ namespace Avalonia.Controls
UpdateDisplayedRows(DisplayData.FirstScrollingSlot, cellsHeight);
if (allowVertScrollbar &&
DoubleUtil.GreaterThan(cellsHeight, 0) &&
DoubleUtil.LessThanOrClose(vertScrollBarWidth, cellsWidth) &&
MathUtilities.GreaterThan(cellsHeight, 0) &&
MathUtilities.LessThanOrClose(vertScrollBarWidth, cellsWidth) &&
DisplayData.NumTotallyDisplayedScrollingElements != VisibleSlotCount)
{
cellsWidth -= vertScrollBarWidth;
@ -3389,9 +3389,9 @@ namespace Avalonia.Controls
if (allowHorizScrollbar &&
needVertScrollbar && !needHorizScrollbar &&
DoubleUtil.GreaterThan(totalVisibleWidth, cellsWidth) &&
DoubleUtil.LessThan(totalVisibleFrozenWidth, cellsWidth) &&
DoubleUtil.LessThanOrClose(horizScrollBarHeight, cellsHeight))
MathUtilities.GreaterThan(totalVisibleWidth, cellsWidth) &&
MathUtilities.LessThan(totalVisibleFrozenWidth, cellsWidth) &&
MathUtilities.LessThanOrClose(horizScrollBarHeight, cellsHeight))
{
cellsWidth += vertScrollBarWidth;
cellsHeight -= horizScrollBarHeight;
@ -3422,7 +3422,7 @@ namespace Avalonia.Controls
if (allowVertScrollbar)
{
if (cellsHeight > 0 &&
DoubleUtil.LessThanOrClose(vertScrollBarWidth, cellsWidth) &&
MathUtilities.LessThanOrClose(vertScrollBarWidth, cellsWidth) &&
DisplayData.NumTotallyDisplayedScrollingElements != VisibleSlotCount)
{
cellsWidth -= vertScrollBarWidth;
@ -3439,9 +3439,9 @@ namespace Avalonia.Controls
if (allowHorizScrollbar)
{
if (cellsWidth > 0 &&
DoubleUtil.LessThanOrClose(horizScrollBarHeight, cellsHeight) &&
DoubleUtil.GreaterThan(totalVisibleWidth, cellsWidth) &&
DoubleUtil.LessThan(totalVisibleFrozenWidth, cellsWidth))
MathUtilities.LessThanOrClose(horizScrollBarHeight, cellsHeight) &&
MathUtilities.GreaterThan(totalVisibleWidth, cellsWidth) &&
MathUtilities.LessThan(totalVisibleFrozenWidth, cellsWidth))
{
cellsHeight -= horizScrollBarHeight;
Debug.Assert(cellsHeight >= 0);
@ -5387,7 +5387,7 @@ namespace Avalonia.Controls
private void SetVerticalOffset(double newVerticalOffset)
{
_verticalOffset = newVerticalOffset;
if (_vScrollBar != null && !DoubleUtil.AreClose(newVerticalOffset, _vScrollBar.Value))
if (_vScrollBar != null && !MathUtilities.AreClose(newVerticalOffset, _vScrollBar.Value))
{
_vScrollBar.Value = _verticalOffset;
}

2
src/Avalonia.Controls.DataGrid/DataGridColumnHeader.cs

@ -301,7 +301,7 @@ namespace Avalonia.Controls
private static bool CanResizeColumn(DataGridColumn column)
{
if (column.OwningGrid != null && column.OwningGrid.ColumnsInternal != null && column.OwningGrid.UsesStarSizing &&
(column.OwningGrid.ColumnsInternal.LastVisibleColumn == column || !DoubleUtil.AreClose(column.OwningGrid.ColumnsInternal.VisibleEdgedColumnsWidth, column.OwningGrid.CellsWidth)))
(column.OwningGrid.ColumnsInternal.LastVisibleColumn == column || !MathUtilities.AreClose(column.OwningGrid.ColumnsInternal.VisibleEdgedColumnsWidth, column.OwningGrid.CellsWidth)))
{
return false;
}

16
src/Avalonia.Controls.DataGrid/DataGridColumns.cs

@ -44,7 +44,7 @@ namespace Avalonia.Controls
/// <returns>The remaining amount of adjustment.</returns>
internal double AdjustColumnWidths(int displayIndex, double amount, bool userInitiated)
{
if (!DoubleUtil.IsZero(amount))
if (!MathUtilities.IsZero(amount))
{
if (amount < 0)
{
@ -777,7 +777,7 @@ namespace Avalonia.Controls
private double AdjustStarColumnWidths(int displayIndex, double adjustment, bool userInitiated)
{
double remainingAdjustment = adjustment;
if (DoubleUtil.IsZero(remainingAdjustment))
if (MathUtilities.IsZero(remainingAdjustment))
{
return remainingAdjustment;
}
@ -843,7 +843,7 @@ namespace Avalonia.Controls
/// <returns>The remaining amount of adjustment.</returns>
private double AdjustStarColumnWidths(int displayIndex, double remainingAdjustment, bool userInitiated, Func<DataGridColumn, double> targetWidth)
{
if (DoubleUtil.IsZero(remainingAdjustment))
if (MathUtilities.IsZero(remainingAdjustment))
{
return remainingAdjustment;
}
@ -1244,7 +1244,7 @@ namespace Avalonia.Controls
Debug.Assert(amount < 0);
Debug.Assert(column.Width.UnitType != DataGridLengthUnitType.Star);
if (DoubleUtil.GreaterThanOrClose(targetWidth, column.Width.DisplayValue))
if (MathUtilities.GreaterThanOrClose(targetWidth, column.Width.DisplayValue))
{
return amount;
}
@ -1271,7 +1271,7 @@ namespace Avalonia.Controls
/// <returns>The remaining amount of adjustment.</returns>
private double DecreaseNonStarColumnWidths(int displayIndex, Func<DataGridColumn, double> targetWidth, double amount, bool reverse, bool affectNewColumns)
{
if (DoubleUtil.GreaterThanOrClose(amount, 0))
if (MathUtilities.GreaterThanOrClose(amount, 0))
{
return amount;
}
@ -1285,7 +1285,7 @@ namespace Avalonia.Controls
(affectNewColumns || column.IsInitialDesiredWidthDetermined)))
{
amount = DecreaseNonStarColumnWidth(column, Math.Max(column.ActualMinWidth, targetWidth(column)), amount);
if (DoubleUtil.IsZero(amount))
if (MathUtilities.IsZero(amount))
{
break;
}
@ -1392,7 +1392,7 @@ namespace Avalonia.Controls
/// <returns>The remaining amount of adjustment.</returns>
private double IncreaseNonStarColumnWidths(int displayIndex, Func<DataGridColumn, double> targetWidth, double amount, bool reverse, bool affectNewColumns)
{
if (DoubleUtil.LessThanOrClose(amount, 0))
if (MathUtilities.LessThanOrClose(amount, 0))
{
return amount;
}
@ -1406,7 +1406,7 @@ namespace Avalonia.Controls
(affectNewColumns || column.IsInitialDesiredWidthDetermined)))
{
amount = IncreaseNonStarColumnWidth(column, Math.Min(column.ActualMaxWidth, targetWidth(column)), amount);
if (DoubleUtil.IsZero(amount))
if (MathUtilities.IsZero(amount))
{
break;
}

2
src/Avalonia.Controls.DataGrid/DataGridLength.cs

@ -529,7 +529,7 @@ namespace Avalonia.Controls
// in this case drop value part and print only "Star"
case DataGridLengthUnitType.Star:
return (
DoubleUtil.AreClose(1.0, dataGridLength.Value.Value)
MathUtilities.AreClose(1.0, dataGridLength.Value.Value)
? _starSuffix
: Convert.ToString(dataGridLength.Value.Value, culture ?? CultureInfo.CurrentCulture) + DataGridLengthConverter._starSuffix);

2
src/Avalonia.Controls.DataGrid/DataGridRow.cs

@ -879,7 +879,7 @@ namespace Avalonia.Controls
&& (double.IsNaN(_detailsContent.Height))
&& (AreDetailsVisible)
&& (!double.IsNaN(_detailsDesiredHeight))
&& !DoubleUtil.AreClose(_detailsContent.Bounds.Inflate(_detailsContent.Margin).Height, _detailsDesiredHeight)
&& !MathUtilities.AreClose(_detailsContent.Bounds.Inflate(_detailsContent.Margin).Height, _detailsDesiredHeight)
&& Slot != -1)
{
_detailsDesiredHeight = _detailsContent.Bounds.Inflate(_detailsContent.Margin).Height;

60
src/Avalonia.Controls.DataGrid/DataGridRows.cs

@ -329,7 +329,7 @@ namespace Avalonia.Controls
internal void OnRowsMeasure()
{
if (!DoubleUtil.IsZero(DisplayData.PendingVerticalScrollHeight))
if (!MathUtilities.IsZero(DisplayData.PendingVerticalScrollHeight))
{
ScrollSlotsByHeight(DisplayData.PendingVerticalScrollHeight);
DisplayData.PendingVerticalScrollHeight = 0;
@ -432,7 +432,7 @@ namespace Avalonia.Controls
}
else if (DisplayData.FirstScrollingSlot == slot && slot != -1)
{
if (!DoubleUtil.IsZero(NegVerticalOffset))
if (!MathUtilities.IsZero(NegVerticalOffset))
{
// First displayed row is partially scrolled of. Let's scroll it so that NegVerticalOffset becomes 0.
DisplayData.PendingVerticalScrollHeight = -NegVerticalOffset;
@ -447,7 +447,7 @@ namespace Avalonia.Controls
{
// Scroll up to the new row so it becomes the first displayed row
firstFullSlot = DisplayData.FirstScrollingSlot - 1;
if (DoubleUtil.GreaterThan(NegVerticalOffset, 0))
if (MathUtilities.GreaterThan(NegVerticalOffset, 0))
{
deltaY = -NegVerticalOffset;
}
@ -470,7 +470,7 @@ namespace Avalonia.Controls
// Figure out how much of the last row is cut off
double rowHeight = GetExactSlotElementHeight(DisplayData.LastScrollingSlot);
double availableHeight = AvailableSlotElementRoom + rowHeight;
if (DoubleUtil.AreClose(rowHeight, availableHeight))
if (MathUtilities.AreClose(rowHeight, availableHeight))
{
if (DisplayData.LastScrollingSlot == slot)
{
@ -499,7 +499,7 @@ namespace Avalonia.Controls
{
ResetDisplayedRows();
}
if (DoubleUtil.GreaterThanOrClose(GetExactSlotElementHeight(slot), CellsHeight))
if (MathUtilities.GreaterThanOrClose(GetExactSlotElementHeight(slot), CellsHeight))
{
// The entire row won't fit in the DataGrid so we start showing it from the top
NegVerticalOffset = 0;
@ -519,7 +519,7 @@ namespace Avalonia.Controls
}
//
Debug.Assert(DoubleUtil.LessThanOrClose(NegVerticalOffset, _verticalOffset));
Debug.Assert(MathUtilities.LessThanOrClose(NegVerticalOffset, _verticalOffset));
SetVerticalOffset(_verticalOffset);
@ -1660,7 +1660,7 @@ namespace Avalonia.Controls
private void ScrollSlotsByHeight(double height)
{
Debug.Assert(DisplayData.FirstScrollingSlot >= 0);
Debug.Assert(!DoubleUtil.IsZero(height));
Debug.Assert(!MathUtilities.IsZero(height));
_scrollingByHeight = true;
try
@ -1672,7 +1672,7 @@ namespace Avalonia.Controls
{
// Scrolling Down
int lastVisibleSlot = GetPreviousVisibleSlot(SlotCount);
if (_vScrollBar != null && DoubleUtil.AreClose(_vScrollBar.Maximum, newVerticalOffset))
if (_vScrollBar != null && MathUtilities.AreClose(_vScrollBar.Maximum, newVerticalOffset))
{
// We've scrolled to the bottom of the ScrollBar, automatically place the user at the very bottom
// of the DataGrid. If this produces very odd behavior, evaluate the coping strategy used by
@ -1684,7 +1684,7 @@ namespace Avalonia.Controls
else
{
deltaY = GetSlotElementHeight(newFirstScrollingSlot) - NegVerticalOffset;
if (DoubleUtil.LessThan(height, deltaY))
if (MathUtilities.LessThan(height, deltaY))
{
// We've merely covered up more of the same row we're on
NegVerticalOffset += height;
@ -1707,7 +1707,7 @@ namespace Avalonia.Controls
}
else
{
while (DoubleUtil.LessThanOrClose(deltaY, height))
while (MathUtilities.LessThanOrClose(deltaY, height))
{
if (newFirstScrollingSlot < lastVisibleSlot)
{
@ -1727,7 +1727,7 @@ namespace Avalonia.Controls
double rowHeight = GetExactSlotElementHeight(newFirstScrollingSlot);
double remainingHeight = height - deltaY;
if (DoubleUtil.LessThanOrClose(rowHeight, remainingHeight))
if (MathUtilities.LessThanOrClose(rowHeight, remainingHeight))
{
deltaY += rowHeight;
}
@ -1744,7 +1744,7 @@ namespace Avalonia.Controls
else
{
// Scrolling Up
if (DoubleUtil.GreaterThanOrClose(height + NegVerticalOffset, 0))
if (MathUtilities.GreaterThanOrClose(height + NegVerticalOffset, 0))
{
// We've merely exposing more of the row we're on
NegVerticalOffset += height;
@ -1778,7 +1778,7 @@ namespace Avalonia.Controls
else
{
int lastScrollingSlot = DisplayData.LastScrollingSlot;
while (DoubleUtil.GreaterThan(deltaY, height))
while (MathUtilities.GreaterThan(deltaY, height))
{
if (newFirstScrollingSlot > 0)
{
@ -1797,7 +1797,7 @@ namespace Avalonia.Controls
}
double rowHeight = GetExactSlotElementHeight(newFirstScrollingSlot);
double remainingHeight = height - deltaY;
if (DoubleUtil.LessThanOrClose(rowHeight + remainingHeight, 0))
if (MathUtilities.LessThanOrClose(rowHeight + remainingHeight, 0))
{
deltaY -= rowHeight;
}
@ -1809,7 +1809,7 @@ namespace Avalonia.Controls
}
}
}
if (DoubleUtil.GreaterThanOrClose(0, newVerticalOffset) && newFirstScrollingSlot != 0)
if (MathUtilities.GreaterThanOrClose(0, newVerticalOffset) && newFirstScrollingSlot != 0)
{
// We've scrolled to the top of the ScrollBar, automatically place the user at the very top
// of the DataGrid. If this produces very odd behavior, evaluate the RowHeight estimate.
@ -1822,7 +1822,7 @@ namespace Avalonia.Controls
}
double firstRowHeight = GetExactSlotElementHeight(newFirstScrollingSlot);
if (DoubleUtil.LessThan(firstRowHeight, NegVerticalOffset))
if (MathUtilities.LessThan(firstRowHeight, NegVerticalOffset))
{
// We've scrolled off more of the first row than what's possible. This can happen
// if the first row got shorter (Ex: Collpasing RowDetails) or if the user has a recycling
@ -1838,11 +1838,11 @@ namespace Avalonia.Controls
UpdateDisplayedRows(newFirstScrollingSlot, CellsHeight);
double firstElementHeight = GetExactSlotElementHeight(DisplayData.FirstScrollingSlot);
if (DoubleUtil.GreaterThan(NegVerticalOffset, firstElementHeight))
if (MathUtilities.GreaterThan(NegVerticalOffset, firstElementHeight))
{
int firstElementSlot = DisplayData.FirstScrollingSlot;
// We filled in some rows at the top and now we have a NegVerticalOffset that's greater than the first element
while (newFirstScrollingSlot > 0 && DoubleUtil.GreaterThan(NegVerticalOffset, firstElementHeight))
while (newFirstScrollingSlot > 0 && MathUtilities.GreaterThan(NegVerticalOffset, firstElementHeight))
{
int previousSlot = GetPreviousVisibleSlot(firstElementSlot);
if (previousSlot == -1)
@ -1872,7 +1872,7 @@ namespace Avalonia.Controls
{
_verticalOffset = NegVerticalOffset;
}
else if (DoubleUtil.GreaterThan(NegVerticalOffset, newVerticalOffset))
else if (MathUtilities.GreaterThan(NegVerticalOffset, newVerticalOffset))
{
// The scrolled-in row was larger than anticipated. Adjust the DataGrid so the ScrollBar thumb
// can stay in the same place
@ -1890,8 +1890,8 @@ namespace Avalonia.Controls
DisplayData.FullyRecycleElements();
Debug.Assert(DoubleUtil.GreaterThanOrClose(NegVerticalOffset, 0));
Debug.Assert(DoubleUtil.GreaterThanOrClose(_verticalOffset, NegVerticalOffset));
Debug.Assert(MathUtilities.GreaterThanOrClose(NegVerticalOffset, 0));
Debug.Assert(MathUtilities.GreaterThanOrClose(_verticalOffset, NegVerticalOffset));
}
finally
{
@ -2032,7 +2032,7 @@ namespace Avalonia.Controls
double deltaY = -NegVerticalOffset;
int visibleScrollingRows = 0;
if (DoubleUtil.LessThanOrClose(displayHeight, 0) || SlotCount == 0 || ColumnsItemsInternal.Count == 0)
if (MathUtilities.LessThanOrClose(displayHeight, 0) || SlotCount == 0 || ColumnsItemsInternal.Count == 0)
{
return;
}
@ -2044,7 +2044,7 @@ namespace Avalonia.Controls
}
int slot = firstDisplayedScrollingSlot;
while (slot < SlotCount && !DoubleUtil.GreaterThanOrClose(deltaY, displayHeight))
while (slot < SlotCount && !MathUtilities.GreaterThanOrClose(deltaY, displayHeight))
{
deltaY += GetExactSlotElementHeight(slot);
visibleScrollingRows++;
@ -2052,7 +2052,7 @@ namespace Avalonia.Controls
slot = GetNextVisibleSlot(slot);
}
while (DoubleUtil.LessThan(deltaY, displayHeight) && slot >= 0)
while (MathUtilities.LessThan(deltaY, displayHeight) && slot >= 0)
{
slot = GetPreviousVisibleSlot(firstDisplayedScrollingSlot);
if (slot >= 0)
@ -2063,14 +2063,14 @@ namespace Avalonia.Controls
}
}
// If we're up to the first row, and we still have room left, uncover as much of the first row as we can
if (firstDisplayedScrollingSlot == 0 && DoubleUtil.LessThan(deltaY, displayHeight))
if (firstDisplayedScrollingSlot == 0 && MathUtilities.LessThan(deltaY, displayHeight))
{
double newNegVerticalOffset = Math.Max(0, NegVerticalOffset - displayHeight + deltaY);
deltaY += NegVerticalOffset - newNegVerticalOffset;
NegVerticalOffset = newNegVerticalOffset;
}
if (DoubleUtil.GreaterThan(deltaY, displayHeight) || (DoubleUtil.AreClose(deltaY, displayHeight) && DoubleUtil.GreaterThan(NegVerticalOffset, 0)))
if (MathUtilities.GreaterThan(deltaY, displayHeight) || (MathUtilities.AreClose(deltaY, displayHeight) && MathUtilities.GreaterThan(NegVerticalOffset, 0)))
{
DisplayData.NumTotallyDisplayedScrollingElements = visibleScrollingRows - 1;
}
@ -2108,7 +2108,7 @@ namespace Avalonia.Controls
double deltaY = 0;
int visibleScrollingRows = 0;
if (DoubleUtil.LessThanOrClose(displayHeight, 0) || SlotCount == 0 || ColumnsItemsInternal.Count == 0)
if (MathUtilities.LessThanOrClose(displayHeight, 0) || SlotCount == 0 || ColumnsItemsInternal.Count == 0)
{
ResetDisplayedRows();
return;
@ -2120,7 +2120,7 @@ namespace Avalonia.Controls
}
int slot = lastDisplayedScrollingRow;
while (DoubleUtil.LessThan(deltaY, displayHeight) && slot >= 0)
while (MathUtilities.LessThan(deltaY, displayHeight) && slot >= 0)
{
deltaY += GetExactSlotElementHeight(slot);
visibleScrollingRows++;
@ -2542,7 +2542,7 @@ namespace Avalonia.Controls
double heightChange = UpdateRowGroupVisibility(rowGroupInfo, isVisible, isDisplayed: false);
// Use epsilon instead of 0 here so that in the off chance that our estimates put the vertical offset negative
// the user can still scroll to the top since the offset is non-zero
SetVerticalOffset(Math.Max(DoubleUtil.DBL_EPSILON, _verticalOffset + heightChange));
SetVerticalOffset(Math.Max(MathUtilities.DoubleEpsilon, _verticalOffset + heightChange));
}
else
{
@ -3024,4 +3024,4 @@ namespace Avalonia.Controls
}
#endif
}
}
}

6
src/Avalonia.Controls.DataGrid/Primitives/DataGridCellsPresenter.cs

@ -310,9 +310,9 @@ namespace Avalonia.Controls.Primitives
double leftEdge = column.IsFrozen ? frozenLeftEdge : scrollingLeftEdge;
double rightEdge = leftEdge + column.ActualWidth;
return
DoubleUtil.GreaterThan(rightEdge, 0) &&
DoubleUtil.LessThanOrClose(leftEdge, OwningGrid.CellsWidth) &&
DoubleUtil.GreaterThan(rightEdge, frozenLeftEdge); // scrolling column covered up by frozen column(s)
MathUtilities.GreaterThan(rightEdge, 0) &&
MathUtilities.LessThanOrClose(leftEdge, OwningGrid.CellsWidth) &&
MathUtilities.GreaterThan(rightEdge, frozenLeftEdge); // scrolling column covered up by frozen column(s)
}
}
}

136
src/Avalonia.Controls.DataGrid/Utils/DoubleUtil.cs

@ -1,136 +0,0 @@
// (c) Copyright Microsoft Corporation.
// This source is subject to the Microsoft Public License (Ms-PL).
// Please see http://go.microsoft.com/fwlink/?LinkID=131993 for details.
// All other rights reserved.
using System;
namespace Avalonia.Controls.Utils
{
internal static class DoubleUtil
{
internal const double DBL_EPSILON = 1e-6;
/// <summary>
/// AreClose - Returns whether or not two doubles are "close". That is, whether or
/// not they are within epsilon of each other. Note that this epsilon is proportional
/// to the numbers themselves to that AreClose survives scalar multiplication.
/// There are plenty of ways for this to return false even for numbers which
/// are theoretically identical, so no code calling this should fail to work if this
/// returns false. This is important enough to repeat:
/// NB: NO CODE CALLING THIS FUNCTION SHOULD DEPEND ON ACCURATE RESULTS - this should be
/// used for optimizations *only*.
/// </summary>
/// <returns>
/// bool - the result of the AreClose comparison.
/// </returns>
/// <param name="value1"> The first double to compare. </param>
/// <param name="value2"> The second double to compare. </param>
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)) < DBL_EPSILON
double eps = (Math.Abs(value1) + Math.Abs(value2) + 10.0) * DBL_EPSILON;
double delta = value1 - value2;
return (-eps < delta) && (eps > delta);
}
/// <summary>
/// GreaterThan - Returns whether or not the first double is greater than the second double.
/// That is, whether or not the first is strictly greater than *and* not within epsilon of
/// the other number. Note that this epsilon is proportional to the numbers themselves
/// to that AreClose survives scalar multiplication. Note,
/// There are plenty of ways for this to return false even for numbers which
/// are theoretically identical, so no code calling this should fail to work if this
/// returns false. This is important enough to repeat:
/// NB: NO CODE CALLING THIS FUNCTION SHOULD DEPEND ON ACCURATE RESULTS - this should be
/// used for optimizations *only*.
/// </summary>
/// <returns>
/// bool - the result of the GreaterThan comparison.
/// </returns>
/// <param name="value1"> The first double to compare. </param>
/// <param name="value2"> The second double to compare. </param>
public static bool GreaterThan(double value1, double value2)
{
return (value1 > value2) && !AreClose(value1, value2);
}
/// <summary>
/// GreaterThanOrClose - Returns whether or not the first double is greater than or close to
/// the second double. That is, whether or not the first is strictly greater than or within
/// epsilon of the other number. Note that this epsilon is proportional to the numbers
/// themselves to that AreClose survives scalar multiplication. Note,
/// There are plenty of ways for this to return false even for numbers which
/// are theoretically identical, so no code calling this should fail to work if this
/// returns false. This is important enough to repeat:
/// NB: NO CODE CALLING THIS FUNCTION SHOULD DEPEND ON ACCURATE RESULTS - this should be
/// used for optimizations *only*.
/// </summary>
/// <returns>
/// bool - the result of the GreaterThanOrClose comparison.
/// </returns>
/// <param name="value1"> The first double to compare. </param>
/// <param name="value2"> The second double to compare. </param>
public static bool GreaterThanOrClose(double value1, double value2)
{
return (value1 > value2) || AreClose(value1, value2);
}
/// <summary>
/// IsZero - Returns whether or not the double is "close" to 0. Same as AreClose(double, 0),
/// but this is faster.
/// </summary>
/// <returns>
/// bool - the result of the IsZero comparison.
/// </returns>
/// <param name="value"> The double to compare to 0. </param>
public static bool IsZero(double value)
{
return Math.Abs(value) < 10.0 * DBL_EPSILON;
}
/// <summary>
/// LessThan - Returns whether or not the first double is less than the second double.
/// That is, whether or not the first is strictly less than *and* not within epsilon of
/// the other number. Note that this epsilon is proportional to the numbers themselves
/// to that AreClose survives scalar multiplication. Note,
/// There are plenty of ways for this to return false even for numbers which
/// are theoretically identical, so no code calling this should fail to work if this
/// returns false. This is important enough to repeat:
/// NB: NO CODE CALLING THIS FUNCTION SHOULD DEPEND ON ACCURATE RESULTS - this should be
/// used for optimizations *only*.
/// </summary>
/// <returns>
/// bool - the result of the LessThan comparison.
/// </returns>
/// <param name="value1"> The first double to compare. </param>
/// <param name="value2"> The second double to compare. </param>
public static bool LessThan(double value1, double value2)
{
return (value1 < value2) && !AreClose(value1, value2);
}
/// <summary>
/// LessThanOrClose - Returns whether or not the first double is less than or close to
/// the second double. That is, whether or not the first is strictly less than or within
/// epsilon of the other number. Note that this epsilon is proportional to the numbers
/// themselves to that AreClose survives scalar multiplication. Note,
/// There are plenty of ways for this to return false even for numbers which
/// are theoretically identical, so no code calling this should fail to work if this
/// returns false. This is important enough to repeat:
/// NB: NO CODE CALLING THIS FUNCTION SHOULD DEPEND ON ACCURATE RESULTS - this should be
/// used for optimizations *only*.
/// </summary>
/// <returns>
/// bool - the result of the LessThanOrClose comparison.
/// </returns>
/// <param name="value1"> The first double to compare. </param>
/// <param name="value2"> The second double to compare. </param>
public static bool LessThanOrClose(double value1, double value2)
{
return (value1 < value2) || AreClose(value1, value2);
}
}
}

109
tests/Avalonia.Base.UnitTests/Utilities/MathUtilitiesTests.cs

@ -92,14 +92,14 @@ namespace Avalonia.Base.UnitTests.Utilities
}
[Fact]
public void Clamp_Input_NaN_Return_NaN()
public void Float_Clamp_Input_NaN_Return_NaN()
{
var clamp = MathUtilities.Clamp(double.NaN, 0.0, 1.0);
Assert.True(double.IsNaN(clamp));
}
[Fact]
public void Clamp_Input_NegativeInfinity_Return_Min()
public void Float_Clamp_Input_NegativeInfinity_Return_Min()
{
const double min = 0.0;
const double max = 1.0;
@ -108,12 +108,115 @@ namespace Avalonia.Base.UnitTests.Utilities
}
[Fact]
public void Clamp_Input_PositiveInfinity_Return_Max()
public void Float_Clamp_Input_PositiveInfinity_Return_Max()
{
const double min = 0.0;
const double max = 1.0;
var actual = MathUtilities.Clamp(double.PositiveInfinity, min, max);
Assert.Equal(max, actual);
}
[Fact]
public void Double_Float_Zero_Less_Than_One()
{
var actual = MathUtilities.LessThan(0d, 1d);
Assert.True(actual);
}
[Fact]
public void Single_Float_Zero_Less_Than_One()
{
var actual = MathUtilities.LessThan(0f, 1f);
Assert.True(actual);
}
[Fact]
public void Double_Float_One_Not_Less_Than_Zero()
{
var actual = MathUtilities.LessThan(1d, 0d);
Assert.False(actual);
}
[Fact]
public void Single_Float_One_Not_Less_Than_Zero()
{
var actual = MathUtilities.LessThan(1f, 0f);
Assert.False(actual);
}
[Fact]
public void Double_Float_Zero_Not_Greater_Than_One()
{
var actual = MathUtilities.GreaterThan(0d, 1d);
Assert.False(actual);
}
[Fact]
public void Single_Float_Zero_Not_Greater_Than_One()
{
var actual = MathUtilities.GreaterThan(0f, 1f);
Assert.False(actual);
}
[Fact]
public void Double_Float_One_Greater_Than_Zero()
{
var actual = MathUtilities.GreaterThan(1d, 0d);
Assert.True(actual);
}
[Fact]
public void Single_Float_One_Greater_Than_Zero()
{
var actual = MathUtilities.GreaterThan(1f, 0f);
Assert.True(actual);
}
[Fact]
public void Double_Float_One_Less_Than_Or_Close_One()
{
var actual = MathUtilities.LessThanOrClose(1d, 1d);
Assert.True(actual);
}
[Fact]
public void Single_Float_One_Less_Than_Or_Close_One()
{
var actual = MathUtilities.LessThanOrClose(1f, 1f);
Assert.True(actual);
}
[Fact]
public void Double_Float_One_Greater_Than_Or_Close_One()
{
var actual = MathUtilities.GreaterThanOrClose(1d, 1d);
Assert.True(actual);
}
[Fact]
public void Single_Float_One_Greater_Than_Or_Close_One()
{
var actual = MathUtilities.GreaterThanOrClose(1f, 1f);
Assert.True(actual);
}
[Fact]
public void Round_Layout_Value_Without_DPI_Aware()
{
const double value = 42.5;
var expectedValue = Math.Round(value);
var actualValue = MathUtilities.RoundLayoutValue(value, 1.0);
Assert.Equal(expectedValue, actualValue);
}
[Fact]
public void Round_Layout_Value_With_DPI_Aware()
{
const double dpiScale = 1.25;
const double value = 42.5;
var expectedValue = Math.Round(value * dpiScale) / dpiScale;
var actualValue = MathUtilities.RoundLayoutValue(value, dpiScale);
Assert.Equal(expectedValue, actualValue);
}
}
}

Loading…
Cancel
Save