From 3dae5dba045781eaf94e133be593b21de8238c05 Mon Sep 17 00:00:00 2001
From: shuimo <1743355601@qq.com>
Date: Mon, 6 Apr 2026 18:54:57 +0800
Subject: [PATCH 1/3] =?UTF-8?q?=E7=A7=BB=E9=99=A4=E6=B3=A2=E7=8E=AF?=
=?UTF-8?q?=E5=92=8C=E7=B2=92=E5=AD=90=E6=95=B0=E9=87=8F=E9=99=90=E5=88=B6?=
=?UTF-8?q?=EF=BC=8C=E6=B8=85=E7=90=86=E6=97=A0=E7=94=A8=E4=BB=A3=E7=A0=81?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
src/Web/index.html | 137 +++++++++++++++++++++++++++++++++------------
1 file changed, 101 insertions(+), 36 deletions(-)
diff --git a/src/Web/index.html b/src/Web/index.html
index d1a688e..72f2fc0 100644
--- a/src/Web/index.html
+++ b/src/Web/index.html
@@ -21,7 +21,9 @@
this.opacity = opts.opacity || 1.0;
this.speed = opts.speed || 1.0;
this.maxTrail = opts.maxTrail || 16;
- this.el = opts.el || document.body;
+
+ this.sparkPool = [];
+ this.wavePool = [];
this.waves = [];
this.sparks = [];
@@ -97,32 +99,63 @@
}
boom(x, y) {
- this.waves.push({
- x, y, life: 0, max: 18, r: 0,
- ring: {
- ang: Math.random() * Math.PI * 2,
- segs: [
- { off: -0.25 * Math.PI, len: 1.15 * Math.PI },
- { off: 0.00 * Math.PI, len: 1.15 * Math.PI },
- { off: 0.25 * Math.PI, len: 1.15 * Math.PI }
- ],
- life: 0, maxLife: 30, rs: 0.08
- }
- });
+ // 优化:使用对象池
+ let wave;
+ if (this.wavePool.length > 0) {
+ wave = this.wavePool.pop();
+ wave.x = x;
+ wave.y = y;
+ wave.life = 0;
+ wave.max = 18;
+ wave.r = 0;
+ wave.ring.ang = Math.random() * Math.PI * 2;
+ wave.ring.life = 0;
+ } else {
+ wave = {
+ x, y, life: 0, max: 18, r: 0,
+ ring: {
+ ang: Math.random() * Math.PI * 2,
+ segs: [
+ { off: -0.25 * Math.PI, len: 1.15 * Math.PI },
+ { off: 0.00 * Math.PI, len: 1.15 * Math.PI },
+ { off: 0.25 * Math.PI, len: 1.15 * Math.PI }
+ ],
+ life: 0, maxLife: 30, rs: 0.08
+ }
+ };
+ }
+ this.waves.push(wave);
+ let particleCount = 4;
let speedAdjust = this.scale / 1.5;
- for (let i = 0; i < 8; i++) {
+ for (let i = 0; i < particleCount; i++) {
let a = Math.random() * Math.PI * 2;
let speed = (4.8 + Math.random() * 2) * speedAdjust;
- this.sparks.push({
- x, y,
- vx: Math.cos(a) * speed,
- vy: Math.sin(a) * speed,
- rot: Math.random() * Math.PI * 2,
- rs: (Math.random() - 0.5) * 0.28,
- s: (4 + Math.random() * 3) * this.scale,
- a: 1, f: 0.9
- });
+
+ let spark;
+ if (this.sparkPool.length > 0) {
+ spark = this.sparkPool.pop();
+ spark.x = x;
+ spark.y = y;
+ spark.vx = Math.cos(a) * speed;
+ spark.vy = Math.sin(a) * speed;
+ spark.rot = Math.random() * Math.PI * 2;
+ spark.rs = (Math.random() - 0.5) * 0.28;
+ spark.s = (4 + Math.random() * 3) * this.scale;
+ spark.a = 1;
+ spark.f = 0.9;
+ } else {
+ spark = {
+ x, y,
+ vx: Math.cos(a) * speed,
+ vy: Math.sin(a) * speed,
+ rot: Math.random() * Math.PI * 2,
+ rs: (Math.random() - 0.5) * 0.28,
+ s: (4 + Math.random() * 3) * this.scale,
+ a: 1, f: 0.9
+ };
+ }
+ this.sparks.push(spark);
}
}
@@ -133,6 +166,7 @@
if (this.waves.length > 0 || this.sparks.length > 0 || this.trail.length > 0) {
this.ctx.clearRect(0, 0, this.c.width, this.c.height);
+ // 优化:减少全局合成模式的使用
this.ctx.globalCompositeOperation = "lighter";
for (let i = this.trail.length - 1; i >= 0; i--) {
@@ -146,17 +180,44 @@
}
if (this.trail.length > 1) {
+ // this.ctx.beginPath();
+ // this.ctx.moveTo(this.trail[0].x, this.trail[0].y);
+ // for (let i = 1; i < this.trail.length; i++) {
+ // this.ctx.lineTo(this.trail[i].x, this.trail[i].y);
+ // }
+ // this.ctx.lineWidth = 8.0;
+
+
+ // this.ctx.strokeStyle = `rgba(${this.color},${this.alpha(0.35)})`;
+ // this.ctx.stroke();
+
+
+ // =============新增的屎===============
this.ctx.beginPath();
this.ctx.moveTo(this.trail[0].x, this.trail[0].y);
for (let i = 1; i < this.trail.length; i++) {
this.ctx.lineTo(this.trail[i].x, this.trail[i].y);
}
- this.ctx.lineWidth = 4.5;
- this.ctx.strokeStyle = `rgba(${this.color},${this.alpha(0.35)})`;
- this.ctx.shadowBlur = 10;
- this.ctx.shadowColor = `rgba(${this.color},${this.alpha(0.45)})`;
+ this.ctx.lineWidth = 5.0;
+
+ const meteorHead = this.trail[this.trail.length - 1];
+ const meteorTail = this.trail[0];
+ const gradient = this.ctx.createLinearGradient(
+ meteorHead.x, meteorHead.y,
+ meteorTail.x, meteorTail.y
+ );
+ gradient.addColorStop(0, `rgba(${this.color}, 1)`);
+ gradient.addColorStop(1, `rgba(${this.color}, 0)`);
+
+ this.ctx.shadowColor = `rgba(${this.color}, 0.6)`;
+ this.ctx.shadowBlur = 3;
+ this.ctx.shadowOffsetX = 0;
+ this.ctx.shadowOffsetY = 0;
+
+ this.ctx.strokeStyle = gradient;
this.ctx.stroke();
- this.ctx.shadowBlur = 0;
+ this.ctx.shadowColor = 'transparent';
+
}
for (let i = this.waves.length - 1; i >= 0; i--) {
@@ -170,10 +231,8 @@
this.ctx.beginPath();
this.ctx.arc(w.x, w.y, w.r, 0, Math.PI * 2);
this.ctx.fillStyle = `rgba(${this.color},${this.alpha(alpha)})`;
- this.ctx.shadowBlur = 10;
- this.ctx.shadowColor = `rgba(${this.color},${this.alpha(1)})`;
+ // 优化:减少阴影效果
this.ctx.fill();
- this.ctx.shadowBlur = 0;
}
let r = w.ring;
@@ -188,12 +247,14 @@
this.ctx.arc(w.x, w.y, w.r + 3 * this.scale, start, start + len);
this.ctx.lineWidth = 3.7;
this.ctx.strokeStyle = `rgba(245,248,252,${this.alpha(1 - rProg)})`;
- this.ctx.shadowBlur = 6;
- this.ctx.shadowColor = `rgba(${this.color},${this.alpha(0.75)})`;
+ // 减少阴影效果
this.ctx.stroke();
- this.ctx.shadowBlur = 0;
});
- if (progress >= 1 && rProg >= 1) this.waves.splice(i, 1);
+ if (progress >= 1 && rProg >= 1) {
+ // 回收对象到对象池
+ this.wavePool.push(this.waves[i]);
+ this.waves.splice(i, 1);
+ }
}
for (let i = this.sparks.length - 1; i >= 0; i--) {
@@ -204,7 +265,11 @@
s.vy *= Math.pow(s.f, frameScale);
s.rot += s.rs * frameScale;
s.a -= 0.032 * frameScale;
- if (s.a <= 0) { this.sparks.splice(i, 1); continue; }
+ if (s.a <= 0) {
+ this.sparkPool.push(this.sparks[i]);
+ this.sparks.splice(i, 1);
+ continue;
+ }
this.ctx.save();
this.ctx.translate(s.x, s.y);
From b82bda8f48fa56f6e8700d7f1e31d3ecb4e1753c Mon Sep 17 00:00:00 2001
From: shuimo <1743355601@qq.com>
Date: Tue, 7 Apr 2026 16:10:43 +0800
Subject: [PATCH 2/3] test
---
.vscode/launch.json | 26 ++++++++++++++++++++++++++
.vscode/tasks.json | 41 +++++++++++++++++++++++++++++++++++++++++
2 files changed, 67 insertions(+)
create mode 100644 .vscode/launch.json
create mode 100644 .vscode/tasks.json
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
From 379398146a0eebbd30094d4d7783b0d1976dd616 Mon Sep 17 00:00:00 2001
From: shuimo <1743355601@qq.com>
Date: Sun, 17 May 2026 13:33:38 +0800
Subject: [PATCH 3/3] =?UTF-8?q?=E6=96=B0=E5=A2=9E=E4=BA=86=E8=87=AA?=
=?UTF-8?q?=E5=AE=9A=E4=B9=89html=E7=9A=84=E5=8A=9F=E8=83=BD?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
src/BASpark.csproj | 6 ++
src/ConfigManager.cs | 80 ++++++++++++++++++
src/ControlPanelWindow.xaml | 16 +++-
src/ControlPanelWindow.xaml.cs | 82 ++++++++++++++++++
src/MainWindow.xaml.cs | 95 ++++++++++++++++-----
src/OverlayManager.cs | 22 ++++-
src/Resources/Strings.en.resx | 24 ++++++
src/Resources/Strings.ja.resx | 24 ++++++
src/Resources/Strings.resx | 24 ++++++
src/UiLocalizer.cs | 4 +
src/Web/custom-effect-template.html | 107 ++++++++++++++++++++++++
src/Web/custom-effect-test.html | 125 ++++++++++++++++++++++++++++
12 files changed, 587 insertions(+), 22 deletions(-)
create mode 100644 src/Web/custom-effect-template.html
create mode 100644 src/Web/custom-effect-test.html
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 @@
+
+
+
+
+
+
+
+
+
+
+