feat(工具栏设置): 添加工具栏配置页面及功能实现

实现工具栏配置页面,允许用户调整工具栏按钮的顺序和可见性
包含主工具栏、画布控制和尾部按钮三个区域的配置
支持恢复默认布局功能
This commit is contained in:
PrefacedCorg
2026-05-02 13:41:02 +08:00
parent 7736d88657
commit 723c0b9cdc
18 changed files with 714 additions and 206 deletions
@@ -0,0 +1,92 @@
<ui:Page x:Class="Ink_Canvas.Windows.SettingsViews.Pages.ToolbarPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="clr-namespace:Ink_Canvas.Windows.SettingsViews.Pages"
xmlns:ui="http://schemas.inkore.net/lib/ui/wpf/modern"
xmlns:ikw="http://schemas.inkore.net/lib/ui/wpf"
xmlns:dd="urn:gong-wpf-dragdrop"
mc:Ignorable="d"
Title="工具栏">
<Page.Resources>
<Style x:Key="ToolbarItemStyle" TargetType="ListBox">
<Setter Property="HorizontalAlignment" Value="Stretch"/>
<Setter Property="dd:DragDrop.IsDragSource" Value="True"/>
<Setter Property="dd:DragDrop.IsDropTarget" Value="True"/>
<Setter Property="dd:DragDrop.UseDefaultEffectDataTemplate" Value="False"/>
<Setter Property="dd:DragDrop.SelectDroppedItems" Value="True"/>
<Setter Property="dd:DragDrop.DropTargetAdornerBrush" Value="#2563eb"/>
<Setter Property="dd:DragDrop.DropHandler" Value="{Binding}"/>
<Setter Property="ScrollViewer.PanningMode" Value="VerticalOnly"/>
<Setter Property="ScrollViewer.HorizontalScrollBarVisibility" Value="Disabled"/>
<Setter Property="dd:DragDrop.EffectMoveAdornerTemplate">
<Setter.Value>
<DataTemplate>
<Border Background="#2563eb" CornerRadius="12" Padding="16,8"
Opacity="0.9">
<TextBlock Text="移动" Foreground="White"
FontSize="13" FontWeight="Medium"
VerticalAlignment="Center"/>
</Border>
</DataTemplate>
</Setter.Value>
</Setter>
</Style>
<DataTemplate x:Key="ToolbarItemTemplate">
<Grid Height="44" Margin="0,1">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<TextBlock Grid.Column="0" Text="&#xE76F;"
FontFamily="Segoe MDL2 Assets" FontSize="14"
VerticalAlignment="Center" Margin="0,0,10,0" Opacity="0.45"/>
<TextBlock Grid.Column="1" Text="{Binding DisplayName}"
FontSize="14" VerticalAlignment="Center"/>
<CheckBox Grid.Column="2" IsChecked="{Binding IsVisible, Mode=TwoWay}"
VerticalAlignment="Center" Margin="12,0,0,0"
ToolTip="显示或隐藏此按钮"/>
</Grid>
</DataTemplate>
</Page.Resources>
<ScrollViewer PanningMode="VerticalFirst">
<StackPanel Margin="59,0,59,0" MaxWidth="1000">
<!-- 主工具栏 -->
<TextBlock Text="主工具栏" FontSize="16" FontWeight="Bold" Margin="0,20,0,4"/>
<TextBlock Text="拖动调整顺序,开关控制显示。隐藏的按钮不会出现在浮动工具栏中。"
Opacity="0.65" FontSize="12" Margin="0,0,0,8"/>
<ListBox Name="MainItemsList" Style="{StaticResource ToolbarItemStyle}"
ItemTemplate="{StaticResource ToolbarItemTemplate}"
ItemsSource="{Binding MainItems}" />
<!-- 画布控制 -->
<TextBlock Text="画布控制" FontSize="16" FontWeight="Bold" Margin="0,24,0,4"/>
<TextBlock Text="批注模式下可见的工具按钮排序。"
Opacity="0.65" FontSize="12" Margin="0,0,0,8"/>
<ListBox Name="CanvasItemsList" Style="{StaticResource ToolbarItemStyle}"
ItemTemplate="{StaticResource ToolbarItemTemplate}"
ItemsSource="{Binding CanvasItems}" />
<!-- 尾部按钮 -->
<TextBlock Text="尾部按钮" FontSize="16" FontWeight="Bold" Margin="0,24,0,4"/>
<TextBlock Text="白板、工具、折叠按钮排序。"
Opacity="0.65" FontSize="12" Margin="0,0,0,8"/>
<ListBox Name="EndItemsList" Style="{StaticResource ToolbarItemStyle}"
ItemTemplate="{StaticResource ToolbarItemTemplate}"
ItemsSource="{Binding EndItems}" />
<Button Content="恢复默认布局" Click="ButtonReset_Click"
Padding="20,10" HorizontalAlignment="Left" Margin="0,16,0,0"/>
</StackPanel>
</ScrollViewer>
</ui:Page>
@@ -0,0 +1,232 @@
using GongSolutions.Wpf.DragDrop;
using Ink_Canvas.Controls.Toolbar;
using Ink_Canvas.Helpers;
using Ink_Canvas.Windows.SettingsViews.Helpers;
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Linq;
using System.Windows;
using System.Windows.Controls;
using Page = iNKORE.UI.WPF.Modern.Controls.Page;
namespace Ink_Canvas.Windows.SettingsViews.Pages
{
public partial class ToolbarPage : Page, IDropTarget
{
public class ToolbarItemViewModel : INotifyPropertyChanged
{
public string Id { get; }
public string DisplayName { get; }
private int _order;
public int Order { get => _order; set { _order = value; OnPropertyChanged(nameof(Order)); } }
private bool _isVisible = true;
public bool IsVisible { get => _isVisible; set { _isVisible = value; OnPropertyChanged(nameof(IsVisible)); } }
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged(string name) => PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(name));
public ToolbarItemViewModel(string id, string displayName, int order, bool isVisible)
{
Id = id; DisplayName = displayName; _order = order; _isVisible = isVisible;
}
}
private static readonly string LogTag = "ToolbarPage";
private bool _isLoaded;
public ObservableCollection<ToolbarItemViewModel> MainItems { get; } = new();
public ObservableCollection<ToolbarItemViewModel> CanvasItems { get; } = new();
public ObservableCollection<ToolbarItemViewModel> EndItems { get; } = new();
public ToolbarPage()
{
InitializeComponent();
DataContext = this;
Loaded += OnPageLoaded;
}
private void OnPageLoaded(object sender, RoutedEventArgs e)
{
try { LoadSettings(); }
catch (Exception ex)
{
LogHelper.WriteLogToFile($"{LogTag}: LoadSettings 异常: {ex.GetType().Name}: {ex.Message}\n{ex.StackTrace}", LogHelper.LogType.Error);
}
_isLoaded = true;
}
private void LoadSettings()
{
LogHelper.WriteLogToFile($"{LogTag}: LoadSettings 开始", LogHelper.LogType.Info);
MainItems.Clear(); CanvasItems.Clear(); EndItems.Clear();
var layout = SettingsManager.Settings?.Toolbar ?? new ToolbarLayoutSettings();
IReadOnlyList<IToolbarItem> discoveredItems;
try { discoveredItems = ToolbarRegistry.Discover(); }
catch (Exception ex) { LogHelper.WriteLogToFile($"{LogTag}: Discover 失败: {ex.Message}", LogHelper.LogType.Error); return; }
foreach (var item in discoveredItems)
{
try
{
if (!layout.Items.TryGetValue(item.Id, out var cfg))
{
cfg = new ToolbarItemConfig
{
Visible = item.DefaultVisible,
Order = item.DefaultOrder,
Slot = item.DefaultSlot,
Position = item.DefaultPosition,
AnchorName = item.DefaultAnchorName
};
}
string displayName;
try { displayName = item.DisplayName ?? item.Id; }
catch { displayName = item.Id; }
var vm = new ToolbarItemViewModel(item.Id, displayName, cfg.Order, cfg.Visible);
switch (cfg.Slot)
{
case ToolbarSlot.FloatingBarMain: MainItems.Add(vm); break;
case ToolbarSlot.FloatingBarCanvasControls: CanvasItems.Add(vm); break;
case ToolbarSlot.FloatingBarEnd: EndItems.Add(vm); break;
}
}
catch (Exception ex)
{
LogHelper.WriteLogToFile($"{LogTag}: 处理条目失败 [{item?.Id}]: {ex.Message}", LogHelper.LogType.Warning);
}
}
ReorderCollections();
LogHelper.WriteLogToFile($"{LogTag}: LoadSettings 完成 Main={MainItems.Count} Canvas={CanvasItems.Count} End={EndItems.Count}", LogHelper.LogType.Info);
}
private void ReorderCollections()
{
SortCollection(MainItems);
SortCollection(CanvasItems);
SortCollection(EndItems);
}
private static void SortCollection(ObservableCollection<ToolbarItemViewModel> collection)
{
if (collection == null) return;
var sorted = collection.OrderBy(x => x.Order).ToList();
for (int i = 0; i < sorted.Count; i++)
{
var oldIndex = collection.IndexOf(sorted[i]);
if (oldIndex != -1 && oldIndex != i)
collection.Move(oldIndex, i);
}
}
public new void DragOver(IDropInfo dropInfo)
{
if (dropInfo.Data is not ToolbarItemViewModel) return;
dropInfo.DropTargetAdorner = DropTargetAdorners.Insert;
dropInfo.Effects = DragDropEffects.Move;
}
public new void Drop(IDropInfo dropInfo)
{
if (dropInfo.Data is not ToolbarItemViewModel vm) return;
if (dropInfo.TargetCollection is not ObservableCollection<ToolbarItemViewModel> target) return;
var oldIndex = target.IndexOf(vm);
var newIndex = oldIndex < dropInfo.UnfilteredInsertIndex ? dropInfo.UnfilteredInsertIndex - 1 : dropInfo.UnfilteredInsertIndex;
var finalIndex = Math.Min(newIndex >= target.Count ? target.Count - 1 : newIndex, target.Count);
if (!target.Contains(vm))
{
if (dropInfo.DragInfo.SourceCollection is ObservableCollection<ToolbarItemViewModel> source)
source.Remove(vm);
target.Insert(dropInfo.UnfilteredInsertIndex, vm);
}
else if (oldIndex != -1 && oldIndex != finalIndex)
{
target.Move(oldIndex, finalIndex);
}
UpdateOrdersFromCollection(target);
SaveSettings();
}
private static void UpdateOrdersFromCollection(ObservableCollection<ToolbarItemViewModel> collection)
{
for (int i = 0; i < collection.Count; i++)
collection[i].Order = (i + 1) * 10;
}
private void SaveSettings()
{
if (!_isLoaded) return;
try
{
var settings = SettingsManager.Settings;
if (settings == null) return;
if (settings.Toolbar == null) settings.Toolbar = new ToolbarLayoutSettings();
var layout = settings.Toolbar;
foreach (var vm in MainItems.Concat(CanvasItems).Concat(EndItems))
{
if (!layout.Items.TryGetValue(vm.Id, out var cfg))
{
var item = ToolbarRegistry.Discover().FirstOrDefault(i => i.Id == vm.Id);
cfg = new ToolbarItemConfig
{
Visible = item?.DefaultVisible ?? true,
Order = item?.DefaultOrder ?? 0,
Slot = item?.DefaultSlot ?? ToolbarSlot.FloatingBarMain,
Position = item?.DefaultPosition ?? ToolbarInsertPosition.Prepend,
AnchorName = item?.DefaultAnchorName
};
layout.Items[vm.Id] = cfg;
}
cfg.Visible = vm.IsVisible;
cfg.Order = vm.Order;
}
SettingsManager.SaveSettingsToFile();
LogHelper.WriteLogToFile($"{LogTag}: 设置已保存", LogHelper.LogType.Info);
Application.Current.Dispatcher.BeginInvoke(new Action(() =>
{
try
{
var mainWindow = Application.Current.Windows.OfType<MainWindow>().FirstOrDefault();
mainWindow?.RebuildToolbar();
}
catch (Exception ex) { LogHelper.WriteLogToFile($"{LogTag}: RebuildToolbar 异常: {ex.Message}", LogHelper.LogType.Error); }
}));
}
catch (Exception ex)
{
LogHelper.WriteLogToFile($"{LogTag}: SaveSettings 异常: {ex.GetType().Name}: {ex.Message}\n{ex.StackTrace}", LogHelper.LogType.Error);
}
}
private void ButtonReset_Click(object sender, RoutedEventArgs e)
{
try
{
SettingsManager.Settings?.Toolbar?.Items.Clear();
SettingsManager.SaveSettingsToFile();
Application.Current.Dispatcher.BeginInvoke(new Action(() =>
{
try { Application.Current.Windows.OfType<MainWindow>().FirstOrDefault()?.RebuildToolbar(); }
catch (Exception ex) { LogHelper.WriteLogToFile($"{LogTag}: Reset Rebuild 异常: {ex.Message}", LogHelper.LogType.Error); }
}));
LoadSettings();
}
catch (Exception ex)
{
LogHelper.WriteLogToFile($"{LogTag}: ButtonReset 异常: {ex.GetType().Name}: {ex.Message}\n{ex.StackTrace}", LogHelper.LogType.Error);
}
}
}
}
@@ -202,6 +202,15 @@
<ui:FontIcon Icon="{x:Static ui:SegoeFluentIcons.KeyboardStandard}"/>
</ui:NavigationViewItem.Icon>
</ui:NavigationViewItem>
<ui:NavigationViewItem
x:Name="ToolbarPageItem"
Content="工具栏"
Tag="ToolbarPage"
ToolTipService.ToolTip="工具栏按钮排序与可见性">
<ui:NavigationViewItem.Icon>
<ui:FontIcon Icon="{x:Static ui:SegoeFluentIcons.Rename}"/>
</ui:NavigationViewItem.Icon>
</ui:NavigationViewItem>
</ui:NavigationViewItem.MenuItems>
</ui:NavigationViewItem>
@@ -45,6 +45,7 @@ namespace Ink_Canvas.Windows.SettingsViews
{ "WindowPage", typeof(WindowPage) },
{ "AppearancePage", typeof(AppearancePage) },
{ "HotkeyPage", typeof(HotkeyPage) },
{ "ToolbarPage", typeof(ToolbarPage) },
{ "UpdatePage", typeof(UpdatePage) },
{ "ExperimentalPage", typeof(ExperimentalPage) },
{ "AdvancedPage", typeof(AdvancedPage) },
@@ -312,8 +313,10 @@ namespace Ink_Canvas.Windows.SettingsViews
if (!_pages.TryGetValue(pageTag, out var cachedPage))
{
Ink_Canvas.Helpers.LogHelper.WriteLogToFile($"SettingsWindow: 创建页面实例 {pageTag} ({pageType.Name})", Ink_Canvas.Helpers.LogHelper.LogType.Info);
cachedPage = Activator.CreateInstance(pageType);
_pages.Add(pageTag, cachedPage);
Ink_Canvas.Helpers.LogHelper.WriteLogToFile($"SettingsWindow: 页面实例 {pageTag} 创建成功", Ink_Canvas.Helpers.LogHelper.LogType.Info);
}
if (cachedPage is PluginSettingsPage pluginSettingsPage && pluginInfo != null)
@@ -325,6 +328,7 @@ namespace Ink_Canvas.Windows.SettingsViews
}
catch (Exception ex)
{
Ink_Canvas.Helpers.LogHelper.WriteLogToFile($"SettingsWindow: 导航到 {pageTag} 异常: {ex.GetType().Name}: {ex.Message}\n{ex.StackTrace}", Ink_Canvas.Helpers.LogHelper.LogType.Error);
MessageBox.Show($"导航到页面时出错: {ex.Message}", "错误", MessageBoxButton.OK, MessageBoxImage.Error);
}
finally