Permalink
Fetching contributors…
Cannot retrieve contributors at this time
296 lines (274 sloc) 11.4 KB
using System;
using System.Globalization;
using System.Collections.Generic;
using System.Linq.Expressions;
namespace Eto.Forms
{
/// <summary>
/// Represents an immutable, inclusive start/end range of <see cref="IComparable{T}"/> values
/// </summary>
/// <copyright>(c) 2014 by Curtis Wensley</copyright>
/// <license type="BSD-3">See LICENSE for full terms</license>
public struct Range<T> : IEquatable<Range<T>>
where T : struct, IComparable<T>
{
T start;
T end;
/// <summary>
/// Gets the start value of the range
/// </summary>
/// <value>The start of the range.</value>
public T Start { get { return start; } }
/// <summary>
/// Gets the end value of the range.
/// </summary>
/// <value>The end of the range.</value>
public T End { get { return end; } }
/// <summary>
/// Initializes a new instance of the <see cref="Eto.Forms.Range{T}"/> struct with a value for both the start and end.
/// </summary>
/// <param name="value">Value for the start and end of the range.</param>
public Range(T value)
{
start = value;
end = value;
}
/// <summary>
/// Initializes a new instance of the <see cref="Eto.Forms.Range{T}"/> struct.
/// </summary>
/// <param name="start">Start of the range (inclusive).</param>
/// <param name="end">End of the range (inclusive).</param>
public Range(T start, T end)
{
this.start = start;
this.end = end;
}
/// <summary>
/// Creates a copy of the current range with a different start value.
/// </summary>
/// <returns>A new instance of the range with the specified start value.</returns>
/// <param name="start">Start of the new range.</param>
public Range<T> WithStart(T start)
{
return new Range<T>(start, End);
}
/// <summary>
/// Creates a copy of the current range with a different end value.
/// </summary>
/// <returns>A new instance of the range with the specified end value.</returns>
/// <param name="end">End of the new range.</param>
public Range<T> WithEnd(T end)
{
return new Range<T>(Start, end);
}
/// <summary>
/// Determines if the specified <paramref name="value"/> is between or equal to the <see cref="Start"/> and <see cref="End"/> of this range.
/// </summary>
/// <param name="value">Value to check if it is within this range.</param>
public bool Contains(T value)
{
var comparer = Comparer<T>.Default;
return comparer.Compare(Start, value) <= 0
&& comparer.Compare(End, value) >= 0;
}
/// <summary>
/// Iterates the range between the start and end values.
/// </summary>
/// <remarks>
/// This can be used to return an enumerable that iterates between the start and end of the range given the
/// specified <paramref name="increment"/> function.
/// </remarks>
/// <example>
/// <code>
/// // iterate over an int range
/// var intRange = new Range&lt;int&gt;(1, 200);
/// foreach (var item in intRange.Iterate(i => i + 1))
/// {
/// // logic
/// }
///
/// // iterate over a date range by minute
/// var dateRange = new Range&lt;DateTime&gt;(DateTime.Today, DateTime.Today.AddDays(2));
/// foreach (var item in dateRange.Iterate(i => i.AddMinutes(1)))
/// {
/// // logic
/// }
/// </code>
/// </example>
/// <param name="increment">Delegate to increment the value for each iteration of the enumerable.</param>
public IEnumerable<T> Iterate(Func<T, T> increment)
{
T item = start;
var comparer = Comparer<T>.Default;
while (comparer.Compare(item, end) <= 0)
{
yield return item;
item = increment(item);
}
}
/// <summary>
/// Determines if the specified <paramref name="range"/> touches (but doesn't intersect) this instance.
/// </summary>
/// <remarks>
/// This can be used to determine if one range comes after or before another range, given the specified
/// <paramref name="increment"/> function.
/// The increment function is used as this class does not assume how to increment each value, e.g. for a
/// <see cref="DateTime"/> value, you can increment by day, minute, second, etc.
/// </remarks>
/// <param name="range">Range to check if it touches this range.</param>
/// <param name="increment">Delegate to increment the value for checking if the ranges touch.</param>
/// <returns><c>true</c> if the ranges touch, <c>false</c> otherwise.</returns>
public bool Touches(Range<T> range, Func<T, T> increment)
{
var comparer = Comparer<T>.Default;
return comparer.Compare(Start, increment(range.End)) == 0
|| comparer.Compare(increment(End), range.Start) == 0;
}
/// <summary>
/// Determines if the specified <paramref name="range"/> intersects (overlaps) this instance.
/// </summary>
/// <param name="range">Range to check for intersection.</param>
/// <returns><c>true</c> if the range intersects this instance, <c>false</c> otherwise.</returns>
public bool Intersects(Range<T> range)
{
var comparer = Comparer<T>.Default;
var startVal = comparer.Compare(Start, range.Start) >= 0 ? Start : range.Start;
var endVal = comparer.Compare(End, range.End) <= 0 ? End : range.End;
return comparer.Compare(startVal, endVal) <= 0;
}
/// <summary>
/// Gets the intersection of this instance and the specified <paramref name="range"/>.
/// </summary>
/// <param name="range">Range to intersect with.</param>
/// <returns>A new instance of a range that is the intersection of this instance and the specified range, or null if they do not intersect.</returns>
public Range<T>? Intersect(Range<T> range)
{
var comparer = Comparer<T>.Default;
var startVal = comparer.Compare(Start, range.Start) >= 0 ? Start : range.Start;
var endVal = comparer.Compare(End, range.End) <= 0 ? End : range.End;
return comparer.Compare(startVal, endVal) <= 0 ? (Range<T>?)new Range<T>(startVal, endVal) : null;
}
/// <summary>
/// Gets the union of this instance and the specified <paramref name="range"/>, including touching ranges.
/// </summary>
/// <remarks>
/// This is similar to <see cref="Union(Range{T})"/>, however this handles when the two ranges are touching.
/// The <paramref name="increment"/> delegate is used to determine if the ranges are touching by incrementing the ends
/// of the ranges and comparing that value to the start of the other range.
/// </remarks>
/// <param name="range">Range to union with.</param>
/// <param name="increment">Delegate to increment the value for checking if the ranges touch.</param>
/// <returns>The union of this instance and the specified range, or null if they are neither intersecting or touching.</returns>
public Range<T>? Union(Range<T> range, Func<T, T> increment)
{
if (Intersects(range) || Touches(range, increment))
{
var comparer = Comparer<T>.Default;
var startVal = comparer.Compare(Start, range.Start) <= 0 ? Start : range.Start;
var endVal = comparer.Compare(End, range.End) >= 0 ? End : range.End;
return comparer.Compare(startVal, endVal) <= 0 ? (Range<T>?)new Range<T>(startVal, endVal) : null;
}
return null;
}
/// <summary>
/// Gets the union of this instance and an intersecting <paramref name="range"/>.
/// </summary>
/// <remarks>
/// This is similar to <see cref="Union(Range{T},Func{T,T})"/>, however this only handles when the two ranges are intersecting.
/// To union two ranges that touch, use the <see cref="Union(Range{T},Func{T,T})"/> method instead.
/// </remarks>
/// <param name="range">Range to union with.</param>
/// <returns>The union of this instance and the specified range, or null if they are not intersecting.</returns>
public Range<T>? Union(Range<T> range)
{
if (Intersects(range))
{
var comparer = Comparer<T>.Default;
var startVal = comparer.Compare(Start, range.Start) <= 0 ? Start : range.Start;
var endVal = comparer.Compare(End, range.End) >= 0 ? End : range.End;
return comparer.Compare(startVal, endVal) <= 0 ? (Range<T>?)new Range<T>(startVal, endVal) : null;
}
return null;
}
/// <summary>
/// Operator to compare two ranges for inequality.
/// </summary>
/// <param name="range1">First range to compare.</param>
/// <param name="range2">Second range to compare.</param>
/// <returns><c>true</c> if the two ranges are not equal, <c>false</c> if they are.</returns>
public static bool operator !=(Range<T> range1, Range<T> range2)
{
return !(range1 == range2);
}
/// <summary>
/// Operator to compare two ranges for equality
/// </summary>
/// <param name="range1">First range to compare.</param>
/// <param name="range2">Second range to compare.</param>
/// <returns><c>true</c> if the two ranges are equal, <c>false</c> if they are not.</returns>
public static bool operator ==(Range<T> range1, Range<T> range2)
{
var comparer = Comparer<T>.Default;
return comparer.Compare(range1.Start, range2.Start) == 0
&& comparer.Compare(range1.End, range2.End) == 0;
}
/// <summary>
/// Determines whether the specified <see cref="System.Object"/> is equal to the current <see cref="Range{T}"/>.
/// </summary>
/// <param name="obj">The <see cref="System.Object"/> to compare with the current <see cref="Range{T}"/>.</param>
/// <returns><c>true</c> if the specified <see cref="System.Object"/> is equal to the current <see cref="Range{T}"/>; otherwise, <c>false</c>.</returns>
public override bool Equals(object obj)
{
return obj is Range<T> && ((Range<T>)obj == this);
}
/// <summary>
/// Serves as a hash function for a <see cref="Eto.Forms.Range{T}"/> object.
/// </summary>
/// <returns>A hash code for this instance that is suitable for use in hashing algorithms and data structures such as a hash table.</returns>
public override int GetHashCode()
{
return Start.GetHashCode() ^ End.GetHashCode();
}
/// <summary>
/// Returns a <see cref="System.String"/> that represents the current <see cref="Range{T}"/>.
/// </summary>
/// <returns>A <see cref="System.String"/> that represents the current <see cref="Range{T}"/>.</returns>
public override string ToString()
{
return string.Format(CultureInfo.InvariantCulture, "Start={0}, End={1}", Start, End);
}
/// <summary>
/// Determines whether the specified <paramref name="other"/> range is equal to the current <see cref="Range{T}"/>.
/// </summary>
/// <param name="other">The <see cref="Range{T}"/> to compare with the current <see cref="Range{T}"/>.</param>
/// <returns><c>true</c> if the specified <see cref="Range{T}"/> is equal to the current <see cref="Range{T}"/>; otherwise, <c>false</c>.</returns>
public bool Equals(Range<T> other)
{
return this == other;
}
}
/// <summary>
/// Extensions for the <see cref="Range{T}"/> structure
/// </summary>
public static class RangeExtensions
{
/// <summary>
/// Gets the interval for the specified <paramref name="range"/> between the start and end dates.
/// </summary>
/// <param name="range">Range to get the interval for.</param>
/// <returns>A new TimeSpan that is the difference between the start and end dates of the specified range.</returns>
public static TimeSpan Interval(this Range<DateTime> range)
{
return range.End - range.Start;
}
/// <summary>
/// Gets the length of the specified <paramref name="range"/> between the start and end values.
/// </summary>
/// <param name="range">Range to get the length for.</param>
/// <returns>The length between the start and end values of the specified range.</returns>
public static int Length(this Range<int> range)
{
return range.End - range.Start + 1;
}
}
}