Browse Source

Remove duplicate code for MathUtilities

pull/4104/head
Rustam Sayfutdinov 6 years ago
parent
commit
1ce6df7186
  1. 1
      src/Avalonia.Base/Properties/AssemblyInfo.cs
  2. 2
      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. 48
      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.Base.UnitTests")]
[assembly: InternalsVisibleTo("Avalonia.UnitTests")] [assembly: InternalsVisibleTo("Avalonia.UnitTests")]
[assembly: InternalsVisibleTo("DynamicProxyGenAssembly2")] [assembly: InternalsVisibleTo("DynamicProxyGenAssembly2")]
[assembly: InternalsVisibleTo("Avalonia.Controls.DataGrid")]

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

@ -9,7 +9,7 @@ namespace Avalonia.Utilities
public static class MathUtilities public static class MathUtilities
{ {
// smallest such that 1.0+DoubleEpsilon != 1.0 // smallest such that 1.0+DoubleEpsilon != 1.0
private const double DoubleEpsilon = 2.2204460492503131e-016; internal static readonly double DoubleEpsilon = 2.2204460492503131e-016;
/// <summary> /// <summary>
/// AreClose - Returns whether or not two doubles are "close". That is, whether or /// AreClose - Returns whether or not two doubles are "close". That is, whether or

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

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

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

@ -301,7 +301,7 @@ namespace Avalonia.Controls
private static bool CanResizeColumn(DataGridColumn column) private static bool CanResizeColumn(DataGridColumn column)
{ {
if (column.OwningGrid != null && column.OwningGrid.ColumnsInternal != null && column.OwningGrid.UsesStarSizing && 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; return false;
} }

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

@ -44,7 +44,7 @@ namespace Avalonia.Controls
/// <returns>The remaining amount of adjustment.</returns> /// <returns>The remaining amount of adjustment.</returns>
internal double AdjustColumnWidths(int displayIndex, double amount, bool userInitiated) internal double AdjustColumnWidths(int displayIndex, double amount, bool userInitiated)
{ {
if (!DoubleUtil.IsZero(amount)) if (!MathUtilities.IsZero(amount))
{ {
if (amount < 0) if (amount < 0)
{ {
@ -777,7 +777,7 @@ namespace Avalonia.Controls
private double AdjustStarColumnWidths(int displayIndex, double adjustment, bool userInitiated) private double AdjustStarColumnWidths(int displayIndex, double adjustment, bool userInitiated)
{ {
double remainingAdjustment = adjustment; double remainingAdjustment = adjustment;
if (DoubleUtil.IsZero(remainingAdjustment)) if (MathUtilities.IsZero(remainingAdjustment))
{ {
return remainingAdjustment; return remainingAdjustment;
} }
@ -843,7 +843,7 @@ namespace Avalonia.Controls
/// <returns>The remaining amount of adjustment.</returns> /// <returns>The remaining amount of adjustment.</returns>
private double AdjustStarColumnWidths(int displayIndex, double remainingAdjustment, bool userInitiated, Func<DataGridColumn, double> targetWidth) private double AdjustStarColumnWidths(int displayIndex, double remainingAdjustment, bool userInitiated, Func<DataGridColumn, double> targetWidth)
{ {
if (DoubleUtil.IsZero(remainingAdjustment)) if (MathUtilities.IsZero(remainingAdjustment))
{ {
return remainingAdjustment; return remainingAdjustment;
} }
@ -1244,7 +1244,7 @@ namespace Avalonia.Controls
Debug.Assert(amount < 0); Debug.Assert(amount < 0);
Debug.Assert(column.Width.UnitType != DataGridLengthUnitType.Star); Debug.Assert(column.Width.UnitType != DataGridLengthUnitType.Star);
if (DoubleUtil.GreaterThanOrClose(targetWidth, column.Width.DisplayValue)) if (MathUtilities.GreaterThanOrClose(targetWidth, column.Width.DisplayValue))
{ {
return amount; return amount;
} }
@ -1271,7 +1271,7 @@ namespace Avalonia.Controls
/// <returns>The remaining amount of adjustment.</returns> /// <returns>The remaining amount of adjustment.</returns>
private double DecreaseNonStarColumnWidths(int displayIndex, Func<DataGridColumn, double> targetWidth, double amount, bool reverse, bool affectNewColumns) 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; return amount;
} }
@ -1285,7 +1285,7 @@ namespace Avalonia.Controls
(affectNewColumns || column.IsInitialDesiredWidthDetermined))) (affectNewColumns || column.IsInitialDesiredWidthDetermined)))
{ {
amount = DecreaseNonStarColumnWidth(column, Math.Max(column.ActualMinWidth, targetWidth(column)), amount); amount = DecreaseNonStarColumnWidth(column, Math.Max(column.ActualMinWidth, targetWidth(column)), amount);
if (DoubleUtil.IsZero(amount)) if (MathUtilities.IsZero(amount))
{ {
break; break;
} }
@ -1392,7 +1392,7 @@ namespace Avalonia.Controls
/// <returns>The remaining amount of adjustment.</returns> /// <returns>The remaining amount of adjustment.</returns>
private double IncreaseNonStarColumnWidths(int displayIndex, Func<DataGridColumn, double> targetWidth, double amount, bool reverse, bool affectNewColumns) 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; return amount;
} }
@ -1406,7 +1406,7 @@ namespace Avalonia.Controls
(affectNewColumns || column.IsInitialDesiredWidthDetermined))) (affectNewColumns || column.IsInitialDesiredWidthDetermined)))
{ {
amount = IncreaseNonStarColumnWidth(column, Math.Min(column.ActualMaxWidth, targetWidth(column)), amount); amount = IncreaseNonStarColumnWidth(column, Math.Min(column.ActualMaxWidth, targetWidth(column)), amount);
if (DoubleUtil.IsZero(amount)) if (MathUtilities.IsZero(amount))
{ {
break; 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" // in this case drop value part and print only "Star"
case DataGridLengthUnitType.Star: case DataGridLengthUnitType.Star:
return ( return (
DoubleUtil.AreClose(1.0, dataGridLength.Value.Value) MathUtilities.AreClose(1.0, dataGridLength.Value.Value)
? _starSuffix ? _starSuffix
: Convert.ToString(dataGridLength.Value.Value, culture ?? CultureInfo.CurrentCulture) + DataGridLengthConverter._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)) && (double.IsNaN(_detailsContent.Height))
&& (AreDetailsVisible) && (AreDetailsVisible)
&& (!double.IsNaN(_detailsDesiredHeight)) && (!double.IsNaN(_detailsDesiredHeight))
&& !DoubleUtil.AreClose(_detailsContent.Bounds.Inflate(_detailsContent.Margin).Height, _detailsDesiredHeight) && !MathUtilities.AreClose(_detailsContent.Bounds.Inflate(_detailsContent.Margin).Height, _detailsDesiredHeight)
&& Slot != -1) && Slot != -1)
{ {
_detailsDesiredHeight = _detailsContent.Bounds.Inflate(_detailsContent.Margin).Height; _detailsDesiredHeight = _detailsContent.Bounds.Inflate(_detailsContent.Margin).Height;

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

@ -329,7 +329,7 @@ namespace Avalonia.Controls
internal void OnRowsMeasure() internal void OnRowsMeasure()
{ {
if (!DoubleUtil.IsZero(DisplayData.PendingVerticalScrollHeight)) if (!MathUtilities.IsZero(DisplayData.PendingVerticalScrollHeight))
{ {
ScrollSlotsByHeight(DisplayData.PendingVerticalScrollHeight); ScrollSlotsByHeight(DisplayData.PendingVerticalScrollHeight);
DisplayData.PendingVerticalScrollHeight = 0; DisplayData.PendingVerticalScrollHeight = 0;
@ -432,7 +432,7 @@ namespace Avalonia.Controls
} }
else if (DisplayData.FirstScrollingSlot == slot && slot != -1) 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. // First displayed row is partially scrolled of. Let's scroll it so that NegVerticalOffset becomes 0.
DisplayData.PendingVerticalScrollHeight = -NegVerticalOffset; DisplayData.PendingVerticalScrollHeight = -NegVerticalOffset;
@ -447,7 +447,7 @@ namespace Avalonia.Controls
{ {
// Scroll up to the new row so it becomes the first displayed row // Scroll up to the new row so it becomes the first displayed row
firstFullSlot = DisplayData.FirstScrollingSlot - 1; firstFullSlot = DisplayData.FirstScrollingSlot - 1;
if (DoubleUtil.GreaterThan(NegVerticalOffset, 0)) if (MathUtilities.GreaterThan(NegVerticalOffset, 0))
{ {
deltaY = -NegVerticalOffset; deltaY = -NegVerticalOffset;
} }
@ -470,7 +470,7 @@ namespace Avalonia.Controls
// Figure out how much of the last row is cut off // Figure out how much of the last row is cut off
double rowHeight = GetExactSlotElementHeight(DisplayData.LastScrollingSlot); double rowHeight = GetExactSlotElementHeight(DisplayData.LastScrollingSlot);
double availableHeight = AvailableSlotElementRoom + rowHeight; double availableHeight = AvailableSlotElementRoom + rowHeight;
if (DoubleUtil.AreClose(rowHeight, availableHeight)) if (MathUtilities.AreClose(rowHeight, availableHeight))
{ {
if (DisplayData.LastScrollingSlot == slot) if (DisplayData.LastScrollingSlot == slot)
{ {
@ -499,7 +499,7 @@ namespace Avalonia.Controls
{ {
ResetDisplayedRows(); 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 // The entire row won't fit in the DataGrid so we start showing it from the top
NegVerticalOffset = 0; NegVerticalOffset = 0;
@ -519,7 +519,7 @@ namespace Avalonia.Controls
} }
// //
Debug.Assert(DoubleUtil.LessThanOrClose(NegVerticalOffset, _verticalOffset)); Debug.Assert(MathUtilities.LessThanOrClose(NegVerticalOffset, _verticalOffset));
SetVerticalOffset(_verticalOffset); SetVerticalOffset(_verticalOffset);
@ -1660,7 +1660,7 @@ namespace Avalonia.Controls
private void ScrollSlotsByHeight(double height) private void ScrollSlotsByHeight(double height)
{ {
Debug.Assert(DisplayData.FirstScrollingSlot >= 0); Debug.Assert(DisplayData.FirstScrollingSlot >= 0);
Debug.Assert(!DoubleUtil.IsZero(height)); Debug.Assert(!MathUtilities.IsZero(height));
_scrollingByHeight = true; _scrollingByHeight = true;
try try
@ -1672,7 +1672,7 @@ namespace Avalonia.Controls
{ {
// Scrolling Down // Scrolling Down
int lastVisibleSlot = GetPreviousVisibleSlot(SlotCount); 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 // 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 // of the DataGrid. If this produces very odd behavior, evaluate the coping strategy used by
@ -1684,7 +1684,7 @@ namespace Avalonia.Controls
else else
{ {
deltaY = GetSlotElementHeight(newFirstScrollingSlot) - NegVerticalOffset; 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 // We've merely covered up more of the same row we're on
NegVerticalOffset += height; NegVerticalOffset += height;
@ -1707,7 +1707,7 @@ namespace Avalonia.Controls
} }
else else
{ {
while (DoubleUtil.LessThanOrClose(deltaY, height)) while (MathUtilities.LessThanOrClose(deltaY, height))
{ {
if (newFirstScrollingSlot < lastVisibleSlot) if (newFirstScrollingSlot < lastVisibleSlot)
{ {
@ -1727,7 +1727,7 @@ namespace Avalonia.Controls
double rowHeight = GetExactSlotElementHeight(newFirstScrollingSlot); double rowHeight = GetExactSlotElementHeight(newFirstScrollingSlot);
double remainingHeight = height - deltaY; double remainingHeight = height - deltaY;
if (DoubleUtil.LessThanOrClose(rowHeight, remainingHeight)) if (MathUtilities.LessThanOrClose(rowHeight, remainingHeight))
{ {
deltaY += rowHeight; deltaY += rowHeight;
} }
@ -1744,7 +1744,7 @@ namespace Avalonia.Controls
else else
{ {
// Scrolling Up // 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 // We've merely exposing more of the row we're on
NegVerticalOffset += height; NegVerticalOffset += height;
@ -1778,7 +1778,7 @@ namespace Avalonia.Controls
else else
{ {
int lastScrollingSlot = DisplayData.LastScrollingSlot; int lastScrollingSlot = DisplayData.LastScrollingSlot;
while (DoubleUtil.GreaterThan(deltaY, height)) while (MathUtilities.GreaterThan(deltaY, height))
{ {
if (newFirstScrollingSlot > 0) if (newFirstScrollingSlot > 0)
{ {
@ -1797,7 +1797,7 @@ namespace Avalonia.Controls
} }
double rowHeight = GetExactSlotElementHeight(newFirstScrollingSlot); double rowHeight = GetExactSlotElementHeight(newFirstScrollingSlot);
double remainingHeight = height - deltaY; double remainingHeight = height - deltaY;
if (DoubleUtil.LessThanOrClose(rowHeight + remainingHeight, 0)) if (MathUtilities.LessThanOrClose(rowHeight + remainingHeight, 0))
{ {
deltaY -= rowHeight; 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 // 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. // of the DataGrid. If this produces very odd behavior, evaluate the RowHeight estimate.
@ -1822,7 +1822,7 @@ namespace Avalonia.Controls
} }
double firstRowHeight = GetExactSlotElementHeight(newFirstScrollingSlot); 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 // 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 // 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); UpdateDisplayedRows(newFirstScrollingSlot, CellsHeight);
double firstElementHeight = GetExactSlotElementHeight(DisplayData.FirstScrollingSlot); double firstElementHeight = GetExactSlotElementHeight(DisplayData.FirstScrollingSlot);
if (DoubleUtil.GreaterThan(NegVerticalOffset, firstElementHeight)) if (MathUtilities.GreaterThan(NegVerticalOffset, firstElementHeight))
{ {
int firstElementSlot = DisplayData.FirstScrollingSlot; 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 // 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); int previousSlot = GetPreviousVisibleSlot(firstElementSlot);
if (previousSlot == -1) if (previousSlot == -1)
@ -1872,7 +1872,7 @@ namespace Avalonia.Controls
{ {
_verticalOffset = NegVerticalOffset; _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 // The scrolled-in row was larger than anticipated. Adjust the DataGrid so the ScrollBar thumb
// can stay in the same place // can stay in the same place
@ -1890,8 +1890,8 @@ namespace Avalonia.Controls
DisplayData.FullyRecycleElements(); DisplayData.FullyRecycleElements();
Debug.Assert(DoubleUtil.GreaterThanOrClose(NegVerticalOffset, 0)); Debug.Assert(MathUtilities.GreaterThanOrClose(NegVerticalOffset, 0));
Debug.Assert(DoubleUtil.GreaterThanOrClose(_verticalOffset, NegVerticalOffset)); Debug.Assert(MathUtilities.GreaterThanOrClose(_verticalOffset, NegVerticalOffset));
} }
finally finally
{ {
@ -2032,7 +2032,7 @@ namespace Avalonia.Controls
double deltaY = -NegVerticalOffset; double deltaY = -NegVerticalOffset;
int visibleScrollingRows = 0; int visibleScrollingRows = 0;
if (DoubleUtil.LessThanOrClose(displayHeight, 0) || SlotCount == 0 || ColumnsItemsInternal.Count == 0) if (MathUtilities.LessThanOrClose(displayHeight, 0) || SlotCount == 0 || ColumnsItemsInternal.Count == 0)
{ {
return; return;
} }
@ -2044,7 +2044,7 @@ namespace Avalonia.Controls
} }
int slot = firstDisplayedScrollingSlot; int slot = firstDisplayedScrollingSlot;
while (slot < SlotCount && !DoubleUtil.GreaterThanOrClose(deltaY, displayHeight)) while (slot < SlotCount && !MathUtilities.GreaterThanOrClose(deltaY, displayHeight))
{ {
deltaY += GetExactSlotElementHeight(slot); deltaY += GetExactSlotElementHeight(slot);
visibleScrollingRows++; visibleScrollingRows++;
@ -2052,7 +2052,7 @@ namespace Avalonia.Controls
slot = GetNextVisibleSlot(slot); slot = GetNextVisibleSlot(slot);
} }
while (DoubleUtil.LessThan(deltaY, displayHeight) && slot >= 0) while (MathUtilities.LessThan(deltaY, displayHeight) && slot >= 0)
{ {
slot = GetPreviousVisibleSlot(firstDisplayedScrollingSlot); slot = GetPreviousVisibleSlot(firstDisplayedScrollingSlot);
if (slot >= 0) 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 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); double newNegVerticalOffset = Math.Max(0, NegVerticalOffset - displayHeight + deltaY);
deltaY += NegVerticalOffset - newNegVerticalOffset; deltaY += NegVerticalOffset - newNegVerticalOffset;
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; DisplayData.NumTotallyDisplayedScrollingElements = visibleScrollingRows - 1;
} }
@ -2108,7 +2108,7 @@ namespace Avalonia.Controls
double deltaY = 0; double deltaY = 0;
int visibleScrollingRows = 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(); ResetDisplayedRows();
return; return;
@ -2120,7 +2120,7 @@ namespace Avalonia.Controls
} }
int slot = lastDisplayedScrollingRow; int slot = lastDisplayedScrollingRow;
while (DoubleUtil.LessThan(deltaY, displayHeight) && slot >= 0) while (MathUtilities.LessThan(deltaY, displayHeight) && slot >= 0)
{ {
deltaY += GetExactSlotElementHeight(slot); deltaY += GetExactSlotElementHeight(slot);
visibleScrollingRows++; visibleScrollingRows++;
@ -2542,7 +2542,7 @@ namespace Avalonia.Controls
double heightChange = UpdateRowGroupVisibility(rowGroupInfo, isVisible, isDisplayed: false); 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 // 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 // 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 else
{ {
@ -3024,4 +3024,4 @@ namespace Avalonia.Controls
} }
#endif #endif
} }
} }

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

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

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

@ -53,14 +53,14 @@ namespace Avalonia.Base.UnitTests.Utilities
} }
[Fact] [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); var clamp = MathUtilities.Clamp(double.NaN, 0.0, 1.0);
Assert.True(double.IsNaN(clamp)); Assert.True(double.IsNaN(clamp));
} }
[Fact] [Fact]
public void Clamp_Input_NegativeInfinity_Return_Min() public void Float_Clamp_Input_NegativeInfinity_Return_Min()
{ {
const double min = 0.0; const double min = 0.0;
const double max = 1.0; const double max = 1.0;
@ -69,12 +69,54 @@ namespace Avalonia.Base.UnitTests.Utilities
} }
[Fact] [Fact]
public void Clamp_Input_PositiveInfinity_Return_Max() public void Float_Clamp_Input_PositiveInfinity_Return_Max()
{ {
const double min = 0.0; const double min = 0.0;
const double max = 1.0; const double max = 1.0;
var actual = MathUtilities.Clamp(double.PositiveInfinity, min, max); var actual = MathUtilities.Clamp(double.PositiveInfinity, min, max);
Assert.Equal(max, actual); Assert.Equal(max, actual);
} }
[Fact]
public void Zero_Less_Than_One()
{
var actual = MathUtilities.LessThan(0, 1);
Assert.True(actual);
}
[Fact]
public void One_Not_Less_Than_Zero()
{
var actual = MathUtilities.LessThan(1, 0);
Assert.False(actual);
}
[Fact]
public void Zero_Not_Greater_Than_One()
{
var actual = MathUtilities.GreaterThan(0, 1);
Assert.False(actual);
}
[Fact]
public void One_Greater_Than_Zero()
{
var actual = MathUtilities.GreaterThan(1, 0);
Assert.True(actual);
}
[Fact]
public void One_Less_Than_Or_Close_One()
{
var actual = MathUtilities.LessThanOrClose(1, 1);
Assert.True(actual);
}
[Fact]
public void One_Greater_Than_Or_Close_One()
{
var actual = MathUtilities.GreaterThanOrClose(1, 1);
Assert.True(actual);
}
} }
} }

Loading…
Cancel
Save