mirror of
https://github.com/wwiinnddyy/LanMountainDesktop.git
synced 2026-06-22 00:54:26 +08:00
settings_re4
This commit is contained in:
129
LanMountainDesktop/Views/ComponentLibraryWindow.axaml
Normal file
129
LanMountainDesktop/Views/ComponentLibraryWindow.axaml
Normal 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>
|
||||
237
LanMountainDesktop/Views/ComponentLibraryWindow.axaml.cs
Normal file
237
LanMountainDesktop/Views/ComponentLibraryWindow.axaml.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
@@ -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 = [];
|
||||
|
||||
@@ -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 = [];
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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();
|
||||
|
||||
|
||||
@@ -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(), " ");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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());
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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 = [];
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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];
|
||||
|
||||
@@ -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 = [];
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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)
|
||||
{
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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];
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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="组件库">
|
||||
<StackPanel Orientation="Horizontal"
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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>
|
||||
@@ -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; }
|
||||
}
|
||||
@@ -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>
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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>
|
||||
@@ -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; }
|
||||
}
|
||||
@@ -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>
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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>
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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>
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
165
LanMountainDesktop/Views/SettingsWindow.axaml
Normal file
165
LanMountainDesktop/Views/SettingsWindow.axaml
Normal 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>
|
||||
467
LanMountainDesktop/Views/SettingsWindow.axaml.cs
Normal file
467
LanMountainDesktop/Views/SettingsWindow.axaml.cs
Normal 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
|
||||
};
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user