csharpc-sharpdotnetxamlavaloniauicross-platformcross-platform-xamlavaloniaguimulti-platformuser-interfacedotnetcore
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
341 lines
10 KiB
341 lines
10 KiB
// This source file is adapted from the WinUI project.
|
|
// (https://github.com/microsoft/microsoft-ui-xaml)
|
|
//
|
|
// Licensed to The Avalonia Project under MIT License, courtesy of The .NET Foundation.
|
|
|
|
using System;
|
|
using System.Collections.Generic;
|
|
|
|
namespace Avalonia.Controls.Selection
|
|
{
|
|
internal readonly struct IndexRange : IEquatable<IndexRange>
|
|
{
|
|
private static readonly IndexRange s_invalid = new IndexRange(int.MinValue, int.MinValue);
|
|
|
|
public IndexRange(int index)
|
|
{
|
|
Begin = index;
|
|
End = index;
|
|
}
|
|
|
|
public IndexRange(int begin, int end)
|
|
{
|
|
// Accept out of order begin/end pairs, just swap them.
|
|
if (begin > end)
|
|
{
|
|
int temp = begin;
|
|
begin = end;
|
|
end = temp;
|
|
}
|
|
|
|
Begin = begin;
|
|
End = end;
|
|
}
|
|
|
|
public int Begin { get; }
|
|
public int End { get; }
|
|
public int Count => (End - Begin) + 1;
|
|
|
|
public bool Contains(int index) => index >= Begin && index <= End;
|
|
|
|
public bool Split(int splitIndex, out IndexRange before, out IndexRange after)
|
|
{
|
|
bool afterIsValid;
|
|
|
|
before = new IndexRange(Begin, splitIndex);
|
|
|
|
if (splitIndex < End)
|
|
{
|
|
after = new IndexRange(splitIndex + 1, End);
|
|
afterIsValid = true;
|
|
}
|
|
else
|
|
{
|
|
after = new IndexRange();
|
|
afterIsValid = false;
|
|
}
|
|
|
|
return afterIsValid;
|
|
}
|
|
|
|
public bool Intersects(IndexRange other)
|
|
{
|
|
return (Begin <= other.End) && (End >= other.Begin);
|
|
}
|
|
|
|
public bool Adjacent(IndexRange other)
|
|
{
|
|
return Begin == other.End + 1 || End == other.Begin - 1;
|
|
}
|
|
|
|
public override bool Equals(object? obj)
|
|
{
|
|
return obj is IndexRange range && Equals(range);
|
|
}
|
|
|
|
public bool Equals(IndexRange other)
|
|
{
|
|
return Begin == other.Begin && End == other.End;
|
|
}
|
|
|
|
public override int GetHashCode()
|
|
{
|
|
var hashCode = 1903003160;
|
|
hashCode = hashCode * -1521134295 + Begin.GetHashCode();
|
|
hashCode = hashCode * -1521134295 + End.GetHashCode();
|
|
return hashCode;
|
|
}
|
|
|
|
public override string ToString() => FormattableString.Invariant($"[{Begin}..{End}]");
|
|
|
|
public static bool operator ==(IndexRange left, IndexRange right) => left.Equals(right);
|
|
public static bool operator !=(IndexRange left, IndexRange right) => !(left == right);
|
|
|
|
public static bool Contains(IReadOnlyList<IndexRange>? ranges, int index)
|
|
{
|
|
if (ranges is null || index < 0)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
foreach (var range in ranges)
|
|
{
|
|
if (range.Contains(index))
|
|
{
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
public static int GetAt(IReadOnlyList<IndexRange> ranges, int index)
|
|
{
|
|
var currentIndex = 0;
|
|
|
|
foreach (var range in ranges)
|
|
{
|
|
var currentCount = range.Count;
|
|
|
|
if (index >= currentIndex && index < currentIndex + currentCount)
|
|
{
|
|
return range.Begin + (index - currentIndex);
|
|
}
|
|
|
|
currentIndex += currentCount;
|
|
}
|
|
|
|
throw new IndexOutOfRangeException("The index was out of range.");
|
|
}
|
|
|
|
public static int Add(
|
|
IList<IndexRange> ranges,
|
|
IndexRange range,
|
|
IList<IndexRange>? added = null)
|
|
{
|
|
var result = 0;
|
|
|
|
for (var i = 0; i < ranges.Count && range != s_invalid; ++i)
|
|
{
|
|
var existing = ranges[i];
|
|
|
|
if (range.Intersects(existing) || range.Adjacent(existing))
|
|
{
|
|
if (range.Begin < existing.Begin)
|
|
{
|
|
var add = new IndexRange(range.Begin, existing.Begin - 1);
|
|
ranges[i] = new IndexRange(range.Begin, existing.End);
|
|
added?.Add(add);
|
|
result += add.Count;
|
|
}
|
|
|
|
range = range.End <= existing.End ?
|
|
s_invalid :
|
|
new IndexRange(existing.End + 1, range.End);
|
|
}
|
|
else if (range.End < existing.Begin)
|
|
{
|
|
ranges.Insert(i, range);
|
|
added?.Add(range);
|
|
result += range.Count;
|
|
range = s_invalid;
|
|
}
|
|
}
|
|
|
|
if (range != s_invalid)
|
|
{
|
|
ranges.Add(range);
|
|
added?.Add(range);
|
|
result += range.Count;
|
|
}
|
|
|
|
MergeRanges(ranges);
|
|
return result;
|
|
}
|
|
|
|
public static int Add(
|
|
IList<IndexRange> destination,
|
|
IReadOnlyList<IndexRange> source,
|
|
IList<IndexRange>? added = null)
|
|
{
|
|
var result = 0;
|
|
|
|
foreach (var range in source)
|
|
{
|
|
result += Add(destination, range, added);
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
public static int Intersect(
|
|
IList<IndexRange> ranges,
|
|
IndexRange range,
|
|
IList<IndexRange>? removed = null)
|
|
{
|
|
var result = 0;
|
|
|
|
for (var i = 0; i < ranges.Count && range != s_invalid; ++i)
|
|
{
|
|
var existing = ranges[i];
|
|
|
|
if (existing.End < range.Begin || existing.Begin > range.End)
|
|
{
|
|
removed?.Add(existing);
|
|
ranges.RemoveAt(i--);
|
|
result += existing.Count;
|
|
}
|
|
else
|
|
{
|
|
if (existing.Begin < range.Begin)
|
|
{
|
|
var except = new IndexRange(existing.Begin, range.Begin - 1);
|
|
removed?.Add(except);
|
|
ranges[i] = existing = new IndexRange(range.Begin, existing.End);
|
|
result += except.Count;
|
|
}
|
|
|
|
if (existing.End > range.End)
|
|
{
|
|
var except = new IndexRange(range.End + 1, existing.End);
|
|
removed?.Add(except);
|
|
ranges[i] = new IndexRange(existing.Begin, range.End);
|
|
result += except.Count;
|
|
}
|
|
}
|
|
}
|
|
|
|
MergeRanges(ranges);
|
|
|
|
if (removed is object)
|
|
{
|
|
MergeRanges(removed);
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
public static int Remove(
|
|
IList<IndexRange>? ranges,
|
|
IndexRange range,
|
|
IList<IndexRange>? removed = null)
|
|
{
|
|
if (ranges is null)
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
var result = 0;
|
|
|
|
for (var i = 0; i < ranges.Count; ++i)
|
|
{
|
|
var existing = ranges[i];
|
|
|
|
if (range.Intersects(existing))
|
|
{
|
|
if (range.Begin <= existing.Begin && range.End >= existing.End)
|
|
{
|
|
ranges.RemoveAt(i--);
|
|
removed?.Add(existing);
|
|
result += existing.Count;
|
|
}
|
|
else if (range.Begin > existing.Begin && range.End >= existing.End)
|
|
{
|
|
ranges[i] = new IndexRange(existing.Begin, range.Begin - 1);
|
|
removed?.Add(new IndexRange(range.Begin, existing.End));
|
|
result += existing.End - (range.Begin - 1);
|
|
}
|
|
else if (range.Begin > existing.Begin && range.End < existing.End)
|
|
{
|
|
ranges[i] = new IndexRange(existing.Begin, range.Begin - 1);
|
|
ranges.Insert(++i, new IndexRange(range.End + 1, existing.End));
|
|
removed?.Add(range);
|
|
result += range.Count;
|
|
}
|
|
else if (range.End <= existing.End)
|
|
{
|
|
var remove = new IndexRange(existing.Begin, range.End);
|
|
ranges[i] = new IndexRange(range.End + 1, existing.End);
|
|
removed?.Add(remove);
|
|
result += remove.Count;
|
|
}
|
|
}
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
public static int Remove(
|
|
IList<IndexRange> destination,
|
|
IReadOnlyList<IndexRange> source,
|
|
IList<IndexRange>? added = null)
|
|
{
|
|
var result = 0;
|
|
|
|
foreach (var range in source)
|
|
{
|
|
result += Remove(destination, range, added);
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
public static IEnumerable<int> EnumerateIndices(IEnumerable<IndexRange> ranges)
|
|
{
|
|
foreach (var range in ranges)
|
|
{
|
|
for (var i = range.Begin; i <= range.End; ++i)
|
|
{
|
|
yield return i;
|
|
}
|
|
}
|
|
}
|
|
|
|
public static int GetCount(IEnumerable<IndexRange> ranges)
|
|
{
|
|
var result = 0;
|
|
|
|
foreach (var range in ranges)
|
|
{
|
|
result += (range.End - range.Begin) + 1;
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
private static void MergeRanges(IList<IndexRange> ranges)
|
|
{
|
|
for (var i = ranges.Count - 2; i >= 0; --i)
|
|
{
|
|
var r = ranges[i];
|
|
var r1 = ranges[i + 1];
|
|
|
|
if (r.Intersects(r1) || r.End == r1.Begin - 1)
|
|
{
|
|
ranges[i] = new IndexRange(r.Begin, r1.End);
|
|
ranges.RemoveAt(i + 1);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|