You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

318 lines
10 KiB
C#

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Net.NetworkInformation;
using Avalonia;
using Avalonia.Controls;
using Avalonia.Controls.Primitives;
using Avalonia.Input;
using Avalonia.Interactivity;
using Avalonia.Layout;
using Avalonia.Media;
using Sln.Wcs.UI.ViewModels;
namespace Sln.Wcs.UI.Views;
public partial class MainWindow : Window
{
private readonly NavigationViewModel _navVm;
private readonly List<Popup> _openPopups = new();
private readonly Process _currentProcess;
private TimeSpan _lastCpuTime;
private DateTime _lastCpuCheck;
private readonly DateTime _startTime;
private long _prevBytesSent;
private long _prevBytesReceived;
private DateTime _prevNetCheck;
public MainWindow(NavigationViewModel navigationVm)
{
InitializeComponent();
_navVm = navigationVm;
DataContext = _navVm;
_navVm.PageChanged += OnPageChanged;
BuildMenu();
_navVm.LoadDefaultPage();
_currentProcess = Process.GetCurrentProcess();
_lastCpuTime = _currentProcess.TotalProcessorTime;
_lastCpuCheck = DateTime.UtcNow;
_startTime = DateTime.UtcNow;
// 时钟
var timer = new System.Timers.Timer(1000);
timer.Elapsed += (_, _) =>
Avalonia.Threading.Dispatcher.UIThread.Post(() =>
{
ClockText.Text = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss");
RefreshSystemStatus();
});
timer.Start();
ClockText.Text = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss");
// 网络速率初始采样
(_prevBytesSent, _prevBytesReceived) = GetNetworkBytes();
_prevNetCheck = DateTime.UtcNow;
// 退出按钮
LogoutBtn.Click += (_, _) => Close();
}
private void RefreshSystemStatus()
{
// CPU usage
var now = DateTime.UtcNow;
var currentCpuTime = _currentProcess.TotalProcessorTime;
var cpuUsedMs = (currentCpuTime - _lastCpuTime).TotalMilliseconds;
var elapsedMs = (now - _lastCpuCheck).TotalMilliseconds;
_lastCpuTime = currentCpuTime;
_lastCpuCheck = now;
var cpuPercent = elapsedMs > 0
? cpuUsedMs / elapsedMs / Environment.ProcessorCount * 100.0
: 0.0;
CpuUsageText.Text = $"{cpuPercent:F1}%";
// CPU indicator color
var cpuColor = cpuPercent switch
{
< 50 => "#00E676",
< 80 => "#FFB74D",
_ => "#FF5252"
};
CpuStatusDot.Fill = Brush.Parse(cpuColor);
// Memory usage
var memBytes = _currentProcess.WorkingSet64;
var memMB = memBytes / 1024.0 / 1024.0;
MemUsageText.Text = memMB >= 1024
? $"{memMB / 1024.0:F1} GB"
: $"{memMB:F0} MB";
// Memory indicator color
var memColor = memMB switch
{
< 512 => "#00E676",
< 1024 => "#FFB74D",
_ => "#FF5252"
};
MemStatusDot.Fill = Brush.Parse(memColor);
// Network status & speed
var (bytesSent, bytesReceived) = GetNetworkBytes();
var netNow = DateTime.UtcNow;
var netElapsed = (netNow - _prevNetCheck).TotalSeconds;
var activeInterface = bytesSent >= 0;
if (activeInterface && netElapsed > 0)
{
var downSpeed = (bytesReceived - _prevBytesReceived) / netElapsed;
var upSpeed = (bytesSent - _prevBytesSent) / netElapsed;
NetDownText.Text = FormatSpeed(downSpeed);
NetUpText.Text = FormatSpeed(upSpeed);
}
else
{
NetDownText.Text = "0 KB/s";
NetUpText.Text = "0 KB/s";
}
_prevBytesSent = bytesSent;
_prevBytesReceived = bytesReceived;
_prevNetCheck = netNow;
NetStatusDot.Fill = Brush.Parse(activeInterface ? "#00E676" : "#FF5252");
NetStatusText.Text = activeInterface ? "已连接" : "未连接";
// Uptime
var uptime = DateTime.UtcNow - _startTime;
UptimeText.Text = uptime.TotalDays >= 1
? $"{(int)uptime.TotalDays}d {uptime.Hours:D2}:{uptime.Minutes:D2}:{uptime.Seconds:D2}"
: $"{uptime.Hours:D2}:{uptime.Minutes:D2}:{uptime.Seconds:D2}";
}
private void BuildMenu()
{
MenuContainer.Children.Clear();
foreach (var item in _navVm.TopMenuItems)
{
var btn = new Button
{
Content = item.Label,
Background = Brushes.Transparent,
Foreground = Brush.Parse("#8B9BB5"),
FontSize = 13,
Padding = new Thickness(14, 12),
Cursor = new Cursor(StandardCursorType.Hand),
BorderThickness = new Thickness(0),
};
if (item.Action != null)
{
btn.Click += (_, _) => item.Action();
}
if (item.Children != null && item.Children.Count > 0)
{
var popup = new Popup
{
PlacementTarget = btn,
Placement = PlacementMode.Bottom,
IsLightDismissEnabled = false,
};
var popupBorder = new Border
{
Background = Brush.Parse("#0F1620"),
BorderBrush = Brush.Parse("#1A2F4A"),
BorderThickness = new Thickness(1),
CornerRadius = new CornerRadius(4),
MinWidth = 160,
};
var stack = new StackPanel { Margin = new Thickness(4) };
foreach (var sub in item.Children)
{
var subBtn = new Button
{
Content = sub.Label,
Background = Brushes.Transparent,
Foreground = Brush.Parse("#8B9BB5"),
FontSize = 12,
HorizontalAlignment = HorizontalAlignment.Stretch,
HorizontalContentAlignment = HorizontalAlignment.Left,
Padding = new Thickness(12, 8),
BorderThickness = new Thickness(0),
Cursor = new Cursor(StandardCursorType.Hand),
};
subBtn.Click += (_, _) =>
{
popup.Close();
sub.Action();
};
subBtn.PointerEntered += (_, _) =>
subBtn.Background = Brush.Parse("#1B3A5C");
subBtn.PointerExited += (_, _) =>
subBtn.Background = Brushes.Transparent;
stack.Children.Add(subBtn);
}
popupBorder.Child = stack;
popup.Child = popupBorder;
// Hover 展开,离开按钮或 popup 时关闭
var closeTimer = new System.Timers.Timer(200) { AutoReset = false };
bool mouseInPopup = false;
bool mouseInButton = false;
popupBorder.PointerEntered += (_, _) => { mouseInPopup = true; closeTimer.Stop(); };
popupBorder.PointerExited += (_, _) => { mouseInPopup = false; TryClose(); };
btn.PointerEntered += (_, _) =>
{
mouseInButton = true;
closeTimer.Stop();
btn.Background = Brush.Parse("#1B3A5C");
btn.Foreground = Brush.Parse("#4FC3F7");
CloseAllPopups();
popup.Open();
};
btn.PointerExited += (_, _) =>
{
mouseInButton = false;
TryClose();
};
void TryClose()
{
closeTimer.Stop();
closeTimer.Start();
closeTimer.Elapsed += (_, _) =>
{
Avalonia.Threading.Dispatcher.UIThread.Post(() =>
{
if (!mouseInButton && !mouseInPopup)
{
popup.Close();
}
});
};
}
popup.Closed += (_, _) =>
{
btn.Background = Brushes.Transparent;
btn.Foreground = Brush.Parse("#8B9BB5");
};
_openPopups.Add(popup);
}
else
{
btn.PointerEntered += (_, _) =>
{
btn.Background = Brush.Parse("#1B3A5C");
btn.Foreground = Brush.Parse("#4FC3F7");
};
btn.PointerExited += (_, _) =>
{
btn.Background = Brushes.Transparent;
btn.Foreground = Brush.Parse("#8B9BB5");
};
}
MenuContainer.Children.Add(btn);
}
}
private void CloseAllPopups()
{
foreach (var p in _openPopups)
p.Close();
}
private void OnPageChanged(Control? view)
{
if (view != null)
{
view.HorizontalAlignment = HorizontalAlignment.Stretch;
view.VerticalAlignment = VerticalAlignment.Stretch;
ContentArea.Child = view;
}
}
private static (long sent, long received) GetNetworkBytes()
{
if (!NetworkInterface.GetIsNetworkAvailable())
return (-1, -1);
long totalSent = 0, totalReceived = 0;
var hasActive = false;
foreach (var ni in NetworkInterface.GetAllNetworkInterfaces())
{
if (ni.OperationalStatus == OperationalStatus.Up
&& ni.NetworkInterfaceType != NetworkInterfaceType.Loopback)
{
var stats = ni.GetIPStatistics();
totalSent += stats.BytesSent;
totalReceived += stats.BytesReceived;
hasActive = true;
}
}
return hasActive ? (totalSent, totalReceived) : (-1, -1);
}
private static string FormatSpeed(double bytesPerSecond)
{
if (bytesPerSecond < 0) bytesPerSecond = 0;
return bytesPerSecond switch
{
>= 1024 * 1024 => $"{bytesPerSecond / 1024 / 1024:F1} MB/s",
>= 1024 => $"{bytesPerSecond / 1024:F1} KB/s",
_ => $"{bytesPerSecond:F0} B/s"
};
}
}