From 98355368e2d8a4cc606aaae23e402258db169f47 Mon Sep 17 00:00:00 2001 From: ywydog Date: Sat, 30 May 2026 03:07:23 +0000 Subject: [PATCH 01/80] =?UTF-8?q?feat:=20=E6=82=AC=E6=B5=AE=E7=AA=97?= =?UTF-8?q?=E5=8A=9F=E8=83=BD=E5=A2=9E=E5=BC=BA=E4=B8=8E=E9=85=8D=E7=BD=AE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: traeagent --- Actions/ToggleFloatingWindowLayerAction.cs | 16 +++ ConfigHandlers/ButtonRulesetConfig.cs | 13 ++ ConfigHandlers/MainConfigData.cs | 21 ++++ Plugin.cs | 4 + Services/FloatingWindowService.cs | 119 +++++++++++++++++- .../FloatingWindowEditorSettingsPage.axaml | 64 +++++++++- SettingsPage/SystemToolsSettingsViewModel.cs | 30 +++++ Triggers/FloatingWindowTriggerConfig.cs | 2 + 8 files changed, 264 insertions(+), 5 deletions(-) create mode 100644 Actions/ToggleFloatingWindowLayerAction.cs create mode 100644 ConfigHandlers/ButtonRulesetConfig.cs diff --git a/Actions/ToggleFloatingWindowLayerAction.cs b/Actions/ToggleFloatingWindowLayerAction.cs new file mode 100644 index 0000000..e2db996 --- /dev/null +++ b/Actions/ToggleFloatingWindowLayerAction.cs @@ -0,0 +1,16 @@ +using ClassIsland.Core; +using ClassIsland.Core.Abstractions.Automation; +using ClassIsland.Core.Attributes; +using SystemTools.Services; + +namespace SystemTools.Actions; + +[ActionInfo("SystemTools.ToggleFloatingWindowLayer", "切换悬浮窗置顶/置底", "\uE9A8")] +public class ToggleFloatingWindowLayerAction : ActionBase +{ + public override Task ExecuteAsync(object? settings, CancellationToken cancellationToken = new CancellationToken()) + { + IAppHost.GetService().ToggleWindowLayer(); + return Task.CompletedTask; + } +} diff --git a/ConfigHandlers/ButtonRulesetConfig.cs b/ConfigHandlers/ButtonRulesetConfig.cs new file mode 100644 index 0000000..f3ba01e --- /dev/null +++ b/ConfigHandlers/ButtonRulesetConfig.cs @@ -0,0 +1,13 @@ +using System.Text.Json.Serialization; +using ClassIsland.Core.Models.Ruleset; +using CommunityToolkit.Mvvm.ComponentModel; + +namespace SystemTools.ConfigHandlers; + +public partial class ButtonRulesetConfig : ObservableRecipient +{ + [ObservableProperty] private bool _isVisible = true; + [ObservableProperty] private int _position = -1; + [ObservableProperty] private bool _rulesetEnabled = false; + [JsonPropertyName("ruleset")] public Ruleset Ruleset { get; set; } = new(); +} diff --git a/ConfigHandlers/MainConfigData.cs b/ConfigHandlers/MainConfigData.cs index c0e0eb8..541b94e 100644 --- a/ConfigHandlers/MainConfigData.cs +++ b/ConfigHandlers/MainConfigData.cs @@ -4,6 +4,7 @@ using System.Text.Json.Serialization; using System.Collections.Generic; using CommunityToolkit.Mvvm.ComponentModel; +using ClassIsland.Core.Models.Ruleset; namespace SystemTools.ConfigHandlers; @@ -330,6 +331,26 @@ public int FloatingWindowLayerRecheckMode } } + bool _floatingWindowRulesetEnabled = false; + + [JsonPropertyName("floatingWindowRulesetEnabled")] + public bool FloatingWindowRulesetEnabled + { + get => _floatingWindowRulesetEnabled; + set + { + if (value == _floatingWindowRulesetEnabled) return; + _floatingWindowRulesetEnabled = value; + OnPropertyChanged(); + } + } + + [JsonPropertyName("floatingWindowRuleset")] + public Ruleset FloatingWindowRuleset { get; set; } = new(); + + [JsonPropertyName("floatingWindowButtonRulesets")] + public Dictionary FloatingWindowButtonRulesets { get; set; } = new(); + // 行动功能启用状态(Key: 行动ID, Value: 是否启用) [JsonPropertyName("enabledActions")] public Dictionary EnabledActions { get; set; } = new(); diff --git a/Plugin.cs b/Plugin.cs index e0bd5b5..168dcd8 100644 --- a/Plugin.cs +++ b/Plugin.cs @@ -283,6 +283,8 @@ private void RegisterBaseActions(IServiceCollection services) { RegisterActionIfEnabled(services, config, "SystemTools.ShowFloatingWindow"); + RegisterActionIfEnabled(services, config, + "SystemTools.ToggleFloatingWindowLayer"); } // 其他工具 @@ -821,6 +823,8 @@ private void BuildFloatingWindowMenu(MainConfigData config) if (config.EnableFloatingWindowFeature && config.IsActionEnabled("SystemTools.ShowFloatingWindow")) items.Add(new ActionMenuTreeItem("SystemTools.ShowFloatingWindow", "显示悬浮窗", "\uEA37")); + if (config.EnableFloatingWindowFeature && config.IsActionEnabled("SystemTools.ToggleFloatingWindowLayer")) + items.Add(new ActionMenuTreeItem("SystemTools.ToggleFloatingWindowLayer", "切换悬浮窗置顶/置底", "\uE9A8")); if (items.Count > 0) { diff --git a/Services/FloatingWindowService.cs b/Services/FloatingWindowService.cs index 0f22fa4..e2c4acd 100644 --- a/Services/FloatingWindowService.cs +++ b/Services/FloatingWindowService.cs @@ -7,6 +7,7 @@ using Avalonia.Styling; using Avalonia.Threading; using Avalonia.VisualTree; +using ClassIsland.Core.Abstractions.Services; using ClassIsland.Core.Controls; using System; using System.Collections.Generic; @@ -63,6 +64,7 @@ public class FloatingWindowService private LowLevelMouseProc? _lowLevelMouseProc; private DispatcherTimer LayerRecheck50MsTimer { get; } = new() { Interval = TimeSpan.FromMilliseconds(50) }; private DispatcherTimer LayerRecheck1MsTimer { get; } = new() { Interval = TimeSpan.FromMilliseconds(1) }; + private DispatcherTimer RulesetCheckTimer { get; } = new() { Interval = TimeSpan.FromSeconds(1) }; private delegate void WinEventProc(IntPtr hWinEventHook, uint @event, IntPtr hwnd, int idObject, int idChild, uint idEventThread, uint dwmsEventTime); @@ -102,6 +104,7 @@ public void Start() EnsureLayerRecheckHooks(); EnsureGlobalInputHooks(); SubscribeThemeChanged(); + StartRulesetCheckTimer(); ApplyVisibility(); RefreshLayerRecheckMode(); RecheckWindowLayer(); @@ -122,6 +125,7 @@ public void Stop() LayerRecheck50MsTimer.Stop(); LayerRecheck1MsTimer.Stop(); + RulesetCheckTimer.Stop(); RemoveLayerRecheckHooks(); RemoveGlobalInputHooks(); UnsubscribeThemeChanged(); @@ -321,6 +325,99 @@ private void OnWindowLoaded(object? sender, RoutedEventArgs e) RecheckWindowLayer(); } + private bool _rulesetHidingWindow = false; + private readonly HashSet _rulesetHiddenButtons = new(); + + private void StartRulesetCheckTimer() + { + RulesetCheckTimer.Tick -= OnRulesetCheckTimerTick; + RulesetCheckTimer.Tick += OnRulesetCheckTimerTick; + RulesetCheckTimer.Start(); + } + + private void OnRulesetCheckTimerTick(object? sender, EventArgs e) + { + CheckFloatingWindowRuleset(); + CheckButtonRulesets(); + } + + private void CheckFloatingWindowRuleset() + { + var data = _configHandler.Data; + if (!data.FloatingWindowRulesetEnabled) + { + if (_rulesetHidingWindow) + { + _rulesetHidingWindow = false; + ApplyVisibility(); + } + return; + } + + var rulesetService = IAppHost.TryGetService(); + if (rulesetService == null) + { + return; + } + + var isSatisfied = rulesetService.IsRulesetSatisfied(data.FloatingWindowRuleset); + var shouldHide = !isSatisfied; + + if (shouldHide != _rulesetHidingWindow) + { + _rulesetHidingWindow = shouldHide; + ApplyVisibility(); + } + } + + private void CheckButtonRulesets() + { + var data = _configHandler.Data; + var rulesetService = IAppHost.TryGetService(); + if (rulesetService == null) + { + return; + } + + var changed = false; + foreach (var entry in _entries.Values) + { + if (!data.FloatingWindowButtonRulesets.TryGetValue(entry.ButtonId, out var config)) + { + continue; + } + + var shouldHide = false; + if (!config.IsVisible) + { + shouldHide = true; + } + else if (config.RulesetEnabled) + { + shouldHide = !rulesetService.IsRulesetSatisfied(config.Ruleset); + } + + var wasHidden = _rulesetHiddenButtons.Contains(entry.ButtonId); + if (shouldHide != wasHidden) + { + if (shouldHide) + { + _rulesetHiddenButtons.Add(entry.ButtonId); + } + else + { + _rulesetHiddenButtons.Remove(entry.ButtonId); + } + changed = true; + } + } + + if (changed) + { + Dispatcher.UIThread.Post(RefreshWindowButtons); + } + } + private void ApplyVisibility() { EnsureWindow(); @@ -329,7 +426,7 @@ private void ApplyVisibility() return; } - if (_configHandler.Data.ShowFloatingWindow && _entries.Count > 0) + if (_configHandler.Data.ShowFloatingWindow && _entries.Count > 0 && !_rulesetHidingWindow) { if (!_window.IsVisible) { @@ -506,7 +603,9 @@ private void RefreshWindowButtons() private List> GetOrderedRows() { - var values = _entries.Values.ToDictionary(x => x.ButtonId, x => x); + var values = _entries.Values + .Where(x => !_rulesetHiddenButtons.Contains(x.ButtonId)) + .ToDictionary(x => x.ButtonId, x => x); var order = _configHandler.Data.FloatingWindowButtonOrder ?? []; var orderedIds = values.Keys @@ -1137,6 +1236,22 @@ private void RecheckWindowLayer() PInvoke.SetWindowPos(hwnd, HwndTopmost, 0, 0, 0, 0, flags); } + public void ToggleWindowLayer() + { + var data = _configHandler.Data; + data.FloatingWindowLayer = data.FloatingWindowLayer == 1 ? 0 : 1; + _configHandler.Save(); + Dispatcher.UIThread.Post(() => + { + if (_window != null) + { + _window.Topmost = data.FloatingWindowLayer == 1; + } + RecheckWindowLayer(); + RefreshLayerRecheckMode(); + }); + } + public static string ConvertIcon(string raw) { if (string.IsNullOrWhiteSpace(raw)) return "?"; diff --git a/SettingsPage/FloatingWindowEditorSettingsPage.axaml b/SettingsPage/FloatingWindowEditorSettingsPage.axaml index d6a4281..1ade033 100644 --- a/SettingsPage/FloatingWindowEditorSettingsPage.axaml +++ b/SettingsPage/FloatingWindowEditorSettingsPage.axaml @@ -6,6 +6,7 @@ xmlns:ci="http://classisland.tech/schemas/xaml/core" xmlns:controls="clr-namespace:FluentAvalonia.UI.Controls;assembly=FluentAvalonia" xmlns:controls1="clr-namespace:SystemTools.Controls" + xmlns:ruleset="clr-namespace:ClassIsland.Core.Controls.Ruleset;assembly=ClassIsland.Core" mc:Ignorable="d" d:DesignHeight="450" d:DesignWidth="800"> @@ -114,7 +115,7 @@ - @@ -141,7 +142,6 @@ - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - \ No newline at end of file + diff --git a/SettingsPage/SystemToolsSettingsViewModel.cs b/SettingsPage/SystemToolsSettingsViewModel.cs index b39978b..2cae2da 100644 --- a/SettingsPage/SystemToolsSettingsViewModel.cs +++ b/SettingsPage/SystemToolsSettingsViewModel.cs @@ -53,6 +53,14 @@ public partial class FloatingTriggerRow : ObservableObject [ObservableProperty] private ObservableCollection _buttons = new(); } +public partial class FloatingTriggerButtonConfigItem : ObservableObject +{ + [ObservableProperty] private string _buttonId = string.Empty; + [ObservableProperty] private string _icon = string.Empty; + [ObservableProperty] private string _buttonName = string.Empty; + [ObservableProperty] private ButtonRulesetConfig _config = new(); +} + public partial class SystemToolsSettingsViewModel : ObservableObject, IDisposable { [ObservableProperty] private MainConfigData _settings; @@ -76,6 +84,7 @@ public partial class SystemToolsSettingsViewModel : ObservableObject, IDisposabl [ObservableProperty] private ObservableCollection _floatingTriggerRows = new(); [ObservableProperty] private bool _hasFloatingTriggerEntries; + [ObservableProperty] private ObservableCollection _floatingTriggerButtonConfigs = new(); private const string DownloadUrl = "https://livefile.xesimg.com/programme/python_assets/f94fcfa40c9de41d6df09566a51e3130.exe"; @@ -327,6 +336,27 @@ public void RefreshFloatingTriggers() } PersistFloatingTriggerRows(updateWindow: false, forceSave: false); + RefreshFloatingTriggerButtonConfigs(entries); + } + + public void RefreshFloatingTriggerButtonConfigs(Dictionary entries) + { + FloatingTriggerButtonConfigs.Clear(); + foreach (var entry in entries.Values) + { + if (!Settings.FloatingWindowButtonRulesets.TryGetValue(entry.ButtonId, out var config)) + { + config = new ButtonRulesetConfig(); + Settings.FloatingWindowButtonRulesets[entry.ButtonId] = config; + } + FloatingTriggerButtonConfigs.Add(new FloatingTriggerButtonConfigItem + { + ButtonId = entry.ButtonId, + Icon = entry.Icon, + ButtonName = entry.LayoutName, + Config = config + }); + } } public void AddFloatingTriggerRow() diff --git a/Triggers/FloatingWindowTriggerConfig.cs b/Triggers/FloatingWindowTriggerConfig.cs index 91f9f70..9580d1b 100644 --- a/Triggers/FloatingWindowTriggerConfig.cs +++ b/Triggers/FloatingWindowTriggerConfig.cs @@ -8,4 +8,6 @@ public partial class FloatingWindowTriggerConfig : ObservableRecipient [ObservableProperty] private string _buttonId = Guid.NewGuid().ToString("N"); [ObservableProperty] private string _icon = "/uEA37"; [ObservableProperty] private string _buttonName = "触发按钮 1"; + [ObservableProperty] private bool _isVisible = true; + [ObservableProperty] private int _position = -1; } From b095a924396b231227cedd64352c19c7dfdcb814 Mon Sep 17 00:00:00 2001 From: ywydog Date: Sat, 30 May 2026 03:13:13 +0000 Subject: [PATCH 02/80] =?UTF-8?q?feat:=20=E6=82=AC=E6=B5=AE=E7=AA=97?= =?UTF-8?q?=E5=8A=9F=E8=83=BD=E5=A2=9E=E5=BC=BA=E4=B8=8E=E9=85=8D=E7=BD=AE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: traeagent --- ConfigHandlers/MainConfigData.cs | 14 ++++++++++++++ Services/FloatingWindowService.cs | 2 +- .../FloatingWindowEditorSettingsPage.axaml | 8 ++++++++ 3 files changed, 23 insertions(+), 1 deletion(-) diff --git a/ConfigHandlers/MainConfigData.cs b/ConfigHandlers/MainConfigData.cs index 541b94e..e447d39 100644 --- a/ConfigHandlers/MainConfigData.cs +++ b/ConfigHandlers/MainConfigData.cs @@ -345,6 +345,20 @@ public bool FloatingWindowRulesetEnabled } } + bool _floatingWindowDragHandleAlwaysVisible = false; + + [JsonPropertyName("floatingWindowDragHandleAlwaysVisible")] + public bool FloatingWindowDragHandleAlwaysVisible + { + get => _floatingWindowDragHandleAlwaysVisible; + set + { + if (value == _floatingWindowDragHandleAlwaysVisible) return; + _floatingWindowDragHandleAlwaysVisible = value; + OnPropertyChanged(); + } + } + [JsonPropertyName("floatingWindowRuleset")] public Ruleset FloatingWindowRuleset { get; set; } = new(); diff --git a/Services/FloatingWindowService.cs b/Services/FloatingWindowService.cs index e2c4acd..139de11 100644 --- a/Services/FloatingWindowService.cs +++ b/Services/FloatingWindowService.cs @@ -479,7 +479,7 @@ private void RefreshWindowButtons() _stackPanel.Children.Clear(); - if (_isTouchDeviceDetected) + if (_isTouchDeviceDetected || _configHandler.Data.FloatingWindowDragHandleAlwaysVisible) { _touchDragHandle = CreateTouchDragHandle(scale, contentForeground); _stackPanel.Children.Add(_touchDragHandle); diff --git a/SettingsPage/FloatingWindowEditorSettingsPage.axaml b/SettingsPage/FloatingWindowEditorSettingsPage.axaml index 1ade033..cd97d15 100644 --- a/SettingsPage/FloatingWindowEditorSettingsPage.axaml +++ b/SettingsPage/FloatingWindowEditorSettingsPage.axaml @@ -140,6 +140,14 @@ + + + + + + Date: Sat, 30 May 2026 03:22:51 +0000 Subject: [PATCH 03/80] =?UTF-8?q?feat:=20=E6=82=AC=E6=B5=AE=E7=AA=97?= =?UTF-8?q?=E5=8A=9F=E8=83=BD=E5=A2=9E=E5=BC=BA=E4=B8=8E=E9=85=8D=E7=BD=AE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: traeagent --- Actions/BackgroundPlayAudioAction.cs | 1 - Actions/ToggleFloatingWindowLayerAction.cs | 28 ++++++++++++++++++---- 2 files changed, 23 insertions(+), 6 deletions(-) diff --git a/Actions/BackgroundPlayAudioAction.cs b/Actions/BackgroundPlayAudioAction.cs index 3c20efd..af53882 100644 --- a/Actions/BackgroundPlayAudioAction.cs +++ b/Actions/BackgroundPlayAudioAction.cs @@ -6,7 +6,6 @@ using System; using System.IO; using System.Threading.Tasks; -using ClassIsland.Shared; using SystemTools.Settings; namespace SystemTools.Actions; diff --git a/Actions/ToggleFloatingWindowLayerAction.cs b/Actions/ToggleFloatingWindowLayerAction.cs index e2db996..e1d0345 100644 --- a/Actions/ToggleFloatingWindowLayerAction.cs +++ b/Actions/ToggleFloatingWindowLayerAction.cs @@ -1,16 +1,34 @@ +using System; +using System.Threading.Tasks; using ClassIsland.Core; using ClassIsland.Core.Abstractions.Automation; using ClassIsland.Core.Attributes; +using Microsoft.Extensions.Logging; using SystemTools.Services; namespace SystemTools.Actions; -[ActionInfo("SystemTools.ToggleFloatingWindowLayer", "切换悬浮窗置顶/置底", "\uE9A8")] -public class ToggleFloatingWindowLayerAction : ActionBase +[ActionInfo("SystemTools.ToggleFloatingWindowLayer", "切换悬浮窗置顶/置底", "\uE9A8", false)] +public class ToggleFloatingWindowLayerAction(ILogger logger) : ActionBase { - public override Task ExecuteAsync(object? settings, CancellationToken cancellationToken = new CancellationToken()) + private readonly ILogger _logger = logger; + + protected override async Task OnInvoke() { - IAppHost.GetService().ToggleWindowLayer(); - return Task.CompletedTask; + _logger.LogDebug("ToggleFloatingWindowLayerAction OnInvoke 开始"); + + try + { + IAppHost.GetService().ToggleWindowLayer(); + _logger.LogInformation("已切换悬浮窗置顶/置底状态"); + } + catch (Exception ex) + { + _logger.LogError(ex, "切换悬浮窗置顶/置底失败"); + throw; + } + + await base.OnInvoke(); + _logger.LogDebug("ToggleFloatingWindowLayerAction OnInvoke 完成"); } } From c9ad6c67deb031f9eb5e7236376f1775dcfd6439 Mon Sep 17 00:00:00 2001 From: ywydog Date: Sat, 30 May 2026 03:30:02 +0000 Subject: [PATCH 04/80] =?UTF-8?q?feat:=20=E6=82=AC=E6=B5=AE=E7=AA=97?= =?UTF-8?q?=E5=8A=9F=E8=83=BD=E5=A2=9E=E5=BC=BA=E4=B8=8E=E9=85=8D=E7=BD=AE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: traeagent --- Actions/ToggleFloatingWindowLayerAction.cs | 2 +- Services/FloatingWindowService.cs | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/Actions/ToggleFloatingWindowLayerAction.cs b/Actions/ToggleFloatingWindowLayerAction.cs index e1d0345..94ca285 100644 --- a/Actions/ToggleFloatingWindowLayerAction.cs +++ b/Actions/ToggleFloatingWindowLayerAction.cs @@ -1,8 +1,8 @@ using System; using System.Threading.Tasks; -using ClassIsland.Core; using ClassIsland.Core.Abstractions.Automation; using ClassIsland.Core.Attributes; +using ClassIsland.Shared; using Microsoft.Extensions.Logging; using SystemTools.Services; diff --git a/Services/FloatingWindowService.cs b/Services/FloatingWindowService.cs index 139de11..572fc67 100644 --- a/Services/FloatingWindowService.cs +++ b/Services/FloatingWindowService.cs @@ -9,6 +9,7 @@ using Avalonia.VisualTree; using ClassIsland.Core.Abstractions.Services; using ClassIsland.Core.Controls; +using ClassIsland.Shared; using System; using System.Collections.Generic; using System.Linq; From 93445cb4202aa6e8b325739557e4f8556ba7b098 Mon Sep 17 00:00:00 2001 From: ywydog Date: Sat, 30 May 2026 03:47:26 +0000 Subject: [PATCH 05/80] =?UTF-8?q?feat:=20=E6=82=AC=E6=B5=AE=E7=AA=97?= =?UTF-8?q?=E5=8A=9F=E8=83=BD=E5=A2=9E=E5=BC=BA=E4=B8=8E=E9=85=8D=E7=BD=AE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: traeagent --- Actions/ToggleFloatingWindowProfileAction.cs | 34 ++++++++++++++++++++ ConfigHandlers/MainConfigData.cs | 15 +++++++++ Plugin.cs | 7 +++- Services/FloatingWindowService.cs | 12 +++++++ 4 files changed, 67 insertions(+), 1 deletion(-) create mode 100644 Actions/ToggleFloatingWindowProfileAction.cs diff --git a/Actions/ToggleFloatingWindowProfileAction.cs b/Actions/ToggleFloatingWindowProfileAction.cs new file mode 100644 index 0000000..86da348 --- /dev/null +++ b/Actions/ToggleFloatingWindowProfileAction.cs @@ -0,0 +1,34 @@ +using System; +using System.Threading.Tasks; +using ClassIsland.Core.Abstractions.Automation; +using ClassIsland.Core.Attributes; +using ClassIsland.Shared; +using Microsoft.Extensions.Logging; +using SystemTools.Services; + +namespace SystemTools.Actions; + +[ActionInfo("SystemTools.ToggleFloatingWindowProfile", "切换悬浮窗配置方案", "\uE9A8", false)] +public class ToggleFloatingWindowProfileAction(ILogger logger) : ActionBase +{ + private readonly ILogger _logger = logger; + + protected override async Task OnInvoke() + { + _logger.LogDebug("ToggleFloatingWindowProfileAction OnInvoke 开始"); + + try + { + IAppHost.GetService().ToggleWindowProfile(); + _logger.LogInformation("已切换悬浮窗配置方案"); + } + catch (Exception ex) + { + _logger.LogError(ex, "切换悬浮窗配置方案失败"); + throw; + } + + await base.OnInvoke(); + _logger.LogDebug("ToggleFloatingWindowProfileAction OnInvoke 完成"); + } +} diff --git a/ConfigHandlers/MainConfigData.cs b/ConfigHandlers/MainConfigData.cs index e447d39..6b97899 100644 --- a/ConfigHandlers/MainConfigData.cs +++ b/ConfigHandlers/MainConfigData.cs @@ -331,6 +331,21 @@ public int FloatingWindowLayerRecheckMode } } + int _floatingWindowProfileIndex = 0; + + [JsonPropertyName("floatingWindowProfileIndex")] + public int FloatingWindowProfileIndex + { + get => _floatingWindowProfileIndex; + set + { + var normalized = value is 0 or 1 ? value : 0; + if (normalized == _floatingWindowProfileIndex) return; + _floatingWindowProfileIndex = normalized; + OnPropertyChanged(); + } + } + bool _floatingWindowRulesetEnabled = false; [JsonPropertyName("floatingWindowRulesetEnabled")] diff --git a/Plugin.cs b/Plugin.cs index 168dcd8..96b32c2 100644 --- a/Plugin.cs +++ b/Plugin.cs @@ -285,6 +285,8 @@ private void RegisterBaseActions(IServiceCollection services) "SystemTools.ShowFloatingWindow"); RegisterActionIfEnabled(services, config, "SystemTools.ToggleFloatingWindowLayer"); + RegisterActionIfEnabled(services, config, + "SystemTools.ToggleFloatingWindowProfile"); } // 其他工具 @@ -524,7 +526,8 @@ private void BuildBaseActionTree() } // 悬浮窗设置 - if (config.EnableFloatingWindowFeature && config.IsActionEnabled("SystemTools.ShowFloatingWindow")) + if (config.EnableFloatingWindowFeature && HasAnyActionEnabled(config, "SystemTools.ShowFloatingWindow", + "SystemTools.ToggleFloatingWindowLayer", "SystemTools.ToggleFloatingWindowProfile")) { IActionService.ActionMenuTree["SystemTools 行动"].Add(new ActionMenuTreeGroup("悬浮窗设置…", "\uEA37")); BuildFloatingWindowMenu(config); @@ -825,6 +828,8 @@ private void BuildFloatingWindowMenu(MainConfigData config) items.Add(new ActionMenuTreeItem("SystemTools.ShowFloatingWindow", "显示悬浮窗", "\uEA37")); if (config.EnableFloatingWindowFeature && config.IsActionEnabled("SystemTools.ToggleFloatingWindowLayer")) items.Add(new ActionMenuTreeItem("SystemTools.ToggleFloatingWindowLayer", "切换悬浮窗置顶/置底", "\uE9A8")); + if (config.EnableFloatingWindowFeature && config.IsActionEnabled("SystemTools.ToggleFloatingWindowProfile")) + items.Add(new ActionMenuTreeItem("SystemTools.ToggleFloatingWindowProfile", "切换悬浮窗配置方案", "\uE9A8")); if (items.Count > 0) { diff --git a/Services/FloatingWindowService.cs b/Services/FloatingWindowService.cs index 572fc67..f26fe12 100644 --- a/Services/FloatingWindowService.cs +++ b/Services/FloatingWindowService.cs @@ -1253,6 +1253,18 @@ public void ToggleWindowLayer() }); } + public void ToggleWindowProfile() + { + var data = _configHandler.Data; + data.FloatingWindowProfileIndex = data.FloatingWindowProfileIndex == 0 ? 1 : 0; + _configHandler.Save(); + Dispatcher.UIThread.Post(() => + { + RefreshWindowButtons(); + ApplyVisibility(); + }); + } + public static string ConvertIcon(string raw) { if (string.IsNullOrWhiteSpace(raw)) return "?"; From 0be6d3b5ff6196a4315fc52eaee00ff268d96111 Mon Sep 17 00:00:00 2001 From: ywydog Date: Sat, 30 May 2026 03:53:22 +0000 Subject: [PATCH 06/80] =?UTF-8?q?feat:=20=E6=82=AC=E6=B5=AE=E7=AA=97?= =?UTF-8?q?=E5=8A=9F=E8=83=BD=E5=A2=9E=E5=BC=BA=E4=B8=8E=E9=85=8D=E7=BD=AE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: traeagent --- SettingsPage/SystemToolsSettingsViewModel.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/SettingsPage/SystemToolsSettingsViewModel.cs b/SettingsPage/SystemToolsSettingsViewModel.cs index 2cae2da..1002f64 100644 --- a/SettingsPage/SystemToolsSettingsViewModel.cs +++ b/SettingsPage/SystemToolsSettingsViewModel.cs @@ -227,6 +227,8 @@ public void InitializeFeatureItems() if (Settings.EnableFloatingWindowFeature) { actions.Add(("SystemTools.ShowFloatingWindow", "显示悬浮窗", "悬浮窗设置")); + actions.Add(("SystemTools.ToggleFloatingWindowLayer", "切换悬浮窗置顶/置底", "悬浮窗设置")); + actions.Add(("SystemTools.ToggleFloatingWindowProfile", "切换悬浮窗配置方案", "悬浮窗设置")); } foreach (var (id, name, group) in actions) From e9dff25c9f63f21330a12a8abb0ea872ffdb036e Mon Sep 17 00:00:00 2001 From: ywydog Date: Sat, 30 May 2026 03:58:09 +0000 Subject: [PATCH 07/80] =?UTF-8?q?feat:=20=E6=82=AC=E6=B5=AE=E7=AA=97?= =?UTF-8?q?=E5=8A=9F=E8=83=BD=E5=A2=9E=E5=BC=BA=E4=B8=8E=E9=85=8D=E7=BD=AE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: traeagent --- Actions/ToggleFloatingWindowLayerAction.cs | 6 +++--- Plugin.cs | 2 +- SettingsPage/SystemToolsSettingsViewModel.cs | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/Actions/ToggleFloatingWindowLayerAction.cs b/Actions/ToggleFloatingWindowLayerAction.cs index 94ca285..c348c13 100644 --- a/Actions/ToggleFloatingWindowLayerAction.cs +++ b/Actions/ToggleFloatingWindowLayerAction.cs @@ -8,7 +8,7 @@ namespace SystemTools.Actions; -[ActionInfo("SystemTools.ToggleFloatingWindowLayer", "切换悬浮窗置顶/置底", "\uE9A8", false)] +[ActionInfo("SystemTools.ToggleFloatingWindowLayer", "切换悬浮窗层级", "\uE9A8", false)] public class ToggleFloatingWindowLayerAction(ILogger logger) : ActionBase { private readonly ILogger _logger = logger; @@ -20,11 +20,11 @@ protected override async Task OnInvoke() try { IAppHost.GetService().ToggleWindowLayer(); - _logger.LogInformation("已切换悬浮窗置顶/置底状态"); + _logger.LogInformation("已切换悬浮窗层级状态"); } catch (Exception ex) { - _logger.LogError(ex, "切换悬浮窗置顶/置底失败"); + _logger.LogError(ex, "切换悬浮窗层级失败"); throw; } diff --git a/Plugin.cs b/Plugin.cs index 96b32c2..8fffc96 100644 --- a/Plugin.cs +++ b/Plugin.cs @@ -827,7 +827,7 @@ private void BuildFloatingWindowMenu(MainConfigData config) if (config.EnableFloatingWindowFeature && config.IsActionEnabled("SystemTools.ShowFloatingWindow")) items.Add(new ActionMenuTreeItem("SystemTools.ShowFloatingWindow", "显示悬浮窗", "\uEA37")); if (config.EnableFloatingWindowFeature && config.IsActionEnabled("SystemTools.ToggleFloatingWindowLayer")) - items.Add(new ActionMenuTreeItem("SystemTools.ToggleFloatingWindowLayer", "切换悬浮窗置顶/置底", "\uE9A8")); + items.Add(new ActionMenuTreeItem("SystemTools.ToggleFloatingWindowLayer", "切换悬浮窗层级", "\uE9A8")); if (config.EnableFloatingWindowFeature && config.IsActionEnabled("SystemTools.ToggleFloatingWindowProfile")) items.Add(new ActionMenuTreeItem("SystemTools.ToggleFloatingWindowProfile", "切换悬浮窗配置方案", "\uE9A8")); diff --git a/SettingsPage/SystemToolsSettingsViewModel.cs b/SettingsPage/SystemToolsSettingsViewModel.cs index 1002f64..c78b2d1 100644 --- a/SettingsPage/SystemToolsSettingsViewModel.cs +++ b/SettingsPage/SystemToolsSettingsViewModel.cs @@ -227,7 +227,7 @@ public void InitializeFeatureItems() if (Settings.EnableFloatingWindowFeature) { actions.Add(("SystemTools.ShowFloatingWindow", "显示悬浮窗", "悬浮窗设置")); - actions.Add(("SystemTools.ToggleFloatingWindowLayer", "切换悬浮窗置顶/置底", "悬浮窗设置")); + actions.Add(("SystemTools.ToggleFloatingWindowLayer", "切换悬浮窗层级", "悬浮窗设置")); actions.Add(("SystemTools.ToggleFloatingWindowProfile", "切换悬浮窗配置方案", "悬浮窗设置")); } From fbbf0e789ab34ff708a02669608f09e8bd0899d6 Mon Sep 17 00:00:00 2001 From: ywydog Date: Sat, 30 May 2026 04:09:34 +0000 Subject: [PATCH 08/80] =?UTF-8?q?feat:=20=E6=82=AC=E6=B5=AE=E7=AA=97?= =?UTF-8?q?=E5=8A=9F=E8=83=BD=E5=A2=9E=E5=BC=BA=E4=B8=8E=E9=85=8D=E7=BD=AE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: traeagent --- ConfigHandlers/FloatingWindowProfile.cs | 44 ++++++++++++++ ConfigHandlers/MainConfigData.cs | 7 +++ Services/FloatingWindowService.cs | 60 ++++++++++++++++++- .../FloatingWindowEditorSettingsPage.axaml | 26 ++++++++ .../FloatingWindowEditorSettingsPage.axaml.cs | 5 ++ 5 files changed, 141 insertions(+), 1 deletion(-) create mode 100644 ConfigHandlers/FloatingWindowProfile.cs diff --git a/ConfigHandlers/FloatingWindowProfile.cs b/ConfigHandlers/FloatingWindowProfile.cs new file mode 100644 index 0000000..15cf788 --- /dev/null +++ b/ConfigHandlers/FloatingWindowProfile.cs @@ -0,0 +1,44 @@ +using System; +using System.Collections.Generic; +using System.Text.Json.Serialization; +using ClassIsland.Core.Models.Ruleset; +using CommunityToolkit.Mvvm.ComponentModel; + +namespace SystemTools.ConfigHandlers; + +public partial class FloatingWindowProfile : ObservableObject +{ + [ObservableProperty] private string _name = "未命名方案"; + + [ObservableProperty] private bool _showFloatingWindow = true; + + [ObservableProperty] private bool _floatingWindowHorizontal; + + [JsonPropertyName("floatingWindowButtonOrder")] + public List FloatingWindowButtonOrder { get; set; } = new(); + + [JsonPropertyName("floatingWindowButtonRows")] + public List> FloatingWindowButtonRows { get; set; } = new(); + + [ObservableProperty] private double _floatingWindowScale = 1.0; + + [ObservableProperty] private int _floatingWindowPositionX = 100; + + [ObservableProperty] private int _floatingWindowPositionY = 100; + + [ObservableProperty] private int _floatingWindowLayer = 1; + + [ObservableProperty] private int _floatingWindowLayerRecheckMode = 1; + + [ObservableProperty] private bool _floatingWindowShadowEnabled = true; + + [ObservableProperty] private bool _floatingWindowDragHandleAlwaysVisible; + + [ObservableProperty] private bool _floatingWindowRulesetEnabled; + + [JsonPropertyName("floatingWindowRuleset")] + public Ruleset FloatingWindowRuleset { get; set; } = new(); + + [JsonPropertyName("floatingWindowButtonRulesets")] + public Dictionary FloatingWindowButtonRulesets { get; set; } = new(); +} diff --git a/ConfigHandlers/MainConfigData.cs b/ConfigHandlers/MainConfigData.cs index 6b97899..5987093 100644 --- a/ConfigHandlers/MainConfigData.cs +++ b/ConfigHandlers/MainConfigData.cs @@ -331,6 +331,13 @@ public int FloatingWindowLayerRecheckMode } } + [JsonPropertyName("floatingWindowProfiles")] + public List FloatingWindowProfiles { get; set; } = new() + { + new FloatingWindowProfile { Name = "方案 1" }, + new FloatingWindowProfile { Name = "方案 2" } + }; + int _floatingWindowProfileIndex = 0; [JsonPropertyName("floatingWindowProfileIndex")] diff --git a/Services/FloatingWindowService.cs b/Services/FloatingWindowService.cs index f26fe12..bc9e07d 100644 --- a/Services/FloatingWindowService.cs +++ b/Services/FloatingWindowService.cs @@ -1256,15 +1256,73 @@ public void ToggleWindowLayer() public void ToggleWindowProfile() { var data = _configHandler.Data; - data.FloatingWindowProfileIndex = data.FloatingWindowProfileIndex == 0 ? 1 : 0; + var currentIndex = data.FloatingWindowProfileIndex; + var newIndex = currentIndex == 0 ? 1 : 0; + + SaveCurrentProfile(currentIndex); + data.FloatingWindowProfileIndex = newIndex; + LoadProfile(newIndex); _configHandler.Save(); + Dispatcher.UIThread.Post(() => { RefreshWindowButtons(); ApplyVisibility(); + RecheckWindowLayer(); + RefreshLayerRecheckMode(); }); } + private void SaveCurrentProfile(int index) + { + var data = _configHandler.Data; + if (index < 0 || index >= data.FloatingWindowProfiles.Count) + { + return; + } + + var profile = data.FloatingWindowProfiles[index]; + profile.ShowFloatingWindow = data.ShowFloatingWindow; + profile.FloatingWindowHorizontal = data.FloatingWindowHorizontal; + profile.FloatingWindowButtonOrder = new List(data.FloatingWindowButtonOrder ?? []); + profile.FloatingWindowButtonRows = (data.FloatingWindowButtonRows ?? []).Select(r => new List(r)).ToList(); + profile.FloatingWindowScale = data.FloatingWindowScale; + profile.FloatingWindowPositionX = data.FloatingWindowPositionX; + profile.FloatingWindowPositionY = data.FloatingWindowPositionY; + profile.FloatingWindowLayer = data.FloatingWindowLayer; + profile.FloatingWindowLayerRecheckMode = data.FloatingWindowLayerRecheckMode; + profile.FloatingWindowShadowEnabled = data.FloatingWindowShadowEnabled; + profile.FloatingWindowDragHandleAlwaysVisible = data.FloatingWindowDragHandleAlwaysVisible; + profile.FloatingWindowRulesetEnabled = data.FloatingWindowRulesetEnabled; + profile.FloatingWindowRuleset = data.FloatingWindowRuleset; + profile.FloatingWindowButtonRulesets = new Dictionary(data.FloatingWindowButtonRulesets ?? []); + } + + private void LoadProfile(int index) + { + var data = _configHandler.Data; + if (index < 0 || index >= data.FloatingWindowProfiles.Count) + { + return; + } + + var profile = data.FloatingWindowProfiles[index]; + data.ShowFloatingWindow = profile.ShowFloatingWindow; + data.FloatingWindowHorizontal = profile.FloatingWindowHorizontal; + data.FloatingWindowButtonOrder = new List(profile.FloatingWindowButtonOrder ?? []); + data.FloatingWindowButtonRows = (profile.FloatingWindowButtonRows ?? []).Select(r => new List(r)).ToList(); + data.FloatingWindowScale = profile.FloatingWindowScale; + data.FloatingWindowPositionX = profile.FloatingWindowPositionX; + data.FloatingWindowPositionY = profile.FloatingWindowPositionY; + data.FloatingWindowLayer = profile.FloatingWindowLayer; + data.FloatingWindowLayerRecheckMode = profile.FloatingWindowLayerRecheckMode; + data.FloatingWindowShadowEnabled = profile.FloatingWindowShadowEnabled; + data.FloatingWindowDragHandleAlwaysVisible = profile.FloatingWindowDragHandleAlwaysVisible; + data.FloatingWindowRulesetEnabled = profile.FloatingWindowRulesetEnabled; + data.FloatingWindowRuleset = profile.FloatingWindowRuleset; + data.FloatingWindowButtonRulesets = new Dictionary(profile.FloatingWindowButtonRulesets ?? []); + } + public static string ConvertIcon(string raw) { if (string.IsNullOrWhiteSpace(raw)) return "?"; diff --git a/SettingsPage/FloatingWindowEditorSettingsPage.axaml b/SettingsPage/FloatingWindowEditorSettingsPage.axaml index cd97d15..3f7a415 100644 --- a/SettingsPage/FloatingWindowEditorSettingsPage.axaml +++ b/SettingsPage/FloatingWindowEditorSettingsPage.axaml @@ -35,6 +35,32 @@ + + + + + + + + + + + + + + + + + + + + + + +``` + +#### ViewModel变更 + +**FloatingTriggerItem新增属性:** +```csharp +public partial class FloatingTriggerItem : ObservableObject +{ + [ObservableProperty] private string _buttonId = string.Empty; + [ObservableProperty] private string _icon = string.Empty; + [ObservableProperty] private string _buttonName = string.Empty; + [ObservableProperty] private bool _isSettingsExpanded = false; // 新增 + [ObservableProperty] private ButtonRulesetConfig _config = new(); // 新增,直接绑定规则集配置 +} +``` + +**重构RefreshFloatingTriggers:** +- 行内按钮直接绑定对应的 `ButtonRulesetConfig` +- 移除底部集中的 `FloatingTriggerButtonConfigs` 集合 +- 或者保留底部面板作为备用,但默认折叠 + +#### 代码后置事件 + +```csharp +private void OnButtonSettingsClick(object? sender, RoutedEventArgs e) +{ + // 找到对应的 FloatingTriggerItem + // 切换 IsSettingsExpanded 状态 + // 关闭其他按钮的设置面板(单开模式) +} +``` + +### 二、拖拽视觉反馈优化 + +#### 1. 拖拽把手扩大热区 +```xml + + + +``` + +#### 2. 拖拽开始时添加视觉反馈 +```csharp +private void OnFloatingTriggerItemPointerPressed(object? sender, PointerPressedEventArgs e) +{ + // ... 现有逻辑 ... + + // 添加按压效果 + border.Opacity = 0.7; + border.BoxShadow = new BoxShadows(new BoxShadow { ... }); // 添加阴影 +} + +private void OnFloatingTriggerItemPointerMoved(object? sender, PointerEventArgs e) +{ + // ... 现有逻辑 ... + + // 拖拽开始时添加拖动效果 + if (border != null) + { + border.Opacity = 0.5; + // 或者添加 IsDragging 样式类 + } +} + +private void OnFloatingTriggerItemPointerReleased(object? sender, PointerReleasedEventArgs e) +{ + // 恢复视觉效果 + if (_floatingDragSourceBorder != null) + { + _floatingDragSourceBorder.Opacity = 1.0; + _floatingDragSourceBorder.Classes.Remove("dragging"); + } + // ... 现有逻辑 ... +} +``` + +#### 3. 添加拖拽样式 +```xml + +``` + +#### 4. 按钮池卡片拖拽反馈 +- 整个卡片作为拖拽区域时,鼠标悬停显示拖拽光标 +- 拖拽开始时卡片半透明并放大 +- 拖拽把手始终可见,提示用户可以拖拽 + +### 三、数据流调整 + +#### 当前数据流 +``` +FloatingWindowProfile.FloatingWindowButtonRulesets[buttonId] -> + ViewModel.FloatingTriggerButtonConfigs -> + XAML ItemsControl (底部面板) +``` + +#### 新数据流 +``` +FloatingWindowProfile.FloatingWindowButtonRulesets[buttonId] -> + ViewModel.FloatingTriggerRows[].Buttons[].Config -> + XAML 行内展开面板 +``` + +#### 实现步骤 +1. 给 `FloatingTriggerItem` 添加 `Config` 属性 +2. 在 `RefreshFloatingTriggers` 中,为每个按钮查找对应的 `ButtonRulesetConfig` +3. 修改行内按钮XAML,添加设置按钮和展开面板 +4. 移除或折叠底部的按钮规则集面板 + +## 实施步骤 + +### 步骤1:修改数据模型 +- [ ] 给 `FloatingTriggerItem` 添加 `IsSettingsExpanded` 和 `Config` 属性 +- [ ] 修改 `RefreshFloatingTriggers` 为每个按钮绑定 `ButtonRulesetConfig` + +### 步骤2:修改行内按钮XAML +- [ ] 在按钮卡片Grid中添加"设置"按钮列 +- [ ] 在按钮卡片下方添加规则集配置展开面板 +- [ ] 绑定 `IsSettingsExpanded`、`Config.IsVisible`、`Config.RulesetEnabled`、`Config.Ruleset` + +### 步骤3:添加事件处理 +- [ ] 添加 `OnButtonSettingsClick` 方法 +- [ ] 实现单开逻辑(展开一个时关闭其他) + +### 步骤4:移除/折叠底部按钮规则集面板 +- [ ] 将底部"按钮规则集"区域移除或改为可选查看 + +### 步骤5:优化拖拽视觉反馈 +- [ ] 扩大拖拽把手热区 +- [ ] 添加拖拽开始/结束时的透明度变化 +- [ ] 添加拖拽样式类 +- [ ] 为按钮池卡片添加悬停和拖拽光标提示 + +### 步骤6:测试验证 +- [ ] 测试点击设置按钮展开/折叠规则集 +- [ ] 测试规则集修改后实时生效 +- [ ] 测试拖拽按钮时的视觉反馈 +- [ ] 测试按钮池拖拽到行中 + +## 风险与注意事项 + +1. **性能**:每个按钮都绑定一个 `RulesetControl`,如果按钮很多可能影响性能。建议采用懒加载或虚拟化。 +2. **空间**:行内展开面板会占用较多垂直空间,需要确保展开/折叠体验流畅。 +3. **数据同步**:行内修改的 `Config` 需要同步到底层的 `FloatingWindowProfile.FloatingWindowButtonRulesets` 字典中。 +4. **拖拽冲突**:设置按钮的点击事件和拖拽事件需要正确区分,避免点击设置按钮时触发拖拽。 From 3cf4f2be3bcd91bd846ebac976d4a5ff47bf2562 Mon Sep 17 00:00:00 2001 From: ywydog Date: Mon, 1 Jun 2026 05:04:16 +0000 Subject: [PATCH 36/80] =?UTF-8?q?feat:=20=E6=82=AC=E6=B5=AE=E7=AA=97?= =?UTF-8?q?=E5=8A=9F=E8=83=BD=E5=A2=9E=E5=BC=BA=E4=B8=8E=E9=85=8D=E7=BD=AE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: traeagent --- ...loating-window-button-ruleset-inline-ui.md | 258 +++++++++++------- .../FloatingWindowEditorSettingsPage.axaml | 171 ++++++------ .../FloatingWindowEditorSettingsPage.axaml.cs | 44 +++ SettingsPage/SystemToolsSettingsViewModel.cs | 48 ++-- 4 files changed, 298 insertions(+), 223 deletions(-) diff --git a/.trae/documents/floating-window-button-ruleset-inline-ui.md b/.trae/documents/floating-window-button-ruleset-inline-ui.md index 3430fe0..143c590 100644 --- a/.trae/documents/floating-window-button-ruleset-inline-ui.md +++ b/.trae/documents/floating-window-button-ruleset-inline-ui.md @@ -6,6 +6,11 @@ 1. **按钮规则集UI应该像ClassIsland组件一样**:点击按钮后,可以在按钮旁边直接调整规则集设置,而不是在底部的折叠面板中集中管理 2. **拖拽反馈不明确**:拖动按钮时无法确定自己是否正在拖动,缺少视觉反馈 +用户特别叮嘱: +- **性能优化**:避免不必要的UI渲染和重复保存 +- **去掉单独的按钮显示开关**:不需要 `IsVisible` 开关,因为删除按钮即可实现隐藏 +- **配置文件保存**:确保修改后及时、正确地写入JSON文件 + ## 现状分析 ### 当前按钮规则集UI @@ -25,46 +30,44 @@ ### 一、按钮规则集内联配置(核心改动) 参考ClassIsland组件配置方式: -- 每个按钮在行内显示时,右侧添加一个"设置"按钮 -- 点击设置按钮后,在该按钮下方展开规则集配置面板 -- 面板包含:显示/隐藏开关、启用规则集开关、规则集编辑器 +- 每个按钮在行内显示时,右侧添加一个"规则集"按钮(齿轮图标) +- 点击后在该按钮下方展开**精简的规则集配置面板** +- 面板只包含:**启用规则集开关** + **规则集编辑器**(去掉 `IsVisible` 开关,因为删除按钮即可隐藏) #### XAML结构变更 **行内按钮模板修改:** ```xml - - - - ... - - ... - - - - - - - - - - - + + + + + ... + + ... + + - - - - - - + + + + + + + + + + + + ``` @@ -77,140 +80,183 @@ public partial class FloatingTriggerItem : ObservableObject [ObservableProperty] private string _buttonId = string.Empty; [ObservableProperty] private string _icon = string.Empty; [ObservableProperty] private string _buttonName = string.Empty; - [ObservableProperty] private bool _isSettingsExpanded = false; // 新增 - [ObservableProperty] private ButtonRulesetConfig _config = new(); // 新增,直接绑定规则集配置 + [ObservableProperty] private bool _isRulesetExpanded = false; // 新增:控制展开状态 + [ObservableProperty] private ButtonRulesetConfig _config = new(); // 新增:直接引用规则集配置 } ``` **重构RefreshFloatingTriggers:** -- 行内按钮直接绑定对应的 `ButtonRulesetConfig` -- 移除底部集中的 `FloatingTriggerButtonConfigs` 集合 -- 或者保留底部面板作为备用,但默认折叠 +- 为每个按钮查找并绑定对应的 `ButtonRulesetConfig` +- 如果字典中不存在,自动创建新的配置并加入字典 +- 移除底部集中的 `FloatingTriggerButtonConfigs` 集合及相关代码 #### 代码后置事件 ```csharp -private void OnButtonSettingsClick(object? sender, RoutedEventArgs e) +private void OnButtonRulesetClick(object? sender, RoutedEventArgs e) { // 找到对应的 FloatingTriggerItem - // 切换 IsSettingsExpanded 状态 - // 关闭其他按钮的设置面板(单开模式) + // 切换 IsRulesetExpanded 状态 + // 关闭其他按钮的规则集面板(单开模式,避免同时展开多个占用空间) +} +``` + +#### 配置文件保存策略 + +```csharp +// 规则集修改后自动保存 +// 利用 ButtonRulesetConfig 的 PropertyChanged 事件 +// 或者由 RulesetControl 的变更触发保存 + +// 在 ViewModel 中订阅 Config 属性变更 +private void SubscribeButtonConfigChanges() +{ + foreach (var row in FloatingTriggerRows) + { + foreach (var button in row.Buttons) + { + button.Config.PropertyChanged += OnButtonConfigPropertyChanged; + } + } +} + +private void OnButtonConfigPropertyChanged(object? sender, PropertyChangedEventArgs e) +{ + // 直接保存整个 profile,确保 JSON 文件更新 + _floatingWindowService.ProfileManager.SaveProfile(); + // 通知悬浮窗更新显示状态 + _floatingWindowService.UpdateWindowState(); } ``` -### 二、拖拽视觉反馈优化 +### 二、移除底部按钮规则集面板 + +- 删除 XAML 中"规则集设置"折叠面板内的"按钮规则集"区域 +- 删除 `FloatingTriggerButtonConfigItem` 类 +- 删除 `FloatingTriggerButtonConfigs` 集合 +- 删除 `RefreshFloatingTriggerButtonConfigs` 方法 +- 保留"悬浮窗规则集"(整体控制悬浮窗显示/隐藏) + +### 三、拖拽视觉反馈优化 #### 1. 拖拽把手扩大热区 ```xml - + ``` #### 2. 拖拽开始时添加视觉反馈 ```csharp +private Border? _dragVisualBorder; + private void OnFloatingTriggerItemPointerPressed(object? sender, PointerPressedEventArgs e) { // ... 现有逻辑 ... - // 添加按压效果 - border.Opacity = 0.7; - border.BoxShadow = new BoxShadows(new BoxShadow { ... }); // 添加阴影 + // 记录原始视觉效果 + _dragVisualBorder = border; + _dragOriginalOpacity = border.Opacity; } private void OnFloatingTriggerItemPointerMoved(object? sender, PointerEventArgs e) { - // ... 现有逻辑 ... + // ... 现有逻辑(距离判断)... - // 拖拽开始时添加拖动效果 - if (border != null) + // 一旦判定为拖拽,立即添加视觉反馈 + if (_dragVisualBorder != null && _isDragging) { - border.Opacity = 0.5; - // 或者添加 IsDragging 样式类 + _dragVisualBorder.Opacity = 0.6; + _dragVisualBorder.Classes.Add("dragging"); } } private void OnFloatingTriggerItemPointerReleased(object? sender, PointerReleasedEventArgs e) { // 恢复视觉效果 - if (_floatingDragSourceBorder != null) + if (_dragVisualBorder != null) { - _floatingDragSourceBorder.Opacity = 1.0; - _floatingDragSourceBorder.Classes.Remove("dragging"); + _dragVisualBorder.Opacity = _dragOriginalOpacity; + _dragVisualBorder.Classes.Remove("dragging"); + _dragVisualBorder = null; } // ... 现有逻辑 ... } ``` -#### 3. 添加拖拽样式 +#### 3. 添加拖拽样式(在XAML Resources中) ```xml ``` #### 4. 按钮池卡片拖拽反馈 -- 整个卡片作为拖拽区域时,鼠标悬停显示拖拽光标 -- 拖拽开始时卡片半透明并放大 -- 拖拽把手始终可见,提示用户可以拖拽 +- 鼠标悬停在按钮池卡片上时显示 `Cursor="SizeAll"` +- 拖拽开始时卡片透明度降至 0.5 +- 拖拽把手(`⋮`)使用更深的颜色,始终清晰可见 -### 三、数据流调整 +## 实施步骤 -#### 当前数据流 -``` -FloatingWindowProfile.FloatingWindowButtonRulesets[buttonId] -> - ViewModel.FloatingTriggerButtonConfigs -> - XAML ItemsControl (底部面板) -``` +### 步骤1:修改数据模型(SystemToolsSettingsViewModel.cs) +- [ ] 给 `FloatingTriggerItem` 添加 `IsRulesetExpanded` 和 `Config` 属性 +- [ ] 修改 `RefreshFloatingTriggers`:为每个按钮查找/创建 `ButtonRulesetConfig` +- [ ] 删除 `FloatingTriggerButtonConfigItem` 类 +- [ ] 删除 `FloatingTriggerButtonConfigs` 属性 +- [ ] 删除 `RefreshFloatingTriggerButtonConfigs` 方法 -#### 新数据流 -``` -FloatingWindowProfile.FloatingWindowButtonRulesets[buttonId] -> - ViewModel.FloatingTriggerRows[].Buttons[].Config -> - XAML 行内展开面板 -``` +### 步骤2:修改行内按钮XAML(FloatingWindowEditorSettingsPage.axaml) +- [ ] 将按钮卡片改为 `Grid RowDefinitions="Auto,Auto"` +- [ ] 第一行添加"规则集"按钮(``) +- [ ] 第二行添加规则集展开面板(仅包含启用开关和规则集编辑器) +- [ ] 移除 `IsVisible` ToggleSwitch(用户不需要单独的显示开关) -#### 实现步骤 -1. 给 `FloatingTriggerItem` 添加 `Config` 属性 -2. 在 `RefreshFloatingTriggers` 中,为每个按钮查找对应的 `ButtonRulesetConfig` -3. 修改行内按钮XAML,添加设置按钮和展开面板 -4. 移除或折叠底部的按钮规则集面板 +### 步骤3:添加事件处理(FloatingWindowEditorSettingsPage.axaml.cs) +- [ ] 添加 `OnButtonRulesetClick` 方法 +- [ ] 实现单开逻辑(展开一个时关闭其他按钮的规则集面板) -## 实施步骤 +### 步骤4:移除底部按钮规则集面板(FloatingWindowEditorSettingsPage.axaml) +- [ ] 删除"规则集设置"折叠面板中的"按钮规则集"区域 -### 步骤1:修改数据模型 -- [ ] 给 `FloatingTriggerItem` 添加 `IsSettingsExpanded` 和 `Config` 属性 -- [ ] 修改 `RefreshFloatingTriggers` 为每个按钮绑定 `ButtonRulesetConfig` +### 步骤5:优化拖拽视觉反馈 +- [ ] 扩大拖拽把手热区(padding 4,2 → 8,6) +- [ ] 添加 `Cursor="SizeAll"` 到拖拽把手 +- [ ] 在 `PointerMoved` 中判定拖拽后添加 `dragging` 样式类 +- [ ] 在 `PointerReleased` 中移除 `dragging` 样式类 +- [ ] 添加拖拽样式到 XAML Resources +- [ ] 为按钮池卡片添加悬停光标提示 + +### 步骤6:配置文件保存 +- [ ] 在 `FloatingTriggerItem` 的 `Config` 属性变更时,调用 `SaveProfile()` +- [ ] 确保 `ButtonRulesetConfig` 的 `PropertyChanged` 能正确传播到 profile 保存 +- [ ] 测试:修改规则集后检查 JSON 文件是否及时更新 + +### 步骤7:测试验证 +- [ ] 测试点击规则集按钮展开/折叠 +- [ ] 测试规则集修改后实时生效且 JSON 保存正确 +- [ ] 测试拖拽按钮时的视觉反馈(透明度变化、阴影) +- [ ] 测试按钮池拖拽到行中 +- [ ] 测试删除按钮后配置文件中对应规则集是否被清理 -### 步骤2:修改行内按钮XAML -- [ ] 在按钮卡片Grid中添加"设置"按钮列 -- [ ] 在按钮卡片下方添加规则集配置展开面板 -- [ ] 绑定 `IsSettingsExpanded`、`Config.IsVisible`、`Config.RulesetEnabled`、`Config.Ruleset` +## 性能优化策略 -### 步骤3:添加事件处理 -- [ ] 添加 `OnButtonSettingsClick` 方法 -- [ ] 实现单开逻辑(展开一个时关闭其他) +1. **懒加载规则集编辑器**:`RulesetControl` 只在展开时初始化,折叠时释放资源 +2. **批量保存**:如果连续快速修改多个属性,使用防抖(debounce)延迟保存,避免频繁写入磁盘 +3. **避免重复刷新**:`UpdateWindowState()` 只在必要属性变更时调用,而非每次保存都调用 +4. **移除冗余集合**:删除 `FloatingTriggerButtonConfigs` 减少内存占用和绑定开销 -### 步骤4:移除/折叠底部按钮规则集面板 -- [ ] 将底部"按钮规则集"区域移除或改为可选查看 +## 配置文件保存注意事项 -### 步骤5:优化拖拽视觉反馈 -- [ ] 扩大拖拽把手热区 -- [ ] 添加拖拽开始/结束时的透明度变化 -- [ ] 添加拖拽样式类 -- [ ] 为按钮池卡片添加悬停和拖拽光标提示 - -### 步骤6:测试验证 -- [ ] 测试点击设置按钮展开/折叠规则集 -- [ ] 测试规则集修改后实时生效 -- [ ] 测试拖拽按钮时的视觉反馈 -- [ ] 测试按钮池拖拽到行中 +1. **保存时机**: + - 规则集配置修改后立即保存(通过 `PropertyChanged` 监听) + - 按钮拖拽排序后保存(已有 `PersistFloatingTriggerRows`) + - 按钮添加/删除后保存 -## 风险与注意事项 +2. **保存内容**: + - `FloatingWindowButtonRulesets` 字典中只保留当前存在的按钮ID + - 删除按钮时同步清理对应的规则集配置(在 `PruneInvalidButtonIds` 中已实现) -1. **性能**:每个按钮都绑定一个 `RulesetControl`,如果按钮很多可能影响性能。建议采用懒加载或虚拟化。 -2. **空间**:行内展开面板会占用较多垂直空间,需要确保展开/折叠体验流畅。 -3. **数据同步**:行内修改的 `Config` 需要同步到底层的 `FloatingWindowProfile.FloatingWindowButtonRulesets` 字典中。 -4. **拖拽冲突**:设置按钮的点击事件和拖拽事件需要正确区分,避免点击设置按钮时触发拖拽。 +3. **线程安全**: + - `SaveProfile()` 使用 `ConfigureFileHelper.SaveConfig`,确保线程安全 + - UI 线程上的修改通过 `Dispatcher.UIThread.Post` 触发保存 diff --git a/SettingsPage/FloatingWindowEditorSettingsPage.axaml b/SettingsPage/FloatingWindowEditorSettingsPage.axaml index aa4068f..d08ed24 100644 --- a/SettingsPage/FloatingWindowEditorSettingsPage.axaml +++ b/SettingsPage/FloatingWindowEditorSettingsPage.axaml @@ -10,6 +10,12 @@ mc:Ignorable="d" d:DesignHeight="450" d:DesignWidth="800"> + + + + + - + - - - - - - - - - - - - + IsVisible="{Binding ViewModel.SelectedFloatingTriggerRow, Converter={x:Static ObjectConverters.IsNotNull}}"> + + + + + + + + + + + + + + + + + + + @@ -537,32 +555,23 @@ - - - - - - - - - - - - - - + + + + + + + + Date: Wed, 3 Jun 2026 13:43:51 +0000 Subject: [PATCH 60/80] =?UTF-8?q?feat:=20=E6=82=AC=E6=B5=AE=E7=AA=97?= =?UTF-8?q?=E5=8A=9F=E8=83=BD=E5=A2=9E=E5=BC=BA=E4=B8=8E=E9=85=8D=E7=BD=AE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: traeagent --- SettingsPage/FloatingWindowEditorSettingsPage.axaml | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/SettingsPage/FloatingWindowEditorSettingsPage.axaml b/SettingsPage/FloatingWindowEditorSettingsPage.axaml index 48e24e6..b641df5 100644 --- a/SettingsPage/FloatingWindowEditorSettingsPage.axaml +++ b/SettingsPage/FloatingWindowEditorSettingsPage.axaml @@ -215,9 +215,10 @@ PreviewOpacity="0.5" CanDragWithoutDragThumb="True"> - + + + + Date: Wed, 3 Jun 2026 13:50:23 +0000 Subject: [PATCH 61/80] =?UTF-8?q?feat:=20=E6=82=AC=E6=B5=AE=E7=AA=97?= =?UTF-8?q?=E5=8A=9F=E8=83=BD=E5=A2=9E=E5=BC=BA=E4=B8=8E=E9=85=8D=E7=BD=AE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: traeagent --- SettingsPage/SystemToolsSettingsViewModel.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/SettingsPage/SystemToolsSettingsViewModel.cs b/SettingsPage/SystemToolsSettingsViewModel.cs index 908e429..e97cadd 100644 --- a/SettingsPage/SystemToolsSettingsViewModel.cs +++ b/SettingsPage/SystemToolsSettingsViewModel.cs @@ -53,12 +53,12 @@ public partial class FloatingTriggerItem : ObservableObject /// /// FluentIconSource,供 IconSourceElement 使用 /// - public FluentAvalonia.UI.Controls.FluentIconSource? IconSource + public ClassIsland.Core.Controls.FluentIconSource? IconSource { get { if (string.IsNullOrEmpty(_icon)) return null; - return new FluentAvalonia.UI.Controls.FluentIconSource { Glyph = _icon }; + return new ClassIsland.Core.Controls.FluentIconSource { Glyph = _icon }; } } From a71f2506a0585f8a2981758ab809f6e8b2eefc4e Mon Sep 17 00:00:00 2001 From: ywydog Date: Wed, 3 Jun 2026 14:21:20 +0000 Subject: [PATCH 62/80] =?UTF-8?q?feat:=20=E6=82=AC=E6=B5=AE=E7=AA=97?= =?UTF-8?q?=E5=8A=9F=E8=83=BD=E5=A2=9E=E5=BC=BA=E4=B8=8E=E9=85=8D=E7=BD=AE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: traeagent --- .../FloatingWindowEditorSettingsPage.axaml | 27 +++++++++---------- SettingsPage/SystemToolsSettingsViewModel.cs | 9 +++---- 2 files changed, 15 insertions(+), 21 deletions(-) diff --git a/SettingsPage/FloatingWindowEditorSettingsPage.axaml b/SettingsPage/FloatingWindowEditorSettingsPage.axaml index b641df5..4c3821a 100644 --- a/SettingsPage/FloatingWindowEditorSettingsPage.axaml +++ b/SettingsPage/FloatingWindowEditorSettingsPage.axaml @@ -7,7 +7,8 @@ xmlns:controls="clr-namespace:FluentAvalonia.UI.Controls;assembly=FluentAvalonia" xmlns:controls1="clr-namespace:SystemTools.Controls" xmlns:ruleset="clr-namespace:ClassIsland.Core.Controls.Ruleset;assembly=ClassIsland.Core" - xmlns:behaviors="clr-namespace:ClassIsland.Core.Behaviors;assembly=ClassIsland.Core" + xmlns:local="clr-namespace:SystemTools" + xmlns:dragDrop="using:Avalonia.Xaml.Interactions.DragAndDrop" xmlns:avaloniaXaml="using:Avalonia.Xaml.Interactivity" mc:Ignorable="d" d:DesignHeight="800" @@ -167,7 +168,7 @@ - @@ -209,19 +210,19 @@ - - - + + - - - + + @@ -233,7 +234,7 @@ - @@ -305,22 +306,18 @@ - - + - - - - diff --git a/SettingsPage/SystemToolsSettingsViewModel.cs b/SettingsPage/SystemToolsSettingsViewModel.cs index e97cadd..f806e37 100644 --- a/SettingsPage/SystemToolsSettingsViewModel.cs +++ b/SettingsPage/SystemToolsSettingsViewModel.cs @@ -57,15 +57,12 @@ public ClassIsland.Core.Controls.FluentIconSource? IconSource { get { - if (string.IsNullOrEmpty(_icon)) return null; - return new ClassIsland.Core.Controls.FluentIconSource { Glyph = _icon }; + if (string.IsNullOrEmpty(Icon)) return null; + return new ClassIsland.Core.Controls.FluentIconSource { Glyph = Icon }; } } - partial void OnIconChanged(string value) - { - OnPropertyChanged(nameof(IconSource)); - } + partial void OnIconChanged(string value) { OnPropertyChanged(nameof(IconSource)); } } public partial class FloatingTriggerRow : ObservableObject From cd6dcabf8b6f6bf629e1287a51127558926d381a Mon Sep 17 00:00:00 2001 From: ywydog Date: Wed, 3 Jun 2026 14:32:34 +0000 Subject: [PATCH 63/80] =?UTF-8?q?feat:=20=E6=82=AC=E6=B5=AE=E7=AA=97?= =?UTF-8?q?=E5=8A=9F=E8=83=BD=E5=A2=9E=E5=BC=BA=E4=B8=8E=E9=85=8D=E7=BD=AE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: traeagent --- .../FloatingWindowEditorSettingsPage.axaml | 15 ++++++++------- SystemTools.csproj | 1 + 2 files changed, 9 insertions(+), 7 deletions(-) diff --git a/SettingsPage/FloatingWindowEditorSettingsPage.axaml b/SettingsPage/FloatingWindowEditorSettingsPage.axaml index 4c3821a..f6a7a53 100644 --- a/SettingsPage/FloatingWindowEditorSettingsPage.axaml +++ b/SettingsPage/FloatingWindowEditorSettingsPage.axaml @@ -8,7 +8,8 @@ xmlns:controls1="clr-namespace:SystemTools.Controls" xmlns:ruleset="clr-namespace:ClassIsland.Core.Controls.Ruleset;assembly=ClassIsland.Core" xmlns:local="clr-namespace:SystemTools" - xmlns:dragDrop="using:Avalonia.Xaml.Interactions.DragAndDrop" + xmlns:behaviors="clr-namespace:ClassIsland.Core.Behaviors;assembly=ClassIsland.Core" + xmlns:dragDrop="clr-namespace:Avalonia.Xaml.Interactions.DragAndDrop;assembly=Avalonia.Xaml.Interactions" xmlns:avaloniaXaml="using:Avalonia.Xaml.Interactivity" mc:Ignorable="d" d:DesignHeight="800" @@ -210,18 +211,18 @@ - - + - - + + - - + diff --git a/SystemTools.csproj b/SystemTools.csproj index 8aa6925..e5843dd 100644 --- a/SystemTools.csproj +++ b/SystemTools.csproj @@ -13,6 +13,7 @@ + From 6fea7917976367017e07ec6f30df711b3ac05dd3 Mon Sep 17 00:00:00 2001 From: ywydog Date: Wed, 3 Jun 2026 15:08:37 +0000 Subject: [PATCH 64/80] =?UTF-8?q?feat:=20=E6=82=AC=E6=B5=AE=E7=AA=97?= =?UTF-8?q?=E5=8A=9F=E8=83=BD=E5=A2=9E=E5=BC=BA=E4=B8=8E=E9=85=8D=E7=BD=AE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: traeagent --- SettingsPage/FloatingWindowDropHandler.cs | 177 ++++++++---------- .../FloatingWindowEditorSettingsPage.axaml | 69 +------ .../FloatingWindowEditorSettingsPage.axaml.cs | 128 +++++++++++++ SystemTools.csproj | 5 +- 4 files changed, 214 insertions(+), 165 deletions(-) diff --git a/SettingsPage/FloatingWindowDropHandler.cs b/SettingsPage/FloatingWindowDropHandler.cs index 9ad9c7b..c87c573 100644 --- a/SettingsPage/FloatingWindowDropHandler.cs +++ b/SettingsPage/FloatingWindowDropHandler.cs @@ -3,141 +3,116 @@ using Avalonia.Controls; using Avalonia.Input; using Avalonia.VisualTree; -using Avalonia.Xaml.Interactions.DragAndDrop; namespace SystemTools; /// -/// 悬浮窗按钮拖放处理器,参照 ClassIsland 的 EditableComponentsListBoxDropHandler 设计 +/// 悬浮窗按钮拖放处理器 /// 处理按钮在行内排序、跨行移动、从组件库添加 +/// 使用标准 Avalonia DragDrop,不依赖 ClassIsland 的 ManagedDragDrop /// -public class FloatingWindowDropHandler : DropHandlerBase +public class FloatingWindowDropHandler { - private FloatingWindowEditorSettingsPage? _page; + private readonly FloatingWindowEditorSettingsPage _page; public FloatingWindowDropHandler(FloatingWindowEditorSettingsPage page) { _page = page; } - private static (int index, bool found) GetTargetIndex(ListBox listBox, DragEventArgs e, - ObservableCollection items) + /// + /// 处理按钮拖放到行内 + /// + public bool HandleButtonDrop(ListBox targetListBox, DragEventArgs e, + FloatingWindowButtonDragData dragData, ObservableCollection targetList) { - var pos = e.GetPosition(listBox); - if (listBox.GetVisualAt(pos) is Control targetControl - && targetControl.FindAncestorOfType() is { } listBoxItem - && listBoxItem.DataContext is FloatingTriggerItem targetItem) + var viewModel = _page.ViewModel; + if (viewModel == null) return false; + + var (targetIndex, foundTargetIndex) = GetTargetIndex(targetListBox, e, targetList); + var insertIndex = foundTargetIndex ? targetIndex + 1 : targetList.Count; + + // 找到目标行 + var targetRow = viewModel.FloatingTriggerRows.FirstOrDefault(r => r.Buttons == targetList); + if (targetRow == null) return false; + var rowIndex = viewModel.FloatingTriggerRows.IndexOf(targetRow); + + if (dragData.SourceCollection == null) { - var rPos = e.GetPosition(listBoxItem); - var index = items.IndexOf(targetItem); - if (index >= 0) - { - return (rPos.X <= listBoxItem.Bounds.Width / 2 ? index - 1 : index, true); - } + // 从组件库添加 + if (dragData.Item == null) return false; + var isInRow = viewModel.FloatingTriggerRows.Any(r => r.Buttons.Any(b => b.ButtonId == dragData.Item.ButtonId)); + if (isInRow) return false; + viewModel.AddTriggerFromPool(dragData.Item.ButtonId, rowIndex, insertIndex); + } + else if (!ReferenceEquals(dragData.SourceCollection, targetList)) + { + // 跨行移动 + if (dragData.Item == null) return false; + viewModel.MoveFloatingTrigger(dragData.Item.ButtonId, rowIndex, insertIndex); + } + else + { + // 行内排序 + if (dragData.Item == null) return false; + var sourceIndex = targetList.IndexOf(dragData.Item); + if (sourceIndex < 0) return false; + var moveIndex = foundTargetIndex ? targetIndex : targetList.Count - 1; + var newIndex = sourceIndex > moveIndex ? moveIndex + 1 : moveIndex; + MoveItem(targetList, sourceIndex, System.Math.Clamp(newIndex, 0, targetList.Count - 1)); + viewModel.PersistFloatingTriggerRows(); } - return (items.Count > 0 ? items.Count - 1 : -1, items.Count > 0); + return true; } - private bool ValidateCore(ListBox listBox, DragEventArgs e, object? sourceContext, object? targetContext, - bool execute) + /// + /// 处理从组件库拖入的按钮(SourceCollection 为 null) + /// + public bool HandlePoolItemDrop(ListBox targetListBox, DragEventArgs e, + FloatingTriggerItem poolItem, ObservableCollection targetList) { - e.Handled = true; - if (_page == null) return false; - var viewModel = _page.ViewModel; if (viewModel == null) return false; - // 处理从组件库拖入的按钮(sourceContext 是 FloatingTriggerItem,来自按钮池) - if (sourceContext is FloatingTriggerItem poolItem && targetContext is ObservableCollection targetList) - { - // 检查是否来自按钮池(不在任何行中) - var isInRow = viewModel.FloatingTriggerRows.Any(r => r.Buttons.Any(b => b.ButtonId == poolItem.ButtonId)); - if (isInRow) return false; + var isInRow = viewModel.FloatingTriggerRows.Any(r => r.Buttons.Any(b => b.ButtonId == poolItem.ButtonId)); + if (isInRow) return false; - if (execute) - { - var (targetIndex, foundTargetIndex) = GetTargetIndex(listBox, e, targetList); - var insertIndex = foundTargetIndex ? targetIndex + 1 : targetList.Count; - - // 找到目标行 - var targetRow = viewModel.FloatingTriggerRows.FirstOrDefault(r => r.Buttons == targetList); - if (targetRow == null) return false; + var (targetIndex, foundTargetIndex) = GetTargetIndex(targetListBox, e, targetList); + var insertIndex = foundTargetIndex ? targetIndex + 1 : targetList.Count; - var rowIndex = viewModel.FloatingTriggerRows.IndexOf(targetRow); - viewModel.AddTriggerFromPool(poolItem.ButtonId, rowIndex, insertIndex); - } + var targetRow = viewModel.FloatingTriggerRows.FirstOrDefault(r => r.Buttons == targetList); + if (targetRow == null) return false; + var rowIndex = viewModel.FloatingTriggerRows.IndexOf(targetRow); - return true; - } + viewModel.AddTriggerFromPool(poolItem.ButtonId, rowIndex, insertIndex); + return true; + } - // 处理行内按钮拖拽排序/跨行移动 - if (sourceContext is FloatingWindowButtonDragData data - && targetContext is ObservableCollection components) + private static (int index, bool found) GetTargetIndex(ListBox listBox, DragEventArgs e, + ObservableCollection items) + { + var pos = e.GetPosition(listBox); + if (listBox.GetVisualAt(pos) is Control targetControl + && targetControl.FindAncestorOfType() is { } listBoxItem + && listBoxItem.DataContext is FloatingTriggerItem targetItem) { - if (data.Item == null) return false; - - var (targetIndex, foundTargetIndex) = GetTargetIndex(listBox, e, components); - var insertIndex = foundTargetIndex ? targetIndex + 1 : components.Count; - - if (execute) + var rPos = e.GetPosition(listBoxItem); + var index = items.IndexOf(targetItem); + if (index >= 0) { - var targetRow = viewModel.FloatingTriggerRows.FirstOrDefault(r => r.Buttons == components); - if (targetRow == null) return false; - var rowIndex = viewModel.FloatingTriggerRows.IndexOf(targetRow); - - if (data.SourceCollection != null && !ReferenceEquals(data.SourceCollection, components)) - { - // 跨行移动 - var sourceRow = viewModel.FloatingTriggerRows.FirstOrDefault(r => r.Buttons == data.SourceCollection); - if (sourceRow == null) return false; - var sourceRowIndex = viewModel.FloatingTriggerRows.IndexOf(sourceRow); - var sourceIndex = data.SourceCollection.IndexOf(data.Item); - if (sourceIndex < 0) return false; - - viewModel.MoveFloatingTrigger(data.Item.ButtonId, rowIndex, insertIndex); - } - else - { - // 行内排序 - var sourceIndex = components.IndexOf(data.Item); - if (sourceIndex < 0) return false; - - if (ReferenceEquals(data.SourceCollection, components)) - { - var moveIndex = foundTargetIndex ? targetIndex : components.Count - 1; - var newIndex = sourceIndex > moveIndex ? moveIndex + 1 : moveIndex; - MoveItem(components, sourceIndex, System.Math.Clamp(newIndex, 0, components.Count - 1)); - viewModel.PersistFloatingTriggerRows(); - } - } + return (rPos.X <= listBoxItem.Bounds.Width / 2 ? index - 1 : index, true); } - - return true; } - return false; - } - - public override bool Validate(object? sender, DragEventArgs e, object? sourceContext, object? targetContext, - object? state) - { - if (e.Handled) return false; - return sender switch - { - ListBox listBox => ValidateCore(listBox, e, sourceContext, targetContext, false), - _ => false - }; + return (items.Count > 0 ? items.Count - 1 : -1, items.Count > 0); } - public override bool Execute(object? sender, DragEventArgs e, object? sourceContext, object? targetContext, - object? state) + private static void MoveItem(ObservableCollection list, int oldIndex, int newIndex) { - if (e.Handled) return false; - return sender switch - { - ListBox listBox => ValidateCore(listBox, e, sourceContext, targetContext, true), - _ => false - }; + if (oldIndex == newIndex) return; + var item = list[oldIndex]; + list.RemoveAt(oldIndex); + list.Insert(newIndex, item); } } diff --git a/SettingsPage/FloatingWindowEditorSettingsPage.axaml b/SettingsPage/FloatingWindowEditorSettingsPage.axaml index f6a7a53..882e964 100644 --- a/SettingsPage/FloatingWindowEditorSettingsPage.axaml +++ b/SettingsPage/FloatingWindowEditorSettingsPage.axaml @@ -8,9 +8,6 @@ xmlns:controls1="clr-namespace:SystemTools.Controls" xmlns:ruleset="clr-namespace:ClassIsland.Core.Controls.Ruleset;assembly=ClassIsland.Core" xmlns:local="clr-namespace:SystemTools" - xmlns:behaviors="clr-namespace:ClassIsland.Core.Behaviors;assembly=ClassIsland.Core" - xmlns:dragDrop="clr-namespace:Avalonia.Xaml.Interactions.DragAndDrop;assembly=Avalonia.Xaml.Interactions" - xmlns:avaloniaXaml="using:Avalonia.Xaml.Interactivity" mc:Ignorable="d" d:DesignHeight="800" d:DesignWidth="800"> @@ -78,7 +75,8 @@ BorderThickness="1" CornerRadius="8" Padding="4" - MinHeight="80"> + MinHeight="80" + x:Name="RowDropBorder"> + VerticalAlignment="Center" + PointerPressed="OnRowDragThumbPointerPressed" /> - + - - - - - - @@ -207,40 +198,8 @@ - - - - - - - - - - - - - - - - - - - - - - @@ -280,7 +239,8 @@ - + - - - - - - - - - diff --git a/SettingsPage/FloatingWindowEditorSettingsPage.axaml.cs b/SettingsPage/FloatingWindowEditorSettingsPage.axaml.cs index 14e9e7b..2e578f9 100644 --- a/SettingsPage/FloatingWindowEditorSettingsPage.axaml.cs +++ b/SettingsPage/FloatingWindowEditorSettingsPage.axaml.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.ObjectModel; using System.ComponentModel; using System.IO; using System.Linq; @@ -56,6 +57,14 @@ public FloatingWindowEditorSettingsPage() private bool _isDisposed; + // ===== 拖拽状态 ===== + private FloatingTriggerItem? _dragItem; + private ObservableCollection? _dragSourceCollection; + private FloatingTriggerRow? _dragRow; + private bool _isDragging; + private Point _dragStartPoint; + private const double DragThreshold = 5.0; + protected override void OnDetachedFromVisualTree(VisualTreeAttachmentEventArgs e) { base.OnDetachedFromVisualTree(e); @@ -283,6 +292,125 @@ private void OnAvailableItemSelectionChanged(object? sender, SelectionChangedEve } } + // ===== 拖拽处理(标准 Avalonia DragDrop) ===== + + /// + /// 行拖拽把手按下:开始行拖拽 + /// + private void OnRowDragThumbPointerPressed(object? sender, PointerPressedEventArgs e) + { + if (sender is not Control control) return; + + // 找到所属行 + var row = control.GetVisualAncestors() + .OfType() + .Select(b => b.DataContext) + .OfType() + .FirstOrDefault(); + if (row == null) return; + + _dragRow = row; + _dragItem = null; + _dragSourceCollection = null; + _dragStartPoint = e.GetPosition(this); + _isDragging = false; + + e.Handled = true; + e.PreventGestureHandler(); + } + + /// + /// 行内按钮按下:开始按钮拖拽 + /// + private void OnButtonPointerPressed(object? sender, PointerPressedEventArgs e) + { + if (sender is not Control { DataContext: FloatingTriggerItem item }) return; + + // 找到所属行的 Buttons 集合 + var row = item.GetVisualAncestors() + .OfType() + .Select(b => b.DataContext) + .OfType() + .FirstOrDefault(); + if (row == null) return; + + _dragItem = item; + _dragSourceCollection = row.Buttons; + _dragRow = null; + _dragStartPoint = e.GetPosition(this); + _isDragging = false; + + e.Handled = true; + e.PreventGestureHandler(); + } + + /// + /// 组件库项按下:开始从组件库拖拽 + /// + private void OnPoolItemPointerPressed(object? sender, PointerPressedEventArgs e) + { + if (sender is not Control { DataContext: FloatingTriggerItem item }) return; + + _dragItem = item; + _dragSourceCollection = null; // null 表示来自组件库 + _dragRow = null; + _dragStartPoint = e.GetPosition(this); + _isDragging = false; + + e.Handled = true; + e.PreventGestureHandler(); + } + + protected override void OnPointerMoved(PointerEventArgs e) + { + base.OnPointerMoved(e); + + if (_dragItem == null && _dragRow == null) return; + if (_isDragging) return; + + var currentPos = e.GetPosition(this); + var delta = currentPos - _dragStartPoint; + + if (System.Math.Abs(delta.X) < DragThreshold && System.Math.Abs(delta.Y) < DragThreshold) + return; + + _isDragging = true; + + // 执行拖拽 + if (_dragRow != null) + { + // 行拖拽 + var data = new DataObject(); + data.Set("FloatingWindowRow", _dragRow); + DragDrop.DoDragDrop(e, data, DragDropEffects.Move); + } + else if (_dragItem != null) + { + // 按钮拖拽(行内/跨行/从组件库) + var data = new DataObject(); + data.Set("FloatingWindowButton", new FloatingWindowButtonDragData + { + Item = _dragItem, + SourceCollection = _dragSourceCollection + }); + DragDrop.DoDragDrop(e, data, DragDropEffects.Move); + } + + _dragItem = null; + _dragSourceCollection = null; + _dragRow = null; + _isDragging = false; + } + + protected override void OnPointerReleased(PointerReleasedEventArgs e) + { + base.OnPointerReleased(e); + _dragItem = null; + _dragSourceCollection = null; + _dragRow = null; + _isDragging = false; + } + // ===== 规则集 Drawer(参照 ClassIsland) ===== private void ButtonOpenButtonRuleset_OnClick(object? sender, RoutedEventArgs e) diff --git a/SystemTools.csproj b/SystemTools.csproj index e5843dd..e9832cf 100644 --- a/SystemTools.csproj +++ b/SystemTools.csproj @@ -11,9 +11,8 @@ - - - + + From 55c0fc71e13ebabb41f181e4e75962314b0a0ef4 Mon Sep 17 00:00:00 2001 From: ywydog Date: Wed, 3 Jun 2026 21:56:13 +0000 Subject: [PATCH 65/80] =?UTF-8?q?feat:=20=E6=82=AC=E6=B5=AE=E7=AA=97?= =?UTF-8?q?=E5=8A=9F=E8=83=BD=E5=A2=9E=E5=BC=BA=E4=B8=8E=E9=85=8D=E7=BD=AE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: traeagent --- SettingsPage/FloatingWindowEditorSettingsPage.axaml.cs | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/SettingsPage/FloatingWindowEditorSettingsPage.axaml.cs b/SettingsPage/FloatingWindowEditorSettingsPage.axaml.cs index 2e578f9..d922721 100644 --- a/SettingsPage/FloatingWindowEditorSettingsPage.axaml.cs +++ b/SettingsPage/FloatingWindowEditorSettingsPage.axaml.cs @@ -316,7 +316,6 @@ private void OnRowDragThumbPointerPressed(object? sender, PointerPressedEventArg _isDragging = false; e.Handled = true; - e.PreventGestureHandler(); } /// @@ -324,10 +323,10 @@ private void OnRowDragThumbPointerPressed(object? sender, PointerPressedEventArg /// private void OnButtonPointerPressed(object? sender, PointerPressedEventArgs e) { - if (sender is not Control { DataContext: FloatingTriggerItem item }) return; + if (sender is not Control control || control.DataContext is not FloatingTriggerItem item) return; // 找到所属行的 Buttons 集合 - var row = item.GetVisualAncestors() + var row = control.GetVisualAncestors() .OfType() .Select(b => b.DataContext) .OfType() @@ -341,7 +340,6 @@ private void OnButtonPointerPressed(object? sender, PointerPressedEventArgs e) _isDragging = false; e.Handled = true; - e.PreventGestureHandler(); } /// @@ -349,7 +347,7 @@ private void OnButtonPointerPressed(object? sender, PointerPressedEventArgs e) /// private void OnPoolItemPointerPressed(object? sender, PointerPressedEventArgs e) { - if (sender is not Control { DataContext: FloatingTriggerItem item }) return; + if (sender is not Control control || control.DataContext is not FloatingTriggerItem item) return; _dragItem = item; _dragSourceCollection = null; // null 表示来自组件库 @@ -358,7 +356,6 @@ private void OnPoolItemPointerPressed(object? sender, PointerPressedEventArgs e) _isDragging = false; e.Handled = true; - e.PreventGestureHandler(); } protected override void OnPointerMoved(PointerEventArgs e) From 37f5be331e04e3fb1a1922bea93adfd45274525a Mon Sep 17 00:00:00 2001 From: ywydog Date: Wed, 3 Jun 2026 21:59:30 +0000 Subject: [PATCH 66/80] =?UTF-8?q?feat:=20=E6=82=AC=E6=B5=AE=E7=AA=97?= =?UTF-8?q?=E5=8A=9F=E8=83=BD=E5=A2=9E=E5=BC=BA=E4=B8=8E=E9=85=8D=E7=BD=AE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: traeagent --- SystemTools.csproj | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/SystemTools.csproj b/SystemTools.csproj index e9832cf..8aa6925 100644 --- a/SystemTools.csproj +++ b/SystemTools.csproj @@ -11,8 +11,8 @@ - - + + From 090f43bc4898d5f61aabc4b35a6b1750e1fba1de Mon Sep 17 00:00:00 2001 From: ywydog Date: Wed, 3 Jun 2026 22:07:13 +0000 Subject: [PATCH 67/80] =?UTF-8?q?feat:=20=E6=82=AC=E6=B5=AE=E7=AA=97?= =?UTF-8?q?=E5=8A=9F=E8=83=BD=E5=A2=9E=E5=BC=BA=E4=B8=8E=E9=85=8D=E7=BD=AE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: traeagent --- .../Components/BetterCarouselContainerComponent.axaml.cs | 6 ++++++ Controls/Components/LocalQuoteComponent.axaml.cs | 5 +++++ Controls/Components/NextClassDisplayComponent.axaml.cs | 5 +++++ 3 files changed, 16 insertions(+) diff --git a/Controls/Components/BetterCarouselContainerComponent.axaml.cs b/Controls/Components/BetterCarouselContainerComponent.axaml.cs index f3705b4..ad17503 100644 --- a/Controls/Components/BetterCarouselContainerComponent.axaml.cs +++ b/Controls/Components/BetterCarouselContainerComponent.axaml.cs @@ -82,6 +82,12 @@ public int SelectedIndex public new event PropertyChangedEventHandler? PropertyChanged; + public BetterCarouselContainerComponent() + { + InitializeComponent(); + InitializeAnimations(); + } + public BetterCarouselContainerComponent(IRulesetService rulesetService, ILessonsService lessonsService) { _rulesetService = rulesetService; diff --git a/Controls/Components/LocalQuoteComponent.axaml.cs b/Controls/Components/LocalQuoteComponent.axaml.cs index 5efb792..df9329d 100644 --- a/Controls/Components/LocalQuoteComponent.axaml.cs +++ b/Controls/Components/LocalQuoteComponent.axaml.cs @@ -70,6 +70,11 @@ static LocalQuoteComponent() Encoding.RegisterProvider(CodePagesEncodingProvider.Instance); } + public LocalQuoteComponent() + { + InitializeComponent(); + } + public LocalQuoteComponent(ILessonsService lessonsService) { _lessonsService = lessonsService; diff --git a/Controls/Components/NextClassDisplayComponent.axaml.cs b/Controls/Components/NextClassDisplayComponent.axaml.cs index 498106e..192f6f3 100644 --- a/Controls/Components/NextClassDisplayComponent.axaml.cs +++ b/Controls/Components/NextClassDisplayComponent.axaml.cs @@ -94,6 +94,11 @@ private set public new event PropertyChangedEventHandler? PropertyChanged; + public NextClassDisplayComponent() + { + InitializeComponent(); + } + public NextClassDisplayComponent(ILessonsService lessonsService, IProfileService profileService, IExactTimeService exactTimeService) { _lessonsService = lessonsService; From f0fd1b159c5b203d3d2885d5946c270f9cc039a0 Mon Sep 17 00:00:00 2001 From: ywydog Date: Wed, 3 Jun 2026 22:13:10 +0000 Subject: [PATCH 68/80] =?UTF-8?q?feat:=20=E6=82=AC=E6=B5=AE=E7=AA=97?= =?UTF-8?q?=E5=8A=9F=E8=83=BD=E5=A2=9E=E5=BC=BA=E4=B8=8E=E9=85=8D=E7=BD=AE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: traeagent --- .../Components/BetterCarouselContainerComponent.axaml.cs | 4 ++-- Controls/Components/LocalQuoteComponent.axaml.cs | 8 ++++---- Controls/Components/NextClassDisplayComponent.axaml.cs | 6 +++--- 3 files changed, 9 insertions(+), 9 deletions(-) diff --git a/Controls/Components/BetterCarouselContainerComponent.axaml.cs b/Controls/Components/BetterCarouselContainerComponent.axaml.cs index ad17503..8399468 100644 --- a/Controls/Components/BetterCarouselContainerComponent.axaml.cs +++ b/Controls/Components/BetterCarouselContainerComponent.axaml.cs @@ -22,8 +22,8 @@ namespace SystemTools.Controls.Components; [ComponentInfo("A7C3455E-6A4E-4D4D-9D0D-7C6FCB5E1E3A", "更好的轮播容器", "\uF0DB", "带有可单独设置组件显示时长等高级功能的轮播容器")] public partial class BetterCarouselContainerComponent : ComponentBase, INotifyPropertyChanged { - private readonly ILessonsService _lessonsService; - private readonly IRulesetService _rulesetService; + private readonly IRulesetService? _rulesetService; + private readonly ILessonsService? _lessonsService; private readonly Random _random = new(); private Animation? _slideInAnimation; diff --git a/Controls/Components/LocalQuoteComponent.axaml.cs b/Controls/Components/LocalQuoteComponent.axaml.cs index df9329d..fe6a92a 100644 --- a/Controls/Components/LocalQuoteComponent.axaml.cs +++ b/Controls/Components/LocalQuoteComponent.axaml.cs @@ -30,11 +30,11 @@ public partial class LocalQuoteComponent : ComponentBase, IN { private const double SwapMotionOffset = 20; - private readonly DispatcherTimer _carouselTimer; - private readonly ILessonsService _lessonsService; + private readonly DispatcherTimer? _carouselTimer; + private readonly ILessonsService? _lessonsService; private readonly List _quotes = []; - private readonly Animation _swapOutAnimation; - private readonly Animation _swapInAnimation; + private readonly Animation? _swapOutAnimation; + private readonly Animation? _swapInAnimation; private readonly Random _random = new(); private int _currentIndex = -1; private string _loadedPath = string.Empty; diff --git a/Controls/Components/NextClassDisplayComponent.axaml.cs b/Controls/Components/NextClassDisplayComponent.axaml.cs index 192f6f3..a2f9db1 100644 --- a/Controls/Components/NextClassDisplayComponent.axaml.cs +++ b/Controls/Components/NextClassDisplayComponent.axaml.cs @@ -21,9 +21,9 @@ public partial class NextClassDisplayComponent : ComponentBase Date: Wed, 3 Jun 2026 22:21:34 +0000 Subject: [PATCH 69/80] =?UTF-8?q?feat:=20=E6=82=AC=E6=B5=AE=E7=AA=97?= =?UTF-8?q?=E5=8A=9F=E8=83=BD=E5=A2=9E=E5=BC=BA=E4=B8=8E=E9=85=8D=E7=BD=AE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: traeagent --- .../FloatingWindowEditorSettingsPage.axaml | 79 ++++--------------- .../FloatingWindowEditorSettingsPage.axaml.cs | 63 ++++++++++----- 2 files changed, 59 insertions(+), 83 deletions(-) diff --git a/SettingsPage/FloatingWindowEditorSettingsPage.axaml b/SettingsPage/FloatingWindowEditorSettingsPage.axaml index 882e964..ee3077e 100644 --- a/SettingsPage/FloatingWindowEditorSettingsPage.axaml +++ b/SettingsPage/FloatingWindowEditorSettingsPage.axaml @@ -145,6 +145,13 @@ + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/SettingsPage/FloatingWindowEditorSettingsPage.axaml.cs b/SettingsPage/FloatingWindowEditorSettingsPage.axaml.cs index d922721..57031a2 100644 --- a/SettingsPage/FloatingWindowEditorSettingsPage.axaml.cs +++ b/SettingsPage/FloatingWindowEditorSettingsPage.axaml.cs @@ -258,6 +258,51 @@ private void OnRemoveTriggerFromRowClick(object? sender, RoutedEventArgs e) ViewModel.RemoveTriggerToPool(buttonId); } + /// + /// 按钮规则集按钮点击:打开该按钮的规则集 Drawer + /// + private void OnButtonRulesetClick(object? sender, RoutedEventArgs e) + { + if (sender is not Button button || button.Tag is not string buttonId) + return; + + // 在所有行中查找该按钮 + var item = ViewModel.FloatingTriggerRows + .SelectMany(r => r.Buttons) + .FirstOrDefault(b => b.ButtonId == buttonId); + if (item == null) return; + + ViewModel.SelectedFloatingTriggerItem = item; + + if (this.FindResource("RulesetControl") is not ClassIsland.Core.Controls.Ruleset.RulesetControl control) + return; + control.Ruleset = item.Config.HidingRules; + OpenDrawer("RulesetControl"); + } + + /// + /// 行规则集按钮点击:打开该行的规则集 Drawer + /// + private void OnRowRulesetClick(object? sender, RoutedEventArgs e) + { + if (sender is not Control control) + return; + + var row = control.GetVisualAncestors() + .OfType() + .Select(b => b.DataContext) + .OfType() + .FirstOrDefault(); + if (row == null) return; + + ViewModel.SelectedFloatingTriggerRow = row; + + if (this.FindResource("RulesetControl") is not ClassIsland.Core.Controls.Ruleset.RulesetControl rulesetControl) + return; + rulesetControl.Ruleset = row.RowRuleset.HidingRules; + OpenDrawer("RulesetControl"); + } + // ===== 选中状态处理 ===== private void OnRowSelectionChanged(object? sender, SelectionChangedEventArgs e) @@ -410,24 +455,6 @@ protected override void OnPointerReleased(PointerReleasedEventArgs e) // ===== 规则集 Drawer(参照 ClassIsland) ===== - private void ButtonOpenButtonRuleset_OnClick(object? sender, RoutedEventArgs e) - { - if (this.FindResource("RulesetControl") is not ClassIsland.Core.Controls.Ruleset.RulesetControl control - || ViewModel.SelectedFloatingTriggerItem == null) - return; - control.Ruleset = ViewModel.SelectedFloatingTriggerItem.Config.HidingRules; - OpenDrawer("RulesetControl"); - } - - private void ButtonOpenRowRuleset_OnClick(object? sender, RoutedEventArgs e) - { - if (this.FindResource("RulesetControl") is not ClassIsland.Core.Controls.Ruleset.RulesetControl control - || ViewModel.SelectedFloatingTriggerRow == null) - return; - control.Ruleset = ViewModel.SelectedFloatingTriggerRow.RowRuleset.HidingRules; - OpenDrawer("RulesetControl"); - } - private void ButtonOpenFloatingWindowRuleset_OnClick(object? sender, RoutedEventArgs e) { if (this.FindResource("RulesetControl") is not ClassIsland.Core.Controls.Ruleset.RulesetControl control) From 13fb9c19f95058c4aa272af2b05b991f5db041e8 Mon Sep 17 00:00:00 2001 From: ywydog Date: Thu, 4 Jun 2026 04:56:29 +0000 Subject: [PATCH 70/80] =?UTF-8?q?feat:=20=E6=82=AC=E6=B5=AE=E7=AA=97?= =?UTF-8?q?=E5=8A=9F=E8=83=BD=E5=A2=9E=E5=BC=BA=E4=B8=8E=E9=85=8D=E7=BD=AE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: traeagent --- .../FloatingWindowEditorSettingsPage.axaml.cs | 21 ++++++---- ...7\346\234\254\346\226\207\346\241\243.txt" | 41 +++++++++++++++++++ 2 files changed, 53 insertions(+), 9 deletions(-) create mode 100644 "\346\226\260\345\273\272\346\226\207\346\234\254\346\226\207\346\241\243.txt" diff --git a/SettingsPage/FloatingWindowEditorSettingsPage.axaml.cs b/SettingsPage/FloatingWindowEditorSettingsPage.axaml.cs index 57031a2..4502dab 100644 --- a/SettingsPage/FloatingWindowEditorSettingsPage.axaml.cs +++ b/SettingsPage/FloatingWindowEditorSettingsPage.axaml.cs @@ -323,18 +323,21 @@ private void OnButtonSelectionChanged(object? sender, SelectionChangedEventArgs private void OnAvailableItemSelectionChanged(object? sender, SelectionChangedEventArgs e) { - if (sender is ListBox listBox && listBox.SelectedItem is FloatingTriggerItem item) + if (sender is not ListBox listBox || listBox.SelectedItem is not FloatingTriggerItem item) { - // 点击组件库项:添加到第一行末尾 - if (ViewModel.FloatingTriggerRows.Count == 0) - { - ViewModel.AddFloatingTriggerRow(); - } - ViewModel.AddTriggerFromPool(item.ButtonId, 0, ViewModel.FloatingTriggerRows[0].Buttons.Count); + return; + } + + // 先清除选中状态,避免移除项时选择模型与集合冲突(ArgumentOutOfRangeException) + var buttonId = item.ButtonId; + listBox.SelectedItem = null; - // 清除选中状态 - listBox.SelectedItem = null; + // 点击组件库项:添加到第一行末尾 + if (ViewModel.FloatingTriggerRows.Count == 0) + { + ViewModel.AddFloatingTriggerRow(); } + ViewModel.AddTriggerFromPool(buttonId, 0, ViewModel.FloatingTriggerRows[0].Buttons.Count); } // ===== 拖拽处理(标准 Avalonia DragDrop) ===== diff --git "a/\346\226\260\345\273\272\346\226\207\346\234\254\346\226\207\346\241\243.txt" "b/\346\226\260\345\273\272\346\226\207\346\234\254\346\226\207\346\241\243.txt" new file mode 100644 index 0000000..ddddd62 --- /dev/null +++ "b/\346\226\260\345\273\272\346\226\207\346\234\254\346\226\207\346\241\243.txt" @@ -0,0 +1,41 @@ +򿪷ύʱ뱣Ϣ +TraceID: b273d7f1676845099d6d730214ad9657 +================================ +² ClassIsland ߷ǰ²Ŀ߷⣺ +- SystemTools [SystemTools,2.5.0.106] +================================ +System.ArgumentOutOfRangeException: Index was out of range. Must be non-negative and less than the size of the collection. (Parameter 'index') + at System.Collections.Generic.List`1.get_Item(Int32 index) + at Avalonia.Controls.ItemsSourceView`1.GetAt(Int32 index) + at Avalonia.Controls.Selection.SelectedItems`1.GetEnumerator()+MoveNext() + at System.Collections.Generic.LargeArrayBuilder`1.AddRange(IEnumerable`1 items) + at System.Collections.Generic.EnumerableHelpers.ToArray[T](IEnumerable`1 source) + at Avalonia.Controls.Selection.InternalSelectionModel.OnSelectionChanged(Object sender, SelectionModelSelectionChangedEventArgs e) + at Avalonia.Controls.Selection.SelectionModel`1.CommitOperation(Operation operation, Boolean raisePropertyChanged) + at Avalonia.Controls.Selection.SelectionModel`1.OnSelectionRemoved(Int32 index, Int32 count, IReadOnlyList`1 deselectedItems) + at Avalonia.Controls.Selection.SelectionNodeBase`1.OnSourceCollectionChanged(NotifyCollectionChangedEventArgs e) + at Avalonia.Controls.Selection.SelectionModel`1.OnSourceCollectionChanged(NotifyCollectionChangedEventArgs e) + at Avalonia.Controls.Utils.CollectionChangedEventManager.Entry..OnEvent>g__Notify|6_0(INotifyCollectionChanged incc, NotifyCollectionChangedEventArgs args, WeakReference`1[] listeners) + at Avalonia.Utilities.WeakEvent`2.Subscription.OnEvent(Object sender, TEventArgs eventArgs) + at System.Collections.ObjectModel.ObservableCollection`1.OnCollectionChanged(NotifyCollectionChangedEventArgs e) + at System.Collections.ObjectModel.Collection`1.Remove(T item) + at SystemTools.SystemToolsSettingsViewModel.AddTriggerFromPool(String buttonId, Int32 targetRowIndex, Int32 targetIndex) in D:\a\SystemTools\SystemTools\SettingsPage\SystemToolsSettingsViewModel.cs:line 625 + at SystemTools.FloatingWindowEditorSettingsPage.OnAvailableItemSelectionChanged(Object sender, SelectionChangedEventArgs e) in D:\a\SystemTools\SystemTools\SettingsPage\FloatingWindowEditorSettingsPage.axaml.cs:line 333 + at Avalonia.Interactivity.EventRoute.RaiseEventImpl(RoutedEventArgs e) + at Avalonia.Interactivity.EventRoute.RaiseEvent(Interactive source, RoutedEventArgs e) + at Avalonia.Interactivity.Interactive.RaiseEvent(RoutedEventArgs e) + at Avalonia.Controls.Primitives.SelectingItemsControl.OnSelectionModelSelectionChanged(Object sender, SelectionModelSelectionChangedEventArgs e) + at Avalonia.Controls.Selection.SelectionModel`1.CommitOperation(Operation operation, Boolean raisePropertyChanged) + at Avalonia.Controls.Selection.SelectionModelExtensions.BatchUpdateOperation.Dispose() + at Avalonia.Controls.Primitives.SelectingItemsControl.UpdateSelection(Int32 index, Boolean select, Boolean rangeModifier, Boolean toggleModifier, Boolean rightButton, Boolean fromFocus) + at Avalonia.Controls.Primitives.SelectingItemsControl.UpdateSelection(Control container, Boolean select, Boolean rangeModifier, Boolean toggleModifier, Boolean rightButton, Boolean fromFocus) + at Avalonia.Controls.Primitives.SelectingItemsControl.UpdateSelectionFromEventSource(Object eventSource, Boolean select, Boolean rangeModifier, Boolean toggleModifier, Boolean rightButton, Boolean fromFocus) + at Avalonia.Controls.ListBox.UpdateSelectionFromPointerEvent(Control source, PointerEventArgs e) + at Avalonia.Controls.ListBoxItem.OnPointerReleased(PointerReleasedEventArgs e) + at Avalonia.Reactive.LightweightObservableBase`1.PublishNext(T value) + at Avalonia.Interactivity.EventRoute.RaiseEventImpl(RoutedEventArgs e) + at Avalonia.Interactivity.EventRoute.RaiseEvent(Interactive source, RoutedEventArgs e) + at Avalonia.Interactivity.Interactive.RaiseEvent(RoutedEventArgs e) + at Avalonia.Input.TouchDevice.ProcessRawEvent(RawInputEventArgs ev) + at Avalonia.Controls.TopLevel.<>c.b__150_0(Object state) + at Avalonia.Threading.Dispatcher.Send(SendOrPostCallback action, Object arg, Nullable`1 priority) \ No newline at end of file From 956c0b7da6149eb764e4195fbadcf92ab9a40068 Mon Sep 17 00:00:00 2001 From: ywydog Date: Thu, 4 Jun 2026 05:12:36 +0000 Subject: [PATCH 71/80] =?UTF-8?q?feat:=20=E6=82=AC=E6=B5=AE=E7=AA=97?= =?UTF-8?q?=E5=8A=9F=E8=83=BD=E5=A2=9E=E5=BC=BA=E4=B8=8E=E9=85=8D=E7=BD=AE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: traeagent --- .../FloatingWindowEditorSettingsPage.axaml | 92 ++++++------ .../FloatingWindowEditorSettingsPage.axaml.cs | 136 +++++++++++++----- 2 files changed, 140 insertions(+), 88 deletions(-) diff --git a/SettingsPage/FloatingWindowEditorSettingsPage.axaml b/SettingsPage/FloatingWindowEditorSettingsPage.axaml index ee3077e..68ff5cf 100644 --- a/SettingsPage/FloatingWindowEditorSettingsPage.axaml +++ b/SettingsPage/FloatingWindowEditorSettingsPage.axaml @@ -76,7 +76,10 @@ CornerRadius="8" Padding="4" MinHeight="80" - x:Name="RowDropBorder"> + x:Name="RowDropBorder" + AllowDrop="True" + DragOver="OnRowDropBorderDragOver" + Drop="OnRowDropBorderDrop"> @@ -231,57 +233,47 @@ Background="Transparent" Margin="0,2" /> - + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/SettingsPage/FloatingWindowEditorSettingsPage.axaml.cs b/SettingsPage/FloatingWindowEditorSettingsPage.axaml.cs index 4502dab..360f253 100644 --- a/SettingsPage/FloatingWindowEditorSettingsPage.axaml.cs +++ b/SettingsPage/FloatingWindowEditorSettingsPage.axaml.cs @@ -93,7 +93,8 @@ protected override void OnDetachedFromVisualTree(VisualTreeAttachmentEventArgs e private void OnProfilePropertyChanged(object? sender, PropertyChangedEventArgs e) { - if (e.PropertyName is nameof(FloatingWindowProfile.FloatingWindowScale) + if (e.PropertyName is nameof(FloatingWindowProfile.ShowFloatingWindow) + or nameof(FloatingWindowProfile.FloatingWindowScale) or nameof(FloatingWindowProfile.FloatingWindowIconSize) or nameof(FloatingWindowProfile.FloatingWindowTextSize) or nameof(FloatingWindowProfile.FloatingWindowOpacity) @@ -208,12 +209,7 @@ private void OnInsertRowBelowClick(object? sender, RoutedEventArgs e) return; } - var row = control.GetVisualAncestors() - .OfType() - .Select(b => b.DataContext) - .OfType() - .FirstOrDefault(); - + var row = control.DataContext as FloatingTriggerRow; if (row == null) { return; @@ -274,6 +270,9 @@ private void OnButtonRulesetClick(object? sender, RoutedEventArgs e) ViewModel.SelectedFloatingTriggerItem = item; + // 自动启用按钮规则集,确保规则集生效 + item.Config.HideOnRule = true; + if (this.FindResource("RulesetControl") is not ClassIsland.Core.Controls.Ruleset.RulesetControl control) return; control.Ruleset = item.Config.HidingRules; @@ -288,15 +287,15 @@ private void OnRowRulesetClick(object? sender, RoutedEventArgs e) if (sender is not Control control) return; - var row = control.GetVisualAncestors() - .OfType() - .Select(b => b.DataContext) - .OfType() - .FirstOrDefault(); + // 通过 DataContext 获取所属行(比视觉树遍历更可靠) + var row = control.DataContext as FloatingTriggerRow; if (row == null) return; ViewModel.SelectedFloatingTriggerRow = row; + // 自动启用行规则集,确保规则集生效 + row.RowRuleset.HideOnRule = true; + if (this.FindResource("RulesetControl") is not ClassIsland.Core.Controls.Ruleset.RulesetControl rulesetControl) return; rulesetControl.Ruleset = row.RowRuleset.HidingRules; @@ -332,12 +331,17 @@ private void OnAvailableItemSelectionChanged(object? sender, SelectionChangedEve var buttonId = item.ButtonId; listBox.SelectedItem = null; - // 点击组件库项:添加到第一行末尾 - if (ViewModel.FloatingTriggerRows.Count == 0) + // 延迟执行添加操作,确保 SelectionChanged 事件处理完成后再修改集合 + // 否则 AvailableFloatingTriggerItems.Remove 会在选择模型迭代期间触发集合变更,导致 ArgumentOutOfRangeException + Dispatcher.UIThread.Post(() => { - ViewModel.AddFloatingTriggerRow(); - } - ViewModel.AddTriggerFromPool(buttonId, 0, ViewModel.FloatingTriggerRows[0].Buttons.Count); + // 点击组件库项:添加到第一行末尾 + if (ViewModel.FloatingTriggerRows.Count == 0) + { + ViewModel.AddFloatingTriggerRow(); + } + ViewModel.AddTriggerFromPool(buttonId, 0, ViewModel.FloatingTriggerRows[0].Buttons.Count); + }); } // ===== 拖拽处理(标准 Avalonia DragDrop) ===== @@ -349,12 +353,8 @@ private void OnRowDragThumbPointerPressed(object? sender, PointerPressedEventArg { if (sender is not Control control) return; - // 找到所属行 - var row = control.GetVisualAncestors() - .OfType() - .Select(b => b.DataContext) - .OfType() - .FirstOrDefault(); + // 通过 DataContext 获取所属行 + var row = control.DataContext as FloatingTriggerRow; if (row == null) return; _dragRow = row; @@ -373,12 +373,8 @@ private void OnButtonPointerPressed(object? sender, PointerPressedEventArgs e) { if (sender is not Control control || control.DataContext is not FloatingTriggerItem item) return; - // 找到所属行的 Buttons 集合 - var row = control.GetVisualAncestors() - .OfType() - .Select(b => b.DataContext) - .OfType() - .FirstOrDefault(); + // 通过 ViewModel 查找所属行的 Buttons 集合 + var row = ViewModel.FloatingTriggerRows.FirstOrDefault(r => r.Buttons.Contains(item)); if (row == null) return; _dragItem = item; @@ -395,15 +391,8 @@ private void OnButtonPointerPressed(object? sender, PointerPressedEventArgs e) /// private void OnPoolItemPointerPressed(object? sender, PointerPressedEventArgs e) { - if (sender is not Control control || control.DataContext is not FloatingTriggerItem item) return; - - _dragItem = item; - _dragSourceCollection = null; // null 表示来自组件库 - _dragRow = null; - _dragStartPoint = e.GetPosition(this); - _isDragging = false; - - e.Handled = true; + // 组件库现在通过 SelectionChanged 点击添加,不再需要拖拽 + // 保留此方法以避免编译错误,但不再执行拖拽逻辑 } protected override void OnPointerMoved(PointerEventArgs e) @@ -465,4 +454,75 @@ private void ButtonOpenFloatingWindowRuleset_OnClick(object? sender, RoutedEvent control.Ruleset = ViewModel.CurrentFloatingWindowProfile.FloatingWindowHidingRules; OpenDrawer("RulesetControl"); } + + // ===== 行区域拖放处理 ===== + + private void OnRowDropBorderDragOver(object? sender, DragEventArgs e) + { + if (e.Data.Contains("FloatingWindowButton")) + { + e.DragEffects = DragDropEffects.Move; + e.Handled = true; + } + else + { + e.DragEffects = DragDropEffects.None; + } + } + + private void OnRowDropBorderDrop(object? sender, DragEventArgs e) + { + if (!e.Data.Contains("FloatingWindowButton")) + return; + + var dragData = e.Data.Get("FloatingWindowButton") as FloatingWindowButtonDragData; + if (dragData?.Item == null) + return; + + e.Handled = true; + + // 确定目标行和位置 + if (ViewModel.FloatingTriggerRows.Count == 0) + { + ViewModel.AddFloatingTriggerRow(); + } + + var targetRowIndex = 0; + var targetIndex = ViewModel.FloatingTriggerRows[0].Buttons.Count; + + // 尝试确定更精确的放置位置 + if (sender is Control targetControl) + { + var pos = e.GetPosition(targetControl); + // 找到最近的行 + if (this.FindControl("ListBoxRows") is ListBox rowsList) + { + for (int i = 0; i < ViewModel.FloatingTriggerRows.Count; i++) + { + if (rowsList.ItemContainerGenerator.ContainerFromIndex(i) is ListBoxItem lbi) + { + var itemPos = lbi.TransformToVisual(rowsList).Value.Transform(new Point(0, 0)); + var itemBounds = lbi.Bounds; + if (pos.Y >= itemPos.Y && pos.Y <= itemPos.Y + itemBounds.Height) + { + targetRowIndex = i; + targetIndex = ViewModel.FloatingTriggerRows[i].Buttons.Count; + break; + } + } + } + } + } + + if (dragData.SourceCollection == null) + { + // 从组件库拖入 + ViewModel.AddTriggerFromPool(dragData.Item.ButtonId, targetRowIndex, targetIndex); + } + else + { + // 从其他行拖入 + ViewModel.MoveFloatingTrigger(dragData.Item.ButtonId, targetRowIndex, targetIndex); + } + } } From ad020b5a8dafe31ff6a0b543d161f47e594cfd67 Mon Sep 17 00:00:00 2001 From: ywydog Date: Thu, 4 Jun 2026 10:30:43 +0000 Subject: [PATCH 72/80] =?UTF-8?q?feat:=20=E6=82=AC=E6=B5=AE=E7=AA=97?= =?UTF-8?q?=E5=8A=9F=E8=83=BD=E5=A2=9E=E5=BC=BA=E4=B8=8E=E9=85=8D=E7=BD=AE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: traeagent --- SettingsPage/FloatingWindowEditorSettingsPage.axaml.cs | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/SettingsPage/FloatingWindowEditorSettingsPage.axaml.cs b/SettingsPage/FloatingWindowEditorSettingsPage.axaml.cs index 360f253..ca8798b 100644 --- a/SettingsPage/FloatingWindowEditorSettingsPage.axaml.cs +++ b/SettingsPage/FloatingWindowEditorSettingsPage.axaml.cs @@ -333,7 +333,7 @@ private void OnAvailableItemSelectionChanged(object? sender, SelectionChangedEve // 延迟执行添加操作,确保 SelectionChanged 事件处理完成后再修改集合 // 否则 AvailableFloatingTriggerItems.Remove 会在选择模型迭代期间触发集合变更,导致 ArgumentOutOfRangeException - Dispatcher.UIThread.Post(() => + Avalonia.Threading.Dispatcher.UIThread.Post(() => { // 点击组件库项:添加到第一行末尾 if (ViewModel.FloatingTriggerRows.Count == 0) @@ -499,9 +499,11 @@ private void OnRowDropBorderDrop(object? sender, DragEventArgs e) { for (int i = 0; i < ViewModel.FloatingTriggerRows.Count; i++) { - if (rowsList.ItemContainerGenerator.ContainerFromIndex(i) is ListBoxItem lbi) + if (rowsList.ContainerFromIndex(i) is ListBoxItem lbi) { - var itemPos = lbi.TransformToVisual(rowsList).Value.Transform(new Point(0, 0)); + var transform = lbi.TransformToVisual(rowsList); + if (transform == null) continue; + var itemPos = transform.Value.Transform(new Point(0, 0)); var itemBounds = lbi.Bounds; if (pos.Y >= itemPos.Y && pos.Y <= itemPos.Y + itemBounds.Height) { From f334f9f0dbd756616200fe1c822aa9ff9eebf8b8 Mon Sep 17 00:00:00 2001 From: ywydog Date: Thu, 4 Jun 2026 10:37:55 +0000 Subject: [PATCH 73/80] =?UTF-8?q?feat:=20=E6=82=AC=E6=B5=AE=E7=AA=97?= =?UTF-8?q?=E5=8A=9F=E8=83=BD=E5=A2=9E=E5=BC=BA=E4=B8=8E=E9=85=8D=E7=BD=AE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: traeagent --- SettingsPage/FloatingWindowEditorSettingsPage.axaml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/SettingsPage/FloatingWindowEditorSettingsPage.axaml b/SettingsPage/FloatingWindowEditorSettingsPage.axaml index 68ff5cf..ab9b468 100644 --- a/SettingsPage/FloatingWindowEditorSettingsPage.axaml +++ b/SettingsPage/FloatingWindowEditorSettingsPage.axaml @@ -77,9 +77,9 @@ Padding="4" MinHeight="80" x:Name="RowDropBorder" - AllowDrop="True" - DragOver="OnRowDropBorderDragOver" - Drop="OnRowDropBorderDrop"> + DragDrop.AllowDrop="True" + DragDrop.DragOver="OnRowDropBorderDragOver" + DragDrop.Drop="OnRowDropBorderDrop"> Date: Thu, 4 Jun 2026 11:00:19 +0000 Subject: [PATCH 74/80] =?UTF-8?q?feat:=20=E6=82=AC=E6=B5=AE=E7=AA=97?= =?UTF-8?q?=E5=8A=9F=E8=83=BD=E5=A2=9E=E5=BC=BA=E4=B8=8E=E9=85=8D=E7=BD=AE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: traeagent --- ConfigHandlers/ButtonRulesetConfig.cs | 7 +- ConfigHandlers/FloatingWindowProfile.cs | 3 +- ConfigHandlers/RowRulesetConfig.cs | 3 +- SettingsPage/FloatingWindowButtonDragData.cs | 49 --- SettingsPage/FloatingWindowDropHandler.cs | 118 ------ .../FloatingWindowEditorSettingsPage.axaml | 17 +- .../FloatingWindowEditorSettingsPage.axaml.cs | 353 ++++++++++++++---- SettingsPage/SystemToolsSettingsViewModel.cs | 33 +- 8 files changed, 324 insertions(+), 259 deletions(-) delete mode 100644 SettingsPage/FloatingWindowButtonDragData.cs delete mode 100644 SettingsPage/FloatingWindowDropHandler.cs diff --git a/ConfigHandlers/ButtonRulesetConfig.cs b/ConfigHandlers/ButtonRulesetConfig.cs index 767b216..9a04569 100644 --- a/ConfigHandlers/ButtonRulesetConfig.cs +++ b/ConfigHandlers/ButtonRulesetConfig.cs @@ -13,14 +13,11 @@ public partial class ButtonRulesetConfig : ObservableObject [JsonPropertyName("isVisible")] private bool _isVisible = true; - [ObservableProperty] - [JsonPropertyName("position")] - private int _position = -1; - [ObservableProperty] [JsonPropertyName("hideOnRule")] private bool _hideOnRule; + [ObservableProperty] [JsonPropertyName("hidingRules")] - public Ruleset HidingRules { get; set; } = new(); + private Ruleset _hidingRules = new(); } diff --git a/ConfigHandlers/FloatingWindowProfile.cs b/ConfigHandlers/FloatingWindowProfile.cs index 47a7c26..27b1e7b 100644 --- a/ConfigHandlers/FloatingWindowProfile.cs +++ b/ConfigHandlers/FloatingWindowProfile.cs @@ -73,8 +73,9 @@ public partial class FloatingWindowProfile : ObservableObject [JsonPropertyName("floatingWindowHideOnRule")] private bool _floatingWindowHideOnRule; + [ObservableProperty] [JsonPropertyName("floatingWindowHidingRules")] - public Ruleset FloatingWindowHidingRules { get; set; } = new(); + private Ruleset _floatingWindowHidingRules = new(); [JsonPropertyName("floatingWindowButtonRulesets")] public Dictionary FloatingWindowButtonRulesets { get; set; } = new(); diff --git a/ConfigHandlers/RowRulesetConfig.cs b/ConfigHandlers/RowRulesetConfig.cs index 5e7766b..64f157a 100644 --- a/ConfigHandlers/RowRulesetConfig.cs +++ b/ConfigHandlers/RowRulesetConfig.cs @@ -17,6 +17,7 @@ public partial class RowRulesetConfig : ObservableObject [JsonPropertyName("hideOnRule")] private bool _hideOnRule; + [ObservableProperty] [JsonPropertyName("hidingRules")] - public Ruleset HidingRules { get; set; } = new(); + private Ruleset _hidingRules = new(); } diff --git a/SettingsPage/FloatingWindowButtonDragData.cs b/SettingsPage/FloatingWindowButtonDragData.cs deleted file mode 100644 index ed35285..0000000 --- a/SettingsPage/FloatingWindowButtonDragData.cs +++ /dev/null @@ -1,49 +0,0 @@ -using System.Collections.ObjectModel; -using System.Linq; -using Avalonia; -using Avalonia.Data.Converters; - -namespace SystemTools; - -/// -/// 悬浮窗按钮拖拽数据,参照 ClassIsland 的 EditableComponentsListBoxDragData 设计 -/// 使用 MultiBinding + Create 转换器模式 -/// -public class FloatingWindowButtonDragData : AvaloniaObject -{ - public static readonly StyledProperty ItemProperty = - AvaloniaProperty.Register(nameof(Item)); - - public FloatingTriggerItem? Item - { - get => GetValue(ItemProperty); - set => SetValue(ItemProperty, value); - } - - public static readonly StyledProperty?> SourceCollectionProperty = - AvaloniaProperty.Register?>( - nameof(SourceCollection)); - - public ObservableCollection? SourceCollection - { - get => GetValue(SourceCollectionProperty); - set => SetValue(SourceCollectionProperty, value); - } - - /// - /// MultiBinding 转换器,与 ClassIsland 的 EditableComponentsListBoxDragData.Create 模式一致 - /// 绑定顺序:{Binding}(当前项), {Binding $parent[ListBox].ItemsSource}(源集合) - /// - public static FuncMultiValueConverter Create { get; } = new(o => - { - var l = o.ToList(); - if (l.Count < 2 || l[0] is not FloatingTriggerItem item - || l[1] is not ObservableCollection source) - return null; - return new FloatingWindowButtonDragData() - { - Item = item, - SourceCollection = source - }; - }); -} diff --git a/SettingsPage/FloatingWindowDropHandler.cs b/SettingsPage/FloatingWindowDropHandler.cs deleted file mode 100644 index c87c573..0000000 --- a/SettingsPage/FloatingWindowDropHandler.cs +++ /dev/null @@ -1,118 +0,0 @@ -using System.Collections.ObjectModel; -using System.Linq; -using Avalonia.Controls; -using Avalonia.Input; -using Avalonia.VisualTree; - -namespace SystemTools; - -/// -/// 悬浮窗按钮拖放处理器 -/// 处理按钮在行内排序、跨行移动、从组件库添加 -/// 使用标准 Avalonia DragDrop,不依赖 ClassIsland 的 ManagedDragDrop -/// -public class FloatingWindowDropHandler -{ - private readonly FloatingWindowEditorSettingsPage _page; - - public FloatingWindowDropHandler(FloatingWindowEditorSettingsPage page) - { - _page = page; - } - - /// - /// 处理按钮拖放到行内 - /// - public bool HandleButtonDrop(ListBox targetListBox, DragEventArgs e, - FloatingWindowButtonDragData dragData, ObservableCollection targetList) - { - var viewModel = _page.ViewModel; - if (viewModel == null) return false; - - var (targetIndex, foundTargetIndex) = GetTargetIndex(targetListBox, e, targetList); - var insertIndex = foundTargetIndex ? targetIndex + 1 : targetList.Count; - - // 找到目标行 - var targetRow = viewModel.FloatingTriggerRows.FirstOrDefault(r => r.Buttons == targetList); - if (targetRow == null) return false; - var rowIndex = viewModel.FloatingTriggerRows.IndexOf(targetRow); - - if (dragData.SourceCollection == null) - { - // 从组件库添加 - if (dragData.Item == null) return false; - var isInRow = viewModel.FloatingTriggerRows.Any(r => r.Buttons.Any(b => b.ButtonId == dragData.Item.ButtonId)); - if (isInRow) return false; - viewModel.AddTriggerFromPool(dragData.Item.ButtonId, rowIndex, insertIndex); - } - else if (!ReferenceEquals(dragData.SourceCollection, targetList)) - { - // 跨行移动 - if (dragData.Item == null) return false; - viewModel.MoveFloatingTrigger(dragData.Item.ButtonId, rowIndex, insertIndex); - } - else - { - // 行内排序 - if (dragData.Item == null) return false; - var sourceIndex = targetList.IndexOf(dragData.Item); - if (sourceIndex < 0) return false; - var moveIndex = foundTargetIndex ? targetIndex : targetList.Count - 1; - var newIndex = sourceIndex > moveIndex ? moveIndex + 1 : moveIndex; - MoveItem(targetList, sourceIndex, System.Math.Clamp(newIndex, 0, targetList.Count - 1)); - viewModel.PersistFloatingTriggerRows(); - } - - return true; - } - - /// - /// 处理从组件库拖入的按钮(SourceCollection 为 null) - /// - public bool HandlePoolItemDrop(ListBox targetListBox, DragEventArgs e, - FloatingTriggerItem poolItem, ObservableCollection targetList) - { - var viewModel = _page.ViewModel; - if (viewModel == null) return false; - - var isInRow = viewModel.FloatingTriggerRows.Any(r => r.Buttons.Any(b => b.ButtonId == poolItem.ButtonId)); - if (isInRow) return false; - - var (targetIndex, foundTargetIndex) = GetTargetIndex(targetListBox, e, targetList); - var insertIndex = foundTargetIndex ? targetIndex + 1 : targetList.Count; - - var targetRow = viewModel.FloatingTriggerRows.FirstOrDefault(r => r.Buttons == targetList); - if (targetRow == null) return false; - var rowIndex = viewModel.FloatingTriggerRows.IndexOf(targetRow); - - viewModel.AddTriggerFromPool(poolItem.ButtonId, rowIndex, insertIndex); - return true; - } - - private static (int index, bool found) GetTargetIndex(ListBox listBox, DragEventArgs e, - ObservableCollection items) - { - var pos = e.GetPosition(listBox); - if (listBox.GetVisualAt(pos) is Control targetControl - && targetControl.FindAncestorOfType() is { } listBoxItem - && listBoxItem.DataContext is FloatingTriggerItem targetItem) - { - var rPos = e.GetPosition(listBoxItem); - var index = items.IndexOf(targetItem); - if (index >= 0) - { - return (rPos.X <= listBoxItem.Bounds.Width / 2 ? index - 1 : index, true); - } - } - - return (items.Count > 0 ? items.Count - 1 : -1, items.Count > 0); - } - - private static void MoveItem(ObservableCollection list, int oldIndex, int newIndex) - { - if (oldIndex == newIndex) return; - var item = list[oldIndex]; - list.RemoveAt(oldIndex); - list.Insert(newIndex, item); - } -} diff --git a/SettingsPage/FloatingWindowEditorSettingsPage.axaml b/SettingsPage/FloatingWindowEditorSettingsPage.axaml index ab9b468..2f26e18 100644 --- a/SettingsPage/FloatingWindowEditorSettingsPage.axaml +++ b/SettingsPage/FloatingWindowEditorSettingsPage.axaml @@ -12,9 +12,15 @@ d:DesignHeight="800" d:DesignWidth="800"> - + - + + + + + + + @@ -105,7 +111,7 @@ x:Name="ListBoxRows" ItemsSource="{Binding ViewModel.FloatingTriggerRows}" SelectedItem="{Binding ViewModel.SelectedFloatingTriggerRow, Mode=TwoWay}" - IsVisible="{Binding ViewModel.FloatingTriggerRows.Count}" + IsVisible="{Binding ViewModel.HasFloatingTriggerEntries}" Background="Transparent" Margin="-4" SelectionChanged="OnRowSelectionChanged"> @@ -125,11 +131,14 @@ VerticalAlignment="Center" PointerPressed="OnRowDragThumbPointerPressed" /> - + diff --git a/SettingsPage/FloatingWindowEditorSettingsPage.axaml.cs b/SettingsPage/FloatingWindowEditorSettingsPage.axaml.cs index ca8798b..97476b1 100644 --- a/SettingsPage/FloatingWindowEditorSettingsPage.axaml.cs +++ b/SettingsPage/FloatingWindowEditorSettingsPage.axaml.cs @@ -12,6 +12,7 @@ using ClassIsland.Core.Abstractions; using ClassIsland.Core.Abstractions.Controls; using ClassIsland.Core.Attributes; +using ClassIsland.Core.Controls.Ruleset; using ClassIsland.Shared; using SystemTools.ConfigHandlers; using SystemTools.Services; @@ -37,8 +38,6 @@ public FloatingWindowEditorSettingsPage() DataContext = this; InitializeComponent(); - DropHandler = new FloatingWindowDropHandler(this); - ViewModel.RefreshFloatingWindowProfiles(); ViewModel.RefreshFloatingTriggers(); ViewModel.CurrentFloatingWindowProfile.PropertyChanged += OnProfilePropertyChanged; @@ -46,14 +45,10 @@ public FloatingWindowEditorSettingsPage() ViewModel.ProfileChanged += OnViewModelProfileChanged; // 注册悬浮窗规则集变更监听 - if (ViewModel.CurrentFloatingWindowProfile.FloatingWindowHidingRules is INotifyPropertyChanged hidingRules) - { - hidingRules.PropertyChanged += OnHidingRulesPropertyChanged; - } + RegisterHidingRulesEvents(); } public SystemToolsSettingsViewModel ViewModel { get; } - public FloatingWindowDropHandler DropHandler { get; } private bool _isDisposed; @@ -63,7 +58,14 @@ public FloatingWindowEditorSettingsPage() private FloatingTriggerRow? _dragRow; private bool _isDragging; private Point _dragStartPoint; - private const double DragThreshold = 5.0; + // 触摸屏需要更大阈值,避免误触拖拽 + private const double DragThreshold = 8.0; + + // ===== 规则集 Drawer 状态 ===== + private enum RulesetTargetType { Button, Row, Window } + private RulesetTargetType _currentRulesetTarget; + private FloatingTriggerItem? _currentButtonTarget; + private FloatingTriggerRow? _currentRowTarget; protected override void OnDetachedFromVisualTree(VisualTreeAttachmentEventArgs e) { @@ -81,11 +83,7 @@ protected override void OnDetachedFromVisualTree(VisualTreeAttachmentEventArgs e ViewModel.Settings.PropertyChanged -= OnSettingsPropertyChanged; ViewModel.ProfileChanged -= OnViewModelProfileChanged; - // 注销悬浮窗规则集变更监听 - if (ViewModel.CurrentFloatingWindowProfile.FloatingWindowHidingRules is INotifyPropertyChanged hidingRules) - { - hidingRules.PropertyChanged -= OnHidingRulesPropertyChanged; - } + UnregisterHidingRulesEvents(); ViewModel.Dispose(); _isDisposed = true; @@ -108,6 +106,13 @@ or nameof(FloatingWindowProfile.FloatingWindowHorizontal)) IAppHost.GetService().ProfileManager.SaveProfile(); IAppHost.GetService().UpdateWindowState(); } + else if (e.PropertyName == nameof(FloatingWindowProfile.FloatingWindowHidingRules)) + { + // Ruleset 对象被替换时,重新注册事件 + UnregisterHidingRulesEvents(); + RegisterHidingRulesEvents(); + IAppHost.GetService().ProfileManager.SaveProfile(); + } } /// @@ -119,13 +124,26 @@ public void ReattachProfilePropertyChanged() ViewModel.CurrentFloatingWindowProfile.PropertyChanged += OnProfilePropertyChanged; // 重新注册悬浮窗规则集变更监听 + UnregisterHidingRulesEvents(); + RegisterHidingRulesEvents(); + } + + private void RegisterHidingRulesEvents() + { if (ViewModel.CurrentFloatingWindowProfile.FloatingWindowHidingRules is INotifyPropertyChanged hidingRules) { - hidingRules.PropertyChanged -= OnHidingRulesPropertyChanged; hidingRules.PropertyChanged += OnHidingRulesPropertyChanged; } } + private void UnregisterHidingRulesEvents() + { + if (ViewModel.CurrentFloatingWindowProfile.FloatingWindowHidingRules is INotifyPropertyChanged hidingRules) + { + hidingRules.PropertyChanged -= OnHidingRulesPropertyChanged; + } + } + private void OnSettingsPropertyChanged(object? sender, PropertyChangedEventArgs e) { if (e.PropertyName is nameof(MainConfigData.FloatingWindowTheme)) @@ -254,6 +272,8 @@ private void OnRemoveTriggerFromRowClick(object? sender, RoutedEventArgs e) ViewModel.RemoveTriggerToPool(buttonId); } + // ===== 规则集 Drawer(参照 ClassIsland,含 IsVisible/HideOnRule 开关) ===== + /// /// 按钮规则集按钮点击:打开该按钮的规则集 Drawer /// @@ -269,14 +289,11 @@ private void OnButtonRulesetClick(object? sender, RoutedEventArgs e) if (item == null) return; ViewModel.SelectedFloatingTriggerItem = item; + _currentRulesetTarget = RulesetTargetType.Button; + _currentButtonTarget = item; + _currentRowTarget = null; - // 自动启用按钮规则集,确保规则集生效 - item.Config.HideOnRule = true; - - if (this.FindResource("RulesetControl") is not ClassIsland.Core.Controls.Ruleset.RulesetControl control) - return; - control.Ruleset = item.Config.HidingRules; - OpenDrawer("RulesetControl"); + OpenRulesetDrawer(item.Config.HidingRules, item.Config.IsVisible, item.Config.HideOnRule); } /// @@ -287,19 +304,103 @@ private void OnRowRulesetClick(object? sender, RoutedEventArgs e) if (sender is not Control control) return; - // 通过 DataContext 获取所属行(比视觉树遍历更可靠) + // 通过 DataContext 获取所属行 var row = control.DataContext as FloatingTriggerRow; if (row == null) return; ViewModel.SelectedFloatingTriggerRow = row; + _currentRulesetTarget = RulesetTargetType.Row; + _currentRowTarget = row; + _currentButtonTarget = null; + + OpenRulesetDrawer(row.RowRuleset.HidingRules, row.RowRuleset.IsVisible, row.RowRuleset.HideOnRule); + } + + private void ButtonOpenFloatingWindowRuleset_OnClick(object? sender, RoutedEventArgs e) + { + _currentRulesetTarget = RulesetTargetType.Window; + _currentButtonTarget = null; + _currentRowTarget = null; - // 自动启用行规则集,确保规则集生效 - row.RowRuleset.HideOnRule = true; + var profile = ViewModel.CurrentFloatingWindowProfile; + OpenRulesetDrawer(profile.FloatingWindowHidingRules, true, profile.FloatingWindowHideOnRule); + } - if (this.FindResource("RulesetControl") is not ClassIsland.Core.Controls.Ruleset.RulesetControl rulesetControl) + /// + /// 打开规则集 Drawer,包含 IsVisible/HideOnRule 开关和规则集编辑器 + /// + private void OpenRulesetDrawer(ClassIsland.Core.Models.Ruleset.Ruleset ruleset, bool isVisible, bool hideOnRule) + { + if (this.FindResource("RulesetDrawerContent") is not StackPanel panel) return; - rulesetControl.Ruleset = row.RowRuleset.HidingRules; - OpenDrawer("RulesetControl"); + + // 找到 Drawer 内的控件 + if (panel.Children.Count >= 2 + && panel.Children[0] is StackPanel togglesPanel + && panel.Children[1] is RulesetControl rulesetControl) + { + // 设置 IsVisible 开关 + if (togglesPanel.Children.Count >= 2 + && togglesPanel.Children[0] is ToggleSwitch isVisibleToggle + && togglesPanel.Children[1] is ToggleSwitch hideOnRuleToggle) + { + isVisibleToggle.IsChecked = isVisible; + isVisibleToggle.IsCheckedChanged -= OnDrawerIsVisibleChanged; + isVisibleToggle.IsCheckedChanged += OnDrawerIsVisibleChanged; + + hideOnRuleToggle.IsChecked = hideOnRule; + hideOnRuleToggle.IsCheckedChanged -= OnDrawerHideOnRuleChanged; + hideOnRuleToggle.IsCheckedChanged += OnDrawerHideOnRuleChanged; + + // 按钮和行才显示 IsVisible 开关,悬浮窗级别隐藏 + isVisibleToggle.IsVisible = _currentRulesetTarget != RulesetTargetType.Window; + } + + rulesetControl.Ruleset = ruleset; + } + + OpenDrawer("RulesetDrawerContent"); + } + + private void OnDrawerIsVisibleChanged(object? sender, RoutedEventArgs e) + { + if (sender is not ToggleSwitch toggle) return; + var value = toggle.IsChecked == true; + + switch (_currentRulesetTarget) + { + case RulesetTargetType.Button when _currentButtonTarget != null: + _currentButtonTarget.Config.IsVisible = value; + break; + case RulesetTargetType.Row when _currentRowTarget != null: + _currentRowTarget.RowRuleset.IsVisible = value; + break; + } + + IAppHost.GetService().ProfileManager.SaveProfile(); + IAppHost.GetService().UpdateWindowState(); + } + + private void OnDrawerHideOnRuleChanged(object? sender, RoutedEventArgs e) + { + if (sender is not ToggleSwitch toggle) return; + var value = toggle.IsChecked == true; + + switch (_currentRulesetTarget) + { + case RulesetTargetType.Button when _currentButtonTarget != null: + _currentButtonTarget.Config.HideOnRule = value; + break; + case RulesetTargetType.Row when _currentRowTarget != null: + _currentRowTarget.RowRuleset.HideOnRule = value; + break; + case RulesetTargetType.Window: + ViewModel.CurrentFloatingWindowProfile.FloatingWindowHideOnRule = value; + break; + } + + IAppHost.GetService().ProfileManager.SaveProfile(); + IAppHost.GetService().UpdateWindowState(); } // ===== 选中状态处理 ===== @@ -386,15 +487,6 @@ private void OnButtonPointerPressed(object? sender, PointerPressedEventArgs e) e.Handled = true; } - /// - /// 组件库项按下:开始从组件库拖拽 - /// - private void OnPoolItemPointerPressed(object? sender, PointerPressedEventArgs e) - { - // 组件库现在通过 SelectionChanged 点击添加,不再需要拖拽 - // 保留此方法以避免编译错误,但不再执行拖拽逻辑 - } - protected override void OnPointerMoved(PointerEventArgs e) { base.OnPointerMoved(e); @@ -422,11 +514,8 @@ protected override void OnPointerMoved(PointerEventArgs e) { // 按钮拖拽(行内/跨行/从组件库) var data = new DataObject(); - data.Set("FloatingWindowButton", new FloatingWindowButtonDragData - { - Item = _dragItem, - SourceCollection = _dragSourceCollection - }); + data.Set("FloatingWindowButtonId", _dragItem.ButtonId); + data.Set("FloatingWindowButtonSource", _dragSourceCollection); DragDrop.DoDragDrop(e, data, DragDropEffects.Move); } @@ -445,21 +534,11 @@ protected override void OnPointerReleased(PointerReleasedEventArgs e) _isDragging = false; } - // ===== 规则集 Drawer(参照 ClassIsland) ===== - - private void ButtonOpenFloatingWindowRuleset_OnClick(object? sender, RoutedEventArgs e) - { - if (this.FindResource("RulesetControl") is not ClassIsland.Core.Controls.Ruleset.RulesetControl control) - return; - control.Ruleset = ViewModel.CurrentFloatingWindowProfile.FloatingWindowHidingRules; - OpenDrawer("RulesetControl"); - } - // ===== 行区域拖放处理 ===== private void OnRowDropBorderDragOver(object? sender, DragEventArgs e) { - if (e.Data.Contains("FloatingWindowButton")) + if (e.Data.Contains("FloatingWindowButtonId") || e.Data.Contains("FloatingWindowRow")) { e.DragEffects = DragDropEffects.Move; e.Handled = true; @@ -472,14 +551,42 @@ private void OnRowDropBorderDragOver(object? sender, DragEventArgs e) private void OnRowDropBorderDrop(object? sender, DragEventArgs e) { - if (!e.Data.Contains("FloatingWindowButton")) - return; + e.Handled = true; - var dragData = e.Data.Get("FloatingWindowButton") as FloatingWindowButtonDragData; - if (dragData?.Item == null) + // 处理行拖拽排序 + if (e.Data.Contains("FloatingWindowRow")) + { + var sourceRow = e.Data.Get("FloatingWindowRow") as FloatingTriggerRow; + if (sourceRow == null) return; + + var targetIndex = FindTargetRowIndex(e, sender as Control); + if (targetIndex < 0) return; + + var sourceIndex = ViewModel.FloatingTriggerRows.IndexOf(sourceRow); + if (sourceIndex < 0 || sourceIndex == targetIndex) return; + + // 移动行 + ViewModel.FloatingTriggerRows.RemoveAt(sourceIndex); + if (targetIndex > sourceIndex) targetIndex--; + ViewModel.FloatingTriggerRows.Insert(targetIndex, sourceRow); + + // 重新计算行索引 + for (int i = 0; i < ViewModel.FloatingTriggerRows.Count; i++) + { + ViewModel.FloatingTriggerRows[i].RowIndex = i + 1; + } + + ViewModel.PersistFloatingTriggerRows(); return; + } - e.Handled = true; + // 处理按钮拖拽 + if (!e.Data.Contains("FloatingWindowButtonId")) return; + + var buttonId = e.Data.Get("FloatingWindowButtonId") as string; + if (string.IsNullOrEmpty(buttonId)) return; + + var sourceCollection = e.Data.Get("FloatingWindowButtonSource") as ObservableCollection; // 确定目标行和位置 if (ViewModel.FloatingTriggerRows.Count == 0) @@ -487,44 +594,130 @@ private void OnRowDropBorderDrop(object? sender, DragEventArgs e) ViewModel.AddFloatingTriggerRow(); } - var targetRowIndex = 0; - var targetIndex = ViewModel.FloatingTriggerRows[0].Buttons.Count; + var targetRowIndex = FindTargetRowIndex(e, sender as Control); + if (targetRowIndex < 0) targetRowIndex = 0; + targetRowIndex = System.Math.Clamp(targetRowIndex, 0, ViewModel.FloatingTriggerRows.Count - 1); + var targetIndex = ViewModel.FloatingTriggerRows[targetRowIndex].Buttons.Count; + + if (sourceCollection == null) + { + // 从组件库拖入 + ViewModel.AddTriggerFromPool(buttonId, targetRowIndex, targetIndex); + } + else + { + // 从其他行拖入 + ViewModel.MoveFloatingTrigger(buttonId, targetRowIndex, targetIndex); + } + } + + /// + /// 根据拖放位置确定目标行索引 + /// + private int FindTargetRowIndex(DragEventArgs e, Control? targetControl) + { + if (targetControl == null) return -1; + + var pos = e.GetPosition(targetControl); + if (this.FindControl("ListBoxRows") is not ListBox rowsList) + return -1; + + for (int i = 0; i < ViewModel.FloatingTriggerRows.Count; i++) + { + if (rowsList.ContainerFromIndex(i) is ListBoxItem lbi) + { + var transform = lbi.TransformToVisual(rowsList); + if (transform == null) continue; + var itemPos = transform.Value.Transform(new Point(0, 0)); + var itemBounds = lbi.Bounds; + if (pos.Y >= itemPos.Y && pos.Y <= itemPos.Y + itemBounds.Height) + { + return i; + } + } + } + + return -1; + } + + // ===== 行内按钮拖放处理 ===== + + private void OnInnerButtonDragOver(object? sender, DragEventArgs e) + { + if (e.Data.Contains("FloatingWindowButtonId")) + { + e.DragEffects = DragDropEffects.Move; + e.Handled = true; + } + else + { + e.DragEffects = DragDropEffects.None; + } + } + + private void OnInnerButtonDrop(object? sender, DragEventArgs e) + { + if (!e.Data.Contains("FloatingWindowButtonId")) return; + + var buttonId = e.Data.Get("FloatingWindowButtonId") as string; + if (string.IsNullOrEmpty(buttonId)) return; + + var sourceCollection = e.Data.Get("FloatingWindowButtonSource") as ObservableCollection; + + // 通过 DataContext 获取目标行 + if (sender is not Control targetControl) return; + var targetRow = targetControl.DataContext as FloatingTriggerRow; + if (targetRow == null) return; - // 尝试确定更精确的放置位置 - if (sender is Control targetControl) + var targetRowIndex = ViewModel.FloatingTriggerRows.IndexOf(targetRow); + if (targetRowIndex < 0) return; + + // 尝试确定精确的插入位置 + var targetIndex = targetRow.Buttons.Count; + if (sender is ListBox listBox) { - var pos = e.GetPosition(targetControl); - // 找到最近的行 - if (this.FindControl("ListBoxRows") is ListBox rowsList) + var pos = e.GetPosition(listBox); + for (int i = 0; i < targetRow.Buttons.Count; i++) { - for (int i = 0; i < ViewModel.FloatingTriggerRows.Count; i++) + if (listBox.ContainerFromIndex(i) is ListBoxItem lbi) { - if (rowsList.ContainerFromIndex(i) is ListBoxItem lbi) + var itemPos = lbi.TransformToVisual(listBox).Value.Transform(new Point(0, 0)); + var itemBounds = lbi.Bounds; + if (pos.X >= itemPos.X && pos.X <= itemPos.X + itemBounds.Width / 2) + { + targetIndex = i; + break; + } + if (pos.X <= itemPos.X + itemBounds.Width && i == targetRow.Buttons.Count - 1) { - var transform = lbi.TransformToVisual(rowsList); - if (transform == null) continue; - var itemPos = transform.Value.Transform(new Point(0, 0)); - var itemBounds = lbi.Bounds; - if (pos.Y >= itemPos.Y && pos.Y <= itemPos.Y + itemBounds.Height) - { - targetRowIndex = i; - targetIndex = ViewModel.FloatingTriggerRows[i].Buttons.Count; - break; - } + targetIndex = i + 1; } } } } - if (dragData.SourceCollection == null) + e.Handled = true; + + if (sourceCollection == null) { // 从组件库拖入 - ViewModel.AddTriggerFromPool(dragData.Item.ButtonId, targetRowIndex, targetIndex); + ViewModel.AddTriggerFromPool(buttonId, targetRowIndex, targetIndex); + } + else if (!ReferenceEquals(sourceCollection, targetRow.Buttons)) + { + // 跨行移动 + ViewModel.MoveFloatingTrigger(buttonId, targetRowIndex, targetIndex); } else { - // 从其他行拖入 - ViewModel.MoveFloatingTrigger(dragData.Item.ButtonId, targetRowIndex, targetIndex); + // 行内排序 + var item = targetRow.Buttons.FirstOrDefault(b => b.ButtonId == buttonId); + if (item == null) return; + var sourceIndex = targetRow.Buttons.IndexOf(item); + if (sourceIndex < 0 || sourceIndex == targetIndex) return; + + targetRow.Buttons.Move(sourceIndex, System.Math.Clamp(targetIndex, 0, targetRow.Buttons.Count - 1)); + ViewModel.PersistFloatingTriggerRows(); } } } diff --git a/SettingsPage/SystemToolsSettingsViewModel.cs b/SettingsPage/SystemToolsSettingsViewModel.cs index f806e37..957b209 100644 --- a/SettingsPage/SystemToolsSettingsViewModel.cs +++ b/SettingsPage/SystemToolsSettingsViewModel.cs @@ -543,6 +543,7 @@ public bool RemoveFloatingTriggerRow(FloatingTriggerRow row) var targetRow = index > 0 ? FloatingTriggerRows[index - 1] : FloatingTriggerRows[index + 1]; foreach (var item in row.Buttons) { + // 按钮的 Config 事件监听保持不变(对象引用不变,事件仍有效) targetRow.Buttons.Add(item); } @@ -623,6 +624,21 @@ public bool AddTriggerFromPool(string buttonId, int targetRowIndex, int targetIn targetIndex = Math.Clamp(targetIndex, 0, destinationRow.Buttons.Count); AvailableFloatingTriggerItems.Remove(poolItem); + + // 确保按钮有 Config(池项可能没有),并注册事件监听 + var profile = CurrentFloatingWindowProfile; + if (!profile.FloatingWindowButtonRulesets.TryGetValue(buttonId, out var btnConfig)) + { + btnConfig = new ButtonRulesetConfig(); + profile.FloatingWindowButtonRulesets[buttonId] = btnConfig; + } + poolItem.Config = btnConfig; + poolItem.Config.PropertyChanged += OnButtonConfigPropertyChanged; + if (poolItem.Config.HidingRules is INotifyPropertyChanged btnHidingRules) + { + btnHidingRules.PropertyChanged += OnButtonConfigPropertyChanged; + } + destinationRow.Buttons.Insert(targetIndex, poolItem); PersistFloatingTriggerRows(); return true; @@ -703,7 +719,22 @@ public void PersistFloatingTriggerRows(bool updateWindow = true, bool forceSave // 同步每行的 RowRuleset 引用(确保ViewModel中的修改反映到profile) for (int i = 0; i < FloatingTriggerRows.Count; i++) { - FloatingTriggerRows[i].RowRuleset = rowRulesets[i]; + var vmRow = FloatingTriggerRows[i]; + if (!ReferenceEquals(vmRow.RowRuleset, rowRulesets[i])) + { + // RowRuleset 引用变更时,重新注册事件 + vmRow.RowRuleset.PropertyChanged -= OnRowRulesetPropertyChanged; + if (vmRow.RowRuleset.HidingRules is INotifyPropertyChanged oldHidingRules) + { + oldHidingRules.PropertyChanged -= OnRowRulesetPropertyChanged; + } + vmRow.RowRuleset = rowRulesets[i]; + vmRow.RowRuleset.PropertyChanged += OnRowRulesetPropertyChanged; + if (vmRow.RowRuleset.HidingRules is INotifyPropertyChanged newHidingRules) + { + newHidingRules.PropertyChanged += OnRowRulesetPropertyChanged; + } + } } // 清理不再使用的按钮规则集配置 From 3d79b40ee4e1d21e262629a05de2b8032175f87d Mon Sep 17 00:00:00 2001 From: ywydog Date: Thu, 4 Jun 2026 11:04:35 +0000 Subject: [PATCH 75/80] =?UTF-8?q?feat:=20=E6=82=AC=E6=B5=AE=E7=AA=97?= =?UTF-8?q?=E5=8A=9F=E8=83=BD=E5=A2=9E=E5=BC=BA=E4=B8=8E=E9=85=8D=E7=BD=AE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: traeagent --- .../FloatingWindowEditorSettingsPage.axaml | 8 --- .../FloatingWindowEditorSettingsPage.axaml.cs | 64 +++++++++++-------- 2 files changed, 37 insertions(+), 35 deletions(-) diff --git a/SettingsPage/FloatingWindowEditorSettingsPage.axaml b/SettingsPage/FloatingWindowEditorSettingsPage.axaml index 2f26e18..b6c71ec 100644 --- a/SettingsPage/FloatingWindowEditorSettingsPage.axaml +++ b/SettingsPage/FloatingWindowEditorSettingsPage.axaml @@ -12,15 +12,7 @@ d:DesignHeight="800" d:DesignWidth="800"> - - - - - - - - diff --git a/SettingsPage/FloatingWindowEditorSettingsPage.axaml.cs b/SettingsPage/FloatingWindowEditorSettingsPage.axaml.cs index 97476b1..8cdc913 100644 --- a/SettingsPage/FloatingWindowEditorSettingsPage.axaml.cs +++ b/SettingsPage/FloatingWindowEditorSettingsPage.axaml.cs @@ -67,6 +67,11 @@ private enum RulesetTargetType { Button, Row, Window } private FloatingTriggerItem? _currentButtonTarget; private FloatingTriggerRow? _currentRowTarget; + // Drawer 内的控件引用 + private ToggleSwitch? _drawerIsVisibleToggle; + private ToggleSwitch? _drawerHideOnRuleToggle; + private RulesetControl? _drawerRulesetControl; + protected override void OnDetachedFromVisualTree(VisualTreeAttachmentEventArgs e) { base.OnDetachedFromVisualTree(e); @@ -327,45 +332,51 @@ private void ButtonOpenFloatingWindowRuleset_OnClick(object? sender, RoutedEvent } /// - /// 打开规则集 Drawer,包含 IsVisible/HideOnRule 开关和规则集编辑器 + /// 打开规则集 Drawer,包含 IsVisible/HideOnRule 开关和规则集编辑器(参照 ClassIsland) /// private void OpenRulesetDrawer(ClassIsland.Core.Models.Ruleset.Ruleset ruleset, bool isVisible, bool hideOnRule) { - if (this.FindResource("RulesetDrawerContent") is not StackPanel panel) - return; + // 每次打开时动态构建 Drawer 内容,避免资源单例问题 + var panel = new StackPanel { Spacing = 8, Margin = new Thickness(0, 8, 0, 0) }; - // 找到 Drawer 内的控件 - if (panel.Children.Count >= 2 - && panel.Children[0] is StackPanel togglesPanel - && panel.Children[1] is RulesetControl rulesetControl) + // 开关面板 + var togglesPanel = new StackPanel { Orientation = Orientation.Horizontal, Spacing = 16, Margin = new Thickness(0, 0, 0, 8) }; + + _drawerIsVisibleToggle = new ToggleSwitch { - // 设置 IsVisible 开关 - if (togglesPanel.Children.Count >= 2 - && togglesPanel.Children[0] is ToggleSwitch isVisibleToggle - && togglesPanel.Children[1] is ToggleSwitch hideOnRuleToggle) - { - isVisibleToggle.IsChecked = isVisible; - isVisibleToggle.IsCheckedChanged -= OnDrawerIsVisibleChanged; - isVisibleToggle.IsCheckedChanged += OnDrawerIsVisibleChanged; + OnContent = "显示", + OffContent = "隐藏", + ToolTip.Tip = "控制此项目是否显示", + IsChecked = isVisible, + IsVisible = _currentRulesetTarget != RulesetTargetType.Window + }; + _drawerIsVisibleToggle.IsCheckedChanged += OnDrawerIsVisibleChanged; - hideOnRuleToggle.IsChecked = hideOnRule; - hideOnRuleToggle.IsCheckedChanged -= OnDrawerHideOnRuleChanged; - hideOnRuleToggle.IsCheckedChanged += OnDrawerHideOnRuleChanged; + _drawerHideOnRuleToggle = new ToggleSwitch + { + OnContent = "按规则隐藏", + OffContent = "禁用规则", + ToolTip.Tip = "启用后,满足规则集条件时自动隐藏", + IsChecked = hideOnRule + }; + _drawerHideOnRuleToggle.IsCheckedChanged += OnDrawerHideOnRuleChanged; - // 按钮和行才显示 IsVisible 开关,悬浮窗级别隐藏 - isVisibleToggle.IsVisible = _currentRulesetTarget != RulesetTargetType.Window; - } + togglesPanel.Children.Add(_drawerIsVisibleToggle); + togglesPanel.Children.Add(_drawerHideOnRuleToggle); + panel.Children.Add(togglesPanel); - rulesetControl.Ruleset = ruleset; - } + // 规则集编辑器 + _drawerRulesetControl = new RulesetControl { Classes = { "in-drawer" }, Ruleset = ruleset }; + panel.Children.Add(_drawerRulesetControl); + // 将内容放入 Resources 并打开 Drawer + this.Resources["RulesetDrawerContent"] = panel; OpenDrawer("RulesetDrawerContent"); } private void OnDrawerIsVisibleChanged(object? sender, RoutedEventArgs e) { - if (sender is not ToggleSwitch toggle) return; - var value = toggle.IsChecked == true; + var value = _drawerIsVisibleToggle?.IsChecked == true; switch (_currentRulesetTarget) { @@ -383,8 +394,7 @@ private void OnDrawerIsVisibleChanged(object? sender, RoutedEventArgs e) private void OnDrawerHideOnRuleChanged(object? sender, RoutedEventArgs e) { - if (sender is not ToggleSwitch toggle) return; - var value = toggle.IsChecked == true; + var value = _drawerHideOnRuleToggle?.IsChecked == true; switch (_currentRulesetTarget) { From dd90a6e9f0d577f8790ea5ec9e4cb2c185190390 Mon Sep 17 00:00:00 2001 From: ywydog Date: Thu, 4 Jun 2026 11:09:56 +0000 Subject: [PATCH 76/80] =?UTF-8?q?feat:=20=E6=82=AC=E6=B5=AE=E7=AA=97?= =?UTF-8?q?=E5=8A=9F=E8=83=BD=E5=A2=9E=E5=BC=BA=E4=B8=8E=E9=85=8D=E7=BD=AE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: traeagent --- .../FloatingWindowEditorSettingsPage.axaml.cs | 28 ++++++++++--------- 1 file changed, 15 insertions(+), 13 deletions(-) diff --git a/SettingsPage/FloatingWindowEditorSettingsPage.axaml.cs b/SettingsPage/FloatingWindowEditorSettingsPage.axaml.cs index 8cdc913..560a516 100644 --- a/SettingsPage/FloatingWindowEditorSettingsPage.axaml.cs +++ b/SettingsPage/FloatingWindowEditorSettingsPage.axaml.cs @@ -340,25 +340,25 @@ private void OpenRulesetDrawer(ClassIsland.Core.Models.Ruleset.Ruleset ruleset, var panel = new StackPanel { Spacing = 8, Margin = new Thickness(0, 8, 0, 0) }; // 开关面板 - var togglesPanel = new StackPanel { Orientation = Orientation.Horizontal, Spacing = 16, Margin = new Thickness(0, 0, 0, 8) }; + var togglesPanel = new StackPanel { Orientation = Avalonia.Layout.Orientation.Horizontal, Spacing = 16, Margin = new Thickness(0, 0, 0, 8) }; _drawerIsVisibleToggle = new ToggleSwitch { OnContent = "显示", OffContent = "隐藏", - ToolTip.Tip = "控制此项目是否显示", IsChecked = isVisible, IsVisible = _currentRulesetTarget != RulesetTargetType.Window }; + ToolTip.SetTip(_drawerIsVisibleToggle, "控制此项目是否显示"); _drawerIsVisibleToggle.IsCheckedChanged += OnDrawerIsVisibleChanged; _drawerHideOnRuleToggle = new ToggleSwitch { OnContent = "按规则隐藏", OffContent = "禁用规则", - ToolTip.Tip = "启用后,满足规则集条件时自动隐藏", IsChecked = hideOnRule }; + ToolTip.SetTip(_drawerHideOnRuleToggle, "启用后,满足规则集条件时自动隐藏"); _drawerHideOnRuleToggle.IsCheckedChanged += OnDrawerHideOnRuleChanged; togglesPanel.Children.Add(_drawerIsVisibleToggle); @@ -525,7 +525,7 @@ protected override void OnPointerMoved(PointerEventArgs e) // 按钮拖拽(行内/跨行/从组件库) var data = new DataObject(); data.Set("FloatingWindowButtonId", _dragItem.ButtonId); - data.Set("FloatingWindowButtonSource", _dragSourceCollection); + data.Set("FloatingWindowButtonSource", _dragSourceCollection!); DragDrop.DoDragDrop(e, data, DragDropEffects.Move); } @@ -569,16 +569,16 @@ private void OnRowDropBorderDrop(object? sender, DragEventArgs e) var sourceRow = e.Data.Get("FloatingWindowRow") as FloatingTriggerRow; if (sourceRow == null) return; - var targetIndex = FindTargetRowIndex(e, sender as Control); - if (targetIndex < 0) return; + var rowTargetIndex = FindTargetRowIndex(e, sender as Control); + if (rowTargetIndex < 0) return; var sourceIndex = ViewModel.FloatingTriggerRows.IndexOf(sourceRow); - if (sourceIndex < 0 || sourceIndex == targetIndex) return; + if (sourceIndex < 0 || sourceIndex == rowTargetIndex) return; // 移动行 ViewModel.FloatingTriggerRows.RemoveAt(sourceIndex); - if (targetIndex > sourceIndex) targetIndex--; - ViewModel.FloatingTriggerRows.Insert(targetIndex, sourceRow); + if (rowTargetIndex > sourceIndex) rowTargetIndex--; + ViewModel.FloatingTriggerRows.Insert(rowTargetIndex, sourceRow); // 重新计算行索引 for (int i = 0; i < ViewModel.FloatingTriggerRows.Count; i++) @@ -607,17 +607,17 @@ private void OnRowDropBorderDrop(object? sender, DragEventArgs e) var targetRowIndex = FindTargetRowIndex(e, sender as Control); if (targetRowIndex < 0) targetRowIndex = 0; targetRowIndex = System.Math.Clamp(targetRowIndex, 0, ViewModel.FloatingTriggerRows.Count - 1); - var targetIndex = ViewModel.FloatingTriggerRows[targetRowIndex].Buttons.Count; + var btnTargetIndex = ViewModel.FloatingTriggerRows[targetRowIndex].Buttons.Count; if (sourceCollection == null) { // 从组件库拖入 - ViewModel.AddTriggerFromPool(buttonId, targetRowIndex, targetIndex); + ViewModel.AddTriggerFromPool(buttonId, targetRowIndex, btnTargetIndex); } else { // 从其他行拖入 - ViewModel.MoveFloatingTrigger(buttonId, targetRowIndex, targetIndex); + ViewModel.MoveFloatingTrigger(buttonId, targetRowIndex, btnTargetIndex); } } @@ -691,7 +691,9 @@ private void OnInnerButtonDrop(object? sender, DragEventArgs e) { if (listBox.ContainerFromIndex(i) is ListBoxItem lbi) { - var itemPos = lbi.TransformToVisual(listBox).Value.Transform(new Point(0, 0)); + var transform = lbi.TransformToVisual(listBox); + if (transform == null) continue; + var itemPos = transform.Value.Transform(new Point(0, 0)); var itemBounds = lbi.Bounds; if (pos.X >= itemPos.X && pos.X <= itemPos.X + itemBounds.Width / 2) { From 8461cabe271e067f202d3f4c84ebed1819e5c6a9 Mon Sep 17 00:00:00 2001 From: ywydog Date: Thu, 4 Jun 2026 11:32:33 +0000 Subject: [PATCH 77/80] =?UTF-8?q?feat:=20=E6=82=AC=E6=B5=AE=E7=AA=97?= =?UTF-8?q?=E5=8A=9F=E8=83=BD=E5=A2=9E=E5=BC=BA=E4=B8=8E=E9=85=8D=E7=BD=AE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: traeagent --- Actions/ShowFloatingWindowAction.cs | 11 +++-- ConfigHandlers/FloatingWindowProfile.cs | 16 +------ .../FloatingWindowProfileManager.cs | 5 --- Plugin.cs | 13 +++--- Services/FloatingWindowService.cs | 6 +-- .../FloatingWindowEditorSettingsPage.axaml | 10 ++--- .../FloatingWindowEditorSettingsPage.axaml.cs | 45 ++++++++++--------- SettingsPage/SystemToolsSettingsViewModel.cs | 7 +-- 8 files changed, 55 insertions(+), 58 deletions(-) diff --git a/Actions/ShowFloatingWindowAction.cs b/Actions/ShowFloatingWindowAction.cs index 14fe77e..1bb98f8 100644 --- a/Actions/ShowFloatingWindowAction.cs +++ b/Actions/ShowFloatingWindowAction.cs @@ -5,6 +5,7 @@ using Microsoft.Extensions.Logging; using SystemTools.Services; using SystemTools.Settings; +using SystemTools.Shared; namespace SystemTools.Actions; @@ -23,7 +24,7 @@ protected override async Task OnInvoke() try { var shouldShow = Settings.ShowFloatingWindow; - var profile = _floatingWindowService.ProfileManager.CurrentProfile; + var config = GlobalConstants.MainConfig?.Data; // 如果没有可用的悬浮窗组件,则强制隐藏且不允许显示 if (_floatingWindowService.Entries.Count == 0) @@ -32,8 +33,12 @@ protected override async Task OnInvoke() _logger.LogDebug("没有可用的悬浮窗组件,强制隐藏悬浮窗"); } - profile.ShowFloatingWindow = shouldShow; - _floatingWindowService.ProfileManager.SaveProfile(); + if (config != null) + { + config.ShowFloatingWindow = shouldShow; + GlobalConstants.MainConfig?.Save(); + } + _floatingWindowService.UpdateWindowState(); _logger.LogInformation("悬浮窗状态已更新为: {State}", shouldShow ? "开启" : "关闭"); diff --git a/ConfigHandlers/FloatingWindowProfile.cs b/ConfigHandlers/FloatingWindowProfile.cs index 27b1e7b..3a4f88a 100644 --- a/ConfigHandlers/FloatingWindowProfile.cs +++ b/ConfigHandlers/FloatingWindowProfile.cs @@ -1,13 +1,13 @@ using System.Collections.Generic; using System.Linq; using System.Text.Json.Serialization; -using ClassIsland.Core.Models.Ruleset; using CommunityToolkit.Mvvm.ComponentModel; namespace SystemTools.ConfigHandlers; /// -/// 悬浮窗配置方案,保存一套完整的悬浮窗配置。 +/// 悬浮窗配置方案,保存一套完整的悬浮窗布局和外观配置。 +/// 注意:显示状态(ShowFloatingWindow)和规则集(HideOnRule/HidingRules)是全局设置,不随方案切换。 /// public partial class FloatingWindowProfile : ObservableObject { @@ -15,10 +15,6 @@ public partial class FloatingWindowProfile : ObservableObject [JsonPropertyName("name")] private string _name = "Default"; - [ObservableProperty] - [JsonPropertyName("showFloatingWindow")] - private bool _showFloatingWindow = true; - [ObservableProperty] [JsonPropertyName("floatingWindowHorizontal")] private bool _floatingWindowHorizontal; @@ -69,14 +65,6 @@ public partial class FloatingWindowProfile : ObservableObject [JsonPropertyName("floatingWindowDragHandleAlwaysVisible")] private bool _floatingWindowDragHandleAlwaysVisible; - [ObservableProperty] - [JsonPropertyName("floatingWindowHideOnRule")] - private bool _floatingWindowHideOnRule; - - [ObservableProperty] - [JsonPropertyName("floatingWindowHidingRules")] - private Ruleset _floatingWindowHidingRules = new(); - [JsonPropertyName("floatingWindowButtonRulesets")] public Dictionary FloatingWindowButtonRulesets { get; set; } = new(); diff --git a/ConfigHandlers/FloatingWindowProfileManager.cs b/ConfigHandlers/FloatingWindowProfileManager.cs index d64c810..caa0895 100644 --- a/ConfigHandlers/FloatingWindowProfileManager.cs +++ b/ConfigHandlers/FloatingWindowProfileManager.cs @@ -19,7 +19,6 @@ public class FloatingWindowProfileManager public static FloatingWindowProfile DefaultProfile { get; } = new() { Name = "Default", - ShowFloatingWindow = true, FloatingWindowScale = 1.0, FloatingWindowIconSize = 22, FloatingWindowTextSize = 12, @@ -31,7 +30,6 @@ public class FloatingWindowProfileManager FloatingWindowShadowEnabled = true, FloatingWindowButtonOrder = new List(), FloatingWindowButtonRows = new List>(), - FloatingWindowHidingRules = new(), FloatingWindowButtonRulesets = new Dictionary(), FloatingWindowRowRulesets = new List() }; @@ -59,7 +57,6 @@ public void MigrateFromLegacyConfig(MainConfigData legacyData) var profile = new FloatingWindowProfile { Name = "Default", - ShowFloatingWindow = legacyData.ShowFloatingWindow, FloatingWindowHorizontal = legacyData.FloatingWindowHorizontal, FloatingWindowButtonOrder = new List(legacyData.FloatingWindowButtonOrder ?? []), FloatingWindowButtonRows = (legacyData.FloatingWindowButtonRows ?? []).Select(r => new List(r)).ToList(), @@ -73,8 +70,6 @@ public void MigrateFromLegacyConfig(MainConfigData legacyData) FloatingWindowLayerRecheckMode = legacyData.FloatingWindowLayerRecheckMode, FloatingWindowShadowEnabled = legacyData.FloatingWindowShadowEnabled, FloatingWindowDragHandleAlwaysVisible = legacyData.FloatingWindowDragHandleAlwaysVisible, - FloatingWindowHideOnRule = legacyData.FloatingWindowRulesetEnabled, - FloatingWindowHidingRules = legacyData.FloatingWindowRuleset, FloatingWindowButtonRulesets = new Dictionary(legacyData.FloatingWindowButtonRulesets ?? []), FloatingWindowRowRulesets = new List(legacyData.FloatingWindowRowRulesets ?? []) }; diff --git a/Plugin.cs b/Plugin.cs index 0e12b9d..18b7be3 100644 --- a/Plugin.cs +++ b/Plugin.cs @@ -946,9 +946,12 @@ private void RegisterOrUpdateFloatingWindowTrayMenu() return; } - var profile = IAppHost.GetService().ProfileManager.CurrentProfile; - profile.ShowFloatingWindow = !profile.ShowFloatingWindow; - IAppHost.GetService().ProfileManager.SaveProfile(); + var config = GlobalConstants.MainConfig?.Data; + if (config != null) + { + config.ShowFloatingWindow = !config.ShowFloatingWindow; + GlobalConstants.MainConfig?.Save(); + } IAppHost.GetService().UpdateWindowState(); UpdateFloatingWindowTrayMenuHeader(); }; @@ -1008,8 +1011,8 @@ private void UpdateFloatingWindowTrayMenuHeader() return; } - var profile = IAppHost.GetService().ProfileManager.CurrentProfile; - _toggleFloatingWindowMenuItem.Header = profile.ShowFloatingWindow + var config = GlobalConstants.MainConfig?.Data; + _toggleFloatingWindowMenuItem.Header = config is { ShowFloatingWindow: true } ? "隐藏悬浮窗" : "显示悬浮窗"; } diff --git a/Services/FloatingWindowService.cs b/Services/FloatingWindowService.cs index b52fd94..d79b867 100644 --- a/Services/FloatingWindowService.cs +++ b/Services/FloatingWindowService.cs @@ -393,7 +393,7 @@ private void OnRulesetStatusUpdated(object? sender, EventArgs e) private void CheckFloatingWindowRuleset() { var profile = _profileManager.CurrentProfile; - if (!profile.FloatingWindowHideOnRule) + if (!_configHandler.Data.FloatingWindowRulesetEnabled) { if (_rulesetHidingWindow) { @@ -409,7 +409,7 @@ private void CheckFloatingWindowRuleset() return; } - var isSatisfied = rulesetService.IsRulesetSatisfied(profile.FloatingWindowHidingRules); + var isSatisfied = rulesetService.IsRulesetSatisfied(_configHandler.Data.FloatingWindowRuleset); var shouldHide = isSatisfied; if (shouldHide != _rulesetHidingWindow) @@ -531,7 +531,7 @@ private void ApplyVisibility() } var profile = _profileManager.CurrentProfile; - var shouldShow = profile.ShowFloatingWindow && _entries.Count > 0 && !_rulesetHidingWindow; + var shouldShow = _configHandler.Data.ShowFloatingWindow && _entries.Count > 0 && !_rulesetHidingWindow; if (shouldShow) { diff --git a/SettingsPage/FloatingWindowEditorSettingsPage.axaml b/SettingsPage/FloatingWindowEditorSettingsPage.axaml index b6c71ec..873ad99 100644 --- a/SettingsPage/FloatingWindowEditorSettingsPage.axaml +++ b/SettingsPage/FloatingWindowEditorSettingsPage.axaml @@ -56,7 +56,7 @@ @@ -443,21 +443,21 @@ - + + Description="在指定规则满足时,自动隐藏悬浮窗。此为全局设置,不随配置方案切换。"> + IsChecked="{Binding ViewModel.Settings.FloatingWindowRulesetEnabled}" /> diff --git a/SettingsPage/FloatingWindowEditorSettingsPage.axaml.cs b/SettingsPage/FloatingWindowEditorSettingsPage.axaml.cs index 560a516..02d69dc 100644 --- a/SettingsPage/FloatingWindowEditorSettingsPage.axaml.cs +++ b/SettingsPage/FloatingWindowEditorSettingsPage.axaml.cs @@ -44,7 +44,7 @@ public FloatingWindowEditorSettingsPage() ViewModel.Settings.PropertyChanged += OnSettingsPropertyChanged; ViewModel.ProfileChanged += OnViewModelProfileChanged; - // 注册悬浮窗规则集变更监听 + // 注册全局设置变更监听(ShowFloatingWindow 和规则集不随方案切换) RegisterHidingRulesEvents(); } @@ -96,28 +96,19 @@ protected override void OnDetachedFromVisualTree(VisualTreeAttachmentEventArgs e private void OnProfilePropertyChanged(object? sender, PropertyChangedEventArgs e) { - if (e.PropertyName is nameof(FloatingWindowProfile.ShowFloatingWindow) - or nameof(FloatingWindowProfile.FloatingWindowScale) + if (e.PropertyName is nameof(FloatingWindowProfile.FloatingWindowScale) or nameof(FloatingWindowProfile.FloatingWindowIconSize) or nameof(FloatingWindowProfile.FloatingWindowTextSize) or nameof(FloatingWindowProfile.FloatingWindowOpacity) or nameof(FloatingWindowProfile.FloatingWindowShadowEnabled) or nameof(FloatingWindowProfile.FloatingWindowLayer) or nameof(FloatingWindowProfile.FloatingWindowLayerRecheckMode) - or nameof(FloatingWindowProfile.FloatingWindowHideOnRule) or nameof(FloatingWindowProfile.FloatingWindowDragHandleAlwaysVisible) or nameof(FloatingWindowProfile.FloatingWindowHorizontal)) { IAppHost.GetService().ProfileManager.SaveProfile(); IAppHost.GetService().UpdateWindowState(); } - else if (e.PropertyName == nameof(FloatingWindowProfile.FloatingWindowHidingRules)) - { - // Ruleset 对象被替换时,重新注册事件 - UnregisterHidingRulesEvents(); - RegisterHidingRulesEvents(); - IAppHost.GetService().ProfileManager.SaveProfile(); - } } /// @@ -135,7 +126,7 @@ public void ReattachProfilePropertyChanged() private void RegisterHidingRulesEvents() { - if (ViewModel.CurrentFloatingWindowProfile.FloatingWindowHidingRules is INotifyPropertyChanged hidingRules) + if (ViewModel.Settings.FloatingWindowRuleset is INotifyPropertyChanged hidingRules) { hidingRules.PropertyChanged += OnHidingRulesPropertyChanged; } @@ -143,7 +134,7 @@ private void RegisterHidingRulesEvents() private void UnregisterHidingRulesEvents() { - if (ViewModel.CurrentFloatingWindowProfile.FloatingWindowHidingRules is INotifyPropertyChanged hidingRules) + if (ViewModel.Settings.FloatingWindowRuleset is INotifyPropertyChanged hidingRules) { hidingRules.PropertyChanged -= OnHidingRulesPropertyChanged; } @@ -156,6 +147,19 @@ private void OnSettingsPropertyChanged(object? sender, PropertyChangedEventArgs GlobalConstants.MainConfig?.Save(); IAppHost.GetService().UpdateWindowState(); } + else if (e.PropertyName is nameof(MainConfigData.ShowFloatingWindow) + or nameof(MainConfigData.FloatingWindowRulesetEnabled)) + { + GlobalConstants.MainConfig?.Save(); + IAppHost.GetService().UpdateWindowState(); + } + else if (e.PropertyName == nameof(MainConfigData.FloatingWindowRuleset)) + { + // Ruleset 对象被替换时,重新注册事件 + UnregisterHidingRulesEvents(); + RegisterHidingRulesEvents(); + GlobalConstants.MainConfig?.Save(); + } } private void OnViewModelProfileChanged(object? sender, EventArgs e) @@ -165,7 +169,7 @@ private void OnViewModelProfileChanged(object? sender, EventArgs e) private void OnHidingRulesPropertyChanged(object? sender, PropertyChangedEventArgs e) { - IAppHost.GetService().ProfileManager.SaveProfile(); + GlobalConstants.MainConfig?.Save(); } private void OnFloatingWindowVisibleToggleChanged(object? sender, RoutedEventArgs e) @@ -176,11 +180,11 @@ private void OnFloatingWindowVisibleToggleChanged(object? sender, RoutedEventArg } var service = IAppHost.GetService(); - var profile = ViewModel.CurrentFloatingWindowProfile; + var config = ViewModel.Settings; // 没有可用按钮时强制隐藏 var shouldShow = toggle.IsChecked == true && service.Entries.Count > 0; - profile.ShowFloatingWindow = shouldShow; + config.ShowFloatingWindow = shouldShow; // 同步 ToggleSwitch 状态(可能被强制隐藏) if (toggle.IsChecked != shouldShow) @@ -188,7 +192,7 @@ private void OnFloatingWindowVisibleToggleChanged(object? sender, RoutedEventArg toggle.IsChecked = shouldShow; } - service.ProfileManager.SaveProfile(); + GlobalConstants.MainConfig?.Save(); service.UpdateWindowState(); } @@ -327,8 +331,8 @@ private void ButtonOpenFloatingWindowRuleset_OnClick(object? sender, RoutedEvent _currentButtonTarget = null; _currentRowTarget = null; - var profile = ViewModel.CurrentFloatingWindowProfile; - OpenRulesetDrawer(profile.FloatingWindowHidingRules, true, profile.FloatingWindowHideOnRule); + var config = ViewModel.Settings; + OpenRulesetDrawer(config.FloatingWindowRuleset, true, config.FloatingWindowRulesetEnabled); } /// @@ -405,7 +409,8 @@ private void OnDrawerHideOnRuleChanged(object? sender, RoutedEventArgs e) _currentRowTarget.RowRuleset.HideOnRule = value; break; case RulesetTargetType.Window: - ViewModel.CurrentFloatingWindowProfile.FloatingWindowHideOnRule = value; + ViewModel.Settings.FloatingWindowRulesetEnabled = value; + GlobalConstants.MainConfig?.Save(); break; } diff --git a/SettingsPage/SystemToolsSettingsViewModel.cs b/SettingsPage/SystemToolsSettingsViewModel.cs index 957b209..9ea9c7d 100644 --- a/SettingsPage/SystemToolsSettingsViewModel.cs +++ b/SettingsPage/SystemToolsSettingsViewModel.cs @@ -309,10 +309,11 @@ public void RefreshFloatingTriggers() HasFloatingTriggerEntries = entries.Count > 0; var profile = CurrentFloatingWindowProfile; - if (!HasFloatingTriggerEntries && profile.ShowFloatingWindow) + var globalShow = _configHandler.Data.ShowFloatingWindow; + if (!HasFloatingTriggerEntries && globalShow) { - profile.ShowFloatingWindow = false; - _floatingWindowService.ProfileManager.SaveProfile(); + _configHandler.Data.ShowFloatingWindow = false; + _configHandler.Save(); _floatingWindowService.UpdateWindowState(); } From 53ba59d55782d6ade221388d5d73e46cb4726ccc Mon Sep 17 00:00:00 2001 From: ywydog Date: Thu, 4 Jun 2026 13:32:16 +0000 Subject: [PATCH 78/80] =?UTF-8?q?feat:=20=E6=82=AC=E6=B5=AE=E7=AA=97?= =?UTF-8?q?=E5=8A=9F=E8=83=BD=E5=A2=9E=E5=BC=BA=E4=B8=8E=E9=85=8D=E7=BD=AE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: traeagent --- Services/FloatingWindowService.cs | 36 ++++++++++++++++++++++--------- 1 file changed, 26 insertions(+), 10 deletions(-) diff --git a/Services/FloatingWindowService.cs b/Services/FloatingWindowService.cs index d79b867..39a7228 100644 --- a/Services/FloatingWindowService.cs +++ b/Services/FloatingWindowService.cs @@ -53,6 +53,7 @@ public class FloatingWindowService private readonly Dictionary _buttonWidthCache = new(); private bool _allowWindowClose; private bool _restoringFromMinimized; + private bool _isStopped; private bool _isTouchDeviceDetected; private bool _touchDragAllowed; private PixelPoint _touchDragStartScreenPoint; @@ -119,6 +120,7 @@ public void Start() public void Stop() { + _isStopped = true; Dispatcher.UIThread.Post(() => { if (_window != null) @@ -164,8 +166,10 @@ public void UnregisterTrigger(FloatingWindowTrigger trigger) public void UpdateWindowState() { + if (_isStopped) return; Dispatcher.UIThread.Post(() => { + if (_isStopped) return; ApplyVisibility(); RefreshLayerRecheckMode(); RecheckWindowLayer(); @@ -176,8 +180,10 @@ public void UpdateWindowState() private void NotifyEntriesChanged() { EntriesChanged?.Invoke(this, EventArgs.Empty); + if (_isStopped) return; Dispatcher.UIThread.Post(() => { + if (_isStopped) return; ApplyVisibility(); RecheckWindowLayer(); RefreshWindowButtons(); @@ -258,7 +264,7 @@ public void ToggleWindowTheme() private void EnsureWindow() { - if (_window != null) + if (_window != null || _isStopped) { return; } @@ -293,10 +299,7 @@ private void EnsureWindow() if (!_allowWindowClose) { e.Cancel = true; - if (_window is { IsVisible: false }) - { - _window.Show(); - } + // 不在 Closing 事件中调用 Show(),窗口可能处于关闭过程中 } }; _window.PropertyChanged += OnWindowPropertyChanged; @@ -319,7 +322,7 @@ private void OnWindowPropertyChanged(object? sender, AvaloniaPropertyChangedEven private void RestoreWindowFromMinimized() { - if (_window == null || _restoringFromMinimized) + if (_window == null || _restoringFromMinimized || _isStopped) { return; } @@ -330,17 +333,26 @@ private void RestoreWindowFromMinimized() { try { - if (_window == null) + if (_window == null || _isStopped) { return; } if (!_window.IsVisible) { - _window.Show(); + try { _window.Show(); } + catch (InvalidOperationException) + { + _window = null; + _stackPanel = null; + _windowContainer = null; + } } - _window.WindowState = WindowState.Normal; + if (_window != null) + { + _window.WindowState = WindowState.Normal; + } } finally { @@ -524,6 +536,7 @@ private void CheckRowRulesets() private void ApplyVisibility() { + if (_isStopped) return; EnsureWindow(); if (_window == null) { @@ -543,13 +556,16 @@ private void ApplyVisibility() } catch (InvalidOperationException) { + // 窗口已关闭(被外部关闭或竞态条件),需要重建 _window = null; _stackPanel = null; _windowContainer = null; + if (_isStopped) return; EnsureWindow(); if (_window != null) { - _window.Show(); + try { _window.Show(); } + catch (InvalidOperationException) { /* 放弃重建 */ } } } } From d16421248fb12c01d34758ec2739ba1931beb1a2 Mon Sep 17 00:00:00 2001 From: ywydog Date: Fri, 5 Jun 2026 04:38:31 +0000 Subject: [PATCH 79/80] =?UTF-8?q?feat:=20=E6=82=AC=E6=B5=AE=E7=AA=97?= =?UTF-8?q?=E5=8A=9F=E8=83=BD=E5=A2=9E=E5=BC=BA=E4=B8=8E=E9=85=8D=E7=BD=AE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: traeagent --- .../FloatingWindowEditorSettingsPage.axaml | 19 +++-- .../FloatingWindowEditorSettingsPage.axaml.cs | 78 +++---------------- 2 files changed, 21 insertions(+), 76 deletions(-) diff --git a/SettingsPage/FloatingWindowEditorSettingsPage.axaml b/SettingsPage/FloatingWindowEditorSettingsPage.axaml index 873ad99..e2fad1b 100644 --- a/SettingsPage/FloatingWindowEditorSettingsPage.axaml +++ b/SettingsPage/FloatingWindowEditorSettingsPage.axaml @@ -114,12 +114,11 @@ - + @@ -141,23 +140,23 @@ - +