Files
HC_APTBS/MainWindow.xaml
LucianoDev 827b811b39 feat: developer tools page, auto-test orchestrator, BIP display, tests redesign
Bundles several feature streams that have been iterating on the working tree:

- Developer Tools page (Debug-only via DEVELOPER_TOOLS symbol): hosts the
  identification card, manual KWP write + transaction log, ROM/EEPROM dump
  card with progress banner and completion message, persisted custom-commands
  library, persisted EEPROM passwords library. New service primitives:
  IKwpService.SendRawCustomAsync / ReadEepromAsync / ReadRomEepromAsync.
  Persistence mirrors the Clients XML pattern in two new files
  (custom_commands.xml, eeprom_passwords.xml).
- Auto-test orchestrator (IAutoTestOrchestrator + AutoTestState): linear
  K-Line read -> unlock -> bench-on -> test sequence with snackbar UI and
  progress dialog VM, gated on dashboard alarms.
- BIP-STATUS display: BipDisplayViewModel + BipDisplayView, RAM read at
  0x0106 via IKwpService.ReadBipStatusAsync; status definitions in
  BipStatusDefinition.
- Tests page redesign: TestSectionCard + PhaseTileView replacing the old
  TestPlanView/TestRunningView/TestDoneView/TestPreconditionsView/
  TestSectionView controls and their VMs.
- Pump command sliders: Fluent thick-track style with overhang thumb,
  click-anywhere-and-drag, mouse-wheel adjustment.
- Window startup: app.manifest declares PerMonitorV2 DPI awareness,
  MainWindow installs a WM_GETMINMAXINFO hook in OnSourceInitialized and
  maximizes there (after the hook is in place) so the app fits the work
  area exactly on any display configuration.
- Misc: PercentToPixelsConverter, seed_aliases.py one-shot pump-alias
  importer, tools/Import-BipStatus.ps1, kline_eeprom_spec.md and
  dump-functions reference docs.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-05-07 13:59:50 +02:00

292 lines
16 KiB
XML

<ui:FluentWindow x:Class="HC_APTBS.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:ui="http://schemas.lepo.co/wpfui/2022/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:vm="clr-namespace:HC_APTBS.ViewModels"
xmlns:uc="clr-namespace:HC_APTBS.Views.UserControls"
xmlns:pages="clr-namespace:HC_APTBS.Views.Pages"
xmlns:models="clr-namespace:HC_APTBS.Models"
mc:Ignorable="d"
Title="{DynamicResource App.Title}"
MinHeight="600" MinWidth="900"
WindowStartupLocation="CenterScreen"
FontFamily="Ebrima"
Closing="OnWindowClosing">
<DockPanel>
<!-- ── WPF-UI custom title bar (replaces OS chrome) ──────────────────── -->
<ui:TitleBar DockPanel.Dock="Top"
Title="{DynamicResource App.Title}"
ShowMaximize="True"
ShowMinimize="True"/>
<!-- ── Persistent app header: pump identification + connection state ──── -->
<Border DockPanel.Dock="Top" Background="Transparent"
BorderBrush="#999" BorderThickness="0,0,0,1" Visibility="Collapsed">
<Grid Margin="4,2">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<!-- Pump selector + K-Line ECU info -->
<uc:PumpIdentificationView DataContext="{Binding PumpIdentification}"/>
<!-- Connection indicators + CAN connect/disconnect -->
<StackPanel Grid.Column="1" Orientation="Horizontal"
VerticalAlignment="Center" Margin="8,0,0,0">
<TextBlock Text="{DynamicResource Status.Label}" VerticalAlignment="Center"
FontSize="10" Margin="0,0,6,0"/>
<Border Width="54" Margin="2,0">
<Border.Style>
<Style TargetType="Border" BasedOn="{StaticResource ConnIndicator}">
<Style.Triggers>
<DataTrigger Binding="{Binding IsCanConnected}" Value="True">
<Setter Property="Background" Value="#26C200"/>
</DataTrigger>
</Style.Triggers>
</Style>
</Border.Style>
<TextBlock Text="{DynamicResource Status.Can}" HorizontalAlignment="Center"
FontSize="10" Padding="2"/>
</Border>
<Border Width="54" Margin="2,0">
<Border.Style>
<Style TargetType="Border" BasedOn="{StaticResource ConnIndicator}">
<Style.Triggers>
<DataTrigger Binding="{Binding IsBenchConnected}" Value="True">
<Setter Property="Background" Value="#26C200"/>
</DataTrigger>
</Style.Triggers>
</Style>
</Border.Style>
<TextBlock Text="{DynamicResource Status.Bench}" HorizontalAlignment="Center"
FontSize="10" Padding="2"/>
</Border>
<Border Width="54" Margin="2,0">
<Border.Style>
<Style TargetType="Border" BasedOn="{StaticResource ConnIndicator}">
<Style.Triggers>
<DataTrigger Binding="{Binding IsPumpConnected}" Value="True">
<Setter Property="Background" Value="#26C200"/>
</DataTrigger>
</Style.Triggers>
</Style>
</Border.Style>
<TextBlock Text="{DynamicResource Status.Pump}" HorizontalAlignment="Center"
FontSize="10" Padding="2"/>
</Border>
<Border Width="54" Margin="2,0,8,0">
<Border.Style>
<Style TargetType="Border" BasedOn="{StaticResource ConnIndicator}">
<Style.Triggers>
<DataTrigger Binding="{Binding KLineState}" Value="{x:Static models:KLineConnectionState.Connected}">
<Setter Property="Background" Value="#26C200"/>
</DataTrigger>
<DataTrigger Binding="{Binding KLineState}" Value="{x:Static models:KLineConnectionState.Failed}">
<Setter Property="Background" Value="#FF3333"/>
</DataTrigger>
</Style.Triggers>
</Style>
</Border.Style>
<TextBlock Text="{DynamicResource Status.KLine}" HorizontalAlignment="Center"
FontSize="10" Padding="2"/>
</Border>
<Button Content="{DynamicResource Bench.ConnectCan}" Width="110" Margin="4,0,4,0"
Command="{Binding ConnectCanCommand}"/>
<Button Content="{DynamicResource Bench.DisconnectCan}" Width="120"
Command="{Binding DisconnectCanCommand}"
IsEnabled="{Binding IsCanConnected}"/>
</StackPanel>
</Grid>
</Border>
<!-- ── Status bar ─────────────────────────────────────────────────── -->
<StatusBar DockPanel.Dock="Bottom" Height="24" Background="#FFD0D0D0" Visibility="Collapsed">
<StatusBarItem>
<TextBlock Text="{Binding CanStatusText}" Margin="4,0"/>
</StatusBarItem>
<Separator/>
<StatusBarItem>
<TextBlock Text="{Binding VerboseStatus}" Margin="4,0"/>
</StatusBarItem>
</StatusBar>
<!-- ── Main content: Fluent NavigationView (selector only) + hidden-tab page host ──── -->
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<!--
NavigationView used as a pure selector — page hosting stays with
the TabControl in column 1. PaneDisplayMode=Left gives the
standard Fluent expanded rail with icon + text per item and
animated selection indicator.
-->
<ui:NavigationView x:Name="NavView"
Grid.Column="0"
PaneDisplayMode="Left"
OpenPaneLength="180"
IsBackButtonVisible="Collapsed"
IsPaneToggleVisible="False"
ItemTemplate="{StaticResource BoxyNavItemTemplate}">
<ui:NavigationView.MenuItems>
<ui:NavigationViewItem Content="{DynamicResource Nav.Dashboard}"
Tag="{x:Static vm:AppPage.Dashboard}"
TargetPageTag="Dashboard">
<ui:NavigationViewItem.Icon>
<ui:SymbolIcon Symbol="Home28"/>
</ui:NavigationViewItem.Icon>
</ui:NavigationViewItem>
<ui:NavigationViewItem Content="{DynamicResource Nav.Bench}"
Tag="{x:Static vm:AppPage.Bench}"
TargetPageTag="Bench">
<ui:NavigationViewItem.Icon>
<ui:SymbolIcon Symbol="ArrowSyncCircle24"/>
</ui:NavigationViewItem.Icon>
</ui:NavigationViewItem>
<ui:NavigationViewItem Content="{DynamicResource Nav.Pump}"
Tag="{x:Static vm:AppPage.Pump}"
TargetPageTag="Pump">
<ui:NavigationViewItem.Icon >
<ui:SymbolIcon Symbol="PulseSquare24" />
</ui:NavigationViewItem.Icon>
</ui:NavigationViewItem>
<ui:NavigationViewItem Content="{DynamicResource Nav.Tests}"
Tag="{x:Static vm:AppPage.Tests}"
TargetPageTag="Tests">
<ui:NavigationViewItem.Icon>
<ui:SymbolIcon Symbol="BeakerEdit24"/>
</ui:NavigationViewItem.Icon>
</ui:NavigationViewItem>
<ui:NavigationViewItem Content="{DynamicResource Nav.Results}"
Tag="{x:Static vm:AppPage.Results}"
TargetPageTag="Results">
<ui:NavigationViewItem.Icon>
<ui:SymbolIcon Symbol="DocumentTable24"/>
</ui:NavigationViewItem.Icon>
</ui:NavigationViewItem>
</ui:NavigationView.MenuItems>
<ui:NavigationView.FooterMenuItems>
<ui:NavigationViewItem Content="{DynamicResource Nav.Settings}"
Tag="{x:Static vm:AppPage.Settings}"
TargetPageTag="Settings">
<ui:NavigationViewItem.Icon>
<ui:SymbolIcon Symbol="Wrench24"/>
</ui:NavigationViewItem.Icon>
</ui:NavigationViewItem>
</ui:NavigationView.FooterMenuItems>
</ui:NavigationView>
<!-- Page host: hidden-tab TabControl + bottom-snackbar overlays -->
<Grid Grid.Column="1">
<!--
TabItem order must match AppPage enum values:
Bench=0, Pump=1, Tests=2, Dashboard=3, Settings=4, Results=5.
-->
<TabControl x:Name="PageTabs"
Style="{StaticResource HiddenTabsTabControl}"
SelectedIndex="{Binding SelectedPage, Converter={StaticResource EnumToInt}}">
<TabItem>
<pages:BenchPage DataContext="{Binding BenchPage}"/>
</TabItem>
<TabItem>
<pages:PumpPage DataContext="{Binding PumpPage}"/>
</TabItem>
<TabItem>
<pages:TestsPage DataContext="{Binding TestsPage}"/>
</TabItem>
<TabItem>
<pages:DashboardPage DataContext="{Binding DashboardPage}"/>
</TabItem>
<TabItem>
<pages:SettingsPage DataContext="{Binding SettingsPage}"/>
</TabItem>
<TabItem>
<pages:ResultsPage DataContext="{Binding ResultsPage}"/>
</TabItem>
</TabControl>
<uc:UnlockSnackbarView DataContext="{Binding CurrentUnlockVm}"
HorizontalAlignment="Center"
VerticalAlignment="Bottom"
Margin="0,0,0,8"/>
<uc:AutoTestSnackbarView DataContext="{Binding AutoTestProgress}"
HorizontalAlignment="Center"
VerticalAlignment="Bottom"
Margin="0,0,0,8"/>
<!-- Dashboard CAN connect/disconnect snackbar (page-bottom, matches other snackbars) -->
<Border DataContext="{Binding DashboardPage.Devices.Transition}"
Visibility="{Binding Converter={StaticResource NullToVis}}"
Style="{StaticResource SnackbarShell}"
HorizontalAlignment="Center" VerticalAlignment="Bottom"
MinWidth="340" MaxWidth="640"
Margin="0,0,0,8" Padding="16,10">
<DockPanel LastChildFill="True">
<Grid DockPanel.Dock="Left" Width="22" Height="22" Margin="0,0,10,0">
<ui:ProgressRing IsIndeterminate="True" Width="18" Height="18"
Visibility="{Binding IsBusy, Converter={StaticResource BoolToVis}}"/>
<ui:SymbolIcon Symbol="CheckmarkCircle24" FontSize="18"
Foreground="{DynamicResource SystemFillColorSuccessBrush}">
<ui:SymbolIcon.Style>
<Style TargetType="ui:SymbolIcon">
<Setter Property="Visibility" Value="Collapsed"/>
<Style.Triggers>
<MultiDataTrigger>
<MultiDataTrigger.Conditions>
<Condition Binding="{Binding IsBusy}" Value="False"/>
<Condition Binding="{Binding IsSuccess}" Value="True"/>
</MultiDataTrigger.Conditions>
<Setter Property="Visibility" Value="Visible"/>
</MultiDataTrigger>
</Style.Triggers>
</Style>
</ui:SymbolIcon.Style>
</ui:SymbolIcon>
<ui:SymbolIcon Symbol="ErrorCircle24" FontSize="18"
Foreground="{DynamicResource SystemFillColorCriticalBrush}">
<ui:SymbolIcon.Style>
<Style TargetType="ui:SymbolIcon">
<Setter Property="Visibility" Value="Collapsed"/>
<Style.Triggers>
<MultiDataTrigger>
<MultiDataTrigger.Conditions>
<Condition Binding="{Binding IsBusy}" Value="False"/>
<Condition Binding="{Binding IsSuccess}" Value="False"/>
</MultiDataTrigger.Conditions>
<Setter Property="Visibility" Value="Visible"/>
</MultiDataTrigger>
</Style.Triggers>
</Style>
</ui:SymbolIcon.Style>
</ui:SymbolIcon>
</Grid>
<TextBlock Text="{Binding Message}"
VerticalAlignment="Center"
FontSize="13"
TextTrimming="CharacterEllipsis"
Foreground="{DynamicResource TextFillColorPrimaryBrush}"/>
</DockPanel>
</Border>
</Grid>
</Grid>
</DockPanel>
</ui:FluentWindow>