using System.Text.Json; using Microsoft.UI.Xaml.Media; using Microsoft.UI.Xaml.Media.Imaging; using XFEEDToolBox.Controls; using XFEEDToolBox.Core.Navigation; using XFEEDToolBox.Core.Rendering; using XFEEDToolBox.Views; namespace XFEEDToolBox; public partial class App { // This fixture only displays original icon resources in an isolated offline // instance. It cannot bind a flight driver or read the player's Journal. private static async Task RunGameIconProbeAsync(string reportPath) { var directory = Path.GetDirectoryName(Path.GetFullPath(reportPath))!; Directory.CreateDirectory(directory); var samples = new List(); var refitSources = new List(); string? failure = null; static void Require(bool value, string message) { if (!value) throw new InvalidOperationException(message); } try { Require(ViewModel.OfflinePreview && Runtime.FlightDriver is null && Runtime.Journal.Sequence == 0, "Game icon fixture requires isolated --offline-preview."); MainWindow.AppWindow.Move(new(-30000, -30000)); MainWindow.AppWindow.Resize(new(2560, 2200)); MainWindow.AppWindow.Show(false); var shell = AppShellPage.Current!; shell.Navigate(typeof(CurrentSystemPage)); await Task.Delay(200); var page = (CurrentSystemPage)shell.CurrentPage!; var types = new (int Kind, SystemLocationCategory Category, string Name)[] { (1, SystemLocationCategory.Star, "HIP 12099"), (19, SystemLocationCategory.Asteroid, "HIP 12099 A Belt Cluster 2"), (3, SystemLocationCategory.Planet, "HIP 12099 1"), (2, SystemLocationCategory.LandablePlanet, "HIP 12099 1 b"), (30, SystemLocationCategory.Settlement, "Jameson Crash Site"), (18, SystemLocationCategory.Station, "Ray Enterprise"), (14, SystemLocationCategory.Carrier, "SPACEBALL1 | L1NK"), (22, SystemLocationCategory.Signal, "$USS_DegradedEmissions;"), (23, SystemLocationCategory.PointOfInterest, "$MULTIPLAYER_SCENARIO42_TITLE;"), (0, SystemLocationCategory.PointOfInterest, "Interest point"), (15, SystemLocationCategory.Carrier, "Squadron carrier") }; var places = types.Select((x, i) => new SystemLocation(i + 1, (ulong)i + 100, x.Name, "", x.Kind, x.Category, null, null, (i + 1) * 299792458d, i == 5, "icon-fixture-" + i)).ToArray(); ViewModel.ApplyCurrentSystemSnapshot(new(1, "HIP 12099", 1, 1, DateTimeOffset.UtcNow, places)); await Task.Delay(300); page.UpdateLayout(); var list = Descendants(page).OfType().Single(); foreach (var row in ViewModel.CurrentSystemLocations) { var container = list.ContainerFromItem(row) as FrameworkElement; Require(container is not null, "Location fixture did not fit in its viewport."); var icon = Descendants(container).OfType().First(); var name = Descendants(container).OfType().First(x => x.Text == row.Name); var distance = Descendants(container).OfType().First(x => x.Text == row.Distance); var iconPosition = icon.TransformToVisual(page).TransformPoint(new()); var namePosition = name.TransformToVisual(page).TransformPoint(new()); var distancePosition = distance.TransformToVisual(page).TransformPoint(new()); Require(icon.IconKey == row.IconKey && icon.ActualWidth > 0 && icon.ActualHeight > 0, "Location's actual XAML icon binding did not load."); Require(iconPosition.X + icon.ActualWidth <= namePosition.X && namePosition.X + name.ActualWidth <= distancePosition.X, "Location icon, name or distance overlaps."); samples.Add(new { row.Name, kind = row.Record.NativeKind, icon = icon.IconKey.ToString(), iconX = iconPosition.X, iconY = iconPosition.Y, icon.ActualWidth, icon.ActualHeight, nameX = namePosition.X, nameRight = namePosition.X + name.ActualWidth, distanceX = distancePosition.X }); } await CapturePageAsync(page, Path.Combine(directory, "current-system-original-icons.png")); shell.Navigate(typeof(RefitPage)); await Task.Delay(200); var refit = (RefitPage)shell.CurrentPage!; var categories = (ListView)refit.FindName("RefitCategoryList"); foreach (var category in ViewModel.RefitCategories) { var container = categories.ContainerFromItem(category); var source = Descendants(container).OfType().Select(x => x.Source).OfType().Single(); Require(source.UriSource.AbsoluteUri == category.Icon, "Refit category did not bind its original game SVG."); refitSources.Add(source.UriSource.AbsoluteUri); } var tabs = (SelectorBar)refit.FindName("InspectorTabs"); tabs.SelectedItem = tabs.Items[1]; await Task.Delay(100); refit.UpdateLayout(); Require(tabs.ActualHeight <= 100 && Descendants(tabs).OfType().All(x => x.ActualWidth <= 24 && x.ActualHeight <= 24), "Refit's engineer icon expanded the inspector tab instead of keeping its icon size."); await CapturePageAsync(refit, Path.Combine(directory, "refit-original-icons.png")); shell.Navigate(typeof(CurrentSystemPage)); await Task.Delay(100); // Render the actual filter content without showing a Popup, which // could be repositioned by Windows onto the player's foreground. var filter = (Flyout)Descendants(page).OfType().Single().Flyout; var filterContent = (FrameworkElement)filter.Content; filter.Content = null; var panel = new StackPanel { Spacing = 16 }; panel.Children.Add(new TextBlock { Text = "当前星系 · 游戏原始筛选图标", FontSize = 24 }); panel.Children.Add(filterContent); var filterPage = new Page { Background = page.Background, Content = new Border { Padding = new(32), Child = panel, Background = (Brush)Current.Resources["PanelBrush"] } }; MainWindow.AppWindow.Resize(new(900, 1450)); MainWindow.Content = filterPage; await CapturePageAsync(filterPage, Path.Combine(directory, "current-system-filters.png")); Require(Descendants(filterContent).OfType().Count() == 10, "A filter category has no original icon."); var grid = new Grid { ColumnSpacing = 16, RowSpacing = 10, Margin = new(28) }; for (var column = 0; column < 6; column++) grid.ColumnDefinitions.Add(new()); var icons = Enum.GetValues(); for (var index = 0; index < icons.Length; index++) { if (index % 6 == 0) grid.RowDefinitions.Add(new() { Height = GridLength.Auto }); var card = new StackPanel { Spacing = 5, Margin = new(4), HorizontalAlignment = HorizontalAlignment.Stretch }; card.Children.Add(new GameIcon { IconKey = icons[index], Width = 46, Height = 46, HorizontalAlignment = HorizontalAlignment.Center }); card.Children.Add(new TextBlock { Text = icons[index].ToString(), FontSize = 12, TextAlignment = TextAlignment.Center, MaxWidth = 180, TextWrapping = TextWrapping.Wrap, HorizontalAlignment = HorizontalAlignment.Center }); Grid.SetColumn(card, index % 6); Grid.SetRow(card, index / 6); grid.Children.Add(card); } grid.RowDefinitions.Add(new() { Height = GridLength.Auto }); var rating = new RatingControl { MaxRating = 5, Value = 3, ItemInfo = new RatingItemImageInfo { Image = new BitmapImage(new("ms-appx:///Assets/GameIcons/EngineeringGradeSelected.png")), UnsetImage = new BitmapImage(new("ms-appx:///Assets/GameIcons/EngineeringGradeEmpty.png")) } }; Grid.SetRow(rating, grid.RowDefinitions.Count - 1); Grid.SetColumnSpan(rating, 6); grid.Children.Add(rating); var catalogue = new Page { Background = (Brush)Current.Resources["PanelBrush"], Content = grid }; MainWindow.AppWindow.Resize(new(2200, 1800)); MainWindow.Content = catalogue; await CapturePageAsync(catalogue, Path.Combine(directory, "game-icon-catalogue.png")); Require(Descendants(catalogue).OfType().Count() == icons.Length, "Incomplete original icon catalogue."); } catch (Exception error) { failure = error.ToString(); } await File.WriteAllTextAsync(reportPath, JsonSerializer.Serialize(new { samples, refitSources, failure, uiError = ViewModel.ErrorMessage, fixtureOnly = true, gameChannelBound = Runtime.FlightDriver is not null, journalEvents = Runtime.Journal.Sequence }, new JsonSerializerOptions { WriteIndented = true })); MainWindow.Close(); } }