diff --git a/Ink Canvas/Plugins/PluginManager.cs b/Ink Canvas/Plugins/PluginManager.cs index 7ea4be82d..c9ccb7ea5 100644 --- a/Ink Canvas/Plugins/PluginManager.cs +++ b/Ink Canvas/Plugins/PluginManager.cs @@ -61,6 +61,7 @@ private readonly Dictionary _registrationScopes // 而是设置 _boardToolbarRebuildPending,由加载完成后统一重建一次。 private bool _isLoadingBatch; private bool _boardToolbarRebuildPending; + private bool _floatingToolbarRebuildPending; // 卸载后仍在等待 GC 完成回收的 ALC 弱引用,用于校验是否真正卸载成功。 private readonly Dictionary _unloadingContexts @@ -373,8 +374,8 @@ public async Task LoadAllAsync() _market?.RefreshMergedPlugins(); Log(string.Format("Plugin loading complete. Loaded {0} plugins", _plugins.Count(p => p.LoadStatus == PluginLoadStatus.Loaded))); - // 批量加载期间累积的白板工具栏重建请求,在此统一排队一次。 - FlushPendingBoardToolbarRebuild(); + // 批量加载期间累积的工具栏(浮动栏与白板)重建请求,在此统一排队一次。 + FlushPendingToolbarRebuild(); } catch (Exception ex) { @@ -421,8 +422,8 @@ public IReadOnlyList InstallPendingPackages(string approvedPackagePath = if (installedIds.Count > 0) { - // 批量加载期间累积的白板工具栏重建请求,在此统一排队一次。 - FlushPendingBoardToolbarRebuild(); + // 批量加载期间累积的工具栏(浮动栏与白板)重建请求,在此统一排队一次。 + FlushPendingToolbarRebuild(); try { @@ -2040,25 +2041,36 @@ internal void BuildServiceProvider() } /// - /// 批量加载结束后,若有插件注册了白板工具栏项,排队一次 。 + /// 批量加载结束后,若有插件注册了白板或浮动工具栏项,排队触发重建。 /// 避免每个插件组件都单独排队重建,与授权弹窗嵌套消息循环交错时引发 UI 渲染错乱。 /// - private void FlushPendingBoardToolbarRebuild() + private void FlushPendingToolbarRebuild() { - if (!_boardToolbarRebuildPending) return; + if (!_boardToolbarRebuildPending && !_floatingToolbarRebuildPending) return; + bool rebuildBoard = _boardToolbarRebuildPending; + bool rebuildFloating = _floatingToolbarRebuildPending; _boardToolbarRebuildPending = false; + _floatingToolbarRebuildPending = false; try { if (Application.Current?.MainWindow is MainWindow mw) { - mw.Dispatcher.BeginInvoke(new Action(mw.RebuildBoardToolbar), - System.Windows.Threading.DispatcherPriority.ApplicationIdle); + if (rebuildBoard) + { + mw.Dispatcher.BeginInvoke(new Action(mw.RebuildBoardToolbar), + System.Windows.Threading.DispatcherPriority.ApplicationIdle); + } + if (rebuildFloating) + { + mw.Dispatcher.BeginInvoke(new Action(mw.RebuildToolbar), + System.Windows.Threading.DispatcherPriority.ApplicationIdle); + } } } catch (Exception ex) { - LogError("Failed to flush pending board toolbar rebuild", ex); + LogError("Failed to flush pending toolbar rebuild", ex); } } @@ -2087,16 +2099,17 @@ private void RegisterToolbarItemCore(PluginToolbarItemInfo itemInfo, PluginToolb // 仅在插件首次注册时自动追加到目标工具栏;后续启动只加入组件库, // 避免用户删除组件后重启又被自动加回。 bool isFirstRegistration = IsFirstToolbarRegistration(); + bool autoAddToActiveConfig = isFirstRegistration && itemInfo.AutoAddToActiveConfig; bool isWhiteboard = targetSurface == PluginToolbarSurface.Whiteboard; bool registered; if (isWhiteboard) registered = Controls.Toolbar.BoardToolbar.BoardToolbarRegistry.RegisterPluginItem( itemInfo, - autoAddToActiveConfig: isFirstRegistration); + autoAddToActiveConfig: autoAddToActiveConfig); else registered = Controls.Toolbar.FloatingToolbar.ToolbarRegistry.RegisterPluginItem( itemInfo, - autoAddToActiveConfig: isFirstRegistration); + autoAddToActiveConfig: autoAddToActiveConfig); if (!registered) return; if (_currentLoadingPlugin != null) @@ -2134,6 +2147,17 @@ private void RegisterToolbarItemCore(PluginToolbarItemInfo itemInfo, PluginToolb { TrackUndo("toolbar:" + itemId, () => Controls.Toolbar.FloatingToolbar.ToolbarRegistry.UnregisterPluginItem(itemId)); + + // 浮动工具栏重建:批量加载期间只标记,加载完成后统一重建;运行时热注册立即重建。 + if (_isLoadingBatch) + { + _floatingToolbarRebuildPending = true; + } + else if (Application.Current?.MainWindow is MainWindow mw) + { + mw.Dispatcher.BeginInvoke(new Action(mw.RebuildToolbar), + System.Windows.Threading.DispatcherPriority.ApplicationIdle); + } } Log(string.Format("Plugin registered toolbar item: {0} (surface={1}, autoAdd={2})", diff --git a/Ink Canvas/Windows/SettingsViews/SettingsWindow.xaml.cs b/Ink Canvas/Windows/SettingsViews/SettingsWindow.xaml.cs index 68a237947..3be0c1a67 100644 --- a/Ink Canvas/Windows/SettingsViews/SettingsWindow.xaml.cs +++ b/Ink Canvas/Windows/SettingsViews/SettingsWindow.xaml.cs @@ -978,15 +978,61 @@ private void LoadPluginSettingsPages() { Content = string.Format(NavStrings.Nav_PluginSettingsFormat, plugin.Name), Tag = pageTag, - Icon = new FontIcon - { - Glyph = "\uE713" - } + Icon = CreatePluginNavItemIcon(plugin) }; NavigationViewControl.MenuItems.Add(navItem); } } + private static object CreatePluginNavItemIcon(Ink_Canvas.Plugins.PluginInfo plugin) + { + try + { + if (plugin != null && !string.IsNullOrEmpty(plugin.PluginFolderPath)) + { + var iconRelativePath = plugin.Manifest?.Icon; + string iconPath = null; + if (!string.IsNullOrEmpty(iconRelativePath)) + { + var candidate = System.IO.Path.Combine(plugin.PluginFolderPath, iconRelativePath); + if (System.IO.File.Exists(candidate)) iconPath = candidate; + } + if (iconPath == null) + { + var candidateMono = System.IO.Path.Combine(plugin.PluginFolderPath, "icon_mono.png"); + if (System.IO.File.Exists(candidateMono)) iconPath = candidateMono; + else + { + var candidatePng = System.IO.Path.Combine(plugin.PluginFolderPath, "icon.png"); + if (System.IO.File.Exists(candidatePng)) iconPath = candidatePng; + } + } + + if (iconPath != null) + { + return new BitmapIcon + { + UriSource = new Uri(iconPath, UriKind.Absolute), + ShowAsMonochrome = true, + Width = 16, + Height = 16, + VerticalAlignment = VerticalAlignment.Center, + HorizontalAlignment = HorizontalAlignment.Center + }; + } + } + } + catch + { + // Fallback to default glyph + } + + return new FontIcon + { + Glyph = "\uE713" + }; + } + private void PluginManager_PluginsChanged(object sender, Ink_Canvas.Plugins.PluginInfo e) { Dispatcher.BeginInvoke( diff --git a/InkCanvas.PluginSdk/IPluginHost.cs b/InkCanvas.PluginSdk/IPluginHost.cs index bfb2cb731..f5a5b9128 100644 --- a/InkCanvas.PluginSdk/IPluginHost.cs +++ b/InkCanvas.PluginSdk/IPluginHost.cs @@ -178,6 +178,12 @@ public class PluginToolbarItemInfo /// 弹窗实际打开或关闭时由宿主通知插件。 public Action PopupStateChanged { get; set; } + + /// + /// 是否在插件首次注册时自动添加到当前活跃工具栏配置中。 + /// 默认为 true。若设为 false,则仅添加到工具栏组件库,需用户在设置中手动添加。 + /// + public bool AutoAddToActiveConfig { get; set; } = true; } ///