settings_re4

This commit is contained in:
lincube
2026-03-13 22:20:12 +08:00
parent 3b3f060f33
commit 5fdaa2539b
89 changed files with 5778 additions and 192 deletions

View File

@@ -0,0 +1,129 @@
<Window xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:vm="using:LanMountainDesktop.ViewModels"
xmlns:fi="using:FluentIcons.Avalonia"
x:Class="LanMountainDesktop.Views.ComponentLibraryWindow"
x:DataType="vm:ComponentLibraryWindowViewModel"
Width="980"
Height="620"
MinWidth="760"
MinHeight="500"
CanResize="True"
SystemDecorations="Full"
Title="Component Library"
Background="{DynamicResource AdaptiveSurfaceBaseBrush}">
<Grid RowDefinitions="Auto,*"
RowSpacing="10"
Margin="14">
<Grid ColumnDefinitions="*,Auto"
Margin="6,4,6,0">
<TextBlock VerticalAlignment="Center"
FontSize="18"
FontWeight="SemiBold"
Foreground="{DynamicResource AdaptiveTextPrimaryBrush}"
Text="{Binding Title}" />
<Button Grid.Column="1"
Width="34"
Height="34"
Padding="0"
Background="Transparent"
BorderThickness="0"
Click="OnCloseClick">
<fi:FluentIcon Icon="Dismiss"
IconVariant="Regular" />
</Button>
</Grid>
<Grid Grid.Row="1"
ColumnDefinitions="240,*"
ColumnSpacing="12">
<Border Classes="glass-panel"
CornerRadius="24"
Padding="10">
<ListBox x:Name="CategoryListBox"
Background="Transparent"
BorderThickness="0"
SelectionChanged="OnCategorySelectionChanged"
ItemsSource="{Binding Categories}">
<ListBox.ItemTemplate>
<DataTemplate x:DataType="vm:ComponentLibraryCategoryViewModel">
<Border Padding="10"
Margin="0,0,0,6"
CornerRadius="14"
Background="{DynamicResource AdaptiveNavItemBackgroundBrush}">
<Grid ColumnDefinitions="Auto,*"
ColumnSpacing="8">
<fi:SymbolIcon Symbol="{Binding Icon}"
IconVariant="Regular"
FontSize="16" />
<TextBlock Grid.Column="1"
VerticalAlignment="Center"
FontWeight="SemiBold"
Foreground="{DynamicResource AdaptiveTextPrimaryBrush}"
Text="{Binding Title}" />
</Grid>
</Border>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</Border>
<Border Grid.Column="1"
Classes="glass-strong"
CornerRadius="24"
Padding="10">
<ScrollViewer VerticalScrollBarVisibility="Auto"
HorizontalScrollBarVisibility="Disabled">
<ItemsControl ItemsSource="{Binding Components}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<WrapPanel Orientation="Horizontal" />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate x:DataType="vm:ComponentLibraryItemViewModel">
<Border Width="240"
Height="220"
Margin="6"
CornerRadius="18"
Padding="10"
Background="{DynamicResource AdaptiveSurfaceRaisedBrush}"
BorderBrush="{DynamicResource AdaptiveButtonBorderBrush}"
BorderThickness="1">
<Grid RowDefinitions="*,Auto,Auto"
RowSpacing="8">
<Border CornerRadius="12"
Background="{DynamicResource AdaptiveGlassPanelBackgroundBrush}"
BorderThickness="1"
BorderBrush="{DynamicResource AdaptiveGlassPanelBorderBrush}"
Padding="8">
<ContentControl HorizontalAlignment="Center"
VerticalAlignment="Center"
Content="{Binding PreviewControl}" />
</Border>
<TextBlock Grid.Row="1"
HorizontalAlignment="Center"
FontWeight="SemiBold"
Foreground="{DynamicResource AdaptiveTextPrimaryBrush}"
Text="{Binding DisplayName}" />
<Button Grid.Row="2"
HorizontalAlignment="Center"
Padding="12,6"
Tag="{Binding ComponentId}"
Click="OnAddComponentClick">
<TextBlock Text="Add to Desktop" />
</Button>
</Grid>
</Border>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</ScrollViewer>
</Border>
</Grid>
</Grid>
</Window>

View File

@@ -0,0 +1,237 @@
using System;
using System.Collections.Generic;
using System.Linq;
using Avalonia.Controls;
using Avalonia.Interactivity;
using FluentIcons.Common;
using LanMountainDesktop.Services;
using LanMountainDesktop.ViewModels;
namespace LanMountainDesktop.Views;
public partial class ComponentLibraryWindow : Window
{
private IComponentLibraryService? _componentLibraryService;
private Func<double, ComponentLibraryCreateContext>? _createContextFactory;
private Func<string, string, string>? _localize;
private readonly ComponentLibraryWindowViewModel _viewModel = new();
public ComponentLibraryWindow()
{
InitializeComponent();
DataContext = _viewModel;
}
public ComponentLibraryWindow(
IComponentLibraryService componentLibraryService,
Func<double, ComponentLibraryCreateContext> createContextFactory,
Func<string, string, string> localize)
: this()
{
_componentLibraryService = componentLibraryService ?? throw new ArgumentNullException(nameof(componentLibraryService));
_createContextFactory = createContextFactory ?? throw new ArgumentNullException(nameof(createContextFactory));
_localize = localize ?? throw new ArgumentNullException(nameof(localize));
Reload();
}
public event EventHandler<string>? AddComponentRequested;
public void Reload()
{
if (_componentLibraryService is null ||
_createContextFactory is null ||
_localize is null)
{
return;
}
_viewModel.Title = _localize("component_library.title", "Widgets");
_viewModel.Categories.Clear();
_viewModel.Components.Clear();
var categories = _componentLibraryService.GetDesktopCategories();
foreach (var category in categories)
{
var itemModels = category.Components
.Select(CreateComponentItem)
.ToArray();
_viewModel.Categories.Add(new ComponentLibraryCategoryViewModel(
category.Id,
GetLocalizedCategoryTitle(category.Id),
ResolveCategoryIcon(category.Id),
itemModels));
}
if (_viewModel.Categories.Count == 0)
{
return;
}
if (CategoryListBox is not null)
{
CategoryListBox.SelectedIndex = 0;
}
}
private ComponentLibraryItemViewModel CreateComponentItem(ComponentLibraryComponentEntry entry)
{
if (_componentLibraryService is null ||
_createContextFactory is null ||
_localize is null)
{
return new ComponentLibraryItemViewModel(entry.ComponentId, entry.DisplayName, previewControl: null);
}
Control? previewControl = null;
_componentLibraryService.TryCreateControl(
entry.ComponentId,
_createContextFactory(42),
out previewControl,
out _);
if (previewControl is not null)
{
previewControl.IsHitTestVisible = false;
previewControl.Focusable = false;
}
return new ComponentLibraryItemViewModel(
entry.ComponentId,
string.IsNullOrWhiteSpace(entry.DisplayNameLocalizationKey)
? entry.DisplayName
: _localize(entry.DisplayNameLocalizationKey, entry.DisplayName),
previewControl);
}
private void OnCategorySelectionChanged(object? sender, SelectionChangedEventArgs e)
{
_ = sender;
_ = e;
_viewModel.Components.Clear();
if (CategoryListBox?.SelectedItem is not ComponentLibraryCategoryViewModel selectedCategory)
{
return;
}
foreach (var component in selectedCategory.Components)
{
_viewModel.Components.Add(component);
}
}
private void OnAddComponentClick(object? sender, RoutedEventArgs e)
{
_ = e;
if (sender is not Button button ||
button.Tag is not string componentId ||
string.IsNullOrWhiteSpace(componentId))
{
return;
}
AddComponentRequested?.Invoke(this, componentId);
}
private void OnCloseClick(object? sender, RoutedEventArgs e)
{
_ = sender;
_ = e;
Hide();
}
private Symbol ResolveCategoryIcon(string categoryId)
{
if (string.Equals(categoryId, "Clock", StringComparison.OrdinalIgnoreCase))
{
return Symbol.Clock;
}
if (string.Equals(categoryId, "Date", StringComparison.OrdinalIgnoreCase))
{
return Symbol.CalendarDate;
}
if (string.Equals(categoryId, "Weather", StringComparison.OrdinalIgnoreCase))
{
return Symbol.WeatherSunny;
}
if (string.Equals(categoryId, "Board", StringComparison.OrdinalIgnoreCase))
{
return Symbol.Edit;
}
if (string.Equals(categoryId, "Media", StringComparison.OrdinalIgnoreCase))
{
return Symbol.Play;
}
if (string.Equals(categoryId, "Info", StringComparison.OrdinalIgnoreCase))
{
return Symbol.Apps;
}
if (string.Equals(categoryId, "Calculator", StringComparison.OrdinalIgnoreCase))
{
return Symbol.Calculator;
}
if (string.Equals(categoryId, "Study", StringComparison.OrdinalIgnoreCase))
{
return Symbol.Apps;
}
return Symbol.Apps;
}
private string GetLocalizedCategoryTitle(string categoryId)
{
if (_localize is null)
{
return categoryId;
}
if (string.Equals(categoryId, "Clock", StringComparison.OrdinalIgnoreCase))
{
return _localize("component_category.clock", "Clock");
}
if (string.Equals(categoryId, "Date", StringComparison.OrdinalIgnoreCase))
{
return _localize("component_category.date", "Calendar");
}
if (string.Equals(categoryId, "Weather", StringComparison.OrdinalIgnoreCase))
{
return _localize("component_category.weather", "Weather");
}
if (string.Equals(categoryId, "Board", StringComparison.OrdinalIgnoreCase))
{
return _localize("component_category.board", "Board");
}
if (string.Equals(categoryId, "Media", StringComparison.OrdinalIgnoreCase))
{
return _localize("component_category.media", "Media");
}
if (string.Equals(categoryId, "Info", StringComparison.OrdinalIgnoreCase))
{
return _localize("component_category.info", "Info");
}
if (string.Equals(categoryId, "Calculator", StringComparison.OrdinalIgnoreCase))
{
return _localize("component_category.calculator", "Calculator");
}
if (string.Equals(categoryId, "Study", StringComparison.OrdinalIgnoreCase))
{
return _localize("component_category.study", "Study");
}
return categoryId;
}
}

View File

@@ -8,6 +8,8 @@ using Avalonia.Media;
using Avalonia.Styling;
using Avalonia.Threading;
using LanMountainDesktop.ComponentSystem;
using LanMountainDesktop.Models;
using LanMountainDesktop.PluginSdk;
using LanMountainDesktop.Services;
using LanMountainDesktop.Services.Settings;
@@ -59,7 +61,7 @@ public partial class AnalogClockWidget : UserControl, IDesktopComponentWidget, I
private string _componentId = BuiltInComponentIds.DesktopClock;
private string _placementId = string.Empty;
private readonly ISettingsService _settingsService = HostSettingsFacadeProvider.GetOrCreate().Settings;
private ISettingsService _settingsService = HostSettingsFacadeProvider.GetOrCreate().Settings;
private readonly LocalizationService _localizationService = new();
private TimeZoneService? _timeZoneService;
private double _currentCellSize = 48;

View File

@@ -35,8 +35,8 @@ public partial class BaiduHotSearchWidget : UserControl, IDesktopComponentWidget
Interval = TimeSpan.FromMinutes(15)
};
private readonly AppSettingsService _appSettingsService = new();
private readonly ComponentSettingsService _componentSettingsService = new();
private LanMountainDesktop.PluginSdk.ISettingsService _appSettingsService = LanMountainDesktop.Services.Settings.HostSettingsFacadeProvider.GetOrCreate().Settings;
private IComponentInstanceSettingsStore _componentSettingsService = HostComponentSettingsStoreProvider.GetOrCreate();
private readonly LocalizationService _localizationService = new();
private readonly List<BaiduHotSearchItemSnapshot> _activeItems = [];
private readonly List<HotItemVisual> _hotItemVisuals = [];

View File

@@ -33,8 +33,8 @@ public partial class BilibiliHotSearchWidget : UserControl, IDesktopComponentWid
Interval = TimeSpan.FromMinutes(15)
};
private readonly AppSettingsService _appSettingsService = new();
private readonly ComponentSettingsService _componentSettingsService = new();
private LanMountainDesktop.PluginSdk.ISettingsService _appSettingsService = LanMountainDesktop.Services.Settings.HostSettingsFacadeProvider.GetOrCreate().Settings;
private IComponentInstanceSettingsStore _componentSettingsService = HostComponentSettingsStoreProvider.GetOrCreate();
private readonly LocalizationService _localizationService = new();
private readonly List<BilibiliHotSearchItemSnapshot> _activeItems = [];
private readonly List<HotItemVisual> _hotItemVisuals = [];

View File

@@ -336,8 +336,6 @@ public partial class BrowserWidget : UserControl, IDesktopComponentWidget,
GoButton.IsEnabled = true;
AddressTextBox.IsEnabled = true;
UnavailableOverlay.IsVisible = false;
TryNavigate(_lastKnownUri, "Activate");
}
private void DeactivateWebView(bool clearUrl)

View File

@@ -9,6 +9,7 @@ using Avalonia.Styling;
using Avalonia.Threading;
using LanMountainDesktop.ComponentSystem;
using LanMountainDesktop.Models;
using LanMountainDesktop.PluginSdk;
using LanMountainDesktop.Services;
using LanMountainDesktop.Services.Settings;
@@ -27,7 +28,7 @@ public partial class ClassScheduleWidget : UserControl, IDesktopComponentWidget,
Interval = TimeSpan.FromMinutes(4)
};
private readonly ISettingsService _settingsService = HostSettingsFacadeProvider.GetOrCreate().Settings;
private ISettingsService _settingsService = HostSettingsFacadeProvider.GetOrCreate().Settings;
private readonly LocalizationService _localizationService = new();
private readonly IClassIslandScheduleDataService _scheduleService = new ClassIslandScheduleDataService();

View File

@@ -44,8 +44,8 @@ public partial class CnrDailyNewsWidget : UserControl, IDesktopComponentWidget,
Interval = TimeSpan.FromMinutes(30)
};
private readonly AppSettingsService _appSettingsService = new();
private readonly ComponentSettingsService _componentSettingsService = new();
private LanMountainDesktop.PluginSdk.ISettingsService _appSettingsService = LanMountainDesktop.Services.Settings.HostSettingsFacadeProvider.GetOrCreate().Settings;
private IComponentInstanceSettingsStore _componentSettingsService = HostComponentSettingsStoreProvider.GetOrCreate();
private readonly LocalizationService _localizationService = new();
private readonly Bitmap?[] _newsBitmaps = new Bitmap?[2];
private readonly List<string?> _newsUrls = [];
@@ -875,4 +875,3 @@ public partial class CnrDailyNewsWidget : UserControl, IDesktopComponentWidget,
return MultiWhitespaceRegex.Replace(text.Trim(), " ");
}
}

View File

@@ -15,6 +15,7 @@ using Avalonia.Media.Imaging;
using Avalonia.Threading;
using LanMountainDesktop.ComponentSystem;
using LanMountainDesktop.Models;
using LanMountainDesktop.PluginSdk;
using LanMountainDesktop.Services;
using LanMountainDesktop.Services.Settings;
@@ -59,7 +60,7 @@ public partial class DailyArtworkWidget : UserControl, IDesktopComponentWidget,
Interval = TimeSpan.FromHours(6)
};
private readonly ISettingsService _settingsService = HostSettingsFacadeProvider.GetOrCreate().Settings;
private ISettingsService _settingsService = HostSettingsFacadeProvider.GetOrCreate().Settings;
private readonly LocalizationService _localizationService = new();
private IRecommendationInfoService _recommendationService = DefaultRecommendationService;

View File

@@ -55,7 +55,7 @@ public partial class DailyPoetryWidget : UserControl, IDesktopComponentWidget, I
Interval = TimeSpan.FromHours(6)
};
private readonly AppSettingsService _settingsService = new();
private LanMountainDesktop.PluginSdk.ISettingsService _settingsService = LanMountainDesktop.Services.Settings.HostSettingsFacadeProvider.GetOrCreate().Settings;
private readonly LocalizationService _localizationService = new();
private IRecommendationInfoService _recommendationService = DefaultRecommendationService;

View File

@@ -33,8 +33,8 @@ public partial class DailyWord2x2Widget : UserControl, IDesktopComponentWidget,
Interval = TimeSpan.FromHours(6)
};
private readonly AppSettingsService _appSettingsService = new();
private readonly ComponentSettingsService _componentSettingsService = new();
private LanMountainDesktop.PluginSdk.ISettingsService _appSettingsService = LanMountainDesktop.Services.Settings.HostSettingsFacadeProvider.GetOrCreate().Settings;
private IComponentInstanceSettingsStore _componentSettingsService = HostComponentSettingsStoreProvider.GetOrCreate();
private readonly LocalizationService _localizationService = new();
private IRecommendationInfoService _recommendationService = DefaultRecommendationService;

View File

@@ -31,8 +31,8 @@ public partial class DailyWordWidget : UserControl, IDesktopComponentWidget, IRe
Interval = TimeSpan.FromHours(6)
};
private readonly AppSettingsService _appSettingsService = new();
private readonly ComponentSettingsService _componentSettingsService = new();
private LanMountainDesktop.PluginSdk.ISettingsService _appSettingsService = LanMountainDesktop.Services.Settings.HostSettingsFacadeProvider.GetOrCreate().Settings;
private IComponentInstanceSettingsStore _componentSettingsService = HostComponentSettingsStoreProvider.GetOrCreate();
private readonly LocalizationService _localizationService = new();
private IRecommendationInfoService _recommendationService = DefaultRecommendationService;

View File

@@ -1,6 +1,7 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using Avalonia.Controls;
using LanMountainDesktop.ComponentSystem;
using LanMountainDesktop.PluginSdk;
@@ -16,7 +17,9 @@ public sealed record DesktopComponentControlFactoryContext(
IWeatherInfoService WeatherInfoService,
IRecommendationInfoService RecommendationInfoService,
ICalculatorDataService CalculatorDataService,
ISettingsFacadeService SettingsFacade,
ISettingsService SettingsService,
IComponentInstanceSettingsStore ComponentSettingsStore,
IComponentSettingsAccessor ComponentSettingsAccessor,
string? PlacementId = null);
@@ -87,10 +90,15 @@ public sealed class DesktopComponentRuntimeDescriptor
IWeatherInfoService weatherInfoService,
IRecommendationInfoService recommendationInfoService,
ICalculatorDataService calculatorDataService,
ISettingsFacadeService settingsFacade,
string? placementId = null)
{
var settingsService = HostSettingsFacadeProvider.GetOrCreate().Settings;
ArgumentNullException.ThrowIfNull(settingsFacade);
var settingsService = settingsFacade.Settings;
var componentAccessor = settingsService.GetComponentAccessor(Definition.Id, placementId);
var componentSettingsStore = new ComponentSettingsService(settingsService);
componentSettingsStore.SetScopedComponentContext(Definition.Id, placementId);
var control = _controlFactory(new DesktopComponentControlFactoryContext(
Definition,
cellSize,
@@ -98,20 +106,37 @@ public sealed class DesktopComponentRuntimeDescriptor
weatherInfoService,
recommendationInfoService,
calculatorDataService,
settingsFacade,
settingsService,
componentSettingsStore,
componentAccessor,
placementId));
var runtimeContext = new DesktopComponentRuntimeContext(
Definition.Id,
placementId,
settingsFacade,
settingsService,
componentAccessor);
componentAccessor,
componentSettingsStore);
ApplySettingsDependencies(control, settingsService, componentSettingsStore);
if (control is IComponentRuntimeContextAware runtimeContextAwareComponent)
{
runtimeContextAwareComponent.SetComponentRuntimeContext(runtimeContext);
}
if (control is IComponentSettingsContextAware settingsContextAwareComponent)
{
settingsContextAwareComponent.SetComponentSettingsContext(new DesktopComponentSettingsContext(
Definition.Id,
placementId,
settingsFacade,
settingsService,
componentAccessor,
componentSettingsStore));
}
if (control is IComponentPlacementContextAware placementAwareComponent)
{
placementAwareComponent.SetComponentPlacementContext(Definition.Id, placementId);
@@ -149,6 +174,57 @@ public sealed class DesktopComponentRuntimeDescriptor
{
return _cornerRadiusResolver(Math.Max(1, cellSize));
}
private static void ApplySettingsDependencies(
object? target,
ISettingsService settingsService,
IComponentInstanceSettingsStore componentSettingsStore)
{
if (target is null)
{
return;
}
var flags = BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic;
foreach (var field in target.GetType().GetFields(flags))
{
if (field.IsInitOnly)
{
continue;
}
if (typeof(ISettingsService).IsAssignableFrom(field.FieldType))
{
field.SetValue(target, settingsService);
continue;
}
if (typeof(IComponentInstanceSettingsStore).IsAssignableFrom(field.FieldType))
{
field.SetValue(target, componentSettingsStore);
}
}
foreach (var property in target.GetType().GetProperties(flags))
{
if (!property.CanWrite)
{
continue;
}
if (typeof(ISettingsService).IsAssignableFrom(property.PropertyType))
{
property.SetValue(target, settingsService);
continue;
}
if (typeof(IComponentInstanceSettingsStore).IsAssignableFrom(property.PropertyType))
{
property.SetValue(target, componentSettingsStore);
}
}
}
}
public sealed class DesktopComponentRuntimeRegistry
@@ -368,8 +444,11 @@ public sealed class DesktopComponentRuntimeRegistry
];
}
public static DesktopComponentRuntimeRegistry CreateDefault(ComponentRegistry componentRegistry)
public static DesktopComponentRuntimeRegistry CreateDefault(
ComponentRegistry componentRegistry,
ISettingsFacadeService settingsFacade)
{
_ = settingsFacade;
return new DesktopComponentRuntimeRegistry(componentRegistry, GetDefaultRegistrations());
}

View File

@@ -36,7 +36,7 @@ public partial class ExchangeRateCalculatorWidget : UserControl, IDesktopCompone
Interval = TimeSpan.FromMinutes(30)
};
private readonly AppSettingsService _settingsService = new();
private LanMountainDesktop.PluginSdk.ISettingsService _settingsService = LanMountainDesktop.Services.Settings.HostSettingsFacadeProvider.GetOrCreate().Settings;
private readonly LocalizationService _localizationService = new();
private IRecommendationInfoService _recommendationService = DefaultRecommendationService;
private ICalculatorDataService _calculatorDataService = DefaultCalculatorService;

View File

@@ -11,6 +11,7 @@ using Avalonia.Media;
using Avalonia.Threading;
using LanMountainDesktop.ComponentSystem;
using LanMountainDesktop.Models;
using LanMountainDesktop.PluginSdk;
using LanMountainDesktop.Services;
using LanMountainDesktop.Services.Settings;
using LanMountainDesktop.Theme;
@@ -26,7 +27,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 ISettingsService _settingsService = HostSettingsFacadeProvider.GetOrCreate().Settings;
private ISettingsService _settingsService = HostSettingsFacadeProvider.GetOrCreate().Settings;
private readonly LocalizationService _localizationService = new();
private IWeatherInfoService _weatherInfoService = DefaultWeatherInfoService;

View File

@@ -95,8 +95,8 @@ public partial class HourlyWeatherWidget : UserControl, IDesktopComponentWidget,
Interval = FluttermotionToken.WeatherAnimationFrameInterval
};
private readonly AppSettingsService _settingsService = new();
private IComponentInstanceSettingsStore _componentSettingsStore = new ComponentSettingsService();
private LanMountainDesktop.PluginSdk.ISettingsService _settingsService = LanMountainDesktop.Services.Settings.HostSettingsFacadeProvider.GetOrCreate().Settings;
private IComponentInstanceSettingsStore _componentSettingsStore = HostComponentSettingsStoreProvider.GetOrCreate();
private readonly LocalizationService _localizationService = new();
private readonly Dictionary<WeatherVisualKind, IBrush> _backgroundBrushCache = new();
private readonly Dictionary<HyperOS3WeatherVisualKind, IBrush> _particleBrushCache = new();

View File

@@ -44,8 +44,8 @@ public partial class IfengNewsWidget : UserControl, IDesktopComponentWidget, IRe
Interval = TimeSpan.FromMinutes(20)
};
private readonly AppSettingsService _appSettingsService = new();
private readonly ComponentSettingsService _componentSettingsService = new();
private LanMountainDesktop.PluginSdk.ISettingsService _appSettingsService = LanMountainDesktop.Services.Settings.HostSettingsFacadeProvider.GetOrCreate().Settings;
private IComponentInstanceSettingsStore _componentSettingsService = HostComponentSettingsStoreProvider.GetOrCreate();
private readonly LocalizationService _localizationService = new();
private readonly List<DailyNewsItemSnapshot> _activeItems = [];
private readonly List<NewsItemVisual> _itemVisuals = [];

View File

@@ -93,8 +93,8 @@ public partial class MultiDayWeatherWidget : UserControl, IDesktopComponentWidge
Interval = FluttermotionToken.WeatherAnimationFrameInterval
};
private readonly AppSettingsService _settingsService = new();
private IComponentInstanceSettingsStore _componentSettingsStore = new ComponentSettingsService();
private LanMountainDesktop.PluginSdk.ISettingsService _settingsService = LanMountainDesktop.Services.Settings.HostSettingsFacadeProvider.GetOrCreate().Settings;
private IComponentInstanceSettingsStore _componentSettingsStore = HostComponentSettingsStoreProvider.GetOrCreate();
private readonly LocalizationService _localizationService = new();
private readonly Dictionary<WeatherVisualKind, IBrush> _backgroundBrushCache = new();
private readonly Dictionary<HyperOS3WeatherVisualKind, IBrush> _particleBrushCache = new();

View File

@@ -29,7 +29,7 @@ public partial class MusicControlWidget : UserControl, IDesktopComponentWidget,
private readonly IMusicControlService _musicControlService = MusicControlServiceFactory.CreateDefault();
private readonly MonetColorService _monetColorService = new();
private readonly AppSettingsService _settingsService = new();
private LanMountainDesktop.PluginSdk.ISettingsService _settingsService = LanMountainDesktop.Services.Settings.HostSettingsFacadeProvider.GetOrCreate().Settings;
private readonly LocalizationService _localizationService = new();
private CancellationTokenSource? _refreshCts;

View File

@@ -26,7 +26,7 @@ public partial class RecordingWidget : UserControl, IDesktopComponentWidget, IDe
private readonly IAudioRecorderService _audioRecorderService = AudioRecorderServiceFactory.CreateRecorder();
private readonly IStudyAnalyticsService _studyAnalyticsService = StudyAnalyticsServiceFactory.CreateDefault();
private readonly AppSettingsService _settingsService = new();
private LanMountainDesktop.PluginSdk.ISettingsService _settingsService = LanMountainDesktop.Services.Settings.HostSettingsFacadeProvider.GetOrCreate().Settings;
private readonly LocalizationService _localizationService = new();
private readonly List<Border> _waveBars = [];
private readonly double[] _waveLevels = new double[WaveBarCount];

View File

@@ -45,8 +45,8 @@ public partial class Stcn24ForumWidget : UserControl, IDesktopComponentWidget, I
Interval = TimeSpan.FromMinutes(20)
};
private readonly AppSettingsService _appSettingsService = new();
private readonly ComponentSettingsService _componentSettingsService = new();
private LanMountainDesktop.PluginSdk.ISettingsService _appSettingsService = LanMountainDesktop.Services.Settings.HostSettingsFacadeProvider.GetOrCreate().Settings;
private IComponentInstanceSettingsStore _componentSettingsService = HostComponentSettingsStoreProvider.GetOrCreate();
private readonly LocalizationService _localizationService = new();
private readonly List<Stcn24ForumPostItemSnapshot> _activeItems = [];
private readonly List<ForumItemVisual> _itemVisuals = [];

View File

@@ -41,7 +41,7 @@ public partial class StudyDeductionReasonsWidget : UserControl, IDesktopComponen
private static readonly Color LightSubstrate = Color.Parse("#FFF1F5FA");
private readonly IStudyAnalyticsService _studyAnalyticsService = StudyAnalyticsServiceFactory.CreateDefault();
private readonly AppSettingsService _settingsService = new();
private LanMountainDesktop.PluginSdk.ISettingsService _settingsService = LanMountainDesktop.Services.Settings.HostSettingsFacadeProvider.GetOrCreate().Settings;
private readonly LocalizationService _localizationService = new();
private readonly DispatcherTimer _uiTimer = new()
{
@@ -73,6 +73,7 @@ public partial class StudyDeductionReasonsWidget : UserControl, IDesktopComponen
AttachedToVisualTree += OnAttachedToVisualTree;
DetachedFromVisualTree += OnDetachedFromVisualTree;
SizeChanged += OnSizeChanged;
ActualThemeVariantChanged += OnActualThemeVariantChanged;
ApplyVariableFontFamily();
ReloadLanguageCode();
@@ -114,6 +115,11 @@ public partial class StudyDeductionReasonsWidget : UserControl, IDesktopComponen
ApplyTypographyByBackground(ResolvePanelBackgroundColor());
}
private void OnActualThemeVariantChanged(object? sender, EventArgs e)
{
RefreshVisual();
}
private void OnUiTimerTick(object? sender, EventArgs e)
{
RefreshVisual();
@@ -572,7 +578,7 @@ public partial class StudyDeductionReasonsWidget : UserControl, IDesktopComponen
return solidBackground.Color;
}
if (Resources.TryGetResource("AdaptiveGlassStrongBackgroundBrush", ActualThemeVariant, out var resource) &&
if (this.TryFindResource("AdaptiveGlassStrongBackgroundBrush", out var resource) &&
resource is ISolidColorBrush solidBrush)
{
return solidBrush.Color;

View File

@@ -13,8 +13,8 @@ public partial class StudyEnvironmentWidget : UserControl, IDesktopComponentWidg
{
private readonly IStudyAnalyticsService _studyAnalyticsService = StudyAnalyticsServiceFactory.CreateDefault();
private readonly StudyAnalyticsMonitoringLeaseCoordinator _monitoringLeaseCoordinator = StudyAnalyticsMonitoringLeaseCoordinatorFactory.CreateDefault();
private readonly AppSettingsService _appSettingsService = new();
private readonly ComponentSettingsService _componentSettingsService = new();
private LanMountainDesktop.PluginSdk.ISettingsService _appSettingsService = LanMountainDesktop.Services.Settings.HostSettingsFacadeProvider.GetOrCreate().Settings;
private IComponentInstanceSettingsStore _componentSettingsService = HostComponentSettingsStoreProvider.GetOrCreate();
private readonly LocalizationService _localizationService = new();
private readonly DispatcherTimer _uiTimer = new()
{
@@ -38,6 +38,7 @@ public partial class StudyEnvironmentWidget : UserControl, IDesktopComponentWidg
AttachedToVisualTree += OnAttachedToVisualTree;
DetachedFromVisualTree += OnDetachedFromVisualTree;
SizeChanged += OnSizeChanged;
ActualThemeVariantChanged += OnActualThemeVariantChanged;
ReloadDisplaySettings();
ApplyCellSize(_currentCellSize);
@@ -106,6 +107,11 @@ public partial class StudyEnvironmentWidget : UserControl, IDesktopComponentWidg
UpdateAdaptiveLayout();
}
private void OnActualThemeVariantChanged(object? sender, EventArgs e)
{
RefreshVisual();
}
private void OnUiTimerTick(object? sender, EventArgs e)
{
RefreshVisual();
@@ -330,7 +336,7 @@ public partial class StudyEnvironmentWidget : UserControl, IDesktopComponentWidg
private IBrush TryResolveThemeBrush(string resourceKey, string fallbackHex)
{
if (Resources.TryGetResource(resourceKey, ActualThemeVariant, out var resource) && resource is IBrush brush)
if (this.TryFindResource(resourceKey, out var resource) && resource is IBrush brush)
{
return brush;
}
@@ -352,6 +358,7 @@ public partial class StudyEnvironmentWidget : UserControl, IDesktopComponentWidg
AttachedToVisualTree -= OnAttachedToVisualTree;
DetachedFromVisualTree -= OnDetachedFromVisualTree;
SizeChanged -= OnSizeChanged;
ActualThemeVariantChanged -= OnActualThemeVariantChanged;
_monitoringLease?.Dispose();
_monitoringLease = null;

View File

@@ -41,7 +41,7 @@ public partial class StudyInterruptDensityWidget : UserControl, IDesktopComponen
private readonly IStudyAnalyticsService _studyAnalyticsService = StudyAnalyticsServiceFactory.CreateDefault();
private readonly StudyAnalyticsMonitoringLeaseCoordinator _monitoringLeaseCoordinator = StudyAnalyticsMonitoringLeaseCoordinatorFactory.CreateDefault();
private readonly AppSettingsService _settingsService = new();
private LanMountainDesktop.PluginSdk.ISettingsService _settingsService = LanMountainDesktop.Services.Settings.HostSettingsFacadeProvider.GetOrCreate().Settings;
private readonly LocalizationService _localizationService = new();
private readonly DispatcherTimer _uiTimer = new()
{
@@ -79,6 +79,7 @@ public partial class StudyInterruptDensityWidget : UserControl, IDesktopComponen
AttachedToVisualTree += OnAttachedToVisualTree;
DetachedFromVisualTree += OnDetachedFromVisualTree;
SizeChanged += OnSizeChanged;
ActualThemeVariantChanged += OnActualThemeVariantChanged;
ApplyVariableFontFamily();
ReloadLanguageCode();
@@ -123,6 +124,11 @@ public partial class StudyInterruptDensityWidget : UserControl, IDesktopComponen
ApplyTypographyByBackground(ResolvePanelBackgroundColor());
}
private void OnActualThemeVariantChanged(object? sender, EventArgs e)
{
RefreshVisual();
}
private void OnUiTimerTick(object? sender, EventArgs e)
{
RefreshVisual();
@@ -506,7 +512,7 @@ public partial class StudyInterruptDensityWidget : UserControl, IDesktopComponen
return solidBackground.Color;
}
if (Resources.TryGetResource("AdaptiveGlassStrongBackgroundBrush", ActualThemeVariant, out var resource) &&
if (this.TryFindResource("AdaptiveGlassStrongBackgroundBrush", out var resource) &&
resource is ISolidColorBrush solidBrush)
{
return solidBrush.Color;

View File

@@ -55,7 +55,7 @@ public partial class StudyNoiseCurveWidget : UserControl, IDesktopComponentWidge
private readonly object _snapshotSync = new();
private readonly IStudyAnalyticsService _studyAnalyticsService = StudyAnalyticsServiceFactory.CreateDefault();
private readonly StudyAnalyticsMonitoringLeaseCoordinator _monitoringLeaseCoordinator = StudyAnalyticsMonitoringLeaseCoordinatorFactory.CreateDefault();
private readonly AppSettingsService _settingsService = new();
private LanMountainDesktop.PluginSdk.ISettingsService _settingsService = LanMountainDesktop.Services.Settings.HostSettingsFacadeProvider.GetOrCreate().Settings;
private readonly LocalizationService _localizationService = new();
private readonly DispatcherTimer _renderTimer = new()
{
@@ -89,6 +89,7 @@ public partial class StudyNoiseCurveWidget : UserControl, IDesktopComponentWidge
AttachedToVisualTree += OnAttachedToVisualTree;
DetachedFromVisualTree += OnDetachedFromVisualTree;
SizeChanged += OnSizeChanged;
ActualThemeVariantChanged += OnActualThemeVariantChanged;
ReloadLanguageCode();
ApplyCellSize(_currentCellSize);
@@ -192,6 +193,24 @@ public partial class StudyNoiseCurveWidget : UserControl, IDesktopComponentWidge
ApplyTypographyByBackground(panelColor);
}
private void OnActualThemeVariantChanged(object? sender, EventArgs e)
{
var panelColor = ResolvePanelBackgroundColor();
ApplyTypographyByBackground(panelColor);
ApplyStatusBadgeStyle(StatusVisualKind.Default, panelColor);
lock (_snapshotSync)
{
_pendingSnapshot = _studyAnalyticsService.GetSnapshot();
_hasPendingSnapshot = true;
}
if (!_renderTimer.IsEnabled)
{
OnRenderTimerTick(this, EventArgs.Empty);
}
}
private void OnStudySnapshotUpdated(object? sender, StudyAnalyticsSnapshotChangedEventArgs e)
{
lock (_snapshotSync)
@@ -375,7 +394,7 @@ public partial class StudyNoiseCurveWidget : UserControl, IDesktopComponentWidge
return solidBackground.Color;
}
if (Resources.TryGetResource("AdaptiveGlassStrongBackgroundBrush", ActualThemeVariant, out var resource) &&
if (this.TryFindResource("AdaptiveGlassStrongBackgroundBrush", out var resource) &&
resource is ISolidColorBrush solidBrush)
{
return solidBrush.Color;
@@ -580,6 +599,7 @@ public partial class StudyNoiseCurveWidget : UserControl, IDesktopComponentWidge
AttachedToVisualTree -= OnAttachedToVisualTree;
DetachedFromVisualTree -= OnDetachedFromVisualTree;
SizeChanged -= OnSizeChanged;
ActualThemeVariantChanged -= OnActualThemeVariantChanged;
if (_isSubscribed)
{

View File

@@ -42,7 +42,7 @@ public partial class StudyNoiseDistributionWidget : UserControl, IDesktopCompone
private readonly IStudyAnalyticsService _studyAnalyticsService = StudyAnalyticsServiceFactory.CreateDefault();
private readonly StudyAnalyticsMonitoringLeaseCoordinator _monitoringLeaseCoordinator = StudyAnalyticsMonitoringLeaseCoordinatorFactory.CreateDefault();
private readonly AppSettingsService _settingsService = new();
private LanMountainDesktop.PluginSdk.ISettingsService _settingsService = LanMountainDesktop.Services.Settings.HostSettingsFacadeProvider.GetOrCreate().Settings;
private readonly LocalizationService _localizationService = new();
private readonly DispatcherTimer _uiTimer = new()
{
@@ -75,6 +75,7 @@ public partial class StudyNoiseDistributionWidget : UserControl, IDesktopCompone
AttachedToVisualTree += OnAttachedToVisualTree;
DetachedFromVisualTree += OnDetachedFromVisualTree;
SizeChanged += OnSizeChanged;
ActualThemeVariantChanged += OnActualThemeVariantChanged;
ApplyVariableFontFamily();
ReloadLanguageCode();
@@ -129,6 +130,11 @@ public partial class StudyNoiseDistributionWidget : UserControl, IDesktopCompone
ApplyTypographyByBackground(ResolvePanelBackgroundColor());
}
private void OnActualThemeVariantChanged(object? sender, EventArgs e)
{
RefreshVisual();
}
private void OnUiTimerTick(object? sender, EventArgs e)
{
RefreshVisual();
@@ -396,7 +402,7 @@ public partial class StudyNoiseDistributionWidget : UserControl, IDesktopCompone
return solidBackground.Color;
}
if (Resources.TryGetResource("AdaptiveGlassStrongBackgroundBrush", ActualThemeVariant, out var resource) &&
if (this.TryFindResource("AdaptiveGlassStrongBackgroundBrush", out var resource) &&
resource is ISolidColorBrush solidBrush)
{
return solidBrush.Color;
@@ -627,10 +633,9 @@ public partial class StudyNoiseDistributionWidget : UserControl, IDesktopCompone
AttachedToVisualTree -= OnAttachedToVisualTree;
DetachedFromVisualTree -= OnDetachedFromVisualTree;
SizeChanged -= OnSizeChanged;
ActualThemeVariantChanged -= OnActualThemeVariantChanged;
_monitoringLease?.Dispose();
_monitoringLease = null;
}
}

View File

@@ -42,7 +42,7 @@ public partial class StudyScoreOverviewWidget : UserControl, IDesktopComponentWi
private readonly IStudyAnalyticsService _studyAnalyticsService = StudyAnalyticsServiceFactory.CreateDefault();
private readonly StudyAnalyticsMonitoringLeaseCoordinator _monitoringLeaseCoordinator = StudyAnalyticsMonitoringLeaseCoordinatorFactory.CreateDefault();
private readonly AppSettingsService _settingsService = new();
private LanMountainDesktop.PluginSdk.ISettingsService _settingsService = LanMountainDesktop.Services.Settings.HostSettingsFacadeProvider.GetOrCreate().Settings;
private readonly LocalizationService _localizationService = new();
private readonly DispatcherTimer _uiTimer = new()
{
@@ -68,6 +68,7 @@ public partial class StudyScoreOverviewWidget : UserControl, IDesktopComponentWi
AttachedToVisualTree += OnAttachedToVisualTree;
DetachedFromVisualTree += OnDetachedFromVisualTree;
SizeChanged += OnSizeChanged;
ActualThemeVariantChanged += OnActualThemeVariantChanged;
ApplyVariableFontFamily();
ReloadLanguageCode();
@@ -112,6 +113,11 @@ public partial class StudyScoreOverviewWidget : UserControl, IDesktopComponentWi
ApplyTypographyByBackground(ResolvePanelBackgroundColor());
}
private void OnActualThemeVariantChanged(object? sender, EventArgs e)
{
RefreshVisual();
}
private void OnUiTimerTick(object? sender, EventArgs e)
{
RefreshVisual();
@@ -501,7 +507,7 @@ public partial class StudyScoreOverviewWidget : UserControl, IDesktopComponentWi
return solidBackground.Color;
}
if (Resources.TryGetResource("AdaptiveGlassStrongBackgroundBrush", ActualThemeVariant, out var resource) &&
if (this.TryFindResource("AdaptiveGlassStrongBackgroundBrush", out var resource) &&
resource is ISolidColorBrush solidBrush)
{
return solidBrush.Color;

View File

@@ -50,7 +50,7 @@ public partial class StudySessionControlWidget : UserControl, IDesktopComponentW
private readonly IStudyAnalyticsService _studyAnalyticsService = StudyAnalyticsServiceFactory.CreateDefault();
private readonly StudyAnalyticsMonitoringLeaseCoordinator _monitoringLeaseCoordinator = StudyAnalyticsMonitoringLeaseCoordinatorFactory.CreateDefault();
private readonly AppSettingsService _settingsService = new();
private LanMountainDesktop.PluginSdk.ISettingsService _settingsService = LanMountainDesktop.Services.Settings.HostSettingsFacadeProvider.GetOrCreate().Settings;
private readonly LocalizationService _localizationService = new();
private readonly DispatcherTimer _uiTimer = new()
{
@@ -76,6 +76,7 @@ public partial class StudySessionControlWidget : UserControl, IDesktopComponentW
AttachedToVisualTree += OnAttachedToVisualTree;
DetachedFromVisualTree += OnDetachedFromVisualTree;
SizeChanged += OnSizeChanged;
ActualThemeVariantChanged += OnActualThemeVariantChanged;
ReloadLanguageCode();
ApplyCellSize(_currentCellSize);
@@ -119,6 +120,11 @@ public partial class StudySessionControlWidget : UserControl, IDesktopComponentW
ApplyTypographyByBackground(ResolvePanelBackgroundColor());
}
private void OnActualThemeVariantChanged(object? sender, EventArgs e)
{
RefreshVisual();
}
private void OnUiTimerTick(object? sender, EventArgs e)
{
RefreshVisual();
@@ -334,7 +340,7 @@ public partial class StudySessionControlWidget : UserControl, IDesktopComponentW
return solidBackground.Color;
}
if (Resources.TryGetResource("AdaptiveGlassStrongBackgroundBrush", ActualThemeVariant, out var resource) &&
if (this.TryFindResource("AdaptiveGlassStrongBackgroundBrush", out var resource) &&
resource is ISolidColorBrush solidBrush)
{
return solidBrush.Color;
@@ -484,5 +490,6 @@ public partial class StudySessionControlWidget : UserControl, IDesktopComponentW
AttachedToVisualTree -= OnAttachedToVisualTree;
DetachedFromVisualTree -= OnDetachedFromVisualTree;
SizeChanged -= OnSizeChanged;
ActualThemeVariantChanged -= OnActualThemeVariantChanged;
}
}

View File

@@ -47,7 +47,7 @@ public partial class StudySessionHistoryWidget : UserControl, IDesktopComponentW
private static readonly Color LightSubstrate = Color.Parse("#FFF1F5FA");
private readonly IStudyAnalyticsService _studyAnalyticsService = StudyAnalyticsServiceFactory.CreateDefault();
private readonly AppSettingsService _settingsService = new();
private LanMountainDesktop.PluginSdk.ISettingsService _settingsService = LanMountainDesktop.Services.Settings.HostSettingsFacadeProvider.GetOrCreate().Settings;
private readonly LocalizationService _localizationService = new();
private double _currentCellSize = 48;
@@ -72,6 +72,7 @@ public partial class StudySessionHistoryWidget : UserControl, IDesktopComponentW
AttachedToVisualTree += OnAttachedToVisualTree;
DetachedFromVisualTree += OnDetachedFromVisualTree;
SizeChanged += OnSizeChanged;
ActualThemeVariantChanged += OnActualThemeVariantChanged;
DialogCancelButton.Click += (_, _) => CloseDialog();
DialogConfirmButton.Click += (_, _) => ConfirmDialog();
DialogRenameTextBox.KeyDown += OnDialogRenameTextBoxKeyDown;
@@ -133,6 +134,14 @@ public partial class StudySessionHistoryWidget : UserControl, IDesktopComponentW
}
}
private void OnActualThemeVariantChanged(object? sender, EventArgs e)
{
if (_currentSnapshot is not null)
{
RenderSnapshot(_currentSnapshot);
}
}
private void OnStudySnapshotUpdated(object? sender, StudyAnalyticsSnapshotChangedEventArgs e)
{
Dispatcher.UIThread.Post(() =>
@@ -657,7 +666,7 @@ public partial class StudySessionHistoryWidget : UserControl, IDesktopComponentW
return solidBackground.Color;
}
if (Resources.TryGetResource("AdaptiveGlassStrongBackgroundBrush", ActualThemeVariant, out var resource) &&
if (this.TryFindResource("AdaptiveGlassStrongBackgroundBrush", out var resource) &&
resource is ISolidColorBrush solidBrush)
{
return solidBrush.Color;
@@ -747,6 +756,7 @@ public partial class StudySessionHistoryWidget : UserControl, IDesktopComponentW
AttachedToVisualTree -= OnAttachedToVisualTree;
DetachedFromVisualTree -= OnDetachedFromVisualTree;
SizeChanged -= OnSizeChanged;
ActualThemeVariantChanged -= OnActualThemeVariantChanged;
DialogCancelButton.Click -= (_, _) => CloseDialog();
DialogConfirmButton.Click -= (_, _) => ConfirmDialog();
DialogRenameTextBox.KeyDown -= OnDialogRenameTextBoxKeyDown;
@@ -758,5 +768,3 @@ public partial class StudySessionHistoryWidget : UserControl, IDesktopComponentW
}
}
}

View File

@@ -41,8 +41,8 @@ public partial class WeatherClockWidget : UserControl, IDesktopComponentWidget,
Interval = TimeSpan.FromMinutes(12)
};
private readonly AppSettingsService _settingsService = new();
private IComponentInstanceSettingsStore _componentSettingsStore = new ComponentSettingsService();
private LanMountainDesktop.PluginSdk.ISettingsService _settingsService = LanMountainDesktop.Services.Settings.HostSettingsFacadeProvider.GetOrCreate().Settings;
private IComponentInstanceSettingsStore _componentSettingsStore = HostComponentSettingsStoreProvider.GetOrCreate();
private readonly LocalizationService _localizationService = new();
private readonly Line _hourHandLine = CreateHandLine("#232938", 4.0);
private readonly Line _minuteHandLine = CreateHandLine("#2F3749", 2.8);

View File

@@ -89,8 +89,8 @@ public partial class WeatherWidget : UserControl, IDesktopComponentWidget, IDesk
Interval = FluttermotionToken.WeatherAnimationFrameInterval
};
private readonly AppSettingsService _settingsService = new();
private IComponentInstanceSettingsStore _componentSettingsStore = new ComponentSettingsService();
private LanMountainDesktop.PluginSdk.ISettingsService _settingsService = LanMountainDesktop.Services.Settings.HostSettingsFacadeProvider.GetOrCreate().Settings;
private IComponentInstanceSettingsStore _componentSettingsStore = HostComponentSettingsStoreProvider.GetOrCreate();
private readonly LocalizationService _localizationService = new();
private readonly Dictionary<WeatherVisualKind, IBrush> _backgroundBrushCache = new();
private readonly Dictionary<HyperOS3WeatherVisualKind, IBrush> _particleBrushCache = new();

View File

@@ -92,8 +92,8 @@ public partial class WorldClockWidget : UserControl, IDesktopComponentWidget, IT
Interval = TimeSpan.FromSeconds(1)
};
private readonly AppSettingsService _appSettingsService = new();
private IComponentInstanceSettingsStore _componentSettingsStore = new ComponentSettingsService();
private LanMountainDesktop.PluginSdk.ISettingsService _appSettingsService = LanMountainDesktop.Services.Settings.HostSettingsFacadeProvider.GetOrCreate().Settings;
private IComponentInstanceSettingsStore _componentSettingsStore = HostComponentSettingsStoreProvider.GetOrCreate();
private readonly LocalizationService _localizationService = new();
private readonly ClockEntryVisual[] _entryVisuals = new ClockEntryVisual[WorldClockTimeZoneCatalog.ClockCount];
private readonly TimeZoneInfo[] _entryTimeZones = new TimeZoneInfo[WorldClockTimeZoneCatalog.ClockCount];

View File

@@ -15,6 +15,7 @@ using FluentIcons.Common;
using LanMountainDesktop.ComponentSystem;
using LanMountainDesktop.Models;
using LanMountainDesktop.Services;
using LanMountainDesktop.Services.Settings;
using LanMountainDesktop.Theme;
using LanMountainDesktop.Views.Components;
@@ -44,7 +45,7 @@ public partial class MainWindow
private TranslateTransform? _componentLibraryCategoryHostTransform;
private TranslateTransform? _componentLibraryComponentHostTransform;
private IReadOnlyList<ComponentLibraryCategory> _componentLibraryCategories = Array.Empty<ComponentLibraryCategory>();
private IReadOnlyList<DesktopComponentRuntimeDescriptor> _componentLibraryActiveComponents = Array.Empty<DesktopComponentRuntimeDescriptor>();
private IReadOnlyList<ComponentLibraryComponentEntry> _componentLibraryActiveComponents = Array.Empty<ComponentLibraryComponentEntry>();
private bool _isComponentLibraryCategoryGestureActive;
private bool _isComponentLibraryComponentGestureActive;
private Point _componentLibraryCategoryGestureStartPoint;
@@ -95,13 +96,51 @@ public partial class MainWindow
string Id,
Symbol Icon,
string Title,
IReadOnlyList<DesktopComponentRuntimeDescriptor> Components);
IReadOnlyList<ComponentLibraryComponentEntry> Components);
private readonly record struct ComponentScaleRule(int WidthUnit, int HeightUnit, int MinScale);
private void OnOpenComponentLibraryClick(object? sender, RoutedEventArgs e)
{
_componentLibraryWindowService.Toggle(this);
_ = sender;
_ = e;
if (_isComponentLibraryOpen)
{
CloseComponentLibraryWindow(reopenSettings: false);
return;
}
var settingsWindowService = (Application.Current as App)?.SettingsWindowService;
_reopenSettingsAfterComponentLibraryClose = settingsWindowService?.IsOpen == true;
if (_reopenSettingsAfterComponentLibraryClose)
{
settingsWindowService?.Close();
}
OpenComponentLibraryWindow();
}
private void OnOpenSettingsClick(object? sender, RoutedEventArgs e)
{
_ = sender;
_ = e;
if (_isComponentLibraryOpen)
{
CloseComponentLibraryWindow(reopenSettings: false);
}
var app = Application.Current as App;
if (app?.SettingsWindowService is { } settingsWindowService)
{
settingsWindowService.Toggle(new SettingsWindowOpenRequest(
Source: "MainWindowTaskbar",
Owner: this));
return;
}
app?.OpenIndependentSettingsModule("MainWindowTaskbar");
}
private void OnCloseComponentLibraryClick(object? sender, RoutedEventArgs e)
@@ -220,16 +259,19 @@ public partial class MainWindow
private void ApplyTaskbarActionVisibility(TaskbarContext context)
{
if (BackToWindowsButton is null || OpenComponentLibraryButton is null)
if (BackToWindowsButton is null ||
OpenComponentLibraryButton is null ||
OpenSettingsButton is null)
{
return;
}
var showMinimize = _pinnedTaskbarActions.Contains(TaskbarActionId.MinimizeToWindows);
var showSettings = false;
var showDesktopEdit = true;
var showSettings = true;
var showDesktopEdit = _isSettingsOpen;
BackToWindowsButton.IsVisible = showMinimize;
OpenSettingsButton.IsVisible = showSettings;
OpenComponentLibraryButton.IsVisible = showDesktopEdit;
if (TaskbarFixedActionsHost is not null)
@@ -242,6 +284,8 @@ public partial class MainWindow
TaskbarSettingsActionHost.IsVisible = showSettings || showDesktopEdit;
}
UpdateOpenSettingsActionVisualState();
var dynamicActions = ResolveDynamicTaskbarActions(context)
.Where(action => action.IsVisible)
.ToList();
@@ -256,7 +300,25 @@ public partial class MainWindow
private void UpdateOpenSettingsActionVisualState()
{
// Open-settings action is removed in API-only settings mode.
if (OpenSettingsButtonTextBlock is null || OpenSettingsButton is null)
{
return;
}
var showBackToDesktop = _isSettingsOpen;
var buttonText = L("settings.back_to_desktop", "Back to Desktop");
OpenSettingsButtonTextBlock.IsVisible = showBackToDesktop;
OpenSettingsButtonTextBlock.Text = buttonText;
ToolTip.SetTip(
OpenSettingsButton,
showBackToDesktop
? buttonText
: L("tooltip.open_settings", "Settings"));
var effectiveCellSize = _currentDesktopCellSize > 0
? _currentDesktopCellSize
: Math.Max(32, Math.Min(Bounds.Width, Bounds.Height) / Math.Max(1, _targetShortSideCells));
ApplyWidgetSizing(effectiveCellSize);
}
private void OpenComponentLibraryWindow()
@@ -316,13 +378,109 @@ public partial class MainWindow
_reopenSettingsAfterComponentLibraryClose = false;
if (shouldReopenSettings)
{
AppLogger.Info(
"SettingsFacade",
"Reopen settings request ignored because settings UI entry is disabled during hard-cut migration.");
(Application.Current as App)?.OpenIndependentSettingsModule("ComponentLibraryClose");
}
}, FluttermotionToken.Slow);
}
private void OpenDetachedComponentLibraryWindow()
{
_detachedComponentLibraryWindow ??= CreateDetachedComponentLibraryWindow();
_detachedComponentLibraryWindow.Reload();
if (!_detachedComponentLibraryWindow.IsVisible)
{
if (IsVisible)
{
_detachedComponentLibraryWindow.Show(this);
}
else
{
_detachedComponentLibraryWindow.Show();
}
return;
}
_detachedComponentLibraryWindow.Activate();
}
private void CloseDetachedComponentLibraryWindow()
{
if (_detachedComponentLibraryWindow is null)
{
return;
}
_detachedComponentLibraryWindow.Hide();
}
private ComponentLibraryWindow CreateDetachedComponentLibraryWindow()
{
var window = new ComponentLibraryWindow(
_componentLibraryService,
cellSize => new ComponentLibraryCreateContext(
cellSize,
_timeZoneService,
_weatherDataService,
_recommendationInfoService,
_calculatorDataService,
_settingsFacade),
L);
window.AddComponentRequested += OnDetachedComponentLibraryAddComponentRequested;
window.Closed += OnDetachedComponentLibraryClosed;
return window;
}
private void OnDetachedComponentLibraryAddComponentRequested(object? sender, string componentId)
{
_ = sender;
if (string.IsNullOrWhiteSpace(componentId) ||
_currentDesktopSurfaceIndex < 0 ||
_currentDesktopSurfaceIndex >= _desktopPageCount ||
!_desktopPageComponentGrids.TryGetValue(_currentDesktopSurfaceIndex, out var pageGrid) ||
!_componentRuntimeRegistry.TryGetDescriptor(componentId, out var descriptor))
{
return;
}
var span = NormalizeComponentCellSpan(
componentId,
ComponentPlacementRules.EnsureMinimumSize(
descriptor.Definition,
descriptor.Definition.MinWidthCells,
descriptor.Definition.MinHeightCells));
var row = Math.Max(0, (pageGrid.RowDefinitions.Count - span.HeightCells) / 2);
var column = Math.Max(0, (pageGrid.ColumnDefinitions.Count - span.WidthCells) / 2);
PlaceDesktopComponentOnPage(componentId, _currentDesktopSurfaceIndex, row, column);
}
private void OnDetachedComponentLibraryClosed(object? sender, EventArgs e)
{
_ = e;
if (ReferenceEquals(sender, _detachedComponentLibraryWindow))
{
_detachedComponentLibraryWindow.AddComponentRequested -= OnDetachedComponentLibraryAddComponentRequested;
_detachedComponentLibraryWindow.Closed -= OnDetachedComponentLibraryClosed;
_detachedComponentLibraryWindow = null;
}
}
private void OnSettingsWindowStateChanged(object? sender, EventArgs e)
{
_ = sender;
_ = e;
SyncSettingsWindowState();
}
private void SyncSettingsWindowState()
{
var isOpen = (Application.Current as App)?.SettingsWindowService?.IsOpen == true;
_isSettingsOpen = isOpen;
UpdateDesktopPageAwareComponentContext();
ApplyTaskbarActionVisibility(GetCurrentTaskbarContext());
UpdateOpenSettingsActionVisualState();
}
private void InitializeDesktopComponentDragHandlers()
{
// Global handlers: we capture the pointer during drag, then track move/release anywhere.
@@ -1245,6 +1403,21 @@ public partial class MainWindow
return CreateDesktopComponentControl(runtimeDescriptor, _currentDesktopCellSize, placementId, pageIndex, "DesktopSurface");
}
private Control? CreateDesktopComponentControl(
string componentId,
double cellSize,
string? placementId,
int? pageIndex,
string action)
{
if (!_componentRuntimeRegistry.TryGetDescriptor(componentId, out var runtimeDescriptor))
{
return null;
}
return CreateDesktopComponentControl(runtimeDescriptor, cellSize, placementId, pageIndex, action);
}
private Control? CreateDesktopComponentControl(
DesktopComponentRuntimeDescriptor runtimeDescriptor,
double cellSize,
@@ -1260,6 +1433,7 @@ public partial class MainWindow
_weatherDataService,
_recommendationInfoService,
_calculatorDataService,
_settingsFacade,
placementId);
if (!_componentLibraryService.TryCreateControl(runtimeDescriptor.Definition.Id, createContext, out var component, out var exception) ||
component is null)
@@ -1291,6 +1465,7 @@ public partial class MainWindow
}
internal bool IsComponentLibraryOpenFromService => _isComponentLibraryOpen;
internal bool IsDetachedComponentLibraryWindowOpenFromService => _detachedComponentLibraryWindow is { IsVisible: true };
internal void OpenComponentLibraryWindowFromService()
{
@@ -1302,6 +1477,44 @@ public partial class MainWindow
CloseComponentLibraryWindow(reopenSettings: false);
}
internal void OpenDetachedComponentLibraryWindowFromService()
{
OpenDetachedComponentLibraryWindow();
}
internal void CloseDetachedComponentLibraryWindowFromService()
{
CloseDetachedComponentLibraryWindow();
}
public bool TryGetSettingsWindowAnchorBounds(out PixelRect anchorBounds)
{
anchorBounds = default;
if (!IsVisible || BottomTaskbarContainer is null)
{
return false;
}
var origin = BottomTaskbarContainer.TranslatePoint(new Point(0, 0), this);
if (origin is null)
{
return false;
}
var scale = RenderScaling > 0 ? RenderScaling : 1d;
var width = (int)Math.Round(BottomTaskbarContainer.Bounds.Width * scale);
var height = (int)Math.Round(BottomTaskbarContainer.Bounds.Height * scale);
if (width <= 0 || height <= 0)
{
return false;
}
var x = Position.X + (int)Math.Round(origin.Value.X * scale);
var y = Position.Y + (int)Math.Round(origin.Value.Y * scale);
anchorBounds = new PixelRect(x, y, width, height);
return true;
}
private void CollapseComponentLibraryPanel()
{
// Animate component library panel collapsing downward
@@ -1314,6 +1527,7 @@ public partial class MainWindow
_isComponentLibraryOpen = false;
CancelDesktopComponentDrag();
CancelDesktopComponentResize(restoreOriginalSpan: true);
CloseDetachedComponentLibraryWindow();
ClearDesktopComponentSelection();
ClearSelectedLauncherTile(refreshTaskbar: false);
UpdateDesktopComponentHostEditState();
@@ -2170,27 +2384,18 @@ public partial class MainWindow
private IReadOnlyList<ComponentLibraryCategory> GetComponentLibraryCategories()
{
var descriptors = _componentRuntimeRegistry.GetDesktopComponents();
if (descriptors.Count == 0)
var categories = _componentLibraryService.GetDesktopCategories();
if (categories.Count == 0)
{
return Array.Empty<ComponentLibraryCategory>();
}
return descriptors
.GroupBy(descriptor => descriptor.Definition.Category, StringComparer.OrdinalIgnoreCase)
.OrderBy(group => group.Key, StringComparer.OrdinalIgnoreCase)
.Select(group =>
{
var categoryId = string.IsNullOrWhiteSpace(group.Key) ? "Other" : group.Key.Trim();
var components = group
.OrderBy(descriptor => descriptor.Definition.DisplayName, StringComparer.OrdinalIgnoreCase)
.ToList();
return new ComponentLibraryCategory(
categoryId,
ResolveComponentLibraryCategoryIcon(categoryId),
GetLocalizedComponentLibraryCategoryTitle(categoryId),
components);
})
return categories
.Select(category => new ComponentLibraryCategory(
category.Id,
ResolveComponentLibraryCategoryIcon(category.Id),
GetLocalizedComponentLibraryCategoryTitle(category.Id),
category.Components))
.ToList();
}
@@ -2411,12 +2616,7 @@ public partial class MainWindow
for (var i = 0; i < componentCount; i++)
{
var descriptor = _componentLibraryActiveComponents[i];
var definition = descriptor.Definition;
if (!definition.AllowDesktopPlacement)
{
continue;
}
var component = _componentLibraryActiveComponents[i];
var page = new Grid
{
@@ -2429,8 +2629,8 @@ public partial class MainWindow
var previewMaxWidth = _componentLibraryComponentPageWidth * 0.94;
var previewMaxHeight = viewportHeight * 0.86;
var previewSpan = NormalizeComponentCellSpan(
definition.Id,
(definition.MinWidthCells, definition.MinHeightCells));
component.ComponentId,
(component.MinWidthCells, component.MinHeightCells));
var previewCellSize = Math.Min(
previewMaxWidth / Math.Max(1, previewSpan.WidthCells),
previewMaxHeight / Math.Max(1, previewSpan.HeightCells));
@@ -2441,7 +2641,7 @@ public partial class MainWindow
var renderCellSize = Math.Clamp(previewCellSize * 1.15, 26, 110);
var previewControl = CreateDesktopComponentControl(
descriptor,
component.ComponentId,
renderCellSize,
placementId: null,
pageIndex: null,
@@ -2479,13 +2679,13 @@ public partial class MainWindow
Background = Brushes.Transparent,
BorderThickness = new Thickness(0),
Child = previewViewbox,
Tag = definition.Id
Tag = component.ComponentId
};
previewBorder.PointerPressed += OnComponentLibraryComponentPreviewPointerPressed;
var label = new TextBlock
{
Text = GetLocalizedComponentDisplayName(descriptor),
Text = GetLocalizedComponentDisplayName(component),
FontSize = 14,
FontWeight = FontWeight.SemiBold,
Foreground = GetThemeBrush("AdaptiveTextPrimaryBrush"),
@@ -2544,11 +2744,11 @@ public partial class MainWindow
ComponentLibraryComponentPagesContainer.ColumnDefinitions.Clear();
}
private string GetLocalizedComponentDisplayName(DesktopComponentRuntimeDescriptor descriptor)
private string GetLocalizedComponentDisplayName(ComponentLibraryComponentEntry component)
{
return string.IsNullOrWhiteSpace(descriptor.DisplayNameLocalizationKey)
? descriptor.Definition.DisplayName
: L(descriptor.DisplayNameLocalizationKey, descriptor.Definition.DisplayName);
return string.IsNullOrWhiteSpace(component.DisplayNameLocalizationKey)
? component.DisplayName
: L(component.DisplayNameLocalizationKey, component.DisplayName);
}
private void OnComponentLibraryComponentPreviewPointerPressed(object? sender, PointerPressedEventArgs e)

View File

@@ -2,10 +2,12 @@ using System;
using System.Globalization;
using System.IO;
using System.Linq;
using Avalonia;
using Avalonia.Controls;
using Avalonia.Interactivity;
using Avalonia.Media;
using Avalonia.Media.Imaging;
using Avalonia.Styling;
using Avalonia.Threading;
using FluentAvalonia.UI.Controls;
using LanMountainDesktop.Models;
@@ -276,6 +278,13 @@ public partial class MainWindow
private void ApplyNightModeState(bool enabled, bool refreshPalettes)
{
_isNightMode = enabled;
var requestedThemeVariant = enabled ? ThemeVariant.Dark : ThemeVariant.Light;
RequestedThemeVariant = requestedThemeVariant;
if (Application.Current is not null)
{
Application.Current.RequestedThemeVariant = requestedThemeVariant;
}
if (!refreshPalettes)
{
return;
@@ -335,13 +344,44 @@ public partial class MainWindow
_suppressSettingsPersistence = true;
try
{
InitializeLocalization(snapshot.LanguageCode);
if (string.IsNullOrWhiteSpace(snapshot.TimeZoneId))
{
_timeZoneService.CurrentTimeZone = TimeZoneInfo.Local;
}
else
{
_timeZoneService.SetTimeZoneById(snapshot.TimeZoneId);
}
_targetShortSideCells = Math.Clamp(
snapshot.GridShortSideCells > 0 ? snapshot.GridShortSideCells : CalculateDefaultShortSideCellCountFromDpi(),
MinShortSideCells,
MaxShortSideCells);
_gridSpacingPreset = _gridSettingsService.NormalizeSpacingPreset(snapshot.GridSpacingPreset);
_desktopEdgeInsetPercent = Math.Clamp(snapshot.DesktopEdgeInsetPercent, MinEdgeInsetPercent, MaxEdgeInsetPercent);
_statusBarSpacingMode = NormalizeStatusBarSpacingMode(snapshot.StatusBarSpacingMode);
_statusBarCustomSpacingPercent = Math.Clamp(snapshot.StatusBarCustomSpacingPercent, 0, 30);
ApplyTaskbarSettings(snapshot);
InitializeWeatherSettings(snapshot);
InitializeAutoStartWithWindowsSetting(snapshot);
InitializeAppRenderModeSetting(snapshot);
InitializeUpdateSettings(snapshot);
InitializeDesktopSurfaceState(layoutSnapshot);
InitializeLauncherVisibilitySettings(launcherSnapshot);
InitializeDesktopComponentPlacements(layoutSnapshot);
TryRestoreWallpaper(snapshot.WallpaperPath);
if (TryParseColor(snapshot.ThemeColor, out var savedThemeColor))
{
_selectedThemeColor = savedThemeColor;
}
_isNightMode = snapshot.IsNightMode ?? (CalculateCurrentBackgroundLuminance() < LightBackgroundLuminanceThreshold);
ApplyNightModeState(_isNightMode, refreshPalettes: true);
ApplyWallpaperBrush();
UpdateWallpaperDisplay();
InitializeTimeZoneSettings();
ApplyLocalization();
RebuildDesktopGrid();
}
finally

View File

@@ -285,9 +285,9 @@
Grid.Column="2"
Background="Transparent"
BorderThickness="0">
<Grid ColumnDefinitions="Auto,Auto"
<Grid ColumnDefinitions="Auto,Auto,Auto"
ColumnSpacing="8">
<Button x:Name="OpenComponentLibraryButton"
<Button x:Name="OpenSettingsButton"
Grid.Column="0"
IsVisible="False"
Padding="8"
@@ -296,6 +296,31 @@
Background="Transparent"
BorderThickness="0"
Foreground="{DynamicResource AdaptiveTextPrimaryBrush}"
Click="OnOpenSettingsClick"
ToolTip.Tip="Settings">
<StackPanel Orientation="Horizontal"
HorizontalAlignment="Center"
VerticalAlignment="Center"
Spacing="8">
<fi:FluentIcon x:Name="OpenSettingsIcon"
Icon="Settings"
IconVariant="Regular" />
<TextBlock x:Name="OpenSettingsButtonTextBlock"
IsVisible="False"
Foreground="{DynamicResource AdaptiveTextPrimaryBrush}"
Text="Settings" />
</StackPanel>
</Button>
<Button x:Name="OpenComponentLibraryButton"
Grid.Column="1"
IsVisible="False"
Padding="8"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch"
Background="Transparent"
BorderThickness="0"
Foreground="{DynamicResource AdaptiveTextPrimaryBrush}"
Click="OnOpenComponentLibraryClick"
ToolTip.Tip="&#32452;&#20214;&#24211;">
<StackPanel Orientation="Horizontal"

View File

@@ -17,6 +17,7 @@ using Avalonia.Platform;
using Avalonia.Platform.Storage;
using Avalonia.Styling;
using Avalonia.Threading;
using Avalonia.VisualTree;
using FluentAvalonia.Styling;
using LanMountainDesktop.ComponentSystem;
using LanMountainDesktop.Models;
@@ -29,7 +30,7 @@ using LibVLCSharp.Shared;
namespace LanMountainDesktop.Views;
public partial class MainWindow : Window
public partial class MainWindow : Window, ISettingsWindowAnchorProvider
{
private enum WallpaperPlacement
{
@@ -84,7 +85,7 @@ public partial class MainWindow : Window
private readonly ISettingsService _settingsService;
private readonly IComponentLayoutStore _componentLayoutStore = ComponentDomainStorageProvider.Instance;
private readonly IComponentStateStore _componentStateStore = ComponentDomainStorageProvider.Instance;
private readonly IComponentInstanceSettingsStore _componentSettingsStore = new ComponentSettingsService();
private readonly IComponentInstanceSettingsStore _componentSettingsStore = HostComponentSettingsStoreProvider.GetOrCreate();
private readonly LocalizationService _localizationService = new();
private readonly TimeZoneService _timeZoneService;
private readonly WindowsStartupService _windowsStartupService = new();
@@ -94,7 +95,8 @@ public partial class MainWindow : Window
private readonly ComponentRegistry _componentRegistry;
private readonly DesktopComponentRuntimeRegistry _componentRuntimeRegistry;
private readonly IComponentLibraryService _componentLibraryService;
private readonly IComponentLibraryWindowService _componentLibraryWindowService = new ComponentLibraryWindowService();
private readonly IEmbeddedComponentLibraryService _componentLibraryWindowService = new EmbeddedComponentLibraryService();
private ComponentLibraryWindow? _detachedComponentLibraryWindow;
private readonly FluentAvaloniaTheme? _fluentAvaloniaTheme;
private readonly HashSet<string> _topStatusComponentIds = new(StringComparer.OrdinalIgnoreCase);
private readonly HashSet<TaskbarActionId> _pinnedTaskbarActions = [];
@@ -190,13 +192,19 @@ public partial class MainWindow : Window
InitializeComponent();
_componentRuntimeRegistry = DesktopComponentRegistryFactory.CreateRuntimeRegistry(
_componentRegistry,
pluginRuntimeService);
pluginRuntimeService,
_settingsFacade);
_componentLibraryService = new ComponentLibraryService(_componentRegistry, _componentRuntimeRegistry);
_fluentAvaloniaTheme = Application.Current?.Styles.OfType<FluentAvaloniaTheme>().FirstOrDefault();
_settingsService.Changed += OnSettingsChanged;
PropertyChanged += OnWindowPropertyChanged;
InitializeDesktopSurfaceSwipeHandlers();
InitializeDesktopComponentDragHandlers();
if (Application.Current is App app && app.SettingsWindowService is { } settingsWindowService)
{
settingsWindowService.StateChanged += OnSettingsWindowStateChanged;
_isSettingsOpen = settingsWindowService.IsOpen;
}
}
private void OnNightModeIsCheckedChanged(object? sender, RoutedEventArgs e)
@@ -234,6 +242,7 @@ public partial class MainWindow : Window
protected override void OnOpened(EventArgs e)
{
base.OnOpened(e);
SyncSettingsWindowState();
_suppressSettingsPersistence = true;
var snapshot = _settingsService.LoadSnapshot<AppSettingsSnapshot>(SettingsScope.App);
@@ -295,6 +304,13 @@ public partial class MainWindow : Window
protected override void OnClosed(EventArgs e)
{
PersistSettings();
if (_detachedComponentLibraryWindow is not null)
{
_detachedComponentLibraryWindow.AddComponentRequested -= OnDetachedComponentLibraryAddComponentRequested;
_detachedComponentLibraryWindow.Closed -= OnDetachedComponentLibraryClosed;
_detachedComponentLibraryWindow.Close();
}
_detachedComponentLibraryWindow = null;
StopVideoWallpaper();
DisposeLauncherResources();
_videoWallpaperMedia?.Dispose();
@@ -316,6 +332,10 @@ public partial class MainWindow : Window
_settingsService.Changed -= OnSettingsChanged;
PropertyChanged -= OnWindowPropertyChanged;
DesktopHost.SizeChanged -= OnDesktopHostSizeChanged;
if (Application.Current is App app && app.SettingsWindowService is { } settingsWindowService)
{
settingsWindowService.StateChanged -= OnSettingsWindowStateChanged;
}
base.OnClosed(e);
}
@@ -985,6 +1005,17 @@ public partial class MainWindow : Window
BackToWindowsIcon.FontSize = taskbarIconSize;
BackToWindowsTextBlock.FontSize = taskbarTextSize;
SetButtonContentSpacing(BackToWindowsButton, buttonContentSpacing);
OpenSettingsButton.Margin = new Thickness(0);
OpenSettingsButton.Padding = taskbarButtonPadding;
OpenSettingsButton.FontSize = taskbarTextSize;
OpenSettingsButton.MinHeight = taskbarCellHeight;
OpenSettingsButton.MinWidth = OpenSettingsButtonTextBlock.IsVisible
? Math.Clamp(taskbarCellHeight * 2.35, 100, 340)
: Math.Clamp(taskbarCellHeight * 1.10, 48, 88);
OpenSettingsIcon.FontSize = taskbarIconSize;
OpenSettingsButtonTextBlock.FontSize = taskbarTextSize;
SetButtonContentSpacing(OpenSettingsButton, buttonContentSpacing);
OpenComponentLibraryButton.Margin = new Thickness(0);
OpenComponentLibraryButton.Padding = taskbarButtonPadding;

View File

@@ -0,0 +1,80 @@
<UserControl xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:vm="using:LanMountainDesktop.ViewModels"
xmlns:controls="using:LanMountainDesktop.Controls"
xmlns:ui="using:FluentAvalonia.UI.Controls"
x:Class="LanMountainDesktop.Views.SettingsPages.AboutSettingsPage"
x:DataType="vm:AboutSettingsPageViewModel">
<ScrollViewer VerticalScrollBarVisibility="Auto">
<StackPanel Classes="settings-page-container">
<TextBlock Classes="settings-section-title"
Text="{Binding PageTitle}" />
<TextBlock Classes="settings-section-description"
Text="{Binding PageDescription}" />
<controls:SettingsSectionCard IconKey="Info"
Title="{Binding AppInfoHeader}">
<controls:SettingsSectionCard.CardContent>
<StackPanel Spacing="14">
<StackPanel Classes="settings-item">
<TextBlock Classes="settings-item-label"
Text="{Binding VersionLabel}" />
<TextBlock Opacity="0.82"
Text="{Binding VersionText}" />
</StackPanel>
<StackPanel Classes="settings-item">
<TextBlock Classes="settings-item-label"
Text="{Binding RenderBackendLabel}" />
<TextBlock Opacity="0.82"
Text="{Binding RenderBackendText}" />
</StackPanel>
</StackPanel>
</controls:SettingsSectionCard.CardContent>
</controls:SettingsSectionCard>
<ui:SettingsExpander Classes="settings-expander-card"
Header="{Binding UpdateHeader}"
IsExpanded="True">
<StackPanel Spacing="12">
<controls:SettingsOptionCard IconKey="ArrowSync"
Title="{Binding AutoCheckUpdatesLabel}">
<controls:SettingsOptionCard.ActionContent>
<ToggleSwitch IsChecked="{Binding AutoCheckUpdates}" />
</controls:SettingsOptionCard.ActionContent>
</controls:SettingsOptionCard>
<controls:SettingsOptionCard IconKey="ArrowSync"
Title="{Binding IncludePrereleaseUpdatesLabel}">
<controls:SettingsOptionCard.ActionContent>
<ToggleSwitch IsChecked="{Binding IncludePrereleaseUpdates}" />
</controls:SettingsOptionCard.ActionContent>
</controls:SettingsOptionCard>
<controls:SettingsOptionCard IconKey="ArrowSync"
Title="{Binding UpdateChannelLabel}">
<controls:SettingsOptionCard.DetailsContent>
<ComboBox ItemsSource="{Binding UpdateChannels}"
SelectedItem="{Binding SelectedUpdateChannel}">
<ComboBox.ItemTemplate>
<DataTemplate x:DataType="vm:SelectionOption">
<TextBlock Text="{Binding Label}" />
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
</controls:SettingsOptionCard.DetailsContent>
</controls:SettingsOptionCard>
<StackPanel Orientation="Horizontal"
Spacing="12">
<Button Command="{Binding CheckForUpdatesCommand}"
Content="{Binding CheckForUpdatesButtonText}" />
<TextBlock VerticalAlignment="Center"
Opacity="0.76"
Text="{Binding UpdateStatus}" />
</StackPanel>
</StackPanel>
</ui:SettingsExpander>
</StackPanel>
</ScrollViewer>
</UserControl>

View File

@@ -0,0 +1,30 @@
using LanMountainDesktop.PluginSdk;
using LanMountainDesktop.Services.Settings;
using LanMountainDesktop.ViewModels;
namespace LanMountainDesktop.Views.SettingsPages;
[SettingsPageInfo(
"about",
"About",
SettingsPageCategory.About,
IconKey = "Info",
SortOrder = 40,
TitleLocalizationKey = "settings.about.title",
DescriptionLocalizationKey = "settings.about.description")]
public partial class AboutSettingsPage : SettingsPageBase
{
public AboutSettingsPage()
: this(new AboutSettingsPageViewModel(HostSettingsFacadeProvider.GetOrCreate()))
{
}
public AboutSettingsPage(AboutSettingsPageViewModel viewModel)
{
ViewModel = viewModel;
DataContext = ViewModel;
InitializeComponent();
}
public AboutSettingsPageViewModel ViewModel { get; }
}

View File

@@ -0,0 +1,97 @@
<UserControl xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:vm="using:LanMountainDesktop.ViewModels"
xmlns:controls="using:LanMountainDesktop.Controls"
x:Class="LanMountainDesktop.Views.SettingsPages.AppearanceSettingsPage"
x:DataType="vm:AppearanceSettingsPageViewModel">
<ScrollViewer VerticalScrollBarVisibility="Auto">
<StackPanel Classes="settings-page-container">
<TextBlock Classes="settings-section-title"
Text="{Binding PageTitle}" />
<TextBlock Classes="settings-section-description"
Text="{Binding PageDescription}" />
<controls:SettingsOptionCard IconKey="DesignIdeas"
Title="{Binding NightModeLabel}">
<controls:SettingsOptionCard.ActionContent>
<ToggleSwitch IsChecked="{Binding IsNightMode}" />
</controls:SettingsOptionCard.ActionContent>
</controls:SettingsOptionCard>
<controls:SettingsOptionCard IconKey="DesignIdeas"
Title="{Binding UseSystemChromeLabel}">
<controls:SettingsOptionCard.ActionContent>
<ToggleSwitch IsChecked="{Binding UseSystemChrome}" />
</controls:SettingsOptionCard.ActionContent>
</controls:SettingsOptionCard>
<controls:SettingsOptionCard IconKey="DesignIdeas"
Title="{Binding ThemeColorLabel}">
<controls:SettingsOptionCard.DetailsContent>
<TextBox Watermark="#AABBCC"
Text="{Binding ThemeColor}" />
</controls:SettingsOptionCard.DetailsContent>
</controls:SettingsOptionCard>
<controls:SettingsSectionCard IconKey="DesignIdeas"
Title="{Binding WallpaperHeader}">
<controls:SettingsSectionCard.CardContent>
<StackPanel Spacing="14">
<StackPanel Classes="settings-item">
<TextBlock Classes="settings-item-label"
Text="{Binding WallpaperPathLabel}" />
<TextBox IsReadOnly="True"
Text="{Binding WallpaperPath}" />
</StackPanel>
<StackPanel Classes="settings-item">
<TextBlock Classes="settings-item-label"
Text="{Binding WallpaperPlacementLabel}" />
<ComboBox ItemsSource="{Binding WallpaperPlacements}"
SelectedItem="{Binding SelectedWallpaperPlacement}">
<ComboBox.ItemTemplate>
<DataTemplate x:DataType="vm:SelectionOption">
<TextBlock Text="{Binding Label}" />
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
</StackPanel>
<Button HorizontalAlignment="Left"
Click="OnBrowseWallpaperClick"
Content="{Binding ImportWallpaperButtonText}" />
</StackPanel>
</controls:SettingsSectionCard.CardContent>
</controls:SettingsSectionCard>
<controls:SettingsSectionCard IconKey="DesignIdeas"
Title="{Binding ClockHeader}"
Description="{Binding ClockDescription}">
<controls:SettingsSectionCard.CardContent>
<StackPanel Spacing="14">
<controls:SettingsOptionCard IconKey="DesignIdeas"
Title="{Binding ClockHeader}">
<controls:SettingsOptionCard.ActionContent>
<ToggleSwitch IsChecked="{Binding ShowClock}" />
</controls:SettingsOptionCard.ActionContent>
</controls:SettingsOptionCard>
<controls:SettingsOptionCard IconKey="DesignIdeas"
Title="{Binding ClockFormatLabel}">
<controls:SettingsOptionCard.DetailsContent>
<ComboBox ItemsSource="{Binding ClockFormats}"
SelectedItem="{Binding SelectedClockFormat}">
<ComboBox.ItemTemplate>
<DataTemplate x:DataType="vm:SelectionOption">
<TextBlock Text="{Binding Label}" />
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
</controls:SettingsOptionCard.DetailsContent>
</controls:SettingsOptionCard>
</StackPanel>
</controls:SettingsSectionCard.CardContent>
</controls:SettingsSectionCard>
</StackPanel>
</ScrollViewer>
</UserControl>

View File

@@ -0,0 +1,54 @@
using Avalonia.Controls;
using Avalonia.Platform.Storage;
using LanMountainDesktop.PluginSdk;
using LanMountainDesktop.Services.Settings;
using LanMountainDesktop.ViewModels;
using System.Linq;
namespace LanMountainDesktop.Views.SettingsPages;
[SettingsPageInfo(
"appearance",
"Appearance",
SettingsPageCategory.Appearance,
IconKey = "DesignIdeas",
SortOrder = 10,
TitleLocalizationKey = "settings.appearance.title",
DescriptionLocalizationKey = "settings.appearance.description")]
public partial class AppearanceSettingsPage : SettingsPageBase
{
public AppearanceSettingsPage()
: this(new AppearanceSettingsPageViewModel(HostSettingsFacadeProvider.GetOrCreate()))
{
}
public AppearanceSettingsPage(AppearanceSettingsPageViewModel viewModel)
{
ViewModel = viewModel;
DataContext = ViewModel;
InitializeComponent();
}
public AppearanceSettingsPageViewModel ViewModel { get; }
private async void OnBrowseWallpaperClick(object? sender, Avalonia.Interactivity.RoutedEventArgs e)
{
if (TopLevel.GetTopLevel(this)?.StorageProvider is not { } storageProvider)
{
return;
}
var files = await storageProvider.OpenFilePickerAsync(new FilePickerOpenOptions
{
Title = ViewModel.FilePickerTitle,
AllowMultiple = false
});
var file = files.FirstOrDefault();
var localPath = file?.TryGetLocalPath();
if (!string.IsNullOrWhiteSpace(localPath))
{
await ViewModel.ImportWallpaperAsync(localPath);
}
}
}

View File

@@ -0,0 +1,50 @@
<UserControl xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:vm="using:LanMountainDesktop.ViewModels"
xmlns:ui="using:FluentAvalonia.UI.Controls"
x:Class="LanMountainDesktop.Views.SettingsPages.ComponentsSettingsPage"
x:DataType="vm:ComponentsSettingsPageViewModel">
<ScrollViewer VerticalScrollBarVisibility="Auto">
<StackPanel Classes="settings-page-container">
<TextBlock Classes="settings-section-title"
Text="{Binding PageTitle}" />
<TextBlock Classes="settings-section-description"
Text="{Binding PageDescription}" />
<ui:SettingsExpander Classes="settings-expander-card"
Header="{Binding GridHeader}"
IsExpanded="True">
<StackPanel Spacing="14">
<StackPanel Classes="settings-item">
<TextBlock Classes="settings-item-label"
Text="{Binding ShortSideCellsLabel}" />
<NumericUpDown Minimum="6"
Maximum="96"
Value="{Binding ShortSideCells}" />
</StackPanel>
<StackPanel Classes="settings-item">
<TextBlock Classes="settings-item-label"
Text="{Binding EdgeInsetPercentLabel}" />
<NumericUpDown Minimum="0"
Maximum="30"
Value="{Binding EdgeInsetPercent}" />
</StackPanel>
<StackPanel Classes="settings-item">
<TextBlock Classes="settings-item-label"
Text="{Binding SpacingPresetLabel}" />
<ComboBox ItemsSource="{Binding SpacingPresets}"
SelectedItem="{Binding SelectedSpacingPreset}">
<ComboBox.ItemTemplate>
<DataTemplate x:DataType="vm:SelectionOption">
<TextBlock Text="{Binding Label}" />
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
</StackPanel>
</StackPanel>
</ui:SettingsExpander>
</StackPanel>
</ScrollViewer>
</UserControl>

View File

@@ -0,0 +1,30 @@
using LanMountainDesktop.PluginSdk;
using LanMountainDesktop.Services.Settings;
using LanMountainDesktop.ViewModels;
namespace LanMountainDesktop.Views.SettingsPages;
[SettingsPageInfo(
"components",
"Components",
SettingsPageCategory.Components,
IconKey = "GridDots",
SortOrder = 20,
TitleLocalizationKey = "settings.components.title",
DescriptionLocalizationKey = "settings.components.description")]
public partial class ComponentsSettingsPage : SettingsPageBase
{
public ComponentsSettingsPage()
: this(new ComponentsSettingsPageViewModel(HostSettingsFacadeProvider.GetOrCreate()))
{
}
public ComponentsSettingsPage(ComponentsSettingsPageViewModel viewModel)
{
ViewModel = viewModel;
DataContext = ViewModel;
InitializeComponent();
}
public ComponentsSettingsPageViewModel ViewModel { get; }
}

View File

@@ -0,0 +1,87 @@
<UserControl xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:vm="using:LanMountainDesktop.ViewModels"
xmlns:controls="using:LanMountainDesktop.Controls"
xmlns:ui="using:FluentAvalonia.UI.Controls"
x:Class="LanMountainDesktop.Views.SettingsPages.GeneralSettingsPage"
x:DataType="vm:GeneralSettingsPageViewModel">
<ScrollViewer VerticalScrollBarVisibility="Auto">
<StackPanel Classes="settings-page-container">
<TextBlock Classes="settings-section-title"
Text="{Binding PageTitle}" />
<TextBlock Classes="settings-section-description"
Text="{Binding PageDescription}" />
<controls:SettingsOptionCard IconKey="Settings"
Title="{Binding LanguageHeader}">
<controls:SettingsOptionCard.DetailsContent>
<ComboBox MinWidth="240"
ItemsSource="{Binding Languages}"
SelectedItem="{Binding SelectedLanguage}">
<ComboBox.ItemTemplate>
<DataTemplate x:DataType="vm:SelectionOption">
<TextBlock Text="{Binding Label}" />
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
</controls:SettingsOptionCard.DetailsContent>
</controls:SettingsOptionCard>
<controls:SettingsOptionCard IconKey="Settings"
Title="{Binding TimeZoneHeader}"
Description="{Binding TimeZoneDescription}">
<controls:SettingsOptionCard.DetailsContent>
<ComboBox MinWidth="280"
ItemsSource="{Binding TimeZones}"
SelectedItem="{Binding SelectedTimeZone}">
<ComboBox.ItemTemplate>
<DataTemplate x:DataType="vm:TimeZoneOption">
<TextBlock Text="{Binding Label}" />
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
</controls:SettingsOptionCard.DetailsContent>
</controls:SettingsOptionCard>
<controls:SettingsSectionCard IconKey="Info"
Title="{Binding PreviewHeader}">
<controls:SettingsSectionCard.CardContent>
<Grid ColumnDefinitions="Auto,*"
ColumnSpacing="16"
RowDefinitions="Auto,Auto"
RowSpacing="12">
<TextBlock FontWeight="SemiBold"
Text="{Binding PreviewTimeLabel}" />
<TextBlock Grid.Column="1"
Opacity="0.82"
Text="{Binding PreviewTimeText}" />
<TextBlock Grid.Row="1"
FontWeight="SemiBold"
Text="{Binding PreviewDateLabel}" />
<TextBlock Grid.Row="1"
Grid.Column="1"
Opacity="0.82"
Text="{Binding PreviewDateText}" />
</Grid>
</controls:SettingsSectionCard.CardContent>
</controls:SettingsSectionCard>
<ui:SettingsExpander Classes="settings-expander-card"
Header="{Binding RuntimeHeader}"
Description="{Binding RuntimeDescription}"
IsExpanded="True">
<ui:SettingsExpander.Footer>
<ComboBox MinWidth="240"
ItemsSource="{Binding RenderModes}"
SelectedItem="{Binding SelectedRenderMode}">
<ComboBox.ItemTemplate>
<DataTemplate x:DataType="vm:SelectionOption">
<TextBlock Text="{Binding Label}" />
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
</ui:SettingsExpander.Footer>
</ui:SettingsExpander>
</StackPanel>
</ScrollViewer>
</UserControl>

View File

@@ -0,0 +1,36 @@
using LanMountainDesktop.PluginSdk;
using LanMountainDesktop.Services.Settings;
using LanMountainDesktop.ViewModels;
namespace LanMountainDesktop.Views.SettingsPages;
[SettingsPageInfo(
"general",
"General",
SettingsPageCategory.General,
IconKey = "Settings",
SortOrder = 0,
TitleLocalizationKey = "settings.general.title",
DescriptionLocalizationKey = "settings.general.description")]
public partial class GeneralSettingsPage : SettingsPageBase
{
public GeneralSettingsPage()
: this(new GeneralSettingsPageViewModel(HostSettingsFacadeProvider.GetOrCreate()))
{
}
public GeneralSettingsPage(GeneralSettingsPageViewModel viewModel)
{
ViewModel = viewModel;
ViewModel.RestartRequested += OnRestartRequested;
DataContext = ViewModel;
InitializeComponent();
}
public GeneralSettingsPageViewModel ViewModel { get; }
private void OnRestartRequested()
{
RequestRestart(ViewModel.RenderModeRestartMessage);
}
}

View File

@@ -0,0 +1,18 @@
<UserControl xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:vm="using:LanMountainDesktop.ViewModels"
x:Class="LanMountainDesktop.Views.SettingsPages.GeneratedPluginSettingsPage"
x:DataType="vm:PluginGeneratedSettingsPageViewModel">
<ScrollViewer VerticalScrollBarVisibility="Auto">
<StackPanel Classes="settings-page-container">
<TextBlock Classes="settings-section-title"
Text="{Binding Title}" />
<TextBlock x:Name="DescriptionTextBlock"
Classes="settings-section-description"
Text="{Binding Description}" />
<StackPanel x:Name="DynamicOptionsHost"
Spacing="0" />
</StackPanel>
</ScrollViewer>
</UserControl>

View File

@@ -0,0 +1,226 @@
using System;
using System.Collections.Generic;
using System.Linq;
using Avalonia.Controls;
using LanMountainDesktop.Controls;
using LanMountainDesktop.PluginSdk;
using LanMountainDesktop.Services.Settings;
using LanMountainDesktop.ViewModels;
namespace LanMountainDesktop.Views.SettingsPages;
public partial class GeneratedPluginSettingsPage : SettingsPageBase
{
public GeneratedPluginSettingsPage()
: this(
new PluginGeneratedSettingsPageViewModel(
HostSettingsFacadeProvider.GetOrCreate().Settings,
string.Empty,
new PluginSettingsSectionRegistration("_preview", "preview", []),
new PluginLocalizer(AppContext.BaseDirectory, "en-US")))
{
}
public GeneratedPluginSettingsPage(PluginGeneratedSettingsPageViewModel viewModel)
{
ViewModel = viewModel;
DataContext = ViewModel;
InitializeComponent();
if (DescriptionTextBlock is not null)
{
DescriptionTextBlock.IsVisible = !string.IsNullOrWhiteSpace(ViewModel.Description);
}
BuildDynamicOptions();
}
public PluginGeneratedSettingsPageViewModel ViewModel { get; }
private void BuildDynamicOptions()
{
if (DynamicOptionsHost is null)
{
return;
}
DynamicOptionsHost.Children.Clear();
foreach (var option in ViewModel.Section.Options)
{
DynamicOptionsHost.Children.Add(CreateOptionControl(option));
}
}
private Control CreateOptionControl(SettingsOptionDefinition option)
{
var title = ViewModel.Localizer.GetString(option.TitleLocalizationKey, option.TitleLocalizationKey);
var description = string.IsNullOrWhiteSpace(option.DescriptionLocalizationKey)
? null
: ViewModel.Localizer.GetString(option.DescriptionLocalizationKey, option.DescriptionLocalizationKey);
var card = new SettingsOptionCard
{
IconKey = "Settings",
Title = title,
Description = description
};
switch (option.OptionType)
{
case SettingsOptionType.Toggle:
card.ActionContent = CreateToggle(option);
break;
case SettingsOptionType.Number:
card.DetailsContent = CreateNumber(option);
break;
case SettingsOptionType.Select:
card.DetailsContent = CreateSelect(option);
break;
case SettingsOptionType.Path:
card.DetailsContent = CreateText(option, "Path");
break;
case SettingsOptionType.List:
card.DetailsContent = CreateText(option, "Comma-separated values");
break;
default:
card.DetailsContent = CreateText(option, null);
break;
}
return card;
}
private Control CreateToggle(SettingsOptionDefinition option)
{
var toggleSwitch = new ToggleSwitch
{
IsChecked = ViewModel.SettingsService.GetValue<bool?>(
SettingsScope.Plugin,
option.Key,
ViewModel.PluginId,
sectionId: ViewModel.Section.Id) ?? (option.DefaultValue as bool? ?? false)
};
toggleSwitch.IsCheckedChanged += (_, _) =>
{
ViewModel.SettingsService.SetValue(
SettingsScope.Plugin,
option.Key,
toggleSwitch.IsChecked == true,
ViewModel.PluginId,
sectionId: ViewModel.Section.Id,
changedKeys: [option.Key]);
};
return toggleSwitch;
}
private Control CreateNumber(SettingsOptionDefinition option)
{
var currentValue = ViewModel.SettingsService.GetValue<double?>(
SettingsScope.Plugin,
option.Key,
ViewModel.PluginId,
sectionId: ViewModel.Section.Id);
var numeric = new NumericUpDown
{
Minimum = (decimal)(option.Minimum ?? 0d),
Maximum = (decimal)(option.Maximum ?? 9999d),
Value = (decimal)(currentValue ?? Convert.ToDouble(option.DefaultValue ?? 0d))
};
numeric.ValueChanged += (_, _) =>
{
ViewModel.SettingsService.SetValue(
SettingsScope.Plugin,
option.Key,
(double)(numeric.Value ?? 0m),
ViewModel.PluginId,
sectionId: ViewModel.Section.Id,
changedKeys: [option.Key]);
};
return numeric;
}
private Control CreateSelect(SettingsOptionDefinition option)
{
var choices = option.Choices
.Select(choice => new SelectionOption(
choice.Value,
ViewModel.Localizer.GetString(choice.TitleLocalizationKey, choice.TitleLocalizationKey)))
.ToArray();
var comboBox = new ComboBox
{
ItemsSource = choices
};
var currentValue = ViewModel.SettingsService.GetValue<string>(
SettingsScope.Plugin,
option.Key,
ViewModel.PluginId,
sectionId: ViewModel.Section.Id);
comboBox.SelectedItem = choices.FirstOrDefault(choice =>
string.Equals(choice.Value, currentValue ?? option.DefaultValue?.ToString(), StringComparison.OrdinalIgnoreCase));
comboBox.SelectionChanged += (_, _) =>
{
if (comboBox.SelectedItem is not SelectionOption selected)
{
return;
}
ViewModel.SettingsService.SetValue(
SettingsScope.Plugin,
option.Key,
selected.Value,
ViewModel.PluginId,
sectionId: ViewModel.Section.Id,
changedKeys: [option.Key]);
};
return comboBox;
}
private Control CreateText(SettingsOptionDefinition option, string? watermark)
{
var currentValue = option.OptionType == SettingsOptionType.List
? string.Join(
", ",
ViewModel.SettingsService.GetValue<IReadOnlyList<string>>(
SettingsScope.Plugin,
option.Key,
ViewModel.PluginId,
sectionId: ViewModel.Section.Id) ?? (option.DefaultValue as IReadOnlyList<string> ?? []))
: ViewModel.SettingsService.GetValue<string>(
SettingsScope.Plugin,
option.Key,
ViewModel.PluginId,
sectionId: ViewModel.Section.Id) ?? option.DefaultValue?.ToString() ?? string.Empty;
var textBox = new TextBox
{
Watermark = watermark,
Text = currentValue
};
textBox.LostFocus += (_, _) =>
{
object value = option.OptionType == SettingsOptionType.List
? textBox.Text?
.Split(',', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries)
.ToArray() ?? []
: textBox.Text ?? string.Empty;
ViewModel.SettingsService.SetValue(
SettingsScope.Plugin,
option.Key,
value,
ViewModel.PluginId,
sectionId: ViewModel.Section.Id,
changedKeys: [option.Key]);
};
return textBox;
}
}

View File

@@ -0,0 +1,87 @@
<UserControl xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:vm="using:LanMountainDesktop.ViewModels"
x:Class="LanMountainDesktop.Views.SettingsPages.PluginsSettingsPage"
x:Name="Root"
x:DataType="vm:PluginsSettingsPageViewModel">
<ScrollViewer VerticalScrollBarVisibility="Auto">
<StackPanel Classes="settings-page-container">
<TextBlock Classes="settings-section-title"
Text="{Binding PageTitle}" />
<TextBlock Classes="settings-section-description"
Text="{Binding PageDescription}" />
<StackPanel Orientation="Horizontal"
Spacing="12"
Margin="0,0,0,18">
<Button Command="{Binding RefreshCommand}"
Content="{Binding RefreshButtonText}" />
<TextBlock VerticalAlignment="Center"
Opacity="0.76"
Text="{Binding StatusMessage}" />
</StackPanel>
<TextBlock Classes="settings-subsection-title"
Text="{Binding InstalledHeader}" />
<ItemsControl ItemsSource="{Binding InstalledPlugins}">
<ItemsControl.ItemTemplate>
<DataTemplate x:DataType="vm:InstalledPluginItemViewModel">
<Border Classes="settings-list-item">
<Grid Classes="settings-list-actions"
ColumnDefinitions="*,Auto,Auto">
<StackPanel>
<TextBlock FontWeight="SemiBold"
Text="{Binding Name}" />
<TextBlock Opacity="0.76"
FontSize="12"
Text="{Binding Description}" />
<TextBlock Opacity="0.58"
FontSize="11"
Text="{Binding Version}" />
</StackPanel>
<ToggleSwitch Grid.Column="1"
IsChecked="{Binding IsEnabled}"
Command="{Binding #Root.DataContext.TogglePluginCommand}"
CommandParameter="{Binding}" />
<Button Grid.Column="2"
Command="{Binding #Root.DataContext.DeletePluginCommand}"
CommandParameter="{Binding}"
Content="{Binding #Root.DataContext.DeleteButtonText}" />
</Grid>
</Border>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
<TextBlock Classes="settings-subsection-title"
Text="{Binding MarketplaceHeader}" />
<ItemsControl ItemsSource="{Binding MarketPlugins}">
<ItemsControl.ItemTemplate>
<DataTemplate x:DataType="vm:PluginMarketItemViewModel">
<Border Classes="settings-list-item">
<Grid Classes="settings-list-actions"
ColumnDefinitions="*,Auto">
<StackPanel>
<TextBlock FontWeight="SemiBold"
Text="{Binding Name}" />
<TextBlock Opacity="0.76"
FontSize="12"
Text="{Binding Description}" />
<TextBlock Opacity="0.58"
FontSize="11"
Text="{Binding Version}" />
</StackPanel>
<Button Grid.Column="1"
Command="{Binding #Root.DataContext.InstallPluginCommand}"
CommandParameter="{Binding}"
Content="{Binding #Root.DataContext.InstallButtonText}" />
</Grid>
</Border>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</StackPanel>
</ScrollViewer>
</UserControl>

View File

@@ -0,0 +1,41 @@
using LanMountainDesktop.PluginSdk;
using LanMountainDesktop.Services.Settings;
using LanMountainDesktop.ViewModels;
namespace LanMountainDesktop.Views.SettingsPages;
[SettingsPageInfo(
"plugins",
"Plugins",
SettingsPageCategory.Plugins,
IconKey = "PuzzlePiece",
SortOrder = 30,
TitleLocalizationKey = "settings.plugins.title",
DescriptionLocalizationKey = "settings.plugins.description")]
public partial class PluginsSettingsPage : SettingsPageBase
{
public PluginsSettingsPage()
: this(new PluginsSettingsPageViewModel(HostSettingsFacadeProvider.GetOrCreate()))
{
}
public PluginsSettingsPage(PluginsSettingsPageViewModel viewModel)
{
ViewModel = viewModel;
ViewModel.RestartRequested += OnRestartRequested;
DataContext = ViewModel;
InitializeComponent();
}
public PluginsSettingsPageViewModel ViewModel { get; }
public override async void OnNavigatedTo(object? parameter)
{
await ViewModel.InitializeAsync();
}
private void OnRestartRequested()
{
RequestRestart(ViewModel.RestartRequiredMessage);
}
}

View File

@@ -0,0 +1,165 @@
<Window xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:vm="using:LanMountainDesktop.ViewModels"
xmlns:ui="using:FluentAvalonia.UI.Controls"
xmlns:fi="using:FluentIcons.Avalonia"
x:Class="LanMountainDesktop.Views.SettingsWindow"
x:DataType="vm:SettingsWindowViewModel"
Width="1120"
Height="760"
MinWidth="920"
MinHeight="620"
CanResize="True"
WindowStartupLocation="Manual"
SystemDecorations="BorderOnly"
FontFamily="{DynamicResource AppFontFamily}"
Background="{DynamicResource AdaptiveSurfaceBaseBrush}"
Icon="/Assets/avalonia-logo.ico"
Title="{Binding Title}">
<Grid Background="{DynamicResource AdaptiveSurfaceBaseBrush}"
RowDefinitions="Auto,Auto,*">
<Border x:Name="WindowTitleBarHost"
Height="48"
Padding="12,0,12,0"
Background="{DynamicResource AdaptiveSurfaceBaseBrush}"
BorderBrush="{DynamicResource AdaptiveGlassPanelBorderBrush}"
BorderThickness="0,0,0,1"
PointerPressed="OnWindowTitleBarPointerPressed">
<Grid ColumnDefinitions="Auto,Auto,*,Auto,Auto"
ColumnSpacing="8"
VerticalAlignment="Center">
<Button x:Name="TogglePaneButton"
Width="40"
Height="32"
Padding="0"
Background="Transparent"
BorderThickness="0"
Click="OnTogglePaneButtonClick">
<fi:FluentIcon x:Name="TogglePaneButtonIcon"
Icon="PanelLeftExpand"
IconVariant="Regular" />
</Button>
<fi:FluentIcon x:Name="WindowBrandIcon"
Grid.Column="1"
Margin="6,0,2,0"
Icon="Settings"
IconVariant="Filled"
IsHitTestVisible="False"
VerticalAlignment="Center" />
<StackPanel Grid.Column="2"
Orientation="Horizontal"
VerticalAlignment="Center"
Spacing="10">
<TextBlock x:Name="WindowTitleTextBlock"
FontSize="12"
FontWeight="SemiBold"
IsHitTestVisible="False"
Text="{Binding Title}" />
</StackPanel>
<Button x:Name="RestartNowButton"
Grid.Column="3"
Padding="10,6"
Margin="0,0,4,0"
Background="Transparent"
IsVisible="{Binding IsRestartRequested}"
Click="OnRestartNowClick">
<StackPanel Orientation="Horizontal"
Spacing="6">
<fi:FluentIcon x:Name="RestartButtonIcon"
Icon="ArrowSync"
IconVariant="Regular" />
<TextBlock x:Name="RestartButtonTextBlock"
Text="{Binding RestartButtonText}" />
</StackPanel>
</Button>
<Button x:Name="CloseWindowButton"
Grid.Column="4"
Width="40"
Height="32"
Padding="0"
Background="Transparent"
BorderThickness="0"
Click="OnCloseWindowClick">
<fi:FluentIcon x:Name="CloseWindowButtonIcon"
Icon="Dismiss"
IconVariant="Regular" />
</Button>
</Grid>
</Border>
<ui:InfoBar x:Name="RestartInfoBar"
Grid.Row="1"
IsOpen="{Binding IsRestartRequested}"
Margin="16,8,16,0"
Severity="Informational"
IsClosable="False"
Title="{Binding RestartTitle}"
Message="{Binding RestartMessage}">
<ui:InfoBar.ActionButton>
<Button Click="OnRestartNowClick"
Content="{Binding RestartButtonText}" />
</ui:InfoBar.ActionButton>
</ui:InfoBar>
<ui:NavigationView x:Name="RootNavigationView"
Grid.Row="2"
Margin="0,8,0,0"
Background="Transparent"
PaneDisplayMode="Auto"
OpenPaneLength="283"
IsSettingsVisible="False"
IsPaneToggleButtonVisible="False"
IsBackButtonVisible="False"
SelectionChanged="OnNavigationSelectionChanged">
<ui:NavigationView.Resources>
<SolidColorBrush x:Key="NavigationViewContentBackground" Color="Transparent" />
<SolidColorBrush x:Key="NavigationViewContentGridBorderBrush" Color="Transparent" />
</ui:NavigationView.Resources>
<Grid ColumnDefinitions="*,Auto"
ColumnSpacing="20"
Margin="12,0,16,16">
<ui:Frame x:Name="ContentFrame" />
<Border x:Name="DrawerBorder"
Grid.Column="1"
Width="296"
Background="{DynamicResource AdaptiveSurfaceRaisedBrush}"
BorderBrush="{DynamicResource AdaptiveGlassPanelBorderBrush}"
BorderThickness="1"
CornerRadius="16"
Padding="20"
BoxShadow="0 8 24 #14000000"
IsVisible="{Binding IsDrawerOpen}">
<Grid RowDefinitions="Auto,*"
RowSpacing="16">
<Grid ColumnDefinitions="*,Auto">
<TextBlock x:Name="DrawerTitleTextBlock"
FontSize="16"
FontWeight="SemiBold"
Text="{Binding DrawerTitle}" />
<Button Grid.Column="1"
Width="32"
Height="32"
Padding="0"
Background="Transparent"
BorderThickness="0"
Click="OnCloseDrawerClick">
<fi:FluentIcon Icon="Dismiss"
IconVariant="Regular" />
</Button>
</Grid>
<ContentControl x:Name="DrawerContentHost"
Grid.Row="1" />
</Grid>
</Border>
</Grid>
</ui:NavigationView>
</Grid>
</Window>

View File

@@ -0,0 +1,467 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Avalonia;
using Avalonia.Controls;
using Avalonia.Input;
using Avalonia.Platform;
using FluentAvalonia.UI.Controls;
using LanMountainDesktop.PluginSdk;
using LanMountainDesktop.Services;
using LanMountainDesktop.Services.Settings;
using LanMountainDesktop.ViewModels;
using Symbol = FluentIcons.Common.Symbol;
namespace LanMountainDesktop.Views;
public partial class SettingsWindow : Window, ISettingsPageHostContext
{
private readonly ISettingsPageRegistry _pageRegistry;
private readonly IHostApplicationLifecycle _hostApplicationLifecycle;
private readonly Dictionary<string, Control> _cachedPages = new(StringComparer.OrdinalIgnoreCase);
private readonly bool _useSystemChrome;
public SettingsWindow()
: this(
new SettingsWindowViewModel(),
EmptySettingsPageRegistry.Instance,
new HostApplicationLifecycleService())
{
}
public SettingsWindow(
SettingsWindowViewModel viewModel,
ISettingsPageRegistry pageRegistry,
IHostApplicationLifecycle hostApplicationLifecycle,
bool useSystemChrome = false)
{
_useSystemChrome = useSystemChrome;
ViewModel = viewModel;
_pageRegistry = pageRegistry;
_hostApplicationLifecycle = hostApplicationLifecycle;
DataContext = ViewModel;
InitializeComponent();
ApplyChromeMode(useSystemChrome);
Opened += OnOpened;
SizeChanged += OnWindowSizeChanged;
Closed += OnClosed;
Loaded += OnLoaded;
PendingRestartStateService.StateChanged += OnPendingRestartStateChanged;
}
public SettingsWindowViewModel ViewModel { get; }
private void OnLoaded(object? sender, Avalonia.Interactivity.RoutedEventArgs e)
{
SyncPendingRestartState();
SyncTitleText();
UpdateChromeMetrics();
UpdatePaneToggleIcon();
}
public void ReloadPages(string? pageId)
{
ViewModel.Pages.Clear();
foreach (var page in _pageRegistry.GetPages().Where(page => !page.HideDefault))
{
ViewModel.Pages.Add(page);
}
_cachedPages.Clear();
CloseDrawer();
RebuildNavigationItems();
NavigateTo(pageId ?? ViewModel.Pages.FirstOrDefault()?.PageId);
}
public void OpenDrawer(Control content, string? title = null)
{
if (DrawerContentHost is null)
{
return;
}
DrawerContentHost.Content = content;
ViewModel.DrawerTitle = title ?? ViewModel.DrawerFallbackTitle;
ViewModel.IsDrawerOpen = true;
SyncTitleText();
}
public void CloseDrawer()
{
if (DrawerContentHost is not null)
{
DrawerContentHost.Content = null;
}
ViewModel.IsDrawerOpen = false;
ViewModel.DrawerTitle = null;
SyncTitleText();
}
public void RequestRestart(string? reason = null)
{
ViewModel.RestartMessage = string.IsNullOrWhiteSpace(reason)
? ViewModel.GetDefaultRestartMessage()
: reason;
ViewModel.IsRestartRequested = true;
}
public void ApplyChromeMode(bool useSystemChrome)
{
if (useSystemChrome || OperatingSystem.IsMacOS())
{
ExtendClientAreaToDecorationsHint = true;
ExtendClientAreaChromeHints = ExtendClientAreaChromeHints.PreferSystemChrome;
ExtendClientAreaTitleBarHeightHint = -1;
SystemDecorations = SystemDecorations.Full;
return;
}
SystemDecorations = SystemDecorations.BorderOnly;
ExtendClientAreaToDecorationsHint = true;
ExtendClientAreaChromeHints = ExtendClientAreaChromeHints.NoChrome;
ExtendClientAreaTitleBarHeightHint = 48;
}
public void RefreshShellText()
{
SyncPendingRestartState();
SyncTitleText();
}
private void RebuildNavigationItems()
{
if (RootNavigationView is null)
{
return;
}
RootNavigationView.MenuItems.Clear();
SettingsPageCategory? previousCategory = null;
foreach (var page in ViewModel.Pages)
{
if (previousCategory is not null && previousCategory != page.Category)
{
RootNavigationView.MenuItems.Add(new NavigationViewItemSeparator());
}
RootNavigationView.MenuItems.Add(new NavigationViewItem
{
Content = page.Title,
Tag = page.PageId,
IconSource = new FluentIcons.Avalonia.Fluent.SymbolIconSource
{
Symbol = MapIcon(page.IconKey),
IconVariant = FluentIcons.Common.IconVariant.Regular
}
});
previousCategory = page.Category;
}
}
private void OnNavigationSelectionChanged(object? sender, NavigationViewSelectionChangedEventArgs e)
{
var selectedItem = e.SelectedItemContainer ?? e.SelectedItem as NavigationViewItem;
NavigateTo(selectedItem?.Tag as string);
}
private void NavigateTo(string? pageId)
{
var descriptor = ResolveDescriptor(pageId);
if (descriptor is null)
{
return;
}
var page = GetOrCreatePage(descriptor);
if (page is SettingsPageBase settingsPage)
{
settingsPage.InitializeHostContext(this);
settingsPage.NavigationUri = new Uri($"lmd://settings/{descriptor.PageId}", UriKind.Absolute);
settingsPage.OnNavigatedTo(null);
}
if (ContentFrame is not null)
{
ContentFrame.Content = page;
}
ViewModel.CurrentPageTitle = descriptor.Title;
ViewModel.CurrentPageDescription = descriptor.Description;
ViewModel.CurrentPageId = descriptor.PageId;
TrySelectNavigationItem(descriptor.PageId);
SyncTitleText();
}
private SettingsPageDescriptor? ResolveDescriptor(string? pageId)
{
if (!string.IsNullOrWhiteSpace(pageId) &&
_pageRegistry.TryGetPage(pageId, out var descriptor) &&
descriptor is not null)
{
return descriptor;
}
return ViewModel.Pages.FirstOrDefault();
}
private Control GetOrCreatePage(SettingsPageDescriptor descriptor)
{
if (_cachedPages.TryGetValue(descriptor.PageId, out var page))
{
return page;
}
page = descriptor.CreatePage(this);
if (page is SettingsPageBase settingsPage)
{
settingsPage.InitializeHostContext(this);
}
_cachedPages[descriptor.PageId] = page;
return page;
}
private void TrySelectNavigationItem(string pageId)
{
if (RootNavigationView is null)
{
return;
}
foreach (var item in RootNavigationView.MenuItems.OfType<NavigationViewItem>())
{
if (string.Equals(item.Tag as string, pageId, StringComparison.OrdinalIgnoreCase))
{
RootNavigationView.SelectedItem = item;
return;
}
}
}
private async void OnRestartNowClick(object? sender, Avalonia.Interactivity.RoutedEventArgs e)
{
_ = sender;
_ = e;
_hostApplicationLifecycle.TryRestart(new HostApplicationLifecycleRequest(
Source: "SettingsWindow",
Reason: "User accepted restart from settings window."));
await Task.CompletedTask;
}
private void OnCloseDrawerClick(object? sender, Avalonia.Interactivity.RoutedEventArgs e)
{
_ = sender;
_ = e;
CloseDrawer();
}
private void OnPendingRestartStateChanged()
{
SyncPendingRestartState();
}
private void SyncPendingRestartState()
{
if (!PendingRestartStateService.HasPendingRestart && !ViewModel.IsRestartRequested)
{
return;
}
if (PendingRestartStateService.HasPendingRestart && string.IsNullOrWhiteSpace(ViewModel.RestartMessage))
{
ViewModel.RestartMessage = ViewModel.GetDefaultRestartMessage();
}
ViewModel.IsRestartRequested = ViewModel.IsRestartRequested || PendingRestartStateService.HasPendingRestart;
}
private void OnOpened(object? sender, EventArgs e)
{
_ = sender;
_ = e;
UpdateChromeMetrics();
}
private void OnWindowSizeChanged(object? sender, SizeChangedEventArgs e)
{
_ = sender;
_ = e;
UpdateChromeMetrics();
}
private void OnClosed(object? sender, EventArgs e)
{
_cachedPages.Clear();
PendingRestartStateService.StateChanged -= OnPendingRestartStateChanged;
Opened -= OnOpened;
SizeChanged -= OnWindowSizeChanged;
}
private void OnWindowTitleBarPointerPressed(object? sender, PointerPressedEventArgs e)
{
_ = sender;
if (e.GetCurrentPoint(this).Properties.IsLeftButtonPressed)
{
BeginMoveDrag(e);
}
}
private void OnTogglePaneButtonClick(object? sender, Avalonia.Interactivity.RoutedEventArgs e)
{
_ = sender;
_ = e;
if (RootNavigationView is null)
{
return;
}
RootNavigationView.IsPaneOpen = !RootNavigationView.IsPaneOpen;
UpdatePaneToggleIcon();
}
private void OnCloseWindowClick(object? sender, Avalonia.Interactivity.RoutedEventArgs e)
{
_ = sender;
_ = e;
Close();
}
private void UpdatePaneToggleIcon()
{
if (TogglePaneButtonIcon is null || RootNavigationView is null)
{
return;
}
TogglePaneButtonIcon.Icon = RootNavigationView.IsPaneOpen
? FluentIcons.Common.Icon.PanelLeftContract
: FluentIcons.Common.Icon.PanelLeftExpand;
}
private void UpdateChromeMetrics()
{
if (_useSystemChrome)
{
if (WindowTitleBarHost is { })
{
WindowTitleBarHost.IsVisible = false;
}
return;
}
if (WindowTitleBarHost is null ||
TogglePaneButton is null ||
TogglePaneButtonIcon is null ||
WindowBrandIcon is null ||
WindowTitleTextBlock is null ||
RestartNowButton is null ||
RestartButtonIcon is null ||
RestartButtonTextBlock is null ||
CloseWindowButton is null ||
CloseWindowButtonIcon is null ||
DrawerTitleTextBlock is null ||
RootNavigationView is null)
{
return;
}
var width = Bounds.Width > 1 ? Bounds.Width : Math.Max(Width, MinWidth);
var height = Bounds.Height > 1 ? Bounds.Height : Math.Max(Height, MinHeight);
var layoutScale = Math.Clamp(Math.Min(width / 1120d, height / 760d), 0.90, 1.18);
var titleBarHeight = Math.Clamp(48d * layoutScale, 44d, 58d);
var titleBarButtonWidth = Math.Clamp(40d * layoutScale, 36d, 48d);
var titleBarButtonHeight = Math.Clamp(32d * layoutScale, 30d, 38d);
var titleFontSize = Math.Clamp(12d * layoutScale, 11d, 14d);
var titleBarIconSize = Math.Clamp(16d * layoutScale, 15d, 20d);
var drawerTitleFontSize = Math.Clamp(16d * layoutScale, 14d, 20d);
var chromePadding = Math.Clamp(12d * layoutScale, 10d, 18d);
var restartSpacing = Math.Clamp(6d * layoutScale, 6d, 10d);
ExtendClientAreaTitleBarHeightHint = titleBarHeight;
WindowTitleBarHost.Height = titleBarHeight;
WindowTitleBarHost.Padding = new Thickness(chromePadding, 0, chromePadding, 0);
TogglePaneButton.Width = titleBarButtonWidth;
TogglePaneButton.Height = titleBarButtonHeight;
TogglePaneButtonIcon.FontSize = titleBarIconSize;
WindowBrandIcon.FontSize = titleBarIconSize + 2;
WindowTitleTextBlock.FontSize = titleFontSize;
RestartNowButton.Padding = new Thickness(chromePadding * 0.9, Math.Max(6, chromePadding * 0.5));
if (RestartNowButton.Content is StackPanel restartStack)
{
restartStack.Spacing = restartSpacing;
}
RestartButtonIcon.FontSize = titleBarIconSize;
RestartButtonTextBlock.FontSize = titleFontSize;
CloseWindowButton.Width = titleBarButtonWidth;
CloseWindowButton.Height = titleBarButtonHeight;
CloseWindowButtonIcon.FontSize = titleBarIconSize;
DrawerTitleTextBlock.FontSize = drawerTitleFontSize;
RootNavigationView.OpenPaneLength = Math.Clamp(283d * layoutScale, 248d, 320d);
}
private void SyncTitleText()
{
Title = ViewModel.Title;
if (_useSystemChrome)
{
return;
}
if (WindowTitleTextBlock is null ||
DrawerTitleTextBlock is null)
{
return;
}
WindowTitleTextBlock.Text = ViewModel.Title;
DrawerTitleTextBlock.IsVisible = !string.IsNullOrWhiteSpace(ViewModel.DrawerTitle);
}
private sealed class EmptySettingsPageRegistry : ISettingsPageRegistry
{
public static EmptySettingsPageRegistry Instance { get; } = new();
public void Rebuild()
{
}
public IReadOnlyList<SettingsPageDescriptor> GetPages()
{
return Array.Empty<SettingsPageDescriptor>();
}
public bool TryGetPage(string pageId, out SettingsPageDescriptor? descriptor)
{
descriptor = null;
return false;
}
}
private static Symbol MapIcon(string iconKey)
{
return iconKey?.Trim() switch
{
"DesignIdeas" => Symbol.Color,
"GridDots" => Symbol.GridDots,
"PuzzlePiece" => Symbol.PuzzlePiece,
"Info" => Symbol.Info,
"ArrowSync" => Symbol.ArrowSync,
_ => Symbol.Settings
};
}
}