initial commit
This commit is contained in:
@@ -0,0 +1,214 @@
|
||||
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);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user