Compare commits
92 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 578ffb7c56 | |||
| feca1aa46d | |||
| b399429d9d | |||
| 5388bef3ea | |||
| cbb17ee03f | |||
| 51dcc374ce | |||
| 1640238728 | |||
| 0fa4010625 | |||
| e6d391e98b | |||
| 6659db651d | |||
| b50049c822 | |||
| c1d584e3e7 | |||
| f776dac04d | |||
| 6e7a0e36f4 | |||
| 585b712c4c | |||
| 2ca7d2a337 | |||
| ea03e8e7c7 | |||
| 778894482f | |||
| 1035f7ef92 | |||
| 6e4f8bc982 | |||
| 36e47cec43 | |||
| 8b2bc352a6 | |||
| 057fb35d00 | |||
| bcbece5bd6 | |||
| 5aad206e0d | |||
| 74344c4782 | |||
| f280358f56 | |||
| 243201502b | |||
| 8c090218d1 | |||
| 6ca1c598d4 | |||
| 0c078ef863 | |||
| fea6576dfb | |||
| 24bc2cf138 | |||
| e6b707ea51 | |||
| 6183011952 | |||
| d80a59556e | |||
| ebbe018bae | |||
| bad05f77b5 | |||
| ea375a9ce6 | |||
| d3382d7856 | |||
| acf0c17d7a | |||
| 83568c8b33 | |||
| cc80529498 | |||
| 002970921d | |||
| ea74592e89 | |||
| fa38d3d664 | |||
| cffedb8cb7 | |||
| c77beb662e | |||
| 77dd83c2d6 | |||
| ceb99cea2b | |||
| 04a8224484 | |||
| 2f54e9d7b3 | |||
| 1165e5bbf2 | |||
| 24c6ca60a3 | |||
| 8f3b65c6d4 | |||
| 4835e3db50 | |||
| 7a30c93ff5 | |||
| 1fca17d557 | |||
| 069a478559 | |||
| 9279783fc3 | |||
| 742a62b6ff | |||
| 140e92eeda | |||
| 34c2dab82a | |||
| fa2366c373 | |||
| e2d898df14 | |||
| cacc67b11d | |||
| c890f95092 | |||
| 7c9a5f8265 | |||
| 7eafcb02cb | |||
| d4517d3c53 | |||
| 62a9a097aa | |||
| e7fa1caf6c | |||
| e347443a0a | |||
| b77e540863 | |||
| df4168e8fa | |||
| 576f86ce48 | |||
| c3335c0c24 | |||
| 26f79da2f9 | |||
| 11cb1815ad | |||
| a6fdb07e8b | |||
| ffdb3cd431 | |||
| 5f9b01ef04 | |||
| ca53624149 | |||
| 6484450ad3 | |||
| 7c97b683ea | |||
| 378b3e73f2 | |||
| a2f21357b6 | |||
| e0f35450e1 | |||
| cc9f58fb6a | |||
| e8be85141b | |||
| 1bf57ea2f5 | |||
| 20441543f0 |
+8
-4
@@ -1,4 +1,5 @@
|
|||||||
{
|
{
|
||||||
|
"$schema": "https://www.schemastore.org/all-contributors.json",
|
||||||
"projectName": "community",
|
"projectName": "community",
|
||||||
"projectOwner": "InkCanvasForClass",
|
"projectOwner": "InkCanvasForClass",
|
||||||
"files": [
|
"files": [
|
||||||
@@ -6,7 +7,7 @@
|
|||||||
],
|
],
|
||||||
"commitType": "docs",
|
"commitType": "docs",
|
||||||
"commitConvention": "angular",
|
"commitConvention": "angular",
|
||||||
"contributorsPerLine": 7,
|
"contributorsPerLine": 5,
|
||||||
"contributors": [
|
"contributors": [
|
||||||
{
|
{
|
||||||
"login": "CJKmkp",
|
"login": "CJKmkp",
|
||||||
@@ -100,7 +101,8 @@
|
|||||||
"avatar_url": "https://avatars.githubusercontent.com/u/156585442?v=4",
|
"avatar_url": "https://avatars.githubusercontent.com/u/156585442?v=4",
|
||||||
"profile": "https://github.com/Tayasui-rainnya",
|
"profile": "https://github.com/Tayasui-rainnya",
|
||||||
"contributions": [
|
"contributions": [
|
||||||
"design"
|
"design",
|
||||||
|
"code"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -110,7 +112,8 @@
|
|||||||
"profile": "https://github.com/doudou0720",
|
"profile": "https://github.com/doudou0720",
|
||||||
"contributions": [
|
"contributions": [
|
||||||
"code",
|
"code",
|
||||||
"blog"
|
"blog",
|
||||||
|
"infra"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -141,5 +144,6 @@
|
|||||||
"blog"
|
"blog"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
]
|
],
|
||||||
|
"repoType": "github"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
|
||||||
Microsoft Visual Studio Solution File, Format Version 12.00
|
Microsoft Visual Studio Solution File, Format Version 12.00
|
||||||
# Visual Studio Version 17
|
# Visual Studio Version 17
|
||||||
VisualStudioVersion = 17.5.33530.505
|
VisualStudioVersion = 17.5.33530.505
|
||||||
|
|||||||
+25
-4
@@ -1,8 +1,9 @@
|
|||||||
<Application x:Class="Ink_Canvas.App"
|
<Application x:Class="Ink_Canvas.App"
|
||||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||||
xmlns:local="clr-namespace:Ink_Canvas"
|
xmlns:local="clr-namespace:Ink_Canvas"
|
||||||
xmlns:tb="http://www.hardcodet.net/taskbar"
|
xmlns:props="clr-namespace:Ink_Canvas.Properties"
|
||||||
|
xmlns:tb="clr-namespace:H.NotifyIcon;assembly=H.NotifyIcon.Wpf"
|
||||||
xmlns:ui="http://schemas.inkore.net/lib/ui/wpf/modern"
|
xmlns:ui="http://schemas.inkore.net/lib/ui/wpf/modern"
|
||||||
xmlns:ikw="http://schemas.inkore.net/lib/ui/wpf"
|
xmlns:ikw="http://schemas.inkore.net/lib/ui/wpf"
|
||||||
>
|
>
|
||||||
@@ -32,10 +33,30 @@
|
|||||||
</Image>
|
</Image>
|
||||||
</MenuItem.Icon>
|
</MenuItem.Icon>
|
||||||
</MenuItem>
|
</MenuItem>
|
||||||
|
<MenuItem Name="TempShowMainWindowTrayIconMenuItem" Click="TempShowMainWindowTrayIconMenuItem_Clicked">
|
||||||
|
<MenuItem.Header>
|
||||||
|
<ikw:SimpleStackPanel Orientation="Horizontal" Margin="-4,0,0,0">
|
||||||
|
<TextBlock FontSize="14" VerticalAlignment="Center" Foreground="#18181b" Text="{x:Static props:Strings.Tray_TempShowMainWindow}" />
|
||||||
|
</ikw:SimpleStackPanel>
|
||||||
|
</MenuItem.Header>
|
||||||
|
<MenuItem.Icon>
|
||||||
|
<Image Width="28" Height="28" Margin="-2">
|
||||||
|
<Image.Source>
|
||||||
|
<DrawingImage>
|
||||||
|
<DrawingImage.Drawing>
|
||||||
|
<DrawingGroup ClipGeometry="M0,0 V24 H24 V0 H0 Z">
|
||||||
|
<GeometryDrawing Brush="#27272a" Geometry="F0 M24,24z M0,0z M5,6C4.73478,6 4.48043,6.10536 4.29289,6.29289 4.10536,6.48043 4,6.73478 4,7L4,17C4,17.2652 4.10536,17.5196 4.29289,17.7071 4.48043,17.8946 4.73478,18 5,18L19,18C19.2652,18 19.5196,17.8946 19.7071,17.7071 19.8946,17.5196 20,17.2652 20,17L20,7C20,6.73478 19.8946,6.48043 19.7071,6.29289 19.5196,6.10536 19.2652,6 19,6L5,6z M2.87868,4.87868C3.44129,4.31607,4.20435,4,5,4L19,4C19.7957,4 20.5587,4.31607 21.1213,4.87868 21.6839,5.44129 22,6.20435 22,7L22,17C22,17.7957 21.6839,18.5587 21.1213,19.1213 20.5587,19.6839 19.7957,20 19,20L5,20C4.20435,20 3.44129,19.6839 2.87868,19.1213 2.31607,18.5587 2,17.7956 2,17L2,7C2,6.20435,2.31607,5.44129,2.87868,4.87868z M5,8C5,7.44772,5.44772,7,6,7L6.01,7C6.56228,7 7.01,7.44772 7.01,8 7.01,8.55228 6.56228,9 6.01,9L6,9C5.44772,9,5,8.55228,5,8z M9,7C8.44772,7 8,7.44772 8,8 8,8.55228 8.44772,9 9,9L9.01,9C9.56228,9 10.01,8.55228 10.01,8 10.01,7.44772 9.56228,7 9.01,7L9,7z" />
|
||||||
|
</DrawingGroup>
|
||||||
|
</DrawingImage.Drawing>
|
||||||
|
</DrawingImage>
|
||||||
|
</Image.Source>
|
||||||
|
</Image>
|
||||||
|
</MenuItem.Icon>
|
||||||
|
</MenuItem>
|
||||||
<MenuItem Name="OpenSettingsTrayIconMenuItem" Click="OpenSettingsTrayIconMenuItem_Clicked">
|
<MenuItem Name="OpenSettingsTrayIconMenuItem" Click="OpenSettingsTrayIconMenuItem_Clicked">
|
||||||
<MenuItem.Header>
|
<MenuItem.Header>
|
||||||
<ikw:SimpleStackPanel Orientation="Horizontal" Margin="-4,0,0,0">
|
<ikw:SimpleStackPanel Orientation="Horizontal" Margin="-4,0,0,0">
|
||||||
<TextBlock FontSize="14" VerticalAlignment="Center" Foreground="#18181b" Text="打开设置" />
|
<TextBlock FontSize="14" VerticalAlignment="Center" Foreground="#18181b" Text="{x:Static props:Strings.Tray_OpenSettings}" />
|
||||||
</ikw:SimpleStackPanel>
|
</ikw:SimpleStackPanel>
|
||||||
</MenuItem.Header>
|
</MenuItem.Header>
|
||||||
<MenuItem.Icon>
|
<MenuItem.Icon>
|
||||||
@@ -242,7 +263,7 @@
|
|||||||
ToolTipText="InkCanvasForClass"
|
ToolTipText="InkCanvasForClass"
|
||||||
ContextMenu="{StaticResource SysTrayMenu}"
|
ContextMenu="{StaticResource SysTrayMenu}"
|
||||||
IconSource="/Resources/icc.ico"/>
|
IconSource="/Resources/icc.ico"/>
|
||||||
<ResourceDictionary.MergedDictionaries>
|
<ResourceDictionary.MergedDictionaries>
|
||||||
<ui:ThemeResources/>
|
<ui:ThemeResources/>
|
||||||
<ui:XamlControlsResources />
|
<ui:XamlControlsResources />
|
||||||
<ResourceDictionary Source="Resources/SeewoImageDictionary.xaml"/>
|
<ResourceDictionary Source="Resources/SeewoImageDictionary.xaml"/>
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
using Hardcodet.Wpf.TaskbarNotification;
|
using H.NotifyIcon;
|
||||||
using Ink_Canvas.Helpers;
|
using Ink_Canvas.Helpers;
|
||||||
using Ink_Canvas.Properties;
|
using Ink_Canvas.Properties;
|
||||||
using iNKORE.UI.WPF.Modern.Controls;
|
using iNKORE.UI.WPF.Modern.Controls;
|
||||||
@@ -1061,6 +1061,7 @@ namespace Ink_Canvas
|
|||||||
}
|
}
|
||||||
|
|
||||||
_taskbar = (TaskbarIcon)FindResource("TaskbarTrayIcon");
|
_taskbar = (TaskbarIcon)FindResource("TaskbarTrayIcon");
|
||||||
|
_taskbar.ForceCreate();
|
||||||
|
|
||||||
StartArgs = e.Args;
|
StartArgs = e.Args;
|
||||||
|
|
||||||
|
|||||||
@@ -43,5 +43,5 @@ using System.Windows;
|
|||||||
// You can specify all the values or you can default the Build and Revision Numbers
|
// You can specify all the values or you can default the Build and Revision Numbers
|
||||||
// by using the '*' as shown below:
|
// by using the '*' as shown below:
|
||||||
// [assembly: AssemblyVersion("1.0.*")]
|
// [assembly: AssemblyVersion("1.0.*")]
|
||||||
[assembly: AssemblyVersion("1.7.18.9")]
|
[assembly: AssemblyVersion("1.7.18.10")]
|
||||||
[assembly: AssemblyFileVersion("1.7.18.9")]
|
[assembly: AssemblyFileVersion("1.7.18.10")]
|
||||||
|
|||||||
@@ -0,0 +1,24 @@
|
|||||||
|
<UserControl x:Class="Ink_Canvas.Controls.CopyButton"
|
||||||
|
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||||
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||||
|
xmlns:ui="http://schemas.inkore.net/lib/ui/wpf/modern">
|
||||||
|
<Button x:Name="CopyButtonControl" Padding="6" Click="CopyButton_Click"
|
||||||
|
ToolTipService.ToolTip="Copy">
|
||||||
|
<Grid>
|
||||||
|
<ui:FontIcon x:Name="FontIcon_Copy" FontSize="16"
|
||||||
|
Icon="{x:Static ui:SegoeFluentIcons.Copy}" RenderTransformOrigin="0.5 0.5">
|
||||||
|
<FrameworkElement.RenderTransform>
|
||||||
|
<ScaleTransform x:Name="ScaleTransform_Copy"
|
||||||
|
ScaleX="1" ScaleY="{Binding ScaleX, RelativeSource={RelativeSource Self}}"/>
|
||||||
|
</FrameworkElement.RenderTransform>
|
||||||
|
</ui:FontIcon>
|
||||||
|
<ui:FontIcon x:Name="FontIcon_Success" FontSize="16"
|
||||||
|
Icon="{x:Static ui:SegoeFluentIcons.CheckMark}" RenderTransformOrigin="0.5 0.5">
|
||||||
|
<FrameworkElement.RenderTransform>
|
||||||
|
<ScaleTransform x:Name="ScaleTransform_Success"
|
||||||
|
ScaleX="0" ScaleY="{Binding ScaleX, RelativeSource={RelativeSource Self}}"/>
|
||||||
|
</FrameworkElement.RenderTransform>
|
||||||
|
</ui:FontIcon>
|
||||||
|
</Grid>
|
||||||
|
</Button>
|
||||||
|
</UserControl>
|
||||||
@@ -0,0 +1,115 @@
|
|||||||
|
using System;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using System.Windows;
|
||||||
|
using System.Windows.Controls;
|
||||||
|
using System.Windows.Media;
|
||||||
|
using System.Windows.Media.Animation;
|
||||||
|
|
||||||
|
namespace Ink_Canvas.Controls
|
||||||
|
{
|
||||||
|
public partial class CopyButton : UserControl
|
||||||
|
{
|
||||||
|
public static readonly DependencyProperty TextProperty = DependencyProperty.Register(
|
||||||
|
nameof(Text), typeof(string), typeof(CopyButton), new PropertyMetadata(string.Empty));
|
||||||
|
|
||||||
|
public string Text
|
||||||
|
{
|
||||||
|
get => (string)GetValue(TextProperty);
|
||||||
|
set => SetValue(TextProperty, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public event EventHandler Click;
|
||||||
|
|
||||||
|
public CopyButton()
|
||||||
|
{
|
||||||
|
InitializeComponent();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void CopyButton_Click(object sender, RoutedEventArgs e)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (!string.IsNullOrEmpty(Text))
|
||||||
|
{
|
||||||
|
Clipboard.SetText(Text);
|
||||||
|
}
|
||||||
|
|
||||||
|
ShowSuccessAnimation();
|
||||||
|
Click?.Invoke(this, EventArgs.Empty);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
MessageBox.Show(ex.ToString(), "Unable to Perform Copy", MessageBoxButton.OK, MessageBoxImage.Error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async void ShowSuccessAnimation()
|
||||||
|
{
|
||||||
|
var copyScaleAnim = new DoubleAnimation
|
||||||
|
{
|
||||||
|
To = 0,
|
||||||
|
Duration = TimeSpan.FromMilliseconds(150)
|
||||||
|
};
|
||||||
|
ScaleTransform_Copy.BeginAnimation(ScaleTransform.ScaleXProperty, copyScaleAnim);
|
||||||
|
|
||||||
|
var copyOpacityAnim = new DoubleAnimation
|
||||||
|
{
|
||||||
|
To = 0,
|
||||||
|
BeginTime = TimeSpan.FromMilliseconds(100),
|
||||||
|
Duration = TimeSpan.FromMilliseconds(10)
|
||||||
|
};
|
||||||
|
FontIcon_Copy.BeginAnimation(UIElement.OpacityProperty, copyOpacityAnim);
|
||||||
|
|
||||||
|
await Task.Delay(150);
|
||||||
|
var successScaleAnim = new DoubleAnimation
|
||||||
|
{
|
||||||
|
To = 1,
|
||||||
|
Duration = TimeSpan.FromMilliseconds(150),
|
||||||
|
EasingFunction = new BackEase { EasingMode = EasingMode.EaseOut, Amplitude = 0.2 }
|
||||||
|
};
|
||||||
|
ScaleTransform_Success.BeginAnimation(ScaleTransform.ScaleXProperty, successScaleAnim);
|
||||||
|
|
||||||
|
var successOpacityAnim = new DoubleAnimation
|
||||||
|
{
|
||||||
|
To = 1,
|
||||||
|
Duration = TimeSpan.FromMilliseconds(15)
|
||||||
|
};
|
||||||
|
FontIcon_Success.BeginAnimation(UIElement.OpacityProperty, successOpacityAnim);
|
||||||
|
|
||||||
|
await Task.Delay(1000);
|
||||||
|
ShowCopyAnimation();
|
||||||
|
}
|
||||||
|
|
||||||
|
private async void ShowCopyAnimation()
|
||||||
|
{
|
||||||
|
var successOpacityAnim = new DoubleAnimation
|
||||||
|
{
|
||||||
|
To = 0,
|
||||||
|
Duration = TimeSpan.FromMilliseconds(150)
|
||||||
|
};
|
||||||
|
FontIcon_Success.BeginAnimation(UIElement.OpacityProperty, successOpacityAnim);
|
||||||
|
|
||||||
|
await Task.Delay(150);
|
||||||
|
var copyScaleAnim = new DoubleAnimation
|
||||||
|
{
|
||||||
|
To = 1,
|
||||||
|
Duration = TimeSpan.Zero
|
||||||
|
};
|
||||||
|
ScaleTransform_Copy.BeginAnimation(ScaleTransform.ScaleXProperty, copyScaleAnim);
|
||||||
|
|
||||||
|
var copyOpacityAnim = new DoubleAnimation
|
||||||
|
{
|
||||||
|
To = 1,
|
||||||
|
Duration = TimeSpan.FromMilliseconds(150)
|
||||||
|
};
|
||||||
|
FontIcon_Copy.BeginAnimation(UIElement.OpacityProperty, copyOpacityAnim);
|
||||||
|
|
||||||
|
var successScaleAnim = new DoubleAnimation
|
||||||
|
{
|
||||||
|
To = 0,
|
||||||
|
Duration = TimeSpan.Zero
|
||||||
|
};
|
||||||
|
ScaleTransform_Success.BeginAnimation(ScaleTransform.ScaleXProperty, successScaleAnim);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,145 @@
|
|||||||
|
using Ink_Canvas.Helpers;
|
||||||
|
using System;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using System.Windows;
|
||||||
|
using System.Windows.Controls;
|
||||||
|
using System.Windows.Media;
|
||||||
|
using System.Windows.Media.Imaging;
|
||||||
|
|
||||||
|
namespace Ink_Canvas.Controls
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// 画布上的多页 PDF:仅显示当前页;翻页与页码由主窗口 PDF 侧栏控制(无 XAML 文件)。
|
||||||
|
/// </summary>
|
||||||
|
public class PdfEmbeddedView : UserControl
|
||||||
|
{
|
||||||
|
private readonly Image _pageImage;
|
||||||
|
|
||||||
|
private string _pdfPath;
|
||||||
|
private uint _pageCount;
|
||||||
|
private uint _currentIndex;
|
||||||
|
private bool _compressLargePictures;
|
||||||
|
private bool _isPagingBusy;
|
||||||
|
private bool _layoutSizeCommitted;
|
||||||
|
|
||||||
|
/// <summary>页码或可翻页状态变化(用于更新侧栏)。</summary>
|
||||||
|
public event EventHandler PageNavigationStateChanged;
|
||||||
|
|
||||||
|
public PdfEmbeddedView()
|
||||||
|
{
|
||||||
|
MinWidth = 80;
|
||||||
|
MinHeight = 60;
|
||||||
|
|
||||||
|
var grid = new Grid { ClipToBounds = true };
|
||||||
|
_pageImage = new Image
|
||||||
|
{
|
||||||
|
Stretch = Stretch.Uniform,
|
||||||
|
HorizontalAlignment = HorizontalAlignment.Center,
|
||||||
|
VerticalAlignment = VerticalAlignment.Center
|
||||||
|
};
|
||||||
|
grid.Children.Add(_pageImage);
|
||||||
|
Content = grid;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 初始化并显示指定页;由 MainWindow 在 UI 线程创建后调用。
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="initialPageIndex">从 0 开始的页码,超出范围时夹紧到合法区间。</param>
|
||||||
|
public async Task InitializeAsync(string pdfFilePath, uint pageCount, bool compressLargePictures, uint initialPageIndex = 0)
|
||||||
|
{
|
||||||
|
_pdfPath = pdfFilePath ?? throw new ArgumentNullException(nameof(pdfFilePath));
|
||||||
|
_pageCount = pageCount;
|
||||||
|
_compressLargePictures = compressLargePictures;
|
||||||
|
if (_pageCount == 0)
|
||||||
|
_currentIndex = 0;
|
||||||
|
else
|
||||||
|
_currentIndex = initialPageIndex >= _pageCount ? _pageCount - 1 : initialPageIndex;
|
||||||
|
|
||||||
|
await ShowPageAsync(_currentIndex);
|
||||||
|
}
|
||||||
|
|
||||||
|
public string PdfPath => _pdfPath;
|
||||||
|
|
||||||
|
public uint PageCount => _pageCount;
|
||||||
|
|
||||||
|
public uint CurrentPageIndex => _currentIndex;
|
||||||
|
|
||||||
|
public string PageLabelText => _pageCount == 0 ? "" : $"{_currentIndex + 1} / {_pageCount}";
|
||||||
|
|
||||||
|
public bool CanGoPrevious => !_isPagingBusy && _pageCount > 1 && _currentIndex > 0;
|
||||||
|
|
||||||
|
public bool CanGoNext => !_isPagingBusy && _pageCount > 1 && _currentIndex < _pageCount - 1;
|
||||||
|
|
||||||
|
public async Task GoToPreviousPageAsync()
|
||||||
|
{
|
||||||
|
await GoRelativeAsync(-1);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task GoToNextPageAsync()
|
||||||
|
{
|
||||||
|
await GoRelativeAsync(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void NotifyPageNavigationStateChanged()
|
||||||
|
{
|
||||||
|
PageNavigationStateChanged?.Invoke(this, EventArgs.Empty);
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task GoRelativeAsync(int delta)
|
||||||
|
{
|
||||||
|
if (_isPagingBusy || _pageCount <= 1)
|
||||||
|
return;
|
||||||
|
int next = (int)_currentIndex + delta;
|
||||||
|
if (next < 0 || next >= _pageCount)
|
||||||
|
return;
|
||||||
|
_currentIndex = (uint)next;
|
||||||
|
await ShowPageAsync(_currentIndex);
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task ShowPageAsync(uint pageIndex)
|
||||||
|
{
|
||||||
|
_isPagingBusy = true;
|
||||||
|
NotifyPageNavigationStateChanged();
|
||||||
|
try
|
||||||
|
{
|
||||||
|
BitmapSource raw = await PdfWinRtHelper.RenderPageToBitmapSourceAsync(_pdfPath, pageIndex);
|
||||||
|
if (raw == null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
BitmapSource display = ApplyCompressionIfNeeded(raw);
|
||||||
|
_pageImage.Source = display;
|
||||||
|
if (!_layoutSizeCommitted)
|
||||||
|
{
|
||||||
|
bool callerSized = !double.IsNaN(Width) && Width > 0 && !double.IsNaN(Height) && Height > 0;
|
||||||
|
if (!callerSized)
|
||||||
|
{
|
||||||
|
Width = display.PixelWidth;
|
||||||
|
Height = display.PixelHeight;
|
||||||
|
}
|
||||||
|
|
||||||
|
_layoutSizeCommitted = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
_isPagingBusy = false;
|
||||||
|
NotifyPageNavigationStateChanged();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private BitmapSource ApplyCompressionIfNeeded(BitmapSource rendered)
|
||||||
|
{
|
||||||
|
int width = rendered.PixelWidth;
|
||||||
|
int height = rendered.PixelHeight;
|
||||||
|
if (_compressLargePictures && (width > 1920 || height > 1080))
|
||||||
|
{
|
||||||
|
double scaleX = 1920.0 / width;
|
||||||
|
double scaleY = 1080.0 / height;
|
||||||
|
double scale = Math.Min(scaleX, scaleY);
|
||||||
|
return new TransformedBitmap(rendered, new ScaleTransform(scale, scale));
|
||||||
|
}
|
||||||
|
|
||||||
|
return rendered;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -285,6 +285,7 @@ namespace Ink_Canvas.Helpers
|
|||||||
// 功能快捷键
|
// 功能快捷键
|
||||||
RegisterHotkey("DrawLine", Key.L, ModifierKeys.Alt, () => _mainWindow.BtnDrawLine_Click(null, null));
|
RegisterHotkey("DrawLine", Key.L, ModifierKeys.Alt, () => _mainWindow.BtnDrawLine_Click(null, null));
|
||||||
RegisterHotkey("Screenshot", Key.C, ModifierKeys.Alt, () => _mainWindow.SaveScreenShotToDesktop());
|
RegisterHotkey("Screenshot", Key.C, ModifierKeys.Alt, () => _mainWindow.SaveScreenShotToDesktop());
|
||||||
|
RegisterHotkey("QuickDraw", Key.K, ModifierKeys.Alt, () => _mainWindow.OpenQuickDrawFromHotkey());
|
||||||
RegisterHotkey("Hide", Key.V, ModifierKeys.Alt, () => _mainWindow.SymbolIconEmoji_MouseUp(null, null));
|
RegisterHotkey("Hide", Key.V, ModifierKeys.Alt, () => _mainWindow.SymbolIconEmoji_MouseUp(null, null));
|
||||||
|
|
||||||
// 退出快捷键
|
// 退出快捷键
|
||||||
@@ -1033,6 +1034,7 @@ namespace Ink_Canvas.Helpers
|
|||||||
new HotkeyConfigItem { Name = "Pen5", Key = Key.D5, Modifiers = ModifierKeys.Alt },
|
new HotkeyConfigItem { Name = "Pen5", Key = Key.D5, Modifiers = ModifierKeys.Alt },
|
||||||
new HotkeyConfigItem { Name = "DrawLine", Key = Key.L, Modifiers = ModifierKeys.Alt },
|
new HotkeyConfigItem { Name = "DrawLine", Key = Key.L, Modifiers = ModifierKeys.Alt },
|
||||||
new HotkeyConfigItem { Name = "Screenshot", Key = Key.C, Modifiers = ModifierKeys.Alt },
|
new HotkeyConfigItem { Name = "Screenshot", Key = Key.C, Modifiers = ModifierKeys.Alt },
|
||||||
|
new HotkeyConfigItem { Name = "QuickDraw", Key = Key.K, Modifiers = ModifierKeys.Alt },
|
||||||
new HotkeyConfigItem { Name = "Hide", Key = Key.V, Modifiers = ModifierKeys.Alt },
|
new HotkeyConfigItem { Name = "Hide", Key = Key.V, Modifiers = ModifierKeys.Alt },
|
||||||
new HotkeyConfigItem { Name = "Exit", Key = Key.Escape, Modifiers = ModifierKeys.None }
|
new HotkeyConfigItem { Name = "Exit", Key = Key.Escape, Modifiers = ModifierKeys.None }
|
||||||
});
|
});
|
||||||
@@ -1111,6 +1113,14 @@ namespace Ink_Canvas.Helpers
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 旧版 HotkeyConfig.json 无「快抽」项时补注册默认组合,避免升级后无快捷键
|
||||||
|
if (successCount > 0 && !IsHotkeyRegistered("QuickDraw"))
|
||||||
|
{
|
||||||
|
var quickDrawAction = GetActionByName("QuickDraw");
|
||||||
|
if (quickDrawAction != null && RegisterHotkey("QuickDraw", Key.K, ModifierKeys.Alt, quickDrawAction))
|
||||||
|
successCount++;
|
||||||
|
}
|
||||||
|
|
||||||
if (successCount > 0)
|
if (successCount > 0)
|
||||||
{
|
{
|
||||||
_hotkeysShouldBeRegistered = true;
|
_hotkeysShouldBeRegistered = true;
|
||||||
@@ -1221,6 +1231,8 @@ namespace Ink_Canvas.Helpers
|
|||||||
return () => _mainWindow.BtnDrawLine_Click(null, null);
|
return () => _mainWindow.BtnDrawLine_Click(null, null);
|
||||||
case "Screenshot":
|
case "Screenshot":
|
||||||
return () => _mainWindow.SaveScreenShotToDesktop();
|
return () => _mainWindow.SaveScreenShotToDesktop();
|
||||||
|
case "QuickDraw":
|
||||||
|
return () => _mainWindow.OpenQuickDrawFromHotkey();
|
||||||
case "Hide":
|
case "Hide":
|
||||||
return () => _mainWindow.SymbolIconEmoji_MouseUp(null, null);
|
return () => _mainWindow.SymbolIconEmoji_MouseUp(null, null);
|
||||||
case "Exit":
|
case "Exit":
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
using Ink_Canvas;
|
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using System.Windows;
|
using System.Windows;
|
||||||
@@ -49,13 +48,17 @@ namespace Ink_Canvas.Helpers
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
if (alternates.Count > 0)
|
if (alternates.Count > 0)
|
||||||
analysisAlternate = alternates[0];
|
{
|
||||||
|
var altFinal = alternates[0];
|
||||||
|
if (altFinal?.AlternateNodes != null && altFinal.AlternateNodes.Count > 0)
|
||||||
|
analysisAlternate = altFinal;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
analyzer.Dispose();
|
analyzer.Dispose();
|
||||||
|
|
||||||
if (analysisAlternate != null && analysisAlternate.AlternateNodes.Count > 0)
|
if (analysisAlternate != null && analysisAlternate.AlternateNodes != null && analysisAlternate.AlternateNodes.Count > 0)
|
||||||
{
|
{
|
||||||
var node = analysisAlternate.AlternateNodes[0] as InkDrawingNode;
|
var node = analysisAlternate.AlternateNodes[0] as InkDrawingNode;
|
||||||
if (node == null)
|
if (node == null)
|
||||||
@@ -149,6 +152,9 @@ namespace Ink_Canvas.Helpers
|
|||||||
|
|
||||||
var node = legacy.InkDrawingNode;
|
var node = legacy.InkDrawingNode;
|
||||||
var shape = node.GetShape();
|
var shape = node.GetShape();
|
||||||
|
if (shape == null)
|
||||||
|
return InkShapeRecognitionResult.Empty;
|
||||||
|
|
||||||
var hot = ClonePointCollection(node.HotPoints);
|
var hot = ClonePointCollection(node.HotPoints);
|
||||||
return new InkShapeRecognitionResult(
|
return new InkShapeRecognitionResult(
|
||||||
node.GetShapeName(),
|
node.GetShapeName(),
|
||||||
@@ -170,6 +176,9 @@ namespace Ink_Canvas.Helpers
|
|||||||
|
|
||||||
public static bool IsContainShapeType(string name)
|
public static bool IsContainShapeType(string name)
|
||||||
{
|
{
|
||||||
|
if (string.IsNullOrEmpty(name))
|
||||||
|
return false;
|
||||||
|
|
||||||
if (name.Contains("Triangle") || name.Contains("Circle") ||
|
if (name.Contains("Triangle") || name.Contains("Circle") ||
|
||||||
name.Contains("Rectangle") || name.Contains("Diamond") ||
|
name.Contains("Rectangle") || name.Contains("Diamond") ||
|
||||||
name.Contains("Parallelogram") || name.Contains("Square")
|
name.Contains("Parallelogram") || name.Contains("Square")
|
||||||
|
|||||||
@@ -390,11 +390,15 @@ namespace Ink_Canvas.Helpers
|
|||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
PPTApplication.PresentationOpen -= OnPresentationOpen;
|
// 再次检查PPTApplication是否为null,因为可能在异步操作期间被修改
|
||||||
PPTApplication.PresentationClose -= OnPresentationClose;
|
if (PPTApplication != null && Marshal.IsComObject(PPTApplication))
|
||||||
PPTApplication.SlideShowBegin -= OnSlideShowBegin;
|
{
|
||||||
PPTApplication.SlideShowNextSlide -= OnSlideShowNextSlide;
|
PPTApplication.PresentationOpen -= OnPresentationOpen;
|
||||||
PPTApplication.SlideShowEnd -= OnSlideShowEnd;
|
PPTApplication.PresentationClose -= OnPresentationClose;
|
||||||
|
PPTApplication.SlideShowBegin -= OnSlideShowBegin;
|
||||||
|
PPTApplication.SlideShowNextSlide -= OnSlideShowNextSlide;
|
||||||
|
PPTApplication.SlideShowEnd -= OnSlideShowEnd;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
catch (COMException comEx)
|
catch (COMException comEx)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -0,0 +1,79 @@
|
|||||||
|
using System;
|
||||||
|
using System.IO;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using System.Windows;
|
||||||
|
using System.Windows.Media.Imaging;
|
||||||
|
using Windows.Data.Pdf;
|
||||||
|
using Windows.Storage;
|
||||||
|
using Windows.Storage.Streams;
|
||||||
|
|
||||||
|
namespace Ink_Canvas.Helpers
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// 使用 Windows.Data.Pdf(WinRT)将 PDF 页渲染为 WPF 可用的位图。
|
||||||
|
/// </summary>
|
||||||
|
internal static class PdfWinRtHelper
|
||||||
|
{
|
||||||
|
public static async Task<uint> GetPageCountAsync(string pdfPath)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrWhiteSpace(pdfPath) || !File.Exists(pdfPath))
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
var file = await StorageFile.GetFileFromPathAsync(pdfPath).AsTask();
|
||||||
|
var doc = await PdfDocument.LoadFromFileAsync(file).AsTask();
|
||||||
|
if (doc.IsPasswordProtected)
|
||||||
|
return 0;
|
||||||
|
return doc.PageCount;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static async Task<BitmapSource> RenderPageToBitmapSourceAsync(string pdfPath, uint pageIndex)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrWhiteSpace(pdfPath) || !File.Exists(pdfPath))
|
||||||
|
return null;
|
||||||
|
|
||||||
|
var file = await StorageFile.GetFileFromPathAsync(pdfPath).AsTask();
|
||||||
|
var doc = await PdfDocument.LoadFromFileAsync(file).AsTask();
|
||||||
|
if (doc.IsPasswordProtected)
|
||||||
|
return null;
|
||||||
|
if (pageIndex >= doc.PageCount)
|
||||||
|
return null;
|
||||||
|
|
||||||
|
var page = doc.GetPage(pageIndex);
|
||||||
|
try
|
||||||
|
{
|
||||||
|
using (var ras = new InMemoryRandomAccessStream())
|
||||||
|
{
|
||||||
|
await page.RenderToStreamAsync(ras).AsTask();
|
||||||
|
ras.Seek(0);
|
||||||
|
|
||||||
|
var ms = new MemoryStream();
|
||||||
|
using (var netStream = ras.AsStreamForRead())
|
||||||
|
netStream.CopyTo(ms);
|
||||||
|
ms.Position = 0;
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
return await Application.Current.Dispatcher.InvokeAsync(() =>
|
||||||
|
{
|
||||||
|
var bi = new BitmapImage();
|
||||||
|
bi.BeginInit();
|
||||||
|
bi.StreamSource = ms;
|
||||||
|
bi.CacheOption = BitmapCacheOption.OnLoad;
|
||||||
|
bi.EndInit();
|
||||||
|
bi.Freeze();
|
||||||
|
return (BitmapSource)bi;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
ms.Dispose();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
(page as IDisposable)?.Dispose();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,274 +0,0 @@
|
|||||||
using iNKORE.UI.WPF.Controls;
|
|
||||||
using System;
|
|
||||||
using System.Windows;
|
|
||||||
using System.Windows.Controls;
|
|
||||||
using System.Windows.Input;
|
|
||||||
using System.Windows.Media;
|
|
||||||
|
|
||||||
namespace Ink_Canvas.Helpers.Plugins.BuiltIn.SuperLauncher
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// 启动台按钮控件
|
|
||||||
/// </summary>
|
|
||||||
public class LauncherButton
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// 父插件
|
|
||||||
/// </summary>
|
|
||||||
private readonly SuperLauncherPlugin _plugin;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 实际按钮控件
|
|
||||||
/// </summary>
|
|
||||||
private readonly SimpleStackPanel _panel;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 获取按钮UI元素
|
|
||||||
/// </summary>
|
|
||||||
public UIElement Element => _panel;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 构造函数
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="plugin">父插件</param>
|
|
||||||
public LauncherButton(SuperLauncherPlugin plugin)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
_plugin = plugin;
|
|
||||||
LogHelper.WriteLogToFile("开始创建启动台按钮");
|
|
||||||
|
|
||||||
// 创建SimpleStackPanel
|
|
||||||
_panel = new SimpleStackPanel
|
|
||||||
{
|
|
||||||
Name = "Launcher_Icon",
|
|
||||||
Orientation = Orientation.Vertical,
|
|
||||||
HorizontalAlignment = HorizontalAlignment.Center,
|
|
||||||
Width = 28,
|
|
||||||
Margin = new Thickness(0, -2, 0, 0),
|
|
||||||
Background = Brushes.Transparent
|
|
||||||
};
|
|
||||||
|
|
||||||
LogHelper.WriteLogToFile("创建SimpleStackPanel完成");
|
|
||||||
|
|
||||||
// 添加图标
|
|
||||||
var image = CreateIconImage();
|
|
||||||
_panel.Children.Add(image);
|
|
||||||
|
|
||||||
// 添加文本
|
|
||||||
TextBlock textBlock = new TextBlock
|
|
||||||
{
|
|
||||||
Text = "启动台",
|
|
||||||
Foreground = Brushes.Black,
|
|
||||||
FontSize = 8,
|
|
||||||
Margin = new Thickness(0, 1, 0, 0),
|
|
||||||
TextAlignment = TextAlignment.Center
|
|
||||||
};
|
|
||||||
_panel.Children.Add(textBlock);
|
|
||||||
|
|
||||||
// 设置鼠标事件
|
|
||||||
_panel.MouseDown += Panel_MouseDown;
|
|
||||||
_panel.MouseUp += Panel_MouseUp;
|
|
||||||
_panel.MouseLeave += Panel_MouseLeave;
|
|
||||||
|
|
||||||
// 右键菜单支持
|
|
||||||
_panel.ContextMenu = CreateContextMenu();
|
|
||||||
|
|
||||||
// 设置工具提示
|
|
||||||
_panel.ToolTip = "启动台";
|
|
||||||
|
|
||||||
LogHelper.WriteLogToFile("启动台按钮创建完成");
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
LogHelper.WriteLogToFile($"创建启动台按钮时出错: {ex.Message}", LogHelper.LogType.Error);
|
|
||||||
LogHelper.NewLog(ex);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 创建右键菜单
|
|
||||||
/// </summary>
|
|
||||||
private ContextMenu CreateContextMenu()
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
// 创建菜单
|
|
||||||
ContextMenu menu = new ContextMenu();
|
|
||||||
|
|
||||||
// 创建位置切换菜单项
|
|
||||||
MenuItem positionMenuItem = new MenuItem();
|
|
||||||
positionMenuItem.Header = _plugin.Config.ButtonPosition == LauncherButtonPosition.Left ?
|
|
||||||
"移至右侧" : "移至左侧";
|
|
||||||
positionMenuItem.Click += (s, e) =>
|
|
||||||
{
|
|
||||||
// 切换位置
|
|
||||||
_plugin.Config.ButtonPosition = _plugin.Config.ButtonPosition == LauncherButtonPosition.Left ?
|
|
||||||
LauncherButtonPosition.Right : LauncherButtonPosition.Left;
|
|
||||||
|
|
||||||
// 更新按钮位置
|
|
||||||
_plugin.UpdateButtonPosition();
|
|
||||||
|
|
||||||
// 保存配置
|
|
||||||
_plugin.SaveConfig();
|
|
||||||
|
|
||||||
LogHelper.WriteLogToFile($"通过右键菜单切换启动台按钮位置为: {_plugin.Config.ButtonPosition}");
|
|
||||||
};
|
|
||||||
menu.Items.Add(positionMenuItem);
|
|
||||||
|
|
||||||
// 添加设置菜单项
|
|
||||||
MenuItem settingsMenuItem = new MenuItem();
|
|
||||||
settingsMenuItem.Header = "打开设置";
|
|
||||||
settingsMenuItem.Click += (s, e) =>
|
|
||||||
{
|
|
||||||
// 打开插件设置窗口
|
|
||||||
var mainWindow = Application.Current.MainWindow;
|
|
||||||
if (mainWindow != null)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
// 使用反射调用主窗口的ShowPluginSettings方法
|
|
||||||
var method = mainWindow.GetType().GetMethod("ShowPluginSettings");
|
|
||||||
if (method != null)
|
|
||||||
{
|
|
||||||
method.Invoke(mainWindow, null);
|
|
||||||
LogHelper.WriteLogToFile("已打开插件设置窗口");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
LogHelper.WriteLogToFile($"打开插件设置窗口失败: {ex.Message}", LogHelper.LogType.Error);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
menu.Items.Add(settingsMenuItem);
|
|
||||||
|
|
||||||
return menu;
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
LogHelper.WriteLogToFile($"创建右键菜单时出错: {ex.Message}", LogHelper.LogType.Error);
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 获取实际的UI元素
|
|
||||||
/// </summary>
|
|
||||||
[Obsolete("使用Element属性代替")]
|
|
||||||
public UIElement GetUIElement()
|
|
||||||
{
|
|
||||||
return _panel;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 创建图标图像
|
|
||||||
/// </summary>
|
|
||||||
private Image CreateIconImage()
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
// 创建图像
|
|
||||||
Image image = new Image
|
|
||||||
{
|
|
||||||
Height = 17,
|
|
||||||
Margin = new Thickness(0, 3, 0, 0)
|
|
||||||
};
|
|
||||||
|
|
||||||
// 设置位图缩放模式
|
|
||||||
RenderOptions.SetBitmapScalingMode(image, BitmapScalingMode.HighQuality);
|
|
||||||
|
|
||||||
// 创建绘图图像
|
|
||||||
DrawingImage drawingImage = new DrawingImage();
|
|
||||||
DrawingGroup drawingGroup = new DrawingGroup();
|
|
||||||
drawingGroup.ClipGeometry = Geometry.Parse("M0,0 V24 H24 V0 H0 Z");
|
|
||||||
|
|
||||||
// 使用提供的应用网格图标
|
|
||||||
GeometryDrawing geometryDrawing = new GeometryDrawing
|
|
||||||
{
|
|
||||||
Brush = new SolidColorBrush(Color.FromRgb(0x1B, 0x1B, 0x1B)),
|
|
||||||
Geometry = Geometry.Parse("F0 M24,24z M0,0z M4.41721,4.29873C4.35178,4.29873,4.29873,4.35178,4.29873,4.41721L4.29873,9.15646C4.29873,9.22189,4.35178,9.27494,4.41721,9.27494L9.15646,9.27494C9.22189,9.27494,9.27494,9.22189,9.27494,9.15646L9.27494,4.41721C9.27494,4.35178,9.22189,4.29873,9.15646,4.29873L4.41721,4.29873z M2.64,4.41721C2.64,3.43569,3.43569,2.64,4.41721,2.64L9.15646,2.64C10.138,2.64,10.9337,3.43569,10.9337,4.41721L10.9337,9.15646C10.9337,10.138,10.138,10.9337,9.15646,10.9337L4.41721,10.9337C3.43569,10.9337,2.64,10.138,2.64,9.15646L2.64,4.41721z M14.8435,4.29873C14.7781,4.29873,14.7251,4.35178,14.7251,4.41721L14.7251,9.15646C14.7251,9.22189,14.7781,9.27494,14.8435,9.27494L19.5828,9.27494C19.6482,9.27494,19.7013,9.22189,19.7013,9.15646L19.7013,4.41721C19.7013,4.35178,19.6482,4.29873,19.5828,4.29873L14.8435,4.29873z M13.0663,4.41721C13.0663,3.43569,13.862,2.64,14.8435,2.64L19.5828,2.64C20.5643,2.64,21.36,3.43569,21.36,4.41721L21.36,9.15646C21.36,10.138,20.5643,10.9337,19.5828,10.9337L14.8435,10.9337C13.862,10.9337,13.0663,10.138,13.0663,9.15646L13.0663,4.41721z M14.8435,14.7251C14.7781,14.7251,14.7251,14.7781,14.7251,14.8435L14.7251,19.5828C14.7251,19.6482,14.7781,19.7013,14.8435,19.7013L19.5828,19.7013C19.6482,19.7013,19.7013,19.6482,19.7013,19.5828L19.7013,14.8435C19.7013,14.7781,19.6482,14.7251,19.5828,14.7251L14.8435,14.7251z M13.0663,14.8435C13.0663,13.862,13.862,13.0663,14.8435,13.0663L19.5828,13.0663C20.5643,13.0663,21.36,13.862,21.36,14.8435L21.36,19.5828C21.36,20.5643,20.5643,21.36,19.5828,21.36L14.8435,21.36C13.862,21.36,13.0663,20.5643,13.0663,19.5828L13.0663,14.8435z M4.41721,14.7251C4.35178,14.7251,4.29873,14.7781,4.29873,14.8435L4.29873,19.5828C4.29873,19.6482,4.35178,19.7013,4.41721,19.7013L9.15646,19.7013C9.22189,19.7013,9.27494,19.6482,9.27494,19.5828L9.27494,14.8435C9.27494,14.7781,9.22189,14.7251,9.15646,14.7251L4.41721,14.7251z M2.64,14.8435C2.64,13.862,3.43569,13.0663,4.41721,13.0663L9.15646,13.0663C10.138,13.0663,10.9337,13.862,10.9337,14.8435L10.9337,19.5828C10.9337,20.5643,10.138,21.36,9.15646,21.36L4.41721,21.36C3.43569,21.36,2.64,20.5643,2.64,19.5828L2.64,14.8435z")
|
|
||||||
};
|
|
||||||
|
|
||||||
drawingGroup.Children.Add(geometryDrawing);
|
|
||||||
|
|
||||||
// 设置图像源
|
|
||||||
drawingImage.Drawing = drawingGroup;
|
|
||||||
image.Source = drawingImage;
|
|
||||||
|
|
||||||
return image;
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
LogHelper.WriteLogToFile($"创建图标图像时出错: {ex.Message}", LogHelper.LogType.Error);
|
|
||||||
LogHelper.NewLog(ex);
|
|
||||||
|
|
||||||
// 返回一个空图像
|
|
||||||
return new Image();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 鼠标按下事件
|
|
||||||
/// </summary>
|
|
||||||
private void Panel_MouseDown(object sender, MouseButtonEventArgs e)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
// 提供反馈
|
|
||||||
_panel.Background = new SolidColorBrush(Color.FromArgb(40, 0, 0, 0));
|
|
||||||
LogHelper.WriteLogToFile("启动台按钮鼠标按下");
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
LogHelper.WriteLogToFile($"启动台按钮鼠标按下事件出错: {ex.Message}", LogHelper.LogType.Error);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 鼠标抬起事件
|
|
||||||
/// </summary>
|
|
||||||
private void Panel_MouseUp(object sender, MouseButtonEventArgs e)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
// 只有左键点击才显示启动台窗口
|
|
||||||
if (e.ChangedButton != MouseButton.Left)
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 恢复背景
|
|
||||||
_panel.Background = Brushes.Transparent;
|
|
||||||
LogHelper.WriteLogToFile("启动台按钮鼠标抬起,准备显示启动台窗口");
|
|
||||||
|
|
||||||
// 获取按钮在屏幕上的位置
|
|
||||||
Point buttonPosition = _panel.PointToScreen(new Point(_panel.ActualWidth / 2, 0));
|
|
||||||
|
|
||||||
// 显示启动台窗口
|
|
||||||
_plugin.ShowLauncherWindow(buttonPosition);
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
LogHelper.WriteLogToFile($"启动台按钮鼠标抬起事件出错: {ex.Message}", LogHelper.LogType.Error);
|
|
||||||
LogHelper.NewLog(ex);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 鼠标离开事件
|
|
||||||
/// </summary>
|
|
||||||
private void Panel_MouseLeave(object sender, MouseEventArgs e)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
// 恢复背景
|
|
||||||
_panel.Background = Brushes.Transparent;
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
LogHelper.WriteLogToFile($"启动台按钮鼠标离开事件出错: {ex.Message}", LogHelper.LogType.Error);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,332 +0,0 @@
|
|||||||
using Microsoft.Win32;
|
|
||||||
using Newtonsoft.Json;
|
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Diagnostics;
|
|
||||||
using System.Drawing;
|
|
||||||
using System.IO;
|
|
||||||
using System.Runtime.InteropServices;
|
|
||||||
using System.Windows;
|
|
||||||
using System.Windows.Interop;
|
|
||||||
using System.Windows.Media;
|
|
||||||
using System.Windows.Media.Imaging;
|
|
||||||
|
|
||||||
namespace Ink_Canvas.Helpers.Plugins.BuiltIn.SuperLauncher
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// 启动台按钮位置
|
|
||||||
/// </summary>
|
|
||||||
public enum LauncherButtonPosition
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// 左侧
|
|
||||||
/// </summary>
|
|
||||||
Left,
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 右侧
|
|
||||||
/// </summary>
|
|
||||||
Right
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 启动台配置
|
|
||||||
/// </summary>
|
|
||||||
public class LauncherConfig
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// 启动台按钮位置
|
|
||||||
/// </summary>
|
|
||||||
public LauncherButtonPosition ButtonPosition { get; set; } = LauncherButtonPosition.Right;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 启动台应用程序列表
|
|
||||||
/// </summary>
|
|
||||||
public List<LauncherItem> Items { get; set; } = new List<LauncherItem>();
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 启动台应用项
|
|
||||||
/// </summary>
|
|
||||||
public class LauncherItem
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// 应用程序名称
|
|
||||||
/// </summary>
|
|
||||||
public string Name { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 应用程序路径
|
|
||||||
/// </summary>
|
|
||||||
public string Path { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 是否可见
|
|
||||||
/// </summary>
|
|
||||||
public bool IsVisible { get; set; } = true;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 在启动台中的位置(0-39)
|
|
||||||
/// </summary>
|
|
||||||
public int Position { get; set; } = -1;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 是否已固定位置
|
|
||||||
/// </summary>
|
|
||||||
public bool IsPositionFixed { get; set; } = false;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 图标缓存
|
|
||||||
/// </summary>
|
|
||||||
[JsonIgnore]
|
|
||||||
private ImageSource _iconCache;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 获取应用程序图标
|
|
||||||
/// </summary>
|
|
||||||
[JsonIgnore]
|
|
||||||
public ImageSource Icon
|
|
||||||
{
|
|
||||||
get
|
|
||||||
{
|
|
||||||
if (_iconCache != null)
|
|
||||||
{
|
|
||||||
return _iconCache;
|
|
||||||
}
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
if (File.Exists(Path))
|
|
||||||
{
|
|
||||||
// 从文件中获取图标
|
|
||||||
Icon icon = System.Drawing.Icon.ExtractAssociatedIcon(Path);
|
|
||||||
if (icon != null)
|
|
||||||
{
|
|
||||||
_iconCache = Imaging.CreateBitmapSourceFromHIcon(
|
|
||||||
icon.Handle,
|
|
||||||
Int32Rect.Empty,
|
|
||||||
BitmapSizeOptions.FromEmptyOptions());
|
|
||||||
|
|
||||||
icon.Dispose();
|
|
||||||
return _iconCache;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
// 从注册表中获取文件类型关联图标
|
|
||||||
string extension = System.IO.Path.GetExtension(Path);
|
|
||||||
if (!string.IsNullOrEmpty(extension))
|
|
||||||
{
|
|
||||||
string fileType = Registry.ClassesRoot.OpenSubKey(extension)?.GetValue(string.Empty) as string;
|
|
||||||
if (!string.IsNullOrEmpty(fileType))
|
|
||||||
{
|
|
||||||
string iconPath = Registry.ClassesRoot.OpenSubKey(fileType + "\\DefaultIcon")?.GetValue(string.Empty) as string;
|
|
||||||
if (!string.IsNullOrEmpty(iconPath))
|
|
||||||
{
|
|
||||||
string[] parts = iconPath.Split(',');
|
|
||||||
string iconFile = parts[0].Trim('"');
|
|
||||||
int iconIndex = parts.Length > 1 ? Convert.ToInt32(parts[1]) : 0;
|
|
||||||
|
|
||||||
if (File.Exists(iconFile))
|
|
||||||
{
|
|
||||||
Icon icon = IconExtractor.Extract(iconFile, iconIndex, true);
|
|
||||||
if (icon != null)
|
|
||||||
{
|
|
||||||
_iconCache = Imaging.CreateBitmapSourceFromHIcon(
|
|
||||||
icon.Handle,
|
|
||||||
Int32Rect.Empty,
|
|
||||||
BitmapSizeOptions.FromEmptyOptions());
|
|
||||||
|
|
||||||
icon.Dispose();
|
|
||||||
return _iconCache;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
LogHelper.WriteLogToFile($"获取应用图标时出错: {ex.Message}", LogHelper.LogType.Error);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 返回默认图标
|
|
||||||
return GetDefaultIcon();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 获取默认图标
|
|
||||||
/// </summary>
|
|
||||||
private ImageSource GetDefaultIcon()
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
// 对于资源管理器,使用特定图标
|
|
||||||
if (Path.EndsWith("explorer.exe", StringComparison.OrdinalIgnoreCase))
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
// 直接从C:\Windows\explorer.exe获取图标
|
|
||||||
string explorerPath = @"C:\Windows\explorer.exe";
|
|
||||||
if (File.Exists(explorerPath))
|
|
||||||
{
|
|
||||||
Icon icon = System.Drawing.Icon.ExtractAssociatedIcon(explorerPath);
|
|
||||||
if (icon != null)
|
|
||||||
{
|
|
||||||
_iconCache = Imaging.CreateBitmapSourceFromHIcon(
|
|
||||||
icon.Handle,
|
|
||||||
Int32Rect.Empty,
|
|
||||||
BitmapSizeOptions.FromEmptyOptions());
|
|
||||||
|
|
||||||
icon.Dispose();
|
|
||||||
return _iconCache;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
LogHelper.WriteLogToFile($"获取资源管理器图标时出错: {ex.Message}", LogHelper.LogType.Warning);
|
|
||||||
// 如果获取Windows图标失败,回退到默认图标
|
|
||||||
}
|
|
||||||
|
|
||||||
// 回退到备用图标
|
|
||||||
string explorerIconPath = System.IO.Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Resources", "Icons-Fluent", "ic_fluent_folder_24_regular.png");
|
|
||||||
if (File.Exists(explorerIconPath))
|
|
||||||
{
|
|
||||||
Uri uri = new Uri(explorerIconPath);
|
|
||||||
BitmapImage image = new BitmapImage(uri);
|
|
||||||
_iconCache = image;
|
|
||||||
return _iconCache;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 返回一个简单的默认图标
|
|
||||||
string iconPath = System.IO.Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Resources", "Icons-png", "icc.png");
|
|
||||||
if (File.Exists(iconPath))
|
|
||||||
{
|
|
||||||
Uri uri = new Uri(iconPath);
|
|
||||||
BitmapImage image = new BitmapImage(uri);
|
|
||||||
_iconCache = image;
|
|
||||||
return _iconCache;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 如果还是没有找到,尝试使用应用程序图标
|
|
||||||
string appIconPath = System.IO.Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Resources", "Icons-Fluent", "ic_fluent_apps_24_regular.png");
|
|
||||||
if (File.Exists(appIconPath))
|
|
||||||
{
|
|
||||||
Uri uri = new Uri(appIconPath);
|
|
||||||
BitmapImage image = new BitmapImage(uri);
|
|
||||||
_iconCache = image;
|
|
||||||
return _iconCache;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
LogHelper.WriteLogToFile($"获取默认图标时出错: {ex.Message}", LogHelper.LogType.Error);
|
|
||||||
}
|
|
||||||
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 启动应用程序
|
|
||||||
/// </summary>
|
|
||||||
public void Launch()
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
if (string.IsNullOrEmpty(Path))
|
|
||||||
{
|
|
||||||
LogHelper.WriteLogToFile("无法启动应用程序:路径为空", LogHelper.LogType.Error);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 检查文件是否存在
|
|
||||||
if (!File.Exists(Path) && !Path.Contains(":\\"))
|
|
||||||
{
|
|
||||||
// 可能是系统命令,如explorer.exe
|
|
||||||
ProcessStartInfo psi = new ProcessStartInfo
|
|
||||||
{
|
|
||||||
FileName = Path,
|
|
||||||
UseShellExecute = true
|
|
||||||
};
|
|
||||||
Process.Start(psi);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
// 使用Process.Start启动应用程序
|
|
||||||
ProcessStartInfo psi = new ProcessStartInfo
|
|
||||||
{
|
|
||||||
FileName = Path,
|
|
||||||
UseShellExecute = true
|
|
||||||
};
|
|
||||||
Process.Start(psi);
|
|
||||||
}
|
|
||||||
|
|
||||||
LogHelper.WriteLogToFile($"已启动应用程序: {Path}");
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
LogHelper.WriteLogToFile($"启动应用程序时出错: {ex.Message}", LogHelper.LogType.Error);
|
|
||||||
MessageBox.Show($"启动应用程序时出错: {ex.Message}", "错误", MessageBoxButton.OK, MessageBoxImage.Error);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 图标提取工具类
|
|
||||||
/// </summary>
|
|
||||||
public static class IconExtractor
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// 从文件中提取图标
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="file">文件路径</param>
|
|
||||||
/// <param name="index">图标索引</param>
|
|
||||||
/// <param name="largeIcon">是否提取大图标</param>
|
|
||||||
/// <returns>提取的图标</returns>
|
|
||||||
public static Icon Extract(string file, int index, bool largeIcon)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
IntPtr large;
|
|
||||||
IntPtr small;
|
|
||||||
ExtractIconEx(file, index, out large, out small, 1);
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
return Icon.FromHandle(largeIcon ? large : small);
|
|
||||||
}
|
|
||||||
catch
|
|
||||||
{
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
finally
|
|
||||||
{
|
|
||||||
if (large != IntPtr.Zero)
|
|
||||||
DestroyIcon(large);
|
|
||||||
|
|
||||||
if (small != IntPtr.Zero)
|
|
||||||
DestroyIcon(small);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch
|
|
||||||
{
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
[DllImport("Shell32.dll", EntryPoint = "ExtractIconEx")]
|
|
||||||
private static extern int ExtractIconEx(
|
|
||||||
[MarshalAs(UnmanagedType.LPStr)] string lpszFile,
|
|
||||||
int nIconIndex,
|
|
||||||
out IntPtr phiconLarge,
|
|
||||||
out IntPtr phiconSmall,
|
|
||||||
int nIcons);
|
|
||||||
|
|
||||||
[DllImport("User32.dll")]
|
|
||||||
private static extern int DestroyIcon(IntPtr hIcon);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,143 +0,0 @@
|
|||||||
<UserControl x:Class="Ink_Canvas.Helpers.Plugins.BuiltIn.SuperLauncher.LauncherSettingsControl"
|
|
||||||
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.Helpers.Plugins.BuiltIn.SuperLauncher"
|
|
||||||
mc:Ignorable="d"
|
|
||||||
d:DesignHeight="500" d:DesignWidth="600">
|
|
||||||
|
|
||||||
<UserControl.Resources>
|
|
||||||
<!-- 自定义按钮样式 -->
|
|
||||||
<Style x:Key="DefaultButtonStyle" TargetType="Button">
|
|
||||||
<Setter Property="FontWeight" Value="SemiBold"/>
|
|
||||||
<Setter Property="FontSize" Value="12"/>
|
|
||||||
<Setter Property="Cursor" Value="Hand"/>
|
|
||||||
<Setter Property="Template">
|
|
||||||
<Setter.Value>
|
|
||||||
<ControlTemplate TargetType="Button">
|
|
||||||
<Border x:Name="border"
|
|
||||||
Background="{TemplateBinding Background}"
|
|
||||||
BorderBrush="{TemplateBinding BorderBrush}"
|
|
||||||
BorderThickness="{TemplateBinding BorderThickness}"
|
|
||||||
CornerRadius="4"
|
|
||||||
Padding="{TemplateBinding Padding}">
|
|
||||||
<ContentPresenter HorizontalAlignment="Center"
|
|
||||||
VerticalAlignment="Center"
|
|
||||||
TextElement.Foreground="{TemplateBinding Foreground}"/>
|
|
||||||
</Border>
|
|
||||||
<ControlTemplate.Triggers>
|
|
||||||
<Trigger Property="IsMouseOver" Value="True">
|
|
||||||
<Setter TargetName="border" Property="Opacity" Value="0.8"/>
|
|
||||||
<Setter Property="Effect">
|
|
||||||
<Setter.Value>
|
|
||||||
<DropShadowEffect Color="Black" Direction="270" ShadowDepth="2" Opacity="0.3" BlurRadius="4"/>
|
|
||||||
</Setter.Value>
|
|
||||||
</Setter>
|
|
||||||
</Trigger>
|
|
||||||
<Trigger Property="IsPressed" Value="True">
|
|
||||||
<Setter TargetName="border" Property="Opacity" Value="0.6"/>
|
|
||||||
<Setter Property="RenderTransform">
|
|
||||||
<Setter.Value>
|
|
||||||
<ScaleTransform ScaleX="0.95" ScaleY="0.95"/>
|
|
||||||
</Setter.Value>
|
|
||||||
</Setter>
|
|
||||||
<Setter Property="RenderTransformOrigin" Value="0.5,0.5"/>
|
|
||||||
</Trigger>
|
|
||||||
<Trigger Property="IsEnabled" Value="False">
|
|
||||||
<Setter TargetName="border" Property="Opacity" Value="0.4"/>
|
|
||||||
<Setter Property="Cursor" Value="Arrow"/>
|
|
||||||
</Trigger>
|
|
||||||
</ControlTemplate.Triggers>
|
|
||||||
</ControlTemplate>
|
|
||||||
</Setter.Value>
|
|
||||||
</Setter>
|
|
||||||
</Style>
|
|
||||||
</UserControl.Resources>
|
|
||||||
|
|
||||||
<Grid Margin="10">
|
|
||||||
<Grid.RowDefinitions>
|
|
||||||
<RowDefinition Height="Auto"/>
|
|
||||||
<RowDefinition Height="Auto"/>
|
|
||||||
<RowDefinition Height="*"/>
|
|
||||||
<RowDefinition Height="Auto"/>
|
|
||||||
</Grid.RowDefinitions>
|
|
||||||
|
|
||||||
<!-- 标题 -->
|
|
||||||
<TextBlock Grid.Row="0" Text="超级启动台设置" FontSize="16" FontWeight="Bold" Margin="0,0,0,15" Foreground="Black"/>
|
|
||||||
|
|
||||||
<!-- 基本设置 -->
|
|
||||||
<StackPanel Grid.Row="1" Margin="0,0,0,15">
|
|
||||||
<TextBlock Text="基本设置" FontSize="14" FontWeight="SemiBold" Margin="0,0,0,10" Foreground="Black"/>
|
|
||||||
|
|
||||||
<Grid Margin="10,0,0,0">
|
|
||||||
<Grid.ColumnDefinitions>
|
|
||||||
<ColumnDefinition Width="120"/>
|
|
||||||
<ColumnDefinition Width="*"/>
|
|
||||||
</Grid.ColumnDefinitions>
|
|
||||||
<Grid.RowDefinitions>
|
|
||||||
<RowDefinition Height="Auto"/>
|
|
||||||
</Grid.RowDefinitions>
|
|
||||||
|
|
||||||
<!-- 按钮位置 -->
|
|
||||||
<TextBlock Grid.Row="0" Grid.Column="0" Text="按钮位置:" VerticalAlignment="Center" Foreground="Black"/>
|
|
||||||
<StackPanel Grid.Row="0" Grid.Column="1" Orientation="Horizontal" Margin="0,5">
|
|
||||||
<RadioButton x:Name="RbtnLeft" Content="浮动栏左侧" Margin="0,0,20,0" Checked="RbtnPosition_Checked" Foreground="Black"/>
|
|
||||||
<RadioButton x:Name="RbtnRight" Content="浮动栏右侧" IsChecked="True" Checked="RbtnPosition_Checked" Foreground="Black"/>
|
|
||||||
</StackPanel>
|
|
||||||
</Grid>
|
|
||||||
</StackPanel>
|
|
||||||
|
|
||||||
<!-- 应用管理 -->
|
|
||||||
<Grid Grid.Row="2">
|
|
||||||
<Grid.RowDefinitions>
|
|
||||||
<RowDefinition Height="Auto"/>
|
|
||||||
<RowDefinition Height="*"/>
|
|
||||||
</Grid.RowDefinitions>
|
|
||||||
|
|
||||||
<TextBlock Grid.Row="0" Text="应用管理" FontSize="14" FontWeight="SemiBold" Margin="0,0,0,10" Foreground="Black"/>
|
|
||||||
|
|
||||||
<Border Grid.Row="1" BorderThickness="1" BorderBrush="#CCCCCC" CornerRadius="5">
|
|
||||||
<Grid>
|
|
||||||
<Grid.RowDefinitions>
|
|
||||||
<RowDefinition Height="*"/>
|
|
||||||
<RowDefinition Height="Auto"/>
|
|
||||||
</Grid.RowDefinitions>
|
|
||||||
|
|
||||||
<!-- 应用列表 -->
|
|
||||||
<DataGrid Grid.Row="0" x:Name="DgApps" AutoGenerateColumns="False" Margin="5"
|
|
||||||
CanUserAddRows="False" CanUserDeleteRows="False"
|
|
||||||
HeadersVisibility="Column" SelectionMode="Single"
|
|
||||||
SelectionChanged="DgApps_SelectionChanged">
|
|
||||||
<DataGrid.Columns>
|
|
||||||
<DataGridCheckBoxColumn Header="显示" Binding="{Binding IsVisible}" Width="50"/>
|
|
||||||
<DataGridTextColumn Header="名称" Binding="{Binding Name}" Width="150"/>
|
|
||||||
<DataGridTextColumn Header="路径" Binding="{Binding Path}" Width="*"/>
|
|
||||||
<DataGridTextColumn Header="位置" Binding="{Binding Position}" Width="50"/>
|
|
||||||
</DataGrid.Columns>
|
|
||||||
</DataGrid>
|
|
||||||
|
|
||||||
<!-- 操作按钮 -->
|
|
||||||
<StackPanel Grid.Row="1" Orientation="Horizontal" Margin="5">
|
|
||||||
<Button x:Name="BtnAdd" Content="添加" Padding="10,5" Margin="0,5,5,5" Click="BtnAdd_Click"
|
|
||||||
Background="#FF007ACC" Foreground="White" BorderBrush="#FF005A9B" BorderThickness="1"
|
|
||||||
Style="{StaticResource DefaultButtonStyle}"/>
|
|
||||||
<Button x:Name="BtnEdit" Content="编辑" Padding="10,5" Margin="5" Click="BtnEdit_Click"
|
|
||||||
Background="#FF6C757D" Foreground="White" BorderBrush="#FF5A6268" BorderThickness="1"
|
|
||||||
Style="{StaticResource DefaultButtonStyle}"/>
|
|
||||||
<Button x:Name="BtnDelete" Content="删除" Padding="10,5" Margin="5" Click="BtnDelete_Click"
|
|
||||||
Background="#FFDC3545" Foreground="White" BorderBrush="#FFBD2130" BorderThickness="1"
|
|
||||||
Style="{StaticResource DefaultButtonStyle}"/>
|
|
||||||
</StackPanel>
|
|
||||||
</Grid>
|
|
||||||
</Border>
|
|
||||||
</Grid>
|
|
||||||
|
|
||||||
<!-- 底部按钮 -->
|
|
||||||
<StackPanel Grid.Row="3" Orientation="Horizontal" HorizontalAlignment="Right" Margin="0,15,0,0">
|
|
||||||
<Button x:Name="BtnSave" Content="保存设置" Padding="15,5" Click="BtnSave_Click"
|
|
||||||
Background="#FF28A745" Foreground="White" BorderBrush="#FF1E7E34" BorderThickness="1"
|
|
||||||
Style="{StaticResource DefaultButtonStyle}"/>
|
|
||||||
</StackPanel>
|
|
||||||
</Grid>
|
|
||||||
</UserControl>
|
|
||||||
@@ -1,396 +0,0 @@
|
|||||||
using Ink_Canvas.Windows;
|
|
||||||
using Microsoft.Win32;
|
|
||||||
using System;
|
|
||||||
using System.ComponentModel;
|
|
||||||
using System.IO;
|
|
||||||
using System.Windows;
|
|
||||||
using System.Windows.Controls;
|
|
||||||
|
|
||||||
namespace Ink_Canvas.Helpers.Plugins.BuiltIn.SuperLauncher
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// LauncherSettingsControl.xaml 的交互逻辑
|
|
||||||
/// </summary>
|
|
||||||
public partial class LauncherSettingsControl : UserControl
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// 父插件
|
|
||||||
/// </summary>
|
|
||||||
private readonly SuperLauncherPlugin _plugin;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 构造函数
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="plugin">父插件</param>
|
|
||||||
public LauncherSettingsControl(SuperLauncherPlugin plugin)
|
|
||||||
{
|
|
||||||
InitializeComponent();
|
|
||||||
|
|
||||||
_plugin = plugin;
|
|
||||||
|
|
||||||
// 设置按钮位置
|
|
||||||
RbtnLeft.IsChecked = _plugin.Config.ButtonPosition == LauncherButtonPosition.Left;
|
|
||||||
RbtnRight.IsChecked = _plugin.Config.ButtonPosition == LauncherButtonPosition.Right;
|
|
||||||
|
|
||||||
// 绑定应用列表
|
|
||||||
DgApps.ItemsSource = _plugin.LauncherItems;
|
|
||||||
|
|
||||||
// 初始化按钮状态
|
|
||||||
UpdateButtonStates();
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 更新按钮状态
|
|
||||||
/// </summary>
|
|
||||||
private void UpdateButtonStates()
|
|
||||||
{
|
|
||||||
bool hasSelection = DgApps.SelectedItem != null;
|
|
||||||
BtnEdit.IsEnabled = hasSelection;
|
|
||||||
BtnDelete.IsEnabled = hasSelection;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 位置单选按钮选择事件
|
|
||||||
/// </summary>
|
|
||||||
private void RbtnPosition_Checked(object sender, RoutedEventArgs e)
|
|
||||||
{
|
|
||||||
if (!IsLoaded) return;
|
|
||||||
|
|
||||||
LauncherButtonPosition oldPosition = _plugin.Config.ButtonPosition;
|
|
||||||
|
|
||||||
if (sender == RbtnLeft)
|
|
||||||
{
|
|
||||||
_plugin.Config.ButtonPosition = LauncherButtonPosition.Left;
|
|
||||||
}
|
|
||||||
else if (sender == RbtnRight)
|
|
||||||
{
|
|
||||||
_plugin.Config.ButtonPosition = LauncherButtonPosition.Right;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 如果位置发生变化,更新按钮位置
|
|
||||||
if (oldPosition != _plugin.Config.ButtonPosition)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
// 更新按钮位置
|
|
||||||
_plugin.UpdateButtonPosition();
|
|
||||||
|
|
||||||
// 保存配置
|
|
||||||
_plugin.SaveConfig();
|
|
||||||
|
|
||||||
LogHelper.WriteLogToFile($"启动台按钮位置已更改为: {_plugin.Config.ButtonPosition}");
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
LogHelper.WriteLogToFile($"更新启动台按钮位置时出错: {ex.Message}", LogHelper.LogType.Error);
|
|
||||||
MessageBox.Show($"更新启动台按钮位置时出错: {ex.Message}", "错误", MessageBoxButton.OK, MessageBoxImage.Error);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 添加按钮点击事件
|
|
||||||
/// </summary>
|
|
||||||
private void BtnAdd_Click(object sender, RoutedEventArgs e)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
// 创建新的启动项
|
|
||||||
LauncherItem item = new LauncherItem
|
|
||||||
{
|
|
||||||
Name = "",
|
|
||||||
Path = "",
|
|
||||||
IsVisible = true,
|
|
||||||
Position = -1 // 让插件管理器分配位置
|
|
||||||
};
|
|
||||||
|
|
||||||
// 直接显示编辑对话框
|
|
||||||
EditLauncherItem(item, true);
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
LogHelper.WriteLogToFile($"添加启动项时出错: {ex.Message}", LogHelper.LogType.Error);
|
|
||||||
MessageBox.Show($"添加启动项时出错: {ex.Message}", "错误", MessageBoxButton.OK, MessageBoxImage.Error);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 编辑应用按钮点击事件
|
|
||||||
/// </summary>
|
|
||||||
private void BtnEdit_Click(object sender, RoutedEventArgs e)
|
|
||||||
{
|
|
||||||
if (DgApps.SelectedItem is LauncherItem item)
|
|
||||||
{
|
|
||||||
EditLauncherItem(item, false);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 删除应用按钮点击事件
|
|
||||||
/// </summary>
|
|
||||||
private void BtnDelete_Click(object sender, RoutedEventArgs e)
|
|
||||||
{
|
|
||||||
if (DgApps.SelectedItem is LauncherItem item)
|
|
||||||
{
|
|
||||||
// 确认删除
|
|
||||||
MessageBoxResult result = MessageBox.Show(
|
|
||||||
$"确定要删除 {item.Name} 吗?",
|
|
||||||
"删除确认",
|
|
||||||
MessageBoxButton.YesNo,
|
|
||||||
MessageBoxImage.Question);
|
|
||||||
|
|
||||||
if (result == MessageBoxResult.Yes)
|
|
||||||
{
|
|
||||||
// 从集合中移除
|
|
||||||
_plugin.LauncherItems.Remove(item);
|
|
||||||
|
|
||||||
// 保存配置
|
|
||||||
_plugin.SaveConfig();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 保存设置按钮点击事件
|
|
||||||
/// </summary>
|
|
||||||
private void BtnSave_Click(object sender, RoutedEventArgs e)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
// 保存配置
|
|
||||||
_plugin.SaveConfig();
|
|
||||||
|
|
||||||
// 如果插件已启用,重新加载启动台按钮
|
|
||||||
if (_plugin.IsEnabled)
|
|
||||||
{
|
|
||||||
_plugin.Disable();
|
|
||||||
_plugin.Enable();
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
// 如果插件未启用,则启用它
|
|
||||||
_plugin.Enable();
|
|
||||||
|
|
||||||
// 通知PluginSettingsWindow刷新插件列表
|
|
||||||
var window = Window.GetWindow(this);
|
|
||||||
if (window is PluginSettingsWindow pluginSettingsWindow)
|
|
||||||
{
|
|
||||||
// 触发刷新
|
|
||||||
pluginSettingsWindow.RefreshPluginList();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
MessageBox.Show("设置已保存并应用!", "提示", MessageBoxButton.OK, MessageBoxImage.Information);
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
LogHelper.WriteLogToFile($"保存设置时出错: {ex.Message}", LogHelper.LogType.Error);
|
|
||||||
MessageBox.Show($"保存设置时出错: {ex.Message}", "错误", MessageBoxButton.OK, MessageBoxImage.Error);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 应用项选择变更事件
|
|
||||||
/// </summary>
|
|
||||||
private void DgApps_SelectionChanged(object sender, SelectionChangedEventArgs e)
|
|
||||||
{
|
|
||||||
UpdateButtonStates();
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 编辑启动项
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="item">启动项</param>
|
|
||||||
/// <param name="isNew">是否为新建</param>
|
|
||||||
private void EditLauncherItem(LauncherItem item, bool isNew)
|
|
||||||
{
|
|
||||||
// 创建简单的编辑窗口
|
|
||||||
Window editWindow = new Window
|
|
||||||
{
|
|
||||||
Title = isNew ? "添加" : "编辑应用",
|
|
||||||
Width = 400,
|
|
||||||
Height = 200,
|
|
||||||
WindowStartupLocation = WindowStartupLocation.CenterScreen,
|
|
||||||
ResizeMode = ResizeMode.NoResize
|
|
||||||
};
|
|
||||||
|
|
||||||
// 创建编辑表单
|
|
||||||
Grid grid = new Grid
|
|
||||||
{
|
|
||||||
Margin = new Thickness(20)
|
|
||||||
};
|
|
||||||
|
|
||||||
grid.RowDefinitions.Add(new RowDefinition { Height = GridLength.Auto });
|
|
||||||
grid.RowDefinitions.Add(new RowDefinition { Height = GridLength.Auto });
|
|
||||||
grid.RowDefinitions.Add(new RowDefinition { Height = new GridLength(1, GridUnitType.Star) });
|
|
||||||
|
|
||||||
grid.ColumnDefinitions.Add(new ColumnDefinition { Width = new GridLength(80) });
|
|
||||||
grid.ColumnDefinitions.Add(new ColumnDefinition { Width = new GridLength(1, GridUnitType.Star) });
|
|
||||||
|
|
||||||
// 名称输入框
|
|
||||||
TextBlock nameLabel = new TextBlock
|
|
||||||
{
|
|
||||||
Text = "名称:",
|
|
||||||
VerticalAlignment = VerticalAlignment.Center
|
|
||||||
};
|
|
||||||
TextBox nameTextBox = new TextBox
|
|
||||||
{
|
|
||||||
Text = item.Name,
|
|
||||||
Margin = new Thickness(0, 5, 0, 5)
|
|
||||||
};
|
|
||||||
|
|
||||||
Grid.SetRow(nameLabel, 0);
|
|
||||||
Grid.SetColumn(nameLabel, 0);
|
|
||||||
Grid.SetRow(nameTextBox, 0);
|
|
||||||
Grid.SetColumn(nameTextBox, 1);
|
|
||||||
|
|
||||||
grid.Children.Add(nameLabel);
|
|
||||||
grid.Children.Add(nameTextBox);
|
|
||||||
|
|
||||||
// 路径输入框
|
|
||||||
TextBlock pathLabel = new TextBlock
|
|
||||||
{
|
|
||||||
Text = "路径:",
|
|
||||||
VerticalAlignment = VerticalAlignment.Center
|
|
||||||
};
|
|
||||||
Grid pathGrid = new Grid();
|
|
||||||
pathGrid.ColumnDefinitions.Add(new ColumnDefinition { Width = new GridLength(1, GridUnitType.Star) });
|
|
||||||
pathGrid.ColumnDefinitions.Add(new ColumnDefinition { Width = new GridLength() });
|
|
||||||
|
|
||||||
TextBox pathTextBox = new TextBox
|
|
||||||
{
|
|
||||||
Text = item.Path,
|
|
||||||
Margin = new Thickness(0, 5, 5, 5)
|
|
||||||
};
|
|
||||||
Button browseButton = new Button
|
|
||||||
{
|
|
||||||
Content = "浏览",
|
|
||||||
Padding = new Thickness(5, 0, 5, 0),
|
|
||||||
Margin = new Thickness(0, 5, 0, 5)
|
|
||||||
};
|
|
||||||
|
|
||||||
browseButton.Click += (s, e) =>
|
|
||||||
{
|
|
||||||
OpenFileDialog dialog = new OpenFileDialog
|
|
||||||
{
|
|
||||||
Title = "选择应用程序",
|
|
||||||
Filter = "应用程序 (*.exe)|*.exe|所有文件 (*.*)|*.*",
|
|
||||||
Multiselect = false,
|
|
||||||
FileName = pathTextBox.Text
|
|
||||||
};
|
|
||||||
|
|
||||||
if (dialog.ShowDialog() == true)
|
|
||||||
{
|
|
||||||
pathTextBox.Text = dialog.FileName;
|
|
||||||
|
|
||||||
// 如果选择的是.exe文件,自动获取文件名填入名称字段
|
|
||||||
if (Path.GetExtension(dialog.FileName).ToLower() == ".exe")
|
|
||||||
{
|
|
||||||
string fileName = Path.GetFileNameWithoutExtension(dialog.FileName);
|
|
||||||
// 只有在名称字段为空或者是新建项目时才自动填入
|
|
||||||
if (string.IsNullOrWhiteSpace(nameTextBox.Text) || isNew)
|
|
||||||
{
|
|
||||||
nameTextBox.Text = fileName;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
Grid.SetColumn(pathTextBox, 0);
|
|
||||||
Grid.SetColumn(browseButton, 1);
|
|
||||||
pathGrid.Children.Add(pathTextBox);
|
|
||||||
pathGrid.Children.Add(browseButton);
|
|
||||||
|
|
||||||
Grid.SetRow(pathLabel, 1);
|
|
||||||
Grid.SetColumn(pathLabel, 0);
|
|
||||||
Grid.SetRow(pathGrid, 1);
|
|
||||||
Grid.SetColumn(pathGrid, 1);
|
|
||||||
|
|
||||||
grid.Children.Add(pathLabel);
|
|
||||||
grid.Children.Add(pathGrid);
|
|
||||||
|
|
||||||
// 确认和取消按钮
|
|
||||||
StackPanel buttonPanel = new StackPanel
|
|
||||||
{
|
|
||||||
Orientation = Orientation.Horizontal,
|
|
||||||
HorizontalAlignment = HorizontalAlignment.Right,
|
|
||||||
Margin = new Thickness(0, 10, 0, 0)
|
|
||||||
};
|
|
||||||
|
|
||||||
Button okButton = new Button
|
|
||||||
{
|
|
||||||
Content = "确定",
|
|
||||||
Padding = new Thickness(15, 5, 15, 5),
|
|
||||||
Margin = new Thickness(0, 0, 10, 0),
|
|
||||||
IsDefault = true
|
|
||||||
};
|
|
||||||
|
|
||||||
Button cancelButton = new Button
|
|
||||||
{
|
|
||||||
Content = "取消",
|
|
||||||
Padding = new Thickness(15, 5, 15, 5),
|
|
||||||
IsCancel = true
|
|
||||||
};
|
|
||||||
|
|
||||||
okButton.Click += (s, e) =>
|
|
||||||
{
|
|
||||||
// 验证输入
|
|
||||||
if (string.IsNullOrWhiteSpace(nameTextBox.Text))
|
|
||||||
{
|
|
||||||
MessageBox.Show("请输入应用名称!", "提示", MessageBoxButton.OK, MessageBoxImage.Warning);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (string.IsNullOrWhiteSpace(pathTextBox.Text))
|
|
||||||
{
|
|
||||||
MessageBox.Show("请输入应用路径!", "提示", MessageBoxButton.OK, MessageBoxImage.Warning);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 更新项目
|
|
||||||
item.Name = nameTextBox.Text;
|
|
||||||
item.Path = pathTextBox.Text;
|
|
||||||
|
|
||||||
// 如果是新建,添加到集合
|
|
||||||
if (isNew)
|
|
||||||
{
|
|
||||||
_plugin.AddLauncherItem(item);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
// 触发属性变更通知,刷新DataGrid
|
|
||||||
if (DgApps.ItemsSource is ICollectionView view)
|
|
||||||
{
|
|
||||||
view.Refresh();
|
|
||||||
}
|
|
||||||
|
|
||||||
// 保存配置
|
|
||||||
_plugin.SaveConfig();
|
|
||||||
}
|
|
||||||
|
|
||||||
editWindow.DialogResult = true;
|
|
||||||
editWindow.Close();
|
|
||||||
};
|
|
||||||
|
|
||||||
cancelButton.Click += (s, e) =>
|
|
||||||
{
|
|
||||||
editWindow.DialogResult = false;
|
|
||||||
editWindow.Close();
|
|
||||||
};
|
|
||||||
|
|
||||||
buttonPanel.Children.Add(okButton);
|
|
||||||
buttonPanel.Children.Add(cancelButton);
|
|
||||||
|
|
||||||
Grid.SetRow(buttonPanel, 2);
|
|
||||||
Grid.SetColumnSpan(buttonPanel, 2);
|
|
||||||
|
|
||||||
grid.Children.Add(buttonPanel);
|
|
||||||
|
|
||||||
// 设置窗口内容
|
|
||||||
editWindow.Content = grid;
|
|
||||||
|
|
||||||
// 显示窗口
|
|
||||||
editWindow.ShowDialog();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,91 +0,0 @@
|
|||||||
<Window x:Class="Ink_Canvas.Helpers.Plugins.BuiltIn.SuperLauncher.LauncherWindow"
|
|
||||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
|
||||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
|
||||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
|
||||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
|
||||||
xmlns:local="clr-namespace:Ink_Canvas.Helpers.Plugins.BuiltIn.SuperLauncher"
|
|
||||||
mc:Ignorable="d"
|
|
||||||
Title="启动台"
|
|
||||||
Width="400"
|
|
||||||
Height="300"
|
|
||||||
WindowStyle="None"
|
|
||||||
AllowsTransparency="True"
|
|
||||||
Background="#80000000"
|
|
||||||
ResizeMode="NoResize"
|
|
||||||
Topmost="True"
|
|
||||||
Deactivated="Window_Deactivated"
|
|
||||||
ShowInTaskbar="False">
|
|
||||||
|
|
||||||
<Window.Resources>
|
|
||||||
<!-- 应用项样式 -->
|
|
||||||
<Style x:Key="LauncherItemStyle" TargetType="Button">
|
|
||||||
<Setter Property="Width" Value="80"/>
|
|
||||||
<Setter Property="Height" Value="80"/>
|
|
||||||
<Setter Property="Margin" Value="5"/>
|
|
||||||
<Setter Property="Background" Value="#40FFFFFF"/>
|
|
||||||
<Setter Property="Foreground" Value="White"/>
|
|
||||||
<Setter Property="BorderThickness" Value="0"/>
|
|
||||||
<Setter Property="Template">
|
|
||||||
<Setter.Value>
|
|
||||||
<ControlTemplate TargetType="Button">
|
|
||||||
<Border x:Name="border" Background="{TemplateBinding Background}"
|
|
||||||
BorderBrush="{TemplateBinding BorderBrush}"
|
|
||||||
BorderThickness="{TemplateBinding BorderThickness}"
|
|
||||||
CornerRadius="8">
|
|
||||||
<Grid>
|
|
||||||
<Grid.RowDefinitions>
|
|
||||||
<RowDefinition Height="*"/>
|
|
||||||
<RowDefinition Height="Auto"/>
|
|
||||||
</Grid.RowDefinitions>
|
|
||||||
<Image Grid.Row="0" Source="{Binding Icon}" Width="32" Height="32" Margin="0,10,0,5"/>
|
|
||||||
<TextBlock Grid.Row="1" Text="{Binding Name}" TextWrapping="Wrap" TextAlignment="Center"
|
|
||||||
Margin="2,0,2,8" FontSize="11" Foreground="White"/>
|
|
||||||
</Grid>
|
|
||||||
</Border>
|
|
||||||
<ControlTemplate.Triggers>
|
|
||||||
<Trigger Property="IsMouseOver" Value="True">
|
|
||||||
<Setter Property="Background" Value="#80FFFFFF"/>
|
|
||||||
</Trigger>
|
|
||||||
<Trigger Property="IsPressed" Value="True">
|
|
||||||
<Setter Property="Background" Value="#C0FFFFFF"/>
|
|
||||||
</Trigger>
|
|
||||||
</ControlTemplate.Triggers>
|
|
||||||
</ControlTemplate>
|
|
||||||
</Setter.Value>
|
|
||||||
</Setter>
|
|
||||||
</Style>
|
|
||||||
</Window.Resources>
|
|
||||||
|
|
||||||
<Border CornerRadius="15" Background="#80000000" BorderThickness="1" BorderBrush="#40FFFFFF">
|
|
||||||
<Grid>
|
|
||||||
<Grid.RowDefinitions>
|
|
||||||
<RowDefinition Height="Auto"/>
|
|
||||||
<RowDefinition Height="*"/>
|
|
||||||
</Grid.RowDefinitions>
|
|
||||||
|
|
||||||
<!-- 标题栏 -->
|
|
||||||
<Grid Grid.Row="0" Height="40">
|
|
||||||
<TextBlock Text="启动台" Foreground="White" FontSize="18" FontWeight="Bold"
|
|
||||||
VerticalAlignment="Center" Margin="15,0,0,0"/>
|
|
||||||
<StackPanel Orientation="Horizontal" HorizontalAlignment="Right" Margin="0,0,10,0">
|
|
||||||
<Button x:Name="BtnFixMode" Click="BtnFixMode_Click" Width="30" Height="30"
|
|
||||||
Margin="5,0" Background="Transparent" BorderThickness="0"
|
|
||||||
ToolTip="切换固定模式">
|
|
||||||
<Path x:Name="FixModeIcon" Data="M7,2V13H10V22L17,10H13L17,2H7Z" Fill="White" Stretch="Uniform" Width="16" Height="16"/>
|
|
||||||
</Button>
|
|
||||||
<Button x:Name="BtnClose" Click="BtnClose_Click" Width="30" Height="30"
|
|
||||||
Background="Transparent" BorderThickness="0"
|
|
||||||
ToolTip="关闭">
|
|
||||||
<Path Data="M19,6.41L17.59,5L12,10.59L6.41,5L5,6.41L10.59,12L5,17.59L6.41,19L12,13.41L17.59,19L19,17.59L13.41,12L19,6.41Z"
|
|
||||||
Fill="White" Stretch="Uniform" Width="16" Height="16"/>
|
|
||||||
</Button>
|
|
||||||
</StackPanel>
|
|
||||||
</Grid>
|
|
||||||
|
|
||||||
<!-- 应用网格 -->
|
|
||||||
<ScrollViewer Grid.Row="1" Margin="10" VerticalScrollBarVisibility="Auto" HorizontalScrollBarVisibility="Disabled">
|
|
||||||
<WrapPanel x:Name="AppPanel" Orientation="Horizontal" HorizontalAlignment="Center"/>
|
|
||||||
</ScrollViewer>
|
|
||||||
</Grid>
|
|
||||||
</Border>
|
|
||||||
</Window>
|
|
||||||
@@ -1,466 +0,0 @@
|
|||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.ComponentModel;
|
|
||||||
using System.Diagnostics;
|
|
||||||
using System.IO;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Threading;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
using System.Windows;
|
|
||||||
using System.Windows.Controls;
|
|
||||||
using System.Windows.Input;
|
|
||||||
using System.Windows.Media;
|
|
||||||
using System.Windows.Threading;
|
|
||||||
|
|
||||||
namespace Ink_Canvas.Helpers.Plugins.BuiltIn.SuperLauncher
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// LauncherWindow.xaml 的交互逻辑
|
|
||||||
/// </summary>
|
|
||||||
public partial class LauncherWindow : Window
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// 父插件
|
|
||||||
/// </summary>
|
|
||||||
private readonly SuperLauncherPlugin _plugin;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 是否处于固定模式
|
|
||||||
/// </summary>
|
|
||||||
private bool _isFixMode;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 应用项按钮列表
|
|
||||||
/// </summary>
|
|
||||||
private readonly Dictionary<Button, LauncherItem> _appButtons = new Dictionary<Button, LauncherItem>();
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 拖拽中的按钮
|
|
||||||
/// </summary>
|
|
||||||
private Button _draggingButton;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 拖拽开始位置
|
|
||||||
/// </summary>
|
|
||||||
private Point _dragStartPoint;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 构造函数
|
|
||||||
/// </summary>
|
|
||||||
public LauncherWindow(SuperLauncherPlugin plugin)
|
|
||||||
{
|
|
||||||
InitializeComponent();
|
|
||||||
|
|
||||||
_plugin = plugin;
|
|
||||||
|
|
||||||
// 加载应用项
|
|
||||||
LoadLauncherItems();
|
|
||||||
|
|
||||||
// 添加鼠标按下事件(用于拖动窗口)
|
|
||||||
MouseDown += (s, e) =>
|
|
||||||
{
|
|
||||||
if (e.ChangedButton == MouseButton.Left && e.ButtonState == MouseButtonState.Pressed)
|
|
||||||
{
|
|
||||||
DragMove();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// 根据应用数量调整窗口大小
|
|
||||||
AdjustWindowSize();
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 加载启动台应用项
|
|
||||||
/// </summary>
|
|
||||||
private void LoadLauncherItems()
|
|
||||||
{
|
|
||||||
// 清空现有应用项
|
|
||||||
AppPanel.Children.Clear();
|
|
||||||
_appButtons.Clear();
|
|
||||||
|
|
||||||
// 获取显示的应用项
|
|
||||||
var visibleItems = _plugin.LauncherItems
|
|
||||||
.Where(item => item.IsVisible)
|
|
||||||
.OrderBy(item => item.Position)
|
|
||||||
.ToList();
|
|
||||||
|
|
||||||
foreach (var item in visibleItems)
|
|
||||||
{
|
|
||||||
// 创建应用按钮
|
|
||||||
Button appButton = new Button
|
|
||||||
{
|
|
||||||
Style = (Style)FindResource("LauncherItemStyle"),
|
|
||||||
DataContext = item,
|
|
||||||
Tag = item.Position
|
|
||||||
};
|
|
||||||
|
|
||||||
// 添加点击事件
|
|
||||||
appButton.Click += AppButton_Click;
|
|
||||||
|
|
||||||
// 在固定模式下,添加拖拽事件
|
|
||||||
appButton.PreviewMouseDown += AppButton_PreviewMouseDown;
|
|
||||||
appButton.PreviewMouseMove += AppButton_PreviewMouseMove;
|
|
||||||
appButton.PreviewMouseUp += AppButton_PreviewMouseUp;
|
|
||||||
|
|
||||||
// 记录按钮和项目的对应关系
|
|
||||||
_appButtons.Add(appButton, item);
|
|
||||||
|
|
||||||
// 添加到面板
|
|
||||||
AppPanel.Children.Add(appButton);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 根据应用数量调整窗口大小
|
|
||||||
/// </summary>
|
|
||||||
private void AdjustWindowSize()
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
// 每行最多显示4个应用
|
|
||||||
const int appsPerRow = 4;
|
|
||||||
|
|
||||||
// 计算行数
|
|
||||||
int visibleCount = _appButtons.Count;
|
|
||||||
int rowCount = (int)Math.Ceiling(visibleCount / (double)appsPerRow);
|
|
||||||
|
|
||||||
// 设置窗口宽度(每个应用90像素宽 = 80 + 5*2)
|
|
||||||
Width = Math.Min(appsPerRow * 90 + 40, 400); // 最大宽度400
|
|
||||||
|
|
||||||
// 设置窗口高度(每个应用90像素高 = 80 + 5*2)
|
|
||||||
Height = Math.Min(rowCount * 90 + 60, 600); // 最大高度600,标题栏40 + 边距20
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
LogHelper.WriteLogToFile($"调整启动台窗口大小时出错: {ex.Message}", LogHelper.LogType.Error);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 应用按钮点击事件
|
|
||||||
/// </summary>
|
|
||||||
private void AppButton_Click(object sender, RoutedEventArgs e)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
if (_isFixMode) return; // 在固定模式下,不响应点击事件
|
|
||||||
|
|
||||||
if (sender is Button button && _appButtons.TryGetValue(button, out LauncherItem item))
|
|
||||||
{
|
|
||||||
// 获取应用路径和名称,用于后续启动
|
|
||||||
string appPath = item.Path;
|
|
||||||
string appName = item.Name;
|
|
||||||
|
|
||||||
LogHelper.WriteLogToFile($"点击启动应用: {appName}, 路径: {appPath}");
|
|
||||||
|
|
||||||
// 首先标记窗口正在关闭
|
|
||||||
IsClosing = true;
|
|
||||||
|
|
||||||
// 创建一个应用启动任务
|
|
||||||
var launchTask = new Task(() =>
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
// 等待一段时间,确保窗口关闭流程已经开始
|
|
||||||
Thread.Sleep(200);
|
|
||||||
|
|
||||||
// 使用UI线程启动应用
|
|
||||||
Application.Current.Dispatcher.Invoke(() =>
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
// 检查应用路径是否存在
|
|
||||||
if (File.Exists(appPath) || !appPath.Contains(":\\"))
|
|
||||||
{
|
|
||||||
// 创建进程启动信息
|
|
||||||
var psi = new ProcessStartInfo
|
|
||||||
{
|
|
||||||
FileName = appPath,
|
|
||||||
UseShellExecute = true,
|
|
||||||
};
|
|
||||||
|
|
||||||
// 启动应用程序
|
|
||||||
var process = Process.Start(psi);
|
|
||||||
LogHelper.WriteLogToFile($"应用程序 {appName} 已启动");
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
LogHelper.WriteLogToFile($"应用路径不存在: {appPath}", LogHelper.LogType.Error);
|
|
||||||
MessageBox.Show($"找不到应用程序: {appPath}", "错误", MessageBoxButton.OK, MessageBoxImage.Error);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
LogHelper.WriteLogToFile($"启动应用程序失败: {ex.Message}", LogHelper.LogType.Error);
|
|
||||||
MessageBox.Show($"启动应用程序失败: {ex.Message}", "错误", MessageBoxButton.OK, MessageBoxImage.Error);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
LogHelper.WriteLogToFile($"应用启动任务出错: {ex.Message}", LogHelper.LogType.Error);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// 关闭窗口
|
|
||||||
try
|
|
||||||
{
|
|
||||||
Dispatcher.BeginInvoke(new Action(() =>
|
|
||||||
{
|
|
||||||
try { Close(); } catch (Exception ex) { System.Diagnostics.Debug.WriteLine(ex); }
|
|
||||||
|
|
||||||
// 启动应用程序任务
|
|
||||||
launchTask.Start();
|
|
||||||
}), DispatcherPriority.Background);
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
LogHelper.WriteLogToFile($"关闭窗口或启动任务时出错: {ex.Message}", LogHelper.LogType.Error);
|
|
||||||
// 如果无法通过UI关闭窗口,直接启动任务
|
|
||||||
launchTask.Start();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
LogHelper.WriteLogToFile($"应用按钮点击事件出错: {ex.Message}", LogHelper.LogType.Error);
|
|
||||||
try { IsClosing = true; Close(); } catch (Exception innerEx) { System.Diagnostics.Debug.WriteLine(innerEx); }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#region 固定模式拖拽事件
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 应用按钮鼠标按下事件
|
|
||||||
/// </summary>
|
|
||||||
private void AppButton_PreviewMouseDown(object sender, MouseButtonEventArgs e)
|
|
||||||
{
|
|
||||||
if (!_isFixMode) return;
|
|
||||||
|
|
||||||
if (e.ChangedButton == MouseButton.Left && sender is Button button)
|
|
||||||
{
|
|
||||||
_draggingButton = button;
|
|
||||||
_dragStartPoint = e.GetPosition(AppPanel);
|
|
||||||
button.CaptureMouse();
|
|
||||||
button.Opacity = 0.7;
|
|
||||||
|
|
||||||
// 阻止事件冒泡,以避免触发按钮点击
|
|
||||||
e.Handled = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 应用按钮鼠标移动事件
|
|
||||||
/// </summary>
|
|
||||||
private void AppButton_PreviewMouseMove(object sender, MouseEventArgs e)
|
|
||||||
{
|
|
||||||
if (!_isFixMode || _draggingButton == null) return;
|
|
||||||
|
|
||||||
if (e.LeftButton == MouseButtonState.Pressed)
|
|
||||||
{
|
|
||||||
Point currentPosition = e.GetPosition(AppPanel);
|
|
||||||
|
|
||||||
// 移动按钮
|
|
||||||
System.Windows.Controls.Canvas.SetLeft(_draggingButton, currentPosition.X - _draggingButton.ActualWidth / 2);
|
|
||||||
System.Windows.Controls.Canvas.SetTop(_draggingButton, currentPosition.Y - _draggingButton.ActualHeight / 2);
|
|
||||||
|
|
||||||
// 将按钮移到最上层
|
|
||||||
Panel.SetZIndex(_draggingButton, 100);
|
|
||||||
|
|
||||||
// 阻止事件冒泡
|
|
||||||
e.Handled = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 应用按钮鼠标释放事件
|
|
||||||
/// </summary>
|
|
||||||
private void AppButton_PreviewMouseUp(object sender, MouseButtonEventArgs e)
|
|
||||||
{
|
|
||||||
if (!_isFixMode || _draggingButton == null) return;
|
|
||||||
|
|
||||||
// 释放鼠标捕获
|
|
||||||
_draggingButton.ReleaseMouseCapture();
|
|
||||||
|
|
||||||
// 计算新位置
|
|
||||||
Point releasePoint = e.GetPosition(AppPanel);
|
|
||||||
int newPosition = CalculateGridPosition(releasePoint);
|
|
||||||
|
|
||||||
// 获取当前项目
|
|
||||||
LauncherItem currentItem = _appButtons[_draggingButton];
|
|
||||||
|
|
||||||
// 重新排序
|
|
||||||
ReorderItems(currentItem, newPosition);
|
|
||||||
|
|
||||||
// 重新加载应用项
|
|
||||||
LoadLauncherItems();
|
|
||||||
|
|
||||||
// 保存配置
|
|
||||||
_plugin.SaveConfig();
|
|
||||||
|
|
||||||
// 清除拖拽状态
|
|
||||||
_draggingButton.Opacity = 1;
|
|
||||||
Panel.SetZIndex(_draggingButton, 0);
|
|
||||||
_draggingButton = null;
|
|
||||||
|
|
||||||
// 阻止事件冒泡
|
|
||||||
e.Handled = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 计算网格位置
|
|
||||||
/// </summary>
|
|
||||||
private int CalculateGridPosition(Point point)
|
|
||||||
{
|
|
||||||
// 计算行和列
|
|
||||||
int columnCount = 4; // 每行最多4个应用
|
|
||||||
int columnWidth = 90; // 应用宽度(包括边距)
|
|
||||||
int rowHeight = 90; // 应用高度(包括边距)
|
|
||||||
|
|
||||||
int column = (int)(point.X / columnWidth);
|
|
||||||
int row = (int)(point.Y / rowHeight);
|
|
||||||
|
|
||||||
// 确保在有效范围内
|
|
||||||
column = Math.Max(0, Math.Min(column, columnCount - 1));
|
|
||||||
row = Math.Max(0, row);
|
|
||||||
|
|
||||||
// 计算位置索引
|
|
||||||
return row * columnCount + column;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 重新排序应用项
|
|
||||||
/// </summary>
|
|
||||||
private void ReorderItems(LauncherItem item, int newPosition)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
// 设置项目为固定位置
|
|
||||||
item.IsPositionFixed = true;
|
|
||||||
|
|
||||||
// 如果位置相同,无需调整
|
|
||||||
if (item.Position == newPosition)
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 获取所有可见项目
|
|
||||||
var visibleItems = _plugin.LauncherItems
|
|
||||||
.Where(i => i.IsVisible)
|
|
||||||
.OrderBy(i => i.Position)
|
|
||||||
.ToList();
|
|
||||||
|
|
||||||
// 移除当前项目
|
|
||||||
visibleItems.Remove(item);
|
|
||||||
|
|
||||||
// 查找插入位置
|
|
||||||
int insertIndex = 0;
|
|
||||||
for (int i = 0; i < visibleItems.Count; i++)
|
|
||||||
{
|
|
||||||
if (visibleItems[i].Position >= newPosition)
|
|
||||||
{
|
|
||||||
insertIndex = i;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
insertIndex = i + 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 插入项目
|
|
||||||
visibleItems.Insert(insertIndex, item);
|
|
||||||
|
|
||||||
// 重新分配位置
|
|
||||||
for (int i = 0; i < visibleItems.Count; i++)
|
|
||||||
{
|
|
||||||
visibleItems[i].Position = i;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
LogHelper.WriteLogToFile($"重新排序应用项时出错: {ex.Message}", LogHelper.LogType.Error);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#endregion
|
|
||||||
|
|
||||||
#region 窗口事件处理
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 窗口失去焦点事件
|
|
||||||
/// </summary>
|
|
||||||
private void Window_Deactivated(object sender, EventArgs e)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
// 只有在非固定模式、窗口已加载、未处于关闭状态且IsLoaded=true时关闭窗口
|
|
||||||
if (!_isFixMode && IsLoaded && !IsClosing)
|
|
||||||
{
|
|
||||||
// 标记为正在关闭
|
|
||||||
IsClosing = true;
|
|
||||||
|
|
||||||
// 使用Dispatcher.BeginInvoke而不是直接调用Close,避免冲突
|
|
||||||
Dispatcher.BeginInvoke(new Action(() =>
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
// 再次检查窗口状态
|
|
||||||
if (IsLoaded && !IsClosing)
|
|
||||||
{
|
|
||||||
Close();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
LogHelper.WriteLogToFile($"延迟关闭窗口时出错: {ex.Message}", LogHelper.LogType.Error);
|
|
||||||
}
|
|
||||||
}), DispatcherPriority.Background);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
LogHelper.WriteLogToFile($"窗口失去焦点关闭时出错: {ex.Message}", LogHelper.LogType.Error);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 窗口是否正在关闭
|
|
||||||
/// </summary>
|
|
||||||
private bool IsClosing { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 重写OnClosing方法,标记窗口正在关闭
|
|
||||||
/// </summary>
|
|
||||||
protected override void OnClosing(CancelEventArgs e)
|
|
||||||
{
|
|
||||||
IsClosing = true;
|
|
||||||
base.OnClosing(e);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 关闭按钮点击事件
|
|
||||||
/// </summary>
|
|
||||||
private void BtnClose_Click(object sender, RoutedEventArgs e)
|
|
||||||
{
|
|
||||||
Close();
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 固定模式按钮点击事件
|
|
||||||
/// </summary>
|
|
||||||
private void BtnFixMode_Click(object sender, RoutedEventArgs e)
|
|
||||||
{
|
|
||||||
// 切换固定模式
|
|
||||||
_isFixMode = !_isFixMode;
|
|
||||||
|
|
||||||
// 更新固定模式按钮图标颜色
|
|
||||||
FixModeIcon.Fill = _isFixMode ? Brushes.Yellow : Brushes.White;
|
|
||||||
|
|
||||||
// 显示提示
|
|
||||||
if (_isFixMode)
|
|
||||||
{
|
|
||||||
MessageBox.Show("已进入固定模式,您可以拖动应用图标调整位置。", "提示", MessageBoxButton.OK, MessageBoxImage.Information);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#endregion
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,589 +0,0 @@
|
|||||||
using Ink_Canvas.Helpers.Plugins.BuiltIn.SuperLauncher;
|
|
||||||
using Newtonsoft.Json;
|
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Collections.ObjectModel;
|
|
||||||
using System.IO;
|
|
||||||
using System.Windows;
|
|
||||||
using System.Windows.Controls;
|
|
||||||
using System.Windows.Media;
|
|
||||||
|
|
||||||
namespace Ink_Canvas.Helpers.Plugins.BuiltIn
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// 超级启动台插件
|
|
||||||
/// </summary>
|
|
||||||
public class SuperLauncherPlugin : PluginBase
|
|
||||||
{
|
|
||||||
#region 插件基本信息
|
|
||||||
|
|
||||||
public override string Name => "超级启动台";
|
|
||||||
|
|
||||||
public override string Description => "在浮动栏添加一个启动台按钮,可快速启动常用应用程序。";
|
|
||||||
|
|
||||||
public override Version Version => new Version(1, 0, 1);
|
|
||||||
|
|
||||||
public override string Author => "ICC CE 团队";
|
|
||||||
|
|
||||||
public override bool IsBuiltIn => true;
|
|
||||||
|
|
||||||
#endregion
|
|
||||||
|
|
||||||
#region 插件属性和字段
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 启动台配置
|
|
||||||
/// </summary>
|
|
||||||
public LauncherConfig Config { get; private set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 启动台应用程序列表
|
|
||||||
/// </summary>
|
|
||||||
public ObservableCollection<LauncherItem> LauncherItems { get; private set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 启动台按钮
|
|
||||||
/// </summary>
|
|
||||||
private LauncherButton _launcherButton;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 启动台窗口
|
|
||||||
/// </summary>
|
|
||||||
private LauncherWindow _launcherWindow;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 配置文件路径
|
|
||||||
/// </summary>
|
|
||||||
private readonly string _configPath = Path.Combine(App.RootPath, "PluginConfigs", "SuperLauncher.json");
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 标记是否已添加到浮动栏
|
|
||||||
/// </summary>
|
|
||||||
private bool _isAddedToFloatingBar;
|
|
||||||
|
|
||||||
#endregion
|
|
||||||
|
|
||||||
#region 插件生命周期
|
|
||||||
|
|
||||||
public override void Initialize()
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
base.Initialize();
|
|
||||||
|
|
||||||
// 创建配置目录
|
|
||||||
string configDir = Path.Combine(App.RootPath, "PluginConfigs");
|
|
||||||
if (!Directory.Exists(configDir))
|
|
||||||
{
|
|
||||||
Directory.CreateDirectory(configDir);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 加载配置
|
|
||||||
LoadConfig();
|
|
||||||
|
|
||||||
LogHelper.WriteLogToFile("超级启动台插件已初始化");
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
LogHelper.WriteLogToFile($"初始化超级启动台插件时出错: {ex.Message}", LogHelper.LogType.Error);
|
|
||||||
LogHelper.NewLog(ex);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public override void Enable()
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
if (IsEnabled) return; // 防止重复启用
|
|
||||||
|
|
||||||
// 创建启动台按钮
|
|
||||||
if (_launcherButton == null)
|
|
||||||
{
|
|
||||||
_launcherButton = new LauncherButton(this);
|
|
||||||
LogHelper.WriteLogToFile("超级启动台按钮已创建");
|
|
||||||
}
|
|
||||||
|
|
||||||
// 添加启动台按钮到浮动栏
|
|
||||||
AddLauncherButtonToFloatingBar();
|
|
||||||
|
|
||||||
// 设置启用状态
|
|
||||||
base.Enable();
|
|
||||||
|
|
||||||
// 保存插件配置
|
|
||||||
SavePluginSettings();
|
|
||||||
|
|
||||||
LogHelper.WriteLogToFile("超级启动台插件已启用");
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
LogHelper.WriteLogToFile($"启用超级启动台插件时出错: {ex.Message}", LogHelper.LogType.Error);
|
|
||||||
LogHelper.NewLog(ex);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public override void Disable()
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
if (!IsEnabled) return; // 防止重复禁用
|
|
||||||
|
|
||||||
// 从浮动栏移除启动台按钮
|
|
||||||
RemoveLauncherButtonFromFloatingBar();
|
|
||||||
|
|
||||||
// 如果启动台窗口打开,则关闭
|
|
||||||
if (_launcherWindow != null && _launcherWindow.IsVisible)
|
|
||||||
{
|
|
||||||
_launcherWindow.Close();
|
|
||||||
_launcherWindow = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 设置禁用状态
|
|
||||||
base.Disable();
|
|
||||||
|
|
||||||
// 保存插件配置
|
|
||||||
SavePluginSettings();
|
|
||||||
|
|
||||||
LogHelper.WriteLogToFile("超级启动台插件已禁用");
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
LogHelper.WriteLogToFile($"禁用超级启动台插件时出错: {ex.Message}", LogHelper.LogType.Error);
|
|
||||||
LogHelper.NewLog(ex);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public override UserControl GetSettingsView()
|
|
||||||
{
|
|
||||||
return new LauncherSettingsControl(this);
|
|
||||||
}
|
|
||||||
|
|
||||||
public override void Cleanup()
|
|
||||||
{
|
|
||||||
// 保存配置
|
|
||||||
SaveConfig();
|
|
||||||
|
|
||||||
// 从浮动栏移除启动台按钮
|
|
||||||
RemoveLauncherButtonFromFloatingBar();
|
|
||||||
|
|
||||||
// 如果启动台窗口打开,则关闭
|
|
||||||
if (_launcherWindow != null && _launcherWindow.IsVisible)
|
|
||||||
{
|
|
||||||
_launcherWindow.Close();
|
|
||||||
_launcherWindow = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
base.Cleanup();
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 保存插件设置
|
|
||||||
/// </summary>
|
|
||||||
public override void SavePluginSettings()
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
// 确保配置已加载
|
|
||||||
if (Config == null)
|
|
||||||
{
|
|
||||||
LoadConfig();
|
|
||||||
}
|
|
||||||
|
|
||||||
// 更新其他设置,但不更改插件启用状态
|
|
||||||
|
|
||||||
// 保存配置
|
|
||||||
SaveConfig();
|
|
||||||
|
|
||||||
LogHelper.WriteLogToFile("超级启动台插件设置已保存");
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
LogHelper.WriteLogToFile($"保存超级启动台插件设置时出错: {ex.Message}", LogHelper.LogType.Error);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#endregion
|
|
||||||
|
|
||||||
#region 配置管理
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 加载配置
|
|
||||||
/// </summary>
|
|
||||||
private void LoadConfig()
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
if (File.Exists(_configPath))
|
|
||||||
{
|
|
||||||
string json = File.ReadAllText(_configPath);
|
|
||||||
Config = JsonConvert.DeserializeObject<LauncherConfig>(json) ?? CreateDefaultConfig();
|
|
||||||
LauncherItems = new ObservableCollection<LauncherItem>(Config.Items ?? new List<LauncherItem>());
|
|
||||||
|
|
||||||
// 注意:不再根据配置更改插件启用状态
|
|
||||||
// 插件状态由PluginManager统一管理
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
Config = CreateDefaultConfig();
|
|
||||||
LauncherItems = new ObservableCollection<LauncherItem>(Config.Items);
|
|
||||||
SaveConfig();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
LogHelper.WriteLogToFile($"加载超级启动台配置时出错: {ex.Message}", LogHelper.LogType.Error);
|
|
||||||
Config = CreateDefaultConfig();
|
|
||||||
LauncherItems = new ObservableCollection<LauncherItem>(Config.Items);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 保存配置
|
|
||||||
/// </summary>
|
|
||||||
public void SaveConfig()
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
// 同步LauncherItems到Config
|
|
||||||
Config.Items = new List<LauncherItem>(LauncherItems);
|
|
||||||
|
|
||||||
// 序列化并保存配置
|
|
||||||
string json = JsonConvert.SerializeObject(Config, Formatting.Indented);
|
|
||||||
File.WriteAllText(_configPath, json);
|
|
||||||
|
|
||||||
LogHelper.WriteLogToFile("超级启动台配置已保存");
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
LogHelper.WriteLogToFile($"保存超级启动台配置时出错: {ex.Message}", LogHelper.LogType.Error);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 创建默认配置
|
|
||||||
/// </summary>
|
|
||||||
private LauncherConfig CreateDefaultConfig()
|
|
||||||
{
|
|
||||||
var config = new LauncherConfig
|
|
||||||
{
|
|
||||||
ButtonPosition = LauncherButtonPosition.Right,
|
|
||||||
// 不再使用IsEnabled,插件状态由PluginManager管理
|
|
||||||
Items = new List<LauncherItem>
|
|
||||||
{
|
|
||||||
new LauncherItem
|
|
||||||
{
|
|
||||||
Name = "资源管理器",
|
|
||||||
Path = @"C:\Windows\explorer.exe",
|
|
||||||
IsVisible = true,
|
|
||||||
Position = 0
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
return config;
|
|
||||||
}
|
|
||||||
|
|
||||||
#endregion
|
|
||||||
|
|
||||||
#region 启动台按钮管理
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 将启动台按钮添加到浮动栏
|
|
||||||
/// </summary>
|
|
||||||
private void AddLauncherButtonToFloatingBar()
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
// 如果已经添加,先移除
|
|
||||||
if (_isAddedToFloatingBar)
|
|
||||||
{
|
|
||||||
RemoveLauncherButtonFromFloatingBar();
|
|
||||||
_isAddedToFloatingBar = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 获取主窗口实例
|
|
||||||
var mainWindow = Application.Current.MainWindow;
|
|
||||||
if (mainWindow == null)
|
|
||||||
{
|
|
||||||
LogHelper.WriteLogToFile("未找到主窗口实例,无法添加启动台按钮", LogHelper.LogType.Error);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 创建启动台按钮
|
|
||||||
_launcherButton = new LauncherButton(this);
|
|
||||||
var buttonElement = _launcherButton.Element;
|
|
||||||
|
|
||||||
// 查找浮动栏
|
|
||||||
var floatingBar = mainWindow.FindName("StackPanelFloatingBar") as Panel;
|
|
||||||
if (floatingBar == null)
|
|
||||||
{
|
|
||||||
// 如果直接查找失败,则尝试遍历可视树查找
|
|
||||||
Panel floatingBarPanelFromTree = null;
|
|
||||||
FindStackPanelFloatingBar(mainWindow, ref floatingBarPanelFromTree);
|
|
||||||
floatingBar = floatingBarPanelFromTree;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (floatingBar == null)
|
|
||||||
{
|
|
||||||
LogHelper.WriteLogToFile("未找到浮动栏,无法添加启动台按钮", LogHelper.LogType.Error);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 添加启动台按钮到浮动栏
|
|
||||||
if (Config.ButtonPosition == LauncherButtonPosition.Left)
|
|
||||||
{
|
|
||||||
floatingBar.Children.Insert(0, buttonElement);
|
|
||||||
LogHelper.WriteLogToFile("启动台按钮已添加到浮动栏左侧");
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
floatingBar.Children.Add(buttonElement);
|
|
||||||
LogHelper.WriteLogToFile("启动台按钮已添加到浮动栏右侧");
|
|
||||||
}
|
|
||||||
|
|
||||||
_isAddedToFloatingBar = true;
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
LogHelper.WriteLogToFile($"添加启动台按钮到浮动栏时出错: {ex.Message}", LogHelper.LogType.Error);
|
|
||||||
LogHelper.NewLog(ex);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 递归查找StackPanelFloatingBar
|
|
||||||
/// </summary>
|
|
||||||
private void FindStackPanelFloatingBar(DependencyObject parent, ref Panel result)
|
|
||||||
{
|
|
||||||
if (parent == null || result != null) return;
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
// 检查当前对象是否为我们要找的面板
|
|
||||||
if (parent is Panel panel && panel.Name == "StackPanelFloatingBar")
|
|
||||||
{
|
|
||||||
result = panel;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 获取子元素数量
|
|
||||||
int childCount = VisualTreeHelper.GetChildrenCount(parent);
|
|
||||||
|
|
||||||
// 遍历所有子元素
|
|
||||||
for (int i = 0; i < childCount; i++)
|
|
||||||
{
|
|
||||||
DependencyObject child = VisualTreeHelper.GetChild(parent, i);
|
|
||||||
FindStackPanelFloatingBar(child, ref result);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
LogHelper.WriteLogToFile($"查找StackPanelFloatingBar时出错: {ex.Message}", LogHelper.LogType.Error);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 从浮动栏移除启动台按钮
|
|
||||||
/// </summary>
|
|
||||||
private void RemoveLauncherButtonFromFloatingBar()
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
if (!_isAddedToFloatingBar || _launcherButton == null)
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 获取主窗口实例
|
|
||||||
var mainWindow = Application.Current.MainWindow;
|
|
||||||
if (mainWindow == null)
|
|
||||||
{
|
|
||||||
LogHelper.WriteLogToFile("未找到主窗口实例,无法移除启动台按钮", LogHelper.LogType.Error);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 获取按钮元素
|
|
||||||
var buttonElement = _launcherButton.Element;
|
|
||||||
|
|
||||||
// 查找浮动栏
|
|
||||||
var floatingBar = mainWindow.FindName("StackPanelFloatingBar") as Panel;
|
|
||||||
if (floatingBar == null)
|
|
||||||
{
|
|
||||||
// 如果直接查找失败,则尝试遍历可视树查找
|
|
||||||
Panel floatingBarPanelFromTree = null;
|
|
||||||
FindStackPanelFloatingBar(mainWindow, ref floatingBarPanelFromTree);
|
|
||||||
floatingBar = floatingBarPanelFromTree;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (floatingBar == null)
|
|
||||||
{
|
|
||||||
LogHelper.WriteLogToFile("未找到浮动栏,无法移除启动台按钮", LogHelper.LogType.Error);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 从浮动栏移除启动台按钮
|
|
||||||
if (floatingBar.Children.Contains(buttonElement))
|
|
||||||
{
|
|
||||||
floatingBar.Children.Remove(buttonElement);
|
|
||||||
LogHelper.WriteLogToFile("启动台按钮已从浮动栏移除");
|
|
||||||
}
|
|
||||||
|
|
||||||
_isAddedToFloatingBar = false;
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
LogHelper.WriteLogToFile($"移除启动台按钮时出错: {ex.Message}", LogHelper.LogType.Error);
|
|
||||||
LogHelper.NewLog(ex);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 更新启动台按钮位置
|
|
||||||
/// </summary>
|
|
||||||
public void UpdateButtonPosition()
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
// 如果按钮已添加到浮动栏,重新添加以更新位置
|
|
||||||
if (_isAddedToFloatingBar)
|
|
||||||
{
|
|
||||||
RemoveLauncherButtonFromFloatingBar();
|
|
||||||
AddLauncherButtonToFloatingBar();
|
|
||||||
LogHelper.WriteLogToFile($"启动台按钮位置已更新为: {Config.ButtonPosition}");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
LogHelper.WriteLogToFile($"更新启动台按钮位置时出错: {ex.Message}", LogHelper.LogType.Error);
|
|
||||||
LogHelper.NewLog(ex);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#endregion
|
|
||||||
|
|
||||||
#region 启动台功能
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 显示启动台窗口
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="buttonPosition">按钮在屏幕上的位置</param>
|
|
||||||
public void ShowLauncherWindow(Point buttonPosition)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
// 如果窗口已存在,关闭它
|
|
||||||
if (_launcherWindow != null && _launcherWindow.IsVisible)
|
|
||||||
{
|
|
||||||
_launcherWindow.Close();
|
|
||||||
_launcherWindow = null;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 创建新的启动台窗口
|
|
||||||
_launcherWindow = new LauncherWindow(this);
|
|
||||||
|
|
||||||
// 计算窗口位置,使其位于按钮上方
|
|
||||||
PositionLauncherWindow(_launcherWindow, buttonPosition);
|
|
||||||
|
|
||||||
// 显示窗口
|
|
||||||
_launcherWindow.Show();
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
LogHelper.WriteLogToFile($"显示启动台窗口时出错: {ex.Message}", LogHelper.LogType.Error);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 设置启动台窗口位置
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="window">启动台窗口</param>
|
|
||||||
/// <param name="buttonPosition">按钮在屏幕上的位置</param>
|
|
||||||
private void PositionLauncherWindow(LauncherWindow window, Point buttonPosition)
|
|
||||||
{
|
|
||||||
// 确保窗口已加载
|
|
||||||
if (window.ActualWidth == 0 || window.ActualHeight == 0)
|
|
||||||
{
|
|
||||||
window.WindowStartupLocation = WindowStartupLocation.CenterScreen;
|
|
||||||
|
|
||||||
// 设置窗口加载完成后的位置
|
|
||||||
window.Loaded += (s, e) =>
|
|
||||||
{
|
|
||||||
// 窗口位于按钮上方居中
|
|
||||||
double left = buttonPosition.X - (window.ActualWidth / 2);
|
|
||||||
double top = buttonPosition.Y - window.ActualHeight - 10; // 在按钮上方留出一些间距
|
|
||||||
|
|
||||||
// 确保窗口在屏幕内
|
|
||||||
left = Math.Max(0, Math.Min(left, SystemParameters.WorkArea.Width - window.ActualWidth));
|
|
||||||
top = Math.Max(0, Math.Min(top, SystemParameters.WorkArea.Height - window.ActualHeight));
|
|
||||||
|
|
||||||
window.Left = left;
|
|
||||||
window.Top = top;
|
|
||||||
};
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
// 窗口位于按钮上方居中
|
|
||||||
double left = buttonPosition.X - (window.ActualWidth / 2);
|
|
||||||
double top = buttonPosition.Y - window.ActualHeight - 10; // 在按钮上方留出一些间距
|
|
||||||
|
|
||||||
// 确保窗口在屏幕内
|
|
||||||
left = Math.Max(0, Math.Min(left, SystemParameters.WorkArea.Width - window.ActualWidth));
|
|
||||||
top = Math.Max(0, Math.Min(top, SystemParameters.WorkArea.Height - window.ActualHeight));
|
|
||||||
|
|
||||||
window.Left = left;
|
|
||||||
window.Top = top;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 添加应用到启动台
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="item">启动台项</param>
|
|
||||||
public void AddLauncherItem(LauncherItem item)
|
|
||||||
{
|
|
||||||
// 如果项目数量已达上限,则不添加
|
|
||||||
if (LauncherItems.Count >= 40)
|
|
||||||
{
|
|
||||||
MessageBox.Show("启动台项目数量已达上限(40个)!", "提示", MessageBoxButton.OK, MessageBoxImage.Information);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 寻找合适的位置
|
|
||||||
if (item.Position < 0)
|
|
||||||
{
|
|
||||||
item.Position = FindNextAvailablePosition();
|
|
||||||
}
|
|
||||||
|
|
||||||
// 添加项目并保存配置
|
|
||||||
LauncherItems.Add(item);
|
|
||||||
SaveConfig();
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 查找下一个可用位置
|
|
||||||
/// </summary>
|
|
||||||
private int FindNextAvailablePosition()
|
|
||||||
{
|
|
||||||
// 获取已使用的位置列表
|
|
||||||
var usedPositions = new HashSet<int>();
|
|
||||||
foreach (var item in LauncherItems)
|
|
||||||
{
|
|
||||||
usedPositions.Add(item.Position);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 查找第一个可用位置
|
|
||||||
for (int i = 0; i < 40; i++)
|
|
||||||
{
|
|
||||||
if (!usedPositions.Contains(i))
|
|
||||||
{
|
|
||||||
return i;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 如果所有位置都已使用,则返回0
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
#endregion
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,92 +0,0 @@
|
|||||||
using System.Windows.Controls;
|
|
||||||
|
|
||||||
namespace Ink_Canvas.Helpers.Plugins
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// 增强的插件基类,提供对插件服务的访问和基本实现
|
|
||||||
/// </summary>
|
|
||||||
public abstract class EnhancedPluginBase : PluginBase, IEnhancedPlugin
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// 插件服务实例
|
|
||||||
/// </summary>
|
|
||||||
public IPluginService PluginService { get; private set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 构造函数
|
|
||||||
/// </summary>
|
|
||||||
protected EnhancedPluginBase()
|
|
||||||
{
|
|
||||||
PluginService = PluginServiceManager.Instance;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 插件启动时调用,在Initialize之后
|
|
||||||
/// </summary>
|
|
||||||
public virtual void OnStartup()
|
|
||||||
{
|
|
||||||
LogHelper.WriteLogToFile($"插件 {Name} 已启动");
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 插件关闭时调用,在Cleanup之前
|
|
||||||
/// </summary>
|
|
||||||
public virtual void OnShutdown()
|
|
||||||
{
|
|
||||||
LogHelper.WriteLogToFile($"插件 {Name} 正在关闭");
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 获取插件的菜单项
|
|
||||||
/// </summary>
|
|
||||||
/// <returns>菜单项集合</returns>
|
|
||||||
public virtual MenuItem[] GetMenuItems()
|
|
||||||
{
|
|
||||||
return new MenuItem[0];
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 获取插件的工具栏按钮
|
|
||||||
/// </summary>
|
|
||||||
/// <returns>工具栏按钮集合</returns>
|
|
||||||
public virtual Button[] GetToolbarButtons()
|
|
||||||
{
|
|
||||||
return new Button[0];
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 获取插件的状态栏信息
|
|
||||||
/// </summary>
|
|
||||||
/// <returns>状态栏信息</returns>
|
|
||||||
public virtual string GetStatusBarInfo()
|
|
||||||
{
|
|
||||||
return $"{Name} v{Version} - {(IsEnabled ? "已启用" : "已禁用")}";
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 插件配置变更时调用
|
|
||||||
/// </summary>
|
|
||||||
public virtual void OnConfigurationChanged()
|
|
||||||
{
|
|
||||||
LogHelper.WriteLogToFile($"插件 {Name} 配置已变更");
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 重写初始化方法,调用OnStartup
|
|
||||||
/// </summary>
|
|
||||||
public override void Initialize()
|
|
||||||
{
|
|
||||||
base.Initialize();
|
|
||||||
OnStartup();
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 重写清理方法,调用OnShutdown
|
|
||||||
/// </summary>
|
|
||||||
public override void Cleanup()
|
|
||||||
{
|
|
||||||
OnShutdown();
|
|
||||||
base.Cleanup();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,241 +0,0 @@
|
|||||||
using System.Windows.Controls;
|
|
||||||
|
|
||||||
namespace Ink_Canvas.Helpers.Plugins
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// 增强的插件基类 V2,提供对三个专门服务接口的访问
|
|
||||||
/// 插件开发者可以根据需要选择性地使用这些服务
|
|
||||||
/// </summary>
|
|
||||||
public abstract class EnhancedPluginBaseV2 : PluginBase, IEnhancedPlugin
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// 获取服务实例
|
|
||||||
/// </summary>
|
|
||||||
public IGetService GetService { get; private set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 窗口服务实例
|
|
||||||
/// </summary>
|
|
||||||
public IWindowService WindowService { get; private set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 操作服务实例
|
|
||||||
/// </summary>
|
|
||||||
public IActionService ActionService { get; private set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 插件服务实例(兼容性)
|
|
||||||
/// </summary>
|
|
||||||
public IPluginService PluginService { get; private set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 构造函数
|
|
||||||
/// </summary>
|
|
||||||
protected EnhancedPluginBaseV2()
|
|
||||||
{
|
|
||||||
// 初始化所有服务实例
|
|
||||||
PluginService = PluginServiceManager.Instance;
|
|
||||||
GetService = PluginServiceManager.Instance;
|
|
||||||
WindowService = PluginServiceManager.Instance;
|
|
||||||
ActionService = PluginServiceManager.Instance;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 插件启动时调用,在Initialize之后
|
|
||||||
/// </summary>
|
|
||||||
public virtual void OnStartup()
|
|
||||||
{
|
|
||||||
LogHelper.WriteLogToFile($"插件 {Name} 已启动");
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 插件关闭时调用,在Cleanup之前
|
|
||||||
/// </summary>
|
|
||||||
public virtual void OnShutdown()
|
|
||||||
{
|
|
||||||
LogHelper.WriteLogToFile($"插件 {Name} 正在关闭");
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 获取插件的菜单项
|
|
||||||
/// </summary>
|
|
||||||
/// <returns>菜单项集合</returns>
|
|
||||||
public virtual MenuItem[] GetMenuItems()
|
|
||||||
{
|
|
||||||
return new MenuItem[0];
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 获取插件的工具栏按钮
|
|
||||||
/// </summary>
|
|
||||||
/// <returns>工具栏按钮集合</returns>
|
|
||||||
public virtual Button[] GetToolbarButtons()
|
|
||||||
{
|
|
||||||
return new Button[0];
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 获取插件的状态栏信息
|
|
||||||
/// </summary>
|
|
||||||
/// <returns>状态栏信息</returns>
|
|
||||||
public virtual string GetStatusBarInfo()
|
|
||||||
{
|
|
||||||
return $"{Name} v{Version} - {(IsEnabled ? "已启用" : "已禁用")}";
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 插件配置变更时调用
|
|
||||||
/// </summary>
|
|
||||||
public virtual void OnConfigurationChanged()
|
|
||||||
{
|
|
||||||
LogHelper.WriteLogToFile($"插件 {Name} 配置已变更");
|
|
||||||
}
|
|
||||||
|
|
||||||
#region 便捷方法
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 显示通知消息
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="message">消息内容</param>
|
|
||||||
/// <param name="type">消息类型</param>
|
|
||||||
protected void ShowNotification(string message, NotificationType type = NotificationType.Info)
|
|
||||||
{
|
|
||||||
WindowService.ShowNotification(message, type);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 显示确认对话框
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="message">消息内容</param>
|
|
||||||
/// <param name="title">标题</param>
|
|
||||||
/// <returns>用户选择结果</returns>
|
|
||||||
protected bool ShowConfirmDialog(string message, string title = "确认")
|
|
||||||
{
|
|
||||||
return WindowService.ShowConfirmDialog(message, title);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 显示输入对话框
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="message">提示消息</param>
|
|
||||||
/// <param name="title">标题</param>
|
|
||||||
/// <param name="defaultValue">默认值</param>
|
|
||||||
/// <returns>用户输入内容</returns>
|
|
||||||
protected string ShowInputDialog(string message, string title = "输入", string defaultValue = "")
|
|
||||||
{
|
|
||||||
return WindowService.ShowInputDialog(message, title, defaultValue);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 获取系统设置
|
|
||||||
/// </summary>
|
|
||||||
/// <typeparam name="T">设置类型</typeparam>
|
|
||||||
/// <param name="key">设置键</param>
|
|
||||||
/// <param name="defaultValue">默认值</param>
|
|
||||||
/// <returns>设置值</returns>
|
|
||||||
protected T GetSetting<T>(string key, T defaultValue = default(T))
|
|
||||||
{
|
|
||||||
return GetService.GetSetting(key, defaultValue);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 设置系统设置
|
|
||||||
/// </summary>
|
|
||||||
/// <typeparam name="T">设置类型</typeparam>
|
|
||||||
/// <param name="key">设置键</param>
|
|
||||||
/// <param name="value">设置值</param>
|
|
||||||
protected void SetSetting<T>(string key, T value)
|
|
||||||
{
|
|
||||||
ActionService.SetSetting(key, value);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 保存设置
|
|
||||||
/// </summary>
|
|
||||||
protected void SaveSettings()
|
|
||||||
{
|
|
||||||
ActionService.SaveSettings();
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 清除当前画布
|
|
||||||
/// </summary>
|
|
||||||
protected void ClearCanvas()
|
|
||||||
{
|
|
||||||
ActionService.ClearCanvas();
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 撤销操作
|
|
||||||
/// </summary>
|
|
||||||
protected void Undo()
|
|
||||||
{
|
|
||||||
ActionService.Undo();
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 重做操作
|
|
||||||
/// </summary>
|
|
||||||
protected void Redo()
|
|
||||||
{
|
|
||||||
ActionService.Redo();
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 检查是否可以撤销
|
|
||||||
/// </summary>
|
|
||||||
protected bool CanUndo => GetService.CanUndo;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 检查是否可以重做
|
|
||||||
/// </summary>
|
|
||||||
protected bool CanRedo => GetService.CanRedo;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 获取当前绘制模式
|
|
||||||
/// </summary>
|
|
||||||
protected int CurrentDrawingMode => GetService.CurrentDrawingMode;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 设置绘制模式
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="mode">绘制模式</param>
|
|
||||||
protected void SetDrawingMode(int mode)
|
|
||||||
{
|
|
||||||
ActionService.SetDrawingMode(mode);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 注册事件处理器
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="eventName">事件名称</param>
|
|
||||||
/// <param name="handler">事件处理器</param>
|
|
||||||
protected void RegisterEventHandler(string eventName, System.EventHandler handler)
|
|
||||||
{
|
|
||||||
ActionService.RegisterEventHandler(eventName, handler);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 注销事件处理器
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="eventName">事件名称</param>
|
|
||||||
/// <param name="handler">事件处理器</param>
|
|
||||||
protected void UnregisterEventHandler(string eventName, System.EventHandler handler)
|
|
||||||
{
|
|
||||||
ActionService.UnregisterEventHandler(eventName, handler);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 触发事件
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="eventName">事件名称</param>
|
|
||||||
/// <param name="sender">事件发送者</param>
|
|
||||||
/// <param name="args">事件参数</param>
|
|
||||||
protected void TriggerEvent(string eventName, object sender, System.EventArgs args)
|
|
||||||
{
|
|
||||||
ActionService.TriggerEvent(eventName, sender, args);
|
|
||||||
}
|
|
||||||
|
|
||||||
#endregion
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,296 +0,0 @@
|
|||||||
using System;
|
|
||||||
using System.Windows.Media;
|
|
||||||
|
|
||||||
namespace Ink_Canvas.Helpers.Plugins
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// 操作服务接口,统一所有执行操作相关的方法
|
|
||||||
/// </summary>
|
|
||||||
public interface IActionService
|
|
||||||
{
|
|
||||||
#region 画布操作
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 清除当前画布
|
|
||||||
/// </summary>
|
|
||||||
void ClearCanvas();
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 清除所有画布
|
|
||||||
/// </summary>
|
|
||||||
void ClearAllCanvases();
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 添加新页面
|
|
||||||
/// </summary>
|
|
||||||
void AddNewPage();
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 删除当前页面
|
|
||||||
/// </summary>
|
|
||||||
void DeleteCurrentPage();
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 切换到指定页面
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="pageIndex">页面索引</param>
|
|
||||||
void SwitchToPage(int pageIndex);
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 切换到下一页
|
|
||||||
/// </summary>
|
|
||||||
void NextPage();
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 切换到上一页
|
|
||||||
/// </summary>
|
|
||||||
void PreviousPage();
|
|
||||||
|
|
||||||
#endregion
|
|
||||||
|
|
||||||
#region 绘制操作
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 设置绘制模式
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="mode">绘制模式</param>
|
|
||||||
void SetDrawingMode(int mode);
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 设置笔触宽度
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="width">宽度</param>
|
|
||||||
void SetInkWidth(double width);
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 设置笔触颜色
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="color">颜色</param>
|
|
||||||
void SetInkColor(Color color);
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 设置高亮笔宽度
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="width">宽度</param>
|
|
||||||
void SetHighlighterWidth(double width);
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 设置橡皮擦大小
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="size">大小</param>
|
|
||||||
void SetEraserSize(int size);
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 设置橡皮擦类型
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="type">类型</param>
|
|
||||||
void SetEraserType(int type);
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 设置橡皮擦形状
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="shape">形状</param>
|
|
||||||
void SetEraserShape(int shape);
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 设置笔触透明度
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="alpha">透明度</param>
|
|
||||||
void SetInkAlpha(double alpha);
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 设置笔触样式
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="style">样式</param>
|
|
||||||
void SetInkStyle(int style);
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 设置背景颜色
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="color">颜色</param>
|
|
||||||
void SetBackgroundColor(string color);
|
|
||||||
|
|
||||||
#endregion
|
|
||||||
|
|
||||||
#region 文件操作
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 保存画布内容
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="filePath">文件路径</param>
|
|
||||||
void SaveCanvas(string filePath);
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 加载画布内容
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="filePath">文件路径</param>
|
|
||||||
void LoadCanvas(string filePath);
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 导出为图片
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="filePath">文件路径</param>
|
|
||||||
/// <param name="format">图片格式</param>
|
|
||||||
void ExportAsImage(string filePath, string format);
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 导出为PDF
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="filePath">文件路径</param>
|
|
||||||
void ExportAsPDF(string filePath);
|
|
||||||
|
|
||||||
#endregion
|
|
||||||
|
|
||||||
#region 撤销重做操作
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 撤销操作
|
|
||||||
/// </summary>
|
|
||||||
void Undo();
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 重做操作
|
|
||||||
/// </summary>
|
|
||||||
void Redo();
|
|
||||||
|
|
||||||
#endregion
|
|
||||||
|
|
||||||
#region 选择操作
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 全选
|
|
||||||
/// </summary>
|
|
||||||
void SelectAll();
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 取消选择
|
|
||||||
/// </summary>
|
|
||||||
void DeselectAll();
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 删除选中内容
|
|
||||||
/// </summary>
|
|
||||||
void DeleteSelected();
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 复制选中内容
|
|
||||||
/// </summary>
|
|
||||||
void CopySelected();
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 剪切选中内容
|
|
||||||
/// </summary>
|
|
||||||
void CutSelected();
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 粘贴内容
|
|
||||||
/// </summary>
|
|
||||||
void Paste();
|
|
||||||
|
|
||||||
#endregion
|
|
||||||
|
|
||||||
#region 系统设置操作
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 设置系统设置
|
|
||||||
/// </summary>
|
|
||||||
/// <typeparam name="T">设置类型</typeparam>
|
|
||||||
/// <param name="key">设置键</param>
|
|
||||||
/// <param name="value">设置值</param>
|
|
||||||
void SetSetting<T>(string key, T value);
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 保存设置到文件
|
|
||||||
/// </summary>
|
|
||||||
void SaveSettings();
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 从文件加载设置
|
|
||||||
/// </summary>
|
|
||||||
void LoadSettings();
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 重置设置为默认值
|
|
||||||
/// </summary>
|
|
||||||
void ResetSettings();
|
|
||||||
|
|
||||||
#endregion
|
|
||||||
|
|
||||||
#region 插件管理操作
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 启用插件
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="pluginName">插件名称</param>
|
|
||||||
void EnablePlugin(string pluginName);
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 禁用插件
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="pluginName">插件名称</param>
|
|
||||||
void DisablePlugin(string pluginName);
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 卸载插件
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="pluginName">插件名称</param>
|
|
||||||
void UnloadPlugin(string pluginName);
|
|
||||||
|
|
||||||
#endregion
|
|
||||||
|
|
||||||
#region 事件系统操作
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 注册事件处理器
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="eventName">事件名称</param>
|
|
||||||
/// <param name="handler">事件处理器</param>
|
|
||||||
void RegisterEventHandler(string eventName, EventHandler handler);
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 注销事件处理器
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="eventName">事件名称</param>
|
|
||||||
/// <param name="handler">事件处理器</param>
|
|
||||||
void UnregisterEventHandler(string eventName, EventHandler handler);
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 触发事件
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="eventName">事件名称</param>
|
|
||||||
/// <param name="sender">事件发送者</param>
|
|
||||||
/// <param name="args">事件参数</param>
|
|
||||||
void TriggerEvent(string eventName, object sender, EventArgs args);
|
|
||||||
|
|
||||||
#endregion
|
|
||||||
|
|
||||||
#region 应用程序操作
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 重启应用程序
|
|
||||||
/// </summary>
|
|
||||||
void RestartApplication();
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 退出应用程序
|
|
||||||
/// </summary>
|
|
||||||
void ExitApplication();
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 检查更新
|
|
||||||
/// </summary>
|
|
||||||
void CheckForUpdates();
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 打开帮助文档
|
|
||||||
/// </summary>
|
|
||||||
void OpenHelpDocument();
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 打开关于页面
|
|
||||||
/// </summary>
|
|
||||||
void OpenAboutPage();
|
|
||||||
|
|
||||||
#endregion
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,178 +0,0 @@
|
|||||||
using System;
|
|
||||||
using System.IO;
|
|
||||||
|
|
||||||
namespace Ink_Canvas.Helpers.Plugins
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// ICCPP 插件适配器,用于加载和管理 .iccpp 格式的插件
|
|
||||||
/// </summary>
|
|
||||||
public class ICCPPPluginAdapter : PluginBase
|
|
||||||
{
|
|
||||||
private readonly byte[] _pluginData;
|
|
||||||
private readonly string _pluginPath;
|
|
||||||
private readonly string _pluginName;
|
|
||||||
private readonly Version _pluginVersion;
|
|
||||||
private bool _isInitialized;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 创建 ICCPP 插件适配器
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="pluginPath">插件文件路径</param>
|
|
||||||
/// <param name="pluginData">插件文件数据</param>
|
|
||||||
public ICCPPPluginAdapter(string pluginPath, byte[] pluginData)
|
|
||||||
{
|
|
||||||
_pluginPath = pluginPath;
|
|
||||||
_pluginData = pluginData;
|
|
||||||
PluginPath = pluginPath;
|
|
||||||
|
|
||||||
// 从文件名获取插件名称
|
|
||||||
_pluginName = Path.GetFileNameWithoutExtension(pluginPath);
|
|
||||||
_pluginVersion = new Version(1, 0, 0); // 默认版本
|
|
||||||
|
|
||||||
// 尝试从插件数据中读取更多信息
|
|
||||||
TryReadPluginMetadata();
|
|
||||||
}
|
|
||||||
|
|
||||||
public ICCPPPluginAdapter()
|
|
||||||
{
|
|
||||||
_pluginPath = string.Empty;
|
|
||||||
_pluginData = new byte[0];
|
|
||||||
PluginPath = string.Empty;
|
|
||||||
_pluginName = "ICCPPPlugin";
|
|
||||||
_pluginVersion = new Version(1, 0, 0);
|
|
||||||
// 可选:初始化其他字段
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 尝试从插件数据中读取元数据
|
|
||||||
/// </summary>
|
|
||||||
private void TryReadPluginMetadata()
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
// 这里可以根据 .iccpp 文件的实际格式解析元数据
|
|
||||||
// 例如,如果文件有特定的头部结构,可以在这里解析
|
|
||||||
|
|
||||||
// 示例:如果前100字节包含元数据
|
|
||||||
if (_pluginData.Length > 100)
|
|
||||||
{
|
|
||||||
// 解析元数据的代码...
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
LogHelper.WriteLogToFile($"解析插件 {_pluginName} 元数据时出错: {ex.Message}", LogHelper.LogType.Error);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#region IPlugin 接口实现
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 插件名称
|
|
||||||
/// </summary>
|
|
||||||
public override string Name => _pluginName;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 插件描述
|
|
||||||
/// </summary>
|
|
||||||
public override string Description => $"{_pluginName} (ICCPP 格式插件)";
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 插件版本
|
|
||||||
/// </summary>
|
|
||||||
public override Version Version => _pluginVersion;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 插件作者
|
|
||||||
/// </summary>
|
|
||||||
public override string Author => "未知";
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 是否为内置插件
|
|
||||||
/// </summary>
|
|
||||||
public override bool IsBuiltIn => false;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 初始化插件
|
|
||||||
/// </summary>
|
|
||||||
public override void Initialize()
|
|
||||||
{
|
|
||||||
if (_isInitialized) return;
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
// 这里可以添加 .iccpp 插件的初始化逻辑
|
|
||||||
// 例如,根据文件格式加载特定资源
|
|
||||||
|
|
||||||
LogHelper.WriteLogToFile($"ICCPP 插件 {Name} 已初始化");
|
|
||||||
_isInitialized = true;
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
LogHelper.WriteLogToFile($"初始化 ICCPP 插件 {Name} 时出错: {ex.Message}", LogHelper.LogType.Error);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 启用插件
|
|
||||||
/// </summary>
|
|
||||||
public override void Enable()
|
|
||||||
{
|
|
||||||
if (IsEnabled) return;
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
// 这里可以添加 .iccpp 插件的启用逻辑
|
|
||||||
// 例如,加载动态库、注册事件等
|
|
||||||
|
|
||||||
base.Enable(); // 设置启用状态并触发事件
|
|
||||||
LogHelper.WriteLogToFile($"ICCPP 插件 {Name} 已启用");
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
LogHelper.WriteLogToFile($"启用 ICCPP 插件 {Name} 时出错: {ex.Message}", LogHelper.LogType.Error);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 禁用插件
|
|
||||||
/// </summary>
|
|
||||||
public override void Disable()
|
|
||||||
{
|
|
||||||
if (!IsEnabled) return;
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
// 这里可以添加 .iccpp 插件的禁用逻辑
|
|
||||||
// 例如,卸载动态库、注销事件等
|
|
||||||
|
|
||||||
base.Disable(); // 设置禁用状态并触发事件
|
|
||||||
LogHelper.WriteLogToFile($"ICCPP 插件 {Name} 已禁用");
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
LogHelper.WriteLogToFile($"禁用 ICCPP 插件 {Name} 时出错: {ex.Message}", LogHelper.LogType.Error);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 清理插件资源
|
|
||||||
/// </summary>
|
|
||||||
public override void Cleanup()
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
// 这里可以添加 .iccpp 插件的清理逻辑
|
|
||||||
// 例如,释放资源等
|
|
||||||
|
|
||||||
LogHelper.WriteLogToFile($"ICCPP 插件 {Name} 已清理资源");
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
LogHelper.WriteLogToFile($"清理 ICCPP 插件 {Name} 资源时出错: {ex.Message}", LogHelper.LogType.Error);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#endregion
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,48 +0,0 @@
|
|||||||
using System.Windows.Controls;
|
|
||||||
|
|
||||||
namespace Ink_Canvas.Helpers.Plugins
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// 增强的插件接口,提供对插件服务的访问
|
|
||||||
/// </summary>
|
|
||||||
public interface IEnhancedPlugin : IPlugin
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// 获取插件服务实例
|
|
||||||
/// </summary>
|
|
||||||
IPluginService PluginService { get; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 插件启动时调用,在Initialize之后
|
|
||||||
/// </summary>
|
|
||||||
void OnStartup();
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 插件关闭时调用,在Cleanup之前
|
|
||||||
/// </summary>
|
|
||||||
void OnShutdown();
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 获取插件的菜单项
|
|
||||||
/// </summary>
|
|
||||||
/// <returns>菜单项集合</returns>
|
|
||||||
MenuItem[] GetMenuItems();
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 获取插件的工具栏按钮
|
|
||||||
/// </summary>
|
|
||||||
/// <returns>工具栏按钮集合</returns>
|
|
||||||
Button[] GetToolbarButtons();
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 获取插件的状态栏信息
|
|
||||||
/// </summary>
|
|
||||||
/// <returns>状态栏信息</returns>
|
|
||||||
string GetStatusBarInfo();
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 插件配置变更时调用
|
|
||||||
/// </summary>
|
|
||||||
void OnConfigurationChanged();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,214 +0,0 @@
|
|||||||
using System.Collections.Generic;
|
|
||||||
using System.Windows;
|
|
||||||
using System.Windows.Controls;
|
|
||||||
using System.Windows.Media;
|
|
||||||
|
|
||||||
namespace Ink_Canvas.Helpers.Plugins
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// 获取服务接口,统一所有获取类的方法
|
|
||||||
/// </summary>
|
|
||||||
public interface IGetService
|
|
||||||
{
|
|
||||||
#region 窗口和UI获取
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 获取主窗口引用
|
|
||||||
/// </summary>
|
|
||||||
Window MainWindow { get; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 获取当前画布
|
|
||||||
/// </summary>
|
|
||||||
InkCanvas CurrentCanvas { get; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 获取所有画布页面
|
|
||||||
/// </summary>
|
|
||||||
List<Canvas> AllCanvasPages { get; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 获取当前页面索引
|
|
||||||
/// </summary>
|
|
||||||
int CurrentPageIndex { get; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 获取当前页面数量
|
|
||||||
/// </summary>
|
|
||||||
int TotalPageCount { get; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 获取浮动工具栏
|
|
||||||
/// </summary>
|
|
||||||
FrameworkElement FloatingToolBar { get; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 获取左侧面板
|
|
||||||
/// </summary>
|
|
||||||
FrameworkElement LeftPanel { get; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 获取右侧面板
|
|
||||||
/// </summary>
|
|
||||||
FrameworkElement RightPanel { get; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 获取顶部面板
|
|
||||||
/// </summary>
|
|
||||||
FrameworkElement TopPanel { get; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 获取底部面板
|
|
||||||
/// </summary>
|
|
||||||
FrameworkElement BottomPanel { get; }
|
|
||||||
|
|
||||||
#endregion
|
|
||||||
|
|
||||||
#region 绘制工具状态获取
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 获取当前绘制模式
|
|
||||||
/// </summary>
|
|
||||||
int CurrentDrawingMode { get; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 获取当前笔触宽度
|
|
||||||
/// </summary>
|
|
||||||
double CurrentInkWidth { get; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 获取当前笔触颜色
|
|
||||||
/// </summary>
|
|
||||||
Color CurrentInkColor { get; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 获取当前高亮笔宽度
|
|
||||||
/// </summary>
|
|
||||||
double CurrentHighlighterWidth { get; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 获取当前橡皮擦大小
|
|
||||||
/// </summary>
|
|
||||||
int CurrentEraserSize { get; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 获取当前橡皮擦类型
|
|
||||||
/// </summary>
|
|
||||||
int CurrentEraserType { get; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 获取当前橡皮擦形状
|
|
||||||
/// </summary>
|
|
||||||
int CurrentEraserShape { get; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 获取当前笔触透明度
|
|
||||||
/// </summary>
|
|
||||||
double CurrentInkAlpha { get; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 获取当前笔触样式
|
|
||||||
/// </summary>
|
|
||||||
int CurrentInkStyle { get; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 获取当前背景颜色
|
|
||||||
/// </summary>
|
|
||||||
string CurrentBackgroundColor { get; }
|
|
||||||
|
|
||||||
#endregion
|
|
||||||
|
|
||||||
#region 应用状态获取
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 获取当前主题模式
|
|
||||||
/// </summary>
|
|
||||||
bool IsDarkTheme { get; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 获取当前是否为白板模式
|
|
||||||
/// </summary>
|
|
||||||
bool IsWhiteboardMode { get; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 获取当前是否为PPT模式
|
|
||||||
/// </summary>
|
|
||||||
bool IsPPTMode { get; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 获取当前是否为全屏模式
|
|
||||||
/// </summary>
|
|
||||||
bool IsFullScreenMode { get; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 获取当前是否为画板模式
|
|
||||||
/// </summary>
|
|
||||||
bool IsCanvasMode { get; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 获取当前是否为选择模式
|
|
||||||
/// </summary>
|
|
||||||
bool IsSelectionMode { get; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 获取当前是否为擦除模式
|
|
||||||
/// </summary>
|
|
||||||
bool IsEraserMode { get; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 获取当前是否为形状绘制模式
|
|
||||||
/// </summary>
|
|
||||||
bool IsShapeDrawingMode { get; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 获取当前是否为高亮模式
|
|
||||||
/// </summary>
|
|
||||||
bool IsHighlighterMode { get; }
|
|
||||||
|
|
||||||
#endregion
|
|
||||||
|
|
||||||
#region 撤销重做状态获取
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 获取是否可以撤销
|
|
||||||
/// </summary>
|
|
||||||
bool CanUndo { get; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 获取是否可以重做
|
|
||||||
/// </summary>
|
|
||||||
bool CanRedo { get; }
|
|
||||||
|
|
||||||
#endregion
|
|
||||||
|
|
||||||
#region 系统设置获取
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 获取系统设置
|
|
||||||
/// </summary>
|
|
||||||
/// <typeparam name="T">设置类型</typeparam>
|
|
||||||
/// <param name="key">设置键</param>
|
|
||||||
/// <param name="defaultValue">默认值</param>
|
|
||||||
/// <returns>设置值</returns>
|
|
||||||
T GetSetting<T>(string key, T defaultValue = default(T));
|
|
||||||
|
|
||||||
#endregion
|
|
||||||
|
|
||||||
#region 插件信息获取
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 获取所有已加载的插件
|
|
||||||
/// </summary>
|
|
||||||
/// <returns>插件列表</returns>
|
|
||||||
List<IPlugin> GetAllPlugins();
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 获取指定插件
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="pluginName">插件名称</param>
|
|
||||||
/// <returns>插件实例</returns>
|
|
||||||
IPlugin GetPlugin(string pluginName);
|
|
||||||
|
|
||||||
#endregion
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,67 +0,0 @@
|
|||||||
using System;
|
|
||||||
using System.Windows.Controls;
|
|
||||||
|
|
||||||
namespace Ink_Canvas.Helpers.Plugins
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// 定义插件的基本接口
|
|
||||||
/// </summary>
|
|
||||||
public interface IPlugin
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// 插件名称
|
|
||||||
/// </summary>
|
|
||||||
string Name { get; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 插件描述
|
|
||||||
/// </summary>
|
|
||||||
string Description { get; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 插件版本
|
|
||||||
/// </summary>
|
|
||||||
Version Version { get; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 插件作者
|
|
||||||
/// </summary>
|
|
||||||
string Author { get; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 是否为内置插件
|
|
||||||
/// </summary>
|
|
||||||
bool IsBuiltIn { get; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 初始化插件
|
|
||||||
/// 此方法在插件加载时被调用,用于执行一些初始化工作
|
|
||||||
/// </summary>
|
|
||||||
void Initialize();
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 启用插件
|
|
||||||
/// 此方法在插件被用户或系统启用时调用,激活插件功能
|
|
||||||
/// </summary>
|
|
||||||
void Enable();
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 禁用插件
|
|
||||||
/// 此方法在插件被用户或系统禁用时调用,停用插件功能
|
|
||||||
/// </summary>
|
|
||||||
void Disable();
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 获取插件设置界面
|
|
||||||
/// 此方法返回插件的设置界面控件,用于展示在设置窗口
|
|
||||||
/// </summary>
|
|
||||||
/// <returns>插件设置界面</returns>
|
|
||||||
UserControl GetSettingsView();
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 插件卸载时的清理工作
|
|
||||||
/// 此方法在插件被卸载前调用,用于释放资源和执行清理
|
|
||||||
/// </summary>
|
|
||||||
void Cleanup();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,38 +0,0 @@
|
|||||||
namespace Ink_Canvas.Helpers.Plugins
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// 插件服务接口,提供对软件内部功能的访问
|
|
||||||
/// 继承自三个专门的服务接口:获取服务、窗口服务、操作服务
|
|
||||||
/// </summary>
|
|
||||||
public interface IPluginService : IGetService, IWindowService, IActionService
|
|
||||||
{
|
|
||||||
// 这个接口现在继承自三个专门的服务接口
|
|
||||||
// 所有方法都在子接口中定义,这里不需要重复定义
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 通知类型枚举
|
|
||||||
/// </summary>
|
|
||||||
public enum NotificationType
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// 信息
|
|
||||||
/// </summary>
|
|
||||||
Info,
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 成功
|
|
||||||
/// </summary>
|
|
||||||
Success,
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 警告
|
|
||||||
/// </summary>
|
|
||||||
Warning,
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 错误
|
|
||||||
/// </summary>
|
|
||||||
Error
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,152 +0,0 @@
|
|||||||
namespace Ink_Canvas.Helpers.Plugins
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// 窗口服务接口,统一所有窗口操作相关的方法
|
|
||||||
/// </summary>
|
|
||||||
public interface IWindowService
|
|
||||||
{
|
|
||||||
#region 窗口显示和隐藏
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 显示设置窗口
|
|
||||||
/// </summary>
|
|
||||||
void ShowSettingsWindow();
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 隐藏设置窗口
|
|
||||||
/// </summary>
|
|
||||||
void HideSettingsWindow();
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 显示插件设置窗口
|
|
||||||
/// </summary>
|
|
||||||
void ShowPluginSettingsWindow();
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 隐藏插件设置窗口
|
|
||||||
/// </summary>
|
|
||||||
void HidePluginSettingsWindow();
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 显示帮助窗口
|
|
||||||
/// </summary>
|
|
||||||
void ShowHelpWindow();
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 隐藏帮助窗口
|
|
||||||
/// </summary>
|
|
||||||
void HideHelpWindow();
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 显示关于窗口
|
|
||||||
/// </summary>
|
|
||||||
void ShowAboutWindow();
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 隐藏关于窗口
|
|
||||||
/// </summary>
|
|
||||||
void HideAboutWindow();
|
|
||||||
|
|
||||||
#endregion
|
|
||||||
|
|
||||||
#region 对话框和通知
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 显示通知消息
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="message">消息内容</param>
|
|
||||||
/// <param name="type">消息类型</param>
|
|
||||||
void ShowNotification(string message, NotificationType type = NotificationType.Info);
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 显示确认对话框
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="message">消息内容</param>
|
|
||||||
/// <param name="title">标题</param>
|
|
||||||
/// <returns>用户选择结果</returns>
|
|
||||||
bool ShowConfirmDialog(string message, string title = "确认");
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 显示输入对话框
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="message">提示消息</param>
|
|
||||||
/// <param name="title">标题</param>
|
|
||||||
/// <param name="defaultValue">默认值</param>
|
|
||||||
/// <returns>用户输入内容</returns>
|
|
||||||
string ShowInputDialog(string message, string title = "输入", string defaultValue = "");
|
|
||||||
|
|
||||||
#endregion
|
|
||||||
|
|
||||||
#region 窗口状态控制
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 设置窗口全屏状态
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="isFullScreen">是否全屏</param>
|
|
||||||
void SetFullScreen(bool isFullScreen);
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 设置窗口置顶状态
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="isTopMost">是否置顶</param>
|
|
||||||
void SetTopMost(bool isTopMost);
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 设置窗口可见性
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="isVisible">是否可见</param>
|
|
||||||
void SetWindowVisibility(bool isVisible);
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 最小化窗口
|
|
||||||
/// </summary>
|
|
||||||
void MinimizeWindow();
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 最大化窗口
|
|
||||||
/// </summary>
|
|
||||||
void MaximizeWindow();
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 恢复窗口
|
|
||||||
/// </summary>
|
|
||||||
void RestoreWindow();
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 关闭窗口
|
|
||||||
/// </summary>
|
|
||||||
void CloseWindow();
|
|
||||||
|
|
||||||
#endregion
|
|
||||||
|
|
||||||
#region 窗口位置和大小
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 设置窗口位置
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="x">X坐标</param>
|
|
||||||
/// <param name="y">Y坐标</param>
|
|
||||||
void SetWindowPosition(double x, double y);
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 设置窗口大小
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="width">宽度</param>
|
|
||||||
/// <param name="height">高度</param>
|
|
||||||
void SetWindowSize(double width, double height);
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 获取窗口位置
|
|
||||||
/// </summary>
|
|
||||||
/// <returns>窗口位置</returns>
|
|
||||||
(double x, double y) GetWindowPosition();
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 获取窗口大小
|
|
||||||
/// </summary>
|
|
||||||
/// <returns>窗口大小</returns>
|
|
||||||
(double width, double height) GetWindowSize();
|
|
||||||
|
|
||||||
#endregion
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,161 +0,0 @@
|
|||||||
using System;
|
|
||||||
using System.Windows.Controls;
|
|
||||||
|
|
||||||
namespace Ink_Canvas.Helpers.Plugins
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// 插件基类,提供基本实现
|
|
||||||
/// </summary>
|
|
||||||
public abstract class PluginBase : IPlugin
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// 插件状态(私有字段)
|
|
||||||
/// </summary>
|
|
||||||
private bool _isEnabled;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 插件状态(公共属性)
|
|
||||||
/// </summary>
|
|
||||||
public bool IsEnabled
|
|
||||||
{
|
|
||||||
get => _isEnabled;
|
|
||||||
protected set
|
|
||||||
{
|
|
||||||
if (_isEnabled != value)
|
|
||||||
{
|
|
||||||
_isEnabled = value;
|
|
||||||
OnEnabledStateChanged(value);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 插件ID
|
|
||||||
/// </summary>
|
|
||||||
public string Id { get; protected set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 插件路径
|
|
||||||
/// </summary>
|
|
||||||
public string PluginPath { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 插件名称
|
|
||||||
/// </summary>
|
|
||||||
public abstract string Name { get; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 插件描述
|
|
||||||
/// </summary>
|
|
||||||
public abstract string Description { get; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 插件版本
|
|
||||||
/// </summary>
|
|
||||||
public abstract Version Version { get; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 插件作者
|
|
||||||
/// </summary>
|
|
||||||
public abstract string Author { get; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 是否为内置插件
|
|
||||||
/// </summary>
|
|
||||||
public virtual bool IsBuiltIn => false;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 状态变更事件
|
|
||||||
/// </summary>
|
|
||||||
public event EventHandler<bool> EnabledStateChanged;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 初始化插件
|
|
||||||
/// </summary>
|
|
||||||
public virtual void Initialize()
|
|
||||||
{
|
|
||||||
Id = GetType().FullName;
|
|
||||||
|
|
||||||
// 添加日志,记录插件名称
|
|
||||||
try
|
|
||||||
{
|
|
||||||
string name = Name;
|
|
||||||
LogHelper.WriteLogToFile($"初始化插件: ID={Id}, 名称={name ?? "未命名"}");
|
|
||||||
|
|
||||||
if (string.IsNullOrEmpty(name))
|
|
||||||
{
|
|
||||||
LogHelper.WriteLogToFile($"警告: 插件 {Id} 的名称为空", LogHelper.LogType.Warning);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
LogHelper.WriteLogToFile($"获取插件名称时出错: {ex.Message}", LogHelper.LogType.Error);
|
|
||||||
}
|
|
||||||
|
|
||||||
LogHelper.WriteLogToFile($"插件 {Name} 已初始化");
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 启用插件
|
|
||||||
/// </summary>
|
|
||||||
public virtual void Enable()
|
|
||||||
{
|
|
||||||
if (!IsEnabled)
|
|
||||||
{
|
|
||||||
IsEnabled = true;
|
|
||||||
LogHelper.WriteLogToFile($"插件 {Name} 已启用");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 禁用插件
|
|
||||||
/// </summary>
|
|
||||||
public virtual void Disable()
|
|
||||||
{
|
|
||||||
if (IsEnabled)
|
|
||||||
{
|
|
||||||
IsEnabled = false;
|
|
||||||
LogHelper.WriteLogToFile($"插件 {Name} 已禁用");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 获取插件设置界面
|
|
||||||
/// </summary>
|
|
||||||
/// <returns>插件设置界面</returns>
|
|
||||||
public virtual UserControl GetSettingsView()
|
|
||||||
{
|
|
||||||
// 默认返回空设置页面
|
|
||||||
return new UserControl();
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 插件卸载时的清理工作
|
|
||||||
/// </summary>
|
|
||||||
public virtual void Cleanup()
|
|
||||||
{
|
|
||||||
LogHelper.WriteLogToFile($"插件 {Name} 已卸载");
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 保存插件自身的设置
|
|
||||||
/// 注意:此方法仅用于保存插件的特定设置,不应影响插件启用/禁用状态
|
|
||||||
/// 插件启用状态由PluginManager统一管理
|
|
||||||
/// </summary>
|
|
||||||
public virtual void SavePluginSettings()
|
|
||||||
{
|
|
||||||
// 默认实现不做任何事情
|
|
||||||
// 子类可以重写此方法,将自身设置保存到配置文件中
|
|
||||||
LogHelper.WriteLogToFile($"插件 {Name} 设置已保存", LogHelper.LogType.Event);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 触发状态变更事件
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="isEnabled">是否启用</param>
|
|
||||||
protected virtual void OnEnabledStateChanged(bool isEnabled)
|
|
||||||
{
|
|
||||||
EnabledStateChanged?.Invoke(this, isEnabled);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,273 +0,0 @@
|
|||||||
using Newtonsoft.Json;
|
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.IO;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
|
|
||||||
namespace Ink_Canvas.Helpers.Plugins
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// 插件配置管理器,允许插件管理自己的配置
|
|
||||||
/// </summary>
|
|
||||||
public class PluginConfigurationManager
|
|
||||||
{
|
|
||||||
private static readonly string PluginConfigDirectory = Path.Combine(App.RootPath, "PluginConfigs");
|
|
||||||
private static readonly Dictionary<string, Dictionary<string, object>> _pluginConfigs = new Dictionary<string, Dictionary<string, object>>();
|
|
||||||
private static readonly object _lockObject = new object();
|
|
||||||
|
|
||||||
static PluginConfigurationManager()
|
|
||||||
{
|
|
||||||
// 确保配置目录存在
|
|
||||||
if (!Directory.Exists(PluginConfigDirectory))
|
|
||||||
{
|
|
||||||
Directory.CreateDirectory(PluginConfigDirectory);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 获取插件配置值
|
|
||||||
/// </summary>
|
|
||||||
/// <typeparam name="T">配置值类型</typeparam>
|
|
||||||
/// <param name="pluginName">插件名称</param>
|
|
||||||
/// <param name="key">配置键</param>
|
|
||||||
/// <param name="defaultValue">默认值</param>
|
|
||||||
/// <returns>配置值</returns>
|
|
||||||
public static T GetConfiguration<T>(string pluginName, string key, T defaultValue = default(T))
|
|
||||||
{
|
|
||||||
lock (_lockObject)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
if (_pluginConfigs.TryGetValue(pluginName, out var pluginConfig))
|
|
||||||
{
|
|
||||||
if (pluginConfig.TryGetValue(key, out var value))
|
|
||||||
{
|
|
||||||
if (value is T typedValue)
|
|
||||||
{
|
|
||||||
return typedValue;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 尝试类型转换
|
|
||||||
try
|
|
||||||
{
|
|
||||||
return (T)Convert.ChangeType(value, typeof(T));
|
|
||||||
}
|
|
||||||
catch
|
|
||||||
{
|
|
||||||
return defaultValue;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
LogHelper.WriteLogToFile($"获取插件 {pluginName} 配置 {key} 时出错: {ex.Message}", LogHelper.LogType.Error);
|
|
||||||
}
|
|
||||||
|
|
||||||
return defaultValue;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 设置插件配置值
|
|
||||||
/// </summary>
|
|
||||||
/// <typeparam name="T">配置值类型</typeparam>
|
|
||||||
/// <param name="pluginName">插件名称</param>
|
|
||||||
/// <param name="key">配置键</param>
|
|
||||||
/// <param name="value">配置值</param>
|
|
||||||
public static void SetConfiguration<T>(string pluginName, string key, T value)
|
|
||||||
{
|
|
||||||
lock (_lockObject)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
if (!_pluginConfigs.ContainsKey(pluginName))
|
|
||||||
{
|
|
||||||
_pluginConfigs[pluginName] = new Dictionary<string, object>();
|
|
||||||
}
|
|
||||||
|
|
||||||
_pluginConfigs[pluginName][key] = value;
|
|
||||||
|
|
||||||
// 异步保存配置
|
|
||||||
Task.Run(() => SavePluginConfiguration(pluginName));
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
LogHelper.WriteLogToFile($"设置插件 {pluginName} 配置 {key} 时出错: {ex.Message}", LogHelper.LogType.Error);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 删除插件配置
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="pluginName">插件名称</param>
|
|
||||||
/// <param name="key">配置键</param>
|
|
||||||
public static void RemoveConfiguration(string pluginName, string key)
|
|
||||||
{
|
|
||||||
lock (_lockObject)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
if (_pluginConfigs.TryGetValue(pluginName, out var pluginConfig))
|
|
||||||
{
|
|
||||||
if (pluginConfig.Remove(key))
|
|
||||||
{
|
|
||||||
// 异步保存配置
|
|
||||||
Task.Run(() => SavePluginConfiguration(pluginName));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
LogHelper.WriteLogToFile($"删除插件 {pluginName} 配置 {key} 时出错: {ex.Message}", LogHelper.LogType.Error);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 获取插件的所有配置
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="pluginName">插件名称</param>
|
|
||||||
/// <returns>配置字典</returns>
|
|
||||||
public static Dictionary<string, object> GetAllConfigurations(string pluginName)
|
|
||||||
{
|
|
||||||
lock (_lockObject)
|
|
||||||
{
|
|
||||||
if (_pluginConfigs.TryGetValue(pluginName, out var pluginConfig))
|
|
||||||
{
|
|
||||||
return new Dictionary<string, object>(pluginConfig);
|
|
||||||
}
|
|
||||||
return new Dictionary<string, object>();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 清除插件的所有配置
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="pluginName">插件名称</param>
|
|
||||||
public static void ClearAllConfigurations(string pluginName)
|
|
||||||
{
|
|
||||||
lock (_lockObject)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
if (_pluginConfigs.Remove(pluginName))
|
|
||||||
{
|
|
||||||
// 删除配置文件
|
|
||||||
string configFile = Path.Combine(PluginConfigDirectory, $"{pluginName}.json");
|
|
||||||
if (File.Exists(configFile))
|
|
||||||
{
|
|
||||||
File.Delete(configFile);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
LogHelper.WriteLogToFile($"清除插件 {pluginName} 所有配置时出错: {ex.Message}", LogHelper.LogType.Error);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 加载插件配置
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="pluginName">插件名称</param>
|
|
||||||
public static void LoadPluginConfiguration(string pluginName)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
string configFile = Path.Combine(PluginConfigDirectory, $"{pluginName}.json");
|
|
||||||
if (File.Exists(configFile))
|
|
||||||
{
|
|
||||||
string json = File.ReadAllText(configFile);
|
|
||||||
var config = JsonConvert.DeserializeObject<Dictionary<string, object>>(json);
|
|
||||||
|
|
||||||
lock (_lockObject)
|
|
||||||
{
|
|
||||||
_pluginConfigs[pluginName] = config ?? new Dictionary<string, object>();
|
|
||||||
}
|
|
||||||
|
|
||||||
LogHelper.WriteLogToFile($"已加载插件 {pluginName} 的配置");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
LogHelper.WriteLogToFile($"加载插件 {pluginName} 配置时出错: {ex.Message}", LogHelper.LogType.Error);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 保存插件配置
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="pluginName">插件名称</param>
|
|
||||||
private static void SavePluginConfiguration(string pluginName)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
Dictionary<string, object> pluginConfig;
|
|
||||||
lock (_lockObject)
|
|
||||||
{
|
|
||||||
if (!_pluginConfigs.TryGetValue(pluginName, out pluginConfig))
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
string configFile = Path.Combine(PluginConfigDirectory, $"{pluginName}.json");
|
|
||||||
string json = JsonConvert.SerializeObject(pluginConfig, Formatting.Indented);
|
|
||||||
File.WriteAllText(configFile, json);
|
|
||||||
|
|
||||||
LogHelper.WriteLogToFile($"已保存插件 {pluginName} 的配置");
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
LogHelper.WriteLogToFile($"保存插件 {pluginName} 配置时出错: {ex.Message}", LogHelper.LogType.Error);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 加载所有插件的配置
|
|
||||||
/// </summary>
|
|
||||||
public static void LoadAllPluginConfigurations()
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
if (Directory.Exists(PluginConfigDirectory))
|
|
||||||
{
|
|
||||||
string[] configFiles = Directory.GetFiles(PluginConfigDirectory, "*.json");
|
|
||||||
foreach (string configFile in configFiles)
|
|
||||||
{
|
|
||||||
string pluginName = Path.GetFileNameWithoutExtension(configFile);
|
|
||||||
LoadPluginConfiguration(pluginName);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
LogHelper.WriteLogToFile($"加载所有插件配置时出错: {ex.Message}", LogHelper.LogType.Error);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 保存所有插件的配置
|
|
||||||
/// </summary>
|
|
||||||
public static void SaveAllPluginConfigurations()
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
lock (_lockObject)
|
|
||||||
{
|
|
||||||
foreach (string pluginName in _pluginConfigs.Keys)
|
|
||||||
{
|
|
||||||
SavePluginConfiguration(pluginName);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
LogHelper.WriteLogToFile($"保存所有插件配置时出错: {ex.Message}", LogHelper.LogType.Error);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
File diff suppressed because it is too large
Load Diff
@@ -1,509 +0,0 @@
|
|||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Windows;
|
|
||||||
using System.Windows.Controls;
|
|
||||||
using System.Windows.Media;
|
|
||||||
|
|
||||||
namespace Ink_Canvas.Helpers.Plugins
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// 插件服务管理器,实现IPluginService接口,提供对软件内部功能的访问
|
|
||||||
/// </summary>
|
|
||||||
public class PluginServiceManager : IPluginService
|
|
||||||
{
|
|
||||||
private static PluginServiceManager _instance;
|
|
||||||
private MainWindow _mainWindow;
|
|
||||||
private Dictionary<string, EventHandler> _eventHandlers;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 单例实例
|
|
||||||
/// </summary>
|
|
||||||
public static PluginServiceManager Instance
|
|
||||||
{
|
|
||||||
get
|
|
||||||
{
|
|
||||||
if (_instance == null)
|
|
||||||
{
|
|
||||||
_instance = new PluginServiceManager();
|
|
||||||
}
|
|
||||||
return _instance;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private PluginServiceManager()
|
|
||||||
{
|
|
||||||
_eventHandlers = new Dictionary<string, EventHandler>();
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 设置主窗口引用
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="mainWindow">主窗口实例</param>
|
|
||||||
public void SetMainWindow(MainWindow mainWindow)
|
|
||||||
{
|
|
||||||
_mainWindow = mainWindow;
|
|
||||||
}
|
|
||||||
|
|
||||||
#region 窗口和UI访问
|
|
||||||
|
|
||||||
public Window MainWindow => _mainWindow;
|
|
||||||
|
|
||||||
public InkCanvas CurrentCanvas => null; // 暂时返回null,避免访问权限问题
|
|
||||||
|
|
||||||
public List<Canvas> AllCanvasPages => new List<Canvas>(); // 暂时返回空列表
|
|
||||||
|
|
||||||
public int CurrentPageIndex => 0; // 暂时返回0
|
|
||||||
|
|
||||||
public int TotalPageCount => 0; // 暂时返回0
|
|
||||||
|
|
||||||
public FrameworkElement FloatingToolBar => _mainWindow?.ViewboxFloatingBar;
|
|
||||||
|
|
||||||
public FrameworkElement LeftPanel => _mainWindow?.BlackboardLeftSide;
|
|
||||||
|
|
||||||
public FrameworkElement RightPanel => _mainWindow?.BlackboardRightSide;
|
|
||||||
|
|
||||||
public FrameworkElement TopPanel => _mainWindow?.BorderTools;
|
|
||||||
|
|
||||||
public FrameworkElement BottomPanel => _mainWindow?.BorderSettings;
|
|
||||||
|
|
||||||
#endregion
|
|
||||||
|
|
||||||
#region 绘制工具状态
|
|
||||||
|
|
||||||
public int CurrentDrawingMode => 0; // 暂时返回0
|
|
||||||
|
|
||||||
public double CurrentInkWidth => 2.5; // 暂时返回默认值
|
|
||||||
|
|
||||||
public Color CurrentInkColor => Colors.Black; // 暂时返回默认值
|
|
||||||
|
|
||||||
public double CurrentHighlighterWidth => 20.0; // 暂时返回默认值
|
|
||||||
|
|
||||||
public int CurrentEraserSize => 2; // 暂时返回默认值
|
|
||||||
|
|
||||||
public int CurrentEraserType => 0; // 暂时返回默认值
|
|
||||||
|
|
||||||
public int CurrentEraserShape => 0; // 暂时返回默认值
|
|
||||||
|
|
||||||
public double CurrentInkAlpha => 255.0; // 暂时返回默认值
|
|
||||||
|
|
||||||
public int CurrentInkStyle => 0; // 暂时返回默认值
|
|
||||||
|
|
||||||
public string CurrentBackgroundColor => "#162924"; // 暂时返回默认值
|
|
||||||
|
|
||||||
#endregion
|
|
||||||
|
|
||||||
#region 应用状态
|
|
||||||
|
|
||||||
public bool IsDarkTheme => false; // 暂时返回默认值
|
|
||||||
|
|
||||||
public bool IsWhiteboardMode => false; // 暂时返回默认值
|
|
||||||
|
|
||||||
public bool IsPPTMode => false; // 暂时返回默认值
|
|
||||||
|
|
||||||
public bool IsFullScreenMode => false; // 暂时返回默认值
|
|
||||||
|
|
||||||
public bool IsCanvasMode => true; // 暂时返回默认值
|
|
||||||
|
|
||||||
public bool IsSelectionMode => false; // 暂时返回默认值
|
|
||||||
|
|
||||||
public bool IsEraserMode => false; // 暂时返回默认值
|
|
||||||
|
|
||||||
public bool IsShapeDrawingMode => false; // 暂时返回默认值
|
|
||||||
|
|
||||||
public bool IsHighlighterMode => false; // 暂时返回默认值
|
|
||||||
|
|
||||||
#endregion
|
|
||||||
|
|
||||||
#region IGetService 实现
|
|
||||||
|
|
||||||
public bool CanUndo => false; // 暂时返回默认值
|
|
||||||
|
|
||||||
public bool CanRedo => false; // 暂时返回默认值
|
|
||||||
|
|
||||||
public T GetSetting<T>(string key, T defaultValue = default(T))
|
|
||||||
{
|
|
||||||
// 暂时不实现,避免访问权限问题
|
|
||||||
return defaultValue;
|
|
||||||
}
|
|
||||||
|
|
||||||
public List<IPlugin> GetAllPlugins()
|
|
||||||
{
|
|
||||||
return new List<IPlugin>(PluginManager.Instance.Plugins);
|
|
||||||
}
|
|
||||||
|
|
||||||
public IPlugin GetPlugin(string pluginName)
|
|
||||||
{
|
|
||||||
return PluginManager.Instance.Plugins.FirstOrDefault(p => p.Name == pluginName);
|
|
||||||
}
|
|
||||||
|
|
||||||
#endregion
|
|
||||||
|
|
||||||
#region IWindowService 实现
|
|
||||||
|
|
||||||
public void ShowSettingsWindow()
|
|
||||||
{
|
|
||||||
// 暂时不实现,避免访问权限问题
|
|
||||||
}
|
|
||||||
|
|
||||||
public void HideSettingsWindow()
|
|
||||||
{
|
|
||||||
// 暂时不实现,避免访问权限问题
|
|
||||||
}
|
|
||||||
|
|
||||||
public void ShowPluginSettingsWindow()
|
|
||||||
{
|
|
||||||
// 暂时不实现,避免访问权限问题
|
|
||||||
}
|
|
||||||
|
|
||||||
public void HidePluginSettingsWindow()
|
|
||||||
{
|
|
||||||
// 暂时不实现,避免访问权限问题
|
|
||||||
}
|
|
||||||
|
|
||||||
public void ShowHelpWindow()
|
|
||||||
{
|
|
||||||
// 暂时不实现,避免访问权限问题
|
|
||||||
}
|
|
||||||
|
|
||||||
public void HideHelpWindow()
|
|
||||||
{
|
|
||||||
// 暂时不实现,避免访问权限问题
|
|
||||||
}
|
|
||||||
|
|
||||||
public void ShowAboutWindow()
|
|
||||||
{
|
|
||||||
// 暂时不实现,避免访问权限问题
|
|
||||||
}
|
|
||||||
|
|
||||||
public void HideAboutWindow()
|
|
||||||
{
|
|
||||||
// 暂时不实现,避免访问权限问题
|
|
||||||
}
|
|
||||||
|
|
||||||
public void ShowNotification(string message, NotificationType type = NotificationType.Info)
|
|
||||||
{
|
|
||||||
// 暂时不实现,避免访问权限问题
|
|
||||||
}
|
|
||||||
|
|
||||||
public bool ShowConfirmDialog(string message, string title = "确认")
|
|
||||||
{
|
|
||||||
// 暂时不实现,避免访问权限问题
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
public string ShowInputDialog(string message, string title = "输入", string defaultValue = "")
|
|
||||||
{
|
|
||||||
// 暂时不实现,避免访问权限问题
|
|
||||||
return defaultValue;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void SetFullScreen(bool isFullScreen)
|
|
||||||
{
|
|
||||||
// 暂时不实现,避免访问权限问题
|
|
||||||
}
|
|
||||||
|
|
||||||
public void SetTopMost(bool isTopMost)
|
|
||||||
{
|
|
||||||
// 暂时不实现,避免访问权限问题
|
|
||||||
}
|
|
||||||
|
|
||||||
public void SetWindowVisibility(bool isVisible)
|
|
||||||
{
|
|
||||||
// 暂时不实现,避免访问权限问题
|
|
||||||
}
|
|
||||||
|
|
||||||
public void MinimizeWindow()
|
|
||||||
{
|
|
||||||
// 暂时不实现,避免访问权限问题
|
|
||||||
}
|
|
||||||
|
|
||||||
public void MaximizeWindow()
|
|
||||||
{
|
|
||||||
// 暂时不实现,避免访问权限问题
|
|
||||||
}
|
|
||||||
|
|
||||||
public void RestoreWindow()
|
|
||||||
{
|
|
||||||
// 暂时不实现,避免访问权限问题
|
|
||||||
}
|
|
||||||
|
|
||||||
public void CloseWindow()
|
|
||||||
{
|
|
||||||
// 暂时不实现,避免访问权限问题
|
|
||||||
}
|
|
||||||
|
|
||||||
public void SetWindowPosition(double x, double y)
|
|
||||||
{
|
|
||||||
// 暂时不实现,避免访问权限问题
|
|
||||||
}
|
|
||||||
|
|
||||||
public void SetWindowSize(double width, double height)
|
|
||||||
{
|
|
||||||
// 暂时不实现,避免访问权限问题
|
|
||||||
}
|
|
||||||
|
|
||||||
public (double x, double y) GetWindowPosition()
|
|
||||||
{
|
|
||||||
// 暂时不实现,避免访问权限问题
|
|
||||||
return (0, 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
public (double width, double height) GetWindowSize()
|
|
||||||
{
|
|
||||||
// 暂时不实现,避免访问权限问题
|
|
||||||
return (800, 600);
|
|
||||||
}
|
|
||||||
|
|
||||||
#endregion
|
|
||||||
|
|
||||||
#region IActionService 实现
|
|
||||||
|
|
||||||
public void ClearCanvas()
|
|
||||||
{
|
|
||||||
// 暂时不实现,避免访问权限问题
|
|
||||||
}
|
|
||||||
|
|
||||||
public void ClearAllCanvases()
|
|
||||||
{
|
|
||||||
// 暂时不实现,避免访问权限问题
|
|
||||||
}
|
|
||||||
|
|
||||||
public void AddNewPage()
|
|
||||||
{
|
|
||||||
// 暂时不实现,避免访问权限问题
|
|
||||||
}
|
|
||||||
|
|
||||||
public void DeleteCurrentPage()
|
|
||||||
{
|
|
||||||
// 暂时不实现,避免访问权限问题
|
|
||||||
}
|
|
||||||
|
|
||||||
public void SwitchToPage(int pageIndex)
|
|
||||||
{
|
|
||||||
// 暂时不实现,避免访问权限问题
|
|
||||||
}
|
|
||||||
|
|
||||||
public void NextPage()
|
|
||||||
{
|
|
||||||
// 暂时不实现,避免访问权限问题
|
|
||||||
}
|
|
||||||
|
|
||||||
public void PreviousPage()
|
|
||||||
{
|
|
||||||
// 暂时不实现,避免访问权限问题
|
|
||||||
}
|
|
||||||
|
|
||||||
public void SetDrawingMode(int mode)
|
|
||||||
{
|
|
||||||
// 暂时不实现,避免访问权限问题
|
|
||||||
}
|
|
||||||
|
|
||||||
public void SetInkWidth(double width)
|
|
||||||
{
|
|
||||||
// 暂时不实现,避免访问权限问题
|
|
||||||
}
|
|
||||||
|
|
||||||
public void SetInkColor(Color color)
|
|
||||||
{
|
|
||||||
// 暂时不实现,避免访问权限问题
|
|
||||||
}
|
|
||||||
|
|
||||||
public void SetHighlighterWidth(double width)
|
|
||||||
{
|
|
||||||
// 暂时不实现,避免访问权限问题
|
|
||||||
}
|
|
||||||
|
|
||||||
public void SetEraserSize(int size)
|
|
||||||
{
|
|
||||||
// 暂时不实现,避免访问权限问题
|
|
||||||
}
|
|
||||||
|
|
||||||
public void SetEraserType(int type)
|
|
||||||
{
|
|
||||||
// 暂时不实现,避免访问权限问题
|
|
||||||
}
|
|
||||||
|
|
||||||
public void SetEraserShape(int shape)
|
|
||||||
{
|
|
||||||
// 暂时不实现,避免访问权限问题
|
|
||||||
}
|
|
||||||
|
|
||||||
public void SetInkAlpha(double alpha)
|
|
||||||
{
|
|
||||||
// 暂时不实现,避免访问权限问题
|
|
||||||
}
|
|
||||||
|
|
||||||
public void SetInkStyle(int style)
|
|
||||||
{
|
|
||||||
// 暂时不实现,避免访问权限问题
|
|
||||||
}
|
|
||||||
|
|
||||||
public void SetBackgroundColor(string color)
|
|
||||||
{
|
|
||||||
// 暂时不实现,避免访问权限问题
|
|
||||||
}
|
|
||||||
|
|
||||||
public void SaveCanvas(string filePath)
|
|
||||||
{
|
|
||||||
// 暂时不实现,避免访问权限问题
|
|
||||||
}
|
|
||||||
|
|
||||||
public void LoadCanvas(string filePath)
|
|
||||||
{
|
|
||||||
// 暂时不实现,避免访问权限问题
|
|
||||||
}
|
|
||||||
|
|
||||||
public void ExportAsImage(string filePath, string format)
|
|
||||||
{
|
|
||||||
// 暂时不实现,避免访问权限问题
|
|
||||||
}
|
|
||||||
|
|
||||||
public void ExportAsPDF(string filePath)
|
|
||||||
{
|
|
||||||
// 暂时不实现,避免访问权限问题
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Undo()
|
|
||||||
{
|
|
||||||
// 暂时不实现,避免访问权限问题
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Redo()
|
|
||||||
{
|
|
||||||
// 暂时不实现,避免访问权限问题
|
|
||||||
}
|
|
||||||
|
|
||||||
public void SelectAll()
|
|
||||||
{
|
|
||||||
// 暂时不实现,避免访问权限问题
|
|
||||||
}
|
|
||||||
|
|
||||||
public void DeselectAll()
|
|
||||||
{
|
|
||||||
// 暂时不实现,避免访问权限问题
|
|
||||||
}
|
|
||||||
|
|
||||||
public void DeleteSelected()
|
|
||||||
{
|
|
||||||
// 暂时不实现,避免访问权限问题
|
|
||||||
}
|
|
||||||
|
|
||||||
public void CopySelected()
|
|
||||||
{
|
|
||||||
// 暂时不实现,避免访问权限问题
|
|
||||||
}
|
|
||||||
|
|
||||||
public void CutSelected()
|
|
||||||
{
|
|
||||||
// 暂时不实现,避免访问权限问题
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Paste()
|
|
||||||
{
|
|
||||||
// 暂时不实现,避免访问权限问题
|
|
||||||
}
|
|
||||||
|
|
||||||
public void SetSetting<T>(string key, T value)
|
|
||||||
{
|
|
||||||
// 暂时不实现,避免访问权限问题
|
|
||||||
}
|
|
||||||
|
|
||||||
public void SaveSettings()
|
|
||||||
{
|
|
||||||
// 暂时不实现,避免访问权限问题
|
|
||||||
}
|
|
||||||
|
|
||||||
public void LoadSettings()
|
|
||||||
{
|
|
||||||
// 暂时不实现,避免访问权限问题
|
|
||||||
}
|
|
||||||
|
|
||||||
public void ResetSettings()
|
|
||||||
{
|
|
||||||
// 暂时不实现,避免访问权限问题
|
|
||||||
}
|
|
||||||
|
|
||||||
public void EnablePlugin(string pluginName)
|
|
||||||
{
|
|
||||||
var plugin = GetPlugin(pluginName);
|
|
||||||
if (plugin != null)
|
|
||||||
{
|
|
||||||
PluginManager.Instance.TogglePlugin(plugin, true);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void DisablePlugin(string pluginName)
|
|
||||||
{
|
|
||||||
var plugin = GetPlugin(pluginName);
|
|
||||||
if (plugin != null)
|
|
||||||
{
|
|
||||||
PluginManager.Instance.TogglePlugin(plugin, false);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void UnloadPlugin(string pluginName)
|
|
||||||
{
|
|
||||||
var plugin = GetPlugin(pluginName);
|
|
||||||
if (plugin != null)
|
|
||||||
{
|
|
||||||
PluginManager.Instance.UnloadPlugin(plugin);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void RegisterEventHandler(string eventName, EventHandler handler)
|
|
||||||
{
|
|
||||||
if (!_eventHandlers.ContainsKey(eventName))
|
|
||||||
{
|
|
||||||
_eventHandlers[eventName] = handler;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
_eventHandlers[eventName] += handler;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void UnregisterEventHandler(string eventName, EventHandler handler)
|
|
||||||
{
|
|
||||||
if (_eventHandlers.ContainsKey(eventName))
|
|
||||||
{
|
|
||||||
_eventHandlers[eventName] -= handler;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void TriggerEvent(string eventName, object sender, EventArgs args)
|
|
||||||
{
|
|
||||||
if (_eventHandlers.ContainsKey(eventName))
|
|
||||||
{
|
|
||||||
_eventHandlers[eventName]?.Invoke(sender, args);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void RestartApplication()
|
|
||||||
{
|
|
||||||
// 暂时不实现,避免访问权限问题
|
|
||||||
}
|
|
||||||
|
|
||||||
public void ExitApplication()
|
|
||||||
{
|
|
||||||
// 暂时不实现,避免访问权限问题
|
|
||||||
}
|
|
||||||
|
|
||||||
public void CheckForUpdates()
|
|
||||||
{
|
|
||||||
// 暂时不实现,避免访问权限问题
|
|
||||||
}
|
|
||||||
|
|
||||||
public void OpenHelpDocument()
|
|
||||||
{
|
|
||||||
// 暂时不实现,避免访问权限问题
|
|
||||||
}
|
|
||||||
|
|
||||||
public void OpenAboutPage()
|
|
||||||
{
|
|
||||||
// 暂时不实现,避免访问权限问题
|
|
||||||
}
|
|
||||||
|
|
||||||
#endregion
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,276 +0,0 @@
|
|||||||
using System;
|
|
||||||
using System.Windows;
|
|
||||||
using System.Windows.Controls;
|
|
||||||
|
|
||||||
namespace Ink_Canvas.Helpers.Plugins
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// 插件模板,用于开发者参考
|
|
||||||
/// 注意:实际开发时,请将此类移到单独的程序集中
|
|
||||||
/// </summary>
|
|
||||||
public class PluginTemplate : PluginBase
|
|
||||||
{
|
|
||||||
#region 插件基本信息
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 插件名称
|
|
||||||
/// </summary>
|
|
||||||
public override string Name => "插件模板";
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 插件描述
|
|
||||||
/// </summary>
|
|
||||||
public override string Description => "这是一个插件开发模板,用于开发者参考。";
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 插件版本
|
|
||||||
/// </summary>
|
|
||||||
public override Version Version => new Version(1, 0, 0);
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 插件作者
|
|
||||||
/// </summary>
|
|
||||||
public override string Author => "Your Name";
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 是否为内置插件(外部插件请返回false)
|
|
||||||
/// </summary>
|
|
||||||
public override bool IsBuiltIn => false;
|
|
||||||
|
|
||||||
#endregion
|
|
||||||
|
|
||||||
#region 插件生命周期
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 插件初始化
|
|
||||||
/// 在这里进行插件的初始化工作,如加载配置、注册事件等
|
|
||||||
/// </summary>
|
|
||||||
public override void Initialize()
|
|
||||||
{
|
|
||||||
// 先调用基类方法,这样会设置插件ID和记录日志
|
|
||||||
base.Initialize();
|
|
||||||
|
|
||||||
// TODO: 在这里进行插件初始化工作
|
|
||||||
|
|
||||||
// 示例:记录初始化信息
|
|
||||||
LogHelper.WriteLogToFile($"插件 {Name} 开始初始化");
|
|
||||||
|
|
||||||
// 示例:加载配置
|
|
||||||
LoadConfig();
|
|
||||||
|
|
||||||
// 示例:注册自定义事件
|
|
||||||
// MainWindow.Instance.SomeEvent += OnSomeEvent;
|
|
||||||
|
|
||||||
LogHelper.WriteLogToFile($"插件 {Name} 初始化完成");
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 启用插件
|
|
||||||
/// 在这里激活插件功能
|
|
||||||
/// </summary>
|
|
||||||
public override void Enable()
|
|
||||||
{
|
|
||||||
// 先调用基类方法,这样会设置插件状态和记录日志
|
|
||||||
base.Enable();
|
|
||||||
|
|
||||||
// TODO: 在这里启用插件功能
|
|
||||||
|
|
||||||
LogHelper.WriteLogToFile($"插件 {Name} 已启用");
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 禁用插件
|
|
||||||
/// 在这里停用插件功能
|
|
||||||
/// </summary>
|
|
||||||
public override void Disable()
|
|
||||||
{
|
|
||||||
// 先调用基类方法,这样会设置插件状态和记录日志
|
|
||||||
base.Disable();
|
|
||||||
|
|
||||||
// TODO: 在这里禁用插件功能
|
|
||||||
|
|
||||||
LogHelper.WriteLogToFile($"插件 {Name} 已禁用");
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 清理资源
|
|
||||||
/// 在插件卸载时调用,清理资源
|
|
||||||
/// </summary>
|
|
||||||
public override void Cleanup()
|
|
||||||
{
|
|
||||||
// TODO: 在这里清理插件资源
|
|
||||||
|
|
||||||
// 示例:取消注册事件
|
|
||||||
// MainWindow.Instance.SomeEvent -= OnSomeEvent;
|
|
||||||
|
|
||||||
// 示例:保存配置
|
|
||||||
SaveConfig();
|
|
||||||
|
|
||||||
// 最后调用基类方法
|
|
||||||
base.Cleanup();
|
|
||||||
}
|
|
||||||
|
|
||||||
#endregion
|
|
||||||
|
|
||||||
#region 插件配置
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 加载插件配置
|
|
||||||
/// </summary>
|
|
||||||
private void LoadConfig()
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
// TODO: 从文件或其他位置加载配置
|
|
||||||
// 示例:
|
|
||||||
// string configPath = Path.Combine(App.RootPath, "PluginConfigs", "YourPluginName.json");
|
|
||||||
// if (File.Exists(configPath))
|
|
||||||
// {
|
|
||||||
// string json = File.ReadAllText(configPath);
|
|
||||||
// YourConfig = Newtonsoft.Json.JsonConvert.DeserializeObject<YourConfigClass>(json);
|
|
||||||
// }
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
LogHelper.WriteLogToFile($"加载插件配置时出错: {ex.Message}", LogHelper.LogType.Error);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 保存插件配置
|
|
||||||
/// </summary>
|
|
||||||
private void SaveConfig()
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
// TODO: 保存配置到文件或其他位置
|
|
||||||
// 示例:
|
|
||||||
// string configDir = Path.Combine(App.RootPath, "PluginConfigs");
|
|
||||||
// if (!Directory.Exists(configDir))
|
|
||||||
// {
|
|
||||||
// Directory.CreateDirectory(configDir);
|
|
||||||
// }
|
|
||||||
// string configPath = Path.Combine(configDir, "YourPluginName.json");
|
|
||||||
// string json = Newtonsoft.Json.JsonConvert.SerializeObject(YourConfig, Newtonsoft.Json.Formatting.Indented);
|
|
||||||
// File.WriteAllText(configPath, json);
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
LogHelper.WriteLogToFile($"保存插件配置时出错: {ex.Message}", LogHelper.LogType.Error);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#endregion
|
|
||||||
|
|
||||||
#region 插件设置界面
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 获取插件设置界面
|
|
||||||
/// </summary>
|
|
||||||
/// <returns>插件设置界面</returns>
|
|
||||||
public override UserControl GetSettingsView()
|
|
||||||
{
|
|
||||||
// 创建插件设置界面
|
|
||||||
return new PluginTemplateSettingsControl();
|
|
||||||
}
|
|
||||||
|
|
||||||
#endregion
|
|
||||||
|
|
||||||
#region 插件功能方法
|
|
||||||
|
|
||||||
// TODO: 在这里添加插件的具体功能方法
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 示例方法:执行一些功能
|
|
||||||
/// </summary>
|
|
||||||
public void DoSomething()
|
|
||||||
{
|
|
||||||
if (!IsEnabled) return;
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
// TODO: 实现你的功能
|
|
||||||
MessageBox.Show("插件功能执行示例", "插件模板", MessageBoxButton.OK, MessageBoxImage.Information);
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
LogHelper.WriteLogToFile($"执行插件功能时出错: {ex.Message}", LogHelper.LogType.Error);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#endregion
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 插件设置控件
|
|
||||||
/// </summary>
|
|
||||||
public class PluginTemplateSettingsControl : UserControl
|
|
||||||
{
|
|
||||||
public PluginTemplateSettingsControl()
|
|
||||||
{
|
|
||||||
// 创建设置界面布局
|
|
||||||
var panel = new StackPanel
|
|
||||||
{
|
|
||||||
Margin = new Thickness(10)
|
|
||||||
};
|
|
||||||
|
|
||||||
// 添加标题
|
|
||||||
panel.Children.Add(new TextBlock
|
|
||||||
{
|
|
||||||
Text = "插件模板设置",
|
|
||||||
FontSize = 16,
|
|
||||||
FontWeight = FontWeights.Bold,
|
|
||||||
Margin = new Thickness(0, 0, 0, 10)
|
|
||||||
});
|
|
||||||
|
|
||||||
// 添加说明文字
|
|
||||||
panel.Children.Add(new TextBlock
|
|
||||||
{
|
|
||||||
Text = "这是一个示例设置界面,你可以在这里添加自己的设置控件。",
|
|
||||||
TextWrapping = TextWrapping.Wrap,
|
|
||||||
Margin = new Thickness(0, 0, 0, 15)
|
|
||||||
});
|
|
||||||
|
|
||||||
// 添加示例设置选项
|
|
||||||
var checkBox = new CheckBox
|
|
||||||
{
|
|
||||||
Content = "启用某项功能",
|
|
||||||
Margin = new Thickness(0, 0, 0, 10)
|
|
||||||
};
|
|
||||||
panel.Children.Add(checkBox);
|
|
||||||
|
|
||||||
// 添加文本输入框
|
|
||||||
panel.Children.Add(new TextBlock
|
|
||||||
{
|
|
||||||
Text = "设置项:",
|
|
||||||
Margin = new Thickness(0, 5, 0, 5)
|
|
||||||
});
|
|
||||||
|
|
||||||
panel.Children.Add(new TextBox
|
|
||||||
{
|
|
||||||
Margin = new Thickness(0, 0, 0, 10),
|
|
||||||
Width = 200,
|
|
||||||
HorizontalAlignment = HorizontalAlignment.Left
|
|
||||||
});
|
|
||||||
|
|
||||||
// 添加按钮
|
|
||||||
var button = new Button
|
|
||||||
{
|
|
||||||
Content = "保存设置",
|
|
||||||
Padding = new Thickness(10, 5, 10, 5),
|
|
||||||
Margin = new Thickness(0, 10, 0, 0),
|
|
||||||
HorizontalAlignment = HorizontalAlignment.Left
|
|
||||||
};
|
|
||||||
|
|
||||||
button.Click += (sender, e) =>
|
|
||||||
{
|
|
||||||
MessageBox.Show("设置已保存!", "插件模板", MessageBoxButton.OK, MessageBoxImage.Information);
|
|
||||||
};
|
|
||||||
|
|
||||||
panel.Children.Add(button);
|
|
||||||
|
|
||||||
// 设置控件内容
|
|
||||||
Content = panel;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -49,16 +49,13 @@ namespace Ink_Canvas.Helpers
|
|||||||
if (string.IsNullOrWhiteSpace(softwareName))
|
if (string.IsNullOrWhiteSpace(softwareName))
|
||||||
return null;
|
return null;
|
||||||
|
|
||||||
// 64 位进程默认只枚举 64 位注册表视图;32 位希沃常写在 WOW6432Node 下,需一并扫描。
|
// 须用 OpenBaseKey + RegistryView 显式指定视图:Registry.LocalMachine.OpenSubKey 跟随进程位数,
|
||||||
string[] uninstallRoots =
|
// 32 位进程下无法靠拼接 WOW6432Node 路径进入 64 位视图,会找不到 64 位安装的展台。
|
||||||
|
const string uninstallSubKey = @"SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall";
|
||||||
|
foreach (RegistryView view in new[] { RegistryView.Registry64, RegistryView.Registry32 })
|
||||||
{
|
{
|
||||||
@"SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall",
|
using (RegistryKey baseKey = RegistryKey.OpenBaseKey(RegistryHive.LocalMachine, view))
|
||||||
@"SOFTWARE\WOW6432Node\Microsoft\Windows\CurrentVersion\Uninstall",
|
using (RegistryKey key = baseKey.OpenSubKey(uninstallSubKey))
|
||||||
};
|
|
||||||
|
|
||||||
foreach (string root in uninstallRoots)
|
|
||||||
{
|
|
||||||
using (RegistryKey key = Registry.LocalMachine.OpenSubKey(root))
|
|
||||||
{
|
{
|
||||||
if (key == null) continue;
|
if (key == null) continue;
|
||||||
string found = FindInUninstallKey(key, softwareName);
|
string found = FindInUninstallKey(key, softwareName);
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Windows;
|
using System.Windows;
|
||||||
using System.Windows.Ink;
|
using System.Windows.Ink;
|
||||||
@@ -100,8 +100,8 @@ namespace Ink_Canvas.Helpers
|
|||||||
var item = _currentStrokeHistory[_currentIndex];
|
var item = _currentStrokeHistory[_currentIndex];
|
||||||
item.StrokeHasBeenCleared = !item.StrokeHasBeenCleared;
|
item.StrokeHasBeenCleared = !item.StrokeHasBeenCleared;
|
||||||
_currentIndex--;
|
_currentIndex--;
|
||||||
OnUndoStateChanged?.Invoke(_currentIndex > -1);
|
OnUndoStateChanged?.Invoke(CanUndo);
|
||||||
OnRedoStateChanged?.Invoke(_currentStrokeHistory.Count - _currentIndex - 1 > 0);
|
OnRedoStateChanged?.Invoke(CanRedo);
|
||||||
return item;
|
return item;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -137,9 +137,15 @@ namespace Ink_Canvas.Helpers
|
|||||||
}
|
}
|
||||||
private void NotifyUndoRedoState()
|
private void NotifyUndoRedoState()
|
||||||
{
|
{
|
||||||
OnUndoStateChanged?.Invoke(_currentIndex > -1);
|
OnUndoStateChanged?.Invoke(CanUndo);
|
||||||
OnRedoStateChanged?.Invoke(_currentStrokeHistory.Count - _currentIndex - 1 > 0);
|
OnRedoStateChanged?.Invoke(CanRedo);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>当前历史是否允许撤销。</summary>
|
||||||
|
public bool CanUndo => _currentIndex > -1;
|
||||||
|
|
||||||
|
/// <summary>当前历史是否允许重做。</summary>
|
||||||
|
public bool CanRedo => _currentStrokeHistory.Count > 0 && _currentStrokeHistory.Count - _currentIndex - 1 > 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
public class TimeMachineHistory
|
public class TimeMachineHistory
|
||||||
|
|||||||
@@ -3,7 +3,6 @@ using System;
|
|||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Globalization;
|
using System.Globalization;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Runtime.InteropServices.WindowsRuntime;
|
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using System.Windows;
|
using System.Windows;
|
||||||
using System.Windows.Ink;
|
using System.Windows.Ink;
|
||||||
@@ -19,6 +18,9 @@ namespace Ink_Canvas.Helpers
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
internal static class WinRtHandwritingRecognizer
|
internal static class WinRtHandwritingRecognizer
|
||||||
{
|
{
|
||||||
|
private static WinRtInk.InkRecognizer _preferredHandwritingRecognizer;
|
||||||
|
private static bool _preferredHandwritingRecognizerResolved;
|
||||||
|
|
||||||
private static void LogHandwriting(string message, LogHelper.LogType logType = LogHelper.LogType.Info)
|
private static void LogHandwriting(string message, LogHelper.LogType logType = LogHelper.LogType.Info)
|
||||||
{
|
{
|
||||||
LogHelper.WriteLogToFile("[手写体] " + message, logType);
|
LogHelper.WriteLogToFile("[手写体] " + message, logType);
|
||||||
@@ -27,29 +29,13 @@ namespace Ink_Canvas.Helpers
|
|||||||
public static bool IsApiAvailable =>
|
public static bool IsApiAvailable =>
|
||||||
OSVersion.GetOperatingSystem() >= OSVersionExtension.OperatingSystem.Windows10;
|
OSVersion.GetOperatingSystem() >= OSVersionExtension.OperatingSystem.Windows10;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 启动阶段不再预热线程内 WinRT 手写管线。历史上曾用 <see cref="WinRtInkShapeRecognizer.CreateMinimalWarmupStrokeCollection"/> 跑全链路,
|
||||||
|
/// 会显著拖慢启动;与更早的「空 <see cref="StrokeCollection"/>」一样,此处不再在 Idle 上做任何工作。
|
||||||
|
/// 首次真正需要手写识别时由 <see cref="RecognizeHandwritingAsync"/> 承担冷启动成本。
|
||||||
|
/// </summary>
|
||||||
public static void Warmup()
|
public static void Warmup()
|
||||||
{
|
{
|
||||||
if (!IsApiAvailable || !Environment.Is64BitProcess) return;
|
|
||||||
try
|
|
||||||
{
|
|
||||||
var d = Application.Current?.Dispatcher;
|
|
||||||
if (d == null) return;
|
|
||||||
d.BeginInvoke(new Action(async () =>
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
await RecognizeHandwritingAsync(new StrokeCollection()).ConfigureAwait(true);
|
|
||||||
}
|
|
||||||
catch
|
|
||||||
{
|
|
||||||
// ignore
|
|
||||||
}
|
|
||||||
}));
|
|
||||||
}
|
|
||||||
catch
|
|
||||||
{
|
|
||||||
// ignore
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -57,15 +43,21 @@ namespace Ink_Canvas.Helpers
|
|||||||
/// 再对每一分词用 <see cref="WinRtInk.InkRecognizerContainer"/> 取 <c>GetTextCandidates</c>(与当前 SDK 中部分版本的
|
/// 再对每一分词用 <see cref="WinRtInk.InkRecognizerContainer"/> 取 <c>GetTextCandidates</c>(与当前 SDK 中部分版本的
|
||||||
/// <see cref="WinRtInk.InkRecognitionResult"/> 未暴露笔画映射的局限兼容)。
|
/// <see cref="WinRtInk.InkRecognitionResult"/> 未暴露笔画映射的局限兼容)。
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public static async Task<HandwritingRecognitionResult> RecognizeHandwritingAsync(StrokeCollection strokes)
|
/// <param name="verboseTrace">为 false 时跳过详细识别日志(用于 <see cref="Warmup"/> 等)。</param>
|
||||||
|
public static async Task<HandwritingRecognitionResult> RecognizeHandwritingAsync(
|
||||||
|
StrokeCollection strokes,
|
||||||
|
bool verboseTrace = true)
|
||||||
{
|
{
|
||||||
if (!IsApiAvailable || strokes == null || strokes.Count == 0)
|
if (!IsApiAvailable || strokes == null || strokes.Count == 0)
|
||||||
return HandwritingRecognitionResult.Empty;
|
return HandwritingRecognitionResult.Empty;
|
||||||
|
|
||||||
var traceRecognition = strokes.Count > 0;
|
var traceRecognition = verboseTrace;
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
|
var recognizer = new WinRtInk.InkRecognizerContainer();
|
||||||
|
TryApplyPreferredHandwritingRecognizer(recognizer, traceRecognition);
|
||||||
|
|
||||||
var analyzer = new WinAnalysis.InkAnalyzer();
|
var analyzer = new WinAnalysis.InkAnalyzer();
|
||||||
var idToWpf = new Dictionary<uint, Stroke>();
|
var idToWpf = new Dictionary<uint, Stroke>();
|
||||||
|
|
||||||
@@ -92,19 +84,21 @@ namespace Ink_Canvas.Helpers
|
|||||||
LogHandwriting(
|
LogHandwriting(
|
||||||
"识别:AnalyzeAsync 未得到 Updated,Status=" +
|
"识别:AnalyzeAsync 未得到 Updated,Status=" +
|
||||||
(analysisResult == null ? "null" : analysisResult.Status.ToString()) +
|
(analysisResult == null ? "null" : analysisResult.Status.ToString()) +
|
||||||
",有效笔画数=" + idToWpf.Count);
|
",有效笔画数=" + idToWpf.Count +
|
||||||
return HandwritingRecognitionResult.Empty;
|
",尝试整批 RecognizeAsync 回退。");
|
||||||
|
return await RecognizeHandwritingWholeInkAsync(strokes, traceRecognition).ConfigureAwait(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
var wordNodes = analyzer.AnalysisRoot?.FindNodes(WinAnalysis.InkAnalysisNodeKind.InkWord);
|
var wordNodes = analyzer.AnalysisRoot?.FindNodes(WinAnalysis.InkAnalysisNodeKind.InkWord);
|
||||||
if (wordNodes == null || wordNodes.Count == 0)
|
if (wordNodes == null || wordNodes.Count == 0)
|
||||||
{
|
{
|
||||||
if (traceRecognition)
|
if (traceRecognition)
|
||||||
LogHandwriting("识别:未找到 InkWord 节点(可能被判为绘图或非书写),有效笔画数=" + idToWpf.Count);
|
LogHandwriting(
|
||||||
return HandwritingRecognitionResult.Empty;
|
"识别:未找到 InkWord 节点(墨迹分析常将非横平笔划判为绘图),有效笔画数=" + idToWpf.Count +
|
||||||
|
",改用整批 RecognizeAsync 回退。");
|
||||||
|
return await RecognizeHandwritingWholeInkAsync(strokes, traceRecognition).ConfigureAwait(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
var recognizer = new WinRtInk.InkRecognizerContainer();
|
|
||||||
var segments = new List<HandwritingWordSegment>();
|
var segments = new List<HandwritingWordSegment>();
|
||||||
|
|
||||||
foreach (var node in wordNodes)
|
foreach (var node in wordNodes)
|
||||||
@@ -232,6 +226,243 @@ namespace Ink_Canvas.Helpers
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static void TryApplyPreferredHandwritingRecognizer(
|
||||||
|
WinRtInk.InkRecognizerContainer container,
|
||||||
|
bool logDetail)
|
||||||
|
{
|
||||||
|
if (container == null)
|
||||||
|
return;
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (!_preferredHandwritingRecognizerResolved)
|
||||||
|
{
|
||||||
|
_preferredHandwritingRecognizerResolved = true;
|
||||||
|
var all = container.GetRecognizers();
|
||||||
|
_preferredHandwritingRecognizer = SelectBestInkRecognizer(all);
|
||||||
|
if (logDetail)
|
||||||
|
{
|
||||||
|
if (_preferredHandwritingRecognizer != null)
|
||||||
|
LogHandwriting("识别器:已选用 \"" + _preferredHandwritingRecognizer.Name + "\"。");
|
||||||
|
else if (all != null && all.Count > 0)
|
||||||
|
LogHandwriting("识别器:未匹配到与 UI/区域语言对应的引擎,使用系统默认(共 " + all.Count + " 个)。");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_preferredHandwritingRecognizer != null)
|
||||||
|
container.SetDefaultRecognizer(_preferredHandwritingRecognizer);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
LogHelper.WriteLogToFile("[手写体] 设置默认手写识别器失败: " + ex.Message, LogHelper.LogType.Warning);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static WinRtInk.InkRecognizer SelectBestInkRecognizer(
|
||||||
|
IReadOnlyList<WinRtInk.InkRecognizer> list)
|
||||||
|
{
|
||||||
|
if (list == null || list.Count == 0)
|
||||||
|
return null;
|
||||||
|
|
||||||
|
var culture = PrimaryHandwritingCulture();
|
||||||
|
var lang = (culture?.TwoLetterISOLanguageName ?? string.Empty).ToLowerInvariant();
|
||||||
|
var name = culture?.Name ?? string.Empty;
|
||||||
|
|
||||||
|
bool wantZhHans = lang == "zh" &&
|
||||||
|
(name.IndexOf("hans", StringComparison.OrdinalIgnoreCase) >= 0 ||
|
||||||
|
name.Equals("zh-cn", StringComparison.OrdinalIgnoreCase) ||
|
||||||
|
name.Equals("zh-sg", StringComparison.OrdinalIgnoreCase) ||
|
||||||
|
(name.IndexOf("hant", StringComparison.OrdinalIgnoreCase) < 0 &&
|
||||||
|
!name.Equals("zh-tw", StringComparison.OrdinalIgnoreCase) &&
|
||||||
|
!name.Equals("zh-hk", StringComparison.OrdinalIgnoreCase) &&
|
||||||
|
!name.Equals("zh-mo", StringComparison.OrdinalIgnoreCase)));
|
||||||
|
|
||||||
|
bool wantZhHant = lang == "zh" &&
|
||||||
|
(name.IndexOf("hant", StringComparison.OrdinalIgnoreCase) >= 0 ||
|
||||||
|
name.Equals("zh-tw", StringComparison.OrdinalIgnoreCase) ||
|
||||||
|
name.Equals("zh-hk", StringComparison.OrdinalIgnoreCase) ||
|
||||||
|
name.Equals("zh-mo", StringComparison.OrdinalIgnoreCase));
|
||||||
|
|
||||||
|
WinRtInk.InkRecognizer Pick(Func<string, bool> match)
|
||||||
|
{
|
||||||
|
foreach (var r in list)
|
||||||
|
{
|
||||||
|
var n = r?.Name;
|
||||||
|
if (string.IsNullOrEmpty(n))
|
||||||
|
continue;
|
||||||
|
if (match(n))
|
||||||
|
return r;
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (wantZhHans)
|
||||||
|
{
|
||||||
|
var r = Pick(n =>
|
||||||
|
n.IndexOf("简体", StringComparison.OrdinalIgnoreCase) >= 0 ||
|
||||||
|
n.IndexOf("簡體", StringComparison.OrdinalIgnoreCase) >= 0 ||
|
||||||
|
(n.IndexOf("中文", StringComparison.OrdinalIgnoreCase) >= 0 &&
|
||||||
|
(n.IndexOf("简体", StringComparison.OrdinalIgnoreCase) >= 0 ||
|
||||||
|
n.IndexOf("簡體", StringComparison.OrdinalIgnoreCase) >= 0)) ||
|
||||||
|
(n.IndexOf("Chinese", StringComparison.OrdinalIgnoreCase) >= 0 &&
|
||||||
|
(n.IndexOf("Simplified", StringComparison.OrdinalIgnoreCase) >= 0 ||
|
||||||
|
n.IndexOf("Hans", StringComparison.OrdinalIgnoreCase) >= 0 ||
|
||||||
|
n.IndexOf("PRC", StringComparison.OrdinalIgnoreCase) >= 0)));
|
||||||
|
if (r != null)
|
||||||
|
return r;
|
||||||
|
r = Pick(n =>
|
||||||
|
n.IndexOf("中文", StringComparison.OrdinalIgnoreCase) >= 0 ||
|
||||||
|
n.IndexOf("Chinese", StringComparison.OrdinalIgnoreCase) >= 0);
|
||||||
|
if (r != null)
|
||||||
|
return r;
|
||||||
|
}
|
||||||
|
else if (wantZhHant)
|
||||||
|
{
|
||||||
|
var r = Pick(n =>
|
||||||
|
n.IndexOf("繁体", StringComparison.OrdinalIgnoreCase) >= 0 ||
|
||||||
|
n.IndexOf("繁體", StringComparison.OrdinalIgnoreCase) >= 0 ||
|
||||||
|
(n.IndexOf("中文", StringComparison.OrdinalIgnoreCase) >= 0 &&
|
||||||
|
(n.IndexOf("繁体", StringComparison.OrdinalIgnoreCase) >= 0 ||
|
||||||
|
n.IndexOf("繁體", StringComparison.OrdinalIgnoreCase) >= 0)) ||
|
||||||
|
(n.IndexOf("Chinese", StringComparison.OrdinalIgnoreCase) >= 0 &&
|
||||||
|
(n.IndexOf("Traditional", StringComparison.OrdinalIgnoreCase) >= 0 ||
|
||||||
|
n.IndexOf("Hant", StringComparison.OrdinalIgnoreCase) >= 0 ||
|
||||||
|
n.IndexOf("Taiwan", StringComparison.OrdinalIgnoreCase) >= 0 ||
|
||||||
|
n.IndexOf("Hong Kong", StringComparison.OrdinalIgnoreCase) >= 0)));
|
||||||
|
if (r != null)
|
||||||
|
return r;
|
||||||
|
r = Pick(n =>
|
||||||
|
n.IndexOf("中文", StringComparison.OrdinalIgnoreCase) >= 0 ||
|
||||||
|
n.IndexOf("Chinese", StringComparison.OrdinalIgnoreCase) >= 0);
|
||||||
|
if (r != null)
|
||||||
|
return r;
|
||||||
|
}
|
||||||
|
else if (lang == "ja")
|
||||||
|
{
|
||||||
|
var r = Pick(n =>
|
||||||
|
n.IndexOf("Japanese", StringComparison.OrdinalIgnoreCase) >= 0 ||
|
||||||
|
n.IndexOf("日本語", StringComparison.OrdinalIgnoreCase) >= 0 ||
|
||||||
|
n.IndexOf("日语", StringComparison.OrdinalIgnoreCase) >= 0);
|
||||||
|
if (r != null)
|
||||||
|
return r;
|
||||||
|
}
|
||||||
|
else if (lang == "en")
|
||||||
|
{
|
||||||
|
var r = Pick(n => n.IndexOf("English", StringComparison.OrdinalIgnoreCase) >= 0);
|
||||||
|
if (r != null)
|
||||||
|
return r;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (lang == "zh")
|
||||||
|
{
|
||||||
|
var r = Pick(n =>
|
||||||
|
n.IndexOf("中文", StringComparison.OrdinalIgnoreCase) >= 0 ||
|
||||||
|
n.IndexOf("Chinese", StringComparison.OrdinalIgnoreCase) >= 0);
|
||||||
|
if (r != null)
|
||||||
|
return r;
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static CultureInfo PrimaryHandwritingCulture()
|
||||||
|
{
|
||||||
|
var ui = CultureInfo.CurrentUICulture;
|
||||||
|
var ct = CultureInfo.CurrentCulture;
|
||||||
|
if (string.Equals(ui.TwoLetterISOLanguageName, "zh", StringComparison.OrdinalIgnoreCase))
|
||||||
|
return ui;
|
||||||
|
if (string.Equals(ct.TwoLetterISOLanguageName, "zh", StringComparison.OrdinalIgnoreCase))
|
||||||
|
return ct;
|
||||||
|
return ui;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static async Task<HandwritingRecognitionResult> RecognizeHandwritingWholeInkAsync(
|
||||||
|
StrokeCollection strokes,
|
||||||
|
bool traceRecognition)
|
||||||
|
{
|
||||||
|
if (strokes == null || strokes.Count == 0)
|
||||||
|
return HandwritingRecognitionResult.Empty;
|
||||||
|
|
||||||
|
var container = new WinRtInk.InkStrokeContainer();
|
||||||
|
foreach (Stroke s in strokes)
|
||||||
|
{
|
||||||
|
var ink = WinRtInkShapeRecognizer.CreateInkStrokeFromWpf(s);
|
||||||
|
if (ink != null)
|
||||||
|
container.AddStroke(ink);
|
||||||
|
}
|
||||||
|
|
||||||
|
var winStrokes = container.GetStrokes();
|
||||||
|
if (winStrokes == null || winStrokes.Count == 0)
|
||||||
|
{
|
||||||
|
if (traceRecognition)
|
||||||
|
LogHandwriting("整批回退:无有效 WinRT 笔画。");
|
||||||
|
return HandwritingRecognitionResult.Empty;
|
||||||
|
}
|
||||||
|
|
||||||
|
var reco = new WinRtInk.InkRecognizerContainer();
|
||||||
|
TryApplyPreferredHandwritingRecognizer(reco, false);
|
||||||
|
|
||||||
|
IReadOnlyList<WinRtInk.InkRecognitionResult> rr;
|
||||||
|
try
|
||||||
|
{
|
||||||
|
rr = await reco
|
||||||
|
.RecognizeAsync(container, WinRtInk.InkRecognitionTarget.All)
|
||||||
|
.AsTask()
|
||||||
|
.ConfigureAwait(true);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
if (traceRecognition)
|
||||||
|
LogHandwriting("整批回退:RecognizeAsync 异常:" + ex.Message);
|
||||||
|
return HandwritingRecognitionResult.Empty;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (rr == null || rr.Count == 0 || rr[0] == null)
|
||||||
|
{
|
||||||
|
if (traceRecognition)
|
||||||
|
LogHandwriting("整批回退:RecognizeAsync 无结果。");
|
||||||
|
return HandwritingRecognitionResult.Empty;
|
||||||
|
}
|
||||||
|
|
||||||
|
var cands = rr[0].GetTextCandidates();
|
||||||
|
var primary = (cands != null && cands.Count > 0) ? cands[0] : string.Empty;
|
||||||
|
if (string.IsNullOrWhiteSpace(primary))
|
||||||
|
{
|
||||||
|
if (traceRecognition)
|
||||||
|
LogHandwriting("整批回退:候选文本为空。");
|
||||||
|
return HandwritingRecognitionResult.Empty;
|
||||||
|
}
|
||||||
|
|
||||||
|
var merged = new List<string>();
|
||||||
|
if (cands != null)
|
||||||
|
{
|
||||||
|
foreach (var c in cands)
|
||||||
|
{
|
||||||
|
if (!string.IsNullOrEmpty(c) && !merged.Contains(c))
|
||||||
|
merged.Add(c);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var bounds = UnionStrokeBounds(strokes);
|
||||||
|
var group = new List<Stroke>();
|
||||||
|
foreach (Stroke s in strokes)
|
||||||
|
group.Add(s);
|
||||||
|
|
||||||
|
var seg = new HandwritingWordSegment(primary, merged, bounds, group);
|
||||||
|
return new HandwritingRecognitionResult(new List<HandwritingWordSegment> { seg });
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Rect UnionStrokeBounds(StrokeCollection strokes)
|
||||||
|
{
|
||||||
|
if (strokes == null || strokes.Count == 0)
|
||||||
|
return Rect.Empty;
|
||||||
|
|
||||||
|
var r = strokes[0].GetBounds();
|
||||||
|
for (var i = 1; i < strokes.Count; i++)
|
||||||
|
r = Rect.Union(r, strokes[i].GetBounds());
|
||||||
|
return r;
|
||||||
|
}
|
||||||
|
|
||||||
private const string DefaultHandwritingFontFamilyList = "Ink Free,KaiTi,Segoe Script";
|
private const string DefaultHandwritingFontFamilyList = "Ink Free,KaiTi,Segoe Script";
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|||||||
@@ -28,7 +28,8 @@ namespace Ink_Canvas.Helpers
|
|||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
await RecognizeShapeAsync(new StrokeCollection());
|
// 空 StrokeCollection 在 RecognizeShapeAsync 入口会直接返回,无法预热 WinRT InkAnalyzer。
|
||||||
|
await RecognizeShapeAsync(CreateMinimalWarmupStrokeCollection()).ConfigureAwait(true);
|
||||||
}
|
}
|
||||||
catch
|
catch
|
||||||
{
|
{
|
||||||
@@ -99,6 +100,23 @@ namespace Ink_Canvas.Helpers
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 极短合成笔画,供 <see cref="Warmup"/> 等场景走完整 WinRT 转换与分析管线(空集合在入口处会被直接返回)。
|
||||||
|
/// </summary>
|
||||||
|
internal static StrokeCollection CreateMinimalWarmupStrokeCollection()
|
||||||
|
{
|
||||||
|
var da = new DrawingAttributes { Color = Colors.Black, Width = 2, Height = 2 };
|
||||||
|
var pts = new StylusPointCollection
|
||||||
|
{
|
||||||
|
new StylusPoint(8, 8),
|
||||||
|
new StylusPoint(14, 10),
|
||||||
|
new StylusPoint(20, 8),
|
||||||
|
};
|
||||||
|
var col = new StrokeCollection();
|
||||||
|
col.Add(new Stroke(pts, da));
|
||||||
|
return col;
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>供 WinRT 手写等模块复用:将 WPF <see cref="Stroke"/> 转为 WinRT <see cref="global::Windows.UI.Input.Inking.InkStroke"/>。</summary>
|
/// <summary>供 WinRT 手写等模块复用:将 WPF <see cref="Stroke"/> 转为 WinRT <see cref="global::Windows.UI.Input.Inking.InkStroke"/>。</summary>
|
||||||
internal static global::Windows.UI.Input.Inking.InkStroke CreateInkStrokeFromWpf(Stroke stroke)
|
internal static global::Windows.UI.Input.Inking.InkStroke CreateInkStrokeFromWpf(Stroke stroke)
|
||||||
{
|
{
|
||||||
@@ -106,6 +124,9 @@ namespace Ink_Canvas.Helpers
|
|||||||
return null;
|
return null;
|
||||||
|
|
||||||
var da = stroke.DrawingAttributes;
|
var da = stroke.DrawingAttributes;
|
||||||
|
if (da == null)
|
||||||
|
return null;
|
||||||
|
|
||||||
var wda = new global::Windows.UI.Input.Inking.InkDrawingAttributes
|
var wda = new global::Windows.UI.Input.Inking.InkDrawingAttributes
|
||||||
{
|
{
|
||||||
PenTip = global::Windows.UI.Input.Inking.PenTipShape.Circle,
|
PenTip = global::Windows.UI.Input.Inking.PenTipShape.Circle,
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
using Hardcodet.Wpf.TaskbarNotification;
|
using H.NotifyIcon;
|
||||||
using Microsoft.Toolkit.Uwp.Notifications;
|
using Microsoft.Toolkit.Uwp.Notifications;
|
||||||
using System;
|
using System;
|
||||||
using System.Windows;
|
using System.Windows;
|
||||||
@@ -40,10 +40,9 @@ namespace Ink_Canvas.Helpers
|
|||||||
|
|
||||||
taskbar.Visibility = Visibility.Visible;
|
taskbar.Visibility = Visibility.Visible;
|
||||||
|
|
||||||
taskbar.ShowBalloonTip(
|
taskbar.ShowNotification(
|
||||||
"InkCanvasForClass CE",
|
"InkCanvasForClass CE",
|
||||||
$"发现新版本!:{version}",
|
$"发现新版本!:{version}");
|
||||||
BalloonIcon.Info);
|
|
||||||
}
|
}
|
||||||
catch
|
catch
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -114,6 +114,7 @@
|
|||||||
<Reference Include="Microsoft.VisualBasic" />
|
<Reference Include="Microsoft.VisualBasic" />
|
||||||
<Reference Include="netstandard" />
|
<Reference Include="netstandard" />
|
||||||
<Reference Include="System.Windows.Forms" />
|
<Reference Include="System.Windows.Forms" />
|
||||||
|
<Reference Include="System.ComponentModel.Composition" />
|
||||||
<Reference Include="Microsoft.CSharp" />
|
<Reference Include="Microsoft.CSharp" />
|
||||||
<Reference Include="System.Data.DataSetExtensions" />
|
<Reference Include="System.Data.DataSetExtensions" />
|
||||||
<Reference Include="System.Management" />
|
<Reference Include="System.Management" />
|
||||||
@@ -135,12 +136,13 @@
|
|||||||
<PackageReference Include="Costura.Fody" Version="6.0.0">
|
<PackageReference Include="Costura.Fody" Version="6.0.0">
|
||||||
<PrivateAssets>all</PrivateAssets>
|
<PrivateAssets>all</PrivateAssets>
|
||||||
</PackageReference>
|
</PackageReference>
|
||||||
<PackageReference Include="Hardcodet.NotifyIcon.Wpf" Version="2.0.1" />
|
<PackageReference Include="H.NotifyIcon.Wpf" Version="2.0.131" />
|
||||||
<PackageReference Include="iNKORE.UI.WPF.Modern" Version="0.10.2.1" />
|
<PackageReference Include="iNKORE.UI.WPF.Modern" Version="0.10.2.1" />
|
||||||
<PackageReference Include="iNKORE.UI.WPF" Version="1.2.8" />
|
<PackageReference Include="iNKORE.UI.WPF" Version="1.2.8" />
|
||||||
<PackageReference Include="MdXaml" Version="1.27.0" />
|
<PackageReference Include="MdXaml" Version="1.27.0" />
|
||||||
<PackageReference Include="Microsoft.Office.Interop.PowerPoint" Version="15.0.4420.1018" />
|
<PackageReference Include="Microsoft.Office.Interop.PowerPoint" Version="15.0.4420.1018" />
|
||||||
<PackageReference Include="Microsoft.Windows.SDK.Contracts" Version="10.0.19041.2" />
|
<PackageReference Include="Microsoft.Windows.SDK.Contracts" Version="10.0.19041.2" />
|
||||||
|
<PackageReference Include="System.Runtime.WindowsRuntime" Version="4.7.0" />
|
||||||
<PackageReference Include="MicrosoftOfficeCore" Version="15.0.0" />
|
<PackageReference Include="MicrosoftOfficeCore" Version="15.0.0" />
|
||||||
<PackageReference Include="Microsoft.Toolkit.Uwp.Notifications" Version="7.1.3" />
|
<PackageReference Include="Microsoft.Toolkit.Uwp.Notifications" Version="7.1.3" />
|
||||||
<PackageReference Include="Microsoft.International.Converters.PinYinConverter" Version="1.0.0" />
|
<PackageReference Include="Microsoft.International.Converters.PinYinConverter" Version="1.0.0" />
|
||||||
@@ -563,14 +565,7 @@
|
|||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<Compile Remove="AssemblyInfo.cs" />
|
<Compile Remove="AssemblyInfo.cs" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
|
||||||
<EmbeddedResource Remove="Properties\Strings.en-US.resx" />
|
|
||||||
<EmbeddedResource Remove="**\Strings.en-US.resx" />
|
|
||||||
<None Include="Properties\Strings.en-US.resx" />
|
|
||||||
<EmbeddedResource Include="Properties\Strings.enUS.xml">
|
|
||||||
<LogicalName>Ink_Canvas.Properties.Strings.enUS.xml</LogicalName>
|
|
||||||
</EmbeddedResource>
|
|
||||||
</ItemGroup>
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<None Remove="MainWindow.xaml~RF6c3144.TMP" />
|
<None Remove="MainWindow.xaml~RF6c3144.TMP" />
|
||||||
<None Remove="Resources\Cursors\Cursor.cur" />
|
<None Remove="Resources\Cursors\Cursor.cur" />
|
||||||
|
|||||||
+174
-31
@@ -247,21 +247,6 @@
|
|||||||
<!-- 主要导航按钮 -->
|
<!-- 主要导航按钮 -->
|
||||||
<ScrollViewer Grid.Row="1" VerticalScrollBarVisibility="Auto" HorizontalScrollBarVisibility="Disabled">
|
<ScrollViewer Grid.Row="1" VerticalScrollBarVisibility="Auto" HorizontalScrollBarVisibility="Disabled">
|
||||||
<StackPanel>
|
<StackPanel>
|
||||||
<!-- Plugins -->
|
|
||||||
<Button Width="40" Height="40" Margin="0,5,0,0" Style="{StaticResource NavButton}"
|
|
||||||
Click="NavPlugins_Click" Tag="plugins" ToolTip="{x:Static props:Strings.Nav_Plugins}">
|
|
||||||
<Image Width="20" Height="20">
|
|
||||||
<Image.Source>
|
|
||||||
<DrawingImage>
|
|
||||||
<DrawingImage.Drawing>
|
|
||||||
<GeometryDrawing Brush="White"
|
|
||||||
Geometry="M342.826667 213.333333a149.333333 149.333333 0 0 1 295.68 0H682.666667a128 128 0 0 1 128 128v44.16a149.333333 149.333333 0 0 1 0 295.68V725.333333a128 128 0 0 1-128 128h-128v-21.333333a64 64 0 0 0-128 0v21.333333H298.666667a128 128 0 0 1-128-128v-128h21.333333a64 64 0 0 0 0-128H170.666667V341.333333a128 128 0 0 1 128-128h44.16zM426.666667 234.666667V298.666667H298.666667q-17.664 0-30.165334 12.501333T256 341.333333v56.576q22.528 10.752 41.6 29.866667Q341.333333 471.466667 341.333333 533.333333q0 61.866667-43.733333 105.6-19.072 19.072-41.6 29.824V725.333333q0 17.664 12.501333 30.165334T298.666667 768h56.576q10.752-22.528 29.866666-41.6Q428.8 682.666667 490.666667 682.666667q61.866667 0 105.6 43.733333 19.072 19.072 29.824 41.6H682.666667q17.664 0 30.165333-12.501333T725.333333 725.333333v-128h64a64 64 0 0 0 0-128H725.333333V341.333333q0-17.664-12.501333-30.165333T682.666667 298.666667h-128V234.666667a64 64 0 1 0-128 0z"/>
|
|
||||||
</DrawingImage.Drawing>
|
|
||||||
</DrawingImage>
|
|
||||||
</Image.Source>
|
|
||||||
</Image>
|
|
||||||
</Button>
|
|
||||||
|
|
||||||
<!-- Startup -->
|
<!-- Startup -->
|
||||||
<Button Width="40" Height="40" Margin="0,10,0,0" Style="{StaticResource NavButton}"
|
<Button Width="40" Height="40" Margin="0,10,0,0" Style="{StaticResource NavButton}"
|
||||||
Click="NavStartup_Click" Tag="startup" ToolTip="{x:Static props:Strings.Nav_Startup}">
|
Click="NavStartup_Click" Tag="startup" ToolTip="{x:Static props:Strings.Nav_Startup}">
|
||||||
@@ -639,6 +624,67 @@
|
|||||||
</ikw:SimpleStackPanel>
|
</ikw:SimpleStackPanel>
|
||||||
</ikw:SimpleStackPanel>
|
</ikw:SimpleStackPanel>
|
||||||
</Border>
|
</Border>
|
||||||
|
|
||||||
|
<Border x:Name="Net472CompatibilityWarningPanel"
|
||||||
|
Background="#FFFFF4C2"
|
||||||
|
BorderBrush="#FFF0C24B"
|
||||||
|
BorderThickness="1"
|
||||||
|
CornerRadius="10"
|
||||||
|
Padding="14,12"
|
||||||
|
Margin="0,14,0,0"
|
||||||
|
Visibility="Collapsed">
|
||||||
|
<Grid>
|
||||||
|
<Grid.ColumnDefinitions>
|
||||||
|
<ColumnDefinition Width="*" />
|
||||||
|
<ColumnDefinition Width="Auto" />
|
||||||
|
</Grid.ColumnDefinitions>
|
||||||
|
<TextBlock Grid.Column="0"
|
||||||
|
TextWrapping="Wrap"
|
||||||
|
Foreground="#FF2A2A2A"
|
||||||
|
FontSize="14"
|
||||||
|
Text="此版本为最后一个基于.NET Framework 4.7.2的版本,您需要手动确认才能继续接受自动更新。" />
|
||||||
|
<Button Grid.Column="1"
|
||||||
|
Margin="14,0,0,0"
|
||||||
|
Padding="18,6"
|
||||||
|
MinWidth="92"
|
||||||
|
Click="ConfirmNetCompatibilityChangeButton_Click"
|
||||||
|
Background="#FFFFFFFF"
|
||||||
|
BorderBrush="#FFE5E7EB"
|
||||||
|
Foreground="#FF111827"
|
||||||
|
Content="确认">
|
||||||
|
<Button.Template>
|
||||||
|
<ControlTemplate TargetType="{x:Type Button}">
|
||||||
|
<Border x:Name="Bd"
|
||||||
|
Background="{TemplateBinding Background}"
|
||||||
|
BorderBrush="{TemplateBinding BorderBrush}"
|
||||||
|
BorderThickness="{TemplateBinding BorderThickness}"
|
||||||
|
CornerRadius="8"
|
||||||
|
SnapsToDevicePixels="True">
|
||||||
|
<ContentPresenter HorizontalAlignment="Center"
|
||||||
|
VerticalAlignment="Center"
|
||||||
|
RecognizesAccessKey="True"
|
||||||
|
Margin="{TemplateBinding Padding}" />
|
||||||
|
</Border>
|
||||||
|
<ControlTemplate.Triggers>
|
||||||
|
<Trigger Property="IsMouseOver" Value="True">
|
||||||
|
<Setter TargetName="Bd" Property="Background" Value="#FFF9FAFB" />
|
||||||
|
<Setter TargetName="Bd" Property="BorderBrush" Value="#FFD1D5DB" />
|
||||||
|
</Trigger>
|
||||||
|
<Trigger Property="IsPressed" Value="True">
|
||||||
|
<Setter TargetName="Bd" Property="Background" Value="#FFF3F4F6" />
|
||||||
|
<Setter TargetName="Bd" Property="BorderBrush" Value="#FF9CA3AF" />
|
||||||
|
</Trigger>
|
||||||
|
<Trigger Property="IsEnabled" Value="False">
|
||||||
|
<Setter TargetName="Bd" Property="Background" Value="#FFE7E7E7" />
|
||||||
|
<Setter Property="Foreground" Value="#FF7A7A7A" />
|
||||||
|
</Trigger>
|
||||||
|
</ControlTemplate.Triggers>
|
||||||
|
</ControlTemplate>
|
||||||
|
</Button.Template>
|
||||||
|
</Button>
|
||||||
|
</Grid>
|
||||||
|
</Border>
|
||||||
|
|
||||||
<GroupBox Name="GroupBoxModeSettings">
|
<GroupBox Name="GroupBoxModeSettings">
|
||||||
<GroupBox.Header>
|
<GroupBox.Header>
|
||||||
<TextBlock Margin="0,12,0,0" Text="{i18n:I18n Key=Settings_Mode}" FontWeight="Bold" Foreground="#fafafa"
|
<TextBlock Margin="0,12,0,0" Text="{i18n:I18n Key=Settings_Mode}" FontWeight="Bold" Foreground="#fafafa"
|
||||||
@@ -658,28 +704,15 @@
|
|||||||
</ikw:SimpleStackPanel>
|
</ikw:SimpleStackPanel>
|
||||||
</GroupBox>
|
</GroupBox>
|
||||||
|
|
||||||
<GroupBox Name="GroupBoxNewSettings">
|
<GroupBox Name="GroupBoxSettings">
|
||||||
<GroupBox.Header>
|
<GroupBox.Header>
|
||||||
<TextBlock Margin="0,12,0,0" Text="{i18n:I18n Key=Settings_NewWindow}" FontWeight="Bold" Foreground="#fafafa"
|
<TextBlock Margin="0,12,0,0" Text="{i18n:I18n Key=Settings_NewWindow}" FontWeight="Bold" Foreground="#fafafa"
|
||||||
FontSize="26" />
|
FontSize="26" />
|
||||||
</GroupBox.Header>
|
</GroupBox.Header>
|
||||||
<ikw:SimpleStackPanel Spacing="6" Margin="0,10,0,0">
|
<ikw:SimpleStackPanel Spacing="6" Margin="0,10,0,0">
|
||||||
<TextBlock TextWrapping="Wrap" Margin="0,0,0,10" Foreground="#fafafa" Text="{i18n:I18n Key=Settings_NewWindowDesc}"/>
|
<TextBlock TextWrapping="Wrap" Margin="0,0,0,10" Foreground="#fafafa" Text="{i18n:I18n Key=Settings_NewWindowDesc}"/>
|
||||||
<Button x:Name="BtnOpenNewSettings" Content="{i18n:I18n Key=Btn_OpenNewSettings}"
|
<Button x:Name="BtnOpenSettings" Content="{i18n:I18n Key=Btn_OpenNewSettings}"
|
||||||
HorizontalAlignment="Left" Click="BtnOpenNewSettings_Click"
|
HorizontalAlignment="Left" Click="BtnOpenNewNewSettings_Click"
|
||||||
Padding="15,5" Margin="0,10,0,0"/>
|
|
||||||
</ikw:SimpleStackPanel>
|
|
||||||
</GroupBox>
|
|
||||||
|
|
||||||
<GroupBox Name="GroupBoxPlugins">
|
|
||||||
<GroupBox.Header>
|
|
||||||
<TextBlock Margin="0,12,0,0" Text="{i18n:I18n Key=Settings_Plugins}" FontWeight="Bold" Foreground="#fafafa"
|
|
||||||
FontSize="26" />
|
|
||||||
</GroupBox.Header>
|
|
||||||
<ikw:SimpleStackPanel Spacing="6" Margin="0,10,0,0">
|
|
||||||
<TextBlock TextWrapping="Wrap" Margin="0,0,0,10" Foreground="#fafafa" Text="{i18n:I18n Key=Settings_PluginsDesc}"/>
|
|
||||||
<Button x:Name="BtnOpenPluginManager" Content="{i18n:I18n Key=Btn_OpenPluginManager}"
|
|
||||||
HorizontalAlignment="Left" Click="BtnOpenPluginManager_Click"
|
|
||||||
Padding="15,5" Margin="0,10,0,0"/>
|
Padding="15,5" Margin="0,10,0,0"/>
|
||||||
</ikw:SimpleStackPanel>
|
</ikw:SimpleStackPanel>
|
||||||
</GroupBox>
|
</GroupBox>
|
||||||
@@ -831,6 +864,15 @@
|
|||||||
FontSize="26" />
|
FontSize="26" />
|
||||||
</GroupBox.Header>
|
</GroupBox.Header>
|
||||||
<ikw:SimpleStackPanel Spacing="6">
|
<ikw:SimpleStackPanel Spacing="6">
|
||||||
|
<ikw:SimpleStackPanel Orientation="Horizontal" HorizontalAlignment="Left">
|
||||||
|
<TextBlock Foreground="#fafafa" Text="{i18n:I18n Key=Canvas_DisableHardwareAcceleration}" VerticalAlignment="Center"
|
||||||
|
FontSize="14" Margin="0,0,16,0" />
|
||||||
|
<ui:ToggleSwitch OnContent="" OffContent="" Name="ToggleSwitchDisableHardwareAcceleration"
|
||||||
|
IsOn="False" FontFamily="Microsoft YaHei UI" FontWeight="Bold"
|
||||||
|
Toggled="ToggleSwitchDisableHardwareAcceleration_Toggled" />
|
||||||
|
</ikw:SimpleStackPanel>
|
||||||
|
<TextBlock Text="{i18n:I18n Key=Canvas_DisableHardwareAccelerationHint}" TextWrapping="Wrap" Foreground="#a1a1aa" />
|
||||||
|
|
||||||
<ikw:SimpleStackPanel Orientation="Horizontal" HorizontalAlignment="Left">
|
<ikw:SimpleStackPanel Orientation="Horizontal" HorizontalAlignment="Left">
|
||||||
<TextBlock Foreground="#fafafa" Text="{i18n:I18n Key=Canvas_ShowCursor}" VerticalAlignment="Center"
|
<TextBlock Foreground="#fafafa" Text="{i18n:I18n Key=Canvas_ShowCursor}" VerticalAlignment="Center"
|
||||||
FontSize="14" Margin="0,0,16,0" />
|
FontSize="14" Margin="0,0,16,0" />
|
||||||
@@ -4976,6 +5018,107 @@
|
|||||||
</Viewbox>
|
</Viewbox>
|
||||||
</Border>
|
</Border>
|
||||||
|
|
||||||
|
<Border Name="BorderPdfPageSidebar"
|
||||||
|
HorizontalAlignment="Left"
|
||||||
|
VerticalAlignment="Top"
|
||||||
|
Margin="0,0,0,0"
|
||||||
|
Width="64"
|
||||||
|
CornerRadius="8"
|
||||||
|
Background="{DynamicResource FloatBarBackground}"
|
||||||
|
BorderBrush="#33FFFFFF"
|
||||||
|
BorderThickness="1"
|
||||||
|
SnapsToDevicePixels="True"
|
||||||
|
Visibility="Collapsed"
|
||||||
|
Panel.ZIndex="1001"
|
||||||
|
ui:ThemeManager.RequestedTheme="Dark">
|
||||||
|
<Border.Effect>
|
||||||
|
<DropShadowEffect BlurRadius="24"
|
||||||
|
ShadowDepth="3"
|
||||||
|
Direction="270"
|
||||||
|
Opacity="0.45"
|
||||||
|
Color="#000000" />
|
||||||
|
</Border.Effect>
|
||||||
|
<Border.Resources>
|
||||||
|
<Style x:Key="PdfSidebarNavButtonStyle" TargetType="Border">
|
||||||
|
<Setter Property="Width" Value="40" />
|
||||||
|
<Setter Property="Height" Value="36" />
|
||||||
|
<Setter Property="CornerRadius" Value="4" />
|
||||||
|
<Setter Property="BorderThickness" Value="0" />
|
||||||
|
<Setter Property="Cursor" Value="Hand" />
|
||||||
|
<Setter Property="SnapsToDevicePixels" Value="True" />
|
||||||
|
<Setter Property="Background" Value="Transparent" />
|
||||||
|
<Style.Triggers>
|
||||||
|
<Trigger Property="IsMouseOver" Value="True">
|
||||||
|
<Setter Property="Background">
|
||||||
|
<Setter.Value>
|
||||||
|
<SolidColorBrush Color="#FFFFFF" Opacity="0.08" />
|
||||||
|
</Setter.Value>
|
||||||
|
</Setter>
|
||||||
|
</Trigger>
|
||||||
|
</Style.Triggers>
|
||||||
|
</Style>
|
||||||
|
</Border.Resources>
|
||||||
|
<Grid>
|
||||||
|
<ikw:SimpleStackPanel VerticalAlignment="Center"
|
||||||
|
HorizontalAlignment="Center"
|
||||||
|
Spacing="8"
|
||||||
|
Margin="0,10,0,10">
|
||||||
|
<TextBlock Text="PDF"
|
||||||
|
FontSize="10"
|
||||||
|
FontWeight="SemiBold"
|
||||||
|
HorizontalAlignment="Center"
|
||||||
|
Foreground="{DynamicResource FloatBarForeground}"
|
||||||
|
Opacity="0.78" />
|
||||||
|
<Border CornerRadius="4"
|
||||||
|
Padding="8,6"
|
||||||
|
BorderThickness="0"
|
||||||
|
SnapsToDevicePixels="True"
|
||||||
|
Background="#14FFFFFF">
|
||||||
|
<TextBlock Name="TextBlockPdfSidebarPageLabel"
|
||||||
|
Text="— / —"
|
||||||
|
FontSize="12"
|
||||||
|
FontWeight="SemiBold"
|
||||||
|
VerticalAlignment="Center"
|
||||||
|
HorizontalAlignment="Center"
|
||||||
|
TextAlignment="Center"
|
||||||
|
Foreground="{DynamicResource FloatBarForeground}"
|
||||||
|
Opacity="0.55" />
|
||||||
|
</Border>
|
||||||
|
<Border Name="BorderPdfSidebarPagePrev"
|
||||||
|
Style="{StaticResource PdfSidebarNavButtonStyle}"
|
||||||
|
Opacity="0.35"
|
||||||
|
IsHitTestVisible="False"
|
||||||
|
ToolTip="上一页"
|
||||||
|
MouseDown="Border_MouseDown"
|
||||||
|
MouseUp="BorderPdfSidebarPagePrev_MouseUp">
|
||||||
|
<ui:FontIcon Icon="{x:Static ui:SegoeFluentIcons.ChevronLeft}"
|
||||||
|
FontSize="14"
|
||||||
|
HorizontalAlignment="Center"
|
||||||
|
VerticalAlignment="Center"
|
||||||
|
Foreground="{DynamicResource FloatBarForeground}" />
|
||||||
|
</Border>
|
||||||
|
<Border Name="BorderPdfSidebarPageNext"
|
||||||
|
Style="{StaticResource PdfSidebarNavButtonStyle}"
|
||||||
|
Opacity="0.35"
|
||||||
|
IsHitTestVisible="False"
|
||||||
|
ToolTip="下一页"
|
||||||
|
MouseDown="Border_MouseDown"
|
||||||
|
MouseUp="BorderPdfSidebarPageNext_MouseUp">
|
||||||
|
<ui:FontIcon Icon="{x:Static ui:SegoeFluentIcons.ChevronRight}"
|
||||||
|
FontSize="14"
|
||||||
|
HorizontalAlignment="Center"
|
||||||
|
VerticalAlignment="Center"
|
||||||
|
Foreground="{DynamicResource FloatBarForeground}" />
|
||||||
|
</Border>
|
||||||
|
<TextBlock Text="翻页"
|
||||||
|
FontSize="9"
|
||||||
|
HorizontalAlignment="Center"
|
||||||
|
Foreground="{DynamicResource FloatBarForeground}"
|
||||||
|
Opacity="0.55" />
|
||||||
|
</ikw:SimpleStackPanel>
|
||||||
|
</Grid>
|
||||||
|
</Border>
|
||||||
|
|
||||||
<!-- 图片缩放选择点 -->
|
<!-- 图片缩放选择点 -->
|
||||||
<Canvas Name="ImageResizeHandlesCanvas"
|
<Canvas Name="ImageResizeHandlesCanvas"
|
||||||
Visibility="Collapsed"
|
Visibility="Collapsed"
|
||||||
|
|||||||
+188
-93
@@ -1,5 +1,4 @@
|
|||||||
using Ink_Canvas.Helpers;
|
using Ink_Canvas.Helpers;
|
||||||
using Ink_Canvas.Helpers.Plugins;
|
|
||||||
using Ink_Canvas.Windows;
|
using Ink_Canvas.Windows;
|
||||||
using iNKORE.UI.WPF.Modern;
|
using iNKORE.UI.WPF.Modern;
|
||||||
using iNKORE.UI.WPF.Modern.Controls;
|
using iNKORE.UI.WPF.Modern.Controls;
|
||||||
@@ -83,6 +82,8 @@ namespace Ink_Canvas
|
|||||||
private static Cursor _cachedPenCursor = null;
|
private static Cursor _cachedPenCursor = null;
|
||||||
private static readonly object _cursorLock = new object();
|
private static readonly object _cursorLock = new object();
|
||||||
|
|
||||||
|
internal static DateTime? TrayTemporaryShowUntilUtc;
|
||||||
|
|
||||||
#region Window Initialization
|
#region Window Initialization
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -1179,6 +1180,7 @@ namespace Ink_Canvas
|
|||||||
loadPenCanvas();
|
loadPenCanvas();
|
||||||
//加载设置
|
//加载设置
|
||||||
LoadSettings(true);
|
LoadSettings(true);
|
||||||
|
ScheduleNetCompatibilityChangePromptAfterStartup();
|
||||||
ApplyLanguageFromSettings();
|
ApplyLanguageFromSettings();
|
||||||
AutoBackupManager.Initialize(Settings);
|
AutoBackupManager.Initialize(Settings);
|
||||||
CheckUpdateChannelAndTelemetryConsistency();
|
CheckUpdateChannelAndTelemetryConsistency();
|
||||||
@@ -1366,8 +1368,6 @@ namespace Ink_Canvas
|
|||||||
}), DispatcherPriority.Loaded);
|
}), DispatcherPriority.Loaded);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 初始化插件系统
|
|
||||||
InitializePluginSystem();
|
|
||||||
// 确保开关和设置同步
|
// 确保开关和设置同步
|
||||||
ToggleSwitchNoFocusMode.IsOn = Settings.Advanced.IsNoFocusMode;
|
ToggleSwitchNoFocusMode.IsOn = Settings.Advanced.IsNoFocusMode;
|
||||||
ApplyNoFocusMode();
|
ApplyNoFocusMode();
|
||||||
@@ -1410,6 +1410,7 @@ namespace Ink_Canvas
|
|||||||
|
|
||||||
// 检查模式设置并应用
|
// 检查模式设置并应用
|
||||||
CheckMainWindowVisibility();
|
CheckMainWindowVisibility();
|
||||||
|
EnsurePptOnlyVisibilityProbeTimer();
|
||||||
|
|
||||||
// 检查是否通过--board参数启动,如果是则自动切换到白板模式
|
// 检查是否通过--board参数启动,如果是则自动切换到白板模式
|
||||||
if (App.StartWithBoardMode)
|
if (App.StartWithBoardMode)
|
||||||
@@ -1501,6 +1502,149 @@ namespace Ink_Canvas
|
|||||||
AddTouchSupportToSliders();
|
AddTouchSupportToSliders();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void ShowNetCompatibilityChangePromptIfNeeded()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (IsNetCompatibilityChangePromptAcknowledged() || IsNetCompatibilityChangeConfirmed())
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
MessageBox.Show(
|
||||||
|
"下个版本将更换.NET6,请前往设置确认以便继续使用自动更新",
|
||||||
|
"兼容性变更",
|
||||||
|
MessageBoxButton.OK,
|
||||||
|
MessageBoxImage.Warning);
|
||||||
|
PersistNetCompatibilityChangePromptAcknowledgement();
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
LogHelper.WriteLogToFile($"兼容性变更提示弹窗失败: {ex.Message}", LogHelper.LogType.Error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ScheduleNetCompatibilityChangePromptAfterStartup()
|
||||||
|
{
|
||||||
|
Dispatcher.BeginInvoke(new Action(async () =>
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
// 等待主窗口启动流程与初始渲染基本完成后再提示,避免“刚启动就弹窗”。
|
||||||
|
await Task.Delay(1000);
|
||||||
|
ShowNetCompatibilityChangePromptIfNeeded();
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
LogHelper.WriteLogToFile($"延迟显示兼容性变更提示失败: {ex.Message}", LogHelper.LogType.Error);
|
||||||
|
}
|
||||||
|
}), DispatcherPriority.ApplicationIdle);
|
||||||
|
}
|
||||||
|
|
||||||
|
private string GetNetCompatibilityConfirmationFlagPath()
|
||||||
|
{
|
||||||
|
return Path.Combine(App.RootPath, "Configs", "NetCompatibilityConfirmed.flag");
|
||||||
|
}
|
||||||
|
|
||||||
|
private string GetNetCompatibilityPromptAcknowledgedFlagPath()
|
||||||
|
{
|
||||||
|
return Path.Combine(App.RootPath, "Configs", "NetCompatibilityPromptAcknowledged.flag");
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool IsNetCompatibilityChangePromptAcknowledged()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
return File.Exists(GetNetCompatibilityPromptAcknowledgedFlagPath());
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
LogHelper.WriteLogToFile($"读取兼容性提示弹窗标记失败: {ex.Message}", LogHelper.LogType.Error);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void PersistNetCompatibilityChangePromptAcknowledgement()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var flagPath = GetNetCompatibilityPromptAcknowledgedFlagPath();
|
||||||
|
var dir = Path.GetDirectoryName(flagPath);
|
||||||
|
if (!string.IsNullOrWhiteSpace(dir) && !Directory.Exists(dir))
|
||||||
|
{
|
||||||
|
Directory.CreateDirectory(dir);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!File.Exists(flagPath))
|
||||||
|
{
|
||||||
|
File.WriteAllText(flagPath, "acknowledged=true");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
LogHelper.WriteLogToFile($"写入兼容性提示弹窗标记失败: {ex.Message}", LogHelper.LogType.Error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool IsNetCompatibilityChangeConfirmed()
|
||||||
|
{
|
||||||
|
if (Settings?.Startup?.HasConfirmedNetCompatibilityChange == true)
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var flagPath = GetNetCompatibilityConfirmationFlagPath();
|
||||||
|
if (File.Exists(flagPath))
|
||||||
|
{
|
||||||
|
if (Settings?.Startup != null && !Settings.Startup.HasConfirmedNetCompatibilityChange)
|
||||||
|
{
|
||||||
|
Settings.Startup.HasConfirmedNetCompatibilityChange = true;
|
||||||
|
SaveSettingsToFile();
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
LogHelper.WriteLogToFile($"读取兼容性确认标记失败: {ex.Message}", LogHelper.LogType.Error);
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void PersistNetCompatibilityChangeConfirmation()
|
||||||
|
{
|
||||||
|
if (Settings?.Startup == null)
|
||||||
|
{
|
||||||
|
Settings.Startup = new Startup();
|
||||||
|
}
|
||||||
|
|
||||||
|
Settings.Startup.HasConfirmedNetCompatibilityChange = true;
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var flagPath = GetNetCompatibilityConfirmationFlagPath();
|
||||||
|
var dir = Path.GetDirectoryName(flagPath);
|
||||||
|
if (!string.IsNullOrWhiteSpace(dir) && !Directory.Exists(dir))
|
||||||
|
{
|
||||||
|
Directory.CreateDirectory(dir);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!File.Exists(flagPath))
|
||||||
|
{
|
||||||
|
File.WriteAllText(flagPath, "confirmed=true");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
LogHelper.WriteLogToFile($"写入兼容性确认标记失败: {ex.Message}", LogHelper.LogType.Error);
|
||||||
|
}
|
||||||
|
|
||||||
|
SaveSettingsToFile();
|
||||||
|
}
|
||||||
|
|
||||||
private void ApplyLanguageFromSettings()
|
private void ApplyLanguageFromSettings()
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
@@ -1917,6 +2061,12 @@ namespace Ink_Canvas
|
|||||||
|
|
||||||
private async void AutoUpdate()
|
private async void AutoUpdate()
|
||||||
{
|
{
|
||||||
|
if (Settings?.Startup?.HasConfirmedNetCompatibilityChange != true)
|
||||||
|
{
|
||||||
|
LogHelper.WriteLogToFile("AutoUpdate | 自动更新已暂停:未确认兼容性变更(NET472 -> .NET6)", LogHelper.LogType.Warning);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (!string.IsNullOrEmpty(Settings.Startup.AutoUpdatePauseUntilDate))
|
if (!string.IsNullOrEmpty(Settings.Startup.AutoUpdatePauseUntilDate))
|
||||||
{
|
{
|
||||||
if (DateTime.TryParse(Settings.Startup.AutoUpdatePauseUntilDate, out DateTime pauseUntilDate))
|
if (DateTime.TryParse(Settings.Startup.AutoUpdatePauseUntilDate, out DateTime pauseUntilDate))
|
||||||
@@ -2578,9 +2728,6 @@ namespace Ink_Canvas
|
|||||||
case "about":
|
case "about":
|
||||||
targetGroupBox = GroupBoxAbout;
|
targetGroupBox = GroupBoxAbout;
|
||||||
break;
|
break;
|
||||||
case "plugins":
|
|
||||||
targetGroupBox = GroupBoxPlugins;
|
|
||||||
break;
|
|
||||||
default:
|
default:
|
||||||
// 默认滚动到顶部
|
// 默认滚动到顶部
|
||||||
SettingsPanelScrollViewer.ScrollToTop();
|
SettingsPanelScrollViewer.ScrollToTop();
|
||||||
@@ -2728,9 +2875,6 @@ namespace Ink_Canvas
|
|||||||
case "about":
|
case "about":
|
||||||
SetNavButtonTag("about");
|
SetNavButtonTag("about");
|
||||||
break;
|
break;
|
||||||
case "plugins":
|
|
||||||
SetNavButtonTag("plugins");
|
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2817,92 +2961,13 @@ namespace Ink_Canvas
|
|||||||
|
|
||||||
#endregion Navigation Sidebar Methods
|
#endregion Navigation Sidebar Methods
|
||||||
|
|
||||||
#region 插件???
|
|
||||||
|
|
||||||
// 添加插件系统初始化方法
|
|
||||||
private void InitializePluginSystem()
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
// 初始化插件管理器
|
|
||||||
PluginManager.Instance.Initialize();
|
|
||||||
LogHelper.WriteLogToFile("插件系统已初始化");
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
LogHelper.WriteLogToFile($"初始化插件系统时出错: {ex.Message}", LogHelper.LogType.Error);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 添加插件管理导航点击事件处理
|
|
||||||
private void NavPlugins_Click(object sender, RoutedEventArgs e)
|
|
||||||
{
|
|
||||||
ShowSettingsSection("plugins");
|
|
||||||
}
|
|
||||||
|
|
||||||
// 添加打开插件管理器按钮点击事件
|
|
||||||
private void BtnOpenPluginManager_Click(object sender, RoutedEventArgs e)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
// 暂时隐藏设置面板
|
|
||||||
BorderSettings.Visibility = Visibility.Hidden;
|
|
||||||
BorderSettingsMask.Visibility = Visibility.Hidden;
|
|
||||||
|
|
||||||
// 创建并显示插件设置窗口
|
|
||||||
PluginSettingsWindow pluginSettingsWindow = new PluginSettingsWindow();
|
|
||||||
|
|
||||||
// 设置窗口关闭事件,用于在插件管理窗口关闭后恢复设置面板
|
|
||||||
pluginSettingsWindow.Closed += (s, args) =>
|
|
||||||
{
|
|
||||||
// 恢复设置面板显示
|
|
||||||
BorderSettings.Visibility = Visibility.Visible;
|
|
||||||
BorderSettingsMask.Visibility = Visibility.Visible;
|
|
||||||
};
|
|
||||||
|
|
||||||
// 显示插件设置窗口
|
|
||||||
pluginSettingsWindow.ShowDialog();
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
// 确保在发生错误时也恢复设置面板显示
|
|
||||||
BorderSettings.Visibility = Visibility.Visible;
|
|
||||||
BorderSettingsMask.Visibility = Visibility.Visible;
|
|
||||||
|
|
||||||
LogHelper.WriteLogToFile($"打开插件管理器时出错: {ex.Message}", LogHelper.LogType.Error);
|
|
||||||
MessageBox.Show($"打开插件管理器时出错: {ex.Message}", "错误", MessageBoxButton.OK, MessageBoxImage.Error);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
#endregion 插件???
|
|
||||||
|
|
||||||
#region 新设置窗口
|
#region 新设置窗口
|
||||||
|
private async void BtnOpenNewNewSettings_Click(object sender, RoutedEventArgs e)
|
||||||
/// <summary>
|
|
||||||
/// 在隐藏子面板后打开新的设置窗口;若需要则先提示并验证安全密码,并在正在打开或隐藏设置面板时不执行任何操作。
|
|
||||||
/// </summary>
|
|
||||||
/// <remarks>
|
|
||||||
/// 在验证密码失败或发生异常时会中止操作。成功通过验证后以模式窗口方式显示设置窗口并将当前窗口设为其所有者。
|
|
||||||
/// </remarks>
|
|
||||||
private async void BtnOpenNewSettings_Click(object sender, RoutedEventArgs e)
|
|
||||||
{
|
{
|
||||||
if (isOpeningOrHidingSettingsPane) return;
|
if (isOpeningOrHidingSettingsPane) return;
|
||||||
HideSubPanels();
|
HideSubPanels();
|
||||||
{
|
{
|
||||||
try
|
var settingsWindow = new Windows.SettingsViews.SettingsWindow();
|
||||||
{
|
|
||||||
if (SecurityManager.IsPasswordRequiredForEnterSettings(Settings))
|
|
||||||
{
|
|
||||||
bool ok = await SecurityManager.PromptAndVerifyAsync(Settings, this, "进入设置", "请输入安全密码以进入设置。");
|
|
||||||
if (!ok) return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
LogHelper.WriteLogToFile($"安全密码校验失败: {ex}", LogHelper.LogType.Error);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
var settingsWindow = new SettingsWindow();
|
|
||||||
settingsWindow.Owner = this;
|
settingsWindow.Owner = this;
|
||||||
settingsWindow.ShowDialog();
|
settingsWindow.ShowDialog();
|
||||||
}
|
}
|
||||||
@@ -4444,9 +4509,11 @@ namespace Ink_Canvas
|
|||||||
{
|
{
|
||||||
Hide();
|
Hide();
|
||||||
LogHelper.WriteLogToFile("已切换到仅PPT模式,主窗口已隐藏", LogHelper.LogType.Event);
|
LogHelper.WriteLogToFile("已切换到仅PPT模式,主窗口已隐藏", LogHelper.LogType.Event);
|
||||||
|
EnsurePptOnlyVisibilityProbeTimer();
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
StopPptOnlyVisibilityProbeTimer();
|
||||||
// 如果切换到正常模式,显示主窗口
|
// 如果切换到正常模式,显示主窗口
|
||||||
Show();
|
Show();
|
||||||
LogHelper.WriteLogToFile("已切换到正常模式,主窗口已显示", LogHelper.LogType.Event);
|
LogHelper.WriteLogToFile("已切换到正常模式,主窗口已显示", LogHelper.LogType.Event);
|
||||||
@@ -4462,14 +4529,26 @@ namespace Ink_Canvas
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// 检查是否应该显示主窗口(基于PPT模式和PPT放映状态)
|
/// 检查是否应该显示主窗口(基于PPT模式和PPT放映状态)
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private void CheckMainWindowVisibility()
|
internal void CheckMainWindowVisibility()
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
|
if (!IsLoaded)
|
||||||
|
return;
|
||||||
|
|
||||||
if (Settings.ModeSettings.IsPPTOnlyMode)
|
if (Settings.ModeSettings.IsPPTOnlyMode)
|
||||||
{
|
{
|
||||||
// 仅PPT模式下,只有在PPT放映时才显示
|
if (TrayTemporaryShowUntilUtc.HasValue && DateTime.UtcNow < TrayTemporaryShowUntilUtc.Value)
|
||||||
bool isInSlideShow = BtnPPTSlideShowEnd.Visibility == Visibility.Visible;
|
{
|
||||||
|
if (!IsVisible)
|
||||||
|
Show();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 仅PPT模式:以 COM/UI 状态为主,Win32 检测全屏放映窗口(screenClass)作兜底,避免 COM 异常时无法唤出
|
||||||
|
bool comUiSlideShow = BtnPPTSlideShowEnd.Visibility == Visibility.Visible;
|
||||||
|
bool win32SlideShow = IsPowerPointSlideshowSurfacePresentWin32();
|
||||||
|
bool isInSlideShow = comUiSlideShow || win32SlideShow;
|
||||||
if (isInSlideShow && !IsVisible)
|
if (isInSlideShow && !IsVisible)
|
||||||
{
|
{
|
||||||
Show();
|
Show();
|
||||||
@@ -4785,6 +4864,22 @@ namespace Ink_Canvas
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
internal void OpenQuickDrawFromHotkey()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (Settings?.RandSettings?.EnableQuickDraw != true)
|
||||||
|
return;
|
||||||
|
|
||||||
|
var quickDrawWindow = new QuickDrawWindow();
|
||||||
|
quickDrawWindow.ShowDialog();
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
LogHelper.WriteLogToFile($"打开快抽窗口失败: {ex.Message}", LogHelper.LogType.Error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 显示快抽悬浮按钮
|
/// 显示快抽悬浮按钮
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
using Ink_Canvas.Controls;
|
||||||
using Ink_Canvas.Helpers;
|
using Ink_Canvas.Helpers;
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
@@ -81,7 +82,7 @@ namespace Ink_Canvas
|
|||||||
var missingElements = 0;
|
var missingElements = 0;
|
||||||
foreach (UIElement child in inkCanvas.Children)
|
foreach (UIElement child in inkCanvas.Children)
|
||||||
{
|
{
|
||||||
if (child is Image || child is MediaElement)
|
if (child is Image || child is MediaElement || child is PdfEmbeddedView)
|
||||||
{
|
{
|
||||||
if (child is Image img && img.Tag is string tag && tag == VideoPresenterLiveFrameTag)
|
if (child is Image img && img.Tag is string tag && tag == VideoPresenterLiveFrameTag)
|
||||||
{
|
{
|
||||||
@@ -225,6 +226,7 @@ namespace Ink_Canvas
|
|||||||
if (TimeMachineHistories[targetIndex] == null)
|
if (TimeMachineHistories[targetIndex] == null)
|
||||||
{
|
{
|
||||||
timeMachine.ClearStrokeHistory();
|
timeMachine.ClearStrokeHistory();
|
||||||
|
SyncPdfPageSidebarWithCanvas();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -281,7 +283,11 @@ namespace Ink_Canvas
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
private void ProcessElementsAfterRestore(List<UIElement> elements)
|
private void ProcessElementsAfterRestore(List<UIElement> elements)
|
||||||
{
|
{
|
||||||
if (elements == null || elements.Count == 0) return;
|
if (elements == null || elements.Count == 0)
|
||||||
|
{
|
||||||
|
SyncPdfPageSidebarWithCanvas();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// 使用低优先级异步处理,让 UI 先响应,图片位置和事件绑定稍后完成
|
// 使用低优先级异步处理,让 UI 先响应,图片位置和事件绑定稍后完成
|
||||||
Dispatcher.BeginInvoke(DispatcherPriority.Loaded, new Action(() =>
|
Dispatcher.BeginInvoke(DispatcherPriority.Loaded, new Action(() =>
|
||||||
@@ -310,7 +316,19 @@ namespace Ink_Canvas
|
|||||||
}
|
}
|
||||||
BindElementEvents(media);
|
BindElementEvents(media);
|
||||||
}
|
}
|
||||||
|
else if (element is PdfEmbeddedView pdf)
|
||||||
|
{
|
||||||
|
double left = InkCanvas.GetLeft(pdf);
|
||||||
|
double top = InkCanvas.GetTop(pdf);
|
||||||
|
if (double.IsNaN(left) || double.IsNaN(top))
|
||||||
|
{
|
||||||
|
CenterAndScaleElement(pdf);
|
||||||
|
}
|
||||||
|
BindElementEvents(pdf);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
SyncPdfPageSidebarWithCanvas();
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,13 +1,11 @@
|
|||||||
using Ink_Canvas.Helpers;
|
using Ink_Canvas.Helpers;
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Threading.Tasks;
|
|
||||||
using System.Windows;
|
using System.Windows;
|
||||||
using System.Windows.Controls;
|
using System.Windows.Controls;
|
||||||
using System.Windows.Ink;
|
using System.Windows.Ink;
|
||||||
using System.Windows.Input;
|
using System.Windows.Input;
|
||||||
using System.Windows.Media;
|
using System.Windows.Media;
|
||||||
using System.Windows.Media.Animation;
|
|
||||||
using System.Windows.Media.Imaging;
|
using System.Windows.Media.Imaging;
|
||||||
|
|
||||||
namespace Ink_Canvas
|
namespace Ink_Canvas
|
||||||
@@ -52,6 +50,7 @@ namespace Ink_Canvas
|
|||||||
AnimationsHelper.HideWithSlideAndFade(TwoFingerGestureBorder);
|
AnimationsHelper.HideWithSlideAndFade(TwoFingerGestureBorder);
|
||||||
AnimationsHelper.HideWithSlideAndFade(BoardTwoFingerGestureBorder);
|
AnimationsHelper.HideWithSlideAndFade(BoardTwoFingerGestureBorder);
|
||||||
EnableTwoFingerGestureBorder.Visibility = Visibility.Collapsed;
|
EnableTwoFingerGestureBorder.Visibility = Visibility.Collapsed;
|
||||||
|
SyncPdfPageSidebarWithCanvas();
|
||||||
}
|
}
|
||||||
|
|
||||||
BtnHideInkCanvas_Click(BtnHideInkCanvas, null);
|
BtnHideInkCanvas_Click(BtnHideInkCanvas, null);
|
||||||
@@ -553,37 +552,22 @@ namespace Ink_Canvas
|
|||||||
BoardHighlightPenTabButton.Background = new SolidColorBrush(Colors.Transparent);
|
BoardHighlightPenTabButton.Background = new SolidColorBrush(Colors.Transparent);
|
||||||
BoardHighlightPenTabButtonIndicator.Visibility = Visibility.Collapsed;
|
BoardHighlightPenTabButtonIndicator.Visibility = Visibility.Collapsed;
|
||||||
|
|
||||||
// PenPalette.Margin = new Thickness(-160, -200, -33, 32);
|
// 动态计算面板位置,使其对齐笔按钮(考虑快捷调色盘等动态宽度)
|
||||||
await Dispatcher.InvokeAsync(() =>
|
await Dispatcher.InvokeAsync(() =>
|
||||||
{
|
{
|
||||||
var marginAnimation = new ThicknessAnimation
|
PenPalette.BeginAnimation(MarginProperty, null);
|
||||||
{
|
var currentMargin = PenPalette.Margin;
|
||||||
Duration = TimeSpan.FromSeconds(0.1),
|
// 先设置正确的Top/Bottom,保持当前Left/Right
|
||||||
From = PenPalette.Margin,
|
PenPalette.Margin = new Thickness(currentMargin.Left, -200, currentMargin.Right, 32);
|
||||||
To = new Thickness(-160, -200, -33, 32),
|
UpdatePenPalettePosition();
|
||||||
EasingFunction = new CubicEase()
|
|
||||||
};
|
|
||||||
PenPalette.BeginAnimation(MarginProperty, marginAnimation);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
await Dispatcher.InvokeAsync(() =>
|
await Dispatcher.InvokeAsync(() =>
|
||||||
{
|
{
|
||||||
var marginAnimation = new ThicknessAnimation
|
BoardPenPaletteGrid.BeginAnimation(MarginProperty, null);
|
||||||
{
|
var currentMargin = BoardPenPaletteGrid.Margin;
|
||||||
Duration = TimeSpan.FromSeconds(0.1),
|
BoardPenPaletteGrid.Margin = new Thickness(currentMargin.Left, -200, currentMargin.Right, 50);
|
||||||
From = PenPalette.Margin,
|
|
||||||
To = new Thickness(-160, -200, -33, 50),
|
|
||||||
EasingFunction = new CubicEase()
|
|
||||||
};
|
|
||||||
BoardPenPaletteGrid.BeginAnimation(MarginProperty, marginAnimation);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
await Task.Delay(100);
|
|
||||||
|
|
||||||
await Dispatcher.InvokeAsync(() => { PenPalette.Margin = new Thickness(-160, -200, -33, 32); });
|
|
||||||
|
|
||||||
await Dispatcher.InvokeAsync(() => { BoardPenPaletteGrid.Margin = new Thickness(-160, -200, -33, 50); });
|
|
||||||
}
|
}
|
||||||
else if (penType == 1)
|
else if (penType == 1)
|
||||||
{
|
{
|
||||||
@@ -621,36 +605,22 @@ namespace Ink_Canvas
|
|||||||
BoardHighlightPenTabButton.Background = new SolidColorBrush(Color.FromArgb(72, 219, 234, 254));
|
BoardHighlightPenTabButton.Background = new SolidColorBrush(Color.FromArgb(72, 219, 234, 254));
|
||||||
BoardHighlightPenTabButtonIndicator.Visibility = Visibility.Visible;
|
BoardHighlightPenTabButtonIndicator.Visibility = Visibility.Visible;
|
||||||
|
|
||||||
// PenPalette.Margin = new Thickness(-160, -157, -33, 32);
|
// 动态计算面板位置,使其对齐笔按钮(考虑快捷调色盘等动态宽度)
|
||||||
await Dispatcher.InvokeAsync(() =>
|
await Dispatcher.InvokeAsync(() =>
|
||||||
{
|
{
|
||||||
var marginAnimation = new ThicknessAnimation
|
PenPalette.BeginAnimation(MarginProperty, null);
|
||||||
{
|
var currentMargin = PenPalette.Margin;
|
||||||
Duration = TimeSpan.FromSeconds(0.1),
|
// 荧光笔模式面板稍小,使用不同的Top/Bottom
|
||||||
From = PenPalette.Margin,
|
PenPalette.Margin = new Thickness(currentMargin.Left, -157, currentMargin.Right, 32);
|
||||||
To = new Thickness(-160, -157, -33, 32),
|
UpdatePenPalettePosition();
|
||||||
EasingFunction = new CubicEase()
|
|
||||||
};
|
|
||||||
PenPalette.BeginAnimation(MarginProperty, marginAnimation);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
await Dispatcher.InvokeAsync(() =>
|
await Dispatcher.InvokeAsync(() =>
|
||||||
{
|
{
|
||||||
var marginAnimation = new ThicknessAnimation
|
BoardPenPaletteGrid.BeginAnimation(MarginProperty, null);
|
||||||
{
|
var currentMargin = BoardPenPaletteGrid.Margin;
|
||||||
Duration = TimeSpan.FromSeconds(0.1),
|
BoardPenPaletteGrid.Margin = new Thickness(currentMargin.Left, -154, currentMargin.Right, 50);
|
||||||
From = PenPalette.Margin,
|
|
||||||
To = new Thickness(-160, -154, -33, 50),
|
|
||||||
EasingFunction = new CubicEase()
|
|
||||||
};
|
|
||||||
BoardPenPaletteGrid.BeginAnimation(MarginProperty, marginAnimation);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
await Task.Delay(100);
|
|
||||||
|
|
||||||
await Dispatcher.InvokeAsync(() => { PenPalette.Margin = new Thickness(-160, -157, -33, 32); });
|
|
||||||
|
|
||||||
await Dispatcher.InvokeAsync(() => { BoardPenPaletteGrid.Margin = new Thickness(-160, -154, -33, 50); });
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
using Ink_Canvas.Controls;
|
||||||
using Ink_Canvas.Helpers;
|
using Ink_Canvas.Helpers;
|
||||||
using Microsoft.Win32;
|
using Microsoft.Win32;
|
||||||
using System;
|
using System;
|
||||||
@@ -34,6 +35,14 @@ namespace Ink_Canvas
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
private Point dragStartPoint;
|
private Point dragStartPoint;
|
||||||
|
|
||||||
|
/// <summary>页码侧栏当前订阅 <see cref="PdfEmbeddedView.PageNavigationStateChanged"/> 的 PDF 视图。</summary>
|
||||||
|
private PdfEmbeddedView _pdfPageSidebarEventSource;
|
||||||
|
|
||||||
|
private bool _pdfSidebarPositionRefreshPending;
|
||||||
|
|
||||||
|
/// <summary>为 true 时,下一次成功算出 PDF 边界后的侧栏定位使用宿主 Visual 变换(仅用于刚插入/恢复 PDF 的首帧对齐)。</summary>
|
||||||
|
private bool _pdfSidebarNextPositionUseHostTransform;
|
||||||
|
|
||||||
#region Image
|
#region Image
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 处理图片插入按钮点击事件
|
/// 处理图片插入按钮点击事件
|
||||||
@@ -56,48 +65,52 @@ namespace Ink_Canvas
|
|||||||
private async void BtnImageInsert_Click(object sender, RoutedEventArgs e)
|
private async void BtnImageInsert_Click(object sender, RoutedEventArgs e)
|
||||||
{
|
{
|
||||||
OpenFileDialog openFileDialog = new OpenFileDialog();
|
OpenFileDialog openFileDialog = new OpenFileDialog();
|
||||||
openFileDialog.Filter = "Image files (*.jpg; *.jpeg; *.png; *.bmp)|*.jpg;*.jpeg;*.png;*.bmp";
|
openFileDialog.Filter = "图片与 PDF|*.jpg;*.jpeg;*.png;*.bmp;*.gif;*.pdf|图片文件|*.jpg;*.jpeg;*.png;*.bmp;*.gif|PDF|*.pdf";
|
||||||
|
|
||||||
if (openFileDialog.ShowDialog() == true)
|
if (openFileDialog.ShowDialog() == true)
|
||||||
{
|
{
|
||||||
string filePath = openFileDialog.FileName;
|
string filePath = openFileDialog.FileName;
|
||||||
|
|
||||||
Image image = await CreateAndCompressImageAsync(filePath);
|
FrameworkElement element = await CreateAndCompressImageAsync(filePath);
|
||||||
|
|
||||||
if (image != null)
|
if (element != null)
|
||||||
{
|
{
|
||||||
string timestamp = "img_" + DateTime.Now.ToString("yyyyMMdd_HH_mm_ss_fff");
|
string timestamp = "img_" + DateTime.Now.ToString("yyyyMMdd_HH_mm_ss_fff");
|
||||||
image.Name = timestamp;
|
element.Name = timestamp;
|
||||||
|
|
||||||
// 设置图片属性,避免被InkCanvas选择系统处理
|
// 设置图片属性,避免被InkCanvas选择系统处理
|
||||||
image.IsHitTestVisible = true;
|
element.IsHitTestVisible = true;
|
||||||
image.Focusable = false;
|
element.Focusable = false;
|
||||||
|
|
||||||
// 初始化InkCanvas选择设置
|
// 初始化InkCanvas选择设置
|
||||||
InitializeInkCanvasSelectionSettings();
|
InitializeInkCanvasSelectionSettings();
|
||||||
|
|
||||||
// 先添加到画布
|
// 先添加到画布
|
||||||
inkCanvas.Children.Add(image);
|
inkCanvas.Children.Add(element);
|
||||||
|
|
||||||
// 等待图片加载完成后再进行后续处理
|
// 等待图片加载完成后再进行后续处理
|
||||||
image.Loaded += (s, args) =>
|
element.Loaded += (s, args) =>
|
||||||
{
|
{
|
||||||
Dispatcher.BeginInvoke(new Action(() =>
|
Dispatcher.BeginInvoke(new Action(() =>
|
||||||
{
|
{
|
||||||
// 初始化TransformGroup
|
// 初始化TransformGroup
|
||||||
InitializeElementTransform(image);
|
InitializeElementTransform(element);
|
||||||
|
|
||||||
// 居中缩放
|
// 居中缩放
|
||||||
CenterAndScaleElement(image);
|
CenterAndScaleElement(element);
|
||||||
|
|
||||||
// 最后绑定事件处理器
|
// 最后绑定事件处理器
|
||||||
BindElementEvents(image);
|
BindElementEvents(element);
|
||||||
|
|
||||||
LogHelper.WriteLogToFile($"图片插入完成: {image.Name}");
|
if (element is PdfEmbeddedView)
|
||||||
|
_pdfSidebarNextPositionUseHostTransform = true;
|
||||||
|
SyncPdfPageSidebarWithCanvas();
|
||||||
|
|
||||||
|
LogHelper.WriteLogToFile($"图片插入完成: {element.Name}");
|
||||||
}), DispatcherPriority.Loaded);
|
}), DispatcherPriority.Loaded);
|
||||||
};
|
};
|
||||||
|
|
||||||
timeMachine.CommitElementInsertHistory(image);
|
timeMachine.CommitElementInsertHistory(element);
|
||||||
|
|
||||||
// 插入图片后切换到选择模式并刷新浮动栏高光显示
|
// 插入图片后切换到选择模式并刷新浮动栏高光显示
|
||||||
SetCurrentToolMode(InkCanvasEditingMode.Select);
|
SetCurrentToolMode(InkCanvasEditingMode.Select);
|
||||||
@@ -270,13 +283,13 @@ namespace Ink_Canvas
|
|||||||
ApplyMouseDragTransform(element, currentPoint, dragStartPoint);
|
ApplyMouseDragTransform(element, currentPoint, dragStartPoint);
|
||||||
|
|
||||||
// 如果是图片元素,更新工具栏位置
|
// 如果是图片元素,更新工具栏位置
|
||||||
if (element is Image && BorderImageSelectionControl?.Visibility == Visibility.Visible)
|
if (IsBitmapLikeCanvasElement(element) && BorderImageSelectionControl?.Visibility == Visibility.Visible)
|
||||||
{
|
{
|
||||||
UpdateImageSelectionToolbarPosition(element);
|
UpdateImageSelectionToolbarPosition(element);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 如果是图片元素,更新选择点位置
|
// 如果是图片元素,更新选择点位置
|
||||||
if (element is Image && ImageResizeHandlesCanvas?.Visibility == Visibility.Visible)
|
if (IsBitmapLikeCanvasElement(element) && ImageResizeHandlesCanvas?.Visibility == Visibility.Visible)
|
||||||
{
|
{
|
||||||
UpdateImageResizeHandlesPosition(GetElementActualBounds(element));
|
UpdateImageResizeHandlesPosition(GetElementActualBounds(element));
|
||||||
}
|
}
|
||||||
@@ -306,13 +319,13 @@ namespace Ink_Canvas
|
|||||||
ApplyWheelScaleTransform(element, e);
|
ApplyWheelScaleTransform(element, e);
|
||||||
|
|
||||||
// 如果是图片元素,更新工具栏位置
|
// 如果是图片元素,更新工具栏位置
|
||||||
if (element is Image && BorderImageSelectionControl?.Visibility == Visibility.Visible)
|
if (IsBitmapLikeCanvasElement(element) && BorderImageSelectionControl?.Visibility == Visibility.Visible)
|
||||||
{
|
{
|
||||||
UpdateImageSelectionToolbarPosition(element);
|
UpdateImageSelectionToolbarPosition(element);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 如果是图片元素,更新选择点位置
|
// 如果是图片元素,更新选择点位置
|
||||||
if (element is Image && ImageResizeHandlesCanvas?.Visibility == Visibility.Visible)
|
if (IsBitmapLikeCanvasElement(element) && ImageResizeHandlesCanvas?.Visibility == Visibility.Visible)
|
||||||
{
|
{
|
||||||
UpdateImageResizeHandlesPosition(GetElementActualBounds(element));
|
UpdateImageResizeHandlesPosition(GetElementActualBounds(element));
|
||||||
}
|
}
|
||||||
@@ -396,13 +409,13 @@ namespace Ink_Canvas
|
|||||||
ApplyTouchManipulationTransform(element, e);
|
ApplyTouchManipulationTransform(element, e);
|
||||||
|
|
||||||
// 如果是图片元素,更新工具栏位置
|
// 如果是图片元素,更新工具栏位置
|
||||||
if (element is Image && BorderImageSelectionControl?.Visibility == Visibility.Visible)
|
if (IsBitmapLikeCanvasElement(element) && BorderImageSelectionControl?.Visibility == Visibility.Visible)
|
||||||
{
|
{
|
||||||
UpdateImageSelectionToolbarPosition(element);
|
UpdateImageSelectionToolbarPosition(element);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 如果是图片元素,更新选择点位置
|
// 如果是图片元素,更新选择点位置
|
||||||
if (element is Image && ImageResizeHandlesCanvas?.Visibility == Visibility.Visible)
|
if (IsBitmapLikeCanvasElement(element) && ImageResizeHandlesCanvas?.Visibility == Visibility.Visible)
|
||||||
{
|
{
|
||||||
UpdateImageResizeHandlesPosition(GetElementActualBounds(element));
|
UpdateImageResizeHandlesPosition(GetElementActualBounds(element));
|
||||||
}
|
}
|
||||||
@@ -523,12 +536,12 @@ namespace Ink_Canvas
|
|||||||
currentSelectedElement = element;
|
currentSelectedElement = element;
|
||||||
|
|
||||||
// 根据元素类型显示不同的选择工具栏
|
// 根据元素类型显示不同的选择工具栏
|
||||||
if (element is Image)
|
if (IsBitmapLikeCanvasElement(element))
|
||||||
{
|
{
|
||||||
// 显示图片选择工具栏并设置位置
|
// 显示图片选择工具栏并设置位置
|
||||||
if (BorderImageSelectionControl != null)
|
if (BorderImageSelectionControl != null)
|
||||||
{
|
{
|
||||||
// 计算工具栏位置
|
// 计算工具栏位置(内部会同步 PDF 右侧栏位置)
|
||||||
UpdateImageSelectionToolbarPosition(element);
|
UpdateImageSelectionToolbarPosition(element);
|
||||||
BorderImageSelectionControl.Visibility = Visibility.Visible;
|
BorderImageSelectionControl.Visibility = Visibility.Visible;
|
||||||
}
|
}
|
||||||
@@ -568,6 +581,8 @@ namespace Ink_Canvas
|
|||||||
// 保持选择模式,这样用户可以直接点击墨迹来选择
|
// 保持选择模式,这样用户可以直接点击墨迹来选择
|
||||||
inkCanvas.EditingMode = InkCanvasEditingMode.Select;
|
inkCanvas.EditingMode = InkCanvasEditingMode.Select;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
SyncPdfPageSidebarWithCanvas();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -607,6 +622,8 @@ namespace Ink_Canvas
|
|||||||
{
|
{
|
||||||
inkCanvas.EditingMode = InkCanvasEditingMode.Select;
|
inkCanvas.EditingMode = InkCanvasEditingMode.Select;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
SyncPdfPageSidebarWithCanvas();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -914,15 +931,24 @@ namespace Ink_Canvas
|
|||||||
/// - 否则使用原始尺寸
|
/// - 否则使用原始尺寸
|
||||||
/// - 返回创建的Image对象
|
/// - 返回创建的Image对象
|
||||||
/// </remarks>
|
/// </remarks>
|
||||||
private async Task<Image> CreateAndCompressImageAsync(string filePath)
|
/// <summary>与图片选择工具栏、缩放控制点联动的画布位图类元素(普通图片或多页 PDF 嵌入)。</summary>
|
||||||
|
private static bool IsBitmapLikeCanvasElement(FrameworkElement fe)
|
||||||
{
|
{
|
||||||
|
return fe is Image || fe is PdfEmbeddedView;
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task<FrameworkElement> CreateAndCompressImageAsync(string filePath)
|
||||||
|
{
|
||||||
|
string fileExtension = Path.GetExtension(filePath);
|
||||||
|
if (string.Equals(fileExtension, ".pdf", StringComparison.OrdinalIgnoreCase))
|
||||||
|
return await CreateAndCompressImageFromPdfAsync(filePath);
|
||||||
|
|
||||||
string savePath = Path.Combine(Settings.Automation.AutoSavedStrokesLocation, "File Dependency");
|
string savePath = Path.Combine(Settings.Automation.AutoSavedStrokesLocation, "File Dependency");
|
||||||
if (!Directory.Exists(savePath))
|
if (!Directory.Exists(savePath))
|
||||||
{
|
{
|
||||||
Directory.CreateDirectory(savePath);
|
Directory.CreateDirectory(savePath);
|
||||||
}
|
}
|
||||||
|
|
||||||
string fileExtension = Path.GetExtension(filePath);
|
|
||||||
string timestamp = "img_" + DateTime.Now.ToString("yyyyMMdd_HH_mm_ss_fff");
|
string timestamp = "img_" + DateTime.Now.ToString("yyyyMMdd_HH_mm_ss_fff");
|
||||||
string newFilePath = Path.Combine(savePath, timestamp + fileExtension);
|
string newFilePath = Path.Combine(savePath, timestamp + fileExtension);
|
||||||
|
|
||||||
@@ -965,6 +991,84 @@ namespace Ink_Canvas
|
|||||||
return image;
|
return image;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 插入完整 PDF:嵌入控件内可翻页,右下角显示页码(类似希沃白板交互)。
|
||||||
|
/// </summary>
|
||||||
|
private async Task<PdfEmbeddedView> CreateAndCompressImageFromPdfAsync(string filePath)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
string savePath = Path.Combine(Settings.Automation.AutoSavedStrokesLocation, "File Dependency");
|
||||||
|
if (!Directory.Exists(savePath))
|
||||||
|
Directory.CreateDirectory(savePath);
|
||||||
|
|
||||||
|
string timestamp = "img_" + DateTime.Now.ToString("yyyyMMdd_HH_mm_ss_fff");
|
||||||
|
string newFilePath = Path.Combine(savePath, timestamp + ".pdf");
|
||||||
|
await Task.Run(() => File.Copy(filePath, newFilePath, true));
|
||||||
|
|
||||||
|
uint pageCount = await PdfWinRtHelper.GetPageCountAsync(newFilePath);
|
||||||
|
if (pageCount == 0)
|
||||||
|
{
|
||||||
|
ShowNotification("无法打开 PDF(可能已加密、损坏或不支持)。");
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool compress = isLoaded && Settings.Canvas.IsCompressPicturesUploaded;
|
||||||
|
var view = new PdfEmbeddedView();
|
||||||
|
await view.InitializeAsync(newFilePath, pageCount, compress);
|
||||||
|
view.Tag = filePath;
|
||||||
|
return view;
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
LogHelper.WriteLogToFile($"插入 PDF 失败: {ex.Message}", LogHelper.LogType.Error);
|
||||||
|
ShowNotification($"插入 PDF 失败: {ex.Message}");
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>从保存的 <see cref="CanvasElementInfo"/> 恢复 PDF(与打开墨迹时的图片恢复流程一致,不单独写入时间轴)。</summary>
|
||||||
|
private async Task RestorePdfFromElementInfoAsync(CanvasElementInfo info)
|
||||||
|
{
|
||||||
|
if (info == null || inkCanvas == null) return;
|
||||||
|
if (!string.Equals(info.Type, "Pdf", StringComparison.OrdinalIgnoreCase)) return;
|
||||||
|
if (string.IsNullOrEmpty(info.SourcePath) || !File.Exists(info.SourcePath)) return;
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
uint pageCount = await PdfWinRtHelper.GetPageCountAsync(info.SourcePath);
|
||||||
|
if (pageCount == 0) return;
|
||||||
|
|
||||||
|
bool compress = isLoaded && Settings.Canvas.IsCompressPicturesUploaded;
|
||||||
|
uint initial = 0;
|
||||||
|
if (info.PdfCurrentPage.HasValue)
|
||||||
|
initial = (uint)Math.Max(0, Math.Min(info.PdfCurrentPage.Value, (int)pageCount - 1));
|
||||||
|
|
||||||
|
var view = new PdfEmbeddedView
|
||||||
|
{
|
||||||
|
Name = "pdf_" + DateTime.Now.ToString("yyyyMMdd_HH_mm_ss_fff")
|
||||||
|
};
|
||||||
|
await view.InitializeAsync(info.SourcePath, pageCount, compress, initial);
|
||||||
|
|
||||||
|
if (info.Width > 0) view.Width = info.Width;
|
||||||
|
if (info.Height > 0) view.Height = info.Height;
|
||||||
|
|
||||||
|
InkCanvas.SetLeft(view, info.Left);
|
||||||
|
InkCanvas.SetTop(view, info.Top);
|
||||||
|
|
||||||
|
InitializeElementTransform(view);
|
||||||
|
BindElementEvents(view);
|
||||||
|
inkCanvas.Children.Add(view);
|
||||||
|
_pdfSidebarNextPositionUseHostTransform = true;
|
||||||
|
SyncPdfPageSidebarWithCanvas();
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
LogHelper.WriteLogToFile($"从 .elements.json 恢复 PDF 失败: {ex.Message}", LogHelper.LogType.Error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
#region Media
|
#region Media
|
||||||
@@ -1453,6 +1557,10 @@ namespace Ink_Canvas
|
|||||||
|
|
||||||
// 设置工具栏位置
|
// 设置工具栏位置
|
||||||
BorderImageSelectionControl.Margin = new Thickness(toolbarLeft, toolbarTop, 0, 0);
|
BorderImageSelectionControl.Margin = new Thickness(toolbarLeft, toolbarTop, 0, 0);
|
||||||
|
|
||||||
|
var pdfTarget = GetPdfSidebarTargetElement();
|
||||||
|
if (pdfTarget != null && BorderPdfPageSidebar != null && BorderPdfPageSidebar.Visibility == Visibility.Visible)
|
||||||
|
UpdatePdfPageSidebarPosition(pdfTarget);
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
@@ -1460,6 +1568,245 @@ namespace Ink_Canvas
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private const double PdfPageSidebarGap = 10;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 侧栏绑定的 PDF:若当前选中的是 PDF 则用该项;否则用画布上最后一个 PdfEmbeddedView。
|
||||||
|
/// </summary>
|
||||||
|
private PdfEmbeddedView GetPdfSidebarTargetElement()
|
||||||
|
{
|
||||||
|
if (inkCanvas == null) return null;
|
||||||
|
var pdfs = inkCanvas.Children.OfType<PdfEmbeddedView>().ToList();
|
||||||
|
if (pdfs.Count == 0) return null;
|
||||||
|
if (currentSelectedElement is PdfEmbeddedView sel && pdfs.Contains(sel))
|
||||||
|
return sel;
|
||||||
|
return pdfs[pdfs.Count - 1];
|
||||||
|
}
|
||||||
|
|
||||||
|
private void AttachPdfPageSidebarEvents(PdfEmbeddedView pdf)
|
||||||
|
{
|
||||||
|
if (pdf == null || _pdfPageSidebarEventSource == pdf) return;
|
||||||
|
DetachPdfPageSidebarEvents();
|
||||||
|
_pdfPageSidebarEventSource = pdf;
|
||||||
|
_pdfPageSidebarEventSource.PageNavigationStateChanged += SelectedPdf_PageNavigationStateChanged;
|
||||||
|
_pdfPageSidebarEventSource.LayoutUpdated += OnPdfSidebarTargetLayoutUpdated;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void DetachPdfPageSidebarEvents()
|
||||||
|
{
|
||||||
|
if (_pdfPageSidebarEventSource != null)
|
||||||
|
{
|
||||||
|
_pdfPageSidebarEventSource.PageNavigationStateChanged -= SelectedPdf_PageNavigationStateChanged;
|
||||||
|
_pdfPageSidebarEventSource.LayoutUpdated -= OnPdfSidebarTargetLayoutUpdated;
|
||||||
|
_pdfPageSidebarEventSource = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnPdfSidebarTargetLayoutUpdated(object sender, EventArgs e)
|
||||||
|
{
|
||||||
|
if (BorderPdfPageSidebar?.Visibility != Visibility.Visible || inkCanvas == null) return;
|
||||||
|
if (!(sender is PdfEmbeddedView p) || !ReferenceEquals(_pdfPageSidebarEventSource, p)) return;
|
||||||
|
if (!inkCanvas.Children.Contains(p))
|
||||||
|
SyncPdfPageSidebarWithCanvas();
|
||||||
|
else
|
||||||
|
RequestPdfSidebarPositionRefresh();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>在下一帧合并更新侧栏位置(初始布局、翻页改尺寸后 ActualWidth 仍为 0 时尤其需要)。</summary>
|
||||||
|
private void RequestPdfSidebarPositionRefresh()
|
||||||
|
{
|
||||||
|
if (_pdfSidebarPositionRefreshPending) return;
|
||||||
|
_pdfSidebarPositionRefreshPending = true;
|
||||||
|
Dispatcher.BeginInvoke(new Action(() =>
|
||||||
|
{
|
||||||
|
_pdfSidebarPositionRefreshPending = false;
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var t = GetPdfSidebarTargetElement();
|
||||||
|
if (t == null || BorderPdfPageSidebar?.Visibility != Visibility.Visible)
|
||||||
|
{
|
||||||
|
SyncPdfPageSidebarWithCanvas();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!inkCanvas.Children.Contains(t))
|
||||||
|
{
|
||||||
|
SyncPdfPageSidebarWithCanvas();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
t.UpdateLayout();
|
||||||
|
inkCanvas.UpdateLayout();
|
||||||
|
UpdatePdfPageSidebarPosition(t);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
LogHelper.WriteLogToFile($"PDF 侧栏延迟定位失败: {ex.Message}", LogHelper.LogType.Warning);
|
||||||
|
}
|
||||||
|
}), DispatcherPriority.Render);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 画布上存在 PDF 时始终显示右侧页码栏并跟随目标 PDF;无任何 PDF 时隐藏。
|
||||||
|
/// </summary>
|
||||||
|
private void SyncPdfPageSidebarWithCanvas()
|
||||||
|
{
|
||||||
|
if (BorderPdfPageSidebar == null || inkCanvas == null) return;
|
||||||
|
|
||||||
|
// 屏幕模式(已退出白板/黑板)下不显示侧栏,避免画布仍含 PDF 时栏残留在桌面上
|
||||||
|
if (currentMode == 0)
|
||||||
|
{
|
||||||
|
DetachPdfPageSidebarEvents();
|
||||||
|
BorderPdfPageSidebar.Visibility = Visibility.Collapsed;
|
||||||
|
ResetPdfSidebarToIdle();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var pdf = GetPdfSidebarTargetElement();
|
||||||
|
if (pdf == null)
|
||||||
|
{
|
||||||
|
DetachPdfPageSidebarEvents();
|
||||||
|
BorderPdfPageSidebar.Visibility = Visibility.Collapsed;
|
||||||
|
ResetPdfSidebarToIdle();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
AttachPdfPageSidebarEvents(pdf);
|
||||||
|
BorderPdfPageSidebar.Visibility = Visibility.Visible;
|
||||||
|
UpdatePdfSidebarFromPdf(pdf);
|
||||||
|
pdf.UpdateLayout();
|
||||||
|
inkCanvas.UpdateLayout();
|
||||||
|
UpdatePdfPageSidebarPosition(pdf);
|
||||||
|
RequestPdfSidebarPositionRefresh();
|
||||||
|
Dispatcher.BeginInvoke(new Action(() =>
|
||||||
|
{
|
||||||
|
var t = GetPdfSidebarTargetElement();
|
||||||
|
if (t != null && BorderPdfPageSidebar?.Visibility == Visibility.Visible && inkCanvas.Children.Contains(t))
|
||||||
|
UpdatePdfPageSidebarPosition(t);
|
||||||
|
}), DispatcherPriority.ContextIdle);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 将 PDF 专用页码栏贴在当前 PDF 右侧。常态与早期实现一致:画布坐标 + <c>Measure(Width, ∞)</c>;仅在 <see cref="_pdfSidebarNextPositionUseHostTransform"/> 为 true 时用宿主 Visual 变换对齐首帧。
|
||||||
|
/// </summary>
|
||||||
|
private void UpdatePdfPageSidebarPosition(FrameworkElement element)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (BorderPdfPageSidebar == null || inkCanvas == null || !(element is PdfEmbeddedView pdfEl))
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (!inkCanvas.Children.Contains(pdfEl))
|
||||||
|
{
|
||||||
|
SyncPdfPageSidebarWithCanvas();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool wantHostOnce = _pdfSidebarNextPositionUseHostTransform;
|
||||||
|
|
||||||
|
// 插入首帧:先布局再取界,便于 Transform 与墨迹一致;常态与最初 PDF 侧栏实现一致,不在此强制 UpdateLayout。
|
||||||
|
if (wantHostOnce)
|
||||||
|
pdfEl.UpdateLayout();
|
||||||
|
|
||||||
|
Rect b = GetElementActualBounds(pdfEl);
|
||||||
|
if (b.Width <= 0 || b.Height <= 0 || double.IsNaN(b.Width) || double.IsNaN(b.Height))
|
||||||
|
{
|
||||||
|
Dispatcher.BeginInvoke(new Action(() =>
|
||||||
|
{
|
||||||
|
var t = GetPdfSidebarTargetElement();
|
||||||
|
if (t is PdfEmbeddedView pe && inkCanvas.Children.Contains(pe) && BorderPdfPageSidebar?.Visibility == Visibility.Visible)
|
||||||
|
UpdatePdfPageSidebarPosition(pe);
|
||||||
|
}), DispatcherPriority.Loaded);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Visual sidebarHost = VisualTreeHelper.GetParent(BorderPdfPageSidebar) as Visual;
|
||||||
|
double left = 0, top = 0, maxLeft = 0, maxTop = 0;
|
||||||
|
bool hostOk = false;
|
||||||
|
|
||||||
|
if (wantHostOnce && sidebarHost != null)
|
||||||
|
{
|
||||||
|
BorderPdfPageSidebar.Measure(new Size(double.PositiveInfinity, double.PositiveInfinity));
|
||||||
|
double sidebarW = BorderPdfPageSidebar.DesiredSize.Width;
|
||||||
|
double sidebarH = BorderPdfPageSidebar.DesiredSize.Height;
|
||||||
|
if (sidebarW <= 0)
|
||||||
|
sidebarW = BorderPdfPageSidebar.Width;
|
||||||
|
if (sidebarH <= 0)
|
||||||
|
sidebarH = BorderPdfPageSidebar.ActualHeight;
|
||||||
|
if (sidebarH <= 0)
|
||||||
|
sidebarH = 220;
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
GeneralTransform inkToHost = inkCanvas.TransformToVisual(sidebarHost);
|
||||||
|
Point tl = inkToHost.Transform(new Point(b.Left, b.Top));
|
||||||
|
Point br = inkToHost.Transform(new Point(b.Right, b.Bottom));
|
||||||
|
double rightX = Math.Max(tl.X, br.X);
|
||||||
|
double midY = (tl.Y + br.Y) * 0.5;
|
||||||
|
left = rightX + PdfPageSidebarGap;
|
||||||
|
top = midY - sidebarH * 0.5;
|
||||||
|
|
||||||
|
var feHost = sidebarHost as FrameworkElement;
|
||||||
|
double hostW = feHost != null && feHost.ActualWidth > 0 ? feHost.ActualWidth : inkCanvas.ActualWidth;
|
||||||
|
double hostH = feHost != null && feHost.ActualHeight > 0 ? feHost.ActualHeight : inkCanvas.ActualHeight;
|
||||||
|
maxLeft = Math.Max(0, hostW - sidebarW);
|
||||||
|
maxTop = Math.Max(0, hostH - sidebarH);
|
||||||
|
|
||||||
|
if (left > maxLeft)
|
||||||
|
{
|
||||||
|
double leftEdge = Math.Min(tl.X, br.X);
|
||||||
|
double leftAlt = leftEdge - PdfPageSidebarGap - sidebarW;
|
||||||
|
if (leftAlt >= 0)
|
||||||
|
left = leftAlt;
|
||||||
|
}
|
||||||
|
|
||||||
|
hostOk = true;
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
hostOk = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!hostOk)
|
||||||
|
{
|
||||||
|
// 与 ea74592「PDF 侧栏」初版一致:固定宽度测量竖向所需高度,再按墨迹边界与 inkCanvas 尺寸夹紧。
|
||||||
|
BorderPdfPageSidebar.Measure(new Size(BorderPdfPageSidebar.Width, double.PositiveInfinity));
|
||||||
|
double sidebarW = BorderPdfPageSidebar.DesiredSize.Width;
|
||||||
|
double sidebarH = BorderPdfPageSidebar.DesiredSize.Height;
|
||||||
|
if (sidebarW <= 0)
|
||||||
|
sidebarW = BorderPdfPageSidebar.Width;
|
||||||
|
if (sidebarH <= 0)
|
||||||
|
sidebarH = BorderPdfPageSidebar.ActualHeight;
|
||||||
|
if (sidebarH <= 0)
|
||||||
|
sidebarH = 220;
|
||||||
|
|
||||||
|
left = b.Right + PdfPageSidebarGap;
|
||||||
|
top = b.Top + (b.Height * 0.5) - (sidebarH * 0.5);
|
||||||
|
maxLeft = Math.Max(0, inkCanvas.ActualWidth - sidebarW);
|
||||||
|
maxTop = Math.Max(0, inkCanvas.ActualHeight - sidebarH);
|
||||||
|
if (left > maxLeft)
|
||||||
|
{
|
||||||
|
double leftAlt = b.Left - PdfPageSidebarGap - sidebarW;
|
||||||
|
if (leftAlt >= 0)
|
||||||
|
left = leftAlt;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (wantHostOnce)
|
||||||
|
_pdfSidebarNextPositionUseHostTransform = false;
|
||||||
|
|
||||||
|
left = Math.Max(0, Math.Min(left, maxLeft));
|
||||||
|
top = Math.Max(0, Math.Min(top, maxTop));
|
||||||
|
|
||||||
|
BorderPdfPageSidebar.Margin = new Thickness(left, top, 0, 0);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
LogHelper.WriteLogToFile($"更新 PDF 右侧页码栏位置失败: {ex.Message}", LogHelper.LogType.Error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 获取元素的实际边界(考虑变换)
|
/// 获取元素的实际边界(考虑变换)
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -1644,7 +1991,7 @@ namespace Ink_Canvas
|
|||||||
ApplyRotateTransform(currentSelectedElement, -45);
|
ApplyRotateTransform(currentSelectedElement, -45);
|
||||||
|
|
||||||
// 更新工具栏位置
|
// 更新工具栏位置
|
||||||
if (currentSelectedElement is Image && BorderImageSelectionControl?.Visibility == Visibility.Visible)
|
if (IsBitmapLikeCanvasElement(currentSelectedElement) && BorderImageSelectionControl?.Visibility == Visibility.Visible)
|
||||||
{
|
{
|
||||||
UpdateImageSelectionToolbarPosition(currentSelectedElement);
|
UpdateImageSelectionToolbarPosition(currentSelectedElement);
|
||||||
}
|
}
|
||||||
@@ -1678,7 +2025,7 @@ namespace Ink_Canvas
|
|||||||
ApplyRotateTransform(currentSelectedElement, 45);
|
ApplyRotateTransform(currentSelectedElement, 45);
|
||||||
|
|
||||||
// 更新工具栏位置
|
// 更新工具栏位置
|
||||||
if (currentSelectedElement is Image && BorderImageSelectionControl?.Visibility == Visibility.Visible)
|
if (IsBitmapLikeCanvasElement(currentSelectedElement) && BorderImageSelectionControl?.Visibility == Visibility.Visible)
|
||||||
{
|
{
|
||||||
UpdateImageSelectionToolbarPosition(currentSelectedElement);
|
UpdateImageSelectionToolbarPosition(currentSelectedElement);
|
||||||
}
|
}
|
||||||
@@ -1714,7 +2061,7 @@ namespace Ink_Canvas
|
|||||||
ApplyScaleTransform(currentSelectedElement, 0.9, elementCenter);
|
ApplyScaleTransform(currentSelectedElement, 0.9, elementCenter);
|
||||||
|
|
||||||
// 更新工具栏位置
|
// 更新工具栏位置
|
||||||
if (currentSelectedElement is Image && BorderImageSelectionControl?.Visibility == Visibility.Visible)
|
if (IsBitmapLikeCanvasElement(currentSelectedElement) && BorderImageSelectionControl?.Visibility == Visibility.Visible)
|
||||||
{
|
{
|
||||||
UpdateImageSelectionToolbarPosition(currentSelectedElement);
|
UpdateImageSelectionToolbarPosition(currentSelectedElement);
|
||||||
}
|
}
|
||||||
@@ -1750,7 +2097,7 @@ namespace Ink_Canvas
|
|||||||
ApplyScaleTransform(currentSelectedElement, 1.1, elementCenter);
|
ApplyScaleTransform(currentSelectedElement, 1.1, elementCenter);
|
||||||
|
|
||||||
// 更新工具栏位置
|
// 更新工具栏位置
|
||||||
if (currentSelectedElement is Image && BorderImageSelectionControl?.Visibility == Visibility.Visible)
|
if (IsBitmapLikeCanvasElement(currentSelectedElement) && BorderImageSelectionControl?.Visibility == Visibility.Visible)
|
||||||
{
|
{
|
||||||
UpdateImageSelectionToolbarPosition(currentSelectedElement);
|
UpdateImageSelectionToolbarPosition(currentSelectedElement);
|
||||||
}
|
}
|
||||||
@@ -1764,19 +2111,110 @@ namespace Ink_Canvas
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void ResetPdfSidebarToIdle()
|
||||||
|
{
|
||||||
|
if (TextBlockPdfSidebarPageLabel != null)
|
||||||
|
{
|
||||||
|
TextBlockPdfSidebarPageLabel.Text = "— / —";
|
||||||
|
TextBlockPdfSidebarPageLabel.Opacity = 0.55;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (BorderPdfSidebarPagePrev != null)
|
||||||
|
{
|
||||||
|
BorderPdfSidebarPagePrev.Opacity = 0.35;
|
||||||
|
BorderPdfSidebarPagePrev.IsHitTestVisible = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (BorderPdfSidebarPageNext != null)
|
||||||
|
{
|
||||||
|
BorderPdfSidebarPageNext.Opacity = 0.35;
|
||||||
|
BorderPdfSidebarPageNext.IsHitTestVisible = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void UpdatePdfSidebarFromPdf(PdfEmbeddedView pdf)
|
||||||
|
{
|
||||||
|
if (pdf == null) return;
|
||||||
|
|
||||||
|
if (TextBlockPdfSidebarPageLabel != null)
|
||||||
|
{
|
||||||
|
TextBlockPdfSidebarPageLabel.Text = pdf.PageLabelText;
|
||||||
|
TextBlockPdfSidebarPageLabel.Opacity = 1.0;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool prevOk = pdf.CanGoPrevious;
|
||||||
|
bool nextOk = pdf.CanGoNext;
|
||||||
|
if (BorderPdfSidebarPagePrev != null)
|
||||||
|
{
|
||||||
|
BorderPdfSidebarPagePrev.Opacity = prevOk ? 1.0 : 0.35;
|
||||||
|
BorderPdfSidebarPagePrev.IsHitTestVisible = prevOk;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (BorderPdfSidebarPageNext != null)
|
||||||
|
{
|
||||||
|
BorderPdfSidebarPageNext.Opacity = nextOk ? 1.0 : 0.35;
|
||||||
|
BorderPdfSidebarPageNext.IsHitTestVisible = nextOk;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void SelectedPdf_PageNavigationStateChanged(object sender, EventArgs e)
|
||||||
|
{
|
||||||
|
Dispatcher.BeginInvoke(new Action(() =>
|
||||||
|
{
|
||||||
|
if (sender is PdfEmbeddedView pdf)
|
||||||
|
{
|
||||||
|
if (!inkCanvas.Children.Contains(pdf))
|
||||||
|
{
|
||||||
|
SyncPdfPageSidebarWithCanvas();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
UpdatePdfSidebarFromPdf(pdf);
|
||||||
|
UpdatePdfPageSidebarPosition(pdf);
|
||||||
|
RequestPdfSidebarPositionRefresh();
|
||||||
|
}
|
||||||
|
if (currentSelectedElement != null && IsBitmapLikeCanvasElement(currentSelectedElement))
|
||||||
|
{
|
||||||
|
UpdateImageSelectionToolbarPosition(currentSelectedElement);
|
||||||
|
if (ImageResizeHandlesCanvas?.Visibility == Visibility.Visible)
|
||||||
|
UpdateImageResizeHandlesPosition(GetElementActualBounds(currentSelectedElement));
|
||||||
|
}
|
||||||
|
}), DispatcherPriority.Loaded);
|
||||||
|
}
|
||||||
|
|
||||||
|
private async void BorderPdfSidebarPagePrev_MouseUp(object sender, MouseButtonEventArgs e)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var pdf = GetPdfSidebarTargetElement();
|
||||||
|
if (pdf != null && pdf.CanGoPrevious)
|
||||||
|
await pdf.GoToPreviousPageAsync();
|
||||||
|
SyncPdfPageSidebarWithCanvas();
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
LogHelper.WriteLogToFile($"PDF 上一页失败: {ex.Message}", LogHelper.LogType.Error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async void BorderPdfSidebarPageNext_MouseUp(object sender, MouseButtonEventArgs e)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var pdf = GetPdfSidebarTargetElement();
|
||||||
|
if (pdf != null && pdf.CanGoNext)
|
||||||
|
await pdf.GoToNextPageAsync();
|
||||||
|
SyncPdfPageSidebarWithCanvas();
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
LogHelper.WriteLogToFile($"PDF 下一页失败: {ex.Message}", LogHelper.LogType.Error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 处理图片删除功能
|
/// 处理图片删除功能
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="sender">事件发送者</param>
|
|
||||||
/// <param name="e">事件参数</param>
|
|
||||||
/// <remarks>
|
|
||||||
/// - 检查当前是否有选中元素
|
|
||||||
/// - 保存删除前的编辑模式
|
|
||||||
/// - 记录删除历史
|
|
||||||
/// - 从画布中移除
|
|
||||||
/// - 清除选中状态
|
|
||||||
/// - 包含异常处理
|
|
||||||
/// </remarks>
|
|
||||||
private void BorderImageDelete_MouseUp(object sender, MouseButtonEventArgs e)
|
private void BorderImageDelete_MouseUp(object sender, MouseButtonEventArgs e)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
@@ -1789,16 +2227,19 @@ namespace Ink_Canvas
|
|||||||
// 记录删除历史
|
// 记录删除历史
|
||||||
timeMachine.CommitElementRemoveHistory(currentSelectedElement);
|
timeMachine.CommitElementRemoveHistory(currentSelectedElement);
|
||||||
|
|
||||||
|
var toRemove = currentSelectedElement;
|
||||||
// 从画布中移除
|
// 从画布中移除
|
||||||
inkCanvas.Children.Remove(currentSelectedElement);
|
inkCanvas.Children.Remove(toRemove);
|
||||||
|
|
||||||
// 清除选中状态
|
// 清除选中状态
|
||||||
UnselectElement(currentSelectedElement);
|
UnselectElement(toRemove);
|
||||||
currentSelectedElement = null;
|
currentSelectedElement = null;
|
||||||
|
|
||||||
// 恢复到删除前的编辑模式
|
// 恢复到删除前的编辑模式
|
||||||
inkCanvas.EditingMode = previousEditingMode;
|
inkCanvas.EditingMode = previousEditingMode;
|
||||||
|
|
||||||
|
SyncPdfPageSidebarWithCanvas();
|
||||||
|
|
||||||
LogHelper.WriteLogToFile($"图片删除完成,已恢复到编辑模式: {previousEditingMode}");
|
LogHelper.WriteLogToFile($"图片删除完成,已恢复到编辑模式: {previousEditingMode}");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -2029,7 +2470,7 @@ namespace Ink_Canvas
|
|||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
if (currentSelectedElement is Image image && sender is Ellipse ellipse)
|
if (IsBitmapLikeCanvasElement(currentSelectedElement) && sender is Ellipse ellipse)
|
||||||
{
|
{
|
||||||
isResizingImage = true;
|
isResizingImage = true;
|
||||||
imageResizeStartPoint = e.GetPosition(inkCanvas);
|
imageResizeStartPoint = e.GetPosition(inkCanvas);
|
||||||
@@ -2072,10 +2513,10 @@ namespace Ink_Canvas
|
|||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
if (isResizingImage && currentSelectedElement is Image image && sender is Ellipse ellipse)
|
if (isResizingImage && IsBitmapLikeCanvasElement(currentSelectedElement) && sender is Ellipse ellipse)
|
||||||
{
|
{
|
||||||
var currentPoint = e.GetPosition(inkCanvas);
|
var currentPoint = e.GetPosition(inkCanvas);
|
||||||
ResizeImageByHandle(image, imageResizeStartPoint, currentPoint, activeResizeHandle);
|
ResizeImageByHandle(currentSelectedElement, imageResizeStartPoint, currentPoint, activeResizeHandle);
|
||||||
imageResizeStartPoint = currentPoint;
|
imageResizeStartPoint = currentPoint;
|
||||||
e.Handled = true;
|
e.Handled = true;
|
||||||
}
|
}
|
||||||
@@ -2087,11 +2528,11 @@ namespace Ink_Canvas
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 根据控制点缩放图片
|
// 根据控制点缩放图片
|
||||||
private void ResizeImageByHandle(Image image, Point startPoint, Point currentPoint, string handleName)
|
private void ResizeImageByHandle(FrameworkElement element, Point startPoint, Point currentPoint, string handleName)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
if (image.RenderTransform is TransformGroup transformGroup)
|
if (element.RenderTransform is TransformGroup transformGroup)
|
||||||
{
|
{
|
||||||
var scaleTransform = transformGroup.Children.OfType<ScaleTransform>().FirstOrDefault();
|
var scaleTransform = transformGroup.Children.OfType<ScaleTransform>().FirstOrDefault();
|
||||||
var translateTransform = transformGroup.Children.OfType<TranslateTransform>().FirstOrDefault();
|
var translateTransform = transformGroup.Children.OfType<TranslateTransform>().FirstOrDefault();
|
||||||
@@ -2099,7 +2540,7 @@ namespace Ink_Canvas
|
|||||||
if (scaleTransform == null || translateTransform == null) return;
|
if (scaleTransform == null || translateTransform == null) return;
|
||||||
|
|
||||||
// 获取图片的当前边界
|
// 获取图片的当前边界
|
||||||
Rect currentBounds = GetElementActualBounds(image);
|
Rect currentBounds = GetElementActualBounds(element);
|
||||||
double deltaX = currentPoint.X - startPoint.X;
|
double deltaX = currentPoint.X - startPoint.X;
|
||||||
double deltaY = currentPoint.Y - startPoint.Y;
|
double deltaY = currentPoint.Y - startPoint.Y;
|
||||||
|
|
||||||
@@ -2160,7 +2601,10 @@ namespace Ink_Canvas
|
|||||||
translateTransform.Y += translateY;
|
translateTransform.Y += translateY;
|
||||||
|
|
||||||
// 更新选择点位置
|
// 更新选择点位置
|
||||||
UpdateImageResizeHandlesPosition(GetElementActualBounds(image));
|
UpdateImageResizeHandlesPosition(GetElementActualBounds(element));
|
||||||
|
|
||||||
|
if (BorderImageSelectionControl?.Visibility == Visibility.Visible)
|
||||||
|
UpdateImageSelectionToolbarPosition(element);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
using Ink_Canvas.Controls;
|
||||||
using Ink_Canvas.Helpers;
|
using Ink_Canvas.Helpers;
|
||||||
using iNKORE.UI.WPF.Modern;
|
using iNKORE.UI.WPF.Modern;
|
||||||
using System;
|
using System;
|
||||||
@@ -18,7 +19,6 @@ using Application = System.Windows.Application;
|
|||||||
using Button = System.Windows.Controls.Button;
|
using Button = System.Windows.Controls.Button;
|
||||||
using Cursors = System.Windows.Input.Cursors;
|
using Cursors = System.Windows.Input.Cursors;
|
||||||
using HorizontalAlignment = System.Windows.HorizontalAlignment;
|
using HorizontalAlignment = System.Windows.HorizontalAlignment;
|
||||||
using Image = System.Windows.Controls.Image;
|
|
||||||
using MessageBox = iNKORE.UI.WPF.Modern.Controls.MessageBox;
|
using MessageBox = iNKORE.UI.WPF.Modern.Controls.MessageBox;
|
||||||
using MouseEventArgs = System.Windows.Input.MouseEventArgs;
|
using MouseEventArgs = System.Windows.Input.MouseEventArgs;
|
||||||
using OpenFileDialog = Microsoft.Win32.OpenFileDialog;
|
using OpenFileDialog = Microsoft.Win32.OpenFileDialog;
|
||||||
@@ -49,6 +49,7 @@ namespace Ink_Canvas
|
|||||||
else
|
else
|
||||||
{
|
{
|
||||||
HideSubPanels();
|
HideSubPanels();
|
||||||
|
UpdateTwoFingerGestureBorderPosition();
|
||||||
AnimationsHelper.ShowWithSlideFromBottomAndFade(TwoFingerGestureBorder);
|
AnimationsHelper.ShowWithSlideFromBottomAndFade(TwoFingerGestureBorder);
|
||||||
AnimationsHelper.ShowWithSlideFromBottomAndFade(BoardTwoFingerGestureBorder);
|
AnimationsHelper.ShowWithSlideFromBottomAndFade(BoardTwoFingerGestureBorder);
|
||||||
}
|
}
|
||||||
@@ -370,7 +371,7 @@ namespace Ink_Canvas
|
|||||||
/// <param name="autoAlignCenter">
|
/// <param name="autoAlignCenter">
|
||||||
/// 是否自動居中浮動工具欄
|
/// 是否自動居中浮動工具欄
|
||||||
/// </param>
|
/// </param>
|
||||||
private async void HideSubPanels(string mode = null, bool autoAlignCenter = false)
|
internal async void HideSubPanels(string mode = null, bool autoAlignCenter = false)
|
||||||
{
|
{
|
||||||
AnimationsHelper.HideWithSlideAndFade(BorderTools);
|
AnimationsHelper.HideWithSlideAndFade(BorderTools);
|
||||||
AnimationsHelper.HideWithSlideAndFade(BoardBorderTools);
|
AnimationsHelper.HideWithSlideAndFade(BoardBorderTools);
|
||||||
@@ -1742,6 +1743,7 @@ namespace Ink_Canvas
|
|||||||
else
|
else
|
||||||
{
|
{
|
||||||
HideSubPanels();
|
HideSubPanels();
|
||||||
|
UpdateBorderToolsPosition();
|
||||||
AnimationsHelper.ShowWithSlideFromBottomAndFade(BorderTools);
|
AnimationsHelper.ShowWithSlideFromBottomAndFade(BorderTools);
|
||||||
AnimationsHelper.ShowWithSlideFromBottomAndFade(BoardBorderTools);
|
AnimationsHelper.ShowWithSlideFromBottomAndFade(BoardBorderTools);
|
||||||
}
|
}
|
||||||
@@ -2439,6 +2441,7 @@ namespace Ink_Canvas
|
|||||||
else
|
else
|
||||||
{
|
{
|
||||||
HideSubPanels();
|
HideSubPanels();
|
||||||
|
UpdatePenPalettePosition();
|
||||||
AnimationsHelper.ShowWithSlideFromBottomAndFade(PenPalette);
|
AnimationsHelper.ShowWithSlideFromBottomAndFade(PenPalette);
|
||||||
AnimationsHelper.ShowWithSlideFromBottomAndFade(BoardPenPalette);
|
AnimationsHelper.ShowWithSlideFromBottomAndFade(BoardPenPalette);
|
||||||
}
|
}
|
||||||
@@ -2548,6 +2551,7 @@ namespace Ink_Canvas
|
|||||||
// 已是橡皮状态,再次点击才弹出/收起面板
|
// 已是橡皮状态,再次点击才弹出/收起面板
|
||||||
if (EraserSizePanel.Visibility == Visibility.Collapsed)
|
if (EraserSizePanel.Visibility == Visibility.Collapsed)
|
||||||
{
|
{
|
||||||
|
UpdateEraserSizePanelPosition();
|
||||||
AnimationsHelper.ShowWithSlideFromBottomAndFade(EraserSizePanel);
|
AnimationsHelper.ShowWithSlideFromBottomAndFade(EraserSizePanel);
|
||||||
if (BoardEraserSizePanel != null)
|
if (BoardEraserSizePanel != null)
|
||||||
AnimationsHelper.ShowWithSlideFromBottomAndFade(BoardEraserSizePanel);
|
AnimationsHelper.ShowWithSlideFromBottomAndFade(BoardEraserSizePanel);
|
||||||
@@ -2598,6 +2602,7 @@ namespace Ink_Canvas
|
|||||||
// 已是橡皮状态,再次点击才弹出/收起面板
|
// 已是橡皮状态,再次点击才弹出/收起面板
|
||||||
if (BoardEraserSizePanel != null && BoardEraserSizePanel.Visibility == Visibility.Collapsed)
|
if (BoardEraserSizePanel != null && BoardEraserSizePanel.Visibility == Visibility.Collapsed)
|
||||||
{
|
{
|
||||||
|
UpdateEraserSizePanelPosition();
|
||||||
AnimationsHelper.ShowWithSlideFromBottomAndFade(BoardEraserSizePanel);
|
AnimationsHelper.ShowWithSlideFromBottomAndFade(BoardEraserSizePanel);
|
||||||
AnimationsHelper.ShowWithSlideFromBottomAndFade(EraserSizePanel);
|
AnimationsHelper.ShowWithSlideFromBottomAndFade(EraserSizePanel);
|
||||||
}
|
}
|
||||||
@@ -3177,7 +3182,7 @@ namespace Ink_Canvas
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="sender">发送者</param>
|
/// <param name="sender">发送者</param>
|
||||||
/// <param name="e">路由事件参数</param>
|
/// <param name="e">路由事件参数</param>
|
||||||
private async void BtnSettings_Click(object sender, RoutedEventArgs e)
|
internal async void BtnSettings_Click(object sender, RoutedEventArgs e)
|
||||||
{
|
{
|
||||||
if (BorderSettings.Visibility == Visibility.Visible)
|
if (BorderSettings.Visibility == Visibility.Visible)
|
||||||
{
|
{
|
||||||
@@ -3298,6 +3303,9 @@ namespace Ink_Canvas
|
|||||||
// 清空触摸点计数器
|
// 清空触摸点计数器
|
||||||
dec.Clear();
|
dec.Clear();
|
||||||
|
|
||||||
|
if (isPalmEraserActive)
|
||||||
|
isPalmEraserActive = false;
|
||||||
|
|
||||||
// 确保触摸事件能正常响应
|
// 确保触摸事件能正常响应
|
||||||
inkCanvas.IsHitTestVisible = true;
|
inkCanvas.IsHitTestVisible = true;
|
||||||
inkCanvas.IsManipulationEnabled = true;
|
inkCanvas.IsManipulationEnabled = true;
|
||||||
@@ -3567,6 +3575,8 @@ namespace Ink_Canvas
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
SyncPdfPageSidebarWithCanvas();
|
||||||
}
|
}
|
||||||
|
|
||||||
private int BoundsWidth = 5;
|
private int BoundsWidth = 5;
|
||||||
@@ -3774,32 +3784,29 @@ namespace Ink_Canvas
|
|||||||
// Open file dialog to select image
|
// Open file dialog to select image
|
||||||
var dialog = new OpenFileDialog
|
var dialog = new OpenFileDialog
|
||||||
{
|
{
|
||||||
Filter = "图片文件|*.jpg;*.jpeg;*.png;*.bmp;*.gif"
|
Filter = "图片与 PDF|*.jpg;*.jpeg;*.png;*.bmp;*.gif;*.pdf|图片文件|*.jpg;*.jpeg;*.png;*.bmp;*.gif|PDF|*.pdf"
|
||||||
};
|
};
|
||||||
if (dialog.ShowDialog() == true)
|
if (dialog.ShowDialog() == true)
|
||||||
{
|
{
|
||||||
string filePath = dialog.FileName;
|
string filePath = dialog.FileName;
|
||||||
Image image = await CreateAndCompressImageAsync(filePath);
|
FrameworkElement element = await CreateAndCompressImageAsync(filePath);
|
||||||
if (image != null)
|
if (element != null)
|
||||||
{
|
{
|
||||||
string timestamp = "img_" + DateTime.Now.ToString("yyyyMMdd_HH_mm_ss_fff");
|
string timestamp = "img_" + DateTime.Now.ToString("yyyyMMdd_HH_mm_ss_fff");
|
||||||
image.Name = timestamp;
|
element.Name = timestamp;
|
||||||
|
|
||||||
// 初始化TransformGroup
|
// 初始化TransformGroup
|
||||||
if (image is FrameworkElement element)
|
var transformGroup = new TransformGroup();
|
||||||
{
|
transformGroup.Children.Add(new ScaleTransform(1, 1));
|
||||||
var transformGroup = new TransformGroup();
|
transformGroup.Children.Add(new TranslateTransform(0, 0));
|
||||||
transformGroup.Children.Add(new ScaleTransform(1, 1));
|
transformGroup.Children.Add(new RotateTransform(0));
|
||||||
transformGroup.Children.Add(new TranslateTransform(0, 0));
|
element.RenderTransform = transformGroup;
|
||||||
transformGroup.Children.Add(new RotateTransform(0));
|
|
||||||
element.RenderTransform = transformGroup;
|
|
||||||
}
|
|
||||||
|
|
||||||
CenterAndScaleElement(image);
|
CenterAndScaleElement(element);
|
||||||
|
|
||||||
// 设置图片属性,避免被InkCanvas选择系统处理
|
// 设置图片属性,避免被InkCanvas选择系统处理
|
||||||
image.IsHitTestVisible = true;
|
element.IsHitTestVisible = true;
|
||||||
image.Focusable = false;
|
element.Focusable = false;
|
||||||
|
|
||||||
// 初始化InkCanvas选择设置
|
// 初始化InkCanvas选择设置
|
||||||
if (inkCanvas != null)
|
if (inkCanvas != null)
|
||||||
@@ -3810,34 +3817,33 @@ namespace Ink_Canvas
|
|||||||
inkCanvas.EditingMode = InkCanvasEditingMode.None;
|
inkCanvas.EditingMode = InkCanvasEditingMode.None;
|
||||||
}
|
}
|
||||||
|
|
||||||
inkCanvas.Children.Add(image);
|
inkCanvas.Children.Add(element);
|
||||||
|
|
||||||
// 绑定事件处理器
|
// 绑定事件处理器
|
||||||
if (image is FrameworkElement elementForEvents)
|
element.MouseLeftButtonDown += Element_MouseLeftButtonDown;
|
||||||
{
|
element.MouseLeftButtonUp += Element_MouseLeftButtonUp;
|
||||||
// 鼠标事件
|
element.MouseMove += Element_MouseMove;
|
||||||
elementForEvents.MouseLeftButtonDown += Element_MouseLeftButtonDown;
|
element.MouseWheel += Element_MouseWheel;
|
||||||
elementForEvents.MouseLeftButtonUp += Element_MouseLeftButtonUp;
|
|
||||||
elementForEvents.MouseMove += Element_MouseMove;
|
|
||||||
elementForEvents.MouseWheel += Element_MouseWheel;
|
|
||||||
|
|
||||||
// 触摸事件
|
// 触摸事件
|
||||||
elementForEvents.TouchDown += Element_TouchDown;
|
element.TouchDown += Element_TouchDown;
|
||||||
elementForEvents.TouchUp += Element_TouchUp;
|
element.TouchUp += Element_TouchUp;
|
||||||
elementForEvents.IsManipulationEnabled = true;
|
element.IsManipulationEnabled = true;
|
||||||
elementForEvents.ManipulationDelta += Element_ManipulationDelta;
|
element.ManipulationDelta += Element_ManipulationDelta;
|
||||||
elementForEvents.ManipulationCompleted += Element_ManipulationCompleted;
|
element.ManipulationCompleted += Element_ManipulationCompleted;
|
||||||
|
|
||||||
// 设置光标
|
// 设置光标
|
||||||
elementForEvents.Cursor = Cursors.Hand;
|
element.Cursor = Cursors.Hand;
|
||||||
}
|
|
||||||
|
|
||||||
timeMachine.CommitElementInsertHistory(image);
|
timeMachine.CommitElementInsertHistory(element);
|
||||||
|
|
||||||
// 插入图片后切换到选择模式并刷新浮动栏高光显示
|
// 插入图片后切换到选择模式并刷新浮动栏高光显示
|
||||||
SetCurrentToolMode(InkCanvasEditingMode.Select);
|
SetCurrentToolMode(InkCanvasEditingMode.Select);
|
||||||
UpdateCurrentToolMode("select");
|
UpdateCurrentToolMode("select");
|
||||||
HideSubPanels("select");
|
HideSubPanels("select");
|
||||||
|
if (element is PdfEmbeddedView)
|
||||||
|
_pdfSidebarNextPositionUseHostTransform = true;
|
||||||
|
SyncPdfPageSidebarWithCanvas();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -3847,32 +3853,29 @@ namespace Ink_Canvas
|
|||||||
{
|
{
|
||||||
var dialog = new OpenFileDialog
|
var dialog = new OpenFileDialog
|
||||||
{
|
{
|
||||||
Filter = "图片文件|*.jpg;*.jpeg;*.png;*.bmp;*.gif"
|
Filter = "图片与 PDF|*.jpg;*.jpeg;*.png;*.bmp;*.gif;*.pdf|图片文件|*.jpg;*.jpeg;*.png;*.bmp;*.gif|PDF|*.pdf"
|
||||||
};
|
};
|
||||||
if (dialog.ShowDialog() == true)
|
if (dialog.ShowDialog() == true)
|
||||||
{
|
{
|
||||||
string filePath = dialog.FileName;
|
string filePath = dialog.FileName;
|
||||||
Image image = await CreateAndCompressImageAsync(filePath);
|
FrameworkElement element = await CreateAndCompressImageAsync(filePath);
|
||||||
if (image != null)
|
if (element != null)
|
||||||
{
|
{
|
||||||
string timestamp = "img_" + DateTime.Now.ToString("yyyyMMdd_HH_mm_ss_fff");
|
string timestamp = "img_" + DateTime.Now.ToString("yyyyMMdd_HH_mm_ss_fff");
|
||||||
image.Name = timestamp;
|
element.Name = timestamp;
|
||||||
|
|
||||||
// 初始化TransformGroup
|
// 初始化TransformGroup
|
||||||
if (image is FrameworkElement element)
|
var transformGroup = new TransformGroup();
|
||||||
{
|
transformGroup.Children.Add(new ScaleTransform(1, 1));
|
||||||
var transformGroup = new TransformGroup();
|
transformGroup.Children.Add(new TranslateTransform(0, 0));
|
||||||
transformGroup.Children.Add(new ScaleTransform(1, 1));
|
transformGroup.Children.Add(new RotateTransform(0));
|
||||||
transformGroup.Children.Add(new TranslateTransform(0, 0));
|
element.RenderTransform = transformGroup;
|
||||||
transformGroup.Children.Add(new RotateTransform(0));
|
|
||||||
element.RenderTransform = transformGroup;
|
|
||||||
}
|
|
||||||
|
|
||||||
CenterAndScaleElement(image);
|
CenterAndScaleElement(element);
|
||||||
|
|
||||||
// 设置图片属性,避免被InkCanvas选择系统处理
|
// 设置图片属性,避免被InkCanvas选择系统处理
|
||||||
image.IsHitTestVisible = true;
|
element.IsHitTestVisible = true;
|
||||||
image.Focusable = false;
|
element.Focusable = false;
|
||||||
|
|
||||||
// 初始化InkCanvas选择设置
|
// 初始化InkCanvas选择设置
|
||||||
if (inkCanvas != null)
|
if (inkCanvas != null)
|
||||||
@@ -3883,34 +3886,33 @@ namespace Ink_Canvas
|
|||||||
inkCanvas.EditingMode = InkCanvasEditingMode.None;
|
inkCanvas.EditingMode = InkCanvasEditingMode.None;
|
||||||
}
|
}
|
||||||
|
|
||||||
inkCanvas.Children.Add(image);
|
inkCanvas.Children.Add(element);
|
||||||
|
|
||||||
// 绑定事件处理器
|
// 绑定事件处理器
|
||||||
if (image is FrameworkElement elementForEvents)
|
element.MouseLeftButtonDown += Element_MouseLeftButtonDown;
|
||||||
{
|
element.MouseLeftButtonUp += Element_MouseLeftButtonUp;
|
||||||
// 鼠标事件
|
element.MouseMove += Element_MouseMove;
|
||||||
elementForEvents.MouseLeftButtonDown += Element_MouseLeftButtonDown;
|
element.MouseWheel += Element_MouseWheel;
|
||||||
elementForEvents.MouseLeftButtonUp += Element_MouseLeftButtonUp;
|
|
||||||
elementForEvents.MouseMove += Element_MouseMove;
|
|
||||||
elementForEvents.MouseWheel += Element_MouseWheel;
|
|
||||||
|
|
||||||
// 触摸事件
|
// 触摸事件
|
||||||
elementForEvents.TouchDown += Element_TouchDown;
|
element.TouchDown += Element_TouchDown;
|
||||||
elementForEvents.TouchUp += Element_TouchUp;
|
element.TouchUp += Element_TouchUp;
|
||||||
elementForEvents.IsManipulationEnabled = true;
|
element.IsManipulationEnabled = true;
|
||||||
elementForEvents.ManipulationDelta += Element_ManipulationDelta;
|
element.ManipulationDelta += Element_ManipulationDelta;
|
||||||
elementForEvents.ManipulationCompleted += Element_ManipulationCompleted;
|
element.ManipulationCompleted += Element_ManipulationCompleted;
|
||||||
|
|
||||||
// 设置光标
|
// 设置光标
|
||||||
elementForEvents.Cursor = Cursors.Hand;
|
element.Cursor = Cursors.Hand;
|
||||||
}
|
|
||||||
|
|
||||||
timeMachine.CommitElementInsertHistory(image);
|
timeMachine.CommitElementInsertHistory(element);
|
||||||
|
|
||||||
// 插入图片后切换到选择模式并刷新浮动栏高光显示
|
// 插入图片后切换到选择模式并刷新浮动栏高光显示
|
||||||
SetCurrentToolMode(InkCanvasEditingMode.Select);
|
SetCurrentToolMode(InkCanvasEditingMode.Select);
|
||||||
UpdateCurrentToolMode("select");
|
UpdateCurrentToolMode("select");
|
||||||
HideSubPanels("select");
|
HideSubPanels("select");
|
||||||
|
if (element is PdfEmbeddedView)
|
||||||
|
_pdfSidebarNextPositionUseHostTransform = true;
|
||||||
|
SyncPdfPageSidebarWithCanvas();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -3920,32 +3922,29 @@ namespace Ink_Canvas
|
|||||||
{
|
{
|
||||||
var dialog = new OpenFileDialog
|
var dialog = new OpenFileDialog
|
||||||
{
|
{
|
||||||
Filter = "图片文件|*.jpg;*.jpeg;*.png;*.bmp;*.gif"
|
Filter = "图片与 PDF|*.jpg;*.jpeg;*.png;*.bmp;*.gif;*.pdf|图片文件|*.jpg;*.jpeg;*.png;*.bmp;*.gif|PDF|*.pdf"
|
||||||
};
|
};
|
||||||
if (dialog.ShowDialog() == true)
|
if (dialog.ShowDialog() == true)
|
||||||
{
|
{
|
||||||
string filePath = dialog.FileName;
|
string filePath = dialog.FileName;
|
||||||
Image image = await CreateAndCompressImageAsync(filePath); // 补充image定义
|
FrameworkElement element = await CreateAndCompressImageAsync(filePath);
|
||||||
if (image != null)
|
if (element != null)
|
||||||
{
|
{
|
||||||
string timestamp = "img_" + DateTime.Now.ToString("yyyyMMdd_HH_mm_ss_fff");
|
string timestamp = "img_" + DateTime.Now.ToString("yyyyMMdd_HH_mm_ss_fff");
|
||||||
image.Name = timestamp;
|
element.Name = timestamp;
|
||||||
|
|
||||||
// 初始化TransformGroup
|
// 初始化TransformGroup
|
||||||
if (image is FrameworkElement element)
|
var transformGroup = new TransformGroup();
|
||||||
{
|
transformGroup.Children.Add(new ScaleTransform(1, 1));
|
||||||
var transformGroup = new TransformGroup();
|
transformGroup.Children.Add(new TranslateTransform(0, 0));
|
||||||
transformGroup.Children.Add(new ScaleTransform(1, 1));
|
transformGroup.Children.Add(new RotateTransform(0));
|
||||||
transformGroup.Children.Add(new TranslateTransform(0, 0));
|
element.RenderTransform = transformGroup;
|
||||||
transformGroup.Children.Add(new RotateTransform(0));
|
|
||||||
element.RenderTransform = transformGroup;
|
|
||||||
}
|
|
||||||
|
|
||||||
CenterAndScaleElement(image);
|
CenterAndScaleElement(element);
|
||||||
|
|
||||||
// 设置图片属性,避免被InkCanvas选择系统处理
|
// 设置图片属性,避免被InkCanvas选择系统处理
|
||||||
image.IsHitTestVisible = true;
|
element.IsHitTestVisible = true;
|
||||||
image.Focusable = false;
|
element.Focusable = false;
|
||||||
|
|
||||||
// 初始化InkCanvas选择设置
|
// 初始化InkCanvas选择设置
|
||||||
if (inkCanvas != null)
|
if (inkCanvas != null)
|
||||||
@@ -3956,34 +3955,33 @@ namespace Ink_Canvas
|
|||||||
inkCanvas.EditingMode = InkCanvasEditingMode.None;
|
inkCanvas.EditingMode = InkCanvasEditingMode.None;
|
||||||
}
|
}
|
||||||
|
|
||||||
inkCanvas.Children.Add(image);
|
inkCanvas.Children.Add(element);
|
||||||
|
|
||||||
// 绑定事件处理器
|
// 绑定事件处理器
|
||||||
if (image is FrameworkElement elementForEvents)
|
element.MouseLeftButtonDown += Element_MouseLeftButtonDown;
|
||||||
{
|
element.MouseLeftButtonUp += Element_MouseLeftButtonUp;
|
||||||
// 鼠标事件
|
element.MouseMove += Element_MouseMove;
|
||||||
elementForEvents.MouseLeftButtonDown += Element_MouseLeftButtonDown;
|
element.MouseWheel += Element_MouseWheel;
|
||||||
elementForEvents.MouseLeftButtonUp += Element_MouseLeftButtonUp;
|
|
||||||
elementForEvents.MouseMove += Element_MouseMove;
|
|
||||||
elementForEvents.MouseWheel += Element_MouseWheel;
|
|
||||||
|
|
||||||
// 触摸事件
|
// 触摸事件
|
||||||
elementForEvents.TouchDown += Element_TouchDown;
|
element.TouchDown += Element_TouchDown;
|
||||||
elementForEvents.TouchUp += Element_TouchUp;
|
element.TouchUp += Element_TouchUp;
|
||||||
elementForEvents.IsManipulationEnabled = true;
|
element.IsManipulationEnabled = true;
|
||||||
elementForEvents.ManipulationDelta += Element_ManipulationDelta;
|
element.ManipulationDelta += Element_ManipulationDelta;
|
||||||
elementForEvents.ManipulationCompleted += Element_ManipulationCompleted;
|
element.ManipulationCompleted += Element_ManipulationCompleted;
|
||||||
|
|
||||||
// 设置光标
|
// 设置光标
|
||||||
elementForEvents.Cursor = Cursors.Hand;
|
element.Cursor = Cursors.Hand;
|
||||||
}
|
|
||||||
|
|
||||||
timeMachine.CommitElementInsertHistory(image);
|
timeMachine.CommitElementInsertHistory(element);
|
||||||
|
|
||||||
// 插入图片后切换到选择模式并刷新浮动栏高光显示
|
// 插入图片后切换到选择模式并刷新浮动栏高光显示
|
||||||
SetCurrentToolMode(InkCanvasEditingMode.Select);
|
SetCurrentToolMode(InkCanvasEditingMode.Select);
|
||||||
UpdateCurrentToolMode("select");
|
UpdateCurrentToolMode("select");
|
||||||
HideSubPanels("select");
|
HideSubPanels("select");
|
||||||
|
if (element is PdfEmbeddedView)
|
||||||
|
_pdfSidebarNextPositionUseHostTransform = true;
|
||||||
|
SyncPdfPageSidebarWithCanvas();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -4216,6 +4214,101 @@ namespace Ink_Canvas
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 通用子面板位置更新方法:根据触发按钮的位置,动态调整子面板的水平位置,
|
||||||
|
/// 使面板水平中心对齐按钮中心。不改变面板大小,不改变上下边距。
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="button">触发按钮元素</param>
|
||||||
|
/// <param name="panel">需要定位的子面板</param>
|
||||||
|
/// <param name="defaultPanelWidth">面板默认宽度(当无法从Margin计算时使用)</param>
|
||||||
|
private void UpdateSubPanelPosition(FrameworkElement button, FrameworkElement panel, double defaultPanelWidth)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (button == null || panel == null) return;
|
||||||
|
if (!(panel.Parent is FrameworkElement panelContainer)) return;
|
||||||
|
|
||||||
|
var ancestor = StackPanelFloatingBar;
|
||||||
|
if (ancestor == null) return;
|
||||||
|
|
||||||
|
// 获取按钮中心的X坐标(相对于 StackPanelFloatingBar 坐标系)
|
||||||
|
var buttonTransform = button.TransformToAncestor(ancestor);
|
||||||
|
var buttonOrigin = buttonTransform.Transform(new Point(0, 0));
|
||||||
|
double buttonCenterX = buttonOrigin.X + button.ActualWidth / 2.0;
|
||||||
|
|
||||||
|
// 获取面板容器(零宽度Grid)的X坐标(相对于 StackPanelFloatingBar 坐标系)
|
||||||
|
var containerTransform = panelContainer.TransformToAncestor(ancestor);
|
||||||
|
var containerOrigin = containerTransform.Transform(new Point(0, 0));
|
||||||
|
double containerX = containerOrigin.X;
|
||||||
|
|
||||||
|
// 计算当前面板宽度(保持不变):panelWidth = -Margin.Left - Margin.Right
|
||||||
|
double currentLeft = panel.Margin.Left;
|
||||||
|
double currentRight = panel.Margin.Right;
|
||||||
|
double panelWidth = -currentLeft - currentRight;
|
||||||
|
if (panelWidth <= 0) panelWidth = defaultPanelWidth;
|
||||||
|
|
||||||
|
// 计算新的左边距,使面板水平中心对齐按钮:
|
||||||
|
// panel_center = containerX + newLeft + panelWidth/2 = buttonCenterX
|
||||||
|
// => newLeft = buttonCenterX - containerX - panelWidth/2
|
||||||
|
double newLeft = buttonCenterX - containerX - panelWidth / 2.0;
|
||||||
|
|
||||||
|
// 保持面板宽度不变:-newLeft - newRight = panelWidth
|
||||||
|
// => newRight = -panelWidth - newLeft
|
||||||
|
double newRight = -panelWidth - newLeft;
|
||||||
|
|
||||||
|
// 清除可能残留的 Margin 动画(HoldEnd 会阻止本地值生效)
|
||||||
|
panel.BeginAnimation(FrameworkElement.MarginProperty, null);
|
||||||
|
|
||||||
|
// 更新边距,仅调整Left/Right,保持Top/Bottom不变
|
||||||
|
var margin = panel.Margin;
|
||||||
|
panel.Margin = new Thickness(newLeft, margin.Top, newRight, margin.Bottom);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
LogHelper.WriteLogToFile($"更新子面板位置失败: {ex.Message}", LogHelper.LogType.Error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 更新批注子面板(PenPalette)的弹出位置,使其水平中心对齐笔按钮。
|
||||||
|
/// </summary>
|
||||||
|
private void UpdatePenPalettePosition()
|
||||||
|
{
|
||||||
|
UpdateSubPanelPosition(Pen_Icon, PenPalette, 193);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 更新工具面板(BorderTools)的弹出位置,使其水平中心对齐工具按钮。
|
||||||
|
/// </summary>
|
||||||
|
private void UpdateBorderToolsPosition()
|
||||||
|
{
|
||||||
|
UpdateSubPanelPosition(ToolsFloatingBarBtn, BorderTools, 119);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 更新橡皮擦尺寸面板(EraserSizePanel)的弹出位置,使其水平中心对齐橡皮擦按钮。
|
||||||
|
/// </summary>
|
||||||
|
private void UpdateEraserSizePanelPosition()
|
||||||
|
{
|
||||||
|
UpdateSubPanelPosition(Eraser_Icon, EraserSizePanel, 120);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 更新形状绘制面板(BorderDrawShape)的弹出位置,使其水平中心对齐形状按钮。
|
||||||
|
/// </summary>
|
||||||
|
private void UpdateBorderDrawShapePosition()
|
||||||
|
{
|
||||||
|
UpdateSubPanelPosition(ShapeDrawFloatingBarBtn, BorderDrawShape, 317);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 更新手势面板(TwoFingerGestureBorder)的弹出位置,使其水平中心对齐手势按钮。
|
||||||
|
/// </summary>
|
||||||
|
private void UpdateTwoFingerGestureBorderPosition()
|
||||||
|
{
|
||||||
|
UpdateSubPanelPosition(EnableTwoFingerGestureBorder, TwoFingerGestureBorder, 119);
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 隐藏浮动栏高光显示
|
/// 隐藏浮动栏高光显示
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|||||||
@@ -30,15 +30,19 @@ namespace Ink_Canvas
|
|||||||
public Bitmap CameraImage;
|
public Bitmap CameraImage;
|
||||||
public BitmapSource CameraBitmapSource;
|
public BitmapSource CameraBitmapSource;
|
||||||
public bool AddToWhiteboard;
|
public bool AddToWhiteboard;
|
||||||
|
public bool IncludeInk;
|
||||||
|
public BitmapSource InkOverlayBitmapSource;
|
||||||
|
|
||||||
public ScreenshotResult(Rectangle area, List<Point> path = null, Bitmap cameraImage = null,
|
public ScreenshotResult(Rectangle area, List<Point> path = null, Bitmap cameraImage = null,
|
||||||
BitmapSource cameraBitmapSource = null, bool addToWhiteboard = false)
|
BitmapSource cameraBitmapSource = null, bool addToWhiteboard = false, bool includeInk = false, BitmapSource inkOverlayBitmapSource = null)
|
||||||
{
|
{
|
||||||
Area = area;
|
Area = area;
|
||||||
Path = path;
|
Path = path;
|
||||||
CameraImage = cameraImage;
|
CameraImage = cameraImage;
|
||||||
CameraBitmapSource = cameraBitmapSource;
|
CameraBitmapSource = cameraBitmapSource;
|
||||||
AddToWhiteboard = addToWhiteboard;
|
AddToWhiteboard = addToWhiteboard;
|
||||||
|
IncludeInk = includeInk;
|
||||||
|
InkOverlayBitmapSource = inkOverlayBitmapSource;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -60,6 +64,8 @@ namespace Ink_Canvas
|
|||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
|
var inkOverlayPreview = CreateInkOverlayPreviewBitmapSource();
|
||||||
|
|
||||||
// 隐藏主窗口以避免截图包含窗口本身
|
// 隐藏主窗口以避免截图包含窗口本身
|
||||||
var originalVisibility = Visibility;
|
var originalVisibility = Visibility;
|
||||||
Visibility = Visibility.Hidden;
|
Visibility = Visibility.Hidden;
|
||||||
@@ -68,7 +74,7 @@ namespace Ink_Canvas
|
|||||||
await Task.Delay(200);
|
await Task.Delay(200);
|
||||||
|
|
||||||
// 启动区域选择截图
|
// 启动区域选择截图
|
||||||
var screenshotResult = await ShowScreenshotSelector();
|
var screenshotResult = await ShowScreenshotSelector(inkOverlayPreview);
|
||||||
|
|
||||||
// 恢复窗口显示
|
// 恢复窗口显示
|
||||||
Visibility = originalVisibility;
|
Visibility = originalVisibility;
|
||||||
@@ -104,11 +110,33 @@ namespace Ink_Canvas
|
|||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
|
if (screenshotResult.Value.IncludeInk && screenshotResult.Value.InkOverlayBitmapSource != null)
|
||||||
|
{
|
||||||
|
var withInkBitmap = OverlayInkOnCapturedBitmap(finalBitmap, screenshotResult.Value.Area, screenshotResult.Value.InkOverlayBitmapSource);
|
||||||
|
if (withInkBitmap != null && withInkBitmap != finalBitmap)
|
||||||
|
{
|
||||||
|
if (needDisposeFinalBitmap && finalBitmap != originalBitmap)
|
||||||
|
{
|
||||||
|
finalBitmap.Dispose();
|
||||||
|
}
|
||||||
|
finalBitmap = withInkBitmap;
|
||||||
|
needDisposeFinalBitmap = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// 如果有路径信息,应用形状遮罩
|
// 如果有路径信息,应用形状遮罩
|
||||||
if (screenshotResult.Value.Path != null && screenshotResult.Value.Path.Count > 0)
|
if (screenshotResult.Value.Path != null && screenshotResult.Value.Path.Count > 0)
|
||||||
{
|
{
|
||||||
finalBitmap = ApplyShapeMask(originalBitmap, screenshotResult.Value.Path, screenshotResult.Value.Area);
|
var maskedBitmap = ApplyShapeMask(finalBitmap, screenshotResult.Value.Path, screenshotResult.Value.Area);
|
||||||
needDisposeFinalBitmap = true; // 标记需要释放新创建的位图
|
if (maskedBitmap != null && maskedBitmap != finalBitmap)
|
||||||
|
{
|
||||||
|
if (needDisposeFinalBitmap && finalBitmap != originalBitmap)
|
||||||
|
{
|
||||||
|
finalBitmap.Dispose();
|
||||||
|
}
|
||||||
|
finalBitmap = maskedBitmap;
|
||||||
|
needDisposeFinalBitmap = true; // 标记需要释放新创建的位图
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 将截图转换为WPF Image并插入到画布
|
// 将截图转换为WPF Image并插入到画布
|
||||||
@@ -190,16 +218,19 @@ namespace Ink_Canvas
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 显示截图区域选择器
|
/// 显示截图区域选择器并返回用户的截图结果(区域截图或摄像头截图)。
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <returns>截图结果,包含区域、路径和摄像头截图信息</returns>
|
/// <param name="inkOverlayPreview">当用户选择包含墨迹的区域截图时,用于作为墨迹叠加的预览 <see cref="BitmapSource"/>;可为 <c>null</c>。</param>
|
||||||
|
/// <returns>若用户确认截图则返回 <see cref="ScreenshotResult"/>,否则返回 <c>null</c>。返回的结果可能为摄像头截图或区域截图,摄像头截图会包含 <see cref="ScreenshotResult.CameraBitmapSource"/> 或 <see cref="ScreenshotResult.CameraImage"/>,区域截图会包含有效的区域与路径。</returns>
|
||||||
/// <remarks>
|
/// <remarks>
|
||||||
/// 该方法会:
|
/// 该方法会:
|
||||||
/// 1. 显示截图选择器窗口
|
/// 1. 在 UI 线程(通过 <see cref="Application.Current.Dispatcher"/>)上显示截图选择器窗口 <see cref="ScreenshotSelectorWindow"/>;
|
||||||
/// 2. 获取用户选择的区域或摄像头截图
|
/// 2. 获取用户选择的区域截图或摄像头截图;
|
||||||
/// 3. 返回截图结果
|
/// 3. 根据用户选择构建并返回 <see cref="ScreenshotResult"/>;
|
||||||
|
/// 4. 若用户取消对话框或未确认截图,返回 <c>null</c>;
|
||||||
|
/// 5. 方法内部捕获异常并记录日志(不会向调用方抛出异常),如需外部处理请调整实现以重新抛出或传回错误信息。
|
||||||
/// </remarks>
|
/// </remarks>
|
||||||
private async Task<ScreenshotResult?> ShowScreenshotSelector()
|
private async Task<ScreenshotResult?> ShowScreenshotSelector(BitmapSource inkOverlayPreview = null)
|
||||||
{
|
{
|
||||||
ScreenshotResult? result = null;
|
ScreenshotResult? result = null;
|
||||||
|
|
||||||
@@ -207,7 +238,7 @@ namespace Ink_Canvas
|
|||||||
{
|
{
|
||||||
await Application.Current.Dispatcher.InvokeAsync(() =>
|
await Application.Current.Dispatcher.InvokeAsync(() =>
|
||||||
{
|
{
|
||||||
var selectorWindow = new ScreenshotSelectorWindow();
|
var selectorWindow = new ScreenshotSelectorWindow(inkOverlayPreview);
|
||||||
if (selectorWindow.ShowDialog() == true)
|
if (selectorWindow.ShowDialog() == true)
|
||||||
{
|
{
|
||||||
// 检查是否是摄像头截图
|
// 检查是否是摄像头截图
|
||||||
@@ -218,7 +249,9 @@ namespace Ink_Canvas
|
|||||||
null, // 摄像头截图不需要路径
|
null, // 摄像头截图不需要路径
|
||||||
null, // 不再使用Bitmap
|
null, // 不再使用Bitmap
|
||||||
selectorWindow.CameraBitmapSource, // 摄像头BitmapSource
|
selectorWindow.CameraBitmapSource, // 摄像头BitmapSource
|
||||||
selectorWindow.ShouldAddToWhiteboard
|
selectorWindow.ShouldAddToWhiteboard,
|
||||||
|
false,
|
||||||
|
null
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
else if (selectorWindow.CameraImage != null)
|
else if (selectorWindow.CameraImage != null)
|
||||||
@@ -228,7 +261,9 @@ namespace Ink_Canvas
|
|||||||
null, // 摄像头截图不需要路径
|
null, // 摄像头截图不需要路径
|
||||||
selectorWindow.CameraImage, // 摄像头图像
|
selectorWindow.CameraImage, // 摄像头图像
|
||||||
null,
|
null,
|
||||||
selectorWindow.ShouldAddToWhiteboard
|
selectorWindow.ShouldAddToWhiteboard,
|
||||||
|
false,
|
||||||
|
null
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
@@ -238,7 +273,9 @@ namespace Ink_Canvas
|
|||||||
selectorWindow.SelectedPath,
|
selectorWindow.SelectedPath,
|
||||||
null,
|
null,
|
||||||
null,
|
null,
|
||||||
selectorWindow.ShouldAddToWhiteboard
|
selectorWindow.ShouldAddToWhiteboard,
|
||||||
|
selectorWindow.IncludeInkInScreenshot,
|
||||||
|
selectorWindow.IncludeInkInScreenshot ? inkOverlayPreview : null
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -304,6 +341,142 @@ namespace Ink_Canvas
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private BitmapSource CreateInkOverlayPreviewBitmapSource()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (inkCanvas == null)
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ((inkCanvas.Strokes?.Count ?? 0) == 0 && inkCanvas.Children.Count == 0)
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (inkCanvas.ActualWidth <= 0 || inkCanvas.ActualHeight <= 0)
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
var virtualScreen = SystemInformation.VirtualScreen;
|
||||||
|
var source = PresentationSource.FromVisual(inkCanvas);
|
||||||
|
var transformToDevice = source?.CompositionTarget?.TransformToDevice ?? System.Windows.Media.Matrix.Identity;
|
||||||
|
|
||||||
|
// PointToScreen 返回WPF坐标(DIP),统一转换为设备像素后再与 VirtualScreen 对齐
|
||||||
|
var inkTopLeftDip = inkCanvas.PointToScreen(new Point(0, 0));
|
||||||
|
var inkTopLeftPx = transformToDevice.Transform(inkTopLeftDip);
|
||||||
|
var offsetX = inkTopLeftPx.X - virtualScreen.Left;
|
||||||
|
var offsetY = inkTopLeftPx.Y - virtualScreen.Top;
|
||||||
|
var widthPx = inkCanvas.ActualWidth * transformToDevice.M11;
|
||||||
|
var heightPx = inkCanvas.ActualHeight * transformToDevice.M22;
|
||||||
|
|
||||||
|
var drawingVisual = new DrawingVisual();
|
||||||
|
using (var dc = drawingVisual.RenderOpen())
|
||||||
|
{
|
||||||
|
// 使用完整 InkCanvas 视觉树,确保包含图片等子元素
|
||||||
|
var visualBrush = new VisualBrush(inkCanvas)
|
||||||
|
{
|
||||||
|
Stretch = Stretch.Fill
|
||||||
|
};
|
||||||
|
dc.DrawRectangle(visualBrush, null, new Rect(offsetX, offsetY, widthPx, heightPx));
|
||||||
|
}
|
||||||
|
|
||||||
|
var rtb = new RenderTargetBitmap(
|
||||||
|
Math.Max(1, virtualScreen.Width),
|
||||||
|
Math.Max(1, virtualScreen.Height),
|
||||||
|
96,
|
||||||
|
96,
|
||||||
|
PixelFormats.Pbgra32);
|
||||||
|
rtb.Render(drawingVisual);
|
||||||
|
rtb.Freeze();
|
||||||
|
return rtb;
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
LogHelper.WriteLogToFile($"创建截图墨迹预览失败: {ex.Message}", LogHelper.LogType.Warning);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private Bitmap OverlayInkOnCapturedBitmap(Bitmap capturedBitmap, Rectangle captureArea, BitmapSource inkOverlayBitmapSource)
|
||||||
|
{
|
||||||
|
if (capturedBitmap == null || inkOverlayBitmapSource == null)
|
||||||
|
{
|
||||||
|
return capturedBitmap;
|
||||||
|
}
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var virtualScreen = SystemInformation.VirtualScreen;
|
||||||
|
var sourceRect = new Rectangle(
|
||||||
|
captureArea.X - virtualScreen.X,
|
||||||
|
captureArea.Y - virtualScreen.Y,
|
||||||
|
captureArea.Width,
|
||||||
|
captureArea.Height);
|
||||||
|
|
||||||
|
sourceRect.Intersect(new Rectangle(0, 0, inkOverlayBitmapSource.PixelWidth, inkOverlayBitmapSource.PixelHeight));
|
||||||
|
if (sourceRect.Width <= 0 || sourceRect.Height <= 0)
|
||||||
|
{
|
||||||
|
return capturedBitmap;
|
||||||
|
}
|
||||||
|
|
||||||
|
using (var inkOverlayBitmap = ConvertBitmapSourceToBitmap(inkOverlayBitmapSource))
|
||||||
|
{
|
||||||
|
if (inkOverlayBitmap == null)
|
||||||
|
{
|
||||||
|
return capturedBitmap;
|
||||||
|
}
|
||||||
|
|
||||||
|
Bitmap resultBitmap = null;
|
||||||
|
try
|
||||||
|
{
|
||||||
|
resultBitmap = new Bitmap(capturedBitmap.Width, capturedBitmap.Height, PixelFormat.Format32bppArgb);
|
||||||
|
using (var g = Graphics.FromImage(resultBitmap))
|
||||||
|
{
|
||||||
|
g.DrawImage(capturedBitmap, 0, 0, capturedBitmap.Width, capturedBitmap.Height);
|
||||||
|
|
||||||
|
var targetRect = new Rectangle(0, 0, Math.Min(sourceRect.Width, capturedBitmap.Width), Math.Min(sourceRect.Height, capturedBitmap.Height));
|
||||||
|
g.DrawImage(inkOverlayBitmap, targetRect, sourceRect, GraphicsUnit.Pixel);
|
||||||
|
}
|
||||||
|
|
||||||
|
return resultBitmap;
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
resultBitmap?.Dispose();
|
||||||
|
throw;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
LogHelper.WriteLogToFile($"叠加截图墨迹失败: {ex.Message}", LogHelper.LogType.Warning);
|
||||||
|
return capturedBitmap;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private Bitmap ConvertBitmapSourceToBitmap(BitmapSource bitmapSource)
|
||||||
|
{
|
||||||
|
if (bitmapSource == null)
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
using (var memoryStream = new MemoryStream())
|
||||||
|
{
|
||||||
|
var encoder = new PngBitmapEncoder();
|
||||||
|
encoder.Frames.Add(BitmapFrame.Create(bitmapSource));
|
||||||
|
encoder.Save(memoryStream);
|
||||||
|
memoryStream.Position = 0;
|
||||||
|
using (var tempBitmap = new Bitmap(memoryStream))
|
||||||
|
{
|
||||||
|
return new Bitmap(tempBitmap);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 将截图插入到画布
|
/// 将截图插入到画布
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ using Microsoft.Office.Core;
|
|||||||
using Microsoft.Office.Interop.PowerPoint;
|
using Microsoft.Office.Interop.PowerPoint;
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using System.Diagnostics;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Runtime.InteropServices;
|
using System.Runtime.InteropServices;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
@@ -167,6 +168,18 @@ namespace Ink_Canvas
|
|||||||
/// 断开连接后退出PPT模式的延迟时间(毫秒),即连接断开后多长时间才退出PPT模式。
|
/// 断开连接后退出PPT模式的延迟时间(毫秒),即连接断开后多长时间才退出PPT模式。
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private const int ExitPPTModeAfterDisconnectDelayMs = 1200;
|
private const int ExitPPTModeAfterDisconnectDelayMs = 1200;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 仅PPT模式下周期性探测放映界面(COM 失效时依赖 Win32),间隔不宜过小以免多余开销。
|
||||||
|
/// </summary>
|
||||||
|
private DispatcherTimer _pptOnlyVisibilityProbeTimer;
|
||||||
|
|
||||||
|
private const int PptOnlyVisibilityProbeIntervalMs = 800;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// PowerPoint 全屏放映顶层窗口类名(与编辑态 PPTFrameClass 区分)。
|
||||||
|
/// </summary>
|
||||||
|
private const string PowerPointSlideShowWindowClassName = "screenClass";
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
#region PPT Managers
|
#region PPT Managers
|
||||||
@@ -638,6 +651,8 @@ namespace Ink_Canvas
|
|||||||
ClosePowerPointApplication();
|
ClosePowerPointApplication();
|
||||||
ClearStaticInteropState();
|
ClearStaticInteropState();
|
||||||
|
|
||||||
|
StopPptOnlyVisibilityProbeTimer();
|
||||||
|
|
||||||
LogHelper.WriteLogToFile("PPT管理器已释放", LogHelper.LogType.Event);
|
LogHelper.WriteLogToFile("PPT管理器已释放", LogHelper.LogType.Event);
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
@@ -700,6 +715,104 @@ namespace Ink_Canvas
|
|||||||
}
|
}
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
|
#region 仅PPT模式可见性(COM + Win32 兜底)
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 在启用「仅PPT模式」时启动轻量探测,COM 事件延迟或失效时仍可根据全屏放映窗口显示主窗口。
|
||||||
|
/// </summary>
|
||||||
|
internal void EnsurePptOnlyVisibilityProbeTimer()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (!Settings.ModeSettings.IsPPTOnlyMode)
|
||||||
|
{
|
||||||
|
StopPptOnlyVisibilityProbeTimer();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_pptOnlyVisibilityProbeTimer == null)
|
||||||
|
{
|
||||||
|
_pptOnlyVisibilityProbeTimer = new DispatcherTimer
|
||||||
|
{
|
||||||
|
Interval = TimeSpan.FromMilliseconds(PptOnlyVisibilityProbeIntervalMs)
|
||||||
|
};
|
||||||
|
_pptOnlyVisibilityProbeTimer.Tick += (_, __) => CheckMainWindowVisibility();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!_pptOnlyVisibilityProbeTimer.IsEnabled)
|
||||||
|
_pptOnlyVisibilityProbeTimer.Start();
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
LogHelper.WriteLogToFile($"仅PPT可见性探测计时器启动失败: {ex.Message}", LogHelper.LogType.Warning);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
internal void StopPptOnlyVisibilityProbeTimer()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
_pptOnlyVisibilityProbeTimer?.Stop();
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 检测是否存在 PowerPoint 全屏放映顶层窗口(类名 screenClass,进程 powerpnt),用于 COM 不可用时的兜底。
|
||||||
|
/// </summary>
|
||||||
|
internal bool IsPowerPointSlideshowSurfacePresentWin32()
|
||||||
|
{
|
||||||
|
if (!Settings.ModeSettings.IsPPTOnlyMode)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
bool found = false;
|
||||||
|
EnumWindows((hWnd, _) =>
|
||||||
|
{
|
||||||
|
if (!IsWindow(hWnd) || !IsWindowVisible(hWnd))
|
||||||
|
return true;
|
||||||
|
|
||||||
|
var cls = new StringBuilder(256);
|
||||||
|
if (GetClassName(hWnd, cls, cls.Capacity) == 0)
|
||||||
|
return true;
|
||||||
|
|
||||||
|
if (!string.Equals(cls.ToString(), PowerPointSlideShowWindowClassName, StringComparison.OrdinalIgnoreCase))
|
||||||
|
return true;
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
GetWindowThreadProcessId(hWnd, out uint pid);
|
||||||
|
using (var proc = Process.GetProcessById((int)pid))
|
||||||
|
{
|
||||||
|
var name = proc.ProcessName;
|
||||||
|
if (string.Equals(name, "POWERPNT", StringComparison.OrdinalIgnoreCase))
|
||||||
|
{
|
||||||
|
found = true;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}, IntPtr.Zero);
|
||||||
|
|
||||||
|
return found;
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
LogHelper.WriteLogToFile($"Win32 检测 PPT 放映窗口失败: {ex.Message}", LogHelper.LogType.Trace);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
#region New PPT Event Handlers
|
#region New PPT Event Handlers
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 处理 PowerPoint 连接状态的变更:更新界面连接/放映状态,并在断开时启动一个短延迟以安全退出 PPT 模式。
|
/// 处理 PowerPoint 连接状态的变更:更新界面连接/放映状态,并在断开时启动一个短延迟以安全退出 PPT 模式。
|
||||||
@@ -731,6 +844,8 @@ namespace Ink_Canvas
|
|||||||
_ = HandleManualSlideShowEnd();
|
_ = HandleManualSlideShowEnd();
|
||||||
if (Settings.PowerPointSettings.UseRotPptLink)
|
if (Settings.PowerPointSettings.UseRotPptLink)
|
||||||
_pptManager?.ReloadConnection();
|
_pptManager?.ReloadConnection();
|
||||||
|
|
||||||
|
CheckMainWindowVisibility();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -1114,6 +1229,9 @@ namespace Ink_Canvas
|
|||||||
|
|
||||||
// 加载当前页墨迹
|
// 加载当前页墨迹
|
||||||
LoadCurrentSlideInk(currentSlide);
|
LoadCurrentSlideInk(currentSlide);
|
||||||
|
|
||||||
|
// 仅PPT模式:放映开始立即同步主窗口可见性(勿仅依赖 SlideShowStateChanged 定时器)
|
||||||
|
CheckMainWindowVisibility();
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!isFloatingBarFolded)
|
if (!isFloatingBarFolded)
|
||||||
@@ -1399,6 +1517,8 @@ namespace Ink_Canvas
|
|||||||
currentMode = 0;
|
currentMode = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
SyncPdfPageSidebarWithCanvas();
|
||||||
|
|
||||||
ClearStrokes(true);
|
ClearStrokes(true);
|
||||||
// 清空备份历史记录,防止退出白板时恢复已结束PPT的墨迹
|
// 清空备份历史记录,防止退出白板时恢复已结束PPT的墨迹
|
||||||
// 注意:这里只清空索引0的备份,不影响白板页面的墨迹(索引1及以上)
|
// 注意:这里只清空索引0的备份,不影响白板页面的墨迹(索引1及以上)
|
||||||
@@ -1435,6 +1555,8 @@ namespace Ink_Canvas
|
|||||||
|
|
||||||
UpdateCurrentToolMode("cursor");
|
UpdateCurrentToolMode("cursor");
|
||||||
SetFloatingBarHighlightPosition("cursor");
|
SetFloatingBarHighlightPosition("cursor");
|
||||||
|
|
||||||
|
CheckMainWindowVisibility();
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
@@ -2291,6 +2413,7 @@ namespace Ink_Canvas
|
|||||||
_pptUIManager?.UpdateSlideShowStatus(false);
|
_pptUIManager?.UpdateSlideShowStatus(false);
|
||||||
_pptUIManager?.UpdateSidebarExitButtons(false);
|
_pptUIManager?.UpdateSidebarExitButtons(false);
|
||||||
LogHelper.WriteLogToFile("手动更新放映结束UI状态", LogHelper.LogType.Trace);
|
LogHelper.WriteLogToFile("手动更新放映结束UI状态", LogHelper.LogType.Trace);
|
||||||
|
CheckMainWindowVisibility();
|
||||||
});
|
});
|
||||||
|
|
||||||
// 手动处理自动收纳,因为OnPPTSlideShowEnd事件可能未触发
|
// 手动处理自动收纳,因为OnPPTSlideShowEnd事件可能未触发
|
||||||
@@ -2323,6 +2446,7 @@ namespace Ink_Canvas
|
|||||||
{
|
{
|
||||||
_pptUIManager?.UpdateSlideShowStatus(false);
|
_pptUIManager?.UpdateSlideShowStatus(false);
|
||||||
_pptUIManager?.UpdateSidebarExitButtons(false);
|
_pptUIManager?.UpdateSidebarExitButtons(false);
|
||||||
|
CheckMainWindowVisibility();
|
||||||
});
|
});
|
||||||
|
|
||||||
// 异常情况下也手动处理自动收纳
|
// 异常情况下也手动处理自动收纳
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
using Ink_Canvas.Controls;
|
||||||
using Ink_Canvas.Helpers;
|
using Ink_Canvas.Helpers;
|
||||||
using Newtonsoft.Json;
|
using Newtonsoft.Json;
|
||||||
using System;
|
using System;
|
||||||
@@ -16,6 +17,7 @@ using System.Windows.Ink;
|
|||||||
using System.Windows.Input;
|
using System.Windows.Input;
|
||||||
using System.Windows.Media;
|
using System.Windows.Media;
|
||||||
using System.Windows.Media.Imaging;
|
using System.Windows.Media.Imaging;
|
||||||
|
using System.Windows.Threading;
|
||||||
using System.Xml;
|
using System.Xml;
|
||||||
using System.Xml.Linq;
|
using System.Xml.Linq;
|
||||||
using Color = System.Drawing.Color;
|
using Color = System.Drawing.Color;
|
||||||
@@ -28,16 +30,58 @@ namespace Ink_Canvas
|
|||||||
// 1. 定义元素信息结构
|
// 1. 定义元素信息结构
|
||||||
public class CanvasElementInfo
|
public class CanvasElementInfo
|
||||||
{
|
{
|
||||||
public string Type { get; set; } // "Image"
|
public string Type { get; set; } // "Image" | "Pdf"
|
||||||
public string SourcePath { get; set; }
|
public string SourcePath { get; set; }
|
||||||
public double Left { get; set; }
|
public double Left { get; set; }
|
||||||
public double Top { get; set; }
|
public double Top { get; set; }
|
||||||
public double Width { get; set; }
|
public double Width { get; set; }
|
||||||
public double Height { get; set; }
|
public double Height { get; set; }
|
||||||
public string Stretch { get; set; } = "Fill"; // 默认为Fill
|
public string Stretch { get; set; } = "Fill"; // 默认为Fill
|
||||||
|
/// <summary>PDF 当前页(从 0 开始),仅 Type == Pdf 时有效。</summary>
|
||||||
|
public int? PdfCurrentPage { get; set; }
|
||||||
|
/// <summary>保存时的 PDF 总页数,用于校验;仅 Type == Pdf 时有效。</summary>
|
||||||
|
public int? PdfPageCount { get; set; }
|
||||||
}
|
}
|
||||||
public partial class MainWindow : Window
|
public partial class MainWindow : Window
|
||||||
{
|
{
|
||||||
|
/// <summary>收集画布上图片与 PDF 的元数据,写入 .elements.json(与墨迹文件同路径)。</summary>
|
||||||
|
private void CollectCanvasElementsMetadata(List<CanvasElementInfo> elementInfos)
|
||||||
|
{
|
||||||
|
if (elementInfos == null || inkCanvas == null) return;
|
||||||
|
|
||||||
|
foreach (var child in inkCanvas.Children)
|
||||||
|
{
|
||||||
|
if (child is Image img && img.Source is BitmapImage bmp)
|
||||||
|
{
|
||||||
|
elementInfos.Add(new CanvasElementInfo
|
||||||
|
{
|
||||||
|
Type = "Image",
|
||||||
|
SourcePath = bmp.UriSource?.LocalPath ?? "",
|
||||||
|
Left = InkCanvas.GetLeft(img),
|
||||||
|
Top = InkCanvas.GetTop(img),
|
||||||
|
Width = img.Width,
|
||||||
|
Height = img.Height,
|
||||||
|
Stretch = img.Stretch.ToString()
|
||||||
|
});
|
||||||
|
}
|
||||||
|
else if (child is PdfEmbeddedView pdf && !string.IsNullOrEmpty(pdf.PdfPath))
|
||||||
|
{
|
||||||
|
elementInfos.Add(new CanvasElementInfo
|
||||||
|
{
|
||||||
|
Type = "Pdf",
|
||||||
|
SourcePath = pdf.PdfPath,
|
||||||
|
Left = InkCanvas.GetLeft(pdf),
|
||||||
|
Top = InkCanvas.GetTop(pdf),
|
||||||
|
Width = pdf.Width,
|
||||||
|
Height = pdf.Height,
|
||||||
|
Stretch = "Uniform",
|
||||||
|
PdfCurrentPage = (int)pdf.CurrentPageIndex,
|
||||||
|
PdfPageCount = (int)pdf.PageCount
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 保存墨迹的鼠标释放事件处理
|
/// 保存墨迹的鼠标释放事件处理
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -419,22 +463,7 @@ namespace Ink_Canvas
|
|||||||
|
|
||||||
// 保存元素信息
|
// 保存元素信息
|
||||||
var elementInfos = new List<CanvasElementInfo>();
|
var elementInfos = new List<CanvasElementInfo>();
|
||||||
foreach (var child in inkCanvas.Children)
|
CollectCanvasElementsMetadata(elementInfos);
|
||||||
{
|
|
||||||
if (child is Image img && img.Source is BitmapImage bmp)
|
|
||||||
{
|
|
||||||
elementInfos.Add(new CanvasElementInfo
|
|
||||||
{
|
|
||||||
Type = "Image",
|
|
||||||
SourcePath = bmp.UriSource?.LocalPath ?? "",
|
|
||||||
Left = InkCanvas.GetLeft(img),
|
|
||||||
Top = InkCanvas.GetTop(img),
|
|
||||||
Width = img.Width,
|
|
||||||
Height = img.Height,
|
|
||||||
Stretch = img.Stretch.ToString()
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
string elementsPath = Settings.Automation.IsSaveStrokesAsXML ? Path.ChangeExtension(savePathWithName, ".elements.json") : Path.ChangeExtension(savePathWithName, ".elements.json");
|
string elementsPath = Settings.Automation.IsSaveStrokesAsXML ? Path.ChangeExtension(savePathWithName, ".elements.json") : Path.ChangeExtension(savePathWithName, ".elements.json");
|
||||||
File.WriteAllText(elementsPath, JsonConvert.SerializeObject(elementInfos, Newtonsoft.Json.Formatting.Indented));
|
File.WriteAllText(elementsPath, JsonConvert.SerializeObject(elementInfos, Newtonsoft.Json.Formatting.Indented));
|
||||||
}
|
}
|
||||||
@@ -485,22 +514,7 @@ namespace Ink_Canvas
|
|||||||
|
|
||||||
// 同时保存元素信息
|
// 同时保存元素信息
|
||||||
var elementInfos = new List<CanvasElementInfo>();
|
var elementInfos = new List<CanvasElementInfo>();
|
||||||
foreach (var child in inkCanvas.Children)
|
CollectCanvasElementsMetadata(elementInfos);
|
||||||
{
|
|
||||||
if (child is Image img && img.Source is BitmapImage bmp)
|
|
||||||
{
|
|
||||||
elementInfos.Add(new CanvasElementInfo
|
|
||||||
{
|
|
||||||
Type = "Image",
|
|
||||||
SourcePath = bmp.UriSource?.LocalPath ?? "",
|
|
||||||
Left = InkCanvas.GetLeft(img),
|
|
||||||
Top = InkCanvas.GetTop(img),
|
|
||||||
Width = img.Width,
|
|
||||||
Height = img.Height,
|
|
||||||
Stretch = img.Stretch.ToString()
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
File.WriteAllText(Path.ChangeExtension(xmlPath, ".elements.json"), JsonConvert.SerializeObject(elementInfos, Newtonsoft.Json.Formatting.Indented));
|
File.WriteAllText(Path.ChangeExtension(xmlPath, ".elements.json"), JsonConvert.SerializeObject(elementInfos, Newtonsoft.Json.Formatting.Indented));
|
||||||
|
|
||||||
// 异步上传到Dlass
|
// 异步上传到Dlass
|
||||||
@@ -1266,6 +1280,10 @@ namespace Ink_Canvas
|
|||||||
InkCanvas.SetTop(img, info.Top);
|
InkCanvas.SetTop(img, info.Top);
|
||||||
inkCanvas.Children.Add(img);
|
inkCanvas.Children.Add(img);
|
||||||
}
|
}
|
||||||
|
else if (string.Equals(info.Type, "Pdf", StringComparison.OrdinalIgnoreCase) && File.Exists(info.SourcePath))
|
||||||
|
{
|
||||||
|
Dispatcher.BeginInvoke(new Action(() => { _ = RestorePdfFromElementInfoAsync(info); }), DispatcherPriority.Loaded);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1416,6 +1434,10 @@ namespace Ink_Canvas
|
|||||||
InkCanvas.SetTop(img, info.Top);
|
InkCanvas.SetTop(img, info.Top);
|
||||||
inkCanvas.Children.Add(img);
|
inkCanvas.Children.Add(img);
|
||||||
}
|
}
|
||||||
|
else if (string.Equals(info.Type, "Pdf", StringComparison.OrdinalIgnoreCase) && File.Exists(info.SourcePath))
|
||||||
|
{
|
||||||
|
Dispatcher.BeginInvoke(new Action(() => { _ = RestorePdfFromElementInfoAsync(info); }), DispatcherPriority.Loaded);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -162,10 +162,11 @@ namespace Ink_Canvas
|
|||||||
var originalVisibility = Visibility;
|
var originalVisibility = Visibility;
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
|
var inkOverlayPreview = CreateInkOverlayPreviewBitmapSource();
|
||||||
Visibility = Visibility.Hidden;
|
Visibility = Visibility.Hidden;
|
||||||
await Task.Delay(200);
|
await Task.Delay(200);
|
||||||
|
|
||||||
var screenshotResult = await ShowScreenshotSelector();
|
var screenshotResult = await ShowScreenshotSelector(inkOverlayPreview);
|
||||||
|
|
||||||
if (!screenshotResult.HasValue)
|
if (!screenshotResult.HasValue)
|
||||||
{
|
{
|
||||||
@@ -202,10 +203,32 @@ namespace Ink_Canvas
|
|||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
|
if (screenshotResult.Value.IncludeInk && screenshotResult.Value.InkOverlayBitmapSource != null)
|
||||||
|
{
|
||||||
|
var withInkBitmap = OverlayInkOnCapturedBitmap(finalBitmap, screenshotResult.Value.Area, screenshotResult.Value.InkOverlayBitmapSource);
|
||||||
|
if (withInkBitmap != null && withInkBitmap != finalBitmap)
|
||||||
|
{
|
||||||
|
if (needDisposeFinalBitmap && finalBitmap != originalBitmap)
|
||||||
|
{
|
||||||
|
finalBitmap.Dispose();
|
||||||
|
}
|
||||||
|
finalBitmap = withInkBitmap;
|
||||||
|
needDisposeFinalBitmap = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (screenshotResult.Value.Path != null && screenshotResult.Value.Path.Count > 0)
|
if (screenshotResult.Value.Path != null && screenshotResult.Value.Path.Count > 0)
|
||||||
{
|
{
|
||||||
finalBitmap = ApplyShapeMask(originalBitmap, screenshotResult.Value.Path, screenshotResult.Value.Area);
|
var maskedBitmap = ApplyShapeMask(finalBitmap, screenshotResult.Value.Path, screenshotResult.Value.Area);
|
||||||
needDisposeFinalBitmap = true;
|
if (maskedBitmap != null && maskedBitmap != finalBitmap)
|
||||||
|
{
|
||||||
|
if (needDisposeFinalBitmap && finalBitmap != originalBitmap)
|
||||||
|
{
|
||||||
|
finalBitmap.Dispose();
|
||||||
|
}
|
||||||
|
finalBitmap = maskedBitmap;
|
||||||
|
needDisposeFinalBitmap = true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var directory = Path.GetDirectoryName(desktopPath);
|
var directory = Path.GetDirectoryName(desktopPath);
|
||||||
@@ -275,10 +298,32 @@ namespace Ink_Canvas
|
|||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
|
if (screenshotResult.IncludeInk && screenshotResult.InkOverlayBitmapSource != null)
|
||||||
|
{
|
||||||
|
var withInkBitmap = OverlayInkOnCapturedBitmap(finalBitmap, screenshotResult.Area, screenshotResult.InkOverlayBitmapSource);
|
||||||
|
if (withInkBitmap != null && withInkBitmap != finalBitmap)
|
||||||
|
{
|
||||||
|
if (needDisposeFinalBitmap && finalBitmap != originalBitmap)
|
||||||
|
{
|
||||||
|
finalBitmap.Dispose();
|
||||||
|
}
|
||||||
|
finalBitmap = withInkBitmap;
|
||||||
|
needDisposeFinalBitmap = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (screenshotResult.Path != null && screenshotResult.Path.Count > 0)
|
if (screenshotResult.Path != null && screenshotResult.Path.Count > 0)
|
||||||
{
|
{
|
||||||
finalBitmap = ApplyShapeMask(originalBitmap, screenshotResult.Path, screenshotResult.Area);
|
var maskedBitmap = ApplyShapeMask(finalBitmap, screenshotResult.Path, screenshotResult.Area);
|
||||||
needDisposeFinalBitmap = true;
|
if (maskedBitmap != null && maskedBitmap != finalBitmap)
|
||||||
|
{
|
||||||
|
if (needDisposeFinalBitmap && finalBitmap != originalBitmap)
|
||||||
|
{
|
||||||
|
finalBitmap.Dispose();
|
||||||
|
}
|
||||||
|
finalBitmap = maskedBitmap;
|
||||||
|
needDisposeFinalBitmap = true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
bitmapSourceForClipboard = ConvertBitmapToBitmapSource(finalBitmap);
|
bitmapSourceForClipboard = ConvertBitmapToBitmapSource(finalBitmap);
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
using Ink_Canvas.Controls;
|
||||||
using Ink_Canvas.Helpers;
|
using Ink_Canvas.Helpers;
|
||||||
using iNKORE.UI.WPF.Controls;
|
using iNKORE.UI.WPF.Controls;
|
||||||
using System;
|
using System;
|
||||||
@@ -610,7 +611,7 @@ namespace Ink_Canvas
|
|||||||
|
|
||||||
// 检查是否有图片元素被选中(通过InkCanvas的选中元素)
|
// 检查是否有图片元素被选中(通过InkCanvas的选中元素)
|
||||||
var selectedElements = inkCanvas.GetSelectedElements();
|
var selectedElements = inkCanvas.GetSelectedElements();
|
||||||
bool hasImageElement = selectedElements.Any(element => element is Image);
|
bool hasImageElement = selectedElements.Any(element => element is Image || element is PdfEmbeddedView);
|
||||||
|
|
||||||
// 如果有图片元素被选中,不显示选择框
|
// 如果有图片元素被选中,不显示选择框
|
||||||
if (hasImageElement)
|
if (hasImageElement)
|
||||||
@@ -621,7 +622,7 @@ namespace Ink_Canvas
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 检查是否有图片元素被选中(通过currentSelectedElement)
|
// 检查是否有图片元素被选中(通过currentSelectedElement)
|
||||||
if (currentSelectedElement != null && currentSelectedElement is Image)
|
if (currentSelectedElement != null && (currentSelectedElement is Image || currentSelectedElement is PdfEmbeddedView))
|
||||||
{
|
{
|
||||||
GridInkCanvasSelectionCover.Visibility = Visibility.Collapsed;
|
GridInkCanvasSelectionCover.Visibility = Visibility.Collapsed;
|
||||||
HideSelectionDisplay();
|
HideSelectionDisplay();
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
using Hardcodet.Wpf.TaskbarNotification;
|
using H.NotifyIcon;
|
||||||
using Ink_Canvas.Helpers;
|
using Ink_Canvas.Helpers;
|
||||||
using Newtonsoft.Json;
|
using Newtonsoft.Json;
|
||||||
using OSVersionExtension;
|
using OSVersionExtension;
|
||||||
@@ -617,6 +617,18 @@ namespace Ink_Canvas
|
|||||||
private void ToggleSwitchIsAutoUpdate_Toggled(object sender, RoutedEventArgs e)
|
private void ToggleSwitchIsAutoUpdate_Toggled(object sender, RoutedEventArgs e)
|
||||||
{
|
{
|
||||||
if (!isLoaded) return;
|
if (!isLoaded) return;
|
||||||
|
|
||||||
|
if (ToggleSwitchIsAutoUpdate.IsOn && Settings?.Startup?.HasConfirmedNetCompatibilityChange != true)
|
||||||
|
{
|
||||||
|
ToggleSwitchIsAutoUpdate.IsOn = false;
|
||||||
|
MessageBox.Show(
|
||||||
|
"此版本为最后一个NET472版本,您需要手动确认才能继续接受自动更新。",
|
||||||
|
"兼容性变更",
|
||||||
|
MessageBoxButton.OK,
|
||||||
|
MessageBoxImage.Warning);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
Settings.Startup.IsAutoUpdate = ToggleSwitchIsAutoUpdate.IsOn;
|
Settings.Startup.IsAutoUpdate = ToggleSwitchIsAutoUpdate.IsOn;
|
||||||
|
|
||||||
// 自动更新关闭时隐藏静默更新选项
|
// 自动更新关闭时隐藏静默更新选项
|
||||||
@@ -651,6 +663,18 @@ namespace Ink_Canvas
|
|||||||
private void ToggleSwitchIsAutoUpdateWithSilence_Toggled(object sender, RoutedEventArgs e)
|
private void ToggleSwitchIsAutoUpdateWithSilence_Toggled(object sender, RoutedEventArgs e)
|
||||||
{
|
{
|
||||||
if (!isLoaded) return;
|
if (!isLoaded) return;
|
||||||
|
|
||||||
|
if (ToggleSwitchIsAutoUpdateWithSilence.IsOn && Settings?.Startup?.HasConfirmedNetCompatibilityChange != true)
|
||||||
|
{
|
||||||
|
ToggleSwitchIsAutoUpdateWithSilence.IsOn = false;
|
||||||
|
MessageBox.Show(
|
||||||
|
"此版本为最后一个NET472版本,您需要手动确认才能继续接受自动更新。",
|
||||||
|
"兼容性变更",
|
||||||
|
MessageBoxButton.OK,
|
||||||
|
MessageBoxImage.Warning);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
Settings.Startup.IsAutoUpdateWithSilence = ToggleSwitchIsAutoUpdateWithSilence.IsOn;
|
Settings.Startup.IsAutoUpdateWithSilence = ToggleSwitchIsAutoUpdateWithSilence.IsOn;
|
||||||
|
|
||||||
// 静默更新的时间设置区域只在静默更新开启时显示
|
// 静默更新的时间设置区域只在静默更新开启时显示
|
||||||
@@ -3331,55 +3355,16 @@ namespace Ink_Canvas
|
|||||||
SaveSettingsToFile();
|
SaveSettingsToFile();
|
||||||
}
|
}
|
||||||
|
|
||||||
// 注释掉这些方法,因为对应的UI控件还没有在XAML中定义
|
private void ToggleSwitchDisableHardwareAcceleration_Toggled(object sender, RoutedEventArgs e)
|
||||||
/*
|
{
|
||||||
private void ToggleSwitchAsyncInkSmoothing_Toggled(object sender, RoutedEventArgs e) {
|
|
||||||
if (!isLoaded) return;
|
if (!isLoaded) return;
|
||||||
Settings.Canvas.UseAsyncInkSmoothing = ToggleSwitchAsyncInkSmoothing.IsOn;
|
|
||||||
|
Settings.Canvas.UseHardwareAcceleration = !ToggleSwitchDisableHardwareAcceleration.IsOn;
|
||||||
|
|
||||||
_inkSmoothingManager?.UpdateConfig();
|
_inkSmoothingManager?.UpdateConfig();
|
||||||
SaveSettingsToFile();
|
SaveSettingsToFile();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void ToggleSwitchHardwareAcceleration_Toggled(object sender, RoutedEventArgs e) {
|
|
||||||
if (!isLoaded) return;
|
|
||||||
Settings.Canvas.UseHardwareAcceleration = ToggleSwitchHardwareAcceleration.IsOn;
|
|
||||||
_inkSmoothingManager?.UpdateConfig();
|
|
||||||
SaveSettingsToFile();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void ComboBoxInkSmoothingQuality_SelectionChanged(object sender, SelectionChangedEventArgs e) {
|
|
||||||
if (!isLoaded) return;
|
|
||||||
Settings.Canvas.InkSmoothingQuality = ComboBoxInkSmoothingQuality.SelectedIndex;
|
|
||||||
_inkSmoothingManager?.UpdateConfig();
|
|
||||||
SaveSettingsToFile();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void SliderMaxConcurrentTasks_ValueChanged(object sender, RoutedPropertyChangedEventArgs<double> e) {
|
|
||||||
if (!isLoaded) return;
|
|
||||||
Settings.Canvas.MaxConcurrentSmoothingTasks = (int)SliderMaxConcurrentTasks.Value;
|
|
||||||
_inkSmoothingManager?.UpdateConfig();
|
|
||||||
SaveSettingsToFile();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void ButtonApplyRecommendedSettings_Click(object sender, RoutedEventArgs e) {
|
|
||||||
// 应用推荐的性能设置
|
|
||||||
Helpers.InkSmoothingManager.ApplyRecommendedSettings();
|
|
||||||
LoadSettings(false);
|
|
||||||
_inkSmoothingManager?.UpdateConfig();
|
|
||||||
SaveSettingsToFile();
|
|
||||||
|
|
||||||
ShowNotification("已应用推荐的性能设置");
|
|
||||||
}
|
|
||||||
|
|
||||||
private void ButtonShowPerformanceStats_Click(object sender, RoutedEventArgs e) {
|
|
||||||
if (_inkSmoothingManager != null)
|
|
||||||
{
|
|
||||||
var stats = _inkSmoothingManager.GetPerformanceStats();
|
|
||||||
ShowNotification($"性能统计: {stats}");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
*/
|
|
||||||
|
|
||||||
private void ToggleSwitchAutoSaveStrokesInPowerPoint_Toggled(object sender, RoutedEventArgs e)
|
private void ToggleSwitchAutoSaveStrokesInPowerPoint_Toggled(object sender, RoutedEventArgs e)
|
||||||
{
|
{
|
||||||
if (!isLoaded) return;
|
if (!isLoaded) return;
|
||||||
@@ -3597,6 +3582,11 @@ namespace Ink_Canvas
|
|||||||
RestoreNonStrokeElements(preservedElements);
|
RestoreNonStrokeElements(preservedElements);
|
||||||
isInMultiTouchMode = true;
|
isInMultiTouchMode = true;
|
||||||
|
|
||||||
|
palmEraserWasEnabledBeforeMultiTouch = Settings.Canvas.EnablePalmEraser;
|
||||||
|
Settings.Canvas.EnablePalmEraser = false;
|
||||||
|
if (ToggleSwitchEnablePalmEraser != null)
|
||||||
|
ToggleSwitchEnablePalmEraser.IsOn = false;
|
||||||
|
|
||||||
// 恢复到之前的编辑状态
|
// 恢复到之前的编辑状态
|
||||||
inkCanvas.EditingMode = currentEditingMode;
|
inkCanvas.EditingMode = currentEditingMode;
|
||||||
drawingShapeMode = currentDrawingShapeMode;
|
drawingShapeMode = currentDrawingShapeMode;
|
||||||
@@ -3627,6 +3617,13 @@ namespace Ink_Canvas
|
|||||||
RestoreNonStrokeElements(preservedElements);
|
RestoreNonStrokeElements(preservedElements);
|
||||||
isInMultiTouchMode = false;
|
isInMultiTouchMode = false;
|
||||||
|
|
||||||
|
if (palmEraserWasEnabledBeforeMultiTouch)
|
||||||
|
{
|
||||||
|
Settings.Canvas.EnablePalmEraser = true;
|
||||||
|
if (ToggleSwitchEnablePalmEraser != null)
|
||||||
|
ToggleSwitchEnablePalmEraser.IsOn = true;
|
||||||
|
}
|
||||||
|
|
||||||
// 恢复到之前的编辑状态
|
// 恢复到之前的编辑状态
|
||||||
inkCanvas.EditingMode = currentEditingMode;
|
inkCanvas.EditingMode = currentEditingMode;
|
||||||
drawingShapeMode = currentDrawingShapeMode;
|
drawingShapeMode = currentDrawingShapeMode;
|
||||||
@@ -5330,7 +5327,7 @@ namespace Ink_Canvas
|
|||||||
SaveSettingsToFile();
|
SaveSettingsToFile();
|
||||||
|
|
||||||
// 如果启用了自动更新,立即执行完整的检查更新操作
|
// 如果启用了自动更新,立即执行完整的检查更新操作
|
||||||
if (Settings.Startup.IsAutoUpdate)
|
if (Settings.Startup.IsAutoUpdate && Settings.Startup.HasConfirmedNetCompatibilityChange)
|
||||||
{
|
{
|
||||||
LogHelper.WriteLogToFile($"AutoUpdate | Channel changed to {newChannel}, performing immediate update check");
|
LogHelper.WriteLogToFile($"AutoUpdate | Channel changed to {newChannel}, performing immediate update check");
|
||||||
ResetUpdateCheckRetry();
|
ResetUpdateCheckRetry();
|
||||||
@@ -5359,6 +5356,33 @@ namespace Ink_Canvas
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void ConfirmNetCompatibilityChangeButton_Click(object sender, RoutedEventArgs e)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
PersistNetCompatibilityChangeConfirmation();
|
||||||
|
|
||||||
|
if (Net472CompatibilityWarningPanel != null)
|
||||||
|
{
|
||||||
|
Net472CompatibilityWarningPanel.Visibility = Visibility.Collapsed;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ToggleSwitchIsAutoUpdate != null)
|
||||||
|
{
|
||||||
|
ToggleSwitchIsAutoUpdate.IsEnabled = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ToggleSwitchIsAutoUpdateWithSilence != null)
|
||||||
|
{
|
||||||
|
ToggleSwitchIsAutoUpdateWithSilence.IsEnabled = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
LogHelper.WriteLogToFile($"确认兼容性变更失败: {ex.Message}", LogHelper.LogType.Error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private async void ManualUpdateButton_Click(object sender, RoutedEventArgs e)
|
private async void ManualUpdateButton_Click(object sender, RoutedEventArgs e)
|
||||||
{
|
{
|
||||||
ManualUpdateButton.IsEnabled = false;
|
ManualUpdateButton.IsEnabled = false;
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
using Hardcodet.Wpf.TaskbarNotification;
|
using H.NotifyIcon;
|
||||||
using Ink_Canvas.Helpers;
|
using Ink_Canvas.Helpers;
|
||||||
using Newtonsoft.Json;
|
using Newtonsoft.Json;
|
||||||
using Newtonsoft.Json.Linq;
|
using Newtonsoft.Json.Linq;
|
||||||
@@ -267,7 +267,7 @@ namespace Ink_Canvas
|
|||||||
ToggleSwitchIsAutoUpdate.IsOn = Settings.Startup.IsAutoUpdate;
|
ToggleSwitchIsAutoUpdate.IsOn = Settings.Startup.IsAutoUpdate;
|
||||||
|
|
||||||
// 只有在启用了自动更新功能时才检查更新
|
// 只有在启用了自动更新功能时才检查更新
|
||||||
if (Settings.Startup.IsAutoUpdate && !skipAutoUpdateCheck)
|
if (Settings.Startup.IsAutoUpdate && Settings.Startup.HasConfirmedNetCompatibilityChange && !skipAutoUpdateCheck)
|
||||||
{
|
{
|
||||||
if (isStartup)
|
if (isStartup)
|
||||||
{
|
{
|
||||||
@@ -288,6 +288,8 @@ namespace Ink_Canvas
|
|||||||
ToggleSwitchIsAutoUpdateWithSilence.IsOn = true;
|
ToggleSwitchIsAutoUpdateWithSilence.IsOn = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ApplyNetCompatibilityConfirmationGateToUpdateSettingsUi();
|
||||||
|
|
||||||
// 初始化更新通道选择
|
// 初始化更新通道选择
|
||||||
foreach (var radioButton in UpdateChannelSelector.Items)
|
foreach (var radioButton in UpdateChannelSelector.Items)
|
||||||
{
|
{
|
||||||
@@ -954,22 +956,7 @@ namespace Ink_Canvas
|
|||||||
drawingAttributes.FitToCurve = false;
|
drawingAttributes.FitToCurve = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 注释掉新的墨迹平滑性能设置,因为UI控件还没有定义
|
ToggleSwitchDisableHardwareAcceleration.IsOn = !Settings.Canvas.UseHardwareAcceleration;
|
||||||
/*
|
|
||||||
// 初始化新的墨迹平滑性能设置
|
|
||||||
ToggleSwitchAsyncInkSmoothing.IsOn = Settings.Canvas.UseAsyncInkSmoothing;
|
|
||||||
ToggleSwitchHardwareAcceleration.IsOn = Settings.Canvas.UseHardwareAcceleration;
|
|
||||||
ComboBoxInkSmoothingQuality.SelectedIndex = Settings.Canvas.InkSmoothingQuality;
|
|
||||||
SliderMaxConcurrentTasks.Value = Settings.Canvas.MaxConcurrentSmoothingTasks > 0 ?
|
|
||||||
Settings.Canvas.MaxConcurrentSmoothingTasks : Environment.ProcessorCount;
|
|
||||||
|
|
||||||
// 检查硬件加速支持
|
|
||||||
if (!Helpers.InkSmoothingManager.IsHardwareAccelerationSupported())
|
|
||||||
{
|
|
||||||
ToggleSwitchHardwareAcceleration.IsEnabled = false;
|
|
||||||
// 可以添加提示文本说明硬件加速不可用
|
|
||||||
}
|
|
||||||
*/
|
|
||||||
|
|
||||||
// 初始化直线自动拉直相关设置
|
// 初始化直线自动拉直相关设置
|
||||||
ToggleSwitchAutoStraightenLine.IsOn = Settings.Canvas.AutoStraightenLine;
|
ToggleSwitchAutoStraightenLine.IsOn = Settings.Canvas.AutoStraightenLine;
|
||||||
@@ -1332,6 +1319,26 @@ namespace Ink_Canvas
|
|||||||
try { RefreshConfigProfileList(); } catch (Exception ex) { LogHelper.WriteLogToFile($"刷新配置文件列表失败: {ex.Message}", LogHelper.LogType.Warning); }
|
try { RefreshConfigProfileList(); } catch (Exception ex) { LogHelper.WriteLogToFile($"刷新配置文件列表失败: {ex.Message}", LogHelper.LogType.Warning); }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void ApplyNetCompatibilityConfirmationGateToUpdateSettingsUi()
|
||||||
|
{
|
||||||
|
bool confirmed = Settings?.Startup?.HasConfirmedNetCompatibilityChange == true;
|
||||||
|
|
||||||
|
if (Net472CompatibilityWarningPanel != null)
|
||||||
|
{
|
||||||
|
Net472CompatibilityWarningPanel.Visibility = confirmed ? Visibility.Collapsed : Visibility.Visible;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ToggleSwitchIsAutoUpdate != null)
|
||||||
|
{
|
||||||
|
ToggleSwitchIsAutoUpdate.IsEnabled = confirmed;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ToggleSwitchIsAutoUpdateWithSilence != null)
|
||||||
|
{
|
||||||
|
ToggleSwitchIsAutoUpdateWithSilence.IsEnabled = confirmed;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 将画笔自动恢复相关的设置应用到界面控件并在启用时初始化自动恢复定时器。
|
/// 将画笔自动恢复相关的设置应用到界面控件并在启用时初始化自动恢复定时器。
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -1502,7 +1509,7 @@ namespace Ink_Canvas
|
|||||||
Settings defaultSettings = new Settings();
|
Settings defaultSettings = new Settings();
|
||||||
|
|
||||||
// 将默认配置和用户配置都序列化为JObject
|
// 将默认配置和用户配置都序列化为JObject
|
||||||
JObject defaultConfigObj = JObject.FromObject(defaultSettings); EnsureDefaultConfigSchemaIncludesIgnoredNullKeys(defaultConfigObj);
|
JObject defaultConfigObj = JObject.FromObject(defaultSettings); EnsureDefaultConfigSchemaIncludesIgnoredNullKeys(defaultConfigObj);
|
||||||
JObject userConfigObj = JObject.Parse(userConfigJson);
|
JObject userConfigObj = JObject.Parse(userConfigJson);
|
||||||
|
|
||||||
// 记录是否有清理操作
|
// 记录是否有清理操作
|
||||||
|
|||||||
@@ -49,6 +49,7 @@ namespace Ink_Canvas
|
|||||||
else
|
else
|
||||||
{
|
{
|
||||||
HideSubPanels();
|
HideSubPanels();
|
||||||
|
UpdateBorderDrawShapePosition();
|
||||||
AnimationsHelper.ShowWithSlideFromBottomAndFade(BorderDrawShape);
|
AnimationsHelper.ShowWithSlideFromBottomAndFade(BorderDrawShape);
|
||||||
AnimationsHelper.ShowWithSlideFromBottomAndFade(BoardBorderDrawShape);
|
AnimationsHelper.ShowWithSlideFromBottomAndFade(BoardBorderDrawShape);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -142,14 +142,24 @@ namespace Ink_Canvas
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void RunStrokeCollectedPostShapeRecognitionTail(InkCanvasStrokeCollectedEventArgs e, bool wasStraightened)
|
/// <summary>
|
||||||
|
/// 收笔后压感/墨迹平滑等尾部处理。返回「当前应登记到手写字形替换批次」的画布笔画引用:
|
||||||
|
/// 同步贝塞尔平滑若替换了笔画,则为新 <see cref="Stroke"/>;否则为 <paramref name="e"/>.Stroke。
|
||||||
|
/// 直线拉直后事件参数中的笔画可能已不在画布上,调用方需另行传入画布上的笔画(见收笔处)。
|
||||||
|
/// </summary>
|
||||||
|
private Stroke RunStrokeCollectedPostShapeRecognitionTail(InkCanvasStrokeCollectedEventArgs e, bool wasStraightened)
|
||||||
{
|
{
|
||||||
|
if (e?.Stroke == null)
|
||||||
|
return null;
|
||||||
|
|
||||||
|
var handwritingScheduleStroke = e.Stroke;
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
foreach (var stylusPoint in e.Stroke.StylusPoints)
|
foreach (var stylusPoint in e.Stroke.StylusPoints)
|
||||||
if ((stylusPoint.PressureFactor > 0.501 || stylusPoint.PressureFactor < 0.5) &&
|
if ((stylusPoint.PressureFactor > 0.501 || stylusPoint.PressureFactor < 0.5) &&
|
||||||
stylusPoint.PressureFactor != 0)
|
stylusPoint.PressureFactor != 0)
|
||||||
return;
|
return e.Stroke;
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
@@ -166,85 +176,89 @@ namespace Ink_Canvas
|
|||||||
}
|
}
|
||||||
catch (Exception ex) { System.Diagnostics.Debug.WriteLine(ex); }
|
catch (Exception ex) { System.Diagnostics.Debug.WriteLine(ex); }
|
||||||
|
|
||||||
switch (Settings.Canvas.InkStyle)
|
// 「屏蔽压感」已在收笔主路径将点集归一成 0.5;此处若再跑 InkStyle 0/1 会重写 PressureFactor,造成假压感。
|
||||||
|
if (!Settings.Canvas.DisablePressure)
|
||||||
{
|
{
|
||||||
case 1:
|
switch (Settings.Canvas.InkStyle)
|
||||||
if (penType == 0)
|
{
|
||||||
try
|
case 1:
|
||||||
{
|
if (penType == 0)
|
||||||
var stylusPoints = new StylusPointCollection();
|
try
|
||||||
var n = e.Stroke.StylusPoints.Count - 1;
|
|
||||||
|
|
||||||
for (var i = 0; i <= n; i++)
|
|
||||||
{
|
{
|
||||||
var speed = GetPointSpeed(e.Stroke.StylusPoints[Math.Max(i - 1, 0)].ToPoint(),
|
var stylusPoints = new StylusPointCollection();
|
||||||
e.Stroke.StylusPoints[i].ToPoint(),
|
var n = e.Stroke.StylusPoints.Count - 1;
|
||||||
e.Stroke.StylusPoints[Math.Min(i + 1, n)].ToPoint());
|
|
||||||
var point = new StylusPoint
|
|
||||||
{
|
|
||||||
PressureFactor = RateBasedPressureFactorFromPointSpeed(speed),
|
|
||||||
X = e.Stroke.StylusPoints[i].X,
|
|
||||||
Y = e.Stroke.StylusPoints[i].Y
|
|
||||||
};
|
|
||||||
stylusPoints.Add(point);
|
|
||||||
}
|
|
||||||
|
|
||||||
e.Stroke.StylusPoints = stylusPoints;
|
|
||||||
}
|
|
||||||
catch (Exception ex) { System.Diagnostics.Debug.WriteLine(ex); }
|
|
||||||
|
|
||||||
break;
|
|
||||||
case 0:
|
|
||||||
if (penType == 0)
|
|
||||||
try
|
|
||||||
{
|
|
||||||
var stylusPoints = new StylusPointCollection();
|
|
||||||
var n = e.Stroke.StylusPoints.Count - 1;
|
|
||||||
var pressure = 0.1;
|
|
||||||
var x = 10;
|
|
||||||
if (n == 1) return;
|
|
||||||
if (n >= x)
|
|
||||||
{
|
|
||||||
for (var i = 0; i < n - x; i++)
|
|
||||||
{
|
|
||||||
var point = new StylusPoint();
|
|
||||||
|
|
||||||
point.PressureFactor = (float)0.5;
|
|
||||||
point.X = e.Stroke.StylusPoints[i].X;
|
|
||||||
point.Y = e.Stroke.StylusPoints[i].Y;
|
|
||||||
stylusPoints.Add(point);
|
|
||||||
}
|
|
||||||
|
|
||||||
for (var i = n - x; i <= n; i++)
|
|
||||||
{
|
|
||||||
var point = new StylusPoint();
|
|
||||||
|
|
||||||
point.PressureFactor = (float)((0.5 - pressure) * (n - i) / x + pressure);
|
|
||||||
point.X = e.Stroke.StylusPoints[i].X;
|
|
||||||
point.Y = e.Stroke.StylusPoints[i].Y;
|
|
||||||
stylusPoints.Add(point);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
for (var i = 0; i <= n; i++)
|
for (var i = 0; i <= n; i++)
|
||||||
{
|
{
|
||||||
var point = new StylusPoint();
|
var speed = GetPointSpeed(e.Stroke.StylusPoints[Math.Max(i - 1, 0)].ToPoint(),
|
||||||
|
e.Stroke.StylusPoints[i].ToPoint(),
|
||||||
point.PressureFactor = (float)(0.4 * (n - i) / n + pressure);
|
e.Stroke.StylusPoints[Math.Min(i + 1, n)].ToPoint());
|
||||||
point.X = e.Stroke.StylusPoints[i].X;
|
var point = new StylusPoint
|
||||||
point.Y = e.Stroke.StylusPoints[i].Y;
|
{
|
||||||
|
PressureFactor = RateBasedPressureFactorFromPointSpeed(speed),
|
||||||
|
X = e.Stroke.StylusPoints[i].X,
|
||||||
|
Y = e.Stroke.StylusPoints[i].Y
|
||||||
|
};
|
||||||
stylusPoints.Add(point);
|
stylusPoints.Add(point);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
e.Stroke.StylusPoints = stylusPoints;
|
||||||
}
|
}
|
||||||
|
catch (Exception ex) { System.Diagnostics.Debug.WriteLine(ex); }
|
||||||
|
|
||||||
e.Stroke.StylusPoints = stylusPoints;
|
break;
|
||||||
}
|
case 0:
|
||||||
catch (Exception ex) { System.Diagnostics.Debug.WriteLine(ex); }
|
if (penType == 0)
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var stylusPoints = new StylusPointCollection();
|
||||||
|
var n = e.Stroke.StylusPoints.Count - 1;
|
||||||
|
var pressure = 0.1;
|
||||||
|
var x = 10;
|
||||||
|
if (n == 1) return e.Stroke;
|
||||||
|
if (n >= x)
|
||||||
|
{
|
||||||
|
for (var i = 0; i < n - x; i++)
|
||||||
|
{
|
||||||
|
var point = new StylusPoint();
|
||||||
|
|
||||||
break;
|
point.PressureFactor = (float)0.5;
|
||||||
case 3:
|
point.X = e.Stroke.StylusPoints[i].X;
|
||||||
break;
|
point.Y = e.Stroke.StylusPoints[i].Y;
|
||||||
|
stylusPoints.Add(point);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (var i = n - x; i <= n; i++)
|
||||||
|
{
|
||||||
|
var point = new StylusPoint();
|
||||||
|
|
||||||
|
point.PressureFactor = (float)((0.5 - pressure) * (n - i) / x + pressure);
|
||||||
|
point.X = e.Stroke.StylusPoints[i].X;
|
||||||
|
point.Y = e.Stroke.StylusPoints[i].Y;
|
||||||
|
stylusPoints.Add(point);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
for (var i = 0; i <= n; i++)
|
||||||
|
{
|
||||||
|
var point = new StylusPoint();
|
||||||
|
|
||||||
|
point.PressureFactor = (float)(0.4 * (n - i) / n + pressure);
|
||||||
|
point.X = e.Stroke.StylusPoints[i].X;
|
||||||
|
point.Y = e.Stroke.StylusPoints[i].Y;
|
||||||
|
stylusPoints.Add(point);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
e.Stroke.StylusPoints = stylusPoints;
|
||||||
|
}
|
||||||
|
catch (Exception ex) { System.Diagnostics.Debug.WriteLine(ex); }
|
||||||
|
|
||||||
|
break;
|
||||||
|
case 3:
|
||||||
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch (Exception ex) { System.Diagnostics.Debug.WriteLine(ex); }
|
catch (Exception ex) { System.Diagnostics.Debug.WriteLine(ex); }
|
||||||
@@ -276,6 +290,7 @@ namespace Ink_Canvas
|
|||||||
inkCanvas.Strokes.Remove(e.Stroke);
|
inkCanvas.Strokes.Remove(e.Stroke);
|
||||||
inkCanvas.Strokes.Add(smoothedStroke);
|
inkCanvas.Strokes.Add(smoothedStroke);
|
||||||
_currentCommitType = CommitReason.UserInput;
|
_currentCommitType = CommitReason.UserInput;
|
||||||
|
handwritingScheduleStroke = smoothedStroke;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -293,6 +308,8 @@ namespace Ink_Canvas
|
|||||||
{
|
{
|
||||||
drawingAttributes.FitToCurve = true;
|
drawingAttributes.FitToCurve = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return handwritingScheduleStroke;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -325,6 +342,10 @@ namespace Ink_Canvas
|
|||||||
&& Math.Abs(strokeDrawingAttributes.Width - BoardBrushInkWidth) < 0.01
|
&& Math.Abs(strokeDrawingAttributes.Width - BoardBrushInkWidth) < 0.01
|
||||||
&& Math.Abs(strokeDrawingAttributes.Height - BoardBrushInkHeight) < 0.01;
|
&& Math.Abs(strokeDrawingAttributes.Height - BoardBrushInkHeight) < 0.01;
|
||||||
|
|
||||||
|
// 手写识别须与画布显示分离:在压感/触摸模拟/笔锋/直线拉直等修改 e.Stroke 之前快照原始落笔点集。
|
||||||
|
var handwritingRawPointsForRecognizer =
|
||||||
|
CloneStylusPointCollectionForHandwritingInput(e.Stroke?.StylusPoints);
|
||||||
|
|
||||||
// 检查是否启用墨迹渐隐功能
|
// 检查是否启用墨迹渐隐功能
|
||||||
if (Settings.Canvas.EnableInkFade && !isBoardBrushStroke)
|
if (Settings.Canvas.EnableInkFade && !isBoardBrushStroke)
|
||||||
{
|
{
|
||||||
@@ -372,6 +393,8 @@ namespace Ink_Canvas
|
|||||||
|
|
||||||
// 标记是否进行了直线拉直
|
// 标记是否进行了直线拉直
|
||||||
bool wasStraightened = false;
|
bool wasStraightened = false;
|
||||||
|
StylusPointCollection preBrushHandwritingPoints = null;
|
||||||
|
Stroke strokeForHandwritingBeautify = null;
|
||||||
|
|
||||||
if (Settings.Canvas.FitToCurve) drawingAttributes.FitToCurve = false;
|
if (Settings.Canvas.FitToCurve) drawingAttributes.FitToCurve = false;
|
||||||
|
|
||||||
@@ -379,7 +402,7 @@ namespace Ink_Canvas
|
|||||||
{
|
{
|
||||||
inkCanvas.Opacity = 1;
|
inkCanvas.Opacity = 1;
|
||||||
var touchPressureSimulationApplied = false;
|
var touchPressureSimulationApplied = false;
|
||||||
var preBrushHandwritingPoints = CloneStylusPointCollectionForHandwritingInput(e.Stroke?.StylusPoints);
|
preBrushHandwritingPoints = handwritingRawPointsForRecognizer;
|
||||||
|
|
||||||
if (Settings.Canvas.DisablePressure)
|
if (Settings.Canvas.DisablePressure)
|
||||||
{
|
{
|
||||||
@@ -488,7 +511,11 @@ namespace Ink_Canvas
|
|||||||
|
|
||||||
// 实时笔锋:勿依赖 DrawingAttributes.IgnorePressure。无压感触摸/鼠标等设备上,运行时仍可能为 true,
|
// 实时笔锋:勿依赖 DrawingAttributes.IgnorePressure。无压感触摸/鼠标等设备上,运行时仍可能为 true,
|
||||||
// 会导致不进入逻辑或进入后渲染仍忽略 PressureFactor;具体在 ApplyVelocityBrushTipFromSpeed 内关闭。
|
// 会导致不进入逻辑或进入后渲染仍忽略 PressureFactor;具体在 ApplyVelocityBrushTipFromSpeed 内关闭。
|
||||||
|
// 「屏蔽压感」时必须跳过:否则会重写 PressureFactor 并强制 IgnorePressure=false,与归一压感冲突。
|
||||||
|
// VelocityBrushTipMix <= 0 时 ApplyVelocityBrushTipFromSpeed 为空操作,无需调用。
|
||||||
if (Settings.Canvas.InkStyle == 3
|
if (Settings.Canvas.InkStyle == 3
|
||||||
|
&& Settings.Canvas.VelocityBrushTipMix > 0
|
||||||
|
&& !Settings.Canvas.DisablePressure
|
||||||
&& !touchPressureSimulationApplied
|
&& !touchPressureSimulationApplied
|
||||||
&& penType != 1
|
&& penType != 1
|
||||||
&& e.Stroke?.DrawingAttributes != null
|
&& e.Stroke?.DrawingAttributes != null
|
||||||
@@ -500,6 +527,8 @@ namespace Ink_Canvas
|
|||||||
|
|
||||||
// Apply line straightening and endpoint snapping if ink-to-shape is enabled
|
// Apply line straightening and endpoint snapping if ink-to-shape is enabled
|
||||||
|
|
||||||
|
Stroke straightStrokeForHandwritingKey = null;
|
||||||
|
|
||||||
if (Settings.InkToShape.IsInkToShapeEnabled)
|
if (Settings.InkToShape.IsInkToShapeEnabled)
|
||||||
{
|
{
|
||||||
// 检查是否启用了直线自动拉直功能
|
// 检查是否启用了直线自动拉直功能
|
||||||
@@ -542,6 +571,8 @@ namespace Ink_Canvas
|
|||||||
inkCanvas.Strokes.Add(straightStroke);
|
inkCanvas.Strokes.Add(straightStroke);
|
||||||
_currentCommitType = CommitReason.UserInput;
|
_currentCommitType = CommitReason.UserInput;
|
||||||
|
|
||||||
|
straightStrokeForHandwritingKey = straightStroke;
|
||||||
|
|
||||||
// We can't modify e.Stroke directly, but we need to update newStrokes
|
// We can't modify e.Stroke directly, but we need to update newStrokes
|
||||||
// to ensure proper shape recognition for the straightened line
|
// to ensure proper shape recognition for the straightened line
|
||||||
if (newStrokes.Contains(e.Stroke))
|
if (newStrokes.Contains(e.Stroke))
|
||||||
@@ -555,8 +586,10 @@ namespace Ink_Canvas
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Stroke strokeForHandwritingBeautify = e.Stroke;
|
strokeForHandwritingBeautify = e.Stroke;
|
||||||
if (wasStraightened && inkCanvas.Strokes.Count > 0)
|
if (wasStraightened && straightStrokeForHandwritingKey != null)
|
||||||
|
strokeForHandwritingBeautify = straightStrokeForHandwritingKey;
|
||||||
|
else if (wasStraightened && inkCanvas.Strokes.Count > 0)
|
||||||
strokeForHandwritingBeautify = inkCanvas.Strokes[inkCanvas.Strokes.Count - 1];
|
strokeForHandwritingBeautify = inkCanvas.Strokes[inkCanvas.Strokes.Count - 1];
|
||||||
|
|
||||||
|
|
||||||
@@ -685,6 +718,18 @@ namespace Ink_Canvas
|
|||||||
var endP = new Point(result.Centroid.X + result.ShapeWidth / 2,
|
var endP = new Point(result.Centroid.X + result.ShapeWidth / 2,
|
||||||
result.Centroid.Y + result.ShapeHeight / 2);
|
result.Centroid.Y + result.ShapeHeight / 2);
|
||||||
|
|
||||||
|
// WinRT 返回的热点顺序/方向不稳定时,用点集反推 IACore 风格椭圆参数(中心/长短轴/方向/四个端点)
|
||||||
|
var hasEllipseParams = TryEstimateEllipseParamsFromStrokes(
|
||||||
|
result.StrokesToRemove,
|
||||||
|
out var ellipseCentroid,
|
||||||
|
out var ellipseA,
|
||||||
|
out var ellipseB,
|
||||||
|
out var ellipseThetaRad,
|
||||||
|
out var ellipseMajor0,
|
||||||
|
out var ellipseMajor1,
|
||||||
|
out var ellipseMinor0,
|
||||||
|
out var ellipseMinor1);
|
||||||
|
|
||||||
foreach (var circle in circles)
|
foreach (var circle in circles)
|
||||||
//判断是否画同心椭圆
|
//判断是否画同心椭圆
|
||||||
if (Math.Abs(result.Centroid.X - circle.Centroid.X) / a < 0.2 &&
|
if (Math.Abs(result.Centroid.X - circle.Centroid.X) / a < 0.2 &&
|
||||||
@@ -772,13 +817,17 @@ namespace Ink_Canvas
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
//纠正垂直与水平关系
|
// 用反推参数替换中心与长短轴(比 WinRT 的包围盒更接近 IACore,且不会竖横翻转)
|
||||||
var newPoints = FixPointsDirection(p[0], p[2]);
|
if (hasEllipseParams)
|
||||||
p[0] = newPoints[0];
|
{
|
||||||
p[2] = newPoints[1];
|
result.Centroid = ellipseCentroid;
|
||||||
newPoints = FixPointsDirection(p[1], p[3]);
|
a = ellipseA;
|
||||||
p[1] = newPoints[0];
|
b = ellipseB;
|
||||||
p[3] = newPoints[1];
|
iniP = new Point(result.Centroid.X - a, result.Centroid.Y - b);
|
||||||
|
endP = new Point(result.Centroid.X + a, result.Centroid.Y + b);
|
||||||
|
// 用端点重写热点,保证后续回退分支也一致
|
||||||
|
p = new PointCollection { ellipseMajor0, ellipseMinor0, ellipseMajor1, ellipseMinor1 };
|
||||||
|
}
|
||||||
|
|
||||||
var pointList = GenerateEllipseGeometry(iniP, endP);
|
var pointList = GenerateEllipseGeometry(iniP, endP);
|
||||||
var point = new StylusPointCollection(pointList);
|
var point = new StylusPointCollection(pointList);
|
||||||
@@ -790,9 +839,8 @@ namespace Ink_Canvas
|
|||||||
if (needRotation)
|
if (needRotation)
|
||||||
{
|
{
|
||||||
var m = new Matrix();
|
var m = new Matrix();
|
||||||
var fe = e.Source as FrameworkElement;
|
// 优先使用反推参数角度;否则用端点向量角度(使用 Atan2 避免斜率无穷)
|
||||||
var tanTheta = (p[2].Y - p[0].Y) / (p[2].X - p[0].X);
|
var theta = hasEllipseParams ? ellipseThetaRad : Math.Atan2(p[2].Y - p[0].Y, p[2].X - p[0].X);
|
||||||
var theta = Math.Atan(tanTheta);
|
|
||||||
m.RotateAt(theta * 180.0 / Math.PI, result.Centroid.X, result.Centroid.Y);
|
m.RotateAt(theta * 180.0 / Math.PI, result.Centroid.X, result.Centroid.Y);
|
||||||
stroke.Transform(m, false);
|
stroke.Transform(m, false);
|
||||||
}
|
}
|
||||||
@@ -910,9 +958,15 @@ namespace Ink_Canvas
|
|||||||
try
|
try
|
||||||
{
|
{
|
||||||
await InkToShapeProcessCoreAsync();
|
await InkToShapeProcessCoreAsync();
|
||||||
|
var strokeAfterTail = RunStrokeCollectedPostShapeRecognitionTail(e, wsTail);
|
||||||
if (Settings.InkToShape.EnableWinRtHandwritingStrokeBeautify)
|
if (Settings.InkToShape.EnableWinRtHandwritingStrokeBeautify)
|
||||||
ScheduleHandwritingGlyphReplaceAfterStrokeCollected(strokeHw, isBoardBrushStroke, preBrushHwPts);
|
{
|
||||||
RunStrokeCollectedPostShapeRecognitionTail(e, wsTail);
|
var canvasStrokeForHw = wsTail ? strokeHw : strokeAfterTail;
|
||||||
|
ScheduleHandwritingGlyphReplaceAfterStrokeCollected(
|
||||||
|
canvasStrokeForHw,
|
||||||
|
isBoardBrushStroke,
|
||||||
|
preBrushHwPts);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
@@ -925,14 +979,21 @@ namespace Ink_Canvas
|
|||||||
if (InkToShapeProcess())
|
if (InkToShapeProcess())
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
else if (Settings.InkToShape.EnableWinRtHandwritingStrokeBeautify)
|
|
||||||
{
|
|
||||||
ScheduleHandwritingGlyphReplaceAfterStrokeCollected(strokeForHandwritingBeautify, isBoardBrushStroke, preBrushHandwritingPoints);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
catch (Exception ex) { System.Diagnostics.Debug.WriteLine(ex); }
|
catch (Exception ex) { System.Diagnostics.Debug.WriteLine(ex); }
|
||||||
|
|
||||||
RunStrokeCollectedPostShapeRecognitionTail(e, wasStraightened);
|
var strokeAfterTailSync = RunStrokeCollectedPostShapeRecognitionTail(e, wasStraightened);
|
||||||
|
if (Settings.InkToShape.EnableWinRtHandwritingStrokeBeautify
|
||||||
|
&& !ShapeRecognitionRouter.ShouldRunShapeRecognition(
|
||||||
|
Settings.InkToShape.IsInkToShapeEnabled,
|
||||||
|
ShapeRecognitionRouter.FromSettingsInt(Settings.InkToShape.ShapeRecognitionEngine)))
|
||||||
|
{
|
||||||
|
var canvasStrokeForHw = wasStraightened ? strokeForHandwritingBeautify : strokeAfterTailSync;
|
||||||
|
ScheduleHandwritingGlyphReplaceAfterStrokeCollected(
|
||||||
|
canvasStrokeForHw,
|
||||||
|
isBoardBrushStroke,
|
||||||
|
preBrushHandwritingPoints);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -967,6 +1028,8 @@ namespace Ink_Canvas
|
|||||||
inkCanvas.Strokes.Remove(original);
|
inkCanvas.Strokes.Remove(original);
|
||||||
inkCanvas.Strokes.Add(smoothed);
|
inkCanvas.Strokes.Add(smoothed);
|
||||||
_currentCommitType = CommitReason.UserInput;
|
_currentCommitType = CommitReason.UserInput;
|
||||||
|
// 收笔尾部仍以 original 登记手写批次;异步平滑后画布对象变为 smoothed,须迁移引用,否则防抖识别时字典 miss 会退回画布几何(非实时笔锋常见)。
|
||||||
|
MigrateHandwritingBeautifyCanvasStrokeReference(original, smoothed);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@@ -2143,12 +2206,16 @@ namespace Ink_Canvas
|
|||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 将沿线速度映射为压感并与硬件压感混合,快写略细、慢写略粗;在落笔时(及手写笔移动时由调用方)统一施加。
|
/// 将沿线速度映射为压感并与硬件压感混合,快写略细、慢写略粗;在落笔时(及手写笔移动时由调用方)统一施加。
|
||||||
/// 无压感设备上系统可能将 <see cref="DrawingAttributes.IgnorePressure"/> 置为 true,此处强制关闭以便粗细随合成压感变化(与「屏蔽压感」无关:调用方已保证未屏蔽)。
|
/// 无压感设备上系统可能将 <see cref="DrawingAttributes.IgnorePressure"/> 置为 true,此处强制关闭以便粗细随合成压感变化。
|
||||||
|
/// 若 <see cref="Settings.Canvas.DisablePressure"/> 为 true,本方法直接返回且不修改 IgnorePressure。
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private void ApplyVelocityBrushTipFromSpeed(Stroke stroke)
|
private void ApplyVelocityBrushTipFromSpeed(Stroke stroke)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
|
if (Settings.Canvas.DisablePressure)
|
||||||
|
return;
|
||||||
|
|
||||||
var mix = Settings.Canvas.VelocityBrushTipMix;
|
var mix = Settings.Canvas.VelocityBrushTipMix;
|
||||||
if (mix <= 0 || stroke == null) return;
|
if (mix <= 0 || stroke == null) return;
|
||||||
if (mix > 1) mix = 1;
|
if (mix > 1) mix = 1;
|
||||||
@@ -2230,6 +2297,124 @@ namespace Ink_Canvas
|
|||||||
return new Point[2] { p1, p2 };
|
return new Point[2] { p1, p2 };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 用点集拟合出 IACore 风格椭圆参数(中心/长短半轴/方向/四个端点)。
|
||||||
|
/// 解决 WinRT 返回热点顺序不稳定导致椭圆纠正角度翻转的问题。
|
||||||
|
/// </summary>
|
||||||
|
private static bool TryEstimateEllipseParamsFromStrokes(
|
||||||
|
StrokeCollection strokes,
|
||||||
|
out Point centroid,
|
||||||
|
out double a,
|
||||||
|
out double b,
|
||||||
|
out double thetaRad,
|
||||||
|
out Point major0,
|
||||||
|
out Point major1,
|
||||||
|
out Point minor0,
|
||||||
|
out Point minor1)
|
||||||
|
{
|
||||||
|
centroid = default;
|
||||||
|
a = b = 0;
|
||||||
|
thetaRad = 0;
|
||||||
|
major0 = major1 = minor0 = minor1 = default;
|
||||||
|
|
||||||
|
if (strokes == null || strokes.Count == 0) return false;
|
||||||
|
|
||||||
|
var pts = new List<Point>(256);
|
||||||
|
foreach (var s in strokes)
|
||||||
|
{
|
||||||
|
if (s?.StylusPoints == null) continue;
|
||||||
|
foreach (var sp in s.StylusPoints)
|
||||||
|
pts.Add(sp.ToPoint());
|
||||||
|
}
|
||||||
|
|
||||||
|
if (pts.Count < 12) return false;
|
||||||
|
|
||||||
|
double mx = 0, my = 0;
|
||||||
|
for (int i = 0; i < pts.Count; i++)
|
||||||
|
{
|
||||||
|
mx += pts[i].X;
|
||||||
|
my += pts[i].Y;
|
||||||
|
}
|
||||||
|
mx /= pts.Count;
|
||||||
|
my /= pts.Count;
|
||||||
|
centroid = new Point(mx, my);
|
||||||
|
|
||||||
|
double sxx = 0, syy = 0, sxy = 0;
|
||||||
|
for (int i = 0; i < pts.Count; i++)
|
||||||
|
{
|
||||||
|
var dx = pts[i].X - mx;
|
||||||
|
var dy = pts[i].Y - my;
|
||||||
|
sxx += dx * dx;
|
||||||
|
syy += dy * dy;
|
||||||
|
sxy += dx * dy;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (sxx + syy < 1e-6) return false;
|
||||||
|
|
||||||
|
thetaRad = 0.5 * Math.Atan2(2.0 * sxy, sxx - syy);
|
||||||
|
if (double.IsNaN(thetaRad) || double.IsInfinity(thetaRad)) return false;
|
||||||
|
|
||||||
|
// 主轴单位向量 v1=(cos,sin),次轴 v2=(-sin,cos)
|
||||||
|
var cos = Math.Cos(thetaRad);
|
||||||
|
var sin = Math.Sin(thetaRad);
|
||||||
|
|
||||||
|
// 投影收集,用分位数抑制离群点
|
||||||
|
var us = new double[pts.Count];
|
||||||
|
var vs = new double[pts.Count];
|
||||||
|
double maxU = double.MinValue, minU = double.MaxValue;
|
||||||
|
double maxV = double.MinValue, minV = double.MaxValue;
|
||||||
|
for (int i = 0; i < pts.Count; i++)
|
||||||
|
{
|
||||||
|
var dx = pts[i].X - mx;
|
||||||
|
var dy = pts[i].Y - my;
|
||||||
|
var u = dx * cos + dy * sin;
|
||||||
|
var v = -dx * sin + dy * cos;
|
||||||
|
us[i] = u;
|
||||||
|
vs[i] = v;
|
||||||
|
if (u > maxU) maxU = u;
|
||||||
|
if (u < minU) minU = u;
|
||||||
|
if (v > maxV) maxV = v;
|
||||||
|
if (v < minV) minV = v;
|
||||||
|
}
|
||||||
|
|
||||||
|
Array.Sort(us);
|
||||||
|
Array.Sort(vs);
|
||||||
|
|
||||||
|
int hi = (int)Math.Round((pts.Count - 1) * 0.98);
|
||||||
|
int lo = (int)Math.Round((pts.Count - 1) * 0.02);
|
||||||
|
hi = Math.Max(0, Math.Min(pts.Count - 1, hi));
|
||||||
|
lo = Math.Max(0, Math.Min(pts.Count - 1, lo));
|
||||||
|
|
||||||
|
var uHi = us[hi];
|
||||||
|
var uLo = us[lo];
|
||||||
|
var vHi = vs[hi];
|
||||||
|
var vLo = vs[lo];
|
||||||
|
|
||||||
|
var aCandidate = Math.Max(Math.Abs(uHi), Math.Abs(uLo));
|
||||||
|
var bCandidate = Math.Max(Math.Abs(vHi), Math.Abs(vLo));
|
||||||
|
if (aCandidate < 1e-3) aCandidate = Math.Max(Math.Abs(maxU), Math.Abs(minU));
|
||||||
|
if (bCandidate < 1e-3) bCandidate = Math.Max(Math.Abs(maxV), Math.Abs(minV));
|
||||||
|
|
||||||
|
a = aCandidate;
|
||||||
|
b = bCandidate;
|
||||||
|
|
||||||
|
// 保证 a 为长半轴
|
||||||
|
if (b > a)
|
||||||
|
{
|
||||||
|
var t = a; a = b; b = t;
|
||||||
|
thetaRad += Math.PI / 2;
|
||||||
|
cos = Math.Cos(thetaRad);
|
||||||
|
sin = Math.Sin(thetaRad);
|
||||||
|
}
|
||||||
|
|
||||||
|
major0 = new Point(mx - a * cos, my - a * sin);
|
||||||
|
major1 = new Point(mx + a * cos, my + a * sin);
|
||||||
|
minor0 = new Point(mx + b * sin, my - b * cos);
|
||||||
|
minor1 = new Point(mx - b * sin, my + b * cos);
|
||||||
|
|
||||||
|
return a > 1e-2 && b > 1e-2;
|
||||||
|
}
|
||||||
|
|
||||||
public StylusPointCollection GenerateFakePressureTriangle(StylusPointCollection points)
|
public StylusPointCollection GenerateFakePressureTriangle(StylusPointCollection points)
|
||||||
{
|
{
|
||||||
var newPoint = new StylusPointCollection();
|
var newPoint = new StylusPointCollection();
|
||||||
@@ -2730,6 +2915,29 @@ namespace Ink_Canvas
|
|||||||
return corners.OrderBy(p => Math.Atan2(p.Y - center.Y, p.X - center.X)).ToList();
|
return corners.OrderBy(p => Math.Atan2(p.Y - center.Y, p.X - center.X)).ToList();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 异步墨迹平滑将画布上的 <paramref name="fromStroke"/> 替换为 <paramref name="toStroke"/> 后,把手写字形替换批次里的画布引用一并迁移,使识别仍命中「原始点快照」字典项。
|
||||||
|
/// </summary>
|
||||||
|
private void MigrateHandwritingBeautifyCanvasStrokeReference(Stroke fromStroke, Stroke toStroke)
|
||||||
|
{
|
||||||
|
if (fromStroke == null || toStroke == null || ReferenceEquals(fromStroke, toStroke))
|
||||||
|
return;
|
||||||
|
if (!Settings.InkToShape.EnableWinRtHandwritingStrokeBeautify)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (_handwritingBeautifyInkInputByCanvasStroke.TryGetValue(fromStroke, out var inkInput))
|
||||||
|
{
|
||||||
|
_handwritingBeautifyInkInputByCanvasStroke.Remove(fromStroke);
|
||||||
|
_handwritingBeautifyInkInputByCanvasStroke[toStroke] = inkInput;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (var i = 0; i < _handwritingRecentStrokesForBeautify.Count; i++)
|
||||||
|
{
|
||||||
|
if (ReferenceEquals(_handwritingRecentStrokesForBeautify[i], fromStroke))
|
||||||
|
_handwritingRecentStrokesForBeautify[i] = toStroke;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 收笔后:在墨迹转形状(若启用)完成之后,将笔画并入批次并启动/重置停笔防抖计时器,再于延迟后多笔合并矫正。
|
/// 收笔后:在墨迹转形状(若启用)完成之后,将笔画并入批次并启动/重置停笔防抖计时器,再于延迟后多笔合并矫正。
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -2788,10 +2996,19 @@ namespace Ink_Canvas
|
|||||||
|
|
||||||
if (preBrushHandwritingPoints != null && preBrushHandwritingPoints.Count > 0)
|
if (preBrushHandwritingPoints != null && preBrushHandwritingPoints.Count > 0)
|
||||||
{
|
{
|
||||||
_handwritingBeautifyInkInputByCanvasStroke[strokeForBeautify] = new Stroke(preBrushHandwritingPoints)
|
// 再拷贝一份给识别专用 Stroke,避免与外部 StylusPointCollection 或 WPF Stroke 内部共享后被改写。
|
||||||
|
var ptsForRecognizer = CloneStylusPointCollectionForHandwritingInput(preBrushHandwritingPoints);
|
||||||
|
if (ptsForRecognizer != null && ptsForRecognizer.Count > 0)
|
||||||
{
|
{
|
||||||
DrawingAttributes = strokeForBeautify.DrawingAttributes.Clone()
|
_handwritingBeautifyInkInputByCanvasStroke[strokeForBeautify] = new Stroke(ptsForRecognizer)
|
||||||
};
|
{
|
||||||
|
DrawingAttributes = strokeForBeautify.DrawingAttributes.Clone()
|
||||||
|
};
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_handwritingBeautifyInkInputByCanvasStroke.Remove(strokeForBeautify);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@@ -2813,7 +3030,11 @@ namespace Ink_Canvas
|
|||||||
|
|
||||||
PruneHandwritingBeautifyBatch();
|
PruneHandwritingBeautifyBatch();
|
||||||
while (_handwritingRecentStrokesForBeautify.Count > HandwritingBeautifyMaxRecentStrokes)
|
while (_handwritingRecentStrokesForBeautify.Count > HandwritingBeautifyMaxRecentStrokes)
|
||||||
|
{
|
||||||
|
var evicted = _handwritingRecentStrokesForBeautify[0];
|
||||||
_handwritingRecentStrokesForBeautify.RemoveAt(0);
|
_handwritingRecentStrokesForBeautify.RemoveAt(0);
|
||||||
|
_handwritingBeautifyInkInputByCanvasStroke.Remove(evicted);
|
||||||
|
}
|
||||||
|
|
||||||
EnsureHandwritingBeautifyDebounceTimer();
|
EnsureHandwritingBeautifyDebounceTimer();
|
||||||
_handwritingBeautifyDebounceTimer.Stop();
|
_handwritingBeautifyDebounceTimer.Stop();
|
||||||
@@ -2856,9 +3077,17 @@ namespace Ink_Canvas
|
|||||||
continue;
|
continue;
|
||||||
canvasStrokes.Add(s);
|
canvasStrokes.Add(s);
|
||||||
if (_handwritingBeautifyInkInputByCanvasStroke.TryGetValue(s, out var inkInput) && inkInput != null)
|
if (_handwritingBeautifyInkInputByCanvasStroke.TryGetValue(s, out var inkInput) && inkInput != null)
|
||||||
|
{
|
||||||
recognitionInput.Add(inkInput);
|
recognitionInput.Add(inkInput);
|
||||||
|
}
|
||||||
else
|
else
|
||||||
|
{
|
||||||
|
LogHelper.WriteLogToFile(
|
||||||
|
"[手写体] 批次识别输入回退为画布笔画(未命中原始点快照)。画布点数=" +
|
||||||
|
(s.StylusPoints?.Count ?? 0),
|
||||||
|
LogHelper.LogType.Info);
|
||||||
recognitionInput.Add(s);
|
recognitionInput.Add(s);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (canvasStrokes.Count == 0)
|
if (canvasStrokes.Count == 0)
|
||||||
|
|||||||
@@ -44,6 +44,9 @@ namespace Ink_Canvas
|
|||||||
/// 多点触控延迟时间(毫秒)
|
/// 多点触控延迟时间(毫秒)
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private const double MULTI_TOUCH_DELAY_MS = 100;
|
private const double MULTI_TOUCH_DELAY_MS = 100;
|
||||||
|
private bool isMultiTouchTimerActive;
|
||||||
|
private bool isPalmEraserActive;
|
||||||
|
private bool palmEraserWasEnabledBeforeMultiTouch;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 保存画布上的非笔画元素(如图片、媒体元素等)
|
/// 保存画布上的非笔画元素(如图片、媒体元素等)
|
||||||
@@ -227,6 +230,12 @@ namespace Ink_Canvas
|
|||||||
RestoreNonStrokeElements(preservedElements);
|
RestoreNonStrokeElements(preservedElements);
|
||||||
isInMultiTouchMode = false;
|
isInMultiTouchMode = false;
|
||||||
|
|
||||||
|
if (palmEraserWasEnabledBeforeMultiTouch)
|
||||||
|
{
|
||||||
|
Settings.Canvas.EnablePalmEraser = true;
|
||||||
|
if (ToggleSwitchEnablePalmEraser != null)
|
||||||
|
ToggleSwitchEnablePalmEraser.IsOn = true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@@ -247,6 +256,11 @@ namespace Ink_Canvas
|
|||||||
// 恢复非笔画元素
|
// 恢复非笔画元素
|
||||||
RestoreNonStrokeElements(preservedElements);
|
RestoreNonStrokeElements(preservedElements);
|
||||||
isInMultiTouchMode = true;
|
isInMultiTouchMode = true;
|
||||||
|
|
||||||
|
palmEraserWasEnabledBeforeMultiTouch = Settings.Canvas.EnablePalmEraser;
|
||||||
|
Settings.Canvas.EnablePalmEraser = false;
|
||||||
|
if (ToggleSwitchEnablePalmEraser != null)
|
||||||
|
ToggleSwitchEnablePalmEraser.IsOn = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -543,10 +557,12 @@ namespace Ink_Canvas
|
|||||||
foreach (var stylusPoint in stylusPointCollection)
|
foreach (var stylusPoint in stylusPointCollection)
|
||||||
strokeVisual.Add(new StylusPoint(stylusPoint.X, stylusPoint.Y, stylusPoint.PressureFactor));
|
strokeVisual.Add(new StylusPoint(stylusPoint.X, stylusPoint.Y, stylusPoint.PressureFactor));
|
||||||
|
|
||||||
// 实时笔锋:在绘制过程中更新压感并整笔重绘预览;否则预览层固定线宽,收笔后改点集也看不到笔锋变化。
|
// 实时笔锋:混合度 > 0 时在绘制过程中更新压感并整笔重绘预览;混合为 0 时与普通过程一致用增量 Redraw,避免每点 ForceRedraw 整笔清空(长笔画卡顿)。
|
||||||
var committedStroke = strokeVisual.Stroke;
|
var committedStroke = strokeVisual.Stroke;
|
||||||
if (committedStroke != null
|
if (committedStroke != null
|
||||||
&& Settings.Canvas.InkStyle == 3
|
&& Settings.Canvas.InkStyle == 3
|
||||||
|
&& Settings.Canvas.VelocityBrushTipMix > 0
|
||||||
|
&& !Settings.Canvas.DisablePressure
|
||||||
&& penType == 0
|
&& penType == 0
|
||||||
&& committedStroke.DrawingAttributes != null
|
&& committedStroke.DrawingAttributes != null
|
||||||
&& !committedStroke.DrawingAttributes.IsHighlighter
|
&& !committedStroke.DrawingAttributes.IsHighlighter
|
||||||
@@ -719,21 +735,13 @@ namespace Ink_Canvas
|
|||||||
/// <param name="e">触摸事件参数</param>
|
/// <param name="e">触摸事件参数</param>
|
||||||
/// <returns>返回触摸边界宽度</returns>
|
/// <returns>返回触摸边界宽度</returns>
|
||||||
/// <remarks>
|
/// <remarks>
|
||||||
/// 根据触摸事件参数计算触摸边界宽度,包括以下逻辑:
|
/// 手掌擦阈值与特殊屏 <c>TouchMultiplier</c> 在激活逻辑中单独参与计算,此处仅返回几何接触尺寸。
|
||||||
/// 1. 获取触摸点的边界
|
|
||||||
/// 2. 如果不是四边红外屏幕,使用边界宽度
|
|
||||||
/// 3. 如果是四边红外屏幕,使用边界宽度和高度的平方根
|
|
||||||
/// 4. 如果是特殊屏幕,乘以触摸倍数
|
|
||||||
/// 5. 返回计算得到的触摸边界宽度
|
|
||||||
/// </remarks>
|
/// </remarks>
|
||||||
public double GetTouchBoundWidth(TouchEventArgs e)
|
public double GetTouchBoundWidth(TouchEventArgs e)
|
||||||
{
|
{
|
||||||
var args = e.GetTouchPoint(null).Bounds;
|
var args = e.GetTouchPoint(null).Bounds;
|
||||||
double value;
|
if (!Settings.Advanced.IsQuadIR) return args.Width;
|
||||||
if (!Settings.Advanced.IsQuadIR) value = args.Width;
|
return Math.Sqrt(args.Width * args.Height);
|
||||||
else value = Math.Sqrt(args.Width * args.Height); //四边红外
|
|
||||||
if (Settings.Advanced.IsSpecialScreen) value *= Settings.Advanced.TouchMultiplier;
|
|
||||||
return value;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -757,28 +765,146 @@ namespace Ink_Canvas
|
|||||||
/// </remarks>
|
/// </remarks>
|
||||||
private void InkCanvas_PreviewTouchDown(object sender, TouchEventArgs e)
|
private void InkCanvas_PreviewTouchDown(object sender, TouchEventArgs e)
|
||||||
{
|
{
|
||||||
|
var touchPointForBar = e.GetTouchPoint(this);
|
||||||
|
var floatingBarBounds = ViewboxFloatingBar.TransformToAncestor(this).TransformBounds(
|
||||||
|
new Rect(0, 0, ViewboxFloatingBar.ActualWidth, ViewboxFloatingBar.ActualHeight));
|
||||||
|
if (floatingBarBounds.Contains(touchPointForBar.Position))
|
||||||
|
return;
|
||||||
|
|
||||||
|
if ((inkCanvas.EditingMode == InkCanvasEditingMode.EraseByPoint
|
||||||
|
|| inkCanvas.EditingMode == InkCanvasEditingMode.EraseByStroke)
|
||||||
|
&& !isPalmEraserActive)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (drawingShapeMode != 0)
|
||||||
|
{
|
||||||
|
inkCanvas.EditingMode = InkCanvasEditingMode.None;
|
||||||
|
SetCursorBasedOnEditingMode(inkCanvas);
|
||||||
|
inkCanvas.CaptureTouch(e.TouchDevice);
|
||||||
|
ViewboxFloatingBar.IsHitTestVisible = false;
|
||||||
|
BlackboardUIGridForInkReplay.IsHitTestVisible = false;
|
||||||
|
|
||||||
|
isTouchDown = true;
|
||||||
|
|
||||||
|
if (dec.Count == 0)
|
||||||
|
{
|
||||||
|
var inkTouchPoint = e.GetTouchPoint(inkCanvas);
|
||||||
|
if (drawingShapeMode == 24 || drawingShapeMode == 25)
|
||||||
|
{
|
||||||
|
if (drawMultiStepShapeCurrentStep == 0)
|
||||||
|
iniP = inkTouchPoint.Position;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
iniP = inkTouchPoint.Position;
|
||||||
|
}
|
||||||
|
lastTouchDownStrokeCollection = inkCanvas.Strokes.Clone();
|
||||||
|
}
|
||||||
|
dec.Add(e.TouchDevice.Id);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
SetCursorBasedOnEditingMode(inkCanvas);
|
||||||
inkCanvas.CaptureTouch(e.TouchDevice);
|
inkCanvas.CaptureTouch(e.TouchDevice);
|
||||||
ViewboxFloatingBar.IsHitTestVisible = false;
|
ViewboxFloatingBar.IsHitTestVisible = false;
|
||||||
BlackboardUIGridForInkReplay.IsHitTestVisible = false;
|
BlackboardUIGridForInkReplay.IsHitTestVisible = false;
|
||||||
|
lastTouchDownTime = DateTime.Now;
|
||||||
dec.Add(e.TouchDevice.Id);
|
dec.Add(e.TouchDevice.Id);
|
||||||
//设备1个的时候,记录中心点
|
|
||||||
|
if (Settings.Canvas.EnablePalmEraser && !isPalmEraserActive && drawingShapeMode == 0)
|
||||||
|
{
|
||||||
|
var touchPoint = e.GetTouchPoint(inkCanvas);
|
||||||
|
double boundWidth = GetTouchBoundWidth(e);
|
||||||
|
|
||||||
|
if ((Settings.Advanced.TouchMultiplier != 0 || !Settings.Advanced.IsSpecialScreen)
|
||||||
|
&& (boundWidth > BoundsWidth))
|
||||||
|
{
|
||||||
|
double thresholdMultiplier;
|
||||||
|
switch (Settings.Canvas.PalmEraserSensitivity)
|
||||||
|
{
|
||||||
|
case 0:
|
||||||
|
thresholdMultiplier = 3.0;
|
||||||
|
break;
|
||||||
|
case 1:
|
||||||
|
thresholdMultiplier = 2.5;
|
||||||
|
break;
|
||||||
|
case 2:
|
||||||
|
default:
|
||||||
|
thresholdMultiplier = 2.0;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
double EraserThresholdValue = Settings.Startup.IsEnableNibMode
|
||||||
|
? Settings.Advanced.NibModeBoundsWidthThresholdValue
|
||||||
|
: Settings.Advanced.FingerModeBoundsWidthThresholdValue;
|
||||||
|
|
||||||
|
if (boundWidth > BoundsWidth * EraserThresholdValue * thresholdMultiplier)
|
||||||
|
{
|
||||||
|
boundWidth *= Settings.Startup.IsEnableNibMode
|
||||||
|
? Settings.Advanced.NibModeBoundsWidthEraserSize
|
||||||
|
: Settings.Advanced.FingerModeBoundsWidthEraserSize;
|
||||||
|
|
||||||
|
if (Settings.Advanced.IsSpecialScreen)
|
||||||
|
boundWidth *= Settings.Advanced.TouchMultiplier;
|
||||||
|
inkCanvas.EditingMode = InkCanvasEditingMode.EraseByPoint;
|
||||||
|
isPalmEraserActive = true;
|
||||||
|
|
||||||
|
EnableEraserOverlay();
|
||||||
|
eraserWidth = boundWidth;
|
||||||
|
UpdateEraserStyle();
|
||||||
|
touchPoint = e.GetTouchPoint(inkCanvas);
|
||||||
|
EraserOverlay_PointerDown(sender);
|
||||||
|
EraserOverlay_PointerMove(sender, touchPoint.Position);
|
||||||
|
if (Settings.Canvas.IsShowCursor)
|
||||||
|
{
|
||||||
|
inkCanvas.ForceCursor = false;
|
||||||
|
inkCanvas.UseCustomCursor = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (dec.Count == 1)
|
if (dec.Count == 1)
|
||||||
{
|
{
|
||||||
var touchPoint = e.GetTouchPoint(inkCanvas);
|
var touchPoint = e.GetTouchPoint(inkCanvas);
|
||||||
centerPoint = touchPoint.Position;
|
centerPoint = touchPoint.Position;
|
||||||
|
|
||||||
//记录第一根手指点击时的 StrokeCollection
|
|
||||||
lastTouchDownStrokeCollection = inkCanvas.Strokes.Clone();
|
lastTouchDownStrokeCollection = inkCanvas.Strokes.Clone();
|
||||||
}
|
}
|
||||||
//设备两个及两个以上,将画笔功能关闭
|
|
||||||
if (dec.Count > 1 || isSingleFingerDragMode || !Settings.Gesture.IsEnableTwoFingerGesture)
|
if (dec.Count > 1 || isSingleFingerDragMode || !Settings.Gesture.IsEnableTwoFingerGesture)
|
||||||
{
|
{
|
||||||
if (isInMultiTouchMode || !Settings.Gesture.IsEnableTwoFingerGesture) return;
|
if (isInMultiTouchMode || !Settings.Gesture.IsEnableTwoFingerGesture) return;
|
||||||
if (inkCanvas.EditingMode == InkCanvasEditingMode.None ||
|
if (inkCanvas.EditingMode == InkCanvasEditingMode.None ||
|
||||||
inkCanvas.EditingMode == InkCanvasEditingMode.Select) return;
|
inkCanvas.EditingMode == InkCanvasEditingMode.Select) return;
|
||||||
|
var timeSinceLastTouch = (DateTime.Now - lastTouchDownTime).TotalMilliseconds;
|
||||||
|
if (timeSinceLastTouch < MULTI_TOUCH_DELAY_MS && inkCanvas.EditingMode == InkCanvasEditingMode.Ink)
|
||||||
|
{
|
||||||
|
if (!isMultiTouchTimerActive)
|
||||||
|
{
|
||||||
|
isMultiTouchTimerActive = true;
|
||||||
|
var remainingTime = MULTI_TOUCH_DELAY_MS - timeSinceLastTouch;
|
||||||
|
Task.Delay((int)remainingTime).ContinueWith(_ =>
|
||||||
|
{
|
||||||
|
Dispatcher.Invoke(() =>
|
||||||
|
{
|
||||||
|
if (dec.Count > 1 && inkCanvas.EditingMode == InkCanvasEditingMode.Ink)
|
||||||
|
inkCanvas.EditingMode = InkCanvasEditingMode.None;
|
||||||
|
isMultiTouchTimerActive = false;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
lastInkCanvasEditingMode = inkCanvas.EditingMode;
|
lastInkCanvasEditingMode = inkCanvas.EditingMode;
|
||||||
inkCanvas.EditingMode = InkCanvasEditingMode.None;
|
if (inkCanvas.EditingMode != InkCanvasEditingMode.EraseByPoint
|
||||||
|
&& inkCanvas.EditingMode != InkCanvasEditingMode.EraseByStroke
|
||||||
|
&& drawingShapeMode == 0)
|
||||||
|
{
|
||||||
|
inkCanvas.EditingMode = InkCanvasEditingMode.None;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -792,6 +918,11 @@ namespace Ink_Canvas
|
|||||||
/// </remarks>
|
/// </remarks>
|
||||||
private void InkCanvas_PreviewTouchMove(object sender, TouchEventArgs e)
|
private void InkCanvas_PreviewTouchMove(object sender, TouchEventArgs e)
|
||||||
{
|
{
|
||||||
|
if (isPalmEraserActive)
|
||||||
|
{
|
||||||
|
var touchPoint = e.GetTouchPoint(inkCanvas);
|
||||||
|
EraserOverlay_PointerMove(sender, touchPoint.Position);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -818,32 +949,18 @@ namespace Ink_Canvas
|
|||||||
/// </remarks>
|
/// </remarks>
|
||||||
private void InkCanvas_PreviewTouchUp(object sender, TouchEventArgs e)
|
private void InkCanvas_PreviewTouchUp(object sender, TouchEventArgs e)
|
||||||
{
|
{
|
||||||
|
if (inkCanvas.EditingMode == InkCanvasEditingMode.EraseByPoint && !isPalmEraserActive)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
inkCanvas.ReleaseAllTouchCaptures();
|
inkCanvas.ReleaseAllTouchCaptures();
|
||||||
ViewboxFloatingBar.IsHitTestVisible = true;
|
ViewboxFloatingBar.IsHitTestVisible = true;
|
||||||
BlackboardUIGridForInkReplay.IsHitTestVisible = true;
|
BlackboardUIGridForInkReplay.IsHitTestVisible = true;
|
||||||
|
|
||||||
//手势完成后切回之前的状态
|
|
||||||
if (dec.Count > 1)
|
|
||||||
if (inkCanvas.EditingMode == InkCanvasEditingMode.None)
|
|
||||||
inkCanvas.EditingMode = lastInkCanvasEditingMode;
|
|
||||||
dec.Remove(e.TouchDevice.Id);
|
dec.Remove(e.TouchDevice.Id);
|
||||||
|
|
||||||
if (dec.Count == 0)
|
if (dec.Count <= 1)
|
||||||
{
|
isMultiTouchTimerActive = false;
|
||||||
isSingleFingerDragMode = false;
|
|
||||||
isWaitUntilNextTouchDown = false;
|
|
||||||
if (drawingShapeMode == 0
|
|
||||||
&& inkCanvas.EditingMode != InkCanvasEditingMode.EraseByPoint
|
|
||||||
&& inkCanvas.EditingMode != InkCanvasEditingMode.EraseByStroke
|
|
||||||
&& inkCanvas.EditingMode != InkCanvasEditingMode.Select
|
|
||||||
&& inkCanvas.EditingMode != InkCanvasEditingMode.None)
|
|
||||||
{
|
|
||||||
if (lastInkCanvasEditingMode != InkCanvasEditingMode.None)
|
|
||||||
{
|
|
||||||
inkCanvas.EditingMode = lastInkCanvasEditingMode;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (drawingShapeMode != 0)
|
if (drawingShapeMode != 0)
|
||||||
{
|
{
|
||||||
@@ -855,12 +972,10 @@ namespace Ink_Canvas
|
|||||||
{
|
{
|
||||||
if (drawMultiStepShapeCurrentStep == 0)
|
if (drawMultiStepShapeCurrentStep == 0)
|
||||||
{
|
{
|
||||||
// 第一笔完成,进入第二笔
|
|
||||||
drawMultiStepShapeCurrentStep = 1;
|
drawMultiStepShapeCurrentStep = 1;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
// 第二笔完成,完成绘制
|
|
||||||
var mouseArgs = new MouseButtonEventArgs(Mouse.PrimaryDevice, 0, MouseButton.Left)
|
var mouseArgs = new MouseButtonEventArgs(Mouse.PrimaryDevice, 0, MouseButton.Left)
|
||||||
{
|
{
|
||||||
RoutedEvent = MouseLeftButtonUpEvent,
|
RoutedEvent = MouseLeftButtonUpEvent,
|
||||||
@@ -880,6 +995,36 @@ namespace Ink_Canvas
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (drawingShapeMode == 0)
|
||||||
|
{
|
||||||
|
if (dec.Count > 1)
|
||||||
|
{
|
||||||
|
if (inkCanvas.EditingMode == InkCanvasEditingMode.None)
|
||||||
|
{
|
||||||
|
if (lastInkCanvasEditingMode != InkCanvasEditingMode.EraseByPoint)
|
||||||
|
inkCanvas.EditingMode = lastInkCanvasEditingMode;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (dec.Count == 0)
|
||||||
|
{
|
||||||
|
isSingleFingerDragMode = false;
|
||||||
|
isWaitUntilNextTouchDown = false;
|
||||||
|
|
||||||
|
if (inkCanvas.EditingMode == InkCanvasEditingMode.None &&
|
||||||
|
lastInkCanvasEditingMode != InkCanvasEditingMode.None &&
|
||||||
|
lastInkCanvasEditingMode != InkCanvasEditingMode.EraseByPoint)
|
||||||
|
{
|
||||||
|
inkCanvas.EditingMode = lastInkCanvasEditingMode;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isPalmEraserActive)
|
||||||
|
{
|
||||||
|
isPalmEraserActive = false;
|
||||||
|
DisableEraserOverlay();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
inkCanvas.Opacity = 1;
|
inkCanvas.Opacity = 1;
|
||||||
|
|
||||||
if (dec.Count == 0)
|
if (dec.Count == 0)
|
||||||
@@ -977,6 +1122,9 @@ namespace Ink_Canvas
|
|||||||
/// </remarks>
|
/// </remarks>
|
||||||
private void Main_Grid_ManipulationDelta(object sender, ManipulationDeltaEventArgs e)
|
private void Main_Grid_ManipulationDelta(object sender, ManipulationDeltaEventArgs e)
|
||||||
{
|
{
|
||||||
|
if (inkCanvas.EditingMode == InkCanvasEditingMode.EraseByPoint)
|
||||||
|
return;
|
||||||
|
|
||||||
if (isInMultiTouchMode || !Settings.Gesture.IsEnableTwoFingerGesture) return;
|
if (isInMultiTouchMode || !Settings.Gesture.IsEnableTwoFingerGesture) return;
|
||||||
|
|
||||||
bool hasMultipleManipulators = e.Manipulators.Count() >= 2;
|
bool hasMultipleManipulators = e.Manipulators.Count() >= 2;
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
using Hardcodet.Wpf.TaskbarNotification;
|
using H.NotifyIcon;
|
||||||
using Ink_Canvas.Helpers;
|
using Ink_Canvas.Helpers;
|
||||||
using iNKORE.UI.WPF.Controls;
|
using iNKORE.UI.WPF.Controls;
|
||||||
using System;
|
using System;
|
||||||
@@ -9,6 +9,7 @@ using System.Windows;
|
|||||||
using System.Windows.Controls;
|
using System.Windows.Controls;
|
||||||
using System.Windows.Forms;
|
using System.Windows.Forms;
|
||||||
using System.Windows.Interop;
|
using System.Windows.Interop;
|
||||||
|
using System.Windows.Threading;
|
||||||
using Application = System.Windows.Application;
|
using Application = System.Windows.Application;
|
||||||
using ContextMenu = System.Windows.Controls.ContextMenu;
|
using ContextMenu = System.Windows.Controls.ContextMenu;
|
||||||
using MenuItem = System.Windows.Controls.MenuItem;
|
using MenuItem = System.Windows.Controls.MenuItem;
|
||||||
@@ -17,6 +18,11 @@ namespace Ink_Canvas
|
|||||||
{
|
{
|
||||||
public partial class App : Application
|
public partial class App : Application
|
||||||
{
|
{
|
||||||
|
private const int TrayTemporaryShowMinutes = 2;
|
||||||
|
|
||||||
|
private DispatcherTimer _trayTemporaryShowTimer;
|
||||||
|
|
||||||
|
private bool _trayTemporaryShowRestoreHideChecked;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 系统托盘菜单打开时的事件处理方法
|
/// 系统托盘菜单打开时的事件处理方法
|
||||||
@@ -131,6 +137,85 @@ namespace Ink_Canvas
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void TempShowMainWindowTrayIconMenuItem_Clicked(object sender, RoutedEventArgs e)
|
||||||
|
{
|
||||||
|
var mainWin = Current.MainWindow as MainWindow;
|
||||||
|
if (mainWin?.IsLoaded != true)
|
||||||
|
return;
|
||||||
|
|
||||||
|
MenuItem hideItem = null;
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var trayMenu = ((TaskbarIcon)Current.Resources["TaskbarTrayIcon"]).ContextMenu;
|
||||||
|
hideItem = trayMenu?.Items.OfType<MenuItem>()
|
||||||
|
.FirstOrDefault(mi => mi.Name == "HideICCMainWindowTrayIconMenuItem");
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
_trayTemporaryShowRestoreHideChecked = hideItem?.IsChecked == true;
|
||||||
|
|
||||||
|
EnsureMainWindowReadyForSettings(mainWin);
|
||||||
|
|
||||||
|
global::Ink_Canvas.MainWindow.TrayTemporaryShowUntilUtc = DateTime.UtcNow.AddMinutes(TrayTemporaryShowMinutes);
|
||||||
|
|
||||||
|
_trayTemporaryShowTimer?.Stop();
|
||||||
|
if (_trayTemporaryShowTimer == null)
|
||||||
|
{
|
||||||
|
_trayTemporaryShowTimer = new DispatcherTimer
|
||||||
|
{
|
||||||
|
Interval = TimeSpan.FromMinutes(TrayTemporaryShowMinutes)
|
||||||
|
};
|
||||||
|
_trayTemporaryShowTimer.Tick += TrayTemporaryShowTimer_OnTick;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_trayTemporaryShowTimer.Interval = TimeSpan.FromMinutes(TrayTemporaryShowMinutes);
|
||||||
|
}
|
||||||
|
|
||||||
|
_trayTemporaryShowTimer.Start();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void TrayTemporaryShowTimer_OnTick(object sender, EventArgs e)
|
||||||
|
{
|
||||||
|
_trayTemporaryShowTimer?.Stop();
|
||||||
|
global::Ink_Canvas.MainWindow.TrayTemporaryShowUntilUtc = null;
|
||||||
|
|
||||||
|
var mainWin = Current.MainWindow as MainWindow;
|
||||||
|
if (mainWin?.IsLoaded != true)
|
||||||
|
{
|
||||||
|
_trayTemporaryShowRestoreHideChecked = false;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (_trayTemporaryShowRestoreHideChecked)
|
||||||
|
{
|
||||||
|
var trayMenu = ((TaskbarIcon)Current.Resources["TaskbarTrayIcon"]).ContextMenu;
|
||||||
|
var hideItem = trayMenu?.Items.OfType<MenuItem>()
|
||||||
|
.FirstOrDefault(mi => mi.Name == "HideICCMainWindowTrayIconMenuItem");
|
||||||
|
if (hideItem != null)
|
||||||
|
hideItem.IsChecked = true;
|
||||||
|
else
|
||||||
|
mainWin.Hide();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
mainWin.CheckMainWindowVisibility();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
LogHelper.WriteLogToFile($"托盘临时显示计时结束处理失败: {ex.Message}", LogHelper.LogType.Warning);
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
_trayTemporaryShowRestoreHideChecked = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private void OpenSettingsTrayIconMenuItem_Clicked(object sender, RoutedEventArgs e)
|
private void OpenSettingsTrayIconMenuItem_Clicked(object sender, RoutedEventArgs e)
|
||||||
{
|
{
|
||||||
var mainWin = Current.MainWindow as MainWindow;
|
var mainWin = Current.MainWindow as MainWindow;
|
||||||
@@ -326,6 +411,10 @@ namespace Ink_Canvas
|
|||||||
/// </remarks>
|
/// </remarks>
|
||||||
private void HideICCMainWindowTrayIconMenuItem_Checked(object sender, RoutedEventArgs e)
|
private void HideICCMainWindowTrayIconMenuItem_Checked(object sender, RoutedEventArgs e)
|
||||||
{
|
{
|
||||||
|
_trayTemporaryShowTimer?.Stop();
|
||||||
|
global::Ink_Canvas.MainWindow.TrayTemporaryShowUntilUtc = null;
|
||||||
|
_trayTemporaryShowRestoreHideChecked = false;
|
||||||
|
|
||||||
var mi = (MenuItem)sender;
|
var mi = (MenuItem)sender;
|
||||||
var mainWin = (MainWindow)Current.MainWindow;
|
var mainWin = (MainWindow)Current.MainWindow;
|
||||||
if (mainWin != null && mainWin.IsLoaded)
|
if (mainWin != null && mainWin.IsLoaded)
|
||||||
|
|||||||
@@ -43,5 +43,5 @@ using System.Windows;
|
|||||||
// You can specify all the values or you can default the Build and Revision Numbers
|
// You can specify all the values or you can default the Build and Revision Numbers
|
||||||
// by using the '*' as shown below:
|
// by using the '*' as shown below:
|
||||||
// [assembly: AssemblyVersion("1.0.*")]
|
// [assembly: AssemblyVersion("1.0.*")]
|
||||||
[assembly: AssemblyVersion("1.7.18.9")]
|
[assembly: AssemblyVersion("1.7.18.10")]
|
||||||
[assembly: AssemblyFileVersion("1.7.18.9")]
|
[assembly: AssemblyFileVersion("1.7.18.10")]
|
||||||
|
|||||||
+3
-1
@@ -19,7 +19,7 @@ namespace Ink_Canvas.Properties
|
|||||||
[CompilerGenerated]
|
[CompilerGenerated]
|
||||||
public static class Strings
|
public static class Strings
|
||||||
{
|
{
|
||||||
private const string EmbeddedEnUsResxName = "Ink_Canvas.Properties.Strings.enUS.xml";
|
private const string EmbeddedEnUsResxName = "Ink_Canvas.Properties.Strings.en-US.resx";
|
||||||
private static readonly object EnUsLock = new object();
|
private static readonly object EnUsLock = new object();
|
||||||
private static Dictionary<string, string> _embeddedEnUs;
|
private static Dictionary<string, string> _embeddedEnUs;
|
||||||
private static ResourceManager _resourceMan;
|
private static ResourceManager _resourceMan;
|
||||||
@@ -119,5 +119,7 @@ namespace Ink_Canvas.Properties
|
|||||||
public static string Nav_About => GetString(nameof(Nav_About)) ?? "关于";
|
public static string Nav_About => GetString(nameof(Nav_About)) ?? "关于";
|
||||||
public static string App_Title => GetString(nameof(App_Title)) ?? "InkCanvasforClass";
|
public static string App_Title => GetString(nameof(App_Title)) ?? "InkCanvasforClass";
|
||||||
public static string Booth_Resolution_Tooltip => GetString(nameof(Booth_Resolution_Tooltip)) ?? "展台/截图分辨率";
|
public static string Booth_Resolution_Tooltip => GetString(nameof(Booth_Resolution_Tooltip)) ?? "展台/截图分辨率";
|
||||||
|
public static string Tray_TempShowMainWindow => GetString(nameof(Tray_TempShowMainWindow)) ?? "显示主窗口(2分钟)";
|
||||||
|
public static string Tray_OpenSettings => GetString(nameof(Tray_OpenSettings)) ?? "打开设置";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -1,766 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<root>
|
|
||||||
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
|
|
||||||
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
|
|
||||||
<xsd:element name="root" msdata:IsDataSet="true">
|
|
||||||
<xsd:complexType>
|
|
||||||
<xsd:choice maxOccurs="unbounded">
|
|
||||||
<xsd:element name="metadata">
|
|
||||||
<xsd:complexType>
|
|
||||||
<xsd:sequence>
|
|
||||||
<xsd:element name="value" type="xsd:string" minOccurs="0" />
|
|
||||||
</xsd:sequence>
|
|
||||||
<xsd:attribute name="name" use="required" type="xsd:string" />
|
|
||||||
<xsd:attribute name="type" type="xsd:string" />
|
|
||||||
<xsd:attribute name="mimetype" type="xsd:string" />
|
|
||||||
<xsd:attribute ref="xml:space" />
|
|
||||||
</xsd:complexType>
|
|
||||||
</xsd:element>
|
|
||||||
<xsd:element name="assembly">
|
|
||||||
<xsd:complexType>
|
|
||||||
<xsd:attribute name="alias" type="xsd:string" />
|
|
||||||
<xsd:attribute name="name" type="xsd:string" />
|
|
||||||
</xsd:complexType>
|
|
||||||
</xsd:element>
|
|
||||||
<xsd:element name="data">
|
|
||||||
<xsd:complexType>
|
|
||||||
<xsd:sequence>
|
|
||||||
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
|
|
||||||
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
|
|
||||||
</xsd:sequence>
|
|
||||||
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
|
|
||||||
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
|
|
||||||
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
|
|
||||||
<xsd:attribute ref="xml:space" />
|
|
||||||
</xsd:complexType>
|
|
||||||
</xsd:element>
|
|
||||||
<xsd:element name="resheader">
|
|
||||||
<xsd:complexType>
|
|
||||||
<xsd:sequence>
|
|
||||||
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
|
|
||||||
</xsd:sequence>
|
|
||||||
<xsd:attribute name="name" type="xsd:string" use="required" />
|
|
||||||
</xsd:complexType>
|
|
||||||
</xsd:element>
|
|
||||||
</xsd:choice>
|
|
||||||
</xsd:complexType>
|
|
||||||
</xsd:element>
|
|
||||||
</xsd:schema>
|
|
||||||
<resheader name="resmimetype">
|
|
||||||
<value>text/microsoft-resx</value>
|
|
||||||
</resheader>
|
|
||||||
<resheader name="version">
|
|
||||||
<value>2.0</value>
|
|
||||||
</resheader>
|
|
||||||
<resheader name="reader">
|
|
||||||
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
|
||||||
</resheader>
|
|
||||||
<resheader name="writer">
|
|
||||||
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
|
||||||
</resheader>
|
|
||||||
<data name="Nav_Plugins" xml:space="preserve">
|
|
||||||
<value>Plugins</value>
|
|
||||||
</data>
|
|
||||||
<data name="Nav_Startup" xml:space="preserve">
|
|
||||||
<value>Startup</value>
|
|
||||||
</data>
|
|
||||||
<data name="Nav_Canvas" xml:space="preserve">
|
|
||||||
<value>Canvas</value>
|
|
||||||
</data>
|
|
||||||
<data name="Nav_CrashAction" xml:space="preserve">
|
|
||||||
<value>Crash Action</value>
|
|
||||||
</data>
|
|
||||||
<data name="Nav_Gesture" xml:space="preserve">
|
|
||||||
<value>Gesture</value>
|
|
||||||
</data>
|
|
||||||
<data name="Nav_InkRecognition" xml:space="preserve">
|
|
||||||
<value>Ink Recognition</value>
|
|
||||||
</data>
|
|
||||||
<data name="Nav_PPT" xml:space="preserve">
|
|
||||||
<value>PPT</value>
|
|
||||||
</data>
|
|
||||||
<data name="Nav_Advanced" xml:space="preserve">
|
|
||||||
<value>Advanced</value>
|
|
||||||
</data>
|
|
||||||
<data name="Nav_Automation" xml:space="preserve">
|
|
||||||
<value>Automation</value>
|
|
||||||
</data>
|
|
||||||
<data name="Nav_RandomWindow" xml:space="preserve">
|
|
||||||
<value>Random Picker</value>
|
|
||||||
</data>
|
|
||||||
<data name="Nav_Theme" xml:space="preserve">
|
|
||||||
<value>Theme</value>
|
|
||||||
</data>
|
|
||||||
<data name="Nav_Shortcuts" xml:space="preserve">
|
|
||||||
<value>Shortcuts</value>
|
|
||||||
</data>
|
|
||||||
<data name="Nav_About" xml:space="preserve">
|
|
||||||
<value>About</value>
|
|
||||||
</data>
|
|
||||||
<data name="App_Title" xml:space="preserve">
|
|
||||||
<value>InkCanvasforClass</value>
|
|
||||||
</data>
|
|
||||||
<data name="Booth_Resolution_Tooltip" xml:space="preserve">
|
|
||||||
<value>Booth / Screenshot resolution</value>
|
|
||||||
</data>
|
|
||||||
<data name="Nav_Gesture_Settings" xml:space="preserve"><value>Gesture</value></data>
|
|
||||||
<data name="Nav_Theme_Settings" xml:space="preserve"><value>Appearance</value></data>
|
|
||||||
<data name="Nav_PPT_Settings" xml:space="preserve"><value>PPT</value></data>
|
|
||||||
<data name="Nav_Advanced_Settings" xml:space="preserve"><value>Advanced</value></data>
|
|
||||||
<data name="Nav_Automation_Settings" xml:space="preserve"><value>Automation</value></data>
|
|
||||||
<data name="Nav_RandomWindow_Settings" xml:space="preserve"><value>Random Picker</value></data>
|
|
||||||
<data name="Nav_Shortcuts_Settings" xml:space="preserve"><value>Shortcuts</value></data>
|
|
||||||
<data name="CollapseNavSidebar" xml:space="preserve"><value>Collapse sidebar</value></data>
|
|
||||||
<data name="ShowNavSidebar" xml:space="preserve"><value>Show sidebar</value></data>
|
|
||||||
<data name="Tooltip_IccProtocol" xml:space="preserve"><value>Control via icc:// protocol</value></data>
|
|
||||||
<data name="Settings_Title" xml:space="preserve"><value>Settings</value></data>
|
|
||||||
<data name="Settings_AutoSaveHint" xml:space="preserve"><value>Changes are saved automatically; some require restart.</value></data>
|
|
||||||
<data name="Btn_Restart" xml:space="preserve"><value>Restart</value></data>
|
|
||||||
<data name="Btn_Reset" xml:space="preserve"><value>Reset</value></data>
|
|
||||||
<data name="Btn_Exit" xml:space="preserve"><value>Exit</value></data>
|
|
||||||
<data name="Settings_Mode" xml:space="preserve"><value>Mode</value></data>
|
|
||||||
<data name="Settings_ModeDesc" xml:space="preserve"><value>Choose run mode. In PPT-only mode the app is hidden until slide show. (Experimental)</value></data>
|
|
||||||
<data name="Mode_Normal" xml:space="preserve"><value>Normal</value></data>
|
|
||||||
<data name="Mode_PPTOnly" xml:space="preserve"><value>PPT only</value></data>
|
|
||||||
<data name="Settings_NewWindow" xml:space="preserve"><value>New settings window</value></data>
|
|
||||||
<data name="Settings_NewWindowDesc" xml:space="preserve"><value>Open a new settings window. (In development)</value></data>
|
|
||||||
<data name="Btn_OpenNewSettings" xml:space="preserve"><value>Open new settings</value></data>
|
|
||||||
<data name="Settings_Plugins" xml:space="preserve"><value>Plugins</value></data>
|
|
||||||
<data name="Settings_PluginsDesc" xml:space="preserve"><value>Extend Ink Canvas with plugins. Enable, disable, or load custom plugins.</value></data>
|
|
||||||
<data name="Btn_OpenPluginManager" xml:space="preserve"><value>Open plugin manager</value></data>
|
|
||||||
<data name="Startup_Start" xml:space="preserve"><value>Startup</value></data>
|
|
||||||
<data name="Startup_NoFocusMode" xml:space="preserve"><value>No-focus mode</value></data>
|
|
||||||
<data name="Startup_NoBorderMode" xml:space="preserve"><value>Borderless</value></data>
|
|
||||||
<data name="Startup_TopMost" xml:space="preserve"><value>Topmost</value></data>
|
|
||||||
<data name="Startup_UIATopMost" xml:space="preserve"><value>UIA topmost</value></data>
|
|
||||||
<data name="Startup_UIATopMostHint" xml:space="preserve"><value># UIA topmost requires admin to take effect.</value></data>
|
|
||||||
<data name="Header_AutoUpdate" xml:space="preserve"><value>Auto-update</value></data>
|
|
||||||
<data name="Header_SilentUpdate" xml:space="preserve"><value>Silent update</value></data>
|
|
||||||
<data name="SilentUpdate_Hint" xml:space="preserve"><value># Silent update installs when app is idle.</value></data>
|
|
||||||
<data name="Update_Channel" xml:space="preserve"><value>Update channel</value></data>
|
|
||||||
<data name="Channel_Release" xml:space="preserve"><value>Stable (Release)</value></data>
|
|
||||||
<data name="Channel_Preview" xml:space="preserve"><value>Preview</value></data>
|
|
||||||
<data name="Channel_Beta" xml:space="preserve"><value>Beta</value></data>
|
|
||||||
<data name="Channel_Hint" xml:space="preserve"><value># Stable for reliability; Preview for new features.</value></data>
|
|
||||||
<data name="Btn_ManualUpdate" xml:space="preserve"><value>Check for updates</value></data>
|
|
||||||
<data name="ManualUpdate_Hint" xml:space="preserve"><value># Check and download now.</value></data>
|
|
||||||
<data name="Btn_VersionFix" xml:space="preserve"><value>Version fix</value></data>
|
|
||||||
<data name="VersionFix_Hint" xml:space="preserve"><value># Download and install latest for current channel.</value></data>
|
|
||||||
<data name="Btn_Rollback" xml:space="preserve"><value>Rollback</value></data>
|
|
||||||
<data name="Rollback_Hint" xml:space="preserve"><value># Open rollback page.</value></data>
|
|
||||||
<data name="SilentUpdate_AfterDownloadHint" xml:space="preserve"><value># When silent update is off, you will be prompted after download.</value></data>
|
|
||||||
<data name="SilentUpdate_TimeRange" xml:space="preserve"><value>Silent update time range</value></data>
|
|
||||||
<data name="Time_Start" xml:space="preserve"><value>Start time</value></data>
|
|
||||||
<data name="Time_End" xml:space="preserve"><value>End time</value></data>
|
|
||||||
<data name="TimeRange_Hint" xml:space="preserve"><value># If end < start…</value></data>
|
|
||||||
<data name="Startup_RunAtLogin" xml:space="preserve"><value>Run at login</value></data>
|
|
||||||
<data name="Startup_MinimizeToSidebar" xml:space="preserve"><value>Minimize to sidebar at startup</value></data>
|
|
||||||
<data name="Canvas_AndInk" xml:space="preserve"><value>Canvas & ink</value></data>
|
|
||||||
<data name="Canvas_ShowPenCursor" xml:space="preserve"><value>Show pen cursor</value></data>
|
|
||||||
<data name="Canvas_PressureTouch" xml:space="preserve"><value>Pressure-sensitive touch</value></data>
|
|
||||||
<data name="Canvas_PressureTouchHint" xml:space="preserve"><value># Touch devices will support pressure.</value></data>
|
|
||||||
<data name="Canvas_IgnorePressure" xml:space="preserve"><value>Ignore pressure</value></data>
|
|
||||||
<data name="Canvas_IgnorePressureHint" xml:space="preserve"><value># Ignore all device pressure.</value></data>
|
|
||||||
<data name="Canvas_EraserSize" xml:space="preserve"><value>Eraser size</value></data>
|
|
||||||
<data name="Size_VerySmall" xml:space="preserve"><value>Very small</value></data>
|
|
||||||
<data name="Size_Small" xml:space="preserve"><value>Small</value></data>
|
|
||||||
<data name="Size_Medium" xml:space="preserve"><value>Medium</value></data>
|
|
||||||
<data name="Size_Large" xml:space="preserve"><value>Large</value></data>
|
|
||||||
<data name="Size_VeryLarge" xml:space="preserve"><value>Very large</value></data>
|
|
||||||
<data name="EraserSize_SwitchHint" xml:space="preserve"><value># Takes effect on next area eraser use.</value></data>
|
|
||||||
<data name="Canvas_HideInkOnExit" xml:space="preserve"><value>Hide ink when leaving canvas</value></data>
|
|
||||||
<data name="Canvas_HideInkOnExitHint" xml:space="preserve"><value># When enabled…</value></data>
|
|
||||||
<data name="Canvas_ClearInkHistory" xml:space="preserve"><value>Clear ink history when clearing</value></data>
|
|
||||||
<data name="Canvas_ClearImageOnClear" xml:space="preserve"><value>Clear images with canvas</value></data>
|
|
||||||
<data name="Canvas_CompressImage" xml:space="preserve"><value>Compress images >1920×1080</value></data>
|
|
||||||
<data name="Canvas_KeepAsymptote" xml:space="preserve"><value>Keep hyperbola asymptotes</value></data>
|
|
||||||
<data name="Yes" xml:space="preserve"><value>Yes</value></data>
|
|
||||||
<data name="No" xml:space="preserve"><value>No</value></data>
|
|
||||||
<data name="AskEachTime" xml:space="preserve"><value>Ask each time</value></data>
|
|
||||||
<data name="Canvas_AsymptoteHint" xml:space="preserve"><value># Disabling may cause undo bugs.</value></data>
|
|
||||||
<data name="Canvas_ShowCircleCenter" xml:space="preserve"><value>Show circle center</value></data>
|
|
||||||
<data name="Canvas_LaunchSeewoVideoShowcaseForWhiteboardBooth" xml:space="preserve"><value>Whiteboard booth opens Seewo Video Showcase</value></data>
|
|
||||||
<data name="Canvas_LaunchSeewoVideoShowcaseForWhiteboardBoothHint" xml:space="preserve"><value>When enabled, the whiteboard toolbar Booth button launches Seewo Video Showcase (must be installed). When disabled, the built-in booth is used.</value></data>
|
|
||||||
<data name="Canvas_WPFBezier" xml:space="preserve"><value>WPF default Bezier smoothing</value></data>
|
|
||||||
<data name="Canvas_AdvancedSmoothing" xml:space="preserve"><value>Advanced curve smoothing (recommended)</value></data>
|
|
||||||
<data name="Canvas_InkFade" xml:space="preserve"><value>Ink fade</value></data>
|
|
||||||
<data name="Canvas_InkFadeHint" xml:space="preserve"><value># Ink will not be drawn on canvas when enabled.</value></data>
|
|
||||||
<data name="Canvas_InkFadeTime" xml:space="preserve"><value>Ink fade time</value></data>
|
|
||||||
<data name="Canvas_HideFadeInPenMenu" xml:space="preserve"><value>Hide fade in pen menu</value></data>
|
|
||||||
<data name="Canvas_HideFadeInPenMenuHint" xml:space="preserve"><value># Fade control will be hidden in pen context menu.</value></data>
|
|
||||||
<data name="Color" xml:space="preserve"><value>Color</value></data>
|
|
||||||
<data name="Color_Default" xml:space="preserve"><value>Default</value></data>
|
|
||||||
<data name="Color_Black" xml:space="preserve"><value>Black</value></data>
|
|
||||||
<data name="Color_White" xml:space="preserve"><value>White</value></data>
|
|
||||||
<data name="Color_Red" xml:space="preserve"><value>Red</value></data>
|
|
||||||
<data name="Color_Yellow" xml:space="preserve"><value>Yellow</value></data>
|
|
||||||
<data name="Color_Blue" xml:space="preserve"><value>Blue</value></data>
|
|
||||||
<data name="Color_Green" xml:space="preserve"><value>Green</value></data>
|
|
||||||
<data name="Color_Orange" xml:space="preserve"><value>Orange</value></data>
|
|
||||||
<data name="Color_Purple" xml:space="preserve"><value>Purple</value></data>
|
|
||||||
<data name="Msg_UpdateReady" xml:space="preserve"><value>Update downloaded. It will install when you close the app.</value></data>
|
|
||||||
<data name="Msg_UpdateReadyTitle" xml:space="preserve"><value>Update ready</value></data>
|
|
||||||
<data name="Msg_UpdateDownloadFailed" xml:space="preserve"><value>Update download failed. Please check your network and try again.</value></data>
|
|
||||||
<data name="Msg_DownloadFailedTitle" xml:space="preserve"><value>Download failed</value></data>
|
|
||||||
<data name="Msg_SkipVersion" xml:space="preserve"><value>Version {0} skipped; you will not be prompted until a newer version is released.</value></data>
|
|
||||||
<data name="Msg_SkipVersionTitle" xml:space="preserve"><value>Version skipped</value></data>
|
|
||||||
<data name="Msg_UnexpectedError" xml:space="preserve"><value>An unexpected error occurred. Save your ink and restart the app.</value></data>
|
|
||||||
<data name="Msg_RestartLimitTitle" xml:space="preserve"><value>Too many restarts</value></data>
|
|
||||||
<data name="Msg_RestartLimit" xml:space="preserve"><value>App has restarted 5 times. Auto-restart stopped. Contact the developer or check the system.</value></data>
|
|
||||||
<data name="Splash_Starting" xml:space="preserve"><value>Starting Ink Canvas...</value></data>
|
|
||||||
<data name="Crash_Title" xml:space="preserve"><value>Crash action</value></data>
|
|
||||||
<data name="Crash_Desc" xml:space="preserve"><value>Choose what to do when an unhandled exception occurs:</value></data>
|
|
||||||
<data name="Crash_SilentRestart" xml:space="preserve"><value>Silent restart</value></data>
|
|
||||||
<data name="Crash_NoAction" xml:space="preserve"><value>No action</value></data>
|
|
||||||
<data name="Crash_Hint" xml:space="preserve"><value># Silent restart: automatically restart without prompt. No action: only log, do not restart.</value></data>
|
|
||||||
<data name="Gesture_Title" xml:space="preserve"><value>Gestures</value></data>
|
|
||||||
<data name="Gesture_AutoToggleTwoFinger" xml:space="preserve"><value>Auto-toggle two-finger move in/out of whiteboard</value></data>
|
|
||||||
<data name="Gesture_AutoToggleHint" xml:space="preserve"><value># When enabled: leaving canvas disables two-finger move; entering whiteboard enables it.</value></data>
|
|
||||||
<data name="Gesture_AllowRotateScale" xml:space="preserve"><value>Allow rotate & scale selected ink</value></data>
|
|
||||||
<data name="Gesture_AllowRotateScaleHint" xml:space="preserve"><value># Allows scaling selected ink with two or more fingers (independent of rotate setting).</value></data>
|
|
||||||
<data name="Gesture_EnablePalmEraser" xml:space="preserve"><value>Enable palm eraser</value></data>
|
|
||||||
<data name="Gesture_PalmSensitivity" xml:space="preserve"><value>Palm eraser sensitivity</value></data>
|
|
||||||
<data name="Gesture_PalmSensitivityLow" xml:space="preserve"><value>Low sensitivity</value></data>
|
|
||||||
<data name="Gesture_PalmSensitivityMedium" xml:space="preserve"><value>Medium sensitivity</value></data>
|
|
||||||
<data name="Gesture_PalmSensitivityHigh" xml:space="preserve"><value>High sensitivity</value></data>
|
|
||||||
<data name="Gesture_PalmHint" xml:space="preserve"><value># Low: larger area/more touches required (less false positive); High: easier to trigger but may mis-detect fingers.</value></data>
|
|
||||||
<data name="InkRecog_Title" xml:space="preserve"><value>Ink correction</value></data>
|
|
||||||
<data name="InkRecog_EnableInkRecognition" xml:space="preserve"><value>Enable ink recognition</value></data>
|
|
||||||
<data name="InkRecog_ShapeEngine" xml:space="preserve"><value>Recognition engine</value></data>
|
|
||||||
<data name="InkRecog_ShapeEngineHint" xml:space="preserve"><value># Auto: WinRT on 64-bit (Windows 10+), IACore on 32-bit. You can force IACore or WinRT.</value></data>
|
|
||||||
<data name="InkRecog_ShapeEngineAuto" xml:space="preserve"><value>Auto</value></data>
|
|
||||||
<data name="InkRecog_ShapeEngineIACore" xml:space="preserve"><value>IACore</value></data>
|
|
||||||
<data name="InkRecog_ShapeEngineWinRT" xml:space="preserve"><value>WinRT</value></data>
|
|
||||||
<data name="InkRecog_HandwritingBeautify" xml:space="preserve"><value>WinRT: recognized text to handwriting glyphs</value></data>
|
|
||||||
<data name="InkRecog_HandwritingBeautifyHint" xml:space="preserve"><value># When the ink correction API runs: WinRT recognizes ink words, then replaces recognized text with outline strokes from a handwriting-style font (default Ink Free / KaiTi / Segoe Script; set handwritingCorrectionFontFamily in Settings JSON). 64-bit + WinRT.</value></data>
|
|
||||||
<data name="InkRecog_BlockRectFakePressure" xml:space="preserve"><value>Block fake pressure on corrected rectangles</value></data>
|
|
||||||
<data name="InkRecog_BlockTriFakePressure" xml:space="preserve"><value>Block fake pressure on corrected triangles</value></data>
|
|
||||||
<data name="InkRecog_FixTriangle" xml:space="preserve"><value>Correct freehand triangles</value></data>
|
|
||||||
<data name="InkRecog_FixRectangle" xml:space="preserve"><value>Correct freehand rectangles</value></data>
|
|
||||||
<data name="InkRecog_FixEllipse" xml:space="preserve"><value>Correct circles and ellipses</value></data>
|
|
||||||
<data name="InkRecog_AutoStraightLine" xml:space="preserve"><value>Auto-straighten lines</value></data>
|
|
||||||
<data name="InkRecog_LengthThreshold" xml:space="preserve"><value>Length threshold</value></data>
|
|
||||||
<data name="InkRecog_Sensitivity" xml:space="preserve"><value>Sensitivity</value></data>
|
|
||||||
<data name="InkRecog_HighPrecisionStraighten" xml:space="preserve"><value>High-precision straightening</value></data>
|
|
||||||
<data name="InkRecog_HighPrecisionHint" xml:space="preserve"><value># When enabled, lines longer than the threshold will be straightened. Sensitivity 0.05–2.0: smaller = stricter; larger = easier to treat as straight. High-precision samples every 10px for better judgement.</value></data>
|
|
||||||
<data name="InkRecog_LineEndpointSnapping" xml:space="preserve"><value>Line endpoint snapping</value></data>
|
|
||||||
<data name="InkRecog_SnappingDistance" xml:space="preserve"><value>Snapping distance</value></data>
|
|
||||||
<data name="Theme_GroupTitle" xml:space="preserve"><value>Personalization</value></data>
|
|
||||||
<data name="Theme_Label" xml:space="preserve"><value>Theme</value></data>
|
|
||||||
<data name="Theme_Light" xml:space="preserve"><value>Light theme</value></data>
|
|
||||||
<data name="Theme_Dark" xml:space="preserve"><value>Dark theme</value></data>
|
|
||||||
<data name="Theme_System" xml:space="preserve"><value>Follow system</value></data>
|
|
||||||
<data name="Theme_EnableSplash" xml:space="preserve"><value>Enable startup animation</value></data>
|
|
||||||
<data name="Theme_SplashStyle" xml:space="preserve"><value>Startup animation style</value></data>
|
|
||||||
<data name="Theme_Splash_Random" xml:space="preserve"><value>Random</value></data>
|
|
||||||
<data name="Theme_Splash_Seasonal" xml:space="preserve"><value>Follow seasons</value></data>
|
|
||||||
<data name="Theme_Splash_Spring" xml:space="preserve"><value>Spring</value></data>
|
|
||||||
<data name="Theme_Splash_Summer" xml:space="preserve"><value>Summer</value></data>
|
|
||||||
<data name="Theme_Splash_Autumn" xml:space="preserve"><value>Autumn</value></data>
|
|
||||||
<data name="Theme_Splash_Winter" xml:space="preserve"><value>Winter</value></data>
|
|
||||||
<data name="Theme_Splash_Horse" xml:space="preserve"><value>Year-of-Horse special</value></data>
|
|
||||||
<data name="Theme_FloatingBarIcon" xml:space="preserve"><value>Floating toolbar icon</value></data>
|
|
||||||
<data name="Theme_FloatingIcon_IccDefault" xml:space="preserve"><value>“ICC-CE” default</value></data>
|
|
||||||
<data name="Theme_FloatingIcon_IccNoShadow" xml:space="preserve"><value>“ICC-CE” no shadow</value></data>
|
|
||||||
<data name="Theme_FloatingIcon_IccDark" xml:space="preserve"><value>“ICC-CE” dark</value></data>
|
|
||||||
<data name="Theme_FloatingIcon_IccDarkBreath" xml:space="preserve"><value>“ICC-CE” dark breathing</value></data>
|
|
||||||
<data name="Theme_FloatingIcon_IccWhiteTransparent" xml:space="preserve"><value>“ICC-CE” white transparent</value></data>
|
|
||||||
<data name="Theme_FloatingIcon_IccBlackTransparent" xml:space="preserve"><value>“ICC-CE” black transparent</value></data>
|
|
||||||
<data name="Theme_FloatingIcon_CoolapkCrossEye" xml:space="preserve"><value>Coolapk cross-eye emoji</value></data>
|
|
||||||
<data name="Theme_FloatingIcon_CoolapkAbused" xml:space="preserve"><value>Coolapk abused emoji</value></data>
|
|
||||||
<data name="Theme_FloatingIcon_CoolapkSmile" xml:space="preserve"><value>Coolapk grin emoji</value></data>
|
|
||||||
<data name="Theme_FloatingIcon_CoolapkUnderwear" xml:space="preserve"><value>Coolapk underwear emoji</value></data>
|
|
||||||
<data name="Theme_FloatingIcon_CoolapkGreenHatDoge" xml:space="preserve"><value>Coolapk green-hat Doge</value></data>
|
|
||||||
<data name="Theme_FloatingIcon_TiebaEmoji" xml:space="preserve"><value>Tieba emoji</value></data>
|
|
||||||
<data name="Theme_CustomFloatingIconLabel" xml:space="preserve"><value>Custom floating icon</value></data>
|
|
||||||
<data name="Theme_Upload" xml:space="preserve"><value>Upload</value></data>
|
|
||||||
<data name="Theme_Manage" xml:space="preserve"><value>Manage</value></data>
|
|
||||||
<data name="Theme_FloatingBarScale" xml:space="preserve"><value>Floating toolbar scale</value></data>
|
|
||||||
<data name="Theme_FloatingBarOpacity" xml:space="preserve"><value>Floating toolbar opacity</value></data>
|
|
||||||
<data name="Theme_FloatingBarOpacityInPPT" xml:space="preserve"><value>Floating bar opacity in PPT</value></data>
|
|
||||||
<data name="Theme_FloatingBarOpacityInPPTHint" xml:space="preserve"><value># Takes effect after re-entering slide show</value></data>
|
|
||||||
<data name="Theme_ShowNibButton" xml:space="preserve"><value>Show nib-mode button in palette</value></data>
|
|
||||||
<data name="Theme_BlackboardScale80" xml:space="preserve"><value>Whiteboard UI 80% scale</value></data>
|
|
||||||
<data name="Theme_ShowTimeInWhiteboard" xml:space="preserve"><value>Show time and date in whiteboard</value></data>
|
|
||||||
<data name="Theme_ShowQuoteInWhiteboard" xml:space="preserve"><value>Show quotes in whiteboard</value></data>
|
|
||||||
<data name="Theme_QuoteSource" xml:space="preserve"><value>Where is the quote from?</value></data>
|
|
||||||
<data name="Theme_QuoteSource_OsuQuotes" xml:space="preserve"><value>osu! player quotes</value></data>
|
|
||||||
<data name="Theme_QuoteSource_Mottos" xml:space="preserve"><value>Inspirational mottos</value></data>
|
|
||||||
<data name="Theme_QuoteSource_GaokaoBless" xml:space="preserve"><value>Gaokao blessings</value></data>
|
|
||||||
<data name="Theme_QuoteSource_Hitokoto" xml:space="preserve"><value>Hitokoto API</value></data>
|
|
||||||
<data name="Theme_Customize" xml:space="preserve"><value>Custom</value></data>
|
|
||||||
<data name="Theme_EnableQuickPanel" xml:space="preserve"><value>Enable quick panel in docked mode</value></data>
|
|
||||||
<data name="Theme_UnfoldButtonIcon" xml:space="preserve"><value>Un-dock button icon</value></data>
|
|
||||||
<data name="Theme_UnfoldIcon_Arrow" xml:space="preserve"><value>Arrow</value></data>
|
|
||||||
<data name="Theme_UnfoldIcon_Pen" xml:space="preserve"><value>Pen</value></data>
|
|
||||||
<data name="Theme_FloatingBarButtonsTitle" xml:space="preserve"><value>Floating bar buttons</value></data>
|
|
||||||
<data name="Theme_UseLegacyFloatingBarUI" xml:space="preserve"><value>Use legacy floating bar UI</value></data>
|
|
||||||
<data name="Theme_ShowShapeButton" xml:space="preserve"><value>Show shape button</value></data>
|
|
||||||
<data name="Theme_ShowUndoButton" xml:space="preserve"><value>Show undo button</value></data>
|
|
||||||
<data name="Theme_ShowRedoButton" xml:space="preserve"><value>Show redo button</value></data>
|
|
||||||
<data name="Theme_ShowClearButton" xml:space="preserve"><value>Show clear button</value></data>
|
|
||||||
<data name="Theme_ShowWhiteboardButton" xml:space="preserve"><value>Show whiteboard button</value></data>
|
|
||||||
<data name="Theme_ShowHideButton" xml:space="preserve"><value>Show hide button</value></data>
|
|
||||||
<data name="Theme_ShowLassoButton" xml:space="preserve"><value>Show lasso select button</value></data>
|
|
||||||
<data name="Theme_ShowClearAndMouseButton" xml:space="preserve"><value>Show clear+mouse button</value></data>
|
|
||||||
<data name="Theme_ShowQuickPalette" xml:space="preserve"><value>Show quick palette</value></data>
|
|
||||||
<data name="Theme_QuickPaletteMode" xml:space="preserve"><value>Quick palette display mode</value></data>
|
|
||||||
<data name="Theme_QuickPalette_SingleRow" xml:space="preserve"><value>Single row (6 colors)</value></data>
|
|
||||||
<data name="Theme_QuickPalette_DoubleRow" xml:space="preserve"><value>Double row (8 colors)</value></data>
|
|
||||||
<data name="Theme_EraserButtonDisplay" xml:space="preserve"><value>Eraser button display</value></data>
|
|
||||||
<data name="Theme_EraserDisplay_Both" xml:space="preserve"><value>Show both</value></data>
|
|
||||||
<data name="Theme_EraserDisplay_AreaOnly" xml:space="preserve"><value>Area eraser only</value></data>
|
|
||||||
<data name="Theme_EraserDisplay_LineOnly" xml:space="preserve"><value>Line eraser only</value></data>
|
|
||||||
<data name="Theme_EraserDisplay_None" xml:space="preserve"><value>Hide all</value></data>
|
|
||||||
<data name="Tray_GroupTitle" xml:space="preserve"><value>Taskbar tray icon</value></data>
|
|
||||||
<data name="Tray_EnableTrayIcon" xml:space="preserve"><value>Enable tray icon</value></data>
|
|
||||||
<data name="PPT_GroupTitle" xml:space="preserve"><value>PPT integration</value></data>
|
|
||||||
<data name="PPT_GroupHint" xml:space="preserve"><value>These settings apply during slide show and override others.</value></data>
|
|
||||||
<data name="PPT_SupportPowerPoint" xml:space="preserve"><value>Microsoft PowerPoint support</value></data>
|
|
||||||
<data name="PPT_Enhancement" xml:space="preserve"><value>PowerPoint enhancement</value></data>
|
|
||||||
<data name="PPT_SkipAnimations" xml:space="preserve"><value>Steal focus to skip animations (PPT)</value></data>
|
|
||||||
<data name="PPT_UseRot" xml:space="preserve"><value>Use ROT integration</value></data>
|
|
||||||
<data name="PPT_SupportWPS" xml:space="preserve"><value>WPS support</value></data>
|
|
||||||
<data name="PPT_KillWppProcess" xml:space="preserve"><value>Kill WPP process (avoid leftovers)</value></data>
|
|
||||||
<data name="PPT_KillWppHint" xml:space="preserve"><value># When disabled, leftover WPP processes may cause slow close or cannot exit completely.</value></data>
|
|
||||||
<data name="PPT_WpsHint1" xml:space="preserve"><value># If you only use PowerPoint, do not enable WPS integration. If you use WPS, it is recommended not to use PowerPoint together.</value></data>
|
|
||||||
<data name="PPT_WpsLagWarning" xml:space="preserve"><value>Enabling WPS support may cause lag when closing WPS!</value></data>
|
|
||||||
<data name="PPT_WpsSupportHint" xml:space="preserve"><value># WPS is supported, but MS Office and WPS cannot be supported at the same time. To enable WPS support, make sure “WPS Office compatibility with third-party systems and software” is enabled in the WPS config tool, otherwise WPS cannot be detected.</value></data>
|
|
||||||
<data name="Canvas_HideStrokeWhenSelecting" xml:space="preserve"><value>Hide ink when exiting board mode</value></data>
|
|
||||||
<data name="Canvas_HideStrokeWhenSelectingHint" xml:space="preserve"><value># When this option is on, ink will not be shown in PPT mode if not in annotation mode.</value></data>
|
|
||||||
<data name="Canvas_ClearInkAlsoClearHistory" xml:space="preserve"><value>Clear ink history when clearing ink</value></data>
|
|
||||||
<data name="Canvas_ClearCanvasAlsoClearImages" xml:space="preserve"><value>Clear images when clearing canvas</value></data>
|
|
||||||
<data name="Canvas_CompressPicturesUploaded" xml:space="preserve"><value>Auto-compress images when inserting (larger than 1920x1080)</value></data>
|
|
||||||
<data name="PPT_FlipButtonsTitle" xml:space="preserve"><value>PPT page-turn buttons</value></data>
|
|
||||||
<data name="PPT_ShowFlipButtons" xml:space="preserve"><value>Show page-turn buttons in PPT mode</value></data>
|
|
||||||
<data name="PPT_Position_LeftBottom" xml:space="preserve"><value>Bottom left</value></data>
|
|
||||||
<data name="PPT_Position_RightBottom" xml:space="preserve"><value>Bottom right</value></data>
|
|
||||||
<data name="PPT_Position_Left" xml:space="preserve"><value>Left</value></data>
|
|
||||||
<data name="PPT_Position_Right" xml:space="preserve"><value>Right</value></data>
|
|
||||||
<data name="PPT_LeftOffset" xml:space="preserve"><value>Left offset</value></data>
|
|
||||||
<data name="PPT_LeftOpacity" xml:space="preserve"><value>Left opacity</value></data>
|
|
||||||
<data name="PPT_RightOffset" xml:space="preserve"><value>Right offset</value></data>
|
|
||||||
<data name="PPT_RightOpacity" xml:space="preserve"><value>Right opacity</value></data>
|
|
||||||
<data name="PPT_OffsetHint" xml:space="preserve"><value># Increase for up, decrease for down; 0 = no offset, centered.</value></data>
|
|
||||||
<data name="PPT_LeftBottomOffset" xml:space="preserve"><value>Bottom left offset</value></data>
|
|
||||||
<data name="PPT_LeftBottomOpacity" xml:space="preserve"><value>Bottom left opacity</value></data>
|
|
||||||
<data name="PPT_RightBottomOffset" xml:space="preserve"><value>Bottom right offset</value></data>
|
|
||||||
<data name="PPT_RightBottomOpacity" xml:space="preserve"><value>Bottom right opacity</value></data>
|
|
||||||
<data name="PPT_OffsetHintHorizontal" xml:space="preserve"><value># Increase for right, decrease for left; 0 = no offset, centered.</value></data>
|
|
||||||
<data name="PPT_SideGroupTitle" xml:space="preserve"><value>Sides</value></data>
|
|
||||||
<data name="PPT_ShowPageNumber" xml:space="preserve"><value>Show page number</value></data>
|
|
||||||
<data name="PPT_HalfOpacity" xml:space="preserve"><value>Half opacity</value></data>
|
|
||||||
<data name="PPT_BlackBackground" xml:space="preserve"><value>Black background</value></data>
|
|
||||||
<data name="PPT_BottomGroupTitle" xml:space="preserve"><value>Bottom left & right</value></data>
|
|
||||||
<data name="PPT_PageButtonClickable" xml:space="preserve"><value>PPT page button clickable</value></data>
|
|
||||||
<data name="PPT_PageButtonClickableHint" xml:space="preserve"><value># When enabled, clicking the page button opens PowerPoint grid thumbnails. Not supported in WPS.</value></data>
|
|
||||||
<data name="PPT_LongPressPageTurn" xml:space="preserve"><value>PPT long-press to turn page</value></data>
|
|
||||||
<data name="PPT_LongPressPageTurnHint" xml:space="preserve"><value># When enabled, long-press on PPT page button to turn pages continuously.</value></data>
|
|
||||||
<data name="Startup_UIAccessTopMostHint" xml:space="preserve"><value># With UIA topmost on, app needs admin to stay on top. To turn off, fully quit then start again; restart will not disable it.</value></data>
|
|
||||||
<data name="Header_SilentUpdate" xml:space="preserve"><value>Silent update</value></data>
|
|
||||||
<data name="Startup_SilentUpdateHint" xml:space="preserve"><value># Silent update installs when the app is idle; no manual action needed.</value></data>
|
|
||||||
<data name="Startup_UpdateChannel" xml:space="preserve"><value>Update channel</value></data>
|
|
||||||
<data name="Update_Release" xml:space="preserve"><value>Stable (Release)</value></data>
|
|
||||||
<data name="Update_Preview" xml:space="preserve"><value>Preview</value></data>
|
|
||||||
<data name="Update_Beta" xml:space="preserve"><value>Beta</value></data>
|
|
||||||
<data name="Startup_UpdateChannelHint" xml:space="preserve"><value># Stable: reliable updates. Preview: new features with better stability than Beta. Beta: earliest new features.</value></data>
|
|
||||||
<data name="Btn_ManualUpdate" xml:space="preserve"><value>Check for updates</value></data>
|
|
||||||
<data name="Startup_ManualUpdateHint" xml:space="preserve"><value># Check and download the latest version now.</value></data>
|
|
||||||
<data name="Btn_FixVersion" xml:space="preserve"><value>Repair installation</value></data>
|
|
||||||
<data name="Startup_FixVersionHint" xml:space="preserve"><value># Repair downloads the latest build for the selected channel and reinstalls; use to fix broken installs.</value></data>
|
|
||||||
<data name="Btn_HistoryRollback" xml:space="preserve"><value>Rollback to previous version</value></data>
|
|
||||||
<data name="Startup_HistoryRollbackHint" xml:space="preserve"><value># Opens a page to manually roll back to an earlier version.</value></data>
|
|
||||||
<data name="Startup_SilentUpdateFullHint" xml:space="preserve"><value># When silent update is off, you will be prompted after download. When on, every 10 minutes the app checks: 1) within silent-update time window 2) not in writing mode 3) not in canvas. If all pass, it will close and update.</value></data>
|
|
||||||
<data name="Startup_SilentUpdateTimePeriod" xml:space="preserve"><value>Silent update time window</value></data>
|
|
||||||
<data name="Startup_StartTime" xml:space="preserve"><value>Start time</value></data>
|
|
||||||
<data name="Startup_EndTime" xml:space="preserve"><value>End time</value></data>
|
|
||||||
<data name="Startup_TimePeriodHint" xml:space="preserve"><value># If end < start, end is next day. If start = end, window is 24h.</value></data>
|
|
||||||
<data name="Startup_RunAtStartup" xml:space="preserve"><value>Run at startup</value></data>
|
|
||||||
<data name="Startup_FoldAtStartup" xml:space="preserve"><value>Dock to sidebar after startup</value></data>
|
|
||||||
<data name="Canvas_GroupTitle" xml:space="preserve"><value>Canvas and ink</value></data>
|
|
||||||
<data name="Canvas_ShowCursor" xml:space="preserve"><value>Show pen cursor</value></data>
|
|
||||||
<data name="Canvas_EnablePressureTouch" xml:space="preserve"><value>Enable pressure-sensitive touch</value></data>
|
|
||||||
<data name="Canvas_EnablePressureTouchHint" xml:space="preserve"><value># When on, touch screens that support pressure will show pressure; for devices not recognized by the system.</value></data>
|
|
||||||
<data name="Canvas_DisablePressure" xml:space="preserve"><value>Ignore pressure</value></data>
|
|
||||||
<data name="Canvas_DisablePressureHint" xml:space="preserve"><value># When on, all strokes use uniform thickness; mutually exclusive with pressure-sensitive touch.</value></data>
|
|
||||||
<data name="Canvas_EraserSize" xml:space="preserve"><value>Eraser size</value></data>
|
|
||||||
<data name="Canvas_EraserSize_VerySmall" xml:space="preserve"><value>Very small</value></data>
|
|
||||||
<data name="Canvas_EraserSize_Small" xml:space="preserve"><value>Small</value></data>
|
|
||||||
<data name="Canvas_EraserSize_Medium" xml:space="preserve"><value>Medium</value></data>
|
|
||||||
<data name="Canvas_EraserSize_Large" xml:space="preserve"><value>Large</value></data>
|
|
||||||
<data name="Canvas_EraserSize_VeryLarge" xml:space="preserve"><value>Very large</value></data>
|
|
||||||
<data name="Canvas_EraserSizeHint" xml:space="preserve"><value># Change takes effect next time you use area eraser.</value></data>
|
|
||||||
<data name="Canvas_KeepHyperbolaAsymptote" xml:space="preserve"><value>Keep hyperbola asymptotes</value></data>
|
|
||||||
<data name="Canvas_Yes" xml:space="preserve"><value>Yes</value></data>
|
|
||||||
<data name="Canvas_No" xml:space="preserve"><value>No</value></data>
|
|
||||||
<data name="Canvas_AskEachTime" xml:space="preserve"><value>Ask each time</value></data>
|
|
||||||
<data name="Canvas_HyperbolaAsymptoteHint" xml:space="preserve"><value># If not kept, undo-related bugs may occur.</value></data>
|
|
||||||
<data name="Canvas_ShowCircleCenter" xml:space="preserve"><value>Show circle center when drawing</value></data>
|
|
||||||
<data name="Canvas_WPFBezierSmoothing" xml:space="preserve"><value>Use WPF default Bezier smoothing</value></data>
|
|
||||||
<data name="Canvas_AdvancedBezierSmoothing" xml:space="preserve"><value>Use advanced curve smoothing (recommended)</value></data>
|
|
||||||
<data name="Canvas_EnableInkFade" xml:space="preserve"><value>Enable ink fade</value></data>
|
|
||||||
<data name="Canvas_EnableInkFadeHint" xml:space="preserve"><value># When on, ink is not committed to canvas; it fades after the set time.</value></data>
|
|
||||||
<data name="Canvas_InkFadeTime" xml:space="preserve"><value>Ink fade time</value></data>
|
|
||||||
<data name="Canvas_HideInkFadeInPenMenu" xml:space="preserve"><value>Hide ink fade control in pen menu</value></data>
|
|
||||||
<data name="Canvas_HideInkFadeInPenMenuHint" xml:space="preserve"><value># When on, the pen context menu will not show the ink fade control.</value></data>
|
|
||||||
<data name="Canvas_BrushAutoRestore" xml:space="preserve"><value>Enable brush auto-restore</value></data>
|
|
||||||
<data name="Canvas_BrushAutoRestoreHint" xml:space="preserve"><value># When on, temporary brush changes will restore at the configured time(s) to the color/opacity/width set here.</value></data>
|
|
||||||
<data name="Canvas_AutoRestoreTimePoints" xml:space="preserve"><value>Auto-restore time points (HH:mm, multiple with ;)</value></data>
|
|
||||||
<data name="Canvas_RestoreColor" xml:space="preserve"><value>Restore color</value></data>
|
|
||||||
<data name="Canvas_Color_Default" xml:space="preserve"><value>Default</value></data>
|
|
||||||
<data name="Canvas_Color_Black" xml:space="preserve"><value>Black</value></data>
|
|
||||||
<data name="Canvas_Color_White" xml:space="preserve"><value>White</value></data>
|
|
||||||
<data name="Canvas_Color_Red" xml:space="preserve"><value>Red</value></data>
|
|
||||||
<data name="Canvas_Color_Yellow" xml:space="preserve"><value>Yellow</value></data>
|
|
||||||
<data name="Canvas_Color_Blue" xml:space="preserve"><value>Blue</value></data>
|
|
||||||
<data name="Canvas_Color_Green" xml:space="preserve"><value>Green</value></data>
|
|
||||||
<data name="Canvas_Color_Orange" xml:space="preserve"><value>Orange</value></data>
|
|
||||||
<data name="Canvas_Color_Purple" xml:space="preserve"><value>Purple</value></data>
|
|
||||||
<data name="Canvas_RestoreWidth" xml:space="preserve"><value>Restore stroke width</value></data>
|
|
||||||
<data name="Canvas_RestoreOpacity" xml:space="preserve"><value>Restore opacity</value></data>
|
|
||||||
<data name="Canvas_SwitchBackAfterEraser" xml:space="preserve"><value>Switch back to annotation after eraser</value></data>
|
|
||||||
<data name="Canvas_SwitchBackAfterEraserHint" xml:space="preserve"><value># When on, after erasing, staying idle for a while will switch back to annotation mode.</value></data>
|
|
||||||
<data name="Canvas_SwitchBackDelay" xml:space="preserve"><value>Auto switch delay</value></data>
|
|
||||||
<data name="Canvas_SwitchBackDelayHint" xml:space="preserve"><value># If you erase again within the delay, the timer resets.</value></data>
|
|
||||||
<data name="InkRecog_LineEndpointSnappingHint" xml:space="preserve"><value># When on, line endpoints near other endpoints will snap and connect.</value></data>
|
|
||||||
<data name="PPT_EnterAnnotationOnShow" xml:space="preserve"><value>Enter annotation mode when starting PPT slide show</value></data>
|
|
||||||
<data name="PPT_ConflictWithAutoFold" xml:space="preserve"><value>Conflicts with "Auto fold when playing PPT" in Automation!</value></data>
|
|
||||||
<data name="PPT_TwoFingerGesture" xml:space="preserve"><value>Allow two-finger gestures in slide show</value></data>
|
|
||||||
<data name="PPT_FingerGestureSlide" xml:space="preserve"><value>Allow finger gesture to turn slides</value></data>
|
|
||||||
<data name="PPT_FingerGestureSlideHint" xml:space="preserve"><value># When canvas is on, finger swipe (not pen) can turn slides in show mode when canvas has no ink.</value></data>
|
|
||||||
<data name="PPT_ShowGestureButtonInShow" xml:space="preserve"><value>Show gesture buttons in PPT slide show</value></data>
|
|
||||||
<data name="PPT_ShowGestureButtonInShowHint" xml:space="preserve"><value># When on, gesture buttons are shown in PPT slide show.</value></data>
|
|
||||||
<data name="PPT_TimeCapsule" xml:space="preserve"><value>PPT time capsule</value></data>
|
|
||||||
<data name="PPT_TimeCapsuleHint" xml:space="preserve"><value># When on, show time capsule in PPT show; can replace minimized timer window.</value></data>
|
|
||||||
<data name="PPT_TimeCapsulePosition" xml:space="preserve"><value>Time capsule position:</value></data>
|
|
||||||
<data name="PPT_TimeCapsulePos_TL" xml:space="preserve"><value>Top left</value></data>
|
|
||||||
<data name="PPT_TimeCapsulePos_TR" xml:space="preserve"><value>Top right</value></data>
|
|
||||||
<data name="PPT_TimeCapsulePos_Center" xml:space="preserve"><value>Top center</value></data>
|
|
||||||
<data name="PPT_ShowQuickPanelInShow" xml:space="preserve"><value>Show quick panel in PPT slide show</value></data>
|
|
||||||
<data name="PPT_ShowQuickPanelInShowHint" xml:space="preserve"><value># When off, quick panel is hidden in PPT slide show.</value></data>
|
|
||||||
<data name="PPT_AutoScreenshot" xml:space="preserve"><value>Auto screenshot on slide change</value></data>
|
|
||||||
<data name="PPT_AutoScreenshotHint" xml:space="preserve"><value># When on, auto-screenshot when turning page with ink on slide.</value></data>
|
|
||||||
<data name="PPT_AutoSaveStrokes" xml:space="preserve"><value>Auto-save slide ink</value></data>
|
|
||||||
<data name="PPT_AutoSaveStrokesHint" xml:space="preserve"><value># When on, ink is saved when ending slide show and loaded next time (same file and page).</value></data>
|
|
||||||
<data name="PPT_RememberLastPage" xml:space="preserve"><value>Remember and prompt last slide position</value></data>
|
|
||||||
<data name="PPT_RememberLastPageHint" xml:space="preserve"><value># When on, last page is recorded; choose Yes to jump to it.</value></data>
|
|
||||||
<data name="PPT_GoToFirstPageOnReenter" xml:space="preserve"><value>Go to first slide when entering show</value></data>
|
|
||||||
<data name="PPT_NotifyHiddenPage" xml:space="preserve"><value>Warn about hidden slides</value></data>
|
|
||||||
<data name="PPT_NotifyAutoPlay" xml:space="preserve"><value>Warn if auto-play is enabled</value></data>
|
|
||||||
<data name="Advanced_Title" xml:space="preserve"><value>Advanced</value></data>
|
|
||||||
<data name="Advanced_TouchMultiplierHint" xml:space="preserve"><value>Adjust when finger-touch shows circle eraser or palm eraser is much larger than palm</value></data>
|
|
||||||
<data name="Advanced_SpecialScreenMode" xml:space="preserve"><value>Special screen mode</value></data>
|
|
||||||
<data name="Advanced_TouchMultiplier" xml:space="preserve"><value>Touch multiplier</value></data>
|
|
||||||
<data name="Advanced_TouchMultiplierCalibrateHint" xml:space="preserve"><value>Tap with pen in the area below to estimate touch size multiplier</value></data>
|
|
||||||
<data name="Advanced_TouchMultiplierValueHint" xml:space="preserve"><value># Value is for reference only</value></data>
|
|
||||||
<data name="Advanced_EraserBindTouchMultiplier" xml:space="preserve"><value>Bind eraser to touch size multiplier</value></data>
|
|
||||||
<data name="Advanced_EraserBindTouchHint" xml:space="preserve"><value># BoundsWidth is used as contact area threshold</value></data>
|
|
||||||
<data name="Advanced_QuadIRMode" xml:space="preserve"><value>Quad IR mode</value></data>
|
|
||||||
<data name="Advanced_Logging" xml:space="preserve"><value>Enable logging</value></data>
|
|
||||||
<data name="Advanced_LogByDate" xml:space="preserve"><value>Save logs by date</value></data>
|
|
||||||
<data name="Advanced_LogRotateHint" xml:space="preserve"><value># Log files over 512 KB are auto-deleted. With date save, logs go to Logs folder; folder is cleared when over 5 MB.</value></data>
|
|
||||||
<data name="Advanced_ConfirmExit" xml:space="preserve"><value>Confirm exit with dialog</value></data>
|
|
||||||
<data name="Advanced_FullScreenHelper" xml:space="preserve"><value>Enable FullScreenHelper</value></data>
|
|
||||||
<data name="Advanced_Experimental" xml:space="preserve"><value>Experimental</value></data>
|
|
||||||
<data name="Advanced_FullScreenHelperHint" xml:space="preserve"><value># Thanks to lindexi for FullScreenHelper; reduces taskbar pop-up and supports multi-monitor fullscreen. Disable if you see odd issues; restart ICC to apply.</value></data>
|
|
||||||
<data name="Advanced_AvoidFullScreenHelper" xml:space="preserve"><value>Enable AvoidFullScreenHelper</value></data>
|
|
||||||
<data name="Advanced_AvoidFullScreenHelperHint" xml:space="preserve"><value># Avoid canvas fullscreen; may fix taskbar not on top and Win11 taskbar unclickable. Can cause floating bar offset with AppBar on left/top. Restart ICC to apply.</value></data>
|
|
||||||
<data name="Advanced_EdgeGestureUtil" xml:space="preserve"><value>Enable EdgeGestureUtil</value></data>
|
|
||||||
<data name="Tools_MoreFeaturesTitle" xml:space="preserve"><value>More features</value></data>
|
|
||||||
<data name="Tools_Timer" xml:space="preserve"><value>Timer</value></data>
|
|
||||||
<data name="Tools_RandomDraw" xml:space="preserve"><value>Random draw</value></data>
|
|
||||||
<data name="Tools_SingleDraw" xml:space="preserve"><value>Single draw</value></data>
|
|
||||||
<data name="Tools_Save" xml:space="preserve"><value>Save</value></data>
|
|
||||||
<data name="Tools_Open" xml:space="preserve"><value>Open...</value></data>
|
|
||||||
<data name="Tools_Replay" xml:space="preserve"><value>Replay</value></data>
|
|
||||||
<data name="Tools_Screenshot" xml:space="preserve"><value>Screenshot</value></data>
|
|
||||||
<data name="Tools_Manual" xml:space="preserve"><value>Manual</value></data>
|
|
||||||
<data name="Tools_Settings" xml:space="preserve"><value>Settings</value></data>
|
|
||||||
<data name="QuickPanel_SingleDraw" xml:space="preserve"><value>Single draw</value></data>
|
|
||||||
<data name="QuickPanel_RandomDraw" xml:space="preserve"><value>Random draw</value></data>
|
|
||||||
<data name="QuickPanel_Timer" xml:space="preserve"><value>Timer</value></data>
|
|
||||||
<data name="QuickPanel_Whiteboard" xml:space="preserve"><value>Whiteboard</value></data>
|
|
||||||
<data name="QuickPanel_ExitShow" xml:space="preserve"><value>Exit slide show</value></data>
|
|
||||||
<data name="QuickPanel_Show" xml:space="preserve"><value>Show</value></data>
|
|
||||||
<data name="QuickPanel_Exit" xml:space="preserve"><value>Exit</value></data>
|
|
||||||
<data name="Backup_Title" xml:space="preserve"><value>Settings backup & restore</value></data>
|
|
||||||
<data name="Backup_Desc" xml:space="preserve"><value># You can manually back up current settings or restore previous backups; backups are also created automatically before updates.</value></data>
|
|
||||||
<data name="Backup_AutoBeforeUpdate" xml:space="preserve"><value>Backup before update</value></data>
|
|
||||||
<data name="Backup_AutoPeriodic" xml:space="preserve"><value>Periodic auto-backup</value></data>
|
|
||||||
<data name="Backup_Interval" xml:space="preserve"><value>Backup interval</value></data>
|
|
||||||
<data name="Backup_Interval_1Day" xml:space="preserve"><value>1 day</value></data>
|
|
||||||
<data name="Backup_Interval_3Days" xml:space="preserve"><value>3 days</value></data>
|
|
||||||
<data name="Backup_Interval_7Days" xml:space="preserve"><value>7 days</value></data>
|
|
||||||
<data name="Backup_Interval_14Days" xml:space="preserve"><value>14 days</value></data>
|
|
||||||
<data name="Backup_Interval_30Days" xml:space="preserve"><value>30 days</value></data>
|
|
||||||
<data name="Backup_Interval_DefaultHint" xml:space="preserve"><value>(default: 7 days)</value></data>
|
|
||||||
<data name="Backup_Manual" xml:space="preserve"><value>Backup now</value></data>
|
|
||||||
<data name="Backup_Restore" xml:space="preserve"><value>Restore backup</value></data>
|
|
||||||
<data name="ConfigProfiles_Title" xml:space="preserve"><value>Config profiles & hot reload</value></data>
|
|
||||||
<data name="ConfigProfiles_Desc" xml:space="preserve"><value># Selecting a profile switches and hot-reloads it; \"Save as\" saves current settings as a new profile.</value></data>
|
|
||||||
<data name="ConfigProfiles_Label" xml:space="preserve"><value>Profile:</value></data>
|
|
||||||
<data name="ConfigProfiles_Delete" xml:space="preserve"><value>Delete profile</value></data>
|
|
||||||
<data name="ConfigProfiles_SaveAs" xml:space="preserve"><value>Save as profile</value></data>
|
|
||||||
<data name="Automation_Title" xml:space="preserve"><value>Automation</value></data>
|
|
||||||
<data name="Automation_AutoFoldTitle" xml:space="preserve"><value>Auto fold</value></data>
|
|
||||||
<data name="AutoFold_App_SeewoBoard5" xml:space="preserve"><value>Seewo Whiteboard 5</value></data>
|
|
||||||
<data name="AutoFold_App_SeewoCamera" xml:space="preserve"><value>Seewo Visual Presenter</value></data>
|
|
||||||
<data name="AutoFold_App_SeewoBoard3" xml:space="preserve"><value>Seewo Whiteboard 3</value></data>
|
|
||||||
<data name="AutoFold_App_SeewoLightBoard" xml:space="preserve"><value>Seewo Lite Whiteboard</value></data>
|
|
||||||
<data name="AutoFold_App_SeewoLightBoard5C" xml:space="preserve"><value>Seewo Lite Whiteboard 5C</value></data>
|
|
||||||
<data name="AutoFold_App_SeewoPinco" xml:space="preserve"><value>Seewo Pinco</value></data>
|
|
||||||
<data name="AutoFold_App_HiteBoard" xml:space="preserve"><value>HiteBoard</value></data>
|
|
||||||
<data name="AutoFold_App_HiteCamera" xml:space="preserve"><value>Hite visual presenter</value></data>
|
|
||||||
<data name="AutoFold_App_HiteLightBoard" xml:space="preserve"><value>Hite Lite Whiteboard</value></data>
|
|
||||||
<data name="AutoFold_App_WenXiangBoard" xml:space="preserve"><value>WenXiang Whiteboard</value></data>
|
|
||||||
<data name="AutoFold_App_MSWhiteboard" xml:space="preserve"><value>Microsoft Whiteboard</value></data>
|
|
||||||
<data name="AutoFold_App_AdmoxBoard" xml:space="preserve"><value>Admox Whiteboard</value></data>
|
|
||||||
<data name="AutoFold_App_AdmoxBooth" xml:space="preserve"><value>Admox visual presenter</value></data>
|
|
||||||
<data name="AutoFold_App_YiYunBoard" xml:space="preserve"><value>YiYun Whiteboard</value></data>
|
|
||||||
<data name="AutoFold_App_YiYunBooth" xml:space="preserve"><value>YiYun visual presenter</value></data>
|
|
||||||
<data name="AutoFold_App_MaxHubBoard" xml:space="preserve"><value>MaxHub Whiteboard</value></data>
|
|
||||||
<data name="AutoFold_IgnoreEasiNoteDesktopAnno" xml:space="preserve"><value>Ignore EN5 desktop annotation window when auto folding</value></data>
|
|
||||||
<data name="AutoFold_OldZyBoard" xml:space="preserve"><value>Auto fold when entering old ZhongYuan whiteboard</value></data>
|
|
||||||
<data name="Automation_AutoFoldInPPT" xml:space="preserve"><value>Auto fold while playing PPT</value></data>
|
|
||||||
<data name="Automation_KeepFoldAfterExit" xml:space="preserve"><value>Keep folded after app exit</value></data>
|
|
||||||
<data name="Automation_KeepFoldAfterExitHint" xml:space="preserve"><value># When on, apps that trigger auto fold will stay folded even after they exit.</value></data>
|
|
||||||
<data name="AutoKill_Title" xml:space="preserve"><value>Auto kill</value></data>
|
|
||||||
<data name="AutoKill_PptTools" xml:space="preserve"><value>Auto kill Seewo PPT tools</value></data>
|
|
||||||
<data name="AutoKill_PptToolsHint" xml:space="preserve"><value># Killing PPT tools disables Seewo classroom helper. Delete Office.dll in its install folder to stop the PPT toolbar without auto kill.</value></data>
|
|
||||||
<data name="AutoKill_EasiNote5" xml:space="preserve"><value>Auto kill Seewo Whiteboard 5</value></data>
|
|
||||||
<data name="AutoKill_HiteAnnotation" xml:space="preserve"><value>Auto kill Hite screen writing</value></data>
|
|
||||||
<data name="AutoKill_HiteAfterKillEnterAnnotation" xml:space="preserve"><value>Enter annotation after killing Hite screen writing</value></data>
|
|
||||||
<data name="AutoKill_YouJiao" xml:space="preserve"><value>Auto kill YouJiao teacher</value></data>
|
|
||||||
<data name="AutoKill_SeewoDesktop2Anno" xml:space="preserve"><value>Auto kill Seewo Desktop 2.0 annotation</value></data>
|
|
||||||
<data name="AutoKill_SeewoDesktop2AnnoHint" xml:space="preserve"><value># Seewo Desktop 2.0 annotation is 64-bit so ICC (32-bit) cannot inspect it deeply; only process name DesktopAnnotation is matched. If you have another app with the same name, keep this off.</value></data>
|
|
||||||
<data name="AutoKill_SameAppTitle" xml:space="preserve"><value>Kill similar apps</value></data>
|
|
||||||
<data name="AutoKill_InkCanvasIC" xml:space="preserve"><value>Auto kill Ink Canvas and IC+</value></data>
|
|
||||||
<data name="AutoKill_ICA" xml:space="preserve"><value>Auto kill ICA (both new & old)</value></data>
|
|
||||||
<data name="AutoKill_Inkeys" xml:space="preserve"><value>Auto kill Inkeys (new only)</value></data>
|
|
||||||
<data name="FileAssoc_Title" xml:space="preserve"><value>File association</value></data>
|
|
||||||
<data name="FileAssoc_Desc" xml:space="preserve"><value>Manage .icstk file association so double-click opens in Ink Canvas.</value></data>
|
|
||||||
<data name="FileAssoc_Unregister" xml:space="preserve"><value>Remove association</value></data>
|
|
||||||
<data name="FileAssoc_Check" xml:space="preserve"><value>Check status</value></data>
|
|
||||||
<data name="FileAssoc_Register" xml:space="preserve"><value>Register association</value></data>
|
|
||||||
<data name="FloatingInterceptor_Title" xml:space="preserve"><value>Floating window interceptor</value></data>
|
|
||||||
<data name="FloatingInterceptor_Desc" xml:space="preserve"><value>Detect and block floating windows from similar software</value></data>
|
|
||||||
<data name="FloatingInterceptor_Enable" xml:space="preserve"><value>Enable floating window interceptor</value></data>
|
|
||||||
<data name="FloatingInterceptor_StatusNotRunning" xml:space="preserve"><value>Interceptor not running</value></data>
|
|
||||||
<data name="Storage_AutoScreenshotOnClear" xml:space="preserve"><value>Auto screenshot on clear</value></data>
|
|
||||||
<data name="Storage_ScreenshotsByDateFolder" xml:space="preserve"><value>Save screenshots in date folders</value></data>
|
|
||||||
<data name="Storage_AutoSaveInkOnScreenshot" xml:space="preserve"><value>Auto-save ink when screenshotting</value></data>
|
|
||||||
<data name="Storage_AutoSaveInk" xml:space="preserve"><value>Auto-save ink periodically</value></data>
|
|
||||||
<data name="Storage_AutoSaveInterval" xml:space="preserve"><value>Save interval</value></data>
|
|
||||||
<data name="Storage_AutoSaveInterval_1Min" xml:space="preserve"><value>1 minute</value></data>
|
|
||||||
<data name="Storage_AutoSaveInterval_3Min" xml:space="preserve"><value>3 minutes</value></data>
|
|
||||||
<data name="Storage_AutoSaveInterval_5Min" xml:space="preserve"><value>5 minutes</value></data>
|
|
||||||
<data name="Storage_AutoSaveInterval_10Min" xml:space="preserve"><value>10 minutes</value></data>
|
|
||||||
<data name="Storage_AutoSaveInterval_15Min" xml:space="preserve"><value>15 minutes</value></data>
|
|
||||||
<data name="Storage_AutoSaveInterval_30Min" xml:space="preserve"><value>30 minutes</value></data>
|
|
||||||
<data name="Storage_AutoSaveInterval_60Min" xml:space="preserve"><value>60 minutes</value></data>
|
|
||||||
<data name="Storage_AutoSaveHint" xml:space="preserve"><value># When on, strokes are auto-saved at the set interval, only when canvas is visible and has ink.</value></data>
|
|
||||||
<data name="Storage_SaveFullPageStrokes" xml:space="preserve"><value>Save full-page strokes</value></data>
|
|
||||||
<data name="Storage_SaveFullPageHint" xml:space="preserve"><value># When on, auto/manual saves store all pages in fullscreen; multiple pages are packed in one archive (whiteboard strokes open only in whiteboard mode; PPT strokes only in slide show mode).</value></data>
|
|
||||||
<data name="Storage_SaveAsXml" xml:space="preserve"><value>Save as XML format</value></data>
|
|
||||||
<data name="Storage_SaveAsXmlHint" xml:space="preserve"><value># When on, strokes are saved as XML (ISF) for easier inspection and editing.</value></data>
|
|
||||||
<data name="Storage_AutoScreenshotMinInk" xml:space="preserve"><value>Minimum ink for auto screenshot</value></data>
|
|
||||||
<data name="Storage_PathTitle" xml:space="preserve"><value>Stroke and screenshot save path</value></data>
|
|
||||||
<data name="Storage_PathBrowse" xml:space="preserve"><value>Browse</value></data>
|
|
||||||
<data name="Storage_PathSetToD" xml:space="preserve"><value>Set save path to D:\Ink Canvas</value></data>
|
|
||||||
<data name="Storage_PathSetToDocuments" xml:space="preserve"><value>Set save path to Documents</value></data>
|
|
||||||
<data name="Storage_PathPermissionHint" xml:space="preserve"><value># Please ensure the save folder is writable.</value></data>
|
|
||||||
<data name="Storage_AutoDeleteTitle" xml:space="preserve"><value>Auto delete old strokes and screenshots</value></data>
|
|
||||||
<data name="Storage_AutoDeleteHint" xml:space="preserve"><value># When on, all .icstk and .png files in the auto-save folder may be deleted!</value></data>
|
|
||||||
<data name="Storage_RetentionTitle" xml:space="preserve"><value>Retention duration</value></data>
|
|
||||||
<data name="Storage_RetentionUnitDays" xml:space="preserve"><value>days</value></data>
|
|
||||||
<data name="CloudStorage_Manage" xml:space="preserve"><value>Cloud storage management</value></data>
|
|
||||||
<data name="FoldMode_Title" xml:space="preserve"><value>Fold mode</value></data>
|
|
||||||
<data name="FoldMode_ExitToAnnotation" xml:space="preserve"><value>Switch to annotation when exiting fold mode</value></data>
|
|
||||||
<data name="FoldMode_ExitToAnnotationHint" xml:space="preserve"><value># When on, exiting fold mode switches back to annotation for convenience.</value></data>
|
|
||||||
<data name="FoldMode_AutoFoldAfterPPT" xml:space="preserve"><value>Auto fold floating bar after PPT show</value></data>
|
|
||||||
<data name="FoldMode_AutoFoldAfterPPTHint" xml:space="preserve"><value># When on, floating bar is auto-folded after exiting PPT slide show.</value></data>
|
|
||||||
<data name="FoldMode_AutoFoldAfterWhiteboard" xml:space="preserve"><value>Auto fold when exiting whiteboard</value></data>
|
|
||||||
<data name="FoldMode_AutoFoldAfterWhiteboardHint" xml:space="preserve"><value># When on, exiting whiteboard folds back to sidebar.</value></data>
|
|
||||||
<data name="Random_Title" xml:space="preserve"><value>Random roll call</value></data>
|
|
||||||
<data name="Random_ShowEditNamesButton" xml:space="preserve"><value>Show button to edit name list</value></data>
|
|
||||||
<data name="Random_BackgroundSettingsTitle" xml:space="preserve"><value>Roll-call window background (legacy UI only)</value></data>
|
|
||||||
<data name="Random_BackgroundSelectLabel" xml:space="preserve"><value>Background:</value></data>
|
|
||||||
<data name="Random_Background_Default" xml:space="preserve"><value>Default background</value></data>
|
|
||||||
<data name="Random_CustomBackgroundLabel" xml:space="preserve"><value>Custom background:</value></data>
|
|
||||||
<data name="Random_CustomBackground_Upload" xml:space="preserve"><value>Upload</value></data>
|
|
||||||
<data name="Random_CustomBackground_Manage" xml:space="preserve"><value>Manage</value></data>
|
|
||||||
<data name="Random_EnableButtons" xml:space="preserve"><value>Enable random & single-draw buttons</value></data>
|
|
||||||
<data name="Random_EnableQuickButton" xml:space="preserve"><value>Enable quick-draw floating button</value></data>
|
|
||||||
<data name="Random_UseExternal" xml:space="preserve"><value>Use external roll-call app</value></data>
|
|
||||||
<data name="Random_ExternalTypeLabel" xml:space="preserve"><value>Roll-call type</value></data>
|
|
||||||
<data name="Random_ExternalType_ClassIsland" xml:space="preserve"><value>ClassIsland</value></data>
|
|
||||||
<data name="Random_ExternalType_SecRandom" xml:space="preserve"><value>SecRandom</value></data>
|
|
||||||
<data name="Random_ExternalType_NamePicker" xml:space="preserve"><value>NamePicker</value></data>
|
|
||||||
<data name="Random_OnceCloseDelay" xml:space="preserve"><value>Single-draw window close delay</value></data>
|
|
||||||
<data name="Random_OnceMaxStudents" xml:space="preserve"><value>Max students per single draw</value></data>
|
|
||||||
<data name="Random_NewUI_Title" xml:space="preserve"><value>New roll-call UI</value></data>
|
|
||||||
<data name="Random_NewUI_Enable" xml:space="preserve"><value>Enable new roll-call UI</value></data>
|
|
||||||
<data name="Random_ML_AvoidRepeat" xml:space="preserve"><value>Use machine learning to avoid repeats</value></data>
|
|
||||||
<data name="Random_ML_HistoryCount" xml:space="preserve"><value>History count for avoidance</value></data>
|
|
||||||
<data name="Random_ML_Weight" xml:space="preserve"><value>Avoidance weight</value></data>
|
|
||||||
<data name="Random_ML_Hint" xml:space="preserve"><value># ML analyzes recent roll-call history to avoid repeating the same students.</value></data>
|
|
||||||
<data name="Timer_Title" xml:space="preserve"><value>Timer settings</value></data>
|
|
||||||
<data name="Timer_UseLegacyButtons" xml:space="preserve"><value>Use legacy timer button UI</value></data>
|
|
||||||
<data name="Timer_NewUI" xml:space="preserve"><value>New timer UI</value></data>
|
|
||||||
<data name="Timer_EnableCountUp" xml:space="preserve"><value>Enable count-up after timeout</value></data>
|
|
||||||
<data name="Timer_OvertimeHighlight" xml:space="preserve"><value>Highlight numbers when overtime</value></data>
|
|
||||||
<data name="Timer_Volume" xml:space="preserve"><value>Timer alert volume</value></data>
|
|
||||||
<data name="Timer_CustomSoundLabel" xml:space="preserve"><value>Custom alert sound:</value></data>
|
|
||||||
<data name="Timer_SelectFile" xml:space="preserve"><value>Select file</value></data>
|
|
||||||
<data name="Timer_Reset" xml:space="preserve"><value>Reset</value></data>
|
|
||||||
<data name="Timer_Progressive" xml:space="preserve"><value>Progressive reminder</value></data>
|
|
||||||
<data name="Timer_ProgressiveVolume" xml:space="preserve"><value>Progressive reminder volume</value></data>
|
|
||||||
<data name="Timer_ProgressiveCustomLabel" xml:space="preserve"><value>Custom progressive reminder audio:</value></data>
|
|
||||||
<data name="Timer_ProgressiveSelectFile" xml:space="preserve"><value>Select file</value></data>
|
|
||||||
<data name="Timer_ProgressiveReset" xml:space="preserve"><value>Reset</value></data>
|
|
||||||
<data name="About_Title" xml:space="preserve"><value>About</value></data>
|
|
||||||
<data name="About_DeviceInfo" xml:space="preserve"><value>Device information</value></data>
|
|
||||||
<data name="About_DeviceIdLabel" xml:space="preserve"><value>Device ID:</value></data>
|
|
||||||
<data name="About_UsageFrequencyLabel" xml:space="preserve"><value>Usage frequency:</value></data>
|
|
||||||
<data name="About_UpdatePriorityLabel" xml:space="preserve"><value>Update priority:</value></data>
|
|
||||||
<data name="About_LaunchCountLabel" xml:space="preserve"><value>Launch count:</value></data>
|
|
||||||
<data name="About_TotalUsageLabel" xml:space="preserve"><value>Total usage time:</value></data>
|
|
||||||
<data name="About_DeviceInfo_Loading" xml:space="preserve"><value>Loading...</value></data>
|
|
||||||
<data name="About_RefreshDeviceInfo" xml:space="preserve"><value>Refresh device info</value></data>
|
|
||||||
<data name="About_PrivacyCheckboxPrefix" xml:space="preserve"><value>I have read and agree to the </value></data>
|
|
||||||
<data name="About_PrivacyCheckboxSuffix" xml:space="preserve"><value> privacy statement</value></data>
|
|
||||||
<data name="About_TelemetryLabel" xml:space="preserve"><value>Anonymous usage data upload:</value></data>
|
|
||||||
<data name="About_Telemetry_Off" xml:space="preserve"><value>Off (no upload)</value></data>
|
|
||||||
<data name="About_Telemetry_Basic" xml:space="preserve"><value>Upload basic data</value></data>
|
|
||||||
<data name="About_Telemetry_Optional" xml:space="preserve"><value>Upload basic + optional data</value></data>
|
|
||||||
<data name="About_LicenseHint" xml:space="preserve"><value># Before using or distributing this software, you must be aware of the related open-source licenses. This software is based on https://github.com/WXRIW/Ink-Canvas.</value></data>
|
|
||||||
<data name="About_LicenseTitle" xml:space="preserve"><value>This software, ICA and Ink Canvas are all open sourced under a license</value></data>
|
|
||||||
<data name="About_LicenseBody" xml:space="preserve"><value>The strong copyleft license requires that complete source code and modifications of the licensed work (including large works using it) be provided under the same license. Copyright and license notices must be retained. Contributors explicitly grant patent rights.</value></data>
|
|
||||||
<data name="About_DevelopersLabel" xml:space="preserve"><value>Developers:</value></data>
|
|
||||||
<data name="About_Dev_ICCCE" xml:space="preserve"><value>Developer of ICC CE</value></data>
|
|
||||||
<data name="About_Dev_ICC" xml:space="preserve"><value>Developer of ICC</value></data>
|
|
||||||
<data name="About_Dev_ICA" xml:space="preserve"><value>Developer of ICA</value></data>
|
|
||||||
<data name="About_Dev_InkCanvas" xml:space="preserve"><value>Developer of Ink Canvas</value></data>
|
|
||||||
<data name="About_Source_ICC" xml:space="preserve"><value>ICC repository:</value></data>
|
|
||||||
<data name="About_Source_ICA" xml:space="preserve"><value>ICA repository:</value></data>
|
|
||||||
<data name="About_Source_InkCanvas" xml:space="preserve"><value>Ink Canvas repository:</value></data>
|
|
||||||
<data name="About_ThanksContributors" xml:space="preserve"><value>Thanks to the following contributors:</value></data>
|
|
||||||
<data name="About_Copyright" xml:space="preserve"><value>© 2025-2026 CJK_mkp. All rights reserved.</value></data>
|
|
||||||
<data name="About_OpenSourceSlogan" xml:space="preserve"><value>We love open-source forever!</value></data>
|
|
||||||
<data name="About_VersionLabel" xml:space="preserve"><value>Version:</value></data>
|
|
||||||
<data name="Common_Close" xml:space="preserve"><value>Close</value></data>
|
|
||||||
<data name="Common_On" xml:space="preserve"><value>On</value></data>
|
|
||||||
<data name="Common_Off" xml:space="preserve"><value>Off</value></data>
|
|
||||||
<data name="Advanced_UriSchemeName" xml:space="preserve"><value>External URI scheme (icc://)</value></data>
|
|
||||||
<data name="Advanced_NibModeBoundsWidthHeader" xml:space="preserve"><value>Nib mode BoundsWidth</value></data>
|
|
||||||
<data name="Advanced_FingerModeBoundsWidthHeader" xml:space="preserve"><value>Finger mode BoundsWidth</value></data>
|
|
||||||
<data name="Advanced_EdgeGestureUtilHint_Part1" xml:space="preserve"><value># EdgeGestureUtil is newly introduced in ICC to temporarily block edge gestures when using touch (e.g., on Windows 10: swipe from the left edge to Task View, from the right edge to Action Center; on Windows 11: swipe up from the bottom to open Start). It works by using</value></data>
|
|
||||||
<data name="Advanced_EdgeGestureUtilHint_Part2" xml:space="preserve"><value>(When the app window is active and in full-screen mode (or an owned window is active), prevents edge gesture behavior.) If anything is abnormal, turn this option off; it should take effect immediately. (Not available on Windows 7/8.)</value></data>
|
|
||||||
<data name="Advanced_ForceFullScreen" xml:space="preserve"><value>Enable ForceFullScreen</value></data>
|
|
||||||
<data name="Advanced_ForceFullScreenHint" xml:space="preserve"><value># When a window size change is detected, automatically uses Win32 API to set this window size to the primary monitor size (in device pixels). Turn it off if you don't need it; takes effect immediately.</value></data>
|
|
||||||
<data name="Advanced_DPIChangeDetection" xml:space="preserve"><value>Enable DPIChangeDetection</value></data>
|
|
||||||
<data name="Advanced_DPIChangeDetectionHint" xml:space="preserve"><value># When a system DPI change is detected, it tries to keep FloatingBar visible. If it goes off-screen, it will attempt to move it into the visible area. (Increasing DPI may trigger this; decreasing DPI won't auto-move—adjust manually.)</value></data>
|
|
||||||
<data name="Advanced_ResolutionChangeDetection" xml:space="preserve"><value>Enable ResolutionChangeDetection</value></data>
|
|
||||||
<data name="Advanced_ResolutionChangeDetectionHint" xml:space="preserve"><value># When a screen resolution change is detected, it tries to keep FloatingBar visible. If it goes off-screen, it will attempt to move it into the visible area. (Reducing resolution may trigger this; if it's still on-screen it won't auto-adjust—adjust manually.)</value></data>
|
|
||||||
<data name="FloatingInterceptor_App_SeewoBoard3" xml:space="preserve"><value>Seewo Whiteboard 3</value></data>
|
|
||||||
<data name="FloatingInterceptor_App_SeewoBoard5" xml:space="preserve"><value>Seewo Whiteboard 5</value></data>
|
|
||||||
<data name="FloatingInterceptor_App_SeewoBoard5C" xml:space="preserve"><value>Seewo Whiteboard 5C</value></data>
|
|
||||||
<data name="FloatingInterceptor_App_SeewoPinco" xml:space="preserve"><value>Seewo Pinco</value></data>
|
|
||||||
<data name="FloatingInterceptor_App_SeewoPincoDrawing" xml:space="preserve"><value>Seewo Pinco pen</value></data>
|
|
||||||
<data name="FloatingInterceptor_App_SeewoPPTTools" xml:space="preserve"><value>Seewo PPT Tools</value></data>
|
|
||||||
<data name="FloatingInterceptor_App_AiClass" xml:space="preserve"><value>AiClass</value></data>
|
|
||||||
<data name="FloatingInterceptor_App_HiteAnnotation" xml:space="preserve"><value>Hite screen writing</value></data>
|
|
||||||
<data name="FloatingInterceptor_App_ChangYanClass" xml:space="preserve"><value>Changyan smart classroom</value></data>
|
|
||||||
<data name="FloatingInterceptor_App_ChangYanPPT" xml:space="preserve"><value>Changyan PPT</value></data>
|
|
||||||
<data name="FloatingInterceptor_App_IntelligentClass" xml:space="preserve"><value>Tianyu Education Cloud</value></data>
|
|
||||||
<data name="FloatingInterceptor_App_SeewoDesktopAnnotation" xml:space="preserve"><value>Seewo desktop pen</value></data>
|
|
||||||
<data name="FloatingInterceptor_App_SeewoDesktopSideBar" xml:space="preserve"><value>Seewo desktop sidebar</value></data>
|
|
||||||
<data name="Board_MultiTouchWriting" xml:space="preserve"><value>Multi-touch writing</value></data>
|
|
||||||
<data name="Board_TwoFingerMove" xml:space="preserve"><value>Two-finger move</value></data>
|
|
||||||
<data name="Board_TwoFingerZoom" xml:space="preserve"><value>Two-finger zoom</value></data>
|
|
||||||
<data name="Board_TwoFingerRotate" xml:space="preserve"><value>Two-finger rotate</value></data>
|
|
||||||
<data name="Board_Background" xml:space="preserve"><value>Background</value></data>
|
|
||||||
<data name="Board_Select" xml:space="preserve"><value>Select</value></data>
|
|
||||||
<data name="Board_Pen" xml:space="preserve"><value>Pen</value></data>
|
|
||||||
<data name="Board_Highlighter" xml:space="preserve"><value>Highlighter</value></data>
|
|
||||||
<data name="Board_Eraser" xml:space="preserve"><value>Eraser</value></data>
|
|
||||||
<data name="Board_EraserOptions" xml:space="preserve"><value>Eraser options</value></data>
|
|
||||||
<data name="Board_Size" xml:space="preserve"><value>Size</value></data>
|
|
||||||
<data name="Board_EraserShape" xml:space="preserve"><value>Eraser shape</value></data>
|
|
||||||
<data name="Board_EraserShape_Circle" xml:space="preserve"><value>Circle</value></data>
|
|
||||||
<data name="Board_EraserShape_Blackboard" xml:space="preserve"><value>Blackboard</value></data>
|
|
||||||
<data name="Board_ClearInk" xml:space="preserve"><value>Clear ink</value></data>
|
|
||||||
<data name="Board_ClearInkAndHistory" xml:space="preserve"><value>Clear ink & history</value></data>
|
|
||||||
<data name="Board_StrokeEraser" xml:space="preserve"><value>Stroke eraser</value></data>
|
|
||||||
<data name="Board_Shape" xml:space="preserve"><value>Shapes</value></data>
|
|
||||||
<data name="Board_ShapeHintLongPress" xml:space="preserve"><value>(Long-press in first row to keep selected)</value></data>
|
|
||||||
<data name="Board_AutoHide" xml:space="preserve"><value>Auto-hide</value></data>
|
|
||||||
<data name="Board_InsertImage" xml:space="preserve"><value>Insert image</value></data>
|
|
||||||
<data name="Board_SelectImage" xml:space="preserve"><value>Select image</value></data>
|
|
||||||
<data name="Board_Screenshot" xml:space="preserve"><value>Screenshot</value></data>
|
|
||||||
<data name="Board_Undo" xml:space="preserve"><value>Undo</value></data>
|
|
||||||
<data name="Board_Redo" xml:space="preserve"><value>Redo</value></data>
|
|
||||||
<data name="Board_Tools" xml:space="preserve"><value>Tools</value></data>
|
|
||||||
<data name="Board_Exit" xml:space="preserve"><value>Exit</value></data>
|
|
||||||
<data name="Board_NewPage" xml:space="preserve"><value>New page</value></data>
|
|
||||||
<data name="Board_PreviousPage" xml:space="preserve"><value>Previous</value></data>
|
|
||||||
<data name="Board_NextPage" xml:space="preserve"><value>Next</value></data>
|
|
||||||
<data name="Board_Page" xml:space="preserve"><value>Page</value></data>
|
|
||||||
<data name="Board_DeleteThisPage" xml:space="preserve"><value>Delete this page</value></data>
|
|
||||||
<data name="Notification_TestText" xml:space="preserve"><value>Test text</value></data>
|
|
||||||
<data name="OldUI_Exit" xml:space="preserve"><value>Exit</value></data>
|
|
||||||
<data name="OldUI_Thickness" xml:space="preserve"><value>Thickness</value></data>
|
|
||||||
<data name="OldUI_Dark" xml:space="preserve"><value>Dark</value></data>
|
|
||||||
<data name="OldUI_Background" xml:space="preserve"><value>Background</value></data>
|
|
||||||
<data name="OldUI_HideCanvas" xml:space="preserve"><value>Hide
canvas</value></data>
|
|
||||||
<data name="OldUI_Check" xml:space="preserve"><value>Check</value></data>
|
|
||||||
<data name="OldUI_SlideshowFromStart" xml:space="preserve"><value>From start
slideshow</value></data>
|
|
||||||
<data name="OldUI_SlideshowEnd" xml:space="preserve"><value>End
slideshow</value></data>
|
|
||||||
<data name="OldUI_SingleFingerDrag" xml:space="preserve"><value>One finger
drag</value></data>
|
|
||||||
<data name="OldUI_Restore" xml:space="preserve"><value>Restore</value></data>
|
|
||||||
<data name="OldUI_ClearAndHide" xml:space="preserve"><value>Clear
&
Hide</value></data>
|
|
||||||
<data name="FloatingBar_Mouse" xml:space="preserve"><value>Mouse</value></data>
|
|
||||||
<data name="FloatingBar_Annotate" xml:space="preserve"><value>Annotate</value></data>
|
|
||||||
<data name="FloatingBar_Clear" xml:space="preserve"><value>Clear</value></data>
|
|
||||||
<data name="Booth_Title" xml:space="preserve"><value>Visual presenter</value></data>
|
|
||||||
<data name="Booth_CapturedPhotos" xml:space="preserve"><value>Captured photos</value></data>
|
|
||||||
<data name="Booth_CameraDevices" xml:space="preserve"><value>Camera devices</value></data>
|
|
||||||
<data name="Booth_Present" xml:space="preserve"><value>Present</value></data>
|
|
||||||
<data name="Booth_Correction" xml:space="preserve"><value>Correct</value></data>
|
|
||||||
<data name="Booth_Capture" xml:space="preserve"><value>Capture</value></data>
|
|
||||||
<data name="Booth_Rotate" xml:space="preserve"><value>Rotate</value></data>
|
|
||||||
<data name="Theme_LanguageLabel" xml:space="preserve"><value>UI language</value></data>
|
|
||||||
<data name="Theme_Language_System" xml:space="preserve"><value>Follow system</value></data>
|
|
||||||
<data name="Theme_Language_ChineseSimplified" xml:space="preserve"><value>Chinese (Simplified)</value></data>
|
|
||||||
<data name="Theme_Language_English" xml:space="preserve"><value>English</value></data>
|
|
||||||
<data name="Theme_Language_RestartHint" xml:space="preserve"><value>You need to restart the app for language changes to fully take effect.</value></data>
|
|
||||||
<data name="FloatingBar_AreaEraser" xml:space="preserve"><value>Area eraser</value></data>
|
|
||||||
<data name="FloatingBar_StrokeEraser" xml:space="preserve"><value>Stroke eraser</value></data>
|
|
||||||
<data name="FloatingBar_LassoSelect" xml:space="preserve"><value>Lasso</value></data>
|
|
||||||
<data name="FloatingBar_Geometry" xml:space="preserve"><value>Geometry</value></data>
|
|
||||||
<data name="FloatingBar_ClearAndMouse" xml:space="preserve"><value>Clear & cursor</value></data>
|
|
||||||
<data name="FloatingBar_Whiteboard" xml:space="preserve"><value>Board</value></data>
|
|
||||||
<data name="FloatingBar_Hide" xml:space="preserve"><value>Hide</value></data>
|
|
||||||
<data name="Geometry_Title" xml:space="preserve"><value>Geometry drawing</value></data>
|
|
||||||
<data name="Geometry_DrawLine" xml:space="preserve"><value>Line</value></data>
|
|
||||||
<data name="Geometry_DrawDashedLine" xml:space="preserve"><value>Dashed line</value></data>
|
|
||||||
<data name="Geometry_DrawDottedLine" xml:space="preserve"><value>Dotted line</value></data>
|
|
||||||
<data name="Geometry_DrawArrow" xml:space="preserve"><value>Arrow</value></data>
|
|
||||||
<data name="Geometry_DrawParallelLines" xml:space="preserve"><value>4 parallel lines</value></data>
|
|
||||||
<data name="Geometry_DrawCenteredSquare" xml:space="preserve"><value>Centered square</value></data>
|
|
||||||
<data name="Geometry_DrawCenteredCircle" xml:space="preserve"><value>Centered circle</value></data>
|
|
||||||
<data name="Geometry_DrawCenteredDashedCircle" xml:space="preserve"><value>Centered dashed circle</value></data>
|
|
||||||
<data name="Geometry_DrawCenteredEllipse" xml:space="preserve"><value>Centered ellipse</value></data>
|
|
||||||
<data name="Geometry_DrawCuboid" xml:space="preserve"><value>Cuboid</value></data>
|
|
||||||
<data name="Geometry_DrawSquare" xml:space="preserve"><value>Square</value></data>
|
|
||||||
<data name="Geometry_DrawCylinder" xml:space="preserve"><value>Cylinder</value></data>
|
|
||||||
<data name="Geometry_DrawCone" xml:space="preserve"><value>Cone</value></data>
|
|
||||||
<data name="FloatingBar_GestureButton" xml:space="preserve"><value>Gesture</value></data>
|
|
||||||
<data name="FloatingBar_GesturePanelTitle" xml:space="preserve"><value>Gesture options</value></data>
|
|
||||||
<data name="FloatingBar_Gesture_MultiTouchWriting" xml:space="preserve"><value>Multi-touch writing</value></data>
|
|
||||||
<data name="FloatingBar_Gesture_TwoFingerMove" xml:space="preserve"><value>Two-finger move</value></data>
|
|
||||||
<data name="FloatingBar_Gesture_TwoFingerZoom" xml:space="preserve"><value>Two-finger zoom</value></data>
|
|
||||||
<data name="FloatingBar_Gesture_TwoFingerRotate" xml:space="preserve"><value>Two-finger rotate</value></data>
|
|
||||||
<data name="Board_Gesture" xml:space="preserve"><value>Gesture</value></data>
|
|
||||||
<data name="Board_GestureOptions" xml:space="preserve"><value>Gesture options</value></data>
|
|
||||||
</root>
|
|
||||||
+2521
-665
File diff suppressed because it is too large
Load Diff
@@ -250,6 +250,8 @@ namespace Ink_Canvas
|
|||||||
public bool HasAcceptedTelemetryPrivacy { get; set; } = false;
|
public bool HasAcceptedTelemetryPrivacy { get; set; } = false;
|
||||||
[JsonProperty("hasShownOobe")]
|
[JsonProperty("hasShownOobe")]
|
||||||
public bool HasShownOobe { get; set; } = false;
|
public bool HasShownOobe { get; set; } = false;
|
||||||
|
[JsonProperty("hasConfirmedNetCompatibilityChange")]
|
||||||
|
public bool HasConfirmedNetCompatibilityChange { get; set; } = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
public class Appearance
|
public class Appearance
|
||||||
|
|||||||
@@ -1,66 +1,70 @@
|
|||||||
<UserControl x:Class="Ink_Canvas.Windows.HotkeyItem"
|
<UserControl x:Class="Ink_Canvas.Windows.HotkeyItem"
|
||||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||||
xmlns:ui="http://schemas.inkore.net/lib/ui/wpf/modern"
|
|
||||||
xmlns:ikw="http://schemas.inkore.net/lib/ui/wpf"
|
xmlns:ikw="http://schemas.inkore.net/lib/ui/wpf"
|
||||||
mc:Ignorable="d"
|
mc:Ignorable="d"
|
||||||
d:DesignHeight="60" d:DesignWidth="600">
|
d:DesignHeight="60"
|
||||||
|
d:DesignWidth="600">
|
||||||
<Border Background="White"
|
|
||||||
BorderBrush="#E0E0E0"
|
<Border Background="#2d2d30"
|
||||||
BorderThickness="1"
|
BorderBrush="#3f3f46"
|
||||||
CornerRadius="5"
|
BorderThickness="1"
|
||||||
Margin="0,2">
|
CornerRadius="6"
|
||||||
<Grid Margin="15,10">
|
Margin="0,2"
|
||||||
|
SnapsToDevicePixels="True">
|
||||||
|
<Grid Margin="14,10">
|
||||||
<Grid.ColumnDefinitions>
|
<Grid.ColumnDefinitions>
|
||||||
<ColumnDefinition Width="*"/>
|
<ColumnDefinition Width="*" />
|
||||||
<ColumnDefinition Width="Auto"/>
|
<ColumnDefinition Width="Auto" />
|
||||||
<ColumnDefinition Width="Auto"/>
|
<ColumnDefinition Width="Auto" />
|
||||||
</Grid.ColumnDefinitions>
|
</Grid.ColumnDefinitions>
|
||||||
|
|
||||||
<!-- 左侧信息 -->
|
|
||||||
<ikw:SimpleStackPanel Grid.Column="0" VerticalAlignment="Center">
|
<ikw:SimpleStackPanel Grid.Column="0" VerticalAlignment="Center">
|
||||||
<TextBlock x:Name="TitleTextBlock"
|
<TextBlock x:Name="TitleTextBlock"
|
||||||
Text="快捷键标题"
|
Text="快捷键标题"
|
||||||
FontSize="14"
|
FontSize="14"
|
||||||
FontWeight="SemiBold"
|
FontWeight="SemiBold"
|
||||||
Foreground="#333333"/>
|
Foreground="#fafafa" />
|
||||||
<TextBlock x:Name="DescriptionTextBlock"
|
<TextBlock x:Name="DescriptionTextBlock"
|
||||||
Text="快捷键描述"
|
Text="快捷键描述"
|
||||||
FontSize="12"
|
FontSize="12"
|
||||||
Foreground="#666666"
|
Foreground="#a1a1aa"
|
||||||
Margin="0,2,0,0"/>
|
Margin="0,2,0,0"
|
||||||
|
TextWrapping="Wrap" />
|
||||||
</ikw:SimpleStackPanel>
|
</ikw:SimpleStackPanel>
|
||||||
|
|
||||||
<!-- 当前快捷键显示 -->
|
<Border Grid.Column="1"
|
||||||
<Border Grid.Column="1"
|
Background="#3f3f46"
|
||||||
Background="#F5F5F5"
|
BorderBrush="#71717a"
|
||||||
BorderBrush="#D0D0D0"
|
BorderThickness="1"
|
||||||
BorderThickness="1"
|
CornerRadius="4"
|
||||||
CornerRadius="3"
|
Margin="10,0,0,0"
|
||||||
Margin="10,0,0,0"
|
Padding="10,6"
|
||||||
Padding="8,4">
|
VerticalAlignment="Center">
|
||||||
<TextBlock x:Name="CurrentHotkeyTextBlock"
|
<TextBlock x:Name="CurrentHotkeyTextBlock"
|
||||||
Text="未设置"
|
Text="未设置"
|
||||||
FontSize="12"
|
FontSize="12"
|
||||||
Foreground="#666666"
|
FontFamily="Consolas"
|
||||||
MinWidth="80"
|
FontWeight="SemiBold"
|
||||||
TextAlignment="Center"/>
|
Foreground="#fafafa"
|
||||||
|
MinWidth="88"
|
||||||
|
TextAlignment="Center"
|
||||||
|
VerticalAlignment="Center" />
|
||||||
</Border>
|
</Border>
|
||||||
|
|
||||||
<!-- 设置按钮 -->
|
<Button x:Name="BtnSetHotkey"
|
||||||
<Button x:Name="BtnSetHotkey"
|
Grid.Column="2"
|
||||||
Grid.Column="2"
|
Content="设置"
|
||||||
Content="设置"
|
Width="72"
|
||||||
Width="60"
|
Height="30"
|
||||||
Height="28"
|
FontSize="12"
|
||||||
Background="#0066BF"
|
|
||||||
Foreground="White"
|
|
||||||
FontSize="12"
|
|
||||||
Margin="10,0,0,0"
|
Margin="10,0,0,0"
|
||||||
Click="BtnSetHotkey_Click"/>
|
VerticalAlignment="Center"
|
||||||
|
Style="{DynamicResource PrimaryButtonStyle}"
|
||||||
|
Padding="12,4"
|
||||||
|
Click="BtnSetHotkey_Click" />
|
||||||
</Grid>
|
</Grid>
|
||||||
</Border>
|
</Border>
|
||||||
</UserControl>
|
</UserControl>
|
||||||
|
|||||||
@@ -11,6 +11,16 @@ namespace Ink_Canvas.Windows
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public partial class HotkeyItem : UserControl
|
public partial class HotkeyItem : UserControl
|
||||||
{
|
{
|
||||||
|
private static readonly SolidColorBrush HotkeyValueForeground = CreateFrozenBrush(0xFA, 0xFA, 0xFA);
|
||||||
|
private static readonly SolidColorBrush HotkeyPlaceholderForeground = CreateFrozenBrush(0xA1, 0xA1, 0xAA);
|
||||||
|
|
||||||
|
private static SolidColorBrush CreateFrozenBrush(byte r, byte g, byte b)
|
||||||
|
{
|
||||||
|
var brush = new SolidColorBrush(Color.FromRgb(r, g, b));
|
||||||
|
brush.Freeze();
|
||||||
|
return brush;
|
||||||
|
}
|
||||||
|
|
||||||
#region Events
|
#region Events
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 快捷键变更事件
|
/// 快捷键变更事件
|
||||||
@@ -80,13 +90,13 @@ namespace Ink_Canvas.Windows
|
|||||||
if (_currentKey == Key.None)
|
if (_currentKey == Key.None)
|
||||||
{
|
{
|
||||||
CurrentHotkeyTextBlock.Text = "未设置";
|
CurrentHotkeyTextBlock.Text = "未设置";
|
||||||
CurrentHotkeyTextBlock.Foreground = Brushes.Gray;
|
CurrentHotkeyTextBlock.Foreground = HotkeyPlaceholderForeground;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
var modifiersText = _currentModifiers == ModifierKeys.None ? "" : $"{_currentModifiers}+";
|
var modifiersText = _currentModifiers == ModifierKeys.None ? "" : $"{_currentModifiers}+";
|
||||||
CurrentHotkeyTextBlock.Text = $"{modifiersText}{_currentKey}";
|
CurrentHotkeyTextBlock.Text = $"{modifiersText}{_currentKey}";
|
||||||
CurrentHotkeyTextBlock.Foreground = Brushes.Black;
|
CurrentHotkeyTextBlock.Foreground = HotkeyValueForeground;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -106,7 +116,7 @@ namespace Ink_Canvas.Windows
|
|||||||
private void StopHotkeyCapture()
|
private void StopHotkeyCapture()
|
||||||
{
|
{
|
||||||
BtnSetHotkey.Content = "设置";
|
BtnSetHotkey.Content = "设置";
|
||||||
BtnSetHotkey.Background = Brushes.DodgerBlue;
|
BtnSetHotkey.ClearValue(Button.BackgroundProperty);
|
||||||
|
|
||||||
// 移除键盘事件处理器
|
// 移除键盘事件处理器
|
||||||
KeyDown -= HotkeyItem_KeyDown;
|
KeyDown -= HotkeyItem_KeyDown;
|
||||||
|
|||||||
@@ -6,211 +6,370 @@
|
|||||||
xmlns:local="clr-namespace:Ink_Canvas.Windows"
|
xmlns:local="clr-namespace:Ink_Canvas.Windows"
|
||||||
xmlns:ui="http://schemas.inkore.net/lib/ui/wpf/modern"
|
xmlns:ui="http://schemas.inkore.net/lib/ui/wpf/modern"
|
||||||
xmlns:ikw="http://schemas.inkore.net/lib/ui/wpf"
|
xmlns:ikw="http://schemas.inkore.net/lib/ui/wpf"
|
||||||
ui:ThemeManager.RequestedTheme="Light"
|
mc:Ignorable="d"
|
||||||
Background="#F9F9F9"
|
Title="快捷键设置"
|
||||||
AllowsTransparency="True"
|
Height="600"
|
||||||
mc:Ignorable="d"
|
Width="800"
|
||||||
WindowStyle="None"
|
|
||||||
WindowStartupLocation="CenterScreen"
|
WindowStartupLocation="CenterScreen"
|
||||||
ResizeMode="CanResize"
|
ResizeMode="CanResize"
|
||||||
Title="快捷键设置"
|
FontFamily="Microsoft YaHei UI"
|
||||||
Height="600"
|
ui:ThemeManager.IsThemeAware="True"
|
||||||
Width="800">
|
ui:TitleBar.ExtendViewIntoTitleBar="True"
|
||||||
|
ui:WindowHelper.SystemBackdropType="Mica"
|
||||||
|
ui:WindowHelper.UseModernWindowStyle="True"
|
||||||
|
ui:TitleBar.Height="48">
|
||||||
|
|
||||||
|
<Window.Resources>
|
||||||
|
<ResourceDictionary>
|
||||||
|
<SolidColorBrush x:Key="WindowBackground" Color="#1e1e1e" />
|
||||||
|
<SolidColorBrush x:Key="BorderBrush" Color="#3f3f46" />
|
||||||
|
<SolidColorBrush x:Key="TextForeground" Color="#fafafa" />
|
||||||
|
<SolidColorBrush x:Key="TextSecondary" Color="#a1a1aa" />
|
||||||
|
<SolidColorBrush x:Key="AccentColor" Color="#3b82f6" />
|
||||||
|
<SolidColorBrush x:Key="TitleForeground" Color="#fafafa" />
|
||||||
|
|
||||||
|
<Style x:Key="PrimaryButtonStyle" TargetType="Button">
|
||||||
|
<Setter Property="Background" Value="{StaticResource AccentColor}" />
|
||||||
|
<Setter Property="Foreground" Value="White" />
|
||||||
|
<Setter Property="BorderThickness" Value="0" />
|
||||||
|
<Setter Property="FontWeight" Value="SemiBold" />
|
||||||
|
<Setter Property="Padding" Value="16,8" />
|
||||||
|
<Setter Property="Cursor" Value="Hand" />
|
||||||
|
<Setter Property="Template">
|
||||||
|
<Setter.Value>
|
||||||
|
<ControlTemplate TargetType="Button">
|
||||||
|
<Border Background="{TemplateBinding Background}"
|
||||||
|
CornerRadius="4"
|
||||||
|
Padding="{TemplateBinding Padding}">
|
||||||
|
<ContentPresenter HorizontalAlignment="Center"
|
||||||
|
VerticalAlignment="Center" />
|
||||||
|
</Border>
|
||||||
|
<ControlTemplate.Triggers>
|
||||||
|
<Trigger Property="IsMouseOver" Value="True">
|
||||||
|
<Setter Property="Background" Value="#2563eb" />
|
||||||
|
</Trigger>
|
||||||
|
<Trigger Property="IsPressed" Value="True">
|
||||||
|
<Setter Property="Background" Value="#1d4ed8" />
|
||||||
|
</Trigger>
|
||||||
|
</ControlTemplate.Triggers>
|
||||||
|
</ControlTemplate>
|
||||||
|
</Setter.Value>
|
||||||
|
</Setter>
|
||||||
|
</Style>
|
||||||
|
|
||||||
|
<Style x:Key="SecondaryButtonStyle" TargetType="Button">
|
||||||
|
<Setter Property="Background" Value="Transparent" />
|
||||||
|
<Setter Property="Foreground" Value="{StaticResource TextForeground}" />
|
||||||
|
<Setter Property="BorderBrush" Value="{StaticResource BorderBrush}" />
|
||||||
|
<Setter Property="BorderThickness" Value="1" />
|
||||||
|
<Setter Property="FontWeight" Value="SemiBold" />
|
||||||
|
<Setter Property="Padding" Value="16,8" />
|
||||||
|
<Setter Property="Cursor" Value="Hand" />
|
||||||
|
<Setter Property="Template">
|
||||||
|
<Setter.Value>
|
||||||
|
<ControlTemplate TargetType="Button">
|
||||||
|
<Border Background="{TemplateBinding Background}"
|
||||||
|
BorderBrush="{TemplateBinding BorderBrush}"
|
||||||
|
BorderThickness="{TemplateBinding BorderThickness}"
|
||||||
|
CornerRadius="4"
|
||||||
|
Padding="{TemplateBinding Padding}">
|
||||||
|
<ContentPresenter HorizontalAlignment="Center"
|
||||||
|
VerticalAlignment="Center" />
|
||||||
|
</Border>
|
||||||
|
<ControlTemplate.Triggers>
|
||||||
|
<Trigger Property="IsMouseOver" Value="True">
|
||||||
|
<Setter Property="Background" Value="#27272a" />
|
||||||
|
</Trigger>
|
||||||
|
</ControlTemplate.Triggers>
|
||||||
|
</ControlTemplate>
|
||||||
|
</Setter.Value>
|
||||||
|
</Setter>
|
||||||
|
</Style>
|
||||||
|
</ResourceDictionary>
|
||||||
|
</Window.Resources>
|
||||||
|
|
||||||
<Grid>
|
<Grid>
|
||||||
<Grid.RowDefinitions>
|
<Grid.RowDefinitions>
|
||||||
<RowDefinition Height="Auto"/>
|
<RowDefinition Height="Auto" />
|
||||||
<RowDefinition Height="*"/>
|
<RowDefinition Height="*" />
|
||||||
<RowDefinition Height="Auto"/>
|
<RowDefinition Height="Auto" />
|
||||||
</Grid.RowDefinitions>
|
</Grid.RowDefinitions>
|
||||||
|
|
||||||
<!-- 标题栏 -->
|
<!-- 标题栏(与云存储管理等新设置窗口一致) -->
|
||||||
<Border Grid.Row="0" Background="#3B82F6" Height="60"
|
<Border x:Name="Border_TitleBarRoot"
|
||||||
|
Grid.Row="0"
|
||||||
|
Height="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=Window}, Path=(ui:TitleBar.Height)}"
|
||||||
MouseLeftButtonDown="TitleBar_MouseLeftButtonDown">
|
MouseLeftButtonDown="TitleBar_MouseLeftButtonDown">
|
||||||
<Grid>
|
<Grid>
|
||||||
<StackPanel Orientation="Horizontal" VerticalAlignment="Center" Margin="20,0,0,0">
|
<Grid.ColumnDefinitions>
|
||||||
<TextBlock Text="快捷键设置"
|
<ColumnDefinition />
|
||||||
Foreground="White"
|
<ColumnDefinition Width="Auto" />
|
||||||
FontSize="22"
|
<ColumnDefinition Width="Auto" />
|
||||||
FontWeight="SemiBold"
|
</Grid.ColumnDefinitions>
|
||||||
VerticalAlignment="Center"/>
|
|
||||||
</StackPanel>
|
<TextBlock Grid.Column="0"
|
||||||
<Button x:Name="BtnClose"
|
Text="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=Window}, Path=Title}"
|
||||||
Content=""
|
VerticalAlignment="Center"
|
||||||
FontFamily="Segoe MDL2 Assets"
|
Margin="12,0,0,0"
|
||||||
HorizontalAlignment="Right"
|
FontSize="12"
|
||||||
VerticalAlignment="Center"
|
FontWeight="SemiBold"
|
||||||
Margin="0,0,20,0"
|
Foreground="{StaticResource TitleForeground}" />
|
||||||
Background="Transparent"
|
|
||||||
BorderThickness="0"
|
<ikw:SimpleStackPanel x:Name="StackPanel_RightButtons"
|
||||||
FontSize="16"
|
Grid.Column="1"
|
||||||
Foreground="White"
|
Orientation="Horizontal"
|
||||||
Click="BtnClose_Click"/>
|
Spacing="5"
|
||||||
|
VerticalAlignment="Center" />
|
||||||
|
|
||||||
|
<Rectangle Grid.Column="2"
|
||||||
|
Width="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=Window}, Path=(ui:TitleBar.SystemOverlayRightInset)}" />
|
||||||
</Grid>
|
</Grid>
|
||||||
</Border>
|
</Border>
|
||||||
|
|
||||||
<!-- 主内容区 -->
|
<!-- 主内容 -->
|
||||||
<ScrollViewer Grid.Row="1" Margin="20" VerticalScrollBarVisibility="Auto">
|
<Border Grid.Row="1"
|
||||||
<ikw:SimpleStackPanel Margin="20">
|
Background="{StaticResource WindowBackground}"
|
||||||
<!-- 说明文字 -->
|
Padding="20,10,20,16">
|
||||||
<TextBlock Text="在这里可以自定义全局快捷键设置。全局快捷键在任何情况下都能生效,即使应用程序不在焦点状态。"
|
<ui:ScrollViewerEx VerticalScrollBarVisibility="Auto"
|
||||||
TextWrapping="Wrap"
|
HorizontalScrollBarVisibility="Disabled"
|
||||||
Margin="0,0,0,20"
|
PanningMode="VerticalOnly"
|
||||||
Foreground="#666666"/>
|
ui:ThemeManager.RequestedTheme="Dark">
|
||||||
|
<ikw:SimpleStackPanel Spacing="14">
|
||||||
|
<TextBlock Text="在这里可以自定义全局快捷键设置。全局快捷键在任何情况下都能生效,即使应用程序不在焦点状态。"
|
||||||
|
TextWrapping="Wrap"
|
||||||
|
Foreground="{StaticResource TextSecondary}"
|
||||||
|
FontSize="14"
|
||||||
|
LineHeight="20" />
|
||||||
|
|
||||||
<!-- 鼠标模式快捷键设置 -->
|
<Border BorderThickness="1"
|
||||||
<GroupBox Header="鼠标模式设置" Margin="0,0,0,20">
|
BorderBrush="{StaticResource BorderBrush}"
|
||||||
<ikw:SimpleStackPanel>
|
CornerRadius="8"
|
||||||
<ikw:SimpleStackPanel Orientation="Horizontal" VerticalAlignment="Center">
|
Padding="18,16"
|
||||||
<ui:ToggleSwitch x:Name="ToggleSwitchEnableHotkeysInMouseMode"
|
Background="#27272a">
|
||||||
Header="在鼠标模式下启用快捷键"
|
<ikw:SimpleStackPanel Spacing="10">
|
||||||
Margin="0,0,10,0"
|
<TextBlock Text="鼠标模式"
|
||||||
Width="200"/>
|
FontSize="18"
|
||||||
|
FontWeight="SemiBold"
|
||||||
|
Foreground="{StaticResource TextForeground}" />
|
||||||
|
<Border Height="1"
|
||||||
|
Background="{StaticResource BorderBrush}"
|
||||||
|
HorizontalAlignment="Stretch" />
|
||||||
|
<ikw:SimpleStackPanel Orientation="Horizontal" VerticalAlignment="Center">
|
||||||
|
<ui:ToggleSwitch x:Name="ToggleSwitchEnableHotkeysInMouseMode"
|
||||||
|
Header="在鼠标模式下启用快捷键"
|
||||||
|
OnContent=""
|
||||||
|
OffContent=""
|
||||||
|
FontFamily="Microsoft YaHei UI"
|
||||||
|
FontWeight="Bold"
|
||||||
|
Margin="0,0,10,0"
|
||||||
|
Width="220" />
|
||||||
|
</ikw:SimpleStackPanel>
|
||||||
|
<TextBlock Text="开启后,即使在鼠标模式下快捷键也会生效"
|
||||||
|
Foreground="{StaticResource TextSecondary}"
|
||||||
|
TextWrapping="Wrap"
|
||||||
|
FontSize="13" />
|
||||||
</ikw:SimpleStackPanel>
|
</ikw:SimpleStackPanel>
|
||||||
<TextBlock Text="开启后,即使在鼠标模式下快捷键也会生效"
|
</Border>
|
||||||
Foreground="#666666"
|
|
||||||
VerticalAlignment="Center"
|
<ikw:SimpleStackPanel x:Name="HotkeyList" Spacing="12">
|
||||||
TextWrapping="Wrap"/>
|
<!-- 基本操作 -->
|
||||||
|
<Border BorderThickness="1"
|
||||||
|
BorderBrush="{StaticResource BorderBrush}"
|
||||||
|
CornerRadius="8"
|
||||||
|
Padding="18,16"
|
||||||
|
Background="#27272a">
|
||||||
|
<ikw:SimpleStackPanel Spacing="10">
|
||||||
|
<TextBlock Text="基本操作"
|
||||||
|
FontSize="18"
|
||||||
|
FontWeight="SemiBold"
|
||||||
|
Foreground="{StaticResource TextForeground}" />
|
||||||
|
<Border Height="1"
|
||||||
|
Background="{StaticResource BorderBrush}"
|
||||||
|
HorizontalAlignment="Stretch" />
|
||||||
|
<ikw:SimpleStackPanel Spacing="4">
|
||||||
|
<local:HotkeyItem x:Name="UndoHotkey"
|
||||||
|
Title="撤销"
|
||||||
|
Description="撤销上一步操作"
|
||||||
|
DefaultKey="Z"
|
||||||
|
DefaultModifiers="Control" />
|
||||||
|
<local:HotkeyItem x:Name="RedoHotkey"
|
||||||
|
Title="重做"
|
||||||
|
Description="重做上一步操作"
|
||||||
|
DefaultKey="Y"
|
||||||
|
DefaultModifiers="Control" />
|
||||||
|
<local:HotkeyItem x:Name="ClearHotkey"
|
||||||
|
Title="清空"
|
||||||
|
Description="清空当前画板内容"
|
||||||
|
DefaultKey="E"
|
||||||
|
DefaultModifiers="Control" />
|
||||||
|
<local:HotkeyItem x:Name="PasteHotkey"
|
||||||
|
Title="粘贴"
|
||||||
|
Description="粘贴剪贴板内容"
|
||||||
|
DefaultKey="V"
|
||||||
|
DefaultModifiers="Control" />
|
||||||
|
</ikw:SimpleStackPanel>
|
||||||
|
</ikw:SimpleStackPanel>
|
||||||
|
</Border>
|
||||||
|
|
||||||
|
<!-- 工具切换 -->
|
||||||
|
<Border BorderThickness="1"
|
||||||
|
BorderBrush="{StaticResource BorderBrush}"
|
||||||
|
CornerRadius="8"
|
||||||
|
Padding="18,16"
|
||||||
|
Background="#27272a">
|
||||||
|
<ikw:SimpleStackPanel Spacing="10">
|
||||||
|
<TextBlock Text="工具切换"
|
||||||
|
FontSize="18"
|
||||||
|
FontWeight="SemiBold"
|
||||||
|
Foreground="{StaticResource TextForeground}" />
|
||||||
|
<Border Height="1"
|
||||||
|
Background="{StaticResource BorderBrush}"
|
||||||
|
HorizontalAlignment="Stretch" />
|
||||||
|
<ikw:SimpleStackPanel Spacing="4">
|
||||||
|
<local:HotkeyItem x:Name="SelectToolHotkey"
|
||||||
|
Title="选择工具"
|
||||||
|
Description="切换到选择工具"
|
||||||
|
DefaultKey="S"
|
||||||
|
DefaultModifiers="Alt" />
|
||||||
|
<local:HotkeyItem x:Name="DrawToolHotkey"
|
||||||
|
Title="绘图工具"
|
||||||
|
Description="切换到绘图工具"
|
||||||
|
DefaultKey="D"
|
||||||
|
DefaultModifiers="Alt" />
|
||||||
|
<local:HotkeyItem x:Name="EraserToolHotkey"
|
||||||
|
Title="橡皮擦工具"
|
||||||
|
Description="切换到橡皮擦工具"
|
||||||
|
DefaultKey="E"
|
||||||
|
DefaultModifiers="Alt" />
|
||||||
|
<local:HotkeyItem x:Name="BlackboardToolHotkey"
|
||||||
|
Title="黑板工具"
|
||||||
|
Description="切换到黑板工具"
|
||||||
|
DefaultKey="B"
|
||||||
|
DefaultModifiers="Alt" />
|
||||||
|
<local:HotkeyItem x:Name="QuitDrawToolHotkey"
|
||||||
|
Title="退出绘图/白板"
|
||||||
|
Description="退出绘图模式或白板模式"
|
||||||
|
DefaultKey="Q"
|
||||||
|
DefaultModifiers="Alt" />
|
||||||
|
</ikw:SimpleStackPanel>
|
||||||
|
</ikw:SimpleStackPanel>
|
||||||
|
</Border>
|
||||||
|
|
||||||
|
<!-- 画笔设置 -->
|
||||||
|
<Border BorderThickness="1"
|
||||||
|
BorderBrush="{StaticResource BorderBrush}"
|
||||||
|
CornerRadius="8"
|
||||||
|
Padding="18,16"
|
||||||
|
Background="#27272a">
|
||||||
|
<ikw:SimpleStackPanel Spacing="10">
|
||||||
|
<TextBlock Text="画笔设置"
|
||||||
|
FontSize="18"
|
||||||
|
FontWeight="SemiBold"
|
||||||
|
Foreground="{StaticResource TextForeground}" />
|
||||||
|
<Border Height="1"
|
||||||
|
Background="{StaticResource BorderBrush}"
|
||||||
|
HorizontalAlignment="Stretch" />
|
||||||
|
<ikw:SimpleStackPanel Spacing="4">
|
||||||
|
<local:HotkeyItem x:Name="Pen1Hotkey"
|
||||||
|
Title="画笔1"
|
||||||
|
Description="选择画笔1"
|
||||||
|
DefaultKey="D1"
|
||||||
|
DefaultModifiers="Alt" />
|
||||||
|
<local:HotkeyItem x:Name="Pen2Hotkey"
|
||||||
|
Title="画笔2"
|
||||||
|
Description="选择画笔2"
|
||||||
|
DefaultKey="D2"
|
||||||
|
DefaultModifiers="Alt" />
|
||||||
|
<local:HotkeyItem x:Name="Pen3Hotkey"
|
||||||
|
Title="画笔3"
|
||||||
|
Description="选择画笔3"
|
||||||
|
DefaultKey="D3"
|
||||||
|
DefaultModifiers="Alt" />
|
||||||
|
<local:HotkeyItem x:Name="Pen4Hotkey"
|
||||||
|
Title="画笔4"
|
||||||
|
Description="选择画笔4"
|
||||||
|
DefaultKey="D4"
|
||||||
|
DefaultModifiers="Alt" />
|
||||||
|
<local:HotkeyItem x:Name="Pen5Hotkey"
|
||||||
|
Title="画笔5"
|
||||||
|
Description="选择画笔5"
|
||||||
|
DefaultKey="D5"
|
||||||
|
DefaultModifiers="Alt" />
|
||||||
|
</ikw:SimpleStackPanel>
|
||||||
|
</ikw:SimpleStackPanel>
|
||||||
|
</Border>
|
||||||
|
|
||||||
|
<!-- 功能快捷键 -->
|
||||||
|
<Border BorderThickness="1"
|
||||||
|
BorderBrush="{StaticResource BorderBrush}"
|
||||||
|
CornerRadius="8"
|
||||||
|
Padding="18,16"
|
||||||
|
Background="#27272a">
|
||||||
|
<ikw:SimpleStackPanel Spacing="10">
|
||||||
|
<TextBlock Text="功能快捷键"
|
||||||
|
FontSize="18"
|
||||||
|
FontWeight="SemiBold"
|
||||||
|
Foreground="{StaticResource TextForeground}" />
|
||||||
|
<Border Height="1"
|
||||||
|
Background="{StaticResource BorderBrush}"
|
||||||
|
HorizontalAlignment="Stretch" />
|
||||||
|
<ikw:SimpleStackPanel Spacing="4">
|
||||||
|
<local:HotkeyItem x:Name="DrawLineHotkey"
|
||||||
|
Title="绘制直线"
|
||||||
|
Description="绘制直线工具"
|
||||||
|
DefaultKey="L"
|
||||||
|
DefaultModifiers="Alt" />
|
||||||
|
<local:HotkeyItem x:Name="ScreenshotHotkey"
|
||||||
|
Title="截图"
|
||||||
|
Description="保存屏幕截图到桌面"
|
||||||
|
DefaultKey="C"
|
||||||
|
DefaultModifiers="Alt" />
|
||||||
|
<local:HotkeyItem x:Name="QuickDrawHotkey"
|
||||||
|
Title="快抽"
|
||||||
|
Description="打开快抽窗口(与悬浮快抽按钮相同)"
|
||||||
|
DefaultKey="K"
|
||||||
|
DefaultModifiers="Alt" />
|
||||||
|
<local:HotkeyItem x:Name="HideHotkey"
|
||||||
|
Title="隐藏"
|
||||||
|
Description="隐藏应用程序"
|
||||||
|
DefaultKey="V"
|
||||||
|
DefaultModifiers="Alt" />
|
||||||
|
<local:HotkeyItem x:Name="ExitHotkey"
|
||||||
|
Title="退出"
|
||||||
|
Description="退出当前模式或应用程序"
|
||||||
|
DefaultKey="Escape"
|
||||||
|
DefaultModifiers="None" />
|
||||||
|
</ikw:SimpleStackPanel>
|
||||||
|
</ikw:SimpleStackPanel>
|
||||||
|
</Border>
|
||||||
</ikw:SimpleStackPanel>
|
</ikw:SimpleStackPanel>
|
||||||
</GroupBox>
|
</ikw:SimpleStackPanel>
|
||||||
|
</ui:ScrollViewerEx>
|
||||||
<!-- 快捷键列表 -->
|
</Border>
|
||||||
<ikw:SimpleStackPanel x:Name="HotkeyList" Margin="0,0,0,20">
|
|
||||||
<!-- 基本操作 -->
|
|
||||||
<GroupBox Header="基本操作" Margin="0,0,0,15">
|
|
||||||
<ikw:SimpleStackPanel>
|
|
||||||
<local:HotkeyItem x:Name="UndoHotkey"
|
|
||||||
Title="撤销"
|
|
||||||
Description="撤销上一步操作"
|
|
||||||
DefaultKey="Z"
|
|
||||||
DefaultModifiers="Control"/>
|
|
||||||
<local:HotkeyItem x:Name="RedoHotkey"
|
|
||||||
Title="重做"
|
|
||||||
Description="重做上一步操作"
|
|
||||||
DefaultKey="Y"
|
|
||||||
DefaultModifiers="Control"/>
|
|
||||||
<local:HotkeyItem x:Name="ClearHotkey"
|
|
||||||
Title="清空"
|
|
||||||
Description="清空当前画板内容"
|
|
||||||
DefaultKey="E"
|
|
||||||
DefaultModifiers="Control"/>
|
|
||||||
<local:HotkeyItem x:Name="PasteHotkey"
|
|
||||||
Title="粘贴"
|
|
||||||
Description="粘贴剪贴板内容"
|
|
||||||
DefaultKey="V"
|
|
||||||
DefaultModifiers="Control"/>
|
|
||||||
</ikw:SimpleStackPanel>
|
|
||||||
</GroupBox>
|
|
||||||
|
|
||||||
<!-- 工具切换 -->
|
|
||||||
<GroupBox Header="工具切换" Margin="0,0,0,15">
|
|
||||||
<ikw:SimpleStackPanel>
|
|
||||||
<local:HotkeyItem x:Name="SelectToolHotkey"
|
|
||||||
Title="选择工具"
|
|
||||||
Description="切换到选择工具"
|
|
||||||
DefaultKey="S"
|
|
||||||
DefaultModifiers="Alt"/>
|
|
||||||
<local:HotkeyItem x:Name="DrawToolHotkey"
|
|
||||||
Title="绘图工具"
|
|
||||||
Description="切换到绘图工具"
|
|
||||||
DefaultKey="D"
|
|
||||||
DefaultModifiers="Alt"/>
|
|
||||||
<local:HotkeyItem x:Name="EraserToolHotkey"
|
|
||||||
Title="橡皮擦工具"
|
|
||||||
Description="切换到橡皮擦工具"
|
|
||||||
DefaultKey="E"
|
|
||||||
DefaultModifiers="Alt"/>
|
|
||||||
<local:HotkeyItem x:Name="BlackboardToolHotkey"
|
|
||||||
Title="黑板工具"
|
|
||||||
Description="切换到黑板工具"
|
|
||||||
DefaultKey="B"
|
|
||||||
DefaultModifiers="Alt"/>
|
|
||||||
<local:HotkeyItem x:Name="QuitDrawToolHotkey"
|
|
||||||
Title="退出绘图/白板"
|
|
||||||
Description="退出绘图模式或白板模式"
|
|
||||||
DefaultKey="Q"
|
|
||||||
DefaultModifiers="Alt"/>
|
|
||||||
</ikw:SimpleStackPanel>
|
|
||||||
</GroupBox>
|
|
||||||
|
|
||||||
<!-- 画笔设置 -->
|
|
||||||
<GroupBox Header="画笔设置" Margin="0,0,0,15">
|
|
||||||
<ikw:SimpleStackPanel>
|
|
||||||
<local:HotkeyItem x:Name="Pen1Hotkey"
|
|
||||||
Title="画笔1"
|
|
||||||
Description="选择画笔1"
|
|
||||||
DefaultKey="D1"
|
|
||||||
DefaultModifiers="Alt"/>
|
|
||||||
<local:HotkeyItem x:Name="Pen2Hotkey"
|
|
||||||
Title="画笔2"
|
|
||||||
Description="选择画笔2"
|
|
||||||
DefaultKey="D2"
|
|
||||||
DefaultModifiers="Alt"/>
|
|
||||||
<local:HotkeyItem x:Name="Pen3Hotkey"
|
|
||||||
Title="画笔3"
|
|
||||||
Description="选择画笔3"
|
|
||||||
DefaultKey="D3"
|
|
||||||
DefaultModifiers="Alt"/>
|
|
||||||
<local:HotkeyItem x:Name="Pen4Hotkey"
|
|
||||||
Title="画笔4"
|
|
||||||
Description="选择画笔4"
|
|
||||||
DefaultKey="D4"
|
|
||||||
DefaultModifiers="Alt"/>
|
|
||||||
<local:HotkeyItem x:Name="Pen5Hotkey"
|
|
||||||
Title="画笔5"
|
|
||||||
Description="选择画笔5"
|
|
||||||
DefaultKey="D5"
|
|
||||||
DefaultModifiers="Alt"/>
|
|
||||||
</ikw:SimpleStackPanel>
|
|
||||||
</GroupBox>
|
|
||||||
|
|
||||||
<!-- 功能快捷键 -->
|
|
||||||
<GroupBox Header="功能快捷键" Margin="0,0,0,15">
|
|
||||||
<ikw:SimpleStackPanel>
|
|
||||||
<local:HotkeyItem x:Name="DrawLineHotkey"
|
|
||||||
Title="绘制直线"
|
|
||||||
Description="绘制直线工具"
|
|
||||||
DefaultKey="L"
|
|
||||||
DefaultModifiers="Alt"/>
|
|
||||||
<local:HotkeyItem x:Name="ScreenshotHotkey"
|
|
||||||
Title="截图"
|
|
||||||
Description="保存屏幕截图到桌面"
|
|
||||||
DefaultKey="C"
|
|
||||||
DefaultModifiers="Alt"/>
|
|
||||||
<local:HotkeyItem x:Name="HideHotkey"
|
|
||||||
Title="隐藏"
|
|
||||||
Description="隐藏应用程序"
|
|
||||||
DefaultKey="V"
|
|
||||||
DefaultModifiers="Alt"/>
|
|
||||||
<local:HotkeyItem x:Name="ExitHotkey"
|
|
||||||
Title="退出"
|
|
||||||
Description="退出当前模式或应用程序"
|
|
||||||
DefaultKey="Escape"
|
|
||||||
DefaultModifiers="None"/>
|
|
||||||
</ikw:SimpleStackPanel>
|
|
||||||
</GroupBox>
|
|
||||||
</ikw:SimpleStackPanel></ikw:SimpleStackPanel>
|
|
||||||
</ScrollViewer>
|
|
||||||
|
|
||||||
<!-- 底部操作栏 -->
|
<!-- 底部操作栏 -->
|
||||||
<Border Grid.Row="2" Background="#F0F0F0" Height="60">
|
<Border Grid.Row="2"
|
||||||
<Grid>
|
Background="#18181b"
|
||||||
<ikw:SimpleStackPanel Orientation="Horizontal" HorizontalAlignment="Right" VerticalAlignment="Center" Margin="0,0,20,0">
|
BorderBrush="{StaticResource BorderBrush}"
|
||||||
<Button x:Name="BtnResetToDefault"
|
BorderThickness="0,1,0,0"
|
||||||
Content="重置为默认"
|
Padding="16,12">
|
||||||
Width="100"
|
<ikw:SimpleStackPanel Orientation="Horizontal"
|
||||||
Height="35"
|
HorizontalAlignment="Right"
|
||||||
Margin="0,0,10,0"
|
VerticalAlignment="Center"
|
||||||
Click="BtnResetToDefault_Click"/>
|
Spacing="10">
|
||||||
<Button x:Name="BtnSave"
|
<Button x:Name="BtnResetToDefault"
|
||||||
Content="保存设置"
|
Content="重置为默认"
|
||||||
Width="100"
|
MinWidth="100"
|
||||||
Height="35"
|
Height="36"
|
||||||
Background="#3B82F6"
|
Style="{StaticResource SecondaryButtonStyle}"
|
||||||
Foreground="White"
|
Click="BtnResetToDefault_Click" />
|
||||||
Click="BtnSave_Click"/>
|
<Button x:Name="BtnSave"
|
||||||
</ikw:SimpleStackPanel>
|
Content="保存设置"
|
||||||
</Grid>
|
MinWidth="100"
|
||||||
|
Height="36"
|
||||||
|
Style="{StaticResource PrimaryButtonStyle}"
|
||||||
|
Click="BtnSave_Click" />
|
||||||
|
</ikw:SimpleStackPanel>
|
||||||
</Border>
|
</Border>
|
||||||
</Grid>
|
</Grid>
|
||||||
</Window>
|
</Window>
|
||||||
|
|||||||
@@ -135,6 +135,9 @@ namespace Ink_Canvas.Windows
|
|||||||
_hotkeyItems["Screenshot"] = ScreenshotHotkey;
|
_hotkeyItems["Screenshot"] = ScreenshotHotkey;
|
||||||
ScreenshotHotkey.HotkeyName = "Screenshot";
|
ScreenshotHotkey.HotkeyName = "Screenshot";
|
||||||
|
|
||||||
|
_hotkeyItems["QuickDraw"] = QuickDrawHotkey;
|
||||||
|
QuickDrawHotkey.HotkeyName = "QuickDraw";
|
||||||
|
|
||||||
_hotkeyItems["Hide"] = HideHotkey;
|
_hotkeyItems["Hide"] = HideHotkey;
|
||||||
HideHotkey.HotkeyName = "Hide";
|
HideHotkey.HotkeyName = "Hide";
|
||||||
|
|
||||||
@@ -243,6 +246,9 @@ namespace Ink_Canvas.Windows
|
|||||||
case "Screenshot":
|
case "Screenshot":
|
||||||
hotkeyItem.SetCurrentHotkey(Key.C, ModifierKeys.Alt);
|
hotkeyItem.SetCurrentHotkey(Key.C, ModifierKeys.Alt);
|
||||||
break;
|
break;
|
||||||
|
case "QuickDraw":
|
||||||
|
hotkeyItem.SetCurrentHotkey(Key.K, ModifierKeys.Alt);
|
||||||
|
break;
|
||||||
case "Hide":
|
case "Hide":
|
||||||
hotkeyItem.SetCurrentHotkey(Key.V, ModifierKeys.Alt);
|
hotkeyItem.SetCurrentHotkey(Key.V, ModifierKeys.Alt);
|
||||||
break;
|
break;
|
||||||
@@ -472,6 +478,8 @@ namespace Ink_Canvas.Windows
|
|||||||
return () => _mainWindow.BtnDrawLine_Click(null, null);
|
return () => _mainWindow.BtnDrawLine_Click(null, null);
|
||||||
case "Screenshot":
|
case "Screenshot":
|
||||||
return () => _mainWindow.SaveScreenShotToDesktop();
|
return () => _mainWindow.SaveScreenShotToDesktop();
|
||||||
|
case "QuickDraw":
|
||||||
|
return () => _mainWindow.OpenQuickDrawFromHotkey();
|
||||||
case "Hide":
|
case "Hide":
|
||||||
return () => _mainWindow.SymbolIconEmoji_MouseUp(null, null);
|
return () => _mainWindow.SymbolIconEmoji_MouseUp(null, null);
|
||||||
case "Exit":
|
case "Exit":
|
||||||
@@ -533,11 +541,6 @@ namespace Ink_Canvas.Windows
|
|||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
#region Event Handlers
|
#region Event Handlers
|
||||||
private void BtnClose_Click(object sender, RoutedEventArgs e)
|
|
||||||
{
|
|
||||||
Close();
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 标题栏拖拽事件
|
/// 标题栏拖拽事件
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|||||||
@@ -3,99 +3,216 @@
|
|||||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||||
xmlns:local="clr-namespace:Ink_Canvas"
|
|
||||||
xmlns:ui="http://schemas.inkore.net/lib/ui/wpf/modern"
|
xmlns:ui="http://schemas.inkore.net/lib/ui/wpf/modern"
|
||||||
xmlns:ikw="http://schemas.inkore.net/lib/ui/wpf"
|
xmlns:ikw="http://schemas.inkore.net/lib/ui/wpf"
|
||||||
ui:ThemeManager.RequestedTheme="Light" Topmost="True" Background="Transparent"
|
mc:Ignorable="d"
|
||||||
mc:Ignorable="d" WindowStyle="None" AllowsTransparency="True"
|
|
||||||
WindowStartupLocation="CenterScreen"
|
|
||||||
Title="Ink Canvas Annotation 使用指南"
|
Title="Ink Canvas Annotation 使用指南"
|
||||||
Height="600" Width="500">
|
Height="600"
|
||||||
<Border Background="{DynamicResource OperatingGuideWindowBackground}" CornerRadius="10" BorderThickness="1" BorderBrush="{DynamicResource OperatingGuideWindowBorderBrush}" Margin="10,10,10,50">
|
Width="520"
|
||||||
<Grid>
|
MinHeight="400"
|
||||||
<Border MouseMove="WindowDragMove" Visibility="Visible" Width="64" Height="15" CornerRadius="8" Background="Gray" Margin="0,0,0,5" HorizontalAlignment="Center" VerticalAlignment="Bottom" />
|
MinWidth="400"
|
||||||
<ScrollViewer Margin="0,0,0,30" VerticalScrollBarVisibility="Auto"
|
WindowStartupLocation="CenterScreen"
|
||||||
PanningMode="VerticalOnly" ui:ThemeManager.RequestedTheme="Light"
|
Topmost="True"
|
||||||
ManipulationBoundaryFeedback="SCManipulationBoundaryFeedback">
|
ResizeMode="CanResize"
|
||||||
<ikw:SimpleStackPanel>
|
FontFamily="Microsoft YaHei UI"
|
||||||
<ikw:SimpleStackPanel Margin="20,20,20,0" Orientation="Horizontal" HorizontalAlignment="Left">
|
ui:ThemeManager.IsThemeAware="True"
|
||||||
<Image Margin="5, 0" Source="{DynamicResource OperatingGuideWindowKeyboardIcon}"
|
ui:TitleBar.ExtendViewIntoTitleBar="True"
|
||||||
RenderOptions.BitmapScalingMode="HighQuality" Height="40" Width="40"/>
|
ui:WindowHelper.SystemBackdropType="Mica"
|
||||||
<TextBlock FontSize="22" Text="软件快捷键" TextWrapping="Wrap" VerticalAlignment="Center" Foreground="{DynamicResource OperatingGuideWindowTextForeground}"/>
|
ui:WindowHelper.UseModernWindowStyle="True"
|
||||||
|
ui:TitleBar.Height="48">
|
||||||
|
<Grid>
|
||||||
|
<Grid.RowDefinitions>
|
||||||
|
<RowDefinition Height="Auto" />
|
||||||
|
<RowDefinition Height="*" />
|
||||||
|
</Grid.RowDefinitions>
|
||||||
|
|
||||||
|
<!-- 自定义标题栏(与 OOBE / 隐私说明等 Fluent 窗口一致) -->
|
||||||
|
<Border x:Name="Border_TitleBarRoot"
|
||||||
|
Height="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=Window}, Path=(ui:TitleBar.Height)}">
|
||||||
|
<Grid>
|
||||||
|
<Grid.ColumnDefinitions>
|
||||||
|
<ColumnDefinition />
|
||||||
|
<ColumnDefinition Width="Auto" />
|
||||||
|
<ColumnDefinition Width="Auto" />
|
||||||
|
</Grid.ColumnDefinitions>
|
||||||
|
|
||||||
|
<TextBlock Text="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=Window}, Path=Title}"
|
||||||
|
VerticalAlignment="Center"
|
||||||
|
Margin="12,0,0,0"
|
||||||
|
FontSize="12"
|
||||||
|
FontWeight="SemiBold" />
|
||||||
|
|
||||||
|
<Rectangle Grid.Column="2"
|
||||||
|
Width="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=Window}, Path=(ui:TitleBar.SystemOverlayRightInset)}" />
|
||||||
|
|
||||||
|
<ikw:SimpleStackPanel x:Name="StackPanel_RightButtons"
|
||||||
|
Grid.Column="1"
|
||||||
|
Orientation="Horizontal"
|
||||||
|
Spacing="5" />
|
||||||
|
</Grid>
|
||||||
|
</Border>
|
||||||
|
|
||||||
|
<Grid Grid.Row="1" Background="{DynamicResource SettingsPageBackground}">
|
||||||
|
<ScrollViewer Padding="0"
|
||||||
|
VerticalScrollBarVisibility="Auto"
|
||||||
|
PanningMode="VerticalOnly"
|
||||||
|
ManipulationBoundaryFeedback="SCManipulationBoundaryFeedback">
|
||||||
|
<ikw:SimpleStackPanel Margin="24,20,24,28" Spacing="4">
|
||||||
|
<ikw:SimpleStackPanel Margin="0,0,0,12"
|
||||||
|
Orientation="Horizontal"
|
||||||
|
HorizontalAlignment="Left"
|
||||||
|
Spacing="12">
|
||||||
|
<Image Height="32"
|
||||||
|
Width="32"
|
||||||
|
Source="{DynamicResource OperatingGuideWindowKeyboardIcon}"
|
||||||
|
RenderOptions.BitmapScalingMode="HighQuality" />
|
||||||
|
<TextBlock FontSize="20"
|
||||||
|
FontWeight="SemiBold"
|
||||||
|
Text="软件快捷键"
|
||||||
|
TextWrapping="Wrap"
|
||||||
|
VerticalAlignment="Center"
|
||||||
|
Foreground="{DynamicResource SettingsPageForeground}" />
|
||||||
</ikw:SimpleStackPanel>
|
</ikw:SimpleStackPanel>
|
||||||
|
|
||||||
<Viewbox Height="320" HorizontalAlignment="Left">
|
<ikw:SimpleStackPanel Margin="0,0,0,8"
|
||||||
<ikw:SimpleStackPanel>
|
MinHeight="28"
|
||||||
<ikw:SimpleStackPanel Margin="20, 0" Height="20" Orientation="Horizontal" HorizontalAlignment="Left">
|
Orientation="Horizontal"
|
||||||
<Image Source="{DynamicResource OperatingGuideWindowControlIcon1}"
|
HorizontalAlignment="Left"
|
||||||
RenderOptions.BitmapScalingMode="HighQuality" Height="20" Width="20"/>
|
Spacing="8">
|
||||||
<TextBlock FontSize="10" Text=" + Z —— 撤销" TextWrapping="Wrap" VerticalAlignment="Center" Foreground="{DynamicResource OperatingGuideWindowTextForeground}"/>
|
<Image Source="{DynamicResource OperatingGuideWindowControlIcon1}"
|
||||||
</ikw:SimpleStackPanel>
|
RenderOptions.BitmapScalingMode="HighQuality"
|
||||||
<ikw:SimpleStackPanel Margin="20, 0" Height="20" Orientation="Horizontal" HorizontalAlignment="Left">
|
Height="20"
|
||||||
<Image Source="{DynamicResource OperatingGuideWindowControlIcon2}"
|
Width="20"
|
||||||
RenderOptions.BitmapScalingMode="HighQuality" Height="20" Width="20"/>
|
VerticalAlignment="Center" />
|
||||||
<TextBlock FontSize="10" Text=" + Y —— 重做" TextWrapping="Wrap" VerticalAlignment="Center" Foreground="{DynamicResource OperatingGuideWindowTextForeground}"/>
|
<TextBlock FontSize="14"
|
||||||
</ikw:SimpleStackPanel>
|
Text=" + Z —— 撤销"
|
||||||
<ikw:SimpleStackPanel Margin="20, 0" Height="20" Orientation="Horizontal" HorizontalAlignment="Left">
|
TextWrapping="Wrap"
|
||||||
<Image Source="{DynamicResource OperatingGuideWindowControlIcon3}"
|
VerticalAlignment="Center"
|
||||||
RenderOptions.BitmapScalingMode="HighQuality" Height="20" Width="20"/>
|
Foreground="{DynamicResource SettingsPageForeground}" />
|
||||||
<TextBlock FontSize="10" Text=" + E —— 清屏" TextWrapping="Wrap" VerticalAlignment="Center" Foreground="{DynamicResource OperatingGuideWindowTextForeground}"/>
|
</ikw:SimpleStackPanel>
|
||||||
</ikw:SimpleStackPanel>
|
<ikw:SimpleStackPanel Margin="0,0,0,8"
|
||||||
<ikw:SimpleStackPanel Margin="20, 0" Height="20" Orientation="Horizontal" HorizontalAlignment="Left">
|
MinHeight="28"
|
||||||
<TextBlock FontSize="10" Text=" Alt + V —— 显示/隐藏笑脸右侧工具栏(Visibility)" TextWrapping="Wrap" VerticalAlignment="Center" Foreground="{DynamicResource OperatingGuideWindowTextForeground}"/>
|
Orientation="Horizontal"
|
||||||
</ikw:SimpleStackPanel>
|
HorizontalAlignment="Left"
|
||||||
<ikw:SimpleStackPanel Margin="20, 0" Height="20" Orientation="Horizontal" HorizontalAlignment="Left">
|
Spacing="8">
|
||||||
<TextBlock FontSize="10" Text=" Alt + C —— 截屏(Capture)" TextWrapping="Wrap" VerticalAlignment="Center" Foreground="{DynamicResource OperatingGuideWindowTextForeground}"/>
|
<Image Source="{DynamicResource OperatingGuideWindowControlIcon2}"
|
||||||
</ikw:SimpleStackPanel>
|
RenderOptions.BitmapScalingMode="HighQuality"
|
||||||
<ikw:SimpleStackPanel Margin="20, 0" Height="20" Orientation="Horizontal" HorizontalAlignment="Left">
|
Height="20"
|
||||||
<TextBlock FontSize="10" Text=" Alt + S —— 切换至选择模式(Select)" TextWrapping="Wrap" VerticalAlignment="Center" Foreground="{DynamicResource OperatingGuideWindowTextForeground}"/>
|
Width="20"
|
||||||
</ikw:SimpleStackPanel>
|
VerticalAlignment="Center" />
|
||||||
<ikw:SimpleStackPanel Margin="20, 0" Height="20" Orientation="Horizontal" HorizontalAlignment="Left">
|
<TextBlock FontSize="14"
|
||||||
<TextBlock FontSize="10" Text=" Alt + D —— 切换至批注模式 / 墨迹颜色选择器(Draw)" TextWrapping="Wrap" VerticalAlignment="Center" Foreground="{DynamicResource OperatingGuideWindowTextForeground}"/>
|
Text=" + Y —— 重做"
|
||||||
</ikw:SimpleStackPanel>
|
TextWrapping="Wrap"
|
||||||
<ikw:SimpleStackPanel Margin="20, 0" Height="20" Orientation="Horizontal" HorizontalAlignment="Left">
|
VerticalAlignment="Center"
|
||||||
<TextBlock FontSize="10" Text=" Alt + Q —— 退出批注模式(Quit)" TextWrapping="Wrap" VerticalAlignment="Center" Foreground="{DynamicResource OperatingGuideWindowTextForeground}"/>
|
Foreground="{DynamicResource SettingsPageForeground}" />
|
||||||
</ikw:SimpleStackPanel>
|
</ikw:SimpleStackPanel>
|
||||||
<ikw:SimpleStackPanel Margin="20, 0" Height="20" Orientation="Horizontal" HorizontalAlignment="Left">
|
<ikw:SimpleStackPanel Margin="0,0,0,8"
|
||||||
<TextBlock FontSize="10" Text=" Alt + B —— 切换/退出画板模式(Board)" TextWrapping="Wrap" VerticalAlignment="Center" Foreground="{DynamicResource OperatingGuideWindowTextForeground}"/>
|
MinHeight="28"
|
||||||
</ikw:SimpleStackPanel>
|
Orientation="Horizontal"
|
||||||
<ikw:SimpleStackPanel Margin="20, 0" Height="20" Orientation="Horizontal" HorizontalAlignment="Left">
|
HorizontalAlignment="Left"
|
||||||
<TextBlock FontSize="10" Text=" Alt + E —— 切换至面积擦/墨迹擦功能(Eraser)" TextWrapping="Wrap" VerticalAlignment="Center" Foreground="{DynamicResource OperatingGuideWindowTextForeground}"/>
|
Spacing="8">
|
||||||
</ikw:SimpleStackPanel>
|
<Image Source="{DynamicResource OperatingGuideWindowControlIcon3}"
|
||||||
<ikw:SimpleStackPanel Margin="20, 0" Height="20" Orientation="Horizontal" HorizontalAlignment="Left">
|
RenderOptions.BitmapScalingMode="HighQuality"
|
||||||
<TextBlock FontSize="10" Text=" Alt + L —— 切换至单次直线绘制功能(Line)" TextWrapping="Wrap" VerticalAlignment="Center" Foreground="{DynamicResource OperatingGuideWindowTextForeground}"/>
|
Height="20"
|
||||||
</ikw:SimpleStackPanel>
|
Width="20"
|
||||||
<ikw:SimpleStackPanel Margin="20, 0" Height="20" Orientation="Horizontal" HorizontalAlignment="Left">
|
VerticalAlignment="Center" />
|
||||||
<TextBlock FontSize="10" Text="Shift + Esc —— 退出 PPT 放映" TextWrapping="Wrap" VerticalAlignment="Center" Foreground="{DynamicResource OperatingGuideWindowTextForeground}"/>
|
<TextBlock FontSize="14"
|
||||||
</ikw:SimpleStackPanel>
|
Text=" + E —— 清屏"
|
||||||
</ikw:SimpleStackPanel>
|
TextWrapping="Wrap"
|
||||||
</Viewbox>
|
VerticalAlignment="Center"
|
||||||
|
Foreground="{DynamicResource SettingsPageForeground}" />
|
||||||
|
</ikw:SimpleStackPanel>
|
||||||
|
<ikw:SimpleStackPanel Margin="0,0,0,8"
|
||||||
|
MinHeight="28"
|
||||||
|
Orientation="Horizontal"
|
||||||
|
HorizontalAlignment="Left">
|
||||||
|
<TextBlock FontSize="14"
|
||||||
|
Text=" Alt + V —— 显示/隐藏笑脸右侧工具栏(Visibility)"
|
||||||
|
TextWrapping="Wrap"
|
||||||
|
VerticalAlignment="Center"
|
||||||
|
Foreground="{DynamicResource SettingsPageForeground}" />
|
||||||
|
</ikw:SimpleStackPanel>
|
||||||
|
<ikw:SimpleStackPanel Margin="0,0,0,8"
|
||||||
|
MinHeight="28"
|
||||||
|
Orientation="Horizontal"
|
||||||
|
HorizontalAlignment="Left">
|
||||||
|
<TextBlock FontSize="14"
|
||||||
|
Text=" Alt + C —— 截屏(Capture)"
|
||||||
|
TextWrapping="Wrap"
|
||||||
|
VerticalAlignment="Center"
|
||||||
|
Foreground="{DynamicResource SettingsPageForeground}" />
|
||||||
|
</ikw:SimpleStackPanel>
|
||||||
|
<ikw:SimpleStackPanel Margin="0,0,0,8"
|
||||||
|
MinHeight="28"
|
||||||
|
Orientation="Horizontal"
|
||||||
|
HorizontalAlignment="Left">
|
||||||
|
<TextBlock FontSize="14"
|
||||||
|
Text=" Alt + S —— 切换至选择模式(Select)"
|
||||||
|
TextWrapping="Wrap"
|
||||||
|
VerticalAlignment="Center"
|
||||||
|
Foreground="{DynamicResource SettingsPageForeground}" />
|
||||||
|
</ikw:SimpleStackPanel>
|
||||||
|
<ikw:SimpleStackPanel Margin="0,0,0,8"
|
||||||
|
MinHeight="28"
|
||||||
|
Orientation="Horizontal"
|
||||||
|
HorizontalAlignment="Left">
|
||||||
|
<TextBlock FontSize="14"
|
||||||
|
Text=" Alt + D —— 切换至批注模式 / 墨迹颜色选择器(Draw)"
|
||||||
|
TextWrapping="Wrap"
|
||||||
|
VerticalAlignment="Center"
|
||||||
|
Foreground="{DynamicResource SettingsPageForeground}" />
|
||||||
|
</ikw:SimpleStackPanel>
|
||||||
|
<ikw:SimpleStackPanel Margin="0,0,0,8"
|
||||||
|
MinHeight="28"
|
||||||
|
Orientation="Horizontal"
|
||||||
|
HorizontalAlignment="Left">
|
||||||
|
<TextBlock FontSize="14"
|
||||||
|
Text=" Alt + Q —— 退出批注模式(Quit)"
|
||||||
|
TextWrapping="Wrap"
|
||||||
|
VerticalAlignment="Center"
|
||||||
|
Foreground="{DynamicResource SettingsPageForeground}" />
|
||||||
|
</ikw:SimpleStackPanel>
|
||||||
|
<ikw:SimpleStackPanel Margin="0,0,0,8"
|
||||||
|
MinHeight="28"
|
||||||
|
Orientation="Horizontal"
|
||||||
|
HorizontalAlignment="Left">
|
||||||
|
<TextBlock FontSize="14"
|
||||||
|
Text=" Alt + B —— 切换/退出画板模式(Board)"
|
||||||
|
TextWrapping="Wrap"
|
||||||
|
VerticalAlignment="Center"
|
||||||
|
Foreground="{DynamicResource SettingsPageForeground}" />
|
||||||
|
</ikw:SimpleStackPanel>
|
||||||
|
<ikw:SimpleStackPanel Margin="0,0,0,8"
|
||||||
|
MinHeight="28"
|
||||||
|
Orientation="Horizontal"
|
||||||
|
HorizontalAlignment="Left">
|
||||||
|
<TextBlock FontSize="14"
|
||||||
|
Text=" Alt + E —— 切换至面积擦/墨迹擦功能(Eraser)"
|
||||||
|
TextWrapping="Wrap"
|
||||||
|
VerticalAlignment="Center"
|
||||||
|
Foreground="{DynamicResource SettingsPageForeground}" />
|
||||||
|
</ikw:SimpleStackPanel>
|
||||||
|
<ikw:SimpleStackPanel Margin="0,0,0,8"
|
||||||
|
MinHeight="28"
|
||||||
|
Orientation="Horizontal"
|
||||||
|
HorizontalAlignment="Left">
|
||||||
|
<TextBlock FontSize="14"
|
||||||
|
Text=" Alt + L —— 切换至单次直线绘制功能(Line)"
|
||||||
|
TextWrapping="Wrap"
|
||||||
|
VerticalAlignment="Center"
|
||||||
|
Foreground="{DynamicResource SettingsPageForeground}" />
|
||||||
|
</ikw:SimpleStackPanel>
|
||||||
|
<ikw:SimpleStackPanel MinHeight="28"
|
||||||
|
Orientation="Horizontal"
|
||||||
|
HorizontalAlignment="Left">
|
||||||
|
<TextBlock FontSize="14"
|
||||||
|
Text="Shift + Esc —— 退出 PPT 放映"
|
||||||
|
TextWrapping="Wrap"
|
||||||
|
VerticalAlignment="Center"
|
||||||
|
Foreground="{DynamicResource SettingsPageForeground}" />
|
||||||
|
</ikw:SimpleStackPanel>
|
||||||
</ikw:SimpleStackPanel>
|
</ikw:SimpleStackPanel>
|
||||||
</ScrollViewer>
|
</ScrollViewer>
|
||||||
<Viewbox Visibility="{Binding ElementName=BigViewController, Path=Visibility}" Margin="20,20,20,20" HorizontalAlignment="Right">
|
|
||||||
<ikw:SimpleStackPanel Height="180" Orientation="Horizontal">
|
|
||||||
<Border x:Name="BtnFullscreen" MouseUp="BtnFullscreen_MouseUp" HorizontalAlignment="Right" VerticalAlignment="Bottom"
|
|
||||||
Margin="5"
|
|
||||||
Background="{DynamicResource OperatingGuideWindowFullscreenButtonBackground}" Height="20" Width="20" CornerRadius="100">
|
|
||||||
<Border.Effect>
|
|
||||||
<DropShadowEffect Direction="0" ShadowDepth="0" Opacity="0.1" BlurRadius="3"/>
|
|
||||||
</Border.Effect>
|
|
||||||
<Viewbox Margin="5.5">
|
|
||||||
<ui:FontIcon Name="FontIconFullscreen" Icon="{x:Static ui:SegoeFluentIcons.FullScreen}" Foreground="{DynamicResource OperatingGuideWindowFullscreenButtonForeground}"/>
|
|
||||||
</Viewbox>
|
|
||||||
</Border>
|
|
||||||
<Border x:Name="BtnClose" MouseUp="BtnClose_MouseUp" HorizontalAlignment="Right" VerticalAlignment="Bottom"
|
|
||||||
Margin="5"
|
|
||||||
Background="{DynamicResource OperatingGuideWindowCloseButtonBackground}" Height="20" Width="20" CornerRadius="100">
|
|
||||||
<Border.Effect>
|
|
||||||
<DropShadowEffect Direction="0" ShadowDepth="0" Opacity="0.1" BlurRadius="3"/>
|
|
||||||
</Border.Effect>
|
|
||||||
<Viewbox Margin="5.5">
|
|
||||||
<ui:FontIcon Icon="{x:Static ui:SegoeFluentIcons.Clear}" Foreground="{DynamicResource OperatingGuideWindowCloseButtonForeground}"/>
|
|
||||||
</Viewbox>
|
|
||||||
</Border>
|
|
||||||
</ikw:SimpleStackPanel>
|
|
||||||
</Viewbox>
|
|
||||||
</Grid>
|
</Grid>
|
||||||
</Border>
|
</Grid>
|
||||||
</Window>
|
</Window>
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
using Ink_Canvas.Helpers;
|
using Ink_Canvas.Helpers;
|
||||||
using iNKORE.UI.WPF.Modern;
|
using iNKORE.UI.WPF.Modern;
|
||||||
using iNKORE.UI.WPF.Modern.Common.IconKeys;
|
|
||||||
using System;
|
using System;
|
||||||
using System.Windows;
|
using System.Windows;
|
||||||
using System.Windows.Input;
|
using System.Windows.Input;
|
||||||
@@ -15,33 +14,10 @@ namespace Ink_Canvas
|
|||||||
public OperatingGuideWindow()
|
public OperatingGuideWindow()
|
||||||
{
|
{
|
||||||
InitializeComponent();
|
InitializeComponent();
|
||||||
|
RefreshTheme();
|
||||||
AnimationsHelper.ShowWithSlideFromBottomAndFade(this, 0.25);
|
AnimationsHelper.ShowWithSlideFromBottomAndFade(this, 0.25);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void BtnClose_MouseUp(object sender, MouseButtonEventArgs e)
|
|
||||||
{
|
|
||||||
Close();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void WindowDragMove(object sender, MouseEventArgs e)
|
|
||||||
{
|
|
||||||
if (e.LeftButton == MouseButtonState.Pressed) DragMove();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void BtnFullscreen_MouseUp(object sender, MouseButtonEventArgs e)
|
|
||||||
{
|
|
||||||
if (WindowState == WindowState.Normal)
|
|
||||||
{
|
|
||||||
WindowState = WindowState.Maximized;
|
|
||||||
FontIconFullscreen.Icon = SegoeFluentIcons.BackToWindow;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
WindowState = WindowState.Normal;
|
|
||||||
FontIconFullscreen.Icon = SegoeFluentIcons.FullScreen;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void SCManipulationBoundaryFeedback(object sender, ManipulationBoundaryFeedbackEventArgs e)
|
private void SCManipulationBoundaryFeedback(object sender, ManipulationBoundaryFeedbackEventArgs e)
|
||||||
{
|
{
|
||||||
e.Handled = true;
|
e.Handled = true;
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
using Ink_Canvas.Controls;
|
||||||
using Ink_Canvas.Helpers;
|
using Ink_Canvas.Helpers;
|
||||||
using Microsoft.Win32;
|
using Microsoft.Win32;
|
||||||
using Newtonsoft.Json;
|
using Newtonsoft.Json;
|
||||||
@@ -101,7 +102,7 @@ namespace Ink_Canvas.Windows
|
|||||||
private double _collapsedOffset = 200; // 折叠时的偏移量(隐藏内容区域)
|
private double _collapsedOffset = 200; // 折叠时的偏移量(隐藏内容区域)
|
||||||
private MainWindow _mainWindow;
|
private MainWindow _mainWindow;
|
||||||
|
|
||||||
private Dictionary<System.Windows.Controls.Image, int> _pptImages = new Dictionary<System.Windows.Controls.Image, int>();
|
private Dictionary<FrameworkElement, int> _pptImages = new Dictionary<FrameworkElement, int>();
|
||||||
|
|
||||||
private Dictionary<int, List<string>> _pptImagePaths = new Dictionary<int, List<string>>();
|
private Dictionary<int, List<string>> _pptImagePaths = new Dictionary<int, List<string>>();
|
||||||
|
|
||||||
@@ -183,9 +184,9 @@ namespace Ink_Canvas.Windows
|
|||||||
{
|
{
|
||||||
foreach (var item in e.OldItems)
|
foreach (var item in e.OldItems)
|
||||||
{
|
{
|
||||||
if (item is System.Windows.Controls.Image image)
|
if (item is FrameworkElement fe && _pptImages.ContainsKey(fe))
|
||||||
{
|
{
|
||||||
RemoveImageFromPPT(image);
|
RemoveImageFromPPT(fe);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -196,20 +197,20 @@ namespace Ink_Canvas.Windows
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void RemoveImageFromPPT(System.Windows.Controls.Image image)
|
private void RemoveImageFromPPT(FrameworkElement element)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
if (image == null) return;
|
if (element == null) return;
|
||||||
|
|
||||||
if (_pptImages.ContainsKey(image))
|
if (_pptImages.ContainsKey(element))
|
||||||
{
|
{
|
||||||
int slideNumber = _pptImages[image];
|
int slideNumber = _pptImages[element];
|
||||||
_pptImages.Remove(image);
|
_pptImages.Remove(element);
|
||||||
|
|
||||||
if (_pptImagePaths.ContainsKey(slideNumber))
|
if (_pptImagePaths.ContainsKey(slideNumber))
|
||||||
{
|
{
|
||||||
string imagePath = image.Tag as string;
|
string imagePath = element.Tag as string;
|
||||||
if (!string.IsNullOrEmpty(imagePath) && _pptImagePaths[slideNumber].Contains(imagePath))
|
if (!string.IsNullOrEmpty(imagePath) && _pptImagePaths[slideNumber].Contains(imagePath))
|
||||||
{
|
{
|
||||||
_pptImagePaths[slideNumber].Remove(imagePath);
|
_pptImagePaths[slideNumber].Remove(imagePath);
|
||||||
@@ -929,7 +930,7 @@ namespace Ink_Canvas.Windows
|
|||||||
{
|
{
|
||||||
var dialog = new OpenFileDialog
|
var dialog = new OpenFileDialog
|
||||||
{
|
{
|
||||||
Filter = "图片文件|*.jpg;*.jpeg;*.png;*.bmp;*.gif"
|
Filter = "图片与 PDF|*.jpg;*.jpeg;*.png;*.bmp;*.gif;*.pdf|图片文件|*.jpg;*.jpeg;*.png;*.bmp;*.gif|PDF|*.pdf"
|
||||||
};
|
};
|
||||||
|
|
||||||
if (dialog.ShowDialog() == true)
|
if (dialog.ShowDialog() == true)
|
||||||
@@ -941,14 +942,14 @@ namespace Ink_Canvas.Windows
|
|||||||
|
|
||||||
if (createImageMethod != null)
|
if (createImageMethod != null)
|
||||||
{
|
{
|
||||||
var imageTask = createImageMethod.Invoke(_mainWindow, new object[] { filePath }) as System.Threading.Tasks.Task<System.Windows.Controls.Image>;
|
var imageTask = createImageMethod.Invoke(_mainWindow, new object[] { filePath }) as System.Threading.Tasks.Task<FrameworkElement>;
|
||||||
if (imageTask != null)
|
if (imageTask != null)
|
||||||
{
|
{
|
||||||
var image = await imageTask;
|
var inserted = await imageTask;
|
||||||
if (image != null)
|
if (inserted != null)
|
||||||
{
|
{
|
||||||
image.Tag = filePath;
|
inserted.Tag = filePath;
|
||||||
await InsertImageToMainWindow(image, filePath);
|
await InsertImageToMainWindow(inserted, filePath);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1185,9 +1186,9 @@ namespace Ink_Canvas.Windows
|
|||||||
}, DispatcherPriority.Normal);
|
}, DispatcherPriority.Normal);
|
||||||
}
|
}
|
||||||
|
|
||||||
private async System.Threading.Tasks.Task InsertImageToMainWindow(System.Windows.Controls.Image image, string originalFilePath = null, bool saveToJson = true)
|
private async System.Threading.Tasks.Task InsertImageToMainWindow(FrameworkElement element, string originalFilePath = null, bool saveToJson = true)
|
||||||
{
|
{
|
||||||
if (_mainWindow == null || image == null) return;
|
if (_mainWindow == null || element == null) return;
|
||||||
|
|
||||||
// 确保在UI线程上执行
|
// 确保在UI线程上执行
|
||||||
await Application.Current.Dispatcher.InvokeAsync(() =>
|
await Application.Current.Dispatcher.InvokeAsync(() =>
|
||||||
@@ -1196,11 +1197,11 @@ namespace Ink_Canvas.Windows
|
|||||||
{
|
{
|
||||||
// 生成唯一名称
|
// 生成唯一名称
|
||||||
string timestamp = "img_" + DateTime.Now.ToString("yyyyMMdd_HH_mm_ss_fff");
|
string timestamp = "img_" + DateTime.Now.ToString("yyyyMMdd_HH_mm_ss_fff");
|
||||||
image.Name = timestamp;
|
element.Name = timestamp;
|
||||||
|
|
||||||
// 设置图片属性
|
// 设置图片属性
|
||||||
image.IsHitTestVisible = true;
|
element.IsHitTestVisible = true;
|
||||||
image.Focusable = false;
|
element.Focusable = false;
|
||||||
|
|
||||||
System.Windows.Controls.InkCanvas inkCanvas = null;
|
System.Windows.Controls.InkCanvas inkCanvas = null;
|
||||||
var inkCanvasField = _mainWindow.GetType().GetField("inkCanvas",
|
var inkCanvasField = _mainWindow.GetType().GetField("inkCanvas",
|
||||||
@@ -1274,7 +1275,7 @@ namespace Ink_Canvas.Windows
|
|||||||
// 如果在PPT模式下,记录图片和页面编号的关联,并保存图片路径
|
// 如果在PPT模式下,记录图片和页面编号的关联,并保存图片路径
|
||||||
if (currentSlideNumber > 0 && !string.IsNullOrEmpty(originalFilePath) && saveToJson)
|
if (currentSlideNumber > 0 && !string.IsNullOrEmpty(originalFilePath) && saveToJson)
|
||||||
{
|
{
|
||||||
_pptImages[image] = currentSlideNumber;
|
_pptImages[element] = currentSlideNumber;
|
||||||
|
|
||||||
// 添加到页面图片路径列表
|
// 添加到页面图片路径列表
|
||||||
if (!_pptImagePaths.ContainsKey(currentSlideNumber))
|
if (!_pptImagePaths.ContainsKey(currentSlideNumber))
|
||||||
@@ -1289,14 +1290,14 @@ namespace Ink_Canvas.Windows
|
|||||||
else if (currentSlideNumber > 0)
|
else if (currentSlideNumber > 0)
|
||||||
{
|
{
|
||||||
// 即使不保存到JSON,也要记录图片和页面编号的关联(用于翻页显示/隐藏)
|
// 即使不保存到JSON,也要记录图片和页面编号的关联(用于翻页显示/隐藏)
|
||||||
_pptImages[image] = currentSlideNumber;
|
_pptImages[element] = currentSlideNumber;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 先添加到画布(与MainWindow的实现保持一致)
|
// 先添加到画布(与MainWindow的实现保持一致)
|
||||||
inkCanvas.Children.Add(image);
|
inkCanvas.Children.Add(element);
|
||||||
|
|
||||||
// 等待图片加载完成后再进行后续处理
|
// 等待图片加载完成后再进行后续处理
|
||||||
image.Loaded += (s, args) =>
|
element.Loaded += (s, args) =>
|
||||||
{
|
{
|
||||||
Application.Current.Dispatcher.BeginInvoke(new Action(() =>
|
Application.Current.Dispatcher.BeginInvoke(new Action(() =>
|
||||||
{
|
{
|
||||||
@@ -1305,19 +1306,19 @@ namespace Ink_Canvas.Windows
|
|||||||
// 初始化TransformGroup
|
// 初始化TransformGroup
|
||||||
var initializeTransformMethod = _mainWindow.GetType().GetMethod("InitializeElementTransform",
|
var initializeTransformMethod = _mainWindow.GetType().GetMethod("InitializeElementTransform",
|
||||||
System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance);
|
System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance);
|
||||||
initializeTransformMethod?.Invoke(_mainWindow, new object[] { image });
|
initializeTransformMethod?.Invoke(_mainWindow, new object[] { element });
|
||||||
|
|
||||||
// 居中缩放
|
// 居中缩放
|
||||||
var centerMethod = _mainWindow.GetType().GetMethod("CenterAndScaleElement",
|
var centerMethod = _mainWindow.GetType().GetMethod("CenterAndScaleElement",
|
||||||
System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance);
|
System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance);
|
||||||
centerMethod?.Invoke(_mainWindow, new object[] { image });
|
centerMethod?.Invoke(_mainWindow, new object[] { element });
|
||||||
|
|
||||||
// 绑定事件处理器
|
// 绑定事件处理器
|
||||||
var bindEventsMethod = _mainWindow.GetType().GetMethod("BindElementEvents",
|
var bindEventsMethod = _mainWindow.GetType().GetMethod("BindElementEvents",
|
||||||
System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance);
|
System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance);
|
||||||
bindEventsMethod?.Invoke(_mainWindow, new object[] { image });
|
bindEventsMethod?.Invoke(_mainWindow, new object[] { element });
|
||||||
|
|
||||||
LogHelper.WriteLogToFile($"图片插入完成: {image.Name}, PPT页面: {currentSlideNumber}");
|
LogHelper.WriteLogToFile($"图片插入完成: {element.Name}, PPT页面: {currentSlideNumber}");
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
@@ -1333,7 +1334,7 @@ namespace Ink_Canvas.Windows
|
|||||||
if (timeMachine != null)
|
if (timeMachine != null)
|
||||||
{
|
{
|
||||||
var commitMethod = timeMachine.GetType().GetMethod("CommitElementInsertHistory");
|
var commitMethod = timeMachine.GetType().GetMethod("CommitElementInsertHistory");
|
||||||
commitMethod?.Invoke(timeMachine, new object[] { image });
|
commitMethod?.Invoke(timeMachine, new object[] { element });
|
||||||
}
|
}
|
||||||
|
|
||||||
// 切换到选择模式
|
// 切换到选择模式
|
||||||
@@ -1699,12 +1700,16 @@ namespace Ink_Canvas.Windows
|
|||||||
|
|
||||||
// 检查已存在的图片路径(通过Tag)
|
// 检查已存在的图片路径(通过Tag)
|
||||||
var existingImagePaths = new HashSet<string>();
|
var existingImagePaths = new HashSet<string>();
|
||||||
foreach (System.Windows.Controls.Image existingImage in inkCanvas.Children.OfType<System.Windows.Controls.Image>())
|
foreach (var existingImage in inkCanvas.Children.OfType<System.Windows.Controls.Image>())
|
||||||
{
|
{
|
||||||
if (existingImage.Tag is string tagPath && !string.IsNullOrEmpty(tagPath))
|
if (existingImage.Tag is string tagPath && !string.IsNullOrEmpty(tagPath))
|
||||||
{
|
|
||||||
existingImagePaths.Add(tagPath);
|
existingImagePaths.Add(tagPath);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
foreach (var existingPdf in inkCanvas.Children.OfType<PdfEmbeddedView>())
|
||||||
|
{
|
||||||
|
if (existingPdf.Tag is string tagPath && !string.IsNullOrEmpty(tagPath))
|
||||||
|
existingImagePaths.Add(tagPath);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 使用反射调用MainWindow的CreateAndCompressImageAsync方法
|
// 使用反射调用MainWindow的CreateAndCompressImageAsync方法
|
||||||
@@ -1733,17 +1738,17 @@ namespace Ink_Canvas.Windows
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
var imageTask = createImageMethod.Invoke(_mainWindow, new object[] { imagePath }) as System.Threading.Tasks.Task<System.Windows.Controls.Image>;
|
var imageTask = createImageMethod.Invoke(_mainWindow, new object[] { imagePath }) as System.Threading.Tasks.Task<FrameworkElement>;
|
||||||
if (imageTask != null)
|
if (imageTask != null)
|
||||||
{
|
{
|
||||||
var image = await imageTask;
|
var inserted = await imageTask;
|
||||||
if (image != null)
|
if (inserted != null)
|
||||||
{
|
{
|
||||||
// 保存原始文件路径到Tag
|
// 保存原始文件路径到Tag
|
||||||
image.Tag = imagePath;
|
inserted.Tag = imagePath;
|
||||||
|
|
||||||
// 插入图片(不保存路径,因为已经存在)
|
// 插入图片(不保存路径,因为已经存在)
|
||||||
await InsertImageToMainWindow(image, imagePath, false);
|
await InsertImageToMainWindow(inserted, imagePath, false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,179 +0,0 @@
|
|||||||
<Window x:Class="Ink_Canvas.Windows.PluginSettingsWindow"
|
|
||||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
|
||||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
|
||||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
|
||||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
|
||||||
xmlns:ui="http://schemas.inkore.net/lib/ui/wpf/modern"
|
|
||||||
xmlns:ikw="http://schemas.inkore.net/lib/ui/wpf"
|
|
||||||
xmlns:local="clr-namespace:Ink_Canvas.Windows"
|
|
||||||
mc:Ignorable="d"
|
|
||||||
Title="插件管理" Height="550" Width="800"
|
|
||||||
WindowStartupLocation="CenterScreen"
|
|
||||||
ResizeMode="CanResize"
|
|
||||||
Topmost="True"
|
|
||||||
ui:ThemeManager.IsThemeAware="True"
|
|
||||||
ui:TitleBar.ExtendViewIntoTitleBar="True"
|
|
||||||
ui:WindowHelper.SystemBackdropType="Mica"
|
|
||||||
ui:WindowHelper.UseModernWindowStyle="True"
|
|
||||||
ui:TitleBar.Height="48">
|
|
||||||
|
|
||||||
<Window.Resources>
|
|
||||||
<!-- 定义必要的资源 -->
|
|
||||||
<SolidColorBrush x:Key="BorderBrush" Color="#DDDDDD"/>
|
|
||||||
<SolidColorBrush x:Key="SystemAccentColorLight1" Color="#3B82F6"/>
|
|
||||||
<SolidColorBrush x:Key="SystemAccentColor" Color="#2563EB"/>
|
|
||||||
<SolidColorBrush x:Key="SystemControlBackgroundChromeMediumBrush" Color="#F0F0F0"/>
|
|
||||||
</Window.Resources>
|
|
||||||
|
|
||||||
<Grid>
|
|
||||||
<Grid.RowDefinitions>
|
|
||||||
<RowDefinition Height="Auto"/>
|
|
||||||
<RowDefinition Height="*"/>
|
|
||||||
<RowDefinition Height="Auto"/>
|
|
||||||
</Grid.RowDefinitions>
|
|
||||||
|
|
||||||
<!-- 自定义标题栏 -->
|
|
||||||
<Border x:Name="Border_TitleBarRoot"
|
|
||||||
Height="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=Window}, Path=(ui:TitleBar.Height)}">
|
|
||||||
<Grid>
|
|
||||||
<Grid.ColumnDefinitions>
|
|
||||||
<ColumnDefinition/>
|
|
||||||
<ColumnDefinition Width="Auto"/>
|
|
||||||
<ColumnDefinition Width="Auto"/>
|
|
||||||
</Grid.ColumnDefinitions>
|
|
||||||
|
|
||||||
<TextBlock Text="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=Window}, Path=Title}"
|
|
||||||
VerticalAlignment="Center" Margin="12,0,0,0" FontSize="12" FontWeight="SemiBold"/>
|
|
||||||
|
|
||||||
<!--Right Inset-->
|
|
||||||
<Rectangle Width="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=Window}, Path=(ui:TitleBar.SystemOverlayRightInset)}"
|
|
||||||
Grid.Column="2"/>
|
|
||||||
|
|
||||||
<!--Right Buttons-->
|
|
||||||
<ikw:SimpleStackPanel x:Name="StackPanel_RightButtons"
|
|
||||||
Orientation="Horizontal" Grid.Column="1" Spacing="5">
|
|
||||||
|
|
||||||
|
|
||||||
</ikw:SimpleStackPanel>
|
|
||||||
</Grid>
|
|
||||||
</Border>
|
|
||||||
|
|
||||||
<!-- 内容区域背景 -->
|
|
||||||
<Border Grid.Row="1" Grid.RowSpan="2"
|
|
||||||
Background="{DynamicResource SystemControlBackgroundAltHighBrush}"
|
|
||||||
BorderThickness="0,1,0,0"
|
|
||||||
BorderBrush="{DynamicResource SystemControlForegroundBaseLowBrush}"/>
|
|
||||||
|
|
||||||
<!-- 主内容区 -->
|
|
||||||
<Grid Grid.Row="1" Margin="20,20,20,20">
|
|
||||||
<Grid.ColumnDefinitions>
|
|
||||||
<ColumnDefinition Width="250"/>
|
|
||||||
<ColumnDefinition Width="*"/>
|
|
||||||
</Grid.ColumnDefinitions>
|
|
||||||
|
|
||||||
<!-- 左侧插件列表 -->
|
|
||||||
<Border Grid.Column="0" BorderThickness="1" BorderBrush="{DynamicResource BorderBrush}" Margin="0,0,10,0" CornerRadius="5">
|
|
||||||
<Grid>
|
|
||||||
<Grid.RowDefinitions>
|
|
||||||
<RowDefinition Height="Auto"/>
|
|
||||||
<RowDefinition Height="*"/>
|
|
||||||
</Grid.RowDefinitions>
|
|
||||||
|
|
||||||
<TextBlock Grid.Row="0" Text="插件列表" Margin="10,10,0,5" FontSize="16" FontWeight="SemiBold" Foreground="Black"/>
|
|
||||||
|
|
||||||
<ListView Grid.Row="1" x:Name="PluginListView" BorderThickness="0" Margin="0,5,0,0"
|
|
||||||
SelectionChanged="PluginListView_SelectionChanged">
|
|
||||||
<ListView.ItemTemplate>
|
|
||||||
<DataTemplate>
|
|
||||||
<Grid Margin="0,5">
|
|
||||||
<Grid.ColumnDefinitions>
|
|
||||||
<ColumnDefinition Width="*"/>
|
|
||||||
<ColumnDefinition Width="Auto"/>
|
|
||||||
</Grid.ColumnDefinitions>
|
|
||||||
<TextBlock Text="{Binding Name}" Grid.Column="0" VerticalAlignment="Center"
|
|
||||||
Foreground="Black" FontWeight="Normal" FontSize="14"/>
|
|
||||||
<ui:ToggleSwitch Grid.Column="1" IsOn="{Binding IsEnabled}"
|
|
||||||
Toggled="PluginToggleSwitch_Toggled"
|
|
||||||
Tag="{Binding}" MinWidth="40"/>
|
|
||||||
</Grid>
|
|
||||||
</DataTemplate>
|
|
||||||
</ListView.ItemTemplate>
|
|
||||||
</ListView>
|
|
||||||
</Grid>
|
|
||||||
</Border>
|
|
||||||
|
|
||||||
<!-- 右侧插件详情和设置 -->
|
|
||||||
<ScrollViewer Grid.Column="1" Margin="10,0,0,0" VerticalScrollBarVisibility="Auto">
|
|
||||||
<Grid>
|
|
||||||
<Grid.RowDefinitions>
|
|
||||||
<RowDefinition Height="Auto"/>
|
|
||||||
<RowDefinition Height="Auto"/>
|
|
||||||
<RowDefinition Height="*"/>
|
|
||||||
</Grid.RowDefinitions>
|
|
||||||
|
|
||||||
<!-- 插件详情 -->
|
|
||||||
<Border Grid.Row="0" BorderThickness="1" BorderBrush="{DynamicResource BorderBrush}" Padding="15" CornerRadius="5">
|
|
||||||
<Grid x:Name="PluginDetailGrid">
|
|
||||||
<Grid.RowDefinitions>
|
|
||||||
<RowDefinition Height="Auto"/>
|
|
||||||
<RowDefinition Height="Auto"/>
|
|
||||||
<RowDefinition Height="Auto"/>
|
|
||||||
<RowDefinition Height="Auto"/>
|
|
||||||
</Grid.RowDefinitions>
|
|
||||||
<Grid.ColumnDefinitions>
|
|
||||||
<ColumnDefinition Width="Auto"/>
|
|
||||||
<ColumnDefinition Width="*"/>
|
|
||||||
<ColumnDefinition Width="Auto"/>
|
|
||||||
</Grid.ColumnDefinitions>
|
|
||||||
|
|
||||||
<TextBlock Grid.Row="0" Grid.Column="0" Text="名称:" FontWeight="SemiBold" Margin="0,0,0,5" Foreground="Black"/>
|
|
||||||
<TextBlock Grid.Row="0" Grid.Column="1" Text="{Binding Name}" Margin="0,0,0,5" Foreground="Black"/>
|
|
||||||
|
|
||||||
<TextBlock Grid.Row="1" Grid.Column="0" Text="版本:" FontWeight="SemiBold" Margin="0,0,0,5" Foreground="Black"/>
|
|
||||||
<TextBlock Grid.Row="1" Grid.Column="1" Text="{Binding Version}" Margin="0,0,0,5" Foreground="Black"/>
|
|
||||||
|
|
||||||
<TextBlock Grid.Row="2" Grid.Column="0" Text="作者:" FontWeight="SemiBold" Margin="0,0,0,5" Foreground="Black"/>
|
|
||||||
<TextBlock Grid.Row="2" Grid.Column="1" Text="{Binding Author}" Margin="0,0,0,5" Foreground="Black"/>
|
|
||||||
|
|
||||||
<TextBlock Grid.Row="3" Grid.Column="0" Text="描述:" FontWeight="SemiBold" Margin="0,0,0,5" Foreground="Black"/>
|
|
||||||
<TextBlock Grid.Row="3" Grid.Column="1" Text="{Binding Description}" TextWrapping="Wrap" Margin="0,0,0,5" Foreground="Black"/>
|
|
||||||
|
|
||||||
<Button Grid.Row="3" Grid.Column="2" x:Name="BtnDeletePlugin" Content="删除插件"
|
|
||||||
Visibility="Collapsed" Click="BtnDeletePlugin_Click"
|
|
||||||
Margin="10,0,0,0" Padding="8,3" HorizontalAlignment="Right"
|
|
||||||
Background="#FFEE5555" Foreground="White"/>
|
|
||||||
</Grid>
|
|
||||||
</Border>
|
|
||||||
|
|
||||||
<!-- 插件设置区域 -->
|
|
||||||
<Border Grid.Row="1" BorderThickness="1" BorderBrush="{DynamicResource BorderBrush}" Margin="0,10,0,0" Padding="15" CornerRadius="5">
|
|
||||||
<StackPanel>
|
|
||||||
<TextBlock Text="插件设置" FontSize="16" FontWeight="SemiBold" Margin="0,0,0,10" Foreground="Black"/>
|
|
||||||
<ContentControl x:Name="PluginSettingsContainer" MinHeight="50"/>
|
|
||||||
</StackPanel>
|
|
||||||
</Border>
|
|
||||||
|
|
||||||
<!-- 插件配置内容区 -->
|
|
||||||
<Border Grid.Row="2" BorderThickness="1" BorderBrush="{DynamicResource BorderBrush}" Margin="0,10,0,0" Padding="0" CornerRadius="5">
|
|
||||||
<ContentControl x:Name="PluginContentContainer" Margin="0"/>
|
|
||||||
</Border>
|
|
||||||
</Grid>
|
|
||||||
</ScrollViewer>
|
|
||||||
</Grid>
|
|
||||||
|
|
||||||
<!-- 底部操作栏 -->
|
|
||||||
<Border Grid.Row="2" Background="{DynamicResource SystemControlBackgroundAltHighBrush}" Height="60">
|
|
||||||
<Grid>
|
|
||||||
<StackPanel Orientation="Horizontal" HorizontalAlignment="Left" VerticalAlignment="Center" Margin="20,0,0,0">
|
|
||||||
<Button x:Name="BtnLoadPlugin" Content="加载本地插件" Click="BtnLoadPlugin_Click"
|
|
||||||
Padding="15,5" Background="{DynamicResource SystemAccentColor}" Foreground="White"/>
|
|
||||||
<Button x:Name="BtnSaveConfig" Content="保存状态" Click="BtnSaveConfig_Click"
|
|
||||||
Padding="15,5" Margin="10,0,0,0" Background="{DynamicResource SystemAccentColor}" Foreground="White"
|
|
||||||
ToolTip="手动保存当前所有插件的启用/禁用状态到配置文件"/>
|
|
||||||
<Button x:Name="BtnExportPlugin" Content="导出插件" Click="BtnExportPlugin_Click"
|
|
||||||
Padding="15,5" Margin="10,0,0,0" Background="{DynamicResource SystemAccentColor}" Foreground="White"/>
|
|
||||||
</StackPanel>
|
|
||||||
</Grid>
|
|
||||||
</Border>
|
|
||||||
</Grid>
|
|
||||||
</Window>
|
|
||||||
@@ -1,727 +0,0 @@
|
|||||||
using Ink_Canvas.Helpers;
|
|
||||||
using Ink_Canvas.Helpers.Plugins;
|
|
||||||
using Ink_Canvas.Helpers.Plugins.BuiltIn;
|
|
||||||
using Ink_Canvas.Helpers.Plugins.BuiltIn.SuperLauncher;
|
|
||||||
using iNKORE.UI.WPF.Modern.Controls;
|
|
||||||
using Microsoft.Win32;
|
|
||||||
using System;
|
|
||||||
using System.Collections.ObjectModel;
|
|
||||||
using System.ComponentModel;
|
|
||||||
using System.IO;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Windows;
|
|
||||||
using System.Windows.Controls;
|
|
||||||
using System.Windows.Threading;
|
|
||||||
using MessageBox = iNKORE.UI.WPF.Modern.Controls.MessageBox;
|
|
||||||
|
|
||||||
namespace Ink_Canvas.Windows
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// PluginSettingsWindow.xaml 的交互逻辑
|
|
||||||
/// </summary>
|
|
||||||
public partial class PluginSettingsWindow : Window, INotifyPropertyChanged
|
|
||||||
{
|
|
||||||
public event PropertyChangedEventHandler PropertyChanged;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 刷新插件列表
|
|
||||||
/// </summary>
|
|
||||||
public void RefreshPluginList()
|
|
||||||
{
|
|
||||||
LoadPlugins();
|
|
||||||
|
|
||||||
// 如果当前选中的插件仍然存在,保持其选中状态
|
|
||||||
if (SelectedPlugin != null)
|
|
||||||
{
|
|
||||||
var matchingPlugin = Plugins.FirstOrDefault(p => p.Plugin.GetType().FullName == SelectedPlugin.GetType().FullName);
|
|
||||||
if (matchingPlugin != null)
|
|
||||||
{
|
|
||||||
PluginListView.SelectedItem = matchingPlugin;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
OnPropertyChanged(nameof(SelectedPlugin));
|
|
||||||
LogHelper.WriteLogToFile("插件列表已刷新");
|
|
||||||
}
|
|
||||||
|
|
||||||
private IPlugin _selectedPlugin;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 当前选中的插件
|
|
||||||
/// </summary>
|
|
||||||
public IPlugin SelectedPlugin
|
|
||||||
{
|
|
||||||
get => _selectedPlugin;
|
|
||||||
set
|
|
||||||
{
|
|
||||||
if (_selectedPlugin != value)
|
|
||||||
{
|
|
||||||
_selectedPlugin = value;
|
|
||||||
OnPropertyChanged(nameof(SelectedPlugin));
|
|
||||||
OnPropertyChanged(nameof(Name));
|
|
||||||
OnPropertyChanged(nameof(Version));
|
|
||||||
OnPropertyChanged(nameof(Author));
|
|
||||||
OnPropertyChanged(nameof(Description));
|
|
||||||
OnPropertyChanged(nameof(IsEnabled));
|
|
||||||
OnPropertyChanged(nameof(IsBuiltIn));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public new string Name => SelectedPlugin?.Name ?? string.Empty;
|
|
||||||
public string Version => SelectedPlugin?.Version?.ToString() ?? string.Empty;
|
|
||||||
public string Author => SelectedPlugin?.Author ?? string.Empty;
|
|
||||||
public string Description => SelectedPlugin?.Description ?? string.Empty;
|
|
||||||
public new bool IsEnabled => SelectedPlugin is PluginBase plugin && plugin.IsEnabled;
|
|
||||||
public bool IsBuiltIn => SelectedPlugin?.IsBuiltIn ?? false;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 插件列表
|
|
||||||
/// </summary>
|
|
||||||
public ObservableCollection<PluginViewModel> Plugins { get; } = new ObservableCollection<PluginViewModel>();
|
|
||||||
|
|
||||||
public PluginSettingsWindow()
|
|
||||||
{
|
|
||||||
InitializeComponent();
|
|
||||||
|
|
||||||
// 设置数据上下文
|
|
||||||
PluginDetailGrid.DataContext = this;
|
|
||||||
|
|
||||||
// 设置导出按钮初始状态
|
|
||||||
BtnExportPlugin.IsEnabled = false;
|
|
||||||
BtnExportPlugin.ToolTip = "请先选择要导出的插件";
|
|
||||||
|
|
||||||
// 加载插件列表
|
|
||||||
LoadPlugins();
|
|
||||||
|
|
||||||
// 注册窗口关闭事件
|
|
||||||
Closing += PluginSettingsWindow_Closing;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 窗口关闭事件处理
|
|
||||||
/// </summary>
|
|
||||||
private void PluginSettingsWindow_Closing(object sender, CancelEventArgs e)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
// 保存插件配置
|
|
||||||
LogHelper.WriteLogToFile("插件管理窗口关闭,保存插件配置...");
|
|
||||||
PluginManager.Instance.SaveConfig();
|
|
||||||
LogHelper.WriteLogToFile("插件配置已保存");
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
LogHelper.WriteLogToFile($"关闭窗口时保存插件配置出错: {ex.Message}", LogHelper.LogType.Error);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 加载插件列表
|
|
||||||
/// </summary>
|
|
||||||
private void LoadPlugins()
|
|
||||||
{
|
|
||||||
Plugins.Clear();
|
|
||||||
|
|
||||||
LogHelper.WriteLogToFile($"开始加载插件列表到UI,插件总数: {PluginManager.Instance.Plugins.Count}");
|
|
||||||
|
|
||||||
// 添加所有已加载的插件
|
|
||||||
foreach (var plugin in PluginManager.Instance.Plugins)
|
|
||||||
{
|
|
||||||
bool isEnabled = false;
|
|
||||||
|
|
||||||
// 从插件实例获取启用状态
|
|
||||||
if (plugin is PluginBase pluginBase)
|
|
||||||
{
|
|
||||||
isEnabled = pluginBase.IsEnabled;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 记录插件详细信息
|
|
||||||
LogHelper.WriteLogToFile($"正在加载插件到UI: 类型={plugin.GetType().FullName}, 名称={plugin.Name ?? "未命名"}, 状态={isEnabled}");
|
|
||||||
|
|
||||||
// 创建视图模型并添加到集合
|
|
||||||
var viewModel = new PluginViewModel(plugin)
|
|
||||||
{
|
|
||||||
IsEnabled = isEnabled
|
|
||||||
};
|
|
||||||
Plugins.Add(viewModel);
|
|
||||||
|
|
||||||
LogHelper.WriteLogToFile($"已加载插件到UI列表: {plugin.Name},状态: {(isEnabled ? "启用" : "禁用")}");
|
|
||||||
}
|
|
||||||
|
|
||||||
// 绑定到ListView
|
|
||||||
LogHelper.WriteLogToFile($"绑定 {Plugins.Count} 个插件到ListView");
|
|
||||||
PluginListView.ItemsSource = Plugins;
|
|
||||||
|
|
||||||
// 如果有插件,选择第一个
|
|
||||||
if (Plugins.Count > 0)
|
|
||||||
{
|
|
||||||
LogHelper.WriteLogToFile($"选择第一个插件: {Plugins[0].Name}");
|
|
||||||
PluginListView.SelectedIndex = 0;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
LogHelper.WriteLogToFile("没有找到任何插件", LogHelper.LogType.Warning);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 更新属性变更通知
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="propertyName">属性名称</param>
|
|
||||||
protected void OnPropertyChanged(string propertyName)
|
|
||||||
{
|
|
||||||
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 插件列表选择变更事件
|
|
||||||
/// </summary>
|
|
||||||
private void PluginListView_SelectionChanged(object sender, SelectionChangedEventArgs e)
|
|
||||||
{
|
|
||||||
if (PluginListView.SelectedItem is PluginViewModel viewModel)
|
|
||||||
{
|
|
||||||
// 设置当前选中的插件
|
|
||||||
SelectedPlugin = viewModel.Plugin;
|
|
||||||
|
|
||||||
// 加载插件设置界面
|
|
||||||
PluginSettingsContainer.Content = SelectedPlugin.GetSettingsView();
|
|
||||||
|
|
||||||
// 设置删除按钮的可见性
|
|
||||||
BtnDeletePlugin.Visibility = !SelectedPlugin.IsBuiltIn ? Visibility.Visible : Visibility.Collapsed;
|
|
||||||
|
|
||||||
// 设置导出按钮的可用状态
|
|
||||||
BtnExportPlugin.IsEnabled = !SelectedPlugin.IsBuiltIn;
|
|
||||||
if (SelectedPlugin.IsBuiltIn)
|
|
||||||
{
|
|
||||||
BtnExportPlugin.ToolTip = "内置插件无法导出";
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
BtnExportPlugin.ToolTip = "将插件导出为.iccpp文件";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
SelectedPlugin = null;
|
|
||||||
PluginSettingsContainer.Content = null;
|
|
||||||
BtnDeletePlugin.Visibility = Visibility.Collapsed;
|
|
||||||
BtnExportPlugin.IsEnabled = false;
|
|
||||||
BtnExportPlugin.ToolTip = "请先选择要导出的插件";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 加载本地插件按钮点击事件
|
|
||||||
/// </summary>
|
|
||||||
private void BtnLoadPlugin_Click(object sender, RoutedEventArgs e)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
// 创建文件对话框
|
|
||||||
OpenFileDialog dialog = new OpenFileDialog
|
|
||||||
{
|
|
||||||
Filter = "ICC插件文件(*.iccpp)|*.iccpp",
|
|
||||||
Title = "选择要加载的插件文件"
|
|
||||||
};
|
|
||||||
|
|
||||||
// 显示对话框
|
|
||||||
if (dialog.ShowDialog() == true)
|
|
||||||
{
|
|
||||||
// 获取插件文件路径
|
|
||||||
string pluginPath = dialog.FileName;
|
|
||||||
|
|
||||||
// 检查是否在Plugins目录下
|
|
||||||
string pluginsDirectory = Path.Combine(App.RootPath, "Plugins");
|
|
||||||
string targetPath = Path.Combine(pluginsDirectory, Path.GetFileName(pluginPath));
|
|
||||||
|
|
||||||
// 确保Plugins目录存在
|
|
||||||
if (!Directory.Exists(pluginsDirectory))
|
|
||||||
{
|
|
||||||
Directory.CreateDirectory(pluginsDirectory);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 如果插件不在Plugins目录下,复制过去
|
|
||||||
if (!string.Equals(pluginPath, targetPath, StringComparison.OrdinalIgnoreCase))
|
|
||||||
{
|
|
||||||
File.Copy(pluginPath, targetPath, true);
|
|
||||||
pluginPath = targetPath;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 加载插件
|
|
||||||
IPlugin plugin = PluginManager.Instance.LoadExternalPlugin(pluginPath);
|
|
||||||
|
|
||||||
if (plugin != null)
|
|
||||||
{
|
|
||||||
// 刷新插件列表
|
|
||||||
LoadPlugins();
|
|
||||||
|
|
||||||
// 选择新加载的插件
|
|
||||||
foreach (var item in Plugins)
|
|
||||||
{
|
|
||||||
if (item.Plugin == plugin)
|
|
||||||
{
|
|
||||||
PluginListView.SelectedItem = item;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
MessageBox.Show($"插件 {plugin.Name} v{plugin.Version} 已成功加载!", "成功", MessageBoxButton.OK, MessageBoxImage.Information);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
MessageBox.Show("插件加载失败,请检查文件是否有效。", "错误", MessageBoxButton.OK, MessageBoxImage.Error);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
MessageBox.Show($"加载插件时发生错误:{ex.Message}", "错误", MessageBoxButton.OK, MessageBoxImage.Error);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 删除插件按钮点击事件
|
|
||||||
/// </summary>
|
|
||||||
private void BtnDeletePlugin_Click(object sender, RoutedEventArgs e)
|
|
||||||
{
|
|
||||||
if (SelectedPlugin == null) return;
|
|
||||||
|
|
||||||
// 不能删除内置插件
|
|
||||||
if (SelectedPlugin.IsBuiltIn)
|
|
||||||
{
|
|
||||||
MessageBox.Show("内置插件无法删除。", "提示", MessageBoxButton.OK, MessageBoxImage.Information);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 保存插件名称,以便在删除后使用
|
|
||||||
string pluginName = SelectedPlugin.Name;
|
|
||||||
|
|
||||||
// 确认删除
|
|
||||||
MessageBoxResult result = MessageBox.Show(
|
|
||||||
$"确定要删除插件 {pluginName} 吗?\n此操作将永久删除插件文件,无法恢复。",
|
|
||||||
"删除确认",
|
|
||||||
MessageBoxButton.YesNo,
|
|
||||||
MessageBoxImage.Warning);
|
|
||||||
|
|
||||||
if (result == MessageBoxResult.Yes)
|
|
||||||
{
|
|
||||||
// 删除插件
|
|
||||||
bool success = PluginManager.Instance.DeletePlugin(SelectedPlugin);
|
|
||||||
|
|
||||||
if (success)
|
|
||||||
{
|
|
||||||
// 刷新插件列表
|
|
||||||
LoadPlugins();
|
|
||||||
|
|
||||||
// 如果还有插件,选择第一个
|
|
||||||
if (Plugins.Count > 0)
|
|
||||||
{
|
|
||||||
PluginListView.SelectedIndex = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
MessageBox.Show($"插件 {pluginName} 已成功删除。", "成功", MessageBoxButton.OK, MessageBoxImage.Information);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
MessageBox.Show($"删除插件 {pluginName} 失败,请稍后重试。", "错误", MessageBoxButton.OK, MessageBoxImage.Error);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 导出插件按钮点击事件
|
|
||||||
/// </summary>
|
|
||||||
private void BtnExportPlugin_Click(object sender, RoutedEventArgs e)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
// 检查是否有选中的插件
|
|
||||||
if (SelectedPlugin == null)
|
|
||||||
{
|
|
||||||
MessageBox.Show("请先选择要导出的插件", "提示", MessageBoxButton.OK, MessageBoxImage.Information);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 检查是否为内置插件
|
|
||||||
if (SelectedPlugin.IsBuiltIn)
|
|
||||||
{
|
|
||||||
MessageBox.Show("内置插件无法导出", "提示", MessageBoxButton.OK, MessageBoxImage.Information);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 检查插件文件是否存在
|
|
||||||
string pluginPath = null;
|
|
||||||
if (SelectedPlugin is PluginBase pluginBase)
|
|
||||||
{
|
|
||||||
pluginPath = pluginBase.PluginPath;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (string.IsNullOrEmpty(pluginPath) || !File.Exists(pluginPath))
|
|
||||||
{
|
|
||||||
MessageBox.Show("插件文件不存在或无法访问", "错误", MessageBoxButton.OK, MessageBoxImage.Error);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 创建保存文件对话框
|
|
||||||
SaveFileDialog dialog = new SaveFileDialog
|
|
||||||
{
|
|
||||||
Filter = "ICC插件文件(*.iccpp)|*.iccpp",
|
|
||||||
Title = "导出插件",
|
|
||||||
FileName = Path.GetFileName(pluginPath)
|
|
||||||
};
|
|
||||||
|
|
||||||
// 显示对话框
|
|
||||||
if (dialog.ShowDialog() == true)
|
|
||||||
{
|
|
||||||
// 获取目标路径
|
|
||||||
string targetPath = dialog.FileName;
|
|
||||||
|
|
||||||
// 如果目标文件已存在,询问是否覆盖
|
|
||||||
if (File.Exists(targetPath) && !string.Equals(pluginPath, targetPath, StringComparison.OrdinalIgnoreCase))
|
|
||||||
{
|
|
||||||
MessageBoxResult result = MessageBox.Show("目标文件已存在,是否覆盖?", "确认", MessageBoxButton.YesNo, MessageBoxImage.Question);
|
|
||||||
if (result != MessageBoxResult.Yes)
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 复制插件文件到目标路径
|
|
||||||
if (!string.Equals(pluginPath, targetPath, StringComparison.OrdinalIgnoreCase))
|
|
||||||
{
|
|
||||||
File.Copy(pluginPath, targetPath, true);
|
|
||||||
}
|
|
||||||
|
|
||||||
LogHelper.WriteLogToFile($"插件 {SelectedPlugin.Name} 已成功导出到: {targetPath}");
|
|
||||||
MessageBox.Show($"插件 {SelectedPlugin.Name} 已成功导出!", "成功", MessageBoxButton.OK, MessageBoxImage.Information);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
LogHelper.WriteLogToFile($"导出插件时出错: {ex.Message}", LogHelper.LogType.Error);
|
|
||||||
MessageBox.Show($"导出插件时发生错误: {ex.Message}", "错误", MessageBoxButton.OK, MessageBoxImage.Error);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 插件开关切换事件
|
|
||||||
/// </summary>
|
|
||||||
private void PluginToggleSwitch_Toggled(object sender, RoutedEventArgs e)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
if (sender is ToggleSwitch toggleSwitch &&
|
|
||||||
toggleSwitch.Tag is IPlugin plugin)
|
|
||||||
{
|
|
||||||
// 记录当前开关状态
|
|
||||||
bool targetState = toggleSwitch.IsOn;
|
|
||||||
|
|
||||||
// 记录插件类型名称和名称,用于稍后查找重载后的插件
|
|
||||||
string pluginTypeName = plugin.GetType().FullName;
|
|
||||||
string pluginName = plugin.Name;
|
|
||||||
bool wasBuiltIn = plugin.IsBuiltIn;
|
|
||||||
|
|
||||||
LogHelper.WriteLogToFile($"UI开关切换: {pluginName}, 目标状态: {(targetState ? "启用" : "禁用")}");
|
|
||||||
|
|
||||||
// 切换插件状态
|
|
||||||
PluginManager.Instance.TogglePlugin(plugin, targetState);
|
|
||||||
|
|
||||||
// 立即同步保存配置到文件(确保状态被立即持久化)
|
|
||||||
PluginManager.Instance.SaveConfig();
|
|
||||||
LogHelper.WriteLogToFile("插件状态已立即保存到配置文件");
|
|
||||||
|
|
||||||
// 延迟一下再检查状态,确保变更已应用
|
|
||||||
Dispatcher.BeginInvoke(new Action(() =>
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
// 查找最新的插件实例
|
|
||||||
IPlugin currentPlugin = null;
|
|
||||||
foreach (var p in PluginManager.Instance.Plugins)
|
|
||||||
{
|
|
||||||
if (p.GetType().FullName == pluginTypeName || p.Name == pluginName)
|
|
||||||
{
|
|
||||||
currentPlugin = p;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (currentPlugin == null)
|
|
||||||
{
|
|
||||||
LogHelper.WriteLogToFile($"无法找到插件: {pluginName},UI状态可能不准确", LogHelper.LogType.Warning);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 检查实际状态
|
|
||||||
bool actualState = currentPlugin is PluginBase pb && pb.IsEnabled;
|
|
||||||
LogHelper.WriteLogToFile($"插件 {pluginName} 实际状态: {(actualState ? "启用" : "禁用")}");
|
|
||||||
|
|
||||||
// 更新视图模型
|
|
||||||
PluginViewModel viewModel = null;
|
|
||||||
if (toggleSwitch.DataContext is PluginViewModel vm)
|
|
||||||
{
|
|
||||||
viewModel = vm;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
viewModel = Plugins.FirstOrDefault(p => p.Plugin == currentPlugin);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (viewModel != null)
|
|
||||||
{
|
|
||||||
// 确保视图模型状态与实际状态一致
|
|
||||||
if (viewModel.IsEnabled != actualState)
|
|
||||||
{
|
|
||||||
LogHelper.WriteLogToFile($"同步视图模型状态: {(actualState ? "启用" : "禁用")}");
|
|
||||||
viewModel.IsEnabled = actualState;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 确保UI开关状态与实际状态一致
|
|
||||||
if (toggleSwitch.IsOn != actualState)
|
|
||||||
{
|
|
||||||
LogHelper.WriteLogToFile($"同步UI开关状态: {(actualState ? "启用" : "禁用")}");
|
|
||||||
toggleSwitch.IsOn = actualState;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 如果是当前选中的插件,更新属性
|
|
||||||
if (currentPlugin == SelectedPlugin)
|
|
||||||
{
|
|
||||||
OnPropertyChanged(nameof(IsEnabled));
|
|
||||||
}
|
|
||||||
|
|
||||||
// 对于内置插件,特别处理
|
|
||||||
if (wasBuiltIn)
|
|
||||||
{
|
|
||||||
// 特殊插件刷新逻辑,如果是超级启动台插件,立即刷新UI
|
|
||||||
if (currentPlugin is SuperLauncherPlugin &&
|
|
||||||
PluginSettingsContainer.Content is LauncherSettingsControl)
|
|
||||||
{
|
|
||||||
// 重新获取设置界面
|
|
||||||
PluginSettingsContainer.Content = currentPlugin.GetSettingsView();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
LogHelper.WriteLogToFile($"插件 {pluginName} UI状态同步完成");
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
LogHelper.WriteLogToFile($"同步UI状态时出错: {ex.Message}", LogHelper.LogType.Error);
|
|
||||||
}
|
|
||||||
}), DispatcherPriority.Background);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
LogHelper.WriteLogToFile($"切换插件状态时出错: {ex.Message}", LogHelper.LogType.Error);
|
|
||||||
MessageBox.Show($"切换插件状态时出错: {ex.Message}", "错误", MessageBoxButton.OK, MessageBoxImage.Error);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 刷新插件列表按钮点击事件
|
|
||||||
/// </summary>
|
|
||||||
private void Button_Refresh_Click(object sender, RoutedEventArgs e)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
// 刷新插件列表
|
|
||||||
RefreshPluginList();
|
|
||||||
LogHelper.WriteLogToFile("用户点击刷新按钮,刷新插件列表");
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
LogHelper.WriteLogToFile($"刷新插件列表时出错: {ex.Message}", LogHelper.LogType.Error);
|
|
||||||
MessageBox.Show($"刷新插件列表时出错: {ex.Message}", "错误", MessageBoxButton.OK, MessageBoxImage.Error);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 保存插件状态按钮点击事件
|
|
||||||
/// </summary>
|
|
||||||
private void BtnSaveConfig_Click(object sender, RoutedEventArgs e)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
int syncCount = 0;
|
|
||||||
|
|
||||||
// 遍历界面上所有插件视图模型,获取开关状态
|
|
||||||
foreach (var viewModel in Plugins)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
if (viewModel.Plugin != null)
|
|
||||||
{
|
|
||||||
// 获取UI中开关的当前状态(从界面控件读取)
|
|
||||||
bool uiState = viewModel.IsEnabled;
|
|
||||||
|
|
||||||
// 获取插件类型名,用于查找配置
|
|
||||||
string pluginTypeName = viewModel.Plugin.GetType().FullName;
|
|
||||||
|
|
||||||
// 查找实际的插件实例(可能与viewModel.Plugin不同,因为可能已经重新加载)
|
|
||||||
IPlugin actualPlugin = null;
|
|
||||||
foreach (var p in PluginManager.Instance.Plugins)
|
|
||||||
{
|
|
||||||
if (p.GetType().FullName == pluginTypeName)
|
|
||||||
{
|
|
||||||
actualPlugin = p;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 如果找不到对应的实际插件实例,跳过
|
|
||||||
if (actualPlugin == null)
|
|
||||||
{
|
|
||||||
LogHelper.WriteLogToFile($"手动保存:无法找到与UI对应的插件实例:{viewModel.Name}", LogHelper.LogType.Warning);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 获取插件实际状态
|
|
||||||
bool pluginState = false;
|
|
||||||
if (actualPlugin is PluginBase pluginBase)
|
|
||||||
{
|
|
||||||
pluginState = pluginBase.IsEnabled;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 如果界面状态与插件实际状态不一致,应用界面状态
|
|
||||||
if (uiState != pluginState)
|
|
||||||
{
|
|
||||||
// 应用界面的状态到插件
|
|
||||||
PluginManager.Instance.TogglePlugin(actualPlugin, uiState);
|
|
||||||
LogHelper.WriteLogToFile($"手动保存:同步插件 {actualPlugin.Name} 状态 {pluginState} -> {uiState}");
|
|
||||||
syncCount++;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 确保配置中的状态也与界面一致
|
|
||||||
if (PluginManager.Instance.PluginStates.TryGetValue(pluginTypeName, out bool configState) && configState != uiState)
|
|
||||||
{
|
|
||||||
PluginManager.Instance.PluginStates[pluginTypeName] = uiState;
|
|
||||||
LogHelper.WriteLogToFile($"手动保存:更新配置中插件 {actualPlugin.Name} 状态 {configState} -> {uiState}");
|
|
||||||
syncCount++;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch (Exception pluginEx)
|
|
||||||
{
|
|
||||||
// 单个插件处理失败不应该影响其他插件
|
|
||||||
LogHelper.WriteLogToFile($"手动保存:处理插件 {viewModel.Name} 时出错: {pluginEx.Message}", LogHelper.LogType.Error);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 保存插件状态配置
|
|
||||||
PluginManager.Instance.SaveConfig();
|
|
||||||
|
|
||||||
// 记录日志
|
|
||||||
LogHelper.WriteLogToFile($"用户手动保存插件状态配置,同步了 {syncCount} 个状态变更");
|
|
||||||
|
|
||||||
// 刷新插件列表,确保UI与最新状态同步
|
|
||||||
RefreshPluginList();
|
|
||||||
|
|
||||||
// 显示保存成功提示
|
|
||||||
string message = syncCount > 0
|
|
||||||
? $"插件状态已成功保存,同步了 {syncCount} 个状态变更"
|
|
||||||
: "插件状态已成功保存,所有插件状态已是最新";
|
|
||||||
MessageBox.Show(message, "保存成功", MessageBoxButton.OK, MessageBoxImage.Information);
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
// 记录错误日志
|
|
||||||
LogHelper.WriteLogToFile($"手动保存插件状态时出错: {ex.Message}", LogHelper.LogType.Error);
|
|
||||||
|
|
||||||
// 显示错误信息
|
|
||||||
MessageBox.Show($"保存插件状态时发生错误: {ex.Message}", "保存失败", MessageBoxButton.OK, MessageBoxImage.Error);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 插件视图模型
|
|
||||||
/// </summary>
|
|
||||||
public class PluginViewModel : INotifyPropertyChanged
|
|
||||||
{
|
|
||||||
public event PropertyChangedEventHandler PropertyChanged;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 插件实例
|
|
||||||
/// </summary>
|
|
||||||
public IPlugin Plugin { get; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 插件名称
|
|
||||||
/// </summary>
|
|
||||||
public string Name
|
|
||||||
{
|
|
||||||
get
|
|
||||||
{
|
|
||||||
string name = Plugin?.Name ?? "未命名插件";
|
|
||||||
LogHelper.WriteLogToFile($"获取插件名称: {name},类型: {Plugin?.GetType().FullName ?? "未知"}");
|
|
||||||
return name;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 插件是否启用
|
|
||||||
/// </summary>
|
|
||||||
private bool _isEnabled;
|
|
||||||
public bool IsEnabled
|
|
||||||
{
|
|
||||||
get => _isEnabled;
|
|
||||||
set
|
|
||||||
{
|
|
||||||
if (_isEnabled != value)
|
|
||||||
{
|
|
||||||
_isEnabled = value;
|
|
||||||
OnPropertyChanged(nameof(IsEnabled));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public PluginViewModel(IPlugin plugin)
|
|
||||||
{
|
|
||||||
Plugin = plugin;
|
|
||||||
|
|
||||||
// 获取实际状态
|
|
||||||
_isEnabled = plugin is PluginBase pluginBase && pluginBase.IsEnabled;
|
|
||||||
|
|
||||||
// 记录日志
|
|
||||||
LogHelper.WriteLogToFile($"创建插件视图模型: {plugin?.GetType().FullName ?? "未知"}, 名称: {plugin?.Name ?? "未命名"}");
|
|
||||||
|
|
||||||
// 注册插件状态变更事件
|
|
||||||
if (plugin is PluginBase pb)
|
|
||||||
{
|
|
||||||
pb.EnabledStateChanged += Plugin_EnabledStateChanged;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 处理插件状态变更事件
|
|
||||||
/// </summary>
|
|
||||||
private void Plugin_EnabledStateChanged(object sender, bool isEnabled)
|
|
||||||
{
|
|
||||||
// 在UI线程上更新状态
|
|
||||||
Application.Current.Dispatcher.BeginInvoke(new Action(() =>
|
|
||||||
{
|
|
||||||
IsEnabled = isEnabled;
|
|
||||||
|
|
||||||
// 确保配置立即保存
|
|
||||||
if (sender is IPlugin plugin)
|
|
||||||
{
|
|
||||||
LogHelper.WriteLogToFile($"视图模型捕获到插件 {plugin.Name} 状态变更: {(isEnabled ? "启用" : "禁用")}");
|
|
||||||
PluginManager.Instance.SaveConfig();
|
|
||||||
LogHelper.WriteLogToFile("视图模型已触发配置保存");
|
|
||||||
}
|
|
||||||
}));
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 属性变更通知
|
|
||||||
/// </summary>
|
|
||||||
protected void OnPropertyChanged(string propertyName)
|
|
||||||
{
|
|
||||||
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -36,6 +36,13 @@
|
|||||||
</Rectangle.Clip>
|
</Rectangle.Clip>
|
||||||
</Rectangle>
|
</Rectangle>
|
||||||
|
|
||||||
|
<!-- 墨迹预览层(用于“包含墨迹”预览) -->
|
||||||
|
<Image Name="InkPreviewImage"
|
||||||
|
Stretch="Fill"
|
||||||
|
IsHitTestVisible="False"
|
||||||
|
Visibility="Collapsed"
|
||||||
|
Panel.ZIndex="950" />
|
||||||
|
|
||||||
<!-- 选择区域容器 -->
|
<!-- 选择区域容器 -->
|
||||||
<Canvas Name="SelectionCanvas" Panel.ZIndex="1000">
|
<Canvas Name="SelectionCanvas" Panel.ZIndex="1000">
|
||||||
<!-- 矩形选择模式 -->
|
<!-- 矩形选择模式 -->
|
||||||
@@ -141,6 +148,18 @@
|
|||||||
Background="#404040" />
|
Background="#404040" />
|
||||||
|
|
||||||
<!-- 操作按钮 -->
|
<!-- 操作按钮 -->
|
||||||
|
<CheckBox Name="IncludeInkCheckBox"
|
||||||
|
Content="包含墨迹"
|
||||||
|
Margin="4,0"
|
||||||
|
VerticalAlignment="Center"
|
||||||
|
Foreground="White"
|
||||||
|
Checked="IncludeInkCheckBox_Checked"
|
||||||
|
Unchecked="IncludeInkCheckBox_Unchecked" />
|
||||||
|
<Rectangle Name="ToolbarDividerRectangle"
|
||||||
|
Width="1"
|
||||||
|
Height="20"
|
||||||
|
Fill="#404040"
|
||||||
|
Margin="8,0" />
|
||||||
<Button Name="ConfirmButton"
|
<Button Name="ConfirmButton"
|
||||||
Content="确认截图"
|
Content="确认截图"
|
||||||
Margin="4,0"
|
Margin="4,0"
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ using System.Windows.Controls;
|
|||||||
using System.Windows.Forms;
|
using System.Windows.Forms;
|
||||||
using System.Windows.Input;
|
using System.Windows.Input;
|
||||||
using System.Windows.Media;
|
using System.Windows.Media;
|
||||||
|
using System.Windows.Media.Imaging;
|
||||||
using System.Windows.Shapes;
|
using System.Windows.Shapes;
|
||||||
using System.Windows.Threading;
|
using System.Windows.Threading;
|
||||||
using Brushes = System.Windows.Media.Brushes;
|
using Brushes = System.Windows.Media.Brushes;
|
||||||
@@ -37,6 +38,7 @@ namespace Ink_Canvas
|
|||||||
private Bitmap _capturedCameraImage = null;
|
private Bitmap _capturedCameraImage = null;
|
||||||
private DateTime _lastBlankClickTime = DateTime.MinValue;
|
private DateTime _lastBlankClickTime = DateTime.MinValue;
|
||||||
private WpfPoint _lastBlankClickPosition;
|
private WpfPoint _lastBlankClickPosition;
|
||||||
|
private readonly BitmapSource _inkOverlayPreview;
|
||||||
|
|
||||||
private const int DoubleClickTimeThresholdMs = 300; // 双击判定时间阈值(常见范围 200~500ms)
|
private const int DoubleClickTimeThresholdMs = 300; // 双击判定时间阈值(常见范围 200~500ms)
|
||||||
private const double DoubleClickDistanceThresholdPx = 12; // 双击判定位置阈值(像素)
|
private const double DoubleClickDistanceThresholdPx = 12; // 双击判定位置阈值(像素)
|
||||||
@@ -55,9 +57,11 @@ namespace Ink_Canvas
|
|||||||
public Bitmap CameraImage { get; private set; }
|
public Bitmap CameraImage { get; private set; }
|
||||||
public System.Windows.Media.Imaging.BitmapSource CameraBitmapSource { get; private set; }
|
public System.Windows.Media.Imaging.BitmapSource CameraBitmapSource { get; private set; }
|
||||||
public bool ShouldAddToWhiteboard { get; private set; }
|
public bool ShouldAddToWhiteboard { get; private set; }
|
||||||
|
public bool IncludeInkInScreenshot { get; private set; }
|
||||||
|
|
||||||
public ScreenshotSelectorWindow()
|
public ScreenshotSelectorWindow(BitmapSource inkOverlayPreview = null)
|
||||||
{
|
{
|
||||||
|
_inkOverlayPreview = inkOverlayPreview;
|
||||||
InitializeComponent();
|
InitializeComponent();
|
||||||
|
|
||||||
// 设置窗口覆盖所有屏幕
|
// 设置窗口覆盖所有屏幕
|
||||||
@@ -71,6 +75,7 @@ namespace Ink_Canvas
|
|||||||
|
|
||||||
// 初始化按钮状态
|
// 初始化按钮状态
|
||||||
InitializeButtonStates();
|
InitializeButtonStates();
|
||||||
|
InitializeInkPreview();
|
||||||
|
|
||||||
// 初始化摄像头服务
|
// 初始化摄像头服务
|
||||||
InitializeCameraService();
|
InitializeCameraService();
|
||||||
@@ -106,6 +111,44 @@ namespace Ink_Canvas
|
|||||||
CameraModeButton.Background = new SolidColorBrush(Color.FromRgb(107, 114, 128)); // 灰色
|
CameraModeButton.Background = new SolidColorBrush(Color.FromRgb(107, 114, 128)); // 灰色
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void InitializeInkPreview()
|
||||||
|
{
|
||||||
|
IncludeInkInScreenshot = false;
|
||||||
|
IncludeInkCheckBox.IsChecked = false;
|
||||||
|
|
||||||
|
if (_inkOverlayPreview != null)
|
||||||
|
{
|
||||||
|
InkPreviewImage.Source = _inkOverlayPreview;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
IncludeInkCheckBox.IsEnabled = false;
|
||||||
|
IncludeInkCheckBox.Opacity = 0.6;
|
||||||
|
IncludeInkCheckBox.ToolTip = "当前无可预览墨迹";
|
||||||
|
}
|
||||||
|
|
||||||
|
UpdateInkPreviewVisibility();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void IncludeInkCheckBox_Checked(object sender, RoutedEventArgs e)
|
||||||
|
{
|
||||||
|
IncludeInkInScreenshot = true;
|
||||||
|
UpdateInkPreviewVisibility();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void IncludeInkCheckBox_Unchecked(object sender, RoutedEventArgs e)
|
||||||
|
{
|
||||||
|
IncludeInkInScreenshot = false;
|
||||||
|
UpdateInkPreviewVisibility();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void UpdateInkPreviewVisibility()
|
||||||
|
{
|
||||||
|
InkPreviewImage.Visibility = IncludeInkInScreenshot && _inkOverlayPreview != null
|
||||||
|
? Visibility.Visible
|
||||||
|
: Visibility.Collapsed;
|
||||||
|
}
|
||||||
|
|
||||||
private void InitializeCameraService()
|
private void InitializeCameraService()
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
@@ -370,6 +413,7 @@ namespace Ink_Canvas
|
|||||||
RectangleModeButton.Background = new SolidColorBrush(Color.FromRgb(37, 99, 235)); // 蓝色
|
RectangleModeButton.Background = new SolidColorBrush(Color.FromRgb(37, 99, 235)); // 蓝色
|
||||||
FreehandModeButton.Background = new SolidColorBrush(Color.FromRgb(107, 114, 128)); // 灰色
|
FreehandModeButton.Background = new SolidColorBrush(Color.FromRgb(107, 114, 128)); // 灰色
|
||||||
FullScreenButton.Background = new SolidColorBrush(Color.FromRgb(107, 114, 128)); // 灰色
|
FullScreenButton.Background = new SolidColorBrush(Color.FromRgb(107, 114, 128)); // 灰色
|
||||||
|
IncludeInkCheckBox.IsEnabled = _inkOverlayPreview != null;
|
||||||
HintText.Text = "拖拽鼠标选择矩形区域";
|
HintText.Text = "拖拽鼠标选择矩形区域";
|
||||||
HintTextBorder.Visibility = Visibility.Visible;
|
HintTextBorder.Visibility = Visibility.Visible;
|
||||||
}
|
}
|
||||||
@@ -383,6 +427,7 @@ namespace Ink_Canvas
|
|||||||
FreehandModeButton.Background = new SolidColorBrush(Color.FromRgb(37, 99, 235)); // 蓝色
|
FreehandModeButton.Background = new SolidColorBrush(Color.FromRgb(37, 99, 235)); // 蓝色
|
||||||
RectangleModeButton.Background = new SolidColorBrush(Color.FromRgb(107, 114, 128)); // 灰色
|
RectangleModeButton.Background = new SolidColorBrush(Color.FromRgb(107, 114, 128)); // 灰色
|
||||||
FullScreenButton.Background = new SolidColorBrush(Color.FromRgb(107, 114, 128)); // 灰色
|
FullScreenButton.Background = new SolidColorBrush(Color.FromRgb(107, 114, 128)); // 灰色
|
||||||
|
IncludeInkCheckBox.IsEnabled = _inkOverlayPreview != null;
|
||||||
HintText.Text = "按住鼠标左键自由绘制,松开后可继续调整或重新绘制,确认后再截图";
|
HintText.Text = "按住鼠标左键自由绘制,松开后可继续调整或重新绘制,确认后再截图";
|
||||||
HintTextBorder.Visibility = Visibility.Visible;
|
HintTextBorder.Visibility = Visibility.Visible;
|
||||||
}
|
}
|
||||||
@@ -402,6 +447,7 @@ namespace Ink_Canvas
|
|||||||
|
|
||||||
// 隐藏摄像头预览
|
// 隐藏摄像头预览
|
||||||
CameraPreviewBorder.Visibility = Visibility.Collapsed;
|
CameraPreviewBorder.Visibility = Visibility.Collapsed;
|
||||||
|
IncludeInkCheckBox.IsEnabled = _inkOverlayPreview != null;
|
||||||
|
|
||||||
// 直接执行全屏截图
|
// 直接执行全屏截图
|
||||||
PerformFullScreenCapture();
|
PerformFullScreenCapture();
|
||||||
@@ -426,6 +472,8 @@ namespace Ink_Canvas
|
|||||||
CameraPreviewBorder.Visibility = Visibility.Visible;
|
CameraPreviewBorder.Visibility = Visibility.Visible;
|
||||||
HintText.Text = "摄像头预览模式,点击确认截图按钮进行截图";
|
HintText.Text = "摄像头预览模式,点击确认截图按钮进行截图";
|
||||||
HintTextBorder.Visibility = Visibility.Visible;
|
HintTextBorder.Visibility = Visibility.Visible;
|
||||||
|
IncludeInkCheckBox.IsEnabled = false;
|
||||||
|
IncludeInkCheckBox.IsChecked = false;
|
||||||
|
|
||||||
// 启动摄像头预览
|
// 启动摄像头预览
|
||||||
if (_cameraService != null && _cameraService.HasAvailableCameras())
|
if (_cameraService != null && _cameraService.HasAvailableCameras())
|
||||||
@@ -491,6 +539,7 @@ namespace Ink_Canvas
|
|||||||
private void ConfirmButton_Click(object sender, RoutedEventArgs e)
|
private void ConfirmButton_Click(object sender, RoutedEventArgs e)
|
||||||
{
|
{
|
||||||
ShouldAddToWhiteboard = false;
|
ShouldAddToWhiteboard = false;
|
||||||
|
IncludeInkInScreenshot = IncludeInkCheckBox.IsChecked == true;
|
||||||
|
|
||||||
// 在自由绘制模式下,按当前自由选区执行确认
|
// 在自由绘制模式下,按当前自由选区执行确认
|
||||||
if (_isFreehandMode)
|
if (_isFreehandMode)
|
||||||
@@ -512,6 +561,7 @@ namespace Ink_Canvas
|
|||||||
private void AddToWhiteboardButton_Click(object sender, RoutedEventArgs e)
|
private void AddToWhiteboardButton_Click(object sender, RoutedEventArgs e)
|
||||||
{
|
{
|
||||||
ShouldAddToWhiteboard = true;
|
ShouldAddToWhiteboard = true;
|
||||||
|
IncludeInkInScreenshot = IncludeInkCheckBox.IsChecked == true;
|
||||||
|
|
||||||
// 在自由绘制模式下,按当前自由选区执行确认
|
// 在自由绘制模式下,按当前自由选区执行确认
|
||||||
if (_isFreehandMode)
|
if (_isFreehandMode)
|
||||||
@@ -578,10 +628,12 @@ namespace Ink_Canvas
|
|||||||
if (hitElement != null && (
|
if (hitElement != null && (
|
||||||
hitElement is Ellipse ||
|
hitElement is Ellipse ||
|
||||||
hitElement is System.Windows.Controls.Button ||
|
hitElement is System.Windows.Controls.Button ||
|
||||||
|
hitElement is System.Windows.Controls.CheckBox ||
|
||||||
hitElement is Border ||
|
hitElement is Border ||
|
||||||
hitElement is TextBlock ||
|
hitElement is TextBlock ||
|
||||||
hitElement is StackPanel ||
|
hitElement is StackPanel ||
|
||||||
hitElement is Separator ||
|
hitElement is Separator ||
|
||||||
|
hitElement.Name == "ToolbarDividerRectangle" ||
|
||||||
hitElement.Name == "SizeInfoBorder" ||
|
hitElement.Name == "SizeInfoBorder" ||
|
||||||
hitElement.Name == "HintText" ||
|
hitElement.Name == "HintText" ||
|
||||||
hitElement.Name == "AdjustModeHint" ||
|
hitElement.Name == "AdjustModeHint" ||
|
||||||
@@ -669,10 +721,12 @@ namespace Ink_Canvas
|
|||||||
if (hitElement != null && (
|
if (hitElement != null && (
|
||||||
hitElement is Ellipse ||
|
hitElement is Ellipse ||
|
||||||
hitElement is System.Windows.Controls.Button ||
|
hitElement is System.Windows.Controls.Button ||
|
||||||
|
hitElement is System.Windows.Controls.CheckBox ||
|
||||||
hitElement is Border ||
|
hitElement is Border ||
|
||||||
hitElement is TextBlock ||
|
hitElement is TextBlock ||
|
||||||
hitElement is StackPanel ||
|
hitElement is StackPanel ||
|
||||||
hitElement is Separator ||
|
hitElement is Separator ||
|
||||||
|
hitElement.Name == "ToolbarDividerRectangle" ||
|
||||||
hitElement.Name == "SizeInfoBorder" ||
|
hitElement.Name == "SizeInfoBorder" ||
|
||||||
hitElement.Name == "HintText" ||
|
hitElement.Name == "HintText" ||
|
||||||
hitElement.Name == "AdjustModeHint"))
|
hitElement.Name == "AdjustModeHint"))
|
||||||
@@ -1178,6 +1232,7 @@ namespace Ink_Canvas
|
|||||||
SelectedPath = null;
|
SelectedPath = null;
|
||||||
CameraImage = null;
|
CameraImage = null;
|
||||||
ShouldAddToWhiteboard = false;
|
ShouldAddToWhiteboard = false;
|
||||||
|
IncludeInkInScreenshot = false;
|
||||||
DialogResult = false;
|
DialogResult = false;
|
||||||
Close();
|
Close();
|
||||||
}
|
}
|
||||||
|
|||||||
+15
-146
@@ -1,5 +1,4 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Reflection;
|
using System.Reflection;
|
||||||
using System.Windows;
|
using System.Windows;
|
||||||
@@ -333,33 +332,8 @@ namespace Ink_Canvas.Windows.SettingsViews
|
|||||||
{
|
{
|
||||||
if (window.GetType().Name == "SettingsWindow")
|
if (window.GetType().Name == "SettingsWindow")
|
||||||
{
|
{
|
||||||
// 根据控件名称确定需要同步的面板
|
// 同步所有面板(保守策略)
|
||||||
var panelToSync = GetPanelForControl(controlName);
|
SyncAllPanels(window);
|
||||||
|
|
||||||
if (panelToSync != null)
|
|
||||||
{
|
|
||||||
// 获取对应的面板属性
|
|
||||||
var panelProp = window.GetType().GetProperty(panelToSync, BindingFlags.Public | BindingFlags.Instance);
|
|
||||||
if (panelProp != null)
|
|
||||||
{
|
|
||||||
var panel = panelProp.GetValue(window) as System.Windows.Controls.UserControl;
|
|
||||||
if (panel != null)
|
|
||||||
{
|
|
||||||
// 调用 LoadSettings 方法重新加载设置
|
|
||||||
var loadMethod = panel.GetType().GetMethod("LoadSettings",
|
|
||||||
BindingFlags.Public | BindingFlags.Instance);
|
|
||||||
if (loadMethod != null)
|
|
||||||
{
|
|
||||||
loadMethod.Invoke(panel, null);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
// 如果无法确定具体面板,则同步所有面板(保守策略)
|
|
||||||
SyncAllPanels(window);
|
|
||||||
}
|
|
||||||
break; // 通常只有一个设置窗口
|
break; // 通常只有一个设置窗口
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -371,102 +345,6 @@ namespace Ink_Canvas.Windows.SettingsViews
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 根据控件名称获取对应的面板名称
|
|
||||||
/// </summary>
|
|
||||||
private static string GetPanelForControl(string controlName)
|
|
||||||
{
|
|
||||||
// 定义控件名称到面板名称的映射
|
|
||||||
var controlToPanel = new Dictionary<string, string>
|
|
||||||
{
|
|
||||||
// StartupPanel
|
|
||||||
{ "ToggleSwitchIsAutoUpdate", "StartupPanel" },
|
|
||||||
{ "ToggleSwitchIsAutoUpdateWithSilence", "StartupPanel" },
|
|
||||||
{ "ToggleSwitchRunAtStartup", "StartupPanel" },
|
|
||||||
{ "ToggleSwitchFoldAtStartup", "StartupPanel" },
|
|
||||||
{ "AutoUpdateWithSilenceStartTimeComboBox", "StartupPanel" },
|
|
||||||
{ "AutoUpdateWithSilenceEndTimeComboBox", "StartupPanel" },
|
|
||||||
|
|
||||||
// ThemePanel
|
|
||||||
{ "ComboBoxTheme", "ThemePanel" },
|
|
||||||
{ "ToggleSwitchEnableSplashScreen", "ThemePanel" },
|
|
||||||
{ "ComboBoxSplashScreenStyle", "ThemePanel" },
|
|
||||||
{ "ToggleSwitchEnableTrayIcon", "ThemePanel" },
|
|
||||||
{ "ComboBoxFloatingBarImg", "ThemePanel" },
|
|
||||||
{ "ComboBoxUnFoldBtnImg", "ThemePanel" },
|
|
||||||
{ "ComboBoxChickenSoupSource", "ThemePanel" },
|
|
||||||
{ "ComboBoxQuickColorPaletteDisplayMode", "ThemePanel" },
|
|
||||||
{ "ComboBoxEraserDisplayOption", "ThemePanel" },
|
|
||||||
{ "ToggleSwitchEnableQuickPanel", "ThemePanel" },
|
|
||||||
{ "ViewboxFloatingBarScaleTransformValueSlider", "ThemePanel" },
|
|
||||||
{ "ViewboxFloatingBarOpacityValueSlider", "ThemePanel" },
|
|
||||||
{ "ViewboxFloatingBarOpacityInPPTValueSlider", "ThemePanel" },
|
|
||||||
|
|
||||||
// PowerPointPanel
|
|
||||||
{ "ToggleSwitchSupportPowerPoint", "PowerPointPanel" },
|
|
||||||
{ "ToggleSwitchShowPPTButton", "PowerPointPanel" },
|
|
||||||
{ "ToggleSwitchEnablePPTButtonPageClickable", "PowerPointPanel" },
|
|
||||||
{ "ToggleSwitchShowCanvasAtNewSlideShow", "PowerPointPanel" },
|
|
||||||
{ "PPTButtonLeftPositionValueSlider", "PowerPointPanel" },
|
|
||||||
{ "PPTButtonRightPositionValueSlider", "PowerPointPanel" },
|
|
||||||
|
|
||||||
// GesturesPanel
|
|
||||||
{ "ToggleSwitchEnableTwoFingerRotationOnSelection", "GesturesPanel" },
|
|
||||||
{ "ToggleSwitchEnablePalmEraser", "GesturesPanel" },
|
|
||||||
{ "ComboBoxPalmEraserSensitivity", "GesturesPanel" },
|
|
||||||
|
|
||||||
// CanvasAndInkPanel
|
|
||||||
{ "ToggleSwitchShowCursor", "CanvasAndInkPanel" },
|
|
||||||
{ "ToggleSwitchDisablePressure", "CanvasAndInkPanel" },
|
|
||||||
{ "ToggleSwitchEnablePressureTouchMode", "CanvasAndInkPanel" },
|
|
||||||
{ "ToggleSwitchLaunchSeewoVideoShowcaseForWhiteboardBooth", "CanvasAndInkPanel" },
|
|
||||||
{ "ComboBoxEraserSize", "CanvasAndInkPanel" },
|
|
||||||
{ "ComboBoxHyperbolaAsymptoteOption", "CanvasAndInkPanel" },
|
|
||||||
{ "ComboBoxAutoSaveStrokesInterval", "CanvasAndInkPanel" },
|
|
||||||
|
|
||||||
// SnapshotPanel
|
|
||||||
{ "AutoSavedStrokesLocation", "SnapshotPanel" },
|
|
||||||
{ "ComboBoxAutoDelSavedFilesDaysThreshold", "SnapshotPanel" },
|
|
||||||
{ "ToggleSwitchAutoDelSavedFiles", "SnapshotPanel" },
|
|
||||||
|
|
||||||
// AdvancedPanel
|
|
||||||
{ "ComboBoxAutoBackupInterval", "AdvancedPanel" },
|
|
||||||
{ "ToggleSwitchIsQuadIR", "AdvancedPanel" },
|
|
||||||
{ "ToggleSwitchIsLogEnabled", "AdvancedPanel" },
|
|
||||||
{ "ToggleSwitchIsSaveLogByDate", "AdvancedPanel" },
|
|
||||||
{ "ToggleSwitchIsSecondConfimeWhenShutdownApp", "AdvancedPanel" },
|
|
||||||
{ "ToggleSwitchIsEnableFullScreenHelper", "AdvancedPanel" },
|
|
||||||
{ "ToggleSwitchIsEnableAvoidFullScreenHelper", "AdvancedPanel" },
|
|
||||||
{ "ToggleSwitchIsEnableEdgeGestureUtil", "AdvancedPanel" },
|
|
||||||
{ "ToggleSwitchIsEnableForceFullScreen", "AdvancedPanel" },
|
|
||||||
{ "ToggleSwitchIsEnableDPIChangeDetection", "AdvancedPanel" },
|
|
||||||
{ "ToggleSwitchIsEnableResolutionChangeDetection", "AdvancedPanel" },
|
|
||||||
{ "ToggleSwitchIsAutoBackupBeforeUpdate", "AdvancedPanel" },
|
|
||||||
{ "ToggleSwitchIsAutoBackupEnabled", "AdvancedPanel" },
|
|
||||||
{ "ToggleSwitchIsEnableUriScheme", "AdvancedPanel" },
|
|
||||||
|
|
||||||
// LuckyRandomPanel
|
|
||||||
{ "ToggleSwitchDisplayRandWindowNamesInputBtn", "LuckyRandomPanel" },
|
|
||||||
{ "ToggleSwitchShowRandomAndSingleDraw", "LuckyRandomPanel" },
|
|
||||||
{ "ToggleSwitchEnableQuickDraw", "LuckyRandomPanel" },
|
|
||||||
{ "ToggleSwitchExternalCaller", "LuckyRandomPanel" },
|
|
||||||
{ "ComboBoxExternalCallerType", "LuckyRandomPanel" },
|
|
||||||
{ "RandWindowOnceCloseLatencySlider", "LuckyRandomPanel" },
|
|
||||||
{ "RandWindowOnceMaxStudentsSlider", "LuckyRandomPanel" },
|
|
||||||
};
|
|
||||||
|
|
||||||
// 查找匹配的面板
|
|
||||||
foreach (var kvp in controlToPanel)
|
|
||||||
{
|
|
||||||
if (controlName.Contains(kvp.Key) || kvp.Key.Contains(controlName))
|
|
||||||
{
|
|
||||||
return kvp.Value;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 同步所有面板的状态(保守策略)
|
/// 同步所有面板的状态(保守策略)
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -474,36 +352,36 @@ namespace Ink_Canvas.Windows.SettingsViews
|
|||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
// 获取所有面板属性
|
// 获取所有页面属性
|
||||||
var panelProperties = settingsWindow.GetType().GetProperties(BindingFlags.Public | BindingFlags.Instance)
|
var pageProperties = settingsWindow.GetType().GetProperties(BindingFlags.Public | BindingFlags.Instance)
|
||||||
.Where(p => p.PropertyType.Name.EndsWith("Panel") &&
|
.Where(p => p.PropertyType.Name.EndsWith("Page") &&
|
||||||
p.PropertyType.IsSubclassOf(typeof(System.Windows.Controls.UserControl)));
|
p.PropertyType.IsSubclassOf(typeof(System.Windows.Controls.Page)));
|
||||||
|
|
||||||
foreach (var panelProp in panelProperties)
|
foreach (var pageProp in pageProperties)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var panel = panelProp.GetValue(settingsWindow) as System.Windows.Controls.UserControl;
|
var page = pageProp.GetValue(settingsWindow) as System.Windows.Controls.Page;
|
||||||
if (panel != null)
|
if (page != null)
|
||||||
{
|
{
|
||||||
// 调用 LoadSettings 方法
|
// 调用 LoadSettings 方法
|
||||||
var loadMethod = panel.GetType().GetMethod("LoadSettings",
|
var loadMethod = page.GetType().GetMethod("LoadSettings",
|
||||||
BindingFlags.Public | BindingFlags.Instance);
|
BindingFlags.Public | BindingFlags.Instance);
|
||||||
if (loadMethod != null)
|
if (loadMethod != null)
|
||||||
{
|
{
|
||||||
loadMethod.Invoke(panel, null);
|
loadMethod.Invoke(page, null);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
System.Diagnostics.Debug.WriteLine($"同步面板 {panelProp.Name} 状态失败: {ex.Message}");
|
System.Diagnostics.Debug.WriteLine($"同步页面 {pageProp.Name} 状态失败: {ex.Message}");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
System.Diagnostics.Debug.WriteLine($"同步所有面板状态失败: {ex.Message}");
|
System.Diagnostics.Debug.WriteLine($"同步所有页面状态失败: {ex.Message}");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -568,7 +446,7 @@ namespace Ink_Canvas.Windows.SettingsViews
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 通知设置窗口更新所有面板的主题
|
/// 通知设置窗口更新所有页面的主题
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private static void NotifySettingsWindowThemeUpdate()
|
private static void NotifySettingsWindowThemeUpdate()
|
||||||
{
|
{
|
||||||
@@ -580,14 +458,6 @@ namespace Ink_Canvas.Windows.SettingsViews
|
|||||||
// 使用类型名称匹配,因为 SettingsWindow 在不同的命名空间中
|
// 使用类型名称匹配,因为 SettingsWindow 在不同的命名空间中
|
||||||
if (window.GetType().Name == "SettingsWindow")
|
if (window.GetType().Name == "SettingsWindow")
|
||||||
{
|
{
|
||||||
// 使用反射调用 ApplyThemeToAllPanels 方法
|
|
||||||
var method = window.GetType().GetMethod("ApplyThemeToAllPanels",
|
|
||||||
BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Instance);
|
|
||||||
if (method != null)
|
|
||||||
{
|
|
||||||
method.Invoke(window, null);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 同时调用 ApplyTheme 方法更新窗口本身
|
// 同时调用 ApplyTheme 方法更新窗口本身
|
||||||
var applyThemeMethod = window.GetType().GetMethod("ApplyTheme",
|
var applyThemeMethod = window.GetType().GetMethod("ApplyTheme",
|
||||||
BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Instance);
|
BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Instance);
|
||||||
@@ -607,7 +477,7 @@ namespace Ink_Canvas.Windows.SettingsViews
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 强制更新所有设置面板的主题(公共方法,可在外部调用)
|
/// 强制更新所有设置页面的主题(公共方法,可在外部调用)
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public static void ForceUpdateAllPanelsTheme()
|
public static void ForceUpdateAllPanelsTheme()
|
||||||
{
|
{
|
||||||
@@ -711,4 +581,3 @@ namespace Ink_Canvas.Windows.SettingsViews
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
<ui:Page
|
||||||
|
x:Class="Ink_Canvas.Windows.SettingsViews.Pages.AboutPage"
|
||||||
|
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||||
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||||
|
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||||
|
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||||
|
xmlns:ui="http://schemas.inkore.net/lib/ui/wpf/modern"
|
||||||
|
xmlns:ikw="http://schemas.inkore.net/lib/ui/wpf"
|
||||||
|
Title="关于"
|
||||||
|
mc:Ignorable="d">
|
||||||
|
<Grid>
|
||||||
|
<StackPanel Margin="24">
|
||||||
|
<TextBlock Text="关于 InkCanvasForClass" Style="{DynamicResource TitleTextBlockStyle}" Margin="0,0,0,16" />
|
||||||
|
<TextBlock Text="这里是关于页面" Style="{DynamicResource BodyTextBlockStyle}" />
|
||||||
|
</StackPanel>
|
||||||
|
</Grid>
|
||||||
|
</ui:Page>
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
using iNKORE.UI.WPF.Modern.Controls;
|
||||||
|
|
||||||
|
namespace Ink_Canvas.Windows.SettingsViews.Pages
|
||||||
|
{
|
||||||
|
public partial class AboutPage : Page
|
||||||
|
{
|
||||||
|
public AboutPage()
|
||||||
|
{
|
||||||
|
InitializeComponent();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,49 @@
|
|||||||
|
<Page x:Class="Ink_Canvas.Windows.SettingsViews.Pages.AppearancePage"
|
||||||
|
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"
|
||||||
|
mc:Ignorable="d"
|
||||||
|
d:DesignHeight="450" d:DesignWidth="800"
|
||||||
|
Title="外观">
|
||||||
|
|
||||||
|
<Grid Margin="24">
|
||||||
|
<StackPanel>
|
||||||
|
<TextBlock Text="外观设置" Style="{DynamicResource TitleTextBlockStyle}" Margin="0,0,0,16" />
|
||||||
|
|
||||||
|
<ui:SettingsCard
|
||||||
|
Header="主题"
|
||||||
|
Description="点击跳转到主题设置页面"
|
||||||
|
IsClickEnabled="True"
|
||||||
|
Click="SettingsCard_Click">
|
||||||
|
<ui:SettingsCard.HeaderIcon>
|
||||||
|
<ui:FontIcon Icon="{x:Static ui:SegoeFluentIcons.Color}"/>
|
||||||
|
</ui:SettingsCard.HeaderIcon>
|
||||||
|
</ui:SettingsCard>
|
||||||
|
|
||||||
|
<ui:SettingsCard
|
||||||
|
Header="颜色"
|
||||||
|
Description="点击跳转到颜色设置页面"
|
||||||
|
IsClickEnabled="True"
|
||||||
|
Click="SettingsCard_Click_1"
|
||||||
|
Margin="0,8,0,0">
|
||||||
|
<ui:SettingsCard.HeaderIcon>
|
||||||
|
<ui:FontIcon Icon="{x:Static ui:SegoeFluentIcons.Color}"/>
|
||||||
|
</ui:SettingsCard.HeaderIcon>
|
||||||
|
</ui:SettingsCard>
|
||||||
|
|
||||||
|
<ui:SettingsCard
|
||||||
|
Header="字体"
|
||||||
|
Description="点击跳转到字体设置页面"
|
||||||
|
IsClickEnabled="True"
|
||||||
|
Click="SettingsCard_Click_2"
|
||||||
|
Margin="0,8,0,0">
|
||||||
|
<ui:SettingsCard.HeaderIcon>
|
||||||
|
<ui:FontIcon Icon="{x:Static ui:SegoeFluentIcons.Font}"/>
|
||||||
|
</ui:SettingsCard.HeaderIcon>
|
||||||
|
</ui:SettingsCard>
|
||||||
|
</StackPanel>
|
||||||
|
</Grid>
|
||||||
|
</Page>
|
||||||
@@ -0,0 +1,40 @@
|
|||||||
|
using System.Windows;
|
||||||
|
using System.Windows.Controls;
|
||||||
|
|
||||||
|
namespace Ink_Canvas.Windows.SettingsViews.Pages
|
||||||
|
{
|
||||||
|
public partial class AppearancePage : Page
|
||||||
|
{
|
||||||
|
public AppearancePage()
|
||||||
|
{
|
||||||
|
InitializeComponent();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void SettingsCard_Click(object sender, RoutedEventArgs e)
|
||||||
|
{
|
||||||
|
SettingsWindow settingsWindow = Window.GetWindow(this) as SettingsWindow;
|
||||||
|
if (settingsWindow != null)
|
||||||
|
{
|
||||||
|
settingsWindow.NavigateToPage("ThemePage");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void SettingsCard_Click_1(object sender, RoutedEventArgs e)
|
||||||
|
{
|
||||||
|
SettingsWindow settingsWindow = Window.GetWindow(this) as SettingsWindow;
|
||||||
|
if (settingsWindow != null)
|
||||||
|
{
|
||||||
|
settingsWindow.NavigateToPage("ColorsPage");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void SettingsCard_Click_2(object sender, RoutedEventArgs e)
|
||||||
|
{
|
||||||
|
SettingsWindow settingsWindow = Window.GetWindow(this) as SettingsWindow;
|
||||||
|
if (settingsWindow != null)
|
||||||
|
{
|
||||||
|
settingsWindow.NavigateToPage("FontsPage");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,48 @@
|
|||||||
|
<Page x:Class="Ink_Canvas.Windows.SettingsViews.Pages.BasicPage"
|
||||||
|
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:i18n="clr-namespace:Ink_Canvas.MarkupExtensions"
|
||||||
|
xmlns:sys="clr-namespace:System;assembly=mscorlib"
|
||||||
|
mc:Ignorable="d"
|
||||||
|
d:DesignHeight="450" d:DesignWidth="800"
|
||||||
|
Title="基本">
|
||||||
|
|
||||||
|
<ScrollViewer Padding="59,0,59,0">
|
||||||
|
<!-- These styles can be referenced to create a consistent SettingsPage layout -->
|
||||||
|
<FrameworkElement.Resources>
|
||||||
|
<!-- Spacing between cards -->
|
||||||
|
<sys:Double x:Key="SettingsCardSpacing">4</sys:Double>
|
||||||
|
<!-- Style (inc. the correct spacing) of a section header -->
|
||||||
|
<Style x:Key="SettingsSectionHeaderTextBlockStyle"
|
||||||
|
BasedOn="{StaticResource BodyStrongTextBlockStyle}"
|
||||||
|
TargetType="TextBlock">
|
||||||
|
<Style.Setters>
|
||||||
|
<Setter Property="Margin" Value="1,30,0,6" />
|
||||||
|
</Style.Setters>
|
||||||
|
</Style>
|
||||||
|
</FrameworkElement.Resources>
|
||||||
|
<Grid>
|
||||||
|
<ikw:SimpleStackPanel MaxWidth="800"
|
||||||
|
HorizontalAlignment="Stretch"
|
||||||
|
Spacing="{StaticResource SettingsCardSpacing}">
|
||||||
|
<TextBlock Style="{StaticResource SettingsSectionHeaderTextBlockStyle}"
|
||||||
|
Text="启动时行为" />
|
||||||
|
|
||||||
|
<ui:SettingsCard
|
||||||
|
Header="启动设置"
|
||||||
|
Description="点击跳转到启动设置页面"
|
||||||
|
IsClickEnabled="True"
|
||||||
|
Click="SettingsCard_Click">
|
||||||
|
<ui:SettingsCard.HeaderIcon>
|
||||||
|
<ui:FontIcon Icon="{x:Static ui:SegoeFluentIcons.Play}"/>
|
||||||
|
</ui:SettingsCard.HeaderIcon>
|
||||||
|
</ui:SettingsCard>
|
||||||
|
</ikw:SimpleStackPanel>
|
||||||
|
</Grid>
|
||||||
|
</ScrollViewer>
|
||||||
|
</Page>
|
||||||
@@ -0,0 +1,27 @@
|
|||||||
|
using System.Windows;
|
||||||
|
using System.Windows.Controls;
|
||||||
|
|
||||||
|
namespace Ink_Canvas.Windows.SettingsViews.Pages
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Basic.xaml 的交互逻辑
|
||||||
|
/// </summary>
|
||||||
|
public partial class BasicPage : Page
|
||||||
|
{
|
||||||
|
public BasicPage()
|
||||||
|
{
|
||||||
|
InitializeComponent();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void SettingsCard_Click(object sender, RoutedEventArgs e)
|
||||||
|
{
|
||||||
|
// 找到SettingsWindow窗口
|
||||||
|
SettingsWindow settingsWindow = Window.GetWindow(this) as SettingsWindow;
|
||||||
|
if (settingsWindow != null)
|
||||||
|
{
|
||||||
|
// 调用NavigateToPage方法导航到启动页面
|
||||||
|
settingsWindow.NavigateToPage("StartupPage");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
<ui:Page
|
||||||
|
x:Class="Ink_Canvas.Windows.SettingsViews.Pages.ColorsPage"
|
||||||
|
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||||
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||||
|
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||||
|
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||||
|
xmlns:ui="http://schemas.inkore.net/lib/ui/wpf/modern"
|
||||||
|
xmlns:ikw="http://schemas.inkore.net/lib/ui/wpf"
|
||||||
|
Title="颜色设置"
|
||||||
|
mc:Ignorable="d">
|
||||||
|
<Grid>
|
||||||
|
<StackPanel Margin="24">
|
||||||
|
<TextBlock Text="颜色设置" Style="{DynamicResource TitleTextBlockStyle}" Margin="0,0,0,16" />
|
||||||
|
<TextBlock Text="这里是颜色设置页面" Style="{DynamicResource BodyTextBlockStyle}" />
|
||||||
|
</StackPanel>
|
||||||
|
</Grid>
|
||||||
|
</ui:Page>
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
using iNKORE.UI.WPF.Modern.Controls;
|
||||||
|
|
||||||
|
namespace Ink_Canvas.Windows.SettingsViews.Pages
|
||||||
|
{
|
||||||
|
public partial class ColorsPage : Page
|
||||||
|
{
|
||||||
|
public ColorsPage()
|
||||||
|
{
|
||||||
|
InitializeComponent();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,38 @@
|
|||||||
|
<Page x:Class="Ink_Canvas.Windows.SettingsViews.Pages.DesignPage"
|
||||||
|
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"
|
||||||
|
mc:Ignorable="d"
|
||||||
|
d:DesignHeight="450" d:DesignWidth="800"
|
||||||
|
Title="设计">
|
||||||
|
|
||||||
|
<Grid Margin="24">
|
||||||
|
<StackPanel>
|
||||||
|
<TextBlock Text="设计设置" Style="{DynamicResource TitleTextBlockStyle}" Margin="0,0,0,16" />
|
||||||
|
|
||||||
|
<ui:SettingsCard
|
||||||
|
Header="图标"
|
||||||
|
Description="点击跳转到图标设置页面"
|
||||||
|
IsClickEnabled="True"
|
||||||
|
Click="SettingsCard_Click">
|
||||||
|
<ui:SettingsCard.HeaderIcon>
|
||||||
|
<ui:FontIcon Icon="{x:Static ui:SegoeFluentIcons.Home}"/>
|
||||||
|
</ui:SettingsCard.HeaderIcon>
|
||||||
|
</ui:SettingsCard>
|
||||||
|
|
||||||
|
<ui:SettingsCard
|
||||||
|
Header="排版"
|
||||||
|
Description="点击跳转到排版设置页面"
|
||||||
|
IsClickEnabled="True"
|
||||||
|
Click="SettingsCard_Click_1"
|
||||||
|
Margin="0,8,0,0">
|
||||||
|
<ui:SettingsCard.HeaderIcon>
|
||||||
|
<ui:FontIcon Icon="{x:Static ui:SegoeFluentIcons.Font}"/>
|
||||||
|
</ui:SettingsCard.HeaderIcon>
|
||||||
|
</ui:SettingsCard>
|
||||||
|
</StackPanel>
|
||||||
|
</Grid>
|
||||||
|
</Page>
|
||||||
@@ -0,0 +1,31 @@
|
|||||||
|
using System.Windows;
|
||||||
|
using System.Windows.Controls;
|
||||||
|
|
||||||
|
namespace Ink_Canvas.Windows.SettingsViews.Pages
|
||||||
|
{
|
||||||
|
public partial class DesignPage : Page
|
||||||
|
{
|
||||||
|
public DesignPage()
|
||||||
|
{
|
||||||
|
InitializeComponent();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void SettingsCard_Click(object sender, RoutedEventArgs e)
|
||||||
|
{
|
||||||
|
SettingsWindow settingsWindow = Window.GetWindow(this) as SettingsWindow;
|
||||||
|
if (settingsWindow != null)
|
||||||
|
{
|
||||||
|
settingsWindow.NavigateToPage("IconographyPage");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void SettingsCard_Click_1(object sender, RoutedEventArgs e)
|
||||||
|
{
|
||||||
|
SettingsWindow settingsWindow = Window.GetWindow(this) as SettingsWindow;
|
||||||
|
if (settingsWindow != null)
|
||||||
|
{
|
||||||
|
settingsWindow.NavigateToPage("TypographyPage");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
<ui:Page
|
||||||
|
x:Class="Ink_Canvas.Windows.SettingsViews.Pages.FontsPage"
|
||||||
|
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||||
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||||
|
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||||
|
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||||
|
xmlns:ui="http://schemas.inkore.net/lib/ui/wpf/modern"
|
||||||
|
xmlns:ikw="http://schemas.inkore.net/lib/ui/wpf"
|
||||||
|
Title="字体设置"
|
||||||
|
mc:Ignorable="d">
|
||||||
|
<Grid>
|
||||||
|
<StackPanel Margin="24">
|
||||||
|
<TextBlock Text="字体设置" Style="{DynamicResource TitleTextBlockStyle}" Margin="0,0,0,16" />
|
||||||
|
<TextBlock Text="这里是字体设置页面" Style="{DynamicResource BodyTextBlockStyle}" />
|
||||||
|
</StackPanel>
|
||||||
|
</Grid>
|
||||||
|
</ui:Page>
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
using iNKORE.UI.WPF.Modern.Controls;
|
||||||
|
|
||||||
|
namespace Ink_Canvas.Windows.SettingsViews.Pages
|
||||||
|
{
|
||||||
|
public partial class FontsPage : Page
|
||||||
|
{
|
||||||
|
public FontsPage()
|
||||||
|
{
|
||||||
|
InitializeComponent();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,78 @@
|
|||||||
|
<Page x:Class="Ink_Canvas.Windows.SettingsViews.Pages.HomePage"
|
||||||
|
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:i18n="clr-namespace:Ink_Canvas.MarkupExtensions"
|
||||||
|
xmlns:sys="clr-namespace:System;assembly=mscorlib"
|
||||||
|
mc:Ignorable="d"
|
||||||
|
d:DesignHeight="450" d:DesignWidth="800"
|
||||||
|
Title="首页">
|
||||||
|
|
||||||
|
<ScrollViewer Padding="59,0,59,0">
|
||||||
|
<!-- These styles can be referenced to create a consistent SettingsPage layout -->
|
||||||
|
<FrameworkElement.Resources>
|
||||||
|
<!-- Spacing between cards -->
|
||||||
|
<sys:Double x:Key="SettingsCardSpacing">4</sys:Double>
|
||||||
|
<!-- Style (inc. the correct spacing) of a section header -->
|
||||||
|
<Style x:Key="SettingsSectionHeaderTextBlockStyle"
|
||||||
|
BasedOn="{StaticResource BodyStrongTextBlockStyle}"
|
||||||
|
TargetType="TextBlock">
|
||||||
|
<Style.Setters>
|
||||||
|
<Setter Property="Margin" Value="1,30,0,6" />
|
||||||
|
</Style.Setters>
|
||||||
|
</Style>
|
||||||
|
</FrameworkElement.Resources>
|
||||||
|
<Grid>
|
||||||
|
<ikw:SimpleStackPanel MaxWidth="800"
|
||||||
|
HorizontalAlignment="Stretch"
|
||||||
|
Spacing="{StaticResource SettingsCardSpacing}">
|
||||||
|
<TextBlock Style="{StaticResource SettingsSectionHeaderTextBlockStyle}"
|
||||||
|
Text="快速导航" />
|
||||||
|
|
||||||
|
<ui:SettingsCard
|
||||||
|
Header="基本设置"
|
||||||
|
Description="点击跳转到基本设置页面"
|
||||||
|
IsClickEnabled="True"
|
||||||
|
Click="SettingsCard_Basic_Click">
|
||||||
|
<ui:SettingsCard.HeaderIcon>
|
||||||
|
<ui:FontIcon Icon="{x:Static ui:SegoeFluentIcons.Home}"/>
|
||||||
|
</ui:SettingsCard.HeaderIcon>
|
||||||
|
</ui:SettingsCard>
|
||||||
|
|
||||||
|
<ui:SettingsCard
|
||||||
|
Header="页面 2"
|
||||||
|
Description="点击跳转到页面 2"
|
||||||
|
IsClickEnabled="True"
|
||||||
|
Click="SettingsCard_Page2_Click">
|
||||||
|
<ui:SettingsCard.HeaderIcon>
|
||||||
|
<ui:SymbolIcon Symbol="Document" />
|
||||||
|
</ui:SettingsCard.HeaderIcon>
|
||||||
|
</ui:SettingsCard>
|
||||||
|
|
||||||
|
<ui:SettingsCard
|
||||||
|
Header="设计设置"
|
||||||
|
Description="点击跳转到设计设置页面"
|
||||||
|
IsClickEnabled="True"
|
||||||
|
Click="SettingsCard_Design_Click">
|
||||||
|
<ui:SettingsCard.HeaderIcon>
|
||||||
|
<ui:FontIcon Icon="{x:Static ui:SegoeFluentIcons.Design}"/>
|
||||||
|
</ui:SettingsCard.HeaderIcon>
|
||||||
|
</ui:SettingsCard>
|
||||||
|
|
||||||
|
<ui:SettingsCard
|
||||||
|
Header="外观设置"
|
||||||
|
Description="点击跳转到外观设置页面"
|
||||||
|
IsClickEnabled="True"
|
||||||
|
Click="SettingsCard_Appearance_Click">
|
||||||
|
<ui:SettingsCard.HeaderIcon>
|
||||||
|
<ui:FontIcon Icon="{x:Static ui:SegoeFluentIcons.Color}"/>
|
||||||
|
</ui:SettingsCard.HeaderIcon>
|
||||||
|
</ui:SettingsCard>
|
||||||
|
</ikw:SimpleStackPanel>
|
||||||
|
</Grid>
|
||||||
|
</ScrollViewer>
|
||||||
|
</Page>
|
||||||
@@ -0,0 +1,60 @@
|
|||||||
|
using System.Windows;
|
||||||
|
using System.Windows.Controls;
|
||||||
|
|
||||||
|
namespace Ink_Canvas.Windows.SettingsViews.Pages
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// HomePage.xaml 的交互逻辑
|
||||||
|
/// </summary>
|
||||||
|
public partial class HomePage : Page
|
||||||
|
{
|
||||||
|
public HomePage()
|
||||||
|
{
|
||||||
|
InitializeComponent();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void SettingsCard_Basic_Click(object sender, RoutedEventArgs e)
|
||||||
|
{
|
||||||
|
// 找到SettingsWindow窗口
|
||||||
|
SettingsWindow settingsWindow = Window.GetWindow(this) as SettingsWindow;
|
||||||
|
if (settingsWindow != null)
|
||||||
|
{
|
||||||
|
// 调用NavigateToPage方法导航到基本设置页面
|
||||||
|
settingsWindow.NavigateToPage("BasicPage");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void SettingsCard_Page2_Click(object sender, RoutedEventArgs e)
|
||||||
|
{
|
||||||
|
// 找到SettingsWindow窗口
|
||||||
|
SettingsWindow settingsWindow = Window.GetWindow(this) as SettingsWindow;
|
||||||
|
if (settingsWindow != null)
|
||||||
|
{
|
||||||
|
// 调用NavigateToPage方法导航到页面2
|
||||||
|
settingsWindow.NavigateToPage("Page2Page");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void SettingsCard_Design_Click(object sender, RoutedEventArgs e)
|
||||||
|
{
|
||||||
|
// 找到SettingsWindow窗口
|
||||||
|
SettingsWindow settingsWindow = Window.GetWindow(this) as SettingsWindow;
|
||||||
|
if (settingsWindow != null)
|
||||||
|
{
|
||||||
|
// 调用NavigateToPage方法导航到设计设置页面
|
||||||
|
settingsWindow.NavigateToPage("DesignPage");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void SettingsCard_Appearance_Click(object sender, RoutedEventArgs e)
|
||||||
|
{
|
||||||
|
// 找到SettingsWindow窗口
|
||||||
|
SettingsWindow settingsWindow = Window.GetWindow(this) as SettingsWindow;
|
||||||
|
if (settingsWindow != null)
|
||||||
|
{
|
||||||
|
// 调用NavigateToPage方法导航到外观设置页面
|
||||||
|
settingsWindow.NavigateToPage("AppearancePage");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,39 @@
|
|||||||
|
<ui:Page
|
||||||
|
x:Class="Ink_Canvas.Windows.SettingsViews.Pages.IconographyPage"
|
||||||
|
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||||
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||||
|
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||||
|
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||||
|
xmlns:ui="http://schemas.inkore.net/lib/ui/wpf/modern"
|
||||||
|
xmlns:ikw="http://schemas.inkore.net/lib/ui/wpf"
|
||||||
|
xmlns:sys="clr-namespace:System;assembly=mscorlib"
|
||||||
|
xmlns:controls="clr-namespace:Ink_Canvas.Controls"
|
||||||
|
Title="图标设置"
|
||||||
|
mc:Ignorable="d">
|
||||||
|
<Grid x:Name="RootGrid">
|
||||||
|
<StackPanel Margin="24">
|
||||||
|
<ui:SettingsExpander x:Name="settingsCard" VerticalAlignment="Top"
|
||||||
|
Description="The SettingsExpander has the same properties as a Card, and you can set SettingsCard as part of the Items collection."
|
||||||
|
Header="SettingsExpander" IsEnabled="True">
|
||||||
|
|
||||||
|
<ui:SettingsExpander.HeaderIcon>
|
||||||
|
<ui:FontIcon Glyph=""/>
|
||||||
|
</ui:SettingsExpander.HeaderIcon>
|
||||||
|
|
||||||
|
|
||||||
|
<controls:CopyButton Text="You can override the Left indention of a SettingsCard by overriding the SettingsCardLeftIndention"/>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
<ui:SettingsExpander.Items>
|
||||||
|
<ui:SettingsCard Description="You can override the Left indention of a SettingsCard by overriding the SettingsCardLeftIndention"
|
||||||
|
Header="Customization">
|
||||||
|
<ui:SettingsCard.Resources>
|
||||||
|
<sys:Double x:Key="SettingsCardLeftIndention">0</sys:Double>
|
||||||
|
</ui:SettingsCard.Resources>
|
||||||
|
</ui:SettingsCard>
|
||||||
|
</ui:SettingsExpander.Items>
|
||||||
|
</ui:SettingsExpander>
|
||||||
|
</StackPanel>
|
||||||
|
</Grid>
|
||||||
|
</ui:Page>
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
namespace Ink_Canvas.Windows.SettingsViews.Pages
|
||||||
|
{
|
||||||
|
public partial class IconographyPage : iNKORE.UI.WPF.Modern.Controls.Page
|
||||||
|
{
|
||||||
|
public IconographyPage()
|
||||||
|
{
|
||||||
|
InitializeComponent();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
<Page x:Class="Ink_Canvas.Windows.SettingsViews.Pages.Page2Page"
|
||||||
|
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"
|
||||||
|
mc:Ignorable="d"
|
||||||
|
d:DesignHeight="450" d:DesignWidth="800"
|
||||||
|
Title="Page2">
|
||||||
|
|
||||||
|
<Grid>
|
||||||
|
|
||||||
|
</Grid>
|
||||||
|
</Page>
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
using System.Windows.Controls;
|
||||||
|
|
||||||
|
namespace Ink_Canvas.Windows.SettingsViews.Pages
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Page2.xaml 的交互逻辑
|
||||||
|
/// </summary>
|
||||||
|
public partial class Page2Page : Page
|
||||||
|
{
|
||||||
|
public Page2Page()
|
||||||
|
{
|
||||||
|
InitializeComponent();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
<ui:Page
|
||||||
|
x:Class="Ink_Canvas.Windows.SettingsViews.Pages.SettingsPage"
|
||||||
|
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||||
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||||
|
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||||
|
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||||
|
xmlns:ui="http://schemas.inkore.net/lib/ui/wpf/modern"
|
||||||
|
xmlns:ikw="http://schemas.inkore.net/lib/ui/wpf"
|
||||||
|
Title="设置"
|
||||||
|
mc:Ignorable="d">
|
||||||
|
<Grid>
|
||||||
|
<StackPanel Margin="24">
|
||||||
|
<TextBlock Text="设置" Style="{DynamicResource TitleTextBlockStyle}" Margin="0,0,0,16" />
|
||||||
|
<TextBlock Text="这里是设置页面" Style="{DynamicResource BodyTextBlockStyle}" />
|
||||||
|
</StackPanel>
|
||||||
|
</Grid>
|
||||||
|
</ui:Page>
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
using iNKORE.UI.WPF.Modern.Controls;
|
||||||
|
|
||||||
|
namespace Ink_Canvas.Windows.SettingsViews.Pages
|
||||||
|
{
|
||||||
|
public partial class SettingsPage : Page
|
||||||
|
{
|
||||||
|
public SettingsPage()
|
||||||
|
{
|
||||||
|
InitializeComponent();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,117 @@
|
|||||||
|
<Page x:Class="Ink_Canvas.Windows.SettingsViews.Pages.StartupPage"
|
||||||
|
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:i18n="clr-namespace:Ink_Canvas.MarkupExtensions"
|
||||||
|
xmlns:sys="clr-namespace:System;assembly=mscorlib"
|
||||||
|
mc:Ignorable="d"
|
||||||
|
d:DesignHeight="950" d:DesignWidth="800"
|
||||||
|
Title="启动">
|
||||||
|
|
||||||
|
<ScrollViewer Padding="59,0,59,0">
|
||||||
|
<FrameworkElement.Resources>
|
||||||
|
<!-- Spacing between cards -->
|
||||||
|
<sys:Double x:Key="SettingsCardSpacing">4</sys:Double>
|
||||||
|
<!-- Style (inc. the correct spacing) of a section header -->
|
||||||
|
<Style x:Key="SettingsSectionHeaderTextBlockStyle"
|
||||||
|
BasedOn="{StaticResource BodyStrongTextBlockStyle}"
|
||||||
|
TargetType="TextBlock">
|
||||||
|
<Style.Setters>
|
||||||
|
<Setter Property="Margin" Value="1,30,0,6" />
|
||||||
|
</Style.Setters>
|
||||||
|
</Style>
|
||||||
|
</FrameworkElement.Resources>
|
||||||
|
<Grid>
|
||||||
|
<ikw:SimpleStackPanel MaxWidth="1000"
|
||||||
|
HorizontalAlignment="Stretch"
|
||||||
|
Spacing="{StaticResource SettingsCardSpacing}">
|
||||||
|
|
||||||
|
<!-- 窗口设置 -->
|
||||||
|
<TextBlock Style="{StaticResource SettingsSectionHeaderTextBlockStyle}"
|
||||||
|
Text="窗口设置" />
|
||||||
|
|
||||||
|
<!-- 窗口无焦点模式 -->
|
||||||
|
<ui:SettingsCard Description="{i18n:I18n Key=Startup_NoFocusModeHint}"
|
||||||
|
Header="{i18n:I18n Key=Startup_NoFocusMode}">
|
||||||
|
<ui:SettingsCard.HeaderIcon>
|
||||||
|
<ui:FontIcon Icon="{x:Static ui:SegoeFluentIcons.View}" />
|
||||||
|
</ui:SettingsCard.HeaderIcon>
|
||||||
|
<ui:ToggleSwitch Name="ToggleSwitchNoFocusMode" Toggled="ToggleSwitchNoFocusMode_Toggled" />
|
||||||
|
</ui:SettingsCard>
|
||||||
|
|
||||||
|
<!-- 窗口无边框模式 -->
|
||||||
|
<ui:SettingsCard Description="{i18n:I18n Key=Startup_NoBorderModeHint}"
|
||||||
|
Header="{i18n:I18n Key=Startup_NoBorderMode}">
|
||||||
|
<ui:SettingsCard.HeaderIcon>
|
||||||
|
<ui:FontIcon Icon="{x:Static ui:SegoeFluentIcons.FullScreen}" />
|
||||||
|
</ui:SettingsCard.HeaderIcon>
|
||||||
|
<ui:ToggleSwitch Name="ToggleSwitchWindowMode" Toggled="ToggleSwitchWindowMode_Toggled" />
|
||||||
|
</ui:SettingsCard>
|
||||||
|
|
||||||
|
<!-- 窗口置顶 -->
|
||||||
|
<ui:SettingsCard Description="{i18n:I18n Key=Startup_TopMostHint}"
|
||||||
|
Header="{i18n:I18n Key=Startup_TopMost}">
|
||||||
|
<ui:SettingsCard.HeaderIcon>
|
||||||
|
<ui:FontIcon Icon="{x:Static ui:SegoeFluentIcons.Pinned}" />
|
||||||
|
</ui:SettingsCard.HeaderIcon>
|
||||||
|
<ui:ToggleSwitch Name="ToggleSwitchAlwaysOnTop" Toggled="ToggleSwitchAlwaysOnTop_Toggled" />
|
||||||
|
</ui:SettingsCard>
|
||||||
|
|
||||||
|
<!-- UIA置顶 -->
|
||||||
|
<ui:SettingsCard Description="{i18n:I18n Key=Startup_UIAccessTopMostHint_1}"
|
||||||
|
Header="{i18n:I18n Key=Startup_UIATopMost}">
|
||||||
|
<ui:SettingsCard.HeaderIcon>
|
||||||
|
<ui:FontIcon Icon="{x:Static ui:SegoeFluentIcons.Shield}" />
|
||||||
|
</ui:SettingsCard.HeaderIcon>
|
||||||
|
<ui:ToggleSwitch Name="ToggleSwitchUIAccessTopMost" Toggled="ToggleSwitchUIAccessTopMost_Toggled" />
|
||||||
|
</ui:SettingsCard>
|
||||||
|
|
||||||
|
<!-- 启动设置 -->
|
||||||
|
<TextBlock Style="{StaticResource SettingsSectionHeaderTextBlockStyle}"
|
||||||
|
Text="启动设置" />
|
||||||
|
|
||||||
|
<!-- 开机时运行 -->
|
||||||
|
<ui:SettingsCard Description="{i18n:I18n Key=Startup_RunAtStartupHint}"
|
||||||
|
Header="{i18n:I18n Key=Startup_RunAtStartup}">
|
||||||
|
<ui:SettingsCard.HeaderIcon>
|
||||||
|
<ui:FontIcon Icon="{x:Static ui:SegoeFluentIcons.Settings}"/>
|
||||||
|
</ui:SettingsCard.HeaderIcon>
|
||||||
|
<ui:ToggleSwitch Name="ToggleSwitchRunAtStartup" Toggled="ToggleSwitchRunAtStartup_Toggled" />
|
||||||
|
</ui:SettingsCard>
|
||||||
|
|
||||||
|
<!-- 开机运行后收纳到侧边栏 -->
|
||||||
|
<ui:SettingsCard Description="{i18n:I18n Key=Startup_FoldAtStartupHint}"
|
||||||
|
Header="{i18n:I18n Key=Startup_FoldAtStartup}">
|
||||||
|
<ui:SettingsCard.HeaderIcon>
|
||||||
|
<ui:FontIcon Icon="{x:Static ui:SegoeFluentIcons.ChevronLeft}" />
|
||||||
|
</ui:SettingsCard.HeaderIcon>
|
||||||
|
<ui:ToggleSwitch Name="ToggleSwitchFoldAtStartup" Toggled="ToggleSwitchFoldAtStartup_Toggled" />
|
||||||
|
</ui:SettingsCard>
|
||||||
|
|
||||||
|
<!-- 模式设置 -->
|
||||||
|
<TextBlock Style="{StaticResource SettingsSectionHeaderTextBlockStyle}"
|
||||||
|
Text="模式设置" />
|
||||||
|
|
||||||
|
<ui:SettingsExpander Description="{i18n:I18n Key=Settings_ModeDesc}"
|
||||||
|
Header="{i18n:I18n Key=Settings_ModeDesc_1}">
|
||||||
|
<ui:SettingsExpander.HeaderIcon>
|
||||||
|
<ui:FontIcon Icon="{x:Static ui:SegoeFluentIcons.Settings}" />
|
||||||
|
</ui:SettingsExpander.HeaderIcon>
|
||||||
|
<ui:SettingsExpander.Items>
|
||||||
|
<ui:SettingsCard Header="仅PPT模式">
|
||||||
|
<ui:ToggleSwitch Name="ToggleSwitchPPTOnlyMode" Toggled="ToggleSwitchPPTOnlyMode_Toggled" />
|
||||||
|
</ui:SettingsCard>
|
||||||
|
</ui:SettingsExpander.Items>
|
||||||
|
</ui:SettingsExpander>
|
||||||
|
|
||||||
|
<!-- 底部空白 -->
|
||||||
|
<Rectangle Height="48" />
|
||||||
|
|
||||||
|
</ikw:SimpleStackPanel>
|
||||||
|
</Grid>
|
||||||
|
</ScrollViewer>
|
||||||
|
</Page>
|
||||||
@@ -0,0 +1,247 @@
|
|||||||
|
using System;
|
||||||
|
using System.Windows;
|
||||||
|
using System.Windows.Controls;
|
||||||
|
|
||||||
|
namespace Ink_Canvas.Windows.SettingsViews.Pages
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// StartupPage.xaml 的交互逻辑
|
||||||
|
/// </summary>
|
||||||
|
public partial class StartupPage : Page
|
||||||
|
{
|
||||||
|
private bool _isLoaded = false;
|
||||||
|
|
||||||
|
public StartupPage()
|
||||||
|
{
|
||||||
|
InitializeComponent();
|
||||||
|
Loaded += StartupPage_Loaded;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void StartupPage_Loaded(object sender, RoutedEventArgs e)
|
||||||
|
{
|
||||||
|
LoadSettings();
|
||||||
|
_isLoaded = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 加载设置到UI
|
||||||
|
/// </summary>
|
||||||
|
private void LoadSettings()
|
||||||
|
{
|
||||||
|
if (MainWindow.Settings == null) return;
|
||||||
|
|
||||||
|
_isLoaded = false;
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
// 窗口无焦点模式
|
||||||
|
if (MainWindow.Settings.Advanced != null)
|
||||||
|
{
|
||||||
|
ToggleSwitchNoFocusMode.IsOn = MainWindow.Settings.Advanced.IsNoFocusMode;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 窗口无边框模式
|
||||||
|
if (MainWindow.Settings.Advanced != null)
|
||||||
|
{
|
||||||
|
ToggleSwitchWindowMode.IsOn = MainWindow.Settings.Advanced.WindowMode;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 窗口置顶
|
||||||
|
if (MainWindow.Settings.Advanced != null)
|
||||||
|
{
|
||||||
|
ToggleSwitchAlwaysOnTop.IsOn = MainWindow.Settings.Advanced.IsAlwaysOnTop;
|
||||||
|
}
|
||||||
|
|
||||||
|
// UIA置顶
|
||||||
|
if (MainWindow.Settings.Advanced != null)
|
||||||
|
{
|
||||||
|
ToggleSwitchUIAccessTopMost.IsOn = MainWindow.Settings.Advanced.EnableUIAccessTopMost;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 开机时运行
|
||||||
|
bool runAtStartup = System.IO.File.Exists(
|
||||||
|
System.Environment.GetFolderPath(System.Environment.SpecialFolder.Startup) + "\\Ink Canvas Annotation.lnk");
|
||||||
|
ToggleSwitchRunAtStartup.IsOn = runAtStartup;
|
||||||
|
|
||||||
|
// 启动时折叠
|
||||||
|
if (MainWindow.Settings.Startup != null)
|
||||||
|
{
|
||||||
|
ToggleSwitchFoldAtStartup.IsOn = MainWindow.Settings.Startup.IsFoldAtStartup;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 仅PPT模式
|
||||||
|
if (MainWindow.Settings.ModeSettings != null)
|
||||||
|
{
|
||||||
|
ToggleSwitchPPTOnlyMode.IsOn = MainWindow.Settings.ModeSettings.IsPPTOnlyMode;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
System.Diagnostics.Debug.WriteLine($"加载启动设置时出错: {ex.Message}");
|
||||||
|
}
|
||||||
|
|
||||||
|
_isLoaded = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
#region 窗口设置事件处理
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 窗口无焦点模式开关事件
|
||||||
|
/// </summary>
|
||||||
|
private void ToggleSwitchNoFocusMode_Toggled(object sender, RoutedEventArgs e)
|
||||||
|
{
|
||||||
|
if (!_isLoaded) return;
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
bool newState = ToggleSwitchNoFocusMode.IsOn;
|
||||||
|
|
||||||
|
// 使用Helper类更新设置并应用
|
||||||
|
Windows.SettingsViews.MainWindowSettingsHelper.InvokeToggleSwitchToggled("ToggleSwitchNoFocusMode", newState);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
System.Diagnostics.Debug.WriteLine($"设置窗口无焦点模式时出错: {ex.Message}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 窗口无边框模式开关事件
|
||||||
|
/// </summary>
|
||||||
|
private void ToggleSwitchWindowMode_Toggled(object sender, RoutedEventArgs e)
|
||||||
|
{
|
||||||
|
if (!_isLoaded) return;
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
bool newState = ToggleSwitchWindowMode.IsOn;
|
||||||
|
|
||||||
|
// 使用Helper类更新设置并应用
|
||||||
|
Windows.SettingsViews.MainWindowSettingsHelper.InvokeToggleSwitchToggled("ToggleSwitchWindowMode", newState);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
System.Diagnostics.Debug.WriteLine($"设置窗口无边框模式时出错: {ex.Message}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 窗口置顶开关事件
|
||||||
|
/// </summary>
|
||||||
|
private void ToggleSwitchAlwaysOnTop_Toggled(object sender, RoutedEventArgs e)
|
||||||
|
{
|
||||||
|
if (!_isLoaded) return;
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
bool newState = ToggleSwitchAlwaysOnTop.IsOn;
|
||||||
|
|
||||||
|
// 使用Helper类更新设置并应用
|
||||||
|
Windows.SettingsViews.MainWindowSettingsHelper.InvokeToggleSwitchToggled("ToggleSwitchAlwaysOnTop", newState);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
System.Diagnostics.Debug.WriteLine($"设置窗口置顶时出错: {ex.Message}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// UIA置顶开关事件
|
||||||
|
/// </summary>
|
||||||
|
private void ToggleSwitchUIAccessTopMost_Toggled(object sender, RoutedEventArgs e)
|
||||||
|
{
|
||||||
|
if (!_isLoaded) return;
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
bool newState = ToggleSwitchUIAccessTopMost.IsOn;
|
||||||
|
|
||||||
|
// 更新Settings对象
|
||||||
|
if (MainWindow.Settings.Advanced != null)
|
||||||
|
{
|
||||||
|
MainWindow.Settings.Advanced.EnableUIAccessTopMost = newState;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 保存设置
|
||||||
|
MainWindow.SaveSettingsToFile();
|
||||||
|
|
||||||
|
// 通知其他面板同步状态
|
||||||
|
Windows.SettingsViews.MainWindowSettingsHelper.NotifySettingsPanelsSyncState("ToggleSwitchUIAccessTopMost");
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
System.Diagnostics.Debug.WriteLine($"设置UIA置顶时出错: {ex.Message}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region 启动设置事件处理
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 开机时运行开关事件
|
||||||
|
/// </summary>
|
||||||
|
private void ToggleSwitchRunAtStartup_Toggled(object sender, RoutedEventArgs e)
|
||||||
|
{
|
||||||
|
if (!_isLoaded) return;
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
bool newState = ToggleSwitchRunAtStartup.IsOn;
|
||||||
|
|
||||||
|
// 使用Helper类更新设置并应用
|
||||||
|
Windows.SettingsViews.MainWindowSettingsHelper.InvokeToggleSwitchToggled("ToggleSwitchRunAtStartup", newState);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
System.Diagnostics.Debug.WriteLine($"设置开机启动时出错: {ex.Message}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 开机运行后收纳到侧边栏开关事件
|
||||||
|
/// </summary>
|
||||||
|
private void ToggleSwitchFoldAtStartup_Toggled(object sender, RoutedEventArgs e)
|
||||||
|
{
|
||||||
|
if (!_isLoaded) return;
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
bool newState = ToggleSwitchFoldAtStartup.IsOn;
|
||||||
|
|
||||||
|
// 使用Helper类更新设置并应用
|
||||||
|
Windows.SettingsViews.MainWindowSettingsHelper.InvokeToggleSwitchToggled("ToggleSwitchFoldAtStartup", newState);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
System.Diagnostics.Debug.WriteLine($"设置开机折叠时出错: {ex.Message}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region 模式设置事件处理
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 仅PPT模式开关事件
|
||||||
|
/// </summary>
|
||||||
|
private void ToggleSwitchPPTOnlyMode_Toggled(object sender, RoutedEventArgs e)
|
||||||
|
{
|
||||||
|
if (!_isLoaded) return;
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
bool newState = ToggleSwitchPPTOnlyMode.IsOn;
|
||||||
|
|
||||||
|
// 使用Helper类更新设置并应用
|
||||||
|
Windows.SettingsViews.MainWindowSettingsHelper.InvokeToggleSwitchToggled("ToggleSwitchMode", newState);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
System.Diagnostics.Debug.WriteLine($"设置仅PPT模式时出错: {ex.Message}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
<ui:Page
|
||||||
|
x:Class="Ink_Canvas.Windows.SettingsViews.Pages.ThemePage"
|
||||||
|
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||||
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||||
|
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||||
|
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||||
|
xmlns:ui="http://schemas.inkore.net/lib/ui/wpf/modern"
|
||||||
|
xmlns:ikw="http://schemas.inkore.net/lib/ui/wpf"
|
||||||
|
Title="主题设置"
|
||||||
|
mc:Ignorable="d">
|
||||||
|
<Grid>
|
||||||
|
<StackPanel Margin="24">
|
||||||
|
<TextBlock Text="主题设置" Style="{DynamicResource TitleTextBlockStyle}" Margin="0,0,0,16" />
|
||||||
|
<TextBlock Text="这里是主题设置页面" Style="{DynamicResource BodyTextBlockStyle}" />
|
||||||
|
</StackPanel>
|
||||||
|
</Grid>
|
||||||
|
</ui:Page>
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
using iNKORE.UI.WPF.Modern.Controls;
|
||||||
|
|
||||||
|
namespace Ink_Canvas.Windows.SettingsViews.Pages
|
||||||
|
{
|
||||||
|
public partial class ThemePage : Page
|
||||||
|
{
|
||||||
|
public ThemePage()
|
||||||
|
{
|
||||||
|
InitializeComponent();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
<ui:Page
|
||||||
|
x:Class="Ink_Canvas.Windows.SettingsViews.Pages.TypographyPage"
|
||||||
|
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||||
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||||
|
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||||
|
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||||
|
xmlns:ui="http://schemas.inkore.net/lib/ui/wpf/modern"
|
||||||
|
xmlns:ikw="http://schemas.inkore.net/lib/ui/wpf"
|
||||||
|
Title="排版设置"
|
||||||
|
mc:Ignorable="d">
|
||||||
|
<Grid>
|
||||||
|
<StackPanel Margin="24">
|
||||||
|
<TextBlock Text="排版设置" Style="{DynamicResource TitleTextBlockStyle}" Margin="0,0,0,16" />
|
||||||
|
<TextBlock Text="这里是排版设置页面" Style="{DynamicResource BodyTextBlockStyle}" />
|
||||||
|
</StackPanel>
|
||||||
|
</Grid>
|
||||||
|
</ui:Page>
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
using iNKORE.UI.WPF.Modern.Controls;
|
||||||
|
|
||||||
|
namespace Ink_Canvas.Windows.SettingsViews.Pages
|
||||||
|
{
|
||||||
|
public partial class TypographyPage : Page
|
||||||
|
{
|
||||||
|
public TypographyPage()
|
||||||
|
{
|
||||||
|
InitializeComponent();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user