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>
This commit is contained in:
2026-05-07 13:59:50 +02:00
parent da0581967b
commit 827b811b39
102 changed files with 7522 additions and 1798 deletions

View File

@@ -42,9 +42,9 @@
<StackPanel Grid.Row="2" Grid.Column="1"
Orientation="Horizontal" HorizontalAlignment="Right"
Margin="0,12,0,0">
<Button Content="{Binding ConfirmText}" MinWidth="80" Height="26" Margin="0,0,8,0"
<Button Content="{Binding ConfirmText}" MinWidth="80" Height="32" Margin="0,0,8,0"
Command="{Binding ConfirmCommand}" IsDefault="True"/>
<Button Content="{Binding CancelText}" MinWidth="80" Height="26"
<Button Content="{Binding CancelText}" MinWidth="80" Height="32"
Command="{Binding CancelCommand}" IsCancel="True"/>
</StackPanel>
</Grid>

View File

@@ -5,7 +5,7 @@
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
mc:Ignorable="d"
Title="{DynamicResource Dialog.OilPump.Title}"
Height="220" Width="440"
Height="260" Width="440"
ResizeMode="NoResize"
WindowStartupLocation="CenterOwner">
@@ -48,9 +48,9 @@
<!-- Buttons -->
<StackPanel Grid.Row="4" Grid.Column="1"
Orientation="Horizontal" HorizontalAlignment="Right" Margin="0,12,0,0">
<Button Content="{DynamicResource Common.Accept}" Width="80" Height="26" Margin="0,0,8,0"
<Button Content="{DynamicResource Common.Accept}" Width="80" Height="36" Margin="0,0,8,0"
Command="{Binding AcceptCommand}"/>
<Button Content="{DynamicResource Common.Cancel}" Width="80" Height="26"
<Button Content="{DynamicResource Common.Cancel}" Width="80" Height="36"
Command="{Binding CancelCommand}" IsCancel="True"/>
</StackPanel>
</Grid>

View File

@@ -5,7 +5,7 @@
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
mc:Ignorable="d"
Title="{DynamicResource Dialog.RpmSafety.Title}"
Height="260" Width="460"
Height="280" Width="460"
ResizeMode="NoResize"
WindowStartupLocation="CenterOwner">
@@ -54,9 +54,9 @@
<!-- Buttons -->
<StackPanel Grid.Row="5" Grid.Column="1"
Orientation="Horizontal" HorizontalAlignment="Right" Margin="0,12,0,0">
<Button Content="{DynamicResource Common.Accept}" Width="80" Height="26" Margin="0,0,8,0"
<Button Content="{DynamicResource Common.Accept}" Width="80" Height="32" Margin="0,0,8,0"
Command="{Binding AcceptCommand}"/>
<Button Content="{DynamicResource Common.Cancel}" Width="80" Height="26"
<Button Content="{DynamicResource Common.Cancel}" Width="80" Height="32"
Command="{Binding CancelCommand}" IsCancel="True"/>
</StackPanel>
</Grid>

View File

@@ -22,8 +22,8 @@
</Grid.RowDefinitions>
<!-- Interlock banner — hidden unless an interlock condition is active -->
<uc:InterlockBannerView Grid.Row="0" DataContext="{Binding Interlock}"
Margin="0,0,0,8"/>
<uc:InterlockBannerView Grid.Row="0" DataContext="{Binding Interlock}" Height="66"
Margin="0,0,0,0"/>
<!-- Three-column body -->
<Grid Grid.Row="1">

View File

@@ -25,7 +25,7 @@
</Grid.RowDefinitions>
<!-- ── Row 0: Connection strip ─────────────────────────────────────── -->
<uc:DashboardConnectionView Grid.Row="0"/>
<uc:DashboardConnectionView Grid.Row="0" Height="66"/>
<!-- ── Row 1: KPI grid (left) + devices column (centre) + test/alarms (right) -->
<Grid Grid.Row="1" Margin="0,0,0,8">
@@ -36,13 +36,13 @@
</Grid.ColumnDefinitions>
<!-- KPI readings grid -->
<uc:DashboardReadingsView Grid.Column="0" Margin="0,0,4,0"/>
<uc:DashboardReadingsView Grid.Column="0" Margin="0,0,0,0"/>
<!-- Devices column: CAN / K-Line / Bench device tiles -->
<uc:DashboardDevicesView Grid.Column="1" Margin="4,0,4,0"/>
<uc:DashboardDevicesView Grid.Column="1" Margin="4"/>
<!-- Right column: test summary + alarms -->
<Grid Grid.Column="2" Margin="4,0,0,0">
<Grid Grid.Column="2" Margin="4">
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="*"/>
@@ -315,18 +315,38 @@
</ui:Button.Icon>
</ui:Button>
<!-- Start Test -->
<!-- Connect & Auto Test — transforms into Cancel while running -->
<ui:Button DockPanel.Dock="Left"
Appearance="Primary"
Content="{DynamicResource Dashboard.Action.StartTest}"
Command="{Binding Root.StartTestCommand}"
MinWidth="160" Height="46"
MinWidth="200" Height="46"
Margin="0,0,8,0"
ToolTipService.ShowOnDisabled="True"
ToolTip="{DynamicResource Dashboard.Action.StartTest.Tip}">
<ui:Button.Icon>
<ui:SymbolIcon Symbol="PlayCircle24"/>
</ui:Button.Icon>
ToolTipService.ShowOnDisabled="True">
<ui:Button.Style>
<Style TargetType="ui:Button" BasedOn="{StaticResource {x:Type ui:Button}}">
<!-- Default (idle): Connect & Auto Test -->
<Setter Property="Appearance" Value="Primary"/>
<Setter Property="Content" Value="{DynamicResource Dashboard.Action.AutoTest}"/>
<Setter Property="Command" Value="{Binding Root.ConnectAndAutoTestCommand}"/>
<Setter Property="ToolTip" Value="{DynamicResource Dashboard.Action.AutoTest.Tip}"/>
<Setter Property="Icon">
<Setter.Value>
<ui:SymbolIcon Symbol="Play24"/>
</Setter.Value>
</Setter>
<Style.Triggers>
<!-- Running: swap to Cancel appearance -->
<DataTrigger Binding="{Binding Root.IsAutoTestActive}" Value="True">
<Setter Property="Appearance" Value="Caution"/>
<Setter Property="Content" Value="{DynamicResource Common.Cancel}"/>
<Setter Property="Command" Value="{Binding Root.CancelAutoTestCommand}"/>
<Setter Property="Icon">
<Setter.Value>
<ui:SymbolIcon Symbol="DismissCircle24"/>
</Setter.Value>
</Setter>
</DataTrigger>
</Style.Triggers>
</Style>
</ui:Button.Style>
</ui:Button>
<!-- Stop -->

View File

@@ -0,0 +1,420 @@
<UserControl x:Class="HC_APTBS.Views.Pages.DeveloperPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:ui="http://schemas.lepo.co/wpfui/2022/xaml"
xmlns:uc="clr-namespace:HC_APTBS.Views.UserControls"
xmlns:vm="clr-namespace:HC_APTBS.ViewModels.Pages"
mc:Ignorable="d"
d:DesignHeight="900" d:DesignWidth="1400"
d:DataContext="{d:DesignInstance vm:DeveloperPageViewModel, IsDesignTimeCreatable=False}">
<!--
Developer Tools page — raw KWP/K-Line workbench. Compiled into Debug builds
only via the DEVELOPER_TOOLS symbol; the page files are removed from Release
builds in HC_APTBS.csproj.
Layout:
Col 0 (1*, 4 stacked cards): Identification, Dump, Custom commands, EEPROM passwords
Col 1 (1.5*): Manual hex write + transaction log (full height)
-->
<UserControl.Resources>
<BooleanToVisibilityConverter x:Key="BoolToVis"/>
<Style x:Key="DevSectionHeader" TargetType="TextBlock">
<Setter Property="FontFamily" Value="{DynamicResource ContentControlThemeFontFamily}"/>
<Setter Property="FontSize" Value="14"/>
<Setter Property="FontWeight" Value="SemiBold"/>
<Setter Property="Foreground" Value="{DynamicResource TextFillColorPrimaryBrush}"/>
<Setter Property="Margin" Value="0,0,0,8"/>
</Style>
<Style x:Key="DevFieldLabel" TargetType="TextBlock">
<Setter Property="FontSize" Value="12"/>
<Setter Property="Foreground" Value="{DynamicResource TextFillColorSecondaryBrush}"/>
<Setter Property="Margin" Value="0,0,0,2"/>
</Style>
</UserControl.Resources>
<Grid Margin="12">
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="1*" MinWidth="320"/>
<ColumnDefinition Width="1.5*" MinWidth="380"/>
</Grid.ColumnDefinitions>
<!-- ── Big "DUMP IN PROGRESS" banner — only for dumps > 0x0A00 bytes ── -->
<Border Grid.Row="0" Grid.Column="0" Grid.ColumnSpan="2"
Margin="0,0,0,12" Padding="20,14" CornerRadius="8"
Background="{DynamicResource AccentFillColorDefaultBrush}"
DataContext="{Binding Dump}"
Visibility="{Binding IsLargeDumpInProgress, Converter={StaticResource BoolToVis}}">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<ui:SymbolIcon Grid.Column="0" Grid.RowSpan="2"
Symbol="ArrowDownload24" FontSize="34"
Foreground="{DynamicResource TextOnAccentFillColorPrimaryBrush}"
VerticalAlignment="Center" Margin="0,0,16,0"/>
<StackPanel Grid.Column="1" Grid.Row="0" Margin="0,0,0,4">
<TextBlock Text="DUMP IN PROGRESS"
FontSize="22" FontWeight="SemiBold"
Foreground="{DynamicResource TextOnAccentFillColorPrimaryBrush}"/>
<TextBlock FontSize="13"
Foreground="{DynamicResource TextOnAccentFillColorSecondaryBrush}">
<Run Text="Reading "/>
<Run Text="{Binding Region}"/>
<Run Text=" — "/>
<Run Text="{Binding TotalBytes}"/>
<Run Text=" bytes total. Current address "/>
<Run Text="0x"/>
<Run Text="{Binding CurrentAddress, StringFormat=X4, Mode=OneWay}"/>
<Run Text="."/>
</TextBlock>
</StackPanel>
<StackPanel Grid.Column="2" Grid.Row="0" VerticalAlignment="Center">
<TextBlock HorizontalAlignment="Right"
FontSize="26" FontWeight="SemiBold"
Foreground="{DynamicResource TextOnAccentFillColorPrimaryBrush}">
<Run Text="{Binding BytesCollected, Mode=OneWay}"/>
<Run Text=" / "/>
<Run Text="{Binding TotalBytes, Mode=OneWay}"/>
</TextBlock>
<TextBlock HorizontalAlignment="Right" FontSize="11"
Foreground="{DynamicResource TextOnAccentFillColorSecondaryBrush}"
Text="bytes"/>
</StackPanel>
<ProgressBar Grid.Column="1" Grid.ColumnSpan="2" Grid.Row="1"
Value="{Binding Progress, Mode=OneWay}" Maximum="1"
Height="10"
Margin="0,8,0,0"
Background="{DynamicResource AccentFillColorSecondaryBrush}"
Foreground="{DynamicResource TextOnAccentFillColorPrimaryBrush}"/>
</Grid>
</Border>
<!-- ── Left column: stacked tool cards ──────────────────────────────── -->
<Grid Grid.Row="1" Grid.Column="0" Margin="0,0,6,0">
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="*"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<!-- Identification card — reuse the existing UserControl, bound to the singleton VM -->
<uc:PumpIdentificationCard Grid.Row="0"
DataContext="{Binding Identification}"
Margin="0,0,0,8"/>
<!-- ── Dump card ────────────────────────────────────────────────── -->
<Border Grid.Row="1" Style="{StaticResource PumpCard}" Margin="0,0,0,8"
DataContext="{Binding Dump}">
<StackPanel>
<DockPanel Margin="0,0,0,8">
<ui:SymbolIcon DockPanel.Dock="Left" Symbol="ArrowDownload24" FontSize="16"
Foreground="{DynamicResource AccentTextFillColorPrimaryBrush}"
Margin="0,0,8,0" VerticalAlignment="Center"/>
<TextBlock Text="ROM / EEPROM dump" Style="{StaticResource DevSectionHeader}" Margin="0"/>
</DockPanel>
<StackPanel Orientation="Horizontal" Margin="0,0,0,8">
<RadioButton GroupName="DumpRegion" Content="ROM"
IsChecked="{Binding IsRomSelected, Mode=TwoWay}"
Margin="0,0,16,0"/>
<RadioButton GroupName="DumpRegion" Content="EEPROM"
IsChecked="{Binding IsEepromSelected, Mode=TwoWay}"/>
</StackPanel>
<Grid Margin="0,0,0,8">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<StackPanel Grid.Column="0" Margin="0,0,4,0">
<TextBlock Text="Start (hex)" Style="{StaticResource DevFieldLabel}"/>
<ui:TextBox Text="{Binding StartAddressHex, UpdateSourceTrigger=PropertyChanged}"
FontFamily="Consolas, 'Courier New'"/>
</StackPanel>
<StackPanel Grid.Column="1" Margin="4,0,0,0">
<TextBlock Text="End (hex)" Style="{StaticResource DevFieldLabel}"/>
<ui:TextBox Text="{Binding EndAddressHex, UpdateSourceTrigger=PropertyChanged}"
FontFamily="Consolas, 'Courier New'"/>
</StackPanel>
</Grid>
<DockPanel Margin="0,0,0,4">
<ui:Button DockPanel.Dock="Right"
Appearance="Primary"
Content="Dump"
Icon="{ui:SymbolIcon ArrowDownload24}"
Command="{Binding DumpCommand}"/>
<ProgressBar Value="{Binding Progress}" Maximum="1" Height="6"
VerticalAlignment="Center" Margin="0,0,8,0"/>
</DockPanel>
<TextBlock Text="{Binding StatusText}" FontSize="11"
Foreground="{DynamicResource TextFillColorSecondaryBrush}"
TextWrapping="Wrap"/>
</StackPanel>
</Border>
<!-- ── Custom commands library card ─────────────────────────────── -->
<Border Grid.Row="2" Style="{StaticResource PumpCard}" Margin="0,0,0,8"
DataContext="{Binding Commands}">
<DockPanel>
<DockPanel DockPanel.Dock="Top" Margin="0,0,0,8">
<ui:SymbolIcon DockPanel.Dock="Left" Symbol="BookmarkMultiple24" FontSize="16"
Foreground="{DynamicResource AccentTextFillColorPrimaryBrush}"
Margin="0,0,8,0" VerticalAlignment="Center"/>
<TextBlock Text="Saved commands" Style="{StaticResource DevSectionHeader}" Margin="0"/>
</DockPanel>
<Grid DockPanel.Dock="Bottom" Margin="0,8,0,0">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<ui:TextBox Grid.Column="0"
Text="{Binding NewName, UpdateSourceTrigger=PropertyChanged}"
PlaceholderText="Name to save current as…"/>
<ui:Button Grid.Column="1" Margin="6,0,0,0"
Content="Save current" Icon="{ui:SymbolIcon Save24}"
Command="{Binding SaveCurrentCommand}"/>
<ui:Button Grid.Column="2" Margin="6,0,0,0"
Appearance="Primary"
Content="Send" Icon="{ui:SymbolIcon Send24}"
Command="{Binding SendSelectedCommand}"/>
<ui:Button Grid.Column="3" Margin="6,0,0,0"
Content="Delete" Icon="{ui:SymbolIcon Delete24}"
Command="{Binding DeleteSelectedCommand}"/>
</Grid>
<ListView ItemsSource="{Binding Items}"
SelectedItem="{Binding Selected, Mode=TwoWay}"
FontFamily="Consolas, 'Courier New'" FontSize="12">
<ListView.View>
<GridView>
<GridViewColumn Header="Name" Width="120"
DisplayMemberBinding="{Binding Name}"/>
<GridViewColumn Header="Hex" Width="220"
DisplayMemberBinding="{Binding HexBytes}"/>
</GridView>
</ListView.View>
</ListView>
</DockPanel>
</Border>
<!-- ── EEPROM passwords library card ────────────────────────────── -->
<Border Grid.Row="3" Style="{StaticResource PumpCard}"
DataContext="{Binding Passwords}">
<DockPanel>
<DockPanel DockPanel.Dock="Top" Margin="0,0,0,8">
<ui:SymbolIcon DockPanel.Dock="Left" Symbol="Key24" FontSize="16"
Foreground="{DynamicResource AccentTextFillColorPrimaryBrush}"
Margin="0,0,8,0" VerticalAlignment="Center"/>
<TextBlock Text="EEPROM passwords" Style="{StaticResource DevSectionHeader}" Margin="0"/>
</DockPanel>
<!-- "Add new" inline editor -->
<Grid DockPanel.Dock="Bottom" Margin="0,8,0,0">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="60"/>
<ColumnDefinition Width="80"/>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<ui:TextBox Grid.Column="0"
Text="{Binding NewName, UpdateSourceTrigger=PropertyChanged}"
PlaceholderText="Name (e.g. 'Bosch v2 Z3')"/>
<ui:TextBox Grid.Column="1" Margin="6,0,0,0"
Text="{Binding NewZoneHex, UpdateSourceTrigger=PropertyChanged}"
PlaceholderText="Zone"
FontFamily="Consolas, 'Courier New'"/>
<ui:TextBox Grid.Column="2" Margin="6,0,0,0"
Text="{Binding NewKeyHex, UpdateSourceTrigger=PropertyChanged}"
PlaceholderText="Key"
FontFamily="Consolas, 'Courier New'"/>
<ui:Button Grid.Column="3" Margin="6,0,0,0"
Content="Add" Icon="{ui:SymbolIcon Add24}"
Command="{Binding AddCommand}"/>
<ui:Button Grid.Column="4" Margin="6,0,0,0"
Appearance="Primary"
Content="Apply" Icon="{ui:SymbolIcon LockOpen24}"
Command="{Binding ApplySelectedCommand}"/>
<ui:Button Grid.Column="5" Margin="6,0,0,0"
Content="Delete" Icon="{ui:SymbolIcon Delete24}"
Command="{Binding DeleteSelectedCommand}"/>
</Grid>
<ListView ItemsSource="{Binding Items}"
SelectedItem="{Binding Selected, Mode=TwoWay}"
FontFamily="Consolas, 'Courier New'" FontSize="12">
<ListView.View>
<GridView>
<GridViewColumn Header="Name" Width="160"
DisplayMemberBinding="{Binding Name}"/>
<GridViewColumn Header="Zone" Width="60">
<GridViewColumn.CellTemplate>
<DataTemplate>
<TextBlock Text="{Binding Zone, StringFormat=X2}"/>
</DataTemplate>
</GridViewColumn.CellTemplate>
</GridViewColumn>
<GridViewColumn Header="Key" Width="80">
<GridViewColumn.CellTemplate>
<DataTemplate>
<TextBlock Text="{Binding Key, StringFormat=X4}"/>
</DataTemplate>
</GridViewColumn.CellTemplate>
</GridViewColumn>
</GridView>
</ListView.View>
</ListView>
</DockPanel>
</Border>
</Grid>
<!-- ── Right column: manual hex write + transaction log ─────────────── -->
<Border Grid.Row="1" Grid.Column="1" Style="{StaticResource PumpCard}" Margin="6,0,0,0">
<DockPanel>
<!-- Header -->
<DockPanel DockPanel.Dock="Top" Margin="0,0,0,8">
<ui:SymbolIcon DockPanel.Dock="Left" Symbol="WrenchScrewdriver24" FontSize="18"
Foreground="{DynamicResource AccentTextFillColorPrimaryBrush}"
VerticalAlignment="Center" Margin="0,0,8,0"/>
<StackPanel>
<TextBlock Text="Manual KWP write"
Style="{StaticResource DevSectionHeader}" Margin="0"/>
<TextBlock Text="Type raw bytes, send over the active K-Line session, watch the wire."
FontSize="11"
Foreground="{DynamicResource TextFillColorSecondaryBrush}"/>
</StackPanel>
</DockPanel>
<!-- Session status banner -->
<Border DockPanel.Dock="Top" Margin="0,0,0,8" Padding="10,6" CornerRadius="4"
BorderThickness="1"
BorderBrush="{DynamicResource ControlStrokeColorDefaultBrush}">
<Border.Style>
<Style TargetType="Border">
<Setter Property="Background" Value="{DynamicResource SystemFillColorCautionBackgroundBrush}"/>
<Style.Triggers>
<DataTrigger Binding="{Binding IsSessionOpen}" Value="True">
<Setter Property="Background" Value="{DynamicResource SystemFillColorSuccessBackgroundBrush}"/>
</DataTrigger>
</Style.Triggers>
</Style>
</Border.Style>
<StackPanel Orientation="Horizontal">
<ui:SymbolIcon FontSize="14" Margin="0,0,6,0" VerticalAlignment="Center">
<ui:SymbolIcon.Style>
<Style TargetType="ui:SymbolIcon">
<Setter Property="Symbol" Value="PlugDisconnected20"/>
<Style.Triggers>
<DataTrigger Binding="{Binding IsSessionOpen}" Value="True">
<Setter Property="Symbol" Value="PlugConnected20"/>
</DataTrigger>
</Style.Triggers>
</Style>
</ui:SymbolIcon.Style>
</ui:SymbolIcon>
<TextBlock VerticalAlignment="Center" FontSize="12">
<TextBlock.Style>
<Style TargetType="TextBlock">
<Setter Property="Text" Value="K-Line session not connected — open one from the identification card on the left."/>
<Style.Triggers>
<DataTrigger Binding="{Binding IsSessionOpen}" Value="True">
<Setter Property="Text" Value="K-Line session is open."/>
</DataTrigger>
</Style.Triggers>
</Style>
</TextBlock.Style>
</TextBlock>
</StackPanel>
</Border>
<!-- Input row -->
<Grid DockPanel.Dock="Top" Margin="0,0,0,8">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<ui:TextBox Grid.Column="0"
Text="{Binding HexInput, UpdateSourceTrigger=PropertyChanged}"
PlaceholderText="Hex bytes — e.g. 18 00 03 FF FF"
FontFamily="Consolas, 'Courier New'"
FontSize="13"
Padding="10,8"/>
<ui:Button Grid.Column="1" Margin="8,0,0,0"
Appearance="Primary"
Content="Send"
Icon="{ui:SymbolIcon Send24}"
Command="{Binding SendCommand}"/>
<ui:Button Grid.Column="2" Margin="8,0,0,0"
Appearance="Secondary"
Content="Clear log"
Icon="{ui:SymbolIcon Eraser24}"
Command="{Binding ClearLogCommand}"/>
</Grid>
<!-- Status -->
<TextBlock DockPanel.Dock="Top" Margin="2,0,0,6"
Text="{Binding StatusText}"
FontSize="12"
Foreground="{DynamicResource TextFillColorSecondaryBrush}"/>
<!-- Log -->
<Border Background="{DynamicResource ControlSolidFillColorDefaultBrush}"
BorderBrush="{DynamicResource CardStrokeColorDefaultBrush}"
BorderThickness="1" CornerRadius="6">
<ScrollViewer x:Name="LogScroller" VerticalScrollBarVisibility="Auto" Padding="8">
<ItemsControl ItemsSource="{Binding Log}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Display}"
FontFamily="Consolas, 'Courier New'"
FontSize="12"
Padding="2,1">
<TextBlock.Style>
<Style TargetType="TextBlock">
<Setter Property="Foreground" Value="{DynamicResource TextFillColorPrimaryBrush}"/>
<Style.Triggers>
<DataTrigger Binding="{Binding Direction}" Value="Tx">
<Setter Property="Foreground" Value="{DynamicResource AccentTextFillColorPrimaryBrush}"/>
</DataTrigger>
<DataTrigger Binding="{Binding Direction}" Value="Info">
<Setter Property="Foreground" Value="{DynamicResource TextFillColorSecondaryBrush}"/>
</DataTrigger>
</Style.Triggers>
</Style>
</TextBlock.Style>
</TextBlock>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</ScrollViewer>
</Border>
</DockPanel>
</Border>
</Grid>
</UserControl>

View File

@@ -0,0 +1,39 @@
using System.Collections.Specialized;
using System.Windows.Controls;
using HC_APTBS.ViewModels.Pages;
namespace HC_APTBS.Views.Pages
{
/// <summary>
/// Code-behind for the Developer Tools page. Auto-scrolls the log to the
/// bottom when entries are appended so the latest TX/RX is always visible.
/// Compiled into Debug builds only — see <c>HC_APTBS.csproj</c>.
/// </summary>
public partial class DeveloperPage : UserControl
{
private DeveloperPageViewModel? _vm;
public DeveloperPage()
{
InitializeComponent();
DataContextChanged += OnDataContextChanged;
}
private void OnDataContextChanged(object sender, System.Windows.DependencyPropertyChangedEventArgs e)
{
if (_vm is { } old)
((INotifyCollectionChanged)old.Log).CollectionChanged -= OnLogChanged;
_vm = DataContext as DeveloperPageViewModel;
if (_vm is not null)
((INotifyCollectionChanged)_vm.Log).CollectionChanged += OnLogChanged;
}
private void OnLogChanged(object? sender, NotifyCollectionChangedEventArgs e)
{
if (e.Action == NotifyCollectionChangedAction.Add)
Dispatcher.BeginInvoke(() => LogScroller.ScrollToBottom());
}
}
}

View File

@@ -26,7 +26,7 @@
</Grid.RowDefinitions>
<!-- ── Top status strip ─────────────────────────────────────────────── -->
<uc:PumpTopStripView Grid.Row="0"/>
<uc:PumpTopStripView Grid.Row="0" Height="66"/>
<!-- ── Body: 3-column card layout ───────────────────────────────────── -->
<Grid Grid.Row="1">
@@ -53,7 +53,7 @@
<!-- Col 2: Identification (Auto) + DTCs (*) -->
<Grid Grid.Column="0">
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="*"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>

View File

@@ -62,6 +62,7 @@
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<TextBlock Grid.Row="0" Grid.Column="0" Text="{DynamicResource Dialog.Settings.TempMax}"
@@ -97,6 +98,10 @@
<CheckBox Grid.Row="6" Grid.Column="0" Grid.ColumnSpan="2"
Content="{DynamicResource Dialog.Settings.IgnoreTin}"
IsChecked="{Binding DefaultIgnoreTin}" Margin="0,4,0,0"/>
<CheckBox Grid.Row="7" Grid.Column="0" Grid.ColumnSpan="2"
Content="{DynamicResource Dialog.Settings.AutoTestSkipsOilPumpConfirm}"
IsChecked="{Binding AutoTestSkipsOilPumpConfirm}" Margin="0,4,0,0"/>
</Grid>
</TabItem>
@@ -298,6 +303,7 @@
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<TextBlock Grid.Row="0" Grid.Column="0" Text="{DynamicResource Dialog.Settings.RefreshBench}"
@@ -334,6 +340,11 @@
VerticalAlignment="Center" Margin="0,0,12,6"/>
<TextBox Grid.Row="6" Grid.Column="1" Text="{Binding FlasherIntervalMs, UpdateSourceTrigger=LostFocus}"
Margin="0,0,0,6"/>
<TextBlock Grid.Row="7" Grid.Column="0" Text="{DynamicResource Dialog.Settings.RpmChartUpdateHz}"
VerticalAlignment="Center" Margin="0,0,12,6"/>
<TextBox Grid.Row="7" Grid.Column="1" Text="{Binding RpmChartUpdateHz, UpdateSourceTrigger=LostFocus}"
Margin="0,0,0,6"/>
</Grid>
</TabItem>

View File

@@ -3,201 +3,364 @@
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:models="clr-namespace:HC_APTBS.Models"
xmlns:ui="http://schemas.lepo.co/wpfui/2022/xaml"
xmlns:uc="clr-namespace:HC_APTBS.Views.UserControls"
xmlns:vm="clr-namespace:HC_APTBS.ViewModels"
xmlns:vmp="clr-namespace:HC_APTBS.ViewModels.Pages"
mc:Ignorable="d"
d:DesignHeight="800" d:DesignWidth="1000"
d:DesignHeight="860" d:DesignWidth="1740"
d:DataContext="{d:DesignInstance Type=vmp:TestsPageViewModel, IsDesignTimeCreatable=False}">
<!--
Plan → Preconditions → Running → Done wizard (ui-structure.md §4).
DataContext: TestsPageViewModel.
Top row: 4-pill stepper. Middle: ContentControl routing CurrentStateVm
through typed DataTemplates. Bottom: wizard footer with Back / Next.
Single-page Tests view.
Row 0: status bar — headline + blocker text + phase progress when running.
Row 1: section cards (always visible) with full live-bar indicators so
phase state is readable at rest and during a run.
Row 2: action bar — toolbar (Check all) + primary action (Start / Abort /
Report + Clear).
Overlay: PASSED / FAILED snackbar pinned to the bottom-right.
-->
<UserControl.Resources>
<!-- Step → View mappings -->
<DataTemplate DataType="{x:Type vmp:PlanStateViewModel}">
<uc:TestPlanView DataContext="{Binding TestPanel}"/>
</DataTemplate>
<BooleanToVisibilityConverter x:Key="BoolToVis"/>
<DataTemplate DataType="{x:Type vm:TestPreconditionsViewModel}">
<uc:TestPreconditionsView/>
<DataTemplate DataType="{x:Type vm:TestSectionViewModel}">
<uc:TestSectionCard Width="430" Compact="False"/>
</DataTemplate>
<DataTemplate DataType="{x:Type vmp:RunningStateViewModel}">
<uc:TestRunningView DataContext="{Binding TestPanel}"/>
</DataTemplate>
<DataTemplate DataType="{x:Type vmp:TestsPageViewModel}">
<uc:TestDoneView/>
</DataTemplate>
<!-- Stepper pill style -->
<Style x:Key="StepPillStyle" TargetType="Border">
<Setter Property="CornerRadius" Value="12"/>
<Setter Property="Padding" Value="14,4"/>
<Setter Property="Margin" Value="0,0,6,0"/>
<Setter Property="Background" Value="#ECEFF1"/>
<Setter Property="BorderBrush" Value="#CFD8DC"/>
<Setter Property="BorderThickness" Value="1"/>
</Style>
</UserControl.Resources>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="*"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<Grid Margin="12">
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="*"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<!-- Wizard stepper -->
<Border Background="White" BorderBrush="#DDD" BorderThickness="0,0,0,1" Padding="10,8">
<ItemsControl>
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel Orientation="Horizontal"/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<!-- ── Status bar ─────────────────────────────────────────────── -->
<Border Style="{StaticResource PumpCard}" Margin="4,0,4,6" Padding="14,10" Height="66">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<Border Style="{StaticResource StepPillStyle}">
<Border.Resources>
<Style TargetType="Border" BasedOn="{StaticResource StepPillStyle}">
<Style.Triggers>
<DataTrigger Binding="{Binding CurrentState}"
Value="{x:Static models:TestFlowState.Plan}">
<Setter Property="Background" Value="#1565C0"/>
<Setter Property="BorderBrush" Value="#0D47A1"/>
</DataTrigger>
</Style.Triggers>
</Style>
</Border.Resources>
<TextBlock Text="{DynamicResource Test.Wizard.Plan}" FontSize="12">
<TextBlock.Style>
<Style TargetType="TextBlock">
<Setter Property="Foreground" Value="#455A64"/>
<!-- State glyph -->
<Border Grid.Column="0" Width="14" Height="14" CornerRadius="7"
VerticalAlignment="Center" Margin="0,0,10,0">
<Border.Style>
<Style TargetType="Border">
<Setter Property="Background" Value="#BBB"/>
<Style.Triggers>
<DataTrigger Binding="{Binding CurrentState}"
Value="{x:Static models:TestFlowState.Plan}">
<Setter Property="Foreground" Value="White"/>
<Setter Property="FontWeight" Value="SemiBold"/>
<DataTrigger Binding="{Binding AllPreconditionsPassed}" Value="True">
<Setter Property="Background" Value="#26C200"/>
</DataTrigger>
<DataTrigger Binding="{Binding IsTestRunning}" Value="True">
<Setter Property="Background" Value="#F9A825"/>
</DataTrigger>
<MultiDataTrigger>
<MultiDataTrigger.Conditions>
<Condition Binding="{Binding HasCompletedResults}" Value="True"/>
<Condition Binding="{Binding IsTestRunning}" Value="False"/>
<Condition Binding="{Binding LastRunPassed}" Value="True"/>
</MultiDataTrigger.Conditions>
<Setter Property="Background" Value="#2E7D32"/>
</MultiDataTrigger>
<MultiDataTrigger>
<MultiDataTrigger.Conditions>
<Condition Binding="{Binding HasCompletedResults}" Value="True"/>
<Condition Binding="{Binding IsTestRunning}" Value="False"/>
<Condition Binding="{Binding LastRunPassed}" Value="False"/>
</MultiDataTrigger.Conditions>
<Setter Property="Background" Value="#B71C1C"/>
</MultiDataTrigger>
</Style.Triggers>
</Style>
</TextBlock.Style>
</TextBlock>
</Border>
</Border.Style>
</Border>
<Border Style="{StaticResource StepPillStyle}">
<Border.Resources>
<Style TargetType="Border" BasedOn="{StaticResource StepPillStyle}">
<Style.Triggers>
<DataTrigger Binding="{Binding CurrentState}"
Value="{x:Static models:TestFlowState.Preconditions}">
<Setter Property="Background" Value="#1565C0"/>
<Setter Property="BorderBrush" Value="#0D47A1"/>
</DataTrigger>
</Style.Triggers>
</Style>
</Border.Resources>
<TextBlock Text="{DynamicResource Test.Wizard.Preconditions}" FontSize="12">
<TextBlock.Style>
<Style TargetType="TextBlock">
<Setter Property="Foreground" Value="#455A64"/>
<Style.Triggers>
<DataTrigger Binding="{Binding CurrentState}"
Value="{x:Static models:TestFlowState.Preconditions}">
<Setter Property="Foreground" Value="White"/>
<Setter Property="FontWeight" Value="SemiBold"/>
</DataTrigger>
</Style.Triggers>
</Style>
</TextBlock.Style>
</TextBlock>
</Border>
<!-- Headline + blocker -->
<StackPanel Grid.Column="1" VerticalAlignment="Center">
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding StatusHeadline}"
FontSize="15" FontWeight="SemiBold"
Foreground="{DynamicResource TextFillColorPrimaryBrush}"/>
<TextBlock Text="{Binding TestPanel.CurrentPhaseName}"
FontSize="13"
Foreground="{DynamicResource AccentTextFillColorPrimaryBrush}"
Margin="10,0,0,0"
Visibility="{Binding IsTestRunning, Converter={StaticResource BoolToVis}}"/>
<TextBlock Text="{Binding TestPanel.SectionLabel}"
FontSize="11" FontStyle="Italic"
Foreground="{DynamicResource TextFillColorTertiaryBrush}"
Margin="8,0,0,0" VerticalAlignment="Center"
Visibility="{Binding IsTestRunning, Converter={StaticResource BoolToVis}}"/>
</StackPanel>
<TextBlock Text="{Binding BlockingReason}"
FontSize="11"
Foreground="{DynamicResource TextFillColorSecondaryBrush}"
Margin="0,2,0,0">
<TextBlock.Style>
<Style TargetType="TextBlock">
<Setter Property="Visibility" Value="Visible"/>
<Style.Triggers>
<DataTrigger Binding="{Binding AllPreconditionsPassed}" Value="True">
<Setter Property="Visibility" Value="Collapsed"/>
</DataTrigger>
<DataTrigger Binding="{Binding IsTestRunning}" Value="True">
<Setter Property="Visibility" Value="Collapsed"/>
</DataTrigger>
<DataTrigger Binding="{Binding HasCompletedResults}" Value="True">
<Setter Property="Visibility" Value="Collapsed"/>
</DataTrigger>
</Style.Triggers>
</Style>
</TextBlock.Style>
</TextBlock>
</StackPanel>
<Border Style="{StaticResource StepPillStyle}">
<Border.Resources>
<Style TargetType="Border" BasedOn="{StaticResource StepPillStyle}">
<Style.Triggers>
<DataTrigger Binding="{Binding CurrentState}"
Value="{x:Static models:TestFlowState.Running}">
<Setter Property="Background" Value="#1565C0"/>
<Setter Property="BorderBrush" Value="#0D47A1"/>
</DataTrigger>
</Style.Triggers>
</Style>
</Border.Resources>
<TextBlock Text="{DynamicResource Test.Wizard.Running}" FontSize="12">
<TextBlock.Style>
<Style TargetType="TextBlock">
<Setter Property="Foreground" Value="#455A64"/>
<Style.Triggers>
<DataTrigger Binding="{Binding CurrentState}"
Value="{x:Static models:TestFlowState.Running}">
<Setter Property="Foreground" Value="White"/>
<Setter Property="FontWeight" Value="SemiBold"/>
</DataTrigger>
</Style.Triggers>
</Style>
</TextBlock.Style>
</TextBlock>
</Border>
<!-- Right pills: estimated time / countdown -->
<StackPanel Grid.Column="2" Orientation="Horizontal" VerticalAlignment="Center">
<!-- Running: phase remaining / total -->
<StackPanel Orientation="Horizontal"
Visibility="{Binding IsTestRunning, Converter={StaticResource BoolToVis}}">
<TextBlock FontFamily="Consolas" FontSize="20" FontWeight="SemiBold"
Foreground="{DynamicResource AccentTextFillColorPrimaryBrush}"
VerticalAlignment="Center">
<Run Text="{Binding TestPanel.PhaseRemainingSeconds, Mode=OneWay}"/>
</TextBlock>
<TextBlock Text="s" FontSize="12"
Foreground="{DynamicResource AccentTextFillColorPrimaryBrush}"
VerticalAlignment="Bottom" Margin="2,0,8,3"/>
<TextBlock FontSize="11"
Foreground="{DynamicResource TextFillColorTertiaryBrush}"
VerticalAlignment="Bottom" Margin="0,0,0,3">
<Run Text="/ "/>
<Run Text="{Binding TestPanel.PhaseTotalSeconds, Mode=OneWay}"/>
<Run Text="s"/>
</TextBlock>
</StackPanel>
<Border Style="{StaticResource StepPillStyle}">
<Border.Resources>
<Style TargetType="Border" BasedOn="{StaticResource StepPillStyle}">
<Style.Triggers>
<DataTrigger Binding="{Binding CurrentState}"
Value="{x:Static models:TestFlowState.Done}">
<Setter Property="Background" Value="#1565C0"/>
<Setter Property="BorderBrush" Value="#0D47A1"/>
</DataTrigger>
</Style.Triggers>
</Style>
</Border.Resources>
<TextBlock Text="{DynamicResource Test.Wizard.Done}" FontSize="12">
<TextBlock.Style>
<Style TargetType="TextBlock">
<Setter Property="Foreground" Value="#455A64"/>
<Style.Triggers>
<DataTrigger Binding="{Binding CurrentState}"
Value="{x:Static models:TestFlowState.Done}">
<Setter Property="Foreground" Value="White"/>
<Setter Property="FontWeight" Value="SemiBold"/>
</DataTrigger>
</Style.Triggers>
</Style>
</TextBlock.Style>
</TextBlock>
</Border>
</ItemsControl>
</Border>
<!-- Idle: estimated total time -->
<Border Padding="10,4" CornerRadius="12">
<Border.Style>
<Style TargetType="Border" BasedOn="{StaticResource TestMetaPill}">
<Style.Triggers>
<DataTrigger Binding="{Binding IsTestRunning}" Value="True">
<Setter Property="Visibility" Value="Collapsed"/>
</DataTrigger>
</Style.Triggers>
</Style>
</Border.Style>
<StackPanel Orientation="Horizontal">
<ui:SymbolIcon Symbol="Timer24" FontSize="14"
Foreground="{DynamicResource TextFillColorSecondaryBrush}"
VerticalAlignment="Center"/>
<TextBlock Style="{StaticResource TestMetaPillText}" Margin="6,0,0,0"
FontSize="12" FontFamily="Consolas">
<Run Text="~"/>
<Run Text="{Binding TestPanel.RemainingSeconds, Mode=OneWay}"/>
<Run Text=" s"/>
</TextBlock>
<TextBlock Style="{StaticResource TestMetaPillText}" Margin="6,0,0,0"
Text="{DynamicResource Test.Plan.Remaining}"/>
</StackPanel>
</Border>
</StackPanel>
<!-- Current step body -->
<ContentControl Grid.Row="1" Content="{Binding CurrentStateVm}"/>
<!-- Phase progress bar (only while running) -->
<ProgressBar Grid.Row="1" Grid.ColumnSpan="3"
Minimum="0" Maximum="1"
Value="{Binding TestPanel.PhaseProgress, Mode=OneWay}"
Height="4" Margin="0,8,0,0"
BorderThickness="0"
Visibility="{Binding IsTestRunning, Converter={StaticResource BoolToVis}}"/>
</Grid>
</Border>
<!-- Wizard footer: Back / Next -->
<Border Grid.Row="2" Padding="10,8"
Background="White" BorderBrush="#DDD" BorderThickness="0,1,0,0">
<!-- ── Section cards (always visible) ─────────────────────────── -->
<ScrollViewer Grid.Row="1"
Margin="0,0,0,8"
HorizontalScrollBarVisibility="Disabled"
VerticalScrollBarVisibility="Disabled">
<ItemsControl ItemsSource="{Binding TestPanel.Tests}"
Margin="4,0,4,0">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<WrapPanel Orientation="Horizontal"/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
</ItemsControl>
</ScrollViewer>
<!-- ── Action bar ─────────────────────────────────────────────── -->
<Border Grid.Row="2" Style="{StaticResource PumpCard}" Margin="4,6,4,0" Padding="12,8">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<!-- Left toolbar: Check-all toggle -->
<StackPanel Orientation="Horizontal" VerticalAlignment="Center">
<ui:Button Command="{Binding TestPanel.ToggleCheckAllCommand}"
Content="{DynamicResource Test.CheckAll}"
Appearance="Secondary"
Padding="12,5" FontSize="12"
ToolTip="{DynamicResource Test.Plan.EnableAll}">
<ui:Button.Icon><ui:SymbolIcon Symbol="SelectAllOn24"/></ui:Button.Icon>
<ui:Button.Style>
<Style TargetType="ui:Button" BasedOn="{StaticResource {x:Type ui:Button}}">
<Setter Property="IsEnabled" Value="True"/>
<Style.Triggers>
<DataTrigger Binding="{Binding IsTestRunning}" Value="True">
<Setter Property="IsEnabled" Value="False"/>
</DataTrigger>
</Style.Triggers>
</Style>
</ui:Button.Style>
</ui:Button>
</StackPanel>
<!-- Right action cluster — switches by state -->
<StackPanel Grid.Column="2" Orientation="Horizontal">
<!-- Idle: Start -->
<ui:Button Command="{Binding StartTestCommand}"
Content="{DynamicResource Test.StartTest}"
Appearance="Primary"
Padding="18,6" FontSize="14" FontWeight="SemiBold">
<ui:Button.Icon><ui:SymbolIcon Symbol="Play24"/></ui:Button.Icon>
<ui:Button.Style>
<Style TargetType="ui:Button" BasedOn="{StaticResource {x:Type ui:Button}}">
<Setter Property="Visibility" Value="Visible"/>
<Style.Triggers>
<DataTrigger Binding="{Binding IsTestRunning}" Value="True">
<Setter Property="Visibility" Value="Collapsed"/>
</DataTrigger>
<DataTrigger Binding="{Binding HasCompletedResults}" Value="True">
<Setter Property="Visibility" Value="Collapsed"/>
</DataTrigger>
</Style.Triggers>
</Style>
</ui:Button.Style>
</ui:Button>
<!-- Running: Abort -->
<ui:Button Command="{Binding AbortCommand}"
Content="{DynamicResource Test.Running.Abort}"
Appearance="Danger"
Padding="18,6" FontSize="14" FontWeight="SemiBold"
Visibility="{Binding IsTestRunning, Converter={StaticResource BoolToVis}}">
<ui:Button.Icon><ui:SymbolIcon Symbol="Stop24"/></ui:Button.Icon>
</ui:Button>
<!-- Done: Report + Clear -->
<StackPanel Orientation="Horizontal">
<StackPanel.Style>
<Style TargetType="StackPanel">
<Setter Property="Visibility" Value="Collapsed"/>
<Style.Triggers>
<MultiDataTrigger>
<MultiDataTrigger.Conditions>
<Condition Binding="{Binding HasCompletedResults}" Value="True"/>
<Condition Binding="{Binding IsTestRunning}" Value="False"/>
</MultiDataTrigger.Conditions>
<Setter Property="Visibility" Value="Visible"/>
</MultiDataTrigger>
</Style.Triggers>
</Style>
</StackPanel.Style>
<ui:Button Command="{Binding ClearTestDataCommand}"
Content="{DynamicResource Test.Done.ClearData}"
Appearance="Secondary"
Padding="14,6" FontSize="12" Margin="0,0,8,0">
<ui:Button.Icon><ui:SymbolIcon Symbol="Delete24"/></ui:Button.Icon>
</ui:Button>
<ui:Button Command="{Binding Root.GenerateReportCommand}"
Content="{DynamicResource Test.Report}"
Appearance="Primary"
Padding="18,6" FontSize="14" FontWeight="SemiBold">
<ui:Button.Icon><ui:SymbolIcon Symbol="DocumentPdf24"/></ui:Button.Icon>
</ui:Button>
</StackPanel>
</StackPanel>
</Grid>
</Border>
</Grid>
<!-- ── Done snackbar overlay (auto-dismiss) ─────────────────────── -->
<Border HorizontalAlignment="Right" VerticalAlignment="Bottom"
Margin="0,0,24,24"
CornerRadius="6"
Padding="14,10"
BorderThickness="1"
Visibility="{Binding ShowDoneSnackbar, Converter={StaticResource BoolToVis}}">
<Border.Style>
<Style TargetType="Border">
<Setter Property="Background" Value="#FFCDD2"/>
<Setter Property="BorderBrush" Value="#B71C1C"/>
<Style.Triggers>
<DataTrigger Binding="{Binding LastRunPassed}" Value="True">
<Setter Property="Background" Value="#C8E6C9"/>
<Setter Property="BorderBrush" Value="#2E7D32"/>
</DataTrigger>
</Style.Triggers>
</Style>
</Border.Style>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<Button Grid.Column="0"
Content="{DynamicResource Test.Wizard.Back}"
Command="{Binding BackCommand}"
Padding="16,6" FontSize="12"/>
<ui:SymbolIcon Grid.Column="0" FontSize="20"
VerticalAlignment="Center" Margin="0,0,10,0">
<ui:SymbolIcon.Style>
<Style TargetType="ui:SymbolIcon">
<Setter Property="Symbol" Value="ErrorCircle24"/>
<Setter Property="Foreground" Value="#B71C1C"/>
<Style.Triggers>
<DataTrigger Binding="{Binding LastRunPassed}" Value="True">
<Setter Property="Symbol" Value="CheckmarkCircle24"/>
<Setter Property="Foreground" Value="#2E7D32"/>
</DataTrigger>
</Style.Triggers>
</Style>
</ui:SymbolIcon.Style>
</ui:SymbolIcon>
<Button Grid.Column="2"
Content="{DynamicResource Test.Wizard.Next}"
Command="{Binding NextCommand}"
Padding="16,6" FontSize="12"
FontWeight="SemiBold"/>
<TextBlock Grid.Column="1"
FontSize="14" FontWeight="SemiBold"
VerticalAlignment="Center">
<TextBlock.Style>
<Style TargetType="TextBlock">
<Setter Property="Text" Value="{DynamicResource Test.Done.Failed}"/>
<Setter Property="Foreground" Value="#B71C1C"/>
<Style.Triggers>
<DataTrigger Binding="{Binding LastRunPassed}" Value="True">
<Setter Property="Text" Value="{DynamicResource Test.Done.Passed}"/>
<Setter Property="Foreground" Value="#2E7D32"/>
</DataTrigger>
</Style.Triggers>
</Style>
</TextBlock.Style>
</TextBlock>
<ui:Button Grid.Column="2"
Command="{Binding DismissSnackbarCommand}"
Appearance="Transparent"
Padding="6,2" Margin="12,0,0,0"
VerticalAlignment="Center"
ToolTip="{DynamicResource Test.Abort.Cancel}">
<ui:Button.Icon><ui:SymbolIcon Symbol="Dismiss24"/></ui:Button.Icon>
</ui:Button>
</Grid>
</Border>
</Grid>

View File

@@ -26,11 +26,11 @@
<!-- ── Zero buttons (docked bottom first) ─────────────────── -->
<UniformGrid DockPanel.Dock="Bottom" Rows="1" Columns="2" Margin="0,10,0,0">
<ui:Button Margin="0,0,4,0" Height="40"
<ui:Button Margin="0,0,4,0" Height="40" HorizontalAlignment="Stretch"
Content="{DynamicResource Bench.Advance.ZeroPsg}"
Command="{Binding AngleDisplay.SetPsgZeroCommand}"
Appearance="Secondary"/>
<ui:Button Margin="4,0,0,0" Height="40"
<ui:Button Margin="4,0,0,0" Height="40" HorizontalAlignment="Stretch"
Content="{DynamicResource Bench.Advance.ZeroInj}"
Command="{Binding AngleDisplay.SetInjZeroCommand}"
Appearance="Secondary"/>

View File

@@ -0,0 +1,115 @@
<UserControl x:Class="HC_APTBS.Views.UserControls.AutoTestSnackbarView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:ui="http://schemas.lepo.co/wpfui/2022/xaml"
mc:Ignorable="d"
d:DesignHeight="80" d:DesignWidth="700">
<!-- DataContext = AutoTestProgressViewModel (may be null — NullToVis collapses the border) -->
<UserControl.Resources>
<BooleanToVisibilityConverter x:Key="BoolToVis"/>
</UserControl.Resources>
<Border Visibility="{Binding Converter={StaticResource NullToVis}}"
Style="{StaticResource SnackbarShell}"
HorizontalAlignment="Center" VerticalAlignment="Bottom"
MinWidth="560" MaxWidth="860">
<DockPanel LastChildFill="True" Margin="16,10">
<!-- ── Leading state icon ────────────────────────────────────── -->
<Grid DockPanel.Dock="Left" Width="30" Height="30" Margin="0,0,12,0">
<!-- In-progress spinner -->
<ui:ProgressRing IsIndeterminate="True" Width="24" Height="24">
<ui:ProgressRing.Style>
<Style TargetType="ui:ProgressRing">
<Setter Property="Visibility" Value="Visible"/>
<Style.Triggers>
<DataTrigger Binding="{Binding IsComplete}" Value="True">
<Setter Property="Visibility" Value="Collapsed"/>
</DataTrigger>
</Style.Triggers>
</Style>
</ui:ProgressRing.Style>
</ui:ProgressRing>
<!-- Success check -->
<ui:SymbolIcon Symbol="CheckmarkCircle24" FontSize="24"
Foreground="{DynamicResource SystemFillColorSuccessBrush}">
<ui:SymbolIcon.Style>
<Style TargetType="ui:SymbolIcon">
<Setter Property="Visibility" Value="Collapsed"/>
<Style.Triggers>
<DataTrigger Binding="{Binding IsSuccess}" Value="True">
<Setter Property="Visibility" Value="Visible"/>
</DataTrigger>
</Style.Triggers>
</Style>
</ui:SymbolIcon.Style>
</ui:SymbolIcon>
<!-- Failure/cancel X -->
<ui:SymbolIcon Symbol="DismissCircle24" FontSize="24"
Foreground="{DynamicResource SystemFillColorCriticalBrush}">
<ui:SymbolIcon.Style>
<Style TargetType="ui:SymbolIcon">
<Setter Property="Visibility" Value="Collapsed"/>
<Style.Triggers>
<MultiDataTrigger>
<MultiDataTrigger.Conditions>
<Condition Binding="{Binding IsComplete}" Value="True"/>
<Condition Binding="{Binding IsSuccess}" Value="False"/>
</MultiDataTrigger.Conditions>
<Setter Property="Visibility" Value="Visible"/>
</MultiDataTrigger>
</Style.Triggers>
</Style>
</ui:SymbolIcon.Style>
</ui:SymbolIcon>
</Grid>
<!-- ── Trailing buttons ───────────────────────────────────────── -->
<StackPanel DockPanel.Dock="Right" Orientation="Horizontal" VerticalAlignment="Center" Margin="12,0,0,0">
<TextBlock Text="{Binding Progress, StringFormat={}{0}%}"
FontFamily="Consolas" FontSize="13" FontWeight="SemiBold"
Foreground="{DynamicResource TextFillColorSecondaryBrush}"
VerticalAlignment="Center" Margin="0,0,12,0"/>
<ui:Button Content="{DynamicResource Common.Cancel}"
Command="{Binding CancelCommand}"
Appearance="Caution"
Visibility="{Binding IsCancellable, Converter={StaticResource BoolToVis}}"
Height="30" Padding="12,4"/>
<ui:Button Content="{DynamicResource Common.Dismiss}"
Click="OnDismissClick"
Appearance="Secondary"
Visibility="{Binding IsComplete, Converter={StaticResource BoolToVis}}"
Height="30" Padding="12,4"/>
</StackPanel>
<!-- ── Middle: type label + phase text + progress bar ─────────── -->
<StackPanel VerticalAlignment="Center">
<DockPanel>
<TextBlock DockPanel.Dock="Left"
Text="{Binding TypeLabel}"
FontWeight="SemiBold" FontSize="12"
Foreground="{DynamicResource AccentTextFillColorPrimaryBrush}"
VerticalAlignment="Center" Margin="0,0,8,0"/>
<TextBlock Text="{Binding PhaseText}"
FontSize="12"
Foreground="{DynamicResource TextFillColorPrimaryBrush}"
VerticalAlignment="Center"
TextTrimming="CharacterEllipsis"/>
</DockPanel>
<ProgressBar Value="{Binding Progress, Mode=OneWay}"
Minimum="0" Maximum="100" Height="3"
Margin="0,4,0,0"/>
</StackPanel>
</DockPanel>
</Border>
</UserControl>

View File

@@ -0,0 +1,59 @@
using System;
using System.ComponentModel;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Threading;
using HC_APTBS.ViewModels.Dialogs;
namespace HC_APTBS.Views.UserControls
{
/// <summary>
/// Shell-level snackbar that mirrors <see cref="UnlockSnackbarView"/> but binds to
/// <see cref="AutoTestProgressViewModel"/>. Auto-dismisses three seconds after a
/// successful run; a failed or cancelled run stays until the operator clicks Dismiss.
/// </summary>
public partial class AutoTestSnackbarView : UserControl
{
private DispatcherTimer? _autoHideTimer;
public AutoTestSnackbarView()
{
InitializeComponent();
DataContextChanged += OnDataContextChanged;
}
private void OnDataContextChanged(object sender, DependencyPropertyChangedEventArgs e)
{
if (e.OldValue is AutoTestProgressViewModel oldVm)
oldVm.PropertyChanged -= OnVmPropertyChanged;
if (e.NewValue is AutoTestProgressViewModel newVm)
newVm.PropertyChanged += OnVmPropertyChanged;
}
private void OnVmPropertyChanged(object? sender, PropertyChangedEventArgs e)
{
if (e.PropertyName != nameof(AutoTestProgressViewModel.IsSuccess)) return;
if (DataContext is not AutoTestProgressViewModel vm) return;
if (vm.IsSuccess == true)
{
_autoHideTimer?.Stop();
_autoHideTimer = new DispatcherTimer { Interval = TimeSpan.FromSeconds(3) };
_autoHideTimer.Tick += (_, _) =>
{
_autoHideTimer!.Stop();
if (vm.CloseCommand.CanExecute(null))
vm.CloseCommand.Execute(null);
};
_autoHideTimer.Start();
}
}
private void OnDismissClick(object sender, RoutedEventArgs e)
{
if (DataContext is AutoTestProgressViewModel vm && vm.CloseCommand.CanExecute(null))
vm.CloseCommand.Execute(null);
}
}
}

View File

@@ -92,31 +92,31 @@
</Grid>
<!-- ── Preset RPM buttons 2×4 ──────────────────────────────── -->
<UniformGrid Rows="2" Columns="4" Margin="0,0,0,10">
<UniformGrid Rows="2" Columns="4" Margin="-2,0,-2,10">
<ui:Button Content="100" CommandParameter="100"
Command="{Binding BenchControl.SetQuickRpmCommand}"
Appearance="Secondary" Height="40" Margin="2" FontSize="12"/>
Appearance="Secondary" Height="40" HorizontalAlignment="Stretch" Margin="2" FontSize="12"/>
<ui:Button Content="200" CommandParameter="200"
Command="{Binding BenchControl.SetQuickRpmCommand}"
Appearance="Secondary" Height="40" Margin="2" FontSize="12"/>
Appearance="Secondary" Height="40" HorizontalAlignment="Stretch" Margin="2" FontSize="12"/>
<ui:Button Content="500" CommandParameter="500"
Command="{Binding BenchControl.SetQuickRpmCommand}"
Appearance="Secondary" Height="40" Margin="2" FontSize="12"/>
Appearance="Secondary" Height="40" HorizontalAlignment="Stretch" Margin="2" FontSize="12"/>
<ui:Button Content="750" CommandParameter="750"
Command="{Binding BenchControl.SetQuickRpmCommand}"
Appearance="Secondary" Height="40" Margin="2" FontSize="12"/>
Appearance="Secondary" Height="40" HorizontalAlignment="Stretch" Margin="2" FontSize="12"/>
<ui:Button Content="1000" CommandParameter="1000"
Command="{Binding BenchControl.SetQuickRpmCommand}"
Appearance="Secondary" Height="40" Margin="2" FontSize="12"/>
Appearance="Secondary" Height="40" HorizontalAlignment="Stretch" Margin="2" FontSize="12"/>
<ui:Button Content="1250" CommandParameter="1250"
Command="{Binding BenchControl.SetQuickRpmCommand}"
Appearance="Secondary" Height="40" Margin="2" FontSize="12"/>
Appearance="Secondary" Height="40" HorizontalAlignment="Stretch" Margin="2" FontSize="12"/>
<ui:Button Content="1500" CommandParameter="1500"
Command="{Binding BenchControl.SetQuickRpmCommand}"
Appearance="Secondary" Height="40" Margin="2" FontSize="12"/>
Appearance="Secondary" Height="40" HorizontalAlignment="Stretch" Margin="2" FontSize="12"/>
<ui:Button Content="2000" CommandParameter="2000"
Command="{Binding BenchControl.SetQuickRpmCommand}"
Appearance="Secondary" Height="40" Margin="2" FontSize="12"/>
Appearance="Secondary" Height="40" HorizontalAlignment="Stretch" Margin="2" FontSize="12"/>
</UniformGrid>
<!-- ── Start / Stop ─────────────────────────────────────────── -->
@@ -129,13 +129,13 @@
<ui:Button Grid.Column="0"
Content="{DynamicResource Bench.Start}"
Command="{Binding BenchControl.StartBenchCommand}"
Appearance="Primary" Height="46" FontWeight="Bold" FontSize="14">
Appearance="Primary" Height="46" HorizontalAlignment="Stretch" FontWeight="Bold" FontSize="14">
<ui:Button.Icon><ui:SymbolIcon Symbol="Play24"/></ui:Button.Icon>
</ui:Button>
<ui:Button Grid.Column="2"
Content="{DynamicResource Bench.Stop}"
Command="{Binding BenchControl.StopBenchCommand}"
Appearance="Danger" Height="46" FontWeight="Bold" FontSize="14">
Appearance="Danger" Height="46" HorizontalAlignment="Stretch" FontWeight="Bold" FontSize="14">
<ui:Button.Icon><ui:SymbolIcon Symbol="Stop24"/></ui:Button.Icon>
</ui:Button>
</Grid>

View File

@@ -0,0 +1,113 @@
<UserControl x:Class="HC_APTBS.Views.UserControls.BipDisplayView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:conv="clr-namespace:HC_APTBS.Converters"
mc:Ignorable="d"
d:DesignHeight="160" d:DesignWidth="480">
<UserControl.Resources>
<conv:HexColorToBrushConverter x:Key="HexToBrush"/>
<BooleanToVisibilityConverter x:Key="BoolToVis"/>
</UserControl.Resources>
<!-- Outer visibility: hidden for non-PSG5-PI pumps -->
<Grid Visibility="{Binding HasDefinition, Converter={StaticResource BoolToVis}}">
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<!-- Header bar: title + raw word chip -->
<DockPanel Margin="0,0,0,4">
<Border DockPanel.Dock="Right"
Background="{DynamicResource ControlFillColorSecondaryBrush}"
BorderBrush="{DynamicResource ControlStrokeColorDefaultBrush}"
BorderThickness="1" CornerRadius="10" Padding="8,2">
<StackPanel Orientation="Horizontal" VerticalAlignment="Center">
<TextBlock Text="{DynamicResource Pump.Bip.RawLabel}" FontSize="11"
Foreground="{DynamicResource TextFillColorSecondaryBrush}"
Margin="0,0,3,0"/>
<TextBlock Text="{Binding RawValue}"
FontSize="11" FontWeight="SemiBold"
Foreground="{DynamicResource TextFillColorPrimaryBrush}"
FontFamily="Consolas, Courier New"/>
</StackPanel>
</Border>
<TextBlock Text="{DynamicResource Pump.Bip.Title}"
FontSize="12" FontWeight="SemiBold"
FontFamily="{DynamicResource ContentControlThemeFontFamily}"
Foreground="{DynamicResource TextFillColorPrimaryBrush}"
VerticalAlignment="Center"/>
</DockPanel>
<!-- BIP rows table -->
<ItemsControl Grid.Row="1" ItemsSource="{Binding Rows}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel Orientation="Vertical"/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<Border Margin="0,1" CornerRadius="4"
BorderThickness="1"
BorderBrush="{DynamicResource ControlStrokeColorDefaultBrush}"
Padding="6,3"
Background="{DynamicResource ControlFillColorDefaultBrush}"
SnapsToDevicePixels="True">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="22"/>
<ColumnDefinition Width="16"/>
<ColumnDefinition Width="58"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<!-- Colour indicator dot -->
<Ellipse Grid.Column="0"
Width="10" Height="10"
HorizontalAlignment="Center" VerticalAlignment="Center"
Fill="{Binding Color, Converter={StaticResource HexToBrush}}"/>
<!-- Index label -->
<TextBlock Grid.Column="1"
Text="{Binding Index}"
FontSize="11" FontWeight="SemiBold" VerticalAlignment="Center"
Foreground="{DynamicResource TextFillColorSecondaryBrush}"/>
<!-- HEX pattern -->
<TextBlock Grid.Column="2"
Text="{Binding HexPattern}"
FontSize="11" FontFamily="Consolas, Courier New"
VerticalAlignment="Center"
Foreground="{DynamicResource TextFillColorSecondaryBrush}"/>
<!-- Description -->
<TextBlock Grid.Column="3"
Text="{Binding Description}"
FontSize="11" VerticalAlignment="Center"
TextTrimming="CharacterEllipsis"
ToolTip="{Binding Description}"
ToolTipService.InitialShowDelay="200"
ToolTipService.ShowDuration="30000">
<TextBlock.Style>
<Style TargetType="TextBlock">
<Setter Property="Foreground" Value="{DynamicResource TextFillColorPrimaryBrush}"/>
<Style.Triggers>
<DataTrigger Binding="{Binding IsActive}" Value="True">
<Setter Property="Foreground" Value="#FF4444"/>
<Setter Property="FontWeight" Value="SemiBold"/>
</DataTrigger>
</Style.Triggers>
</Style>
</TextBlock.Style>
</TextBlock>
</Grid>
</Border>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</Grid>
</UserControl>

View File

@@ -0,0 +1,12 @@
using System.Windows.Controls;
namespace HC_APTBS.Views.UserControls
{
public partial class BipDisplayView : UserControl
{
public BipDisplayView()
{
InitializeComponent();
}
}
}

View File

@@ -11,6 +11,9 @@
Devices column — three equal-height tiles (CAN / K-Line / Bench).
DataContext is DashboardPageViewModel; all commands/collections are under Devices.
-->
<UserControl.Resources>
<BooleanToVisibilityConverter x:Key="BoolToVis"/>
</UserControl.Resources>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="*"/>
@@ -74,16 +77,29 @@
Command="{Binding DataContext.Devices.ToggleDeviceCommand,
RelativeSource={RelativeSource AncestorType=UserControl}}"
CommandParameter="{Binding}"
IsEnabled="{Binding IsEnabled}">
IsEnabled="{Binding IsEnabled}"
ToolTip="{Binding StateLabel}">
<DockPanel>
<TextBlock Text="{Binding StateLabel}"
DockPanel.Dock="Right"
FontSize="11" FontWeight="SemiBold"
Foreground="{DynamicResource TextFillColorSecondaryBrush}"
VerticalAlignment="Center" Margin="6,0,0,0"/>
VerticalAlignment="Center" Margin="6,0,0,0">
<TextBlock.Style>
<Style TargetType="TextBlock">
<Setter Property="Foreground" Value="{DynamicResource TextFillColorTertiaryBrush}"/>
<Style.Triggers>
<DataTrigger Binding="{Binding IsConnected}" Value="True">
<Setter Property="Foreground" Value="{DynamicResource SystemFillColorSuccessBrush}"/>
</DataTrigger>
</Style.Triggers>
</Style>
</TextBlock.Style>
</TextBlock>
<Ellipse DockPanel.Dock="Left">
<Ellipse.Style>
<Style TargetType="Ellipse" BasedOn="{StaticResource StatusDot}">
<!-- Default gray: device enumerated but not owned. -->
<Setter Property="Fill" Value="{DynamicResource TextFillColorTertiaryBrush}"/>
<Style.Triggers>
<DataTrigger Binding="{Binding IsConnected}" Value="True">
<Setter Property="Fill" Value="{DynamicResource SystemFillColorSuccessBrush}"/>
@@ -180,10 +196,15 @@
<Ellipse DockPanel.Dock="Left">
<Ellipse.Style>
<Style TargetType="Ellipse" BasedOn="{StaticResource StatusDot}">
<!-- Default blue: FTDI detected but no session. -->
<Setter Property="Fill" Value="{DynamicResource AccentFillColorDefaultBrush}"/>
<Style.Triggers>
<DataTrigger Binding="{Binding IsConnected}" Value="True">
<Setter Property="Fill" Value="{DynamicResource SystemFillColorSuccessBrush}"/>
</DataTrigger>
<DataTrigger Binding="{Binding IsFailed}" Value="True">
<Setter Property="Fill" Value="{DynamicResource SystemFillColorCriticalBrush}"/>
</DataTrigger>
</Style.Triggers>
</Style>
</Ellipse.Style>
@@ -268,5 +289,7 @@
</Grid>
</Border>
<!-- CAN connect/disconnect snackbar is anchored page-bottom in MainWindow.xaml. -->
</Grid>
</UserControl>

View File

@@ -37,13 +37,13 @@
VerticalAlignment="Center" Margin="0,0,8,0"/>
<ComboBox Grid.Column="1"
SelectedIndex="{Binding VersionIndex}"
Height="28">
Height="36">
<ComboBoxItem Content="V1"/>
<ComboBoxItem Content="V2"/>
<ComboBoxItem Content="V3"/>
<ComboBoxItem Content="V4"/>
</ComboBox>
<CheckBox Grid.Column="2"
<CheckBox Grid.Column="2" HorizontalAlignment="Right"
IsChecked="{Binding IsAutoMode}"
Content="{DynamicResource Dfi.Auto}"
FontSize="12"
@@ -82,7 +82,7 @@
Visibility="{Binding IsBusy, Converter={StaticResource BoolToVis}}"/>
<!-- ── DFI slider ─────────────────────────────────────────────── -->
<Grid DockPanel.Dock="Top" Margin="0,0,0,8">
<Grid DockPanel.Dock="Top" Margin="0,0,0,8" Height="64">
<Slider Value="{Binding SliderRaw}"
Minimum="-145" Maximum="145"
TickFrequency="5" SmallChange="5" LargeChange="5"
@@ -115,12 +115,12 @@
<ColumnDefinition Width="8"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<ui:Button Grid.Column="0"
<ui:Button Grid.Column="0" HorizontalAlignment="Stretch"
Content="{DynamicResource Dfi.Read}"
Command="{Binding ReadDfiCommand}"
Appearance="Secondary"
FontWeight="Bold" Height="32"/>
<ui:Button Grid.Column="2"
<ui:Button Grid.Column="2" HorizontalAlignment="Stretch"
Content="{DynamicResource Dfi.Write}"
Command="{Binding WriteDfiCommand}"
Appearance="Primary"

View File

@@ -27,11 +27,11 @@
<ui:Button Content="{DynamicResource Dtc.Read}"
Command="{Binding ReadCommand}"
Appearance="Secondary"
Height="28" Margin="0,0,6,0"/>
Height="32" Margin="0,0,6,0"/>
<ui:Button Content="{DynamicResource Dtc.Clear}"
Command="{Binding ClearCommand}"
Appearance="Secondary"
Height="28"/>
Height="32"/>
</StackPanel>
</DockPanel>

View File

@@ -3,71 +3,107 @@
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:sys="clr-namespace:System;assembly=System.Runtime"
xmlns:conv="clr-namespace:HC_APTBS.Converters"
xmlns:vm="clr-namespace:HC_APTBS.ViewModels"
mc:Ignorable="d"
d:DataContext="{d:DesignInstance Type=vm:GraphicIndicatorViewModel, IsDesignTimeCreatable=False}">
<!--
Vertical progress bar showing a single measurement value against its
expected value and tolerance band. Shared across phase cards and the
Running view's live measurement table.
Vertical min/max/target gauge. Fluent-styled with DynamicResource tokens so
the control repaints on theme change. The bar fills from the bottom via an
inner ProgressBar (native easing on Value changes). An overlay rectangle
highlights the in-tolerance band; a thin accent line marks the target.
Fill colour reacts live to IsWithinTolerance and locks on IsPhaseCompleted
when the owning phase ends.
-->
<UserControl.Resources>
<conv:PercentToPixelsConverter x:Key="PercentToPixels"/>
<sys:Double x:Key="TrackHeightPx">92</sys:Double>
<!-- Fill-colour style for the inner ProgressBar. Triggers cascade:
phase completed + failed → critical (locked);
live out-of-tolerance → critical;
else → accent. -->
<Style x:Key="IndicatorFillStyle" TargetType="ProgressBar">
<Setter Property="Foreground" Value="{DynamicResource AccentFillColorDefaultBrush}"/>
<Style.Triggers>
<DataTrigger Binding="{Binding IsWithinTolerance}" Value="False">
<Setter Property="Foreground" Value="{DynamicResource SystemFillColorCriticalBrush}"/>
</DataTrigger>
<MultiDataTrigger>
<MultiDataTrigger.Conditions>
<Condition Binding="{Binding IsPhaseCompleted}" Value="True"/>
<Condition Binding="{Binding PhasePassed}" Value="False"/>
</MultiDataTrigger.Conditions>
<Setter Property="Foreground" Value="{DynamicResource SystemFillColorCriticalBrush}"/>
</MultiDataTrigger>
</Style.Triggers>
</Style>
</UserControl.Resources>
<Grid Width="58" Margin="2,0">
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/> <!-- Max label -->
<RowDefinition Height="90"/> <!-- Progress bar -->
<RowDefinition Height="Auto"/> <!-- Min label -->
<RowDefinition Height="Auto"/> <!-- Param name -->
<RowDefinition Height="Auto"/> <!-- Max label -->
<RowDefinition Height="92"/> <!-- Track -->
<RowDefinition Height="Auto"/> <!-- Min label -->
<RowDefinition Height="Auto"/> <!-- Param name -->
</Grid.RowDefinitions>
<!-- Max bound -->
<TextBlock Text="{Binding MaxBound, StringFormat=F1}"
FontSize="9" Foreground="Gray"
HorizontalAlignment="Center" Margin="0,0,0,1"/>
FontSize="10"
Foreground="{DynamicResource TextFillColorSecondaryBrush}"
HorizontalAlignment="Center" Margin="0,0,0,2"/>
<Grid Grid.Row="1">
<Border BorderBrush="Black" BorderThickness="1" SnapsToDevicePixels="True"/>
<!-- Gauge track -->
<Grid Grid.Row="1" SnapsToDevicePixels="True">
<!-- Track background -->
<Border Background="{DynamicResource ControlFillColorDefaultBrush}"
BorderBrush="{DynamicResource ControlStrokeColorDefaultBrush}"
BorderThickness="1"
CornerRadius="3"/>
<!-- In-tolerance band -->
<Canvas ClipToBounds="True" Margin="1">
<Rectangle Width="56"
Fill="{DynamicResource AccentFillColorTertiaryBrush}"
Opacity="0.10"
Canvas.Left="0"
Canvas.Top="{Binding ToleranceBandTopPercent,
Converter={StaticResource PercentToPixels},
ConverterParameter={StaticResource TrackHeightPx}}"
Height="{Binding ToleranceBandHeightPercent,
Converter={StaticResource PercentToPixels},
ConverterParameter={StaticResource TrackHeightPx}}"/>
<!-- Target line (1 px accent) -->
<Line X1="0" X2="56" StrokeThickness="0.4" Opacity="0.20"
Stroke="{DynamicResource AccentTextFillColorPrimaryBrush}"
Canvas.Top="{Binding ExpectedMarkerPercent,
Converter={StaticResource PercentToPixels},
ConverterParameter={StaticResource TrackHeightPx}}"/>
</Canvas>
<!-- Fill bar (bottom-up) -->
<ProgressBar Orientation="Vertical"
Minimum="0" Maximum="100"
Value="{Binding ProgressPercent, Mode=OneWay}"
Background="Transparent"
BorderThickness="0"
Background="White"
Margin="1">
<ProgressBar.Style>
<Style TargetType="ProgressBar">
<Setter Property="Foreground" Value="#4CAF50"/>
<Style.Triggers>
<DataTrigger Binding="{Binding IsWithinTolerance}" Value="False">
<Setter Property="Foreground" Value="#FF5722"/>
</DataTrigger>
</Style.Triggers>
</Style>
</ProgressBar.Style>
</ProgressBar>
<Canvas ClipToBounds="True">
<Line X1="0" X2="58" StrokeDashArray="3,2"
Stroke="LightGray" StrokeThickness="1"
Canvas.Top="18"/>
<Line X1="0" X2="58" StrokeDashArray="3,2"
Stroke="LightGray" StrokeThickness="1"
Canvas.Top="72"/>
</Canvas>
<TextBlock Text="{Binding ExpectedValue, StringFormat=F1}"
FontSize="9" Foreground="#999999"
HorizontalAlignment="Center" VerticalAlignment="Center"
Margin="0,-14,0,0"/>
Opacity="0.55"
Margin="1"
Style="{StaticResource IndicatorFillStyle}"/>
<!-- Numeric readout -->
<TextBlock Text="{Binding DisplayValue}"
FontSize="14" FontWeight="Black"
HorizontalAlignment="Center" VerticalAlignment="Center"
Margin="0,10,0,0">
FontSize="14" FontWeight="SemiBold"
HorizontalAlignment="Center" VerticalAlignment="Center">
<TextBlock.Style>
<Style TargetType="TextBlock">
<Setter Property="Foreground" Value="Black"/>
<Setter Property="Foreground" Value="{DynamicResource TextFillColorPrimaryBrush}"/>
<Style.Triggers>
<DataTrigger Binding="{Binding HasValue}" Value="False">
<Setter Property="Foreground" Value="#CCCCCC"/>
<Setter Property="Foreground" Value="{DynamicResource TextFillColorTertiaryBrush}"/>
</DataTrigger>
</Style.Triggers>
</Style>
@@ -75,16 +111,21 @@
</TextBlock>
</Grid>
<!-- Min bound -->
<TextBlock Grid.Row="2"
Text="{Binding MinBound, StringFormat=F1}"
FontSize="9" Foreground="Gray"
HorizontalAlignment="Center" Margin="0,1,0,0"/>
FontSize="10"
Foreground="{DynamicResource TextFillColorSecondaryBrush}"
HorizontalAlignment="Center" Margin="0,2,0,0"/>
<!-- Parameter name -->
<TextBlock Grid.Row="3"
Text="{Binding ParameterName}"
FontSize="9" FontWeight="SemiBold"
FontSize="11" FontWeight="SemiBold"
Foreground="{DynamicResource TextFillColorSecondaryBrush}"
HorizontalAlignment="Center"
TextTrimming="CharacterEllipsis"
ToolTip="{Binding ParameterName}"/>
ToolTip="{Binding ParameterName}"
Margin="0,1,0,0"/>
</Grid>
</UserControl>

View File

@@ -14,7 +14,8 @@
<BooleanToVisibilityConverter x:Key="BoolToVis"/>
</UserControl.Resources>
<Border CornerRadius="3" Padding="10,6" Margin="0,4"
<Border
BorderThickness="1" CornerRadius="8" Padding="14,10" Margin="0,0,0,8"
Visibility="{Binding IsVisible, Converter={StaticResource BoolToVis}}">
<Border.Style>
<Style TargetType="Border">

View File

@@ -9,8 +9,8 @@
d:DataContext="{d:DesignInstance Type=vm:PhaseCardViewModel, IsDesignTimeCreatable=False}">
<!--
Single phase card. DataContext: PhaseCardViewModel.
Shared between TestPlanView (shows ReadyValues/OperationValues and enable toggle)
and TestRunningView (shows live ResultIndicators and pass/fail colour).
Renders ReadyValues/OperationValues and the enable toggle when idle, and
switches to live ResultIndicators with pass/fail colour while running.
-->
<UserControl.Resources>
<BooleanToVisibilityConverter x:Key="BoolToVis"/>

View File

@@ -0,0 +1,205 @@
<UserControl x:Class="HC_APTBS.Views.UserControls.PhaseTileView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:ui="http://schemas.lepo.co/wpfui/2022/xaml"
xmlns:uc="clr-namespace:HC_APTBS.Views.UserControls"
xmlns:vm="clr-namespace:HC_APTBS.ViewModels"
mc:Ignorable="d"
x:Name="Root"
d:DataContext="{d:DesignInstance Type=vm:PhaseCardViewModel, IsDesignTimeCreatable=False}">
<!--
Fluent phase tile used by both the Plan and Running steps. Set
Compact="true" in Plan to collapse the vertical indicators (showing the
receive parameter list as chips instead) and keep the tile short.
The hover tooltip replaces the old "Show values" toggle globally.
-->
<UserControl.Resources>
<BooleanToVisibilityConverter x:Key="BoolToVis"/>
<DataTemplate DataType="{x:Type vm:OperationValueViewModel}">
<Grid Margin="0,1,0,1">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<TextBlock Text="{Binding Name}" FontSize="11"
Foreground="{DynamicResource TextFillColorSecondaryBrush}"/>
<TextBlock Grid.Column="2"
Text="{Binding Value, StringFormat=F1}"
FontSize="11" FontFamily="Consolas"
Margin="12,0,0,0"
Foreground="{DynamicResource TextFillColorPrimaryBrush}"/>
</Grid>
</DataTemplate>
<!-- Full vertical indicator (used when NOT compact) -->
<DataTemplate x:Key="FullIndicator" DataType="{x:Type vm:GraphicIndicatorViewModel}">
<uc:GraphicIndicatorView/>
</DataTemplate>
<!-- Compact chip (name + expected±tolerance) for Plan step -->
<DataTemplate x:Key="CompactIndicator" DataType="{x:Type vm:GraphicIndicatorViewModel}">
<Border Background="{DynamicResource ControlFillColorSecondaryBrush}"
BorderBrush="{DynamicResource ControlStrokeColorDefaultBrush}"
BorderThickness="1" CornerRadius="8" Padding="6,1"
Margin="0,1,3,1">
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding ParameterName}" FontSize="10"
Foreground="{DynamicResource TextFillColorPrimaryBrush}"/>
<TextBlock Text=":" FontSize="10" Margin="1,0,2,0"
Foreground="{DynamicResource TextFillColorSecondaryBrush}"/>
<TextBlock FontSize="10" FontFamily="Consolas"
Foreground="{DynamicResource TextFillColorSecondaryBrush}">
<Run Text="{Binding ExpectedValue, StringFormat=F1, Mode=OneWay}"/>
<Run Text="±"/>
<Run Text="{Binding Tolerance, StringFormat=F1, Mode=OneWay}"/>
</TextBlock>
</StackPanel>
</Border>
</DataTemplate>
</UserControl.Resources>
<Border MinWidth="84"
Cursor="Hand"
ToolTipService.InitialShowDelay="200"
ToolTipService.ShowDuration="30000"
ToolTipService.Placement="Right">
<Border.InputBindings>
<MouseBinding Gesture="LeftClick"
Command="{Binding ToggleEnabledCommand}"/>
</Border.InputBindings>
<Border.Style>
<Style TargetType="Border" BasedOn="{StaticResource PhaseTile}">
<Style.Triggers>
<Trigger Property="IsMouseOver" Value="True">
<Setter Property="Background" Value="{DynamicResource ControlFillColorSecondaryBrush}"/>
<Setter Property="BorderBrush" Value="{DynamicResource AccentControlElevationBorderBrush}"/>
</Trigger>
<DataTrigger Binding="{Binding IsActive}" Value="True">
<Setter Property="Background" Value="#FFE082"/>
<Setter Property="BorderBrush" Value="#F9A825"/>
</DataTrigger>
<DataTrigger Binding="{Binding IsPassed}" Value="True">
<Setter Property="Background" Value="#C8E6C9"/>
<Setter Property="BorderBrush" Value="#388E3C"/>
</DataTrigger>
<DataTrigger Binding="{Binding IsFailed}" Value="True">
<Setter Property="Background" Value="#FFCDD2"/>
<Setter Property="BorderBrush" Value="#C62828"/>
</DataTrigger>
<DataTrigger Binding="{Binding IsEnabled}" Value="False">
<Setter Property="Background" Value="#3F808080"/>
<Setter Property="BorderBrush" Value="#60808080"/>
<Setter Property="Opacity" Value="0.6"/>
</DataTrigger>
</Style.Triggers>
</Style>
</Border.Style>
<Border.ToolTip>
<ToolTip Padding="10" MaxWidth="260">
<StackPanel>
<TextBlock Text="{Binding Name}"
FontSize="13" FontWeight="SemiBold"
Foreground="{DynamicResource TextFillColorPrimaryBrush}"
Margin="0,0,0,4"/>
<TextBlock Text="{DynamicResource Test.Critical}"
FontSize="10" FontWeight="Bold" Foreground="#E65100"
Margin="0,0,0,6"
Visibility="{Binding IsCritical, Converter={StaticResource BoolToVis}}"/>
<StackPanel Visibility="{Binding ReadyValues.Count, FallbackValue=Collapsed}">
<TextBlock Text="{DynamicResource Test.Required}"
FontSize="10" FontWeight="SemiBold"
Foreground="{DynamicResource TextFillColorSecondaryBrush}"
Margin="0,2,0,2"/>
<ItemsControl ItemsSource="{Binding ReadyValues}" Margin="0,0,0,4"/>
</StackPanel>
<StackPanel Visibility="{Binding OperationValues.Count, FallbackValue=Collapsed}">
<TextBlock Text="{DynamicResource Test.TestLabel}"
FontSize="10" FontWeight="SemiBold"
Foreground="{DynamicResource TextFillColorSecondaryBrush}"
Margin="0,2,0,2"/>
<ItemsControl ItemsSource="{Binding OperationValues}"/>
</StackPanel>
</StackPanel>
</ToolTip>
</Border.ToolTip>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<!-- Row 0: name + critical dot (click the tile to toggle IsEnabled) -->
<Grid Margin="0,0,0,2">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<TextBlock Grid.Column="0"
Text="{Binding Name}"
FontSize="11" FontWeight="SemiBold"
Foreground="{DynamicResource TextFillColorPrimaryBrush}"
TextTrimming="CharacterEllipsis"
VerticalAlignment="Center"/>
<Border Grid.Column="1"
Width="8" Height="8" CornerRadius="4"
Background="#E65100" Margin="4,0,0,0"
VerticalAlignment="Center"
ToolTip="{DynamicResource Test.Critical}"
Visibility="{Binding IsCritical, Converter={StaticResource BoolToVis}}"/>
</Grid>
<!-- Row 1: indicators — compact chips when Compact=true, else full bars -->
<ItemsControl Grid.Row="1" ItemsSource="{Binding ResultIndicators}"
Margin="0,2,0,0">
<ItemsControl.Style>
<Style TargetType="ItemsControl">
<Setter Property="ItemTemplate" Value="{StaticResource FullIndicator}"/>
<Style.Triggers>
<DataTrigger Binding="{Binding Compact, ElementName=Root}" Value="True">
<Setter Property="ItemTemplate" Value="{StaticResource CompactIndicator}"/>
</DataTrigger>
</Style.Triggers>
</Style>
</ItemsControl.Style>
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<WrapPanel Orientation="Horizontal" HorizontalAlignment="Center"/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
</ItemsControl>
<!-- Row 2: result label -->
<TextBlock Grid.Row="2"
Visibility="Collapsed"
Text="{Binding ResultText}"
FontSize="11" FontWeight="SemiBold"
HorizontalAlignment="Center"
Margin="0,3,0,0">
<TextBlock.Style>
<Style TargetType="TextBlock">
<Setter Property="Foreground"
Value="{DynamicResource TextFillColorSecondaryBrush}"/>
<Style.Triggers>
<DataTrigger Binding="{Binding IsPassed}" Value="True">
<Setter Property="Foreground" Value="#2E7D32"/>
</DataTrigger>
<DataTrigger Binding="{Binding IsFailed}" Value="True">
<Setter Property="Foreground" Value="#B71C1C"/>
</DataTrigger>
</Style.Triggers>
</Style>
</TextBlock.Style>
</TextBlock>
</Grid>
</Border>
</UserControl>

View File

@@ -0,0 +1,31 @@
using System.Windows;
using System.Windows.Controls;
namespace HC_APTBS.Views.UserControls
{
/// <summary>
/// Compact Fluent phase tile. Set <see cref="Compact"/>=<c>true</c> in the
/// Plan step so the receives render as chips instead of tall vertical
/// progress bars — keeps every section card under one screen.
/// DataContext is expected to be a <see cref="HC_APTBS.ViewModels.PhaseCardViewModel"/>.
/// </summary>
public partial class PhaseTileView : UserControl
{
/// <summary>When true, receives render as small text chips instead of vertical indicators.</summary>
public static readonly DependencyProperty CompactProperty =
DependencyProperty.Register(nameof(Compact), typeof(bool), typeof(PhaseTileView),
new PropertyMetadata(false));
/// <summary>Compact mode flag (see <see cref="CompactProperty"/>).</summary>
public bool Compact
{
get => (bool)GetValue(CompactProperty);
set => SetValue(CompactProperty, value);
}
public PhaseTileView()
{
InitializeComponent();
}
}
}

View File

@@ -5,7 +5,7 @@
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:ui="http://schemas.lepo.co/wpfui/2022/xaml"
mc:Ignorable="d"
d:DesignHeight="460" d:DesignWidth="260"
d:DesignHeight="490" d:DesignWidth="260"
IsEnabled="{Binding IsEnabled}">
<!-- DataContext = PumpControlViewModel (via {Binding PumpControl}) -->
@@ -24,6 +24,7 @@
<Setter Property="Margin" Value="0,2,0,0"/>
<Setter Property="HorizontalAlignment" Value="Center"/>
</Style>
</UserControl.Resources>
<Border Style="{StaticResource PumpCard}">
@@ -63,16 +64,16 @@
Margin="0,4,0,2"/>
<!-- Vertical slider -->
<Slider Orientation="Vertical"
<Slider Style="{StaticResource FluentThickVerticalSlider}"
Orientation="Vertical"
Minimum="{Binding FbkwMin}" Maximum="{Binding FbkwMax}"
Value="{Binding FbkwValue}"
TickFrequency="{Binding FbkwStep}"
TickPlacement="TopLeft"
IsSnapToTickEnabled="True"
AutoToolTipPrecision="2"
Height="240" Width="32"
Height="360" Width="52"
HorizontalAlignment="Center"
Focusable="False"/>
Loaded="Slider_Loaded"/>
<!-- Min label -->
<TextBlock Text="{Binding FbkwMin, StringFormat=F1}"
@@ -123,16 +124,16 @@
Style="{StaticResource PumpCommandLabel}"
Margin="0,4,0,2"/>
<Slider Orientation="Vertical"
<Slider Style="{StaticResource FluentThickVerticalSlider}"
Orientation="Vertical"
Minimum="{Binding MeMin}" Maximum="{Binding MeMax}"
Value="{Binding MeValue}"
TickFrequency="{Binding MeStep}"
TickPlacement="TopLeft"
IsSnapToTickEnabled="False"
AutoToolTipPrecision="2"
Height="240" Width="32"
Height="360" Width="52"
HorizontalAlignment="Center"
Focusable="False"/>
Loaded="Slider_Loaded"/>
<TextBlock Text="{Binding MeMin, StringFormat=F1}"
Style="{StaticResource PumpCommandLabel}"
@@ -180,16 +181,16 @@
Style="{StaticResource PumpCommandLabel}"
Margin="0,4,0,2"/>
<Slider Orientation="Vertical"
<Slider Style="{StaticResource FluentThickVerticalSlider}"
Orientation="Vertical"
Minimum="{Binding PreInMin}" Maximum="{Binding PreInMax}"
Value="{Binding PreInValue}"
TickFrequency="{Binding PreInStep}"
TickPlacement="TopLeft"
IsSnapToTickEnabled="True"
AutoToolTipPrecision="2"
Height="240" Width="32"
Height="360" Width="52"
HorizontalAlignment="Center"
Focusable="False"/>
Loaded="Slider_Loaded"/>
<TextBlock Text="{Binding PreInMin, StringFormat=F1}"
Style="{StaticResource PumpCommandLabel}"

View File

@@ -1,9 +1,140 @@
using System.Windows;
using System.Windows.Controls;
using System.Windows.Controls.Primitives;
using System.Windows.Input;
using System.Windows.Media;
namespace HC_APTBS.Views.UserControls
{
/// <summary>
/// Pump command sliders. Code-behind only carries the click-anywhere-and-drag
/// gesture for the custom <c>FluentThickVerticalSlider</c> template — clicking
/// outside the thumb captures the mouse on the Slider and tracks the value
/// against the Track's geometry until release.
/// </summary>
public partial class PumpCommandsCard : UserControl
{
public PumpCommandsCard() => InitializeComponent();
/// <summary>
/// Wires the click-anywhere-and-drag handlers using
/// <c>handledEventsToo: true</c> so they fire even if a class-level
/// handler (e.g. <c>Slider.OnPreviewMouseLeftButtonDown</c> when
/// <c>IsMoveToPointEnabled</c> is on) marks the event handled.
/// </summary>
private void Slider_Loaded(object sender, RoutedEventArgs e)
{
if (sender is not Slider slider) return;
slider.AddHandler(PreviewMouseLeftButtonDownEvent,
new MouseButtonEventHandler(Slider_PreviewMouseLeftButtonDown), true);
slider.AddHandler(PreviewMouseLeftButtonUpEvent,
new MouseButtonEventHandler(Slider_PreviewMouseLeftButtonUp), true);
slider.AddHandler(MouseMoveEvent,
new MouseEventHandler(Slider_MouseMove), true);
slider.AddHandler(PreviewMouseWheelEvent,
new MouseWheelEventHandler(Slider_PreviewMouseWheel), true);
}
/// <summary>
/// Adjusts the Slider's value by one step per wheel notch while the cursor
/// is over the slider. The step is taken from <see cref="Slider.TickFrequency"/>
/// when available, falling back to <see cref="RangeBase.SmallChange"/>, then
/// 1% of the slider's range. The event is marked handled so the wheel doesn't
/// also scroll a parent <c>ScrollViewer</c>.
/// </summary>
private void Slider_PreviewMouseWheel(object sender, MouseWheelEventArgs e)
{
if (sender is not Slider slider) return;
double step = slider.TickFrequency;
if (step <= 0) step = slider.SmallChange;
if (step <= 0) step = (slider.Maximum - slider.Minimum) * 0.01;
if (step <= 0) return;
double notches = e.Delta / 120.0;
double newValue = slider.Value + notches * step;
if (newValue < slider.Minimum) newValue = slider.Minimum;
else if (newValue > slider.Maximum) newValue = slider.Maximum;
slider.Value = newValue;
e.Handled = true;
}
/// <summary>
/// On press outside the Thumb, captures the mouse on the Slider so the
/// user can drag from any point on the track in one motion.
/// </summary>
private void Slider_PreviewMouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
if (sender is not Slider slider) return;
if (slider.Template?.FindName("PART_Track", slider) is not Track track) return;
// Direct thumb press — let the Thumb's own drag handle it.
if (IsClickInsideThumb(e.OriginalSource as DependencyObject, track.Thumb)) return;
UpdateValueFromPoint(slider, e.GetPosition(slider));
slider.CaptureMouse();
e.Handled = true;
}
/// <summary>
/// While the Slider has mouse capture, continuously map the cursor
/// position back to a Slider value.
/// </summary>
private void Slider_MouseMove(object sender, MouseEventArgs e)
{
if (sender is not Slider slider || !slider.IsMouseCaptured) return;
UpdateValueFromPoint(slider, e.GetPosition(slider));
}
/// <summary>
/// Releases the Slider's mouse capture on button-up.
/// </summary>
private void Slider_PreviewMouseLeftButtonUp(object sender, MouseButtonEventArgs e)
{
if (sender is Slider slider && slider.IsMouseCaptured)
{
slider.ReleaseMouseCapture();
e.Handled = true;
}
}
/// <summary>
/// Maps a cursor position (in Slider coordinates) to a Slider value using
/// the Slider's own bounds. Bypasses <c>Track.ValueFromPoint</c>, which
/// can return scaled values for custom templates whose RepeatButton sizes
/// don't match the Track's expected layout.
/// </summary>
private static void UpdateValueFromPoint(Slider slider, Point pointInSlider)
{
bool vertical = slider.Orientation == Orientation.Vertical;
double length = vertical ? slider.ActualHeight : slider.ActualWidth;
if (length <= 0) return;
double pos = vertical ? pointInSlider.Y : pointInSlider.X;
double fraction = pos / length;
if (fraction < 0) fraction = 0;
else if (fraction > 1) fraction = 1;
// Vertical (default): top = Maximum. Horizontal (default): left = Minimum.
// IsDirectionReversed flips that on each axis.
bool flip = vertical ? !slider.IsDirectionReversed : slider.IsDirectionReversed;
if (flip) fraction = 1.0 - fraction;
double value = slider.Minimum + fraction * (slider.Maximum - slider.Minimum);
if (value < slider.Minimum) value = slider.Minimum;
else if (value > slider.Maximum) value = slider.Maximum;
slider.Value = value;
}
private static bool IsClickInsideThumb(DependencyObject? src, Thumb? thumb)
{
if (thumb is null || src is null) return false;
for (var node = src; node is not null; node = VisualTreeHelper.GetParent(node))
{
if (node == thumb) return true;
}
return false;
}
}
}

View File

@@ -5,7 +5,7 @@
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:ui="http://schemas.lepo.co/wpfui/2022/xaml"
mc:Ignorable="d"
d:DesignHeight="360" d:DesignWidth="320">
d:DesignHeight="430" d:DesignWidth="490">
<!-- DataContext = PumpIdentificationViewModel (via {Binding Identification}) -->
<UserControl.Resources>
@@ -13,15 +13,15 @@
<Style x:Key="IdLabel" TargetType="TextBlock">
<Setter Property="FontFamily" Value="{DynamicResource ContentControlThemeFontFamily}"/>
<Setter Property="FontSize" Value="11"/>
<Setter Property="FontSize" Value="15"/>
<Setter Property="Foreground" Value="{DynamicResource TextFillColorSecondaryBrush}"/>
<Setter Property="Width" Value="88"/>
<Setter Property="Width" Value="100"/>
<Setter Property="VerticalAlignment" Value="Center"/>
</Style>
<Style x:Key="IdValue" TargetType="TextBlock">
<Setter Property="FontFamily" Value="Consolas"/>
<Setter Property="FontSize" Value="12"/>
<Setter Property="FontSize" Value="16"/>
<Setter Property="Foreground" Value="{DynamicResource TextFillColorPrimaryBrush}"/>
<Setter Property="VerticalAlignment" Value="Center"/>
<Setter Property="TextTrimming" Value="CharacterEllipsis"/>
@@ -73,7 +73,7 @@
</StackPanel>
<!-- ── ECU info: two columns ─────────────────────────────────── -->
<Grid>
<Grid HorizontalAlignment="Stretch" DockPanel.Dock="Top">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="8"/>

View File

@@ -1,6 +1,7 @@
<UserControl x:Class="HC_APTBS.Views.UserControls.PumpIdentificationView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
>
<UserControl.Resources>
<BooleanToVisibilityConverter x:Key="BoolToVis"/>

View File

@@ -27,10 +27,11 @@
Style="{StaticResource PumpCardHeader}" Margin="0"/>
</DockPanel>
<!-- ── Status displays (docked bottom so chart gets the middle) ── -->
<!-- ── Status displays + BIP (docked bottom so chart gets the middle) ── -->
<StackPanel DockPanel.Dock="Bottom" Margin="0,8,0,0">
<uc:StatusDisplayView DataContext="{Binding StatusDisplay1}" Margin="0,0,0,8"/>
<uc:StatusDisplayView DataContext="{Binding StatusDisplay2}"/>
<uc:StatusDisplayView DataContext="{Binding StatusDisplay2}" Margin="0,0,0,8"/>
<uc:BipDisplayView DataContext="{Binding BipDisplay}"/>
</StackPanel>
<!-- ── RPM chart (docked bottom of the upper area) ──────────── -->

View File

@@ -126,7 +126,7 @@
<!-- Pump selector ComboBox -->
<ComboBox ItemsSource="{Binding Identification.PumpIds}"
SelectedItem="{Binding Identification.SelectedPumpId}"
MinWidth="200" FontSize="13" Height="32"
MinWidth="200" FontSize="13" Height="36"
VerticalContentAlignment="Center"/>
<!-- Model / Brand badge -->

View File

@@ -1,91 +0,0 @@
<UserControl x:Class="HC_APTBS.Views.UserControls.TestDoneView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:uc="clr-namespace:HC_APTBS.Views.UserControls"
xmlns:vmp="clr-namespace:HC_APTBS.ViewModels.Pages"
mc:Ignorable="d"
d:DesignHeight="560" d:DesignWidth="900"
Background="#FFEDEDED" Foreground="Black"
d:DataContext="{d:DesignInstance Type=vmp:TestsPageViewModel, IsDesignTimeCreatable=False}">
<!--
Done step of the Tests wizard. DataContext: TestsPageViewModel.
Shows PASS/FAIL banner, embedded ResultDisplayView, and navigation buttons.
-->
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="*"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<!-- Overall pass/fail banner -->
<Border Padding="16,12" Margin="0,0,0,4">
<Border.Style>
<Style TargetType="Border">
<Setter Property="Background" Value="#FFCDD2"/>
<Style.Triggers>
<DataTrigger Binding="{Binding ResultDisplay.OverallPassed}" Value="True">
<Setter Property="Background" Value="#C8E6C9"/>
</DataTrigger>
</Style.Triggers>
</Style>
</Border.Style>
<StackPanel Orientation="Horizontal">
<TextBlock FontSize="26" FontWeight="Bold" VerticalAlignment="Center">
<TextBlock.Style>
<Style TargetType="TextBlock">
<Setter Property="Foreground" Value="#B71C1C"/>
<Setter Property="Text" Value="{DynamicResource Test.Done.Failed}"/>
<Style.Triggers>
<DataTrigger Binding="{Binding ResultDisplay.OverallPassed}" Value="True">
<Setter Property="Foreground" Value="#2E7D32"/>
<Setter Property="Text" Value="{DynamicResource Test.Done.Passed}"/>
</DataTrigger>
</Style.Triggers>
</Style>
</TextBlock.Style>
</TextBlock>
<TextBlock Text="{Binding ResultDisplay.TestName}"
FontSize="14" FontStyle="Italic"
Foreground="#555"
VerticalAlignment="Bottom"
Margin="16,0,0,4"/>
</StackPanel>
</Border>
<!-- Inline results table -->
<uc:ResultDisplayView Grid.Row="1"
DataContext="{Binding ResultDisplay}"
Margin="4"/>
<!-- Action row -->
<Border Grid.Row="2" Padding="10,8"
BorderBrush="#DDD" BorderThickness="0,1,0,0"
Background="White">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<Button Grid.Column="0"
Content="{DynamicResource Test.Done.ViewFullResults}"
Command="{Binding ViewFullResultsCommand}"
Padding="14,6" FontSize="12"/>
<StackPanel Grid.Column="2" Orientation="Horizontal">
<Button Content="{DynamicResource Test.Report}"
Command="{Binding Root.GenerateReportCommand}"
Padding="14,6" FontSize="12" Margin="0,0,8,0"/>
<Button Content="{DynamicResource Test.Done.RunAgain}"
Command="{Binding RunAgainCommand}"
Padding="18,6" FontSize="14" FontWeight="Bold"
Background="#1565C0" Foreground="White" BorderThickness="0"/>
</StackPanel>
</Grid>
</Border>
</Grid>
</UserControl>

View File

@@ -1,16 +0,0 @@
using System.Windows.Controls;
namespace HC_APTBS.Views.UserControls
{
/// <summary>
/// Done step of the Tests wizard — PASS/FAIL banner, results table, Run Again.
/// DataContext is expected to be a <see cref="HC_APTBS.ViewModels.Pages.TestsPageViewModel"/>.
/// </summary>
public partial class TestDoneView : UserControl
{
public TestDoneView()
{
InitializeComponent();
}
}
}

View File

@@ -1,64 +0,0 @@
<UserControl x:Class="HC_APTBS.Views.UserControls.TestPlanView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:uc="clr-namespace:HC_APTBS.Views.UserControls"
xmlns:vm="clr-namespace:HC_APTBS.ViewModels"
mc:Ignorable="d"
d:DesignHeight="520" d:DesignWidth="900"
Background="#FFEDEDED" Foreground="Black"
d:DataContext="{d:DesignInstance Type=vm:TestPanelViewModel, IsDesignTimeCreatable=False}">
<!--
Plan step of the Tests wizard. DataContext: TestPanelViewModel.
The operator picks which test phases to run — no Start/Stop/Report buttons,
those live on the Preconditions step and in the wizard footer.
-->
<UserControl.Resources>
<DataTemplate DataType="{x:Type vm:TestSectionViewModel}">
<uc:TestSectionView/>
</DataTemplate>
</UserControl.Resources>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition/>
</Grid.RowDefinitions>
<!-- Toolbar -->
<Border BorderBrush="Gray" BorderThickness="0,0,0,1" Padding="8,4">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<CheckBox IsChecked="{Binding ShowOperationValues}"
VerticalAlignment="Center">
<TextBlock Text="{DynamicResource Test.ShowValues}" FontSize="12"/>
</CheckBox>
<Button Grid.Column="1" Margin="12,0,0,0"
Command="{Binding ToggleCheckAllCommand}"
Padding="6,2" ToolTip="Enable/disable all phases">
<TextBlock Text="{DynamicResource Test.CheckAll}" FontSize="11"/>
</Button>
<TextBlock Grid.Column="3" VerticalAlignment="Center"
Foreground="DimGray" FontSize="12" Margin="0,0,4,0">
<Run Text="~"/>
<Run Text="{Binding RemainingSeconds, Mode=OneWay}"/>
<Run Text="{DynamicResource Test.SecondsRemaining}"/>
</TextBlock>
</Grid>
</Border>
<!-- Test sections -->
<ScrollViewer Grid.Row="1" VerticalScrollBarVisibility="Auto">
<ItemsControl ItemsSource="{Binding Tests}" Margin="4"/>
</ScrollViewer>
</Grid>
</UserControl>

View File

@@ -1,16 +0,0 @@
using System.Windows.Controls;
namespace HC_APTBS.Views.UserControls
{
/// <summary>
/// Plan step of the Tests wizard — phase enable/disable and duration preview.
/// DataContext is expected to be a <see cref="HC_APTBS.ViewModels.TestPanelViewModel"/>.
/// </summary>
public partial class TestPlanView : UserControl
{
public TestPlanView()
{
InitializeComponent();
}
}
}

View File

@@ -1,173 +0,0 @@
<UserControl x:Class="HC_APTBS.Views.UserControls.TestPreconditionsView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/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"
mc:Ignorable="d"
d:DesignHeight="520" d:DesignWidth="680"
d:DataContext="{d:DesignInstance Type=vm:TestPreconditionsViewModel, IsDesignTimeCreatable=False}">
<!--
Preconditions checklist (Tests page wizard step 4b).
DataContext: TestPreconditionsViewModel.
Rows auto-refresh as underlying properties change; Start button is disabled
until AllPassed is true.
-->
<UserControl.Resources>
<BooleanToVisibilityConverter x:Key="BoolToVis"/>
</UserControl.Resources>
<Border Background="White" BorderBrush="#DDD" BorderThickness="1" CornerRadius="4" Padding="14">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="*"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<!-- Title -->
<TextBlock Grid.Row="0"
Text="{DynamicResource Test.Precheck.Title}"
FontSize="16" FontWeight="SemiBold" Foreground="#222"
Margin="0,0,0,10"/>
<!-- Auth gate (only when a required test has RequiresAuth=true) -->
<Border Grid.Row="1"
Background="#FFF8E1" BorderBrush="#F0C24A" BorderThickness="1"
CornerRadius="3" Padding="10,8" Margin="0,0,0,10"
Visibility="{Binding IsAuthRequired, Converter={StaticResource BoolToVis}}">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<StackPanel Grid.Column="0" VerticalAlignment="Center">
<TextBlock Text="{DynamicResource Test.Precheck.AuthBanner}"
FontSize="12" FontWeight="SemiBold" Foreground="#7A5A00"/>
<TextBlock Text="{Binding TestAuth.AuthenticatedUser}"
FontSize="11" Foreground="#7A5A00"
Visibility="{Binding TestAuth.IsAuthenticated, Converter={StaticResource BoolToVis}}"/>
</StackPanel>
<Button Grid.Column="1"
Content="{DynamicResource Test.Precheck.AuthButton}"
Command="{Binding TestAuth.AuthenticateCommand}"
Padding="10,4" FontSize="12"/>
</Grid>
</Border>
<!-- Checklist -->
<ScrollViewer Grid.Row="2" VerticalScrollBarVisibility="Auto">
<ItemsControl ItemsSource="{Binding Items}">
<ItemsControl.ItemTemplate>
<DataTemplate DataType="{x:Type vm:PreconditionItemViewModel}">
<Border BorderBrush="#EEE" BorderThickness="0,0,0,1" Padding="0,8">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="32"/>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<!-- Status glyph -->
<Border Grid.Column="0" Width="20" Height="20"
CornerRadius="10" VerticalAlignment="Center"
HorizontalAlignment="Left"
Margin="0,0,8,0">
<Border.Style>
<Style TargetType="Border">
<Setter Property="Background" Value="#E74C3C"/>
<Style.Triggers>
<DataTrigger Binding="{Binding IsSatisfied}" Value="True">
<Setter Property="Background" Value="#26C200"/>
</DataTrigger>
<MultiDataTrigger>
<MultiDataTrigger.Conditions>
<Condition Binding="{Binding IsSatisfied}" Value="False"/>
<Condition Binding="{Binding IsRequired}" Value="False"/>
</MultiDataTrigger.Conditions>
<Setter Property="Background" Value="#BBB"/>
</MultiDataTrigger>
</Style.Triggers>
</Style>
</Border.Style>
<TextBlock HorizontalAlignment="Center" VerticalAlignment="Center"
Foreground="White" FontSize="12" FontWeight="Bold">
<TextBlock.Style>
<Style TargetType="TextBlock">
<Setter Property="Text" Value="✕"/>
<Style.Triggers>
<DataTrigger Binding="{Binding IsSatisfied}" Value="True">
<Setter Property="Text" Value="✓"/>
</DataTrigger>
</Style.Triggers>
</Style>
</TextBlock.Style>
</TextBlock>
</Border>
<!-- Label + remediation text -->
<StackPanel Grid.Column="1" VerticalAlignment="Center">
<TextBlock Text="{Binding Label}" FontSize="13" Foreground="#222"/>
<TextBlock Text="{Binding RemediationText}"
FontSize="11" Foreground="#888">
<TextBlock.Style>
<Style TargetType="TextBlock">
<Setter Property="Visibility" Value="Visible"/>
<Style.Triggers>
<DataTrigger Binding="{Binding IsSatisfied}" Value="True">
<Setter Property="Visibility" Value="Collapsed"/>
</DataTrigger>
</Style.Triggers>
</Style>
</TextBlock.Style>
</TextBlock>
</StackPanel>
<!-- Fix-it button -->
<Button Grid.Column="2"
Content="{DynamicResource Test.Precheck.FixButton}"
Command="{Binding NavigateToFixCommand}"
Padding="8,3" FontSize="11" Margin="8,0,0,0"
Visibility="{Binding HasRemediation, Converter={StaticResource BoolToVis}}"/>
</Grid>
</Border>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</ScrollViewer>
<!-- Start button row -->
<Grid Grid.Row="3" Margin="0,14,0,0">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<TextBlock Grid.Column="0" VerticalAlignment="Center"
FontSize="12" Foreground="#666"
Text="{DynamicResource Test.Precheck.NotReady}">
<TextBlock.Style>
<Style TargetType="TextBlock">
<Setter Property="Visibility" Value="Visible"/>
<Style.Triggers>
<DataTrigger Binding="{Binding AllPassed}" Value="True">
<Setter Property="Visibility" Value="Collapsed"/>
</DataTrigger>
</Style.Triggers>
</Style>
</TextBlock.Style>
</TextBlock>
<TextBlock Grid.Column="0" VerticalAlignment="Center"
FontSize="12" Foreground="#26C200" FontWeight="SemiBold"
Text="{DynamicResource Test.Precheck.Ready}"
Visibility="{Binding AllPassed, Converter={StaticResource BoolToVis}}"/>
<Button Grid.Column="1"
Content="{DynamicResource Test.StartTest}"
Command="{Binding StartTestCommand}"
Padding="18,6" FontSize="14" FontWeight="Bold"
Background="#26C200" Foreground="White" BorderThickness="0"/>
</Grid>
</Grid>
</Border>
</UserControl>

View File

@@ -1,16 +0,0 @@
using System.Windows.Controls;
namespace HC_APTBS.Views.UserControls
{
/// <summary>
/// Preconditions checklist for the Tests page wizard (§4b).
/// DataContext is expected to be a <see cref="HC_APTBS.ViewModels.TestPreconditionsViewModel"/>.
/// </summary>
public partial class TestPreconditionsView : UserControl
{
public TestPreconditionsView()
{
InitializeComponent();
}
}
}

View File

@@ -1,159 +0,0 @@
<UserControl x:Class="HC_APTBS.Views.UserControls.TestRunningView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:uc="clr-namespace:HC_APTBS.Views.UserControls"
xmlns:vm="clr-namespace:HC_APTBS.ViewModels"
mc:Ignorable="d"
d:DesignHeight="640" d:DesignWidth="1000"
Background="#FFEDEDED" Foreground="Black"
d:DataContext="{d:DesignInstance Type=vm:TestPanelViewModel, IsDesignTimeCreatable=False}">
<!--
Running step of the Tests wizard. DataContext: TestPanelViewModel.
Shows the active phase countdown, live phase-card updates, and the flowmeter /
angle visuals. Pause / Retry-phase / Skip-phase buttons are rendered disabled —
their IBenchService wiring is deferred (see docs/gap-test-running-controls.md).
-->
<UserControl.Resources>
<BooleanToVisibilityConverter x:Key="BoolToVis"/>
<DataTemplate DataType="{x:Type vm:TestSectionViewModel}">
<uc:TestSectionView/>
</DataTemplate>
</UserControl.Resources>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/> <!-- Phase header + countdown -->
<RowDefinition Height="*"/> <!-- Sections + live charts -->
<RowDefinition Height="Auto"/> <!-- Control row -->
</Grid.RowDefinitions>
<!-- Phase header + countdown + progress -->
<Border Padding="10,8" BorderBrush="#DDD" BorderThickness="0,0,0,1"
Background="White">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<StackPanel VerticalAlignment="Center">
<TextBlock Text="{Binding CurrentPhaseName}"
FontSize="16" FontWeight="SemiBold"
Foreground="#222"/>
<StackPanel Orientation="Horizontal" Margin="0,1,0,0">
<TextBlock Text="{Binding SectionLabel}"
FontSize="11" FontStyle="Italic"
Foreground="#666"/>
<TextBlock Text="{Binding StatusText}"
FontSize="11" FontStyle="Italic"
Foreground="Gray" Margin="8,0,0,0"/>
</StackPanel>
</StackPanel>
<StackPanel Grid.Column="1" Orientation="Horizontal"
VerticalAlignment="Center">
<TextBlock Text="{Binding PhaseRemainingSeconds, Mode=OneWay}"
FontSize="32" FontFamily="Impact"
Foreground="#1565C0"
VerticalAlignment="Center"/>
<TextBlock Text="s" FontSize="18"
Foreground="#1565C0"
VerticalAlignment="Bottom" Margin="2,0,0,4"/>
<TextBlock Foreground="#999" FontSize="11"
VerticalAlignment="Bottom" Margin="8,0,0,4">
<Run Text="/ "/>
<Run Text="{Binding PhaseTotalSeconds, Mode=OneWay}"/>
<Run Text="s"/>
</TextBlock>
</StackPanel>
<ProgressBar Grid.Row="1" Grid.ColumnSpan="2"
Minimum="0" Maximum="1"
Value="{Binding PhaseProgress, Mode=OneWay}"
Height="6" Margin="0,6,0,0"
Background="#EEE" Foreground="#1565C0"
BorderThickness="0"/>
</Grid>
</Border>
<!-- Main body: test sections on left, live charts on right -->
<Grid Grid.Row="1">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="420"/>
</Grid.ColumnDefinitions>
<!-- Live phase-card list (read-only during run, cards colour themselves) -->
<ScrollViewer VerticalScrollBarVisibility="Auto">
<ItemsControl ItemsSource="{Binding Tests}" Margin="4"/>
</ScrollViewer>
<!-- Charts + angle display -->
<Grid Grid.Column="1" Margin="4">
<Grid.RowDefinitions>
<RowDefinition Height="*"/>
<RowDefinition Height="*"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<uc:FlowmeterChartView Grid.Row="0"
DataContext="{Binding DataContext.FlowmeterChart.Delivery,
RelativeSource={RelativeSource AncestorType=Window}}"/>
<uc:FlowmeterChartView Grid.Row="1"
DataContext="{Binding DataContext.FlowmeterChart.Over,
RelativeSource={RelativeSource AncestorType=Window}}"
Margin="0,4,0,0"/>
<uc:AngleDisplayView Grid.Row="2"
DataContext="{Binding DataContext.AngleDisplay,
RelativeSource={RelativeSource AncestorType=Window}}"
Margin="0,4,0,0"/>
</Grid>
</Grid>
<!-- Control row -->
<Border Grid.Row="2" Padding="10,8"
BorderBrush="#DDD" BorderThickness="0,1,0,0"
Background="White">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<StackPanel Orientation="Horizontal" Grid.Column="0">
<Button Content="{DynamicResource Test.Running.Pause}"
IsEnabled="False"
ToolTip="{DynamicResource Test.Running.ComingSoon}"
Padding="12,5" FontSize="12" Margin="0,0,6,0"/>
<Button Content="{DynamicResource Test.Running.Retry}"
IsEnabled="False"
ToolTip="{DynamicResource Test.Running.ComingSoon}"
Padding="12,5" FontSize="12" Margin="0,0,6,0"/>
<Button Content="{DynamicResource Test.Running.Skip}"
IsEnabled="False"
ToolTip="{DynamicResource Test.Running.ComingSoon}"
Padding="12,5" FontSize="12"/>
</StackPanel>
<Button Grid.Column="2"
Content="{DynamicResource Test.Running.Abort}"
Command="{Binding DataContext.TestsPage.AbortCommand,
RelativeSource={RelativeSource AncestorType=Window}}"
Padding="18,6" FontSize="14" FontWeight="Bold"
Background="#C62828" Foreground="White" BorderThickness="0"/>
</Grid>
</Border>
</Grid>
</UserControl>

View File

@@ -1,16 +0,0 @@
using System.Windows.Controls;
namespace HC_APTBS.Views.UserControls
{
/// <summary>
/// Running step of the Tests wizard — live phase progress, flowmeter charts, abort.
/// DataContext is expected to be a <see cref="HC_APTBS.ViewModels.TestPanelViewModel"/>.
/// </summary>
public partial class TestRunningView : UserControl
{
public TestRunningView()
{
InitializeComponent();
}
}
}

View File

@@ -0,0 +1,124 @@
<UserControl x:Class="HC_APTBS.Views.UserControls.TestSectionCard"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:ui="http://schemas.lepo.co/wpfui/2022/xaml"
xmlns:uc="clr-namespace:HC_APTBS.Views.UserControls"
xmlns:vm="clr-namespace:HC_APTBS.ViewModels"
mc:Ignorable="d"
x:Name="Root"
Width="Auto"
d:DataContext="{d:DesignInstance Type=vm:TestSectionViewModel, IsDesignTimeCreatable=False}">
<!--
Fluent card representing one test type (WL, DFI, F, SVME, UP, PFP).
DataContext: TestSectionViewModel. Compact="true" renders phase tiles as
chips (idle planning view); Compact="false" renders them with full vertical
live-bar indicators (while a test is running).
-->
<UserControl.Resources>
<DataTemplate DataType="{x:Type vm:PhaseCardViewModel}">
<uc:PhaseTileView Compact="{Binding Compact,
RelativeSource={RelativeSource AncestorType=uc:TestSectionCard}}"/>
</DataTemplate>
</UserControl.Resources>
<Border Padding="10" Margin="4" Cursor="Hand">
<Border.InputBindings>
<MouseBinding Gesture="LeftClick"
Command="{Binding ToggleAllPhasesCommand}"/>
</Border.InputBindings>
<Border.Style>
<Style TargetType="Border" BasedOn="{StaticResource PumpCard}">
<Style.Triggers>
<Trigger Property="IsMouseOver" Value="True">
<Setter Property="Background" Value="{DynamicResource CardBackgroundFillColorSecondaryBrush}"/>
<Setter Property="BorderBrush" Value="{DynamicResource AccentControlElevationBorderBrush}"/>
</Trigger>
<DataTrigger Binding="{Binding IsActiveTest}" Value="True">
<Setter Property="BorderBrush" Value="#F9A825"/>
<Setter Property="BorderThickness" Value="2"/>
</DataTrigger>
</Style.Triggers>
</Style>
</Border.Style>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<!-- Header row -->
<Grid Margin="0,0,0,6">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<ui:SymbolIcon Grid.Column="0"
Symbol="{Binding IconSymbol}"
FontSize="18"
Foreground="{DynamicResource AccentTextFillColorPrimaryBrush}"
VerticalAlignment="Center"
Margin="0,0,8,0"/>
<StackPanel Grid.Column="1" VerticalAlignment="Center">
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding TestName}"
FontSize="15" FontWeight="SemiBold"
Foreground="{DynamicResource TextFillColorPrimaryBrush}"/>
<TextBlock Text="{Binding Description}"
FontSize="11" FontStyle="Italic"
Foreground="{DynamicResource TextFillColorSecondaryBrush}"
Margin="8,0,0,0" VerticalAlignment="Bottom"/>
</StackPanel>
</StackPanel>
<!-- Metadata pills -->
<StackPanel Grid.Column="2" Orientation="Horizontal" Margin="0,0,0,6">
<Border Style="{StaticResource TestMetaPill}">
<StackPanel Orientation="Horizontal">
<TextBlock Text="{DynamicResource Test.Plan.MetaCond}"
Style="{StaticResource TestMetaPillText}"/>
<TextBlock Style="{StaticResource TestMetaPillText}" Margin="3,0,0,0">
<Run Text="{Binding ConditioningTimeSec, Mode=OneWay}"/><Run Text="s"/>
</TextBlock>
</StackPanel>
</Border>
<Border Style="{StaticResource TestMetaPill}">
<StackPanel Orientation="Horizontal">
<TextBlock Text="{DynamicResource Test.Plan.MetaMeas}"
Style="{StaticResource TestMetaPillText}"/>
<TextBlock Style="{StaticResource TestMetaPillText}" Margin="3,0,0,0">
<Run Text="{Binding MeasurementTimeSec, Mode=OneWay}"/><Run Text="s"/>
</TextBlock>
</StackPanel>
</Border>
<Border Style="{StaticResource TestMetaPill}">
<StackPanel Orientation="Horizontal">
<TextBlock Text="{DynamicResource Test.Plan.MetaMps}"
Style="{StaticResource TestMetaPillText}"/>
<TextBlock Style="{StaticResource TestMetaPillText}" Margin="3,0,0,0"
Text="{Binding MeasurementsPerSecond, StringFormat=F1, Mode=OneWay}"/>
</StackPanel>
</Border>
</StackPanel>
</Grid>
<!-- Phase tiles (wrap horizontally inside the card) -->
<ItemsControl Grid.Row="2" ItemsSource="{Binding Phases}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel Orientation="Horizontal"/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
</ItemsControl>
</Grid>
</Border>
</UserControl>

View File

@@ -0,0 +1,31 @@
using System.Windows;
using System.Windows.Controls;
namespace HC_APTBS.Views.UserControls
{
/// <summary>
/// Fluent section card hosting a test type's phase tiles.
/// Set <see cref="Compact"/>=<c>true</c> to render chips (idle state) or
/// <c>false</c> to render full vertical live-bar indicators (running state).
/// DataContext is expected to be a <see cref="HC_APTBS.ViewModels.TestSectionViewModel"/>.
/// </summary>
public partial class TestSectionCard : UserControl
{
/// <summary>Forwarded to every child <see cref="PhaseTileView.Compact"/>.</summary>
public static readonly DependencyProperty CompactProperty =
DependencyProperty.Register(nameof(Compact), typeof(bool), typeof(TestSectionCard),
new PropertyMetadata(false));
/// <summary>Compact mode flag (see <see cref="CompactProperty"/>).</summary>
public bool Compact
{
get => (bool)GetValue(CompactProperty);
set => SetValue(CompactProperty, value);
}
public TestSectionCard()
{
InitializeComponent();
}
}
}

View File

@@ -1,91 +0,0 @@
<UserControl x:Class="HC_APTBS.Views.UserControls.TestSectionView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:uc="clr-namespace:HC_APTBS.Views.UserControls"
xmlns:vm="clr-namespace:HC_APTBS.ViewModels"
mc:Ignorable="d"
d:DataContext="{d:DesignInstance Type=vm:TestSectionViewModel, IsDesignTimeCreatable=False}">
<!--
Expander header + horizontal list of phase cards. DataContext: TestSectionViewModel.
Used by TestPlanView and TestRunningView — both show the same structure, differing
only in which card state is currently highlighted.
-->
<UserControl.Resources>
<DataTemplate DataType="{x:Type vm:PhaseCardViewModel}">
<uc:PhaseCardView/>
</DataTemplate>
</UserControl.Resources>
<Expander IsExpanded="{Binding IsExpanded}" Margin="0,2,0,0">
<Expander.Header>
<Border Padding="4,2" CornerRadius="2">
<Border.Style>
<Style TargetType="Border">
<Setter Property="Background" Value="Transparent"/>
<Style.Triggers>
<DataTrigger Binding="{Binding IsActiveTest}" Value="True">
<Setter Property="Background" Value="#FFFFF3CD"/>
</DataTrigger>
</Style.Triggers>
</Style>
</Border.Style>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<TextBlock Text="{Binding TestName}"
FontSize="20" FontFamily="Impact" FontStyle="Italic"
VerticalAlignment="Center" Foreground="Black"
Padding="4,0"/>
<TextBlock Grid.Column="1"
Text="{Binding Description}"
FontSize="14" FontStyle="Italic" FontFamily="Impact"
VerticalAlignment="Bottom" Foreground="Gray"
Padding="8,0,0,3"/>
<StackPanel Grid.Column="2" Orientation="Horizontal"
VerticalAlignment="Center" Margin="16,0,0,0">
<TextBlock FontSize="10" Foreground="DimGray">
<Run Text="{DynamicResource Test.Condition}"/>
<Run Text="{Binding ConditioningTimeSec, Mode=OneWay}"/>
<Run Text="s"/>
</TextBlock>
<TextBlock FontSize="10" Foreground="DimGray" Margin="10,0,0,0">
<Run Text="{DynamicResource Test.Measurement}"/>
<Run Text="{Binding MeasurementTimeSec, Mode=OneWay}"/>
<Run Text="s"/>
</TextBlock>
<TextBlock FontSize="10" Foreground="DimGray" Margin="10,0,0,0">
<Run Text="{DynamicResource Test.MeasPerSec}"/>
<Run Text="{Binding MeasurementsPerSecond, StringFormat=F1, Mode=OneWay}"/>
</TextBlock>
</StackPanel>
<CheckBox Grid.Column="3"
IsChecked="{Binding AllPhasesChecked}"
VerticalAlignment="Center" Margin="8,0"
ToolTip="Enable/disable all phases in this test"/>
</Grid>
</Border>
</Expander.Header>
<ScrollViewer HorizontalScrollBarVisibility="Auto"
VerticalScrollBarVisibility="Disabled"
Padding="0,4">
<ItemsControl ItemsSource="{Binding Phases}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel Orientation="Horizontal"/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
</ItemsControl>
</ScrollViewer>
</Expander>
</UserControl>

View File

@@ -1,16 +0,0 @@
using System.Windows.Controls;
namespace HC_APTBS.Views.UserControls
{
/// <summary>
/// One test section — Expander header plus the horizontal row of phase cards.
/// DataContext is expected to be a <see cref="HC_APTBS.ViewModels.TestSectionViewModel"/>.
/// </summary>
public partial class TestSectionView : UserControl
{
public TestSectionView()
{
InitializeComponent();
}
}
}

View File

@@ -84,6 +84,12 @@
Visibility="{Binding IsCancellable, Converter={StaticResource BoolToVis}}"
Height="30" Padding="12,4"/>
<ui:Button Content="{DynamicResource Dialog.Unlock.Retry}"
Command="{Binding RetryCommand}"
Appearance="Primary"
Visibility="{Binding CanRetry, Converter={StaticResource BoolToVis}}"
Height="30" Padding="12,4" Margin="0,0,6,0"/>
<ui:Button Content="{DynamicResource Common.Dismiss}"
Click="OnDismissClick"
Appearance="Secondary"