215 lines
6.7 KiB
C#
215 lines
6.7 KiB
C#
using System;
|
|
using System.Collections.Specialized;
|
|
using System.ComponentModel;
|
|
using System.Linq;
|
|
using System.Windows;
|
|
using System.Windows.Input;
|
|
using System.Windows.Interop;
|
|
using System.Windows.Media;
|
|
using System.Windows.Media.Animation;
|
|
using System.Windows.Shapes;
|
|
using ClashWidget.Helpers;
|
|
using ClashWidget.ViewModels;
|
|
|
|
namespace ClashWidget;
|
|
|
|
public partial class MainWindow : Window
|
|
{
|
|
private readonly MainViewModel _viewModel;
|
|
private Polyline? _downloadLine;
|
|
private Polyline? _uploadLine;
|
|
private bool _isMaximized;
|
|
|
|
public MainWindow()
|
|
{
|
|
InitializeComponent();
|
|
|
|
_viewModel = new MainViewModel();
|
|
DataContext = _viewModel;
|
|
|
|
Opacity = 0;
|
|
Loaded += OnLoaded;
|
|
_viewModel.PropertyChanged += OnViewModelPropertyChanged;
|
|
_viewModel.SpeedHistory.CollectionChanged += OnSpeedHistoryChanged;
|
|
_viewModel.DropdownToggled += OnDropdownToggled;
|
|
|
|
CreateSparklines();
|
|
UpdateStatusDot();
|
|
}
|
|
|
|
private void OnLoaded(object sender, RoutedEventArgs e)
|
|
{
|
|
try
|
|
{
|
|
var hwnd = new WindowInteropHelper(this).Handle;
|
|
if (hwnd != IntPtr.Zero)
|
|
{
|
|
var source = HwndSource.FromHwnd(hwnd);
|
|
if (source?.CompositionTarget != null)
|
|
source.CompositionTarget.BackgroundColor = Colors.Transparent;
|
|
}
|
|
NativeMethods.ApplyAcrylic(this);
|
|
NativeMethods.SetTopmost(this);
|
|
NativeMethods.HideFromTaskbar(this);
|
|
}
|
|
catch { }
|
|
|
|
var workArea = SystemParameters.WorkArea;
|
|
Left = workArea.Right - Width - 20;
|
|
Top = workArea.Top + 20;
|
|
|
|
var fadeIn = new DoubleAnimation(0, 1, TimeSpan.FromMilliseconds(250))
|
|
{
|
|
EasingFunction = new CubicEase { EasingMode = EasingMode.EaseOut }
|
|
};
|
|
BeginAnimation(OpacityProperty, fadeIn);
|
|
|
|
_viewModel.Start();
|
|
}
|
|
|
|
private void CreateSparklines()
|
|
{
|
|
_downloadLine = new Polyline
|
|
{
|
|
Stroke = new SolidColorBrush(Color.FromRgb(0x3B, 0x82, 0xF6)),
|
|
StrokeThickness = 1.5, StrokeLineJoin = PenLineJoin.Round
|
|
};
|
|
_uploadLine = new Polyline
|
|
{
|
|
Stroke = new SolidColorBrush(Color.FromRgb(0x10, 0xB9, 0x81)),
|
|
StrokeThickness = 1, StrokeLineJoin = PenLineJoin.Round
|
|
};
|
|
SparklineCanvas.Children.Add(_downloadLine);
|
|
SparklineCanvas.Children.Add(_uploadLine);
|
|
}
|
|
|
|
private void OnSpeedHistoryChanged(object? sender, NotifyCollectionChangedEventArgs e)
|
|
{
|
|
Dispatcher.BeginInvoke(new Action(DrawSparklines));
|
|
}
|
|
|
|
private void DrawSparklines()
|
|
{
|
|
if (_downloadLine == null || _uploadLine == null) return;
|
|
var history = _viewModel.SpeedHistory.ToList();
|
|
if (history.Count < 2) return;
|
|
double cw = SparklineCanvas.ActualWidth, ch = SparklineCanvas.ActualHeight;
|
|
if (cw < 10 || ch < 10) return;
|
|
double maxVal = Math.Max(history.Max(p => p.DownloadBps), history.Max(p => p.UploadBps));
|
|
if (maxVal < 1) maxVal = 1;
|
|
double xStep = cw / (history.Count - 1);
|
|
var dl = new PointCollection();
|
|
var ul = new PointCollection();
|
|
for (int i = 0; i < history.Count; i++)
|
|
{
|
|
double x = i * xStep;
|
|
dl.Add(new Point(x, ch - (history[i].DownloadBps / maxVal * ch)));
|
|
ul.Add(new Point(x, ch - (history[i].UploadBps / maxVal * ch)));
|
|
}
|
|
_downloadLine.Points = dl;
|
|
_uploadLine.Points = ul;
|
|
}
|
|
|
|
private void OnWindowPreviewClick(object sender, MouseButtonEventArgs e)
|
|
{
|
|
if (NodePopup.IsOpen)
|
|
{
|
|
// Close popup if click is outside the popup
|
|
var pos = e.GetPosition(PopupBorder);
|
|
if (pos.X < 0 || pos.Y < 0 || pos.X > PopupBorder.ActualWidth || pos.Y > PopupBorder.ActualHeight)
|
|
{
|
|
NodePopup.IsOpen = false;
|
|
}
|
|
}
|
|
}
|
|
|
|
private void OnMouseLeftButtonDown(object sender, MouseButtonEventArgs e)
|
|
{
|
|
try { DragMove(); } catch { }
|
|
}
|
|
|
|
private void OnCloseClick(object sender, RoutedEventArgs e) => FadeOutAndClose();
|
|
private void OnMinimizeClick(object sender, RoutedEventArgs e) => WindowState = WindowState.Minimized;
|
|
|
|
private void OnMaximizeClick(object sender, RoutedEventArgs e)
|
|
{
|
|
_isMaximized = !_isMaximized;
|
|
WindowState = _isMaximized ? WindowState.Maximized : WindowState.Normal;
|
|
}
|
|
|
|
private void OnNodeAreaClick(object sender, MouseButtonEventArgs e)
|
|
{
|
|
e.Handled = true;
|
|
NodePopup.IsOpen = true;
|
|
}
|
|
|
|
private void OnPopupOpened(object? sender, EventArgs e)
|
|
{
|
|
_viewModel.IsDropdownOpen = true;
|
|
}
|
|
|
|
private void OnPopupClosed(object? sender, EventArgs e)
|
|
{
|
|
_viewModel.IsDropdownOpen = false;
|
|
}
|
|
|
|
private void OnDropdownToggled(bool open)
|
|
{
|
|
Dispatcher.BeginInvoke(new Action(() => NodePopup.IsOpen = open));
|
|
}
|
|
|
|
private void OnPopupBorderClick(object sender, MouseButtonEventArgs e)
|
|
{
|
|
e.Handled = true; // prevent bubbling to window drag
|
|
}
|
|
|
|
private async void OnNodeItemClick(object sender, System.Windows.Controls.SelectionChangedEventArgs e)
|
|
{
|
|
if (NodeListBox.SelectedItem is string nodeName && !string.IsNullOrEmpty(nodeName))
|
|
{
|
|
await _viewModel.SwitchNodeAsync(nodeName);
|
|
NodePopup.IsOpen = false;
|
|
}
|
|
}
|
|
|
|
private void OnSettingsClick(object sender, RoutedEventArgs e)
|
|
{
|
|
var settings = new SettingsWindow(this);
|
|
settings.Owner = this;
|
|
settings.ShowDialog();
|
|
if (settings.Saved) _viewModel.Reconnect();
|
|
}
|
|
|
|
private async void OnRefreshLatencyClick(object sender, RoutedEventArgs e)
|
|
{
|
|
BtnRefreshLatency.IsEnabled = false;
|
|
try { await _viewModel.RefreshLatencyAsync(); } finally { BtnRefreshLatency.IsEnabled = true; }
|
|
}
|
|
|
|
private void OnViewModelPropertyChanged(object? sender, PropertyChangedEventArgs e)
|
|
{
|
|
if (e.PropertyName == nameof(MainViewModel.IsConnected)) UpdateStatusDot();
|
|
}
|
|
|
|
private void UpdateStatusDot()
|
|
{
|
|
StatusDot.Fill = new SolidColorBrush(_viewModel.IsConnected ? Colors.LimeGreen : Colors.Tomato);
|
|
}
|
|
|
|
private void FadeOutAndClose()
|
|
{
|
|
var fadeOut = new DoubleAnimation(1, 0, TimeSpan.FromMilliseconds(150))
|
|
{
|
|
EasingFunction = new CubicEase { EasingMode = EasingMode.EaseIn }
|
|
};
|
|
fadeOut.Completed += (_, _) => Close();
|
|
BeginAnimation(OpacityProperty, fadeOut);
|
|
}
|
|
|
|
protected override void OnClosed(EventArgs e)
|
|
{
|
|
_viewModel.Dispose();
|
|
base.OnClosed(e);
|
|
}
|
|
}
|