diff --git a/.gitignore b/.gitignore index 2b9a7ea6..c4df8dfc 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,428 @@ -obj/ -bin/ +## Ignore Visual Studio temporary files, build results, and +## files generated by popular Visual Studio add-ons. +## +## Get latest from https://github.com/github/gitignore/blob/main/VisualStudio.gitignore + +# User-specific files +*.rsuser +*.suo +*.user +*.userosscache +*.sln.docstates +*.env + +# User-specific files (MonoDevelop/Xamarin Studio) +*.userprefs + +# Mono auto generated files +mono_crash.* + +# Build results +[Dd]ebug/ +[Dd]ebugPublic/ +[Rr]elease/ +[Rr]eleases/ + +[Dd]ebug/x64/ +[Dd]ebugPublic/x64/ +[Rr]elease/x64/ +[Rr]eleases/x64/ +bin/x64/ +obj/x64/ + +[Dd]ebug/x86/ +[Dd]ebugPublic/x86/ +[Rr]elease/x86/ +[Rr]eleases/x86/ +bin/x86/ +obj/x86/ + +[Ww][Ii][Nn]32/ +[Aa][Rr][Mm]/ +[Aa][Rr][Mm]64/ +[Aa][Rr][Mm]64[Ee][Cc]/ +bld/ +[Oo]bj/ +[Oo]ut/ +[Ll]og/ +[Ll]ogs/ + +# Build results on 'Bin' directories +**/[Bb]in/* +# Uncomment if you have tasks that rely on *.refresh files to move binaries +# (https://github.com/github/gitignore/pull/3736) +#!**/[Bb]in/*.refresh + +# Visual Studio 2015/2017 cache/options directory .vs/ -/Ink Canvas/obj +# Uncomment if you have tasks that create the project's static files in wwwroot +#wwwroot/ + +# Visual Studio 2017 auto generated files +Generated\ Files/ + +# MSTest test Results +[Tt]est[Rr]esult*/ +[Bb]uild[Ll]og.* +*.trx + +# NUnit +*.VisualState.xml +TestResult.xml +nunit-*.xml + +# Approval Tests result files +*.received.* + +# Build Results of an ATL Project +[Dd]ebugPS/ +[Rr]eleasePS/ +dlldata.c + +# Benchmark Results +BenchmarkDotNet.Artifacts/ + +# .NET Core +project.lock.json +project.fragment.lock.json +artifacts/ + +# ASP.NET Scaffolding +ScaffoldingReadMe.txt + +# StyleCop +StyleCopReport.xml + +# Files built by Visual Studio +*_i.c +*_p.c +*_h.h +*.ilk +*.meta +*.obj +*.idb +*.iobj +*.pch +*.pdb +*.ipdb +*.pgc +*.pgd +*.rsp +# but not Directory.Build.rsp, as it configures directory-level build defaults +!Directory.Build.rsp +*.sbr +*.tlb +*.tli +*.tlh +*.tmp +*.tmp_proj +*_wpftmp.csproj +*.log +*.tlog +*.vspscc +*.vssscc +.builds +*.pidb +*.svclog +*.scc + +# Chutzpah Test files +_Chutzpah* + +# Visual C++ cache files +ipch/ +*.aps +*.ncb +*.opendb +*.opensdf +*.sdf +*.cachefile +*.VC.db +*.VC.VC.opendb + +# Visual Studio profiler +*.psess +*.vsp +*.vspx +*.sap + +# Visual Studio Trace Files +*.e2e + +# TFS 2012 Local Workspace +$tf/ + +# Guidance Automation Toolkit +*.gpState + +# ReSharper is a .NET coding add-in +_ReSharper*/ +*.[Rr]e[Ss]harper +*.DotSettings.user + +# TeamCity is a build add-in +_TeamCity* + +# DotCover is a Code Coverage Tool +*.dotCover + +# AxoCover is a Code Coverage Tool +.axoCover/* +!.axoCover/settings.json + +# Coverlet is a free, cross platform Code Coverage Tool +coverage*.json +coverage*.xml +coverage*.info + +# Visual Studio code coverage results +*.coverage +*.coveragexml + +# NCrunch +_NCrunch_* +.NCrunch_* +.*crunch*.local.xml +nCrunchTemp_* + +# MightyMoose +*.mm.* +AutoTest.Net/ + +# Web workbench (sass) +.sass-cache/ + +# Installshield output folder +[Ee]xpress/ + +# DocProject is a documentation generator add-in +DocProject/buildhelp/ +DocProject/Help/*.HxT +DocProject/Help/*.HxC +DocProject/Help/*.hhc +DocProject/Help/*.hhk +DocProject/Help/*.hhp +DocProject/Help/Html2 +DocProject/Help/html + +# Click-Once directory +publish/ + +# Publish Web Output +*.[Pp]ublish.xml +*.azurePubxml +# Note: Comment the next line if you want to checkin your web deploy settings, +# but database connection strings (with potential passwords) will be unencrypted +*.pubxml +*.publishproj + +# Microsoft Azure Web App publish settings. Comment the next line if you want to +# checkin your Azure Web App publish settings, but sensitive information contained +# in these scripts will be unencrypted +PublishScripts/ + +# NuGet Packages +*.nupkg +# NuGet Symbol Packages +*.snupkg +# The packages folder can be ignored because of Package Restore +**/[Pp]ackages/* +# except build/, which is used as an MSBuild target. +!**/[Pp]ackages/build/ +# Uncomment if necessary however generally it will be regenerated when needed +#!**/[Pp]ackages/repositories.config +# NuGet v3's project.json files produces more ignorable files +*.nuget.props +*.nuget.targets + +# Microsoft Azure Build Output +csx/ +*.build.csdef + +# Microsoft Azure Emulator +ecf/ +rcf/ + +# Windows Store app package directories and files +AppPackages/ +BundleArtifacts/ +Package.StoreAssociation.xml +_pkginfo.txt +*.appx +*.appxbundle +*.appxupload + +# Visual Studio cache files +# files ending in .cache can be ignored +*.[Cc]ache +# but keep track of directories ending in .cache +!?*.[Cc]ache/ + +# Others +ClientBin/ +~$* +*~ +*.dbmdl +*.dbproj.schemaview +*.jfm +*.pfx +*.publishsettings +orleans.codegen.cs + +# Including strong name files can present a security risk +# (https://github.com/github/gitignore/pull/2483#issue-259490424) +#*.snk + +# Since there are multiple workflows, uncomment next line to ignore bower_components +# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) +#bower_components/ + +# RIA/Silverlight projects +Generated_Code/ + +# Backup & report files from converting an old project file +# to a newer Visual Studio version. Backup files are not needed, +# because we have git ;-) +_UpgradeReport_Files/ +Backup*/ +UpgradeLog*.XML +UpgradeLog*.htm +ServiceFabricBackup/ +*.rptproj.bak + +# SQL Server files +*.mdf +*.ldf +*.ndf + +# Business Intelligence projects +*.rdl.data +*.bim.layout +*.bim_*.settings +*.rptproj.rsuser +*- [Bb]ackup.rdl +*- [Bb]ackup ([0-9]).rdl +*- [Bb]ackup ([0-9][0-9]).rdl + +# Microsoft Fakes +FakesAssemblies/ + +# GhostDoc plugin setting file +*.GhostDoc.xml + +# Node.js Tools for Visual Studio +.ntvs_analysis.dat +node_modules/ + +# Visual Studio 6 build log +*.plg + +# Visual Studio 6 workspace options file +*.opt + +# Visual Studio 6 auto-generated workspace file (contains which files were open etc.) +*.vbw + +# Visual Studio 6 workspace and project file (working project files containing files to include in project) +*.dsw +*.dsp + +# Visual Studio 6 technical files +*.ncb +*.aps + +# Visual Studio LightSwitch build output +**/*.HTMLClient/GeneratedArtifacts +**/*.DesktopClient/GeneratedArtifacts +**/*.DesktopClient/ModelManifest.xml +**/*.Server/GeneratedArtifacts +**/*.Server/ModelManifest.xml +_Pvt_Extensions + +# Paket dependency manager +**/.paket/paket.exe +paket-files/ + +# FAKE - F# Make +**/.fake/ + +# CodeRush personal settings +**/.cr/personal + +# Python Tools for Visual Studio (PTVS) +**/__pycache__/ +*.pyc + +# Cake - Uncomment if you are using it +#tools/** +#!tools/packages.config + +# Tabs Studio +*.tss + +# Telerik's JustMock configuration file +*.jmconfig + +# BizTalk build output +*.btp.cs +*.btm.cs +*.odx.cs +*.xsd.cs + +# OpenCover UI analysis results +OpenCover/ + +# Azure Stream Analytics local run output +ASALocalRun/ + +# MSBuild Binary and Structured Log +*.binlog +MSBuild_Logs/ + +# AWS SAM Build and Temporary Artifacts folder +.aws-sam + +# NVidia Nsight GPU debugger configuration file +*.nvuser + +# MFractors (Xamarin productivity tool) working folder +**/.mfractor/ + +# Local History for Visual Studio +**/.localhistory/ + +# Visual Studio History (VSHistory) files +.vshistory/ + +# BeatPulse healthcheck temp database +healthchecksdb + +# Backup folder for Package Reference Convert tool in Visual Studio 2017 +MigrationBackup/ + +# Ionide (cross platform F# VS Code tools) working folder +**/.ionide/ + +# Fody - auto-generated XML schema +FodyWeavers.xsd + +# VS Code files for those working on multiple tools +.vscode/* +!.vscode/settings.json +!.vscode/tasks.json +!.vscode/launch.json +!.vscode/extensions.json +!.vscode/*.code-snippets + +# Local History for Visual Studio Code +.history/ + +# Built Visual Studio Code Extensions +*.vsix + +# Windows Installer files from build outputs +*.cab +*.msi +*.msix +*.msm +*.msp \ No newline at end of file diff --git a/AutomaticUpdateVersionControl.txt b/AutomaticUpdateVersionControl.txt index 5ad6231c..b1522148 100644 --- a/AutomaticUpdateVersionControl.txt +++ b/AutomaticUpdateVersionControl.txt @@ -1 +1 @@ -1.7.13.0 +1.7.14.0 diff --git a/Ink Canvas.sln.DotSettings.user b/Ink Canvas.sln.DotSettings.user deleted file mode 100644 index 21fc1f49..00000000 --- a/Ink Canvas.sln.DotSettings.user +++ /dev/null @@ -1,6 +0,0 @@ - - WARNING - C:\Program Files\Microsoft Visual Studio\2022\Community\MSBuild\Current\Bin\amd64\MSBuild.exe - 1114112 - True - True \ No newline at end of file diff --git a/Ink Canvas/App.xaml b/Ink Canvas/App.xaml index 83167e38..aba31b16 100644 --- a/Ink Canvas/App.xaml +++ b/Ink Canvas/App.xaml @@ -232,7 +232,7 @@ ContextMenu="{StaticResource SysTrayMenu}" IconSource="/Resources/icc.ico"/> - + diff --git a/Ink Canvas/App.xaml.cs b/Ink Canvas/App.xaml.cs index 1e04932d..973fd1ee 100644 --- a/Ink Canvas/App.xaml.cs +++ b/Ink Canvas/App.xaml.cs @@ -41,6 +41,8 @@ namespace Ink_Canvas private static Process watchdogProcess; // 新增:标记是否为软件内主动退出 public static bool IsAppExitByUser; + // 新增:标记是否启用了UIA置顶功能 + public static bool IsUIAccessTopMostEnabled; // 新增:退出信号文件路径 private static string watchdogExitSignalFile = Path.Combine(Path.GetTempPath(), "icc_watchdog_exit_" + Process.GetCurrentProcess().Id + ".flag"); // 新增:崩溃日志文件路径 @@ -629,7 +631,7 @@ namespace Ink_Canvas LogHelper.WriteLogToFile("App | 检测到最终应用启动(更新后的应用)"); } - // 在应用启动时自动释放IACore相关DLL + // 释放IACore相关DLL if (_isSplashScreenShown) { SetSplashMessage("正在初始化组件..."); @@ -645,6 +647,22 @@ namespace Ink_Canvas LogHelper.WriteLogToFile($"释放IACore DLL时出错: {ex.Message}", LogHelper.LogType.Error); } + // 释放UIAccess DLL + if (_isSplashScreenShown) + { + SetSplashMessage("正在初始化组件..."); + SetSplashProgress(50); + await Task.Delay(300); + } + try + { + UIAccessDllExtractor.ExtractUIAccessDlls(); + } + catch (Exception ex) + { + LogHelper.WriteLogToFile($"释放UIAccess DLL时出错: {ex.Message}", LogHelper.LogType.Error); + } + // 记录应用启动(设备标识符) if (_isSplashScreenShown) { @@ -1055,7 +1073,15 @@ namespace Ink_Canvas Thread.Sleep(2000); } // 主进程异常退出,自动重启前判断崩溃后操作 - SyncCrashActionFromSettings(); // 新增:同步设置 + SyncCrashActionFromSettings(); // 同步设置 + + if (IsUIAccessTopMostEnabled) + { + string exePath = Process.GetCurrentProcess().MainModule.FileName; + Process.Start(exePath); + Environment.Exit(0); + } + if (CrashAction == CrashActionType.SilentRestart) { StartupCount.Increment(); @@ -1068,7 +1094,6 @@ namespace Ink_Canvas string exePath = Process.GetCurrentProcess().MainModule.FileName; Process.Start(exePath); } - // CrashActionType.NoAction 时不重启,直接退出 } catch { } Environment.Exit(0); diff --git a/Ink Canvas/AssemblyInfo.cs b/Ink Canvas/AssemblyInfo.cs index 567d00bd..5d61c823 100644 --- a/Ink Canvas/AssemblyInfo.cs +++ b/Ink Canvas/AssemblyInfo.cs @@ -49,5 +49,5 @@ using System.Windows; // You can specify all the values or you can default the Build and Revision Numbers // by using the '*' as shown below: // [assembly: AssemblyVersion("1.0.*")] -[assembly: AssemblyVersion("1.7.13.0")] -[assembly: AssemblyFileVersion("1.7.13.0")] +[assembly: AssemblyVersion("1.7.14.0")] +[assembly: AssemblyFileVersion("1.7.14.0")] diff --git a/Ink Canvas/FodyWeavers.xsd b/Ink Canvas/FodyWeavers.xsd deleted file mode 100644 index f2dbece7..00000000 --- a/Ink Canvas/FodyWeavers.xsd +++ /dev/null @@ -1,176 +0,0 @@ - - - - - - - - - - - - A list of assembly names to exclude from the default action of "embed all Copy Local references", delimited with line breaks - - - - - A list of assembly names to include from the default action of "embed all Copy Local references", delimited with line breaks. - - - - - A list of runtime assembly names to exclude from the default action of "embed all Copy Local references", delimited with line breaks - - - - - A list of runtime assembly names to include from the default action of "embed all Copy Local references", delimited with line breaks. - - - - - Obsolete, use UnmanagedWinX86Assemblies instead - - - - - A list of unmanaged X86 (32 bit) assembly names to include, delimited with line breaks. - - - - - Obsolete, use UnmanagedWinX64Assemblies instead. - - - - - A list of unmanaged X64 (64 bit) assembly names to include, delimited with line breaks. - - - - - A list of unmanaged Arm64 (64 bit) assembly names to include, delimited with line breaks. - - - - - The order of preloaded assemblies, delimited with line breaks. - - - - - - This will copy embedded files to disk before loading them into memory. This is helpful for some scenarios that expected an assembly to be loaded from a physical file. - - - - - Controls if .pdbs for reference assemblies are also embedded. - - - - - Controls if runtime assemblies are also embedded. - - - - - Controls whether the runtime assemblies are embedded with their full path or only with their assembly name. - - - - - Embedded assemblies are compressed by default, and uncompressed when they are loaded. You can turn compression off with this option. - - - - - As part of Costura, embedded assemblies are no longer included as part of the build. This cleanup can be turned off. - - - - - The attach method no longer subscribes to the `AppDomain.AssemblyResolve` (.NET 4.x) and `AssemblyLoadContext.Resolving` (.NET 6.0+) events. - - - - - Costura by default will load as part of the module initialization. This flag disables that behavior. Make sure you call CosturaUtility.Initialize() somewhere in your code. - - - - - Costura will by default use assemblies with a name like 'resources.dll' as a satellite resource and prepend the output path. This flag disables that behavior. - - - - - A list of assembly names to exclude from the default action of "embed all Copy Local references", delimited with | - - - - - A list of assembly names to include from the default action of "embed all Copy Local references", delimited with |. - - - - - A list of runtime assembly names to exclude from the default action of "embed all Copy Local references", delimited with | - - - - - A list of runtime assembly names to include from the default action of "embed all Copy Local references", delimited with |. - - - - - Obsolete, use UnmanagedWinX86Assemblies instead - - - - - A list of unmanaged X86 (32 bit) assembly names to include, delimited with |. - - - - - Obsolete, use UnmanagedWinX64Assemblies instead - - - - - A list of unmanaged X64 (64 bit) assembly names to include, delimited with |. - - - - - A list of unmanaged Arm64 (64 bit) assembly names to include, delimited with |. - - - - - The order of preloaded assemblies, delimited with |. - - - - - - - - 'true' to run assembly verification (PEVerify) on the target assembly after all weavers have been executed. - - - - - A comma-separated list of error codes that can be safely ignored in assembly verification. - - - - - 'false' to turn off automatic generation of the XML Schema file. - - - - - \ No newline at end of file diff --git a/Ink Canvas/Helpers/Converters.cs b/Ink Canvas/Helpers/Converters.cs index ac5f7612..31feedb0 100644 --- a/Ink Canvas/Helpers/Converters.cs +++ b/Ink Canvas/Helpers/Converters.cs @@ -112,4 +112,27 @@ namespace Ink_Canvas.Converter } public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) { throw new NotImplementedException(); } } + + public class InverseBooleanToVisibilityConverter : IValueConverter + { + public object Convert(object value, Type targetType, object parameter, CultureInfo culture) + { + if ((bool)value) + { + return Visibility.Collapsed; + } + + return Visibility.Visible; + } + + public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) + { + if ((bool)value) + { + return Visibility.Collapsed; + } + + return Visibility.Visible; + } + } } diff --git a/Ink Canvas/FloatingWindowInterceptorManager.cs b/Ink Canvas/Helpers/FloatingWindowInterceptorManager.cs similarity index 100% rename from Ink Canvas/FloatingWindowInterceptorManager.cs rename to Ink Canvas/Helpers/FloatingWindowInterceptorManager.cs diff --git a/Ink Canvas/Helpers/GlobalHotkeyManager.cs b/Ink Canvas/Helpers/GlobalHotkeyManager.cs index 8df7924d..11fea5a5 100644 --- a/Ink Canvas/Helpers/GlobalHotkeyManager.cs +++ b/Ink Canvas/Helpers/GlobalHotkeyManager.cs @@ -865,7 +865,7 @@ namespace Ink_Canvas.Helpers { // 检查当前是否处于鼠标模式 bool isMouseMode = IsInSelectMode(); - + if (isMouseMode) { // 鼠标模式下,根据设置决定是否启用快捷键 @@ -874,7 +874,7 @@ namespace Ink_Canvas.Helpers else { // 非鼠标模式下,需要检查焦点和屏幕位置 - + // 策略1:鼠标在窗口上时启用热键(最高优先级) if (_isMouseOverWindow) { diff --git a/Ink Canvas/Helpers/PPTInkManager.cs b/Ink Canvas/Helpers/PPTInkManager.cs index cad06029..b450aae8 100644 --- a/Ink Canvas/Helpers/PPTInkManager.cs +++ b/Ink Canvas/Helpers/PPTInkManager.cs @@ -162,7 +162,7 @@ namespace Ink_Canvas.Helpers } /// - /// 强制保存指定页面的墨迹(忽略锁定状态) + /// 强制保存指定页面的墨迹 /// public void ForceSaveSlideStrokes(int slideIndex, StrokeCollection strokes) { diff --git a/Ink Canvas/Helpers/PPTManager.cs b/Ink Canvas/Helpers/PPTManager.cs index 11e1bbed..732409ad 100644 --- a/Ink Canvas/Helpers/PPTManager.cs +++ b/Ink Canvas/Helpers/PPTManager.cs @@ -678,14 +678,25 @@ namespace Ink_Canvas.Helpers { try { - if (!IsConnected || !IsInSlideShow || PPTApplication == null) return false; + if (!IsConnected || PPTApplication == null) return false; if (!Marshal.IsComObject(PPTApplication)) return false; - var slideShowWindow = PPTApplication.SlideShowWindows[1]; - if (slideShowWindow?.View != null) + if (IsInSlideShow && PPTApplication.SlideShowWindows.Count >= 1) { - slideShowWindow.View.GotoSlide(slideNumber); - return true; + var slideShowWindow = PPTApplication.SlideShowWindows[1]; + if (slideShowWindow?.View != null) + { + slideShowWindow.View.GotoSlide(slideNumber); + return true; + } + } + else if (CurrentPresentation != null) + { + if (CurrentPresentation.Windows?.Count >= 1) + { + CurrentPresentation.Windows[1].View.GotoSlide(slideNumber); + return true; + } } return false; } diff --git a/Ink Canvas/Helpers/UIAccessDllExtractor.cs b/Ink Canvas/Helpers/UIAccessDllExtractor.cs new file mode 100644 index 00000000..f9448167 --- /dev/null +++ b/Ink Canvas/Helpers/UIAccessDllExtractor.cs @@ -0,0 +1,165 @@ +using System; +using System.IO; +using System.Reflection; + +namespace Ink_Canvas.Helpers +{ + /// + /// UIAccess DLL释放器 + /// + public static class UIAccessDllExtractor + { + private static readonly string[] RequiredDlls = { + "UIAccessDLL_x64.dll", + "UIAccessDLL_x86.dll" + }; + + /// + /// 在应用启动时释放UIAccess相关DLL + /// + public static void ExtractUIAccessDlls() + { + try + { + string appDirectory = AppDomain.CurrentDomain.BaseDirectory; + LogHelper.WriteLogToFile("开始检查并释放UIAccess相关DLL文件"); + + foreach (string dllName in RequiredDlls) + { + string targetPath = Path.Combine(appDirectory, dllName); + + // 检查文件是否已存在且有效 + if (File.Exists(targetPath) && IsValidDll(targetPath)) + { + LogHelper.WriteLogToFile($"{dllName} 已存在且有效,跳过释放"); + continue; + } + + // 从嵌入资源中释放DLL + if (ExtractDllFromResource(dllName, targetPath)) + { + LogHelper.WriteLogToFile($"成功释放 {dllName} 到 {targetPath}"); + } + else + { + LogHelper.WriteLogToFile($"警告:无法释放 {dllName},可能影响UIA置顶功能", LogHelper.LogType.Warning); + } + } + + LogHelper.WriteLogToFile("UIAccess DLL释放检查完成"); + } + catch (Exception ex) + { + LogHelper.WriteLogToFile($"释放UIAccess DLL时出错: {ex.Message}", LogHelper.LogType.Error); + } + } + + /// + /// 从嵌入资源中提取DLL文件 + /// + private static bool ExtractDllFromResource(string dllName, string targetPath) + { + try + { + Assembly assembly = Assembly.GetExecutingAssembly(); + string resourceName = $"Ink_Canvas.{dllName}"; + + using (Stream resourceStream = assembly.GetManifestResourceStream(resourceName)) + { + if (resourceStream == null) + { + LogHelper.WriteLogToFile($"未找到嵌入资源: {resourceName}", LogHelper.LogType.Warning); + return false; + } + + // 确保目标目录存在 + string targetDirectory = Path.GetDirectoryName(targetPath); + if (!Directory.Exists(targetDirectory)) + { + Directory.CreateDirectory(targetDirectory); + } + + // 写入文件 + using (FileStream fileStream = new FileStream(targetPath, FileMode.Create, FileAccess.Write)) + { + resourceStream.CopyTo(fileStream); + } + + return true; + } + } + catch (Exception ex) + { + LogHelper.WriteLogToFile($"从资源提取 {dllName} 失败: {ex.Message}", LogHelper.LogType.Error); + return false; + } + } + + /// + /// 检查DLL文件是否有效 + /// + private static bool IsValidDll(string filePath) + { + try + { + if (!File.Exists(filePath)) + return false; + + FileInfo fileInfo = new FileInfo(filePath); + + // 检查文件大小(空文件或过小的文件可能无效) + if (fileInfo.Length < 1024) // 小于1KB可能无效 + return false; + + // 简单检查PE头(DLL文件应该以MZ开头) + using (FileStream fs = new FileStream(filePath, FileMode.Open, FileAccess.Read)) + { + byte[] buffer = new byte[2]; + if (fs.Read(buffer, 0, 2) == 2) + { + return buffer[0] == 0x4D && buffer[1] == 0x5A; // "MZ" + } + } + + return false; + } + catch + { + return false; + } + } + + /// + /// 清理释放的DLL文件(可选,在应用退出时调用) + /// + public static void CleanupExtractedDlls() + { + try + { + string appDirectory = AppDomain.CurrentDomain.BaseDirectory; + + foreach (string dllName in RequiredDlls) + { + string filePath = Path.Combine(appDirectory, dllName); + + if (File.Exists(filePath)) + { + try + { + File.Delete(filePath); + LogHelper.WriteLogToFile($"已清理 {dllName}"); + } + catch (Exception ex) + { + LogHelper.WriteLogToFile($"清理 {dllName} 失败: {ex.Message}", LogHelper.LogType.Warning); + } + } + } + } + catch (Exception ex) + { + LogHelper.WriteLogToFile($"清理UIAccess DLL时出错: {ex.Message}", LogHelper.LogType.Error); + } + } + } +} diff --git a/Ink Canvas/InkCanvasForClass.csproj b/Ink Canvas/InkCanvasForClass.csproj index 24d06117..1c25b961 100644 --- a/Ink Canvas/InkCanvasForClass.csproj +++ b/Ink Canvas/InkCanvasForClass.csproj @@ -192,11 +192,14 @@ + + + @@ -243,6 +246,10 @@ + + + + diff --git a/Ink Canvas/InkCanvasForClass.csproj.user b/Ink Canvas/InkCanvasForClass.csproj.user deleted file mode 100644 index 92ed6d11..00000000 --- a/Ink Canvas/InkCanvasForClass.csproj.user +++ /dev/null @@ -1,6 +0,0 @@ - - - - <_LastSelectedProfileId>D:\vs\ica\Ink Canvas\Properties\PublishProfiles\FolderProfile.pubxml - - \ No newline at end of file diff --git a/Ink Canvas/MainWindow.xaml b/Ink Canvas/MainWindow.xaml index d7958e35..98aba723 100644 --- a/Ink Canvas/MainWindow.xaml +++ b/Ink Canvas/MainWindow.xaml @@ -40,6 +40,7 @@ + @@ -609,6 +610,14 @@ IsOn="True" FontFamily="Microsoft YaHei UI" FontWeight="Bold" Toggled="ToggleSwitchAlwaysOnTop_Toggled" /> + + + + + + + + + + diff --git a/Ink Canvas/Windows/MinimizedTimerWindow.xaml.cs b/Ink Canvas/Windows/MinimizedTimerWindow.xaml.cs new file mode 100644 index 00000000..b8004d2b --- /dev/null +++ b/Ink Canvas/Windows/MinimizedTimerWindow.xaml.cs @@ -0,0 +1,392 @@ +using System; +using System.Windows; +using System.Windows.Controls; +using System.Windows.Input; +using System.Windows.Media; +using System.Windows.Shapes; +using System.Windows.Media.Animation; + +namespace Ink_Canvas +{ + /// + /// 最小化计时器窗口 + /// + public partial class MinimizedTimerWindow : Window + { + private NewStyleTimerWindow parentWindow; + private System.Timers.Timer updateTimer; + private bool isMouseOver = false; + private bool isDragging = false; + private Point lastMousePosition; + + public MinimizedTimerWindow(NewStyleTimerWindow parent) + { + InitializeComponent(); + parentWindow = parent; + + // 设置窗口位置 + this.Left = parent.Left; + this.Top = parent.Top; + + // 启动更新定时器 + updateTimer = new System.Timers.Timer(100); // 100ms更新一次 + updateTimer.Elapsed += UpdateTimer_Elapsed; + updateTimer.Start(); + + parentWindow.TimerCompleted += ParentWindow_TimerCompleted; + + // 应用主题 + ApplyTheme(); + } + + private void UpdateTimer_Elapsed(object sender, System.Timers.ElapsedEventArgs e) + { + if (parentWindow != null) + { + Application.Current.Dispatcher.Invoke(() => + { + if (ShouldCloseWindow()) + { + this.Close(); + return; + } + + UpdateTimeDisplay(); + }); + } + } + + private bool ShouldCloseWindow() + { + if (parentWindow == null) return true; + + if (MainWindow.Settings.RandSettings?.EnableOvertimeCountUp == true) + { + if (parentWindow.IsTimerRunning) + { + return false; + } + + var remainingTime = parentWindow.GetRemainingTime(); + if (remainingTime.HasValue && remainingTime.Value.TotalSeconds < 0) + { + return false; + } + + return true; + } + else + { + return !parentWindow.IsTimerRunning; + } + } + + private void UpdateTimeDisplay() + { + if (parentWindow == null) return; + + // 获取剩余时间 + var remainingTime = parentWindow.GetRemainingTime(); + if (remainingTime.HasValue) + { + var timeSpan = remainingTime.Value; + bool isOvertimeMode = timeSpan.TotalSeconds < 0; + bool shouldShowRed = isOvertimeMode && MainWindow.Settings.RandSettings?.EnableOvertimeRedText == true; + + int hours, minutes, seconds; + + if (isOvertimeMode) + { + var totalTimeSpan = parentWindow.GetTotalTimeSpan(); + if (totalTimeSpan.HasValue) + { + var elapsedTime = parentWindow.GetElapsedTime(); + if (elapsedTime.HasValue) + { + var overtimeSpan = elapsedTime.Value - totalTimeSpan.Value; + hours = (int)overtimeSpan.TotalHours; + minutes = overtimeSpan.Minutes; + seconds = overtimeSpan.Seconds; + } + else + { + hours = 0; + minutes = 0; + seconds = 0; + } + } + else + { + hours = 0; + minutes = 0; + seconds = 0; + } + } + else + { + hours = (int)timeSpan.TotalHours; + minutes = timeSpan.Minutes; + seconds = timeSpan.Seconds; + } + + // 更新小时显示 + SetDigitDisplay("MinHour1Display", hours / 10, shouldShowRed); + SetDigitDisplay("MinHour2Display", hours % 10, shouldShowRed); + + // 更新分钟显示 + SetDigitDisplay("MinMinute1Display", minutes / 10, shouldShowRed); + SetDigitDisplay("MinMinute2Display", minutes % 10, shouldShowRed); + + // 更新秒显示 + SetDigitDisplay("MinSecond1Display", seconds / 10, shouldShowRed); + SetDigitDisplay("MinSecond2Display", seconds % 10, shouldShowRed); + + SetColonDisplay(shouldShowRed); + } + } + + private void ParentWindow_TimerCompleted(object sender, EventArgs e) + { + Application.Current.Dispatcher.Invoke(() => + { + this.Close(); + }); + } + + private void SetDigitDisplay(string pathName, int digit, bool isRed = false) + { + var path = this.FindName(pathName) as Path; + if (path != null) + { + string resourceKey = $"Digit{digit}"; + var geometry = this.FindResource(resourceKey) as Geometry; + if (geometry != null) + { + path.Data = geometry; + } + + // 设置颜色 + if (isRed) + { + path.Fill = Brushes.Red; + } + else + { + var defaultBrush = this.FindResource("NewTimerWindowDigitForeground") as Brush; + if (defaultBrush != null) + { + path.Fill = defaultBrush; + } + else + { + bool isLightTheme = IsLightTheme(); + path.Fill = isLightTheme ? Brushes.Black : Brushes.White; + } + } + } + } + + /// + /// 设置最小化窗口冒号显示颜色 + /// + /// 是否显示为红色 + private void SetColonDisplay(bool isRed = false) + { + var colon1 = this.FindName("MinColon1Display") as TextBlock; + var colon2 = this.FindName("MinColon2Display") as TextBlock; + + if (colon1 != null) + { + if (isRed) + { + colon1.Foreground = Brushes.Red; + } + else + { + var defaultBrush = this.FindResource("NewTimerWindowDigitForeground") as Brush; + if (defaultBrush != null) + { + colon1.Foreground = defaultBrush; + } + else + { + bool isLightTheme = IsLightTheme(); + colon1.Foreground = isLightTheme ? Brushes.Black : Brushes.White; + } + } + } + + if (colon2 != null) + { + if (isRed) + { + colon2.Foreground = Brushes.Red; + } + else + { + var defaultBrush = this.FindResource("NewTimerWindowDigitForeground") as Brush; + if (defaultBrush != null) + { + colon2.Foreground = defaultBrush; + } + else + { + bool isLightTheme = IsLightTheme(); + colon2.Foreground = isLightTheme ? Brushes.Black : Brushes.White; + } + } + } + } + + private void ApplyTheme() + { + try + { + + bool isLightTheme = IsLightTheme(); + if (!isLightTheme) + { + SetDarkThemeBorder(); + } + } + catch (Exception ex) + { + System.Diagnostics.Debug.WriteLine($"应用主题时出错: {ex.Message}"); + } + } + + private bool IsLightTheme() + { + try + { + var mainWindow = Application.Current.MainWindow as MainWindow; + if (mainWindow != null) + { + var currentModeField = mainWindow.GetType().GetField("currentMode", + System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance); + if (currentModeField != null) + { + var currentMode = currentModeField.GetValue(mainWindow); + return currentMode?.ToString() == "Light"; + } + } + } + catch + { + // 如果获取主题失败,默认使用浅色主题 + } + return true; + } + + // 设置深色主题下的灰色边框 + private void SetDarkThemeBorder() + { + try + { + // 找到Border元素并设置灰色边框 + var border = this.FindName("MainBorder") as Border; + if (border != null) + { + border.BorderBrush = new SolidColorBrush(Color.FromRgb(64, 64, 64)); + } + } + catch + { + } + } + + private void Window_MouseLeftButtonDown(object sender, MouseButtonEventArgs e) + { + // 记录点击时间 + lastClickTime = DateTime.Now; + // 开始拖动 + isDragging = true; + lastMousePosition = e.GetPosition(this); + this.CaptureMouse(); + } + + private void Window_MouseMove(object sender, MouseEventArgs e) + { + if (isDragging) + { + var currentPosition = e.GetPosition(this); + var deltaX = currentPosition.X - lastMousePosition.X; + var deltaY = currentPosition.Y - lastMousePosition.Y; + + this.Left += deltaX; + this.Top += deltaY; + } + } + + private void Window_MouseLeftButtonUp(object sender, MouseButtonEventArgs e) + { + if (isDragging) + { + isDragging = false; + this.ReleaseMouseCapture(); + + // 如果点击时间很短,认为是单击,恢复主窗口 + var clickDuration = DateTime.Now - lastClickTime; + if (clickDuration.TotalMilliseconds < 200) // 200ms内认为是单击 + { + // 恢复主窗口 + if (parentWindow != null) + { + parentWindow.Show(); + parentWindow.Activate(); + parentWindow.WindowState = WindowState.Normal; + this.Close(); + } + } + } + } + + private DateTime lastClickTime = DateTime.Now; + + private void Window_MouseEnter(object sender, MouseEventArgs e) + { + isMouseOver = true; + // 鼠标进入时显示关闭按钮 + if (CloseButton != null) + { + CloseButton.Opacity = 1.0; + } + } + + private void Window_MouseLeave(object sender, MouseEventArgs e) + { + isMouseOver = false; + // 鼠标离开时隐藏关闭按钮 + if (CloseButton != null) + { + CloseButton.Opacity = 0.7; + } + } + + private void CloseButton_Click(object sender, RoutedEventArgs e) + { + // 停止计时器并关闭窗口 + if (parentWindow != null) + { + parentWindow.StopTimer(); + } + this.Close(); + } + + protected override void OnClosed(EventArgs e) + { + if (parentWindow != null) + { + parentWindow.TimerCompleted -= ParentWindow_TimerCompleted; + } + + // 清理资源 + if (updateTimer != null) + { + updateTimer.Stop(); + updateTimer.Dispose(); + } + base.OnClosed(e); + } + } +} diff --git a/Ink Canvas/Windows/NewStyleTimerWindow.cs b/Ink Canvas/Windows/NewStyleTimerWindow.cs new file mode 100644 index 00000000..8a8eecae --- /dev/null +++ b/Ink Canvas/Windows/NewStyleTimerWindow.cs @@ -0,0 +1,1357 @@ +using Ink_Canvas.Helpers; +using System; +using System.IO; +using System.Media; +using System.Timers; +using System.Windows; +using System.Windows.Controls; +using System.Windows.Input; +using System.Windows.Media; +using System.Windows.Shapes; +using Newtonsoft.Json; + +namespace Ink_Canvas +{ + /// + /// 最近计时记录数据模型 + /// + public class RecentTimersData + { + public string RecentTimer1 { get; set; } = "--:--"; + public string RecentTimer2 { get; set; } = "--:--"; + public string RecentTimer3 { get; set; } = "--:--"; + public string RecentTimer4 { get; set; } = "--:--"; + public string RecentTimer5 { get; set; } = "--:--"; + public string RecentTimer6 { get; set; } = "--:--"; + } + + /// + /// 新计时器UI风格的倒计时器窗口 + /// + public partial class NewStyleTimerWindow : Window + { + public NewStyleTimerWindow() + { + InitializeComponent(); + AnimationsHelper.ShowWithSlideFromBottomAndFade(this, 0.25); + + timer.Elapsed += Timer_Elapsed; + timer.Interval = 50; + InitializeUI(); + + // 应用主题 + ApplyTheme(); + + // 初始化隐藏定时器 + hideTimer = new Timer(1000); // 每秒检查一次 + hideTimer.Elapsed += HideTimer_Elapsed; + lastActivityTime = DateTime.Now; + } + + + private void Timer_Elapsed(object sender, ElapsedEventArgs e) + { + if (!isTimerRunning || isPaused) + { + timer.Stop(); + return; + } + + TimeSpan timeSpan = DateTime.Now - startTime; + TimeSpan totalTimeSpan = new TimeSpan(hour, minute, second); + double spentTimePercent = timeSpan.TotalMilliseconds / (totalTimeSpan.TotalMilliseconds); + + Application.Current.Dispatcher.Invoke(() => + { + if (!isOvertimeMode) + { + TimeSpan leftTimeSpan = totalTimeSpan - timeSpan; + if (leftTimeSpan.Milliseconds > 0) leftTimeSpan += new TimeSpan(0, 0, 1); + + int totalHours = (int)leftTimeSpan.TotalHours; + int displayHours = totalHours; + + if (displayHours > 99) displayHours = 99; + + SetDigitDisplay("Digit1Display", displayHours / 10); + SetDigitDisplay("Digit2Display", displayHours % 10); + SetDigitDisplay("Digit3Display", leftTimeSpan.Minutes / 10); + SetDigitDisplay("Digit4Display", leftTimeSpan.Minutes % 10); + SetDigitDisplay("Digit5Display", leftTimeSpan.Seconds / 10); + SetDigitDisplay("Digit6Display", leftTimeSpan.Seconds % 10); + + SetColonDisplay(false); + + if (leftTimeSpan.TotalSeconds <= 6 && leftTimeSpan.TotalSeconds > 0 && + MainWindow.Settings.RandSettings?.EnableProgressiveReminder == true && + !hasPlayedProgressiveReminder) + { + PlayProgressiveReminderSound(); + hasPlayedProgressiveReminder = true; + } + + if (leftTimeSpan.TotalSeconds <= 0 && MainWindow.Settings.RandSettings?.EnableOvertimeCountUp == true) + { + isOvertimeMode = true; + PlayTimerSound(); + } + else if (leftTimeSpan.TotalSeconds <= 0) + { + SetDigitDisplay("Digit1Display", 0); + SetDigitDisplay("Digit2Display", 0); + SetDigitDisplay("Digit3Display", 0); + SetDigitDisplay("Digit4Display", 0); + SetDigitDisplay("Digit5Display", 0); + SetDigitDisplay("Digit6Display", 0); + + SetColonDisplay(false); + timer.Stop(); + isTimerRunning = false; + StartPauseIcon.Data = Geometry.Parse(PlayIconData); + PlayTimerSound(); + + TimerCompleted?.Invoke(this, EventArgs.Empty); + HandleTimerCompletion(); + } + } + else + { + TimeSpan overtimeSpan = timeSpan - totalTimeSpan; + int totalHours = (int)overtimeSpan.TotalHours; + int displayHours = totalHours; + + if (displayHours > 99) displayHours = 99; + + bool shouldShowRed = MainWindow.Settings.RandSettings?.EnableOvertimeRedText == true; + + SetDigitDisplay("Digit1Display", displayHours / 10, shouldShowRed); + SetDigitDisplay("Digit2Display", displayHours % 10, shouldShowRed); + SetDigitDisplay("Digit3Display", overtimeSpan.Minutes / 10, shouldShowRed); + SetDigitDisplay("Digit4Display", overtimeSpan.Minutes % 10, shouldShowRed); + SetDigitDisplay("Digit5Display", overtimeSpan.Seconds / 10, shouldShowRed); + SetDigitDisplay("Digit6Display", overtimeSpan.Seconds % 10, shouldShowRed); + + SetColonDisplay(shouldShowRed); + } + }); + } + + SoundPlayer player = new SoundPlayer(); + MediaPlayer mediaPlayer = new MediaPlayer(); + + int hour = 0; + int minute = 5; + int second = 0; + + DateTime startTime = DateTime.Now; + DateTime pauseTime = DateTime.Now; + + bool isTimerRunning = false; + bool isPaused = false; + bool isOvertimeMode = false; + TimeSpan remainingTime = TimeSpan.Zero; + bool hasPlayedProgressiveReminder = false; + + Timer timer = new Timer(); + private Timer hideTimer; + private MinimizedTimerWindow minimizedWindow; + private DateTime lastActivityTime; + private bool isFullscreenMode = false; + private FullscreenTimerWindow fullscreenWindow; + public event EventHandler TimerCompleted; + public TimeSpan? GetTotalTimeSpan() + { + return new TimeSpan(hour, minute, second); + } + + public TimeSpan? GetElapsedTime() + { + if (isPaused) return null; + + return DateTime.Now - startTime; + } + + // 最近计时记录 + private string recentTimer1 = "--:--"; + private string recentTimer2 = "--:--"; + private string recentTimer3 = "--:--"; + private string recentTimer4 = "--:--"; + private string recentTimer5 = "--:--"; + private string recentTimer6 = "--:--"; + + // JSON文件路径 + private static readonly string ConfigsFolder = System.IO.Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Configs"); + private static readonly string RecentTimersJsonPath = System.IO.Path.Combine(ConfigsFolder, "RecentTimers.json"); + + private void InitializeUI() + { + UpdateDigitDisplays(); + LoadRecentTimers(); + UpdateRecentTimerDisplays(); + InitializeTabState(); + } + + private void InitializeTabState() + { + // 设置默认选中CommonTab + CommonTimersGrid.Visibility = Visibility.Visible; + RecentTimersGrid.Visibility = Visibility.Collapsed; + + // 设置tab文字颜色和样式 + var commonText = this.FindName("CommonTabText") as TextBlock; + var recentText = this.FindName("RecentTabText") as TextBlock; + if (commonText != null) + { + commonText.FontWeight = FontWeights.Bold; + commonText.Opacity = 1.0; + commonText.Foreground = new SolidColorBrush(Colors.White); + } + if (recentText != null) + { + recentText.FontWeight = FontWeights.Normal; + recentText.Opacity = 0.8; + recentText.Foreground = new SolidColorBrush(Color.FromRgb(102, 102, 102)); + } + + // 设置指示器位置 + var indicator = this.FindName("SegmentedIndicator") as Border; + if (indicator != null) + { + indicator.CornerRadius = new CornerRadius(7.5, 0, 0, 7.5); + indicator.Margin = new Thickness(0, 0, 0, 0); + } + } + + private void ApplyTheme() + { + try + { + // 应用主题设置 + if (MainWindow.Settings != null) + { + ApplyTheme(MainWindow.Settings); + } + } + catch (Exception ex) + { + LogHelper.WriteLogToFile($"应用新计时器UI倒计时窗口主题出错: {ex.Message}", LogHelper.LogType.Error); + } + } + + private void ApplyTheme(Settings settings) + { + try + { + if (settings.Appearance.Theme == 0) // 浅色主题 + { + iNKORE.UI.WPF.Modern.ThemeManager.SetRequestedTheme(this, iNKORE.UI.WPF.Modern.ElementTheme.Light); + } + else if (settings.Appearance.Theme == 1) // 深色主题 + { + iNKORE.UI.WPF.Modern.ThemeManager.SetRequestedTheme(this, iNKORE.UI.WPF.Modern.ElementTheme.Dark); + SetDarkThemeBorder(); + } + else // 跟随系统主题 + { + bool isSystemLight = IsSystemThemeLight(); + if (isSystemLight) + { + iNKORE.UI.WPF.Modern.ThemeManager.SetRequestedTheme(this, iNKORE.UI.WPF.Modern.ElementTheme.Light); + } + else + { + iNKORE.UI.WPF.Modern.ThemeManager.SetRequestedTheme(this, iNKORE.UI.WPF.Modern.ElementTheme.Dark); + SetDarkThemeBorder(); + } + } + } + catch (Exception ex) + { + LogHelper.WriteLogToFile($"应用新计时器UI倒计时窗口主题出错: {ex.Message}", LogHelper.LogType.Error); + } + } + + private bool IsSystemThemeLight() + { + var light = false; + try + { + var registryKey = Microsoft.Win32.Registry.CurrentUser; + var themeKey = registryKey.OpenSubKey(@"Software\Microsoft\Windows\CurrentVersion\Themes\Personalize"); + if (themeKey != null) + { + var value = themeKey.GetValue("AppsUseLightTheme"); + if (value != null) + { + light = (int)value == 1; + } + themeKey.Close(); + } + } + catch + { + // 如果读取注册表失败,默认为浅色主题 + light = true; + } + return light; + } + + private void UpdateDigitDisplays() + { + SetDigitDisplay("Digit1Display", hour / 10); + SetDigitDisplay("Digit2Display", hour % 10); + SetDigitDisplay("Digit3Display", minute / 10); + SetDigitDisplay("Digit4Display", minute % 10); + SetDigitDisplay("Digit5Display", second / 10); + SetDigitDisplay("Digit6Display", second % 10); + + SetColonDisplay(false); + } + + private void HideTimer_Elapsed(object sender, ElapsedEventArgs e) + { + Application.Current.Dispatcher.Invoke(() => + { + // 只有在计时器运行时且不在全屏模式下才检查自动隐藏 + if (isTimerRunning && !isPaused && !isFullscreenMode) + { + var timeSinceLastActivity = DateTime.Now - lastActivityTime; + if (timeSinceLastActivity.TotalSeconds >= 5) + { + ShowMinimizedWindow(); + } + } + }); + } + + private void ShowMinimizedWindow() + { + if (minimizedWindow == null || !minimizedWindow.IsVisible) + { + minimizedWindow = new MinimizedTimerWindow(this); + minimizedWindow.Show(); + + // 隐藏主窗口 + this.Hide(); + } + } + + public void UpdateActivityTime() + { + lastActivityTime = DateTime.Now; + } + + public void SetFullscreenMode(bool isFullscreen) + { + isFullscreenMode = isFullscreen; + } + + // 更新剩余时间 + private void UpdateRemainingTime() + { + if (isTimerRunning && !isPaused) + { + // 获取当前剩余时间 + TimeSpan? currentRemaining = GetRemainingTime(); + if (currentRemaining.HasValue) + { + // 计算已经过去的时间 + TimeSpan elapsedTime = DateTime.Now - startTime; + + // 计算新的总时间 + TimeSpan newTotalTime = new TimeSpan(hour, minute, second); + + // 如果新设置的时间小于已经过去的时间,则设置为0 + if (newTotalTime <= elapsedTime) + { + remainingTime = TimeSpan.Zero; + } + else + { + // 否则,剩余时间 = 新总时间 - 已经过去的时间 + remainingTime = newTotalTime - elapsedTime; + } + } + else + { + // 如果没有剩余时间信息,直接设置新的剩余时间 + remainingTime = new TimeSpan(hour, minute, second); + } + } + } + + // 更新特定时间单位的剩余时间 + private void UpdateSpecificTimeUnit(int newHour, int newMinute, int newSecond) + { + if (isTimerRunning && !isPaused) + { + // 获取当前剩余时间 + TimeSpan? currentRemaining = GetRemainingTime(); + if (currentRemaining.HasValue) + { + // 计算已经过去的时间 + TimeSpan elapsedTime = DateTime.Now - startTime; + + // 计算新的总时间 + TimeSpan newTotalTime = new TimeSpan(newHour, newMinute, newSecond); + + // 如果新设置的时间小于已经过去的时间,则设置为0 + if (newTotalTime <= elapsedTime) + { + remainingTime = TimeSpan.Zero; + } + else + { + // 否则,剩余时间 = 新总时间 - 已经过去的时间 + remainingTime = newTotalTime - elapsedTime; + } + } + else + { + // 如果没有剩余时间信息,直接设置新的剩余时间 + remainingTime = new TimeSpan(newHour, newMinute, newSecond); + } + } + } + + public bool IsTimerRunning => isTimerRunning; + + public TimeSpan? GetRemainingTime() + { + if (isPaused) return null; + + var elapsed = DateTime.Now - startTime; + var totalSeconds = hour * 3600 + minute * 60 + second; + var remaining = totalSeconds - elapsed.TotalSeconds; + + return TimeSpan.FromSeconds(remaining); + } + + public void StopTimer() + { + timer.Stop(); + isTimerRunning = false; + StartPauseIcon.Data = Geometry.Parse(PlayIconData); + } + + private void Window_MouseMove(object sender, MouseEventArgs e) + { + UpdateActivityTime(); + } + + private void Window_MouseEnter(object sender, MouseEventArgs e) + { + UpdateActivityTime(); + } + + /// + /// 根据数字值设置SVG数字显示 + /// + /// Path控件的名称 + /// 要显示的数字(0-9) + /// 是否显示为红色 + private void SetDigitDisplay(string pathName, int digit, bool isRed = false) + { + var path = this.FindName(pathName) as System.Windows.Shapes.Path; + if (path != null) + { + string resourceKey = $"Digit{digit}"; + var geometry = this.FindResource(resourceKey) as Geometry; + if (geometry != null) + { + path.Data = geometry; + } + + if (isRed) + { + path.Fill = Brushes.Red; + } + else + { + var defaultBrush = this.FindResource("NewTimerWindowDigitForeground") as Brush; + if (defaultBrush != null) + { + path.Fill = defaultBrush; + } + else + { + path.Fill = Brushes.White; + } + } + } + } + + /// + /// 设置冒号显示颜色 + /// + /// 是否显示为红色 + private void SetColonDisplay(bool isRed = false) + { + var colon1 = this.FindName("Colon1Display") as TextBlock; + var colon2 = this.FindName("Colon2Display") as TextBlock; + + if (colon1 != null) + { + if (isRed) + { + colon1.Foreground = Brushes.Red; + } + else + { + var defaultBrush = this.FindResource("NewTimerWindowDigitForeground") as Brush; + if (defaultBrush != null) + { + colon1.Foreground = defaultBrush; + } + else + { + colon1.Foreground = Brushes.White; + } + } + } + + if (colon2 != null) + { + if (isRed) + { + colon2.Foreground = Brushes.Red; + } + else + { + var defaultBrush = this.FindResource("NewTimerWindowDigitForeground") as Brush; + if (defaultBrush != null) + { + colon2.Foreground = defaultBrush; + } + else + { + colon2.Foreground = Brushes.White; + } + } + } + } + + // 第1位数字(小时十位) + private void Digit1Plus_Click(object sender, RoutedEventArgs e) + { + if (isTimerRunning) return; + int currentHour = hour; + int hourTens = currentHour / 10; + int hourOnes = currentHour % 10; + + hourTens++; + if (hourTens >= 10) hourTens = 0; + + hour = hourTens * 10 + hourOnes; + UpdateDigitDisplays(); + } + + private void Digit1Minus_Click(object sender, RoutedEventArgs e) + { + if (isTimerRunning) return; + int currentHour = hour; + int hourTens = currentHour / 10; + int hourOnes = currentHour % 10; + + hourTens--; + if (hourTens < 0) hourTens = 9; + + hour = hourTens * 10 + hourOnes; + UpdateDigitDisplays(); + } + + // 第2位数字(小时个位) + private void Digit2Plus_Click(object sender, RoutedEventArgs e) + { + if (isTimerRunning) return; + int currentHour = hour; + int hourTens = currentHour / 10; + int hourOnes = currentHour % 10; + + hourOnes++; + if (hourOnes >= 10) + { + hourOnes = 0; + hourTens++; + if (hourTens >= 10) hourTens = 0; + } + + hour = hourTens * 10 + hourOnes; + UpdateDigitDisplays(); + } + + private void Digit2Minus_Click(object sender, RoutedEventArgs e) + { + if (isTimerRunning) return; + int currentHour = hour; + int hourTens = currentHour / 10; + int hourOnes = currentHour % 10; + + hourOnes--; + if (hourOnes < 0) + { + hourOnes = 9; + hourTens--; + if (hourTens < 0) hourTens = 9; + } + + hour = hourTens * 10 + hourOnes; + UpdateDigitDisplays(); + } + + // 第3位数字(分钟十位) + private void Digit3Plus_Click(object sender, RoutedEventArgs e) + { + if (isTimerRunning) return; + int currentMinute = minute; + int minuteTens = currentMinute / 10; + int minuteOnes = currentMinute % 10; + + minuteTens++; + if (minuteTens >= 6) minuteTens = 0; + + minute = minuteTens * 10 + minuteOnes; + UpdateDigitDisplays(); + } + + private void Digit3Minus_Click(object sender, RoutedEventArgs e) + { + if (isTimerRunning) return; + int currentMinute = minute; + int minuteTens = currentMinute / 10; + int minuteOnes = currentMinute % 10; + + minuteTens--; + if (minuteTens < 0) minuteTens = 5; + + minute = minuteTens * 10 + minuteOnes; + UpdateDigitDisplays(); + } + + // 第4位数字(分钟个位) + private void Digit4Plus_Click(object sender, RoutedEventArgs e) + { + if (isTimerRunning) return; + int currentMinute = minute; + int minuteTens = currentMinute / 10; + int minuteOnes = currentMinute % 10; + + minuteOnes++; + if (minuteOnes >= 10) + { + minuteOnes = 0; + minuteTens++; + if (minuteTens >= 6) minuteTens = 0; + } + + minute = minuteTens * 10 + minuteOnes; + UpdateDigitDisplays(); + } + + private void Digit4Minus_Click(object sender, RoutedEventArgs e) + { + if (isTimerRunning) return; + int currentMinute = minute; + int minuteTens = currentMinute / 10; + int minuteOnes = currentMinute % 10; + + minuteOnes--; + if (minuteOnes < 0) + { + minuteOnes = 9; + minuteTens--; + if (minuteTens < 0) minuteTens = 5; + } + + minute = minuteTens * 10 + minuteOnes; + UpdateDigitDisplays(); + } + + // 第5位数字(秒十位) + private void Digit5Plus_Click(object sender, RoutedEventArgs e) + { + if (isTimerRunning) return; + int currentSecond = second; + int secondTens = currentSecond / 10; + int secondOnes = currentSecond % 10; + + secondTens++; + if (secondTens >= 6) secondTens = 0; + + second = secondTens * 10 + secondOnes; + UpdateDigitDisplays(); + } + + private void Digit5Minus_Click(object sender, RoutedEventArgs e) + { + if (isTimerRunning) return; + int currentSecond = second; + int secondTens = currentSecond / 10; + int secondOnes = currentSecond % 10; + + secondTens--; + if (secondTens < 0) secondTens = 5; + + second = secondTens * 10 + secondOnes; + UpdateDigitDisplays(); + } + + // 第6位数字(秒个位) + private void Digit6Plus_Click(object sender, RoutedEventArgs e) + { + if (isTimerRunning) return; + int currentSecond = second; + int secondTens = currentSecond / 10; + int secondOnes = currentSecond % 10; + + secondOnes++; + if (secondOnes >= 10) + { + secondOnes = 0; + secondTens++; + if (secondTens >= 6) secondTens = 0; + } + + second = secondTens * 10 + secondOnes; + UpdateDigitDisplays(); + } + + private void Digit6Minus_Click(object sender, RoutedEventArgs e) + { + if (isTimerRunning) return; + int currentSecond = second; + int secondTens = currentSecond / 10; + int secondOnes = currentSecond % 10; + + secondOnes--; + if (secondOnes < 0) + { + secondOnes = 9; + secondTens--; + if (secondTens < 0) secondTens = 5; + } + + second = secondTens * 10 + secondOnes; + UpdateDigitDisplays(); + } + + // 图标数据常量 + private const string PlayIconData = "M6.5 4.00004V20C6.49995 20.178 6.54737 20.3527 6.63738 20.5062C6.72739 20.6597 6.85672 20.7864 7.01202 20.8732C7.16733 20.96 7.34299 21.0038 7.52088 21.0001C7.69878 20.9964 7.87245 20.9453 8.024 20.852L21.024 12.852C21.1696 12.7626 21.2898 12.6373 21.3733 12.4881C21.4567 12.339 21.5005 12.1709 21.5005 12C21.5005 11.8291 21.4567 11.6611 21.3733 11.512C21.2898 11.3628 21.1696 11.2375 21.024 11.148L8.024 3.14804C7.87245 3.0548 7.69878 3.00369 7.52088 2.99997C7.34299 2.99626 7.16733 3.04007 7.01202 3.1269C6.85672 3.21372 6.72739 3.34042 6.63738 3.4939C6.54737 3.64739 6.49995 3.82211 6.5 4.00004Z"; + private const string PauseIconData = "M9.5 4H7.5C6.96957 4 6.46086 4.21071 6.08579 4.58579C5.71071 4.96086 5.5 5.46957 5.5 6V18C5.5 18.5304 5.71071 19.0391 6.08579 19.4142C6.46086 19.7893 6.96957 20 7.5 20H9.5C10.0304 20 10.5391 19.7893 10.9142 19.4142C11.2893 19.0391 11.5 18.5304 11.5 18V6C11.5 5.46957 11.2893 4.96086 10.9142 4.58579C10.5391 4.21071 10.0304 4 9.5 4Z M17.5 4H15.5C14.9696 4 14.4609 4.21071 14.0858 4.58579C13.7107 4.96086 13.5 5.46957 13.5 6V18C13.5 18.5304 13.7107 19.0391 14.0858 19.4142C14.4609 19.7893 14.9696 20 15.5 20H17.5C18.0304 20 18.5391 19.7893 18.9142 19.4142C19.2893 19.0391 19.5 18.5304 19.5 18V6C19.5 5.46957 19.2893 4.96086 18.9142 4.58579C18.5391 4.21071 18.0304 4 17.5 4Z"; + + private void StartPause_Click(object sender, RoutedEventArgs e) + { + if (isPaused && isTimerRunning) + { + // 继续计时 + startTime += DateTime.Now - pauseTime; + StartPauseIcon.Data = Geometry.Parse(PauseIconData); + isPaused = false; + timer.Start(); + } + else if (isTimerRunning) + { + // 暂停计时 + pauseTime = DateTime.Now; + StartPauseIcon.Data = Geometry.Parse(PlayIconData); + isPaused = true; + timer.Stop(); + } + else + { + // 开始计时 + if (hour == 0 && minute == 0 && second == 0) + { + second = 1; + UpdateDigitDisplays(); + } + + startTime = DateTime.Now; + StartPauseIcon.Data = Geometry.Parse(PauseIconData); + isPaused = false; + isTimerRunning = true; + isOvertimeMode = false; + hasPlayedProgressiveReminder = false; + timer.Start(); + + // 启动隐藏定时器 + hideTimer.Start(); + + // 保存到最近计时记录 + SaveRecentTimer(); + } + } + + private void Reset_Click(object sender, RoutedEventArgs e) + { + if (!isTimerRunning) + { + UpdateDigitDisplays(); + isOvertimeMode = false; + } + else if (isTimerRunning && isPaused) + { + UpdateDigitDisplays(); + StartPauseIcon.Data = Geometry.Parse(PlayIconData); + isTimerRunning = false; + timer.Stop(); + isPaused = false; + isOvertimeMode = false; + } + else + { + startTime = DateTime.Now; + Timer_Elapsed(timer, null); + } + } + + private void PlayTimerSound() + { + try + { + double volume = MainWindow.Settings.RandSettings?.TimerVolume ?? 1.0; + mediaPlayer.Volume = volume; + + if (!string.IsNullOrEmpty(MainWindow.Settings.RandSettings?.CustomTimerSoundPath) && + System.IO.File.Exists(MainWindow.Settings.RandSettings.CustomTimerSoundPath)) + { + mediaPlayer.Open(new Uri(MainWindow.Settings.RandSettings.CustomTimerSoundPath)); + } + else + { + string tempPath = System.IO.Path.GetTempFileName() + ".wav"; + using (var stream = Properties.Resources.TimerDownNotice) + { + using (var fileStream = new System.IO.FileStream(tempPath, System.IO.FileMode.Create)) + { + stream.CopyTo(fileStream); + } + } + mediaPlayer.Open(new Uri(tempPath)); + } + + mediaPlayer.Play(); + } + catch (Exception ex) + { + System.Diagnostics.Debug.WriteLine($"播放计时器铃声失败: {ex.Message}"); + } + } + + private void PlayProgressiveReminderSound() + { + try + { + double volume = MainWindow.Settings.RandSettings?.ProgressiveReminderVolume ?? 1.0; + mediaPlayer.Volume = volume; + + if (!string.IsNullOrEmpty(MainWindow.Settings.RandSettings?.ProgressiveReminderSoundPath) && + System.IO.File.Exists(MainWindow.Settings.RandSettings.ProgressiveReminderSoundPath)) + { + mediaPlayer.Open(new Uri(MainWindow.Settings.RandSettings.ProgressiveReminderSoundPath)); + } + else + { + string tempPath = System.IO.Path.GetTempFileName() + ".wav"; + using (var stream = Properties.Resources.ProgressiveAudio) + { + using (var fileStream = new System.IO.FileStream(tempPath, System.IO.FileMode.Create)) + { + stream.CopyTo(fileStream); + } + } + mediaPlayer.Open(new Uri(tempPath)); + } + + mediaPlayer.Play(); + } + catch (Exception ex) + { + System.Diagnostics.Debug.WriteLine($"播放渐进提醒音频失败: {ex.Message}"); + } + } + + private void Window_Loaded(object sender, RoutedEventArgs e) + { + // 窗口加载时的初始化 + } + + private void Window_Closing(object sender, System.ComponentModel.CancelEventArgs e) + { + isTimerRunning = false; + + if (MainWindow.Settings != null) + { + var mainWindow = Application.Current.MainWindow as MainWindow; + if (mainWindow != null) + { + try + { + var currentModeField = mainWindow.GetType().GetField("currentMode", + System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance); + if (currentModeField != null) + { + int currentMode = (int)currentModeField.GetValue(mainWindow); + if (currentMode == 1) // 白板模式 + { + mainWindow.Topmost = false; // 保持白板模式下的非置顶状态 + } + else + { + mainWindow.Topmost = true; // 其他模式恢复置顶 + } + } + } + catch + { + // 如果反射失败,使用默认行为 + mainWindow.Topmost = true; + } + } + } + } + + private void CloseButton_Click(object sender, RoutedEventArgs e) + { + Close(); + } + + private void WindowDragMove(object sender, MouseEventArgs e) + { + if (e.LeftButton == MouseButtonState.Pressed) + DragMove(); + } + + private void CommonTab_Click(object sender, RoutedEventArgs e) + { + CommonTimersGrid.Visibility = Visibility.Visible; + RecentTimersGrid.Visibility = Visibility.Collapsed; + + // 更新字体粗细、透明度和颜色 + var commonText = this.FindName("CommonTabText") as TextBlock; + var recentText = this.FindName("RecentTabText") as TextBlock; + if (commonText != null) + { + commonText.FontWeight = FontWeights.Bold; + commonText.Opacity = 1.0; + commonText.Foreground = new SolidColorBrush(Colors.White); + } + if (recentText != null) + { + recentText.FontWeight = FontWeights.Normal; + recentText.Opacity = 0.8; + recentText.Foreground = new SolidColorBrush(Color.FromRgb(102, 102, 102)); + } + + // 移动指示器到左侧 + var indicator = this.FindName("SegmentedIndicator") as Border; + if (indicator != null) + { + // 设置左侧圆角 + indicator.CornerRadius = new CornerRadius(7.5, 0, 0, 7.5); + var animation = new System.Windows.Media.Animation.ThicknessAnimation( + new Thickness(0, 0, 0, 0), + TimeSpan.FromMilliseconds(200)); + indicator.BeginAnimation(Border.MarginProperty, animation); + } + } + + private void RecentTab_Click(object sender, RoutedEventArgs e) + { + CommonTimersGrid.Visibility = Visibility.Collapsed; + RecentTimersGrid.Visibility = Visibility.Visible; + + // 更新字体粗细、透明度和颜色 + var commonText = this.FindName("CommonTabText") as TextBlock; + var recentText = this.FindName("RecentTabText") as TextBlock; + if (commonText != null) + { + commonText.FontWeight = FontWeights.Normal; + commonText.Opacity = 0.8; + commonText.Foreground = new SolidColorBrush(Color.FromRgb(102, 102, 102)); + } + if (recentText != null) + { + recentText.FontWeight = FontWeights.Bold; + recentText.Opacity = 1.0; + recentText.Foreground = new SolidColorBrush(Colors.White); + } + + // 移动指示器到右侧 + var indicator = this.FindName("SegmentedIndicator") as Border; + if (indicator != null) + { + // 设置右侧圆角 + indicator.CornerRadius = new CornerRadius(0, 7.5, 7.5, 0); + var animation = new System.Windows.Media.Animation.ThicknessAnimation( + new Thickness(118, 0, 0, 0), + TimeSpan.FromMilliseconds(200)); + indicator.BeginAnimation(Border.MarginProperty, animation); + } + } + + // 常用计时事件处理 + private void Common5Min_Click(object sender, RoutedEventArgs e) + { + if (isTimerRunning && !isPaused) return; + SetQuickTime(0, 5, 0); + } + + private void Common10Min_Click(object sender, RoutedEventArgs e) + { + if (isTimerRunning && !isPaused) return; + SetQuickTime(0, 10, 0); + } + + private void Common15Min_Click(object sender, RoutedEventArgs e) + { + if (isTimerRunning && !isPaused) return; + SetQuickTime(0, 15, 0); + } + + private void Common30Min_Click(object sender, RoutedEventArgs e) + { + if (isTimerRunning && !isPaused) return; + SetQuickTime(0, 30, 0); + } + + private void Common45Min_Click(object sender, RoutedEventArgs e) + { + if (isTimerRunning && !isPaused) return; + SetQuickTime(0, 45, 0); + } + + private void Common60Min_Click(object sender, RoutedEventArgs e) + { + if (isTimerRunning && !isPaused) return; + SetQuickTime(1, 0, 0); + } + + // 最近计时事件处理 + private void RecentTimer1_Click(object sender, RoutedEventArgs e) + { + if ((isTimerRunning && !isPaused) || recentTimer1 == "--:--") return; + ApplyRecentTimer(recentTimer1); + } + + private void RecentTimer2_Click(object sender, RoutedEventArgs e) + { + if ((isTimerRunning && !isPaused) || recentTimer2 == "--:--") return; + ApplyRecentTimer(recentTimer2); + } + + private void RecentTimer3_Click(object sender, RoutedEventArgs e) + { + if ((isTimerRunning && !isPaused) || recentTimer3 == "--:--") return; + ApplyRecentTimer(recentTimer3); + } + + private void RecentTimer4_Click(object sender, RoutedEventArgs e) + { + if ((isTimerRunning && !isPaused) || recentTimer4 == "--:--") return; + ApplyRecentTimer(recentTimer4); + } + + private void RecentTimer5_Click(object sender, RoutedEventArgs e) + { + if ((isTimerRunning && !isPaused) || recentTimer5 == "--:--") return; + ApplyRecentTimer(recentTimer5); + } + + private void RecentTimer6_Click(object sender, RoutedEventArgs e) + { + if ((isTimerRunning && !isPaused) || recentTimer6 == "--:--") return; + ApplyRecentTimer(recentTimer6); + } + + // 设置快捷时间 + private void SetQuickTime(int h, int m, int s) + { + hour = h; + minute = m; + second = s; + UpdateDigitDisplays(); + } + + // 应用最近计时 + private void ApplyRecentTimer(string timeString) + { + if (timeString == "--:--") return; + + try + { + var parts = timeString.Split(':'); + if (parts.Length == 2) + { + int minutes = int.Parse(parts[0]); + int seconds = int.Parse(parts[1]); + SetQuickTime(0, minutes, seconds); + } + } + catch + { + // 如果解析失败,忽略 + } + } + + // 保存最近计时记录 + private void SaveRecentTimer() + { + if (hour == 0 && minute == 0 && second == 0) return; + + string currentTime = $"{minute:D2}:{second:D2}"; + + // 检查是否已存在相同的时间 + var existingIndex = -1; + if (recentTimer1 == currentTime) existingIndex = 0; + else if (recentTimer2 == currentTime) existingIndex = 1; + else if (recentTimer3 == currentTime) existingIndex = 2; + else if (recentTimer4 == currentTime) existingIndex = 3; + else if (recentTimer5 == currentTime) existingIndex = 4; + else if (recentTimer6 == currentTime) existingIndex = 5; + + if (existingIndex >= 0) + { + // 如果存在重复,将其移到最前面 + string duplicateTimer = GetRecentTimerByIndex(existingIndex); + + // 移除重复项 + RemoveRecentTimerByIndex(existingIndex); + + // 将重复项添加到最前面 + recentTimer6 = recentTimer5; + recentTimer5 = recentTimer4; + recentTimer4 = recentTimer3; + recentTimer3 = recentTimer2; + recentTimer2 = recentTimer1; + recentTimer1 = duplicateTimer; + } + else + { + // 如果不存在重复,正常添加新记录 + recentTimer6 = recentTimer5; + recentTimer5 = recentTimer4; + recentTimer4 = recentTimer3; + recentTimer3 = recentTimer2; + recentTimer2 = recentTimer1; + recentTimer1 = currentTime; + } + + UpdateRecentTimerDisplays(); + SaveRecentTimersToRegistry(); + } + + private string GetRecentTimerByIndex(int index) + { + switch (index) + { + case 0: return recentTimer1; + case 1: return recentTimer2; + case 2: return recentTimer3; + case 3: return recentTimer4; + case 4: return recentTimer5; + case 5: return recentTimer6; + default: return ""; + } + } + + private void RemoveRecentTimerByIndex(int index) + { + switch (index) + { + case 0: + recentTimer1 = recentTimer2; + recentTimer2 = recentTimer3; + recentTimer3 = recentTimer4; + recentTimer4 = recentTimer5; + recentTimer5 = recentTimer6; + recentTimer6 = "--:--"; + break; + case 1: + recentTimer2 = recentTimer3; + recentTimer3 = recentTimer4; + recentTimer4 = recentTimer5; + recentTimer5 = recentTimer6; + recentTimer6 = "--:--"; + break; + case 2: + recentTimer3 = recentTimer4; + recentTimer4 = recentTimer5; + recentTimer5 = recentTimer6; + recentTimer6 = "--:--"; + break; + case 3: + recentTimer4 = recentTimer5; + recentTimer5 = recentTimer6; + recentTimer6 = "--:--"; + break; + case 4: + recentTimer5 = recentTimer6; + recentTimer6 = "--:--"; + break; + case 5: + recentTimer6 = "--:--"; + break; + } + } + + // 更新最近计时显示 + private void UpdateRecentTimerDisplays() + { + try + { + RecentTimer1Text.Text = recentTimer1; + RecentTimer2Text.Text = recentTimer2; + RecentTimer3Text.Text = recentTimer3; + RecentTimer4Text.Text = recentTimer4; + RecentTimer5Text.Text = recentTimer5; + RecentTimer6Text.Text = recentTimer6; + } + catch + { + // 如果UI元素还未初始化,忽略错误 + } + } + + // 从JSON文件加载最近计时记录 + private void LoadRecentTimers() + { + try + { + // 确保Configs文件夹存在 + if (!Directory.Exists(ConfigsFolder)) + { + Directory.CreateDirectory(ConfigsFolder); + } + + if (!File.Exists(RecentTimersJsonPath)) + { + recentTimer1 = "--:--"; + recentTimer2 = "--:--"; + recentTimer3 = "--:--"; + recentTimer4 = "--:--"; + recentTimer5 = "--:--"; + recentTimer6 = "--:--"; + return; + } + + // 读取JSON文件 + string jsonContent = File.ReadAllText(RecentTimersJsonPath); + var data = JsonConvert.DeserializeObject(jsonContent); + + if (data != null) + { + recentTimer1 = data.RecentTimer1 ?? "--:--"; + recentTimer2 = data.RecentTimer2 ?? "--:--"; + recentTimer3 = data.RecentTimer3 ?? "--:--"; + recentTimer4 = data.RecentTimer4 ?? "--:--"; + recentTimer5 = data.RecentTimer5 ?? "--:--"; + recentTimer6 = data.RecentTimer6 ?? "--:--"; + } + else + { + recentTimer1 = "--:--"; + recentTimer2 = "--:--"; + recentTimer3 = "--:--"; + recentTimer4 = "--:--"; + recentTimer5 = "--:--"; + recentTimer6 = "--:--"; + } + } + catch (Exception ex) + { + recentTimer1 = "--:--"; + recentTimer2 = "--:--"; + recentTimer3 = "--:--"; + recentTimer4 = "--:--"; + recentTimer5 = "--:--"; + recentTimer6 = "--:--"; + } + } + + // 保存最近计时记录到JSON文件 + private void SaveRecentTimersToRegistry() + { + try + { + // 确保Configs文件夹存在 + if (!Directory.Exists(ConfigsFolder)) + { + Directory.CreateDirectory(ConfigsFolder); + } + + // 创建数据对象 + var data = new RecentTimersData + { + RecentTimer1 = recentTimer1, + RecentTimer2 = recentTimer2, + RecentTimer3 = recentTimer3, + RecentTimer4 = recentTimer4, + RecentTimer5 = recentTimer5, + RecentTimer6 = recentTimer6 + }; + + // 序列化为JSON并保存到文件 + string jsonContent = JsonConvert.SerializeObject(data, Formatting.Indented); + File.WriteAllText(RecentTimersJsonPath, jsonContent); + } + catch (Exception) + { + } + } + + // 设置深色主题下的灰色边框 + private void SetDarkThemeBorder() + { + try + { + if (MainBorder != null) + { + MainBorder.BorderBrush = new SolidColorBrush(Color.FromRgb(64, 64, 64)); + } + } + catch + { + } + } + + private void Fullscreen_Click(object sender, RoutedEventArgs e) + { + ShowFullscreenTimer(); + } + + private void ShowFullscreenTimer() + { + // 设置全屏模式标志 + isFullscreenMode = true; + + // 创建全屏计时器窗口 + fullscreenWindow = new FullscreenTimerWindow(this); + fullscreenWindow.Show(); + + // 隐藏主窗口 + this.Hide(); + } + + private void HandleTimerCompletion() + { + if (minimizedWindow != null) + { + minimizedWindow.Close(); + minimizedWindow = null; + this.Show(); + this.Activate(); + this.WindowState = WindowState.Normal; + } + else if (fullscreenWindow != null) + { + fullscreenWindow.Close(); + fullscreenWindow = null; + isFullscreenMode = false; + this.Show(); + this.Activate(); + this.WindowState = WindowState.Normal; + } + } + } +} diff --git a/Ink Canvas/Windows/NewStyleTimerWindow.xaml b/Ink Canvas/Windows/NewStyleTimerWindow.xaml new file mode 100644 index 00000000..06b76a1a --- /dev/null +++ b/Ink Canvas/Windows/NewStyleTimerWindow.xaml @@ -0,0 +1,783 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Ink Canvas/Windows/OperatingGuideWindow.xaml.cs b/Ink Canvas/Windows/OperatingGuideWindow.xaml.cs index 39ded587..28e83faa 100644 --- a/Ink Canvas/Windows/OperatingGuideWindow.xaml.cs +++ b/Ink Canvas/Windows/OperatingGuideWindow.xaml.cs @@ -1,8 +1,8 @@ using Ink_Canvas.Helpers; -using System.Windows; -using System.Windows.Input; using iNKORE.UI.WPF.Modern; using System; +using System.Windows; +using System.Windows.Input; namespace Ink_Canvas { @@ -54,9 +54,9 @@ namespace Ink_Canvas try { // 根据当前主题设置窗口主题 - bool isDarkTheme = MainWindow.Settings.Appearance.Theme == 1 || + bool isDarkTheme = MainWindow.Settings.Appearance.Theme == 1 || (MainWindow.Settings.Appearance.Theme == 2 && !IsSystemThemeLight()); - + if (isDarkTheme) { ThemeManager.SetRequestedTheme(this, ElementTheme.Dark); diff --git a/Ink Canvas/Windows/RandWindow.xaml.cs b/Ink Canvas/Windows/RandWindow.xaml.cs index fc16446c..c3052eef 100644 --- a/Ink Canvas/Windows/RandWindow.xaml.cs +++ b/Ink Canvas/Windows/RandWindow.xaml.cs @@ -487,7 +487,7 @@ namespace Ink_Canvas { // 重新应用主题 ApplyTheme(MainWindow.Settings); - + // 强制刷新UI InvalidateVisual(); } diff --git a/Ink Canvas/Windows/SeewoStyleTimerWindow.xaml b/Ink Canvas/Windows/SeewoStyleTimerWindow.xaml deleted file mode 100644 index fd4a0243..00000000 --- a/Ink Canvas/Windows/SeewoStyleTimerWindow.xaml +++ /dev/null @@ -1,322 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/Ink Canvas/Windows/SeewoStyleTimerWindow.xaml.cs b/Ink Canvas/Windows/SeewoStyleTimerWindow.xaml.cs deleted file mode 100644 index e719f753..00000000 --- a/Ink Canvas/Windows/SeewoStyleTimerWindow.xaml.cs +++ /dev/null @@ -1,434 +0,0 @@ -using Ink_Canvas.Helpers; -using System; -using System.Media; -using System.Timers; -using System.Windows; -using System.Windows.Controls; -using System.Windows.Input; -using System.Windows.Interop; -using System.Windows.Media; - -namespace Ink_Canvas -{ - /// - /// 仿希沃风格的倒计时器窗口 - /// - public partial class SeewoStyleTimerWindow : Window - { - public SeewoStyleTimerWindow() - { - InitializeComponent(); - AnimationsHelper.ShowWithSlideFromBottomAndFade(this, 0.25); - - timer.Elapsed += Timer_Elapsed; - timer.Interval = 50; - InitializeUI(); - - // 应用主题 - ApplyTheme(); - } - - - private void Timer_Elapsed(object sender, ElapsedEventArgs e) - { - if (!isTimerRunning || isPaused) - { - timer.Stop(); - return; - } - - TimeSpan timeSpan = DateTime.Now - startTime; - TimeSpan totalTimeSpan = new TimeSpan(hour, minute, second); - TimeSpan leftTimeSpan = totalTimeSpan - timeSpan; - if (leftTimeSpan.Milliseconds > 0) leftTimeSpan += new TimeSpan(0, 0, 1); - - Application.Current.Dispatcher.Invoke(() => - { - Digit1Display.Text = (leftTimeSpan.Hours / 10).ToString(); - Digit2Display.Text = (leftTimeSpan.Hours % 10).ToString(); - Digit3Display.Text = (leftTimeSpan.Minutes / 10).ToString(); - Digit4Display.Text = (leftTimeSpan.Minutes % 10).ToString(); - Digit5Display.Text = (leftTimeSpan.Seconds / 10).ToString(); - Digit6Display.Text = (leftTimeSpan.Seconds % 10).ToString(); - - if (leftTimeSpan.TotalSeconds <= 0) - { - Digit1Display.Text = "0"; - Digit2Display.Text = "0"; - Digit3Display.Text = "0"; - Digit4Display.Text = "0"; - Digit5Display.Text = "0"; - Digit6Display.Text = "0"; - timer.Stop(); - isTimerRunning = false; - StartPauseIcon.Data = Geometry.Parse(PlayIconData); - PlayTimerSound(); - } - }); - } - - SoundPlayer player = new SoundPlayer(); - MediaPlayer mediaPlayer = new MediaPlayer(); - - int hour = 0; - int minute = 5; - int second = 0; - - DateTime startTime = DateTime.Now; - DateTime pauseTime = DateTime.Now; - - bool isTimerRunning = false; - bool isPaused = false; - - Timer timer = new Timer(); - - private void InitializeUI() - { - UpdateDigitDisplays(); - } - - private void ApplyTheme() - { - try - { - // 应用主题设置 - if (MainWindow.Settings != null) - { - ApplyTheme(MainWindow.Settings); - } - } - catch (Exception ex) - { - LogHelper.WriteLogToFile($"应用仿希沃倒计时窗口主题出错: {ex.Message}", LogHelper.LogType.Error); - } - } - - private void ApplyTheme(Settings settings) - { - try - { - if (settings.Appearance.Theme == 0) // 浅色主题 - { - iNKORE.UI.WPF.Modern.ThemeManager.SetRequestedTheme(this, iNKORE.UI.WPF.Modern.ElementTheme.Light); - } - else if (settings.Appearance.Theme == 1) // 深色主题 - { - iNKORE.UI.WPF.Modern.ThemeManager.SetRequestedTheme(this, iNKORE.UI.WPF.Modern.ElementTheme.Dark); - } - else // 跟随系统主题 - { - bool isSystemLight = IsSystemThemeLight(); - if (isSystemLight) - { - iNKORE.UI.WPF.Modern.ThemeManager.SetRequestedTheme(this, iNKORE.UI.WPF.Modern.ElementTheme.Light); - } - else - { - iNKORE.UI.WPF.Modern.ThemeManager.SetRequestedTheme(this, iNKORE.UI.WPF.Modern.ElementTheme.Dark); - } - } - } - catch (Exception ex) - { - LogHelper.WriteLogToFile($"应用仿希沃倒计时窗口主题出错: {ex.Message}", LogHelper.LogType.Error); - } - } - - private bool IsSystemThemeLight() - { - var light = false; - try - { - var registryKey = Microsoft.Win32.Registry.CurrentUser; - var themeKey = registryKey.OpenSubKey(@"Software\Microsoft\Windows\CurrentVersion\Themes\Personalize"); - if (themeKey != null) - { - var value = themeKey.GetValue("AppsUseLightTheme"); - if (value != null) - { - light = (int)value == 1; - } - themeKey.Close(); - } - } - catch - { - // 如果读取注册表失败,默认为浅色主题 - light = true; - } - return light; - } - - private void UpdateDigitDisplays() - { - Digit1Display.Text = (hour / 10).ToString(); - Digit2Display.Text = (hour % 10).ToString(); - Digit3Display.Text = (minute / 10).ToString(); - Digit4Display.Text = (minute % 10).ToString(); - Digit5Display.Text = (second / 10).ToString(); - Digit6Display.Text = (second % 10).ToString(); - } - - // 第1位数字(小时十位) - private void Digit1Plus_Click(object sender, RoutedEventArgs e) - { - if (isTimerRunning) return; - hour += 10; - if (hour >= 100) hour = 0; - UpdateDigitDisplays(); - } - - private void Digit1Minus_Click(object sender, RoutedEventArgs e) - { - if (isTimerRunning) return; - hour -= 10; - if (hour < 0) hour = 90; - UpdateDigitDisplays(); - } - - // 第2位数字(小时个位) - private void Digit2Plus_Click(object sender, RoutedEventArgs e) - { - if (isTimerRunning) return; - hour++; - if (hour >= 100) hour = 0; - UpdateDigitDisplays(); - } - - private void Digit2Minus_Click(object sender, RoutedEventArgs e) - { - if (isTimerRunning) return; - hour--; - if (hour < 0) hour = 99; - UpdateDigitDisplays(); - } - - // 第3位数字(分钟十位) - private void Digit3Plus_Click(object sender, RoutedEventArgs e) - { - if (isTimerRunning) return; - minute += 10; - if (minute >= 60) minute = 0; - UpdateDigitDisplays(); - } - - private void Digit3Minus_Click(object sender, RoutedEventArgs e) - { - if (isTimerRunning) return; - minute -= 10; - if (minute < 0) minute = 50; - UpdateDigitDisplays(); - } - - // 第4位数字(分钟个位) - private void Digit4Plus_Click(object sender, RoutedEventArgs e) - { - if (isTimerRunning) return; - minute++; - if (minute >= 60) minute = 0; - UpdateDigitDisplays(); - } - - private void Digit4Minus_Click(object sender, RoutedEventArgs e) - { - if (isTimerRunning) return; - minute--; - if (minute < 0) minute = 59; - UpdateDigitDisplays(); - } - - // 第5位数字(秒十位) - private void Digit5Plus_Click(object sender, RoutedEventArgs e) - { - if (isTimerRunning) return; - second += 10; - if (second >= 60) second = 0; - UpdateDigitDisplays(); - } - - private void Digit5Minus_Click(object sender, RoutedEventArgs e) - { - if (isTimerRunning) return; - second -= 10; - if (second < 0) second = 50; - UpdateDigitDisplays(); - } - - // 第6位数字(秒个位) - private void Digit6Plus_Click(object sender, RoutedEventArgs e) - { - if (isTimerRunning) return; - second++; - if (second >= 60) second = 0; - UpdateDigitDisplays(); - } - - private void Digit6Minus_Click(object sender, RoutedEventArgs e) - { - if (isTimerRunning) return; - second--; - if (second < 0) second = 59; - UpdateDigitDisplays(); - } - - // 图标数据常量 - private const string PlayIconData = "M6.5 4.00004V20C6.49995 20.178 6.54737 20.3527 6.63738 20.5062C6.72739 20.6597 6.85672 20.7864 7.01202 20.8732C7.16733 20.96 7.34299 21.0038 7.52088 21.0001C7.69878 20.9964 7.87245 20.9453 8.024 20.852L21.024 12.852C21.1696 12.7626 21.2898 12.6373 21.3733 12.4881C21.4567 12.339 21.5005 12.1709 21.5005 12C21.5005 11.8291 21.4567 11.6611 21.3733 11.512C21.2898 11.3628 21.1696 11.2375 21.024 11.148L8.024 3.14804C7.87245 3.0548 7.69878 3.00369 7.52088 2.99997C7.34299 2.99626 7.16733 3.04007 7.01202 3.1269C6.85672 3.21372 6.72739 3.34042 6.63738 3.4939C6.54737 3.64739 6.49995 3.82211 6.5 4.00004Z"; - private const string PauseIconData = "M9.5 4H7.5C6.96957 4 6.46086 4.21071 6.08579 4.58579C5.71071 4.96086 5.5 5.46957 5.5 6V18C5.5 18.5304 5.71071 19.0391 6.08579 19.4142C6.46086 19.7893 6.96957 20 7.5 20H9.5C10.0304 20 10.5391 19.7893 10.9142 19.4142C11.2893 19.0391 11.5 18.5304 11.5 18V6C11.5 5.46957 11.2893 4.96086 10.9142 4.58579C10.5391 4.21071 10.0304 4 9.5 4Z M17.5 4H15.5C14.9696 4 14.4609 4.21071 14.0858 4.58579C13.7107 4.96086 13.5 5.46957 13.5 6V18C13.5 18.5304 13.7107 19.0391 14.0858 19.4142C14.4609 19.7893 14.9696 20 15.5 20H17.5C18.0304 20 18.5391 19.7893 18.9142 19.4142C19.2893 19.0391 19.5 18.5304 19.5 18V6C19.5 5.46957 19.2893 4.96086 18.9142 4.58579C18.5391 4.21071 18.0304 4 17.5 4Z"; - - private void StartPause_Click(object sender, RoutedEventArgs e) - { - if (isPaused && isTimerRunning) - { - // 继续计时 - startTime += DateTime.Now - pauseTime; - StartPauseIcon.Data = Geometry.Parse(PauseIconData); - isPaused = false; - timer.Start(); - } - else if (isTimerRunning) - { - // 暂停计时 - pauseTime = DateTime.Now; - StartPauseIcon.Data = Geometry.Parse(PlayIconData); - isPaused = true; - timer.Stop(); - } - else - { - // 开始计时 - if (hour == 0 && minute == 0 && second == 0) - { - second = 1; - UpdateDigitDisplays(); - } - - startTime = DateTime.Now; - StartPauseIcon.Data = Geometry.Parse(PauseIconData); - isPaused = false; - isTimerRunning = true; - timer.Start(); - } - } - - private void Reset_Click(object sender, RoutedEventArgs e) - { - if (!isTimerRunning) - { - UpdateDigitDisplays(); - } - else if (isTimerRunning && isPaused) - { - UpdateDigitDisplays(); - StartPauseIcon.Data = Geometry.Parse(PlayIconData); - isTimerRunning = false; - timer.Stop(); - isPaused = false; - } - else - { - startTime = DateTime.Now; - Timer_Elapsed(timer, null); - } - } - - private void Fullscreen_Click(object sender, RoutedEventArgs e) - { - if (WindowState == WindowState.Normal) - { - WindowState = WindowState.Maximized; - } - else - { - WindowState = WindowState.Normal; - } - } - - - private void PlayTimerSound() - { - try - { - double volume = MainWindow.Settings.RandSettings?.TimerVolume ?? 1.0; - mediaPlayer.Volume = volume; - - if (!string.IsNullOrEmpty(MainWindow.Settings.RandSettings?.CustomTimerSoundPath) && - System.IO.File.Exists(MainWindow.Settings.RandSettings.CustomTimerSoundPath)) - { - // 播放自定义铃声 - mediaPlayer.Open(new Uri(MainWindow.Settings.RandSettings.CustomTimerSoundPath)); - } - else - { - // 播放默认铃声 - string tempPath = System.IO.Path.GetTempFileName() + ".wav"; - using (var stream = Properties.Resources.TimerDownNotice) - { - using (var fileStream = new System.IO.FileStream(tempPath, System.IO.FileMode.Create)) - { - stream.CopyTo(fileStream); - } - } - mediaPlayer.Open(new Uri(tempPath)); - } - - mediaPlayer.Play(); - } - catch (Exception ex) - { - // 如果播放失败,静默处理 - System.Diagnostics.Debug.WriteLine($"播放计时器铃声失败: {ex.Message}"); - } - } - - private void Window_Loaded(object sender, RoutedEventArgs e) - { - // 窗口加载时的初始化 - } - - private void Window_Closing(object sender, System.ComponentModel.CancelEventArgs e) - { - isTimerRunning = false; - - if (MainWindow.Settings != null) - { - var mainWindow = Application.Current.MainWindow as MainWindow; - if (mainWindow != null) - { - try - { - var currentModeField = mainWindow.GetType().GetField("currentMode", - System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance); - if (currentModeField != null) - { - int currentMode = (int)currentModeField.GetValue(mainWindow); - if (currentMode == 1) // 白板模式 - { - mainWindow.Topmost = false; // 保持白板模式下的非置顶状态 - } - else - { - mainWindow.Topmost = true; // 其他模式恢复置顶 - } - } - } - catch - { - // 如果反射失败,使用默认行为 - mainWindow.Topmost = true; - } - } - } - } - - private void BtnClose_MouseUp(object sender, MouseButtonEventArgs e) - { - Close(); - } - - private void WindowDragMove(object sender, MouseEventArgs e) - { - if (e.LeftButton == MouseButtonState.Pressed) - DragMove(); - } - } -} diff --git a/Ink Canvas/obj/Debug/net472/InkCanvasForClass.csproj.AssemblyReference.cache b/Ink Canvas/obj/Debug/net472/InkCanvasForClass.csproj.AssemblyReference.cache deleted file mode 100644 index c8c2dfbd..00000000 Binary files a/Ink Canvas/obj/Debug/net472/InkCanvasForClass.csproj.AssemblyReference.cache and /dev/null differ diff --git a/README.md b/README.md index a10bd6da..71ab7b17 100644 --- a/README.md +++ b/README.md @@ -114,3 +114,4 @@ GPLv3 ## 项目引用 [Alan-CRL/DesktopDrawpadBlocker](https://github.com/Alan-CRL/DesktopDrawpadBlocker) +[Awesome-Iwb/iwbicons-gallery](https://github.com/awesome-iwb/awesome-iwb/wiki/iwbicons-gallery)「本项目部分图标来自 Awesome Iwb 的 IwbIcons 图标库,由 Douxiba 制作。」 diff --git a/UpdateLog.md b/UpdateLog.md index cf2fa362..af861394 100644 --- a/UpdateLog.md +++ b/UpdateLog.md @@ -98,4 +98,5 @@ ICC CE 1.7.X.X更新日志 97. 改进橡皮系统 98. 新增启动动画 99. 修复仅调色盘状态下浮动栏不居中 -100. 修复希沃白板查杀与思锐希沃启动器导致的重复启动 \ No newline at end of file +100. 修复希沃白板查杀与思锐希沃启动器导致的重复启动 +101. 新增UIA窗口置顶