using System.Collections.ObjectModel;
using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Controls;
using Microsoft.UI.Xaml.Media;
using Microsoft.UI.Xaml.Media.Imaging;
using System.Runtime.InteropServices.WindowsRuntime;
using Windows.Graphics.Imaging;
namespace XFEWarframe.Desktop.Editing;
internal static class RivenVerification
{
internal static async Task VerifyAsync(FrameworkElement page, ObservableCollection<RivenStatRow> rows, Func<Task> calculate, Func<string> name, bool dialogs)
{
var positive = rows[0];
positive.SelectedTag = positive.Tags.Single(x => x.Key == "WeaponFireDamageMod");
var negative = rows[1];
negative.SelectedTag = negative.Tags.Single(x => x.Key == "WeaponCritChanceMod");
negative.Negative = true;
await calculate();
if (positive.Result.Contains('|') || !positive.Result.Contains('%') || !negative.Result.StartsWith('-') || name().Contains("Mod"))
throw new InvalidOperationException("Riven title or formatted stat values are incorrect.");
if (positive.Tags.Any(x => x.DisplayText.Contains('|') || !x.DisplayText.Any(char.IsDigit)))
throw new InvalidOperationException("Riven alternatives contain placeholders or omit calculated values.");
var original = positive.Result;
positive.Value = 11000;
await UntilAsync(() => positive.Result.Length > 0 && positive.Result != original && positive.Result.Contains('%'), "Riven preview did not refresh after changing quality.");
var expected = positive.Tags.Single(x => x.Key == positive.SelectedTag.Key).DisplayValue!.Value;
positive.ModeIndex = 1;
if (positive.Value != expected) throw new InvalidOperationException("Actual-value mode retained the quality number.");
await UntilAsync(() => positive.Result.Contains('%'), "Actual-value preview did not refresh.");
if (!dialogs) return;
var launcher = (FormDialogButton)page.FindName("RivenEditorButton");
var showing = launcher.OpenAsync();
try
{
await UntilAsync(() => launcher.ActiveDialog?.IsLoaded == true, "Riven dialog did not load.");
var dialog = launcher.ActiveDialog!;
dialog.UpdateLayout();
var texts = DialogVerification.Children<TextBlock>(dialog).ToArray();
var fire = texts.Single(x => x.Text == positive.Result && RivenStatText.GetTone(x) == "DT_FIRE_COLOR");
var curse = texts.Single(x => x.Text == negative.Result && RivenStatText.GetTone(x) == "negative");
if (((SolidColorBrush)fire.Foreground).Color == ((SolidColorBrush)curse.Foreground).Color)
throw new InvalidOperationException("Element and negative stat colors were not applied.");
var picker = DialogVerification.Children<AutoSuggestBox>(dialog).First(x => ReferenceEquals(Suggestions.GetCandidates(x), positive.Tags));
picker.Focus(FocusState.Programmatic);
await UntilAsync(() => picker.IsSuggestionListOpen, "Riven suggestion list did not open.");
await UntilAsync(() => VisualTreeHelper.GetOpenPopupsForXamlRoot(dialog.XamlRoot).Any(x =>
DialogVerification.Children<TextBlock>(x.Child).Any(t => t.Text == positive.Tags[0].DisplayText && t.IsLoaded)),
"Riven suggestion list did not render its numeric alternatives.");
var popup = VisualTreeHelper.GetOpenPopupsForXamlRoot(dialog.XamlRoot).First(x =>
DialogVerification.Children<TextBlock>(x.Child).Any(t => t.Text == positive.Tags[0].DisplayText));
var report = Environment.GetCommandLineArgs().First(x => x.StartsWith("--verify-ui=", StringComparison.Ordinal))["--verify-ui=".Length..];
await Task.Delay(250); // Capture after the native popup's opening animation.
await CaptureAsync(popup.Child, Path.ChangeExtension(report, ".riven.png"));
if (!DialogVerification.Children<TextBlock>(popup.Child).Any(x => RivenStatText.GetTone(x) == "DT_FIRE_COLOR"))
throw new InvalidOperationException("Riven suggestion template did not show colored numeric alternatives.");
positive.ModeIndex = 0; positive.Value = 10000;
await UntilAsync(() => positive.Result.Contains('%'), "Riven quality mode did not refresh.");
positive.ModeIndex = 1;
await UntilAsync(() => positive.Result.Contains('%'), "Riven actual-value mode did not refresh.");
var alternate = positive.Tags.Single(x => x.Key == "WeaponCritChanceMod");
var alternateValue = alternate.DisplayValue;
await SuggestionVerification.TypeAsync(picker, alternate.Label);
await SuggestionVerification.SubmitAsync(picker);
await UntilAsync(() => positive.Result.Contains('%'), "Riven selected stat did not refresh.");
if (!ReferenceEquals(positive.SelectedTag, alternate) || positive.Value != alternateValue || picker.Text != alternate.Label)
throw new InvalidOperationException("Riven numeric suggestion selection did not preserve its label and calculated value.");
}
finally { launcher.ActiveDialog?.Hide(); await showing; }
}
private static async Task UntilAsync(Func<bool> condition, string error)
{
for (var i = 0; i < 200 && !condition(); i++) await Task.Delay(20);
if (!condition()) throw new InvalidOperationException(error);
}
private static async Task CaptureAsync(UIElement element, string path)
{
var bitmap = new RenderTargetBitmap();
await bitmap.RenderAsync(element);
var pixels = (await bitmap.GetPixelsAsync()).ToArray();
using var file = File.Create(path);
using var stream = file.AsRandomAccessStream();
var encoder = await BitmapEncoder.CreateAsync(BitmapEncoder.PngEncoderId, stream);
encoder.SetPixelData(BitmapPixelFormat.Bgra8, BitmapAlphaMode.Premultiplied, (uint)bitmap.PixelWidth, (uint)bitmap.PixelHeight, 96, 96, pixels);
await encoder.FlushAsync();
}
}
using System.Collections.ObjectModel;
using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Controls;
using Microsoft.UI.Xaml.Media;
using Microsoft.UI.Xaml.Media.Imaging;
using System.Runtime.InteropServices.WindowsRuntime;
using Windows.Graphics.Imaging;
namespace XFEWarframe.Desktop.Editing;
internal static class RivenVerification
{
internal static async Task VerifyAsync(FrameworkElement page, ObservableCollection<RivenStatRow> rows, Func<Task> calculate, Func<string> name, bool dialogs)
{
var positive = rows[0];
positive.SelectedTag = positive.Tags.Single(x => x.Key == "WeaponFireDamageMod");
var negative = rows[1];
negative.SelectedTag = negative.Tags.Single(x => x.Key == "WeaponCritChanceMod");
negative.Negative = true;
await calculate();
if (positive.Result.Contains('|') || !positive.Result.Contains('%') || !negative.Result.StartsWith('-') || name().Contains("Mod"))
throw new InvalidOperationException("Riven title or formatted stat values are incorrect.");
if (positive.Tags.Any(x => x.DisplayText.Contains('|') || !x.DisplayText.Any(char.IsDigit)))
throw new InvalidOperationException("Riven alternatives contain placeholders or omit calculated values.");
var original = positive.Result;
positive.Value = 11000;
await UntilAsync(() => positive.Result.Length > 0 && positive.Result != original && positive.Result.Contains('%'), "Riven preview did not refresh after changing quality.");
var expected = positive.Tags.Single(x => x.Key == positive.SelectedTag.Key).DisplayValue!.Value;
positive.ModeIndex = 1;
if (positive.Value != expected) throw new InvalidOperationException("Actual-value mode retained the quality number.");
await UntilAsync(() => positive.Result.Contains('%'), "Actual-value preview did not refresh.");
if (!dialogs) return;
var launcher = (FormDialogButton)page.FindName("RivenEditorButton");
var showing = launcher.OpenAsync();
try
{
await UntilAsync(() => launcher.ActiveDialog?.IsLoaded == true, "Riven dialog did not load.");
var dialog = launcher.ActiveDialog!;
dialog.UpdateLayout();
var texts = DialogVerification.Children<TextBlock>(dialog).ToArray();
var fire = texts.Single(x => x.Text == positive.Result && RivenStatText.GetTone(x) == "DT_FIRE_COLOR");
var curse = texts.Single(x => x.Text == negative.Result && RivenStatText.GetTone(x) == "negative");
if (((SolidColorBrush)fire.Foreground).Color == ((SolidColorBrush)curse.Foreground).Color)
throw new InvalidOperationException("Element and negative stat colors were not applied.");
var picker = DialogVerification.Children<AutoSuggestBox>(dialog).First(x => ReferenceEquals(Suggestions.GetCandidates(x), positive.Tags));
picker.Focus(FocusState.Programmatic);
await UntilAsync(() => picker.IsSuggestionListOpen, "Riven suggestion list did not open.");
await UntilAsync(() => VisualTreeHelper.GetOpenPopupsForXamlRoot(dialog.XamlRoot).Any(x =>
DialogVerification.Children<TextBlock>(x.Child).Any(t => t.Text == positive.Tags[0].DisplayText && t.IsLoaded)),
"Riven suggestion list did not render its numeric alternatives.");
var popup = VisualTreeHelper.GetOpenPopupsForXamlRoot(dialog.XamlRoot).First(x =>
DialogVerification.Children<TextBlock>(x.Child).Any(t => t.Text == positive.Tags[0].DisplayText));
var report = Environment.GetCommandLineArgs().First(x => x.StartsWith("--verify-ui=", StringComparison.Ordinal))["--verify-ui=".Length..];
await Task.Delay(250); // Capture after the native popup's opening animation.
await CaptureAsync(popup.Child, Path.ChangeExtension(report, ".riven.png"));
if (!DialogVerification.Children<TextBlock>(popup.Child).Any(x => RivenStatText.GetTone(x) == "DT_FIRE_COLOR"))
throw new InvalidOperationException("Riven suggestion template did not show colored numeric alternatives.");
positive.ModeIndex = 0; positive.Value = 10000;
await UntilAsync(() => positive.Result.Contains('%'), "Riven quality mode did not refresh.");
positive.ModeIndex = 1;
await UntilAsync(() => positive.Result.Contains('%'), "Riven actual-value mode did not refresh.");
var alternate = positive.Tags.Single(x => x.Key == "WeaponCritChanceMod");
var alternateValue = alternate.DisplayValue;
await SuggestionVerification.TypeAsync(picker, alternate.Label);
await SuggestionVerification.SubmitAsync(picker);
await UntilAsync(() => positive.Result.Contains('%'), "Riven selected stat did not refresh.");
if (!ReferenceEquals(positive.SelectedTag, alternate) || positive.Value != alternateValue || picker.Text != alternate.Label)
throw new InvalidOperationException("Riven numeric suggestion selection did not preserve its label and calculated value.");
}
finally { launcher.ActiveDialog?.Hide(); await showing; }
}
private static async Task UntilAsync(Func<bool> condition, string error)
{
for (var i = 0; i < 200 && !condition(); i++) await Task.Delay(20);
if (!condition()) throw new InvalidOperationException(error);
}
private static async Task CaptureAsync(UIElement element, string path)
{
var bitmap = new RenderTargetBitmap();
await bitmap.RenderAsync(element);
var pixels = (await bitmap.GetPixelsAsync()).ToArray();
using var file = File.Create(path);
using var stream = file.AsRandomAccessStream();
var encoder = await BitmapEncoder.CreateAsync(BitmapEncoder.PngEncoderId, stream);
encoder.SetPixelData(BitmapPixelFormat.Bgra8, BitmapAlphaMode.Premultiplied, (uint)bitmap.PixelWidth, (uint)bitmap.PixelHeight, 96, 96, pixels);
await encoder.FlushAsync();
}
}