diff --git a/.vscode/launch.json b/.vscode/launch.json new file mode 100644 index 0000000..a37c1c8 --- /dev/null +++ b/.vscode/launch.json @@ -0,0 +1,26 @@ +{ + "version": "0.2.0", + "configurations": [ + { + // 使用 IntelliSense 找出 C# 调试存在哪些属性 + // 将悬停用于现有属性的说明 + // 有关详细信息,请访问 https://github.com/dotnet/vscode-csharp/blob/main/debugger-launchjson.md + "name": ".NET Core Launch (console)", + "type": "coreclr", + "request": "launch", + "preLaunchTask": "build", + // 如果已更改目标框架,请确保更新程序路径。 + "program": "${workspaceFolder}/src/bin/Debug/net8.0-windows10.0.19041.0/BASpark.dll", + "args": [], + "cwd": "${workspaceFolder}/src", + // 有关“控制台”字段的详细信息,请参阅 https://aka.ms/VSCode-CS-LaunchJson-Console + "console": "internalConsole", + "stopAtEntry": false + }, + { + "name": ".NET Core Attach", + "type": "coreclr", + "request": "attach" + } + ] +} \ No newline at end of file diff --git a/.vscode/tasks.json b/.vscode/tasks.json new file mode 100644 index 0000000..3eb040e --- /dev/null +++ b/.vscode/tasks.json @@ -0,0 +1,41 @@ +{ + "version": "2.0.0", + "tasks": [ + { + "label": "build", + "command": "dotnet", + "type": "process", + "args": [ + "build", + "${workspaceFolder}/src/BASpark.csproj", + "/property:GenerateFullPaths=true", + "/consoleloggerparameters:NoSummary;ForceNoAlign" + ], + "problemMatcher": "$msCompile" + }, + { + "label": "publish", + "command": "dotnet", + "type": "process", + "args": [ + "publish", + "${workspaceFolder}/src/BASpark.csproj", + "/property:GenerateFullPaths=true", + "/consoleloggerparameters:NoSummary;ForceNoAlign" + ], + "problemMatcher": "$msCompile" + }, + { + "label": "watch", + "command": "dotnet", + "type": "process", + "args": [ + "watch", + "run", + "--project", + "${workspaceFolder}/src/BASpark.csproj" + ], + "problemMatcher": "$msCompile" + } + ] +} \ No newline at end of file diff --git a/src/BASpark.csproj b/src/BASpark.csproj index a0d39c7..45cb207 100644 --- a/src/BASpark.csproj +++ b/src/BASpark.csproj @@ -35,5 +35,11 @@ + + + PreserveNewest + + + \ No newline at end of file diff --git a/src/ConfigManager.cs b/src/ConfigManager.cs index e32cc3b..e928597 100644 --- a/src/ConfigManager.cs +++ b/src/ConfigManager.cs @@ -1,7 +1,9 @@ using Microsoft.Win32; using System; using System.Collections.Generic; +using System.IO; using System.Linq; +using System.Reflection; namespace BASpark { @@ -51,8 +53,11 @@ public class ScreenSelectionState public static class ConfigManager { private const string RegPath = @"Software\BASpark"; + public const string CustomHtmlVirtualHost = "baspark.custom"; + public const string CustomHtmlTemplateFileName = "custom-effect-template.html"; public static string ParticleColor { get; set; } = "45,175,255"; + public static string CustomHtmlPath { get; set; } = ""; public static bool IsEffectEnabled { get; set; } = true; public static bool AutoStart { get; set; } = false; public static bool AgreedToPrivacy { get; set; } = false; @@ -122,6 +127,7 @@ public static void Load() ScreenSelections = key.GetValue("ScreenSelections", "")?.ToString() ?? ""; UiLanguage = key.GetValue("UiLanguage", "")?.ToString() ?? ""; ScrollbarVisibility = ParseScrollbarVisibility(key.GetValue("ScrollbarVisibility", "OnScroll")?.ToString()); + CustomHtmlPath = key.GetValue("CustomHtmlPath", "")?.ToString() ?? ""; if (!string.IsNullOrWhiteSpace(UiLanguage)) { Localization.ApplyCulture(UiLanguage); @@ -199,6 +205,80 @@ public static void GetAnimationSpeedsForOverlay(out double trailSpeed, out doubl } } + public static bool IsUsingCustomHtml => TryResolveCustomHtmlUri(out _, out _); + + public static bool TryResolveCustomHtmlUri(out string navigateUri, out string folderPath) + { + navigateUri = string.Empty; + folderPath = string.Empty; + if (string.IsNullOrWhiteSpace(CustomHtmlPath)) + { + return false; + } + + string fullPath; + try + { + fullPath = Path.GetFullPath(CustomHtmlPath.Trim()); + } + catch + { + return false; + } + + if (!File.Exists(fullPath)) + { + return false; + } + + string ext = Path.GetExtension(fullPath); + if (!ext.Equals(".html", StringComparison.OrdinalIgnoreCase) && + !ext.Equals(".htm", StringComparison.OrdinalIgnoreCase)) + { + return false; + } + + folderPath = Path.GetDirectoryName(fullPath) ?? string.Empty; + if (string.IsNullOrEmpty(folderPath)) + { + return false; + } + + string fileName = Path.GetFileName(fullPath); + navigateUri = $"https://{CustomHtmlVirtualHost}/{Uri.EscapeDataString(fileName)}"; + return true; + } + + public static string? GetWebSamplesDirectory() + { + static bool IsWebSamplesDir(string path) => + Directory.Exists(path) && + File.Exists(Path.Combine(path, CustomHtmlTemplateFileName)); + + string besideExe = Path.Combine(AppContext.BaseDirectory, "Web"); + if (IsWebSamplesDir(besideExe)) + { + return besideExe; + } + + string? dir = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location); + for (int i = 0; i < 8 && !string.IsNullOrEmpty(dir); i++) + { + foreach (string relative in new[] { "Web", Path.Combine("src", "Web") }) + { + string candidate = Path.Combine(dir, relative); + if (IsWebSamplesDir(candidate)) + { + return candidate; + } + } + + dir = Path.GetDirectoryName(dir); + } + + return null; + } + public static List GetProfiles() => _profiles; public static FilterProfile? GetActiveProfile() diff --git a/src/ControlPanelWindow.xaml b/src/ControlPanelWindow.xaml index 50c9de4..560816f 100644 --- a/src/ControlPanelWindow.xaml +++ b/src/ControlPanelWindow.xaml @@ -376,7 +376,20 @@ - + + + + + + + + + + @@ -493,6 +506,7 @@ + diff --git a/src/ControlPanelWindow.xaml.cs b/src/ControlPanelWindow.xaml.cs index 6132052..24df604 100644 --- a/src/ControlPanelWindow.xaml.cs +++ b/src/ControlPanelWindow.xaml.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.IO; using System.Collections.ObjectModel; using System.Windows; using System.Windows.Controls; @@ -80,6 +81,8 @@ private struct POINT private bool _isCheckingUpdate = false; private bool _suspendLinkedAnimationUiHandlers; private string _languageAtLoad = Localization.CultureZhCn; + private string _customHtmlPathAtLoad = ""; + private string _customHtmlPathPending = ""; public ObservableCollection Profiles { get; set; } = new ObservableCollection(); public ObservableCollection CurrentProfileProcesses { get; set; } = new ObservableCollection(); @@ -105,6 +108,7 @@ public ControlPanelWindow() LoadSettings(); ApplyScrollbarSettings(); UiLocalizer.ApplyControlPanel(this); + UpdateCustomHtmlPathDisplay(); LoadScreenOptions(); CheckAdminStatus(); LoadRemoteNotice(); @@ -522,6 +526,70 @@ private void LoadSettingsCore() { RadioScrollbarOnScroll.IsChecked = true; } + + _customHtmlPathPending = ConfigManager.CustomHtmlPath ?? ""; + _customHtmlPathAtLoad = _customHtmlPathPending; + UpdateCustomHtmlPathDisplay(); + ApplyCustomHtmlUiState(); + } + + private void UpdateCustomHtmlPathDisplay() + { + TxtCustomHtmlPath.Text = string.IsNullOrWhiteSpace(_customHtmlPathPending) + ? Localization.Get("CustomHtml_Builtin") + : _customHtmlPathPending; + } + + private void ApplyCustomHtmlUiState() + { + bool usingCustom = !string.IsNullOrWhiteSpace(_customHtmlPathPending) && + File.Exists(_customHtmlPathPending); + PanelVisualBuiltIn.IsEnabled = !usingCustom; + BtnVisualReset.IsEnabled = !usingCustom; + } + + private void BrowseCustomHtml_Click(object sender, RoutedEventArgs e) + { + var dialog = new Microsoft.Win32.OpenFileDialog + { + Filter = Localization.Get("CustomHtml_FileFilter"), + Title = Localization.Get("CustomHtml_SelectTitle") + }; + + string? webDir = ConfigManager.GetWebSamplesDirectory(); + if (!string.IsNullOrEmpty(webDir)) + { + dialog.InitialDirectory = webDir; + dialog.FileName = ConfigManager.CustomHtmlTemplateFileName; + } + + if (dialog.ShowDialog() == true) + { + if (string.Equals( + Path.GetFileName(dialog.FileName), + "index.html", + StringComparison.OrdinalIgnoreCase)) + { + System.Windows.MessageBox.Show( + this, + Localization.Get("CustomHtml_BuiltinIndexWarning"), + Localization.Get("CustomHtml_SelectTitle"), + MessageBoxButton.OK, + MessageBoxImage.Warning); + return; + } + + _customHtmlPathPending = dialog.FileName; + UpdateCustomHtmlPathDisplay(); + ApplyCustomHtmlUiState(); + } + } + + private void ClearCustomHtml_Click(object sender, RoutedEventArgs e) + { + _customHtmlPathPending = ""; + UpdateCustomHtmlPathDisplay(); + ApplyCustomHtmlUiState(); } private void ApplyScrollbarSettings() @@ -1167,6 +1235,12 @@ private void SaveSettings_Click(object sender, RoutedEventArgs e) ConfigManager.Save("AutoStart", autoStartEnabled); ConfigManager.Save("EnableTelemetry", CheckTelemetry.IsChecked ?? false); ConfigManager.Save("ParticleColor", ConfigManager.ParticleColor); + bool customHtmlPathChanged = !string.Equals( + _customHtmlPathAtLoad, + _customHtmlPathPending, + StringComparison.OrdinalIgnoreCase); + ConfigManager.CustomHtmlPath = _customHtmlPathPending; + ConfigManager.Save("CustomHtmlPath", _customHtmlPathPending); ConfigManager.Save("EffectScale", effectScale); ConfigManager.Save("EffectOpacity", effectOpacity); ConfigManager.Save("UseLinkedAnimationSpeed", useLinkedAnimationSpeed); @@ -1218,6 +1292,13 @@ private void SaveSettings_Click(object sender, RoutedEventArgs e) App.SetAutoStart(ConfigManager.AutoStart); ApplyAutoStartSettings(); + if (customHtmlPathChanged) + { + App.Overlay?.ReloadAllEffectContent(); + _customHtmlPathAtLoad = _customHtmlPathPending; + } + + ApplyCustomHtmlUiState(); App.Overlay?.UpdateColor(ConfigManager.ParticleColor); GetUiAnimationSpeeds(out double overlayTrail, out double overlayClick); App.Overlay?.UpdateEffectSettings(effectScale, effectOpacity, overlayTrail, overlayClick); @@ -1250,6 +1331,7 @@ private void SaveSettings_Click(object sender, RoutedEventArgs e) if (languageChanged) { UiLocalizer.ApplyControlPanel(this); + UpdateCustomHtmlPathDisplay(); LoadScreenOptions(); ConfigManager.Save("LastNoticeContent", string.Empty); LoadRemoteNotice(); diff --git a/src/MainWindow.xaml.cs b/src/MainWindow.xaml.cs index 3e828df..accafba 100644 --- a/src/MainWindow.xaml.cs +++ b/src/MainWindow.xaml.cs @@ -2,6 +2,7 @@ using System.Diagnostics.CodeAnalysis; using System.Drawing; using System.Globalization; +using System.IO; using System.Linq; using System.Runtime.InteropServices; using System.Windows; @@ -193,11 +194,21 @@ private void SafeEnsureTopmost() public void UpdateColor(string color) { + if (ConfigManager.IsUsingCustomHtml) + { + return; + } + ExecuteScript($"if(window.updateColor) window.updateColor('{color}');"); } public void UpdateEffectSettings(double scale, double opacity, double trailSpeed, double clickSpeed) { + if (ConfigManager.IsUsingCustomHtml) + { + return; + } + string scaleStr = scale.ToString("F2", CultureInfo.InvariantCulture); string opacityStr = opacity.ToString("F2", CultureInfo.InvariantCulture); string trailStr = trailSpeed.ToString("F2", CultureInfo.InvariantCulture); @@ -287,25 +298,9 @@ private async System.Threading.Tasks.Task InitWebView() _processFailedHandler = OnWebViewProcessFailed; coreWebView.ProcessFailed += _processFailedHandler; - var streamInfo = System.Windows.Application.GetResourceStream(new Uri("pack://application:,,,/Web/index.html")); - if (streamInfo != null) - { - using var reader = new System.IO.StreamReader(streamInfo.Stream); - string htmlContent = reader.ReadToEnd(); - coreWebView.NavigateToString(htmlContent); - _navigationCompletedHandler = (s, e) => - { - if (_isClosing) return; - - _lastReportedInputMode = null; - _lastReportedAlwaysTrail = null; - UpdateColor(ConfigManager.ParticleColor); - ConfigManager.GetAnimationSpeedsForOverlay(out double trailSp, out double clickSp); - UpdateEffectSettings(ConfigManager.EffectScale, ConfigManager.EffectOpacity, trailSp, clickSp); - SyncInputContext(InputModeMouse); - }; - coreWebView.NavigationCompleted += _navigationCompletedHandler; - } + _navigationCompletedHandler = OnEffectNavigationCompleted; + coreWebView.NavigationCompleted += _navigationCompletedHandler; + LoadEffectContent(coreWebView); } catch (Exception ex) when (IsExpectedWebViewShutdownException(ex)) { @@ -316,6 +311,68 @@ private async System.Threading.Tasks.Task InitWebView() } } + public void ReloadEffectContent() + { + if (!TryGetCoreWebView2(out CoreWebView2? coreWebView)) + { + return; + } + + LoadEffectContent(coreWebView); + } + + private void LoadEffectContent(CoreWebView2 coreWebView) + { + if (ConfigManager.TryResolveCustomHtmlUri(out string navigateUri, out string folderPath)) + { + try + { + coreWebView.SetVirtualHostNameToFolderMapping( + ConfigManager.CustomHtmlVirtualHost, + folderPath, + CoreWebView2HostResourceAccessKind.Allow); + coreWebView.Navigate(navigateUri); + return; + } + catch + { + } + } + + NavigateBuiltinHtml(coreWebView); + } + + private static void NavigateBuiltinHtml(CoreWebView2 coreWebView) + { + var streamInfo = System.Windows.Application.GetResourceStream(new Uri("pack://application:,,,/Web/index.html")); + if (streamInfo == null) + { + return; + } + + using var reader = new StreamReader(streamInfo.Stream); + coreWebView.NavigateToString(reader.ReadToEnd()); + } + + private void OnEffectNavigationCompleted(object? sender, CoreWebView2NavigationCompletedEventArgs e) + { + if (_isClosing || !e.IsSuccess) + { + return; + } + + _lastReportedInputMode = null; + _lastReportedAlwaysTrail = null; + if (!ConfigManager.IsUsingCustomHtml) + { + UpdateColor(ConfigManager.ParticleColor); + ConfigManager.GetAnimationSpeedsForOverlay(out double trailSp, out double clickSp); + UpdateEffectSettings(ConfigManager.EffectScale, ConfigManager.EffectOpacity, trailSp, clickSp); + } + + SyncInputContext(InputModeMouse); + } + private void OnWebViewProcessFailed(object? sender, CoreWebView2ProcessFailedEventArgs e) { if (_isClosing) return; diff --git a/src/OverlayManager.cs b/src/OverlayManager.cs index 0e92050..1073961 100644 --- a/src/OverlayManager.cs +++ b/src/OverlayManager.cs @@ -130,9 +130,27 @@ public void Start() SystemEvents.SessionSwitch += HandleSessionSwitch; } - public void UpdateColor(string color) => ForEachOverlay(w => w.UpdateColor(color)); - public void UpdateEffectSettings(double scale, double opacity, double trailSpeed, double clickSpeed) => + public void UpdateColor(string color) + { + if (ConfigManager.IsUsingCustomHtml) + { + return; + } + + ForEachOverlay(w => w.UpdateColor(color)); + } + + public void UpdateEffectSettings(double scale, double opacity, double trailSpeed, double clickSpeed) + { + if (ConfigManager.IsUsingCustomHtml) + { + return; + } + ForEachOverlay(w => w.UpdateEffectSettings(scale, opacity, trailSpeed, clickSpeed)); + } + + public void ReloadAllEffectContent() => ForEachOverlay(w => w.ReloadEffectContent()); public void UpdateTrailRefreshRate(int hz) { hz = Math.Clamp(hz, 10, 240); diff --git a/src/Resources/Strings.en.resx b/src/Resources/Strings.en.resx index 2821245..77fcacd 100644 --- a/src/Resources/Strings.en.resx +++ b/src/Resources/Strings.en.resx @@ -535,6 +535,30 @@ Download now? Trail refresh rate + + Custom HTML effect + + + Select HTML file + + + Use built-in effect + + + Built-in visual controls below are disabled while a custom HTML file is in use. + + + (Built-in effect) + + + HTML files (*.html;*.htm)|*.html;*.htm + + + Select effect HTML file + + + index.html is the built-in default effect and cannot be used as a custom file. Copy custom-effect-template.html and edit it, or choose your own HTML. + WebView2 initialization failed: {0} diff --git a/src/Resources/Strings.ja.resx b/src/Resources/Strings.ja.resx index c1e935e..3110319 100644 --- a/src/Resources/Strings.ja.resx +++ b/src/Resources/Strings.ja.resx @@ -535,6 +535,30 @@ トレイル更新率 + + カスタム HTML エフェクト + + + HTML ファイルを選択 + + + 組み込みエフェクトに戻す + + + カスタム HTML 使用時は、下の組み込みビジュアル設定は無効になります。 + + + (組み込みエフェクト) + + + HTML ファイル (*.html;*.htm)|*.html;*.htm + + + エフェクト用 HTML を選択 + + + index.html は組み込みの既定エフェクトのため、カスタムファイルとして選べません。custom-effect-template.html をコピーして編集するか、独自の HTML を選んでください。 + WebView2 の初期化に失敗しました: {0} diff --git a/src/Resources/Strings.resx b/src/Resources/Strings.resx index 67d504e..5465852 100644 --- a/src/Resources/Strings.resx +++ b/src/Resources/Strings.resx @@ -535,6 +535,30 @@ 拖尾刷新率 + + 自定义 HTML 特效 + + + 选择 HTML 文件 + + + 恢复内置特效 + + + 使用自定义 HTML 时,下方内置视觉调节将不可用。 + + + (使用内置特效) + + + HTML 文件 (*.html;*.htm)|*.html;*.htm + + + 选择特效 HTML 文件 + + + index.html 为软件内置默认特效,不能作为自定义文件选用。请复制 custom-effect-template.html 后修改,或选择您自己的 HTML。 + WebView2 初始化失败: {0} diff --git a/src/UiLocalizer.cs b/src/UiLocalizer.cs index c0aa609..7514f91 100644 --- a/src/UiLocalizer.cs +++ b/src/UiLocalizer.cs @@ -91,6 +91,10 @@ public static void ApplyControlPanel(ControlPanelWindow w) w.TxtTrailRefresh.Text = Localization.Get("Visual_TrailRefresh"); w.TxtEffectColor.Text = Localization.Get("Visual_Color"); w.BtnPickColor.Content = Localization.Get("Visual_ChangeColor"); + w.TxtCustomHtmlTitle.Text = Localization.Get("CustomHtml_Title"); + w.BtnBrowseCustomHtml.Content = Localization.Get("CustomHtml_Browse"); + w.BtnClearCustomHtml.Content = Localization.Get("CustomHtml_ClearBuiltin"); + w.TxtCustomHtmlHint.Text = Localization.Get("CustomHtml_Hint"); w.TxtFilterTitle.Text = Localization.Get("Filter_Title"); w.CheckEnvironmentFilter.Content = Localization.Get("Filter_Enable"); diff --git a/src/Web/custom-effect-template.html b/src/Web/custom-effect-template.html new file mode 100644 index 0000000..81a1322 --- /dev/null +++ b/src/Web/custom-effect-template.html @@ -0,0 +1,107 @@ + + + + + + + + + + + + diff --git a/src/Web/custom-effect-test.html b/src/Web/custom-effect-test.html new file mode 100644 index 0000000..fc00a63 --- /dev/null +++ b/src/Web/custom-effect-test.html @@ -0,0 +1,125 @@ + + + + + + + + + + +