setting_re3

This commit is contained in:
lincube
2026-03-13 09:10:00 +08:00
parent c4df243610
commit 3b3f060f33
70 changed files with 1986 additions and 8966 deletions

View File

@@ -9,10 +9,11 @@ using Avalonia.Styling;
using Avalonia.Threading;
using LanMountainDesktop.ComponentSystem;
using LanMountainDesktop.Services;
using LanMountainDesktop.Services.Settings;
namespace LanMountainDesktop.Views.Components;
public partial class AnalogClockWidget : UserControl, IDesktopComponentWidget, ITimeZoneAwareComponentWidget, IComponentPlacementContextAware, IComponentSettingsStoreAware
public partial class AnalogClockWidget : UserControl, IDesktopComponentWidget, ITimeZoneAwareComponentWidget, IComponentPlacementContextAware
{
private static readonly IReadOnlyDictionary<string, string> ZhCityNames =
new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase)
@@ -58,8 +59,7 @@ public partial class AnalogClockWidget : UserControl, IDesktopComponentWidget, I
private string _componentId = BuiltInComponentIds.DesktopClock;
private string _placementId = string.Empty;
private readonly AppSettingsService _appSettingsService = new();
private IComponentInstanceSettingsStore _componentSettingsStore = new ComponentSettingsService();
private readonly ISettingsService _settingsService = HostSettingsFacadeProvider.GetOrCreate().Settings;
private readonly LocalizationService _localizationService = new();
private TimeZoneService? _timeZoneService;
private double _currentCellSize = 48;
@@ -124,12 +124,6 @@ public partial class AnalogClockWidget : UserControl, IDesktopComponentWidget, I
RefreshFromSettings();
}
public void SetComponentSettingsStore(IComponentInstanceSettingsStore settingsStore)
{
_componentSettingsStore = settingsStore ?? new ComponentSettingsService();
RefreshFromSettings();
}
private void OnAttachedToVisualTree(object? sender, VisualTreeAttachmentEventArgs e)
{
InitializeDialIfNeeded();
@@ -376,8 +370,11 @@ public partial class AnalogClockWidget : UserControl, IDesktopComponentWidget, I
private void LoadClockSettings()
{
var appSnapshot = _appSettingsService.Load();
var componentSnapshot = _componentSettingsStore.LoadForComponent(_componentId, _placementId);
var appSnapshot = _settingsService.LoadSnapshot<AppSettingsSnapshot>(SettingsScope.App);
var componentSnapshot = _settingsService.LoadSnapshot<ComponentSettingsSnapshot>(
SettingsScope.ComponentInstance,
_componentId,
_placementId);
_languageCode = _localizationService.NormalizeLanguageCode(appSnapshot.LanguageCode);
var configuredTimeZoneId = string.IsNullOrWhiteSpace(componentSnapshot.DesktopClockTimeZoneId)

View File

@@ -10,10 +10,11 @@ using Avalonia.Threading;
using LanMountainDesktop.ComponentSystem;
using LanMountainDesktop.Models;
using LanMountainDesktop.Services;
using LanMountainDesktop.Services.Settings;
namespace LanMountainDesktop.Views.Components;
public partial class ClassScheduleWidget : UserControl, IDesktopComponentWidget, ITimeZoneAwareComponentWidget, IComponentPlacementContextAware, IComponentSettingsStoreAware
public partial class ClassScheduleWidget : UserControl, IDesktopComponentWidget, ITimeZoneAwareComponentWidget, IComponentPlacementContextAware
{
private sealed record CourseItemViewModel(
string Name,
@@ -26,8 +27,7 @@ public partial class ClassScheduleWidget : UserControl, IDesktopComponentWidget,
Interval = TimeSpan.FromMinutes(4)
};
private readonly AppSettingsService _appSettingsService = new();
private IComponentInstanceSettingsStore _componentSettingsStore = new ComponentSettingsService();
private readonly ISettingsService _settingsService = HostSettingsFacadeProvider.GetOrCreate().Settings;
private readonly LocalizationService _localizationService = new();
private readonly IClassIslandScheduleDataService _scheduleService = new ClassIslandScheduleDataService();
@@ -125,16 +125,13 @@ public partial class ClassScheduleWidget : UserControl, IDesktopComponentWidget,
RefreshSchedule();
}
public void SetComponentSettingsStore(IComponentInstanceSettingsStore settingsStore)
{
_componentSettingsStore = settingsStore ?? new ComponentSettingsService();
RefreshSchedule();
}
private void RefreshSchedule()
{
var appSettings = _appSettingsService.Load();
var componentSettings = _componentSettingsStore.LoadForComponent(_componentId, _placementId);
var appSettings = _settingsService.LoadSnapshot<AppSettingsSnapshot>(SettingsScope.App);
var componentSettings = _settingsService.LoadSnapshot<ComponentSettingsSnapshot>(
SettingsScope.ComponentInstance,
_componentId,
_placementId);
_languageCode = _localizationService.NormalizeLanguageCode(appSettings.LanguageCode);
var now = _timeZoneService?.GetCurrentTime() ?? DateTime.Now;
UpdateHeader(now);

View File

@@ -16,10 +16,11 @@ using Avalonia.Threading;
using LanMountainDesktop.ComponentSystem;
using LanMountainDesktop.Models;
using LanMountainDesktop.Services;
using LanMountainDesktop.Services.Settings;
namespace LanMountainDesktop.Views.Components;
public partial class DailyArtworkWidget : UserControl, IDesktopComponentWidget, IRecommendationInfoAwareComponentWidget, IComponentPlacementContextAware, IComponentSettingsStoreAware
public partial class DailyArtworkWidget : UserControl, IDesktopComponentWidget, IRecommendationInfoAwareComponentWidget, IComponentPlacementContextAware
{
private static readonly IReadOnlyDictionary<DayOfWeek, string> ZhWeekdays =
new Dictionary<DayOfWeek, string>
@@ -58,8 +59,7 @@ public partial class DailyArtworkWidget : UserControl, IDesktopComponentWidget,
Interval = TimeSpan.FromHours(6)
};
private readonly AppSettingsService _settingsService = new();
private IComponentInstanceSettingsStore _componentSettingsStore = new ComponentSettingsService();
private readonly ISettingsService _settingsService = HostSettingsFacadeProvider.GetOrCreate().Settings;
private readonly LocalizationService _localizationService = new();
private IRecommendationInfoService _recommendationService = DefaultRecommendationService;
@@ -149,12 +149,6 @@ public partial class DailyArtworkWidget : UserControl, IDesktopComponentWidget,
RefreshFromSettings();
}
public void SetComponentSettingsStore(IComponentInstanceSettingsStore settingsStore)
{
_componentSettingsStore = settingsStore ?? new ComponentSettingsService();
RefreshFromSettings();
}
private void OnAttachedToVisualTree(object? sender, VisualTreeAttachmentEventArgs e)
{
_isAttached = true;
@@ -651,7 +645,7 @@ public partial class DailyArtworkWidget : UserControl, IDesktopComponentWidget,
{
try
{
var snapshot = _settingsService.Load();
var snapshot = _settingsService.LoadSnapshot<AppSettingsSnapshot>(SettingsScope.App);
_languageCode = _localizationService.NormalizeLanguageCode(snapshot.LanguageCode);
}
catch
@@ -664,7 +658,10 @@ public partial class DailyArtworkWidget : UserControl, IDesktopComponentWidget,
{
try
{
var snapshot = _componentSettingsStore.LoadForComponent(_componentId, _placementId);
var snapshot = _settingsService.LoadSnapshot<ComponentSettingsSnapshot>(
SettingsScope.ComponentInstance,
_componentId,
_placementId);
return DailyArtworkMirrorSources.Normalize(snapshot.DailyArtworkMirrorSource);
}
catch

View File

@@ -3,7 +3,9 @@ using System.Collections.Generic;
using System.Linq;
using Avalonia.Controls;
using LanMountainDesktop.ComponentSystem;
using LanMountainDesktop.PluginSdk;
using LanMountainDesktop.Services;
using LanMountainDesktop.Services.Settings;
namespace LanMountainDesktop.Views.Components;
@@ -14,7 +16,8 @@ public sealed record DesktopComponentControlFactoryContext(
IWeatherInfoService WeatherInfoService,
IRecommendationInfoService RecommendationInfoService,
ICalculatorDataService CalculatorDataService,
IComponentInstanceSettingsStore ComponentSettingsStore,
ISettingsService SettingsService,
IComponentSettingsAccessor ComponentSettingsAccessor,
string? PlacementId = null);
public sealed class DesktopComponentRuntimeRegistration
@@ -84,9 +87,10 @@ public sealed class DesktopComponentRuntimeDescriptor
IWeatherInfoService weatherInfoService,
IRecommendationInfoService recommendationInfoService,
ICalculatorDataService calculatorDataService,
IComponentInstanceSettingsStore componentSettingsStore,
string? placementId = null)
{
var settingsService = HostSettingsFacadeProvider.GetOrCreate().Settings;
var componentAccessor = settingsService.GetComponentAccessor(Definition.Id, placementId);
var control = _controlFactory(new DesktopComponentControlFactoryContext(
Definition,
cellSize,
@@ -94,12 +98,14 @@ public sealed class DesktopComponentRuntimeDescriptor
weatherInfoService,
recommendationInfoService,
calculatorDataService,
componentSettingsStore,
settingsService,
componentAccessor,
placementId));
var runtimeContext = new DesktopComponentRuntimeContext(
Definition.Id,
placementId,
componentSettingsStore);
settingsService,
componentAccessor);
if (control is IComponentRuntimeContextAware runtimeContextAwareComponent)
{
@@ -111,13 +117,6 @@ public sealed class DesktopComponentRuntimeDescriptor
placementAwareComponent.SetComponentPlacementContext(Definition.Id, placementId);
}
if (control is IComponentSettingsStoreAware settingsStoreAwareComponent)
{
settingsStoreAwareComponent.SetComponentSettingsStore(componentSettingsStore);
}
ComponentSettingsService.ApplyScopedContextToTarget(control, Definition.Id, placementId);
if (control is IDesktopComponentWidget sizedComponent)
{
sizedComponent.ApplyCellSize(cellSize);

View File

@@ -12,11 +12,12 @@ using Avalonia.Threading;
using LanMountainDesktop.ComponentSystem;
using LanMountainDesktop.Models;
using LanMountainDesktop.Services;
using LanMountainDesktop.Services.Settings;
using LanMountainDesktop.Theme;
namespace LanMountainDesktop.Views.Components;
public partial class ExtendedWeatherWidget : UserControl, IDesktopComponentWidget, IDesktopPageVisibilityAwareComponentWidget, ITimeZoneAwareComponentWidget, IWeatherInfoAwareComponentWidget, IComponentPlacementContextAware, IComponentSettingsStoreAware
public partial class ExtendedWeatherWidget : UserControl, IDesktopComponentWidget, IDesktopPageVisibilityAwareComponentWidget, ITimeZoneAwareComponentWidget, IWeatherInfoAwareComponentWidget, IComponentPlacementContextAware
{
private static readonly IWeatherInfoService DefaultWeatherInfoService = new XiaomiWeatherService();
private static readonly IReadOnlyList<int> SupportedAutoRefreshIntervalsMinutes = RefreshIntervalCatalog.SupportedIntervalsMinutes;
@@ -25,8 +26,7 @@ public partial class ExtendedWeatherWidget : UserControl, IDesktopComponentWidge
private readonly DispatcherTimer _animationTimer = new() { Interval = FluttermotionToken.WeatherAnimationFrameInterval };
private readonly ScaleTransform _backgroundMotionScaleTransform = new(1, 1);
private readonly TranslateTransform _backgroundMotionTranslateTransform = new();
private readonly AppSettingsService _settingsService = new();
private IComponentInstanceSettingsStore _componentSettingsStore = new ComponentSettingsService();
private readonly ISettingsService _settingsService = HostSettingsFacadeProvider.GetOrCreate().Settings;
private readonly LocalizationService _localizationService = new();
private IWeatherInfoService _weatherInfoService = DefaultWeatherInfoService;
@@ -185,12 +185,6 @@ public partial class ExtendedWeatherWidget : UserControl, IDesktopComponentWidge
RefreshFromSettings();
}
public void SetComponentSettingsStore(IComponentInstanceSettingsStore settingsStore)
{
_componentSettingsStore = settingsStore ?? new ComponentSettingsService();
RefreshFromSettings();
}
public void SetDesktopPageContext(bool isOnActivePage, bool isEditMode)
{
_ = isEditMode;
@@ -265,7 +259,7 @@ public partial class ExtendedWeatherWidget : UserControl, IDesktopComponentWidge
}
_isRefreshing = true;
var app = _settingsService.Load();
var app = _settingsService.LoadSnapshot<AppSettingsSnapshot>(SettingsScope.App);
_languageCode = _localizationService.NormalizeLanguageCode(app.LanguageCode);
var locale = string.Equals(_languageCode, "zh-CN", StringComparison.OrdinalIgnoreCase) ? "zh_cn" : "en_us";
var latitude = double.IsFinite(app.WeatherLatitude) ? Math.Clamp(app.WeatherLatitude, -90, 90) : 39.9042;
@@ -953,7 +947,10 @@ public partial class ExtendedWeatherWidget : UserControl, IDesktopComponentWidge
try
{
var snapshot = _componentSettingsStore.LoadForComponent(_componentId, _placementId);
var snapshot = _settingsService.LoadSnapshot<ComponentSettingsSnapshot>(
SettingsScope.ComponentInstance,
_componentId,
_placementId);
enabled = snapshot.WeatherAutoRefreshEnabled;
intervalMinutes = NormalizeAutoRefreshIntervalMinutes(snapshot.WeatherAutoRefreshIntervalMinutes);
}

View File

@@ -18,7 +18,7 @@ using LanMountainDesktop.Theme;
namespace LanMountainDesktop.Views.Components;
public partial class HourlyWeatherWidget : UserControl, IDesktopComponentWidget, IDesktopPageVisibilityAwareComponentWidget, ITimeZoneAwareComponentWidget, IWeatherInfoAwareComponentWidget, IComponentPlacementContextAware, IComponentSettingsStoreAware
public partial class HourlyWeatherWidget : UserControl, IDesktopComponentWidget, IDesktopPageVisibilityAwareComponentWidget, ITimeZoneAwareComponentWidget, IWeatherInfoAwareComponentWidget, IComponentPlacementContextAware
{
private enum WeatherVisualKind
{
@@ -236,12 +236,6 @@ public partial class HourlyWeatherWidget : UserControl, IDesktopComponentWidget,
RefreshFromSettings();
}
public void SetComponentSettingsStore(IComponentInstanceSettingsStore settingsStore)
{
_componentSettingsStore = settingsStore ?? new ComponentSettingsService();
RefreshFromSettings();
}
public void SetDesktopPageContext(bool isOnActivePage, bool isEditMode)
{
_ = isEditMode;

View File

@@ -16,7 +16,7 @@ using LanMountainDesktop.Theme;
namespace LanMountainDesktop.Views.Components;
public partial class MultiDayWeatherWidget : UserControl, IDesktopComponentWidget, IDesktopPageVisibilityAwareComponentWidget, ITimeZoneAwareComponentWidget, IWeatherInfoAwareComponentWidget, IComponentPlacementContextAware, IComponentSettingsStoreAware
public partial class MultiDayWeatherWidget : UserControl, IDesktopComponentWidget, IDesktopPageVisibilityAwareComponentWidget, ITimeZoneAwareComponentWidget, IWeatherInfoAwareComponentWidget, IComponentPlacementContextAware
{
private enum WeatherVisualKind
{
@@ -234,12 +234,6 @@ public partial class MultiDayWeatherWidget : UserControl, IDesktopComponentWidge
RefreshFromSettings();
}
public void SetComponentSettingsStore(IComponentInstanceSettingsStore settingsStore)
{
_componentSettingsStore = settingsStore ?? new ComponentSettingsService();
RefreshFromSettings();
}
public void SetDesktopPageContext(bool isOnActivePage, bool isEditMode)
{
_ = isEditMode;

View File

@@ -16,7 +16,7 @@ using LanMountainDesktop.Services;
namespace LanMountainDesktop.Views.Components;
public partial class WeatherClockWidget : UserControl, IDesktopComponentWidget, ITimeZoneAwareComponentWidget, IWeatherInfoAwareComponentWidget, IComponentPlacementContextAware, IComponentSettingsStoreAware
public partial class WeatherClockWidget : UserControl, IDesktopComponentWidget, ITimeZoneAwareComponentWidget, IWeatherInfoAwareComponentWidget, IComponentPlacementContextAware
{
private sealed record WeatherClockConfig(
string LanguageCode,
@@ -128,12 +128,6 @@ public partial class WeatherClockWidget : UserControl, IDesktopComponentWidget,
RefreshFromSettings();
}
public void SetComponentSettingsStore(IComponentInstanceSettingsStore settingsStore)
{
_componentSettingsStore = settingsStore ?? new ComponentSettingsService();
RefreshFromSettings();
}
public void ApplyCellSize(double cellSize)
{
_currentCellSize = Math.Max(1, cellSize);

View File

@@ -18,7 +18,7 @@ using LanMountainDesktop.Theme;
namespace LanMountainDesktop.Views.Components;
public partial class WeatherWidget : UserControl, IDesktopComponentWidget, IDesktopPageVisibilityAwareComponentWidget, ITimeZoneAwareComponentWidget, IWeatherInfoAwareComponentWidget, IComponentPlacementContextAware, IComponentSettingsStoreAware
public partial class WeatherWidget : UserControl, IDesktopComponentWidget, IDesktopPageVisibilityAwareComponentWidget, ITimeZoneAwareComponentWidget, IWeatherInfoAwareComponentWidget, IComponentPlacementContextAware
{
private enum WeatherVisualKind
{
@@ -179,12 +179,6 @@ public partial class WeatherWidget : UserControl, IDesktopComponentWidget, IDesk
RefreshFromSettings();
}
public void SetComponentSettingsStore(IComponentInstanceSettingsStore settingsStore)
{
_componentSettingsStore = settingsStore ?? new ComponentSettingsService();
RefreshFromSettings();
}
public void SetDesktopPageContext(bool isOnActivePage, bool isEditMode)
{
_ = isEditMode;

View File

@@ -13,7 +13,7 @@ using LanMountainDesktop.Services;
namespace LanMountainDesktop.Views.Components;
public partial class WorldClockWidget : UserControl, IDesktopComponentWidget, ITimeZoneAwareComponentWidget, IComponentPlacementContextAware, IComponentSettingsStoreAware
public partial class WorldClockWidget : UserControl, IDesktopComponentWidget, ITimeZoneAwareComponentWidget, IComponentPlacementContextAware
{
private const int BaseWidthCells = 4;
private const int BaseHeightCells = 2;
@@ -159,12 +159,6 @@ public partial class WorldClockWidget : UserControl, IDesktopComponentWidget, IT
RefreshFromSettings();
}
public void SetComponentSettingsStore(IComponentInstanceSettingsStore settingsStore)
{
_componentSettingsStore = settingsStore ?? new ComponentSettingsService();
RefreshFromSettings();
}
public void ApplyCellSize(double cellSize)
{
_currentCellSize = Math.Max(1, cellSize);

View File

@@ -101,19 +101,12 @@ public partial class MainWindow
private void OnOpenComponentLibraryClick(object? sender, RoutedEventArgs e)
{
// "Desktop edit" toggle. While editing, show the component library window.
if (_isComponentLibraryOpen)
{
CloseComponentLibraryWindow(reopenSettings: false);
return;
}
OpenComponentLibraryWindow();
_componentLibraryWindowService.Toggle(this);
}
private void OnCloseComponentLibraryClick(object? sender, RoutedEventArgs e)
{
CloseComponentLibraryWindow(reopenSettings: false);
_componentLibraryWindowService.Close(this);
}
private void OnCloseComponentSettingsClick(object? sender, RoutedEventArgs e)
@@ -202,21 +195,6 @@ public partial class MainWindow
{
ClockWidget.SetDisplayFormat(_clockDisplayFormat);
}
if (_clockDisplayFormat == ClockDisplayFormat.HourMinute)
{
if (ClockFormatHMRadio is not null)
{
ClockFormatHMRadio.IsChecked = true;
}
}
else
{
if (ClockFormatHMSSRadio is not null)
{
ClockFormatHMSSRadio.IsChecked = true;
}
}
}
private void ApplyTopStatusComponentVisibility()
@@ -233,15 +211,6 @@ public partial class MainWindow
Grid.SetColumnSpan(ClockWidget, columnSpan);
}
}
if (WallpaperPreviewClockWidget is not null)
{
WallpaperPreviewClockWidget.IsVisible = showClock;
if (showClock)
{
WallpaperPreviewClockWidget.SetDisplayFormat(_clockDisplayFormat);
}
}
}
private TaskbarContext GetCurrentTaskbarContext()
@@ -251,11 +220,7 @@ public partial class MainWindow
private void ApplyTaskbarActionVisibility(TaskbarContext context)
{
if (BackToWindowsButton is null ||
OpenComponentLibraryButton is null ||
WallpaperPreviewBackButtonVisual is null ||
WallpaperPreviewComponentLibraryVisual is null ||
WallpaperPreviewSettingsButtonIcon is null)
if (BackToWindowsButton is null || OpenComponentLibraryButton is null)
{
return;
}
@@ -266,9 +231,6 @@ public partial class MainWindow
BackToWindowsButton.IsVisible = showMinimize;
OpenComponentLibraryButton.IsVisible = showDesktopEdit;
WallpaperPreviewBackButtonVisual.IsVisible = showMinimize;
WallpaperPreviewComponentLibraryVisual.IsVisible = showDesktopEdit;
WallpaperPreviewSettingsButtonIcon.IsVisible = showSettings;
if (TaskbarFixedActionsHost is not null)
{
@@ -280,16 +242,6 @@ public partial class MainWindow
TaskbarSettingsActionHost.IsVisible = showSettings || showDesktopEdit;
}
if (WallpaperPreviewTaskbarFixedActionsHost is not null)
{
WallpaperPreviewTaskbarFixedActionsHost.IsVisible = showMinimize;
}
if (WallpaperPreviewTaskbarSettingsActionHost is not null)
{
WallpaperPreviewTaskbarSettingsActionHost.IsVisible = showSettings || showDesktopEdit;
}
var dynamicActions = ResolveDynamicTaskbarActions(context)
.Where(action => action.IsVisible)
.ToList();
@@ -300,11 +252,6 @@ public partial class MainWindow
{
TaskbarDynamicActionsHost.IsVisible = hasDynamicActions;
}
if (WallpaperPreviewTaskbarDynamicActionsHost is not null)
{
WallpaperPreviewTaskbarDynamicActionsHost.IsVisible = hasDynamicActions;
}
}
private void UpdateOpenSettingsActionVisualState()
@@ -455,14 +402,7 @@ public partial class MainWindow
TaskbarDynamicActionsPanel.Children.Clear();
}
if (WallpaperPreviewTaskbarDynamicActionsHost is not null)
{
WallpaperPreviewTaskbarDynamicActionsHost.Children.Clear();
}
if (actions.Count == 0 ||
TaskbarDynamicActionsPanel is null ||
WallpaperPreviewTaskbarDynamicActionsHost is null)
if (actions.Count == 0 || TaskbarDynamicActionsPanel is null)
{
return;
}
@@ -556,37 +496,6 @@ public partial class MainWindow
TaskbarDynamicActionsPanel.Children.Add(button);
Control previewIcon = new SymbolIcon
{
Symbol = iconSymbol,
IconVariant = IconVariant.Regular,
FontSize = iconSize * 0.85
};
var previewText = new TextBlock
{
Text = action.Title,
FontSize = fontSize * 0.85,
Foreground = (isDeleteAction || isHideAction)
? new SolidColorBrush(Color.Parse("#FFFF6B6B"))
: Foreground,
VerticalAlignment = Avalonia.Layout.VerticalAlignment.Center
};
var previewContent = new StackPanel
{
Orientation = Orientation.Horizontal,
Spacing = spacing * 0.5,
Children = { previewIcon, previewText }
};
var previewBorder = new Border
{
Background = Brushes.Transparent,
BorderThickness = new Thickness(0),
Child = previewContent
};
WallpaperPreviewTaskbarDynamicActionsHost.Children.Add(previewBorder);
}
}
@@ -637,7 +546,7 @@ public partial class MainWindow
}
_desktopComponentPlacements.Remove(placement);
_componentSettingsService.DeleteForComponent(placement.ComponentId, placement.PlacementId);
_componentSettingsStore.DeleteForComponent(placement.ComponentId, placement.PlacementId);
ClearDesktopComponentSelection();
@@ -698,7 +607,7 @@ public partial class MainWindow
foreach (var placement in placementsToRemove)
{
_desktopComponentPlacements.Remove(placement);
_componentSettingsService.DeleteForComponent(placement.ComponentId, placement.PlacementId);
_componentSettingsStore.DeleteForComponent(placement.ComponentId, placement.PlacementId);
}
_desktopPageCount = Math.Clamp(_desktopPageCount - 1, MinDesktopPageCount, MaxDesktopPageCount);
@@ -1345,14 +1254,19 @@ public partial class MainWindow
{
try
{
var component = runtimeDescriptor.CreateControl(
var createContext = new ComponentLibraryCreateContext(
cellSize,
_timeZoneService,
_weatherDataService,
_recommendationInfoService,
_calculatorDataService,
_componentSettingsService,
placementId);
if (!_componentLibraryService.TryCreateControl(runtimeDescriptor.Definition.Id, createContext, out var component, out var exception) ||
component is null)
{
throw exception ?? new InvalidOperationException("Component library service returned no control.");
}
component.Classes.Add(DesktopComponentClass);
return component;
}
@@ -1376,6 +1290,18 @@ public partial class MainWindow
}
}
internal bool IsComponentLibraryOpenFromService => _isComponentLibraryOpen;
internal void OpenComponentLibraryWindowFromService()
{
OpenComponentLibraryWindow();
}
internal void CloseComponentLibraryWindowFromService()
{
CloseComponentLibraryWindow(reopenSettings: false);
}
private void CollapseComponentLibraryPanel()
{
// Animate component library panel collapsing downward

View File

@@ -1,456 +0,0 @@
using System;
using System.Collections.Generic;
using System.Linq;
using Avalonia.Controls;
using Avalonia.Interactivity;
using Avalonia.Layout;
using FluentIcons.Avalonia;
using FluentIcons.Common;
using LanMountainDesktop.Services;
namespace LanMountainDesktop.Views;
public partial class MainWindow
{
private const string AppCodeName = "Administrate";
private const string AppFontName = "MiSans";
private const string FallbackAppVersion = "1.0.0";
private static readonly IReadOnlyDictionary<string, string> ZhTimeZoneNames =
new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase)
{
["China Standard Time"] = "中国标准时间",
["Asia/Shanghai"] = "中国标准时间",
["Tokyo Standard Time"] = "日本标准时间",
["Asia/Tokyo"] = "日本标准时间",
["Pacific Standard Time"] = "太平洋标准时间",
["America/Los_Angeles"] = "太平洋标准时间",
["Eastern Standard Time"] = "美国东部标准时间",
["America/New_York"] = "美国东部标准时间",
["Central European Standard Time"] = "中欧标准时间",
["Europe/Berlin"] = "中欧标准时间",
["GMT Standard Time"] = "格林威治标准时间",
["Europe/London"] = "格林威治标准时间",
["UTC"] = "协调世界时",
["Etc/UTC"] = "协调世界时"
};
private void InitializeLocalization(string? languageCode)
{
_languageCode = _localizationService.NormalizeLanguageCode(languageCode);
if (LanguageComboBox is null)
{
return;
}
_suppressLanguageSelectionEvents = true;
LanguageComboBox.SelectedIndex = string.Equals(_languageCode, "en-US", StringComparison.OrdinalIgnoreCase) ? 1 : 0;
_suppressLanguageSelectionEvents = false;
}
private string L(string key, string fallback)
{
return _localizationService.GetString(_languageCode, key, fallback);
}
private string Lf(string key, string fallback, params object[] args)
{
var template = L(key, fallback);
return string.Format(template, args);
}
private string GetLanguageDisplayName(string languageCode)
{
return string.Equals(languageCode, "en-US", StringComparison.OrdinalIgnoreCase)
? L("settings.region.language_en", "English")
: L("settings.region.language_zh", "Chinese");
}
private string GetLocalizedPlacementDisplayName(WallpaperPlacement placement)
{
return placement switch
{
WallpaperPlacement.Fill => L("placement.fill", "Fill"),
WallpaperPlacement.Fit => L("placement.fit", "Fit"),
WallpaperPlacement.Stretch => L("placement.stretch", "Stretch"),
WallpaperPlacement.Center => L("placement.center", "Center"),
WallpaperPlacement.Tile => L("placement.tile", "Tile"),
_ => L("placement.fill", "Fill")
};
}
private void ApplyLocalization()
{
Title = L("app.title", "LanMountainDesktop");
BackToWindowsTextBlock.Text = L("button.back_to_windows", "Back to Windows");
WallpaperPreviewBackButtonTextBlock.Text = L("button.back_to_windows", "Back to Windows");
ToolTip.SetTip(BackToWindowsButton, L("tooltip.back_to_windows", "Back to Windows"));
OpenComponentLibraryTextBlock.Text = L("button.component_library", "Edit Desktop");
WallpaperPreviewComponentLibraryTextBlock.Text = L("button.component_library", "Edit Desktop");
GridPreviewComponentLibraryTextBlock.Text = L("button.component_library", "Edit Desktop");
ToolTip.SetTip(OpenComponentLibraryButton, L("tooltip.component_library", "Edit Desktop"));
ComponentLibraryTitleTextBlock.Text = L("component_library.title", "Widgets");
ToolTip.SetTip(CloseComponentLibraryButton, L("common.close", "Close"));
ComponentLibraryEmptyTextBlock.Text = L(
"component_library.empty",
"Swipe to pick a category, tap to open, then drag a widget onto the desktop.");
LauncherTitleTextBlock.Text = L("launcher.title", "App Launcher");
LauncherSubtitleTextBlock.Text = OperatingSystem.IsLinux()
? L(
"launcher.subtitle_linux",
"Displays installed apps discovered from Linux desktop entries.")
: L(
"launcher.subtitle",
"Displays all apps and folders based on the Windows Start menu structure.");
ToolTip.SetTip(LauncherFolderBackButton, L("common.back", "Back"));
ToolTip.SetTip(LauncherFolderCloseButton, L("common.close", "Close"));
// SettingsNavHeaderTextBlock no longer exists
SettingsNavWallpaperItem.Content = L("settings.nav.wallpaper", "Wallpaper");
SettingsNavGridItem.Content = L("settings.nav.grid", "Grid");
SettingsNavColorItem.Content = L("settings.nav.color", "Color");
SettingsNavStatusBarItem.Content = L("settings.nav.status_bar", "Status Bar");
SettingsNavWeatherItem.Content = L("settings.nav.weather", "Weather");
SettingsNavRegionItem.Content = L("settings.nav.region", "Region");
SettingsNavUpdateItem.Content = L("settings.nav.update", "Update");
SettingsNavLauncherItem.Content = L("settings.nav.launcher", "App Launcher");
SettingsNavPluginsItem.Content = L("settings.nav.plugins", "Plugins");
SettingsNavPluginMarketItem.Content = L("settings.nav.plugin_market", "Plugin Market");
WallpaperPanelTitleTextBlock.Text = L("settings.wallpaper.title", "Personalize your wallpaper");
WallpaperPlacementSettingsExpander.Header = L("settings.wallpaper.placement_label", "Placement");
WallpaperPlacementSettingsExpander.Description = L(
"settings.wallpaper.placement_desc",
"Adjust how the image fits on the desktop.");
PickWallpaperButton.Content = L("settings.wallpaper.pick_button", "Browse");
ClearWallpaperButton.Content = L("settings.wallpaper.clear_button", "Reset");
GridPanelTitleTextBlock.Text = L("settings.grid.title", "Grid Layout");
GridSpacingSettingsExpander.Header = L("settings.grid.spacing_label", "Grid Spacing");
GridSpacingRelaxedComboBoxItem.Content = L("settings.grid.spacing_relaxed", "Relaxed");
GridSpacingCompactComboBoxItem.Content = L("settings.grid.spacing_compact", "Compact");
GridEdgeInsetSettingsExpander.Header = L("settings.grid.edge_inset_label", "Screen Inset");
ApplyGridButton.Content = L("settings.grid.apply_button", "Apply");
UpdateGridEdgeInsetComputedPxText(_currentDesktopCellSize);
ColorPanelTitleTextBlock.Text = L("settings.color.title", "Color");
ThemeModeSettingsExpander.Header = L("settings.color.day_night_label", "Day/Night");
NightModeToggleSwitch.OffContent = new StackPanel
{
Orientation = Orientation.Horizontal,
Spacing = 6,
Children =
{
new SymbolIcon { Symbol = Symbol.WeatherSunny, IconVariant = IconVariant.Regular, FontSize = 14 },
new TextBlock
{
Text = L("settings.color.day_night_off", "Day"),
VerticalAlignment = Avalonia.Layout.VerticalAlignment.Center
}
}
};
NightModeToggleSwitch.OnContent = new StackPanel
{
Orientation = Orientation.Horizontal,
Spacing = 6,
Children =
{
new SymbolIcon { Symbol = Symbol.WeatherMoon, IconVariant = IconVariant.Regular, FontSize = 14 },
new TextBlock
{
Text = L("settings.color.day_night_on", "Night"),
VerticalAlignment = Avalonia.Layout.VerticalAlignment.Center
}
}
};
RecommendedColorsLabelTextBlock.Text = L("settings.color.recommended_label", "Recommended Colors");
SystemMonetColorsLabelTextBlock.Text = L("settings.color.system_monet_label", "System Monet Colors");
RefreshMonetColorsButton.Content = L("settings.color.refresh_button", "Refresh");
StatusBarPanelTitleTextBlock.Text = L("settings.status_bar.title", "Status Bar");
StatusBarClockSettingsExpander.Header = L("settings.status_bar.clock_header", "Clock");
StatusBarSpacingSettingsExpander.Header = L("settings.status_bar.spacing_header", "Component Spacing");
StatusBarSpacingSettingsExpander.Description = L("settings.status_bar.spacing_desc", "Adjust spacing between status bar components.");
StatusBarSpacingModeCompactItem.Content = L("settings.status_bar.spacing_mode_compact", "Compact");
StatusBarSpacingModeRelaxedItem.Content = L("settings.status_bar.spacing_mode_relaxed", "Relaxed");
StatusBarSpacingModeCustomItem.Content = L("settings.status_bar.spacing_mode_custom", "Custom");
StatusBarSpacingCustomPanel.Content = L("settings.status_bar.spacing_custom_label", "Custom spacing (%)");
WeatherPanelTitleTextBlock.Text = L("settings.weather.title", "Weather");
WeatherPreviewSectionTextBlock.Text = L("settings.weather.preview_section", "Weather Preview");
WeatherSettingsSectionTextBlock.Text = L("settings.weather.settings_section", "Settings");
WeatherPreviewSettingsExpander.Header = L("settings.weather.preview_panel_header", "Weather Preview");
WeatherPreviewSettingsExpander.Description = L(
"settings.weather.preview_panel_desc",
"Refresh and verify current weather service status.");
WeatherPreviewButton.Content = L("settings.weather.refresh_button", "Refresh");
WeatherLocationSettingsExpander.Header = L("settings.weather.location_source_header", "Location Source");
WeatherLocationSettingsExpander.Description = L(
"settings.weather.location_source_desc",
"Choose how weather widgets resolve location.");
WeatherLocationModeCityItem.Content = L("settings.weather.mode_city_search", "City Search");
WeatherLocationModeCoordinatesItem.Content = L("settings.weather.mode_coordinates", "Coordinates");
WeatherLocationModeCityChipItem.Content = L("settings.weather.mode_city_search", "City Search");
WeatherLocationModeCoordinatesChipItem.Content = L("settings.weather.mode_coordinates", "Coordinates");
WeatherAutoRefreshToggleSwitch.Content = L("settings.weather.auto_refresh", "Auto refresh location on startup");
WeatherLocationSelectionTitleTextBlock.Text = L("settings.weather.city_selection_label", "City Selection");
WeatherLocationSelectionDescriptionTextBlock.Text = L(
"settings.weather.location_city_summary_desc",
"Select the current city used for weather queries.");
WeatherCitySearchSettingsExpander.Header = L("settings.weather.city_search_header", "City Search");
WeatherCitySearchSettingsExpander.Description = L(
"settings.weather.city_search_desc",
"Search cities and apply one weather location.");
WeatherCitySearchTextBox.Watermark = L("settings.weather.search_placeholder", "e.g. Beijing");
WeatherSearchButton.Content = L("settings.weather.search_button", "Search");
WeatherApplyCityButton.Content = L("settings.weather.apply_city_button", "Apply City");
WeatherCoordinateSettingsExpander.Header = L("settings.weather.coordinates_header", "Coordinates");
WeatherCoordinateSettingsExpander.Description = L(
"settings.weather.coordinates_desc",
"Set latitude/longitude and optional key/name.");
WeatherLatitudeNumberBox.Header = L("settings.weather.latitude_label", "Latitude");
WeatherLongitudeNumberBox.Header = L("settings.weather.longitude_label", "Longitude");
WeatherLocationKeyTextBox.Watermark = L("settings.weather.location_key_placeholder", "Location key (optional)");
WeatherLocationNameTextBox.Watermark = L("settings.weather.location_name_placeholder", "Display name (optional)");
WeatherApplyCoordinatesButton.Content = L("settings.weather.apply_coordinates_button", "Apply Coordinates");
WeatherAlertFilterSettingsExpander.Header = L("settings.weather.alert_filter_header", "Excluded Alerts");
WeatherAlertFilterSettingsExpander.Description = L(
"settings.weather.alert_filter_desc",
"Alerts containing these words will not be shown. One rule per line.");
WeatherAlertListTitleTextBlock.Text = L("settings.weather.alert_list_label", "Exclude List");
WeatherAlertListDescriptionTextBlock.Text = L("settings.weather.alert_list_desc", "One exclusion rule per line.");
WeatherExcludedAlertsTextBox.Watermark = L("settings.weather.alert_filter_placeholder", "One keyword per line");
WeatherIconPackSettingsExpander.Header = L("settings.weather.icon_style_header", "Weather Icon Style");
WeatherIconPackSettingsExpander.Description = L(
"settings.weather.icon_style_desc",
"Choose Fluent Icon style for weather symbols.");
WeatherIconPackFluentRegularItem.Content = L("settings.weather.icon_style_fluent_regular", "Fluent Regular");
WeatherIconPackFluentFilledItem.Content = L("settings.weather.icon_style_fluent_filled", "Fluent Filled");
WeatherNoTlsSettingsExpander.Header = L("settings.weather.no_tls_header", "No TLS Weather Request");
WeatherNoTlsSettingsExpander.Description = L(
"settings.weather.no_tls_desc",
"Not recommended. Enable only for incompatible network environments.");
WeatherNoTlsToggleSwitch.Content = L("settings.weather.no_tls_toggle", "Allow non-TLS request fallback");
WeatherFooterHintTextBlock.Text = L(
"settings.weather.footer_hint",
"Desktop weather widgets will reuse the location and alert exclusion settings configured here.");
if (string.IsNullOrWhiteSpace(_weatherSearchKeyword))
{
WeatherSearchStatusTextBlock.Text = L(
"settings.weather.search_hint",
"Search by city name and apply one location.");
}
if (!_isWeatherPreviewInProgress)
{
WeatherPreviewResultTextBlock.Text = L(
"settings.weather.preview_hint",
"Use test fetch to verify your weather configuration.");
}
UpdateWeatherLocationStatusText();
RegionPanelTitleTextBlock.Text = L("settings.region.title", "Region");
LanguageSettingsExpander.Header = L("settings.region.language_header", "Language");
LanguageSettingsExpander.Description = L("settings.region.language_desc", "Select application language. Changes apply immediately.");
LanguageChineseItem.Content = L("settings.region.language_zh", "Chinese");
LanguageEnglishItem.Content = L("settings.region.language_en", "English");
TimeZoneSettingsExpander.Header = L("settings.region.timezone_header", "Time Zone");
TimeZoneSettingsExpander.Description = L(
"settings.region.timezone_desc",
"Select a time zone. Clock and calendar widgets will follow this zone.");
ApplyUpdateLocalization();
LauncherSettingsPanelTitleTextBlock.Text = L("settings.launcher.title", "App Launcher");
LauncherHiddenItemsSettingsExpander.Header = L("settings.launcher.hidden_header", "Hidden Items");
LauncherHiddenItemsSettingsExpander.Description = L(
"settings.launcher.hidden_desc",
"Review hidden launcher entries and show them again.");
LauncherHiddenItemsDescriptionTextBlock.Text = L(
"settings.launcher.hidden_hint",
"Right-click an icon in launcher to hide it. Hidden entries appear here.");
LauncherHiddenItemsEmptyTextBlock.Text = L("settings.launcher.hidden_empty", "No hidden items.");
ApplyPluginSettingsLocalization();
ApplyPluginMarketSettingsLocalization();
SettingsNavAboutItem.Content = L("settings.nav.about", "About");
AboutPanelTitleTextBlock.Text = L("settings.about.title", "About");
VersionTextBlock.Text = Lf(
"settings.about.version_format",
"Version: {0}",
GetAppVersionText());
CodeNameTextBlock.Text = Lf(
"settings.about.codename_format",
"Code Name: {0}",
AppCodeName);
FontInfoTextBlock.Text = Lf(
"settings.about.font_format",
"Font: {0}",
AppFontName);
AboutStartupSettingsExpander.Header = L("settings.about.startup_header", "Windows Startup");
AboutStartupSettingsExpander.Description = L(
"settings.about.startup_desc",
"Launch the app automatically when signing in to Windows.");
AboutRenderModeSettingsExpander.Header = L("settings.about.render_mode_header", "Rendering Mode");
AboutRenderModeSettingsExpander.Description = L(
"settings.about.render_mode_desc",
"Choose the rendering backend. Restart the app after changing this option. Unsupported modes fall back to software.");
SetAppRenderModeComboItemContent(AppRenderingModeHelper.Default, L("settings.about.render_mode.default", "Default"));
SetAppRenderModeComboItemContent(AppRenderingModeHelper.Software, L("settings.about.render_mode.software", "Software"));
SetAppRenderModeComboItemContent(AppRenderingModeHelper.AngleEgl, L("settings.about.render_mode.angle_egl", "angleEgl"));
SetAppRenderModeComboItemContent(AppRenderingModeHelper.Wgl, L("settings.about.render_mode.wgl", "WGL"));
SetAppRenderModeComboItemContent(AppRenderingModeHelper.Vulkan, L("settings.about.render_mode.vulkan", "Vulkan"));
UpdateCurrentRenderBackendStatus();
UpdatePendingRestartDock();
if (WallpaperPlacementComboBox?.ItemCount >= 5)
{
if (WallpaperPlacementComboBox.Items[0] is ComboBoxItem fillItem) fillItem.Content = L("placement.fill", "Fill");
if (WallpaperPlacementComboBox.Items[1] is ComboBoxItem fitItem) fitItem.Content = L("placement.fit", "Fit");
if (WallpaperPlacementComboBox.Items[2] is ComboBoxItem stretchItem) stretchItem.Content = L("placement.stretch", "Stretch");
if (WallpaperPlacementComboBox.Items[3] is ComboBoxItem centerItem) centerItem.Content = L("placement.center", "Center");
if (WallpaperPlacementComboBox.Items[4] is ComboBoxItem tileItem) tileItem.Content = L("placement.tile", "Tile");
}
GridInfoTextBlock.Text = Lf(
"settings.grid.info_format",
"Grid: {0} cols x {1} rows | cell {2:F1}px (1:1)",
DesktopGrid.ColumnDefinitions.Count,
DesktopGrid.RowDefinitions.Count,
DesktopGrid.RowDefinitions.Count > 0 ? DesktopGrid.RowDefinitions[0].Height.Value : 0d);
InitializeTimeZoneSettings();
BuildComponentLibraryCategoryPages();
RenderLauncherRootTiles();
RenderLauncherHiddenItemsList();
UpdateOpenSettingsActionVisualState();
UpdateWallpaperDisplay();
}
private void SetAppRenderModeComboItemContent(string tag, string content)
{
var item = AppRenderModeComboBox.Items
.OfType<ComboBoxItem>()
.FirstOrDefault(candidate =>
string.Equals(candidate.Tag?.ToString(), tag, StringComparison.OrdinalIgnoreCase));
if (item is not null)
{
item.Content = content;
}
}
private string GetLocalizedTimeZoneDisplayName(TimeZoneInfo timeZone)
{
var offset = timeZone.GetUtcOffset(DateTime.UtcNow);
var sign = offset >= TimeSpan.Zero ? "+" : "-";
var hours = Math.Abs(offset.Hours);
var minutes = Math.Abs(offset.Minutes);
var name = string.IsNullOrWhiteSpace(timeZone.StandardName)
? timeZone.DisplayName
: timeZone.StandardName;
if (string.Equals(_languageCode, "zh-CN", StringComparison.OrdinalIgnoreCase) &&
ZhTimeZoneNames.TryGetValue(timeZone.Id, out var localizedName))
{
name = localizedName;
}
if (string.IsNullOrWhiteSpace(name))
{
name = timeZone.Id;
}
return $"(UTC{sign}{hours:D2}:{minutes:D2}) {name}";
}
private static string GetAppVersionText()
{
var version = typeof(MainWindow).Assembly.GetName().Version;
if (version is null || version.Major < 0 || version.Minor < 0 || version.Build < 0)
{
return FallbackAppVersion;
}
return $"{version.Major}.{version.Minor}.{version.Build}";
}
private void OnLanguageSelectionChanged(object? sender, SelectionChangedEventArgs e)
{
if (_suppressLanguageSelectionEvents || LanguageComboBox?.SelectedItem is not ComboBoxItem item)
{
return;
}
var selectedLanguage = item.Tag as string;
_languageCode = _localizationService.NormalizeLanguageCode(selectedLanguage);
ApplyLocalization();
ThemeColorStatusTextBlock.Text = Lf(
"settings.region.applied_format",
"Language switched to: {0}",
GetLanguageDisplayName(_languageCode));
PersistSettings();
}
private void UpdateWeatherLocationStatusText()
{
if (WeatherLocationStatusTextBlock is null)
{
return;
}
var modeText = _weatherLocationMode == WeatherLocationMode.Coordinates
? L("settings.weather.mode_coordinates", "Coordinates")
: L("settings.weather.mode_city_search", "City Search");
if (_weatherLocationMode == WeatherLocationMode.CitySearch)
{
if (string.IsNullOrWhiteSpace(_weatherLocationKey))
{
WeatherLocationStatusTextBlock.Text = L(
"settings.weather.status_city_empty",
"No city location is configured.");
UpdateWeatherLocationSummaryCard();
return;
}
var locationName = string.IsNullOrWhiteSpace(_weatherLocationName)
? _weatherLocationKey
: _weatherLocationName;
WeatherLocationStatusTextBlock.Text = Lf(
"settings.weather.status_city_format",
"Mode: {0} | {1} | Key: {2}",
modeText,
locationName,
_weatherLocationKey);
UpdateWeatherLocationSummaryCard();
return;
}
WeatherLocationStatusTextBlock.Text = Lf(
"settings.weather.status_coordinates_format",
"Mode: {0} | Lat {1:F4}, Lon {2:F4} | Key: {3}",
modeText,
_weatherLatitude,
_weatherLongitude,
string.IsNullOrWhiteSpace(_weatherLocationKey)
? BuildCoordinateLocationKey(_weatherLatitude, _weatherLongitude)
: _weatherLocationKey);
UpdateWeatherLocationSummaryCard();
}
}

View File

@@ -7,6 +7,13 @@ public partial class MainWindow
{
private void UpdateCurrentRenderBackendStatus()
{
if (CurrentRenderBackendLabelTextBlock is null ||
CurrentRenderBackendValueTextBlock is null ||
CurrentRenderBackendImplementationTextBlock is null)
{
return;
}
var backendInfo = AppRenderBackendDiagnostics.Detect();
var localizedBackend = GetLocalizedRenderBackendName(backendInfo.ActualBackend);

View File

@@ -1,115 +0,0 @@
using System.Threading.Tasks;
using Avalonia.Interactivity;
using Avalonia.Threading;
using FluentAvalonia.UI.Controls;
using LanMountainDesktop.PluginSdk;
using LanMountainDesktop.Services;
namespace LanMountainDesktop.Views;
public partial class MainWindow
{
private bool _isRestartPromptVisible;
private void OnPendingRestartStateChanged()
{
if (Dispatcher.UIThread.CheckAccess())
{
UpdatePendingRestartDock();
return;
}
Dispatcher.UIThread.Post(UpdatePendingRestartDock);
}
private void UpdatePendingRestartDock()
{
PendingRestartDock.IsVisible = PendingRestartStateService.HasPendingRestart;
PendingRestartDockTitleTextBlock.Text = L("settings.restart_dock.title", "Restart required");
PendingRestartDockDescriptionTextBlock.Text = L(
"settings.restart_dock.description",
"Some changes will take effect after restarting the app.");
PendingRestartDockButtonTextBlock.Text = L("settings.restart_dock.button", "Restart app");
}
private async void OnPendingRestartDockButtonClick(object? sender, RoutedEventArgs e)
{
await ShowGenericRestartPromptAsync();
}
private Task ShowRenderModeRestartPromptAsync(string selectedMode)
{
var message = Lf(
"settings.restart_dialog.render_mode_message",
"Restart the app to switch the rendering mode from \"{0}\" to \"{1}\". Restart now?",
GetLocalizedAppRenderModeDisplayName(_runningAppRenderMode),
GetLocalizedAppRenderModeDisplayName(selectedMode));
return ShowRestartPromptCoreAsync(message);
}
private Task ShowGenericRestartPromptAsync()
{
return ShowRestartPromptCoreAsync(L(
"settings.restart_dock.description",
"Some changes will take effect after restarting the app."));
}
private async Task ShowRestartPromptCoreAsync(string message)
{
if (_isRestartPromptVisible)
{
return;
}
_isRestartPromptVisible = true;
try
{
var dialog = new ContentDialog
{
Title = L("settings.restart_dialog.title", "Restart required"),
Content = message,
PrimaryButtonText = L("settings.restart_dialog.restart", "Restart now"),
CloseButtonText = L("settings.restart_dialog.cancel", "Cancel"),
DefaultButton = ContentDialogButton.Primary
};
var result = await dialog.ShowAsync(this);
if (result == ContentDialogResult.Primary)
{
if (App.CurrentHostApplicationLifecycle?.TryRestart(new HostApplicationLifecycleRequest(
Source: nameof(MainWindow),
Reason: "User confirmed a pending restart prompt.")) != true)
{
UpdatePendingRestartDock();
}
return;
}
UpdatePendingRestartDock();
}
finally
{
_isRestartPromptVisible = false;
}
}
private string GetLocalizedAppRenderModeDisplayName(string renderMode)
{
if (renderMode == AppRenderBackendDiagnostics.Unknown)
{
return L("settings.about.render_mode.unknown", "Unknown");
}
return AppRenderingModeHelper.Normalize(renderMode) switch
{
AppRenderingModeHelper.Software => L("settings.about.render_mode.software", "Software"),
AppRenderingModeHelper.AngleEgl => L("settings.about.render_mode.angle_egl", "angleEgl"),
AppRenderingModeHelper.Wgl => L("settings.about.render_mode.wgl", "WGL"),
AppRenderingModeHelper.Vulkan => L("settings.about.render_mode.vulkan", "Vulkan"),
_ => L("settings.about.render_mode.default", "Default")
};
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,404 @@
using System;
using System.Globalization;
using System.IO;
using System.Linq;
using Avalonia.Controls;
using Avalonia.Interactivity;
using Avalonia.Media;
using Avalonia.Media.Imaging;
using Avalonia.Threading;
using FluentAvalonia.UI.Controls;
using LanMountainDesktop.Models;
using LanMountainDesktop.PluginSdk;
using LanMountainDesktop.Services;
using LanMountainDesktop.Views.Components;
namespace LanMountainDesktop.Views;
public partial class MainWindow
{
private TextBlock? CurrentRenderBackendLabelTextBlock => this.FindControl<TextBlock>("CurrentRenderBackendLabelTextBlock");
private TextBlock? CurrentRenderBackendValueTextBlock => this.FindControl<TextBlock>("CurrentRenderBackendValueTextBlock");
private TextBlock? CurrentRenderBackendImplementationTextBlock => this.FindControl<TextBlock>("CurrentRenderBackendImplementationTextBlock");
private Slider? GridSizeSlider => this.FindControl<Slider>("GridSizeSlider");
private NumberBox? GridSizeNumberBox => this.FindControl<NumberBox>("GridSizeNumberBox");
private Slider? GridEdgeInsetSlider => this.FindControl<Slider>("GridEdgeInsetSlider");
private NumberBox? GridEdgeInsetNumberBox => this.FindControl<NumberBox>("GridEdgeInsetNumberBox");
private TextBlock? GridEdgeInsetComputedPxTextBlock => this.FindControl<TextBlock>("GridEdgeInsetComputedPxTextBlock");
private TextBlock? GridInfoTextBlock => this.FindControl<TextBlock>("GridInfoTextBlock");
private ComboBox? GridSpacingPresetComboBox => this.FindControl<ComboBox>("GridSpacingPresetComboBox");
private Border? GridPreviewHost => this.FindControl<Border>("GridPreviewHost");
private Border? GridPreviewFrame => this.FindControl<Border>("GridPreviewFrame");
private Border? GridPreviewViewport => this.FindControl<Border>("GridPreviewViewport");
private Grid? GridPreviewGrid => this.FindControl<Grid>("GridPreviewGrid");
private Canvas? GridPreviewLinesCanvas => this.FindControl<Canvas>("GridPreviewLinesCanvas");
private Border? GridPreviewTopStatusBarHost => this.FindControl<Border>("GridPreviewTopStatusBarHost");
private StackPanel? GridPreviewTopStatusComponentsPanel => this.FindControl<StackPanel>("GridPreviewTopStatusComponentsPanel");
private Border? GridPreviewBottomTaskbarContainer => this.FindControl<Border>("GridPreviewBottomTaskbarContainer");
private StackPanel? GridPreviewBackButtonVisual => this.FindControl<StackPanel>("GridPreviewBackButtonVisual");
private TextBlock? GridPreviewBackButtonTextBlock => this.FindControl<TextBlock>("GridPreviewBackButtonTextBlock");
private StackPanel? GridPreviewComponentLibraryVisual => this.FindControl<StackPanel>("GridPreviewComponentLibraryVisual");
private FluentIcons.Avalonia.FluentIcon? GridPreviewComponentLibraryIcon => this.FindControl<FluentIcons.Avalonia.FluentIcon>("GridPreviewComponentLibraryIcon");
private TextBlock? GridPreviewComponentLibraryTextBlock => this.FindControl<TextBlock>("GridPreviewComponentLibraryTextBlock");
private FluentIcons.Avalonia.SymbolIcon? GridPreviewSettingsButtonIcon => this.FindControl<FluentIcons.Avalonia.SymbolIcon>("GridPreviewSettingsButtonIcon");
private Border? WallpaperPreviewHost => this.FindControl<Border>("WallpaperPreviewHost");
private Border? WallpaperPreviewFrame => this.FindControl<Border>("WallpaperPreviewFrame");
private Border? WallpaperPreviewViewport => this.FindControl<Border>("WallpaperPreviewViewport");
private Grid? WallpaperPreviewGrid => this.FindControl<Grid>("WallpaperPreviewGrid");
private Border? WallpaperPreviewTopStatusBarHost => this.FindControl<Border>("WallpaperPreviewTopStatusBarHost");
private StackPanel? WallpaperPreviewTopStatusComponentsPanel => this.FindControl<StackPanel>("WallpaperPreviewTopStatusComponentsPanel");
private Border? WallpaperPreviewBottomTaskbarContainer => this.FindControl<Border>("WallpaperPreviewBottomTaskbarContainer");
private ClockWidget? WallpaperPreviewClockWidget => this.FindControl<ClockWidget>("WallpaperPreviewClockWidget");
private StackPanel? WallpaperPreviewBackButtonVisual => this.FindControl<StackPanel>("WallpaperPreviewBackButtonVisual");
private TextBlock? WallpaperPreviewBackButtonTextBlock => this.FindControl<TextBlock>("WallpaperPreviewBackButtonTextBlock");
private StackPanel? WallpaperPreviewComponentLibraryVisual => this.FindControl<StackPanel>("WallpaperPreviewComponentLibraryVisual");
private TextBlock? WallpaperPreviewComponentLibraryTextBlock => this.FindControl<TextBlock>("WallpaperPreviewComponentLibraryTextBlock");
private FluentIcons.Avalonia.SymbolIcon? WallpaperPreviewSettingsButtonIcon => this.FindControl<FluentIcons.Avalonia.SymbolIcon>("WallpaperPreviewSettingsButtonIcon");
private ComboBox? StatusBarSpacingModeComboBox => this.FindControl<ComboBox>("StatusBarSpacingModeComboBox");
private SettingsExpanderItem? StatusBarSpacingCustomPanel => this.FindControl<SettingsExpanderItem>("StatusBarSpacingCustomPanel");
private Slider? StatusBarSpacingSlider => this.FindControl<Slider>("StatusBarSpacingSlider");
private NumberBox? StatusBarSpacingNumberBox => this.FindControl<NumberBox>("StatusBarSpacingNumberBox");
private TextBlock? StatusBarSpacingComputedPxTextBlock => this.FindControl<TextBlock>("StatusBarSpacingComputedPxTextBlock");
private ComboBox? TimeZoneComboBox => this.FindControl<ComboBox>("TimeZoneComboBox");
private SettingsExpander? LauncherHiddenItemsSettingsExpander => this.FindControl<SettingsExpander>("LauncherHiddenItemsSettingsExpander");
private TextBlock? LauncherHiddenItemsEmptyTextBlock => this.FindControl<TextBlock>("LauncherHiddenItemsEmptyTextBlock");
private void OnSettingsChanged(object? sender, SettingsChangedEvent e)
{
_ = sender;
_ = e;
ScheduleReloadFromExternalSettings();
}
private void ScheduleReloadFromExternalSettings()
{
if (_externalSettingsReloadPending)
{
return;
}
_externalSettingsReloadPending = true;
DispatcherTimer.RunOnce(() =>
{
_externalSettingsReloadPending = false;
ReloadFromPersistedSettings();
}, TimeSpan.FromMilliseconds(120));
}
private void OnNightModeChecked(object? sender, RoutedEventArgs e)
{
_ = sender;
_ = e;
ApplyNightModeState(true, refreshPalettes: true);
SchedulePersistSettings();
}
private void OnNightModeUnchecked(object? sender, RoutedEventArgs e)
{
_ = sender;
_ = e;
ApplyNightModeState(false, refreshPalettes: true);
SchedulePersistSettings();
}
private void InitializeLocalization(string? languageCode)
{
_languageCode = _localizationService.NormalizeLanguageCode(languageCode);
}
private string L(string key, string fallback)
{
return _localizationService.GetString(_languageCode, key, fallback);
}
private string Lf(string key, string fallback, params object[] args)
{
var template = L(key, fallback);
return string.Format(CultureInfo.CurrentCulture, template, args);
}
private void ApplyLocalization()
{
Title = L("app.title", "LanMountainDesktop");
BackToWindowsTextBlock.Text = L("button.back_to_windows", "Back to Windows");
OpenComponentLibraryTextBlock.Text = L("button.component_library", "Edit Desktop");
ComponentLibraryTitleTextBlock.Text = L("component_library.title", "Widgets");
LauncherTitleTextBlock.Text = L("launcher.title", "App Launcher");
LauncherSubtitleTextBlock.Text = OperatingSystem.IsLinux()
? L("launcher.subtitle_linux", "Displays installed apps discovered from Linux desktop entries.")
: L("launcher.subtitle", "Displays all apps and folders based on the Windows Start menu structure.");
UpdateCurrentRenderBackendStatus();
RenderLauncherHiddenItemsList();
}
private string GetLocalizedTimeZoneDisplayName(TimeZoneInfo timeZone)
{
var offset = timeZone.GetUtcOffset(DateTime.UtcNow);
var sign = offset >= TimeSpan.Zero ? "+" : "-";
var hours = Math.Abs(offset.Hours);
var minutes = Math.Abs(offset.Minutes);
var name = string.IsNullOrWhiteSpace(timeZone.StandardName) ? timeZone.Id : timeZone.StandardName;
return $"(UTC{sign}{hours:D2}:{minutes:D2}) {name}";
}
private void InitializeWeatherSettings(AppSettingsSnapshot snapshot)
{
_weatherLocationMode = string.Equals(snapshot.WeatherLocationMode, "Coordinates", StringComparison.OrdinalIgnoreCase)
? WeatherLocationMode.Coordinates
: WeatherLocationMode.CitySearch;
_weatherLocationKey = snapshot.WeatherLocationKey ?? string.Empty;
_weatherLocationName = snapshot.WeatherLocationName ?? string.Empty;
_weatherLatitude = snapshot.WeatherLatitude;
_weatherLongitude = snapshot.WeatherLongitude;
_weatherAutoRefreshLocation = snapshot.WeatherAutoRefreshLocation;
_weatherExcludedAlertsRaw = snapshot.WeatherExcludedAlerts ?? string.Empty;
_weatherIconPackId = string.IsNullOrWhiteSpace(snapshot.WeatherIconPackId) ? "FluentRegular" : snapshot.WeatherIconPackId;
_weatherNoTlsRequests = snapshot.WeatherNoTlsRequests;
}
private void InitializeAutoStartWithWindowsSetting(AppSettingsSnapshot snapshot)
{
_autoStartWithWindows = snapshot.AutoStartWithWindows;
}
private void InitializeAppRenderModeSetting(AppSettingsSnapshot snapshot)
{
_selectedAppRenderMode = string.IsNullOrWhiteSpace(snapshot.AppRenderMode)
? AppRenderingModeHelper.Default
: snapshot.AppRenderMode;
_runningAppRenderMode = AppRenderingModeHelper.Normalize(snapshot.AppRenderMode);
}
private void InitializeUpdateSettings(AppSettingsSnapshot snapshot)
{
_ = snapshot;
_ = _updateSettingsService.Get();
}
private void InitializeSettingsIcons()
{
}
private static bool TryParseColor(string? colorText, out Color color)
{
if (!string.IsNullOrWhiteSpace(colorText) && Color.TryParse(colorText, out color))
{
return true;
}
color = default;
return false;
}
private void TryRestoreWallpaper(string? savedWallpaperPath)
{
_wallpaperPath = string.IsNullOrWhiteSpace(savedWallpaperPath) ? null : savedWallpaperPath;
_wallpaperBitmap?.Dispose();
_wallpaperBitmap = null;
if (string.IsNullOrWhiteSpace(_wallpaperPath) || !File.Exists(_wallpaperPath))
{
_wallpaperMediaType = WallpaperMediaType.None;
return;
}
var extension = Path.GetExtension(_wallpaperPath);
if (SupportedVideoExtensions.Contains(extension))
{
_wallpaperMediaType = WallpaperMediaType.Video;
_wallpaperVideoPath = _wallpaperPath;
return;
}
if (!SupportedImageExtensions.Contains(extension))
{
_wallpaperMediaType = WallpaperMediaType.None;
_wallpaperPath = null;
return;
}
try
{
using var stream = File.OpenRead(_wallpaperPath);
_wallpaperBitmap = new Bitmap(stream);
_wallpaperMediaType = WallpaperMediaType.Image;
}
catch
{
_wallpaperMediaType = WallpaperMediaType.None;
_wallpaperPath = null;
_wallpaperBitmap?.Dispose();
_wallpaperBitmap = null;
}
}
private void ApplyWallpaperBrush()
{
if (_wallpaperMediaType == WallpaperMediaType.Image && _wallpaperBitmap is not null)
{
DesktopWallpaperLayer.Background = new ImageBrush(_wallpaperBitmap)
{
Stretch = Stretch.UniformToFill
};
return;
}
DesktopWallpaperLayer.Background = _defaultDesktopBackground ?? Brushes.Transparent;
}
private void UpdateWallpaperDisplay()
{
ApplyWallpaperBrush();
}
private void StopVideoWallpaper()
{
_wallpaperVideoPath = null;
if (_wallpaperMediaType == WallpaperMediaType.Video)
{
_wallpaperMediaType = WallpaperMediaType.None;
}
}
private double CalculateCurrentBackgroundLuminance()
{
var brush = DesktopWallpaperLayer.Background;
if (brush is SolidColorBrush solid)
{
return CalculateRelativeLuminance(solid.Color);
}
return CalculateRelativeLuminance(_selectedThemeColor);
}
private void ApplyNightModeState(bool enabled, bool refreshPalettes)
{
_isNightMode = enabled;
if (!refreshPalettes)
{
return;
}
var palette = _themeSettingsService.BuildPalette(enabled, _wallpaperPath);
_recommendedColors = palette.RecommendedColors;
_monetColors = palette.MonetColors;
}
private static double CalculateRelativeLuminance(Color color)
{
return CalculateRelativeLuminance(color.R / 255d, color.G / 255d, color.B / 255d);
}
private static double CalculateRelativeLuminance(double red, double green, double blue)
{
static double ToLinear(double value) =>
value <= 0.03928 ? value / 12.92 : Math.Pow((value + 0.055) / 1.055, 2.4);
return 0.2126 * ToLinear(red) + 0.7152 * ToLinear(green) + 0.0722 * ToLinear(blue);
}
private void TriggerAutoUpdateCheckIfEnabled()
{
}
private void PersistSettings()
{
if (_suppressSettingsPersistence)
{
return;
}
try
{
_settingsService.SaveSnapshot(SettingsScope.App, BuildAppSettingsSnapshot());
_componentLayoutStore.SaveLayout(BuildDesktopLayoutSettingsSnapshot());
_settingsService.SaveSnapshot(SettingsScope.Launcher, BuildLauncherSettingsSnapshot());
}
catch (Exception ex)
{
AppLogger.Warn("SettingsRuntime", "Failed to persist settings.", ex);
}
}
private void SchedulePersistSettings(int delayMs = 200)
{
DispatcherTimer.RunOnce(PersistSettings, TimeSpan.FromMilliseconds(Math.Max(0, delayMs)));
}
internal void ReloadFromPersistedSettings()
{
var snapshot = _settingsService.LoadSnapshot<AppSettingsSnapshot>(SettingsScope.App);
var layoutSnapshot = _componentLayoutStore.LoadLayout();
var launcherSnapshot = _settingsService.LoadSnapshot<LauncherSettingsSnapshot>(SettingsScope.Launcher);
_suppressSettingsPersistence = true;
try
{
ApplyTaskbarSettings(snapshot);
InitializeWeatherSettings(snapshot);
InitializeDesktopSurfaceState(layoutSnapshot);
InitializeLauncherVisibilitySettings(launcherSnapshot);
InitializeDesktopComponentPlacements(layoutSnapshot);
TryRestoreWallpaper(snapshot.WallpaperPath);
ApplyWallpaperBrush();
RebuildDesktopGrid();
}
finally
{
_suppressSettingsPersistence = false;
}
}
private AppSettingsSnapshot BuildAppSettingsSnapshot()
{
return new AppSettingsSnapshot
{
GridShortSideCells = _targetShortSideCells,
GridSpacingPreset = _gridSpacingPreset,
DesktopEdgeInsetPercent = _desktopEdgeInsetPercent,
IsNightMode = _isNightMode,
ThemeColor = _selectedThemeColor.ToString(),
WallpaperPath = _wallpaperPath,
LanguageCode = _languageCode,
TimeZoneId = _timeZoneService.CurrentTimeZone.Id,
WeatherLocationMode = _weatherLocationMode.ToString(),
WeatherLocationKey = _weatherLocationKey,
WeatherLocationName = _weatherLocationName,
WeatherLatitude = _weatherLatitude,
WeatherLongitude = _weatherLongitude,
WeatherAutoRefreshLocation = _weatherAutoRefreshLocation,
WeatherExcludedAlerts = _weatherExcludedAlertsRaw,
WeatherIconPackId = _weatherIconPackId,
WeatherNoTlsRequests = _weatherNoTlsRequests,
AutoStartWithWindows = _autoStartWithWindows,
AppRenderMode = _selectedAppRenderMode,
TopStatusComponentIds = [.. _topStatusComponentIds],
PinnedTaskbarActions = [.. _pinnedTaskbarActions.Select(v => v.ToString())],
EnableDynamicTaskbarActions = _enableDynamicTaskbarActions,
TaskbarLayoutMode = _taskbarLayoutMode,
ClockDisplayFormat = _clockDisplayFormat == ClockDisplayFormat.HourMinute ? "HourMinute" : "HourMinuteSecond",
StatusBarSpacingMode = _statusBarSpacingMode,
StatusBarCustomSpacingPercent = _statusBarCustomSpacingPercent
};
}
private DesktopLayoutSettingsSnapshot BuildDesktopLayoutSettingsSnapshot()
{
return new DesktopLayoutSettingsSnapshot
{
DesktopPageCount = _desktopPageCount,
CurrentDesktopSurfaceIndex = _currentDesktopSurfaceIndex,
DesktopComponentPlacements = [.. _desktopComponentPlacements]
};
}
private LauncherSettingsSnapshot BuildLauncherSettingsSnapshot()
{
return new LauncherSettingsSnapshot
{
HiddenLauncherAppPaths = [.. _hiddenLauncherAppPaths],
HiddenLauncherFolderPaths = [.. _hiddenLauncherFolderPaths]
};
}
}

View File

@@ -1,493 +0,0 @@
using System;
using System.ComponentModel;
using System.Diagnostics;
using System.IO;
using System.Threading.Tasks;
using Avalonia.Controls;
using Avalonia.Interactivity;
using LanMountainDesktop.Models;
using LanMountainDesktop.PluginSdk;
using LanMountainDesktop.Services;
namespace LanMountainDesktop.Views;
public partial class MainWindow
{
private const string UpdateChannelStable = "Stable";
private const string UpdateChannelPreview = "Preview";
private bool _autoCheckUpdates = true;
private string _updateChannel = UpdateChannelStable;
private bool _suppressUpdateOptionEvents;
private bool _isCheckingUpdates;
private bool _isDownloadingUpdate;
private string _latestReleaseVersionText = "-";
private DateTimeOffset? _latestReleasePublishedAt;
private string _updateStatusText = string.Empty;
private string _updateDownloadProgressText = string.Empty;
private double _updateDownloadProgressPercent;
private GitHubReleaseAsset? _latestReleaseInstallerAsset;
private string? _downloadedUpdateInstallerPath;
private bool IncludePrereleaseUpdates => string.Equals(
_updateChannel,
UpdateChannelPreview,
StringComparison.OrdinalIgnoreCase);
private void InitializeUpdateSettings(AppSettingsSnapshot snapshot)
{
_autoCheckUpdates = snapshot.AutoCheckUpdates;
_updateChannel = NormalizeUpdateChannel(snapshot.UpdateChannel, snapshot.IncludePrereleaseUpdates);
_latestReleaseVersionText = "-";
_latestReleasePublishedAt = null;
_updateDownloadProgressPercent = 0;
_updateDownloadProgressText = L("settings.update.download_progress_idle", "Download progress: -");
_updateStatusText = L("settings.update.status_ready", "Ready to check for updates.");
_latestReleaseInstallerAsset = null;
_downloadedUpdateInstallerPath = null;
_suppressUpdateOptionEvents = true;
try
{
if (AutoCheckUpdatesToggleSwitch is not null)
{
AutoCheckUpdatesToggleSwitch.IsChecked = _autoCheckUpdates;
}
if (UpdateChannelChipListBox is not null)
{
UpdateChannelChipListBox.SelectedIndex = IncludePrereleaseUpdates ? 1 : 0;
}
}
finally
{
_suppressUpdateOptionEvents = false;
}
UpdateUpdatePanelState();
}
private void TriggerAutoUpdateCheckIfEnabled()
{
if (!_autoCheckUpdates)
{
return;
}
_ = CheckForUpdatesAsync(silentWhenNoUpdate: true);
}
private void ApplyUpdateLocalization()
{
SettingsNavUpdateItem.Content = L("settings.nav.update", "Update");
UpdatePanelTitleTextBlock.Text = L("settings.update.title", "Update");
UpdateCurrentVersionLabelTextBlock.Text = L("settings.update.current_version_label", "Current Version");
UpdateLatestVersionLabelTextBlock.Text = L("settings.update.latest_version_label", "Latest Release");
UpdatePublishedAtLabelTextBlock.Text = L("settings.update.published_at_label", "Published At");
UpdateOptionsSettingsExpander.Header = L("settings.update.options_header", "Update Options");
UpdateOptionsSettingsExpander.Description = L(
"settings.update.options_desc",
"Configure update checks and release channel.");
AutoCheckUpdatesToggleSwitch.Content = L(
"settings.update.auto_check_toggle",
"Automatically check for updates on startup");
UpdateChannelLabelTextBlock.Text = L(
"settings.update.channel_label",
"Update Channel");
UpdateChannelStableChipItem.Content = L(
"settings.update.channel_stable",
"Stable");
UpdateChannelPreviewChipItem.Content = L(
"settings.update.channel_preview",
"Preview");
UpdateActionsSettingsExpander.Header = L("settings.update.actions_header", "Update Actions");
UpdateActionsSettingsExpander.Description = L(
"settings.update.actions_desc",
"Check releases, download installer, and start update.");
CheckForUpdatesButton.Content = L("settings.update.check_button", "Check for Updates");
DownloadAndInstallUpdateButton.Content = L("settings.update.download_install_button", "Download & Install");
if (string.IsNullOrWhiteSpace(_updateDownloadProgressText))
{
_updateDownloadProgressText = L("settings.update.download_progress_idle", "Download progress: -");
}
if (string.IsNullOrWhiteSpace(_updateStatusText))
{
_updateStatusText = L("settings.update.status_ready", "Ready to check for updates.");
}
UpdateUpdatePanelState();
}
private async void OnCheckForUpdatesClick(object? sender, RoutedEventArgs e)
{
await CheckForUpdatesAsync(silentWhenNoUpdate: false);
}
private async void OnDownloadAndInstallUpdateClick(object? sender, RoutedEventArgs e)
{
if (_isCheckingUpdates || _isDownloadingUpdate)
{
return;
}
if (_latestReleaseInstallerAsset is null)
{
await CheckForUpdatesAsync(silentWhenNoUpdate: false);
}
if (_latestReleaseInstallerAsset is null)
{
return;
}
await DownloadAndInstallUpdateAsync(_latestReleaseInstallerAsset);
}
private void OnAutoCheckUpdatesToggled(object? sender, RoutedEventArgs e)
{
if (_suppressUpdateOptionEvents || AutoCheckUpdatesToggleSwitch is null)
{
return;
}
_autoCheckUpdates = AutoCheckUpdatesToggleSwitch.IsChecked == true;
PersistSettings();
}
private void OnUpdateChannelSelectionChanged(object? sender, SelectionChangedEventArgs e)
{
if (_suppressUpdateOptionEvents || UpdateChannelChipListBox is null)
{
return;
}
var selectedChannel = UpdateChannelChipListBox.SelectedIndex == 1
? UpdateChannelPreview
: UpdateChannelStable;
if (string.Equals(_updateChannel, selectedChannel, StringComparison.OrdinalIgnoreCase))
{
return;
}
_updateChannel = selectedChannel;
_latestReleaseInstallerAsset = null;
_latestReleaseVersionText = "-";
_latestReleasePublishedAt = null;
_downloadedUpdateInstallerPath = null;
_updateStatusText = Lf(
"settings.update.status_channel_changed_format",
"Update channel switched to {0}. Please check again.",
GetLocalizedUpdateChannelName(_updateChannel));
PersistSettings();
UpdateUpdatePanelState();
}
private async Task CheckForUpdatesAsync(bool silentWhenNoUpdate)
{
if (_isCheckingUpdates || _isDownloadingUpdate)
{
return;
}
if (!OperatingSystem.IsWindows())
{
_updateStatusText = L(
"settings.update.status_windows_only",
"Automatic installer update is currently available only on Windows.");
UpdateUpdatePanelState();
return;
}
_isCheckingUpdates = true;
_updateStatusText = L("settings.update.status_checking", "Checking GitHub releases...");
_updateDownloadProgressPercent = 0;
_updateDownloadProgressText = L("settings.update.download_progress_idle", "Download progress: -");
UpdateUpdatePanelState();
try
{
if (!Version.TryParse(GetAppVersionText(), out var currentVersion))
{
currentVersion = new Version(0, 0, 0);
}
var result = await _releaseUpdateService.CheckForUpdatesAsync(
currentVersion,
IncludePrereleaseUpdates);
if (!result.Success)
{
_latestReleaseInstallerAsset = null;
_latestReleaseVersionText = "-";
_latestReleasePublishedAt = null;
_downloadedUpdateInstallerPath = null;
_updateStatusText = Lf(
"settings.update.status_check_failed_format",
"Update check failed: {0}",
result.ErrorMessage ?? L("common.unknown", "Unknown error"));
return;
}
_latestReleaseInstallerAsset = result.PreferredAsset;
_latestReleaseVersionText = result.LatestVersionText;
_latestReleasePublishedAt = result.Release?.PublishedAt;
_downloadedUpdateInstallerPath = null;
if (!result.IsUpdateAvailable)
{
_latestReleaseInstallerAsset = null;
_updateStatusText = silentWhenNoUpdate
? L("settings.update.status_up_to_date", "You are already on the latest version.")
: L("settings.update.status_up_to_date", "You are already on the latest version.");
return;
}
if (_latestReleaseInstallerAsset is null)
{
_updateStatusText = L(
"settings.update.status_asset_missing",
"A new release is available, but no compatible installer was found.");
return;
}
_updateStatusText = Lf(
"settings.update.status_available_format",
"New version {0} is available. Click Download & Install.",
_latestReleaseVersionText);
}
catch (Exception ex)
{
_updateStatusText = Lf(
"settings.update.status_check_failed_format",
"Update check failed: {0}",
ex.Message);
}
finally
{
_isCheckingUpdates = false;
UpdateUpdatePanelState();
}
}
private async Task DownloadAndInstallUpdateAsync(GitHubReleaseAsset asset)
{
if (_isCheckingUpdates || _isDownloadingUpdate)
{
return;
}
_isDownloadingUpdate = true;
_updateStatusText = L("settings.update.status_downloading", "Downloading installer...");
_updateDownloadProgressPercent = 0;
_updateDownloadProgressText = Lf(
"settings.update.download_progress_format",
"Download progress: {0:F0}%",
_updateDownloadProgressPercent);
UpdateUpdatePanelState();
try
{
var destinationPath = BuildUpdateInstallerPath(asset.Name);
var progress = new Progress<double>(value =>
{
_updateDownloadProgressPercent = Math.Clamp(value * 100d, 0d, 100d);
_updateDownloadProgressText = Lf(
"settings.update.download_progress_format",
"Download progress: {0:F0}%",
_updateDownloadProgressPercent);
UpdateUpdatePanelState();
});
var result = await _releaseUpdateService.DownloadAssetAsync(asset, destinationPath, progress);
if (!result.Success || string.IsNullOrWhiteSpace(result.FilePath))
{
_updateStatusText = Lf(
"settings.update.status_download_failed_format",
"Download failed: {0}",
result.ErrorMessage ?? L("common.unknown", "Unknown error"));
return;
}
_downloadedUpdateInstallerPath = result.FilePath;
_updateDownloadProgressPercent = 100;
_updateDownloadProgressText = Lf(
"settings.update.download_progress_format",
"Download progress: {0:F0}%",
_updateDownloadProgressPercent);
_updateStatusText = L("settings.update.status_launching_installer", "Download complete. Launching installer...");
UpdateUpdatePanelState();
LaunchInstallerAndExit(_downloadedUpdateInstallerPath);
}
catch (Exception ex)
{
_updateStatusText = Lf(
"settings.update.status_download_failed_format",
"Download failed: {0}",
ex.Message);
}
finally
{
_isDownloadingUpdate = false;
UpdateUpdatePanelState();
}
}
private void LaunchInstallerAndExit(string installerPath)
{
if (string.IsNullOrWhiteSpace(installerPath) || !File.Exists(installerPath))
{
_updateStatusText = L(
"settings.update.status_installer_missing",
"Installer file was not found after download.");
UpdateUpdatePanelState();
return;
}
try
{
Process.Start(new ProcessStartInfo
{
FileName = installerPath,
WorkingDirectory = Path.GetDirectoryName(installerPath) ?? Environment.CurrentDirectory,
UseShellExecute = true,
Verb = "runas"
});
_updateStatusText = L(
"settings.update.status_installer_started",
"Installer started. The app will close for update.");
UpdateUpdatePanelState();
_ = App.CurrentHostApplicationLifecycle?.TryExit(new HostApplicationLifecycleRequest(
Source: nameof(MainWindow),
Reason: "Update installer started successfully."));
}
catch (Win32Exception ex) when (ex.NativeErrorCode == 1223)
{
_updateStatusText = L(
"settings.update.status_elevation_cancelled",
"Administrator permission was not granted. Update was cancelled.");
UpdateUpdatePanelState();
}
catch (Exception ex)
{
_updateStatusText = Lf(
"settings.update.status_launch_failed_format",
"Failed to start installer: {0}",
ex.Message);
UpdateUpdatePanelState();
}
}
private void UpdateUpdatePanelState()
{
if (UpdateCurrentVersionValueTextBlock is not null)
{
UpdateCurrentVersionValueTextBlock.Text = GetAppVersionText();
}
if (UpdateLatestVersionValueTextBlock is not null)
{
UpdateLatestVersionValueTextBlock.Text = string.IsNullOrWhiteSpace(_latestReleaseVersionText)
? "-"
: _latestReleaseVersionText;
}
if (UpdatePublishedAtValueTextBlock is not null)
{
UpdatePublishedAtValueTextBlock.Text = _latestReleasePublishedAt.HasValue &&
_latestReleasePublishedAt.Value != DateTimeOffset.MinValue
? _latestReleasePublishedAt.Value.LocalDateTime.ToString("yyyy-MM-dd HH:mm")
: "-";
}
if (UpdateStatusTextBlock is not null)
{
UpdateStatusTextBlock.Text = string.IsNullOrWhiteSpace(_updateStatusText)
? L("settings.update.status_ready", "Ready to check for updates.")
: _updateStatusText;
}
if (UpdateDownloadProgressTextBlock is not null)
{
UpdateDownloadProgressTextBlock.Text = string.IsNullOrWhiteSpace(_updateDownloadProgressText)
? L("settings.update.download_progress_idle", "Download progress: -")
: _updateDownloadProgressText;
}
if (UpdateDownloadProgressBar is not null)
{
UpdateDownloadProgressBar.IsVisible = _isDownloadingUpdate;
UpdateDownloadProgressBar.Value = Math.Clamp(_updateDownloadProgressPercent, 0d, 100d);
}
if (CheckForUpdatesButton is not null)
{
CheckForUpdatesButton.IsEnabled = !_isCheckingUpdates && !_isDownloadingUpdate;
}
if (DownloadAndInstallUpdateButton is not null)
{
DownloadAndInstallUpdateButton.IsEnabled = !_isCheckingUpdates &&
!_isDownloadingUpdate &&
_latestReleaseInstallerAsset is not null;
}
}
private static string NormalizeUpdateChannel(string? channel, bool includePrereleaseFallback)
{
if (string.Equals(channel, UpdateChannelPreview, StringComparison.OrdinalIgnoreCase))
{
return UpdateChannelPreview;
}
if (string.Equals(channel, UpdateChannelStable, StringComparison.OrdinalIgnoreCase))
{
return UpdateChannelStable;
}
return includePrereleaseFallback ? UpdateChannelPreview : UpdateChannelStable;
}
private string GetLocalizedUpdateChannelName(string channel)
{
return string.Equals(channel, UpdateChannelPreview, StringComparison.OrdinalIgnoreCase)
? L("settings.update.channel_preview", "Preview")
: L("settings.update.channel_stable", "Stable");
}
private static string BuildUpdateInstallerPath(string assetName)
{
var appData = Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData);
var updatesDirectory = Path.Combine(appData, "LanMountainDesktop", "Updates");
Directory.CreateDirectory(updatesDirectory);
var safeName = SanitizeFileName(assetName);
return Path.Combine(updatesDirectory, safeName);
}
private static string SanitizeFileName(string fileName)
{
if (string.IsNullOrWhiteSpace(fileName))
{
return $"LanMountainDesktop-Update-{DateTime.Now:yyyyMMddHHmmss}.exe";
}
var sanitized = fileName;
foreach (var c in Path.GetInvalidFileNameChars())
{
sanitized = sanitized.Replace(c, '_');
}
return sanitized;
}
}

View File

@@ -5,7 +5,6 @@
xmlns:fi="using:FluentIcons.Avalonia"
xmlns:ic="using:FluentIcons.Avalonia.Fluent"
xmlns:comp="using:LanMountainDesktop.Views.Components"
xmlns:pages="using:LanMountainDesktop.Views.SettingsPages"
xmlns:vlc="clr-namespace:LibVLCSharp.Avalonia;assembly=LibVLCSharp.Avalonia"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
@@ -321,183 +320,6 @@
</Border>
</Grid>
<Grid x:Name="SettingsPage"
Classes="settings-scope"
IsVisible="False"
Opacity="0"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch">
<Grid.Transitions>
<Transitions>
<DoubleTransition Property="Opacity" Duration="{StaticResource FluttermotionToken.Duration.Page}" />
</Transitions>
</Grid.Transitions>
<Border x:Name="SettingsContentPanel"
Background="Transparent"
BorderThickness="0"
Margin="0"
Padding="8"
HorizontalAlignment="Center"
VerticalAlignment="Center"
ClipToBounds="False">
<Border.RenderTransform>
<TranslateTransform Y="30">
<TranslateTransform.Transitions>
<Transitions>
<DoubleTransition Property="Y" Duration="{StaticResource FluttermotionToken.Duration.Page}" />
</Transitions>
</TranslateTransform.Transitions>
</TranslateTransform>
</Border.RenderTransform>
<Border Classes="mica-strong"
CornerRadius="{DynamicResource DesignCornerRadiusXl}"
Padding="18">
<Grid RowDefinitions="*,Auto"
RowSpacing="14">
<ui:NavigationView x:Name="SettingsNavView"
Grid.Row="0"
PaneDisplayMode="Left"
IsSettingsVisible="False"
OpenPaneLength="220"
SelectionChanged="OnSettingsNavSelectionChanged">
<ui:NavigationView.MenuItems>
<ui:NavigationViewItem x:Name="SettingsNavWallpaperItem" Content="壁纸" Tag="Wallpaper" ToolTip.Tip="壁纸">
<ui:NavigationViewItem.IconSource>
<ic:SymbolIconSource Symbol="Wallpaper" IconVariant="Regular" />
</ui:NavigationViewItem.IconSource>
</ui:NavigationViewItem>
<ui:NavigationViewItem x:Name="SettingsNavGridItem" Content="网格" Tag="Grid" ToolTip.Tip="网格">
<ui:NavigationViewItem.IconSource>
<ic:SymbolIconSource Symbol="Grid" IconVariant="Regular" />
</ui:NavigationViewItem.IconSource>
</ui:NavigationViewItem>
<ui:NavigationViewItem x:Name="SettingsNavColorItem" Content="颜色" Tag="Color" ToolTip.Tip="颜色">
<ui:NavigationViewItem.IconSource>
<ic:SymbolIconSource Symbol="Color" IconVariant="Regular" />
</ui:NavigationViewItem.IconSource>
</ui:NavigationViewItem>
<ui:NavigationViewItem x:Name="SettingsNavStatusBarItem" Content="状态栏" Tag="StatusBar" ToolTip.Tip="状态栏">
<ui:NavigationViewItem.IconSource>
<ic:SymbolIconSource Symbol="Status" IconVariant="Regular" />
</ui:NavigationViewItem.IconSource>
</ui:NavigationViewItem>
<ui:NavigationViewItem x:Name="SettingsNavWeatherItem" Content="天气" Tag="Weather" ToolTip.Tip="天气">
<ui:NavigationViewItem.IconSource>
<ic:SymbolIconSource Symbol="WeatherSunny" IconVariant="Regular" />
</ui:NavigationViewItem.IconSource>
</ui:NavigationViewItem>
<ui:NavigationViewItem x:Name="SettingsNavRegionItem" Content="地区" Tag="Region" ToolTip.Tip="地区">
<ui:NavigationViewItem.IconSource>
<ic:SymbolIconSource Symbol="Globe" IconVariant="Regular" />
</ui:NavigationViewItem.IconSource>
</ui:NavigationViewItem>
<ui:NavigationViewItem x:Name="SettingsNavUpdateItem" Content="更新" Tag="Update" ToolTip.Tip="更新">
<ui:NavigationViewItem.IconSource>
<ic:SymbolIconSource Symbol="ArrowSync" IconVariant="Regular" />
</ui:NavigationViewItem.IconSource>
</ui:NavigationViewItem>
<ui:NavigationViewItem x:Name="SettingsNavAboutItem" Content="关于" Tag="About" ToolTip.Tip="关于">
<ui:NavigationViewItem.IconSource>
<ic:SymbolIconSource Symbol="Info" IconVariant="Regular" />
</ui:NavigationViewItem.IconSource>
</ui:NavigationViewItem>
<ui:NavigationViewItem x:Name="SettingsNavLauncherItem" Content="应用启动台" Tag="Launcher" ToolTip.Tip="应用启动台">
<ui:NavigationViewItem.IconSource>
<ic:SymbolIconSource Symbol="Apps" IconVariant="Regular" />
</ui:NavigationViewItem.IconSource>
</ui:NavigationViewItem>
<ui:NavigationViewItem x:Name="SettingsNavPluginsItem" Content="插件" Tag="Plugins" ToolTip.Tip="插件">
<ui:NavigationViewItem.IconSource>
<ic:SymbolIconSource Symbol="PuzzlePiece" IconVariant="Regular" />
</ui:NavigationViewItem.IconSource>
</ui:NavigationViewItem>
<ui:NavigationViewItem x:Name="SettingsNavPluginMarketItem" Content="插件市场" Tag="PluginMarket" ToolTip.Tip="插件市场">
<ui:NavigationViewItem.IconSource>
<ic:SymbolIconSource Symbol="PuzzlePiece" IconVariant="Regular" />
</ui:NavigationViewItem.IconSource>
</ui:NavigationViewItem>
</ui:NavigationView.MenuItems>
<ScrollViewer x:Name="SettingsContentScrollViewer"
Padding="0,0,16,0"
HorizontalScrollBarVisibility="Disabled"
VerticalScrollBarVisibility="Auto">
<Grid x:Name="SettingsContentPagesHost">
<pages:WallpaperSettingsPage x:Name="WallpaperSettingsPanel" IsVisible="True" />
<pages:GridSettingsPage x:Name="GridSettingsPanel" IsVisible="False" />
<pages:ColorSettingsPage x:Name="ColorSettingsPanel" IsVisible="False" />
<pages:StatusBarSettingsPage x:Name="StatusBarSettingsPanel" IsVisible="False" />
<pages:WeatherSettingsPage x:Name="WeatherSettingsPanel" IsVisible="False" />
<pages:RegionSettingsPage x:Name="RegionSettingsPanel" IsVisible="False" />
<pages:UpdateSettingsPage x:Name="UpdateSettingsPanel" IsVisible="False" />
<pages:LauncherSettingsPage x:Name="LauncherSettingsPanel" IsVisible="False" />
<pages:AboutSettingsPage x:Name="AboutSettingsPanel" IsVisible="False" />
<pages:PluginSettingsPage x:Name="PluginSettingsPanel" IsVisible="False" />
<pages:PluginMarketSettingsPage x:Name="PluginMarketSettingsPanel" IsVisible="False" />
</Grid>
</ScrollViewer>
</ui:NavigationView>
<StackPanel Grid.Row="1"
Spacing="12">
<Border x:Name="PendingRestartDock"
IsVisible="False"
Classes="glass-panel"
CornerRadius="18"
Padding="14,12">
<Grid ColumnDefinitions="Auto,*,Auto"
ColumnSpacing="12">
<Border Width="34"
Height="34"
CornerRadius="17"
Background="{DynamicResource AdaptiveAccentBrush}">
<fi:FluentIcon Icon="ArrowSync"
IconVariant="Regular"
FontSize="16"
Foreground="White"
HorizontalAlignment="Center"
VerticalAlignment="Center" />
</Border>
<StackPanel Grid.Column="1"
Spacing="2"
VerticalAlignment="Center">
<TextBlock x:Name="PendingRestartDockTitleTextBlock"
FontSize="13"
FontWeight="SemiBold"
Text="Restart required" />
<TextBlock x:Name="PendingRestartDockDescriptionTextBlock"
TextWrapping="Wrap"
Foreground="{DynamicResource AdaptiveTextSecondaryBrush}"
Text="Your changes will apply after restarting the app." />
</StackPanel>
<Button x:Name="PendingRestartDockButton"
Grid.Column="2"
Padding="14,8"
Click="OnPendingRestartDockButtonClick">
<StackPanel Orientation="Horizontal" Spacing="8">
<fi:FluentIcon Icon="ArrowSync"
IconVariant="Regular" />
<TextBlock x:Name="PendingRestartDockButtonTextBlock"
VerticalAlignment="Center"
Text="Restart app" />
</StackPanel>
</Button>
</Grid>
</Border>
</StackPanel>
</Grid>
</Border>
</Border>
</Grid>
<Border x:Name="ComponentLibraryWindow"
IsVisible="False"
Opacity="0"

File diff suppressed because it is too large Load Diff

View File

@@ -1,4 +1,4 @@
using System;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
@@ -20,7 +20,9 @@ using Avalonia.Threading;
using FluentAvalonia.Styling;
using LanMountainDesktop.ComponentSystem;
using LanMountainDesktop.Models;
using LanMountainDesktop.PluginSdk;
using LanMountainDesktop.Services;
using LanMountainDesktop.Services.Settings;
using LanMountainDesktop.Theme;
using LanMountainDesktop.Views.Components;
using LibVLCSharp.Shared;
@@ -73,21 +75,26 @@ public partial class MainWindow : Window
[
TaskbarActionId.MinimizeToWindows
];
private readonly DesktopGridLayoutService _gridLayoutService = new();
private readonly MonetColorService _monetColorService = new();
private readonly AppSettingsService _appSettingsService = new();
private readonly DesktopLayoutSettingsService _desktopLayoutSettingsService = new();
private readonly LauncherSettingsService _launcherSettingsService = new();
private readonly ComponentSettingsService _componentSettingsService = new();
private readonly ISettingsFacadeService _settingsFacade = HostSettingsFacadeProvider.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 = new ComponentSettingsService();
private readonly LocalizationService _localizationService = new();
private readonly TimeZoneService _timeZoneService = new();
private readonly TimeZoneService _timeZoneService;
private readonly WindowsStartupService _windowsStartupService = new();
private readonly GitHubReleaseUpdateService _releaseUpdateService = new("wwiinnddyy", "LanMountainDesktop");
private readonly IWeatherDataService _weatherDataService = new XiaomiWeatherService();
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 IComponentLibraryService _componentLibraryService;
private readonly IComponentLibraryWindowService _componentLibraryWindowService = new ComponentLibraryWindowService();
private readonly FluentAvaloniaTheme? _fluentAvaloniaTheme;
private readonly HashSet<string> _topStatusComponentIds = new(StringComparer.OrdinalIgnoreCase);
private readonly HashSet<TaskbarActionId> _pinnedTaskbarActions = [];
@@ -171,71 +178,25 @@ public partial class MainWindow : Window
{
var pluginRuntimeService = (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();
InitializePluginSettingsNavigation();
_componentRuntimeRegistry = DesktopComponentRegistryFactory.CreateRuntimeRegistry(
_componentRegistry,
pluginRuntimeService);
_componentLibraryService = new ComponentLibraryService(_componentRegistry, _componentRuntimeRegistry);
_fluentAvaloniaTheme = Application.Current?.Styles.OfType<FluentAvaloniaTheme>().FirstOrDefault();
AppSettingsService.SettingsSaved += OnExternalAppSettingsSaved;
LauncherSettingsService.SettingsSaved += OnExternalLauncherSettingsSaved;
PendingRestartStateService.StateChanged += OnPendingRestartStateChanged;
_settingsService.Changed += OnSettingsChanged;
PropertyChanged += OnWindowPropertyChanged;
InitializeDesktopSurfaceSwipeHandlers();
InitializeDesktopComponentDragHandlers();
PickWallpaperButton.Click += OnPickWallpaperClick;
ClearWallpaperButton.Click += OnClearWallpaperClick;
WallpaperPlacementComboBox.SelectionChanged += OnWallpaperPlacementSelectionChanged;
GridSizeSlider.ValueChanged += OnGridSizeSliderChanged;
GridSpacingPresetComboBox.SelectionChanged += OnGridSpacingPresetSelectionChanged;
GridEdgeInsetSlider.ValueChanged += OnGridEdgeInsetSliderChanged;
ApplyGridButton.Click += OnApplyGridSizeClick;
NightModeToggleSwitch.IsCheckedChanged += OnNightModeIsCheckedChanged;
RecommendedColorButton1.Click += OnRecommendedColorClick;
RecommendedColorButton2.Click += OnRecommendedColorClick;
RecommendedColorButton3.Click += OnRecommendedColorClick;
RecommendedColorButton4.Click += OnRecommendedColorClick;
RecommendedColorButton5.Click += OnRecommendedColorClick;
RecommendedColorButton6.Click += OnRecommendedColorClick;
RefreshMonetColorsButton.Click += OnRefreshMonetColorsClick;
MonetColorButton1.Click += OnMonetColorClick;
MonetColorButton2.Click += OnMonetColorClick;
MonetColorButton3.Click += OnMonetColorClick;
MonetColorButton4.Click += OnMonetColorClick;
MonetColorButton5.Click += OnMonetColorClick;
MonetColorButton6.Click += OnMonetColorClick;
StatusBarClockToggleSwitch.IsCheckedChanged += OnStatusBarClockIsCheckedChanged;
ClockFormatHMSSRadio.IsCheckedChanged += OnClockFormatChanged;
ClockFormatHMRadio.IsCheckedChanged += OnClockFormatChanged;
StatusBarSpacingModeComboBox.SelectionChanged += OnStatusBarSpacingModeChanged;
StatusBarSpacingSlider.ValueChanged += OnStatusBarSpacingSliderChanged;
WeatherPreviewButton.Click += OnTestWeatherRequestClick;
WeatherLocationModeComboBox.SelectionChanged += OnWeatherLocationModeSelectionChanged;
WeatherLocationModeChipListBox.SelectionChanged += OnWeatherLocationModeChipSelectionChanged;
WeatherAutoRefreshToggleSwitch.IsCheckedChanged += OnWeatherAutoRefreshToggled;
WeatherSearchButton.Click += OnSearchWeatherCityClick;
WeatherApplyCityButton.Click += OnApplyWeatherCitySelectionClick;
WeatherApplyCoordinatesButton.Click += OnApplyWeatherCoordinatesClick;
WeatherExcludedAlertsTextBox.LostFocus += OnWeatherExcludedAlertsLostFocus;
WeatherIconPackComboBox.SelectionChanged += OnWeatherIconPackSelectionChanged;
WeatherNoTlsToggleSwitch.IsCheckedChanged += OnWeatherNoTlsToggled;
LanguageComboBox.SelectionChanged += OnLanguageSelectionChanged;
TimeZoneComboBox.SelectionChanged += OnTimeZoneSelectionChanged;
AutoCheckUpdatesToggleSwitch.IsCheckedChanged += OnAutoCheckUpdatesToggled;
UpdateChannelChipListBox.SelectionChanged += OnUpdateChannelSelectionChanged;
CheckForUpdatesButton.Click += OnCheckForUpdatesClick;
DownloadAndInstallUpdateButton.Click += OnDownloadAndInstallUpdateClick;
AutoStartWithWindowsToggleSwitch.IsCheckedChanged += OnAutoStartWithWindowsToggled;
AppRenderModeComboBox.SelectionChanged += OnAppRenderModeSelectionChanged;
}
private void OnNightModeIsCheckedChanged(object? sender, RoutedEventArgs e)
@@ -275,9 +236,9 @@ public partial class MainWindow : Window
base.OnOpened(e);
_suppressSettingsPersistence = true;
var snapshot = _appSettingsService.Load();
var desktopLayoutSnapshot = _desktopLayoutSettingsService.Load();
var launcherSnapshot = _launcherSettingsService.Load();
var snapshot = _settingsService.LoadSnapshot<AppSettingsSnapshot>(SettingsScope.App);
var desktopLayoutSnapshot = _componentLayoutStore.LoadLayout();
var launcherSnapshot = _settingsService.LoadSnapshot<LauncherSettingsSnapshot>(SettingsScope.Launcher);
if (!string.IsNullOrWhiteSpace(snapshot.TimeZoneId))
{
@@ -289,47 +250,16 @@ public partial class MainWindow : Window
MinShortSideCells,
MaxShortSideCells);
_gridSpacingPreset = _gridLayoutService.NormalizeSpacingPreset(snapshot.GridSpacingPreset);
_suppressGridSpacingEvents = true;
GridSpacingPresetComboBox.SelectedIndex = string.Equals(_gridSpacingPreset, "Compact", StringComparison.OrdinalIgnoreCase) ? 1 : 0;
_suppressGridSpacingEvents = false;
_gridSpacingPreset = _gridSettingsService.NormalizeSpacingPreset(snapshot.GridSpacingPreset);
_desktopEdgeInsetPercent = Math.Clamp(snapshot.DesktopEdgeInsetPercent, MinEdgeInsetPercent, MaxEdgeInsetPercent);
_suppressGridInsetEvents = true;
GridEdgeInsetSlider.Value = _desktopEdgeInsetPercent;
GridEdgeInsetNumberBox.Value = _desktopEdgeInsetPercent;
_suppressGridInsetEvents = false;
GridEdgeInsetNumberBox.ValueChanged += OnGridEdgeInsetNumberBoxChanged;
_statusBarSpacingMode = NormalizeStatusBarSpacingMode(snapshot.StatusBarSpacingMode);
_statusBarCustomSpacingPercent = Math.Clamp(snapshot.StatusBarCustomSpacingPercent, 0, 30);
_suppressStatusBarSpacingEvents = true;
StatusBarSpacingModeComboBox.SelectedIndex = _statusBarSpacingMode switch
{
"Compact" => 0,
"Custom" => 2,
_ => 1
};
StatusBarSpacingSlider.Value = _statusBarCustomSpacingPercent;
StatusBarSpacingNumberBox.Value = _statusBarCustomSpacingPercent;
StatusBarSpacingCustomPanel.IsVisible = string.Equals(_statusBarSpacingMode, "Custom", StringComparison.OrdinalIgnoreCase);
_suppressStatusBarSpacingEvents = false;
StatusBarSpacingNumberBox.ValueChanged += OnStatusBarSpacingNumberBoxChanged;
GridSizeNumberBox.Value = _targetShortSideCells;
GridSizeSlider.Value = _targetShortSideCells;
GridSizeSlider.ValueChanged += OnGridSizeSliderChanged;
GridSizeNumberBox.ValueChanged += OnGridSizeNumberBoxChanged;
RestoreSettingsTabSelection(snapshot);
UpdateSettingsTabContent();
WallpaperPlacementComboBox.SelectedIndex = GetPlacementIndexFromSetting(snapshot.WallpaperPlacement);
_defaultDesktopBackground = DesktopWallpaperLayer.Background;
ApplyTaskbarSettings(snapshot);
InitializeLocalization(snapshot.LanguageCode);
InitializeWeatherSettings(snapshot);
_ = _componentSettingsService.Load();
InitializeAutoStartWithWindowsSetting(snapshot);
InitializeAppRenderModeSetting(snapshot);
InitializeUpdateSettings(snapshot);
@@ -349,15 +279,8 @@ public partial class MainWindow : Window
_isNightMode = snapshot.IsNightMode ?? (CalculateCurrentBackgroundLuminance() < LightBackgroundLuminanceThreshold);
ApplyNightModeState(_isNightMode, refreshPalettes: true);
_suppressStatusBarToggleEvents = true;
StatusBarClockToggleSwitch.IsChecked = _topStatusComponentIds.Contains(BuiltInComponentIds.Clock);
_suppressStatusBarToggleEvents = false;
ApplyLocalization();
ThemeColorStatusTextBlock.Text = Lf("settings.color.theme_ready_format", "Theme color ready: {0}.", _selectedThemeColor);
_settingsContentPanelTransform = SettingsContentPanel.RenderTransform as TranslateTransform;
DesktopHost.SizeChanged += OnDesktopHostSizeChanged;
WallpaperPreviewHost.SizeChanged += OnWallpaperPreviewHostSizeChanged;
GridPreviewHost.SizeChanged += OnGridPreviewHostSizeChanged;
RebuildDesktopGrid();
LoadLauncherEntriesAsync();
InitializeTimeZoneSettings();
@@ -384,28 +307,15 @@ public partial class MainWindow : Window
_wallpaperPreviewSnapshotBitmap = null;
_libVlc?.Dispose();
_libVlc = null;
if (_weatherDataService is IDisposable weatherServiceDisposable)
{
weatherServiceDisposable.Dispose();
}
if (_recommendationInfoService is IDisposable recommendationServiceDisposable)
{
recommendationServiceDisposable.Dispose();
}
_releaseUpdateService.Dispose();
_wallpaperBitmap?.Dispose();
_wallpaperBitmap = null;
AppSettingsService.SettingsSaved -= OnExternalAppSettingsSaved;
LauncherSettingsService.SettingsSaved -= OnExternalLauncherSettingsSaved;
PendingRestartStateService.StateChanged -= OnPendingRestartStateChanged;
_settingsService.Changed -= OnSettingsChanged;
PropertyChanged -= OnWindowPropertyChanged;
DesktopHost.SizeChanged -= OnDesktopHostSizeChanged;
WallpaperPreviewHost.SizeChanged -= OnWallpaperPreviewHostSizeChanged;
GridPreviewHost.SizeChanged -= OnGridPreviewHostSizeChanged;
GridSizeSlider.ValueChanged -= OnGridSizeSliderChanged;
GridSizeNumberBox.ValueChanged -= OnGridSizeNumberBoxChanged;
GridEdgeInsetNumberBox.ValueChanged -= OnGridEdgeInsetNumberBoxChanged;
StatusBarSpacingNumberBox.ValueChanged -= OnStatusBarSpacingNumberBoxChanged;
base.OnClosed(e);
}
@@ -434,6 +344,11 @@ public partial class MainWindow : Window
private void OnGridSizeSliderChanged(object? sender, RoutedEventArgs e)
{
if (GridSizeSlider is null || GridSizeNumberBox is null)
{
return;
}
var sliderValue = (int)Math.Round(GridSizeSlider.Value);
if (Math.Abs(GridSizeNumberBox.Value - sliderValue) > double.Epsilon)
{
@@ -444,6 +359,11 @@ public partial class MainWindow : Window
private void OnGridSizeNumberBoxChanged(object? sender, NumberBoxValueChangedEventArgs e)
{
if (GridSizeSlider is null || GridSizeNumberBox is null)
{
return;
}
var numberBoxValue = (int)Math.Round(GridSizeNumberBox.Value);
if (Math.Abs(GridSizeSlider.Value - numberBoxValue) > double.Epsilon)
{
@@ -454,6 +374,11 @@ public partial class MainWindow : Window
private void OnGridEdgeInsetSliderChanged(object? sender, RoutedEventArgs e)
{
if (GridEdgeInsetSlider is null)
{
return;
}
if (_suppressGridInsetEvents)
{
return;
@@ -466,6 +391,11 @@ public partial class MainWindow : Window
private void OnGridEdgeInsetNumberBoxChanged(object? sender, NumberBoxValueChangedEventArgs e)
{
if (GridEdgeInsetNumberBox is null)
{
return;
}
if (_suppressGridInsetEvents)
{
return;
@@ -511,6 +441,11 @@ public partial class MainWindow : Window
private void OnStatusBarSpacingModeChanged(object? sender, SelectionChangedEventArgs e)
{
if (StatusBarSpacingModeComboBox is null)
{
return;
}
if (_suppressStatusBarSpacingEvents)
{
return;
@@ -529,6 +464,11 @@ public partial class MainWindow : Window
private void OnStatusBarSpacingSliderChanged(object? sender, RangeBaseValueChangedEventArgs e)
{
if (StatusBarSpacingSlider is null)
{
return;
}
if (_suppressStatusBarSpacingEvents)
{
return;
@@ -549,6 +489,11 @@ public partial class MainWindow : Window
private void OnStatusBarSpacingNumberBoxChanged(object? sender, NumberBoxValueChangedEventArgs e)
{
if (StatusBarSpacingNumberBox is null)
{
return;
}
if (_suppressStatusBarSpacingEvents)
{
return;
@@ -597,7 +542,8 @@ public partial class MainWindow : Window
GridPreviewHost is null ||
GridPreviewViewport is null ||
GridPreviewGrid is null ||
GridPreviewLinesCanvas is null)
GridPreviewLinesCanvas is null ||
GridSizeSlider is null)
{
return;
}
@@ -626,11 +572,11 @@ public partial class MainWindow : Window
var innerWidth = Math.Max(1, gridPreviewWidth - horizontalPadding);
var innerHeight = Math.Max(1, gridPreviewHeight - verticalPadding);
var preset = _gridLayoutService.NormalizeSpacingPreset(TryGetSelectedComboBoxTag(GridSpacingPresetComboBox) ?? _gridSpacingPreset);
var gapRatio = _gridLayoutService.ResolveGapRatio(preset);
var preset = _gridSettingsService.NormalizeSpacingPreset(TryGetSelectedComboBoxTag(GridSpacingPresetComboBox) ?? _gridSpacingPreset);
var gapRatio = _gridSettingsService.ResolveGapRatio(preset);
var pendingEdgeInsetPercent = ResolvePendingGridEdgeInsetPercent();
var edgeInset = _gridLayoutService.CalculateEdgeInset(innerWidth, innerHeight, previewShortSideCells, pendingEdgeInsetPercent);
var gridMetrics = _gridLayoutService.CalculateGridMetrics(innerWidth, innerHeight, previewShortSideCells, gapRatio, edgeInset);
var edgeInset = _gridSettingsService.CalculateEdgeInset(innerWidth, innerHeight, previewShortSideCells, pendingEdgeInsetPercent);
var gridMetrics = _gridSettingsService.CalculateGridMetrics(innerWidth, innerHeight, previewShortSideCells, gapRatio, edgeInset);
if (gridMetrics.CellSize <= 0)
{
return;
@@ -676,12 +622,15 @@ public partial class MainWindow : Window
ApplyStatusBarComponentSpacingForPanel(GridPreviewTopStatusComponentsPanel, gridMetrics.CellSize);
UpdateGridEdgeInsetComputedPxText(gridMetrics.CellSize);
GridInfoTextBlock.Text = Lf(
"settings.grid.info_format",
"Grid: {0} cols x {1} rows | cell {2:F1}px (1:1)",
gridMetrics.ColumnCount,
gridMetrics.RowCount,
gridMetrics.CellSize);
if (GridInfoTextBlock is not null)
{
GridInfoTextBlock.Text = Lf(
"settings.grid.info_format",
"Grid: {0} cols x {1} rows | cell {2:F1}px (1:1)",
gridMetrics.ColumnCount,
gridMetrics.RowCount,
gridMetrics.CellSize);
}
DrawGridPreviewLines(gridMetrics);
}
@@ -767,7 +716,12 @@ public partial class MainWindow : Window
private void OnApplyGridSizeClick(object? sender, RoutedEventArgs e)
{
_gridSpacingPreset = _gridLayoutService.NormalizeSpacingPreset(
if (GridSizeNumberBox is null || GridSizeSlider is null)
{
return;
}
_gridSpacingPreset = _gridSettingsService.NormalizeSpacingPreset(
TryGetSelectedComboBoxTag(GridSpacingPresetComboBox) ?? _gridSpacingPreset);
_desktopEdgeInsetPercent = ResolvePendingGridEdgeInsetPercent();
@@ -825,9 +779,9 @@ public partial class MainWindow : Window
{
var hostWidth = DesktopHost.Bounds.Width;
var hostHeight = DesktopHost.Bounds.Height;
var gapRatio = _gridLayoutService.ResolveGapRatio(_gridSpacingPreset);
var edgeInset = _gridLayoutService.CalculateEdgeInset(hostWidth, hostHeight, _targetShortSideCells, _desktopEdgeInsetPercent);
var gridMetrics = _gridLayoutService.CalculateGridMetrics(hostWidth, hostHeight, _targetShortSideCells, gapRatio, edgeInset);
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;
@@ -875,12 +829,15 @@ public partial class MainWindow : Window
UpdateDesktopSurfaceLayout(gridMetrics);
UpdateSettingsViewportInsets(gridMetrics.CellSize);
GridInfoTextBlock.Text = Lf(
"settings.grid.info_format",
"Grid: {0} cols x {1} rows | cell {2:F1}px (1:1)",
gridMetrics.ColumnCount,
gridMetrics.RowCount,
gridMetrics.CellSize);
if (GridInfoTextBlock is not null)
{
GridInfoTextBlock.Text = Lf(
"settings.grid.info_format",
"Grid: {0} cols x {1} rows | cell {2:F1}px (1:1)",
gridMetrics.ColumnCount,
gridMetrics.RowCount,
gridMetrics.CellSize);
}
UpdateWallpaperPreviewLayout();
}
@@ -930,6 +887,11 @@ public partial class MainWindow : Window
private int ResolvePendingGridEdgeInsetPercent()
{
if (GridEdgeInsetNumberBox is null)
{
return _desktopEdgeInsetPercent;
}
var pending = (int)Math.Round(GridEdgeInsetNumberBox.Value);
return Math.Clamp(pending, MinEdgeInsetPercent, MaxEdgeInsetPercent);
}
@@ -1067,47 +1029,7 @@ public partial class MainWindow : Window
private void UpdateSettingsViewportInsets(double cellSize)
{
if (SettingsContentPanel is null)
{
return;
}
var clampedCell = Math.Max(1, cellSize);
var horizontalInset = Math.Clamp(clampedCell * 0.45, 12, 64);
var verticalGap = Math.Clamp(clampedCell * 0.16, 6, 18);
var edgeInset = Math.Max(0, _currentDesktopEdgeInset);
var taskbarCellHeight = Math.Clamp(clampedCell * 0.76, 36, 76);
var taskbarPadding = Math.Clamp(taskbarCellHeight * 0.16, 6, 14);
var taskbarVisualHeight = Math.Max(clampedCell, taskbarCellHeight + taskbarPadding * 2);
if (BottomTaskbarContainer is not null && BottomTaskbarContainer.Bounds.Height > 1)
{
taskbarVisualHeight = Math.Max(taskbarVisualHeight, BottomTaskbarContainer.Bounds.Height);
}
var statusBarVisualHeight = clampedCell;
if (TopStatusBarHost is not null && TopStatusBarHost.Bounds.Height > 1)
{
statusBarVisualHeight = Math.Max(statusBarVisualHeight, TopStatusBarHost.Bounds.Height);
}
var topInset = Math.Max(clampedCell + verticalGap, edgeInset + statusBarVisualHeight + verticalGap);
var bottomInset = Math.Max(clampedCell + verticalGap, edgeInset + taskbarVisualHeight + verticalGap);
// Add extra safety margin so rounded panel corners never clip against viewport edges.
var cornerSafetyMargin = Math.Clamp(clampedCell * 0.12, 4, 12);
var inset = new Thickness(
horizontalInset + cornerSafetyMargin,
topInset + cornerSafetyMargin,
horizontalInset + cornerSafetyMargin,
bottomInset + cornerSafetyMargin);
// Keep panel stretched with explicit viewport insets so it never overlaps fixed chrome.
SettingsContentPanel.HorizontalAlignment = HorizontalAlignment.Stretch;
SettingsContentPanel.VerticalAlignment = VerticalAlignment.Stretch;
SettingsContentPanel.Margin = inset;
SettingsContentPanel.Width = double.NaN;
SettingsContentPanel.Height = double.NaN;
_ = cellSize;
}
private void UpdateWallpaperPreviewLayout()
@@ -1160,9 +1082,9 @@ public partial class MainWindow : Window
var innerWidth = Math.Max(1, previewWidth - horizontalPadding);
var innerHeight = Math.Max(1, previewHeight - verticalPadding);
var gapRatio = _gridLayoutService.ResolveGapRatio(_gridSpacingPreset);
var edgeInset = _gridLayoutService.CalculateEdgeInset(innerWidth, innerHeight, _targetShortSideCells, _desktopEdgeInsetPercent);
var gridMetrics = _gridLayoutService.CalculateGridMetrics(innerWidth, innerHeight, _targetShortSideCells, gapRatio, edgeInset);
var gapRatio = _gridSettingsService.ResolveGapRatio(_gridSpacingPreset);
var edgeInset = _gridSettingsService.CalculateEdgeInset(innerWidth, innerHeight, _targetShortSideCells, _desktopEdgeInsetPercent);
var gridMetrics = _gridSettingsService.CalculateGridMetrics(innerWidth, innerHeight, _targetShortSideCells, gapRatio, edgeInset);
if (gridMetrics.CellSize <= 0)
{
return;
@@ -1273,6 +1195,11 @@ public partial class MainWindow : Window
private void InitializeTimeZoneSettings()
{
if (TimeZoneComboBox is null)
{
return;
}
// Populate timezone dropdown items before selecting current timezone.
_suppressTimeZoneSelectionEvents = true;
TimeZoneComboBox.Items.Clear();
@@ -1299,7 +1226,9 @@ public partial class MainWindow : Window
private void OnTimeZoneSelectionChanged(object? sender, SelectionChangedEventArgs e)
{
if (_suppressTimeZoneSelectionEvents || TimeZoneComboBox.SelectedItem is not ComboBoxItem item)
if (TimeZoneComboBox is null ||
_suppressTimeZoneSelectionEvents ||
TimeZoneComboBox.SelectedItem is not ComboBoxItem item)
{
return;
}

View File

@@ -1,80 +0,0 @@
<UserControl xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:ui="using:FluentAvalonia.UI.Controls"
xmlns:fi="using:FluentIcons.Avalonia.Fluent"
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="1000"
x:Class="LanMountainDesktop.Views.SettingsPages.AboutSettingsPage">
<StackPanel x:Name="AboutSettingsPanel" Spacing="20">
<TextBlock x:Name="AboutPanelTitleTextBlock" FontSize="24" FontWeight="SemiBold" Foreground="{DynamicResource TextFillColorPrimaryBrush}" Text="About" />
<Border Background="{DynamicResource AdaptiveSurfaceRaisedBrush}" CornerRadius="{DynamicResource DesignCornerRadiusMd}" Padding="20">
<StackPanel Spacing="12">
<TextBlock Text="LanMountainDesktop" FontSize="20" FontWeight="SemiBold" Foreground="{DynamicResource TextFillColorPrimaryBrush}" />
<TextBlock Text="Modern desktop shell experience." FontSize="13" Foreground="{DynamicResource TextFillColorSecondaryBrush}" />
<Separator Background="{DynamicResource AdaptiveButtonBorderBrush}" Margin="0,8" />
<TextBlock x:Name="VersionTextBlock" Text="Version: 1.0.0" FontSize="13" Foreground="{DynamicResource TextFillColorPrimaryBrush}" />
<TextBlock x:Name="CodeNameTextBlock" Text="Code Name: Administrate" FontSize="13" FontWeight="SemiBold" Foreground="{DynamicResource AdaptiveAccentBrush}" />
<TextBlock x:Name="FontInfoTextBlock" Text="Font: MiSans" FontSize="12" Foreground="{DynamicResource TextFillColorSecondaryBrush}" />
</StackPanel>
</Border>
<Border Classes="settings-expander-shell">
<ui:SettingsExpander x:Name="AboutStartupSettingsExpander"
Header="Windows Startup"
Description="Launch the app automatically when signing in to Windows."
IsExpanded="True">
<ui:SettingsExpander.IconSource>
<fi:SymbolIconSource Symbol="Window" />
</ui:SettingsExpander.IconSource>
<ui:SettingsExpander.Footer>
<ToggleSwitch x:Name="AutoStartWithWindowsToggleSwitch" />
</ui:SettingsExpander.Footer>
</ui:SettingsExpander>
</Border>
<Border Classes="settings-expander-shell">
<ui:SettingsExpander x:Name="AboutRenderModeSettingsExpander"
Header="Rendering Mode"
Description="Choose the rendering backend. Restart the app after changing this option. Unsupported modes fall back to software."
IsExpanded="True">
<ui:SettingsExpander.IconSource>
<fi:SymbolIconSource Symbol="Window" />
</ui:SettingsExpander.IconSource>
<StackPanel Spacing="4"
Margin="0,4,0,0">
<TextBlock x:Name="CurrentRenderBackendLabelTextBlock"
Text="Current actual backend"
FontSize="12"
FontWeight="SemiBold"
Foreground="{DynamicResource TextFillColorPrimaryBrush}" />
<TextBlock x:Name="CurrentRenderBackendValueTextBlock"
Text="Current backend: Software"
FontSize="13"
TextWrapping="Wrap"
Foreground="{DynamicResource TextFillColorPrimaryBrush}" />
<TextBlock x:Name="CurrentRenderBackendImplementationTextBlock"
Text="Runtime implementation is unavailable."
FontSize="12"
TextWrapping="Wrap"
Foreground="{DynamicResource TextFillColorSecondaryBrush}" />
</StackPanel>
<ui:SettingsExpander.Footer>
<ComboBox x:Name="AppRenderModeComboBox"
MinWidth="180"
SelectedIndex="0"
HorizontalAlignment="Right">
<ComboBoxItem Content="Default" Tag="Default" />
<ComboBoxItem Content="Software" Tag="Software" />
<ComboBoxItem Content="angleEgl" Tag="AngleEgl" />
<ComboBoxItem Content="WGL" Tag="Wgl" />
<ComboBoxItem Content="Vulkan" Tag="Vulkan" />
</ComboBox>
</ui:SettingsExpander.Footer>
</ui:SettingsExpander>
</Border>
</StackPanel>
</UserControl>

View File

@@ -1,12 +0,0 @@
using Avalonia.Controls;
using Avalonia.Markup.Xaml;
namespace LanMountainDesktop.Views.SettingsPages;
public partial class AboutSettingsPage : UserControl
{
public AboutSettingsPage()
{
InitializeComponent();
}
}

View File

@@ -1,58 +0,0 @@
<UserControl xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d"
d:DesignWidth="960"
d:DesignHeight="1400"
x:Class="LanMountainDesktop.Views.SettingsPages.AppearanceSettingsPage">
<StackPanel MaxWidth="920"
Spacing="16">
<TextBlock x:Name="AppearancePageSubtitleTextBlock"
Foreground="{DynamicResource TextFillColorSecondaryBrush}"
TextWrapping="Wrap"
Text="Personalize wallpaper, desktop grid, and accent colors in one place." />
<Border Classes="settings-page-shell">
<StackPanel Spacing="12">
<TextBlock x:Name="AppearanceWallpaperSectionTitleTextBlock"
FontSize="18"
FontWeight="SemiBold"
Text="Wallpaper" />
<TextBlock x:Name="AppearanceWallpaperSectionHintTextBlock"
Foreground="{DynamicResource TextFillColorSecondaryBrush}"
TextWrapping="Wrap"
Text="Use lightweight thumbnails and asset controls instead of heavy live preview." />
<ContentControl x:Name="WallpaperContentHost" />
</StackPanel>
</Border>
<Border Classes="settings-page-shell">
<StackPanel Spacing="12">
<TextBlock x:Name="AppearanceGridSectionTitleTextBlock"
FontSize="18"
FontWeight="SemiBold"
Text="Grid" />
<TextBlock x:Name="AppearanceGridSectionHintTextBlock"
Foreground="{DynamicResource TextFillColorSecondaryBrush}"
TextWrapping="Wrap"
Text="Tune grid density, spacing, and safe edge inset for the desktop canvas." />
<ContentControl x:Name="GridContentHost" />
</StackPanel>
</Border>
<Border Classes="settings-page-shell">
<StackPanel Spacing="12">
<TextBlock x:Name="AppearanceColorSectionTitleTextBlock"
FontSize="18"
FontWeight="SemiBold"
Text="Color" />
<TextBlock x:Name="AppearanceColorSectionHintTextBlock"
Foreground="{DynamicResource TextFillColorSecondaryBrush}"
TextWrapping="Wrap"
Text="Choose theme mode and accent colors with Fluent-consistent swatches." />
<ContentControl x:Name="ColorContentHost" />
</StackPanel>
</Border>
</StackPanel>
</UserControl>

View File

@@ -1,11 +0,0 @@
using Avalonia.Controls;
namespace LanMountainDesktop.Views.SettingsPages;
public partial class AppearanceSettingsPage : UserControl
{
public AppearanceSettingsPage()
{
InitializeComponent();
}
}

View File

@@ -1,218 +0,0 @@
<UserControl xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:ui="using:FluentAvalonia.UI.Controls"
xmlns:fi="using:FluentIcons.Avalonia"
xmlns:ic="using:FluentIcons.Avalonia.Fluent"
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="600"
x:Class="LanMountainDesktop.Views.SettingsPages.ColorSettingsPage">
<StackPanel x:Name="ColorSettingsPanel"
MaxWidth="920"
Spacing="16">
<TextBlock x:Name="ColorPanelTitleTextBlock"
IsVisible="False"
FontSize="24"
FontWeight="SemiBold"
Foreground="{DynamicResource TextFillColorPrimaryBrush}"
Text="Color" />
<TextBlock x:Name="ColorPanelSubtitleTextBlock"
Foreground="{DynamicResource TextFillColorSecondaryBrush}"
TextWrapping="Wrap"
Text="Adjust theme mode and accent colors. The desktop shell will reuse these colors consistently." />
<Border Classes="settings-expander-shell">
<ui:SettingsExpander x:Name="ThemeModeSettingsExpander"
Header="&#26085;&#22812;&#27169;&#24335;"
Description="&#20999;&#25442;&#24212;&#29992;&#30340;&#27973;&#33394;&#25110;&#28145;&#33394;&#20027;&#39064;&#12290;">
<ui:SettingsExpander.IconSource>
<ic:SymbolIconSource Symbol="DarkTheme"
IconVariant="Regular" />
</ui:SettingsExpander.IconSource>
<ui:SettingsExpander.Footer>
<ToggleSwitch x:Name="NightModeToggleSwitch"
OffContent="Day"
OnContent="Night" />
</ui:SettingsExpander.Footer>
</ui:SettingsExpander>
</Border>
<Border Classes="settings-expander-shell">
<ui:SettingsExpander x:Name="ThemeColorSettingsExpander"
Header="&#20027;&#39064;&#33394;"
Description="&#36873;&#25321;&#24212;&#29992;&#30340;&#20027;&#39064;&#28857;&#32512;&#33394;&#12290;">
<ui:SettingsExpander.IconSource>
<ic:SymbolIconSource Symbol="Color"
IconVariant="Regular" />
</ui:SettingsExpander.IconSource>
<ui:SettingsExpanderItem>
<ui:SettingsExpanderItem.Footer>
<StackPanel Spacing="12">
<TextBlock x:Name="RecommendedColorsLabelTextBlock"
Foreground="{DynamicResource TextFillColorSecondaryBrush}"
Text="Recommended Colors" />
<WrapPanel ItemWidth="72"
ItemHeight="56"
Orientation="Horizontal">
<Button x:Name="RecommendedColorButton1"
Width="68"
Height="50"
Padding="8">
<Border x:Name="RecommendedColorSwatch1"
Width="26"
Height="26"
CornerRadius="12"
BorderThickness="0" />
</Button>
<Button x:Name="RecommendedColorButton2"
Width="68"
Height="50"
Padding="8">
<Border x:Name="RecommendedColorSwatch2"
Width="26"
Height="26"
CornerRadius="12"
BorderThickness="0" />
</Button>
<Button x:Name="RecommendedColorButton3"
Width="68"
Height="50"
Padding="8">
<Border x:Name="RecommendedColorSwatch3"
Width="26"
Height="26"
CornerRadius="12"
BorderThickness="0" />
</Button>
<Button x:Name="RecommendedColorButton4"
Width="68"
Height="50"
Padding="8">
<Border x:Name="RecommendedColorSwatch4"
Width="26"
Height="26"
CornerRadius="12"
BorderThickness="0" />
</Button>
<Button x:Name="RecommendedColorButton5"
Width="68"
Height="50"
Padding="8">
<Border x:Name="RecommendedColorSwatch5"
Width="26"
Height="26"
CornerRadius="12"
BorderThickness="0" />
</Button>
<Button x:Name="RecommendedColorButton6"
Width="68"
Height="50"
Padding="8">
<Border x:Name="RecommendedColorSwatch6"
Width="26"
Height="26"
CornerRadius="12"
BorderThickness="0" />
</Button>
</WrapPanel>
</StackPanel>
</ui:SettingsExpanderItem.Footer>
</ui:SettingsExpanderItem>
<ui:SettingsExpanderItem>
<ui:SettingsExpanderItem.Footer>
<StackPanel Spacing="12">
<Grid ColumnDefinitions="*,Auto"
ColumnSpacing="10">
<TextBlock x:Name="SystemMonetColorsLabelTextBlock"
VerticalAlignment="Center"
Foreground="{DynamicResource TextFillColorSecondaryBrush}"
Text="System Monet Colors" />
<Button x:Name="RefreshMonetColorsButton"
Grid.Column="1"
Padding="10,6"
Content="Refresh" />
</Grid>
<WrapPanel ItemWidth="72"
ItemHeight="56"
Orientation="Horizontal">
<Button x:Name="MonetColorButton1"
Width="68"
Height="50"
Padding="8">
<Border x:Name="MonetColorSwatch1"
Width="26"
Height="26"
CornerRadius="12"
BorderThickness="0" />
</Button>
<Button x:Name="MonetColorButton2"
Width="68"
Height="50"
Padding="8">
<Border x:Name="MonetColorSwatch2"
Width="26"
Height="26"
CornerRadius="12"
BorderThickness="0" />
</Button>
<Button x:Name="MonetColorButton3"
Width="68"
Height="50"
Padding="8">
<Border x:Name="MonetColorSwatch3"
Width="26"
Height="26"
CornerRadius="12"
BorderThickness="0" />
</Button>
<Button x:Name="MonetColorButton4"
Width="68"
Height="50"
Padding="8">
<Border x:Name="MonetColorSwatch4"
Width="26"
Height="26"
CornerRadius="12"
BorderThickness="0" />
</Button>
<Button x:Name="MonetColorButton5"
Width="68"
Height="50"
Padding="8">
<Border x:Name="MonetColorSwatch5"
Width="26"
Height="26"
CornerRadius="12"
BorderThickness="0" />
</Button>
<Button x:Name="MonetColorButton6"
Width="68"
Height="50"
Padding="8">
<Border x:Name="MonetColorSwatch6"
Width="26"
Height="26"
CornerRadius="12"
BorderThickness="0" />
</Button>
</WrapPanel>
</StackPanel>
</ui:SettingsExpanderItem.Footer>
</ui:SettingsExpanderItem>
</ui:SettingsExpander>
</Border>
<Border Classes="settings-expander-shell"
Padding="16,14">
<TextBlock x:Name="ThemeColorStatusTextBlock"
Foreground="{DynamicResource AdaptiveTextMutedBrush}"
TextWrapping="Wrap"
Text="Theme color is ready." />
</Border>
</StackPanel>
</UserControl>

View File

@@ -1,12 +0,0 @@
using Avalonia.Controls;
using Avalonia.Markup.Xaml;
namespace LanMountainDesktop.Views.SettingsPages;
public partial class ColorSettingsPage : UserControl
{
public ColorSettingsPage()
{
InitializeComponent();
}
}

View File

@@ -1,48 +0,0 @@
<UserControl xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d"
d:DesignWidth="960"
d:DesignHeight="1200"
x:Class="LanMountainDesktop.Views.SettingsPages.ComponentsSettingsPage">
<StackPanel MaxWidth="920"
Spacing="16">
<TextBlock x:Name="ComponentsPageSubtitleTextBlock"
Foreground="{DynamicResource TextFillColorSecondaryBrush}"
TextWrapping="Wrap"
Text="Review available desktop components and configure the status bar area." />
<Border Classes="settings-page-shell">
<StackPanel Spacing="12">
<TextBlock x:Name="ComponentsSummarySectionTitleTextBlock"
FontSize="18"
FontWeight="SemiBold"
Text="Component Library" />
<TextBlock x:Name="ComponentsSummarySectionHintTextBlock"
Foreground="{DynamicResource TextFillColorSecondaryBrush}"
TextWrapping="Wrap"
Text="Built-in and plugin-contributed components available to the desktop editor." />
<TextBlock x:Name="ComponentsSummaryTextBlock"
TextWrapping="Wrap"
Text="Loading component catalog..." />
<StackPanel x:Name="ComponentCategoryItemsPanel"
Spacing="4" />
</StackPanel>
</Border>
<Border Classes="settings-page-shell">
<StackPanel Spacing="12">
<TextBlock x:Name="ComponentsStatusBarSectionTitleTextBlock"
FontSize="18"
FontWeight="SemiBold"
Text="Status Bar" />
<TextBlock x:Name="ComponentsStatusBarSectionHintTextBlock"
Foreground="{DynamicResource TextFillColorSecondaryBrush}"
TextWrapping="Wrap"
Text="Clock and status-bar component spacing are managed here." />
<ContentControl x:Name="StatusBarContentHost" />
</StackPanel>
</Border>
</StackPanel>
</UserControl>

View File

@@ -1,11 +0,0 @@
using Avalonia.Controls;
namespace LanMountainDesktop.Views.SettingsPages;
public partial class ComponentsSettingsPage : UserControl
{
public ComponentsSettingsPage()
{
InitializeComponent();
}
}

View File

@@ -1,58 +0,0 @@
<UserControl xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d"
d:DesignWidth="960"
d:DesignHeight="1400"
x:Class="LanMountainDesktop.Views.SettingsPages.GeneralSettingsPage">
<StackPanel MaxWidth="920"
Spacing="16">
<TextBlock x:Name="GeneralPageSubtitleTextBlock"
Foreground="{DynamicResource TextFillColorSecondaryBrush}"
TextWrapping="Wrap"
Text="Manage language, launcher, and weather behavior from the independent settings module." />
<Border Classes="settings-page-shell">
<StackPanel Spacing="12">
<TextBlock x:Name="GeneralRegionSectionTitleTextBlock"
FontSize="18"
FontWeight="SemiBold"
Text="Region" />
<TextBlock x:Name="GeneralRegionSectionHintTextBlock"
Foreground="{DynamicResource TextFillColorSecondaryBrush}"
TextWrapping="Wrap"
Text="Language and time zone settings affect the entire desktop shell." />
<ContentControl x:Name="RegionContentHost" />
</StackPanel>
</Border>
<Border Classes="settings-page-shell">
<StackPanel Spacing="12">
<TextBlock x:Name="GeneralLauncherSectionTitleTextBlock"
FontSize="18"
FontWeight="SemiBold"
Text="Launcher" />
<TextBlock x:Name="GeneralLauncherSectionHintTextBlock"
Foreground="{DynamicResource TextFillColorSecondaryBrush}"
TextWrapping="Wrap"
Text="Restore hidden entries and adjust how the app launcher behaves." />
<ContentControl x:Name="LauncherContentHost" />
</StackPanel>
</Border>
<Border Classes="settings-page-shell">
<StackPanel Spacing="12">
<TextBlock x:Name="GeneralWeatherSectionTitleTextBlock"
FontSize="18"
FontWeight="SemiBold"
Text="Weather" />
<TextBlock x:Name="GeneralWeatherSectionHintTextBlock"
Foreground="{DynamicResource TextFillColorSecondaryBrush}"
TextWrapping="Wrap"
Text="Configure shared weather source, location, and icon style for weather widgets." />
<ContentControl x:Name="WeatherContentHost" />
</StackPanel>
</Border>
</StackPanel>
</UserControl>

View File

@@ -1,11 +0,0 @@
using Avalonia.Controls;
namespace LanMountainDesktop.Views.SettingsPages;
public partial class GeneralSettingsPage : UserControl
{
public GeneralSettingsPage()
{
InitializeComponent();
}
}

View File

@@ -1,175 +0,0 @@
<UserControl xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:ui="using:FluentAvalonia.UI.Controls"
xmlns:fi="using:FluentIcons.Avalonia"
xmlns:ic="using:FluentIcons.Avalonia.Fluent"
xmlns:comp="using:LanMountainDesktop.Views.Components"
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="600"
x:Class="LanMountainDesktop.Views.SettingsPages.GridSettingsPage">
<Grid x:Name="GridSettingsPanel"
ColumnDefinitions="280, *"
RowDefinitions="Auto, *">
<TextBlock x:Name="GridPanelTitleTextBlock"
Grid.Row="0" Grid.Column="0" Grid.ColumnSpan="2"
FontSize="24"
FontWeight="SemiBold"
Foreground="{DynamicResource TextFillColorPrimaryBrush}"
Margin="0,0,0,20"
Text="璋冩暣缃戞牸甯冨眬" />
<!-- Left Column: Grid Preview -->
<Border x:Name="GridPreviewHost"
Grid.Row="1" Grid.Column="0"
Margin="0,0,20,0"
Width="256"
MaxWidth="256"
VerticalAlignment="Top"
HorizontalAlignment="Left">
<Border x:Name="GridPreviewFrame"
HorizontalAlignment="Stretch"
CornerRadius="22"
Background="#FF1A1A1A"
Padding="8">
<Border x:Name="GridPreviewViewport"
ClipToBounds="True"
CornerRadius="14"
Background="#30111827">
<Panel>
<Canvas x:Name="GridPreviewLinesCanvas"
HorizontalAlignment="Center"
VerticalAlignment="Center"
IsHitTestVisible="False" />
<Grid x:Name="GridPreviewGrid"
HorizontalAlignment="Center"
VerticalAlignment="Center">
<Border x:Name="GridPreviewTopStatusBarHost"
Grid.Row="0"
Background="Transparent"
Padding="2">
<StackPanel x:Name="GridPreviewTopStatusComponentsPanel"
Orientation="Horizontal"
Spacing="3">
</StackPanel>
</Border>
<Border x:Name="GridPreviewBottomTaskbarContainer"
Classes="glass-strong"
Grid.Row="1"
Margin="3"
CornerRadius="16"
Padding="2">
<Grid ColumnDefinitions="Auto,*,Auto"
ColumnSpacing="3">
<Border x:Name="GridPreviewTaskbarFixedActionsHost" Grid.Column="0">
<StackPanel x:Name="GridPreviewBackButtonVisual" Orientation="Horizontal" Spacing="3">
<fi:SymbolIcon Classes="icon-s" Symbol="Window" />
<TextBlock x:Name="GridPreviewBackButtonTextBlock" Text="鍥炲埌Windows" VerticalAlignment="Center" />
</StackPanel>
</Border>
<StackPanel x:Name="GridPreviewTaskbarDynamicActionsHost"
Grid.Column="1"
Orientation="Horizontal"
HorizontalAlignment="Center"
Spacing="3" />
<Border x:Name="GridPreviewTaskbarSettingsActionHost" Grid.Column="2">
<StackPanel Orientation="Horizontal" Spacing="3">
<StackPanel x:Name="GridPreviewComponentLibraryVisual" IsVisible="False" Orientation="Horizontal" Spacing="3">
<fi:FluentIcon x:Name="GridPreviewComponentLibraryIcon" Classes="icon-s" Icon="Apps" />
<TextBlock x:Name="GridPreviewComponentLibraryTextBlock" Text="Widget library" VerticalAlignment="Center" />
</StackPanel>
<fi:SymbolIcon x:Name="GridPreviewSettingsButtonIcon" Classes="icon-s" Symbol="Settings" />
</StackPanel>
</Border>
</Grid>
</Border>
</Grid>
</Panel>
</Border>
</Border>
</Border>
<!-- Right Column: Settings Content -->
<StackPanel Grid.Row="1" Grid.Column="1"
Margin="20,0,0,0"
Spacing="16">
<Border Classes="settings-expander-shell">
<ui:SettingsExpander x:Name="GridRowsSettingsExpander" Header="Rows" Description="Adjust the density of the grid">
<ui:SettingsExpander.Footer>
<Grid ColumnDefinitions="*,Auto" ColumnSpacing="12" Width="220">
<Slider x:Name="GridSizeSlider"
Grid.Column="0"
Minimum="6"
Maximum="96"
TickFrequency="1"
TickPlacement="None"
Value="12" />
<ui:NumberBox x:Name="GridSizeNumberBox"
Grid.Column="1"
Width="80"
Minimum="6"
Maximum="96"
Foreground="{DynamicResource TextFillColorPrimaryBrush}"
Value="12" />
</Grid>
</ui:SettingsExpander.Footer>
</ui:SettingsExpander>
</Border>
<Border Classes="settings-expander-shell">
<ui:SettingsExpander x:Name="GridSpacingSettingsExpander" Header="Spacing" Description="Adjust the gap between cells">
<ui:SettingsExpander.Footer>
<ComboBox x:Name="GridSpacingPresetComboBox"
Width="120">
<ComboBoxItem x:Name="GridSpacingRelaxedComboBoxItem" Tag="Relaxed" Content="Relaxed" />
<ComboBoxItem x:Name="GridSpacingCompactComboBoxItem" Tag="Compact" Content="Compact" />
</ComboBox>
</ui:SettingsExpander.Footer>
</ui:SettingsExpander>
</Border>
<Border Classes="settings-expander-shell">
<ui:SettingsExpander x:Name="GridEdgeInsetSettingsExpander" Header="Screen Inset" Description="Adjust margins around the desktop">
<ui:SettingsExpander.Footer>
<Grid ColumnDefinitions="*,Auto" ColumnSpacing="12" Width="220">
<Slider x:Name="GridEdgeInsetSlider"
Grid.Column="0"
Minimum="0"
Maximum="30"
TickFrequency="1"
TickPlacement="None"
Value="18" />
<ui:NumberBox x:Name="GridEdgeInsetNumberBox"
Grid.Column="1"
Width="80"
Minimum="0"
Maximum="30"
Foreground="{DynamicResource TextFillColorPrimaryBrush}"
Value="18" />
</Grid>
</ui:SettingsExpander.Footer>
<ui:SettingsExpanderItem>
<ui:SettingsExpanderItem.Footer>
<TextBlock x:Name="GridEdgeInsetComputedPxTextBlock"
Foreground="{DynamicResource TextFillColorSecondaryBrush}"
Text=">= 0 px" />
</ui:SettingsExpanderItem.Footer>
</ui:SettingsExpanderItem>
</ui:SettingsExpander>
</Border>
<Button x:Name="ApplyGridButton"
HorizontalAlignment="Stretch"
Padding="0,10"
Foreground="{DynamicResource TextFillColorPrimaryBrush}"
Content="搴旂敤" />
<TextBlock x:Name="GridInfoTextBlock"
Foreground="{DynamicResource TextFillColorSecondaryBrush}"
Text="Grid: - cols x - rows (1:1)" />
</StackPanel>
</Grid>
</UserControl>

View File

@@ -1,12 +0,0 @@
using Avalonia.Controls;
using Avalonia.Markup.Xaml;
namespace LanMountainDesktop.Views.SettingsPages;
public partial class GridSettingsPage : UserControl
{
public GridSettingsPage()
{
InitializeComponent();
}
}

View File

@@ -1,40 +0,0 @@
<UserControl xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:ui="using:FluentAvalonia.UI.Controls"
xmlns:fi="using:FluentIcons.Avalonia.Fluent"
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="1000"
x:Class="LanMountainDesktop.Views.SettingsPages.LauncherSettingsPage">
<StackPanel x:Name="LauncherSettingsPanel" Spacing="16">
<TextBlock x:Name="LauncherSettingsPanelTitleTextBlock"
FontSize="24"
FontWeight="SemiBold"
Foreground="{DynamicResource TextFillColorPrimaryBrush}"
Text="App Launcher" />
<Border Classes="settings-expander-shell">
<ui:SettingsExpander x:Name="LauncherHiddenItemsSettingsExpander"
Header="Hidden Items"
Description="Review hidden launcher entries and show them again."
IsExpanded="True">
<ui:SettingsExpander.IconSource>
<ui:FontIconSource Glyph="&#xe71e;" FontFamily="{StaticResource SymbolThemeFontFamily}" />
</ui:SettingsExpander.IconSource>
<ui:SettingsExpander.Footer>
<StackPanel Spacing="10">
<TextBlock x:Name="LauncherHiddenItemsDescriptionTextBlock"
Foreground="{DynamicResource TextFillColorSecondaryBrush}"
Text="Right-click an icon in launcher to hide it. Hidden entries appear here." />
<TextBlock x:Name="LauncherHiddenItemsEmptyTextBlock"
IsVisible="False"
Foreground="{DynamicResource TextFillColorSecondaryBrush}"
Text="No hidden items." />
</StackPanel>
</ui:SettingsExpander.Footer>
</ui:SettingsExpander>
</Border>
</StackPanel>
</UserControl>

View File

@@ -1,12 +0,0 @@
using Avalonia.Controls;
using Avalonia.Markup.Xaml;
namespace LanMountainDesktop.Views.SettingsPages;
public partial class LauncherSettingsPage : UserControl
{
public LauncherSettingsPage()
{
InitializeComponent();
}
}

View File

@@ -1,50 +0,0 @@
<UserControl xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:ui="using:FluentAvalonia.UI.Controls"
xmlns:fi="using:FluentIcons.Avalonia.Fluent"
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="600"
x:Class="LanMountainDesktop.Views.SettingsPages.RegionSettingsPage">
<StackPanel x:Name="RegionSettingsPanel"
Spacing="16">
<TextBlock x:Name="RegionPanelTitleTextBlock"
FontSize="24"
FontWeight="SemiBold"
Foreground="{DynamicResource TextFillColorPrimaryBrush}"
Text="Region" />
<Border Classes="settings-expander-shell">
<ui:SettingsExpander x:Name="LanguageSettingsExpander"
Header="Language"
Description="Select application language. Changes apply immediately.">
<ui:SettingsExpander.IconSource>
<fi:SymbolIconSource Symbol="Translate" />
</ui:SettingsExpander.IconSource>
<ui:SettingsExpander.Footer>
<ComboBox x:Name="LanguageComboBox"
Width="220">
<ComboBoxItem x:Name="LanguageChineseItem" Tag="zh-CN" Content="涓枃" />
<ComboBoxItem x:Name="LanguageEnglishItem" Tag="en-US" Content="English" />
</ComboBox>
</ui:SettingsExpander.Footer>
</ui:SettingsExpander>
</Border>
<Border Classes="settings-expander-shell">
<ui:SettingsExpander x:Name="TimeZoneSettingsExpander"
Header="Time Zone"
Description="Select a time zone. Clock and calendar widgets will follow this zone.">
<ui:SettingsExpander.IconSource>
<fi:SymbolIconSource Symbol="Clock" />
</ui:SettingsExpander.IconSource>
<ui:SettingsExpander.Footer>
<ComboBox x:Name="TimeZoneComboBox"
Width="280" />
</ui:SettingsExpander.Footer>
</ui:SettingsExpander>
</Border>
</StackPanel>
</UserControl>

View File

@@ -1,12 +0,0 @@
using Avalonia.Controls;
using Avalonia.Markup.Xaml;
namespace LanMountainDesktop.Views.SettingsPages;
public partial class RegionSettingsPage : UserControl
{
public RegionSettingsPage()
{
InitializeComponent();
}
}

View File

@@ -1,91 +0,0 @@
<UserControl xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:ui="using:FluentAvalonia.UI.Controls"
xmlns:fi="using:FluentIcons.Avalonia"
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="600"
x:Class="LanMountainDesktop.Views.SettingsPages.StatusBarSettingsPage">
<StackPanel x:Name="StatusBarSettingsPanel"
Spacing="16">
<TextBlock x:Name="StatusBarPanelTitleTextBlock"
FontSize="24"
FontWeight="SemiBold"
Foreground="{DynamicResource TextFillColorPrimaryBrush}"
Text="Status Bar" />
<Border Classes="settings-expander-shell">
<ui:SettingsExpander x:Name="StatusBarClockSettingsExpander"
Header="&#31995;&#32479;&#26102;&#38047;"
Description="&#22312;&#29366;&#24577;&#26639;&#19978;&#26174;&#31034;&#26102;&#38388;&#12290;"
IsExpanded="False">
<ui:SettingsExpander.Footer>
<ToggleSwitch x:Name="StatusBarClockToggleSwitch"
OnContent="On"
OffContent="Off" />
</ui:SettingsExpander.Footer>
<ui:SettingsExpanderItem Content="Display Format">
<ui:SettingsExpanderItem.Footer>
<StackPanel Orientation="Horizontal" Spacing="16">
<RadioButton x:Name="ClockFormatHMSSRadio"
Content="HH:mm:ss"
GroupName="ClockFormat"
Tag="Hms" />
<RadioButton x:Name="ClockFormatHMRadio"
Content="HH:mm"
GroupName="ClockFormat"
Tag="Hm" />
</StackPanel>
</ui:SettingsExpanderItem.Footer>
</ui:SettingsExpanderItem>
</ui:SettingsExpander>
</Border>
<Border Classes="settings-expander-shell">
<ui:SettingsExpander x:Name="StatusBarSpacingSettingsExpander"
Header="Component spacing"
Description="Adjust spacing between status bar components."
IsExpanded="False">
<ui:SettingsExpander.Footer>
<ComboBox x:Name="StatusBarSpacingModeComboBox"
Width="150">
<ComboBoxItem x:Name="StatusBarSpacingModeCompactItem" Tag="Compact" Content="Compact" />
<ComboBoxItem x:Name="StatusBarSpacingModeRelaxedItem" Tag="Relaxed" Content="Relaxed" />
<ComboBoxItem x:Name="StatusBarSpacingModeCustomItem" Tag="Custom" Content="Custom" />
</ComboBox>
</ui:SettingsExpander.Footer>
<ui:SettingsExpanderItem x:Name="StatusBarSpacingCustomPanel"
Content="Custom spacing"
IsVisible="False">
<ui:SettingsExpanderItem.Footer>
<StackPanel Orientation="Horizontal" Spacing="12">
<Slider x:Name="StatusBarSpacingSlider"
Width="150"
Minimum="0"
Maximum="30"
TickFrequency="1"
Value="12" />
<ui:NumberBox x:Name="StatusBarSpacingNumberBox"
Width="80"
Minimum="0"
Maximum="30"
Value="12" />
</StackPanel>
</ui:SettingsExpanderItem.Footer>
</ui:SettingsExpanderItem>
<ui:SettingsExpanderItem>
<ui:SettingsExpanderItem.Footer>
<TextBlock x:Name="StatusBarSpacingComputedPxTextBlock"
HorizontalAlignment="Right"
Foreground="{DynamicResource TextFillColorSecondaryBrush}"
Text=">= 0 px" />
</ui:SettingsExpanderItem.Footer>
</ui:SettingsExpanderItem>
</ui:SettingsExpander>
</Border>
</StackPanel>
</UserControl>

View File

@@ -1,12 +0,0 @@
using Avalonia.Controls;
using Avalonia.Markup.Xaml;
namespace LanMountainDesktop.Views.SettingsPages;
public partial class StatusBarSettingsPage : UserControl
{
public StatusBarSettingsPage()
{
InitializeComponent();
}
}

View File

@@ -1,123 +0,0 @@
<UserControl xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:ui="using:FluentAvalonia.UI.Controls"
xmlns:fi="using:FluentIcons.Avalonia.Fluent"
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="1000"
x:Class="LanMountainDesktop.Views.SettingsPages.UpdateSettingsPage">
<StackPanel x:Name="UpdateSettingsPanel"
Spacing="16">
<TextBlock x:Name="UpdatePanelTitleTextBlock"
FontSize="24"
FontWeight="SemiBold"
Foreground="{DynamicResource TextFillColorPrimaryBrush}"
Text="Update" />
<Border Background="{DynamicResource AdaptiveSurfaceRaisedBrush}"
CornerRadius="{DynamicResource DesignCornerRadiusMd}"
Padding="20">
<Grid ColumnDefinitions="Auto,*" RowDefinitions="Auto,Auto,Auto" ColumnSpacing="12" RowSpacing="8">
<TextBlock x:Name="UpdateCurrentVersionLabelTextBlock"
Text="Current Version"
FontWeight="SemiBold"
Foreground="{DynamicResource TextFillColorSecondaryBrush}" />
<TextBlock x:Name="UpdateCurrentVersionValueTextBlock"
Grid.Column="1"
Text="-"
Foreground="{DynamicResource TextFillColorPrimaryBrush}" />
<TextBlock x:Name="UpdateLatestVersionLabelTextBlock"
Grid.Row="1"
Text="Latest Release"
FontWeight="SemiBold"
Foreground="{DynamicResource TextFillColorSecondaryBrush}" />
<TextBlock x:Name="UpdateLatestVersionValueTextBlock"
Grid.Row="1" Grid.Column="1"
Text="-"
Foreground="{DynamicResource TextFillColorPrimaryBrush}" />
<TextBlock x:Name="UpdatePublishedAtLabelTextBlock"
Grid.Row="2"
Text="Published At"
FontWeight="SemiBold"
Foreground="{DynamicResource TextFillColorSecondaryBrush}" />
<TextBlock x:Name="UpdatePublishedAtValueTextBlock"
Grid.Row="2" Grid.Column="1"
Text="-"
Foreground="{DynamicResource TextFillColorPrimaryBrush}" />
</Grid>
</Border>
<Border Classes="settings-expander-shell">
<ui:SettingsExpander x:Name="UpdateOptionsSettingsExpander"
Header="Update Options"
Description="Configure update checks and release channel."
IsExpanded="True">
<ui:SettingsExpander.IconSource>
<fi:SymbolIconSource Symbol="Settings" />
</ui:SettingsExpander.IconSource>
<ui:SettingsExpander.Footer>
<StackPanel Spacing="10">
<ToggleSwitch x:Name="AutoCheckUpdatesToggleSwitch"
Content="Automatically check for updates on startup" />
<TextBlock x:Name="UpdateChannelLabelTextBlock"
Text="Update Channel"
FontWeight="SemiBold"
Foreground="{DynamicResource TextFillColorSecondaryBrush}" />
<ListBox x:Name="UpdateChannelChipListBox"
Classes="settings-chip-list">
<ListBox.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel Orientation="Horizontal" Spacing="8" />
</ItemsPanelTemplate>
</ListBox.ItemsPanel>
<ListBoxItem x:Name="UpdateChannelStableChipItem"
Tag="Stable"
Content="Stable" />
<ListBoxItem x:Name="UpdateChannelPreviewChipItem"
Tag="Preview"
Content="Preview" />
</ListBox>
</StackPanel>
</ui:SettingsExpander.Footer>
</ui:SettingsExpander>
</Border>
<Border Classes="settings-expander-shell">
<ui:SettingsExpander x:Name="UpdateActionsSettingsExpander"
Header="Update Actions"
Description="Check releases, download installer, and start update."
IsExpanded="True">
<ui:SettingsExpander.IconSource>
<fi:SymbolIconSource Symbol="ArrowSync" />
</ui:SettingsExpander.IconSource>
<ui:SettingsExpander.Footer>
<StackPanel Spacing="10">
<StackPanel Orientation="Horizontal" Spacing="10">
<Button x:Name="CheckForUpdatesButton"
MinWidth="140"
Content="Check for Updates" />
<Button x:Name="DownloadAndInstallUpdateButton"
MinWidth="180"
Content="Download &amp; Install" />
</StackPanel>
<ProgressBar x:Name="UpdateDownloadProgressBar"
Minimum="0"
Maximum="100"
Height="6"
IsVisible="False" />
<TextBlock x:Name="UpdateDownloadProgressTextBlock"
Text="Download progress: -"
Foreground="{DynamicResource TextFillColorSecondaryBrush}" />
<TextBlock x:Name="UpdateStatusTextBlock"
Text="Ready to check for updates."
Foreground="{DynamicResource TextFillColorPrimaryBrush}" />
</StackPanel>
</ui:SettingsExpander.Footer>
</ui:SettingsExpander>
</Border>
</StackPanel>
</UserControl>

View File

@@ -1,12 +0,0 @@
using Avalonia.Controls;
using Avalonia.Markup.Xaml;
namespace LanMountainDesktop.Views.SettingsPages;
public partial class UpdateSettingsPage : UserControl
{
public UpdateSettingsPage()
{
InitializeComponent();
}
}

View File

@@ -1,154 +0,0 @@
<UserControl xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:ui="using:FluentAvalonia.UI.Controls"
xmlns:fi="using:FluentIcons.Avalonia"
xmlns:ic="using:FluentIcons.Avalonia.Fluent"
xmlns:comp="using:LanMountainDesktop.Views.Components"
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="600"
x:Class="LanMountainDesktop.Views.SettingsPages.WallpaperSettingsPage">
<Grid x:Name="WallpaperSettingsPanel"
ColumnDefinitions="280, *"
RowDefinitions="Auto, *">
<TextBlock x:Name="WallpaperPanelTitleTextBlock"
Grid.Row="0" Grid.Column="0" Grid.ColumnSpan="2"
FontSize="24"
FontWeight="SemiBold"
Foreground="{DynamicResource TextFillColorPrimaryBrush}"
Margin="0,0,0,20"
Text="Personalize Wallpaper" />
<!-- Left Column: Monitor Preview -->
<Border x:Name="WallpaperPreviewHost"
Grid.Row="1" Grid.Column="0"
Margin="0,0,20,0"
Width="256"
MaxWidth="256"
VerticalAlignment="Top"
HorizontalAlignment="Left">
<!-- Monitor Frame (Bezel) -->
<Border x:Name="WallpaperPreviewFrame"
HorizontalAlignment="Stretch"
CornerRadius="22"
Background="#FF1A1A1A"
Padding="8">
<Border x:Name="WallpaperPreviewViewport"
ClipToBounds="True"
CornerRadius="14"
Background="#30111827">
<Grid>
<Image x:Name="WallpaperPreviewVideoImage"
IsVisible="False"
IsHitTestVisible="False"
Stretch="UniformToFill"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch" />
<Grid x:Name="WallpaperPreviewGrid"
HorizontalAlignment="Center"
VerticalAlignment="Center">
<Border x:Name="WallpaperPreviewTopStatusBarHost"
Grid.Row="0"
Background="Transparent"
Padding="2">
<StackPanel x:Name="WallpaperPreviewTopStatusComponentsPanel"
Orientation="Horizontal"
Spacing="3">
<comp:ClockWidget x:Name="WallpaperPreviewClockWidget"
IsVisible="False" />
</StackPanel>
</Border>
<Border x:Name="WallpaperPreviewBottomTaskbarContainer"
Classes="glass-strong"
Grid.Row="1"
Margin="3"
CornerRadius="16"
Padding="2">
<Grid ColumnDefinitions="Auto,*,Auto"
ColumnSpacing="3">
<Border x:Name="WallpaperPreviewTaskbarFixedActionsHost" Grid.Column="0">
<StackPanel x:Name="WallpaperPreviewBackButtonVisual" Orientation="Horizontal" Spacing="3">
<fi:SymbolIcon Classes="icon-s" Symbol="Window" />
<TextBlock x:Name="WallpaperPreviewBackButtonTextBlock" Text="Back to Windows" VerticalAlignment="Center" />
</StackPanel>
</Border>
<StackPanel x:Name="WallpaperPreviewTaskbarDynamicActionsHost"
Grid.Column="1"
Orientation="Horizontal"
HorizontalAlignment="Center"
Spacing="3" />
<Border x:Name="WallpaperPreviewTaskbarSettingsActionHost" Grid.Column="2">
<StackPanel Orientation="Horizontal" Spacing="3">
<StackPanel x:Name="WallpaperPreviewComponentLibraryVisual" IsVisible="False" Orientation="Horizontal" Spacing="3">
<fi:FluentIcon Classes="icon-s" Icon="Apps" />
<TextBlock x:Name="WallpaperPreviewComponentLibraryTextBlock" Text="Widget library" VerticalAlignment="Center" />
</StackPanel>
<fi:SymbolIcon x:Name="WallpaperPreviewSettingsButtonIcon" Classes="icon-s" Symbol="Settings" />
</StackPanel>
</Border>
</Grid>
</Border>
</Grid>
</Grid>
</Border>
</Border>
</Border>
<!-- Right Column: Settings Content -->
<StackPanel Grid.Row="1" Grid.Column="1"
Margin="20,0,0,0"
Spacing="16">
<StackPanel Spacing="8">
<TextBlock Text="Preview status" FontSize="12" Foreground="{DynamicResource TextFillColorSecondaryBrush}" />
<TextBlock x:Name="WallpaperPathTextBlock"
FontSize="14"
FontWeight="Medium"
Foreground="{DynamicResource TextFillColorPrimaryBrush}"
TextTrimming="CharacterEllipsis"
Text="No file selected" />
<TextBlock x:Name="WallpaperStatusTextBlock"
FontSize="12"
Foreground="{DynamicResource AdaptiveTextMutedBrush}"
Text="Ready" />
</StackPanel>
<Separator Background="{DynamicResource SurfaceStrokeColorDefaultBrush}" Height="1" Margin="0,8" />
<TextBlock Text="Choose image or video" FontSize="16" FontWeight="SemiBold" Foreground="{DynamicResource TextFillColorPrimaryBrush}" />
<Grid ColumnDefinitions="*, *" ColumnSpacing="12">
<Button x:Name="PickWallpaperButton"
Grid.Column="0"
Classes="accent"
HorizontalAlignment="Stretch"
Padding="0,10"
Content="Browse" />
<Button x:Name="ClearWallpaperButton"
Grid.Column="1"
HorizontalAlignment="Stretch"
Padding="0,10"
Content="Reset" />
</Grid>
<Border Classes="settings-expander-shell">
<ui:SettingsExpander x:Name="WallpaperPlacementSettingsExpander"
Header="Placement"
Padding="12,8">
<ui:SettingsExpander.Footer>
<ComboBox x:Name="WallpaperPlacementComboBox"
Width="120">
<ComboBoxItem Content="Fill" />
<ComboBoxItem Content="Fit" />
<ComboBoxItem Content="Stretch" />
<ComboBoxItem Content="Center" />
<ComboBoxItem Content="Tile" />
</ComboBox>
</ui:SettingsExpander.Footer>
</ui:SettingsExpander>
</Border>
</StackPanel>
</Grid>
</UserControl>

View File

@@ -1,12 +0,0 @@
using Avalonia.Controls;
using Avalonia.Markup.Xaml;
namespace LanMountainDesktop.Views.SettingsPages;
public partial class WallpaperSettingsPage : UserControl
{
public WallpaperSettingsPage()
{
InitializeComponent();
}
}

View File

@@ -1,355 +0,0 @@
<UserControl xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:ui="using:FluentAvalonia.UI.Controls"
xmlns:fi="using:FluentIcons.Avalonia.Fluent"
mc:Ignorable="d" d:DesignWidth="860" d:DesignHeight="1200"
x:Class="LanMountainDesktop.Views.SettingsPages.WeatherSettingsPage">
<UserControl.Styles>
<Style Selector="StackPanel.weather-settings-root TextBlock.section-eyebrow">
<Setter Property="FontSize" Value="13" />
<Setter Property="FontWeight" Value="SemiBold" />
<Setter Property="Foreground" Value="{DynamicResource TextFillColorSecondaryBrush}" />
</Style>
<Style Selector="StackPanel.weather-settings-root Border.preview-icon-shell">
<Setter Property="Width" Value="62" />
<Setter Property="Height" Value="62" />
<Setter Property="CornerRadius" Value="18" />
<Setter Property="Background" Value="{DynamicResource AdaptiveSurfaceRaisedBrush}" />
<Setter Property="BorderThickness" Value="1" />
<Setter Property="BorderBrush" Value="{DynamicResource AdaptiveButtonBorderBrush}" />
<Setter Property="Padding" Value="10" />
</Style>
<Style Selector="StackPanel.weather-settings-root Border.settings-note-shell">
<Setter Property="Background" Value="{DynamicResource AdaptiveSurfaceRaisedBrush}" />
<Setter Property="CornerRadius" Value="{DynamicResource DesignCornerRadiusSm}" />
<Setter Property="Padding" Value="14,12" />
</Style>
<Style Selector="StackPanel.weather-settings-root Border.settings-expander-shell">
<Setter Property="Margin" Value="0" />
</Style>
</UserControl.Styles>
<StackPanel x:Name="WeatherSettingsContentPanel"
Classes="settings-animated-intro weather-settings-root"
Margin="0,0,8,0"
Spacing="12">
<TextBlock x:Name="WeatherPanelTitleTextBlock"
FontSize="28"
FontWeight="SemiBold"
Foreground="{DynamicResource TextFillColorPrimaryBrush}"
Text="Weather" />
<StackPanel Spacing="8">
<TextBlock x:Name="WeatherPreviewSectionTextBlock"
Classes="section-eyebrow"
Text="Weather Preview" />
<Border Classes="settings-expander-shell"
Padding="18,16">
<Grid RowDefinitions="Auto,Auto"
RowSpacing="10">
<Grid ColumnDefinitions="Auto,*,Auto"
ColumnSpacing="14">
<Border Classes="preview-icon-shell">
<Image x:Name="WeatherPreviewIconImage"
Stretch="Uniform" />
</Border>
<StackPanel Grid.Column="1"
VerticalAlignment="Center"
Spacing="3">
<TextBlock x:Name="WeatherPreviewTemperatureTextBlock"
FontSize="34"
FontWeight="SemiBold"
Text="--" />
<TextBlock x:Name="WeatherPreviewUpdatedTextBlock"
FontSize="13"
Foreground="{DynamicResource TextFillColorSecondaryBrush}"
Text="-" />
</StackPanel>
<StackPanel Grid.Column="2"
Orientation="Horizontal"
Spacing="8"
VerticalAlignment="Center">
<Button x:Name="WeatherPreviewButton"
Padding="16,8"
Content="Refresh" />
<ui:ProgressRing x:Name="WeatherPreviewProgressRing"
Width="20"
Height="20"
IsActive="True"
IsVisible="False" />
</StackPanel>
</Grid>
<TextBlock x:Name="WeatherPreviewResultTextBlock"
Grid.Row="1"
TextWrapping="Wrap"
Foreground="{DynamicResource TextFillColorSecondaryBrush}"
Text="Use refresh to verify your weather configuration." />
</Grid>
</Border>
</StackPanel>
<Border Background="{DynamicResource SurfaceStrokeColorDefaultBrush}"
Height="1" />
<TextBlock x:Name="WeatherSettingsSectionTextBlock"
Classes="section-eyebrow"
Text="Settings" />
<Border Classes="settings-expander-shell">
<ui:SettingsExpander x:Name="WeatherLocationSettingsExpander"
Header="Location Source"
Description="Choose how weather widgets resolve location."
IsExpanded="True">
<ui:SettingsExpander.Footer>
<ListBox x:Name="WeatherLocationModeChipListBox"
Classes="settings-chip-list"
HorizontalAlignment="Right"
SelectionMode="Single">
<ListBox.ItemsPanel>
<ItemsPanelTemplate>
<WrapPanel Orientation="Horizontal" />
</ItemsPanelTemplate>
</ListBox.ItemsPanel>
<ListBoxItem x:Name="WeatherLocationModeCityChipItem"
Tag="CitySearch"
Content="City Search" />
<ListBoxItem x:Name="WeatherLocationModeCoordinatesChipItem"
Tag="Coordinates"
Content="Coordinates" />
</ListBox>
</ui:SettingsExpander.Footer>
<ui:SettingsExpanderItem>
<Grid ColumnDefinitions="*,Auto"
ColumnSpacing="18">
<StackPanel Spacing="4">
<TextBlock x:Name="WeatherLocationSelectionTitleTextBlock"
FontSize="17"
FontWeight="SemiBold"
Text="City Selection" />
<TextBlock x:Name="WeatherLocationSelectionDescriptionTextBlock"
Foreground="{DynamicResource TextFillColorSecondaryBrush}"
TextWrapping="Wrap"
Text="Select the current city used for weather queries." />
</StackPanel>
<StackPanel Grid.Column="1"
MaxWidth="420"
HorizontalAlignment="Right"
Spacing="4">
<TextBlock x:Name="WeatherLocationValueTextBlock"
FontSize="17"
FontWeight="SemiBold"
TextAlignment="Right"
TextWrapping="Wrap"
Text="No location selected" />
<TextBlock x:Name="WeatherLocationStatusTextBlock"
FontSize="12"
TextAlignment="Right"
TextWrapping="Wrap"
Foreground="{DynamicResource TextFillColorSecondaryBrush}" />
</StackPanel>
</Grid>
</ui:SettingsExpanderItem>
<ui:SettingsExpanderItem>
<ui:SettingsExpanderItem.Footer>
<ToggleSwitch x:Name="WeatherAutoRefreshToggleSwitch"
Content="Auto refresh location on startup" />
</ui:SettingsExpanderItem.Footer>
</ui:SettingsExpanderItem>
<ComboBox x:Name="WeatherLocationModeComboBox"
IsVisible="False">
<ComboBoxItem x:Name="WeatherLocationModeCityItem"
Tag="CitySearch"
Content="City Search" />
<ComboBoxItem x:Name="WeatherLocationModeCoordinatesItem"
Tag="Coordinates"
Content="Coordinates" />
</ComboBox>
</ui:SettingsExpander>
</Border>
<Border Classes="settings-expander-shell">
<ui:SettingsExpander x:Name="WeatherCitySearchSettingsExpander"
Header="City Search"
Description="Search cities and apply one weather location."
IsExpanded="True">
<ui:SettingsExpander.Footer>
<Button x:Name="WeatherApplyCityButton"
Padding="14,8"
Content="Apply City" />
</ui:SettingsExpander.Footer>
<ui:SettingsExpanderItem>
<StackPanel Spacing="12">
<Grid ColumnDefinitions="*,Auto,Auto"
ColumnSpacing="10">
<TextBox x:Name="WeatherCitySearchTextBox"
Watermark="e.g. Beijing" />
<ui:ProgressRing x:Name="WeatherSearchProgressRing"
Grid.Column="1"
Width="22"
Height="22"
IsActive="True"
IsVisible="False" />
<Button x:Name="WeatherSearchButton"
Grid.Column="2"
Padding="14,8"
Content="Search" />
</Grid>
<ComboBox x:Name="WeatherCityResultsComboBox"
HorizontalAlignment="Stretch"
MinWidth="320" />
<TextBlock x:Name="WeatherSearchStatusTextBlock"
FontSize="12"
Foreground="{DynamicResource TextFillColorSecondaryBrush}"
TextWrapping="Wrap"
Text="Search by city name and apply one location." />
</StackPanel>
</ui:SettingsExpanderItem>
</ui:SettingsExpander>
</Border>
<Border Classes="settings-expander-shell">
<ui:SettingsExpander x:Name="WeatherCoordinateSettingsExpander"
Header="Coordinates"
Description="Set latitude/longitude and optional key/name."
IsVisible="False"
IsExpanded="True">
<ui:SettingsExpander.Footer>
<Button x:Name="WeatherApplyCoordinatesButton"
Padding="14,8"
Content="Apply Coordinates" />
</ui:SettingsExpander.Footer>
<ui:SettingsExpanderItem>
<StackPanel Spacing="12">
<Grid ColumnDefinitions="*,*"
ColumnSpacing="10">
<ui:NumberBox x:Name="WeatherLatitudeNumberBox"
Grid.Column="0"
Header="Latitude"
Minimum="-90"
Maximum="90"
SpinButtonPlacementMode="Inline"
SmallChange="0.1"
LargeChange="1"
Value="39.9042" />
<ui:NumberBox x:Name="WeatherLongitudeNumberBox"
Grid.Column="1"
Header="Longitude"
Minimum="-180"
Maximum="180"
SpinButtonPlacementMode="Inline"
SmallChange="0.1"
LargeChange="1"
Value="116.4074" />
</Grid>
<TextBox x:Name="WeatherLocationKeyTextBox"
Watermark="Location key (optional)" />
<TextBox x:Name="WeatherLocationNameTextBox"
Watermark="Display name (optional)" />
<TextBlock x:Name="WeatherCoordinateStatusTextBlock"
FontSize="12"
Foreground="{DynamicResource TextFillColorSecondaryBrush}"
TextWrapping="Wrap" />
</StackPanel>
</ui:SettingsExpanderItem>
</ui:SettingsExpander>
</Border>
<Border Classes="settings-expander-shell">
<ui:SettingsExpander x:Name="WeatherAlertFilterSettingsExpander"
Header="Excluded Alerts"
Description="Alerts containing these words will not be shown. One rule per line."
IsExpanded="True">
<ui:SettingsExpanderItem>
<Grid ColumnDefinitions="Auto,*"
ColumnSpacing="20">
<StackPanel Width="220"
Spacing="4">
<TextBlock x:Name="WeatherAlertListTitleTextBlock"
FontSize="17"
FontWeight="SemiBold"
Text="Exclude List" />
<TextBlock x:Name="WeatherAlertListDescriptionTextBlock"
Foreground="{DynamicResource TextFillColorSecondaryBrush}"
TextWrapping="Wrap"
Text="One exclusion rule per line." />
</StackPanel>
<TextBox x:Name="WeatherExcludedAlertsTextBox"
Grid.Column="1"
MinHeight="96"
MaxHeight="220"
HorizontalAlignment="Stretch"
AcceptsReturn="True"
TextWrapping="Wrap"
Watermark="One keyword per line" />
</Grid>
</ui:SettingsExpanderItem>
</ui:SettingsExpander>
</Border>
<Border Classes="settings-expander-shell">
<ui:SettingsExpander x:Name="WeatherNoTlsSettingsExpander"
Header="No TLS Weather Request"
Description="Not recommended. Enable only for incompatible network environments."
IsExpanded="True">
<ui:SettingsExpander.Footer>
<ToggleSwitch x:Name="WeatherNoTlsToggleSwitch"
Content="Allow non-TLS request fallback" />
</ui:SettingsExpander.Footer>
</ui:SettingsExpander>
</Border>
<Border Classes="settings-note-shell">
<TextBlock x:Name="WeatherFooterHintTextBlock"
TextWrapping="Wrap"
Foreground="{DynamicResource TextFillColorSecondaryBrush}"
Text="Desktop weather widgets will reuse the location and alert exclusion settings configured here." />
</Border>
<Grid IsVisible="False">
<ui:SettingsExpander x:Name="WeatherPreviewSettingsExpander"
Header="Weather Preview"
Description="Refresh and verify current weather service status." />
<fi:SymbolIcon x:Name="WeatherPreviewIconSymbol"
Symbol="WeatherSunny"
IconVariant="Regular" />
<Border Classes="settings-expander-shell">
<ui:SettingsExpander x:Name="WeatherIconPackSettingsExpander"
Header="Weather Icon Style"
Description="Choose Fluent Icon style for weather symbols.">
<ui:SettingsExpander.Footer>
<ComboBox x:Name="WeatherIconPackComboBox"
Width="220">
<ComboBoxItem x:Name="WeatherIconPackFluentRegularItem"
Tag="FluentRegular"
Content="Fluent Regular" />
<ComboBoxItem x:Name="WeatherIconPackFluentFilledItem"
Tag="FluentFilled"
Content="Fluent Filled" />
</ComboBox>
</ui:SettingsExpander.Footer>
</ui:SettingsExpander>
</Border>
</Grid>
</StackPanel>
</UserControl>

View File

@@ -1,12 +0,0 @@
using Avalonia.Controls;
using Avalonia.Markup.Xaml;
namespace LanMountainDesktop.Views.SettingsPages;
public partial class WeatherSettingsPage : UserControl
{
public WeatherSettingsPage()
{
InitializeComponent();
}
}