diff --git a/Actions/ShowFloatingWindowAction.cs b/Actions/ShowFloatingWindowAction.cs index 53fc169..94aceae 100644 --- a/Actions/ShowFloatingWindowAction.cs +++ b/Actions/ShowFloatingWindowAction.cs @@ -35,9 +35,9 @@ protected override async Task OnInvoke() _logger.LogDebug("没有可用的悬浮窗组件,强制隐藏悬浮窗"); } - if (IsRevertable) + if (IsRevertable && config != null) { - PreviousStates[ActionSet.Guid] = GlobalConstants.MainConfig!.Data.ShowFloatingWindow; + PreviousStates[ActionSet.Guid] = config.ShowFloatingWindow; } if (config != null) @@ -70,8 +70,15 @@ protected override async Task OnRevert() return; } - GlobalConstants.MainConfig!.Data.ShowFloatingWindow = previousState; - GlobalConstants.MainConfig.Save(); + var config = GlobalConstants.MainConfig; + if (config == null) + { + _logger.LogWarning("主配置为空,无法恢复悬浮窗状态"); + return; + } + + config.Data.ShowFloatingWindow = previousState; + config.Save(); _floatingWindowService.UpdateWindowState(); _logger.LogInformation("已恢复悬浮窗状态为: {State}", previousState ? "开启" : "关闭"); diff --git a/Actions/SwitchFloatingWindowThemeAction.cs b/Actions/SwitchFloatingWindowThemeAction.cs index f641496..f33b6ae 100644 --- a/Actions/SwitchFloatingWindowThemeAction.cs +++ b/Actions/SwitchFloatingWindowThemeAction.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Concurrent; using System.Threading.Tasks; using ClassIsland.Core.Abstractions.Automation; using ClassIsland.Core.Attributes; @@ -6,6 +7,7 @@ using Microsoft.Extensions.Logging; using SystemTools.Services; using SystemTools.Settings; +using SystemTools.Shared; namespace SystemTools.Actions; @@ -16,6 +18,7 @@ namespace SystemTools.Actions; public class SwitchFloatingWindowThemeAction(ILogger logger) : ActionBase { private readonly ILogger _logger = logger; + private static readonly ConcurrentDictionary PreviousThemes = new(); protected override async Task OnInvoke() { @@ -24,14 +27,25 @@ protected override async Task OnInvoke() try { var service = IAppHost.GetService(); + var config = GlobalConstants.MainConfig?.Data; if (Settings.TargetTheme >= 0) { + if (IsRevertable && config != null) + { + PreviousThemes[ActionSet.Guid] = config.FloatingWindowTheme; + } + service.SetWindowTheme(Settings.TargetTheme); _logger.LogInformation("已设置悬浮窗主题为: {Theme}", GetThemeName(Settings.TargetTheme)); } else { + if (IsRevertable && config != null) + { + PreviousThemes[ActionSet.Guid] = config.FloatingWindowTheme; + } + service.ToggleWindowTheme(); _logger.LogInformation("已切换到下一个悬浮窗主题"); } @@ -46,6 +60,29 @@ protected override async Task OnInvoke() _logger.LogDebug("SwitchFloatingWindowThemeAction OnInvoke 完成"); } + protected override async Task OnRevert() + { + await base.OnRevert(); + + if (!PreviousThemes.TryRemove(ActionSet.Guid, out var previousTheme)) + { + _logger.LogInformation("未找到主题恢复快照,跳过悬浮窗主题恢复。ActionSet={ActionSetGuid}", ActionSet.Guid); + return; + } + + try + { + var service = IAppHost.GetService(); + service.SetWindowTheme(previousTheme); + _logger.LogInformation("已恢复悬浮窗主题为: {Theme}", GetThemeName(previousTheme)); + } + catch (Exception ex) + { + _logger.LogError(ex, "恢复悬浮窗主题失败"); + throw; + } + } + private static string GetThemeName(int theme) { return theme switch diff --git a/Actions/ToggleFloatingWindowLayerAction.cs b/Actions/ToggleFloatingWindowLayerAction.cs index 1382ece..d1edcd3 100644 --- a/Actions/ToggleFloatingWindowLayerAction.cs +++ b/Actions/ToggleFloatingWindowLayerAction.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Concurrent; using System.Threading.Tasks; using ClassIsland.Core.Abstractions.Automation; using ClassIsland.Core.Attributes; @@ -16,6 +17,7 @@ namespace SystemTools.Actions; public class ToggleFloatingWindowLayerAction(ILogger logger) : ActionBase { private readonly ILogger _logger = logger; + private static readonly ConcurrentDictionary PreviousLayers = new(); protected override async Task OnInvoke() { @@ -24,16 +26,27 @@ protected override async Task OnInvoke() try { var service = IAppHost.GetService(); + var profile = service.ProfileManager.CurrentProfile; // 根据设置决定是切换还是设置到指定层级 - // TargetLayer: -1=切换, 0=置顶, 1=置底 + // TargetLayer: -1=切换, 0=置底, 1=置顶 if (Settings.TargetLayer >= 0) { + if (IsRevertable) + { + PreviousLayers[ActionSet.Guid] = profile.FloatingWindowLayer; + } + service.SetWindowLayer(Settings.TargetLayer); - _logger.LogInformation("已设置悬浮窗层级为: {Layer}", Settings.TargetLayer == 0 ? "置顶" : "置底"); + _logger.LogInformation("已设置悬浮窗层级为: {Layer}", Settings.TargetLayer == 0 ? "置底" : "置顶"); } else { + if (IsRevertable) + { + PreviousLayers[ActionSet.Guid] = profile.FloatingWindowLayer; + } + service.ToggleWindowLayer(); _logger.LogInformation("已切换悬浮窗层级状态"); } @@ -47,4 +60,27 @@ protected override async Task OnInvoke() await base.OnInvoke(); _logger.LogDebug("ToggleFloatingWindowLayerAction OnInvoke 完成"); } + + protected override async Task OnRevert() + { + await base.OnRevert(); + + if (!PreviousLayers.TryRemove(ActionSet.Guid, out var previousLayer)) + { + _logger.LogInformation("未找到层级恢复快照,跳过悬浮窗层级恢复。ActionSet={ActionSetGuid}", ActionSet.Guid); + return; + } + + try + { + var service = IAppHost.GetService(); + service.SetWindowLayer(previousLayer); + _logger.LogInformation("已恢复悬浮窗层级为: {Layer}", previousLayer == 0 ? "置底" : "置顶"); + } + catch (Exception ex) + { + _logger.LogError(ex, "恢复悬浮窗层级失败"); + throw; + } + } } diff --git a/Actions/ToggleFloatingWindowProfileAction.cs b/Actions/ToggleFloatingWindowProfileAction.cs index 4f87052..fae43bb 100644 --- a/Actions/ToggleFloatingWindowProfileAction.cs +++ b/Actions/ToggleFloatingWindowProfileAction.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Concurrent; using System.Threading.Tasks; using ClassIsland.Core.Abstractions.Automation; using ClassIsland.Core.Attributes; @@ -16,6 +17,7 @@ namespace SystemTools.Actions; public class ToggleFloatingWindowProfileAction(ILogger logger) : ActionBase { private readonly ILogger _logger = logger; + private static readonly ConcurrentDictionary PreviousProfiles = new(); protected override async Task OnInvoke() { @@ -24,16 +26,27 @@ protected override async Task OnInvoke() try { var service = IAppHost.GetService(); + var currentProfileName = service.ProfileManager.CurrentProfileName; // 根据设置决定是切换到下一个还是切换到指定方案 // TargetProfileName: null=切换到下一个, 其他=指定方案名称 if (!string.IsNullOrWhiteSpace(Settings.TargetProfileName)) { + if (IsRevertable) + { + PreviousProfiles[ActionSet.Guid] = currentProfileName; + } + service.SwitchToProfile(Settings.TargetProfileName); _logger.LogInformation("已切换到悬浮窗配置方案: {Name}", Settings.TargetProfileName); } else { + if (IsRevertable) + { + PreviousProfiles[ActionSet.Guid] = currentProfileName; + } + service.ToggleWindowProfile(); _logger.LogInformation("已切换到下一个悬浮窗配置方案"); } @@ -47,4 +60,27 @@ protected override async Task OnInvoke() await base.OnInvoke(); _logger.LogDebug("ToggleFloatingWindowProfileAction OnInvoke 完成"); } + + protected override async Task OnRevert() + { + await base.OnRevert(); + + if (!PreviousProfiles.TryRemove(ActionSet.Guid, out var previousProfile)) + { + _logger.LogInformation("未找到配置方案恢复快照,跳过悬浮窗配置方案恢复。ActionSet={ActionSetGuid}", ActionSet.Guid); + return; + } + + try + { + var service = IAppHost.GetService(); + service.SwitchToProfile(previousProfile); + _logger.LogInformation("已恢复悬浮窗配置方案为: {Name}", previousProfile); + } + catch (Exception ex) + { + _logger.LogError(ex, "恢复悬浮窗配置方案失败"); + throw; + } + } } diff --git a/Controls/ToggleFloatingWindowLayerSettingsControl.cs b/Controls/ToggleFloatingWindowLayerSettingsControl.cs index c6692fd..d4cd22b 100644 --- a/Controls/ToggleFloatingWindowLayerSettingsControl.cs +++ b/Controls/ToggleFloatingWindowLayerSettingsControl.cs @@ -25,9 +25,9 @@ public ToggleFloatingWindowLayerSettingsControl() { HorizontalAlignment = Avalonia.Layout.HorizontalAlignment.Stretch }; - _layerComboBox.Items.Add(new ComboBoxItem { Content = "切换(置顶↔置底)", Tag = -1 }); - _layerComboBox.Items.Add(new ComboBoxItem { Content = "置顶", Tag = 0 }); - _layerComboBox.Items.Add(new ComboBoxItem { Content = "置底", Tag = 1 }); + _layerComboBox.Items.Add(new ComboBoxItem { Content = "切换(置底↔置顶)", Tag = -1 }); + _layerComboBox.Items.Add(new ComboBoxItem { Content = "置底", Tag = 0 }); + _layerComboBox.Items.Add(new ComboBoxItem { Content = "置顶", Tag = 1 }); _layerComboBox.SelectedIndex = 0; panel.Children.Add(_layerComboBox); diff --git a/Services/FloatingWindowService.cs b/Services/FloatingWindowService.cs index 865e3f0..d26a7c0 100644 --- a/Services/FloatingWindowService.cs +++ b/Services/FloatingWindowService.cs @@ -432,6 +432,8 @@ private void OnRulesetStatusUpdated(object? sender, EventArgs e) CheckFloatingWindowRuleset(); CheckButtonRulesets(); CheckRowRulesets(); + // 规则集变化后,重新评估窗口显隐(避免"所有按钮都被隐藏但窗口还显示") + ApplyVisibility(); } private void CheckFloatingWindowRuleset() @@ -576,7 +578,8 @@ private void ApplyVisibility() } var profile = _profileManager.CurrentProfile; - var shouldShow = _configHandler.Data.ShowFloatingWindow && _entries.Count > 0 && !_rulesetHidingWindow; + var hasVisibleButtons = HasAnyVisibleButton(); + var shouldShow = _configHandler.Data.ShowFloatingWindow && hasVisibleButtons && !_rulesetHidingWindow; if (shouldShow) { @@ -660,16 +663,7 @@ private void RefreshWindowButtons() _stackPanel.HorizontalAlignment = HorizontalAlignment.Center; _stackPanel.Children.Clear(); - - if (_isTouchDeviceDetected) - { - _touchDragHandle = CreateTouchDragHandle(scale, contentForeground); - _stackPanel.Children.Add(_touchDragHandle); - } - else - { - _touchDragHandle = null; - } + _touchDragHandle = null; int rowIndex = 0; foreach (var rowEntries in GetOrderedRows()) @@ -789,6 +783,74 @@ private void RefreshWindowButtons() rowIndex++; } + + // 仅在"至少有一个可见按钮"时才显示拖拽把手,避免孤零零一个把手 + var hasVisibleButtons = _stackPanel.Children.Count > 0; + var showDragHandle = (_isTouchDeviceDetected || _profileManager.CurrentProfile.FloatingWindowDragHandleAlwaysVisible) + && hasVisibleButtons; + + if (showDragHandle) + { + _touchDragHandle = CreateTouchDragHandle(scale, contentForeground); + _stackPanel.Children.Insert(0, _touchDragHandle); + } + } + + /// + /// 判断是否至少有 1 个按钮在"经过规则集过滤后"是可见的。 + /// 用于避免悬浮窗在没有任何可见按钮时(被规则集全部隐藏)仍然显示。 + /// + private bool HasAnyVisibleButton() + { + if (_entries.Count == 0) + { + return false; + } + + var profile = _profileManager.CurrentProfile; + var rowConfigs = profile.FloatingWindowRowRulesets; + var hiddenRowSet = new HashSet(); + + if (rowConfigs != null) + { + for (int i = 0; i < rowConfigs.Count; i++) + { + var cfg = rowConfigs[i]; + var shouldHide = !cfg.IsVisible + || (cfg.HideOnRule && cfg.HidingRules != null + && IAppHost.TryGetService() is { } rs + && rs.IsRulesetSatisfied(cfg.HidingRules)); + if (shouldHide) + { + hiddenRowSet.Add(i); + } + } + } + + int rowIndex = 0; + foreach (var row in profile.FloatingWindowButtonRows ?? []) + { + if (!hiddenRowSet.Contains(rowIndex)) + { + foreach (var id in row) + { + if (_rulesetHiddenButtons.Contains(id)) + { + continue; + } + foreach (var entry in _entries.Values) + { + if (string.Equals(entry.ButtonId, id, StringComparison.Ordinal)) + { + return true; + } + } + } + } + rowIndex++; + } + + return false; } private List> GetOrderedRows() diff --git a/Settings/ToggleFloatingWindowLayerSettings.cs b/Settings/ToggleFloatingWindowLayerSettings.cs index 2dc3579..28f4908 100644 --- a/Settings/ToggleFloatingWindowLayerSettings.cs +++ b/Settings/ToggleFloatingWindowLayerSettings.cs @@ -8,7 +8,7 @@ namespace SystemTools.Settings; public class ToggleFloatingWindowLayerSettings { /// - /// 目标层级。-1 表示切换,0 表示置顶,1 表示置底。 + /// 目标层级。-1 表示切换,0 表示置底,1 表示置顶。 /// [JsonPropertyName("targetLayer")] public int TargetLayer { get; set; } = -1; diff --git a/SettingsPage/FloatingWindowEditorSettingsPage.axaml b/SettingsPage/FloatingWindowEditorSettingsPage.axaml index 075e6f0..8a68795 100644 --- a/SettingsPage/FloatingWindowEditorSettingsPage.axaml +++ b/SettingsPage/FloatingWindowEditorSettingsPage.axaml @@ -17,18 +17,18 @@ - + - + 设置 · 悬浮窗 @@ -58,6 +58,7 @@ OnContent="显示" OffContent="隐藏" IsChecked="{Binding ViewModel.Settings.ShowFloatingWindow, Mode=OneWay}" IsEnabled="{Binding ViewModel.HasFloatingTriggerEntries}" + VerticalAlignment="Center" IsCheckedChanged="OnFloatingWindowVisibleToggleChanged" /> @@ -86,9 +87,35 @@ -