Merge pull request #62 from WXRIW/feat/TimeMachine

[Feature] 新增多步撤回功能 (Project Name: TimeMachine)
This commit is contained in:
XY Wang 2023-05-08 20:02:44 +08:00 committed by GitHub
commit 8a312dc3c9
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 899 additions and 176 deletions

View File

@ -19,6 +19,14 @@ namespace Ink_Canvas
public App() public App()
{ {
this.Startup += new StartupEventHandler(App_Startup); this.Startup += new StartupEventHandler(App_Startup);
this.DispatcherUnhandledException += App_DispatcherUnhandledException;
}
private void App_DispatcherUnhandledException(object sender, System.Windows.Threading.DispatcherUnhandledExceptionEventArgs e)
{
Ink_Canvas.MainWindow.ShowNewMessage("抱歉,出现未预期的异常,可能导致 Ink Canvas 画板运行不稳定。\n建议保存墨迹后重启应用。", true);
LogHelper.NewLog(e.Exception.ToString());
e.Handled = true;
} }
void App_Startup(object sender, StartupEventArgs e) void App_Startup(object sender, StartupEventArgs e)

View File

@ -32,4 +32,21 @@ namespace Ink_Canvas.Converter
} }
} }
} }
public class IsEnabledToOpacityConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
bool isChecked = (bool)value;
if (isChecked == true)
{
return 1d;
}
else
{
return 0.35;
}
}
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture) { throw new NotImplementedException(); }
}
} }

View File

@ -0,0 +1,148 @@
using System.Collections.Generic;
using System.Windows.Ink;
namespace Ink_Canvas.Helpers
{
public class TimeMachine
{
private readonly List<TimeMachineHistory> _currentStrokeHistory = new List<TimeMachineHistory>();
private int _currentIndex = -1;
public delegate void OnUndoStateChange(bool status);
public event OnUndoStateChange OnUndoStateChanged;
public delegate void OnRedoStateChange(bool status);
public event OnRedoStateChange OnRedoStateChanged;
public void CommitStrokeUserInputHistory(StrokeCollection stroke)
{
if (_currentIndex + 1 < _currentStrokeHistory.Count)
{
_currentStrokeHistory.RemoveRange(_currentIndex + 1, (_currentStrokeHistory.Count - 1) - _currentIndex);
}
_currentStrokeHistory.Add(new TimeMachineHistory(stroke, TimeMachineHistoryType.UserInput, false));
_currentIndex = _currentStrokeHistory.Count - 1;
NotifyUndoRedoState();
}
public void CommitStrokeShapeHistory(StrokeCollection strokeToBeReplaced, StrokeCollection generatedStroke)
{
if (_currentIndex + 1 < _currentStrokeHistory.Count)
{
_currentStrokeHistory.RemoveRange(_currentIndex + 1, (_currentStrokeHistory.Count - 1) - _currentIndex);
}
_currentStrokeHistory.Add(new TimeMachineHistory(generatedStroke,
TimeMachineHistoryType.ShapeRecognition,
false,
strokeToBeReplaced));
_currentIndex = _currentStrokeHistory.Count - 1;
NotifyUndoRedoState();
}
public void CommitStrokeRotateHistory(StrokeCollection strokeToBeReplaced, StrokeCollection generatedStroke)
{
if (_currentIndex + 1 < _currentStrokeHistory.Count)
{
_currentStrokeHistory.RemoveRange(_currentIndex + 1, (_currentStrokeHistory.Count - 1) - _currentIndex);
}
_currentStrokeHistory.Add(new TimeMachineHistory(generatedStroke,
TimeMachineHistoryType.Rotate,
false,
strokeToBeReplaced));
_currentIndex = _currentStrokeHistory.Count - 1;
NotifyUndoRedoState();
}
public void CommitStrokeEraseHistory(StrokeCollection stroke, StrokeCollection sourceStroke = null)
{
if (_currentIndex + 1 < _currentStrokeHistory.Count)
{
_currentStrokeHistory.RemoveRange(_currentIndex + 1, (_currentStrokeHistory.Count - 1) - _currentIndex);
}
_currentStrokeHistory.Add(new TimeMachineHistory(stroke, TimeMachineHistoryType.Clear, true, sourceStroke));
_currentIndex = _currentStrokeHistory.Count - 1;
NotifyUndoRedoState();
}
public void ClearStrokeHistory()
{
_currentStrokeHistory.Clear();
_currentIndex = -1;
NotifyUndoRedoState();
}
public TimeMachineHistory Undo()
{
var item = _currentStrokeHistory[_currentIndex];
item.StrokeHasBeenCleared = !item.StrokeHasBeenCleared;
_currentIndex--;
OnUndoStateChanged?.Invoke(_currentIndex > -1);
OnRedoStateChanged?.Invoke(_currentStrokeHistory.Count - _currentIndex - 1 > 0);
return item;
}
public TimeMachineHistory Redo()
{
var item = _currentStrokeHistory[++_currentIndex];
item.StrokeHasBeenCleared = !item.StrokeHasBeenCleared;
NotifyUndoRedoState();
return item;
}
public TimeMachineHistory[] ExportTimeMachineHistory()
{
if (_currentIndex + 1 < _currentStrokeHistory.Count)
{
_currentStrokeHistory.RemoveRange(_currentIndex + 1, (_currentStrokeHistory.Count - 1) - _currentIndex);
}
return _currentStrokeHistory.ToArray();
}
public bool ImportTimeMachineHistory(TimeMachineHistory[] sourceHistory)
{
_currentStrokeHistory.Clear();
_currentStrokeHistory.AddRange(sourceHistory);
_currentIndex = _currentStrokeHistory.Count - 1;
NotifyUndoRedoState();
return true;
}
private void NotifyUndoRedoState()
{
OnUndoStateChanged?.Invoke(_currentIndex > -1);
OnRedoStateChanged?.Invoke(_currentStrokeHistory.Count - _currentIndex - 1 > 0);
}
}
public class TimeMachineHistory
{
public TimeMachineHistoryType CommitType;
public bool StrokeHasBeenCleared;
public StrokeCollection CurrentStroke;
public StrokeCollection ReplacedStroke;
public TimeMachineHistory(StrokeCollection currentStroke, TimeMachineHistoryType commitType, bool strokeHasBeenCleared)
{
CommitType = commitType;
CurrentStroke = currentStroke;
StrokeHasBeenCleared = strokeHasBeenCleared;
ReplacedStroke = null;
}
public TimeMachineHistory(StrokeCollection currentStroke, TimeMachineHistoryType commitType, bool strokeHasBeenCleared, StrokeCollection replacedStroke)
{
CommitType = commitType;
CurrentStroke = currentStroke;
StrokeHasBeenCleared = strokeHasBeenCleared;
ReplacedStroke = replacedStroke;
}
}
public enum TimeMachineHistoryType
{
UserInput,
ShapeRecognition,
Clear,
Rotate
}
}

View File

@ -166,7 +166,8 @@
<Compile Include="Helpers\InkRecognizeHelper.cs" /> <Compile Include="Helpers\InkRecognizeHelper.cs" />
<Compile Include="Helpers\LogHelper.cs" /> <Compile Include="Helpers\LogHelper.cs" />
<Compile Include="Helpers\MultiTouchInput.cs" /> <Compile Include="Helpers\MultiTouchInput.cs" />
<Compile Include="Helpers\VisibilityConverter.cs" /> <Compile Include="Helpers\TimeMachine.cs" />
<Compile Include="Helpers\Converters.cs" />
<Compile Include="NamesInputWindow.xaml.cs"> <Compile Include="NamesInputWindow.xaml.cs">
<DependentUpon>NamesInputWindow.xaml</DependentUpon> <DependentUpon>NamesInputWindow.xaml</DependentUpon>
</Compile> </Compile>

View File

@ -1,4 +1,4 @@
<Window x:Class="Ink_Canvas.MainWindow" <Window x:Class="Ink_Canvas.MainWindow"
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:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
@ -20,7 +20,7 @@
Closing="Window_Closing" Closing="Window_Closing"
Closed="Window_Closed" Closed="Window_Closed"
PreviewKeyDown="Main_Grid_PreviewKeyDown" PreviewKeyDown="Main_Grid_PreviewKeyDown"
Height="10000" Width="1000" Height="1000" Width="1000"
FontFamily="Microsoft YaHei UI" FontFamily="Microsoft YaHei UI"
MouseWheel="Window_MouseWheel" MouseWheel="Window_MouseWheel"
Foreground="Black" Foreground="Black"
@ -28,6 +28,7 @@
<!--资源中添加命令--> <!--资源中添加命令-->
<Window.Resources> <Window.Resources>
<c:VisibilityConverter x:Key="VisibilityConverter"/> <c:VisibilityConverter x:Key="VisibilityConverter"/>
<c:IsEnabledToOpacityConverter x:Key="IsEnabledToOpacityConverter"/>
<RoutedUICommand x:Key="KeyExit" Text=" "/> <RoutedUICommand x:Key="KeyExit" Text=" "/>
<RoutedUICommand x:Key="back_HotKey_Command" Text=" "/> <RoutedUICommand x:Key="back_HotKey_Command" Text=" "/>
<RoutedUICommand x:Key="HotKey_Capture" Text=" "/> <RoutedUICommand x:Key="HotKey_Capture" Text=" "/>
@ -97,13 +98,17 @@
ManipulationDelta="Main_Grid_ManipulationDelta" ManipulationDelta="Main_Grid_ManipulationDelta"
ManipulationCompleted="Main_Grid_ManipulationCompleted" ManipulationCompleted="Main_Grid_ManipulationCompleted"
ManipulationInertiaStarting="inkCanvas_ManipulationInertiaStarting" ManipulationInertiaStarting="inkCanvas_ManipulationInertiaStarting"
IsManipulationEnabled="True" EditingModeChanged="inkCanvas_EditingModeChanged" IsManipulationEnabled="True"
EditingModeChanged="inkCanvas_EditingModeChanged"
PreviewTouchDown="inkCanvas_PreviewTouchDown" PreviewTouchDown="inkCanvas_PreviewTouchDown"
PreviewTouchUp="inkCanvas_PreviewTouchUp" PreviewTouchUp="inkCanvas_PreviewTouchUp"
MouseDown="inkCanvas_MouseDown" MouseMove="inkCanvas_MouseMove" MouseUp="inkCanvas_MouseUp" MouseDown="inkCanvas_MouseDown"
MouseMove="inkCanvas_MouseMove"
MouseUp="inkCanvas_MouseUp"
ManipulationStarting="inkCanvas_ManipulationStarting" ManipulationStarting="inkCanvas_ManipulationStarting"
SelectionChanged="inkCanvas_SelectionChanged" SelectionChanged="inkCanvas_SelectionChanged"
StrokeCollected="inkCanvas_StrokeCollected"> StrokeCollected="inkCanvas_StrokeCollected"
>
<!--<InkCanvas.DefaultDrawingAttributes> <!--<InkCanvas.DefaultDrawingAttributes>
<DrawingAttributes StylusTip="Ellipse" Height="8" Width="4" IgnorePressure="False" FitToCurve="True" > <DrawingAttributes StylusTip="Ellipse" Height="8" Width="4" IgnorePressure="False" FitToCurve="True" >
<DrawingAttributes.StylusTipTransform> <DrawingAttributes.StylusTipTransform>
@ -849,7 +854,7 @@
Margin="0,10,0,0" Width="{Binding ElementName=StackPanelMain, Path=ActualWidth}" Margin="0,10,0,0" Width="{Binding ElementName=StackPanelMain, Path=ActualWidth}"
Click="BtnUndo_Click" Foreground="{Binding ElementName=BtnExit, Path=Foreground}" Click="BtnUndo_Click" Foreground="{Binding ElementName=BtnExit, Path=Foreground}"
Background="{Binding ElementName=BtnExit, Path=Background}" Background="{Binding ElementName=BtnExit, Path=Background}"
IsEnabled="False" IsEnabledChanged="Btn_IsEnabledChanged"> IsEnabled="False" Visibility="Collapsed" IsEnabledChanged="Btn_IsEnabledChanged">
<StackPanel Opacity="0.2"> <StackPanel Opacity="0.2">
<ui:SymbolIcon Symbol="Undo"/> <ui:SymbolIcon Symbol="Undo"/>
<TextBlock Text="撤销" Margin="0,4,0,0"/> <TextBlock Text="撤销" Margin="0,4,0,0"/>
@ -1129,18 +1134,14 @@
</Viewbox> </Viewbox>
</Border> </Border>
</Grid> </Grid>
<Viewbox Margin="0,2"> <ui:SymbolIcon Symbol="Undo" Foreground="#666666" Margin="0,0,-2,0"
<Grid> Opacity="{Binding ElementName=BtnUndo, Path=IsEnabled, Converter={StaticResource IsEnabledToOpacityConverter}}"
<ui:SymbolIcon Symbol="Undo" Foreground="#666666"
Visibility="{Binding ElementName=BtnUndo, Path=Visibility}"
IsEnabled="{Binding ElementName=BtnUndo, Path=IsEnabled}" IsEnabled="{Binding ElementName=BtnUndo, Path=IsEnabled}"
MouseUp="SymbolIconUndo_MouseUp" MouseDown="Border_MouseDown"/> MouseUp="SymbolIconUndo_MouseUp" MouseDown="Border_MouseDown"/>
<ui:SymbolIcon Symbol="Redo" Foreground="#666666" <ui:SymbolIcon Symbol="Redo" Foreground="#666666" Margin="-2,0,0,0"
Visibility="{Binding ElementName=BtnRedo, Path=Visibility}" Opacity="{Binding ElementName=BtnRedo, Path=IsEnabled, Converter={StaticResource IsEnabledToOpacityConverter}}"
IsEnabled="{Binding ElementName=BtnRedo, Path=IsEnabled}" IsEnabled="{Binding ElementName=BtnRedo, Path=IsEnabled}"
MouseUp="SymbolIconRedo_MouseUp" MouseDown="Border_MouseDown"/> MouseUp="SymbolIconRedo_MouseUp" MouseDown="Border_MouseDown"/>
</Grid>
</Viewbox>
</ui:SimpleStackPanel> </ui:SimpleStackPanel>
<ui:SimpleStackPanel Orientation="{Binding ElementName=StackPanelFloatingBar, Path=Orientation}"> <ui:SimpleStackPanel Orientation="{Binding ElementName=StackPanelFloatingBar, Path=Orientation}">
<Grid Width="20" Height="24" Margin="2,0,12,1"> <Grid Width="20" Height="24" Margin="2,0,12,1">

File diff suppressed because it is too large Load Diff