mirror of
https://github.com/wwiinnddyy/LanMountainDesktop.git
synced 2026-06-22 00:54:26 +08:00
275 lines
8.6 KiB
C#
275 lines
8.6 KiB
C#
using System;
|
|
using System.Globalization;
|
|
using Avalonia;
|
|
using Avalonia.Controls;
|
|
using Avalonia.Media;
|
|
using Avalonia.Threading;
|
|
using LanMontainDesktop.Models;
|
|
using LanMontainDesktop.Services;
|
|
|
|
namespace LanMontainDesktop.Views.Components;
|
|
|
|
public partial class StudyEnvironmentWidget : UserControl, IDesktopComponentWidget, IDesktopPageVisibilityAwareComponentWidget
|
|
{
|
|
private readonly IStudyAnalyticsService _studyAnalyticsService = StudyAnalyticsServiceFactory.CreateDefault();
|
|
private readonly AppSettingsService _settingsService = new();
|
|
private readonly LocalizationService _localizationService = new();
|
|
private readonly DispatcherTimer _uiTimer = new()
|
|
{
|
|
Interval = TimeSpan.FromMilliseconds(250)
|
|
};
|
|
|
|
private double _currentCellSize = 48;
|
|
private bool _showDisplayDb = true;
|
|
private bool _showDbfs;
|
|
private string _languageCode = "zh-CN";
|
|
private bool _isAttached;
|
|
private bool _isOnActivePage = true;
|
|
|
|
public StudyEnvironmentWidget()
|
|
{
|
|
InitializeComponent();
|
|
|
|
_uiTimer.Tick += OnUiTimerTick;
|
|
AttachedToVisualTree += OnAttachedToVisualTree;
|
|
DetachedFromVisualTree += OnDetachedFromVisualTree;
|
|
SizeChanged += OnSizeChanged;
|
|
|
|
ReloadDisplaySettings();
|
|
ApplyCellSize(_currentCellSize);
|
|
RefreshVisual();
|
|
}
|
|
|
|
public void ApplyCellSize(double cellSize)
|
|
{
|
|
_currentCellSize = Math.Max(1, cellSize);
|
|
var scale = Math.Clamp(_currentCellSize / 48d, 0.82, 2.2);
|
|
|
|
RootBorder.CornerRadius = new CornerRadius(Math.Clamp(_currentCellSize * 0.34, 10, 28));
|
|
RootBorder.Padding = new Thickness(
|
|
Math.Clamp(14 * scale, 8, 20),
|
|
Math.Clamp(10 * scale, 6, 16));
|
|
|
|
StatusTitleTextBlock.FontSize = Math.Clamp(11 * scale, 9, 18);
|
|
StatusValueTextBlock.FontSize = Math.Clamp(20 * scale, 12, 34);
|
|
NoiseValueTextBlock.FontSize = Math.Clamp(22 * scale, 12, 38);
|
|
NoiseSubValueTextBlock.FontSize = Math.Clamp(12 * scale, 9, 18);
|
|
UpdateAdaptiveLayout();
|
|
}
|
|
|
|
public void SetDesktopPageContext(bool isOnActivePage, bool isEditMode)
|
|
{
|
|
_ = isEditMode;
|
|
_isOnActivePage = isOnActivePage;
|
|
UpdateTimerState();
|
|
}
|
|
|
|
public void RefreshFromSettings()
|
|
{
|
|
ReloadDisplaySettings();
|
|
RefreshVisual();
|
|
}
|
|
|
|
private void OnAttachedToVisualTree(object? sender, VisualTreeAttachmentEventArgs e)
|
|
{
|
|
_isAttached = true;
|
|
ReloadDisplaySettings();
|
|
_ = _studyAnalyticsService.StartOrResumeMonitoring();
|
|
UpdateTimerState();
|
|
RefreshVisual();
|
|
}
|
|
|
|
private void OnDetachedFromVisualTree(object? sender, VisualTreeAttachmentEventArgs e)
|
|
{
|
|
_isAttached = false;
|
|
_uiTimer.Stop();
|
|
}
|
|
|
|
private void OnSizeChanged(object? sender, SizeChangedEventArgs e)
|
|
{
|
|
ApplyCellSize(_currentCellSize);
|
|
UpdateAdaptiveLayout();
|
|
}
|
|
|
|
private void OnUiTimerTick(object? sender, EventArgs e)
|
|
{
|
|
RefreshVisual();
|
|
}
|
|
|
|
private void UpdateTimerState()
|
|
{
|
|
if (_isAttached && _isOnActivePage)
|
|
{
|
|
_uiTimer.Start();
|
|
return;
|
|
}
|
|
|
|
_uiTimer.Stop();
|
|
}
|
|
|
|
private void ReloadDisplaySettings()
|
|
{
|
|
var snapshot = _settingsService.Load();
|
|
_languageCode = _localizationService.NormalizeLanguageCode(snapshot.LanguageCode);
|
|
_showDisplayDb = snapshot.StudyEnvironmentShowDisplayDb;
|
|
_showDbfs = snapshot.StudyEnvironmentShowDbfs;
|
|
if (!_showDisplayDb && !_showDbfs)
|
|
{
|
|
_showDisplayDb = true;
|
|
}
|
|
}
|
|
|
|
private void RefreshVisual()
|
|
{
|
|
var snapshot = _studyAnalyticsService.GetSnapshot();
|
|
|
|
StatusTitleTextBlock.Text = L("study.environment.status_label", "Environment");
|
|
StatusValueTextBlock.Text = ResolveStatusText(snapshot);
|
|
StatusValueTextBlock.Foreground = ResolveStatusBrush(snapshot);
|
|
|
|
if (snapshot.LatestRealtimePoint is not { } realtimePoint)
|
|
{
|
|
NoiseValueTextBlock.Text = L("study.environment.value.unavailable", "--");
|
|
NoiseSubValueTextBlock.IsVisible = false;
|
|
UpdateAdaptiveLayout();
|
|
return;
|
|
}
|
|
|
|
var showDisplay = _showDisplayDb;
|
|
var showDbfs = _showDbfs;
|
|
if (!showDisplay && !showDbfs)
|
|
{
|
|
showDisplay = true;
|
|
}
|
|
|
|
if (showDisplay && showDbfs)
|
|
{
|
|
NoiseValueTextBlock.Text = FormatDisplayDb(realtimePoint.DisplayDb);
|
|
NoiseSubValueTextBlock.Text = FormatDbfs(realtimePoint.Dbfs);
|
|
NoiseSubValueTextBlock.IsVisible = true;
|
|
return;
|
|
}
|
|
|
|
NoiseValueTextBlock.Text = showDisplay
|
|
? FormatDisplayDb(realtimePoint.DisplayDb)
|
|
: FormatDbfs(realtimePoint.Dbfs);
|
|
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)
|
|
{
|
|
if (snapshot.State == StudyAnalyticsRuntimeState.Unsupported)
|
|
{
|
|
return L("study.environment.status.unsupported", "Unsupported");
|
|
}
|
|
|
|
if (snapshot.State == StudyAnalyticsRuntimeState.Error || snapshot.StreamStatus == NoiseStreamStatus.Error)
|
|
{
|
|
return L("study.environment.status.error", "Error");
|
|
}
|
|
|
|
if (snapshot.State == StudyAnalyticsRuntimeState.Paused)
|
|
{
|
|
return L("study.environment.status.paused", "Paused");
|
|
}
|
|
|
|
if (snapshot.StreamStatus == NoiseStreamStatus.Noisy)
|
|
{
|
|
return L("study.environment.status.noisy", "Noisy");
|
|
}
|
|
|
|
if (snapshot.State == StudyAnalyticsRuntimeState.Running && snapshot.StreamStatus == NoiseStreamStatus.Quiet)
|
|
{
|
|
return L("study.environment.status.quiet", "Quiet");
|
|
}
|
|
|
|
if (snapshot.State == StudyAnalyticsRuntimeState.Ready)
|
|
{
|
|
return L("study.environment.status.ready", "Ready");
|
|
}
|
|
|
|
return L("study.environment.status.initializing", "Initializing");
|
|
}
|
|
|
|
private IBrush ResolveStatusBrush(StudyAnalyticsSnapshot snapshot)
|
|
{
|
|
if (snapshot.State == StudyAnalyticsRuntimeState.Unsupported ||
|
|
snapshot.State == StudyAnalyticsRuntimeState.Error ||
|
|
snapshot.StreamStatus == NoiseStreamStatus.Error)
|
|
{
|
|
return CreateBrush("#FFFF7B7B");
|
|
}
|
|
|
|
if (snapshot.StreamStatus == NoiseStreamStatus.Noisy)
|
|
{
|
|
return CreateBrush("#FFFFB14A");
|
|
}
|
|
|
|
if (snapshot.State == StudyAnalyticsRuntimeState.Running &&
|
|
snapshot.StreamStatus == NoiseStreamStatus.Quiet)
|
|
{
|
|
return CreateBrush("#FF6FD7A2");
|
|
}
|
|
|
|
return TryResolveThemeBrush("AdaptiveTextPrimaryBrush", "#FFEFF3FF");
|
|
}
|
|
|
|
private string FormatDisplayDb(double value)
|
|
{
|
|
return string.Format(
|
|
CultureInfo.InvariantCulture,
|
|
L("study.environment.value.display_format", "{0:F1} dB"),
|
|
value);
|
|
}
|
|
|
|
private string FormatDbfs(double value)
|
|
{
|
|
return string.Format(
|
|
CultureInfo.InvariantCulture,
|
|
L("study.environment.value.dbfs_format", "{0:F1} dBFS"),
|
|
value);
|
|
}
|
|
|
|
private string L(string key, string fallback)
|
|
{
|
|
return _localizationService.GetString(_languageCode, key, fallback);
|
|
}
|
|
|
|
private static SolidColorBrush CreateBrush(string hexColor)
|
|
{
|
|
return new SolidColorBrush(Color.Parse(hexColor));
|
|
}
|
|
|
|
private IBrush TryResolveThemeBrush(string resourceKey, string fallbackHex)
|
|
{
|
|
if (Resources.TryGetResource(resourceKey, ActualThemeVariant, out var resource) && resource is IBrush brush)
|
|
{
|
|
return brush;
|
|
}
|
|
|
|
return CreateBrush(fallbackHex);
|
|
}
|
|
}
|