From d2e40744cf5ca3fad1f17ad2cbdf54c7c69149d4 Mon Sep 17 00:00:00 2001 From: tzdwindows 7 <3076584115@qq.com> Date: Sat, 3 Jan 2026 10:41:24 +0800 Subject: [PATCH] =?UTF-8?q?refactor(browser):=20=E9=87=8D=E6=9E=84?= =?UTF-8?q?=E6=B6=88=E6=81=AF=E8=B7=AF=E7=94=B1=E5=92=8C=E7=95=8C=E9=9D=A2?= =?UTF-8?q?=E7=BB=84=E4=BB=B6=E4=BB=A5=E6=94=AF=E6=8C=81=E5=8F=8C=E8=B7=AF?= =?UTF-8?q?=E7=94=B1=E5=92=8C=E4=B8=BB=E9=A2=98=E9=80=82=E9=85=8D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 实现双CefMessageRouter配置,支持javaQuery和cefQuery两种查询方式 - 移除handler判空注释,优化system请求处理逻辑 - 修复java-response解析中的数组越界问题 - 添加运行时计时器管理,避免计时器冲突 - 优化背景图绘制和主题色动态适配 - 实现卡片组件的动态布局和悬停效果 - 改进搜索框的动画和主题适配 - 修复JAR文件处理中的异常捕获和错误响应 - 优化工具卡片的鼠标事件处理和线程安全 - 实现响应式面板布局和组件尺寸同步 --- .../innovators/box/browser/BrowserCore.java | 39 ++- .../decompilation/gui/ModernJarViewer.java | 22 +- .../innovators/box/window/MainWindow.java | 303 ++++++++++-------- .../box/window/util/RoundedSearchField.java | 167 +++++----- src/main/resources/build/build.properties | 2 +- 5 files changed, 284 insertions(+), 249 deletions(-) diff --git a/src/main/java/com/axis/innovators/box/browser/BrowserCore.java b/src/main/java/com/axis/innovators/box/browser/BrowserCore.java index e9e3757..bcc90dc 100644 --- a/src/main/java/com/axis/innovators/box/browser/BrowserCore.java +++ b/src/main/java/com/axis/innovators/box/browser/BrowserCore.java @@ -493,19 +493,10 @@ public class BrowserCore { } private void setupMessageHandlers(WindowOperationHandler handler) { - // 1. 配置 (保持上一轮修复的代码) - CefMessageRouter.CefMessageRouterConfig routerConfig = new CefMessageRouter.CefMessageRouterConfig(); - routerConfig.jsQueryFunction = "javaQuery"; - routerConfig.jsCancelFunction = "javaQueryCancel"; - - // 2. 创建 (保持上一轮修复的代码) - msgRouter = CefMessageRouter.create(routerConfig); - - // 3. 添加处理器 - msgRouter.addHandler(new CefMessageRouterHandlerAdapter() { + // 1. 定义共享的处理器逻辑 (为了避免代码重复,先提取出来) + CefMessageRouterHandlerAdapter commonHandler = new CefMessageRouterHandlerAdapter() { @Override public boolean onQuery(CefBrowser browser, CefFrame frame, long queryId, String request, boolean persistent, CefQueryCallback callback) { - // A. 处理 JS Bridge 请求 (Controller) if (jsController != null && request.startsWith("jsbridge:")) { return jsController.handleQuery(browser, frame, queryId, request, persistent, callback); @@ -513,7 +504,6 @@ public class BrowserCore { // B. 处理 System 请求 (OperationHandler) if (request.startsWith("system:")) { - // ★★★ [增加判空] 防止 handler 为空导致空指针异常 ★★★ if (handler != null) { String[] parts = request.split(":"); String operation = parts.length >= 2 ? parts[1] : null; @@ -521,7 +511,7 @@ public class BrowserCore { handler.handleOperation(new WindowOperation(operation, targetWindow, callback)); return true; } else { - BrowserLog.warn("收到 system 请求但 handler 为空: {}" , request); + BrowserLog.warn("收到 system 请求但 handler 为空: {}", request); callback.failure(404, "No handler for system operation"); return true; } @@ -530,7 +520,7 @@ public class BrowserCore { // C. 处理 Java Response if (request.startsWith("java-response:")) { String[] parts = request.split(":"); - String requestId = parts[1]; + String requestId = parts.length > 1 ? parts[1] : ""; String responseData = parts.length > 2 ? parts[2] : ""; Consumer cb = WindowRegistry.getInstance().getCallback(requestId); if (cb != null) { @@ -543,9 +533,26 @@ public class BrowserCore { } return false; } - }, true); + }; - client.addMessageRouter(msgRouter); + // 2. 配置并创建第一个 Router (使用 javaQuery) + CefMessageRouter.CefMessageRouterConfig config1 = new CefMessageRouter.CefMessageRouterConfig(); + config1.jsQueryFunction = "javaQuery"; + config1.jsCancelFunction = "javaQueryCancel"; + CefMessageRouter msgRouter1 = CefMessageRouter.create(config1); + msgRouter1.addHandler(commonHandler, true); + client.addMessageRouter(msgRouter1); + + // 3. 配置并创建第二个 Router (使用 cefQuery - 满足“再注册”的需求) + CefMessageRouter.CefMessageRouterConfig config2 = new CefMessageRouter.CefMessageRouterConfig(); + config2.jsQueryFunction = "cefQuery"; + config2.jsCancelFunction = "cefQueryCancel"; + CefMessageRouter msgRouter2 = CefMessageRouter.create(config2); + msgRouter2.addHandler(commonHandler, true); + client.addMessageRouter(msgRouter2); + + // 如果类中有 msgRouter 成员变量,可以指向其中任意一个或维护一个列表 + this.msgRouter = msgRouter2; } private void injectJsBridge() { diff --git a/src/main/java/com/axis/innovators/box/decompilation/gui/ModernJarViewer.java b/src/main/java/com/axis/innovators/box/decompilation/gui/ModernJarViewer.java index 7313121..56d17cd 100644 --- a/src/main/java/com/axis/innovators/box/decompilation/gui/ModernJarViewer.java +++ b/src/main/java/com/axis/innovators/box/decompilation/gui/ModernJarViewer.java @@ -67,9 +67,7 @@ public class ModernJarViewer { switch (type) { case "openJar": - // 支持从前端传路径(拖拽进入)或弹窗选择 - String path = json.optString("path", null); - handleOpenJar(parent, path, callback); + handleOpenJar(parent, json.optString("path", null), callback); return true; case "getFile": handleGetFile(json.optString("path"), callback); @@ -137,16 +135,21 @@ public class ModernJarViewer { chooser.setFileFilter(new FileNameExtensionFilter("JAR Files", "jar", "zip", "war")); chooser.setDialogTitle("Select JAR"); if (chooser.showOpenDialog(parent) != JFileChooser.APPROVE_OPTION) { - if (callback != null) callback.failure(404, "Cancelled"); + callback.failure(404, "User cancelled"); return; } file = chooser.getSelectedFile(); + + if (file != null && file.exists()) { + loadJarAndRespond(file, callback); + } else { + callback.failure(404, "File not found or invalid"); + } } - loadJarAndRespond(file, callback); - } catch (Exception e) { - if (callback != null) callback.failure(500, e.getMessage()); + e.printStackTrace(); + callback.failure(500, "Swing Error: " + e.getMessage()); } }); } @@ -189,8 +192,9 @@ public class ModernJarViewer { new Thread(() -> JarAnalyzer.analyze(currentJarFile, classEntries, globalIndex)).start(); if (callback != null) callback.success(root.toString()); - } catch (IOException e) { - if (callback != null) callback.failure(500, "Failed to open JAR: " + e.getMessage()); + } catch (Exception e) { // 关键修改:从 IOException 改为 Exception + e.printStackTrace(); + if (callback != null) callback.failure(500, "Load Error: " + e.getMessage()); } } diff --git a/src/main/java/com/axis/innovators/box/window/MainWindow.java b/src/main/java/com/axis/innovators/box/window/MainWindow.java index 93384cb..fbd5431 100644 --- a/src/main/java/com/axis/innovators/box/window/MainWindow.java +++ b/src/main/java/com/axis/innovators/box/window/MainWindow.java @@ -70,6 +70,7 @@ public class MainWindow extends JFrame { // settings dialog private WindowsJDialog dialog; + private final Map runningTimers = new ConcurrentHashMap<>(); public MainWindow() { // 字体配置:优先使用无衬线现代字体 @@ -219,8 +220,7 @@ public class MainWindow extends JFrame { if (backgroundImage != null) { Graphics2D g2d = (Graphics2D) g.create(); g2d.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER, backgroundOpacity)); - Dimension size = getSize(); - BufferedImage bg = createBlurredBackground(size); + BufferedImage bg = createBlurredBackground(getSize()); if (bg != null) g2d.drawImage(bg, 0, 0, null); g2d.dispose(); } @@ -228,18 +228,16 @@ public class MainWindow extends JFrame { }; // 背景色逻辑优化 - Color baseBg = UIManager.getColor("Panel.background"); - if (baseBg == null) baseBg = new Color(245, 246, 248); if (backgroundImage != null) { mainPanel.setOpaque(false); - mainPanel.setBackground(new Color(0,0,0,0)); } else { mainPanel.setOpaque(true); - // 获取主题中最深的背景色(通常是 Window 的背景,比 Panel 深) - Color deepBg = UIManager.getColor("Window.background"); - // 如果获取不到,就用一个极深的颜色兜底 - if (deepBg == null) deepBg = new Color(24, 24, 24); - mainPanel.setBackground(deepBg); + // --- 修复点:动态获取窗口背景色,不要硬编码 24,24,24 --- + Color bg = UIManager.getColor("Window.background"); + if (bg == null) bg = UIManager.getColor("Panel.background"); + // 如果还是 null,根据主题手动设置安全的浅色/深色 + if (bg == null) bg = isDarkTheme() ? new Color(30, 30, 30) : new Color(242, 242, 242); + mainPanel.setBackground(bg); } mainPanel.setBorder(BorderFactory.createEmptyBorder(0, 0, 0, 0)); @@ -254,11 +252,19 @@ public class MainWindow extends JFrame { sideBar = createSideBar(); center.add(sideBar, BorderLayout.WEST); + // 内容区 cardsLayout = new CardLayout(); cardsPanel = new JPanel(cardsLayout); cardsPanel.setOpaque(false); + cardsPanel.addComponentListener(new ComponentAdapter() { + @Override + public void componentResized(ComponentEvent e) { + syncLayeredBounds(); + } + }); + for (ToolCategory category : categories) { JPanel toolsPanel = createToolsPanel(category); toolsPanel.setOpaque(false); @@ -285,7 +291,12 @@ public class MainWindow extends JFrame { contentPanel.setOpaque(false); contentPanel.setBorder(BorderFactory.createEmptyBorder(0, 12, 0, 12)); // 内容区左右边距 contentPanel.add(layeredPane, BorderLayout.CENTER); - + contentPanel.addComponentListener(new ComponentAdapter() { + @Override + public void componentResized(ComponentEvent e) { + syncLayeredBounds(); + } + }); center.add(contentPanel, BorderLayout.CENTER); mainPanel.add(center, BorderLayout.CENTER); mainPanel.add(createFooter(), BorderLayout.SOUTH); @@ -313,7 +324,7 @@ public class MainWindow extends JFrame { SwingUtilities.updateComponentTreeUI(this); if (searchField != null) { - searchField.defaultBorderColor = UIManager.getColor("TextField.borderColor"); + //searchField.defaultBorderColor = UIManager.getColor("TextField.borderColor"); searchField.repaint(); } @@ -341,14 +352,19 @@ public class MainWindow extends JFrame { private void syncLayeredBounds() { if (layeredPane == null || contentPanel == null) return; Dimension d = contentPanel.getSize(); - // Fallback size check - if (d.width <= 0) d = new Dimension(800, 600); + if (d.width <= 0) return; layeredPane.setBounds(0, 0, d.width, d.height); - // Important: Update cardsPanel size explicitly cardsPanel.setBounds(0, 0, d.width, d.height); + + // 强制当前显示的滚动面板及其内部组件重新布局 + for (Component comp : cardsPanel.getComponents()) { + if (comp.isVisible() && comp instanceof JScrollPane) { + JScrollPane sp = (JScrollPane) comp; + sp.getViewport().getView().revalidate(); // 触发 WrapLayout 计算 + } + } cardsPanel.revalidate(); - layeredPane.repaint(); } // ---------- Header ---------- @@ -359,28 +375,21 @@ public class MainWindow extends JFrame { JLabel title = new JLabel(LanguageManager.getLoadedLanguages().getText("mainWindow.title.2")); title.setFont(new Font(selectFont("Segoe UI", "Microsoft YaHei").getName(), Font.BOLD, 20)); - title.setForeground(UIManager.getColor("Label.foreground")); - - JPanel left = new JPanel(new FlowLayout(FlowLayout.LEFT, 0, 0)); - left.setOpaque(false); - left.add(title); + title.setForeground(UIManager.getColor("Label.foreground")); // 动态颜色 searchField = new RoundedSearchField(320, 36); - // --- 修复开始 --- - // 1. 使用深色半透明背景 (黑色,透明度 80/255),保证在亮色背景图上能看清 - searchField.setBackground(new Color(0, 0, 0, 80)); - // 2. 文字强制设为白色 - searchField.setForeground(Color.WHITE); - // 3. 添加一个淡淡的白色边框,增加轮廓感 - searchField.setBorder(BorderFactory.createCompoundBorder( - BorderFactory.createLineBorder(new Color(255, 255, 255, 50), 1, true), - BorderFactory.createEmptyBorder(4, 10, 4, 10) - )); - // --- 修复结束 --- + // --- 适配浅色模式逻辑 --- + if (backgroundImage != null) { + searchField.setBackground(isDarkTheme() ? new Color(0, 0, 0, 80) : new Color(255, 255, 255, 120)); + searchField.setForeground(isDarkTheme() ? Color.WHITE : Color.BLACK); + } else { + // --- 修复点:直接使用 UI 库的文本框颜色 --- + searchField.setBackground(UIManager.getColor("TextField.background")); + searchField.setForeground(UIManager.getColor("TextField.foreground")); + } - // 如果您的组件支持 setPlaceholder - searchField.putClientProperty("JTextField.placeholderText", "Search tools..."); + searchField.putClientProperty(FlatClientProperties.PLACEHOLDER_TEXT, "Search tools..."); searchField.addDocumentListener(new DocumentListener() { @Override public void insertUpdate(DocumentEvent e) { filterCurrentCategory(searchField.getText()); } @@ -388,21 +397,21 @@ public class MainWindow extends JFrame { @Override public void changedUpdate(DocumentEvent e) { filterCurrentCategory(searchField.getText()); } }); + JPanel left = new JPanel(new FlowLayout(FlowLayout.LEFT, 0, 0)); + left.setOpaque(false); + left.add(title); + JPanel center = new JPanel(new GridBagLayout()); center.setOpaque(false); center.add(searchField); JButton settings = new JButton(LoadIcon.loadIcon("settings.png", 22)); settings.putClientProperty(FlatClientProperties.BUTTON_TYPE, FlatClientProperties.BUTTON_TYPE_BORDERLESS); - settings.setCursor(Cursor.getPredefinedCursor(Cursor.HAND_CURSOR)); - settings.setContentAreaFilled(false); - settings.setFocusPainted(false); settings.addActionListener(e -> showSettings()); header.add(left, BorderLayout.WEST); header.add(center, BorderLayout.CENTER); header.add(settings, BorderLayout.EAST); - return header; } @@ -411,20 +420,22 @@ public class MainWindow extends JFrame { JPanel sidebar = new JPanel(new BorderLayout()) { @Override protected void paintComponent(Graphics g) { - // 如果有背景图片,绘制半透明遮罩 + Graphics2D g2 = (Graphics2D) g.create(); if (backgroundImage != null) { - Graphics2D g2 = (Graphics2D) g.create(); - g2.setColor(new Color(20, 20, 20, 150)); // 加深遮罩,防止背景图干扰文字 + // 有背景图时,叠加半透明遮罩 + g2.setColor(isDarkTheme() ? new Color(20, 20, 20, 150) : new Color(255, 255, 255, 120)); g2.fillRect(0, 0, getWidth(), getHeight()); - g2.dispose(); } else { - // --- 修复重点:没有背景图时,侧边栏完全透明,只画右侧的一条淡淡的线 --- - Graphics2D g2 = (Graphics2D) g.create(); - // 线条颜色:白色,透明度 10% (非常淡,若隐若现) - g2.setColor(new Color(255, 255, 255, 25)); + // --- 修复点:没有背景图时,绘制侧边栏背景 --- + // 浅色模式下,侧边栏通常比背景稍微深一点点(或者有边框) + if (isDarkTheme()) { + g2.setColor(new Color(255, 255, 255, 15)); // 深色模式淡淡的线 + } else { + g2.setColor(new Color(0, 0, 0, 20)); // 浅色模式淡淡的线 + } g2.drawLine(getWidth() - 1, 0, getWidth() - 1, getHeight()); - g2.dispose(); } + g2.dispose(); } }; @@ -477,20 +488,17 @@ public class MainWindow extends JFrame { boolean isSelected = Objects.equals(currentCategoryId, category.getId().toString()); boolean isHover = getMousePosition() != null; - // 1. 先绘制背景 if (isSelected) { - // 选中状态:使用 FlatLaf 的强调色或默认蓝色,带一点透明度让背景图透出来一点点 - g2.setColor(new Color(0, 120, 215, 200)); + // 选中颜色使用主题强调色 + g2.setColor(UIManager.getColor("Component.accentColor") != null ? + UIManager.getColor("Component.accentColor") : new Color(0, 120, 215)); g2.fillRoundRect(0, 0, getWidth(), getHeight(), 12, 12); } else if (isHover) { - // 悬停状态:淡淡的白色/灰色 - g2.setColor(new Color(255, 255, 255, 30)); + // 悬停背景适配 + g2.setColor(isDarkTheme() ? new Color(255, 255, 255, 30) : new Color(0, 0, 0, 20)); g2.fillRoundRect(0, 0, getWidth(), getHeight(), 12, 12); } - g2.dispose(); - - // 2. 再调用 super 绘制文字和图标 super.paintComponent(g); } }; @@ -529,6 +537,15 @@ public class MainWindow extends JFrame { switchCategory(category.getId().toString(), false); }); + button.addChangeListener(e -> { + boolean isSelected = Objects.equals(currentCategoryId, category.getId().toString()); + if (isSelected) { + button.setForeground(Color.WHITE); // 选中时固定白色 + } else { + button.setForeground(UIManager.getColor("Label.foreground")); // 未选中跟随主题 + } + }); + button.addMouseListener(new MouseAdapter() { public void mouseEntered(MouseEvent e) { button.repaint(); } public void mouseExited(MouseEvent e) { button.repaint(); } @@ -574,101 +591,119 @@ public class MainWindow extends JFrame { int w = getWidth(); int h = getHeight(); - int arc = 18; // 圆角稍微大一点,更现代 + int arc = 18; - // --- 修复重点:使用叠加色 --- if (backgroundImage != null) { - // 背景图模式:较重的磨砂黑 - g2.setColor(new Color(30, 30, 30, 180)); + g2.setColor(isDarkTheme() ? new Color(30, 30, 30, 180) : new Color(255, 255, 255, 180)); } else { - // 纯色模式:不要用实色,用半透明的白色覆盖在深色背景上 - // 这种技术叫 Surface Overlay,能保证色调绝对统一 - // 12/255 ≈ 5% 的白色,形成自然的层级感 - g2.setColor(new Color(255, 255, 255, 12)); + // --- 修复点:浅色模式下的卡片应为白色且带有微弱边框 --- + if (isDarkTheme()) { + g2.setColor(new Color(255, 255, 255, 12)); + } else { + g2.setColor(Color.WHITE); // 浅色模式卡片用纯白 + // 绘制一个非常淡的灰色边框,增加层次感 + g2.setPaint(new Color(0, 0, 0, 30)); + g2.drawRoundRect(0, 0, w - 1, h - 1, arc, arc); + g2.setColor(Color.WHITE); + } } - - // 填充背景 g2.fillRoundRect(0, 0, w, h, arc, arc); - // 绘制极淡的边框 (增加精致感) - g2.setColor(new Color(255, 255, 255, 30)); // 12% 透明度的白边 - g2.drawRoundRect(0, 0, w-1, h-1, arc, arc); - - // 如果鼠标悬停(根据 cardElevations 判断),加深一点高亮 - if (cardElevations.getOrDefault(this, 2) > 2) { - g2.setColor(new Color(255, 255, 255, 10)); // 叠加一层高亮 + // 悬停动画效果 + int elevation = cardElevations.getOrDefault(this, 2); + if (elevation > 2) { + // 悬停时加深一点点阴影或光晕 + g2.setColor(isDarkTheme() ? new Color(255, 255, 255, 10) : new Color(0, 0, 0, 5)); g2.fillRoundRect(0, 0, w, h, arc, arc); } - - super.paintComponent(g); g2.dispose(); } - @Override public boolean isOpaque() { return false; } }; - // --- 内容布局保持不变 --- card.setLayout(new BorderLayout(12, 10)); card.setBorder(BorderFactory.createEmptyBorder(16, 16, 16, 16)); card.setPreferredSize(new Dimension(260, 110)); - card.setCursor(Cursor.getPredefinedCursor(Cursor.HAND_CURSOR)); - - JLabel iconLabel; - if (tool.getIcon() == null) { - iconLabel = new JLabel(tool.getImageIcon()); - } else { - iconLabel = new JLabel(LoadIcon.loadIcon(tool.getIcon(), 40)); - } - iconLabel.setVerticalAlignment(SwingConstants.TOP); - - JPanel textPanel = new JPanel(new BorderLayout(0, 4)); - textPanel.setOpaque(false); JLabel titleLabel = new JLabel(tool.getTitle()); - titleLabel.setFont(new Font(selectFont("Segoe UI", "Microsoft YaHei").getName(), Font.BOLD, 15)); - // 强制标题稍亮 - titleLabel.setForeground(new Color(240, 240, 240)); + titleLabel.setFont(new Font(UIManager.getFont("Label.font").getName(), Font.BOLD, 15)); + // --- 关键修复:动态前景色 --- + titleLabel.setForeground(UIManager.getColor("Label.foreground")); JTextArea descArea = new JTextArea(tool.getDescription()); - descArea.setFont(new Font(selectFont("Segoe UI").getName(), Font.PLAIN, 12)); - // 描述文字稍暗,形成对比 - descArea.setForeground(new Color(170, 170, 170)); + descArea.setFont(new Font(UIManager.getFont("Label.font").getName(), Font.PLAIN, 12)); + // --- 关键修复:动态辅助色 --- + descArea.setForeground(UIManager.getColor("Label.disabledForeground")); descArea.setLineWrap(true); descArea.setWrapStyleWord(true); descArea.setEditable(false); descArea.setOpaque(false); - descArea.setBorder(null); descArea.setRows(2); - + descArea.setEnabled(false); + JPanel textPanel = new JPanel(new BorderLayout(0, 4)); + textPanel.setOpaque(false); textPanel.add(titleLabel, BorderLayout.NORTH); textPanel.add(descArea, BorderLayout.CENTER); + JLabel iconLabel = new JLabel(tool.getIcon() == null ? tool.getImageIcon() : LoadIcon.loadIcon(tool.getIcon(), 40)); + iconLabel.setVerticalAlignment(SwingConstants.TOP); + card.add(iconLabel, BorderLayout.WEST); card.add(textPanel, BorderLayout.CENTER); - card.setToolTipText(tool.getName()); - - cardScales.put(card, 1.0f); - cardElevations.put(card, 2); - - CardMouseAdapter adapter = new CardMouseAdapter(card, tool); - card.addMouseListener(adapter); - - card.addMouseListener(new MouseAdapter() { + MouseAdapter cardListener = new MouseAdapter() { @Override public void mouseEntered(MouseEvent e) { animateCardElevation(card, 8); } + @Override public void mouseExited(MouseEvent e) { animateCardElevation(card, 2); } - }); - // 保留事件总线逻辑 - card.setUI(new PanelUI() { - @Override public void installUI(JComponent c) { - GlobalEventBus.EVENT_BUS.post(new TABUIEvents(card, c)); - super.installUI(c); + @Override + public void mousePressed(MouseEvent e) { + // 可以添加一个微弱的按下效果 } - }); + @Override + public void mouseReleased(MouseEvent e) { + // 修复点 1: 使用组件自身的尺寸判断,而不是 getBounds() + if (new Rectangle(0, 0, card.getWidth(), card.getHeight()).contains(e.getPoint())) { + if (tool.getAction() != null) { + // 修复点 2: 确保在 EDT 线程执行,并捕获异常防止界面锁死 + SwingUtilities.invokeLater(() -> { + try { + tool.getAction().actionPerformed(new ActionEvent(card, ActionEvent.ACTION_PERFORMED, "")); + } catch (Exception ex) { + logger.error("Tool action execution failed", ex); + } + }); + } + } + } + }; + String tooltip = tool.getDescription(); + card.setToolTipText(tooltip); + titleLabel.setToolTipText(tooltip); + iconLabel.setToolTipText(tooltip); + descArea.setToolTipText(tooltip); + + MouseAdapter forwarder = new MouseAdapter() { + @Override + public void mouseEntered(MouseEvent e) { dispatch(e); } + @Override + public void mouseExited(MouseEvent e) { dispatch(e); } + @Override + public void mousePressed(MouseEvent e) { dispatch(e); } + @Override + public void mouseReleased(MouseEvent e) { dispatch(e); } + + private void dispatch(MouseEvent e) { + card.dispatchEvent(SwingUtilities.convertMouseEvent(descArea, e, card)); + } + }; + + card.addMouseListener(cardListener); + iconLabel.addMouseListener(cardListener); + descArea.addMouseListener(forwarder); return card; } @@ -707,19 +742,22 @@ public class MainWindow extends JFrame { } private void animateCardElevation(JComponent card, int targetElevation) { - new Timer(15, new ActionListener() { - @Override - public void actionPerformed(ActionEvent e) { - int current = cardElevations.getOrDefault(card, 2); - if (current == targetElevation) { - ((Timer)e.getSource()).stop(); - return; - } - int next = current < targetElevation ? current + 1 : current - 1; - cardElevations.put(card, next); - card.repaint(); + Timer existing = runningTimers.get(card); + if (existing != null) existing.stop(); + + Timer newTimer = new Timer(15, e -> { + int current = cardElevations.getOrDefault(card, 2); + if (current == targetElevation) { + ((Timer)e.getSource()).stop(); + runningTimers.remove(card); + return; } - }).start(); + int next = current < targetElevation ? current + 1 : current - 1; + cardElevations.put(card, next); + card.repaint(); + }); + runningTimers.put(card, newTimer); + newTimer.start(); } // ---------- Settings (Redesigned) ---------- @@ -848,28 +886,25 @@ public class MainWindow extends JFrame { private void filterCurrentCategory(String query) { if (currentCategoryId == null) return; - String q = query == null ? "" : query.trim().toLowerCase(); + String q = query.toLowerCase().trim(); ToolCategory category = categories.stream() - .filter(tc -> Objects.equals(tc.getId().toString(), currentCategoryId)) + .filter(tc -> tc.getId().toString().equals(currentCategoryId)) .findFirst().orElse(null); if (category == null) return; SwingUtilities.invokeLater(() -> { - JPanel newPanel = new JPanel(new WrapLayout(FlowLayout.LEFT, 20, 20)); - newPanel.setOpaque(false); + ResponsivePanel newPanel = new ResponsivePanel(); for (ToolItem tool : category.getTools()) { - if (q.isEmpty() || tool.getTitle().toLowerCase().contains(q) || tool.getName().toLowerCase().contains(q)) { + if (q.isEmpty() || tool.getTitle().toLowerCase().contains(q)) { newPanel.add(createToolCard(tool)); } } - JPanel wrapper = new JPanel(new BorderLayout()); - wrapper.setOpaque(false); - wrapper.add(newPanel, BorderLayout.NORTH); - JScrollPane scrollPane = categoryScrollPanes.get(currentCategoryId); if (scrollPane != null) { - scrollPane.setViewportView(wrapper); + scrollPane.setViewportView(newPanel); + newPanel.revalidate(); + scrollPane.repaint(); } }); } diff --git a/src/main/java/com/axis/innovators/box/window/util/RoundedSearchField.java b/src/main/java/com/axis/innovators/box/window/util/RoundedSearchField.java index a91d14b..90ad936 100644 --- a/src/main/java/com/axis/innovators/box/window/util/RoundedSearchField.java +++ b/src/main/java/com/axis/innovators/box/window/util/RoundedSearchField.java @@ -8,156 +8,145 @@ import java.awt.geom.Point2D; public class RoundedSearchField extends JPanel { private final JTextField textField; - private int targetWidth; - private float animProgress = 0f; - private Timer animTimer; private final int baseWidth; private final int heightPx; - // 动画相关变量 + // 动画核心变量 + private float targetWidth; + private float currentWidth; // 记录当前动画中的宽度,避免依赖 getWidth() + private Timer animTimer; + private float glowPosition = 0f; private final Timer glowTimer; - public Color defaultBorderColor; private boolean focused = false; + private static final float[] GRAD_FRACTIONS = {0f, 0.25f, 0.5f, 0.75f, 1f}; + private static final Color[] GRAD_COLORS = { + new Color(255, 50, 50), + new Color(255, 180, 0), + new Color(0, 200, 0), + new Color(0, 150, 255), + new Color(180, 0, 255) + }; + public RoundedSearchField(int baseWidth, int heightPx) { this.baseWidth = baseWidth; this.heightPx = heightPx; this.targetWidth = baseWidth; + this.currentWidth = baseWidth; // 初始宽度 + setOpaque(false); setLayout(new BorderLayout()); - // 获取系统默认边框色 - defaultBorderColor = UIManager.getColor("TextField.borderColor"); - if (defaultBorderColor == null) { - defaultBorderColor = new Color(180, 180, 180); // 备用默认色 - } - textField = new JTextField(); - textField.setBorder(BorderFactory.createEmptyBorder(6, 10, 6, 10)); + textField.setBorder(BorderFactory.createEmptyBorder(0, 15, 0, 15)); textField.setOpaque(false); - textField.setFont(UIManager.getFont("TextField.font")); + textField.setBackground(new Color(0,0,0,0)); + + // 初始尺寸 setPreferredSize(new Dimension(baseWidth, heightPx)); add(textField, BorderLayout.CENTER); - // 焦点监听器 textField.addFocusListener(new FocusAdapter() { @Override public void focusGained(FocusEvent e) { focused = true; - animateTo(baseWidth + 80); + startAnimation(baseWidth + 80); glowTimer.start(); } - @Override public void focusLost(FocusEvent e) { focused = false; - animateTo(baseWidth); + startAnimation(baseWidth); glowTimer.stop(); repaint(); } }); - // 点击面板聚焦文本框 - addMouseListener(new MouseAdapter() { - @Override public void mouseClicked(MouseEvent e) { - textField.requestFocusInWindow(); - } - }); - - // 尺寸动画定时器 - animTimer = new Timer(16, ae -> { - int curW = getWidth(); - int diff = targetWidth - curW; - if (Math.abs(diff) <= 1) { - setPreferredSize(new Dimension(targetWidth, heightPx)); - revalidate(); - repaint(); + // 尺寸动画:使用差值平滑移动 + animTimer = new Timer(10, ae -> { + float diff = targetWidth - currentWidth; + if (Math.abs(diff) < 0.5f) { // 足够接近目标值 + currentWidth = targetWidth; animTimer.stop(); } else { - int step = Math.max(1, Math.abs(diff) / 6); - int newW = curW + (diff > 0 ? step : -step); - setPreferredSize(new Dimension(newW, heightPx)); - revalidate(); - repaint(); + // 缓动公式:每次移动剩余距离的 20%,产生平滑减速效果 + currentWidth += diff * 0.2f; } + // 关键:强制更新布局 + revalidate(); + repaint(); }); - // 发光动画定时器 glowTimer = new Timer(30, e -> { - glowPosition = (glowPosition + 0.03f) % 1f; + glowPosition = (glowPosition + 0.02f) % 1f; repaint(); }); } - public void updateThemeColors() { - defaultBorderColor = UIManager.getColor("TextField.borderColor"); - if (defaultBorderColor == null) { - defaultBorderColor = new Color(180, 180, 180); - } - repaint(); + // 重写此方法,让布局管理器(如 FlowLayout)实时跟随动画宽度 + @Override + public Dimension getPreferredSize() { + return new Dimension((int) currentWidth, heightPx); + } + + private void startAnimation(int w) { + this.targetWidth = w; + if (!animTimer.isRunning()) animTimer.start(); } @Override protected void paintComponent(Graphics g) { - super.paintComponent(g); Graphics2D g2d = (Graphics2D) g.create(); g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); + g2d.setRenderingHint(RenderingHints.KEY_STROKE_CONTROL, RenderingHints.VALUE_STROKE_PURE); - int arc = heightPx / 2; // 圆角半径 - int borderThickness = focused ? 2 : 1; + int w = getWidth(); + int h = getHeight(); + int arc = h; - // 绘制背景 - g2d.setColor(getBackground()); - g2d.fillRoundRect(0, 0, getWidth(), getHeight(), arc, arc); - - // 绘制边框 - if (focused) { - // 流动彩虹渐变 - float[] fractions = {0f, 0.25f, 0.5f, 0.75f, 1f}; - Color[] colors = { - new Color(255, 0, 0, 200), // 红 - new Color(255, 165, 0, 200), // 橙 - new Color(0, 255, 0, 200), // 绿 - new Color(0, 191, 255, 200), // 蓝 - new Color(148, 0, 211, 200) // 紫 - }; - - // 创建循环渐变 - Point2D start = new Point2D.Float(getWidth() * glowPosition, 0); - Point2D end = new Point2D.Float(getWidth() * glowPosition + getWidth(), 0); - LinearGradientPaint gradient = new LinearGradientPaint( - start, end, fractions, colors - ); - - g2d.setPaint(gradient); - g2d.setStroke(new BasicStroke(2.5f)); + // 背景颜色处理 + Color bgBase = UIManager.getColor("TextField.background"); + if (!focused && bgBase != null && bgBase.getRed() > 240) { + g2d.setColor(new Color(245, 245, 247)); // 浅色模式稍微加深一点区分 } else { - g2d.setColor(defaultBorderColor); - g2d.setStroke(new BasicStroke(1f)); + g2d.setColor(bgBase != null ? bgBase : Color.WHITE); } + g2d.fillRoundRect(0, 0, w, h, arc, arc); - g2d.drawRoundRect(borderThickness/2, borderThickness/2, - getWidth() - borderThickness, getHeight() - borderThickness, - arc, arc); - - // 添加发光效果 if (focused) { - g2d.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER, 0.4f)); - g2d.setStroke(new BasicStroke(4f)); - g2d.drawRoundRect(0, 0, getWidth(), getHeight(), arc, arc); + // 彩虹边框 + float startX = w * glowPosition; + LinearGradientPaint grad = new LinearGradientPaint( + new Point2D.Float(startX - w, 0), + new Point2D.Float(startX, 0), + GRAD_FRACTIONS, GRAD_COLORS, + MultipleGradientPaint.CycleMethod.REPEAT + ); + g2d.setPaint(grad); + g2d.setStroke(new BasicStroke(2.0f)); + g2d.drawRoundRect(1, 1, w - 2, h - 2, arc, arc); + + // 外发光 + g2d.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER, 0.15f)); + g2d.setStroke(new BasicStroke(3.0f)); + g2d.drawRoundRect(0, 0, w, h, arc, arc); + } else { + // 默认边框 + Color borderCol = UIManager.getColor("Component.borderColor"); + if (borderCol == null || borderCol.getRed() > 220) { + borderCol = new Color(210, 210, 215); + } + g2d.setColor(borderCol); + g2d.setStroke(new BasicStroke(1.0f)); + g2d.drawRoundRect(1, 1, w - 2, h - 2, arc, arc); } g2d.dispose(); } - void animateTo(int w) { - this.targetWidth = w; - if (!animTimer.isRunning()) animTimer.start(); - } - public String getText() { return textField.getText(); } public void setText(String t) { textField.setText(t); } public void addActionListener(ActionListener l) { textField.addActionListener(l); } public void addDocumentListener(DocumentListener dl) { textField.getDocument().addDocumentListener(dl); } -} +} \ No newline at end of file diff --git a/src/main/resources/build/build.properties b/src/main/resources/build/build.properties index 3951860..8f927a2 100644 --- a/src/main/resources/build/build.properties +++ b/src/main/resources/build/build.properties @@ -1,4 +1,4 @@ # Auto-generated build information version=0.0.1 -buildTimestamp=2026-01-03T09:20:12.6386031 +buildTimestamp=2026-01-03T10:40:36.7967778 buildSystem=WINDOWS