Files
LanMountainDesktop/LanMountainDesktop/Views/MainWindow.axaml.cs
2026-05-27 09:41:18 +08:00

1338 lines
51 KiB
C#

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Runtime.InteropServices;
using System.Threading.Tasks;
using Avalonia;
using Avalonia.Controls;
using Avalonia.Controls.Primitives;
using Avalonia.Interactivity;
using FluentAvalonia.UI.Controls;
using Avalonia.Layout;
using Avalonia.Media;
using Avalonia.Media.Imaging;
using Avalonia.Platform;
using Avalonia.Platform.Storage;
using Avalonia.Styling;
using Avalonia.Threading;
using Avalonia.VisualTree;
using FluentIcons.Avalonia;
using FluentAvalonia.Styling;
using LanMountainDesktop.ComponentSystem;
using LanMountainDesktop.Models;
using LanMountainDesktop.PluginSdk;
using LanMountainDesktop.Services;
using LanMountainDesktop.Services.Settings;
using LanMountainDesktop.Shared.Contracts.Launcher;
using LanMountainDesktop.Theme;
using LanMountainDesktop.Views.Components;
using FluentIconKind = FluentIcons.Common.Icon;
using FluentIconVariant = FluentIcons.Common.IconVariant;
namespace LanMountainDesktop.Views;
public partial class MainWindow : Window
{
private enum WallpaperMediaType
{
None,
Image,
SolidColor
}
private enum WallpaperDisplayState
{
NoWallpaperConfigured,
TemporarilyUnavailable,
CurrentValidWallpaper
}
private enum WeatherLocationMode
{
CitySearch,
Coordinates
}
private const int StatusBarRowIndex = 0;
private const int MinShortSideCells = 6;
private const int MaxShortSideCells = 96;
private const int MinEdgeInsetPercent = 0;
private const int MaxEdgeInsetPercent = 30;
private const int DefaultEdgeInsetPercent = 18;
private static readonly int SettingsTransitionDurationMs = (int)FluttermotionToken.Page.TotalMilliseconds;
private const double LightBackgroundLuminanceThreshold = 0.57;
private const string TaskbarLayoutBottomFullRowMacStyle = "BottomFullRowMacStyle";
private const string BackToWindowsButtonDisplayModeIconAndText = "IconAndText";
private const string BackToWindowsButtonDisplayModeIconOnly = "IconOnly";
private const string BackToWindowsButtonDisplayModeTextOnly = "TextOnly";
private const string BackToWindowsIconSourceFluentIcon = "FluentIcon";
private const string BackToWindowsIconSourceText = "Text";
private const string DefaultBackToWindowsFluentIconName = "Circle";
private const string DefaultBackToWindowsIconText = "○";
private static readonly HashSet<string> SupportedImageExtensions = new(StringComparer.OrdinalIgnoreCase)
{
".png", ".jpg", ".jpeg", ".bmp", ".gif", ".webp"
};
private static readonly TaskbarActionId[] DefaultPinnedTaskbarActions =
[
TaskbarActionId.MinimizeToWindows
];
private readonly ISettingsFacadeService _settingsFacade = HostSettingsFacadeProvider.GetOrCreate();
private readonly IAppearanceThemeService _appearanceThemeService = HostAppearanceThemeProvider.GetOrCreate();
private readonly IAppLogoService _appLogoService = HostAppLogoProvider.GetOrCreate();
private readonly ICurrentUserProfileService _currentUserProfileService = HostCurrentUserProfileProvider.GetOrCreate();
private readonly IGridSettingsService _gridSettingsService;
private readonly IThemeAppearanceService _themeSettingsService;
private readonly IWeatherSettingsService _weatherSettingsService;
private readonly IRegionSettingsService _regionSettingsService;
private readonly IUpdateSettingsService _updateSettingsService;
private readonly ISettingsService _settingsService;
private readonly IComponentLayoutStore _componentLayoutStore = ComponentDomainStorageProvider.Instance;
private readonly IComponentStateStore _componentStateStore = ComponentDomainStorageProvider.Instance;
private readonly IComponentInstanceSettingsStore _componentSettingsStore = HostComponentSettingsStoreProvider.GetOrCreate();
private readonly LocalizationService _localizationService = new();
private readonly TimeZoneService _timeZoneService;
private readonly WindowsStartupService _windowsStartupService = new();
private readonly IWeatherInfoService _weatherDataService;
private readonly IRecommendationInfoService _recommendationInfoService = new RecommendationDataService();
private readonly ICalculatorDataService _calculatorDataService = new CalculatorDataService();
private readonly ComponentRegistry _componentRegistry;
private readonly DesktopComponentRuntimeRegistry _componentRuntimeRegistry;
private readonly DesktopComponentEditorRegistry _componentEditorRegistry;
private readonly IComponentLibraryService _componentLibraryService;
private readonly IComponentEditorWindowService _componentEditorWindowService;
private readonly IEmbeddedComponentLibraryService _componentLibraryWindowService = new EmbeddedComponentLibraryService();
private readonly IAirAppLauncherService _airAppLauncherService = AirAppLauncherServiceProvider.GetOrCreate();
private ComponentLibraryWindow? _detachedComponentLibraryWindow;
private readonly FluentAvaloniaTheme? _fluentAvaloniaTheme;
private readonly HashSet<string> _topStatusComponentIds = new(StringComparer.OrdinalIgnoreCase);
private readonly HashSet<TaskbarActionId> _pinnedTaskbarActions = [];
private int _targetShortSideCells;
private bool _isSettingsOpen;
private bool _isNightMode;
private bool _enableDynamicTaskbarActions;
private bool _suppressThemeToggleEvents;
private bool _suppressStatusBarToggleEvents;
private bool _suppressLanguageSelectionEvents;
private bool _suppressTimeZoneSelectionEvents;
private bool _suppressWeatherLocationEvents;
private bool _suppressSettingsPersistence;
private bool _isComponentLibraryOpen;
private Border? _selectedDesktopComponentHost;
private bool _reopenSettingsAfterComponentLibraryClose;
private TranslateTransform? _settingsContentPanelTransform;
private IBrush? _defaultDesktopBackground;
private Bitmap? _wallpaperBitmap;
private Bitmap? _lastValidWallpaperBitmap;
private string? _lastValidWallpaperPath;
private WallpaperMediaType _wallpaperMediaType;
private WallpaperDisplayState _wallpaperDisplayState = WallpaperDisplayState.NoWallpaperConfigured;
private string _wallpaperPlacement = WallpaperImageBrushFactory.Fill;
private string _wallpaperType = "Image";
private Color? _wallpaperSolidColor;
private string? _wallpaperPath;
private string _wallpaperStatus = "Current background uses solid color.";
private int _systemWallpaperRefreshIntervalSeconds = 300;
private DispatcherTimer? _systemWallpaperRefreshTimer;
private readonly ISystemWallpaperProvider _systemWallpaperProvider = HostSystemWallpaperProvider.GetOrCreate();
private IReadOnlyList<Color> _recommendedColors = Array.Empty<Color>();
private IReadOnlyList<Color> _monetColors = Array.Empty<Color>();
private Color _selectedThemeColor = Color.Parse("#FF3B82F6");
private double _currentDesktopCellSize;
private double _currentDesktopCellGap;
private double _currentDesktopEdgeInset;
private string _gridSpacingPreset = "Relaxed";
private bool _isSlideAnimationActive;
private TranslateTransform? _desktopPageSlideTransform;
private PixelPoint? _preparedWindowTargetPosition;
private PixelPoint? _preparedWindowHiddenPosition;
private string _statusBarSpacingMode = "Relaxed";
private int _statusBarCustomSpacingPercent = 12;
private bool _statusBarClockTransparentBackground;
private string _clockPosition = "Left"; // Left, Center, Right
private string _clockFontSize = "Medium"; // Small, Medium, Large
private bool _showTextCapsule;
private string _textCapsuleContent = "**Hello** World!";
private string _textCapsulePosition = "Right"; // Left, Center, Right
private bool _textCapsuleTransparentBackground;
private string _textCapsuleFontSize = "Medium"; // Small, Medium, Large
private bool _showNetworkSpeed;
private string _networkSpeedPosition = "Right"; // Left, Center, Right
private string _networkSpeedDisplayMode = "Both"; // Upload, Download, Both
private bool _networkSpeedTransparentBackground;
private bool _showNetworkTypeIcon;
private string _networkSpeedFontSize = "Medium"; // Small, Medium, Large
private bool _statusBarShadowEnabled;
private string _statusBarShadowColor = "#000000";
private double _statusBarShadowOpacity = 0.3;
private int _desktopEdgeInsetPercent = DefaultEdgeInsetPercent;
private string _taskbarLayoutMode = TaskbarLayoutBottomFullRowMacStyle;
private string _backToWindowsButtonDisplayMode = BackToWindowsButtonDisplayModeIconAndText;
private string _backToWindowsIconSource = BackToWindowsIconSourceFluentIcon;
private string _backToWindowsFluentIconName = DefaultBackToWindowsFluentIconName;
private string _backToWindowsIconText = DefaultBackToWindowsIconText;
private string _languageCode = "zh-CN";
private WeatherLocationMode _weatherLocationMode = WeatherLocationMode.CitySearch;
private string _weatherLocationKey = string.Empty;
private string _weatherLocationName = string.Empty;
private double _weatherLatitude = 39.9042;
private double _weatherLongitude = 116.4074;
private bool _weatherAutoRefreshLocation;
private string _weatherExcludedAlertsRaw = string.Empty;
private string _weatherIconPackId = WeatherVisualStyleId.Default;
private bool _weatherNoTlsRequests;
private bool _autoStartWithWindows;
private bool _suppressAutoStartToggleEvents;
private bool _suppressAppRenderModeSelectionEvents;
private string _selectedAppRenderMode = AppRenderingModeHelper.Default;
private string _runningAppRenderMode = AppRenderingModeHelper.Default;
private string _weatherSearchKeyword = string.Empty;
private bool _isWeatherSearchInProgress;
private bool _isWeatherPreviewInProgress;
private ClockDisplayFormat _clockDisplayFormat = ClockDisplayFormat.HourMinuteSecond;
private bool _externalSettingsReloadPending;
private int _persistSettingsRevision;
private int _suppressOwnSettingsReloadCount;
private double CurrentDesktopPitch => _currentDesktopCellSize + _currentDesktopCellGap;
public MainWindow()
{
var pluginRuntimeService = Design.IsDesignMode
? null
: (Application.Current as App)?.PluginRuntimeService;
_componentRegistry = DesktopComponentRegistryFactory.Create(pluginRuntimeService);
_settingsService = _settingsFacade.Settings;
_gridSettingsService = _settingsFacade.Grid;
_themeSettingsService = _settingsFacade.Theme;
_weatherSettingsService = _settingsFacade.Weather;
_regionSettingsService = _settingsFacade.Region;
_updateSettingsService = _settingsFacade.Update;
_timeZoneService = _regionSettingsService.GetTimeZoneService();
_weatherDataService = _weatherSettingsService.GetWeatherInfoService();
InitializeComponent();
Icon = _appLogoService.CreateWindowIcon();
_componentRuntimeRegistry = DesktopComponentRegistryFactory.CreateRuntimeRegistry(
_componentRegistry,
pluginRuntimeService,
_settingsFacade);
_componentEditorRegistry = DesktopComponentEditorRegistryFactory.Create(
_componentRegistry,
pluginRuntimeService);
_componentLibraryService = new ComponentLibraryService(_componentRegistry, _componentRuntimeRegistry);
_componentEditorWindowService = new ComponentEditorWindowService(_settingsFacade);
if (Design.IsDesignMode)
{
ApplyDesignTimePreview();
return;
}
InitializeTaskbarProfileFlyout();
_fluentAvaloniaTheme = Application.Current?.Styles.OfType<FluentAvaloniaTheme>().FirstOrDefault();
_settingsService.Changed += OnSettingsChanged;
_appearanceThemeService.Changed += OnAppearanceThemeChanged;
PropertyChanged += OnWindowPropertyChanged;
InitializeDesktopSurfaceSwipeHandlers();
InitializeDesktopComponentDragHandlers();
if (Application.Current is App app && app.SettingsWindowService is { } settingsWindowService)
{
settingsWindowService.StateChanged += OnSettingsWindowStateChanged;
_isSettingsOpen = settingsWindowService.IsOpen;
}
}
private void ApplyDesignTimePreview()
{
Title = "LanMountainDesktop Preview";
ShowInTaskbar = false;
DesktopWallpaperLayer.Background = new LinearGradientBrush
{
StartPoint = new RelativePoint(0, 0, RelativeUnit.Relative),
EndPoint = new RelativePoint(1, 1, RelativeUnit.Relative),
GradientStops = new GradientStops
{
new GradientStop(Color.Parse("#FFF6F8FB"), 0d),
new GradientStop(Color.Parse("#FFE9EEF7"), 0.55d),
new GradientStop(Color.Parse("#FFDCE5F3"), 1d)
}
};
DesktopWallpaperImageLayer.IsVisible = false;
LauncherPagePanel.IsVisible = false;
ComponentLibraryWindow.IsVisible = false;
BackToWindowsTextBlock.Text = "Back to Windows";
ComponentLibraryTitleTextBlock.Text = "Widgets";
ComponentLibraryBackTextBlock.Text = "Back";
TaskbarProfileDisplayNameTextBlock.Text = "Preview User";
TaskbarProfileSettingsActionTextBlock.Text = "Settings";
TaskbarProfileDesktopEditActionTextBlock.Text = "Edit Desktop";
TaskbarProfileAvatarFallbackText.Text = "P";
TaskbarProfileHeaderAvatarFallbackText.Text = "P";
TaskbarProfileButton.IsEnabled = false;
TaskbarProfilePopup.IsOpen = false;
ClockWidgetLeft.IsVisible = true;
ClockWidgetLeft.SetDisplayFormat(ClockDisplayFormat.HourMinute);
ClockWidgetLeft.SetTransparentBackground(false);
ConfigureDesignTimeDesktopGrid();
PopulateDesignTimeDesktopSurface();
}
private void ConfigureDesignTimeDesktopGrid()
{
const int previewRows = 7;
const int previewColumns = 12;
DesktopGrid.RowDefinitions.Clear();
DesktopGrid.ColumnDefinitions.Clear();
for (var row = 0; row < previewRows; row++)
{
DesktopGrid.RowDefinitions.Add(new RowDefinition(new GridLength(1, GridUnitType.Star)));
}
for (var column = 0; column < previewColumns; column++)
{
DesktopGrid.ColumnDefinitions.Add(new ColumnDefinition(new GridLength(1, GridUnitType.Star)));
}
DesktopGrid.Margin = new Thickness(28);
DesktopGrid.RowSpacing = 14;
DesktopGrid.ColumnSpacing = 14;
DesktopGrid.Width = double.NaN;
DesktopGrid.Height = double.NaN;
Grid.SetRow(TopStatusBarHost, 0);
Grid.SetColumn(TopStatusBarHost, 0);
Grid.SetRowSpan(TopStatusBarHost, 1);
Grid.SetColumnSpan(TopStatusBarHost, previewColumns);
Grid.SetRow(DesktopPagesViewport, 1);
Grid.SetColumn(DesktopPagesViewport, 0);
Grid.SetRowSpan(DesktopPagesViewport, previewRows - 2);
Grid.SetColumnSpan(DesktopPagesViewport, previewColumns);
Grid.SetRow(BottomTaskbarContainer, previewRows - 1);
Grid.SetColumn(BottomTaskbarContainer, 0);
Grid.SetRowSpan(BottomTaskbarContainer, 1);
Grid.SetColumnSpan(BottomTaskbarContainer, previewColumns);
DesktopPagesHost.ColumnDefinitions.Clear();
DesktopPagesHost.ColumnDefinitions.Add(new ColumnDefinition(new GridLength(1, GridUnitType.Star)));
ClockWidgetLeft.ApplyCellSize(72);
}
private void PopulateDesignTimeDesktopSurface()
{
DesktopPagesContainer.Children.Clear();
DesktopPagesContainer.Width = double.NaN;
DesktopPagesContainer.Height = double.NaN;
DesktopPagesContainer.Children.Add(CreateDesignTimePreviewCard(
"Focus Clock",
"Compact widget preview",
32,
32,
300,
170,
"#FFFFFFFF",
"#FFE8EEF8"));
DesktopPagesContainer.Children.Add(CreateDesignTimePreviewCard(
"Weather",
"26°C Qingdao",
360,
86,
260,
132,
"#FFF8FBFF",
"#FFDDE8F6"));
DesktopPagesContainer.Children.Add(CreateDesignTimePreviewCard(
"Study Session",
"Deep work · 48 min",
210,
248,
340,
144,
"#FFFDFEFF",
"#FFE7EEF7"));
}
private static Border CreateDesignTimePreviewCard(
string title,
string subtitle,
double left,
double top,
double width,
double height,
string backgroundColor,
string borderColor)
{
return new Border
{
Width = width,
Height = height,
Margin = new Thickness(left, top, 0, 0),
HorizontalAlignment = HorizontalAlignment.Left,
VerticalAlignment = VerticalAlignment.Top,
Background = new SolidColorBrush(Color.Parse(backgroundColor)),
BorderBrush = new SolidColorBrush(Color.Parse(borderColor)),
BorderThickness = new Thickness(1),
CornerRadius = new CornerRadius(28),
Child = new StackPanel
{
Margin = new Thickness(20),
Spacing = 8,
VerticalAlignment = VerticalAlignment.Center,
Children =
{
new TextBlock
{
Text = title,
FontSize = 20,
FontWeight = FontWeight.SemiBold,
Foreground = new SolidColorBrush(Color.Parse("#FF1E293B"))
},
new TextBlock
{
Text = subtitle,
FontSize = 13,
Foreground = new SolidColorBrush(Color.Parse("#FF64748B"))
}
}
}
};
}
private void OnNightModeIsCheckedChanged(object? sender, RoutedEventArgs e)
{
if (sender is not ToggleButton toggleButton)
{
return;
}
if (toggleButton.IsChecked == true)
{
OnNightModeChecked(sender, e);
return;
}
OnNightModeUnchecked(sender, e);
}
private void OnStatusBarClockIsCheckedChanged(object? sender, RoutedEventArgs e)
{
if (sender is not ToggleButton toggleButton)
{
return;
}
if (toggleButton.IsChecked == true)
{
OnStatusBarClockChecked(sender, e);
return;
}
OnStatusBarClockUnchecked(sender, e);
}
protected override void OnOpened(EventArgs e)
{
base.OnOpened(e);
if (Design.IsDesignMode)
{
ConfigureDesignTimeDesktopGrid();
PopulateDesignTimeDesktopSurface();
return;
}
SyncSettingsWindowState();
_suppressSettingsPersistence = true;
var snapshot = _settingsService.LoadSnapshot<AppSettingsSnapshot>(SettingsScope.App);
var desktopLayoutSnapshot = _componentLayoutStore.LoadLayout();
var launcherSnapshot = _settingsService.LoadSnapshot<LauncherSettingsSnapshot>(SettingsScope.Launcher);
if (!string.IsNullOrWhiteSpace(snapshot.TimeZoneId))
{
_timeZoneService.SetTimeZoneById(snapshot.TimeZoneId);
}
_targetShortSideCells = Math.Clamp(
snapshot.GridShortSideCells > 0 ? snapshot.GridShortSideCells : CalculateDefaultShortSideCellCountFromDpi(),
MinShortSideCells,
MaxShortSideCells);
ShowInTaskbar = snapshot.ShowInTaskbar;
_gridSpacingPreset = _gridSettingsService.NormalizeSpacingPreset(snapshot.GridSpacingPreset);
_desktopEdgeInsetPercent = Math.Clamp(snapshot.DesktopEdgeInsetPercent, MinEdgeInsetPercent, MaxEdgeInsetPercent);
_statusBarSpacingMode = NormalizeStatusBarSpacingMode(snapshot.StatusBarSpacingMode);
_statusBarCustomSpacingPercent = Math.Clamp(snapshot.StatusBarCustomSpacingPercent, 0, 30);
ApplyTaskbarSettings(snapshot);
InitializeLocalization(snapshot.LanguageCode);
InitializeWeatherSettings(snapshot);
InitializeAutoStartWithWindowsSetting(snapshot);
InitializeAppRenderModeSetting(snapshot);
InitializeUpdateSettings(snapshot);
InitializeDesktopSurfaceState(desktopLayoutSnapshot);
InitializeLauncherVisibilitySettings(launcherSnapshot);
InitializeDesktopComponentPlacements(desktopLayoutSnapshot);
InitializeSettingsIcons();
if (TryParseColor(snapshot.ThemeColor, out var savedThemeColor))
{
_selectedThemeColor = savedThemeColor;
}
_isNightMode = snapshot.IsNightMode
?? (Application.Current?.ActualThemeVariant == ThemeVariant.Dark);
_defaultDesktopBackground = CreateNeutralWallpaperFallbackBrush();
TryRestoreWallpaper(
snapshot.WallpaperPath,
snapshot.WallpaperType,
snapshot.WallpaperColor,
snapshot.WallpaperPlacement);
ApplyWallpaperBrush();
UpdateWallpaperDisplay();
if (!snapshot.IsNightMode.HasValue)
{
_isNightMode = CalculateCurrentBackgroundLuminance() < LightBackgroundLuminanceThreshold;
}
ApplyNightModeState(_isNightMode, refreshPalettes: true);
ApplyLocalization();
TelemetryServices.Usage?.TrackMainWindowOpened(
"MainWindow.OnOpened",
IsVisible,
WindowState.ToString());
TelemetryServices.Usage?.TrackSessionStarted("MainWindow.OnOpened");
DesktopHost.SizeChanged += OnDesktopHostSizeChanged;
RebuildDesktopGrid();
LoadLauncherEntriesAsync();
InitializeTimeZoneSettings();
ClockWidgetLeft.SetTimeZoneService(_timeZoneService);
ClockWidgetCenter.SetTimeZoneService(_timeZoneService);
ClockWidgetRight.SetTimeZoneService(_timeZoneService);
_suppressSettingsPersistence = false;
PersistSettings();
TriggerAutoUpdateCheckIfEnabled();
}
protected override void OnClosed(EventArgs e)
{
if (Design.IsDesignMode)
{
base.OnClosed(e);
return;
}
var wasVisible = IsVisible;
var windowState = WindowState.ToString();
SaveAllWhiteboardNotes();
PersistSettings();
_componentEditorWindowService.Close();
if (_detachedComponentLibraryWindow is not null)
{
_detachedComponentLibraryWindow.AddComponentRequested -= OnDetachedComponentLibraryAddComponentRequested;
_detachedComponentLibraryWindow.Closed -= OnDetachedComponentLibraryClosed;
_detachedComponentLibraryWindow.Close();
}
_detachedComponentLibraryWindow = null;
DisposeLauncherResources();
_lastValidWallpaperBitmap?.Dispose();
_lastValidWallpaperBitmap = null;
if (_recommendationInfoService is IDisposable recommendationServiceDisposable)
{
recommendationServiceDisposable.Dispose();
}
_wallpaperBitmap?.Dispose();
_wallpaperBitmap = null;
_settingsService.Changed -= OnSettingsChanged;
_appearanceThemeService.Changed -= OnAppearanceThemeChanged;
PropertyChanged -= OnWindowPropertyChanged;
DesktopHost.SizeChanged -= OnDesktopHostSizeChanged;
if (Application.Current is App app && app.SettingsWindowService is { } settingsWindowService)
{
settingsWindowService.StateChanged -= OnSettingsWindowStateChanged;
}
TelemetryServices.Usage?.TrackMainWindowClosed(
"MainWindow.OnClosed",
wasVisible,
windowState);
base.OnClosed(e);
}
private void OnAppearanceThemeChanged(object? sender, AppearanceThemeSnapshot snapshot)
{
_ = sender;
Dispatcher.UIThread.Post(() =>
{
if (!IsVisible)
{
return;
}
ApplyAdaptiveThemeResources();
_recommendedColors = snapshot.MonetPalette.RecommendedColors;
_monetColors = snapshot.MonetPalette.MonetColors;
ApplyUnifiedMainRectangleChrome(snapshot);
}, DispatcherPriority.Background);
}
private int CalculateDefaultShortSideCellCountFromDpi()
{
var dpi = 96d * RenderScaling;
var count = (int)Math.Round(dpi / 8d);
return Math.Clamp(count, MinShortSideCells, MaxShortSideCells);
}
private void OnDesktopHostSizeChanged(object? sender, SizeChangedEventArgs e)
{
RebuildDesktopGrid();
PersistSettings();
}
private void RebuildDesktopGrid()
{
var hostWidth = DesktopHost.Bounds.Width;
var hostHeight = DesktopHost.Bounds.Height;
var gapRatio = _gridSettingsService.ResolveGapRatio(_gridSpacingPreset);
var edgeInset = _gridSettingsService.CalculateEdgeInset(hostWidth, hostHeight, _targetShortSideCells, _desktopEdgeInsetPercent);
var gridMetrics = _gridSettingsService.CalculateGridMetrics(hostWidth, hostHeight, _targetShortSideCells, gapRatio, edgeInset);
if (gridMetrics.CellSize <= 0)
{
return;
}
_currentDesktopCellSize = gridMetrics.CellSize;
_currentDesktopCellGap = gridMetrics.GapPx;
_currentDesktopEdgeInset = gridMetrics.EdgeInsetPx;
DesktopGrid.RowDefinitions.Clear();
DesktopGrid.ColumnDefinitions.Clear();
DesktopGrid.Margin = new Thickness(gridMetrics.EdgeInsetPx);
DesktopGrid.RowSpacing = gridMetrics.GapPx;
DesktopGrid.ColumnSpacing = gridMetrics.GapPx;
DesktopGrid.Width = gridMetrics.GridWidthPx;
DesktopGrid.Height = gridMetrics.GridHeightPx;
for (var row = 0; row < gridMetrics.RowCount; row++)
{
DesktopGrid.RowDefinitions.Add(new RowDefinition(new GridLength(gridMetrics.CellSize, GridUnitType.Pixel)));
}
for (var col = 0; col < gridMetrics.ColumnCount; col++)
{
DesktopGrid.ColumnDefinitions.Add(new ColumnDefinition(new GridLength(gridMetrics.CellSize, GridUnitType.Pixel)));
}
PlaceStatusBarComponent(
TopStatusBarHost,
column: 0,
requestedColumnSpan: gridMetrics.ColumnCount,
totalColumns: gridMetrics.ColumnCount);
var taskbarRow = gridMetrics.RowCount - 1;
Grid.SetRow(BottomTaskbarContainer, taskbarRow);
Grid.SetColumn(BottomTaskbarContainer, 0);
Grid.SetRowSpan(BottomTaskbarContainer, 1);
Grid.SetColumnSpan(BottomTaskbarContainer, gridMetrics.ColumnCount);
ApplyTopStatusComponentVisibility();
ApplyTaskbarActionVisibility(GetCurrentTaskbarContext());
ApplyWidgetSizing(gridMetrics.CellSize);
ApplyDesktopStatusBarComponentSpacing();
UpdateDesktopSurfaceLayout(gridMetrics);
UpdateSettingsViewportInsets(gridMetrics.CellSize);
}
private void ApplyDesktopStatusBarComponentSpacing()
{
ApplyStatusBarComponentSpacingForPanel(TopStatusLeftPanel, _currentDesktopCellSize);
ApplyStatusBarComponentSpacingForPanel(TopStatusCenterPanel, _currentDesktopCellSize);
ApplyStatusBarComponentSpacingForPanel(TopStatusRightPanel, _currentDesktopCellSize);
}
private int ResolveStatusBarSpacingPercent()
{
return _statusBarSpacingMode switch
{
"Compact" => 6,
"Custom" => Math.Clamp(_statusBarCustomSpacingPercent, 0, 30),
_ => 12
};
}
private void ApplyStatusBarComponentSpacingForPanel(StackPanel? panel, double cellSize)
{
if (panel is null)
{
return;
}
var percent = ResolveStatusBarSpacingPercent();
var spacingPx = Math.Max(0, cellSize) * (percent / 100d);
panel.Spacing = spacingPx;
}
private static string NormalizeStatusBarSpacingMode(string? value)
{
return value switch
{
_ when string.Equals(value, "Compact", StringComparison.OrdinalIgnoreCase) => "Compact",
_ when string.Equals(value, "Custom", StringComparison.OrdinalIgnoreCase) => "Custom",
_ => "Relaxed"
};
}
private static int ClampComponentSpan(int requestedSpan, int axisCellCount)
{
return Math.Clamp(requestedSpan, 1, Math.Max(1, axisCellCount));
}
private static int ClampGridIndex(int requestedIndex, int axisCellCount)
{
return Math.Clamp(requestedIndex, 0, Math.Max(0, axisCellCount - 1));
}
private static void PlaceStatusBarComponent(
Control component,
int column,
int requestedColumnSpan,
int totalColumns)
{
var clampedColumn = ClampGridIndex(column, totalColumns);
var availableColumns = Math.Max(1, totalColumns - clampedColumn);
Grid.SetRow(component, StatusBarRowIndex);
Grid.SetColumn(component, clampedColumn);
Grid.SetRowSpan(component, 1);
Grid.SetColumnSpan(component, ClampComponentSpan(requestedColumnSpan, availableColumns));
}
private void ApplyWidgetSizing(double cellSize)
{
var taskbarCellHeight = Math.Clamp(cellSize * 0.76, 36, 76);
var taskbarTextSize = Math.Clamp(taskbarCellHeight * 0.36, 12, 22);
var taskbarIconSize = Math.Clamp(taskbarCellHeight * 0.46, 16, 34);
var taskbarButtonInset = Math.Clamp(taskbarCellHeight * 0.22, 6, 16);
var buttonContentSpacing = Math.Clamp(taskbarCellHeight * 0.20, 6, 14);
var taskbarButtonPadding = new Thickness(taskbarButtonInset);
// Status bar and taskbar are special surfaces: they should fill their row.
TopStatusBarHost.Margin = new Thickness(0);
TopStatusBarHost.Padding = new Thickness(0);
BottomTaskbarContainer.Margin = new Thickness(0);
ApplyUnifiedMainRectangleChrome();
BottomTaskbarContainer.Padding = new Thickness(Math.Clamp(taskbarCellHeight * 0.16, 6, 14));
ClockWidgetLeft.Margin = new Thickness(0);
ClockWidgetLeft.ApplyCellSize(cellSize);
ClockWidgetCenter.Margin = new Thickness(0);
ClockWidgetCenter.ApplyCellSize(cellSize);
ClockWidgetRight.Margin = new Thickness(0);
ClockWidgetRight.ApplyCellSize(cellSize);
TextCapsuleWidgetLeft.Margin = new Thickness(0);
TextCapsuleWidgetLeft.ApplyCellSize(cellSize);
TextCapsuleWidgetCenter.Margin = new Thickness(0);
TextCapsuleWidgetCenter.ApplyCellSize(cellSize);
TextCapsuleWidgetRight.Margin = new Thickness(0);
TextCapsuleWidgetRight.ApplyCellSize(cellSize);
NetworkSpeedWidgetLeft.Margin = new Thickness(0);
NetworkSpeedWidgetLeft.ApplyCellSize(cellSize);
NetworkSpeedWidgetCenter.Margin = new Thickness(0);
NetworkSpeedWidgetCenter.ApplyCellSize(cellSize);
NetworkSpeedWidgetRight.Margin = new Thickness(0);
NetworkSpeedWidgetRight.ApplyCellSize(cellSize);
var buttonMinWidth = GetBackToWindowsButtonMinWidth(taskbarCellHeight);
BackToWindowsButton.Margin = new Thickness(0);
BackToWindowsButton.Padding = taskbarButtonPadding;
BackToWindowsButton.FontSize = taskbarTextSize;
BackToWindowsButton.MinHeight = taskbarCellHeight;
BackToWindowsButton.MinWidth = buttonMinWidth;
BackToWindowsButton.Width = double.NaN;
BackToWindowsButton.Height = double.NaN;
ApplyBackToWindowsIconCircleSize(taskbarCellHeight);
BackToWindowsTextBlock.FontSize = taskbarTextSize;
RefreshBackToWindowsButtonPresentation(buttonContentSpacing);
TaskbarProfileButton.Margin = new Thickness(0);
TaskbarProfileButton.Padding = new Thickness(0);
TaskbarProfileButton.MinHeight = taskbarCellHeight;
TaskbarProfileButton.MinWidth = taskbarCellHeight;
TaskbarProfileButton.Width = taskbarCellHeight;
TaskbarProfileButton.Height = taskbarCellHeight;
var avatarSize = Math.Clamp(taskbarCellHeight * 0.82, 28, 60);
var avatarRadius = avatarSize / 2d;
TaskbarProfileAvatarBorder.Width = avatarSize;
TaskbarProfileAvatarBorder.Height = avatarSize;
TaskbarProfileAvatarBorder.CornerRadius = new CornerRadius(avatarRadius);
TaskbarProfileAvatarImage.Width = avatarSize;
TaskbarProfileAvatarImage.Height = avatarSize;
TaskbarProfileAvatarFallbackText.FontSize = Math.Clamp(avatarSize * 0.34, 10, 22);
UpdateComponentLibraryLayout(cellSize);
}
private void ApplyUnifiedMainRectangleChrome(AppearanceThemeSnapshot? snapshot = null)
{
var unifiedMainRectangle = new CornerRadius(ResolveUnifiedMainRadiusValue(snapshot));
BottomTaskbarContainer.CornerRadius = unifiedMainRectangle;
if (_currentDesktopCellSize > 0)
{
ClockWidgetLeft.ApplyCellSize(_currentDesktopCellSize);
ClockWidgetCenter.ApplyCellSize(_currentDesktopCellSize);
ClockWidgetRight.ApplyCellSize(_currentDesktopCellSize);
TextCapsuleWidgetLeft.ApplyCellSize(_currentDesktopCellSize);
TextCapsuleWidgetCenter.ApplyCellSize(_currentDesktopCellSize);
TextCapsuleWidgetRight.ApplyCellSize(_currentDesktopCellSize);
NetworkSpeedWidgetLeft.ApplyCellSize(_currentDesktopCellSize);
NetworkSpeedWidgetCenter.ApplyCellSize(_currentDesktopCellSize);
NetworkSpeedWidgetRight.ApplyCellSize(_currentDesktopCellSize);
}
}
private double ResolveUnifiedMainRadiusValue(AppearanceThemeSnapshot? snapshot = null)
{
if (snapshot is not null)
{
return snapshot.CornerRadiusTokens.Lg.TopLeft;
}
return _appearanceThemeService.GetCurrent().CornerRadiusTokens.Lg.TopLeft;
}
private static void SetButtonContentSpacing(Button? button, double spacing)
{
if (button?.Content is StackPanel contentPanel)
{
contentPanel.Spacing = spacing;
}
}
private double GetBackToWindowsButtonMinWidth(double taskbarCellHeight)
{
return _backToWindowsButtonDisplayMode switch
{
BackToWindowsButtonDisplayModeIconOnly => taskbarCellHeight,
BackToWindowsButtonDisplayModeTextOnly => Math.Clamp(taskbarCellHeight * 1.8, 72, 260),
_ => Math.Clamp(taskbarCellHeight * 2.35, 100, 340)
};
}
private double GetBackToWindowsTaskbarCellHeight()
{
if (_currentDesktopCellSize > 0)
{
return Math.Clamp(_currentDesktopCellSize * 0.76, 36, 76);
}
if (BackToWindowsButton.MinHeight > 0 && !double.IsNaN(BackToWindowsButton.MinHeight))
{
return Math.Clamp(BackToWindowsButton.MinHeight, 36, 76);
}
return 48;
}
private double GetBackToWindowsContentSpacing(double taskbarCellHeight)
{
return Math.Clamp(taskbarCellHeight * 0.20, 6, 14);
}
private void ApplyBackToWindowsIconCircleSize(double taskbarCellHeight)
{
var hitBoxSize = Math.Clamp(taskbarCellHeight * 0.62, 24, 44);
var iconSize = Math.Clamp(taskbarCellHeight * 0.32, 14, 24);
BackToWindowsIconCircle.Width = hitBoxSize;
BackToWindowsIconCircle.Height = hitBoxSize;
BackToWindowsIconCircle.CornerRadius = new CornerRadius(hitBoxSize / 2d);
BackToWindowsIconHost.Width = hitBoxSize;
BackToWindowsIconHost.Height = hitBoxSize;
if (BackToWindowsIconHost.Content is FluentIcon fluentIcon)
{
fluentIcon.FontSize = iconSize;
fluentIcon.Width = iconSize;
fluentIcon.Height = iconSize;
}
else if (BackToWindowsIconHost.Content is TextBlock textBlock)
{
textBlock.FontSize = Math.Clamp(taskbarCellHeight * 0.30, 12, 22);
}
}
private static string NormalizeBackToWindowsIconSource(string? value)
{
return string.Equals(value, BackToWindowsIconSourceText, StringComparison.OrdinalIgnoreCase)
? BackToWindowsIconSourceText
: BackToWindowsIconSourceFluentIcon;
}
private static FluentIconKind NormalizeBackToWindowsFluentIcon(string? value)
{
return Enum.TryParse<FluentIconKind>(value, ignoreCase: true, out var icon)
? icon
: FluentIconKind.Circle;
}
private static string NormalizeBackToWindowsIconText(string? value)
{
var normalized = string.IsNullOrWhiteSpace(value)
? DefaultBackToWindowsIconText
: value.Trim();
var enumerator = StringInfo.GetTextElementEnumerator(normalized);
var builder = new System.Text.StringBuilder();
var count = 0;
while (enumerator.MoveNext() && count < 4)
{
builder.Append(enumerator.GetTextElement());
count++;
}
return builder.Length > 0 ? builder.ToString() : DefaultBackToWindowsIconText;
}
private void RefreshBackToWindowsIconContent(double taskbarCellHeight)
{
_backToWindowsIconSource = NormalizeBackToWindowsIconSource(_backToWindowsIconSource);
if (_backToWindowsIconSource == BackToWindowsIconSourceText)
{
BackToWindowsIconHost.Content = new TextBlock
{
Text = NormalizeBackToWindowsIconText(_backToWindowsIconText),
Foreground = GetThemeBrush("AdaptiveTextPrimaryBrush"),
FontWeight = FontWeight.SemiBold,
HorizontalAlignment = HorizontalAlignment.Center,
VerticalAlignment = VerticalAlignment.Center,
TextAlignment = TextAlignment.Center
};
ApplyBackToWindowsIconCircleSize(taskbarCellHeight);
return;
}
BackToWindowsIconHost.Content = new FluentIcon
{
Icon = NormalizeBackToWindowsFluentIcon(_backToWindowsFluentIconName),
IconVariant = FluentIconVariant.Regular,
Foreground = GetThemeBrush("AdaptiveTextPrimaryBrush"),
HorizontalAlignment = HorizontalAlignment.Center,
VerticalAlignment = VerticalAlignment.Center
};
ApplyBackToWindowsIconCircleSize(taskbarCellHeight);
}
private static string NormalizeBackToWindowsButtonDisplayMode(string? value)
{
return value switch
{
_ when string.Equals(value, BackToWindowsButtonDisplayModeIconOnly, StringComparison.OrdinalIgnoreCase) =>
BackToWindowsButtonDisplayModeIconOnly,
_ when string.Equals(value, BackToWindowsButtonDisplayModeTextOnly, StringComparison.OrdinalIgnoreCase) =>
BackToWindowsButtonDisplayModeTextOnly,
_ => BackToWindowsButtonDisplayModeIconAndText
};
}
private void RefreshBackToWindowsButtonPresentation(double? contentSpacing = null)
{
_backToWindowsButtonDisplayMode = NormalizeBackToWindowsButtonDisplayMode(_backToWindowsButtonDisplayMode);
var taskbarCellHeight = GetBackToWindowsTaskbarCellHeight();
BackToWindowsButton.MinWidth = GetBackToWindowsButtonMinWidth(taskbarCellHeight);
RefreshBackToWindowsIconContent(taskbarCellHeight);
var showIcon = _backToWindowsButtonDisplayMode is not BackToWindowsButtonDisplayModeTextOnly;
var showText = _backToWindowsButtonDisplayMode is not BackToWindowsButtonDisplayModeIconOnly;
BackToWindowsIconCircle.IsVisible = showIcon;
BackToWindowsTextBlock.IsVisible = showText;
if (BackToWindowsContentPanel is not null)
{
BackToWindowsContentPanel.Spacing = showIcon && showText
? contentSpacing ?? GetBackToWindowsContentSpacing(taskbarCellHeight)
: 0;
}
BackToWindowsButton.InvalidateMeasure();
BackToWindowsButton.InvalidateArrange();
}
private void UpdateComponentLibraryLayout(double cellSize)
{
if (ComponentLibraryWindow is null)
{
return;
}
var horizontalMargin = Math.Clamp(cellSize * 0.7, 18, 44);
var bottomMargin = Math.Clamp(cellSize * 1.4, 56, 190);
var defaultMargin = new Thickness(horizontalMargin, 20, horizontalMargin, bottomMargin);
if (!_isComponentLibraryWindowPositionCustomized)
{
_savedComponentLibraryMargin = defaultMargin;
}
ComponentLibraryWindow.Margin = _savedComponentLibraryMargin;
ComponentLibraryWindow.CornerRadius = new CornerRadius(Math.Clamp(cellSize * 0.45, 24, 44));
ComponentLibraryWindow.Height = Math.Clamp(cellSize * 4.8, 220, 360);
ComponentLibraryWindow.Width = Math.Clamp(cellSize * 9.2, 360, 760);
}
private void UpdateSettingsViewportInsets(double cellSize)
{
_ = cellSize;
}
private void OnMinimizeClick(object? sender, RoutedEventArgs e)
{
if (_isSlideAnimationActive)
{
return;
}
SlideOutAndMinimizeAsync();
}
private TranslateTransform GetDesktopPageSlideTransform()
{
if (_desktopPageSlideTransform is not null)
{
return _desktopPageSlideTransform;
}
_desktopPageSlideTransform = DesktopPage.RenderTransform as TranslateTransform;
if (_desktopPageSlideTransform is null)
{
_desktopPageSlideTransform = new TranslateTransform();
DesktopPage.RenderTransform = _desktopPageSlideTransform;
}
return _desktopPageSlideTransform;
}
internal bool ShouldUseFullscreenWindow()
{
return GetStartupVisualPreferences().Mode != StartupVisualMode.SlideSplash;
}
internal void EnsureForegroundWindowLayout()
{
if (!IsSlideTransitionEnabled())
{
return;
}
var layout = ResolveWindowAnimationLayout();
ApplyWindowAnimationLayout(layout);
Position = layout.VisiblePosition;
}
private async void SlideOutAndMinimizeAsync()
{
_isSlideAnimationActive = true;
DesktopPage.IsHitTestVisible = false;
var preferences = GetStartupVisualPreferences();
WindowAnimationLayout? slideLayout = null;
if (preferences.Mode == StartupVisualMode.SlideSplash)
{
slideLayout = ResolveWindowAnimationLayout();
ApplyWindowAnimationLayout(slideLayout.Value);
await AnimateWindowPositionAsync(
Position,
slideLayout.Value.HiddenPosition,
FluttermotionToken.Intro).ConfigureAwait(false);
}
else if (preferences.Mode == StartupVisualMode.Fade)
{
DesktopPage.Opacity = 0;
await Task.Delay(FluttermotionToken.Page);
}
if (!_isSlideAnimationActive)
{
return;
}
var snapshot = _settingsService.LoadSnapshot<AppSettingsSnapshot>(SettingsScope.App);
if (snapshot.ShowInTaskbar)
{
WindowState = WindowState.Minimized;
}
else if (Application.Current is App app)
{
app.HideMainWindowToTray(this, "MinimizeAction");
}
else
{
WindowState = WindowState.Minimized;
}
DesktopPage.Opacity = 1;
DesktopPage.IsHitTestVisible = true;
_isSlideAnimationActive = false;
if (slideLayout is { } layout)
{
Position = layout.VisiblePosition;
}
}
public void PrepareEnterAnimation()
{
_isSlideAnimationActive = false;
var preferences = GetStartupVisualPreferences();
_preparedWindowTargetPosition = null;
_preparedWindowHiddenPosition = null;
if (preferences.Mode == StartupVisualMode.SlideSplash)
{
var layout = ResolveWindowAnimationLayout();
_preparedWindowTargetPosition = layout.VisiblePosition;
_preparedWindowHiddenPosition = layout.HiddenPosition;
ApplyWindowAnimationLayout(layout);
Position = layout.HiddenPosition;
DesktopPage.Opacity = 1;
DesktopPage.IsHitTestVisible = false;
_isSlideAnimationActive = true;
return;
}
if (preferences.Mode == StartupVisualMode.Fade)
{
var savedTransitions = DesktopPage.Transitions;
DesktopPage.Transitions = null;
DesktopPage.Opacity = 0;
DesktopPage.Transitions = savedTransitions;
DesktopPage.IsHitTestVisible = false;
_isSlideAnimationActive = true;
return;
}
DesktopPage.Opacity = 1;
DesktopPage.IsHitTestVisible = true;
}
public void PlayEnterAnimation()
{
var preferences = GetStartupVisualPreferences();
if (preferences.Mode == StartupVisualMode.SlideSplash &&
_preparedWindowTargetPosition is { } targetPosition &&
_preparedWindowHiddenPosition is { } hiddenPosition)
{
_ = PlayWindowEnterAnimationAsync(hiddenPosition, targetPosition);
return;
}
DesktopPage.Opacity = 1;
DesktopPage.IsHitTestVisible = true;
_isSlideAnimationActive = false;
}
private bool IsSlideTransitionEnabled()
{
if (!RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
{
return false;
}
return GetStartupVisualPreferences().Mode == StartupVisualMode.SlideSplash;
}
private StartupVisualPreferences GetStartupVisualPreferences()
{
var snapshot = _settingsService.LoadSnapshot<AppSettingsSnapshot>(SettingsScope.App);
return StartupVisualPreferencesResolver.FromFlags(
snapshot.EnableFadeTransition,
snapshot.EnableSlideTransition);
}
private WindowAnimationLayout ResolveWindowAnimationLayout()
{
var screen = Screens.ScreenFromVisual(this) ?? Screens.Primary ?? Screens.All.FirstOrDefault();
var workingArea = screen?.WorkingArea ?? new PixelRect(0, 0, 1920, 1080);
var scaling = Math.Max(screen?.Scaling ?? 1d, 0.01d);
return new WindowAnimationLayout(
new PixelPoint(workingArea.X, workingArea.Y),
new PixelPoint(workingArea.X + workingArea.Width, workingArea.Y),
new Size(workingArea.Width / scaling, workingArea.Height / scaling));
}
private void ApplyWindowAnimationLayout(WindowAnimationLayout layout)
{
WindowState = WindowState.Normal;
Width = layout.WindowSize.Width;
Height = layout.WindowSize.Height;
}
private async Task PlayWindowEnterAnimationAsync(PixelPoint hiddenPosition, PixelPoint targetPosition)
{
Position = hiddenPosition;
await AnimateWindowPositionAsync(hiddenPosition, targetPosition, FluttermotionToken.Intro);
DesktopPage.IsHitTestVisible = true;
_isSlideAnimationActive = false;
}
private async Task AnimateWindowPositionAsync(PixelPoint from, PixelPoint to, TimeSpan duration)
{
var totalMilliseconds = Math.Max(duration.TotalMilliseconds, 1d);
var stopwatch = Stopwatch.StartNew();
while (stopwatch.Elapsed < duration)
{
var progress = Math.Clamp(stopwatch.Elapsed.TotalMilliseconds / totalMilliseconds, 0d, 1d);
var eased = 1d - Math.Pow(1d - progress, 3d);
var x = (int)Math.Round(from.X + ((to.X - from.X) * eased));
var y = (int)Math.Round(from.Y + ((to.Y - from.Y) * eased));
Position = new PixelPoint(x, y);
await Task.Delay(16).ConfigureAwait(false);
}
Position = to;
}
private readonly record struct WindowAnimationLayout(
PixelPoint VisiblePosition,
PixelPoint HiddenPosition,
Size WindowSize);
private void OnWindowPropertyChanged(object? sender, AvaloniaPropertyChangedEventArgs e)
{
if (e.Property != WindowStateProperty)
{
return;
}
var newState = (WindowState)e.NewValue!;
var oldState = (WindowState)e.OldValue!;
if (oldState == WindowState.Minimized && newState != WindowState.Minimized)
{
PrepareEnterAnimation();
if (ShouldUseFullscreenWindow())
{
if (newState != WindowState.FullScreen)
{
WindowState = WindowState.FullScreen;
}
}
else if (newState == WindowState.Minimized)
{
WindowState = WindowState.Normal;
}
Dispatcher.UIThread.Post(() =>
{
PlayEnterAnimation();
}, DispatcherPriority.Background);
return;
}
if (newState == WindowState.Minimized ||
(ShouldUseFullscreenWindow() && newState == WindowState.FullScreen))
{
return;
}
if (_isSlideAnimationActive)
{
return;
}
Dispatcher.UIThread.Post(() =>
{
if (_isSlideAnimationActive)
{
return;
}
if (WindowState is not (WindowState.Minimized or WindowState.FullScreen))
{
if (ShouldUseFullscreenWindow())
{
WindowState = WindowState.FullScreen;
}
}
});
}
private void InitializeTimeZoneSettings()
{
if (TimeZoneComboBox is null)
{
return;
}
// Populate timezone dropdown items before selecting current timezone.
_suppressTimeZoneSelectionEvents = true;
TimeZoneComboBox.Items.Clear();
var timeZones = _timeZoneService.GetAllTimeZones();
foreach (var tz in timeZones)
{
var displayText = GetLocalizedTimeZoneDisplayName(tz);
var item = new ComboBoxItem
{
Content = displayText,
Tag = tz.Id
};
TimeZoneComboBox.Items.Add(item);
// Select current time zone.
if (tz.Id == _timeZoneService.CurrentTimeZone.Id)
{
TimeZoneComboBox.SelectedItem = item;
}
}
_suppressTimeZoneSelectionEvents = false;
}
private void OnTimeZoneSelectionChanged(object? sender, SelectionChangedEventArgs e)
{
if (TimeZoneComboBox is null ||
_suppressTimeZoneSelectionEvents ||
TimeZoneComboBox.SelectedItem is not ComboBoxItem item)
{
return;
}
var timeZoneId = item.Tag?.ToString();
if (string.IsNullOrEmpty(timeZoneId))
{
return;
}
_timeZoneService.SetTimeZoneById(timeZoneId);
PersistSettings();
}
}