mirror of
https://github.com/wwiinnddyy/LanMountainDesktop.git
synced 2026-06-22 00:54:26 +08:00
0.2.2
时钟组件的完善。
This commit is contained in:
273
LanMontainDesktop/Views/Components/TimerWidget.axaml.cs
Normal file
273
LanMontainDesktop/Views/Components/TimerWidget.axaml.cs
Normal file
@@ -0,0 +1,273 @@
|
||||
using System;
|
||||
using System.Globalization;
|
||||
using Avalonia;
|
||||
using Avalonia.Controls;
|
||||
using Avalonia.Controls.Shapes;
|
||||
using Avalonia.Input;
|
||||
using Avalonia.Media;
|
||||
using Avalonia.Styling;
|
||||
using Avalonia.Threading;
|
||||
|
||||
namespace LanMontainDesktop.Views.Components;
|
||||
|
||||
public partial class TimerWidget : UserControl
|
||||
{
|
||||
private const int MaxTimerSeconds = 60;
|
||||
private const double DialSize = 224;
|
||||
private const double Center = DialSize / 2;
|
||||
|
||||
private readonly DispatcherTimer _timer = new()
|
||||
{
|
||||
Interval = TimeSpan.FromSeconds(1)
|
||||
};
|
||||
|
||||
private double _currentCellSize = 48;
|
||||
private bool _isRunning;
|
||||
private int _remainingSeconds;
|
||||
private bool? _isNightModeApplied;
|
||||
|
||||
public TimerWidget()
|
||||
{
|
||||
InitializeComponent();
|
||||
|
||||
_timer.Tick += OnTimerTick;
|
||||
AttachedToVisualTree += OnAttachedToVisualTree;
|
||||
DetachedFromVisualTree += OnDetachedFromVisualTree;
|
||||
SizeChanged += OnSizeChanged;
|
||||
PlayButtonBorder.PointerPressed += OnPlayButtonPointerPressed;
|
||||
|
||||
UpdateVisual();
|
||||
}
|
||||
|
||||
private void OnAttachedToVisualTree(object? sender, VisualTreeAttachmentEventArgs e)
|
||||
{
|
||||
UpdateVisual();
|
||||
_timer.Start();
|
||||
}
|
||||
|
||||
private void OnDetachedFromVisualTree(object? sender, VisualTreeAttachmentEventArgs e)
|
||||
{
|
||||
_timer.Stop();
|
||||
}
|
||||
|
||||
private void OnSizeChanged(object? sender, SizeChangedEventArgs e)
|
||||
{
|
||||
ApplyCellSize(_currentCellSize);
|
||||
}
|
||||
|
||||
private void OnTimerTick(object? sender, EventArgs e)
|
||||
{
|
||||
ApplyModeVisualIfNeeded();
|
||||
|
||||
if (!_isRunning)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (_remainingSeconds > 0)
|
||||
{
|
||||
_remainingSeconds--;
|
||||
}
|
||||
|
||||
if (_remainingSeconds <= 0)
|
||||
{
|
||||
_remainingSeconds = 0;
|
||||
_isRunning = false;
|
||||
}
|
||||
|
||||
UpdateVisual();
|
||||
}
|
||||
|
||||
private void OnPlayButtonPointerPressed(object? sender, PointerPressedEventArgs e)
|
||||
{
|
||||
if (!e.GetCurrentPoint(this).Properties.IsLeftButtonPressed)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (_isRunning)
|
||||
{
|
||||
_isRunning = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (_remainingSeconds <= 0)
|
||||
{
|
||||
_remainingSeconds = MaxTimerSeconds;
|
||||
}
|
||||
|
||||
_isRunning = true;
|
||||
}
|
||||
|
||||
UpdateVisual();
|
||||
e.Handled = true;
|
||||
}
|
||||
|
||||
private void UpdateVisual()
|
||||
{
|
||||
ApplyModeVisualIfNeeded();
|
||||
UpdateNumberVisual();
|
||||
UpdateHandGeometry();
|
||||
UpdatePlayButtonVisual();
|
||||
}
|
||||
|
||||
private void UpdateNumberVisual()
|
||||
{
|
||||
var current = Math.Clamp(_remainingSeconds, 0, MaxTimerSeconds);
|
||||
var top = current == 0 ? MaxTimerSeconds : current - 1;
|
||||
var next = (current + 1) % (MaxTimerSeconds + 1);
|
||||
var nextNext = (current + 2) % (MaxTimerSeconds + 1);
|
||||
|
||||
TopNumberTextBlock.Text = top.ToString(CultureInfo.InvariantCulture);
|
||||
MainNumberTextBlock.Text = current.ToString(CultureInfo.InvariantCulture);
|
||||
NextNumberTextBlock.Text = next.ToString(CultureInfo.InvariantCulture);
|
||||
NextNextNumberTextBlock.Text = nextNext.ToString(CultureInfo.InvariantCulture);
|
||||
}
|
||||
|
||||
private void UpdateHandGeometry()
|
||||
{
|
||||
var angleDeg = (_remainingSeconds % (MaxTimerSeconds + 1)) / 60d * 360d;
|
||||
var radians = angleDeg * Math.PI / 180d;
|
||||
var cos = Math.Cos(radians);
|
||||
var sin = Math.Sin(radians);
|
||||
|
||||
var start = new Point(Center - cos * 2, Center - sin * 2);
|
||||
var end = new Point(Center + cos * 68, Center + sin * 68);
|
||||
var glowEnd = new Point(Center + cos * 74, Center + sin * 74);
|
||||
|
||||
HandLine.StartPoint = start;
|
||||
HandLine.EndPoint = end;
|
||||
HandGlowLine.StartPoint = start;
|
||||
HandGlowLine.EndPoint = glowEnd;
|
||||
}
|
||||
|
||||
private void UpdatePlayButtonVisual()
|
||||
{
|
||||
PlayIconPath.Data = Geometry.Parse(_isRunning
|
||||
? "M 0,0 L 4,0 L 4,14 L 0,14 Z M 8,0 L 12,0 L 12,14 L 8,14 Z"
|
||||
: "M 0,0 L 0,14 L 11,7 Z");
|
||||
}
|
||||
|
||||
private void ApplyModeVisualIfNeeded()
|
||||
{
|
||||
var isNightMode = ResolveIsNightMode();
|
||||
if (_isNightModeApplied.HasValue && _isNightModeApplied.Value == isNightMode)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_isNightModeApplied = isNightMode;
|
||||
ApplyModeVisual(isNightMode);
|
||||
}
|
||||
|
||||
private void ApplyModeVisual(bool isNightMode)
|
||||
{
|
||||
RootBorder.Background = CreateBrush(isNightMode ? "#313540" : "#E8EAEE");
|
||||
TimerPanelBorder.Background = isNightMode
|
||||
? CreateLinearGradientBrush("#2F3441", "#202632")
|
||||
: CreateLinearGradientBrush("#FBFCFE", "#F3F5F9");
|
||||
TimerPanelBorder.BorderBrush = CreateBrush(isNightMode ? "#3B4353" : "#E2E7F0");
|
||||
|
||||
CenterDivider.Background = CreateBrush(isNightMode ? "#434B5C" : "#D5DAE3");
|
||||
|
||||
TopNumberTextBlock.Foreground = CreateBrush(isNightMode ? "#7A8397" : "#AEB4C1");
|
||||
MainNumberTextBlock.Foreground = CreateBrush(isNightMode ? "#F3F6FE" : "#0F141C");
|
||||
NextNumberTextBlock.Foreground = CreateBrush(isNightMode ? "#8089A0" : "#B2B8C4");
|
||||
NextNextNumberTextBlock.Foreground = CreateBrush(isNightMode ? "#6A7388" : "#C8CDD7");
|
||||
|
||||
var markBrush = CreateBrush(isNightMode ? "#5A657D" : "#D0D6E1");
|
||||
ScaleMark1.Background = markBrush;
|
||||
ScaleMark2.Background = markBrush;
|
||||
ScaleMark3.Background = markBrush;
|
||||
ScaleMark4.Background = markBrush;
|
||||
|
||||
PlayButtonBorder.BorderBrush = CreateBrush(isNightMode ? "#4A5367" : "#D3D9E4");
|
||||
PlayIconPath.Fill = CreateBrush(isNightMode ? "#8E98AF" : "#98A2B8");
|
||||
|
||||
CenterDotRing.Fill = CreateBrush(isNightMode ? "#EAF0FF" : "#FDFEFF");
|
||||
CenterDotRing.Stroke = CreateBrush(isNightMode ? "#A9B8D5" : "#E3E8F0");
|
||||
CenterDotCore.Fill = CreateBrush("#FF4D63");
|
||||
HandLine.Stroke = CreateBrush("#FF4D63");
|
||||
HandGlowLine.Stroke = CreateBrush(isNightMode ? "#FF6A6E" : "#FF7A78");
|
||||
HandGlowLine.Opacity = isNightMode ? 0.28 : 0.20;
|
||||
}
|
||||
|
||||
public void ApplyCellSize(double cellSize)
|
||||
{
|
||||
_currentCellSize = Math.Max(1, cellSize);
|
||||
var scale = ResolveScale();
|
||||
|
||||
RootBorder.CornerRadius = new CornerRadius(Math.Clamp(34 * scale, 12, 48));
|
||||
RootBorder.Padding = new Thickness(Math.Clamp(14 * scale, 7, 22));
|
||||
TimerPanelBorder.CornerRadius = new CornerRadius(Math.Clamp(32 * scale, 12, 42));
|
||||
|
||||
PlayButtonBorder.Width = Math.Clamp(42 * scale, 28, 58);
|
||||
PlayButtonBorder.Height = Math.Clamp(42 * scale, 28, 58);
|
||||
PlayButtonBorder.CornerRadius = new CornerRadius(PlayButtonBorder.Width / 2d);
|
||||
|
||||
ApplyModeVisualIfNeeded();
|
||||
}
|
||||
|
||||
private double ResolveScale()
|
||||
{
|
||||
var cellScale = Math.Clamp(_currentCellSize / 44d, 0.60, 1.90);
|
||||
var heightScale = Bounds.Height > 1 ? Math.Clamp(Bounds.Height / 300d, 0.58, 2.0) : 1;
|
||||
var widthScale = Bounds.Width > 1 ? Math.Clamp(Bounds.Width / 300d, 0.58, 2.0) : 1;
|
||||
return Math.Clamp(Math.Min(cellScale, Math.Min(heightScale, widthScale) * 1.05), 0.58, 1.95);
|
||||
}
|
||||
|
||||
private bool ResolveIsNightMode()
|
||||
{
|
||||
if (ActualThemeVariant == ThemeVariant.Dark)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
if (ActualThemeVariant == ThemeVariant.Light)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (this.TryFindResource("AdaptiveSurfaceBaseBrush", out var value) &&
|
||||
value is ISolidColorBrush solidBrush)
|
||||
{
|
||||
return CalculateRelativeLuminance(solidBrush.Color) < 0.45;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private static IBrush CreateBrush(string colorHex)
|
||||
{
|
||||
return new SolidColorBrush(Color.Parse(colorHex));
|
||||
}
|
||||
|
||||
private static IBrush CreateLinearGradientBrush(string fromColorHex, string toColorHex)
|
||||
{
|
||||
return new LinearGradientBrush
|
||||
{
|
||||
StartPoint = new RelativePoint(0, 0, RelativeUnit.Relative),
|
||||
EndPoint = new RelativePoint(1, 1, RelativeUnit.Relative),
|
||||
GradientStops = new GradientStops
|
||||
{
|
||||
new GradientStop(Color.Parse(fromColorHex), 0),
|
||||
new GradientStop(Color.Parse(toColorHex), 1)
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
private static double CalculateRelativeLuminance(Color color)
|
||||
{
|
||||
static double ToLinear(double channel)
|
||||
{
|
||||
return channel <= 0.03928
|
||||
? channel / 12.92
|
||||
: Math.Pow((channel + 0.055) / 1.055, 2.4);
|
||||
}
|
||||
|
||||
var r = ToLinear(color.R / 255d);
|
||||
var g = ToLinear(color.G / 255d);
|
||||
var b = ToLinear(color.B / 255d);
|
||||
return 0.2126 * r + 0.7152 * g + 0.0722 * b;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user