winrar-keygen/MainWindow.xaml.cs
2026-06-29 19:15:52 +08:00

397 lines
13 KiB
C#

using System.ComponentModel;
using System.Diagnostics;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Animation;
using System.Windows.Shapes;
using Wpf.Ui.Controls;
using WinRarKeygenGui.Services;
using WinRarKeygenGui.ViewModels;
namespace WinRarKeygenGui;
public partial class MainWindow : FluentWindow
{
private static readonly Duration HalfFlip = new(TimeSpan.FromMilliseconds(180));
private bool _isFlipping;
private double _lockedHeight;
public MainWindow()
{
InitializeComponent();
Loaded += OnLoaded;
Closing += OnClosing;
LocationChanged += OnLocationChanged;
PreviewKeyDown += OnPreviewKeyDown;
PreviewKeyUp += OnPreviewKeyUp;
if (DataContext is MainViewModel vm)
vm.RequestShowResult += () => Dispatcher.Invoke(FlipToResult);
}
private static bool IsPlainAltKey(KeyEventArgs e)
{
return e.Key is Key.LeftAlt or Key.RightAlt ||
e.SystemKey is Key.LeftAlt or Key.RightAlt;
}
private void OnPreviewKeyDown(object sender, KeyEventArgs e)
{
if (IsPlainAltKey(e))
e.Handled = true;
}
private void OnPreviewKeyUp(object sender, KeyEventArgs e)
{
if (IsPlainAltKey(e))
e.Handled = true;
}
private void SettingsComboBox_PreviewMouseWheel(object sender, MouseWheelEventArgs e)
{
e.Handled = true;
}
private void OnLoaded(object sender, RoutedEventArgs e)
{
if (DataContext is MainViewModel vm)
{
vm.ApplySelectedTheme();
// Set initial encoding pill position (no animation)
SetEncodingPillPosition(vm.SelectedEncoding);
// Listen for encoding changes to animate the pill
vm.PropertyChanged += (s, args) =>
{
if (args.PropertyName == nameof(MainViewModel.SelectedEncoding))
AnimateEncodingPill(vm.SelectedEncoding);
};
// Fire-and-forget background update check
_ = vm.RunUpdateCheckAsync();
}
}
private void SetEncodingPillPosition(int index)
{
if (EncodingPill.ActualWidth <= 0) return;
EncodingPillTranslate.X = index * EncodingPill.ActualWidth;
}
private void AnimateEncodingPill(int index)
{
if (EncodingPill.ActualWidth <= 0) return;
var targetX = index * EncodingPill.ActualWidth;
var animation = new DoubleAnimation
{
To = targetX,
Duration = TimeSpan.FromMilliseconds(200),
EasingFunction = new CubicEase { EasingMode = EasingMode.EaseInOut }
};
EncodingPillTranslate.BeginAnimation(TranslateTransform.XProperty, animation);
}
private void OnLocationChanged(object? sender, EventArgs e)
{
// Force WPF Popups to reposition when window moves
foreach (var popup in new[] { UsernameWarningPopup, LicenseWarningPopup })
{
if (popup.IsOpen)
{
var offset = popup.HorizontalOffset;
popup.HorizontalOffset = offset + 1;
popup.HorizontalOffset = offset;
}
}
}
private void OnClosing(object? sender, CancelEventArgs e)
{
if (DataContext is MainViewModel vm)
vm.SaveSettings(vm.ResolveInitialIsDarkTheme());
}
private void VersionLink_MouseEnter(object sender, MouseEventArgs e)
{
VersionLink.Foreground = (Brush)FindResource("AccentBrush");
}
private void VersionLink_MouseLeave(object sender, MouseEventArgs e)
{
VersionLink.SetResourceReference(ForegroundProperty, "TextFillColorTertiaryBrush");
}
private async void VersionLink_Click(object sender, MouseButtonEventArgs e)
{
if (DataContext is MainViewModel vm)
{
if (vm.HasUpdate)
{
// Update available — open releases page
Process.Start(new ProcessStartInfo
{
FileName = AppEnvironment.ReleasesUrl,
UseShellExecute = true
});
}
else
{
// No update known — trigger manual check
await vm.ManualCheckForUpdateAsync();
}
}
}
private void WinRarPath_MouseEnter(object sender, MouseEventArgs e)
{
WinRarPathLink.Foreground = (Brush)FindResource("AccentBrush");
}
private void WinRarPath_MouseLeave(object sender, MouseEventArgs e)
{
WinRarPathLink.SetResourceReference(ForegroundProperty, "TextFillColorTertiaryBrush");
}
private void WinRarPath_Click(object sender, MouseButtonEventArgs e)
{
var appData = Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData);
var winrarDir = System.IO.Path.Combine(appData, "WinRAR");
System.IO.Directory.CreateDirectory(winrarDir);
Process.Start(new ProcessStartInfo
{
FileName = winrarDir,
UseShellExecute = true
});
}
private void StatusPath_MouseEnter(object sender, MouseEventArgs e)
{
StatusPathLink.Foreground = (Brush)FindResource("AccentBrush");
}
private void StatusPath_MouseLeave(object sender, MouseEventArgs e)
{
StatusPathLink.SetResourceReference(ForegroundProperty, "TextFillColorSecondaryBrush");
}
private void StatusPath_Click(object sender, MouseButtonEventArgs e)
{
if (DataContext is MainViewModel vm && !string.IsNullOrEmpty(vm.StatusFilePath))
{
var dir = System.IO.Path.GetDirectoryName(vm.StatusFilePath);
if (dir != null && System.IO.Directory.Exists(dir))
{
Process.Start(new ProcessStartInfo
{
FileName = dir,
UseShellExecute = true
});
}
}
}
private void Settings_Click(object sender, RoutedEventArgs e)
{
FlipToBack();
}
private void BackToMain_Click(object sender, RoutedEventArgs e)
{
FlipToFront(BackPanel, BackScale);
}
private void ResultBackToMain_Click(object sender, RoutedEventArgs e)
{
FlipToFront(ResultPanel, ResultScale);
}
private void LockWindowHeight()
{
_lockedHeight = ActualHeight;
SizeToContent = SizeToContent.Manual;
Height = _lockedHeight;
}
private void UnlockWindowHeight()
{
SizeToContent = SizeToContent.Height;
}
private void FlipToBack()
{
FlipFromFront(BackPanel, BackScale);
}
private void FlipToResult()
{
FlipFromFront(ResultPanel, ResultScale, onExpanded: LaunchConfetti);
}
/// <summary>Flips away from the front panel to the specified target panel.</summary>
private void FlipFromFront(Border targetPanel, ScaleTransform targetScale, Action? onExpanded = null)
{
if (_isFlipping) return;
// Hide warning popups when leaving front panel
if (DataContext is MainViewModel vm)
vm.HideWarnings();
_isFlipping = true;
LockWindowHeight();
var shrink = new DoubleAnimation(1, 0, HalfFlip)
{
EasingFunction = new CubicEase { EasingMode = EasingMode.EaseIn }
};
shrink.Completed += (_, _) =>
{
FrontPanel.Visibility = Visibility.Collapsed;
targetPanel.Visibility = Visibility.Visible;
var expand = new DoubleAnimation(0, 1, HalfFlip)
{
EasingFunction = new BackEase { EasingMode = EasingMode.EaseOut, Amplitude = 0.3 }
};
expand.Completed += (_, _) =>
{
_isFlipping = false;
onExpanded?.Invoke();
};
targetScale.BeginAnimation(ScaleTransform.ScaleXProperty, expand);
};
FrontScale.BeginAnimation(ScaleTransform.ScaleXProperty, shrink);
}
private void LaunchConfetti()
{
const int count = 50;
const double durationMs = 1600;
var rng = new Random();
var colors = new[]
{
Color.FromRgb(0xE5, 0x39, 0x35), // red
Color.FromRgb(0xFB, 0x8C, 0x00), // orange
Color.FromRgb(0xFF, 0xD6, 0x00), // yellow
Color.FromRgb(0x43, 0xA0, 0x47), // green
Color.FromRgb(0x1E, 0x88, 0xE5), // blue
Color.FromRgb(0x8E, 0x24, 0xAA), // purple
Color.FromRgb(0xE9, 0x1E, 0x63), // pink
Color.FromRgb(0x00, 0xAC, 0xC1), // teal
};
double canvasW = ConfettiCanvas.ActualWidth > 0 ? ConfettiCanvas.ActualWidth : ActualWidth;
double canvasH = ConfettiCanvas.ActualHeight > 0 ? ConfettiCanvas.ActualHeight : ActualHeight;
int cleanupCounter = 0;
for (int i = 0; i < count; i++)
{
var color = colors[rng.Next(colors.Length)];
bool isCircle = rng.Next(2) == 0;
FrameworkElement piece;
if (isCircle)
{
piece = new Ellipse
{
Width = rng.Next(4, 8),
Height = rng.Next(4, 8),
Fill = new SolidColorBrush(color),
IsHitTestVisible = false,
Opacity = 0.85
};
}
else
{
piece = new Rectangle
{
Width = rng.Next(5, 10),
Height = rng.Next(3, 7),
Fill = new SolidColorBrush(color),
RadiusX = 1, RadiusY = 1,
IsHitTestVisible = false,
Opacity = 0.85
};
}
double startX = rng.NextDouble() * canvasW;
double startY = -10 - rng.NextDouble() * 40;
Canvas.SetLeft(piece, startX);
Canvas.SetTop(piece, startY);
piece.RenderTransformOrigin = new Point(0.5, 0.5);
piece.RenderTransform = new RotateTransform(0);
ConfettiCanvas.Children.Add(piece);
var dur = new Duration(TimeSpan.FromMilliseconds(durationMs + rng.Next(-200, 300)));
// Fall down
double endY = canvasH + 20;
var fallAnim = new DoubleAnimation(startY, endY, dur)
{
EasingFunction = new QuadraticEase { EasingMode = EasingMode.EaseIn }
};
// Horizontal drift
double drift = startX + rng.NextDouble() * 80 - 40;
var driftAnim = new DoubleAnimation(startX, drift, dur);
// Rotate
double endAngle = rng.Next(-360, 360);
var rotateAnim = new DoubleAnimation(0, endAngle, dur);
// Fade out in last 40%
var fadeAnim = new DoubleAnimation(0.85, 0, dur)
{
BeginTime = TimeSpan.FromMilliseconds(durationMs * 0.55)
};
// Cleanup when fall completes
fallAnim.Completed += (_, _) =>
{
ConfettiCanvas.Children.Remove(piece);
cleanupCounter++;
if (cleanupCounter >= count)
ConfettiCanvas.Children.Clear();
};
piece.BeginAnimation(Canvas.TopProperty, fallAnim);
piece.BeginAnimation(Canvas.LeftProperty, driftAnim);
((RotateTransform)piece.RenderTransform).BeginAnimation(RotateTransform.AngleProperty, rotateAnim);
piece.BeginAnimation(UIElement.OpacityProperty, fadeAnim);
}
}
/// <summary>Flips back from the specified source panel to the front panel.</summary>
private void FlipToFront(Border sourcePanel, ScaleTransform sourceScale)
{
if (_isFlipping) return;
_isFlipping = true;
LockWindowHeight();
var shrink = new DoubleAnimation(1, 0, HalfFlip)
{
EasingFunction = new CubicEase { EasingMode = EasingMode.EaseIn }
};
shrink.Completed += (_, _) =>
{
sourcePanel.Visibility = Visibility.Collapsed;
FrontPanel.Visibility = Visibility.Visible;
var expand = new DoubleAnimation(0, 1, HalfFlip)
{
EasingFunction = new BackEase { EasingMode = EasingMode.EaseOut, Amplitude = 0.3 }
};
expand.Completed += (_, _) =>
{
_isFlipping = false;
UnlockWindowHeight();
};
FrontScale.BeginAnimation(ScaleTransform.ScaleXProperty, expand);
};
sourceScale.BeginAnimation(ScaleTransform.ScaleXProperty, shrink);
}
}