Browse Source

Merge pull request #2596 from wieslawsoltes/wpfwrappanelimport

Port of WPF WrapPanel
rtb-gl-fixes
Wiesław Šoltés 7 years ago
committed by GitHub
parent
commit
ca6f8f687a
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 118
      src/Avalonia.Base/Utilities/MathUtilities.cs
  2. 196
      src/Avalonia.Controls/WrapPanel.cs

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

@ -1,6 +1,9 @@
// 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.Runtime.InteropServices;
namespace Avalonia.Utilities
{
/// <summary>
@ -8,6 +11,86 @@ namespace Avalonia.Utilities
/// </summary>
public static class MathUtilities
{
/// <summary>
/// AreClose - Returns whether or not two doubles are "close". That is, whether or
/// not they are within epsilon of each other.
/// </summary>
/// <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;
double eps = (Math.Abs(value1) + Math.Abs(value2) + 10.0) * double.Epsilon;
double delta = value1 - value2;
return (-eps < delta) && (eps > delta);
}
/// <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.
/// <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>
/// 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.
/// <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>
/// 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.
/// <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);
}
/// <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.
/// </summary>
/// <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>
/// IsOne - Returns whether or not the double is "close" to 1. Same as AreClose(double, 1),
/// but this is faster.
/// </summary>
/// <param name="value"> The double to compare to 1. </param>
public static bool IsOne(double value)
{
return Math.Abs(value - 1.0) < 10.0 * double.Epsilon;
}
/// <summary>
/// IsZero - Returns whether or not the double is "close" to 0. Same as AreClose(double, 0),
/// but this is faster.
/// </summary>
/// <param name="value"> The double to compare to 0. </param>
public static bool IsZero(double value)
{
return Math.Abs(value) < 10.0 * double.Epsilon;
}
/// <summary>
/// Clamps a value between a minimum and maximum value.
/// </summary>
@ -31,6 +114,39 @@ namespace Avalonia.Utilities
}
}
/// <summary>
/// Calculates the value to be used for layout rounding at high DPI.
/// </summary>
/// <param name="value">Input value to be rounded.</param>
/// <param name="dpiScale">Ratio of screen's DPI to layout DPI</param>
/// <returns>Adjusted value that will produce layout rounding on screen at high dpi.</returns>
/// <remarks>This is a layout helper method. It takes DPI into account and also does not return
/// the rounded value if it is unacceptable for layout, e.g. Infinity or NaN. It's a helper associated with
/// UseLayoutRounding property and should not be used as a general rounding utility.</remarks>
public static double RoundLayoutValue(double value, double dpiScale)
{
double newValue;
// If DPI == 1, don't use DPI-aware rounding.
if (!MathUtilities.AreClose(dpiScale, 1.0))
{
newValue = Math.Round(value * dpiScale) / dpiScale;
// If rounding produces a value unacceptable to layout (NaN, Infinity or MaxValue), use the original value.
if (double.IsNaN(newValue) ||
double.IsInfinity(newValue) ||
MathUtilities.AreClose(newValue, double.MaxValue))
{
newValue = value;
}
}
else
{
newValue = Math.Round(value);
}
return newValue;
}
/// <summary>
/// Clamps a value between a minimum and maximum value.
/// </summary>
@ -54,4 +170,4 @@ namespace Avalonia.Utilities
}
}
}
}
}

196
src/Avalonia.Controls/WrapPanel.cs

@ -6,6 +6,7 @@ using System.Diagnostics;
using System.Linq;
using Avalonia.Input;
using Avalonia.Utilities;
using static System.Math;
@ -92,109 +93,127 @@ namespace Avalonia.Controls
}
}
private UVSize CreateUVSize(Size size) => new UVSize(Orientation, size);
private UVSize CreateUVSize() => new UVSize(Orientation);
/// <inheritdoc/>
protected override Size MeasureOverride(Size availableSize)
protected override Size MeasureOverride(Size constraint)
{
var desiredSize = CreateUVSize();
var lineSize = CreateUVSize();
var uvAvailableSize = CreateUVSize(availableSize);
var curLineSize = new UVSize(Orientation);
var panelSize = new UVSize(Orientation);
var uvConstraint = new UVSize(Orientation, constraint.Width, constraint.Height);
foreach (var child in Children)
var childConstraint = new Size(constraint.Width, constraint.Height);
for (int i = 0, count = Children.Count; i < count; i++)
{
child.Measure(availableSize);
var childSize = CreateUVSize(child.DesiredSize);
if (lineSize.U + childSize.U <= uvAvailableSize.U) // same line
var child = Children[i];
if (child == null) continue;
//Flow passes its own constrint to children
child.Measure(childConstraint);
//this is the size of the child in UV space
var sz = new UVSize(Orientation, child.DesiredSize.Width, child.DesiredSize.Height);
if (MathUtilities.GreaterThan(curLineSize.U + sz.U, uvConstraint.U)) //need to switch to another line
{
lineSize.U += childSize.U;
lineSize.V = Max(lineSize.V, childSize.V);
panelSize.U = Max(curLineSize.U, panelSize.U);
panelSize.V += curLineSize.V;
curLineSize = sz;
if (MathUtilities.GreaterThan(sz.U, uvConstraint.U)) //the element is wider then the constrint - give it a separate line
{
panelSize.U = Max(sz.U, panelSize.U);
panelSize.V += sz.V;
curLineSize = new UVSize(Orientation);
}
}
else // moving to next line
else //continue to accumulate a line
{
desiredSize.U = Max(lineSize.U, uvAvailableSize.U);
desiredSize.V += lineSize.V;
lineSize = childSize;
curLineSize.U += sz.U;
curLineSize.V = Max(sz.V, curLineSize.V);
}
}
// last element
desiredSize.U = Max(lineSize.U, desiredSize.U);
desiredSize.V += lineSize.V;
return desiredSize.ToSize();
//the last line size, if any should be added
panelSize.U = Max(curLineSize.U, panelSize.U);
panelSize.V += curLineSize.V;
//go from UV space to W/H space
return new Size(panelSize.Width, panelSize.Height);
}
/// <inheritdoc/>
protected override Size ArrangeOverride(Size finalSize)
{
int firstInLine = 0;
double accumulatedV = 0;
var uvFinalSize = CreateUVSize(finalSize);
var lineSize = CreateUVSize();
int firstChildInLineIndex = 0;
for (int index = 0; index < Children.Count; index++)
UVSize curLineSize = new UVSize(Orientation);
UVSize uvFinalSize = new UVSize(Orientation, finalSize.Width, finalSize.Height);
for (int i = 0; i < Children.Count; i++)
{
var child = Children[index];
var childSize = CreateUVSize(child.DesiredSize);
if (lineSize.U + childSize.U <= uvFinalSize.U) // same line
var child = Children[i];
if (child == null) continue;
var sz = new UVSize(Orientation, child.DesiredSize.Width, child.DesiredSize.Height);
if (MathUtilities.GreaterThan(curLineSize.U + sz.U, uvFinalSize.U)) //need to switch to another line
{
lineSize.U += childSize.U;
lineSize.V = Max(lineSize.V, childSize.V);
arrangeLine(accumulatedV, curLineSize.V, firstInLine, i);
accumulatedV += curLineSize.V;
curLineSize = sz;
if (MathUtilities.GreaterThan(sz.U, uvFinalSize.U)) //the element is wider then the constraint - give it a separate line
{
//switch to next line which only contain one element
arrangeLine(accumulatedV, sz.V, i, ++i);
accumulatedV += sz.V;
curLineSize = new UVSize(Orientation);
}
firstInLine = i;
}
else // moving to next line
else //continue to accumulate a line
{
var controlsInLine = GetControlsBetween(firstChildInLineIndex, index);
ArrangeLine(accumulatedV, lineSize.V, controlsInLine);
accumulatedV += lineSize.V;
lineSize = childSize;
firstChildInLineIndex = index;
curLineSize.U += sz.U;
curLineSize.V = Max(sz.V, curLineSize.V);
}
}
if (firstChildInLineIndex < Children.Count)
//arrange the last line, if any
if (firstInLine < Children.Count)
{
var controlsInLine = GetControlsBetween(firstChildInLineIndex, Children.Count);
ArrangeLine(accumulatedV, lineSize.V, controlsInLine);
arrangeLine(accumulatedV, curLineSize.V, firstInLine, Children.Count);
}
return finalSize;
}
private IEnumerable<IControl> GetControlsBetween(int first, int last)
{
return Children.Skip(first).Take(last - first);
return finalSize;
}
private void ArrangeLine(double v, double lineV, IEnumerable<IControl> controls)
private void arrangeLine(double v, double lineV, int start, int end)
{
double u = 0;
bool isHorizontal = (Orientation == Orientation.Horizontal);
foreach (var child in controls)
for (int i = start; i < end; i++)
{
var childSize = CreateUVSize(child.DesiredSize);
var x = isHorizontal ? u : v;
var y = isHorizontal ? v : u;
var width = isHorizontal ? childSize.U : lineV;
var height = isHorizontal ? lineV : childSize.U;
child.Arrange(new Rect(x, y, width, height));
u += childSize.U;
var child = Children[i];
if (child != null)
{
UVSize childSize = new UVSize(Orientation, child.DesiredSize.Width, child.DesiredSize.Height);
double layoutSlotU = childSize.U;
child.Arrange(new Rect(
(isHorizontal ? u : v),
(isHorizontal ? v : u),
(isHorizontal ? layoutSlotU : lineV),
(isHorizontal ? lineV : layoutSlotU)));
u += layoutSlotU;
}
}
}
/// <summary>
/// Used to not not write separate code for horizontal and vertical orientation.
/// U is direction in line. (x if orientation is horizontal)
/// V is direction of lines. (y if orientation is horizontal)
/// </summary>
[DebuggerDisplay("U = {U} V = {V}")]
private struct UVSize
{
private readonly Orientation _orientation;
internal double U;
internal double V;
private UVSize(Orientation orientation, double width, double height)
internal UVSize(Orientation orientation, double width, double height)
{
U = V = 0d;
_orientation = orientation;
@ -202,52 +221,25 @@ namespace Avalonia.Controls
Height = height;
}
internal UVSize(Orientation orientation, Size size)
: this(orientation, size.Width, size.Height)
{
}
internal UVSize(Orientation orientation)
{
U = V = 0d;
_orientation = orientation;
}
private double Width
internal double U;
internal double V;
private Orientation _orientation;
internal double Width
{
get { return (_orientation == Orientation.Horizontal ? U : V); }
set
{
if (_orientation == Orientation.Horizontal)
{
U = value;
}
else
{
V = value;
}
}
set { if (_orientation == Orientation.Horizontal) U = value; else V = value; }
}
private double Height
internal double Height
{
get { return (_orientation == Orientation.Horizontal ? V : U); }
set
{
if (_orientation == Orientation.Horizontal)
{
V = value;
}
else
{
U = value;
}
}
}
public Size ToSize()
{
return new Size(Width, Height);
set { if (_orientation == Orientation.Horizontal) V = value; else U = value; }
}
}
}

Loading…
Cancel
Save