Files
LanMountainDesktop/LanMountainDesktop/Views/Components/ClockWidget.axaml.cs

198 lines
5.8 KiB
C#
Raw Normal View History

2026-03-04 15:22:52 +08:00
using System;
2026-02-27 13:43:27 +08:00
using System.Globalization;
using Avalonia;
using Avalonia.Controls;
2026-02-27 15:15:09 +08:00
using Avalonia.Media;
2026-02-27 13:43:27 +08:00
using Avalonia.Threading;
2026-03-04 15:22:52 +08:00
using LanMountainDesktop.Services;
2026-02-27 13:43:27 +08:00
2026-03-04 15:22:52 +08:00
namespace LanMountainDesktop.Views.Components;
2026-02-27 13:43:27 +08:00
2026-03-02 20:02:14 +08:00
public enum ClockDisplayFormat
{
HourMinuteSecond, // HH:mm:ss
HourMinute // HH:mm
}
2026-03-03 04:56:04 +08:00
public partial class ClockWidget : UserControl, IDesktopComponentWidget, ITimeZoneAwareComponentWidget
2026-02-27 13:43:27 +08:00
{
private readonly DispatcherTimer _timer = new()
{
Interval = TimeSpan.FromSeconds(1)
};
2026-03-01 16:50:06 +08:00
private TimeZoneService? _timeZoneService;
2026-03-02 20:02:14 +08:00
private ClockDisplayFormat _displayFormat = ClockDisplayFormat.HourMinuteSecond;
2026-03-19 00:17:21 +08:00
private bool _transparentBackground;
private double _lastAppliedCellSize = 100;
2026-03-01 16:50:06 +08:00
2026-02-27 13:43:27 +08:00
public ClockWidget()
{
InitializeComponent();
_timer.Tick += OnTimerTick;
AttachedToVisualTree += OnAttachedToVisualTree;
DetachedFromVisualTree += OnDetachedFromVisualTree;
UpdateClock();
}
2026-03-02 20:02:14 +08:00
public ClockDisplayFormat DisplayFormat
{
get => _displayFormat;
set
{
_displayFormat = value;
UpdateClock();
}
}
2026-03-19 00:17:21 +08:00
public bool TransparentBackground
{
get => _transparentBackground;
set
{
if (_transparentBackground == value)
{
return;
}
_transparentBackground = value;
ApplyChrome();
ApplyCellSize(_lastAppliedCellSize);
}
}
2026-03-02 20:02:14 +08:00
public void SetDisplayFormat(ClockDisplayFormat format)
{
DisplayFormat = format;
}
2026-03-19 00:17:21 +08:00
public void SetTransparentBackground(bool transparentBackground)
{
TransparentBackground = transparentBackground;
}
2026-03-01 16:50:06 +08:00
public void SetTimeZoneService(TimeZoneService timeZoneService)
{
2026-03-03 11:10:57 +08:00
ClearTimeZoneService();
2026-03-01 16:50:06 +08:00
_timeZoneService = timeZoneService;
_timeZoneService.TimeZoneChanged += OnTimeZoneChanged;
UpdateClock();
}
2026-03-03 11:10:57 +08:00
public void ClearTimeZoneService()
{
if (_timeZoneService is null)
{
return;
}
_timeZoneService.TimeZoneChanged -= OnTimeZoneChanged;
_timeZoneService = null;
}
2026-02-27 13:43:27 +08:00
private void OnAttachedToVisualTree(object? sender, VisualTreeAttachmentEventArgs e)
{
UpdateClock();
_timer.Start();
}
private void OnDetachedFromVisualTree(object? sender, VisualTreeAttachmentEventArgs e)
{
_timer.Stop();
}
private void OnTimerTick(object? sender, EventArgs e)
{
UpdateClock();
}
2026-03-01 16:50:06 +08:00
private void OnTimeZoneChanged(object? sender, EventArgs e)
{
UpdateClock();
}
2026-02-27 13:43:27 +08:00
private void UpdateClock()
{
2026-03-01 16:50:06 +08:00
var now = _timeZoneService?.GetCurrentTime() ?? DateTime.Now;
2026-03-02 20:02:14 +08:00
MainTimeTextBlock.Text = now.ToString("HH:mm", CultureInfo.CurrentCulture);
SecondsTextBlock.Text = now.ToString("ss", CultureInfo.CurrentCulture);
SecondsTextBlock.IsVisible = _displayFormat == ClockDisplayFormat.HourMinuteSecond;
2026-02-27 13:43:27 +08:00
}
2026-02-27 15:15:09 +08:00
public void ApplyCellSize(double cellSize)
{
2026-03-19 00:17:21 +08:00
_lastAppliedCellSize = cellSize;
2026-03-02 20:02:14 +08:00
// --- Class Island “满盈”风格算法 ---
// 1. 计算组件高度:保持与任务栏核心比例一致 (0.74x)
var targetHeight = Math.Clamp(cellSize * 0.74, 34, 74);
RootBorder.Height = targetHeight;
2026-03-20 22:37:37 +08:00
// 2. 主矩形统一到主题主档圆角
RootBorder.CornerRadius = ResolveUnifiedMainRectangle();
2026-03-02 20:02:14 +08:00
RootBorder.VerticalAlignment = Avalonia.Layout.VerticalAlignment.Center;
// 3. 核心:满盈字阶 (Filled Typography)
// 使主时间文字占据容器高度的 ~68%,产生饱满的视觉张力
var mainFontSize = targetHeight * 0.68;
2026-03-02 20:02:14 +08:00
MainTimeTextBlock.FontSize = mainFontSize;
MainTimeTextBlock.FontWeight = FontWeight.SemiBold;
// 4. 次级信息:秒数维持 0.7x 比例,并增强透明度呼吸感
SecondsTextBlock.FontSize = mainFontSize * 0.7;
SecondsTextBlock.Opacity = 0.55;
// 5. 视觉占比:占据约 2.2 个单元格的感官宽度 (cellSize * 2 + gaps)
RootBorder.MinWidth = cellSize * 2.2;
2026-02-27 15:15:09 +08:00
2026-03-02 20:02:14 +08:00
// 6. 间距微调
if (MainTimeTextBlock.Parent is StackPanel panel)
{
panel.Spacing = Math.Clamp(cellSize * 0.06, 2, 8);
2026-03-02 20:02:14 +08:00
}
2026-03-19 00:17:21 +08:00
if (_transparentBackground)
{
RootBorder.MinWidth = 0;
RootBorder.Padding = new Thickness(Math.Clamp(cellSize * 0.06, 4, 10), 0);
2026-03-19 00:17:21 +08:00
return;
}
2026-03-02 20:02:14 +08:00
// 确保清除可能存在的固定 Padding由代码控制“紧密感”
2026-03-19 00:17:21 +08:00
RootBorder.MinWidth = cellSize * 2.2;
RootBorder.Padding = new Thickness(Math.Clamp(cellSize * 0.15, 12, 24), 0);
2026-02-27 15:15:09 +08:00
}
2026-03-19 00:17:21 +08:00
private void ApplyChrome()
{
if (_transparentBackground)
{
RootBorder.Classes.Remove("glass-panel");
RootBorder.Background = Brushes.Transparent;
RootBorder.BorderBrush = Brushes.Transparent;
RootBorder.BorderThickness = new Thickness(0);
RootBorder.BoxShadow = default;
return;
}
if (!RootBorder.Classes.Contains("glass-panel"))
{
RootBorder.Classes.Add("glass-panel");
}
RootBorder.ClearValue(Border.BackgroundProperty);
RootBorder.ClearValue(Border.BorderBrushProperty);
RootBorder.ClearValue(Border.BorderThicknessProperty);
RootBorder.ClearValue(Border.BoxShadowProperty);
}
2026-03-20 22:37:37 +08:00
private CornerRadius ResolveUnifiedMainRectangle() => new(ResolveUnifiedMainRadiusValue());
private static double ResolveUnifiedMainRadiusValue() =>
HostAppearanceThemeProvider.GetOrCreate().GetCurrent().CornerRadiusTokens.Lg.TopLeft;
2026-02-27 13:43:27 +08:00
}