feat(decryption):重构QQ音乐解密工具并增强播放功能

- 新增音频播放功能,支持mp3/ogg/flac格式
- 实现可视化频谱显示与粒子效果- 添加播放列表管理与文件拖放支持
- 改进UI设计,使用现代化布局与配色方案
- 增加设置对话框,支持自定义输出路径
- 实现播放控制(播放/暂停/停止)与进度条拖动- 添加文件信息查看与资源管理器定位功能
-优化日志显示与错误处理机制
- 支持快捷键操作(空格切换播放/暂停)- 增强文件列表渲染,支持长文件名换行显示
This commit is contained in:
tzdwindows 7
2025-10-05 16:08:48 +08:00
parent 3d3b626c73
commit d254e57e1f
13 changed files with 2727 additions and 259 deletions

View File

@@ -115,6 +115,14 @@ dependencies {
implementation 'com.github.axet:TarsosDSP:2.4' implementation 'com.github.axet:TarsosDSP:2.4'
implementation 'org.json:json:20231013' implementation 'org.json:json:20231013'
implementation 'org.casbin:casdoor-java-sdk:1.37.0' implementation 'org.casbin:casdoor-java-sdk:1.37.0'
//implementation 'com.googlecode.soundlibs:tritonus-share:0.3.6-2'
implementation 'com.googlecode.soundlibs:mp3spi:1.9.5-1' // mp3 支持
implementation 'com.googlecode.soundlibs:vorbisspi:1.0.3-2' // ogg 支持
//implementation 'com.googlecode.soundlibs:flac:1.3.3-1' // flac 支持(示例)
implementation 'com.googlecode.soundlibs:jorbis:0.0.17-2' // ogg 依赖
implementation 'cn.dev33:sa-token-spring-boot-starter:1.44.0'
} }
configurations.all { configurations.all {

View File

@@ -1,3 +1,3 @@
#Current Loaded Language #Current Loaded Language
#Mon Aug 18 02:11:52 CST 2025 #Sun Oct 05 16:06:50 CST 2025
loadedLanguage=system\:zh_CN loadedLanguage=system\:zh_CN

View File

@@ -41,6 +41,9 @@ material_oceanic_theme.default.tip=\u57FA\u4E8E MaterialLookAndFeel \u7684\u6D45
flatLightLaf_theme.system.topicName=flatLightLaf\u98CE\u683C flatLightLaf_theme.system.topicName=flatLightLaf\u98CE\u683C
flatLightLaf_theme.default.tip=flatLightLaf\u98CE\u683C flatLightLaf_theme.default.tip=flatLightLaf\u98CE\u683C
blur.system.topicName=\u6A21\u7CCA\u98CE\u683C
blur.default.tip=\u6A21\u7CCA\u4E3B\u9898\uFF0C\u9002\u7528\u4E8E\u9AD8\u5BF9\u6BD4\u5EA6\u7684\u4E3B\u9898
mainWindow.title=\u8F74\u521B\u5DE5\u5177\u7BB1 v1.0 mainWindow.title=\u8F74\u521B\u5DE5\u5177\u7BB1 v1.0
mainWindow.title.2=\u8F74\u521B\u5DE5\u5177\u7BB1 mainWindow.title.2=\u8F74\u521B\u5DE5\u5177\u7BB1
mainWindow.settings.title=\u7CFB\u7EDF\u8BBE\u7F6E mainWindow.settings.title=\u7CFB\u7EDF\u8BBE\u7F6E
@@ -91,7 +94,7 @@ fridaWindow.settings.font=\u5B57\u4F53
fridaWindow.settings.size=\u5927\u5C0F fridaWindow.settings.size=\u5927\u5C0F
fridaWindow.settings.theme=\u4E3B\u9898 fridaWindow.settings.theme=\u4E3B\u9898
fridaWindow.menu.settingsMenu.settingsItem.1=\u8BBE\u7F6E fridaWindow.menu.settingsMenu.settingsItem.1=\u8BBE\u7F6E
fridaWindow.menu.help.about=\u5173\u4E8E fridaWindow.menu.help.about=\u5173\u4E8E\u6211\u4EEC
localWindow.newBtn=\u65B0\u5BF9\u8BDD localWindow.newBtn=\u65B0\u5BF9\u8BDD
localWindow.saveBtn=\u4FDD\u5B58\u8BB0\u5F55 localWindow.saveBtn=\u4FDD\u5B58\u8BB0\u5F55
@@ -165,10 +168,10 @@ fridaWindow.font.preview=\u793A\u4F8B\u6587\u672C\uFF1Aconsole.log("Hello Frida"
fridaWindow.about.title=\u5173\u4E8E fridaWindow.about.title=\u5173\u4E8E
fridaWindow.about.content=\u57FA\u4E8EFrida\u7684\u73B0\u4EE3\u6CE8\u5165\u5DE5\u5177\n\n\u7248\u6743\u6240\u6709 \u00A9 {0} fridaWindow.about.content=\u57FA\u4E8EFrida\u7684\u73B0\u4EE3\u6CE8\u5165\u5DE5\u5177\n\n\u7248\u6743\u6240\u6709 \u00A9 {0}
settings.1.title=\u63D2\u4EF6 settings.1.title=\u63D2\u4EF6\u7BA1\u7406
settings.2.title=\u57FA\u7840\u8BBE\u7F6E settings.2.title=\u57FA\u7840\u8BBE\u7F6E
settings.3.title=\u5173\u4E8E settings.3.title=\u5173\u4E8E\u6211\u4EEC
settings.4.title=\u4E3B\u9898 settings.4.title=\u4E3B\u9898\u8BBE\u7F6E
settings.1.tip=\u63D2\u4EF6\u7BA1\u7406 settings.1.tip=\u63D2\u4EF6\u7BA1\u7406
settings.2.tip=\u5916\u89C2\u8BBE\u7F6E settings.2.tip=\u5916\u89C2\u8BBE\u7F6E
settings.3.tip=\u7248\u672C\u4FE1\u606F settings.3.tip=\u7248\u672C\u4FE1\u606F
@@ -194,6 +197,17 @@ settings.2.language=\u754C\u9762\u8BED\u8A00\uFF1A
settings.2.language.error=\u672A\u77E5\u8BED\u8A00 settings.2.language.error=\u672A\u77E5\u8BED\u8A00
settings.3.infoArea.1=\u8F6F\u4EF6\u7248\u672C: settings.3.infoArea.1=\u8F6F\u4EF6\u7248\u672C:
settings.3.infoArea.2=\u5F00\u53D1\u4F5C\u8005: settings.3.infoArea.2=\u5F00\u53D1\u4F5C\u8005:
settings.3.infoArea.description=\u8FD9\u662F\u4E00\u4E2A\u529F\u80FD\u5F3A\u5927\u7684\u521B\u65B0\u5DE5\u5177\u7BB1\uFF0C\u96C6\u6210\u4E86\u591A\u79CD\u5B9E\u7528\u5DE5\u5177\u3002
settings.3.infoArea.features=\u4E3B\u8981\u529F\u80FD
settings.3.infoArea.feature1=\u6570\u636E\u5206\u6790\u548C\u53EF\u89C6\u5316
settings.3.infoArea.feature2=\u81EA\u52A8\u5316\u5904\u7406
settings.3.infoArea.feature3=\u81EA\u5B9A\u4E49\u63D2\u4EF6\u652F\u6301
settings.3.infoArea.email=tzdwindows 7
settings.3.infoArea.website=https://axisInnovatorsBox.com
settings.3.infoArea.copyright=\u7248\u6743\u6240\u6709 \u00A9 2025 Axis Innovators. \u4FDD\u7559\u6240\u6709\u6743\u5229\u3002
settings.3.infoArea.requirement1=Windows 10 \u6216\u66F4\u9AD8\u7248\u672C
settings.3.infoArea.requirement2=\u81F3\u5C11 4GB \u5185\u5B58
settings.3.infoArea.support=\u5982\u6709\u95EE\u9898\u8BF7\u8054\u7CFB\u6280\u672F\u652F\u6301\u56E2\u961F
settings.4.no_theme=\u6CA1\u6709\u53EF\u7528\u7684\u4E3B\u9898 settings.4.no_theme=\u6CA1\u6709\u53EF\u7528\u7684\u4E3B\u9898
settings.4.search=\u641C\u7D22 settings.4.search=\u641C\u7D22
settings.4.search_empty=\u8BF7\u8F93\u5165\u641C\u7D22\u5185\u5BB9\uFF01 settings.4.search_empty=\u8BF7\u8F93\u5165\u641C\u7D22\u5185\u5BB9\uFF01

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@@ -21,6 +21,8 @@ import com.axis.innovators.box.verification.CasdoorServer;
import com.axis.innovators.box.verification.LoginData; import com.axis.innovators.box.verification.LoginData;
import com.formdev.flatlaf.themes.FlatMacDarkLaf; import com.formdev.flatlaf.themes.FlatMacDarkLaf;
import com.formdev.flatlaf.themes.FlatMacLightLaf; import com.formdev.flatlaf.themes.FlatMacLightLaf;
import com.jogamp.nativewindow.swt.SWTAccessor;
import com.sun.javafx.stage.WindowHelper;
import com.sun.management.HotSpotDiagnosticMXBean; import com.sun.management.HotSpotDiagnosticMXBean;
import mdlaf.MaterialLookAndFeel; import mdlaf.MaterialLookAndFeel;
import mdlaf.themes.JMarsDarkTheme; import mdlaf.themes.JMarsDarkTheme;
@@ -65,6 +67,7 @@ public class AxisInnovatorsBox {
private static final String[] AUTHOR = new String[]{ private static final String[] AUTHOR = new String[]{
"tzdwindows 7", "tzdwindows 7",
"lyxyz5223", "lyxyz5223",
"\uD83D\uDC3EMr. Liu\uD83D\uDC3E",
}; };
/** 我是总任务数 **/ /** 我是总任务数 **/
@@ -75,6 +78,7 @@ public class AxisInnovatorsBox {
LanguageManager.getLoadedLanguages().getText("progressBarManager.title"), LanguageManager.getLoadedLanguages().getText("progressBarManager.title"),
totalTasks); totalTasks);
private static AxisInnovatorsBox main; private static AxisInnovatorsBox main;
private final boolean quickStart;
private MainWindow ex; private MainWindow ex;
private Thread thread; private Thread thread;
private final String[] args; private final String[] args;
@@ -88,19 +92,33 @@ public class AxisInnovatorsBox {
private static LoginData loginData; private static LoginData loginData;
public AxisInnovatorsBox(String[] args, boolean isDebug) { public AxisInnovatorsBox(String[] args, boolean isDebug, boolean quickStart) {
this.args = args; this.args = args;
this.isDebug = isDebug; this.isDebug = isDebug;
this.quickStart = quickStart;
Thread.setDefaultUncaughtExceptionHandler((thread, throwable) -> { Thread.setDefaultUncaughtExceptionHandler((thread, throwable) -> {
organizingCrashReports(throwable instanceof Exception ? organizingCrashReports(throwable instanceof Exception ?
(Exception) throwable : new Exception(throwable)); (Exception) throwable : new Exception(throwable));
}); });
if (quickStart){
initLog4j2();
setTopic();
main = this;
}
}
/**
* 判断当前环境是否为快速启动环境
*/
public boolean getQuickStart() {
return quickStart;
} }
/** /**
* 弹出登录窗口 * 弹出登录窗口
*/ */
private void popupLogin(){ private void popupLogin() {
// 加载登录信息,如果没有,弹出登录弹窗,后续可以删掉默认弹出 // 加载登录信息,如果没有,弹出登录弹窗,后续可以删掉默认弹出
// TODO: login window should not be show when AxisInnovatorsBox initialize, // TODO: login window should not be show when AxisInnovatorsBox initialize,
// it should be show when user click login button. // it should be show when user click login button.
@@ -905,6 +923,14 @@ public class AxisInnovatorsBox {
true true
); );
//main.registrationTopic.addTopic(
// new BlurTopic(),
// LanguageManager.getLoadedLanguages().getText("blur.system.topicName"),
// LanguageManager.getLoadedLanguages().getText("blur.default.tip"),
// LoadIcon.loadIcon(MainWindow.class, "logo.png", 64),
// "system:blur",true
//);
LookAndFeel defaultLaf = isDarkMode ? new FlatMacDarkLaf() : new FlatMacLightLaf(); LookAndFeel defaultLaf = isDarkMode ? new FlatMacDarkLaf() : new FlatMacLightLaf();
UIManager.setLookAndFeel(defaultLaf); UIManager.setLookAndFeel(defaultLaf);
main.registrationTopic.setLoading( main.registrationTopic.setLoading(
@@ -991,40 +1017,41 @@ public class AxisInnovatorsBox {
debugWindow.setVisible(true); debugWindow.setVisible(true);
} }
public static void run(String[] args, boolean isDebug) { public static void run(String[] args, boolean isDebug, boolean quickStart) {
Thread.setDefaultUncaughtExceptionHandler((thread, throwable) -> { Thread.setDefaultUncaughtExceptionHandler((thread, throwable) -> {
if (main != null) { if (main != null) {
main.organizingCrashReports(throwable instanceof Exception ? main.organizingCrashReports(throwable instanceof Exception ?
(Exception) throwable : new Exception(throwable)); (Exception) throwable : new Exception(throwable));
} else { } else {
new AxisInnovatorsBox(args, isDebug) new AxisInnovatorsBox(args, isDebug,true)
.organizingCrashReports(throwable instanceof Exception ? .organizingCrashReports(throwable instanceof Exception ?
(Exception) throwable : new Exception(throwable)); (Exception) throwable : new Exception(throwable));
} }
}); });
// 设置EDT(事件调度线程)的异常处理器 // Set the exception handler for EDT(event dispatcher thread)
System.setProperty("sun.awt.exception.handler", EDTCrashHandler.class.getName()); System.setProperty("sun.awt.exception.handler", EDTCrashHandler.class.getName());
main = new AxisInnovatorsBox(args,isDebug); // Check if AxisInnovatorsBox is started
// If it's started, and it's not a quick start, don't allow it
// because it's already started
// Stop loading if the current running context is the quickStart context
if (AxisInnovatorsBox.getMain() != null
&& !AxisInnovatorsBox.getMain().getQuickStart() || quickStart) {
// Manually created if it is a quickStart context and the AxisInnovatorsBox instance in the context is empty
if (AxisInnovatorsBox.getMain() == null && quickStart) {
new AxisInnovatorsBox(args,isDebug,true);
}
return;
}
main = new AxisInnovatorsBox(args,isDebug,false);
try { try {
main.initLog4j2(); main.initLog4j2();
main.setTopic(); main.setTopic();
main.popupLogin(); //main.popupLogin();
List<Map<String, String>> validFiles = ArgsParser.parseArgs(args);
for (Map<String, String> fileInfo : validFiles) {
String extension = fileInfo.get("extension");
String path = fileInfo.get("path");
OpenFileEvents openFileEvents = new OpenFileEvents(path, extension);
GlobalEventBus.EVENT_BUS.post(openFileEvents);
if (!openFileEvents.isContinue()) {
return;
}
}
main.thread = new Thread(() -> { main.thread = new Thread(() -> {
try { try {
// 主任务1加载插件 // 主任务1加载插件
logger.info("Loaded plugins Started"); logger.info("Loaded plugins Started");
@@ -1033,6 +1060,17 @@ public class AxisInnovatorsBox {
PluginPyLoader.loadAllPlugins(); PluginPyLoader.loadAllPlugins();
logger.info("Loaded plugins End"); logger.info("Loaded plugins End");
List<Map<String, String>> validFiles = ArgsParser.parseArgs(args);
for (Map<String, String> fileInfo : validFiles) {
String extension = fileInfo.get("extension");
String path = fileInfo.get("path");
OpenFileEvents openFileEvents = new OpenFileEvents(path, extension);
GlobalEventBus.EVENT_BUS.post(openFileEvents);
if (!openFileEvents.isContinue()) {
return;
}
}
main.progressBarManager.close(); main.progressBarManager.close();
SwingUtilities.invokeLater(() -> { SwingUtilities.invokeLater(() -> {
@@ -1111,6 +1149,7 @@ public class AxisInnovatorsBox {
} }
ex.initUI(); ex.initUI();
RegistrationSettingsItem.applyAllSettings();
isWindow = true; isWindow = true;
ex.setVisible(true); ex.setVisible(true);

View File

@@ -6,6 +6,7 @@ import com.axis.innovators.box.register.LanguageManager;
import com.axis.innovators.box.tools.ArgsParser; import com.axis.innovators.box.tools.ArgsParser;
import com.axis.innovators.box.tools.FolderCleaner; import com.axis.innovators.box.tools.FolderCleaner;
import com.axis.innovators.box.tools.FolderCreator; import com.axis.innovators.box.tools.FolderCreator;
import org.QQdecryption.ui.DecryptionUI;
import javax.swing.*; import javax.swing.*;
import java.io.File; import java.io.File;
@@ -14,10 +15,7 @@ import java.io.RandomAccessFile;
import java.nio.channels.FileChannel; import java.nio.channels.FileChannel;
import java.nio.channels.FileLock; import java.nio.channels.FileLock;
import java.nio.channels.OverlappingFileLockException; import java.nio.channels.OverlappingFileLockException;
import java.text.Format; import java.util.*;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
/** /**
* @author tzdwindows 7 * @author tzdwindows 7
@@ -30,53 +28,46 @@ public class Main {
private static FileChannel lockChannel = null; private static FileChannel lockChannel = null;
private final static boolean releaseEnvironments = false; private final static boolean releaseEnvironments = false;
public static void main(String[] args) { public static void main(String[] args) {
// 清理日志文件最大日志为10
FolderCleaner.cleanFolder(FolderCreator.getLogsFolder(), 10); FolderCleaner.cleanFolder(FolderCreator.getLogsFolder(), 10);
// 加载保存的语言
LanguageManager.loadSavedLanguage(); LanguageManager.loadSavedLanguage();
// 如果没有加载的语言,则加载默认的语言
if (LanguageManager.getLoadedLanguages() == null) { if (LanguageManager.getLoadedLanguages() == null) {
LanguageManager.loadLanguage("system:zh_CN"); LanguageManager.loadLanguage("system:zh_CN");
} }
// 检查是否包含调试控制台参数
boolean debugWindowEnabled = false; boolean debugWindowEnabled = false;
String pluginsDirectory = null; String pluginsDirectory = null;
// 新增:预处理参数,移除已处理的参数
List<String> remainingArgs = new ArrayList<>(); List<String> remainingArgs = new ArrayList<>();
for (int i = 0; i < args.length; i++) { for (String arg : args) {
if (!releaseEnvironments && "-debugControlWindow-on".equals(args[i])) { if (!releaseEnvironments && "-debugControlWindow-on".equals(arg)) {
debugWindowEnabled = true; debugWindowEnabled = true;
// 不添加到剩余参数中
continue; continue;
} }
// 新增解析pluginsDirectory参数 if (arg.startsWith("pluginsDirectory=")) {
if (args[i].startsWith("pluginsDirectory=")) { pluginsDirectory = arg.substring("pluginsDirectory=".length());
pluginsDirectory = args[i].substring("pluginsDirectory=".length());
// 移除引号(如果存在)
if (pluginsDirectory.startsWith("\"") && pluginsDirectory.endsWith("\"")) { if (pluginsDirectory.startsWith("\"") && pluginsDirectory.endsWith("\"")) {
pluginsDirectory = pluginsDirectory.substring(1, pluginsDirectory.length() - 1); pluginsDirectory = pluginsDirectory.substring(1, pluginsDirectory.length() - 1);
} }
// 不添加到剩余参数中
continue; continue;
} }
remainingArgs.add(arg);
// 其他参数保留
remainingArgs.add(args[i]);
} }
// 如果有设置插件目录,在这里进行设置
if (pluginsDirectory != null && !pluginsDirectory.isEmpty()) { if (pluginsDirectory != null && !pluginsDirectory.isEmpty()) {
System.out.println("Setting up the plugin directory: " + pluginsDirectory); System.out.println("Setting up the plugin directory: " + pluginsDirectory);
FolderCreator.setPluginPath(pluginsDirectory); FolderCreator.setPluginPath(pluginsDirectory);
} }
boolean quickStart = false;
String[] processedArgs = remainingArgs.toArray(new String[0]); String[] processedArgs = remainingArgs.toArray(new String[0]);
final Set<String> mUSICEXTS = new HashSet<>(Arrays.asList(
".mflac", ".mgg", ".qmc0", ".qmc3", ".qmcflac", ".qmcogg",
".tkm", ".qmc2", ".bkcmp3", ".bkcflac", ".ogg"
));
List<Map<String, String>> validFiles = ArgsParser.parseArgs(processedArgs); List<Map<String, String>> validFiles = ArgsParser.parseArgs(processedArgs);
for (Map<String, String> fileInfo : validFiles) { for (Map<String, String> fileInfo : validFiles) {
String extension = fileInfo.get("extension"); String extension = fileInfo.get("extension");
@@ -85,27 +76,35 @@ public class Main {
SwingUtilities.invokeLater(() -> { SwingUtilities.invokeLater(() -> {
try { try {
UIManager.setLookAndFeel(new com.formdev.flatlaf.FlatDarculaLaf()); UIManager.setLookAndFeel(new com.formdev.flatlaf.FlatDarculaLaf());
} catch (Exception ex) { } catch (Exception ignored) {}
ex.printStackTrace();
}
ModernJarViewer viewer = new ModernJarViewer(null, path); ModernJarViewer viewer = new ModernJarViewer(null, path);
viewer.setVisible(true); viewer.setVisible(true);
}); });
releaseLock(); // 释放锁(窗口模式) releaseLock(); // 释放锁(窗口模式)
return; quickStart = true;
} }
if (".html".equals(extension)) { if (".html".equals(extension)) {
MainApplication.popupHTMLWindow(path); MainApplication.popupHTMLWindow(path);
releaseLock(); releaseLock();
return; quickStart = true;
}
if (extension != null && mUSICEXTS.contains(extension.toLowerCase(Locale.ROOT))) {
final String p = path;
SwingUtilities.invokeLater(() -> {
DecryptionUI ui = new DecryptionUI(p);
ui.setVisible(true);
});
releaseLock();
quickStart = true;
} }
} }
if (!acquireLock()) { if (!acquireLock()) {
return; return;
} }
AxisInnovatorsBox.run(processedArgs, debugWindowEnabled); AxisInnovatorsBox.run(processedArgs, debugWindowEnabled,quickStart);
} }
/** /**

View File

@@ -0,0 +1,17 @@
package com.axis.innovators.box.login;
import cn.dev33.satoken.stp.StpUtil;
/**
* @author tzdwindows 7
*/
public class LoginStpUtil {
public static void login(Object id){
StpUtil.login(id);
}
public static void main(String[] args) {
LoginStpUtil.login(1);
}
}

View File

@@ -14,14 +14,18 @@ import javax.swing.border.Border;
import javax.swing.event.DocumentEvent; import javax.swing.event.DocumentEvent;
import javax.swing.event.DocumentListener; import javax.swing.event.DocumentListener;
import javax.swing.plaf.FontUIResource; import javax.swing.plaf.FontUIResource;
import javax.swing.plaf.LayerUI;
import javax.swing.plaf.PanelUI; import javax.swing.plaf.PanelUI;
import javax.swing.plaf.basic.BasicScrollBarUI; import javax.swing.plaf.basic.BasicScrollBarUI;
import javax.swing.plaf.basic.BasicTabbedPaneUI; import javax.swing.plaf.basic.BasicTabbedPaneUI;
import java.awt.*; import java.awt.*;
import java.awt.event.*; import java.awt.event.*;
import java.awt.geom.AffineTransform;
import java.awt.geom.Point2D; import java.awt.geom.Point2D;
import java.awt.geom.RoundRectangle2D; import java.awt.geom.RoundRectangle2D;
import java.awt.image.BufferedImage; import java.awt.image.BufferedImage;
import java.awt.image.ConvolveOp;
import java.awt.image.Kernel;
import java.util.*; import java.util.*;
import java.util.List; import java.util.List;
import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentHashMap;
@@ -34,6 +38,8 @@ import java.util.concurrent.ConcurrentHashMap;
* - 侧栏颜色使用 #3C3F41按你的要求 * - 侧栏颜色使用 #3C3F41按你的要求
* - 搜索框以居中为主,聚焦时带放大动画与圆角外观 * - 搜索框以居中为主,聚焦时带放大动画与圆角外观
* - 尽量避免离屏绘制遗留(使用 component.printAll() 截图) * - 尽量避免离屏绘制遗留(使用 component.printAll() 截图)
* - 添加背景图片和玻璃模糊效果支持
* @author tzdwindows 7
*/ */
public class MainWindow extends JFrame { public class MainWindow extends JFrame {
private static final Logger logger = LogManager.getLogger(MainWindow.class); private static final Logger logger = LogManager.getLogger(MainWindow.class);
@@ -56,27 +62,18 @@ public class MainWindow extends JFrame {
private JPanel sideBar; private JPanel sideBar;
private JPanel contentPanel; private JPanel contentPanel;
private RoundedSearchField searchField; private RoundedSearchField searchField;
private JLabel status;
// 背景图片相关字段
private Image backgroundImage;
private float blurAmount = 0.0f;
private float backgroundOpacity = 1.0f;
private BufferedImage cachedBlurredBackground;
private Dimension cachedBackgroundSize;
// settings dialog // settings dialog
private WindowsJDialog dialog; private WindowsJDialog dialog;
// 侧边栏颜色(比面板背景稍暗)
private static Color SIDEBAR_COLOR = Optional.ofNullable(UIManager.getColor("Panel.background"))
.orElse(new Color(0x3C3F41));
// 卡片背景(深色模式适配,比侧边栏稍亮)
private static Color CARD_BG = Optional.ofNullable(UIManager.getColor("control"))
.map(bg -> ThemeColors.brighten(bg, 0.1f))
.orElse(new Color(0x4A4D50));
// 卡片边框(使用系统边框色或稍亮颜色)
private static Color CARD_BORDER = Optional.ofNullable(UIManager.getColor("controlHighlight"))
.orElse(new Color(0x5C5F61));
// 文本颜色(直接使用系统主题的文本颜色)
private static Color TEXT_COLOR = Optional.ofNullable(UIManager.getColor("textText"))
.orElse(new Color(0xE0E0E0));
public MainWindow() { public MainWindow() {
// 增强字体设置逻辑:优先使用系统支持的中文字体 // 增强字体设置逻辑:优先使用系统支持的中文字体
String[] fontNames = {"Microsoft YaHei", "微软雅黑", "PingFang SC", "SimHei", "宋体", "新宋体", "SansSerif"}; String[] fontNames = {"Microsoft YaHei", "微软雅黑", "PingFang SC", "SimHei", "宋体", "新宋体", "SansSerif"};
@@ -123,13 +120,132 @@ public class MainWindow extends JFrame {
if (layeredPane != null && cardsPanel != null) { if (layeredPane != null && cardsPanel != null) {
cardsPanel.setBounds(0, 0, layeredPane.getWidth(), layeredPane.getHeight()); cardsPanel.setBounds(0, 0, layeredPane.getWidth(), layeredPane.getHeight());
} }
// 窗口大小改变时重新生成模糊背景
if (backgroundImage != null) {
cachedBlurredBackground = null;
repaint();
}
} }
}); });
setLocationRelativeTo(null); //setLocationRelativeTo(null);
addComponentListener(new ComponentAdapter() {
@Override
public void componentShown(ComponentEvent e) {
// 确保只执行一次
removeComponentListener(this);
setLocationRelativeTo(null);
}
});
setDefaultCloseOperation(DISPOSE_ON_CLOSE); setDefaultCloseOperation(DISPOSE_ON_CLOSE);
setSize(1200, 800); setSize(1060, 670);
}
/**
* 设置背景图片和玻璃模糊效果
* @param backgroundImage 背景图片
* @param blurAmount 模糊程度 (0.0f - 1.0f)0为不模糊1为最大模糊
* @param opacity 透明度 (0.0f - 1.0f)0为完全透明1为完全不透明
*/
public void setBackgroundWithGlassEffect(Image backgroundImage, float blurAmount, float opacity) {
this.backgroundImage = backgroundImage;
this.blurAmount = Math.max(0.0f, Math.min(1.0f, blurAmount));
this.backgroundOpacity = Math.max(0.0f, Math.min(1.0f, opacity));
this.cachedBlurredBackground = null;
this.cachedBackgroundSize = null;
// 重新绘制窗口
//if (AxisInnovatorsBox.getMain().isWindow())
// AxisInnovatorsBox.getMain().reloadAllWindow();
}
/**
* 移除背景图片
*/
public void removeBackground() {
this.backgroundImage = null;
this.cachedBlurredBackground = null;
this.cachedBackgroundSize = null;
repaint();
}
/**
* 应用高斯模糊到图片
*/
private BufferedImage applyGaussianBlur(BufferedImage image, float blurFactor) {
if (blurFactor <= 0.0f) return image;
// 根据模糊因子计算模糊半径 (1-15像素)
int radius = Math.max(1, (int)(blurFactor * 15));
// 确保半径为奇数
if (radius % 2 == 0) radius++;
int size = radius * 2 + 1;
float[] data = new float[size * size];
// 计算高斯核
float sigma = radius / 3.0f;
float twoSigmaSquare = 2.0f * sigma * sigma;
float sigmaRoot = (float) Math.sqrt(twoSigmaSquare * Math.PI);
float total = 0.0f;
for (int i = -radius; i <= radius; i++) {
for (int j = -radius; j <= radius; j++) {
float distance = i * i + j * j;
data[(i + radius) * size + (j + radius)] =
(float) Math.exp(-distance / twoSigmaSquare) / sigmaRoot;
total += data[(i + radius) * size + (j + radius)];
}
}
// 归一化
for (int i = 0; i < data.length; i++) {
data[i] /= total;
}
Kernel kernel = new Kernel(size, size, data);
ConvolveOp convolve = new ConvolveOp(kernel, ConvolveOp.EDGE_NO_OP, null);
return convolve.filter(image, null);
}
/**
* 创建带模糊效果的背景图片
*/
private BufferedImage createBlurredBackground(Dimension size) {
if (backgroundImage == null) return null;
// 如果尺寸相同且已有缓存,直接返回缓存
if (cachedBlurredBackground != null && cachedBackgroundSize != null &&
cachedBackgroundSize.equals(size)) {
return cachedBlurredBackground;
}
// 创建背景图片
BufferedImage bgImage = new BufferedImage(size.width, size.height, BufferedImage.TYPE_INT_ARGB);
Graphics2D g2d = bgImage.createGraphics();
// 设置渲染质量
g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
g2d.setRenderingHint(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY);
g2d.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BILINEAR);
// 绘制原始背景图片(缩放以适应窗口)
g2d.drawImage(backgroundImage, 0, 0, size.width, size.height, null);
g2d.dispose();
// 应用模糊效果
if (blurAmount > 0.0f) {
bgImage = applyGaussianBlur(bgImage, blurAmount);
}
// 缓存结果
cachedBlurredBackground = bgImage;
cachedBackgroundSize = new Dimension(size);
return bgImage;
} }
/** /**
@@ -158,13 +274,56 @@ public class MainWindow extends JFrame {
setTitle(LanguageManager.getLoadedLanguages().getText("mainWindow.title")); setTitle(LanguageManager.getLoadedLanguages().getText("mainWindow.title"));
// 主容器 // 主容器 - 使用自定义面板以支持背景绘制
JPanel mainPanel = new JPanel(new BorderLayout()); JPanel mainPanel = new JPanel(new BorderLayout()) {
@Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
// 如果有背景图片,绘制背景
if (backgroundImage != null) {
Graphics2D g2d = (Graphics2D) g.create();
// 设置透明度
g2d.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER, backgroundOpacity));
// 获取带模糊效果的背景
Dimension size = getSize();
BufferedImage bg = createBlurredBackground(size);
if (bg != null) {
g2d.drawImage(bg, 0, 0, null);
}
g2d.dispose();
}
}
};
mainPanel.setBorder(BorderFactory.createEmptyBorder()); mainPanel.setBorder(BorderFactory.createEmptyBorder());
mainPanel.setOpaque(true); mainPanel.setOpaque(true);
Color panelBg = UIManager.getColor("Panel.background");
if (panelBg == null) panelBg = new Color(245, 246, 248); // 设置背景色(如果有背景图片,则使用半透明背景)
mainPanel.setBackground(panelBg); if (backgroundImage != null) {
// 当有背景图片时,使用半透明的背景色
Color panelBg = UIManager.getColor("Panel.background");
if (panelBg != null) {
// 降低背景色的不透明度,让背景图片透出来
Color semiTransparentBg = new Color(
panelBg.getRed(),
panelBg.getGreen(),
panelBg.getBlue(),
(int)(200 * backgroundOpacity) // 调整透明度
);
mainPanel.setBackground(semiTransparentBg);
}
} else {
// 没有背景图片时使用正常背景色
Color panelBg = UIManager.getColor("Panel.background");
if (panelBg == null) panelBg = new Color(245, 246, 248);
mainPanel.setBackground(panelBg);
}
mainPanel.setBorder(BorderFactory.createEmptyBorder(12, 12, 12, 12)); mainPanel.setBorder(BorderFactory.createEmptyBorder(12, 12, 12, 12));
mainPanel.add(createHeader(), BorderLayout.NORTH); mainPanel.add(createHeader(), BorderLayout.NORTH);
@@ -288,7 +447,19 @@ public class MainWindow extends JFrame {
JComponent contentPane = (JComponent) content; JComponent contentPane = (JComponent) content;
Color panelBg = UIManager.getColor("Panel.background"); Color panelBg = UIManager.getColor("Panel.background");
if (panelBg == null) panelBg = new Color(245, 246, 248); if (panelBg == null) panelBg = new Color(245, 246, 248);
contentPane.setBackground(panelBg);
// 如果有背景图片,使用半透明背景
if (backgroundImage != null) {
Color semiTransparentBg = new Color(
panelBg.getRed(),
panelBg.getGreen(),
panelBg.getBlue(),
(int)(200 * backgroundOpacity)
);
contentPane.setBackground(semiTransparentBg);
} else {
contentPane.setBackground(panelBg);
}
contentPane.repaint(); contentPane.repaint();
} }
@@ -309,7 +480,12 @@ public class MainWindow extends JFrame {
} }
if (sideBar != null) { if (sideBar != null) {
sideBar.setBackground(getSidebarColor()); if (backgroundImage != null) {
sideBar.setOpaque(false);
} else {
sideBar.setOpaque(true);
sideBar.setBackground(getSidebarColor());
}
sideBar.repaint(); sideBar.repaint();
} }
@@ -539,7 +715,7 @@ public class MainWindow extends JFrame {
private JPanel createSideBar() { private JPanel createSideBar() {
JPanel sidebar = new JPanel(new BorderLayout()); JPanel sidebar = new JPanel(new BorderLayout());
sidebar.setOpaque(true); sidebar.setOpaque(true);
sidebar.setBackground(SIDEBAR_COLOR); //sidebar.setBackground(SIDEBAR_COLOR);
sidebar.setPreferredSize(new Dimension(220, getHeight())); sidebar.setPreferredSize(new Dimension(220, getHeight()));
sidebar.setBorder(null); sidebar.setBorder(null);
@@ -574,7 +750,7 @@ public class MainWindow extends JFrame {
JScrollPane listScroll = new JScrollPane(listPanel); JScrollPane listScroll = new JScrollPane(listPanel);
listScroll.setBorder(null); listScroll.setBorder(null);
listScroll.setOpaque(false); listScroll.setOpaque(false);
listScroll.getViewport().setOpaque(false); listScroll.getViewport().setOpaque(false); // 确保视口透明
listScroll.setHorizontalScrollBarPolicy(ScrollPaneConstants.HORIZONTAL_SCROLLBAR_NEVER); listScroll.setHorizontalScrollBarPolicy(ScrollPaneConstants.HORIZONTAL_SCROLLBAR_NEVER);
listScroll.setVerticalScrollBarPolicy(ScrollPaneConstants.VERTICAL_SCROLLBAR_AS_NEEDED); listScroll.setVerticalScrollBarPolicy(ScrollPaneConstants.VERTICAL_SCROLLBAR_AS_NEEDED);
listScroll.getVerticalScrollBar().setUI(new CustomScrollBarUI()); listScroll.getVerticalScrollBar().setUI(new CustomScrollBarUI());
@@ -680,7 +856,8 @@ public class MainWindow extends JFrame {
)); ));
// 样式 // 样式
button.setForeground(TEXT_COLOR); button.setForeground(Optional.ofNullable(UIManager.getColor("textText"))
.orElse(new Color(0xE0E0E0)));
// 初始设为不填充,由 updateSelection 决定是否填充背景 // 初始设为不填充,由 updateSelection 决定是否填充背景
button.setOpaque(false); button.setOpaque(false);
button.setContentAreaFilled(true); // 允许根据 opaque/background 填充mouse/selected 状态会切换 opaque button.setContentAreaFilled(true); // 允许根据 opaque/background 填充mouse/selected 状态会切换 opaque
@@ -778,6 +955,14 @@ public class MainWindow extends JFrame {
Color cardBorder = getCardBorder(); Color cardBorder = getCardBorder();
Color shadowColor = isDarkTheme() ? new Color(30, 30, 30) : Color.BLACK; Color shadowColor = isDarkTheme() ? new Color(30, 30, 30) : Color.BLACK;
// 如果有背景图片,卡片背景使用半透明
if (backgroundImage != null) {
cardBg = new Color(cardBg.getRed(), cardBg.getGreen(), cardBg.getBlue(),
(int)(200 * backgroundOpacity));
cardBorder = new Color(cardBorder.getRed(), cardBorder.getGreen(),
cardBorder.getBlue(), (int)(200 * backgroundOpacity));
}
// 1. 绘制阴影(根据主题调整透明度) // 1. 绘制阴影(根据主题调整透明度)
g2.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER, isDarkTheme() ? 0.12f : 0.06f)); g2.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER, isDarkTheme() ? 0.12f : 0.06f));
g2.setColor(shadowColor); g2.setColor(shadowColor);
@@ -826,8 +1011,10 @@ public class MainWindow extends JFrame {
titleLabel.setForeground(UIManager.getColor("Label.foreground")); titleLabel.setForeground(UIManager.getColor("Label.foreground"));
JTextArea descArea = new JTextArea(tool.getDescription()); JTextArea descArea = new JTextArea(tool.getDescription());
descArea.setFont(new Font(selectFont("Segoe UI", "Microsoft YaHei", "SansSerif", 13).getName(), Font.PLAIN, 13)); descArea.setFont(new Font(selectFont("Segoe UI", "Microsoft YaHei", "SansSerif", 13).getName(), Font.PLAIN, 13));
titleLabel.setForeground(TEXT_COLOR); titleLabel.setForeground(Optional.ofNullable(UIManager.getColor("textText"))
descArea.setForeground(TEXT_COLOR.darker()); .orElse(new Color(0xE0E0E0)));
descArea.setForeground(Optional.ofNullable(UIManager.getColor("textText"))
.orElse(new Color(0xE0E0E0)).darker());
descArea.setForeground(new Color(100,100,105)); descArea.setForeground(new Color(100,100,105));
descArea.setLineWrap(true); descArea.setLineWrap(true);
descArea.setWrapStyleWord(true); descArea.setWrapStyleWord(true);
@@ -925,6 +1112,7 @@ public class MainWindow extends JFrame {
card.repaint(); card.repaint();
if (currentElevation == targetElevation && Math.abs(cardScales.get(card) - targetScale) < 0.002f) ((Timer)e.getSource()).stop(); if (currentElevation == targetElevation && Math.abs(cardScales.get(card) - targetScale) < 0.002f) ((Timer)e.getSource()).stop();
} }
}).start(); }).start();
} }
@@ -1035,13 +1223,21 @@ public class MainWindow extends JFrame {
JPanel footer = new JPanel(new BorderLayout()); JPanel footer = new JPanel(new BorderLayout());
footer.setOpaque(false); footer.setOpaque(false);
footer.setBorder(BorderFactory.createEmptyBorder(8, 8, 8, 8)); footer.setBorder(BorderFactory.createEmptyBorder(8, 8, 8, 8));
JLabel status = new JLabel("Ready"); status = new JLabel("Ready");
status.setFont(new Font(selectFont("Segoe UI", "Microsoft YaHei", "SansSerif", 12).getName(), Font.PLAIN, 12)); status.setFont(new Font(selectFont("Segoe UI", "Microsoft YaHei", "SansSerif", 12).getName(), Font.PLAIN, 12));
status.setForeground(UIManager.getColor("Label.foreground")); status.setForeground(UIManager.getColor("Label.foreground"));
footer.add(status, BorderLayout.WEST); footer.add(status, BorderLayout.WEST);
return footer; return footer;
} }
/**
* 更新状态
* @param text 状态名称
*/
public void updataStatus(String text) {
status.setText(text);
}
private void updateSideSelection(String categoryId) { private void updateSideSelection(String categoryId) {
for (Map.Entry<String, JButton> e : sideButtons.entrySet()) { for (Map.Entry<String, JButton> e : sideButtons.entrySet()) {
String id = e.getKey(); String id = e.getKey();
@@ -1138,36 +1334,127 @@ public class MainWindow extends JFrame {
} }
} }
// ---------- showSettings 恢复实现 ---------- // ---------- showSettings 实现 ----------
public void showSettings() { public void showSettings() {
if (dialog == null || AxisInnovatorsBox.getMain().isWindowStartup(dialog)) { if (dialog == null || AxisInnovatorsBox.getMain().isWindowStartup(dialog)) {
dialog = new WindowsJDialog(this, dialog = new WindowsJDialog(this,
LanguageManager.getLoadedLanguages().getText("mainWindow.settings.title"), true); LanguageManager.getLoadedLanguages().getText("mainWindow.settings.title"), true);
} }
dialog.setTitle(LanguageManager.getLoadedLanguages().getText("mainWindow.settings.title")); dialog.setTitle(LanguageManager.getLoadedLanguages().getText("mainWindow.settings.title"));
dialog.setSize(680, 480); dialog.setSize(750, 550);
dialog.setLocationRelativeTo(this); dialog.setLocationRelativeTo(this);
JPanel content = new JPanel(new BorderLayout());
content.setBorder(BorderFactory.createEmptyBorder(12, 12, 12, 12));
content.setOpaque(false);
JTabbedPane tabbedPane = new JTabbedPane(); // 使用 JLayer + LayerUI 来对整个内容做统一的淡入 + 下滑(仿 Apple 风格)动画,
// 这样子组件也会跟随一起动画,而不是只有背景绘制发生变化。
JPanel inner = new JPanel(new BorderLayout());
inner.setBorder(BorderFactory.createEmptyBorder(12, 12, 12, 12));
inner.setOpaque(false); // 背景在 LayerUI 中绘制以便做阴影/圆角
JTabbedPane tabbedPane = new JTabbedPane(JTabbedPane.LEFT);
tabbedPane.setOpaque(false);
tabbedPane.setBorder(BorderFactory.createEmptyBorder(6, 6, 6, 6));
List<RegistrationSettingsItem> registrationSettingsItemList List<RegistrationSettingsItem> registrationSettingsItemList
= RegistrationSettingsItem.getRegistrationSettingsItemList(); = RegistrationSettingsItem.getRegistrationSettingsItemList();
for (RegistrationSettingsItem registrationSettingsItem : registrationSettingsItemList) { for (RegistrationSettingsItem registrationSettingsItem : registrationSettingsItemList) {
registrationSettingsItem.registration(tabbedPane); registrationSettingsItem.registration(tabbedPane);
} }
content.add(tabbedPane, BorderLayout.CENTER); inner.add(tabbedPane, BorderLayout.CENTER);
GlobalEventBus.EVENT_BUS.post(new SettingsLoadEvents(dialog, content)); GlobalEventBus.EVENT_BUS.post(new SettingsLoadEvents(dialog, inner));
dialog.add(content);
// LayerUI 实现:负责绘制圆角背景、阴影,并在 paint 中使用 alpha + translate 来实现动画
class FadeLayerUI extends LayerUI<JComponent> {
private float alpha = 0f;
private int translateY = 20;
public void setAlpha(float alpha) { this.alpha = Math.max(0f, Math.min(1f, alpha)); }
public void setTranslateY(int y) { this.translateY = y; }
@Override
public void paint (Graphics g, JComponent c) {
Graphics2D g2 = (Graphics2D) g.create();
int w = c.getWidth();
int h = c.getHeight();
// 平滑处理
g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
// 先绘制柔和阴影(透明度与 alpha 关联)
int arc = 16;
float shadowAlpha = Math.min(0.35f, alpha * 0.35f);
if (shadowAlpha > 0f) {
g2.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER, shadowAlpha));
g2.setColor(new Color(0, 0, 0, 255));
// 画一个稍微向下偏移的矩形作为阴影基础
g2.fillRoundRect(8, 10 + translateY/3, w - 16, h - 20, arc, arc);
}
// 应用下滑位移 + 透明度到后续的内容绘制(包括子组件)
g2.translate(0, translateY);
g2.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER, alpha));
Color panelBg = UIManager.getColor("Panel.background");
if (panelBg == null)
panelBg = new Color(245, 246, 248);
// 绘制半透明/圆角的主背景
g2.setColor(panelBg);
g2.fillRoundRect(0, 0, w, h, arc, arc);
// 把转换后的 Graphics 传给 super.paint 来绘制子组件(子组件会被 alpha & translate 影响)
super.paint(g2, c);
g2.dispose();
}
}
FadeLayerUI fadeUI = new FadeLayerUI();
JLayer<JComponent> layered = new JLayer<>(inner, fadeUI);
layered.setOpaque(false);
dialog.getContentPane().removeAll();
dialog.getContentPane().setBackground(new Color(0,0,0,0)); // 保持透明(视平台支持情况)
dialog.add(layered);
// 动画 Timer350ms 的淡入 + 下滑(使用 ease-out 曲线)
final int duration = 350; // ms
final long start = System.currentTimeMillis();
Timer timer = new Timer(16, null); // ~60fps
timer.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
float t = (System.currentTimeMillis() - start) / (float) duration;
if (t >= 1f) t = 1f;
// ease-out (cubic)
float eased = 1 - (float) Math.pow(1 - t, 3);
fadeUI.setAlpha(eased);
// translate 从 20 -> 0
fadeUI.setTranslateY((int) ((1 - eased) * 20));
layered.repaint();
if (t >= 1f) {
((Timer) e.getSource()).stop();
}
}
});
timer.setCoalesce(true);
// 初始状态隐藏内容alpha=0启动动画
fadeUI.setAlpha(0f);
fadeUI.setTranslateY(20);
layered.setVisible(true);
timer.start();
dialog.revalidate(); dialog.revalidate();
if (AxisInnovatorsBox.getMain().isWindowStartup(dialog)) { if (AxisInnovatorsBox.getMain().isWindowStartup(dialog)) {
AxisInnovatorsBox.getMain().popupWindow(dialog); AxisInnovatorsBox.getMain().popupWindow(dialog);
} }
} }
// ---------- 内部类ToolCategory, ToolItem 等(保持原结构) ---------- // ---------- 内部类ToolCategory, ToolItem 等(保持原结构) ----------
public static class ToolCategory { public static class ToolCategory {
private final String name; private final String name;
@@ -1365,4 +1652,4 @@ public class MainWindow extends JFrame {
dim.height += rowHeight; dim.height += rowHeight;
} }
} }
} }

File diff suppressed because it is too large Load Diff