using System.Text.Json; using Microsoft.UI.Xaml.Media; using Microsoft.UI.Xaml.Automation.Peers; using Microsoft.UI.Xaml.Automation.Provider; using XFEEDToolBox.Controls; using XFEEDToolBox.Core.Models; using XFEEDToolBox.Core.Outfitting; using XFEEDToolBox.Views; namespace XFEEDToolBox; public partial class App { private static async Task RunRefitAppearanceProbeAsync(string directory) { static void Require(bool value,string message){if(!value)throw new InvalidOperationException(message);} static bool Colour(Brush? brush,byte red,byte green,byte blue)=>brush is SolidColorBrush b&&b.Color.R==red&&b.Color.G==green&&b.Color.B==blue; var fixture=GetArgument(Environment.GetCommandLineArgs(),"--refit-fixture")??throw new ArgumentException("Refit fixture is required."); var document=JsonSerializer.Deserialize(await File.ReadAllTextAsync(fixture),ToolboxSettings.JsonOptions)!; MainWindow.AppWindow.Resize(new(2550,1650)); AppShellPage.Current!.Navigate(typeof(RefitPage));await Task.Delay(180); var page=(RefitPage)AppShellPage.Current.CurrentPage!;ViewModel.LoadRefitProbeDocument(document); var deadline=DateTime.UtcNow.AddSeconds(15);while(ViewModel.RefitOverview.Capacity is null&&DateTime.UtcNow0&&ViewModel.RefitOverview.Pitch>0,"Editor overview did not load calculated power and handling values."); var overview=(RefitShipOverview)page.FindName("RefitOverviewPanel");var scroller=(ScrollViewer)page.FindName("PageScroll"); var tabs=(SelectorBar)page.FindName("InspectorTabs");tabs.SelectedItem=tabs.Items[0];await Task.Delay(120); Require(overview.IsLoaded&&overview.ActualHeight>50,"Missing bottom ship overview."); Require(Colour(Descendants(tabs.Items[0]).OfType().Single(g=>g.Name=="PART_ContainerRoot").Background,255,128,0),"Selected data tab must have the game's orange fill."); Require(Colour(Descendants(tabs.Items[0]).OfType().Single(t=>t.Name=="PART_TextVisual").Foreground,0,0,0),"Selected tab text must be black."); Require(Colour(Descendants(tabs.Items[1]).OfType().Single(t=>t.Name=="PART_TextVisual").Foreground,255,128,0),"Unselected tab text must be orange."); Require(Math.Abs(tabs.Items[0].ActualWidth-tabs.Items[1].ActualWidth)<1&&tabs.Items[0].ActualWidth>tabs.ActualWidth*.45,"Inspector tabs must occupy equal halves."); static void NoScroll(SelectorBar selector) { var items=Descendants(selector).OfType().Single();var scroll=items.ScrollView; Require(scroll is not null&&scroll.HorizontalScrollBarVisibility==ScrollingScrollBarVisibility.Hidden&&scroll.HorizontalScrollMode==ScrollingScrollMode.Disabled,"Selector scrolling must be disabled."); Require(scroll!.ScrollableWidth<1,$"Selector {selector.Name} contents exceed their viewport: selector={selector.ActualWidth}, items={items.ActualWidth}, viewport={scroll.ViewportWidth}, extent={scroll.ExtentWidth}, widths={string.Join(',',selector.Items.Select(i=>i.Width))}."); } NoScroll(tabs); var overviewTabs=(SelectorBar)overview.FindName("OverviewTabs");Require(overviewTabs.Items.Count==4,"Four ship overview views are required."); var preview=(ShipModelView)overview.FindName("OverviewShipModel"); deadline=DateTime.UtcNow.AddSeconds(15);while((!preview.IsRendering||preview.Frames<2)&&DateTime.UtcNow20&&frame.Pixels[i+2]>frame.Pixels[i+1]&&frame.Pixels[i+1]>frame.Pixels[i]*2)orangePixels++; Require(orangePixels>30,"Actual GPU preview pixels must use the orange accent."); var wideHeights=new List();var narrowHeights=new List(); using(var frozen=await preview.FreezeForXamlProbeAsync()) { for(var index=0;index<4;index++) { overviewTabs.SelectedItem=overviewTabs.Items[index];await Task.Delay(100);NoScroll(overviewTabs); Require(preview.ActualWidth>70&&preview.ActualHeight>80&&preview.Visibility==Visibility.Visible,"Each overview must keep its shared ship preview visible."); wideHeights.Add(overview.ActualHeight); await CaptureRefinementElementAsync(overview,Path.Combine(directory,$"ship-overview-{index}.png")); } } Require(wideHeights.Max()-wideHeights.Min()<1,"Wide overview changes height between views: "+string.Join(",",wideHeights)); overviewTabs.SelectedItem=overviewTabs.Items[0];await Task.Delay(80); var table=(ItemsControl)page.FindName("ModulePropertyList");var mass=ViewModel.RefitProperties.Single(p=>p.OriginalName=="Mass"); Require(Descendants(table).OfType().Any(t=>t.Text==mass.Value&&Colour(t.Foreground,255,128,0)),"Module property values must be orange."); Require(Descendants(table).OfType().Any(t=>t.Text==mass.Name&&Colour(t.Foreground,128,128,128)),"Module property labels must be neutral grey."); await CaptureRefinementElementAsync((Border)page.FindName("InspectorPanel"),Path.Combine(directory,"module-data.png")); await CaptureRefinementElementAsync(overview,Path.Combine(directory,"ship-overview-wide.png")); await CapturePageAsync(page,Path.Combine(directory,"ship-editor-wide.png")); var position=overview.TransformToVisual(page).TransformPoint(new(0,0)); scroller.ChangeView(null,scroller.ScrollableHeight,null,true);await Task.Delay(80); var after=overview.TransformToVisual(page).TransformPoint(new(0,0));Require(Math.Abs(position.Y-after.Y)<1,"Bottom overview must remain visible when the editor scrolls."); tabs.SelectedItem=tabs.Items[1];await Task.Delay(80); Require(((FrameworkElement)page.FindName("ModuleEngineeringView")).Visibility==Visibility.Visible&&Colour(Descendants(tabs.Items[1]).OfType().Single(g=>g.Name=="PART_ContainerRoot").Background,255,128,0),"Engineering selection did not switch the panel and orange state."); Require(Colour(Descendants(tabs.Items[1]).OfType().Single().Foreground,0,0,0),"Selected engineering hexagon must follow the black selected foreground."); await CaptureRefinementElementAsync((Border)page.FindName("InspectorPanel"),Path.Combine(directory,"engineering-tab.png")); tabs.SelectedItem=tabs.Items[0];await Task.Delay(80); Require(ReferenceEquals(mass,ViewModel.RefitProperties.Single(p=>p.OriginalName=="Mass")),"Changing inspector tabs replaced bound property rows."); var oldPower=ViewModel.RefitOverview.Deployed;var weapon=document.Slots.First(s=>s.Category=="hardpoint"&&s.Module is {Enabled:true}); var changed=document with{Slots=document.Slots.Select(s=>s.Slot==weapon.Slot?s with{Module=s.Module! with{Enabled=false}}:s).ToArray()}; ViewModel.LoadRefitProbeDocument(changed);deadline=DateTime.UtcNow.AddSeconds(10); while(!(ViewModel.RefitOverview.Deployed200,"Narrow layout must place power below specifications."); await CaptureRefinementElementAsync(overview,Path.Combine(directory,"ship-overview-narrow.png")); narrowHeights.Add(overview.ActualHeight); using(var frozen=await preview.FreezeForXamlProbeAsync()) { for(var index=1;index<4;index++) { overviewTabs.SelectedItem=overviewTabs.Items[index];await Task.Delay(100);NoScroll(overviewTabs); Require(preview.ActualWidth>70&&preview.Visibility==Visibility.Visible,"Narrow overview must preserve its model."); narrowHeights.Add(overview.ActualHeight); await CaptureRefinementElementAsync(overview,Path.Combine(directory,$"ship-overview-narrow-{index}.png")); } } Require(narrowHeights.Max()-narrowHeights.Min()<1,"Narrow overview changes height between views: "+string.Join(",",narrowHeights)); overviewTabs.SelectedItem=overviewTabs.Items[0];await Task.Delay(60); await CapturePageAsync(page,Path.Combine(directory,"ship-editor-narrow.png")); var selectedSlot=ViewModel.RefitSlot;var currentDocument=ViewModel.RefitDocument; var detailsButton=(HyperlinkButton)overview.FindName("ShipDetailsButton"); ((IInvokeProvider)new HyperlinkButtonAutomationPeer(detailsButton).GetPattern(PatternInterface.Invoke)).Invoke();await Task.Delay(200); Require(AppShellPage.Current.CurrentPage is RefitShipDetailsPage,"Whole-ship details must open an independent page."); var details=(RefitShipDetailsPage)AppShellPage.Current.CurrentPage!; Require(ViewModel.RefitDetailSections.Any(s=>s.Record.Key=="weapons")&&ViewModel.RefitDetailSections.Any(s=>s.Record.Key=="ship"),"Details are missing weapon or complete ship values."); Require(ViewModel.RefitDetailSections.Count(s=>s.Record.Key.StartsWith("module:"))==changed.Slots.Count(s=>s.Module is not null),"Details omit installed modules."); await CapturePageAsync(details,Path.Combine(directory,"ship-details-narrow.png")); MainWindow.AppWindow.Resize(new(2550,1650));await Task.Delay(160); await CapturePageAsync(details,Path.Combine(directory,"ship-details-wide.png")); var detailsScroller=(ScrollViewer)details.FindName("DetailsScroll"); async Task ShowDetailSection(Expander section) { section.IsExpanded=true;await Task.Delay(100);details.UpdateLayout(); var top=section.TransformToVisual((UIElement)detailsScroller.Content).TransformPoint(new(0,0)).Y; detailsScroller.ChangeView(null,top,null,true);await Task.Delay(100); Require(detailsScroller.ScrollableWidth<1,"Details must not overflow horizontally."); } foreach(var header in new[]{"武器与伤害","容量、温控与造价","供电与配电",changed.Slots.First(s=>s.Module is not null).Caption}) { var section=Descendants(details).OfType().First(e=>e.Header as string==header);await ShowDetailSection(section); await CapturePageAsync(details,Path.Combine(directory,"ship-details-"+(header.StartsWith("武器")?"weapons":header.StartsWith("容量")?"capacity":header.StartsWith("供电")?"power":"module")+".png")); } var shipValues=Descendants(details).OfType().First(e=>e.Header as string=="整舰计算参数");await ShowDetailSection(shipValues); await CapturePageAsync(details,Path.Combine(directory,"ship-details-all-ship-values.png")); var backButton=(Button)details.FindName("BackToRefitButton"); ((IInvokeProvider)new ButtonAutomationPeer(backButton).GetPattern(PatternInterface.Invoke)).Invoke();await Task.Delay(150); Require(ReferenceEquals(page,AppShellPage.Current.CurrentPage)&&ReferenceEquals(currentDocument,ViewModel.RefitDocument)&&ReferenceEquals(selectedSlot,ViewModel.RefitSlot),"Returning from details changed the editor or loadout."); return new{bottomOverviewVisible=true,orangeSelectedTabWithBlackText=true,equalWidthTabs=true,greyPropertyLabelsAndOrangeValues=true, propertiesRetainIdentity=true,overviewFollowsEditedLoadout=true,overviewFixedWhileScrolling=true,narrowLayoutStacksPower=true,fourOverviewViews=true,sharedOrangeShipPreview=true,pedestalHidden=true,selectorHorizontalScrollingDisabled=true,orangePixels, uniformWideHeights=wideHeights,uniformNarrowHeights=narrowHeights,wholeShipDetailsPage=true,allInstalledModuleDetails=true,returnPreservesEditor=true, ViewModel.RefitOverview.SpeedText,ViewModel.RefitOverview.RotationText,ViewModel.RefitOverview.JumpText,ViewModel.RefitOverview.CapacityText,realGameActionsInvoked=false}; } }