diff --git a/Actions/AdvancedShutdownAction.cs b/Actions/AdvancedShutdownAction.cs index 1a3efca..bf016d7 100644 --- a/Actions/AdvancedShutdownAction.cs +++ b/Actions/AdvancedShutdownAction.cs @@ -35,6 +35,12 @@ public class AdvancedShutdownAction(ILogger logger) : Ac private static bool _allowMainDialogClose; private static bool _allowFloatingWindowClose; + public static void CancelPlanOnAppStopping() + { + StopCountdownProcess(); + TryAbortSystemShutdown(); + } + protected override async Task OnInvoke() { _logger.LogDebug("AdvancedShutdownAction OnInvoke 开始"); @@ -180,6 +186,26 @@ private static void StopCountdownProcess() } } + private static void TryAbortSystemShutdown() + { + try + { + var psi = new ProcessStartInfo + { + FileName = "shutdown", + Arguments = "/a", + UseShellExecute = false, + CreateNoWindow = true, + WindowStyle = ProcessWindowStyle.Hidden + }; + using var process = Process.Start(psi); + process?.WaitForExit(1000); + } + catch + { + } + } + private static int GetRemainingSeconds() { lock (StateLock) @@ -255,6 +281,7 @@ private void StopWatchdog() private void StopAllStates() { StopCountdownProcess(); + TryAbortSystemShutdown(); lock (StateLock) { diff --git a/Actions/LoadTemporaryClassPlanAction.cs b/Actions/LoadTemporaryClassPlanAction.cs index 473f9cb..d5fc783 100644 --- a/Actions/LoadTemporaryClassPlanAction.cs +++ b/Actions/LoadTemporaryClassPlanAction.cs @@ -3,6 +3,7 @@ using ClassIsland.Core.Attributes; using Microsoft.Extensions.Logging; using System; +using System.Collections.Concurrent; using System.Threading.Tasks; using SystemTools.Settings; @@ -18,6 +19,8 @@ public class LoadTemporaryClassPlanAction( private readonly IProfileService _profileService = profileService; private readonly IExactTimeService _exactTimeService = exactTimeService; + private static readonly ConcurrentDictionary PreviousSnapshots = new(); + protected override async Task OnInvoke() { if (!Guid.TryParse(Settings.ClassPlanId, out var classPlanId)) @@ -32,6 +35,13 @@ protected override async Task OnInvoke() return; } + if (IsRevertable) + { + PreviousSnapshots[ActionSet.Guid] = new TempClassPlanSnapshot( + _profileService.Profile.TempClassPlanId, + _profileService.Profile.TempClassPlanSetupTime); + } + _profileService.Profile.TempClassPlanId = classPlanId; _profileService.Profile.TempClassPlanSetupTime = _exactTimeService.GetCurrentLocalDateTime(); _profileService.SaveProfile(); @@ -39,4 +49,24 @@ protected override async Task OnInvoke() await base.OnInvoke(); } + + protected override async Task OnRevert() + { + await base.OnRevert(); + + if (PreviousSnapshots.TryRemove(ActionSet.Guid, out var snapshot)) + { + _profileService.Profile.TempClassPlanId = snapshot.TempClassPlanId; + _profileService.Profile.TempClassPlanSetupTime = snapshot.TempClassPlanSetupTime; + _profileService.SaveProfile(); + _logger.LogInformation("已恢复临时课表为触发前状态。ActionSet={ActionSetGuid}", ActionSet.Guid); + return; + } + + _profileService.Profile.TempClassPlanId = null; + _profileService.SaveProfile(); + _logger.LogInformation("未找到触发前状态,已清除临时课表。ActionSet={ActionSetGuid}", ActionSet.Guid); + } + + private readonly record struct TempClassPlanSnapshot(Guid? TempClassPlanId, DateTime TempClassPlanSetupTime); } diff --git a/ConfigHandlers/MainConfigData.cs b/ConfigHandlers/MainConfigData.cs index 3d2e454..8981084 100644 --- a/ConfigHandlers/MainConfigData.cs +++ b/ConfigHandlers/MainConfigData.cs @@ -86,6 +86,20 @@ public bool EnableFaceRecognition RestartPropertyChanged?.Invoke(this, EventArgs.Empty); } } + + bool _autoMatchMainBackgroundTheme; + + [JsonPropertyName("autoMatchMainBackgroundTheme")] + public bool AutoMatchMainBackgroundTheme + { + get => _autoMatchMainBackgroundTheme; + set + { + if (value == _autoMatchMainBackgroundTheme) return; + _autoMatchMainBackgroundTheme = value; + OnPropertyChanged(); + } + } // ========== 公告相关 ========== /*string _lastAcceptedAnnouncement = string.Empty; diff --git a/Controls/AdvancedShutdownSettingsControl.cs b/Controls/AdvancedShutdownSettingsControl.cs index 7104d24..36dadf9 100644 --- a/Controls/AdvancedShutdownSettingsControl.cs +++ b/Controls/AdvancedShutdownSettingsControl.cs @@ -12,13 +12,6 @@ public AdvancedShutdownSettingsControl() { var panel = new StackPanel { Spacing = 10, Margin = new(10) }; - panel.Children.Add(new TextBlock - { - Text = "高级计时关机设置", - FontWeight = Avalonia.Media.FontWeight.Bold, - FontSize = 14 - }); - var minutesPanel = new StackPanel { Orientation = Avalonia.Layout.Orientation.Horizontal, @@ -45,7 +38,7 @@ public AdvancedShutdownSettingsControl() panel.Children.Add(new TextBlock { - Text = "触发后会弹出独立对话框,可已阅、取消计划或延长时间。", + Text = "拥有独立对话框,可已阅、取消计划、延长时间或立即关机。", TextWrapping = Avalonia.Media.TextWrapping.Wrap, Foreground = Avalonia.Media.Brushes.Gray }); diff --git a/Controls/Components/BetterCarouselContainerComponent.axaml b/Controls/Components/BetterCarouselContainerComponent.axaml index 5910a32..fc74665 100644 --- a/Controls/Components/BetterCarouselContainerComponent.axaml +++ b/Controls/Components/BetterCarouselContainerComponent.axaml @@ -40,24 +40,30 @@ - - - - + + + - + + Margin="0" /> + - - \ No newline at end of file + diff --git a/Controls/Components/BetterCarouselContainerComponent.axaml.cs b/Controls/Components/BetterCarouselContainerComponent.axaml.cs index d1319c6..c9559fb 100644 --- a/Controls/Components/BetterCarouselContainerComponent.axaml.cs +++ b/Controls/Components/BetterCarouselContainerComponent.axaml.cs @@ -76,6 +76,8 @@ public int SelectedIndex public double CurrentProgressPercent { get; private set; } public int AnimationStyleValue => (int)Settings.AnimationStyle; + public bool ShowTopProgressBar => Settings.ShowProgressBar && Settings.ProgressBarPosition == BetterCarouselProgressBarPosition.Top; + public bool ShowBottomProgressBar => Settings.ShowProgressBar && Settings.ProgressBarPosition == BetterCarouselProgressBarPosition.Bottom; public new event PropertyChangedEventHandler? PropertyChanged; @@ -323,15 +325,15 @@ private void OnSettingsPropertyChanged(object? sender, PropertyChangedEventArgs OnPropertyChanged(nameof(AnimationStyleValue)); } - if (e.PropertyName is nameof(Settings.ShowProgressBar)) + if (e.PropertyName is nameof(Settings.ShowProgressBar) or nameof(Settings.ProgressBarPosition)) { - PseudoClasses.Set(":progress-visible", Settings.ShowProgressBar); + OnPropertyChanged(nameof(ShowTopProgressBar)); + OnPropertyChanged(nameof(ShowBottomProgressBar)); } if (e.PropertyName is nameof(Settings.RotationMode) or nameof(Settings.IsAnimationEnabled) or nameof(Settings.ShowProgressBar)) { UpdateProgressState(resetWhenIdle: true); - return; } if (e.PropertyName == nameof(Settings.ComponentDisplayDurations)) @@ -339,6 +341,7 @@ private void OnSettingsPropertyChanged(object? sender, PropertyChangedEventArgs Settings.NormalizeDisplayDurations(); RestartProgress(); } + } private void OnChildrenCollectionChanged(object? sender, NotifyCollectionChangedEventArgs e) @@ -416,8 +419,6 @@ private void EnsureSelectedIndexValid() private void UpdateProgressState(bool resetWhenIdle = false) { - PseudoClasses.Set(":progress-visible", Settings.ShowProgressBar); - var displayable = GetDisplayableIndexes(); if (displayable.Length == 0) { @@ -551,4 +552,4 @@ protected virtual void OnPropertyChanged(string propertyName) { PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); } -} \ No newline at end of file +} diff --git a/Controls/Components/BetterCarouselContainerSettingsControl.axaml b/Controls/Components/BetterCarouselContainerSettingsControl.axaml index 4581203..2a9f754 100644 --- a/Controls/Components/BetterCarouselContainerSettingsControl.axaml +++ b/Controls/Components/BetterCarouselContainerSettingsControl.axaml @@ -66,7 +66,27 @@ Description="显示当前组件剩余显示时长的进度条" IconSource="{ci:FluentIconSource }"> - + + + + + + + + + + + + + + + + @@ -113,4 +133,4 @@ - \ No newline at end of file + diff --git a/Controls/Components/BetterCarouselContainerSettingsControl.axaml.cs b/Controls/Components/BetterCarouselContainerSettingsControl.axaml.cs index 5737298..910c17a 100644 --- a/Controls/Components/BetterCarouselContainerSettingsControl.axaml.cs +++ b/Controls/Components/BetterCarouselContainerSettingsControl.axaml.cs @@ -57,6 +57,7 @@ public partial class BetterCarouselContainerSettingsControl : ComponentBase DurationItems.Count > 0; @@ -141,4 +142,4 @@ protected virtual void OnPropertyChanged(string propertyName) { PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); } -} \ No newline at end of file +} diff --git a/Controls/Components/LocalQuoteComponent.axaml.cs b/Controls/Components/LocalQuoteComponent.axaml.cs index 4156c75..c148483 100644 --- a/Controls/Components/LocalQuoteComponent.axaml.cs +++ b/Controls/Components/LocalQuoteComponent.axaml.cs @@ -164,7 +164,7 @@ private void OnCarouselTicked(object? sender, EventArgs e) private void RefreshTimerInterval() { - var interval = Math.Max(1, Settings.CarouselIntervalSeconds); + var interval = Math.Clamp(Settings.CarouselIntervalSeconds, 1, 8000); _carouselTimer.Interval = TimeSpan.FromSeconds(interval); } diff --git a/Controls/Components/LocalQuoteSettingsControl.axaml b/Controls/Components/LocalQuoteSettingsControl.axaml index 17ce7e2..a4c23ee 100644 --- a/Controls/Components/LocalQuoteSettingsControl.axaml +++ b/Controls/Components/LocalQuoteSettingsControl.axaml @@ -46,7 +46,7 @@ IconSource="{ci:FluentIconSource }"> diff --git a/Controls/InTimePeriodRuleSettingsControl.cs b/Controls/InTimePeriodRuleSettingsControl.cs new file mode 100644 index 0000000..99f563f --- /dev/null +++ b/Controls/InTimePeriodRuleSettingsControl.cs @@ -0,0 +1,91 @@ +using Avalonia.Controls; +using Avalonia.Layout; +using ClassIsland.Core.Abstractions.Controls; +using System; +using SystemTools.Rules; + +namespace SystemTools.Controls; + + +public class InTimePeriodRuleSettingsControl : RuleSettingsControlBase +{ + private readonly TimePicker _startTimePicker; + private readonly TimePicker _endTimePicker; + + public InTimePeriodRuleSettingsControl() + { + var panel = new StackPanel { Spacing = 10 }; + var row = new Grid + { + ColumnDefinitions = new ColumnDefinitions("Auto,*,Auto,*,Auto"), + ColumnSpacing = 8, + VerticalAlignment = VerticalAlignment.Center + }; + + row.Children.Add(new TextBlock { Text = "从", VerticalAlignment = VerticalAlignment.Center }); + + _startTimePicker = new TimePicker + { + ClockIdentifier = "24HourClock", + HorizontalAlignment = HorizontalAlignment.Center, + VerticalAlignment = VerticalAlignment.Center + }; + Grid.SetColumn(_startTimePicker, 1); + row.Children.Add(_startTimePicker); + + var sep = new TextBlock { Text = "至", VerticalAlignment = VerticalAlignment.Center }; + Grid.SetColumn(sep, 2); + row.Children.Add(sep); + + _endTimePicker = new TimePicker + { + ClockIdentifier = "24HourClock", + HorizontalAlignment = HorizontalAlignment.Center, + VerticalAlignment = VerticalAlignment.Center + }; + Grid.SetColumn(_endTimePicker, 3); + row.Children.Add(_endTimePicker); + + var hint = new TextBlock + { + Text = "提示:若起始晚于结束 将按跨天处理", + TextWrapping = Avalonia.Media.TextWrapping.Wrap, + Foreground = Avalonia.Media.Brushes.Gray, + FontSize = 12 + }; + panel.Children.Add(row); + panel.Children.Add(hint); + Content = panel; + + _startTimePicker.SelectedTimeChanged += (s, e) => SyncSettings(); + _endTimePicker.SelectedTimeChanged += (s, e) => SyncSettings(); + } + + protected override void OnInitialized() + { + base.OnInitialized(); + + if (TimeSpan.TryParse(Settings.StartTime, out var start)) + { + _startTimePicker.SelectedTime = start; + } + + if (TimeSpan.TryParse(Settings.EndTime, out var end)) + { + _endTimePicker.SelectedTime = end; + } + } + + private void SyncSettings() + { + if (_startTimePicker.SelectedTime.HasValue) + { + Settings.StartTime = _startTimePicker.SelectedTime.Value.ToString(@"hh\:mm\:ss"); + } + + if (_endTimePicker.SelectedTime.HasValue) + { + Settings.EndTime = _endTimePicker.SelectedTime.Value.ToString(@"hh\:mm\:ss"); + } + } +} diff --git a/Controls/KillProcessSettingsControl.cs b/Controls/KillProcessSettingsControl.cs index e461e3e..888b3c3 100644 --- a/Controls/KillProcessSettingsControl.cs +++ b/Controls/KillProcessSettingsControl.cs @@ -29,7 +29,6 @@ public KillProcessSettingsControl() panel.Children.Add(new TextBlock { Text = "进程名:", - FontWeight = FontWeight.Bold, Margin = new(0, 5, 0, 0) }); diff --git a/Controls/LoadTemporaryClassPlanSettingsControl.cs b/Controls/LoadTemporaryClassPlanSettingsControl.cs index 437bc17..72db858 100644 --- a/Controls/LoadTemporaryClassPlanSettingsControl.cs +++ b/Controls/LoadTemporaryClassPlanSettingsControl.cs @@ -27,7 +27,6 @@ public LoadTemporaryClassPlanSettingsControl() panel.Children.Add(new TextBlock { Text = "选择要加载的临时课表:", - FontWeight = Avalonia.Media.FontWeight.Bold }); _comboBox = new ComboBox @@ -45,7 +44,7 @@ public LoadTemporaryClassPlanSettingsControl() panel.Children.Add(new TextBlock { - Text = "下拉菜单会实时读取 ClassIsland 当前档案中的课表名称(不含临时层课表)。", + Text = "该行动支持恢复。", TextWrapping = Avalonia.Media.TextWrapping.Wrap, Foreground = Avalonia.Media.Brushes.Gray }); diff --git a/Controls/UsingClassPlanRuleSettingsControl.cs b/Controls/UsingClassPlanRuleSettingsControl.cs index 538f2b0..011e1ec 100644 --- a/Controls/UsingClassPlanRuleSettingsControl.cs +++ b/Controls/UsingClassPlanRuleSettingsControl.cs @@ -21,9 +21,8 @@ public class UsingClassPlanRuleSettingsControl : RuleSettingsControlBase(); - var panel = new StackPanel { Spacing = 10, Margin = new(10) }; - panel.Children.Add(new TextBlock { Text = "选择课表:", FontWeight = Avalonia.Media.FontWeight.Bold }); - + var panel = new StackPanel { Spacing = 10 }; + _comboBox = new ComboBox { HorizontalAlignment = HorizontalAlignment.Stretch }; _comboBox.SelectionChanged += (_, _) => { diff --git a/Controls/UsingTimeLayoutRuleSettingsControl.cs b/Controls/UsingTimeLayoutRuleSettingsControl.cs index 1709200..9635db5 100644 --- a/Controls/UsingTimeLayoutRuleSettingsControl.cs +++ b/Controls/UsingTimeLayoutRuleSettingsControl.cs @@ -21,8 +21,7 @@ public class UsingTimeLayoutRuleSettingsControl : RuleSettingsControlBase(); - var panel = new StackPanel { Spacing = 10, Margin = new(10) }; - panel.Children.Add(new TextBlock { Text = "选择时间表:", FontWeight = Avalonia.Media.FontWeight.Bold }); + var panel = new StackPanel { Spacing = 10 }; _comboBox = new ComboBox { HorizontalAlignment = HorizontalAlignment.Stretch }; _comboBox.SelectionChanged += (_, _) => diff --git a/Models/ComponentSettings/BetterCarouselContainerSettings.cs b/Models/ComponentSettings/BetterCarouselContainerSettings.cs index f18a52d..0e05ef5 100644 --- a/Models/ComponentSettings/BetterCarouselContainerSettings.cs +++ b/Models/ComponentSettings/BetterCarouselContainerSettings.cs @@ -29,6 +29,15 @@ public enum BetterCarouselAnimationStyle Flash = 1 } +public enum BetterCarouselProgressBarPosition +{ + [Description("置于底部")] + Bottom = 0, + + [Description("置于顶部")] + Top = 1 +} + public partial class BetterCarouselContainerSettings : ObservableObject, IComponentContainerSettings { public const double DefaultDisplayDurationSeconds = 15; @@ -50,6 +59,12 @@ public partial class BetterCarouselContainerSettings : ObservableObject, ICompon [ObservableProperty] private bool _showProgressBar = true; + [ObservableProperty] + private bool _showSideSeparators = false; + + [ObservableProperty] + private BetterCarouselProgressBarPosition _progressBarPosition = BetterCarouselProgressBarPosition.Bottom; + public ObservableCollection Children { get => _children; diff --git a/Plugin.cs b/Plugin.cs index e88ec7a..8f88328 100644 --- a/Plugin.cs +++ b/Plugin.cs @@ -62,6 +62,7 @@ public override void Initialize(HostBuilderContext context, IServiceCollection s services.AddLogging(); services.AddSingleton(GlobalConstants.MainConfig); services.AddSingleton(); + services.AddSingleton(); // ========== 注册可选人脸识别 ========== if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) @@ -82,6 +83,7 @@ public override void Initialize(HostBuilderContext context, IServiceCollection s // ========== 注册设置页面 ========== services.AddSettingsPage(); + services.AddSettingsPage(); if (GlobalConstants.MainConfig.Data.EnableFloatingWindowFeature) { services.AddSettingsPage(); @@ -106,6 +108,7 @@ public override void Initialize(HostBuilderContext context, IServiceCollection s { IAppHost.GetService().Start(); } + IAppHost.GetService().Start(); _logger = IAppHost.GetService>(); _logger?.LogInformation("[SystemTools]实验性功能状态: {Status}", experimentalEnabled); @@ -293,8 +296,6 @@ private void RegisterBaseTriggers(IServiceCollection services) } } - - private void RegisterBaseRules(IServiceCollection services) { var config = GlobalConstants.MainConfig!.Data; @@ -308,13 +309,19 @@ private void RegisterBaseRules(IServiceCollection services) if (config.IsRuleEnabled("SystemTools.UsingClassPlanRule")) { services.AddRule( - "SystemTools.UsingClassPlanRule", "正在使用某课程表", "\uE82D", HandleUsingClassPlanRule); + "SystemTools.UsingClassPlanRule", "正在使用某课程表", "\uE6B1", HandleUsingClassPlanRule); } if (config.IsRuleEnabled("SystemTools.UsingTimeLayoutRule")) { services.AddRule( - "SystemTools.UsingTimeLayoutRule", "正在使用某时间表", "\uE823", HandleUsingTimeLayoutRule); + "SystemTools.UsingTimeLayoutRule", "正在使用某时间表", "\uE69D", HandleUsingTimeLayoutRule); + } + + if (config.IsRuleEnabled("SystemTools.InTimePeriodRule")) + { + services.AddRule( + "SystemTools.InTimePeriodRule", "是否在某时间段", "\uE4CA", HandleInTimePeriodRule); } } @@ -560,6 +567,24 @@ private static bool HandleUsingTimeLayoutRule(object? settings) return timeLayout.IsActivated; } + private static bool HandleInTimePeriodRule(object? settings) + { + if (settings is not InTimePeriodRuleSettings ruleSettings || + !TimeSpan.TryParse(ruleSettings.StartTime, out var start) || + !TimeSpan.TryParse(ruleSettings.EndTime, out var end)) + { + return false; + } + + var current = IAppHost.TryGetService()?.GetCurrentLocalDateTime().TimeOfDay ?? DateTime.Now.TimeOfDay; + if (start <= end) + { + return current >= start && current <= end; + } + + return current >= start || current <= end; + } + private void BuildSimulationMenu(MainConfigData config) { var items = new List(); @@ -775,6 +800,8 @@ private void RegisterSettingsPageGroup(IServiceCollection services) private void OnAppStopping(object? sender, EventArgs e) { + IAppHost.GetService().Stop(); + AdvancedShutdownAction.CancelPlanOnAppStopping(); if (GlobalConstants.MainConfig?.Data.EnableFloatingWindowFeature == true) { IAppHost.GetService().Stop(); diff --git a/Rules/InTimePeriodRuleSettings.cs b/Rules/InTimePeriodRuleSettings.cs new file mode 100644 index 0000000..947d4e3 --- /dev/null +++ b/Rules/InTimePeriodRuleSettings.cs @@ -0,0 +1,12 @@ +using System.Text.Json.Serialization; + +namespace SystemTools.Rules; + +public class InTimePeriodRuleSettings +{ + [JsonPropertyName("startTime")] + public string StartTime { get; set; } = "08:00:00"; + + [JsonPropertyName("endTime")] + public string EndTime { get; set; } = "18:00:00"; +} diff --git a/Services/AdaptiveThemeSyncService.cs b/Services/AdaptiveThemeSyncService.cs new file mode 100644 index 0000000..b7baa6b --- /dev/null +++ b/Services/AdaptiveThemeSyncService.cs @@ -0,0 +1,123 @@ +using Avalonia.Threading; +using ClassIsland.Core; +using ClassIsland.Core.Abstractions.Services; +using Microsoft.Extensions.Logging; +using System; +using System.Diagnostics; +using System.Drawing; +using System.Runtime.InteropServices; +using ClassIsland.Shared; +using SystemTools.Shared; + +namespace SystemTools.Services; + +public class AdaptiveThemeSyncService(ILogger logger) +{ + private readonly ILogger _logger = logger; + private readonly DispatcherTimer _timer = new() { Interval = TimeSpan.FromSeconds(2) }; + private int? _lastAppliedTheme; + + public void Start() + { + _timer.Tick -= OnTick; + _timer.Tick += OnTick; + _timer.Start(); + } + + public void Stop() + { + _timer.Stop(); + } + + public void RefreshNow() + { + OnTick(this, EventArgs.Empty); + } + + private void OnTick(object? sender, EventArgs e) + { + if (GlobalConstants.MainConfig?.Data.AutoMatchMainBackgroundTheme != true) + { + return; + } + + if (!RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) + { + return; + } + + try + { + var targetTheme = DetectThemeByMainWindowBackground(); + if (targetTheme == null || targetTheme == _lastAppliedTheme) + { + return; + } + + var themeService = IAppHost.TryGetService(); + if (themeService == null) + { + return; + } + + themeService.SetTheme(targetTheme.Value, null); + _lastAppliedTheme = targetTheme; + _logger.LogDebug("已自动匹配主题为:{Theme}", targetTheme == 2 ? "黑暗" : "明亮"); + } + catch (Exception ex) + { + _logger.LogDebug(ex, "自动匹配主界面背景色失败,将在下次计时重试。"); + } + } + + private static int? DetectThemeByMainWindowBackground() + { + var handle = Process.GetCurrentProcess().MainWindowHandle; + if (handle == IntPtr.Zero) + { + return null; + } + + if (!GetWindowRect(handle, out var rect)) + { + return null; + } + + var width = Math.Max(1, rect.Right - rect.Left); + var height = Math.Max(1, rect.Bottom - rect.Top); + + using var bitmap = new Bitmap(width, height); + using var graphics = Graphics.FromImage(bitmap); + graphics.CopyFromScreen(rect.Left, rect.Top, 0, 0, new Size(width, height)); + + var samples = new (int X, int Y)[] + { + (width / 2, height / 2), + (Math.Max(0, width / 4), Math.Max(0, height / 4)), + (Math.Max(0, width * 3 / 4), Math.Max(0, height / 4)), + (Math.Max(0, width / 4), Math.Max(0, height * 3 / 4)), + (Math.Max(0, width * 3 / 4), Math.Max(0, height * 3 / 4)), + }; + + double luminance = 0; + foreach (var sample in samples) + { + var color = bitmap.GetPixel(Math.Clamp(sample.X, 0, width - 1), Math.Clamp(sample.Y, 0, height - 1)); + luminance += 0.299 * color.R + 0.587 * color.G + 0.114 * color.B; + } + luminance /= samples.Length; + + return luminance < 128 ? 2 : 1; // 2=黑暗,1=明亮 + } + + [DllImport("user32.dll")] + private static extern bool GetWindowRect(IntPtr hWnd, out RECT lpRect); + + private struct RECT + { + public int Left; + public int Top; + public int Right; + public int Bottom; + } +} diff --git a/SettingsPage/FloatingWindowEditorSettingsPage.axaml b/SettingsPage/FloatingWindowEditorSettingsPage.axaml index 4a8d03e..d6a4281 100644 --- a/SettingsPage/FloatingWindowEditorSettingsPage.axaml +++ b/SettingsPage/FloatingWindowEditorSettingsPage.axaml @@ -276,6 +276,12 @@ + \ No newline at end of file diff --git a/SettingsPage/MoreFeaturesOptionsSettingsPage.axaml b/SettingsPage/MoreFeaturesOptionsSettingsPage.axaml new file mode 100644 index 0000000..8dc1b25 --- /dev/null +++ b/SettingsPage/MoreFeaturesOptionsSettingsPage.axaml @@ -0,0 +1,20 @@ + + + + + + + + + + + + diff --git a/SettingsPage/MoreFeaturesOptionsSettingsPage.axaml.cs b/SettingsPage/MoreFeaturesOptionsSettingsPage.axaml.cs new file mode 100644 index 0000000..a15fc34 --- /dev/null +++ b/SettingsPage/MoreFeaturesOptionsSettingsPage.axaml.cs @@ -0,0 +1,31 @@ +using Avalonia.Interactivity; +using ClassIsland.Core.Abstractions.Controls; +using ClassIsland.Core.Attributes; +using SystemTools.ConfigHandlers; +using SystemTools.Services; +using SystemTools.Shared; + +namespace SystemTools; + +[SettingsPageInfo("systemtools.settings.more", "更多功能选项…", "\uE28E", "\uE28E", true)] +public partial class MoreFeaturesOptionsSettingsPage : SettingsPageBase +{ + public MainConfigData Config => GlobalConstants.MainConfig!.Data; + + public MoreFeaturesOptionsSettingsPage() + { + InitializeComponent(); + DataContext = this; + } + + private void AutoMatchThemeToggle_OnChanged(object? sender, RoutedEventArgs e) + { + var service = ClassIsland.Shared.IAppHost.GetService(); + if (Config.AutoMatchMainBackgroundTheme) + { + service.RefreshNow(); + } + + GlobalConstants.MainConfig?.Save(); + } +} diff --git a/SettingsPage/SystemToolsSettingsPage.axaml b/SettingsPage/SystemToolsSettingsPage.axaml index f329078..baec749 100644 --- a/SettingsPage/SystemToolsSettingsPage.axaml +++ b/SettingsPage/SystemToolsSettingsPage.axaml @@ -181,6 +181,16 @@ + + +