diff --git a/Ink Canvas/InkCanvasForClass.csproj b/Ink Canvas/InkCanvasForClass.csproj
index 1ecdc92a5..f0498ec4b 100644
--- a/Ink Canvas/InkCanvasForClass.csproj
+++ b/Ink Canvas/InkCanvasForClass.csproj
@@ -224,6 +224,27 @@
True
+
+
+ True
+
+
+
+
+ libs\Dusk.Wpf.Shell.dll
+ True
+
+
+ Dusk.Wpf.Shell.dll
+ PreserveNewest
+
+
+
+
+ Dusk.Native.dll
+ PreserveNewest
+
+
diff --git a/Ink Canvas/MainWindow.xaml b/Ink Canvas/MainWindow.xaml
index f59fdd7ae..c3a1d34b9 100644
--- a/Ink Canvas/MainWindow.xaml
+++ b/Ink Canvas/MainWindow.xaml
@@ -13,6 +13,7 @@
xmlns:helpers="clr-namespace:Ink_Canvas.Helpers"
xmlns:wmk="clr-namespace:WPFMediaKit.DirectShow.Controls;assembly=WPFMediaKit"
xmlns:icons="clr-namespace:Ink_Canvas"
+ xmlns:dusk="clr-namespace:Dusk.Wpf.Shell;assembly=Dusk.Wpf.Shell"
mc:Ignorable="d"
WindowStyle="None"
ResizeMode="NoResize"
@@ -226,7 +227,8 @@
-
+
+
+
-
+
@@ -811,7 +819,7 @@
-
+
-
+
-
@@ -1354,7 +1362,7 @@
Margin="0,0,-60,-150" IsRightSide="True"
HorizontalAlignment="Right" VerticalAlignment="Center" />
-
diff --git a/Ink Canvas/MainWindow.xaml.cs b/Ink Canvas/MainWindow.xaml.cs
index 2f44a9c1e..7993d1783 100644
--- a/Ink Canvas/MainWindow.xaml.cs
+++ b/Ink Canvas/MainWindow.xaml.cs
@@ -1532,6 +1532,7 @@ private void Window_Loaded(object sender, RoutedEventArgs e)
{
LogHelper.WriteLogToFile($"[Startup] 主窗口加载开始,启动模式={App.CurrentStartupMode}", LogHelper.LogType.Info);
loadPenCanvas();
+ ApplyInkEngine(Settings?.Canvas?.InkEngine ?? InkEngineType.Dusk);
// 工具栏插件化按钮先注入到容器,确保 LoadSettings 内部对 Cursor_Icon / Pen_Icon 等的访问非空。
// Settings.Toolbar 此时尚为默认值(全部可见),与旧 XAML 行为一致。
InitializeToolbarPlugins();
@@ -1991,6 +1992,7 @@ private void MainWindow_OnSizeChanged(object sender, SizeChangedEventArgs e)
private void Window_Closed(object sender, EventArgs e)
{
LogHelper.WriteLogToFile("[Exit] 主窗口 Closed,开始释放资源", LogHelper.LogType.Info);
+ DisposeDuskEngine();
RealtimeInkFrameScheduler.Clear();
try
{
@@ -3543,7 +3545,37 @@ internal bool SetCurrentToolMode(InkCanvasEditingMode newMode, Action additional
// 执行模式切换
inkCanvas.EditingMode = newMode;
- SyncWinRTInkPipelineWithLogicalTool();
+
+ if (Settings?.Canvas?.InkEngine == InkEngineType.Dusk)
+ {
+ // 同步切换 Dusk 原生内核墨迹引擎状态
+ switch (newMode)
+ {
+ case InkCanvasEditingMode.Ink:
+ SetDuskCanvasActive(true);
+ SetDuskEditingMode(Dusk.Wpf.Shell.ShellEditingMode.Ink);
+ SyncDuskPenAttributes(drawingAttributes?.Color ?? Ink_DefaultColor, drawingAttributes != null && drawingAttributes.Width > 0 ? drawingAttributes.Width : 2.5);
+ break;
+ case InkCanvasEditingMode.EraseByPoint:
+ SetDuskCanvasActive(true);
+ SetDuskEditingMode(Dusk.Wpf.Shell.ShellEditingMode.EraseByPoint);
+ SetDuskEraserRadius(eraserWidth > 0 ? eraserWidth / 2 : 24.0);
+ break;
+ case InkCanvasEditingMode.EraseByStroke:
+ SetDuskCanvasActive(true);
+ SetDuskEditingMode(Dusk.Wpf.Shell.ShellEditingMode.EraseByStroke);
+ SetDuskEraserRadius(eraserWidth > 0 ? eraserWidth / 2 : 24.0);
+ break;
+ default:
+ SetDuskCanvasActive(false);
+ SetDuskEditingMode(Dusk.Wpf.Shell.ShellEditingMode.None);
+ break;
+ }
+ }
+ else
+ {
+ SyncWinRTInkPipelineWithLogicalTool();
+ }
// 根据模式确定是否为鼠标模式(无工具模式)
bool isMouseMode = newMode == InkCanvasEditingMode.None;
diff --git a/Ink Canvas/MainWindow_cs/MW_BoardControls.cs b/Ink Canvas/MainWindow_cs/MW_BoardControls.cs
index 2d1269cd3..21716cad5 100644
--- a/Ink Canvas/MainWindow_cs/MW_BoardControls.cs
+++ b/Ink Canvas/MainWindow_cs/MW_BoardControls.cs
@@ -202,6 +202,7 @@ private void ClearStrokes(bool isErasedByCode)
// here as well so page switches, loads and code/UI clears cannot leave them behind.
ClearSecAgentSceneElements();
inkCanvas.Strokes.Clear();
+ DuskClear(isErasedByCode);
// 只隐藏 hint,不暂停(ClearStrokes 在切换页面、保存加载时都会被调用,
// 设置 _edgeExpandHintSuspended 会导致后续书写永远无法触发提示)。
HideEdgeExpandHint();
@@ -258,6 +259,7 @@ private void RestoreStrokes(bool isBackupMain = false)
}
var targetIndex = isBackupMain ? 0 : CurrentWhiteboardIndex;
+ DuskSwitchSlot(targetIndex);
// 先清空当前画布的墨迹
inkCanvas.Strokes.Clear();
@@ -811,6 +813,7 @@ private void DeleteWhiteBoardPageByIndex(int pageIndex)
var deletingCurrentPluginPage = pageIndex == CurrentWhiteboardIndex;
if (deletingCurrentPluginPage) CaptureCurrentPluginPageStates();
var removedPluginPage = RemovePluginWhiteboardPageId(pageIndex, oldPluginPageCount);
+ DuskDeleteSlot(pageIndex);
if (pageIndex == CurrentWhiteboardIndex)
{
diff --git a/Ink Canvas/MainWindow_cs/MW_Colors.cs b/Ink Canvas/MainWindow_cs/MW_Colors.cs
index 496d3b700..48c31c932 100644
--- a/Ink Canvas/MainWindow_cs/MW_Colors.cs
+++ b/Ink Canvas/MainWindow_cs/MW_Colors.cs
@@ -639,6 +639,12 @@ private void CheckColorTheme(bool changeColorTheme = false)
UpdatePenIconColor();
// 更新白板工具栏画笔图标颜色
UpdateBoardPenIconColor();
+
+ // 同步笔触属性给 Dusk 原生内核
+ if (duskCanvas != null && inkCanvas != null)
+ {
+ SyncDuskPenAttributes(inkCanvas.DefaultDrawingAttributes.Color, inkCanvas.DefaultDrawingAttributes.Width);
+ }
}
///
diff --git a/Ink Canvas/MainWindow_cs/MW_DuskInk.cs b/Ink Canvas/MainWindow_cs/MW_DuskInk.cs
new file mode 100644
index 000000000..c5d4df2ec
--- /dev/null
+++ b/Ink Canvas/MainWindow_cs/MW_DuskInk.cs
@@ -0,0 +1,270 @@
+using System.Windows.Controls;
+using Dusk.Wpf.Shell;
+using Ink_Canvas.Helpers;
+using System;
+using System.Windows;
+using System.Windows.Media;
+
+namespace Ink_Canvas
+{
+ public partial class MainWindow
+ {
+ private bool _isDuskInitialized;
+
+ ///
+ /// 初始化并接管 Dusk 墨迹引擎
+ ///
+ internal void InitDuskEngine()
+ {
+ if (_isDuskInitialized || duskCanvas == null) return;
+ try
+ {
+ // 1. 全局日志流保持静默或仅用于开发调试,避免每帧高频写盘拖垮 UI 渲染线程
+ WpfNativeInkCanvas.LogAction = null;
+
+ LogHelper.WriteLogToFile("[Dusk.Bridge] 开始初始化 Dusk 墨迹引擎...", LogHelper.LogType.Warning);
+
+ // 2. 验证内核 ABI 版本
+ int abiVersion = WpfNativeInkCanvas.KernelAbiVersion;
+ LogHelper.WriteLogToFile($"[Dusk.Bridge] 成功加载 Dusk.Native 内核, ABI 版本: {abiVersion}", LogHelper.LogType.Warning);
+
+ // 3. 启用内置撤销/重做栈
+ duskCanvas.EnableHistory();
+
+ // 4. 初始同步笔迹外观
+ duskCanvas.EditingMode = ShellEditingMode.Ink;
+ Color defaultColor = drawingAttributes?.Color ?? Ink_DefaultColor;
+ double defaultWidth = drawingAttributes != null && drawingAttributes.Width > 0 ? drawingAttributes.Width : 2.5;
+ double defaultEraserRadius = eraserWidth > 0 ? eraserWidth / 2 : 24.0;
+
+ duskCanvas.InkColor = defaultColor;
+ duskCanvas.StrokeWidth = defaultWidth;
+ duskCanvas.EraserRadius = defaultEraserRadius;
+
+ // 5. 确保 Dusk 画布可见并处于激活状态,同时屏蔽原有 WPF InkCanvas 避免双重渲染与抢占事件
+ duskCanvas.Visibility = Visibility.Visible;
+ duskCanvas.IsHitTestVisible = true;
+ if (inkCanvas != null)
+ {
+ inkCanvas.Visibility = Visibility.Hidden;
+ inkCanvas.IsHitTestVisible = false;
+ }
+
+ // 6. 挂接激光笔衰减与覆盖层渲染帧循环
+ CompositionTarget.Rendering -= DuskCompositionTarget_Rendering;
+ CompositionTarget.Rendering += DuskCompositionTarget_Rendering;
+
+ _isDuskInitialized = true;
+ LogHelper.WriteLogToFile($"[Dusk.Bridge] Dusk 墨迹引擎初始化成功! 初始颜色=#{defaultColor.A:X2}{defaultColor.R:X2}{defaultColor.G:X2}{defaultColor.B:X2}, 初始粗细={defaultWidth:F1}, 初始橡皮半径={defaultEraserRadius:F1}", LogHelper.LogType.Warning);
+ }
+ catch (Exception ex)
+ {
+ LogHelper.WriteLogToFile($"[Dusk.Bridge] 初始化失败: {ex}", LogHelper.LogType.Error);
+ }
+ }
+
+ private void DuskCompositionTarget_Rendering(object sender, EventArgs e)
+ {
+ if (_isDuskInitialized && duskCanvas != null)
+ {
+ long nowMs = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds();
+ duskCanvas.TickOverlays(nowMs);
+ }
+ }
+
+ ///
+ /// 应用指定的墨迹引擎(WPF、Dusk、WinRT)
+ ///
+ internal void ApplyInkEngine(InkEngineType engine)
+ {
+ LogHelper.WriteLogToFile($"[InkEngine] 切换墨迹引擎: {engine}", LogHelper.LogType.Warning);
+ switch (engine)
+ {
+ case InkEngineType.Dusk:
+ ShutdownWinRTInkPipeline();
+ InitDuskEngine();
+ if (duskCanvas != null)
+ {
+ duskCanvas.Visibility = Visibility.Visible;
+ duskCanvas.IsHitTestVisible = (inkCanvas?.EditingMode != InkCanvasEditingMode.None);
+ }
+ if (inkCanvas != null)
+ {
+ inkCanvas.Visibility = Visibility.Hidden;
+ inkCanvas.IsHitTestVisible = false;
+ }
+ break;
+
+ case InkEngineType.WinRT:
+ if (duskCanvas != null)
+ {
+ duskCanvas.IsHitTestVisible = false;
+ duskCanvas.Visibility = Visibility.Collapsed;
+ }
+ if (inkCanvas != null)
+ {
+ inkCanvas.Visibility = Visibility.Visible;
+ inkCanvas.IsHitTestVisible = (inkCanvas.EditingMode != InkCanvasEditingMode.None);
+ }
+ SyncWinRTInkPipelineWithLogicalTool();
+ break;
+
+ case InkEngineType.Wpf:
+ default:
+ ShutdownWinRTInkPipeline();
+ if (duskCanvas != null)
+ {
+ duskCanvas.IsHitTestVisible = false;
+ duskCanvas.Visibility = Visibility.Collapsed;
+ }
+ if (inkCanvas != null)
+ {
+ inkCanvas.Visibility = Visibility.Visible;
+ inkCanvas.IsHitTestVisible = (inkCanvas.EditingMode != InkCanvasEditingMode.None);
+ }
+ break;
+ }
+ }
+
+ ///
+ /// 设置 Dusk 画布激活状态(命中测试与可见性)
+ ///
+ internal void SetDuskCanvasActive(bool active)
+ {
+ if (duskCanvas == null) return;
+ if (Settings?.Canvas?.InkEngine != InkEngineType.Dusk)
+ {
+ duskCanvas.IsHitTestVisible = false;
+ return;
+ }
+ LogHelper.WriteLogToFile($"[Dusk.Bridge] SetDuskCanvasActive: active={active}", LogHelper.LogType.Warning);
+ if (active)
+ {
+ DuskSyncSlot();
+ }
+ duskCanvas.IsHitTestVisible = active;
+ duskCanvas.Visibility = Visibility.Visible;
+
+ if (inkCanvas != null)
+ {
+ inkCanvas.IsHitTestVisible = false;
+ inkCanvas.Visibility = Visibility.Hidden;
+ }
+ }
+
+ ///
+ /// 同步当前笔触颜色与粗细到 Dusk
+ ///
+ internal void SyncDuskPenAttributes(Color color, double width)
+ {
+ if (!_isDuskInitialized || duskCanvas == null) return;
+ LogHelper.WriteLogToFile($"[Dusk.Bridge] SyncDuskPenAttributes: color=#{color.A:X2}{color.R:X2}{color.G:X2}{color.B:X2}, width={width:F1}", LogHelper.LogType.Warning);
+ duskCanvas.InkColor = color;
+ duskCanvas.StrokeWidth = width > 0 ? width : 2.5;
+ duskCanvas.EditingMode = ShellEditingMode.Ink;
+ }
+
+ ///
+ /// 切换 Dusk 编辑模式(笔 / 点橡皮 / 笔段橡皮 / 禁用)
+ ///
+ internal void SetDuskEditingMode(ShellEditingMode mode)
+ {
+ if (!_isDuskInitialized || duskCanvas == null) return;
+ LogHelper.WriteLogToFile($"[Dusk.Bridge] SetDuskEditingMode: mode={mode}", LogHelper.LogType.Warning);
+ duskCanvas.EditingMode = mode;
+ }
+
+ ///
+ /// 设置 Dusk 橡皮擦半径
+ ///
+ internal void SetDuskEraserRadius(double radius)
+ {
+ if (!_isDuskInitialized || duskCanvas == null) return;
+ LogHelper.WriteLogToFile($"[Dusk.Bridge] SetDuskEraserRadius: radius={radius:F1}", LogHelper.LogType.Warning);
+ duskCanvas.EraserRadius = radius > 0 ? radius : 24.0;
+ }
+
+ ///
+ /// 执行 Dusk 撤销
+ ///
+ internal bool DuskUndo()
+ {
+ if (!_isDuskInitialized || duskCanvas == null) return false;
+ LogHelper.WriteLogToFile("[Dusk.Bridge] DuskUndo()", LogHelper.LogType.Warning);
+ return duskCanvas.Undo();
+ }
+
+ ///
+ /// 执行 Dusk 重做
+ ///
+ internal bool DuskRedo()
+ {
+ if (!_isDuskInitialized || duskCanvas == null) return false;
+ LogHelper.WriteLogToFile("[Dusk.Bridge] DuskRedo()", LogHelper.LogType.Warning);
+ return duskCanvas.Redo();
+ }
+
+ ///
+ /// 同步当前模式对应的 Dusk 槽位(白板模式下使用当前白板页码,非白板/桌面批注模式固定使用 Slot 0)
+ ///
+ internal void DuskSyncSlot()
+ {
+ if (!_isDuskInitialized || duskCanvas == null) return;
+ int targetSlot = (currentMode == 1) ? Math.Max(1, CurrentWhiteboardIndex) : 0;
+ if (duskCanvas.CurrentSlot != targetSlot)
+ {
+ LogHelper.WriteLogToFile($"[Dusk.Bridge] DuskSyncSlot: 依据 currentMode={currentMode} 切换槽位 -> Slot {targetSlot}", LogHelper.LogType.Warning);
+ duskCanvas.SwitchSlot(targetSlot);
+ }
+ }
+
+ ///
+ /// 切换 Dusk 墨迹槽位(Slot 0 为桌面批注,Slot 1..N 为白板各页)
+ ///
+ internal void DuskSwitchSlot(int slotIndex)
+ {
+ if (!_isDuskInitialized || duskCanvas == null) return;
+ LogHelper.WriteLogToFile($"[Dusk.Bridge] DuskSwitchSlot: slotIndex={slotIndex}", LogHelper.LogType.Warning);
+ duskCanvas.SwitchSlot(slotIndex);
+ }
+
+ ///
+ /// 删除指定白板页对应的 Dusk 槽位
+ ///
+ internal void DuskDeleteSlot(int slotIndex)
+ {
+ if (!_isDuskInitialized || duskCanvas == null) return;
+ LogHelper.WriteLogToFile($"[Dusk.Bridge] DuskDeleteSlot: slotIndex={slotIndex}", LogHelper.LogType.Warning);
+ duskCanvas.DeleteSlot(slotIndex);
+ }
+
+ ///
+ /// 清空 Dusk 画布(isErasedByCode 为 true 表示切页/暂存过渡,保留当前槽位数据)
+ ///
+ internal void DuskClear(bool isErasedByCode = false)
+ {
+ if (!_isDuskInitialized || duskCanvas == null) return;
+ if (isErasedByCode)
+ {
+ // 代码内部切页/暂存过渡,不销毁当前槽位笔迹
+ return;
+ }
+ LogHelper.WriteLogToFile($"[Dusk.Bridge] DuskClear() 用户显式清空当前槽位画布 (Slot {duskCanvas.CurrentSlot})", LogHelper.LogType.Warning);
+ duskCanvas.Clear();
+ }
+
+ ///
+ /// 释放 Dusk 原生资源
+ ///
+ internal void DisposeDuskEngine()
+ {
+ LogHelper.WriteLogToFile("[Dusk.Bridge] DisposeDuskEngine()", LogHelper.LogType.Warning);
+ CompositionTarget.Rendering -= DuskCompositionTarget_Rendering;
+ if (duskCanvas != null)
+ {
+ duskCanvas.Dispose();
+ }
+ _isDuskInitialized = false;
+ }
+ }
+}
diff --git a/Ink Canvas/MainWindow_cs/MW_Eraser.cs b/Ink Canvas/MainWindow_cs/MW_Eraser.cs
index 330184fab..c855d8d8e 100644
--- a/Ink Canvas/MainWindow_cs/MW_Eraser.cs
+++ b/Ink Canvas/MainWindow_cs/MW_Eraser.cs
@@ -58,7 +58,7 @@ private void EraserOverlayCanvas_Loaded(object sender, RoutedEventArgs e)
canvas.StylusDown += ((o, args) =>
{
if (args.StylusDevice.TabletDevice.Type == TabletDeviceType.Stylus) canvas.CaptureStylus();
- EraserOverlay_PointerDown(sender);
+ EraserOverlay_PointerDown(sender, args.GetPosition(inkCanvas));
});
canvas.StylusUp += ((o, args) =>
{
@@ -72,7 +72,7 @@ private void EraserOverlayCanvas_Loaded(object sender, RoutedEventArgs e)
canvas.MouseDown += ((o, args) =>
{
canvas.CaptureMouse();
- EraserOverlay_PointerDown(sender);
+ EraserOverlay_PointerDown(sender, args.GetPosition(inkCanvas));
});
canvas.MouseUp += ((o, args) =>
{
@@ -108,7 +108,7 @@ private void UpdateEraserStyle()
///
/// 橡皮擦按下事件处理
///
- private void EraserOverlay_PointerDown(object sender)
+ private void EraserOverlay_PointerDown(object sender, Point? startPos = null)
{
_secAgentEraseInitialStates.Clear();
if (currentSelectedElement != null)
@@ -154,6 +154,11 @@ private void EraserOverlay_PointerDown(object sender)
eraserFeedback.Measure(new Size(Double.PositiveInfinity, Double.PositiveInfinity));
eraserFeedback.Visibility = Visibility.Collapsed;
}
+
+ if (Settings?.Canvas?.InkEngine == InkEngineType.Dusk && startPos.HasValue && duskCanvas != null)
+ {
+ duskCanvas.EraseAt(startPos.Value, eraserWidth > 0 ? eraserWidth / 2 : 24.0, isUsingStrokesEraser ? 2 : 1);
+ }
}
///
@@ -213,6 +218,11 @@ private void EraserOverlay_PointerMove(object sender, Point pt)
EraseSecAgentSceneElementsAt(pt);
+ if (Settings?.Canvas?.InkEngine == InkEngineType.Dusk && duskCanvas != null)
+ {
+ duskCanvas.EraseAt(pt, eraserWidth > 0 ? eraserWidth / 2 : 24.0, isUsingStrokesEraser ? 2 : 1);
+ }
+
if (isUsingStrokesEraser)
{
// 笔画橡皮擦模式
diff --git a/Ink Canvas/MainWindow_cs/MW_FloatingBarIcons.cs b/Ink Canvas/MainWindow_cs/MW_FloatingBarIcons.cs
index 488ffcaf4..2936ce630 100644
--- a/Ink Canvas/MainWindow_cs/MW_FloatingBarIcons.cs
+++ b/Ink Canvas/MainWindow_cs/MW_FloatingBarIcons.cs
@@ -975,6 +975,7 @@ internal void ImageBlackboard_MouseUp(object sender, MouseButtonEventArgs e)
if (currentMode == 1)
{
currentMode = 0;
+ DuskSwitchSlot(0);
AutomationBootstrap.Monitor?.NotifyInternalStateChanged();
GridBackgroundCover.Visibility = Visibility.Collapsed;
AnimationsHelper.HideWithSlideAndFade(BlackboardLeftSide);
@@ -1241,7 +1242,7 @@ internal void SymbolIconDelete_MouseUp(object sender, MouseButtonEventArgs e)
GridInkCanvasSelectionCover.Visibility = Visibility.Collapsed;
HideSelectionDisplay();
}
- else if (inkCanvas.Strokes.Count > 0 || HasSecAgentSceneElementsOnCanvas())
+ else if (inkCanvas.Strokes.Count > 0 || (Settings?.Canvas?.InkEngine == InkEngineType.Dusk && duskCanvas != null && duskCanvas.StrokeCount > 0) || HasSecAgentSceneElementsOnCanvas())
{
if (Settings.Automation.IsAutoSaveScreenshotAtClear &&
inkCanvas.Strokes.Count > Settings.Automation.MinimumAutomationStrokeNumber)
@@ -3701,8 +3702,9 @@ internal void PenIcon_Click(object sender, MouseButtonEventArgs e)
GridTransparencyFakeBackground.Background = new SolidColorBrush(StringToColor("#01FFFFFF"));
SetTransparentNotHitThrough();
- inkCanvas.IsHitTestVisible = true;
- inkCanvas.Visibility = Visibility.Visible;
+ SetDuskCanvasActive(true);
+ SetDuskEditingMode(Dusk.Wpf.Shell.ShellEditingMode.Ink);
+ SyncDuskPenAttributes(drawingAttributes?.Color ?? Ink_DefaultColor, drawingAttributes != null && drawingAttributes.Width > 0 ? drawingAttributes.Width : 2.5);
GridBackgroundCoverHolder.Visibility = Visibility.Visible;
GridInkCanvasSelectionCover.Visibility = Visibility.Collapsed;
@@ -4479,6 +4481,12 @@ public void ToggleFingerDragMode(object sender, RoutedEventArgs e)
/// 路由事件参数
private void BtnUndo_Click(object sender, RoutedEventArgs e)
{
+ if (Settings?.Canvas?.InkEngine == InkEngineType.Dusk && duskCanvas != null && duskCanvas.CanUndo)
+ {
+ duskCanvas.Undo();
+ return;
+ }
+
if (inkCanvas.GetSelectedStrokes().Count != 0)
{
GridInkCanvasSelectionCover.Visibility = Visibility.Collapsed;
@@ -4496,6 +4504,12 @@ private void BtnUndo_Click(object sender, RoutedEventArgs e)
/// 路由事件参数
private void BtnRedo_Click(object sender, RoutedEventArgs e)
{
+ if (Settings?.Canvas?.InkEngine == InkEngineType.Dusk && duskCanvas != null && duskCanvas.CanRedo)
+ {
+ duskCanvas.Redo();
+ return;
+ }
+
if (inkCanvas.GetSelectedStrokes().Count != 0)
{
GridInkCanvasSelectionCover.Visibility = Visibility.Collapsed;
@@ -4646,6 +4660,7 @@ private void PerformCanvasClear(bool preserveClearHistory = false)
}
ClearStrokes(false);
+ DuskClear();
// 保存非笔画元素(如图片)
var preservedElements = PreserveNonStrokeElements();
inkCanvas.Children.Clear();
@@ -4968,8 +4983,9 @@ private void BtnHideInkCanvas_Click(object sender, RoutedEventArgs e)
GridTransparencyFakeBackground.Opacity = 1;
GridTransparencyFakeBackground.Background = new SolidColorBrush(StringToColor("#01FFFFFF"));
SetTransparentNotHitThrough();
- inkCanvas.IsHitTestVisible = true;
- inkCanvas.Visibility = Visibility.Visible;
+ SetDuskCanvasActive(true);
+ SetDuskEditingMode(Dusk.Wpf.Shell.ShellEditingMode.Ink);
+ SyncDuskPenAttributes(drawingAttributes?.Color ?? Ink_DefaultColor, drawingAttributes != null && drawingAttributes.Width > 0 ? drawingAttributes.Width : 2.5);
GridBackgroundCoverHolder.Visibility = Visibility.Visible;
@@ -5021,8 +5037,8 @@ private void BtnHideInkCanvas_Click(object sender, RoutedEventArgs e)
//BtnClear_Click(null, null);
}
- inkCanvas.IsHitTestVisible = true;
- inkCanvas.Visibility = Visibility.Visible;
+ SetDuskCanvasActive(false);
+ SetDuskEditingMode(Dusk.Wpf.Shell.ShellEditingMode.None);
}
else
{
@@ -5038,16 +5054,8 @@ private void BtnHideInkCanvas_Click(object sender, RoutedEventArgs e)
}
- if (Settings.PowerPointSettings.IsShowStrokeOnSelectInPowerPoint)
- {
- inkCanvas.Visibility = Visibility.Visible;
- inkCanvas.IsHitTestVisible = true;
- }
- else
- {
- inkCanvas.IsHitTestVisible = true;
- inkCanvas.Visibility = Visibility.Visible;
- }
+ SetDuskCanvasActive(false);
+ SetDuskEditingMode(Dusk.Wpf.Shell.ShellEditingMode.None);
}
GridTransparencyFakeBackground.Opacity = 0;
@@ -5602,6 +5610,27 @@ void ResetIcon(ToolbarImageButton button, string iconType)
break;
}
+ if (duskCanvas != null)
+ {
+ switch (mode)
+ {
+ case "pen":
+ case "color":
+ duskCanvas.EditingMode = Dusk.Wpf.Shell.ShellEditingMode.Ink;
+ break;
+ case "eraser":
+ duskCanvas.EraserRadius = eraserWidth > 0 ? eraserWidth / 2 : 24.0;
+ duskCanvas.EditingMode = Dusk.Wpf.Shell.ShellEditingMode.EraseByPoint;
+ break;
+ case "eraserByStrokes":
+ duskCanvas.EditingMode = Dusk.Wpf.Shell.ShellEditingMode.EraseByStroke;
+ break;
+ default:
+ duskCanvas.EditingMode = Dusk.Wpf.Shell.ShellEditingMode.None;
+ break;
+ }
+ }
+
if (targetButton != null && targetIconType != null)
{
if (!ToolbarRegistry.GetUseRedStyle(targetButton))
diff --git a/Ink Canvas/MainWindow_cs/MW_Settings.cs b/Ink Canvas/MainWindow_cs/MW_Settings.cs
index a8d43058b..9066c09b0 100644
--- a/Ink Canvas/MainWindow_cs/MW_Settings.cs
+++ b/Ink Canvas/MainWindow_cs/MW_Settings.cs
@@ -801,6 +801,10 @@ private void PenWidthSlider_ValueChanged(object sender, RoutedPropertyChangedEve
drawingAttributes.Height = value;
Settings.Canvas.LaserPenWidth = value;
}
+ if (duskCanvas != null && drawingAttributes != null)
+ {
+ SyncDuskPenAttributes(drawingAttributes.Color, drawingAttributes.Width);
+ }
SaveSettingsToFile();
}
diff --git a/Ink Canvas/Properties/CanvasStrings.Designer.cs b/Ink Canvas/Properties/CanvasStrings.Designer.cs
index 0611d9f9b..099545bbd 100644
--- a/Ink Canvas/Properties/CanvasStrings.Designer.cs
+++ b/Ink Canvas/Properties/CanvasStrings.Designer.cs
@@ -264,6 +264,12 @@ public static string GetString(string key)
public static string Canvas_DarkMode => ResourceManager.GetString(nameof(Canvas_DarkMode), _resourceCulture);
+ public static string Canvas_InkEngine => ResourceManager.GetString(nameof(Canvas_InkEngine), _resourceCulture);
+
+ public static string Canvas_InkEngineHint => ResourceManager.GetString(nameof(Canvas_InkEngineHint), _resourceCulture);
+
+ public static string Canvas_InkEngine_WPF => ResourceManager.GetString(nameof(Canvas_InkEngine_WPF), _resourceCulture);
+
public static string Canvas_UseWinRTInk => ResourceManager.GetString(nameof(Canvas_UseWinRTInk), _resourceCulture);
public static string Canvas_UseWinRTInkHint => ResourceManager.GetString(nameof(Canvas_UseWinRTInkHint), _resourceCulture);
diff --git a/Ink Canvas/Properties/CanvasStrings.en-US.resx b/Ink Canvas/Properties/CanvasStrings.en-US.resx
index ccdab1843..4247fba9d 100644
--- a/Ink Canvas/Properties/CanvasStrings.en-US.resx
+++ b/Ink Canvas/Properties/CanvasStrings.en-US.resx
@@ -378,6 +378,15 @@
Dark
+
+ Ink Engine
+
+
+ Select the ink rendering and input processing engine. Takes effect immediately.
+
+
+ WPF Native
+
Use WinRT ink engine (experimental)
diff --git a/Ink Canvas/Properties/CanvasStrings.resx b/Ink Canvas/Properties/CanvasStrings.resx
index 3d81f07bf..f45c2f667 100644
--- a/Ink Canvas/Properties/CanvasStrings.resx
+++ b/Ink Canvas/Properties/CanvasStrings.resx
@@ -378,6 +378,15 @@
黑底
+
+ 墨迹引擎
+
+
+ 选择墨迹渲染与输入处理引擎。切换后即时生效。
+
+
+ WPF 原生
+
使用 WinRT 墨迹引擎(实验性)
diff --git a/Ink Canvas/Properties/CanvasStrings.zh-ME.resx b/Ink Canvas/Properties/CanvasStrings.zh-ME.resx
index 03c1b3ad7..7ddea0082 100644
--- a/Ink Canvas/Properties/CanvasStrings.zh-ME.resx
+++ b/Ink Canvas/Properties/CanvasStrings.zh-ME.resx
@@ -378,6 +378,15 @@
暗黑系
+
+ 墨迹引擎
+
+
+ 选择墨迹渲染与输入处理引擎。切换后即时生效。
+
+
+ WPF 原生
+
采用 WinRT 墨迹引擎(内测中)
diff --git a/Ink Canvas/Resources/Settings.cs b/Ink Canvas/Resources/Settings.cs
index 3843e3940..71f890466 100644
--- a/Ink Canvas/Resources/Settings.cs
+++ b/Ink Canvas/Resources/Settings.cs
@@ -340,6 +340,13 @@ public class Security
public string UsbAuthorizedSns { get; set; } = "";
}
+ public enum InkEngineType
+ {
+ Wpf = 0,
+ Dusk = 1,
+ WinRT = 2
+ }
+
public class Canvas
{
[JsonProperty("inkWidth")]
@@ -426,9 +433,27 @@ public class Canvas
public bool IsCompressPicturesUploaded { get; set; }
[JsonProperty("enablePalmEraser")]
public bool EnablePalmEraser { get; set; } = true;
- /// 实验性:启用 WinRT 系统墨迹管线(CoreInkIndependentInputSource + 系统 Wet Ink),关闭时回退 WPF 原生墨迹。
+ /// 墨迹引擎:0 = WPF 原生, 1 = Dusk, 2 = WinRT 系统湿墨。
+ [JsonProperty("inkEngine")]
+ public InkEngineType InkEngine { get; set; } = InkEngineType.Dusk;
+
+ /// 实验性:兼容旧设置项(为 true 时对应 WinRT 引擎)。
[JsonProperty("useWinRTInk")]
- public bool UseWinRTInk { get; set; } = false;
+ public bool UseWinRTInk
+ {
+ get => InkEngine == InkEngineType.WinRT;
+ set
+ {
+ if (value)
+ {
+ InkEngine = InkEngineType.WinRT;
+ }
+ else if (InkEngine == InkEngineType.WinRT)
+ {
+ InkEngine = InkEngineType.Wpf;
+ }
+ }
+ }
[JsonProperty("palmEraserSensitivity")]
public int PalmEraserSensitivity { get; set; } = 0; // 0-低敏感度, 1-中敏感度, 2-高敏感度
[JsonProperty("clearCanvasAlsoClearImages")]
diff --git a/Ink Canvas/Windows/SettingsViews/Pages/CanvasPage.xaml b/Ink Canvas/Windows/SettingsViews/Pages/CanvasPage.xaml
index 669324435..32761ee90 100644
--- a/Ink Canvas/Windows/SettingsViews/Pages/CanvasPage.xaml
+++ b/Ink Canvas/Windows/SettingsViews/Pages/CanvasPage.xaml
@@ -85,13 +85,19 @@
SwitchName="ToggleSwitchDisablePressure"
Toggled="ToggleSwitchDisablePressure_Toggled" />
-
-
+
+
+
+
+
+
+
+
+
+
+