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

@@ -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"