port from perforce

This commit is contained in:
2026-04-18 22:31:51 +02:00
commit 8d0ab5b7cc
8409 changed files with 3972376 additions and 0 deletions

View File

@@ -0,0 +1,134 @@
using System;
using System.Collections.Generic;
using System.Linq;
namespace NodeNetwork.Utilities
{
//Class for calculating the longest common subsequence of two lists
//For example: the LCS of 'computer' and 'houseboat' is 'out'
public class LongestCommonSubsequence
{
/// <summary>
/// The type of change that occured.
/// </summary>
public enum ChangeType
{
Removed, Added
}
/// <summary>
/// Returns the changes to be made to oldList to reach the state of newList.
/// First all items that are in oldList but not in the LCS of oldList and newList are removed.
/// The list is then identical to the LCS of oldList and newList.
/// Then all items that are in newList but not in the LCS of oldList and newList are added.
/// The list is then identical to newList.
/// </summary>
/// <typeparam name="T">The type of items contained in the two lists</typeparam>
/// <param name="oldList">The first list</param>
/// <param name="newList">The second list</param>
/// <returns></returns>
public static IEnumerable<(int index, T item, ChangeType change)> GetChanges<T>(IList<T> oldList, IList<T> newList)
{
if (oldList == null)
{
throw new ArgumentNullException(nameof(oldList));
}else if (newList == null)
{
throw new ArgumentNullException(nameof(newList));
}
T[] lcs = LongestCommonSubsequence.Calculate(oldList, newList).ToArray();
//Initial data => LCS
int lcsCursor = lcs.Length - 1;
for (int initialDataCursor = oldList.Count - 1; initialDataCursor >= 0; initialDataCursor--)
{
if (lcsCursor >= 0 && oldList[initialDataCursor].Equals(lcs[lcsCursor]))
{
lcsCursor--;
}
else
{
yield return (initialDataCursor, oldList[initialDataCursor], ChangeType.Removed);
}
}
//LCS => newdata
lcsCursor = 0;
for (int newDataCursor = 0; newDataCursor < newList.Count; newDataCursor++)
{
if (lcsCursor < lcs.Length && newList[newDataCursor].Equals(lcs[lcsCursor]))
{
lcsCursor++;
}
else
{
yield return (newDataCursor, newList[newDataCursor], ChangeType.Added);
}
}
}
/// <summary>
/// Returns the longest common subsequence of two lists
/// For example: the LCS of 'computer' and 'houseboat' is 'out'
/// </summary>
/// <typeparam name="T">The type of items contained in the two lists</typeparam>
/// <param name="seq1">The first list</param>
/// <param name="seq2">The second list</param>
/// <returns>An enumerable of items that are both in seq1 and seq2 and which follows the consecutive order of both lists</returns>
public static IEnumerable<T> Calculate<T>(IList<T> seq1, IList<T> seq2)
{
int[,] matrix = CalculateLCSMatrix(seq1, seq2);
return Backtrack(seq1, seq2, matrix).Reverse();
}
private static int[,] CalculateLCSMatrix<T>(IList<T> seq1, IList<T> seq2)
{
int[,] matrix = new int[seq1.Count + 1, seq2.Count + 1];
for (int i = 1; i < matrix.GetLength(0); i++)
{
for (int j = 1; j < matrix.GetLength(1); j++)
{
if (seq1[i - 1].Equals(seq2[j - 1]))
{
matrix[i, j] = matrix[i - 1, j - 1] + 1;
}
else
{
matrix[i, j] = Math.Max(matrix[i - 1, j], matrix[i, j - 1]);
}
}
}
return matrix;
}
private static IEnumerable<T> Backtrack<T>(IList<T> seq1, IList<T> seq2, int[,] matrix)
{
int i = matrix.GetLength(0) - 1;
int j = matrix.GetLength(1) - 1;
bool done = false;
while (!done)
{
if (i > 0 && j > 0 && seq1[i - 1].Equals(seq2[j - 1]))
{
yield return seq1[i - 1];
i -= 1;
j -= 1;
}
else if (j > 0 && (i == 0 || matrix[i, j - 1] >= matrix[i - 1, j]))
{
j -= 1;
}
else if (i > 0 && (j == 0 || matrix[i, j - 1] < matrix[i - 1, j]))
{
i -= 1;
}
else
{
done = true;
}
}
}
}
}

View File

@@ -0,0 +1,100 @@
using System;
using System.Linq.Expressions;
using System.Reactive.Linq;
using DynamicData;
using ReactiveUI;
namespace NodeNetwork.Utilities
{
public static class ReactiveExtensions
{
/// <summary>
/// Pass through values if and only if the last value produced by 'throttleCondition' is false.
/// When 'throttleCondition' is false, no values are passed through.
/// When 'throttleCondition' changes from true to false, if one or more values was blocked during the period
/// in which the throttle was active, the latest value will be passed through.
/// </summary>
/// <typeparam name="T">The datatype in the observable</typeparam>
/// <param name="self">The source observable</param>
/// <param name="throttleCondition">An observable of booleans that determines the current throttle state</param>
/// <returns>The new observable</returns>
public static IObservable<T> ThrottleWhen<T>(this IObservable<T> self, IObservable<bool> throttleCondition)
{
var isPaused = throttleCondition.Prepend(false).DistinctUntilChanged();
return Observable.Defer(() =>
{
object lockObj = new object();
bool gateIsOpen = false;
return Observable.CombineLatest(
self.Synchronize(lockObj).Do(_ => gateIsOpen = true),
isPaused.Synchronize(lockObj).Do(paused => gateIsOpen = !paused && gateIsOpen),
(number, paused) => (number, paused)
)
.Where(tuple => !tuple.paused && gateIsOpen)
.Select(tuple => tuple.number);
});
}
/// <summary>
/// Create a one way list binding from the viewmodel to the view.
/// The view list property will be automatically updated to reflect
/// the viewmodel source list property.
/// </summary>
/// <typeparam name="TView">The type of the view</typeparam>
/// <typeparam name="TViewModel">The type of the viewmodel</typeparam>
/// <typeparam name="TData">The type of the data stored in the list</typeparam>
/// <typeparam name="TProperty">The type of the target property in the view</typeparam>
/// <param name="self">The view used for the binding</param>
/// <param name="vmDummy">A dummy viewmodel parameter, used to infer the viewmodel type</param>
/// <param name="vmProperty">The source property in the viewmodel that contains the list</param>
/// <param name="viewProperty">The target property in the view to bind the list to.</param>
/// <returns>An object that when disposed, disconnects the binding.</returns>
public static IDisposable BindList<TView, TViewModel, TData, TProperty>(
this TView self,
TViewModel vmDummy,
Expression<Func<TViewModel, IObservableList<TData>>> vmProperty,
Expression<Func<TView, TProperty>> viewProperty
)
where TView: class, IViewFor<TViewModel>
where TViewModel: class
{
IDisposable lastBinding = null;
return
// Get latest viewmodel
self.WhenAnyValue(v => v.ViewModel)
.Where(vm => vm != null)
// Get latest non-null list from viewmodel property
.Select(vm => vm.WhenAnyValue(vmProperty))
.Switch()
.Where(sourceList => sourceList != null)
// Clean up last list binding
.Do(p => lastBinding?.Dispose())
// Create new list binding
.Select(sourceList =>
{
lastBinding = sourceList.Connect().Bind(out var list).Subscribe();
return list;
})
// When the observable is disposed, dispose the list binding too
.Finally(() =>
{
lastBinding?.Dispose();
})
// Bind the new bindable list to the view property
.BindTo(self, viewProperty);
}
/// <summary>
/// Takes an observable of T values and returns an observable of tuples of T values containing the latest value and the previous value.
/// The first item in the source observable produces a tuple with the previous value set to default(T).
/// </summary>
/// <typeparam name="T">The type of object in the observable</typeparam>
/// <param name="obs">The source observable</param>
/// <returns>The resulting observable</returns>
public static IObservable<(T OldValue, T NewValue)> PairWithPreviousValue<T>(this IObservable<T> obs)
{
return obs.Scan((oldValue: default(T), newValue: default(T)), (pair, newVal) => (pair.newValue, newVal));
}
}
}

View File

@@ -0,0 +1,22 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Data;
namespace NodeNetwork.Utilities.WPF
{
public class BoolToZIndexConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
return ((bool) value) ? 1 : 0;
}
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
throw new NotSupportedException();
}
}
}

View File

@@ -0,0 +1,23 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Data;
namespace NodeNetwork.Utilities.WPF
{
public class NullVisibilityConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
return value == null ? Visibility.Collapsed : Visibility.Visible;
}
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
throw new NotSupportedException();
}
}
}

View File

@@ -0,0 +1,74 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Data;
using System.Windows.Media;
namespace NodeNetwork.Views.Controls
{
public static class WPFUtils
{
public static T FindParent<T>(DependencyObject childObject) where T : DependencyObject
{
DependencyObject curObj = childObject;
do
{
curObj = VisualTreeHelper.GetParent(curObj);
if (curObj == null) return default(T);
} while (!(curObj is T));
return (T)curObj;
}
public static DependencyObject GetVisualAncestorNLevelsUp(DependencyObject childObject, int levels)
{
DependencyObject curObj = childObject;
for (int i = 0; i < levels; i++)
{
curObj = VisualTreeHelper.GetParent(curObj);
if (curObj == null) return null;
}
return curObj;
}
public static IEnumerable<Point> GetIntersectionPoints(Geometry g1, Geometry g2)
{
Geometry og1 = g1.GetWidenedPathGeometry(new Pen(Brushes.Black, 1.0));
Geometry og2 = g2.GetWidenedPathGeometry(new Pen(Brushes.Black, 1.0));
CombinedGeometry cg = new CombinedGeometry(GeometryCombineMode.Intersect, og1, og2);
PathGeometry pg = cg.GetFlattenedPathGeometry();
foreach (PathFigure figure in pg.Figures)
{
Rect fig = new PathGeometry(new[] { figure }).Bounds;
yield return new Point(fig.Left + fig.Width / 2.0, fig.Top + fig.Height / 2.0);
}
}
public static IEnumerable<T> FindDescendantsOfType<T>(DependencyObject root, bool skipChildrenOnHit) where T : DependencyObject
{
int childCount = VisualTreeHelper.GetChildrenCount(root);
for (int i = 0; i < childCount; i++)
{
var obj = VisualTreeHelper.GetChild(root, i);
if (obj is T t)
{
yield return t;
if (skipChildrenOnHit)
{
continue;
}
}
foreach (var subChild in FindDescendantsOfType<T>(obj, skipChildrenOnHit))
{
yield return subChild;
}
}
}
}
}