噪音监测组件优化
This commit is contained in:
lincube
2026-03-04 13:04:54 +08:00
parent b5f8132a3b
commit 2a41b8c016
3 changed files with 121 additions and 53 deletions

View File

@@ -12,18 +12,24 @@
BorderThickness="1" BorderThickness="1"
CornerRadius="18" CornerRadius="18"
Padding="14,10"> Padding="14,10">
<Grid ColumnDefinitions="*,Auto" <Grid x:Name="LayoutGrid"
ColumnDefinitions="*,Auto"
ColumnSpacing="10"> ColumnSpacing="10">
<StackPanel Spacing="2" <StackPanel x:Name="LeftStatusStack"
Spacing="2"
VerticalAlignment="Center"> VerticalAlignment="Center">
<TextBlock x:Name="StatusTitleTextBlock" <TextBlock x:Name="StatusTitleTextBlock"
Text="环境状态" Text="Environment"
FontSize="11" FontSize="11"
MaxLines="1"
TextTrimming="CharacterEllipsis"
Foreground="{DynamicResource AdaptiveTextSecondaryBrush}" /> Foreground="{DynamicResource AdaptiveTextSecondaryBrush}" />
<TextBlock x:Name="StatusValueTextBlock" <TextBlock x:Name="StatusValueTextBlock"
Text="安静" Text="Quiet"
FontSize="20" FontSize="20"
FontWeight="SemiBold" FontWeight="SemiBold"
MaxLines="1"
TextTrimming="CharacterEllipsis"
Foreground="{DynamicResource AdaptiveTextPrimaryBrush}" /> Foreground="{DynamicResource AdaptiveTextPrimaryBrush}" />
</StackPanel> </StackPanel>
@@ -35,12 +41,16 @@
Text="--" Text="--"
FontSize="22" FontSize="22"
FontWeight="SemiBold" FontWeight="SemiBold"
MaxLines="1"
TextTrimming="CharacterEllipsis"
TextAlignment="Right" TextAlignment="Right"
HorizontalAlignment="Right" HorizontalAlignment="Right"
Foreground="{DynamicResource AdaptiveTextPrimaryBrush}" /> Foreground="{DynamicResource AdaptiveTextPrimaryBrush}" />
<TextBlock x:Name="NoiseSubValueTextBlock" <TextBlock x:Name="NoiseSubValueTextBlock"
Text="" Text=""
FontSize="12" FontSize="12"
MaxLines="1"
TextTrimming="CharacterEllipsis"
TextAlignment="Right" TextAlignment="Right"
HorizontalAlignment="Right" HorizontalAlignment="Right"
Foreground="{DynamicResource AdaptiveTextSecondaryBrush}" Foreground="{DynamicResource AdaptiveTextSecondaryBrush}"

View File

@@ -54,6 +54,7 @@ public partial class StudyEnvironmentWidget : UserControl, IDesktopComponentWidg
StatusValueTextBlock.FontSize = Math.Clamp(20 * scale, 12, 34); StatusValueTextBlock.FontSize = Math.Clamp(20 * scale, 12, 34);
NoiseValueTextBlock.FontSize = Math.Clamp(22 * scale, 12, 38); NoiseValueTextBlock.FontSize = Math.Clamp(22 * scale, 12, 38);
NoiseSubValueTextBlock.FontSize = Math.Clamp(12 * scale, 9, 18); NoiseSubValueTextBlock.FontSize = Math.Clamp(12 * scale, 9, 18);
UpdateAdaptiveLayout();
} }
public void SetDesktopPageContext(bool isOnActivePage, bool isEditMode) public void SetDesktopPageContext(bool isOnActivePage, bool isEditMode)
@@ -87,6 +88,7 @@ public partial class StudyEnvironmentWidget : UserControl, IDesktopComponentWidg
private void OnSizeChanged(object? sender, SizeChangedEventArgs e) private void OnSizeChanged(object? sender, SizeChangedEventArgs e)
{ {
ApplyCellSize(_currentCellSize); ApplyCellSize(_currentCellSize);
UpdateAdaptiveLayout();
} }
private void OnUiTimerTick(object? sender, EventArgs e) private void OnUiTimerTick(object? sender, EventArgs e)
@@ -129,6 +131,7 @@ public partial class StudyEnvironmentWidget : UserControl, IDesktopComponentWidg
{ {
NoiseValueTextBlock.Text = L("study.environment.value.unavailable", "--"); NoiseValueTextBlock.Text = L("study.environment.value.unavailable", "--");
NoiseSubValueTextBlock.IsVisible = false; NoiseSubValueTextBlock.IsVisible = false;
UpdateAdaptiveLayout();
return; return;
} }
@@ -151,6 +154,28 @@ public partial class StudyEnvironmentWidget : UserControl, IDesktopComponentWidg
? FormatDisplayDb(realtimePoint.DisplayDb) ? FormatDisplayDb(realtimePoint.DisplayDb)
: FormatDbfs(realtimePoint.Dbfs); : FormatDbfs(realtimePoint.Dbfs);
NoiseSubValueTextBlock.IsVisible = false; NoiseSubValueTextBlock.IsVisible = false;
UpdateAdaptiveLayout();
}
private void UpdateAdaptiveLayout()
{
var scale = Math.Clamp(_currentCellSize / 48d, 0.82, 2.2);
var width = Bounds.Width;
var height = Bounds.Height;
var showingDualNoiseLines = _showDisplayDb && _showDbfs;
// Collapse the "Environment" label when space is tight so core values remain readable.
var collapseByCell = _currentCellSize <= 40;
var collapseByBounds =
(width > 0 && width < (showingDualNoiseLines ? 230 : 200)) ||
(height > 0 && height < (showingDualNoiseLines ? 102 : 82));
var hideStatusLabel = collapseByCell || collapseByBounds;
StatusTitleTextBlock.IsVisible = !hideStatusLabel;
LeftStatusStack.Spacing = hideStatusLabel ? 0 : Math.Clamp(2 * scale, 1, 4);
LayoutGrid.ColumnSpacing = hideStatusLabel
? Math.Clamp(6 * scale, 4, 10)
: Math.Clamp(10 * scale, 7, 14);
} }
private string ResolveStatusText(StudyAnalyticsSnapshot snapshot) private string ResolveStatusText(StudyAnalyticsSnapshot snapshot)
@@ -239,7 +264,7 @@ public partial class StudyEnvironmentWidget : UserControl, IDesktopComponentWidg
private IBrush TryResolveThemeBrush(string resourceKey, string fallbackHex) private IBrush TryResolveThemeBrush(string resourceKey, string fallbackHex)
{ {
if (TryGetResource(resourceKey, null, out var resource) && resource is IBrush brush) if (Resources.TryGetResource(resourceKey, ActualThemeVariant, out var resource) && resource is IBrush brush)
{ {
return brush; return brush;
} }

View File

@@ -14,15 +14,39 @@ namespace LanMontainDesktop.Views.Components;
public partial class StudyNoiseCurveWidget : UserControl, IDesktopComponentWidget, IDesktopPageVisibilityAwareComponentWidget public partial class StudyNoiseCurveWidget : UserControl, IDesktopComponentWidget, IDesktopPageVisibilityAwareComponentWidget
{ {
private const double NormalTextMinContrast = 4.5; private const double NormalTextMinContrast = 4.5;
private const double LargeTextMinContrast = 3.0; private const double LargeTextMinContrast = 4.5;
private static readonly Color[] LightToneCandidates = // Prefer cool-toned colors first (not plain white), then dark variants when background is bright.
private static readonly Color[] ValueToneCandidates =
{ {
Color.Parse("#FFEAF5FF"),
Color.Parse("#FFDCEEFF"),
Color.Parse("#FFCEE6FA"),
Color.Parse("#FF1A2D42"),
Color.Parse("#FF233A54"),
Color.Parse("#FFFFFFFF"), Color.Parse("#FFFFFFFF"),
Color.Parse("#FFF8FCFF"), Color.Parse("#FF101C2A")
Color.Parse("#FFF0F7FF"), };
Color.Parse("#FFE8F3FF"),
Color.Parse("#FFE0EEFF") private static readonly Color[] AxisToneCandidates =
{
Color.Parse("#FFC7D9EC"),
Color.Parse("#FFBAD0E8"),
Color.Parse("#FFD9E8F6"),
Color.Parse("#FF2C445F"),
Color.Parse("#FF35516F"),
Color.Parse("#FFEAF3FA"),
Color.Parse("#FF1A2C40")
};
private static readonly Color[] StatusTextToneCandidates =
{
Color.Parse("#FFF5FAFF"),
Color.Parse("#FFE6F1FB"),
Color.Parse("#FF18283A"),
Color.Parse("#FF122032"),
Color.Parse("#FFFFFFFF"),
Color.Parse("#FF111B29")
}; };
private static readonly Color DarkSubstrate = Color.Parse("#FF0B1220"); private static readonly Color DarkSubstrate = Color.Parse("#FF0B1220");
@@ -66,8 +90,10 @@ public partial class StudyNoiseCurveWidget : UserControl, IDesktopComponentWidge
ReloadLanguageCode(); ReloadLanguageCode();
ApplyCellSize(_currentCellSize); ApplyCellSize(_currentCellSize);
ApplyDefaultXAxisLabels(); ApplyDefaultXAxisLabels();
ApplyTypographyByBackground(ResolvePanelBackgroundColor());
ApplyStatusBadgeStyle(StatusVisualKind.Default, ResolvePanelBackgroundColor()); var panelColor = ResolvePanelBackgroundColor();
ApplyTypographyByBackground(panelColor);
ApplyStatusBadgeStyle(StatusVisualKind.Default, panelColor);
} }
public void ApplyCellSize(double cellSize) public void ApplyCellSize(double cellSize)
@@ -226,18 +252,18 @@ public partial class StudyNoiseCurveWidget : UserControl, IDesktopComponentWidge
private void ApplyTypographyByBackground(Color panelColor) private void ApplyTypographyByBackground(Color panelColor)
{ {
var samples = BuildPanelBackgroundSamples(panelColor); var samples = BuildPanelBackgroundSamples(panelColor);
var primaryBrush = CreateAdaptiveLightBrush(samples, LargeTextMinContrast, preferredAlpha: 0xF6); var valueBrush = CreateAdaptiveBrush(samples, ValueToneCandidates, LargeTextMinContrast);
var secondaryBrush = CreateAdaptiveLightBrush(samples, NormalTextMinContrast, preferredAlpha: 0xDF); var axisBrush = CreateAdaptiveBrush(samples, AxisToneCandidates, NormalTextMinContrast);
RealtimeValueTextBlock.Foreground = primaryBrush; RealtimeValueTextBlock.Foreground = valueBrush;
YTopTextBlock.Foreground = secondaryBrush; YTopTextBlock.Foreground = axisBrush;
YUpperTextBlock.Foreground = secondaryBrush; YUpperTextBlock.Foreground = axisBrush;
YMiddleTextBlock.Foreground = secondaryBrush; YMiddleTextBlock.Foreground = axisBrush;
YLowerTextBlock.Foreground = secondaryBrush; YLowerTextBlock.Foreground = axisBrush;
YBottomTextBlock.Foreground = secondaryBrush; YBottomTextBlock.Foreground = axisBrush;
XLeftTextBlock.Foreground = secondaryBrush; XLeftTextBlock.Foreground = axisBrush;
XCenterTextBlock.Foreground = secondaryBrush; XCenterTextBlock.Foreground = axisBrush;
XRightTextBlock.Foreground = secondaryBrush; XRightTextBlock.Foreground = axisBrush;
} }
private void ApplyStatusBadgeStyle(StatusVisualKind kind, Color panelColor) private void ApplyStatusBadgeStyle(StatusVisualKind kind, Color panelColor)
@@ -262,7 +288,7 @@ public partial class StudyNoiseCurveWidget : UserControl, IDesktopComponentWidge
StatusBadgeBorder.Background = new SolidColorBrush(badgeColor); StatusBadgeBorder.Background = new SolidColorBrush(badgeColor);
StatusBadgeBorder.BorderBrush = new SolidColorBrush(Color.FromArgb(0x96, 0xFF, 0xFF, 0xFF)); StatusBadgeBorder.BorderBrush = new SolidColorBrush(Color.FromArgb(0x96, 0xFF, 0xFF, 0xFF));
StatusTextBlock.Foreground = CreateAdaptiveLightBrush(new[] { badgeComposite }, NormalTextMinContrast, preferredAlpha: 0xFF); StatusTextBlock.Foreground = CreateAdaptiveBrush(new[] { badgeComposite }, StatusTextToneCandidates, NormalTextMinContrast);
} }
private static StatusVisualKind ResolveStatusVisualKind(StudyAnalyticsSnapshot snapshot) private static StatusVisualKind ResolveStatusVisualKind(StudyAnalyticsSnapshot snapshot)
@@ -294,7 +320,7 @@ public partial class StudyNoiseCurveWidget : UserControl, IDesktopComponentWidge
return solidBackground.Color; return solidBackground.Color;
} }
if (TryGetResource("AdaptiveGlassStrongBackgroundBrush", null, out var resource) && if (Resources.TryGetResource("AdaptiveGlassStrongBackgroundBrush", ActualThemeVariant, out var resource) &&
resource is ISolidColorBrush solidBrush) resource is ISolidColorBrush solidBrush)
{ {
return solidBrush.Color; return solidBrush.Color;
@@ -319,33 +345,40 @@ public partial class StudyNoiseCurveWidget : UserControl, IDesktopComponentWidge
}; };
} }
private static SolidColorBrush CreateAdaptiveLightBrush( private static SolidColorBrush CreateAdaptiveBrush(
IReadOnlyList<Color> backgroundSamples, IReadOnlyList<Color> backgroundSamples,
double minContrast, IReadOnlyList<Color> colorCandidates,
byte preferredAlpha) double minContrast)
{ {
var alphaCandidates = new byte[] if (colorCandidates.Count == 0)
{ {
preferredAlpha, return new SolidColorBrush(Color.Parse("#FFFFFFFF"));
(byte)Math.Clamp(preferredAlpha + 20, 0, 255), }
0xFF
};
for (var alphaIndex = 0; alphaIndex < alphaCandidates.Length; alphaIndex++) for (var i = 0; i < colorCandidates.Count; i++)
{ {
var alpha = alphaCandidates[alphaIndex]; var candidate = colorCandidates[i];
for (var toneIndex = 0; toneIndex < LightToneCandidates.Length; toneIndex++) if (MinContrastRatio(candidate, backgroundSamples) >= minContrast)
{ {
var tone = LightToneCandidates[toneIndex]; return new SolidColorBrush(candidate);
var candidate = Color.FromArgb(alpha, tone.R, tone.G, tone.B);
if (MinContrastRatio(candidate, backgroundSamples) >= minContrast)
{
return new SolidColorBrush(candidate);
}
} }
} }
return new SolidColorBrush(Color.Parse("#FFFFFFFF")); // If none reaches the target, pick the highest-contrast candidate.
var best = colorCandidates[0];
var bestContrast = MinContrastRatio(best, backgroundSamples);
for (var i = 1; i < colorCandidates.Count; i++)
{
var candidate = colorCandidates[i];
var contrast = MinContrastRatio(candidate, backgroundSamples);
if (contrast > bestContrast)
{
best = candidate;
bestContrast = contrast;
}
}
return new SolidColorBrush(best);
} }
private static double MinContrastRatio(Color foreground, IReadOnlyList<Color> backgrounds) private static double MinContrastRatio(Color foreground, IReadOnlyList<Color> backgrounds)
@@ -423,49 +456,49 @@ public partial class StudyNoiseCurveWidget : UserControl, IDesktopComponentWidge
var centerSeconds = Math.Round(duration / 2d, MidpointRounding.AwayFromZero); var centerSeconds = Math.Round(duration / 2d, MidpointRounding.AwayFromZero);
XLeftTextBlock.Text = $"-{leftSeconds:0}s"; XLeftTextBlock.Text = $"-{leftSeconds:0}s";
XCenterTextBlock.Text = $"-{centerSeconds:0}s"; XCenterTextBlock.Text = $"-{centerSeconds:0}s";
XRightTextBlock.Text = L("study.noise_curve.axis.now", "现在"); XRightTextBlock.Text = L("study.noise_curve.axis.now", "Now");
} }
private void ApplyDefaultXAxisLabels() private void ApplyDefaultXAxisLabels()
{ {
XLeftTextBlock.Text = "-12s"; XLeftTextBlock.Text = "-12s";
XCenterTextBlock.Text = "-6s"; XCenterTextBlock.Text = "-6s";
XRightTextBlock.Text = L("study.noise_curve.axis.now", "现在"); XRightTextBlock.Text = L("study.noise_curve.axis.now", "Now");
} }
private string ResolveStatusText(StudyAnalyticsSnapshot snapshot) private string ResolveStatusText(StudyAnalyticsSnapshot snapshot)
{ {
if (snapshot.State == StudyAnalyticsRuntimeState.Unsupported) if (snapshot.State == StudyAnalyticsRuntimeState.Unsupported)
{ {
return L("study.environment.status.unsupported", "不支持"); return L("study.environment.status.unsupported", "Unsupported");
} }
if (snapshot.State == StudyAnalyticsRuntimeState.Error || snapshot.StreamStatus == NoiseStreamStatus.Error) if (snapshot.State == StudyAnalyticsRuntimeState.Error || snapshot.StreamStatus == NoiseStreamStatus.Error)
{ {
return L("study.environment.status.error", "错误"); return L("study.environment.status.error", "Error");
} }
if (snapshot.State == StudyAnalyticsRuntimeState.Paused) if (snapshot.State == StudyAnalyticsRuntimeState.Paused)
{ {
return L("study.environment.status.paused", "已暂停"); return L("study.environment.status.paused", "Paused");
} }
if (snapshot.StreamStatus == NoiseStreamStatus.Noisy) if (snapshot.StreamStatus == NoiseStreamStatus.Noisy)
{ {
return L("study.environment.status.noisy", "嘈杂"); return L("study.environment.status.noisy", "Noisy");
} }
if (snapshot.State == StudyAnalyticsRuntimeState.Running && snapshot.StreamStatus == NoiseStreamStatus.Quiet) if (snapshot.State == StudyAnalyticsRuntimeState.Running && snapshot.StreamStatus == NoiseStreamStatus.Quiet)
{ {
return L("study.environment.status.quiet", "安静"); return L("study.environment.status.quiet", "Quiet");
} }
if (snapshot.State == StudyAnalyticsRuntimeState.Ready) if (snapshot.State == StudyAnalyticsRuntimeState.Ready)
{ {
return L("study.environment.status.ready", "待机"); return L("study.environment.status.ready", "Ready");
} }
return L("study.environment.status.initializing", "初始化中"); return L("study.environment.status.initializing", "Initializing");
} }
private void ReloadLanguageCode() private void ReloadLanguageCode()