feat: redesign Pump page with Fluent card layout, bottom snackbar, and RPM chart

- Replace sub-nav + HiddenTabsTabControl with 3-column Fluent card layout:
  PumpCommandsCard (vertical ME/FBKW/PreIn sliders) + DfiCalibrationCard /
  PumpLiveDataCard (KPI tiles + RPM rolling chart + redesigned status bytes) /
  PumpIdentificationCard + DtcCard
- Add PumpTopStripView: pump selector, model badge, CAN + K-Line chips
- Move immobilizer unlock to MainWindow bottom snackbar (UnlockSnackbarView):
  auto-close on success after 3 s, persist on failure with manual Dismiss
- Redesign StatusDisplayView to 2×8 rounded 28px tiles with bit index + tooltip
- Add NullToVisibilityConverter; add SnackbarShell, PumpCard, and related styles
- Delete obsolete views: UnlockProgressDialog, UnlockPanelView,
  PumpIdentificationPanelView, PumpLiveDataView, DfiManageView,
  DtcListView, PumpControlView
- PumpPageViewModel: remove PumpSubPage enum, add RpmChart wired to Root.PumpRpm

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-04-20 14:03:47 +02:00
parent 197e9d1775
commit 70be693116
37 changed files with 1356 additions and 1317 deletions

View File

@@ -0,0 +1,20 @@
using System;
using System.Globalization;
using System.Windows;
using System.Windows.Data;
namespace HC_APTBS.Converters
{
/// <summary>
/// Returns <see cref="Visibility.Visible"/> when the value is non-null,
/// and <see cref="Visibility.Collapsed"/> when the value is null.
/// </summary>
public sealed class NullToVisibilityConverter : IValueConverter
{
public object Convert(object? value, Type targetType, object parameter, CultureInfo culture)
=> value is null ? Visibility.Collapsed : Visibility.Visible;
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
=> throw new NotSupportedException();
}
}

View File

@@ -123,10 +123,12 @@
<!-- ── Main content: left navigation rail + hidden-tab page host ──── --> <!-- ── Main content: left navigation rail + hidden-tab page host ──── -->
<Grid> <Grid>
<Grid.ColumnDefinitions> <!-- Inner grid: nav rail (col 0) + page host (col 1) -->
<ColumnDefinition Width="180"/> <Grid>
<ColumnDefinition Width="*"/> <Grid.ColumnDefinitions>
</Grid.ColumnDefinitions> <ColumnDefinition Width="180"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<!-- <!--
Nav rail column: primary pages at top, Settings pinned at bottom Nav rail column: primary pages at top, Settings pinned at bottom
@@ -208,6 +210,11 @@
<pages:ResultsPage DataContext="{Binding ResultsPage}"/> <pages:ResultsPage DataContext="{Binding ResultsPage}"/>
</TabItem> </TabItem>
</TabControl> </TabControl>
</Grid>
<uc:UnlockSnackbarView DataContext="{Binding CurrentUnlockVm}"
HorizontalAlignment="Center"
VerticalAlignment="Bottom"
Margin="0,0,0,8"/>
</Grid> </Grid>
</DockPanel> </DockPanel>
</ui:FluentWindow> </ui:FluentWindow>

View File

@@ -166,6 +166,31 @@
<sys:String x:Key="Pump.Rpm">RPM</sys:String> <sys:String x:Key="Pump.Rpm">RPM</sys:String>
<sys:String x:Key="Pump.TEin">T-ein</sys:String> <sys:String x:Key="Pump.TEin">T-ein</sys:String>
<sys:String x:Key="Pump.UnitRpm">1/min</sys:String> <sys:String x:Key="Pump.UnitRpm">1/min</sys:String>
<sys:String x:Key="Pump.UnitCelsius">°C</sys:String>
<sys:String x:Key="Pump.UnitUs">µs</sys:String>
<sys:String x:Key="Pump.Commands.Title">Pump Commands</sys:String>
<sys:String x:Key="Pump.Commands.Me">ME</sys:String>
<sys:String x:Key="Pump.Commands.Fbkw">FBKW</sys:String>
<sys:String x:Key="Pump.Commands.PreIn">Pre-inj</sys:String>
<sys:String x:Key="Pump.LiveData.Title">Live Data</sys:String>
<sys:String x:Key="Pump.LiveData.RPM">Pump RPM</sys:String>
<sys:String x:Key="Pump.LiveData.THyb">T-hyb</sys:String>
<sys:String x:Key="Pump.LiveData.TEin">T-ein</sys:String>
<sys:String x:Key="Pump.LiveData.Me">ME</sys:String>
<sys:String x:Key="Pump.LiveData.Fbkw">FBKW</sys:String>
<sys:String x:Key="Pump.LiveData.RpmChart">RPM trend</sys:String>
<sys:String x:Key="Pump.Status.Active">Active</sys:String>
<sys:String x:Key="Pump.Dfi.Title">Idling Calibration</sys:String>
<sys:String x:Key="Pump.Dfi.Version">Version</sys:String>
<sys:String x:Key="Pump.Dfi.Current">Current DFI</sys:String>
<sys:String x:Key="Pump.Dtcs.Title">Fault Codes</sys:String>
<sys:String x:Key="Pump.Identification.Title">Identification</sys:String>
<sys:String x:Key="Pump.TopStrip.Brand">Brand</sys:String>
<sys:String x:Key="Pump.TopStrip.NoPump">No pump selected</sys:String>
<sys:String x:Key="Pump.TopStrip.Can">CAN</sys:String>
<sys:String x:Key="Pump.TopStrip.KLine">K-Line</sys:String>
<sys:String x:Key="Pump.NoPumpBanner">No pump selected — select a pump to enable diagnostics.</sys:String>
<sys:String x:Key="Pump.FastUnlock">Fast unlock attempt...</sys:String>
<!-- ── Pump identification ──────────────────────────────────────────── --> <!-- ── Pump identification ──────────────────────────────────────────── -->
<sys:String x:Key="PumpId.Label">Pump:</sys:String> <sys:String x:Key="PumpId.Label">Pump:</sys:String>
@@ -323,6 +348,7 @@
<sys:String x:Key="Common.No">No</sys:String> <sys:String x:Key="Common.No">No</sys:String>
<sys:String x:Key="Common.Warning">WARNING</sys:String> <sys:String x:Key="Common.Warning">WARNING</sys:String>
<sys:String x:Key="Common.Disabled">disabled</sys:String> <sys:String x:Key="Common.Disabled">disabled</sys:String>
<sys:String x:Key="Common.Dismiss">Dismiss</sys:String>
<!-- ── Dialog: Report ───────────────────────────────────────────────── --> <!-- ── Dialog: Report ───────────────────────────────────────────────── -->
<sys:String x:Key="Dialog.Report.Title">Generate Report</sys:String> <sys:String x:Key="Dialog.Report.Title">Generate Report</sys:String>

View File

@@ -166,6 +166,31 @@
<sys:String x:Key="Pump.Rpm">RPM</sys:String> <sys:String x:Key="Pump.Rpm">RPM</sys:String>
<sys:String x:Key="Pump.TEin">T-ein</sys:String> <sys:String x:Key="Pump.TEin">T-ein</sys:String>
<sys:String x:Key="Pump.UnitRpm">1/min</sys:String> <sys:String x:Key="Pump.UnitRpm">1/min</sys:String>
<sys:String x:Key="Pump.UnitCelsius">°C</sys:String>
<sys:String x:Key="Pump.UnitUs">µs</sys:String>
<sys:String x:Key="Pump.Commands.Title">Pump Commands</sys:String>
<sys:String x:Key="Pump.Commands.Me">ME</sys:String>
<sys:String x:Key="Pump.Commands.Fbkw">FBKW</sys:String>
<sys:String x:Key="Pump.Commands.PreIn">Pre-inj</sys:String>
<sys:String x:Key="Pump.LiveData.Title">Live Data</sys:String>
<sys:String x:Key="Pump.LiveData.RPM">RPM Bomba</sys:String>
<sys:String x:Key="Pump.LiveData.THyb">T-hyb</sys:String>
<sys:String x:Key="Pump.LiveData.TEin">T-ein</sys:String>
<sys:String x:Key="Pump.LiveData.Me">ME</sys:String>
<sys:String x:Key="Pump.LiveData.Fbkw">FBKW</sys:String>
<sys:String x:Key="Pump.LiveData.RpmChart">Tendencia RPM</sys:String>
<sys:String x:Key="Pump.Status.Active">Activo</sys:String>
<sys:String x:Key="Pump.Dfi.Title">Calibración Ralentí</sys:String>
<sys:String x:Key="Pump.Dfi.Version">Versión</sys:String>
<sys:String x:Key="Pump.Dfi.Current">DFI Actual</sys:String>
<sys:String x:Key="Pump.Dtcs.Title">Códigos de Falla</sys:String>
<sys:String x:Key="Pump.Identification.Title">Identificación</sys:String>
<sys:String x:Key="Pump.TopStrip.Brand">Marca</sys:String>
<sys:String x:Key="Pump.TopStrip.NoPump">Sin bomba seleccionada</sys:String>
<sys:String x:Key="Pump.TopStrip.Can">CAN</sys:String>
<sys:String x:Key="Pump.TopStrip.KLine">K-Line</sys:String>
<sys:String x:Key="Pump.NoPumpBanner">Sin bomba seleccionada — seleccione una bomba para habilitar el diagnóstico.</sys:String>
<sys:String x:Key="Pump.FastUnlock">Intento de desbloqueo rápido...</sys:String>
<!-- ── Pump identification ──────────────────────────────────────────── --> <!-- ── Pump identification ──────────────────────────────────────────── -->
<sys:String x:Key="PumpId.Label">Bomba:</sys:String> <sys:String x:Key="PumpId.Label">Bomba:</sys:String>
@@ -323,6 +348,7 @@
<sys:String x:Key="Common.No">No</sys:String> <sys:String x:Key="Common.No">No</sys:String>
<sys:String x:Key="Common.Warning">ADVERTENCIA</sys:String> <sys:String x:Key="Common.Warning">ADVERTENCIA</sys:String>
<sys:String x:Key="Common.Disabled">deshabilitado</sys:String> <sys:String x:Key="Common.Disabled">deshabilitado</sys:String>
<sys:String x:Key="Common.Dismiss">Cerrar</sys:String>
<!-- ── Dialog: Report ───────────────────────────────────────────────── --> <!-- ── Dialog: Report ───────────────────────────────────────────────── -->
<sys:String x:Key="Dialog.Report.Title">Generar Informe</sys:String> <sys:String x:Key="Dialog.Report.Title">Generar Informe</sys:String>

View File

@@ -1,9 +1,13 @@
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" <ResourceDictionary 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"
xmlns:conv="clr-namespace:HC_APTBS.Converters">
<!-- Boolean → Visibility converter (shared across views) --> <!-- Boolean → Visibility converter (shared across views) -->
<BooleanToVisibilityConverter x:Key="BoolToVis"/> <BooleanToVisibilityConverter x:Key="BoolToVis"/>
<!-- Null → Collapsed, non-null → Visible -->
<conv:NullToVisibilityConverter x:Key="NullToVis"/>
<!-- LCD blue gradient border --> <!-- LCD blue gradient border -->
<Style x:Key="LcdBlue" TargetType="Border"> <Style x:Key="LcdBlue" TargetType="Border">
<Setter Property="BorderBrush" Value="Black"/> <Setter Property="BorderBrush" Value="Black"/>
@@ -160,6 +164,62 @@
<Setter Property="Margin" Value="6,0,4,0"/> <Setter Property="Margin" Value="6,0,4,0"/>
</Style> </Style>
<!-- ── Pump page card chrome (same Fluent look as Dashboard inline cards) ── -->
<Style x:Key="PumpCard" TargetType="Border">
<Setter Property="Background" Value="{DynamicResource CardBackgroundFillColorDefaultBrush}"/>
<Setter Property="BorderBrush" Value="{DynamicResource CardStrokeColorDefaultBrush}"/>
<Setter Property="BorderThickness" Value="1"/>
<Setter Property="CornerRadius" Value="8"/>
<Setter Property="Padding" Value="16"/>
<Setter Property="Margin" Value="4"/>
</Style>
<Style x:Key="PumpCardHeader" 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,10"/>
<Setter Property="VerticalAlignment" Value="Center"/>
</Style>
<!-- Small label beneath a pump slider (parameter name, unit, range limit) -->
<Style x:Key="PumpCommandLabel" TargetType="TextBlock">
<Setter Property="FontFamily" Value="{DynamicResource ContentControlThemeFontFamily}"/>
<Setter Property="FontSize" Value="11"/>
<Setter Property="Foreground" Value="{DynamicResource TextFillColorSecondaryBrush}"/>
<Setter Property="HorizontalAlignment" Value="Center"/>
<Setter Property="TextAlignment" Value="Center"/>
</Style>
<!-- Current-value readout beneath a pump slider -->
<Style x:Key="PumpCommandValue" TargetType="TextBox">
<Setter Property="FontFamily" Value="Consolas"/>
<Setter Property="FontSize" Value="15"/>
<Setter Property="FontWeight" Value="SemiBold"/>
<Setter Property="Foreground" Value="{DynamicResource AccentTextFillColorPrimaryBrush}"/>
<Setter Property="Background" Value="Transparent"/>
<Setter Property="BorderThickness" Value="0"/>
<Setter Property="TextAlignment" Value="Center"/>
<Setter Property="Padding" Value="4,2"/>
<Setter Property="Width" Value="70"/>
<Setter Property="Height" Value="30"/>
</Style>
<!-- Snackbar bottom-overlay shell -->
<Style x:Key="SnackbarShell" TargetType="Border">
<Setter Property="Background" Value="{DynamicResource CardBackgroundFillColorDefaultBrush}"/>
<Setter Property="BorderBrush" Value="{DynamicResource CardStrokeColorDefaultBrush}"/>
<Setter Property="BorderThickness" Value="1"/>
<Setter Property="CornerRadius" Value="10"/>
<Setter Property="Padding" Value="0"/>
<Setter Property="Effect">
<Setter.Value>
<DropShadowEffect Color="#000000" Opacity="0.18" BlurRadius="20" ShadowDepth="4"/>
</Setter.Value>
</Setter>
</Style>
<!-- ── Device row button — hover tint indicates intent (connect=blue, disconnect=red) --> <!-- ── Device row button — hover tint indicates intent (connect=blue, disconnect=red) -->
<Style x:Key="DeviceRow" TargetType="Button"> <Style x:Key="DeviceRow" TargetType="Button">
<Setter Property="Background" Value="{DynamicResource ControlFillColorSecondaryBrush}"/> <Setter Property="Background" Value="{DynamicResource ControlFillColorSecondaryBrush}"/>

View File

@@ -101,9 +101,6 @@ namespace HC_APTBS.ViewModels
} }
} }
/// <summary>The non-modal unlock progress window, if open.</summary>
private UnlockProgressDialog? _unlockDlg;
/// <summary>Remembers the last authenticated username to pre-fill the next auth dialog.</summary> /// <summary>Remembers the last authenticated username to pre-fill the next auth dialog.</summary>
private string _lastAuthenticatedUser = string.Empty; private string _lastAuthenticatedUser = string.Empty;
@@ -380,14 +377,11 @@ namespace HC_APTBS.ViewModels
_unlockCts = new CancellationTokenSource(); _unlockCts = new CancellationTokenSource();
CurrentUnlockVm = new UnlockProgressViewModel(_unlock, pump.UnlockType, _unlockCts, _loc); CurrentUnlockVm = new UnlockProgressViewModel(_unlock, pump.UnlockType, _unlockCts, _loc);
_unlockDlg = new UnlockProgressDialog(_unlockVm!) CurrentUnlockVm.RequestClose += CloseUnlockDialog;
{ Owner = Application.Current.MainWindow };
// Start unlock in background — ViewModel tracks via event subscriptions. // Start unlock in background — ViewModel tracks via event subscriptions.
var unlockTask = _unlock.UnlockAsync(pump, _unlockCts.Token); var unlockTask = _unlock.UnlockAsync(pump, _unlockCts.Token);
_ = unlockTask.ContinueWith(_ => { }, TaskContinuationOptions.OnlyOnFaulted); _ = unlockTask.ContinueWith(_ => { }, TaskContinuationOptions.OnlyOnFaulted);
_unlockDlg.Show(); // Non-modal — user can continue working.
} }
/// <summary> /// <summary>
@@ -409,15 +403,10 @@ namespace HC_APTBS.ViewModels
if (_unlockVm != null) if (_unlockVm != null)
{ {
_unlockVm.RequestClose -= CloseUnlockDialog;
_unlockVm.Dispose(); _unlockVm.Dispose();
CurrentUnlockVm = null; CurrentUnlockVm = null;
} }
if (_unlockDlg != null)
{
_unlockDlg.ForceClose();
_unlockDlg = null;
}
} }
// ── CAN connection ──────────────────────────────────────────────────────── // ── CAN connection ────────────────────────────────────────────────────────

View File

@@ -1,33 +1,19 @@
using System.ComponentModel; using System.ComponentModel;
using System.Windows;
using CommunityToolkit.Mvvm.ComponentModel; using CommunityToolkit.Mvvm.ComponentModel;
using HC_APTBS.Models; using HC_APTBS.Models;
using HC_APTBS.ViewModels.Dialogs; using HC_APTBS.ViewModels.Dialogs;
using SkiaSharp;
namespace HC_APTBS.ViewModels.Pages namespace HC_APTBS.ViewModels.Pages
{ {
/// <summary>Identifies the sub-section shown inside the Pump navigation page.</summary>
public enum PumpSubPage
{
/// <summary>§3.a — Pump selection and K-Line ECU read.</summary>
Identification = 0,
/// <summary>§3.b — Diagnostic Trouble Codes.</summary>
Dtcs = 1,
/// <summary>§3.c — Live pump CAN readings and status words.</summary>
LiveData = 2,
/// <summary>§3.d — DFI calibration and ME/FBKW/PreIn manual control (auth-gated).</summary>
Adaptation = 3,
/// <summary>§3.e — Ford VP44 immobilizer unlock (visible only when required).</summary>
Unlock = 4
}
/// <summary> /// <summary>
/// ViewModel for the Pump navigation page. /// ViewModel for the Pump navigation page.
/// ///
/// <para>Thin façade that groups the pump-related child ViewModels owned by /// <para>Thin façade that groups the pump-related child ViewModels owned by
/// <see cref="MainViewModel"/> and adds sub-page navigation, banner flags, /// <see cref="MainViewModel"/> and adds banner flags. Holds a <see cref="Root"/>
/// and the Adaptation auth gate. Holds a <see cref="Root"/> reference so /// reference so page XAML can bind to MainViewModel-owned properties (PumpRpm,
/// page XAML can bind to MainViewModel-owned properties (PumpRpm, PumpTemp, /// PumpTemp, KLineState, …) via <c>{Binding Root.X}</c>.</para>
/// KLineState, …) via <c>{Binding Root.X}</c>.</para>
/// </summary> /// </summary>
public sealed partial class PumpPageViewModel : ObservableObject public sealed partial class PumpPageViewModel : ObservableObject
{ {
@@ -36,31 +22,29 @@ namespace HC_APTBS.ViewModels.Pages
// ── Child VM façades ────────────────────────────────────────────────────── // ── Child VM façades ──────────────────────────────────────────────────────
/// <summary>Pump selector and K-Line read (§3.a).</summary> /// <summary>Pump selector and K-Line read.</summary>
public PumpIdentificationViewModel Identification => Root.PumpIdentification; public PumpIdentificationViewModel Identification => Root.PumpIdentification;
/// <summary>Diagnostic Trouble Code list (§3.b).</summary> /// <summary>Diagnostic Trouble Code list.</summary>
public DtcListViewModel DtcList { get; } public DtcListViewModel DtcList { get; }
/// <summary>DFI management (§3.d).</summary> /// <summary>DFI management.</summary>
public DfiManageViewModel DfiViewModel => Root.DfiViewModel; public DfiManageViewModel DfiViewModel => Root.DfiViewModel;
/// <summary>Manual pump control sliders (§3.d).</summary> /// <summary>Manual pump control sliders.</summary>
public PumpControlViewModel PumpControl => Root.PumpControl; public PumpControlViewModel PumpControl => Root.PumpControl;
/// <summary>First pump status display — Status word (§3.c).</summary> /// <summary>First pump status display — Status word.</summary>
public StatusDisplayViewModel StatusDisplay1 => Root.StatusDisplay1; public StatusDisplayViewModel StatusDisplay1 => Root.StatusDisplay1;
/// <summary>Second pump status display — Empf3 word (§3.c).</summary> /// <summary>Second pump status display — Empf3 word.</summary>
public StatusDisplayViewModel StatusDisplay2 => Root.StatusDisplay2; public StatusDisplayViewModel StatusDisplay2 => Root.StatusDisplay2;
/// <summary>Current immobilizer unlock VM (§3.e). Null when no unlock is in progress for this pump.</summary> /// <summary>Current immobilizer unlock VM. Null when no unlock is in progress.</summary>
public UnlockProgressViewModel? UnlockVm => Root.CurrentUnlockVm; public UnlockProgressViewModel? UnlockVm => Root.CurrentUnlockVm;
// ── Navigation state ────────────────────────────────────────────────────── /// <summary>Real-time RPM chart (120-sample rolling window).</summary>
public SingleFlowChartViewModel RpmChart { get; }
/// <summary>Currently selected Pump sub-section.</summary>
[ObservableProperty] private PumpSubPage _selectedSubPage = PumpSubPage.Identification;
// ── Banner flags (derived from Root state) ──────────────────────────────── // ── Banner flags (derived from Root state) ────────────────────────────────
@@ -81,13 +65,12 @@ namespace HC_APTBS.ViewModels.Pages
MainViewModel root, MainViewModel root,
DtcListViewModel dtcList) DtcListViewModel dtcList)
{ {
Root = root; Root = root;
DtcList = dtcList; DtcList = dtcList;
RpmChart = new SingleFlowChartViewModel("RPM", new SKColor(0x21, 0x96, 0xF3), maxSamples: 120);
// Initialise derived flags from the current Root state.
RefreshDerivedFlags(); RefreshDerivedFlags();
// Keep the derived flags in sync with Root changes.
Root.PropertyChanged += OnRootPropertyChanged; Root.PropertyChanged += OnRootPropertyChanged;
Root.PumpIdentification.PumpChanged += _ => RefreshDerivedFlags(); Root.PumpIdentification.PumpChanged += _ => RefreshDerivedFlags();
} }
@@ -104,19 +87,23 @@ namespace HC_APTBS.ViewModels.Pages
case nameof(MainViewModel.CurrentUnlockVm): case nameof(MainViewModel.CurrentUnlockVm):
OnPropertyChanged(nameof(UnlockVm)); OnPropertyChanged(nameof(UnlockVm));
break; break;
case nameof(MainViewModel.PumpRpm):
Application.Current.Dispatcher.Invoke(() => RpmChart.AddValue(Root.PumpRpm));
break;
} }
} }
private void RefreshDerivedFlags() private void RefreshDerivedFlags()
{ {
IsPumpSelected = Root.CurrentPump != null; IsPumpSelected = Root.CurrentPump != null;
IsKLineSessionOpen = Root.KLineState == KLineConnectionState.Connected; IsKLineSessionOpen = Root.KLineState == KLineConnectionState.Connected;
IsKLineSessionFailed = Root.KLineState == KLineConnectionState.Failed; IsKLineSessionFailed = Root.KLineState == KLineConnectionState.Failed;
IsUnlockApplicable = Root.CurrentPump != null && Root.CurrentPump.UnlockType != 0; IsUnlockApplicable = Root.CurrentPump != null && Root.CurrentPump.UnlockType != 0;
OnPropertyChanged(nameof(UnlockVm)); OnPropertyChanged(nameof(UnlockVm));
// Drop any stale DTCs from the previous pump.
DtcList.Reset(); DtcList.Reset();
RpmChart.Clear();
} }
} }
} }

View File

@@ -1,6 +1,7 @@
using System; using System;
using System.Collections; using System.Collections;
using System.Collections.ObjectModel; using System.Collections.ObjectModel;
using System.Linq;
using CommunityToolkit.Mvvm.ComponentModel; using CommunityToolkit.Mvvm.ComponentModel;
using HC_APTBS.Models; using HC_APTBS.Models;
@@ -64,6 +65,9 @@ namespace HC_APTBS.ViewModels
/// <summary>16 bit indicators for bits 015 of the current status word.</summary> /// <summary>16 bit indicators for bits 015 of the current status word.</summary>
public ObservableCollection<BitIndicatorViewModel> Bits { get; } = new(); public ObservableCollection<BitIndicatorViewModel> Bits { get; } = new();
/// <summary>Number of bits currently active (set to 1) in the status word.</summary>
public int ActiveCount => Bits.Count(b => b.IsActive);
/// <summary> /// <summary>
/// Fired when a bit that is flagged as an error transitions to active. /// Fired when a bit that is flagged as an error transitions to active.
/// The argument is the bit position (0-based). /// The argument is the bit position (0-based).
@@ -119,6 +123,8 @@ namespace HC_APTBS.ViewModels
if (isSet && statusBit.Enabled) if (isSet && statusBit.Enabled)
ErrorBitDetected?.Invoke(index); ErrorBitDetected?.Invoke(index);
} }
OnPropertyChanged(nameof(ActiveCount));
} }
/// <summary>Resets all indicators to the default green / inactive state.</summary> /// <summary>Resets all indicators to the default green / inactive state.</summary>
@@ -130,6 +136,8 @@ namespace HC_APTBS.ViewModels
Bits[i].Color = "#26C200"; Bits[i].Color = "#26C200";
Bits[i].Description = $"Bit {i}"; Bits[i].Description = $"Bit {i}";
} }
OnPropertyChanged(nameof(ActiveCount));
} }
} }
} }

View File

@@ -1,138 +0,0 @@
<Window x:Class="HC_APTBS.Views.Dialogs.UnlockProgressDialog"
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"
mc:Ignorable="d"
Title="{DynamicResource Dialog.Unlock.Title}"
Height="360" Width="340"
WindowStyle="None" ResizeMode="NoResize"
WindowStartupLocation="CenterOwner"
Topmost="True"
Background="#FF2B2929"
MouseLeftButtonDown="OnMouseDrag"
Closing="OnWindowClosing">
<Window.Resources>
<BooleanToVisibilityConverter x:Key="BoolToVis"/>
</Window.Resources>
<Grid Margin="16">
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="210"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="*"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<!-- Unlock type label -->
<TextBlock Grid.Row="0"
Text="{Binding UnlockTypeLabel, Mode=OneWay}"
FontSize="14" Foreground="#AAAAAA"
HorizontalAlignment="Center" Margin="0,0,0,4"/>
<!-- Progress ring area -->
<Grid Grid.Row="1" HorizontalAlignment="Center" VerticalAlignment="Center">
<!-- Background ring -->
<Ellipse Width="200" Height="200"
Stroke="#4D4D4D" StrokeThickness="10"
Fill="Transparent"/>
<!-- Content inside ring -->
<StackPanel HorizontalAlignment="Center" VerticalAlignment="Center">
<TextBlock Text="{DynamicResource Dialog.Unlock.Progress}"
FontSize="12" Foreground="#888888"
HorizontalAlignment="Center" Margin="0,0,0,4"/>
<!-- Percentage -->
<TextBlock FontSize="60" FontFamily="Courier New"
Foreground="White" HorizontalAlignment="Center">
<TextBlock.Text>
<Binding Path="Progress" Mode="OneWay"
StringFormat="{}{0}%"/>
</TextBlock.Text>
</TextBlock>
<!-- Elapsed time -->
<TextBlock Text="{Binding ElapsedTime, Mode=OneWay}"
FontSize="16" FontFamily="Courier New"
Foreground="#CCCCCC" HorizontalAlignment="Center"
Margin="0,2,0,0"/>
</StackPanel>
</Grid>
<!-- Phase text -->
<TextBlock Grid.Row="2"
Text="{Binding PhaseText, Mode=OneWay}"
FontSize="16" Foreground="White"
HorizontalAlignment="Center" Margin="0,4,0,6"/>
<!-- Progress bar -->
<ProgressBar Grid.Row="3"
Value="{Binding Progress, Mode=OneWay}"
Minimum="0" Maximum="100"
Height="12" Margin="8,0"
Foreground="#00EC00" Background="#3D3D3D"/>
<!-- Result text — overlays the spacer row so it never displaces buttons -->
<TextBlock Grid.Row="4"
Text="{Binding ResultText, Mode=OneWay}"
FontSize="22" FontWeight="Bold"
HorizontalAlignment="Center" VerticalAlignment="Center"
Visibility="{Binding IsComplete, Converter={StaticResource BoolToVis}}">
<TextBlock.Style>
<Style TargetType="TextBlock">
<Setter Property="Foreground" Value="#FF5858"/>
<Style.Triggers>
<DataTrigger Binding="{Binding IsSuccess}" Value="True">
<Setter Property="Foreground" Value="#00EC00"/>
</DataTrigger>
</Style.Triggers>
</Style>
</TextBlock.Style>
</TextBlock>
<!-- Buttons -->
<StackPanel Grid.Row="5" Orientation="Horizontal"
HorizontalAlignment="Center" Margin="0,8,0,0">
<!-- Cancel button -->
<Button Content="{DynamicResource Common.Cancel}" Width="90" Height="30"
Margin="0,0,12,0"
Command="{Binding CancelCommand}"
Foreground="White" FontWeight="Bold">
<Button.Style>
<Style TargetType="Button">
<Setter Property="Background" Value="#FF5858"/>
<Style.Triggers>
<Trigger Property="IsEnabled" Value="False">
<Setter Property="Background" Value="#4D4D4D"/>
<Setter Property="Foreground" Value="#888888"/>
</Trigger>
</Style.Triggers>
</Style>
</Button.Style>
</Button>
<!-- Close button -->
<Button Content="{DynamicResource Common.Close}" Width="90" Height="30"
Command="{Binding CloseCommand}"
Foreground="White" FontWeight="Bold">
<Button.Style>
<Style TargetType="Button">
<Setter Property="Background" Value="#4D4D4D"/>
<Style.Triggers>
<Trigger Property="IsEnabled" Value="True">
<Setter Property="Background" Value="#337AB7"/>
</Trigger>
<Trigger Property="IsEnabled" Value="False">
<Setter Property="Foreground" Value="#888888"/>
</Trigger>
</Style.Triggers>
</Style>
</Button.Style>
</Button>
</StackPanel>
</Grid>
</Window>

View File

@@ -1,49 +0,0 @@
using System.ComponentModel;
using System.Windows;
using System.Windows.Input;
using HC_APTBS.ViewModels.Dialogs;
namespace HC_APTBS.Views.Dialogs
{
/// <summary>
/// Non-modal window showing immobilizer unlock progress.
/// Prevents user-initiated closing until the unlock sequence completes;
/// programmatic close via <see cref="ForceClose"/> always succeeds.
/// Equivalent to the old <c>WUnlocker</c> window.
/// </summary>
public partial class UnlockProgressDialog : Window
{
private bool _forceClose;
/// <summary>Creates the dialog and wires the ViewModel.</summary>
public UnlockProgressDialog(UnlockProgressViewModel vm)
{
InitializeComponent();
DataContext = vm;
vm.RequestClose += ForceClose;
}
/// <summary>Closes the window unconditionally (bypasses the completion guard).</summary>
public void ForceClose()
{
_forceClose = true;
Close();
}
/// <summary>Allows dragging the borderless window by clicking anywhere.</summary>
private void OnMouseDrag(object sender, MouseButtonEventArgs e)
{
if (e.ChangedButton == MouseButton.Left)
DragMove();
}
/// <summary>Prevents user-initiated closing while the unlock sequence is still running.</summary>
private void OnWindowClosing(object? sender, CancelEventArgs e)
{
if (_forceClose) return;
if (DataContext is UnlockProgressViewModel vm && !vm.IsComplete)
e.Cancel = true;
}
}
}

View File

@@ -4,262 +4,62 @@
xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:uc="clr-namespace:HC_APTBS.Views.UserControls" xmlns:uc="clr-namespace:HC_APTBS.Views.UserControls"
xmlns:vm="clr-namespace:HC_APTBS.ViewModels.Pages"
mc:Ignorable="d" mc:Ignorable="d"
d:DesignHeight="780" d:DesignWidth="1100"> d:DesignHeight="900" d:DesignWidth="1400">
<!-- <!--
Pump page — ECU diagnostics and control. Pump page — ECU diagnostics and control.
DataContext = PumpPageViewModel. DataContext = PumpPageViewModel.
Layout: Layout: top strip (row 0) + 3-column card body (row 1).
- Banner row: K-Line session banner + "no pump selected" banner Col 0 (1*, MinWidth=260): PumpCommandsCard (2*) + DfiCalibrationCard (1*)
- Sub-nav (left): Identification / DTCs / LiveData / Adaptation / Unlock Col 1 (1.5*): PumpLiveDataCard (fills)
- Sub-page content (right): selected sub-section via hidden tabs Col 2 (1*, MinWidth=280): PumpIdentificationCard (Auto) + DtcCard (*)
--> -->
<UserControl.Resources> <UserControl.Resources>
<BooleanToVisibilityConverter x:Key="BoolToVis"/> <BooleanToVisibilityConverter x:Key="BoolToVis"/>
<!-- Sub-nav item: narrower than the main rail, same accent pattern -->
<Style x:Key="SubNavItem" TargetType="ListBoxItem">
<Setter Property="Height" Value="44"/>
<Setter Property="Padding" Value="0"/>
<Setter Property="HorizontalContentAlignment" Value="Stretch"/>
<Setter Property="FocusVisualStyle" Value="{x:Null}"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="ListBoxItem">
<Border x:Name="Root" Background="Transparent">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="3"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<Border x:Name="Accent" Grid.Column="0" Background="Transparent"/>
<ContentPresenter Grid.Column="1"
VerticalAlignment="Center"
Margin="12,0,6,0"/>
</Grid>
</Border>
<ControlTemplate.Triggers>
<Trigger Property="IsMouseOver" Value="True">
<Setter TargetName="Root" Property="Background" Value="#ECECEC"/>
</Trigger>
<Trigger Property="IsSelected" Value="True">
<Setter TargetName="Root" Property="Background" Value="#E4EEF7"/>
<Setter TargetName="Accent" Property="Background" Value="#2196F3"/>
</Trigger>
<Trigger Property="IsEnabled" Value="False">
<Setter Property="Opacity" Value="0.35"/>
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
<Style x:Key="SubNavText" TargetType="TextBlock">
<Setter Property="FontSize" Value="13"/>
<Setter Property="Foreground" Value="#333"/>
<Setter Property="VerticalAlignment" Value="Center"/>
</Style>
</UserControl.Resources> </UserControl.Resources>
<Grid> <Grid Margin="12">
<Grid.RowDefinitions> <Grid.RowDefinitions>
<RowDefinition Height="Auto"/> <!-- banners --> <RowDefinition Height="Auto"/>
<RowDefinition Height="*"/> <!-- body --> <RowDefinition Height="*"/>
</Grid.RowDefinitions> </Grid.RowDefinitions>
<!-- ══════════════════════════════════════════════════════════════ <!-- ── Top status strip ─────────────────────────────────────────────── -->
Banners: K-Line session + no pump selected <uc:PumpTopStripView Grid.Row="0"/>
══════════════════════════════════════════════════════════════ -->
<StackPanel Grid.Row="0">
<!-- K-Line session banner (closed / failed) --> <!-- ── Body: 3-column card layout ───────────────────────────────────── -->
<Border Padding="10,6">
<Border.Style>
<Style TargetType="Border">
<Setter Property="Background" Value="#FFF3CD"/>
<Setter Property="BorderBrush" Value="#FFC107"/>
<Setter Property="BorderThickness" Value="0,0,0,1"/>
<Setter Property="Visibility" Value="Visible"/>
<Style.Triggers>
<DataTrigger Binding="{Binding IsKLineSessionOpen}" Value="True">
<Setter Property="Visibility" Value="Collapsed"/>
</DataTrigger>
<DataTrigger Binding="{Binding IsKLineSessionFailed}" Value="True">
<Setter Property="Background" Value="#FDECEA"/>
<Setter Property="BorderBrush" Value="#D62828"/>
</DataTrigger>
</Style.Triggers>
</Style>
</Border.Style>
<TextBlock FontSize="12" TextWrapping="Wrap">
<Run Text="●" FontSize="14"/>
<Run Text=" "/>
<Run Text="{DynamicResource Pump.KLineClosed}"/>
</TextBlock>
</Border>
<!-- No pump selected banner (BoolToVis has no Invert support, so use a DataTrigger style) -->
<Border Background="#E3F2FD" BorderBrush="#2196F3" BorderThickness="0,0,0,1"
Padding="10,6">
<Border.Style>
<Style TargetType="Border">
<Setter Property="Visibility" Value="Collapsed"/>
<Style.Triggers>
<DataTrigger Binding="{Binding IsPumpSelected}" Value="False">
<Setter Property="Visibility" Value="Visible"/>
</DataTrigger>
</Style.Triggers>
</Style>
</Border.Style>
<TextBlock Text="{DynamicResource Pump.NoPumpSelected}"
FontSize="12" Foreground="#0D47A1"/>
</Border>
</StackPanel>
<!-- ══════════════════════════════════════════════════════════════
Body: sub-nav + sub-page content
══════════════════════════════════════════════════════════════ -->
<Grid Grid.Row="1"> <Grid Grid.Row="1">
<Grid.ColumnDefinitions> <Grid.ColumnDefinitions>
<ColumnDefinition Width="180"/> <ColumnDefinition Width="1*" MinWidth="260"/>
<ColumnDefinition Width="*"/> <ColumnDefinition Width="1.5*"/>
<ColumnDefinition Width="1*" MinWidth="280"/>
</Grid.ColumnDefinitions> </Grid.ColumnDefinitions>
<!-- ── Sub-nav rail ───────────────────────────────────────── --> <!-- Col 0: Commands (top 2*) + Idling Calibration (bottom 1*) -->
<Border Grid.Column="0" Background="#F7F7F7" <Grid Grid.Column="0">
BorderBrush="#DDD" BorderThickness="0,0,1,0"> <Grid.RowDefinitions>
<ListBox SelectedValuePath="Tag" BorderThickness="0" <RowDefinition Height="2*"/>
Background="Transparent" <RowDefinition Height="1*"/>
ItemContainerStyle="{StaticResource SubNavItem}" </Grid.RowDefinitions>
SelectedValue="{Binding SelectedSubPage, Mode=TwoWay}">
<ListBoxItem Tag="{x:Static vm:PumpSubPage.Identification}">
<TextBlock Text="{DynamicResource PumpSub.Identification}"
Style="{StaticResource SubNavText}"/>
</ListBoxItem>
<ListBoxItem Tag="{x:Static vm:PumpSubPage.Dtcs}"
IsEnabled="{Binding IsPumpSelected}">
<TextBlock Text="{DynamicResource PumpSub.Dtcs}"
Style="{StaticResource SubNavText}"/>
</ListBoxItem>
<ListBoxItem Tag="{x:Static vm:PumpSubPage.LiveData}"
IsEnabled="{Binding IsPumpSelected}">
<TextBlock Text="{DynamicResource PumpSub.LiveData}"
Style="{StaticResource SubNavText}"/>
</ListBoxItem>
<ListBoxItem Tag="{x:Static vm:PumpSubPage.Adaptation}"
IsEnabled="{Binding IsPumpSelected}">
<TextBlock Text="{DynamicResource PumpSub.Adaptation}"
Style="{StaticResource SubNavText}"/>
</ListBoxItem>
<ListBoxItem Tag="{x:Static vm:PumpSubPage.Unlock}">
<ListBoxItem.Style>
<Style TargetType="ListBoxItem" BasedOn="{StaticResource SubNavItem}">
<Setter Property="Visibility" Value="Collapsed"/>
<Style.Triggers>
<DataTrigger Binding="{Binding IsUnlockApplicable}" Value="True">
<Setter Property="Visibility" Value="Visible"/>
</DataTrigger>
</Style.Triggers>
</Style>
</ListBoxItem.Style>
<TextBlock Text="{DynamicResource PumpSub.Unlock}"
Style="{StaticResource SubNavText}"/>
</ListBoxItem>
</ListBox>
</Border>
<!-- ── Sub-page content ───────────────────────────────────── --> <uc:PumpCommandsCard Grid.Row="0" DataContext="{Binding PumpControl}"/>
<TabControl Grid.Column="1" <uc:DfiCalibrationCard Grid.Row="1" DataContext="{Binding DfiViewModel}"/>
Style="{StaticResource HiddenTabsTabControl}" </Grid>
SelectedIndex="{Binding SelectedSubPage, Converter={StaticResource EnumToInt}}">
<!-- 3a Identification --> <!-- Col 1: Live Data (full height) -->
<TabItem> <uc:PumpLiveDataCard Grid.Column="1"/>
<ScrollViewer VerticalScrollBarVisibility="Auto">
<uc:PumpIdentificationPanelView
DataContext="{Binding Identification}"/>
</ScrollViewer>
</TabItem>
<!-- 3b DTCs --> <!-- Col 2: Identification (Auto) + DTCs (*) -->
<TabItem> <Grid Grid.Column="2">
<ScrollViewer VerticalScrollBarVisibility="Auto"> <Grid.RowDefinitions>
<uc:DtcListView DataContext="{Binding DtcList}"/> <RowDefinition Height="Auto"/>
</ScrollViewer> <RowDefinition Height="*"/>
</TabItem> </Grid.RowDefinitions>
<!-- 3c Live Data — DataContext stays as PumpPageViewModel so <uc:PumpIdentificationCard Grid.Row="0" DataContext="{Binding Identification}"/>
the view can reach Root.PumpXxx and StatusDisplay1/2. --> <uc:DtcCard Grid.Row="1" DataContext="{Binding DtcList}"/>
<TabItem> </Grid>
<ScrollViewer VerticalScrollBarVisibility="Auto">
<uc:PumpLiveDataView/>
</ScrollViewer>
</TabItem>
<!-- 3d Adaptation -->
<TabItem>
<ScrollViewer VerticalScrollBarVisibility="Auto">
<Border Background="#FAFAFA" BorderBrush="#DDD"
BorderThickness="1" CornerRadius="4"
Padding="12" Margin="6">
<StackPanel>
<TextBlock Text="{DynamicResource PumpSub.Adaptation}"
FontSize="15" FontWeight="SemiBold"
Foreground="#333" Margin="0,0,0,8"/>
<uc:DfiManageView DataContext="{Binding DfiViewModel}"/>
<Separator Margin="0,10"/>
<uc:PumpControlView DataContext="{Binding PumpControl}"/>
</StackPanel>
</Border>
</ScrollViewer>
</TabItem>
<!-- 3e Unlock -->
<TabItem>
<ScrollViewer VerticalScrollBarVisibility="Auto">
<Grid>
<!-- "No active unlock" placeholder — visible when UnlockVm is null -->
<Border Background="#F5F5F5" BorderBrush="#CCC"
BorderThickness="1" CornerRadius="4"
Padding="20" Margin="6"
HorizontalAlignment="Center" VerticalAlignment="Center">
<Border.Style>
<Style TargetType="Border">
<Setter Property="Visibility" Value="Collapsed"/>
<Style.Triggers>
<DataTrigger Binding="{Binding UnlockVm}" Value="{x:Null}">
<Setter Property="Visibility" Value="Visible"/>
</DataTrigger>
</Style.Triggers>
</Style>
</Border.Style>
<TextBlock Text="{DynamicResource Pump.NoUnlockActive}"
FontSize="13" Foreground="#666"/>
</Border>
<!-- Active unlock panel — visible when UnlockVm is non-null.
DataContext binds to UnlockVm so `{Binding}` inside the
style evaluates to the VM instance (or null). -->
<uc:UnlockPanelView DataContext="{Binding UnlockVm}">
<uc:UnlockPanelView.Style>
<Style TargetType="uc:UnlockPanelView">
<Setter Property="Visibility" Value="Visible"/>
<Style.Triggers>
<DataTrigger Binding="{Binding}" Value="{x:Null}">
<Setter Property="Visibility" Value="Collapsed"/>
</DataTrigger>
</Style.Triggers>
</Style>
</uc:UnlockPanelView.Style>
</uc:UnlockPanelView>
</Grid>
</ScrollViewer>
</TabItem>
</TabControl>
</Grid> </Grid>
</Grid> </Grid>
</UserControl> </UserControl>

View File

@@ -0,0 +1,132 @@
<UserControl x:Class="HC_APTBS.Views.UserControls.DfiCalibrationCard"
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="260" d:DesignWidth="260">
<!-- DataContext = DfiManageViewModel (via {Binding DfiViewModel}) -->
<UserControl.Resources>
<BooleanToVisibilityConverter x:Key="BoolToVis"/>
</UserControl.Resources>
<Border Style="{StaticResource PumpCard}">
<DockPanel LastChildFill="True">
<!-- ── Card header ───────────────────────────────────────────── -->
<DockPanel DockPanel.Dock="Top" Margin="0,0,0,12">
<ui:SymbolIcon DockPanel.Dock="Left" Symbol="Gauge24" FontSize="16"
Foreground="{DynamicResource AccentTextFillColorPrimaryBrush}"
Margin="0,0,8,0" VerticalAlignment="Center"/>
<TextBlock Text="{DynamicResource Pump.Dfi.Title}"
Style="{StaticResource PumpCardHeader}" Margin="0"/>
</DockPanel>
<!-- ── Version selector row ──────────────────────────────────── -->
<Grid DockPanel.Dock="Top" Margin="0,0,0,10">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<TextBlock Grid.Column="0"
Text="{DynamicResource Pump.Dfi.Version}"
Style="{StaticResource PumpCommandLabel}"
VerticalAlignment="Center" Margin="0,0,8,0"/>
<ComboBox Grid.Column="1"
SelectedIndex="{Binding VersionIndex}"
Height="28">
<ComboBoxItem Content="V1"/>
<ComboBoxItem Content="V2"/>
<ComboBoxItem Content="V3"/>
<ComboBoxItem Content="V4"/>
</ComboBox>
<CheckBox Grid.Column="2"
IsChecked="{Binding IsAutoMode}"
Content="{DynamicResource Dfi.Auto}"
FontSize="12"
Foreground="{DynamicResource TextFillColorPrimaryBrush}"
VerticalAlignment="Center" Margin="8,0,0,0"/>
</Grid>
<!-- ── Current DFI readout ───────────────────────────────────── -->
<Border DockPanel.Dock="Top"
Background="{DynamicResource ControlFillColorSecondaryBrush}"
BorderBrush="{DynamicResource ControlStrokeColorDefaultBrush}"
BorderThickness="1" CornerRadius="6" Padding="12,8"
Margin="0,0,0,12">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<TextBlock Text="{DynamicResource Pump.Dfi.Current}"
FontSize="11"
Foreground="{DynamicResource TextFillColorSecondaryBrush}"
VerticalAlignment="Center"/>
<TextBlock Grid.Column="1"
Text="{Binding CurrentDfi, StringFormat=F2, Mode=OneWay}"
FontFamily="Consolas" FontSize="22" FontWeight="SemiBold"
Foreground="{DynamicResource AccentTextFillColorPrimaryBrush}"
VerticalAlignment="Center"/>
</Grid>
</Border>
<!-- ── Busy indicator ─────────────────────────────────────────── -->
<ProgressBar DockPanel.Dock="Top"
Value="{Binding ProgressPercent, Mode=OneWay}"
Minimum="0" Maximum="100" Height="3"
Margin="0,0,0,6"
Visibility="{Binding IsBusy, Converter={StaticResource BoolToVis}}"/>
<!-- ── DFI slider ─────────────────────────────────────────────── -->
<Grid DockPanel.Dock="Top" Margin="0,0,0,8">
<Slider Value="{Binding SliderRaw}"
Minimum="-145" Maximum="145"
TickFrequency="5" SmallChange="5" LargeChange="5"
IsSnapToTickEnabled="True" TickPlacement="BottomRight"
Margin="0,0,0,4" VerticalAlignment="Center"/>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<TextBlock Grid.Column="0" Text="-1.45"
HorizontalAlignment="Left" VerticalAlignment="Bottom"
FontSize="10" Foreground="{DynamicResource TextFillColorTertiaryBrush}"/>
<TextBlock Grid.Column="1"
Text="{Binding SliderDfiValue, StringFormat=F2, Mode=OneWay}"
HorizontalAlignment="Center" VerticalAlignment="Bottom"
FontFamily="Consolas" FontSize="16" FontWeight="SemiBold"
Foreground="{DynamicResource AccentTextFillColorPrimaryBrush}"/>
<TextBlock Grid.Column="2" Text="+1.45"
HorizontalAlignment="Right" VerticalAlignment="Bottom"
FontSize="10" Foreground="{DynamicResource TextFillColorTertiaryBrush}"/>
</Grid>
</Grid>
<!-- ── Read / Write buttons ──────────────────────────────────── -->
<Grid DockPanel.Dock="Bottom" Margin="0,0,0,0">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="8"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<ui:Button Grid.Column="0"
Content="{DynamicResource Dfi.Read}"
Command="{Binding ReadDfiCommand}"
Appearance="Secondary"
FontWeight="Bold" Height="32"/>
<ui:Button Grid.Column="2"
Content="{DynamicResource Dfi.Write}"
Command="{Binding WriteDfiCommand}"
Appearance="Primary"
FontWeight="Bold" Height="32"/>
</Grid>
</DockPanel>
</Border>
</UserControl>

View File

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

View File

@@ -1,115 +0,0 @@
<UserControl x:Class="HC_APTBS.Views.UserControls.DfiManageView"
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"
mc:Ignorable="d"
d:DesignHeight="150" d:DesignWidth="460" MaxHeight="150">
<UserControl.Resources>
<Style x:Key="LcdGreen" TargetType="Border">
<Setter Property="BorderThickness" Value="3"/>
<Setter Property="SnapsToDevicePixels" Value="True"/>
<Setter Property="BorderBrush">
<Setter.Value>
<LinearGradientBrush StartPoint="0,0" EndPoint="1,1">
<GradientStop Color="DarkSlateGray" Offset="0"/>
<GradientStop Color="LawnGreen" Offset="1"/>
</LinearGradientBrush>
</Setter.Value>
</Setter>
<Setter Property="Background">
<Setter.Value>
<LinearGradientBrush StartPoint="0,0" EndPoint="1,1">
<GradientStop Color="LawnGreen" Offset="0"/>
<GradientStop Color="#57B000" Offset="1.5"/>
</LinearGradientBrush>
</Setter.Value>
</Setter>
</Style>
</UserControl.Resources>
<Grid Margin="16,8">
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="75"/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="144" MinWidth="60"/>
<ColumnDefinition MinWidth="200"/>
</Grid.ColumnDefinitions>
<!-- LEFT: version picker + read/write buttons -->
<ComboBox Margin="4" VerticalAlignment="Bottom"
SelectedIndex="{Binding VersionIndex}">
<ComboBoxItem Content="V1"/>
<ComboBoxItem Content="V2"/>
<ComboBoxItem Content="V3"/>
<ComboBoxItem Content="V4"/>
</ComboBox>
<Grid Grid.Row="1" Margin="4">
<Grid.ColumnDefinitions>
<ColumnDefinition/>
<ColumnDefinition Width="4"/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<Button Content="{DynamicResource Dfi.Read}"
Command="{Binding ReadDfiCommand}"
FontSize="12" FontWeight="Bold" Padding="4"/>
<Button Grid.Column="2" Content="{DynamicResource Dfi.Write}"
Command="{Binding WriteDfiCommand}"
FontSize="12" FontWeight="Bold" Padding="4"/>
</Grid>
<!-- TOP RIGHT: DFI value LCD + auto checkbox -->
<Grid Grid.Row="0" Grid.Column="1" Margin="4">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" MinWidth="200"/>
<ColumnDefinition MinWidth="100"/>
</Grid.ColumnDefinitions>
<Border Grid.ColumnSpan="1" Style="{StaticResource LcdGreen}" Margin="0,0,12,0" >
<Grid Grid.Row="1" >
<Grid.ColumnDefinitions>
<ColumnDefinition/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<TextBlock Text="{DynamicResource Dfi.Label}"
HorizontalAlignment="Center" VerticalAlignment="Center"
Foreground="Black" FontSize="18" FontFamily="Consolas"/>
<TextBlock Text="{Binding CurrentDfi, StringFormat=F2, Mode=OneWay}"
Grid.Column="1"
HorizontalAlignment="Center" VerticalAlignment="Center"
FontSize="26" FontWeight="Bold" Foreground="Black"/>
</Grid>
</Border>
<Border Grid.ColumnSpan="1" BorderThickness="1" BorderBrush="Black" Margin="0,0,12,0" SnapsToDevicePixels="True"/>
</Grid>
<!-- AUTO checkbox — sits outside the column pair; placed in Column=1 outside normal layout -->
<CheckBox IsChecked="{Binding IsAutoMode}"
Content="{DynamicResource Dfi.Auto}"
Grid.Row="0" Grid.Column="1"
Height="Auto"
HorizontalAlignment="Right" VerticalAlignment="Center"
Margin="0,0,10,0"
Foreground="Black" FontSize="20"/>
<!-- BOTTOM RIGHT: slider with range labels + current value -->
<Grid Grid.Row="1" Grid.Column="1">
<Slider Value="{Binding SliderRaw}"
Minimum="-145" Maximum="145"
TickFrequency="5" SmallChange="5" LargeChange="5"
IsSnapToTickEnabled="True" TickPlacement="BottomRight"
Margin="10,0" VerticalAlignment="Center"
Foreground="Black"/>
<TextBlock Text="-1.45" HorizontalAlignment="Left" VerticalAlignment="Bottom" Margin="10,0,0,0" Foreground="DimGray"/>
<TextBlock Text="+1.45" HorizontalAlignment="Right" VerticalAlignment="Bottom" Margin="0,0,10,0" Foreground="DimGray"/>
<TextBlock Text="{Binding SliderDfiValue, StringFormat=F2, Mode=OneWay}"
HorizontalAlignment="Center" VerticalAlignment="Bottom"
Margin="0,0,0,4" FontSize="20" Foreground="Black"/>
</Grid>
</Grid>
</UserControl>

View File

@@ -1,10 +0,0 @@
using System.Windows.Controls;
namespace HC_APTBS.Views.UserControls
{
/// <summary>Code-behind for DfiManageView — no logic; all behaviour is in the ViewModel.</summary>
public partial class DfiManageView : UserControl
{
public DfiManageView() => InitializeComponent();
}
}

View File

@@ -0,0 +1,102 @@
<UserControl x:Class="HC_APTBS.Views.UserControls.DtcCard"
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="280" d:DesignWidth="320">
<!-- DataContext = DtcListViewModel (via {Binding DtcList}) -->
<UserControl.Resources>
<BooleanToVisibilityConverter x:Key="BoolToVis"/>
</UserControl.Resources>
<Border Style="{StaticResource PumpCard}">
<DockPanel LastChildFill="True">
<!-- ── Card header ───────────────────────────────────────────── -->
<DockPanel DockPanel.Dock="Top" Margin="0,0,0,10">
<ui:SymbolIcon DockPanel.Dock="Left" Symbol="Warning24" FontSize="16"
Foreground="{DynamicResource SystemFillColorCautionBrush}"
Margin="0,0,8,0" VerticalAlignment="Center"/>
<TextBlock Text="{DynamicResource Pump.Dtcs.Title}"
Style="{StaticResource PumpCardHeader}" Margin="0"/>
<StackPanel DockPanel.Dock="Right" Orientation="Horizontal"
VerticalAlignment="Center" HorizontalAlignment="Right">
<ui:Button Content="{DynamicResource Dtc.Read}"
Command="{Binding ReadCommand}"
Appearance="Secondary"
Height="28" Margin="0,0,6,0"/>
<ui:Button Content="{DynamicResource Dtc.Clear}"
Command="{Binding ClearCommand}"
Appearance="Secondary"
Height="28"/>
</StackPanel>
</DockPanel>
<!-- ── Busy indicator ─────────────────────────────────────────── -->
<ProgressBar DockPanel.Dock="Top"
IsIndeterminate="True" Height="3" Margin="0,0,0,6"
Visibility="{Binding IsBusy, Converter={StaticResource BoolToVis}}"/>
<!-- ── Status text ─────────────────────────────────────────────── -->
<TextBlock DockPanel.Dock="Top"
Text="{Binding StatusText}"
FontSize="11"
Foreground="{DynamicResource TextFillColorTertiaryBrush}"
Margin="0,0,0,6"/>
<!-- ── "No faults" success pill ───────────────────────────────── -->
<Border DockPanel.Dock="Top"
Background="{DynamicResource SystemFillColorSuccessBackgroundBrush}"
BorderBrush="{DynamicResource SystemFillColorSuccessBrush}"
BorderThickness="1" CornerRadius="6" Padding="10,6"
HorizontalAlignment="Left" Margin="0,0,0,4"
Visibility="{Binding IsClear, Converter={StaticResource BoolToVis}}">
<StackPanel Orientation="Horizontal">
<ui:SymbolIcon Symbol="CheckmarkCircle24" FontSize="14"
Foreground="{DynamicResource SystemFillColorSuccessBrush}"
Margin="0,0,6,0" VerticalAlignment="Center"/>
<TextBlock Text="{DynamicResource Dtc.NoFaults}"
FontSize="12" FontWeight="SemiBold"
Foreground="{DynamicResource SystemFillColorSuccessBrush}"
VerticalAlignment="Center"/>
</StackPanel>
</Border>
<!-- ── DTC rows ───────────────────────────────────────────────── -->
<ScrollViewer VerticalScrollBarVisibility="Auto">
<ItemsControl ItemsSource="{Binding Codes}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<Border Background="{DynamicResource SystemFillColorCautionBackgroundBrush}"
BorderBrush="{DynamicResource SystemFillColorCautionBrush}"
BorderThickness="0,0,0,1" CornerRadius="4"
Padding="8,6" Margin="0,2">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="90"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<TextBlock Text="{Binding Code}"
FontFamily="Consolas" FontWeight="Bold"
FontSize="12"
Foreground="{DynamicResource SystemFillColorCriticalBrush}"
VerticalAlignment="Center"/>
<TextBlock Grid.Column="1"
Text="{Binding Description}"
FontSize="12"
Foreground="{DynamicResource TextFillColorPrimaryBrush}"
TextWrapping="Wrap"
VerticalAlignment="Center"/>
</Grid>
</Border>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</ScrollViewer>
</DockPanel>
</Border>
</UserControl>

View File

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

View File

@@ -1,94 +0,0 @@
<UserControl x:Class="HC_APTBS.Views.UserControls.DtcListView"
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"
mc:Ignorable="d"
d:DesignHeight="360" d:DesignWidth="700">
<!--
Pump page §3.b DTC list. DataContext = DtcListViewModel.
Moves the fault-code surface out of the one-shot KlineErrorsDialog
into a proper sub-section with read / clear actions and a row list.
-->
<UserControl.Resources>
<BooleanToVisibilityConverter x:Key="BoolToVis"/>
</UserControl.Resources>
<Border Background="#FAFAFA" BorderBrush="#DDD" BorderThickness="1"
CornerRadius="4" Padding="12" Margin="6">
<DockPanel>
<!-- ── Header row: title + actions ─────────────────────────── -->
<Grid DockPanel.Dock="Top" Margin="0,0,0,8">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<TextBlock Text="{DynamicResource PumpSub.Dtcs}"
FontSize="15" FontWeight="SemiBold" Foreground="#333"
VerticalAlignment="Center"/>
<StackPanel Grid.Column="1" Orientation="Horizontal">
<Button Content="{DynamicResource Dtc.Read}"
Command="{Binding ReadCommand}"
MinWidth="90" Height="28" Margin="0,0,6,0"
FontWeight="Bold"/>
<Button Content="{DynamicResource Dtc.Clear}"
Command="{Binding ClearCommand}"
MinWidth="90" Height="28"/>
</StackPanel>
</Grid>
<!-- ── Status line ─────────────────────────────────────────── -->
<TextBlock DockPanel.Dock="Top"
Text="{Binding StatusText}"
Foreground="#666" FontSize="11" Margin="0,0,0,6"/>
<!-- ── Busy indicator ──────────────────────────────────────── -->
<ProgressBar DockPanel.Dock="Top"
IsIndeterminate="True" Height="3" Margin="0,0,0,6"
Visibility="{Binding IsBusy, Converter={StaticResource BoolToVis}}"/>
<!-- ── "No faults" banner ──────────────────────────────────── -->
<Border DockPanel.Dock="Top"
Background="#26C200" CornerRadius="3" Padding="10,6"
HorizontalAlignment="Left"
Visibility="{Binding IsClear, Converter={StaticResource BoolToVis}}">
<TextBlock Text="{DynamicResource Dtc.NoFaults}"
Foreground="White" FontWeight="Bold" FontSize="12"/>
</Border>
<!-- ── DTC rows ────────────────────────────────────────────── -->
<ListBox ItemsSource="{Binding Codes}"
BorderThickness="0" Background="Transparent">
<ListBox.ItemContainerStyle>
<Style TargetType="ListBoxItem">
<Setter Property="HorizontalContentAlignment" Value="Stretch"/>
<Setter Property="Padding" Value="0"/>
</Style>
</ListBox.ItemContainerStyle>
<ListBox.ItemTemplate>
<DataTemplate>
<Border Background="#FDECEA" BorderBrush="#D62828"
BorderThickness="0,0,0,1" Padding="8,6" Margin="0,2">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="110"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<TextBlock Text="{Binding Code}"
FontFamily="Consolas" FontWeight="Bold"
FontSize="13" Foreground="#B22222"
VerticalAlignment="Center"/>
<TextBlock Grid.Column="1"
Text="{Binding Description}"
FontSize="12" Foreground="#333"
TextWrapping="Wrap"
VerticalAlignment="Center"/>
</Grid>
</Border>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</DockPanel>
</Border>
</UserControl>

View File

@@ -1,16 +0,0 @@
using System.Windows.Controls;
namespace HC_APTBS.Views.UserControls
{
/// <summary>
/// Pump page §3.b DTC list view. DataContext is
/// <c>DtcListViewModel</c>.
/// </summary>
public partial class DtcListView : UserControl
{
public DtcListView()
{
InitializeComponent();
}
}
}

View File

@@ -0,0 +1,231 @@
<UserControl x:Class="HC_APTBS.Views.UserControls.PumpCommandsCard"
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="460" d:DesignWidth="260"
IsEnabled="{Binding IsEnabled}">
<!-- DataContext = PumpControlViewModel (via {Binding PumpControl}) -->
<UserControl.Resources>
<BooleanToVisibilityConverter x:Key="BoolToVis"/>
<Style x:Key="SettingsTextBox" TargetType="TextBox">
<Setter Property="Width" Value="44"/>
<Setter Property="HorizontalContentAlignment" Value="Center"/>
<Setter Property="VerticalAlignment" Value="Top"/>
<Setter Property="FontSize" Value="13"/>
</Style>
<Style x:Key="SettingsLabel" TargetType="TextBlock">
<Setter Property="VerticalAlignment" Value="Bottom"/>
<Setter Property="FontSize" Value="11"/>
<Setter Property="Foreground" Value="White"/>
<Setter Property="Margin" Value="0,2,0,0"/>
<Setter Property="HorizontalAlignment" Value="Center"/>
</Style>
</UserControl.Resources>
<Border Style="{StaticResource PumpCard}">
<DockPanel LastChildFill="True">
<!-- ── Card header ───────────────────────────────────────────── -->
<DockPanel DockPanel.Dock="Top" Margin="0,0,0,12">
<ui:SymbolIcon DockPanel.Dock="Left" Symbol="ArrowTrendingLines24" FontSize="16"
Foreground="{DynamicResource AccentTextFillColorPrimaryBrush}"
Margin="0,0,8,0" VerticalAlignment="Center"/>
<TextBlock Text="{DynamicResource Pump.Commands.Title}"
Style="{StaticResource PumpCardHeader}" Margin="0"/>
</DockPanel>
<!-- ── Slider columns ────────────────────────────────────────── -->
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="*"/>
<!-- PreIn: collapses when not applicable -->
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<!-- ── FBKW ────────────────────────────────────────────── -->
<StackPanel Grid.Column="0" HorizontalAlignment="Center">
<!-- Settings toggle -->
<ToggleButton x:Name="FbkwToggle"
Width="28" Height="22"
HorizontalAlignment="Center"
Background="Transparent" BorderBrush="Transparent"
Content="..." FontWeight="Bold" FontSize="13"
ToolTip="{DynamicResource PumpCtrl.MinStepMax}"/>
<!-- Max label -->
<TextBlock Text="{Binding FbkwMax, StringFormat=F1}"
Style="{StaticResource PumpCommandLabel}"
Margin="0,4,0,2"/>
<!-- Vertical slider -->
<Slider Orientation="Vertical"
Minimum="{Binding FbkwMin}" Maximum="{Binding FbkwMax}"
Value="{Binding FbkwValue}"
TickFrequency="{Binding FbkwStep}"
TickPlacement="TopLeft"
IsSnapToTickEnabled="True"
AutoToolTipPrecision="2"
Height="240" Width="32"
HorizontalAlignment="Center"
Focusable="False"/>
<!-- Min label -->
<TextBlock Text="{Binding FbkwMin, StringFormat=F1}"
Style="{StaticResource PumpCommandLabel}"
Margin="0,2,0,6"/>
<!-- Editable value box -->
<TextBox Text="{Binding FbkwValue, UpdateSourceTrigger=PropertyChanged, StringFormat=F2}"
Style="{StaticResource PumpCommandValue}"/>
<!-- Parameter name -->
<TextBlock Text="{DynamicResource Pump.Commands.Fbkw}"
Style="{StaticResource PumpCommandLabel}"
Margin="0,6,0,0"/>
<!-- Settings popup -->
<Popup IsOpen="{Binding IsChecked, ElementName=FbkwToggle}"
StaysOpen="False" Placement="Bottom" AllowsTransparency="True">
<Border Background="#BB000000" Padding="8,6" CornerRadius="3">
<UniformGrid Columns="3" Width="180">
<StackPanel HorizontalAlignment="Center">
<TextBox Text="{Binding FbkwMin, UpdateSourceTrigger=PropertyChanged}" Style="{StaticResource SettingsTextBox}"/>
<TextBlock Text="{DynamicResource PumpCtrl.Min}" Style="{StaticResource SettingsLabel}"/>
</StackPanel>
<StackPanel HorizontalAlignment="Center">
<TextBox Text="{Binding FbkwStep, UpdateSourceTrigger=PropertyChanged}" Style="{StaticResource SettingsTextBox}"/>
<TextBlock Text="{DynamicResource PumpCtrl.Step}" Style="{StaticResource SettingsLabel}"/>
</StackPanel>
<StackPanel HorizontalAlignment="Center">
<TextBox Text="{Binding FbkwMax, UpdateSourceTrigger=PropertyChanged}" Style="{StaticResource SettingsTextBox}"/>
<TextBlock Text="{DynamicResource PumpCtrl.Max}" Style="{StaticResource SettingsLabel}"/>
</StackPanel>
</UniformGrid>
</Border>
</Popup>
</StackPanel>
<!-- ── ME ──────────────────────────────────────────────── -->
<StackPanel Grid.Column="1" HorizontalAlignment="Center">
<ToggleButton x:Name="MeToggle"
Width="28" Height="22"
HorizontalAlignment="Center"
Background="Transparent" BorderBrush="Transparent"
Content="..." FontWeight="Bold" FontSize="13"
ToolTip="{DynamicResource PumpCtrl.MinStepMax}"/>
<TextBlock Text="{Binding MeMax, StringFormat=F1}"
Style="{StaticResource PumpCommandLabel}"
Margin="0,4,0,2"/>
<Slider Orientation="Vertical"
Minimum="{Binding MeMin}" Maximum="{Binding MeMax}"
Value="{Binding MeValue}"
TickFrequency="{Binding MeStep}"
TickPlacement="TopLeft"
IsSnapToTickEnabled="False"
AutoToolTipPrecision="2"
Height="240" Width="32"
HorizontalAlignment="Center"
Focusable="False"/>
<TextBlock Text="{Binding MeMin, StringFormat=F1}"
Style="{StaticResource PumpCommandLabel}"
Margin="0,2,0,6"/>
<TextBox Text="{Binding MeValue, UpdateSourceTrigger=PropertyChanged, StringFormat=F2}"
Style="{StaticResource PumpCommandValue}"/>
<TextBlock Text="{DynamicResource Pump.Commands.Me}"
Style="{StaticResource PumpCommandLabel}"
Margin="0,6,0,0"/>
<Popup IsOpen="{Binding IsChecked, ElementName=MeToggle}"
StaysOpen="False" Placement="Bottom" AllowsTransparency="True">
<Border Background="#BB000000" Padding="8,6" CornerRadius="3">
<UniformGrid Columns="3" Width="180">
<StackPanel HorizontalAlignment="Center">
<TextBox Text="{Binding MeMin, UpdateSourceTrigger=PropertyChanged}" Style="{StaticResource SettingsTextBox}"/>
<TextBlock Text="{DynamicResource PumpCtrl.Min}" Style="{StaticResource SettingsLabel}"/>
</StackPanel>
<StackPanel HorizontalAlignment="Center">
<TextBox Text="{Binding MeStep, UpdateSourceTrigger=PropertyChanged}" Style="{StaticResource SettingsTextBox}"/>
<TextBlock Text="{DynamicResource PumpCtrl.Step}" Style="{StaticResource SettingsLabel}"/>
</StackPanel>
<StackPanel HorizontalAlignment="Center">
<TextBox Text="{Binding MeMax, UpdateSourceTrigger=PropertyChanged}" Style="{StaticResource SettingsTextBox}"/>
<TextBlock Text="{DynamicResource PumpCtrl.Max}" Style="{StaticResource SettingsLabel}"/>
</StackPanel>
</UniformGrid>
</Border>
</Popup>
</StackPanel>
<!-- ── PreIn (conditional) ─────────────────────────────── -->
<StackPanel Grid.Column="2" HorizontalAlignment="Center"
Visibility="{Binding IsPreInVisible, Converter={StaticResource BoolToVis}}">
<ToggleButton x:Name="PreInToggle"
Width="28" Height="22"
HorizontalAlignment="Center"
Background="Transparent" BorderBrush="Transparent"
Content="..." FontWeight="Bold" FontSize="13"
ToolTip="{DynamicResource PumpCtrl.MinStepMax}"/>
<TextBlock Text="{Binding PreInMax, StringFormat=F1}"
Style="{StaticResource PumpCommandLabel}"
Margin="0,4,0,2"/>
<Slider Orientation="Vertical"
Minimum="{Binding PreInMin}" Maximum="{Binding PreInMax}"
Value="{Binding PreInValue}"
TickFrequency="{Binding PreInStep}"
TickPlacement="TopLeft"
IsSnapToTickEnabled="True"
AutoToolTipPrecision="2"
Height="240" Width="32"
HorizontalAlignment="Center"
Focusable="False"/>
<TextBlock Text="{Binding PreInMin, StringFormat=F1}"
Style="{StaticResource PumpCommandLabel}"
Margin="0,2,0,6"/>
<TextBox Text="{Binding PreInValue, UpdateSourceTrigger=PropertyChanged, StringFormat=F2}"
Style="{StaticResource PumpCommandValue}"/>
<TextBlock Text="{DynamicResource Pump.Commands.PreIn}"
Style="{StaticResource PumpCommandLabel}"
Margin="0,6,0,0"/>
<Popup IsOpen="{Binding IsChecked, ElementName=PreInToggle}"
StaysOpen="False" Placement="Bottom" AllowsTransparency="True">
<Border Background="#BB000000" Padding="8,6" CornerRadius="3">
<UniformGrid Columns="3" Width="180">
<StackPanel HorizontalAlignment="Center">
<TextBox Text="{Binding PreInMin, UpdateSourceTrigger=PropertyChanged}" Style="{StaticResource SettingsTextBox}"/>
<TextBlock Text="{DynamicResource PumpCtrl.Min}" Style="{StaticResource SettingsLabel}"/>
</StackPanel>
<StackPanel HorizontalAlignment="Center">
<TextBox Text="{Binding PreInStep, UpdateSourceTrigger=PropertyChanged}" Style="{StaticResource SettingsTextBox}"/>
<TextBlock Text="{DynamicResource PumpCtrl.Step}" Style="{StaticResource SettingsLabel}"/>
</StackPanel>
<StackPanel HorizontalAlignment="Center">
<TextBox Text="{Binding PreInMax, UpdateSourceTrigger=PropertyChanged}" Style="{StaticResource SettingsTextBox}"/>
<TextBlock Text="{DynamicResource PumpCtrl.Max}" Style="{StaticResource SettingsLabel}"/>
</StackPanel>
</UniformGrid>
</Border>
</Popup>
</StackPanel>
</Grid>
</DockPanel>
</Border>
</UserControl>

View File

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

View File

@@ -1,194 +0,0 @@
<UserControl x:Class="HC_APTBS.Views.UserControls.PumpControlView"
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"
mc:Ignorable="d"
d:DesignHeight="260" d:DesignWidth="440"
IsEnabled="{Binding IsEnabled}">
<UserControl.Resources>
<BooleanToVisibilityConverter x:Key="BoolToVis"/>
<!-- Shared style for the settings popup min/max/step text boxes -->
<Style x:Key="SettingsTextBox" TargetType="TextBox">
<Setter Property="Width" Value="40"/>
<Setter Property="HorizontalContentAlignment" Value="Center"/>
<Setter Property="VerticalAlignment" Value="Top"/>
<Setter Property="FontSize" Value="14"/>
</Style>
<!-- Label inside settings popup -->
<Style x:Key="SettingsLabel" TargetType="TextBlock">
<Setter Property="VerticalAlignment" Value="Bottom"/>
<Setter Property="FontSize" Value="12"/>
<Setter Property="Foreground" Value="White"/>
<Setter Property="Margin" Value="0,2,0,0"/>
</Style>
</UserControl.Resources>
<StackPanel>
<!-- ═══ FBKW — Advance Control ═══════════════════════════════════════ -->
<Grid Margin="6,6,6,2">
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<TextBlock Text="{DynamicResource PumpCtrl.Fbkw}" HorizontalAlignment="Center"
FontSize="13" Foreground="Black" Margin="0,0,0,2"/>
<DockPanel Grid.Row="1" Margin="4,0,4,2">
<!-- Settings button -->
<ToggleButton x:Name="FbkwSettingsToggle" DockPanel.Dock="Left"
Width="28" Height="28" Margin="0,0,4,0"
Background="Transparent" BorderBrush="Transparent"
Content="..." FontWeight="Bold" FontSize="14"
ToolTip="{DynamicResource PumpCtrl.MinStepMax}"/>
<!-- Numeric text box -->
<TextBox DockPanel.Dock="Right" Width="50" Height="28" Margin="4,0,0,0"
TextAlignment="Center" VerticalContentAlignment="Center"
FontWeight="Bold" FontSize="13"
Text="{Binding FbkwValue, UpdateSourceTrigger=PropertyChanged, StringFormat=F2}"/>
<!-- Slider -->
<Slider Minimum="{Binding FbkwMin}" Maximum="{Binding FbkwMax}"
Value="{Binding FbkwValue}" TickFrequency="{Binding FbkwStep}"
TickPlacement="BottomRight" AutoToolTipPrecision="2"
IsSnapToTickEnabled="True" VerticalAlignment="Center"
Focusable="False" Foreground="Black"/>
</DockPanel>
<!-- Settings popup -->
<Popup IsOpen="{Binding IsChecked, ElementName=FbkwSettingsToggle}"
StaysOpen="False" Placement="Bottom" AllowsTransparency="True"
HorizontalOffset="30">
<Border Background="#BB000000" Padding="8,6" CornerRadius="3">
<UniformGrid Columns="3" Width="180">
<StackPanel HorizontalAlignment="Center">
<TextBox Text="{Binding FbkwMin, UpdateSourceTrigger=PropertyChanged}"
Style="{StaticResource SettingsTextBox}"/>
<TextBlock Text="{DynamicResource PumpCtrl.Min}" Style="{StaticResource SettingsLabel}" HorizontalAlignment="Center"/>
</StackPanel>
<StackPanel HorizontalAlignment="Center">
<TextBox Text="{Binding FbkwStep, UpdateSourceTrigger=PropertyChanged}"
Style="{StaticResource SettingsTextBox}"/>
<TextBlock Text="{DynamicResource PumpCtrl.Step}" Style="{StaticResource SettingsLabel}" HorizontalAlignment="Center"/>
</StackPanel>
<StackPanel HorizontalAlignment="Center">
<TextBox Text="{Binding FbkwMax, UpdateSourceTrigger=PropertyChanged}"
Style="{StaticResource SettingsTextBox}"/>
<TextBlock Text="{DynamicResource PumpCtrl.Max}" Style="{StaticResource SettingsLabel}" HorizontalAlignment="Center"/>
</StackPanel>
</UniformGrid>
</Border>
</Popup>
</Grid>
<!-- ═══ ME — Quantity Control ════════════════════════════════════════ -->
<Grid Margin="6,4,6,2">
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<TextBlock Text="{DynamicResource PumpCtrl.Me}" HorizontalAlignment="Center"
FontSize="13" Foreground="Black" Margin="0,0,0,2"/>
<DockPanel Grid.Row="1" Margin="4,0,4,2">
<ToggleButton x:Name="MeSettingsToggle" DockPanel.Dock="Left"
Width="28" Height="28" Margin="0,0,4,0"
Background="Transparent" BorderBrush="Transparent"
Content="..." FontWeight="Bold" FontSize="14"
ToolTip="{DynamicResource PumpCtrl.MinStepMax}"/>
<TextBox DockPanel.Dock="Right" Width="50" Height="28" Margin="4,0,0,0"
TextAlignment="Center" VerticalContentAlignment="Center"
FontWeight="Bold" FontSize="13"
Text="{Binding MeValue, UpdateSourceTrigger=PropertyChanged, StringFormat=F2}"/>
<Slider Minimum="{Binding MeMin}" Maximum="{Binding MeMax}"
Value="{Binding MeValue}" TickFrequency="{Binding MeStep}"
TickPlacement="BottomRight" AutoToolTipPrecision="2"
IsSnapToTickEnabled="False" VerticalAlignment="Center"
Focusable="False" Foreground="Black"/>
</DockPanel>
<Popup IsOpen="{Binding IsChecked, ElementName=MeSettingsToggle}"
StaysOpen="False" Placement="Bottom" AllowsTransparency="True"
HorizontalOffset="30">
<Border Background="#BB000000" Padding="8,6" CornerRadius="3">
<UniformGrid Columns="3" Width="180">
<StackPanel HorizontalAlignment="Center">
<TextBox Text="{Binding MeMin, UpdateSourceTrigger=PropertyChanged}"
Style="{StaticResource SettingsTextBox}"/>
<TextBlock Text="{DynamicResource PumpCtrl.Min}" Style="{StaticResource SettingsLabel}" HorizontalAlignment="Center"/>
</StackPanel>
<StackPanel HorizontalAlignment="Center">
<TextBox Text="{Binding MeStep, UpdateSourceTrigger=PropertyChanged}"
Style="{StaticResource SettingsTextBox}"/>
<TextBlock Text="{DynamicResource PumpCtrl.Step}" Style="{StaticResource SettingsLabel}" HorizontalAlignment="Center"/>
</StackPanel>
<StackPanel HorizontalAlignment="Center">
<TextBox Text="{Binding MeMax, UpdateSourceTrigger=PropertyChanged}"
Style="{StaticResource SettingsTextBox}"/>
<TextBlock Text="{DynamicResource PumpCtrl.Max}" Style="{StaticResource SettingsLabel}" HorizontalAlignment="Center"/>
</StackPanel>
</UniformGrid>
</Border>
</Popup>
</Grid>
<!-- ═══ PreIn — Pre-injection Quantity ═══════════════════════════════ -->
<Grid Margin="6,4,6,2"
Visibility="{Binding IsPreInVisible, Converter={StaticResource BoolToVis}}">
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<TextBlock Text="{DynamicResource PumpCtrl.PreInj}" HorizontalAlignment="Center"
FontSize="13" Foreground="Black" Margin="0,0,0,2"/>
<DockPanel Grid.Row="1" Margin="4,0,4,2">
<ToggleButton x:Name="PreInSettingsToggle" DockPanel.Dock="Left"
Width="28" Height="28" Margin="0,0,4,0"
Background="Transparent" BorderBrush="Transparent"
Content="..." FontWeight="Bold" FontSize="14"
ToolTip="{DynamicResource PumpCtrl.MinStepMax}"/>
<TextBox DockPanel.Dock="Right" Width="50" Height="28" Margin="4,0,0,0"
TextAlignment="Center" VerticalContentAlignment="Center"
FontWeight="Bold" FontSize="13"
Text="{Binding PreInValue, UpdateSourceTrigger=PropertyChanged, StringFormat=F2}"/>
<Slider Minimum="{Binding PreInMin}" Maximum="{Binding PreInMax}"
Value="{Binding PreInValue}" TickFrequency="{Binding PreInStep}"
TickPlacement="BottomRight" AutoToolTipPrecision="2"
IsSnapToTickEnabled="True" VerticalAlignment="Center"
Focusable="False" Foreground="Black"/>
</DockPanel>
<Popup IsOpen="{Binding IsChecked, ElementName=PreInSettingsToggle}"
StaysOpen="False" Placement="Bottom" AllowsTransparency="True"
HorizontalOffset="30">
<Border Background="#BB000000" Padding="8,6" CornerRadius="3">
<UniformGrid Columns="3" Width="180">
<StackPanel HorizontalAlignment="Center">
<TextBox Text="{Binding PreInMin, UpdateSourceTrigger=PropertyChanged}"
Style="{StaticResource SettingsTextBox}"/>
<TextBlock Text="{DynamicResource PumpCtrl.Min}" Style="{StaticResource SettingsLabel}" HorizontalAlignment="Center"/>
</StackPanel>
<StackPanel HorizontalAlignment="Center">
<TextBox Text="{Binding PreInStep, UpdateSourceTrigger=PropertyChanged}"
Style="{StaticResource SettingsTextBox}"/>
<TextBlock Text="{DynamicResource PumpCtrl.Step}" Style="{StaticResource SettingsLabel}" HorizontalAlignment="Center"/>
</StackPanel>
<StackPanel HorizontalAlignment="Center">
<TextBox Text="{Binding PreInMax, UpdateSourceTrigger=PropertyChanged}"
Style="{StaticResource SettingsTextBox}"/>
<TextBlock Text="{DynamicResource PumpCtrl.Max}" Style="{StaticResource SettingsLabel}" HorizontalAlignment="Center"/>
</StackPanel>
</UniformGrid>
</Border>
</Popup>
</Grid>
</StackPanel>
</UserControl>

View File

@@ -1,15 +0,0 @@
using System.Windows.Controls;
namespace HC_APTBS.Views.UserControls
{
/// <summary>
/// Manual pump control sliders (FBKW, ME, PreIn) with configurable min/max/step popups.
/// </summary>
public partial class PumpControlView : UserControl
{
public PumpControlView()
{
InitializeComponent();
}
}
}

View File

@@ -1,97 +1,82 @@
<UserControl x:Class="HC_APTBS.Views.UserControls.PumpIdentificationPanelView" <UserControl x:Class="HC_APTBS.Views.UserControls.PumpIdentificationCard"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" 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"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 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" mc:Ignorable="d"
d:DesignHeight="360" d:DesignWidth="700"> d:DesignHeight="360" d:DesignWidth="320">
<!--
Pump-page variant of PumpIdentificationView (spec §3.a Identification). <!-- DataContext = PumpIdentificationViewModel (via {Binding Identification}) -->
Reuses PumpIdentificationViewModel. Presents the ECU info panel
in a larger, two-column format suitable for the Pump page.
-->
<UserControl.Resources> <UserControl.Resources>
<BooleanToVisibilityConverter x:Key="BoolToVis"/> <BooleanToVisibilityConverter x:Key="BoolToVis"/>
<Style x:Key="IdLabel" TargetType="TextBlock"> <Style x:Key="IdLabel" TargetType="TextBlock">
<Setter Property="FontSize" Value="12"/> <Setter Property="FontFamily" Value="{DynamicResource ContentControlThemeFontFamily}"/>
<Setter Property="Foreground" Value="#555"/> <Setter Property="FontSize" Value="11"/>
<Setter Property="Width" Value="110"/> <Setter Property="Foreground" Value="{DynamicResource TextFillColorSecondaryBrush}"/>
<Setter Property="Width" Value="88"/>
<Setter Property="VerticalAlignment" Value="Center"/> <Setter Property="VerticalAlignment" Value="Center"/>
</Style> </Style>
<Style x:Key="IdValue" TargetType="TextBlock"> <Style x:Key="IdValue" TargetType="TextBlock">
<Setter Property="FontSize" Value="13"/> <Setter Property="FontFamily" Value="Consolas"/>
<Setter Property="FontFamily" Value="Consolas"/> <Setter Property="FontSize" Value="12"/>
<Setter Property="Foreground" Value="{DynamicResource TextFillColorPrimaryBrush}"/>
<Setter Property="VerticalAlignment" Value="Center"/> <Setter Property="VerticalAlignment" Value="Center"/>
<Setter Property="TextTrimming" Value="CharacterEllipsis"/> <Setter Property="TextTrimming" Value="CharacterEllipsis"/>
</Style> </Style>
</UserControl.Resources> </UserControl.Resources>
<Border Background="#FAFAFA" BorderBrush="#DDD" BorderThickness="1" <Border Style="{StaticResource PumpCard}">
CornerRadius="4" Padding="12" Margin="6"> <DockPanel LastChildFill="True">
<StackPanel>
<!-- ── Section title ────────────────────────────────────────── --> <!-- ── Card header ───────────────────────────────────────────── -->
<TextBlock Text="{DynamicResource PumpSub.Identification}" <DockPanel DockPanel.Dock="Top" Margin="0,0,0,12">
FontSize="15" FontWeight="SemiBold" Foreground="#333" <ui:SymbolIcon DockPanel.Dock="Left" Symbol="Server24" FontSize="16"
Margin="0,0,0,8"/> Foreground="{DynamicResource AccentTextFillColorPrimaryBrush}"
Margin="0,0,8,0" VerticalAlignment="Center"/>
<TextBlock Text="{DynamicResource Pump.Identification.Title}"
Style="{StaticResource PumpCardHeader}"/>
</DockPanel>
<!-- ── Pump selector row ────────────────────────────────────── --> <!-- ── Action row: Read + Disconnect + progress ──────────────── -->
<Grid Margin="0,0,0,10"> <Grid DockPanel.Dock="Top" Margin="0,0,0,10">
<Grid.ColumnDefinitions> <Grid.ColumnDefinitions>
<ColumnDefinition Width="110"/>
<ColumnDefinition Width="*"/> <ColumnDefinition Width="*"/>
<ColumnDefinition Width="Auto"/> <ColumnDefinition Width="8"/>
</Grid.ColumnDefinitions>
<TextBlock Grid.Column="0"
Text="{DynamicResource PumpId.Label}"
VerticalAlignment="Center" FontSize="13"/>
<ComboBox Grid.Column="1"
ItemsSource="{Binding PumpIds}"
SelectedItem="{Binding SelectedPumpId}"
FontSize="18" Margin="0,0,12,0"
VerticalContentAlignment="Center"/>
<TextBlock Grid.Column="2"
Text="{Binding CurrentPump.Model}"
FontSize="13" Foreground="#888"
VerticalAlignment="Center"/>
</Grid>
<!-- ── Action row: Read + Disconnect + progress ─────────────── -->
<Grid Margin="0,0,0,10">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="*"/> <ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions> </Grid.ColumnDefinitions>
<Button Grid.Column="0" <ui:Button Grid.Column="0"
Content="{DynamicResource PumpId.ReadKLine}" Content="{DynamicResource PumpId.ReadKLine}"
Command="{Binding ReadKlineCommand}" Command="{Binding ReadKlineCommand}"
MinWidth="130" Height="34" FontWeight="Bold" Appearance="Primary"
Margin="0,0,8,0"/> FontWeight="Bold" Height="32"/>
<Button Grid.Column="1" <ui:Button Grid.Column="2"
Content="{DynamicResource PumpId.Disconnect}" Content="{DynamicResource PumpId.Disconnect}"
Command="{Binding DisconnectKLineCommand}" Command="{Binding DisconnectKLineCommand}"
MinWidth="110" Height="34" Appearance="Secondary"
Margin="0,0,12,0"/> Height="32"/>
<StackPanel Grid.Column="2"
Orientation="Vertical"
VerticalAlignment="Center"
Visibility="{Binding IsReading, Converter={StaticResource BoolToVis}}">
<TextBlock Text="{Binding ProgressMessage}"
FontSize="11" Foreground="#666"
TextTrimming="CharacterEllipsis"/>
<ProgressBar Value="{Binding ProgressPercent, Mode=OneWay}"
Minimum="0" Maximum="100" Height="8" Margin="0,2,0,0"/>
</StackPanel>
</Grid> </Grid>
<!-- ── Two-column ECU info grid ─────────────────────────────── --> <!-- ── Inline progress (visible while reading) ───────────────── -->
<StackPanel DockPanel.Dock="Top"
Margin="0,0,0,8"
Visibility="{Binding IsReading, Converter={StaticResource BoolToVis}}">
<TextBlock Text="{Binding ProgressMessage}"
FontSize="11"
Foreground="{DynamicResource TextFillColorSecondaryBrush}"
TextTrimming="CharacterEllipsis"
Margin="0,0,0,3"/>
<ProgressBar Value="{Binding ProgressPercent, Mode=OneWay}"
Minimum="0" Maximum="100" Height="4"/>
</StackPanel>
<!-- ── ECU info: two columns ─────────────────────────────────── -->
<Grid> <Grid>
<Grid.ColumnDefinitions> <Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/> <ColumnDefinition Width="*"/>
<ColumnDefinition Width="16"/> <ColumnDefinition Width="8"/>
<ColumnDefinition Width="*"/> <ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions> </Grid.ColumnDefinitions>
@@ -139,14 +124,18 @@
</StackPanel> </StackPanel>
<StackPanel Orientation="Horizontal" Margin="0,2"> <StackPanel Orientation="Horizontal" Margin="0,2">
<TextBlock Text="{DynamicResource PumpId.Errors}" Style="{StaticResource IdLabel}"/> <TextBlock Text="{DynamicResource PumpId.Errors}" Style="{StaticResource IdLabel}"/>
<TextBlock Text="{Binding KlineErrors}" Style="{StaticResource IdValue}" Foreground="DarkRed"/> <TextBlock Text="{Binding KlineErrors}" Style="{StaticResource IdValue}"
Foreground="{DynamicResource SystemFillColorCriticalBrush}"/>
</StackPanel> </StackPanel>
</StackPanel> </StackPanel>
</Grid> </Grid>
<!-- ── Connection error footer (auto-collapses) ─────────────── --> <!-- ── Connection error footer ──────────────────────────────── -->
<Border Background="#FDECEA" BorderBrush="#D62828" BorderThickness="1" <Border DockPanel.Dock="Bottom"
CornerRadius="3" Padding="8,4" Margin="0,10,0,0"> Background="{DynamicResource SystemFillColorCriticalBackgroundBrush}"
BorderBrush="{DynamicResource SystemFillColorCriticalBrush}"
BorderThickness="1" CornerRadius="4"
Padding="8,4" Margin="0,10,0,0">
<Border.Style> <Border.Style>
<Style TargetType="Border"> <Style TargetType="Border">
<Style.Triggers> <Style.Triggers>
@@ -157,9 +146,11 @@
</Style> </Style>
</Border.Style> </Border.Style>
<TextBlock Text="{Binding KlineConnectError}" <TextBlock Text="{Binding KlineConnectError}"
FontSize="12" FontFamily="Consolas" Foreground="#B22222" FontSize="11" FontFamily="Consolas"
Foreground="{DynamicResource SystemFillColorCriticalBrush}"
TextWrapping="Wrap"/> TextWrapping="Wrap"/>
</Border> </Border>
</StackPanel>
</DockPanel>
</Border> </Border>
</UserControl> </UserControl>

View File

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

View File

@@ -1,17 +0,0 @@
using System.Windows.Controls;
namespace HC_APTBS.Views.UserControls
{
/// <summary>
/// Larger page-scoped variant of <see cref="PumpIdentificationView"/> used by the
/// Pump page §3.a Identification sub-section. DataContext is
/// <c>PumpIdentificationViewModel</c>.
/// </summary>
public partial class PumpIdentificationPanelView : UserControl
{
public PumpIdentificationPanelView()
{
InitializeComponent();
}
}
}

View File

@@ -0,0 +1,175 @@
<UserControl x:Class="HC_APTBS.Views.UserControls.PumpLiveDataCard"
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"
xmlns:lvc="clr-namespace:LiveChartsCore.SkiaSharpView.WPF;assembly=LiveChartsCore.SkiaSharpView.WPF"
xmlns:uc="clr-namespace:HC_APTBS.Views.UserControls"
mc:Ignorable="d"
d:DesignHeight="700" d:DesignWidth="500">
<!--
DataContext = PumpPageViewModel.
Live values accessed via Root.PumpRpm / Root.PumpTemp / Root.PumpMe / Root.PumpFbkw / Root.PumpTein.
Status words accessed via StatusDisplay1 and StatusDisplay2 (both StatusDisplayViewModel).
RPM chart via RpmChart (SingleFlowChartViewModel).
-->
<Border Style="{StaticResource PumpCard}">
<DockPanel LastChildFill="True">
<!-- ── Card header ───────────────────────────────────────────── -->
<DockPanel DockPanel.Dock="Top" Margin="0,0,0,12">
<ui:SymbolIcon DockPanel.Dock="Left" Symbol="PulseSquare24" FontSize="16"
Foreground="{DynamicResource AccentTextFillColorPrimaryBrush}"
Margin="0,0,8,0" VerticalAlignment="Center"/>
<TextBlock Text="{DynamicResource Pump.LiveData.Title}"
Style="{StaticResource PumpCardHeader}" Margin="0"/>
</DockPanel>
<!-- ── Status displays (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}"/>
</StackPanel>
<!-- ── RPM chart (docked bottom of the upper area) ──────────── -->
<Border DockPanel.Dock="Bottom"
Background="{DynamicResource ControlFillColorSecondaryBrush}"
BorderBrush="{DynamicResource ControlStrokeColorDefaultBrush}"
BorderThickness="1" CornerRadius="6"
Padding="4,4" Margin="0,8,0,0">
<DockPanel LastChildFill="True">
<TextBlock DockPanel.Dock="Top"
Text="{DynamicResource Pump.LiveData.RpmChart}"
FontSize="11"
Foreground="{DynamicResource TextFillColorSecondaryBrush}"
Margin="4,0,0,2"/>
<lvc:CartesianChart Height="110"
Series="{Binding RpmChart.Series}"
XAxes="{Binding RpmChart.XAxes}"
YAxes="{Binding RpmChart.YAxes}"
TooltipPosition="Hidden"
AnimationsSpeed="00:00:00"/>
</DockPanel>
</Border>
<!-- ── KPI tiles (top portion, fills remaining space) ────────── -->
<UniformGrid Rows="1" Columns="5">
<!-- RPM -->
<Border Style="{StaticResource KpiTile}">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<StackPanel Orientation="Horizontal">
<ui:SymbolIcon Symbol="Gauge24" FontSize="14"
Foreground="{DynamicResource TextFillColorSecondaryBrush}"
Margin="0,0,4,0" VerticalAlignment="Center"/>
<TextBlock Text="{DynamicResource Pump.LiveData.RPM}"
Style="{StaticResource KpiHeaderText}" VerticalAlignment="Center"/>
</StackPanel>
<StackPanel Grid.Row="1" Orientation="Horizontal" VerticalAlignment="Bottom">
<TextBlock Text="{Binding Root.PumpRpm, StringFormat=F0}"
Style="{StaticResource KpiValueText}" FontSize="32"/>
<TextBlock Text="{DynamicResource Pump.UnitRpm}"
Style="{StaticResource KpiUnitText}"/>
</StackPanel>
</Grid>
</Border>
<!-- T-hyb -->
<Border Style="{StaticResource KpiTile}">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<StackPanel Orientation="Horizontal">
<ui:SymbolIcon Symbol="Temperature24" FontSize="14"
Foreground="{DynamicResource TextFillColorSecondaryBrush}"
Margin="0,0,4,0" VerticalAlignment="Center"/>
<TextBlock Text="{DynamicResource Pump.LiveData.THyb}"
Style="{StaticResource KpiHeaderText}" VerticalAlignment="Center"/>
</StackPanel>
<StackPanel Grid.Row="1" Orientation="Horizontal" VerticalAlignment="Bottom">
<TextBlock Text="{Binding Root.PumpTemp, StringFormat=F1}"
Style="{StaticResource KpiValueText}" FontSize="32"/>
<TextBlock Text="{DynamicResource Pump.UnitCelsius}"
Style="{StaticResource KpiUnitText}"/>
</StackPanel>
</Grid>
</Border>
<!-- T-ein -->
<Border Style="{StaticResource KpiTile}">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<StackPanel Orientation="Horizontal">
<ui:SymbolIcon Symbol="Timer24" FontSize="14"
Foreground="{DynamicResource TextFillColorSecondaryBrush}"
Margin="0,0,4,0" VerticalAlignment="Center"/>
<TextBlock Text="{DynamicResource Pump.LiveData.TEin}"
Style="{StaticResource KpiHeaderText}" VerticalAlignment="Center"/>
</StackPanel>
<StackPanel Grid.Row="1" Orientation="Horizontal" VerticalAlignment="Bottom">
<TextBlock Text="{Binding Root.PumpTein, StringFormat=F0}"
Style="{StaticResource KpiValueText}" FontSize="32"/>
<TextBlock Text="{DynamicResource Pump.UnitUs}"
Style="{StaticResource KpiUnitText}"/>
</StackPanel>
</Grid>
</Border>
<!-- ME -->
<Border Style="{StaticResource KpiTile}">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<StackPanel Orientation="Horizontal">
<ui:SymbolIcon Symbol="ArrowTrendingLines24" FontSize="14"
Foreground="{DynamicResource TextFillColorSecondaryBrush}"
Margin="0,0,4,0" VerticalAlignment="Center"/>
<TextBlock Text="{DynamicResource Pump.LiveData.Me}"
Style="{StaticResource KpiHeaderText}" VerticalAlignment="Center"/>
</StackPanel>
<StackPanel Grid.Row="1" Orientation="Horizontal" VerticalAlignment="Bottom">
<TextBlock Text="{Binding Root.PumpMe, StringFormat=F2}"
Style="{StaticResource KpiValueText}" FontSize="32"/>
</StackPanel>
</Grid>
</Border>
<!-- FBKW -->
<Border Style="{StaticResource KpiTile}">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<StackPanel Orientation="Horizontal">
<ui:SymbolIcon Symbol="ArrowTrendingLines24" FontSize="14"
Foreground="{DynamicResource TextFillColorSecondaryBrush}"
Margin="0,0,4,0" VerticalAlignment="Center"/>
<TextBlock Text="{DynamicResource Pump.LiveData.Fbkw}"
Style="{StaticResource KpiHeaderText}" VerticalAlignment="Center"/>
</StackPanel>
<StackPanel Grid.Row="1" Orientation="Horizontal" VerticalAlignment="Bottom">
<TextBlock Text="{Binding Root.PumpFbkw, StringFormat=F2}"
Style="{StaticResource KpiValueText}" FontSize="32"/>
</StackPanel>
</Grid>
</Border>
</UniformGrid>
</DockPanel>
</Border>
</UserControl>

View File

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

View File

@@ -1,130 +0,0 @@
<UserControl x:Class="HC_APTBS.Views.UserControls.PumpLiveDataView"
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"
mc:Ignorable="d"
d:DesignHeight="380" d:DesignWidth="700">
<!--
Pump page §3.c Live Data. DataContext = PumpPageViewModel so the view
can reach the MainViewModel-owned live readings via {Binding Root.X}
and the two StatusDisplay VMs.
-->
<Border Background="#FAFAFA" BorderBrush="#DDD" BorderThickness="1"
CornerRadius="4" Padding="12" Margin="6">
<StackPanel>
<!-- ── Section title ─────────────────────────────────────────── -->
<TextBlock Text="{DynamicResource PumpSub.LiveData}"
FontSize="15" FontWeight="SemiBold" Foreground="#333"
Margin="0,0,0,8"/>
<!-- ── LCD-style readings block ──────────────────────────────── -->
<Border Style="{StaticResource LcdBlue}" Padding="10,6">
<Grid Height="110">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="90"/>
<ColumnDefinition/>
<ColumnDefinition Width="60"/>
<ColumnDefinition Width="16"/>
<ColumnDefinition Width="90"/>
<ColumnDefinition/>
<ColumnDefinition Width="60"/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition/>
<RowDefinition/>
<RowDefinition/>
</Grid.RowDefinitions>
<!-- Left column: T-hyb / RPM / T-ein -->
<TextBlock Text="{DynamicResource Pump.THyb}"
VerticalAlignment="Center" Foreground="#EBEBFF" FontSize="13"/>
<TextBlock Text="{DynamicResource Pump.Rpm}"
Grid.Row="1" VerticalAlignment="Center" Foreground="#EBEBFF" FontSize="13"/>
<TextBlock Text="{DynamicResource Pump.TEin}"
Grid.Row="2" VerticalAlignment="Center" Foreground="#EBEBFF" FontSize="13"/>
<TextBlock Text="{Binding Root.PumpTemp, StringFormat=F2}"
Grid.Column="1"
HorizontalAlignment="Right" VerticalAlignment="Center"
Foreground="#EBEBFF" FontSize="22" FontWeight="Bold" FontFamily="Consolas"/>
<TextBlock Text="{Binding Root.PumpRpm, StringFormat=F0}"
Grid.Column="1" Grid.Row="1"
HorizontalAlignment="Right" VerticalAlignment="Center"
Foreground="#EBEBFF" FontSize="22" FontWeight="Bold" FontFamily="Consolas"/>
<TextBlock Text="{Binding Root.PumpTein, StringFormat=F0}"
Grid.Column="1" Grid.Row="2"
HorizontalAlignment="Right" VerticalAlignment="Center"
Foreground="#EBEBFF" FontSize="22" FontWeight="Bold" FontFamily="Consolas"/>
<TextBlock Text="°C" Grid.Column="2"
VerticalAlignment="Center" Foreground="#EBEBFF" FontSize="12" Margin="4,0"/>
<TextBlock Text="{DynamicResource Pump.UnitRpm}"
Grid.Column="2" Grid.Row="1"
VerticalAlignment="Center" Foreground="#EBEBFF" FontSize="12" Margin="4,0"/>
<TextBlock Text="µs" Grid.Column="2" Grid.Row="2"
VerticalAlignment="Center" Foreground="#EBEBFF" FontSize="12" Margin="4,0"/>
<!-- Right column: ME / FBKW -->
<TextBlock Text="{DynamicResource Bench.PumpMe}"
Grid.Column="4" VerticalAlignment="Center" Foreground="#EBEBFF" FontSize="13"/>
<TextBlock Text="{DynamicResource Bench.PumpFbkw}"
Grid.Column="4" Grid.Row="1"
VerticalAlignment="Center" Foreground="#EBEBFF" FontSize="13"/>
<TextBlock Text="{Binding Root.PumpMe, StringFormat=F2}"
Grid.Column="5"
HorizontalAlignment="Right" VerticalAlignment="Center"
Foreground="#EBEBFF" FontSize="22" FontWeight="Bold" FontFamily="Consolas"/>
<TextBlock Text="{Binding Root.PumpFbkw, StringFormat=F2}"
Grid.Column="5" Grid.Row="1"
HorizontalAlignment="Right" VerticalAlignment="Center"
Foreground="#EBEBFF" FontSize="22" FontWeight="Bold" FontFamily="Consolas"/>
</Grid>
</Border>
<!-- ── Status displays (Status word + Empf3) ─────────────────── -->
<StackPanel Margin="0,8,0,0">
<uc:StatusDisplayView DataContext="{Binding StatusDisplay1}"/>
<uc:StatusDisplayView DataContext="{Binding StatusDisplay2}" Margin="0,4,0,0"/>
</StackPanel>
<!-- ── Engineering expander (raw values) ─────────────────────── -->
<Expander Header="{DynamicResource PumpLive.Engineering}"
IsExpanded="False" Margin="0,10,0,0" FontSize="12">
<Border Background="#1E1E1E" Padding="10,6" CornerRadius="2">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<StackPanel Grid.Column="0">
<TextBlock Foreground="#9CDCFE" FontFamily="Consolas" FontSize="11">
<Run Text="PumpRpm = "/><Run Text="{Binding Root.PumpRpm, StringFormat=F2, Mode=OneWay}"/>
</TextBlock>
<TextBlock Foreground="#9CDCFE" FontFamily="Consolas" FontSize="11">
<Run Text="PumpTemp = "/><Run Text="{Binding Root.PumpTemp, StringFormat=F2, Mode=OneWay}"/>
</TextBlock>
<TextBlock Foreground="#9CDCFE" FontFamily="Consolas" FontSize="11">
<Run Text="PumpMe = "/><Run Text="{Binding Root.PumpMe, StringFormat=F3, Mode=OneWay}"/>
</TextBlock>
</StackPanel>
<StackPanel Grid.Column="1">
<TextBlock Foreground="#9CDCFE" FontFamily="Consolas" FontSize="11">
<Run Text="PumpFbkw = "/><Run Text="{Binding Root.PumpFbkw, StringFormat=F3, Mode=OneWay}"/>
</TextBlock>
<TextBlock Foreground="#9CDCFE" FontFamily="Consolas" FontSize="11">
<Run Text="PumpTein = "/><Run Text="{Binding Root.PumpTein, StringFormat=F0, Mode=OneWay}"/>
</TextBlock>
<TextBlock Foreground="#9CDCFE" FontFamily="Consolas" FontSize="11">
<Run Text="KLineState = "/><Run Text="{Binding Root.KLineState, Mode=OneWay}"/>
</TextBlock>
</StackPanel>
</Grid>
</Border>
</Expander>
</StackPanel>
</Border>
</UserControl>

View File

@@ -1,17 +0,0 @@
using System.Windows.Controls;
namespace HC_APTBS.Views.UserControls
{
/// <summary>
/// Pump page §3.c Live Data view: pump CAN readings, status-word displays,
/// and a collapsible engineering panel. DataContext is
/// <c>PumpPageViewModel</c> (reaches MainViewModel via <c>Root</c>).
/// </summary>
public partial class PumpLiveDataView : UserControl
{
public PumpLiveDataView()
{
InitializeComponent();
}
}
}

View File

@@ -0,0 +1,162 @@
<UserControl x:Class="HC_APTBS.Views.UserControls.PumpTopStripView"
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"
xmlns:models="clr-namespace:HC_APTBS.Models"
mc:Ignorable="d"
d:DesignHeight="52" d:DesignWidth="1100">
<!-- DataContext = PumpPageViewModel. Binds via Root.* and Identification.* -->
<Border Background="{DynamicResource CardBackgroundFillColorDefaultBrush}"
BorderBrush="{DynamicResource CardStrokeColorDefaultBrush}"
BorderThickness="1" CornerRadius="8"
Padding="14,10" Margin="0,0,0,8">
<DockPanel LastChildFill="False">
<!-- ── Right side: CAN chip + K-Line chip ────────────────────── -->
<StackPanel DockPanel.Dock="Right" Orientation="Horizontal" VerticalAlignment="Center">
<!-- CAN chip -->
<Border Style="{StaticResource ConnChip}">
<StackPanel Orientation="Horizontal" VerticalAlignment="Center">
<ui:SymbolIcon Symbol="PlugConnected24" FontSize="15"
Foreground="{DynamicResource TextFillColorSecondaryBrush}"
Margin="0,0,6,0"/>
<TextBlock Text="{DynamicResource Pump.TopStrip.Can}" FontSize="12"
Foreground="{DynamicResource TextFillColorPrimaryBrush}"
VerticalAlignment="Center" Margin="0,0,10,0"/>
<Ellipse>
<Ellipse.Style>
<Style TargetType="Ellipse" BasedOn="{StaticResource StatusDot}">
<Style.Triggers>
<DataTrigger Binding="{Binding Root.IsCanConnected}" Value="True">
<Setter Property="Fill" Value="{DynamicResource SystemFillColorSuccessBrush}"/>
</DataTrigger>
</Style.Triggers>
</Style>
</Ellipse.Style>
</Ellipse>
<TextBlock FontSize="11" FontWeight="SemiBold" VerticalAlignment="Center">
<TextBlock.Style>
<Style TargetType="TextBlock">
<Setter Property="Text" Value="{DynamicResource Dashboard.StateOffline}"/>
<Setter Property="Foreground" Value="{DynamicResource TextFillColorTertiaryBrush}"/>
<Style.Triggers>
<DataTrigger Binding="{Binding Root.IsCanConnected}" Value="True">
<Setter Property="Text" Value="{DynamicResource Dashboard.StateOnline}"/>
<Setter Property="Foreground" Value="{DynamicResource SystemFillColorSuccessBrush}"/>
</DataTrigger>
</Style.Triggers>
</Style>
</TextBlock.Style>
</TextBlock>
</StackPanel>
</Border>
<!-- K-Line chip — 3-state: Connected / Closed / Failed -->
<Border Margin="0,0,0,0">
<Border.Style>
<Style TargetType="Border" BasedOn="{StaticResource ConnChip}">
<Style.Triggers>
<DataTrigger Binding="{Binding Root.KLineState}"
Value="{x:Static models:KLineConnectionState.Failed}">
<Setter Property="Background" Value="{DynamicResource SystemFillColorCautionBackgroundBrush}"/>
<Setter Property="BorderBrush" Value="{DynamicResource SystemFillColorCautionBrush}"/>
</DataTrigger>
</Style.Triggers>
</Style>
</Border.Style>
<StackPanel Orientation="Horizontal" VerticalAlignment="Center">
<ui:SymbolIcon Symbol="UsbPlug24" FontSize="15"
Foreground="{DynamicResource TextFillColorSecondaryBrush}"
Margin="0,0,6,0"/>
<TextBlock Text="{DynamicResource Pump.TopStrip.KLine}" FontSize="12"
Foreground="{DynamicResource TextFillColorPrimaryBrush}"
VerticalAlignment="Center" Margin="0,0,10,0"/>
<Ellipse>
<Ellipse.Style>
<Style TargetType="Ellipse" BasedOn="{StaticResource StatusDot}">
<Style.Triggers>
<DataTrigger Binding="{Binding Root.KLineState}"
Value="{x:Static models:KLineConnectionState.Connected}">
<Setter Property="Fill" Value="{DynamicResource SystemFillColorSuccessBrush}"/>
</DataTrigger>
<DataTrigger Binding="{Binding Root.KLineState}"
Value="{x:Static models:KLineConnectionState.Failed}">
<Setter Property="Fill" Value="{DynamicResource SystemFillColorCautionBrush}"/>
</DataTrigger>
</Style.Triggers>
</Style>
</Ellipse.Style>
</Ellipse>
<TextBlock FontSize="11" FontWeight="SemiBold" VerticalAlignment="Center">
<TextBlock.Style>
<Style TargetType="TextBlock">
<Setter Property="Text" Value="{DynamicResource Dashboard.StateClosed}"/>
<Setter Property="Foreground" Value="{DynamicResource TextFillColorTertiaryBrush}"/>
<Style.Triggers>
<DataTrigger Binding="{Binding Root.KLineState}"
Value="{x:Static models:KLineConnectionState.Connected}">
<Setter Property="Text" Value="{DynamicResource Dashboard.StateOpen}"/>
<Setter Property="Foreground" Value="{DynamicResource SystemFillColorSuccessBrush}"/>
</DataTrigger>
<DataTrigger Binding="{Binding Root.KLineState}"
Value="{x:Static models:KLineConnectionState.Failed}">
<Setter Property="Text" Value="{DynamicResource Dashboard.StateFailed}"/>
<Setter Property="Foreground" Value="{DynamicResource SystemFillColorCautionBrush}"/>
</DataTrigger>
</Style.Triggers>
</Style>
</TextBlock.Style>
</TextBlock>
</StackPanel>
</Border>
</StackPanel>
<!-- ── Left side: Pump selector + Brand/Model label ─────────── -->
<StackPanel DockPanel.Dock="Left" Orientation="Horizontal" VerticalAlignment="Center">
<!-- Pump ID label -->
<TextBlock Text="{DynamicResource PumpId.Label}"
FontSize="12"
Foreground="{DynamicResource TextFillColorSecondaryBrush}"
VerticalAlignment="Center" Margin="0,0,8,0"/>
<!-- Pump selector ComboBox -->
<ComboBox ItemsSource="{Binding Identification.PumpIds}"
SelectedItem="{Binding Identification.SelectedPumpId}"
MinWidth="200" FontSize="13" Height="32"
VerticalContentAlignment="Center"/>
<!-- Model / Brand badge -->
<Border Background="{DynamicResource ControlFillColorSecondaryBrush}"
BorderBrush="{DynamicResource ControlStrokeColorDefaultBrush}"
BorderThickness="1" CornerRadius="10" Padding="10,4" Margin="10,0,0,0">
<StackPanel Orientation="Horizontal" VerticalAlignment="Center">
<TextBlock Text="{DynamicResource Pump.TopStrip.Brand}"
FontSize="11"
Foreground="{DynamicResource TextFillColorSecondaryBrush}"
Margin="0,0,4,0"/>
<TextBlock FontSize="13" FontWeight="SemiBold"
Foreground="{DynamicResource TextFillColorPrimaryBrush}">
<TextBlock.Style>
<Style TargetType="TextBlock">
<Setter Property="Text" Value="{Binding Identification.CurrentPump.Model}"/>
<Style.Triggers>
<DataTrigger Binding="{Binding Identification.CurrentPump}" Value="{x:Null}">
<Setter Property="Text" Value="{DynamicResource Pump.TopStrip.NoPump}"/>
<Setter Property="FontStyle" Value="Italic"/>
<Setter Property="Foreground" Value="{DynamicResource TextFillColorTertiaryBrush}"/>
</DataTrigger>
</Style.Triggers>
</Style>
</TextBlock.Style>
</TextBlock>
</StackPanel>
</Border>
</StackPanel>
</DockPanel>
</Border>
</UserControl>

View File

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

View File

@@ -5,7 +5,7 @@
xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:conv="clr-namespace:HC_APTBS.Converters" xmlns:conv="clr-namespace:HC_APTBS.Converters"
mc:Ignorable="d" mc:Ignorable="d"
d:DesignHeight="70" d:DesignWidth="375"> d:DesignHeight="90" d:DesignWidth="340">
<UserControl.Resources> <UserControl.Resources>
<conv:HexColorToBrushConverter x:Key="HexToBrush"/> <conv:HexColorToBrushConverter x:Key="HexToBrush"/>
@@ -13,45 +13,70 @@
<Grid> <Grid>
<Grid.RowDefinitions> <Grid.RowDefinitions>
<RowDefinition Height="28"/> <RowDefinition Height="Auto"/>
<RowDefinition/> <RowDefinition Height="*"/>
</Grid.RowDefinitions> </Grid.RowDefinitions>
<!-- Title --> <!-- Header: title (left) + "Active: n/16" chip (right) -->
<TextBlock Text="{Binding Title}" <DockPanel Margin="0,0,0,6">
HorizontalAlignment="Center" VerticalAlignment="Center" <Border DockPanel.Dock="Right"
FontSize="14" FontWeight="SemiBold" Foreground="Black"/> Background="{DynamicResource ControlFillColorSecondaryBrush}"
BorderBrush="{DynamicResource ControlStrokeColorDefaultBrush}"
BorderThickness="1" CornerRadius="10" Padding="8,2">
<StackPanel Orientation="Horizontal" VerticalAlignment="Center">
<TextBlock Text="{DynamicResource Pump.Status.Active}"
FontSize="11" FontFamily="{DynamicResource ContentControlThemeFontFamily}"
Foreground="{DynamicResource TextFillColorSecondaryBrush}"
Margin="0,0,3,0"/>
<TextBlock Text=": " FontSize="11"
Foreground="{DynamicResource TextFillColorSecondaryBrush}"/>
<TextBlock Text="{Binding ActiveCount}"
FontSize="11" FontWeight="SemiBold"
Foreground="{DynamicResource TextFillColorPrimaryBrush}"/>
<TextBlock Text="/16" FontSize="11"
Foreground="{DynamicResource TextFillColorTertiaryBrush}"/>
</StackPanel>
</Border>
<TextBlock Text="{Binding Title}"
FontSize="12" FontWeight="SemiBold"
FontFamily="{DynamicResource ContentControlThemeFontFamily}"
Foreground="{DynamicResource TextFillColorPrimaryBrush}"
VerticalAlignment="Center"/>
</DockPanel>
<!-- 16-bit indicator row --> <!-- 2×8 grid of rounded bit tiles -->
<ItemsControl Grid.Row="1" <ItemsControl Grid.Row="1" ItemsSource="{Binding Bits}">
ItemsSource="{Binding Bits}"
Margin="10,0,10,6">
<ItemsControl.ItemsPanel> <ItemsControl.ItemsPanel>
<ItemsPanelTemplate> <ItemsPanelTemplate>
<UniformGrid Rows="1"/> <UniformGrid Rows="2" Columns="8"/>
</ItemsPanelTemplate> </ItemsPanelTemplate>
</ItemsControl.ItemsPanel> </ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate> <ItemsControl.ItemTemplate>
<DataTemplate> <DataTemplate>
<!-- Each bit: coloured dot + bit-number label --> <Border Margin="2" CornerRadius="4"
<Grid Margin="1,0"> BorderThickness="1"
<Grid.RowDefinitions> BorderBrush="{DynamicResource ControlStrokeColorDefaultBrush}"
<RowDefinition Height="3*"/> MinWidth="28" MinHeight="28"
<RowDefinition Height="2*"/> Background="{Binding Color, Converter={StaticResource HexToBrush}}"
</Grid.RowDefinitions> ToolTip="{Binding Description}"
<Rectangle Fill="{Binding Color, Converter={StaticResource HexToBrush}}" ToolTipService.InitialShowDelay="150"
Stroke="#5D5D5D" StrokeThickness="1" ToolTipService.ShowDuration="30000"
Width="16" Height="10" SnapsToDevicePixels="True">
<TextBlock Text="{Binding Index}"
HorizontalAlignment="Center" VerticalAlignment="Center" HorizontalAlignment="Center" VerticalAlignment="Center"
ToolTip="{Binding Description}" FontSize="11" FontWeight="SemiBold">
ToolTipService.InitialShowDelay="150" <TextBlock.Style>
ToolTipService.ShowDuration="30000" <Style TargetType="TextBlock">
SnapsToDevicePixels="True"/> <Setter Property="Foreground" Value="{DynamicResource TextFillColorPrimaryBrush}"/>
<TextBlock Grid.Row="1" FontSize="12" <Style.Triggers>
Text="{Binding Index}" <DataTrigger Binding="{Binding IsActive}" Value="True">
HorizontalAlignment="Center" VerticalAlignment="Center" <Setter Property="Foreground" Value="#FFFFFF"/>
Foreground="DimGray"/> </DataTrigger>
</Grid> </Style.Triggers>
</Style>
</TextBlock.Style>
</TextBlock>
</Border>
</DataTemplate> </DataTemplate>
</ItemsControl.ItemTemplate> </ItemsControl.ItemTemplate>
</ItemsControl> </ItemsControl>

View File

@@ -1,113 +0,0 @@
<UserControl x:Class="HC_APTBS.Views.UserControls.UnlockPanelView"
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"
mc:Ignorable="d"
d:DesignHeight="380" d:DesignWidth="700">
<!--
Pump page §3.e Unlock inline view. DataContext = UnlockProgressViewModel
(exposed from MainViewModel.CurrentUnlockVm). Hidden by the parent when
the selected pump does not require unlock or the VM is null.
-->
<UserControl.Resources>
<BooleanToVisibilityConverter x:Key="BoolToVis"/>
</UserControl.Resources>
<Border Background="#2B2929" BorderBrush="#111" BorderThickness="1"
CornerRadius="4" Padding="18" Margin="6">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/> <!-- title -->
<RowDefinition Height="Auto"/> <!-- type label -->
<RowDefinition Height="210"/> <!-- ring -->
<RowDefinition Height="Auto"/> <!-- phase text -->
<RowDefinition Height="Auto"/> <!-- progress bar -->
<RowDefinition Height="Auto"/> <!-- result -->
<RowDefinition Height="Auto"/> <!-- buttons -->
</Grid.RowDefinitions>
<TextBlock Grid.Row="0"
Text="{DynamicResource PumpSub.Unlock}"
FontSize="15" FontWeight="SemiBold" Foreground="#EEE"
HorizontalAlignment="Center" Margin="0,0,0,4"/>
<TextBlock Grid.Row="1"
Text="{Binding UnlockTypeLabel, Mode=OneWay}"
FontSize="13" Foreground="#AAA"
HorizontalAlignment="Center" Margin="0,0,0,6"/>
<!-- Progress ring -->
<Grid Grid.Row="2" HorizontalAlignment="Center" VerticalAlignment="Center">
<Ellipse Width="200" Height="200"
Stroke="#4D4D4D" StrokeThickness="10"
Fill="Transparent"/>
<StackPanel HorizontalAlignment="Center" VerticalAlignment="Center">
<TextBlock Text="{DynamicResource Dialog.Unlock.Progress}"
FontSize="12" Foreground="#888"
HorizontalAlignment="Center" Margin="0,0,0,4"/>
<TextBlock FontSize="60" FontFamily="Courier New"
Foreground="White" HorizontalAlignment="Center">
<TextBlock.Text>
<Binding Path="Progress" Mode="OneWay"
StringFormat="{}{0}%"/>
</TextBlock.Text>
</TextBlock>
<TextBlock Text="{Binding ElapsedTime, Mode=OneWay}"
FontSize="16" FontFamily="Courier New"
Foreground="#CCC" HorizontalAlignment="Center"
Margin="0,2,0,0"/>
</StackPanel>
</Grid>
<TextBlock Grid.Row="3"
Text="{Binding PhaseText, Mode=OneWay}"
FontSize="16" Foreground="White"
HorizontalAlignment="Center" Margin="0,6"/>
<ProgressBar Grid.Row="4"
Value="{Binding Progress, Mode=OneWay}"
Minimum="0" Maximum="100"
Height="12" Margin="12,0"
Foreground="#00EC00" Background="#3D3D3D"/>
<TextBlock Grid.Row="5"
Text="{Binding ResultText, Mode=OneWay}"
FontSize="22" FontWeight="Bold"
HorizontalAlignment="Center" Margin="0,10,0,0"
Visibility="{Binding IsComplete, Converter={StaticResource BoolToVis}}">
<TextBlock.Style>
<Style TargetType="TextBlock">
<Setter Property="Foreground" Value="#FF5858"/>
<Style.Triggers>
<DataTrigger Binding="{Binding IsSuccess}" Value="True">
<Setter Property="Foreground" Value="#00EC00"/>
</DataTrigger>
</Style.Triggers>
</Style>
</TextBlock.Style>
</TextBlock>
<!-- Only the cancel button is exposed inline; the panel cannot be "closed" here -->
<StackPanel Grid.Row="6" Orientation="Horizontal"
HorizontalAlignment="Center" Margin="0,14,0,0">
<Button Content="{DynamicResource Common.Cancel}"
Command="{Binding CancelCommand}"
Width="110" Height="30"
Foreground="White" FontWeight="Bold">
<Button.Style>
<Style TargetType="Button">
<Setter Property="Background" Value="#FF5858"/>
<Style.Triggers>
<Trigger Property="IsEnabled" Value="False">
<Setter Property="Background" Value="#4D4D4D"/>
<Setter Property="Foreground" Value="#888"/>
</Trigger>
</Style.Triggers>
</Style>
</Button.Style>
</Button>
</StackPanel>
</Grid>
</Border>
</UserControl>

View File

@@ -1,17 +0,0 @@
using System.Windows.Controls;
namespace HC_APTBS.Views.UserControls
{
/// <summary>
/// Inline unlock panel (Pump page §3.e). DataContext is the
/// shared <c>UnlockProgressViewModel</c> also driving the floating
/// progress dialog.
/// </summary>
public partial class UnlockPanelView : UserControl
{
public UnlockPanelView()
{
InitializeComponent();
}
}
}

View File

@@ -0,0 +1,115 @@
<UserControl x:Class="HC_APTBS.Views.UserControls.UnlockSnackbarView"
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 = UnlockProgressViewModel (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 + phase + progress bar ────────────────────── -->
<StackPanel VerticalAlignment="Center">
<DockPanel>
<TextBlock DockPanel.Dock="Left"
Text="{Binding UnlockTypeLabel}"
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,54 @@
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
{
public partial class UnlockSnackbarView : UserControl
{
private DispatcherTimer? _autoHideTimer;
public UnlockSnackbarView()
{
InitializeComponent();
DataContextChanged += OnDataContextChanged;
}
private void OnDataContextChanged(object sender, DependencyPropertyChangedEventArgs e)
{
if (e.OldValue is UnlockProgressViewModel oldVm)
oldVm.PropertyChanged -= OnVmPropertyChanged;
if (e.NewValue is UnlockProgressViewModel newVm)
newVm.PropertyChanged += OnVmPropertyChanged;
}
private void OnVmPropertyChanged(object? sender, PropertyChangedEventArgs e)
{
if (e.PropertyName != nameof(UnlockProgressViewModel.IsSuccess)) return;
if (DataContext is not UnlockProgressViewModel 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 UnlockProgressViewModel vm && vm.CloseCommand.CanExecute(null))
vm.CloseCommand.Execute(null);
}
}
}