diff --git a/language/saved_language.properties b/language/saved_language.properties index fa941b4..b884775 100644 --- a/language/saved_language.properties +++ b/language/saved_language.properties @@ -1,3 +1,3 @@ #Current Loaded Language -#Wed Jul 02 20:11:55 CST 2025 +#Tue Aug 12 21:57:11 CST 2025 loadedLanguage=system\:zh_CN diff --git a/src/main/java/com/axis/innovators/box/gui/MainWindow.java b/src/main/java/com/axis/innovators/box/gui/MainWindow.java index 4b3e42f..da8393d 100644 --- a/src/main/java/com/axis/innovators/box/gui/MainWindow.java +++ b/src/main/java/com/axis/innovators/box/gui/MainWindow.java @@ -8,8 +8,10 @@ import com.formdev.flatlaf.FlatClientProperties; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; -import javax.swing.Timer; import javax.swing.*; +import javax.swing.Timer; +import javax.swing.event.DocumentEvent; +import javax.swing.event.DocumentListener; import javax.swing.plaf.FontUIResource; import javax.swing.plaf.PanelUI; import javax.swing.plaf.basic.BasicScrollBarUI; @@ -18,428 +20,822 @@ import java.awt.*; import java.awt.event.*; import java.awt.geom.RoundRectangle2D; import java.awt.image.BufferedImage; -import java.awt.image.ConvolveOp; -import java.awt.image.Kernel; -import java.util.List; import java.util.*; +import java.util.List; +import java.util.concurrent.ConcurrentHashMap; /** - * 显示窗口(显示的主窗口) - * @author tzdwindows 7 + * MainWindow - 现代化 UI(修正版) + * 变更要点: + * - 使用 JLayeredPane + cardsPanel(CardLayout) 来展示分类,并在 layeredPane 上添加动画覆盖层,避免 CardLayout add 的约束错误 + * - 设置 UIManager 的默认字体来解决中文乱码(优先微软雅黑/Segoe UI) + * - 侧栏颜色使用 #3C3F41(按你的要求) + * - 搜索框以居中为主,聚焦时带放大动画与圆角外观 + * - 尽量避免离屏绘制遗留(使用 component.printAll() 截图) */ public class MainWindow extends JFrame { private static final Logger logger = LogManager.getLogger(MainWindow.class); - private final Map cardScales = new HashMap<>(); - private final Map cardElevations = new HashMap<>(); - private final Color CARD_COLOR = Color.WHITE; + + // 动画/样式状态 + private final Map cardScales = new ConcurrentHashMap<>(); + private final Map cardElevations = new ConcurrentHashMap<>(); + + // 分类与UI容器 private final List categories = new ArrayList<>(); - private SystemTray systemTray; + private final Map categoryScrollPanes = new HashMap<>(); + private final Map categoryToolPanels = new HashMap<>(); + private final Map sideButtons = new HashMap<>(); + private String currentCategoryId = null; + + private JLayeredPane layeredPane; + private JPanel cardsPanel; // 使用 CardLayout 管理每个分类的 JScrollPane + private CardLayout cardsLayout; + + private JPanel sideBar; private JPanel contentPanel; - //private TrayIcon trayIcon; + private RoundedSearchField searchField; + + // settings dialog + private WindowsJDialog dialog; + + // 颜色常量 + private static final Color SIDEBAR_COLOR = new Color(0x3C3F41); // 你指定的颜色 + private static final Color CARD_LIGHT_BG = new Color(250, 250, 250); + private static final Color CARD_BG = new Color(0x4A4D50); // 与侧边栏协调的深灰色 + private static final Color CARD_BORDER = new Color(0x5C5F61); // 稍亮的边框色 + private static final Color TEXT_COLOR = new Color(0xE0E0E0); // 浅灰色文字 public MainWindow() { + // 增强字体设置逻辑:优先使用系统支持的中文字体 + String[] fontNames = {"Microsoft YaHei", "微软雅黑", "PingFang SC", "SimHei", "宋体", "新宋体", "SansSerif"}; + Font defaultFont = selectFont(fontNames, 14); // 增加默认字号 + + // 创建字体资源并设置UI默认字体 + FontUIResource fontRes = new FontUIResource(defaultFont); + Enumeration keys = UIManager.getDefaults().keys(); + while (keys.hasMoreElements()) { + Object key = keys.nextElement(); + Object value = UIManager.get(key); + if (value instanceof FontUIResource) { + UIManager.put(key, fontRes); + } + } + + // 确保特定组件字体被覆盖 + UIManager.put("Label.font", fontRes); + UIManager.put("Button.font", fontRes); + UIManager.put("TextField.font", fontRes); + UIManager.put("TextArea.font", fontRes); + UIManager.put("TabbedPane.font", fontRes); + UIManager.put("TitledBorder.font", fontRes); + + // 图标 setIconImage(LoadIcon.loadIcon("logo.png", 32).getImage()); + + // 避免在有装饰的 frame 上调用 setBackground(alpha) 导致异常 + setUndecorated(false); + + // 尺寸变化时重置卡片缩放(防止变形) addComponentListener(new ComponentAdapter() { @Override public void componentResized(ComponentEvent e) { - for (JComponent card : cardScales.keySet()) { - cardScales.put(card, 1.0f); - card.repaint(); + if (cardScales != null) { + for (JComponent c : cardScales.keySet()) { + cardScales.put(c, 1.0f); + c.repaint(); + } + } + // keep layered bounds synced + if (layeredPane != null && cardsPanel != null) { + cardsPanel.setBounds(0, 0, layeredPane.getWidth(), layeredPane.getHeight()); } } }); } /** - * 添加工具分类 - * @param category 工具分类 + * 添加工具分类(接口保持不变) */ - public void addToolCategory(ToolCategory category){ + public void addToolCategory(ToolCategory category) { categories.add(category); } + /** + * 初始化并显示 UI + */ public void initUI() { setTitle(LanguageManager.getLoadedLanguages().getText("mainWindow.title")); setDefaultCloseOperation(EXIT_ON_CLOSE); setSize(1200, 800); setLocationRelativeTo(null); - setDefaultCloseOperation(JFrame.HIDE_ON_CLOSE); - - getContentPane().setBackground(new Color(0, 0, 0, 0)); - + // 主容器 JPanel mainPanel = new JPanel(new BorderLayout()); - mainPanel.setOpaque(true); - - mainPanel.setLayout(new BorderLayout(20, 20)); - mainPanel.setBorder(BorderFactory.createEmptyBorder(20, 30, 30, 30)); + 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.add(createHeader(), BorderLayout.NORTH); - mainPanel.add(createCategoryTabs(), BorderLayout.CENTER); - //mainPanel.add(createFooter(), BorderLayout.SOUTH); 底部菜单 - add(mainPanel); - //createSystemTray(); - addWindowListener(new WindowAdapter() { - @Override - public void windowClosing(WindowEvent e) { - setVisible(false); - } - }); + // 中间区域:侧边栏 + 内容区 + JPanel center = new JPanel(new BorderLayout(12, 12)); + center.setOpaque(false); + sideBar = createSideBar(); + center.add(sideBar, BorderLayout.WEST); + // cardsPanel(CardLayout)放入 layeredPane,动画时把 overlay 放在 layeredPane 的较高层 + cardsLayout = new CardLayout(); + cardsPanel = new JPanel(cardsLayout); + cardsPanel.setOpaque(false); - JPanel sideBar = createSideBar(); - mainPanel.add(sideBar, BorderLayout.WEST); - - contentPanel = new JPanel(new BorderLayout()); - contentPanel.setOpaque(false); - mainPanel.add(contentPanel, BorderLayout.CENTER); - - mainPanel.setBorder(BorderFactory.createEmptyBorder(0, 0, 0, 0)); - - add(mainPanel); - - } - - private JPanel createSideBar() { - JPanel sidebar = new JPanel(); - sidebar.setLayout(new BoxLayout(sidebar, BoxLayout.Y_AXIS)); - sidebar.setBackground(new Color(35, 35, 35)); - sidebar.setPreferredSize(new Dimension(180, getHeight())); - - // 添加分类按钮 - for (ToolCategory category : categories) { - JButton button = new JButton(category.getName()); - button.setAlignmentX(Component.CENTER_ALIGNMENT); - button.setMaximumSize(new Dimension(160, 40)); - button.setForeground(Color.WHITE); - button.setBackground(new Color(60, 60, 60)); - button.setFocusPainted(false); - button.setBorderPainted(false); - button.setCursor(Cursor.getPredefinedCursor(Cursor.HAND_CURSOR)); - - button.addActionListener(e -> { - JPanel toolsPanel = createToolsPanel(category); - toolsPanel.setOpaque(false); - contentPanel.removeAll(); - JScrollPane scrollPane = new JScrollPane(toolsPanel); - scrollPane.setBorder(null); - scrollPane.setOpaque(false); - scrollPane.getViewport().setOpaque(false); - scrollPane.getVerticalScrollBar().setUI(new CustomScrollBarUI()); - contentPanel.add(scrollPane, BorderLayout.CENTER); - contentPanel.revalidate(); - contentPanel.repaint(); - }); - - sidebar.add(Box.createVerticalStrut(10)); - sidebar.add(button); - } - - // 添加设置按钮 - JButton settingsButton = new JButton("设置"); - settingsButton.setAlignmentX(Component.CENTER_ALIGNMENT); - settingsButton.setMaximumSize(new Dimension(160, 40)); - settingsButton.setForeground(Color.WHITE); - settingsButton.setBackground(new Color(45, 45, 45)); - settingsButton.setFocusPainted(false); - settingsButton.setBorderPainted(false); - settingsButton.setCursor(Cursor.getPredefinedCursor(Cursor.HAND_CURSOR)); - settingsButton.addActionListener(e -> showSettings()); - sidebar.add(Box.createVerticalGlue()); - sidebar.add(settingsButton); - - // 添加关于按钮 - JButton aboutButton = new JButton("关于"); - aboutButton.setAlignmentX(Component.CENTER_ALIGNMENT); - aboutButton.setMaximumSize(new Dimension(160, 40)); - aboutButton.setForeground(Color.WHITE); - aboutButton.setBackground(new Color(45, 45, 45)); - aboutButton.setFocusPainted(false); - aboutButton.setBorderPainted(false); - aboutButton.setCursor(Cursor.getPredefinedCursor(Cursor.HAND_CURSOR)); - aboutButton.addActionListener(e -> JOptionPane.showMessageDialog(this, "作者: tzdwindows7")); - sidebar.add(Box.createVerticalStrut(10)); - sidebar.add(aboutButton); - - return sidebar; - } - - - private JPanel createFooter() { - JPanel footer = new JPanel(); - footer.setLayout(new BoxLayout(footer, BoxLayout.X_AXIS)); - footer.setBorder(BorderFactory.createEmptyBorder(15, 30, 15, 30)); - - JLabel version = new JLabel("轴创工具箱 v1.0"); - version.setFont(new Font("微软雅黑", Font.PLAIN, 12)); - version.setForeground(new Color(120, 120, 120)); - - footer.add(version); - footer.add(Box.createHorizontalGlue()); - - JLabel status = new JLabel("已加载 " + categories.size() + " 个分类, " + - categories.stream().mapToInt(c -> c.getTools().size()).sum() + " 个工具"); - status.setFont(new Font("微软雅黑", Font.PLAIN, 12)); - status.setForeground(new Color(120, 120, 120)); - footer.add(status); - - return footer; - } - - // 基础菜单项创建方法(解决方法不存在问题) - private MenuItem createBaseMenuItem(String text, ActionListener action) { - MenuItem item = new MenuItem(text); - item.addActionListener(action); - return item; - } - - // 圆角图标生成(修正版) - private Image createRoundedIcon(Image source, int cornerRadius) { - int size = Math.max(source.getWidth(null), source.getHeight(null)); - BufferedImage output = new BufferedImage(size, size, BufferedImage.TYPE_INT_ARGB); - - Graphics2D g2 = output.createGraphics(); - g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); - - // 创建剪切路径 - RoundRectangle2D roundRect = new RoundRectangle2D.Float(0, 0, size, size, cornerRadius, cornerRadius); - g2.setClip(roundRect); - g2.drawImage(source, 0, 0, size, size, null); - - g2.dispose(); - return output; - } - - // 添加事件监听 - //private void addTrayEventListeners() { - // // 双击恢复窗口 - // trayIcon.addActionListener(e -> setVisible(true)); -// - // // 右键菜单触发兼容处理 - // trayIcon.addMouseListener(new MouseAdapter() { - // @Override - // public void mouseReleased(MouseEvent e) { - // if (e.isPopupTrigger()) { - // trayIcon.getPopupMenu().show(e.getComponent(), e.getX(), e.getY()); - // } - // } - // }); - //} - - // 初始化中文字体(在main方法或构造函数中调用) - private void initChineseFont() { - String[] fontPriority = { - "Microsoft YaHei", - "PingFang SC", - "SimSun", - Font.DIALOG - }; - - for (String fontName : fontPriority) { - if (isFontAvailable(fontName)) { - setGlobalFont(new Font(fontName, Font.PLAIN, 12)); - break; - } - } - } - - // 字体可用性检查 - private boolean isFontAvailable(String fontName) { - return Arrays.asList(GraphicsEnvironment - .getLocalGraphicsEnvironment() - .getAvailableFontFamilyNames()) - .contains(fontName); - } - - // 全局字体设置 - private void setGlobalFont(Font font) { - FontUIResource fontRes = new FontUIResource(font); - UIManager.put("MenuItem.font", fontRes); - UIManager.put("Menu.font", fontRes); - } - - /** - * 对图像应用动感模糊效果。 - * - * @param srcImage 需要应用动感模糊的源图像。 - * 这个参数应该是一个 `BufferedImage` 类型的对象。 - * @param radius 动感模糊的半径,决定模糊效果的范围。 - * 半径越大,模糊范围越广,模糊效果越强。 - * @param angle 动感模糊的角度,决定模糊效果的方向。 - * 角度单位为度,表示从正Y轴顺时针的旋转角度。 - * 比如: - * - `0` 表示水平方向的模糊。 - * - `90` 表示垂直方向的模糊。 - * - 其他角度则表示不同方向的模糊。 - * @return 返回应用了动感模糊效果的 `BufferedImage` 图像。 - * 原始图像不会被修改,返回的是处理过的新图像。 - */ - public BufferedImage applyMotionBlur(BufferedImage srcImage, int radius, int angle) { - double radian = Math.toRadians(angle); - - float[] matrix = new float[radius * radius]; - float sum = 0.0f; - int index = 0; - - for (int y = -radius / 2; y <= radius / 2; y++) { - for (int x = -radius / 2; x <= radius / 2; x++) { - float weight = (float) (Math.cos(radian) * x + Math.sin(radian) * y); - if (Math.abs(weight) < 1) { - matrix[index++] = 1.0f; - sum += 1.0f; - } else { - matrix[index++] = 0.0f; - } - } - } - - return getBufferedImage(srcImage, radius, matrix, sum); - } - - private BufferedImage getBufferedImage(BufferedImage srcImage, int radius, float[] matrix, float sum) { - for (int i = 0; i < matrix.length; i++) { - matrix[i] /= sum; - } - Kernel kernel = new Kernel(radius, radius, matrix); - ConvolveOp convolve = new ConvolveOp(kernel, ConvolveOp.EDGE_NO_OP, null); - return convolve.filter(srcImage, null); - } - - /** - * 应用高斯模糊效果到给定的图像。 - * - * @param srcImage 输入的原始图像,类型为 `BufferedImage`。 - * @param radius 高斯模糊的半径,表示模糊的强度。半径越大,模糊效果越强。 - * @return 返回模糊处理后的图像,类型为 `BufferedImage`。 - */ - public BufferedImage applyGaussianBlur(BufferedImage srcImage, int radius) { - if (radius > 100) { - radius = 100; - } - - float[] matrix = new float[radius * radius]; - float sigma = radius / 2f; - float sum = 0.0f; - int index = 0; - - for (int y = -radius / 2; y <= radius / 2; y++) { - for (int x = -radius / 2; x <= radius / 2; x++) { - float weight = (float) Math.exp(-(x * x + y * y) / (2 * sigma * sigma)); - matrix[index] = weight; - index++; - sum += weight; - } - } - - return getBufferedImage(srcImage, radius, matrix, sum); - } - - private JComponent createCategoryTabs() { - JTabbedPane tabbedPane = new JTabbedPane(); - - //tabbedPane.putClientProperty(FlatClientProperties.TABBED_PANE_TAB_AREA_ALIGN, Component.LEFT_ALIGNMENT); - tabbedPane.putClientProperty(FlatClientProperties.TABBED_PANE_TAB_HEIGHT, 40); - tabbedPane.putClientProperty(FlatClientProperties.TABBED_PANE_TAB_ICON_PLACEMENT, SwingConstants.TOP); - - tabbedPane.setOpaque(false); - tabbedPane.setBackground(new Color(0, 0, 0, 0)); - tabbedPane.setBorder(null); - - //tabbedPane.setUI(new CustomTabbedPaneUI()); - + // 创建分类内容并添加到 cardsPanel for (ToolCategory category : categories) { JPanel toolsPanel = createToolsPanel(category); toolsPanel.setOpaque(false); - toolsPanel.setBorder(null); + categoryToolPanels.put(category.getId().toString(), toolsPanel); JScrollPane scrollPane = new JScrollPane(toolsPanel); scrollPane.setBorder(null); scrollPane.setOpaque(false); scrollPane.getViewport().setOpaque(false); - JScrollBar verticalScrollBar = scrollPane.getVerticalScrollBar(); - //verticalScrollBar.setUI(new CustomScrollBarUI()); - verticalScrollBar.setPreferredSize(new Dimension(10, 100)); + scrollPane.setVerticalScrollBarPolicy(ScrollPaneConstants.VERTICAL_SCROLLBAR_AS_NEEDED); + scrollPane.getVerticalScrollBar().setUI(new CustomScrollBarUI()); + scrollPane.getHorizontalScrollBar().setUnitIncrement(16); - if (category.getIcon() == null){ - tabbedPane.addTab( - category.getName(), - category.getIconImage(), - scrollPane - ); + categoryScrollPanes.put(category.getId().toString(), scrollPane); + cardsPanel.add(scrollPane, category.getId().toString()); + } + + // layeredPane 管理卡片层与 overlay 层 + layeredPane = new JLayeredPane(); + layeredPane.setLayout(null); // 使用绝对布局,手动设置 bounds + layeredPane.add(cardsPanel, JLayeredPane.DEFAULT_LAYER); + // 初始 bounds 将在后面同步 + cardsPanel.setBounds(0, 0, 900, 600); + + // 内容面板放入 layeredPane + contentPanel = new JPanel(new BorderLayout()); + contentPanel.setOpaque(false); + contentPanel.add(layeredPane, BorderLayout.CENTER); + center.add(contentPanel, BorderLayout.CENTER); + + mainPanel.add(center, BorderLayout.CENTER); + mainPanel.add(createFooter(), BorderLayout.SOUTH); + + add(mainPanel); + + // 同步 layeredPane 与 cardsPanel 大小 + addComponentListener(new ComponentAdapter() { + @Override + public void componentShown(ComponentEvent e) { + SwingUtilities.invokeLater(() -> syncLayeredBounds()); } - tabbedPane.addTab( - category.getName(), - LoadIcon.loadIcon(category.getIcon(), 12), - scrollPane - ); + + @Override + public void componentResized(ComponentEvent e) { + SwingUtilities.invokeLater(() -> syncLayeredBounds()); + } + }); + // 立刻 sync + SwingUtilities.invokeLater(this::syncLayeredBounds); + + // 默认选中第一个分类 + if (!categories.isEmpty()) { + String firstId = categories.get(0).getId().toString(); + SwingUtilities.invokeLater(() -> switchCategory(firstId, true)); } - return tabbedPane; + // 更新 UI 字体样式(确保生效) + SwingUtilities.updateComponentTreeUI(this); } - private JPanel createToolsPanel(ToolCategory category) { - JPanel panel = new JPanel(new GridLayout(0, 3, 20, 20)); - for (ToolItem tool : category.getTools()) { - panel.add(createToolCard(tool)); + private void syncLayeredBounds() { + if (layeredPane == null) return; + Dimension d = contentPanel.getSize(); + // contentPanel may be zero when not displayed; use cardsPanel preferred as fallback + if (d.width <= 0 || d.height <= 0) { + d = getContentPane().getSize(); + if (d.width <= 0 || d.height <= 0) d = new Dimension(900, 600); } - panel.setOpaque(false); - panel.setBorder(null); - return panel; + layeredPane.setBounds(0, 0, d.width, d.height); + layeredPane.setPreferredSize(d); + cardsPanel.setBounds(0, 0, d.width, d.height); + layeredPane.revalidate(); + layeredPane.repaint(); } + // ---------- Header (包含圆角搜索框) ---------- private JPanel createHeader() { - JPanel header = new JPanel(new BorderLayout()); + JPanel header = new JPanel(new BorderLayout(8, 8)); header.setOpaque(false); - header.setBorder(BorderFactory.createEmptyBorder(15, 30, 15, 30)); + header.setBorder(BorderFactory.createEmptyBorder(6, 6, 6, 6)); - // 创建标题 JLabel title = new JLabel(LanguageManager.getLoadedLanguages().getText("mainWindow.title.2")); - title.setFont(new Font("微软雅黑", Font.BOLD, 28)); - title.setForeground(new Color(255, 255, 255)); + title.setFont(new Font(selectFont("Segoe UI", "Microsoft YaHei", "SansSerif", 18).getName(), Font.BOLD, 18)); + title.setForeground(UIManager.getColor("Label.foreground")); + JPanel left = new JPanel(new FlowLayout(FlowLayout.LEFT, 8, 0)); + left.setOpaque(false); + left.add(title); + // 圆角搜索框容器(居中) + searchField = new RoundedSearchField(360, 34); + searchField.setToolTipText("搜索当前分类工具(回车或实时过滤)"); + searchField.addDocumentListener(new DocumentListener() { + @Override public void insertUpdate(DocumentEvent e) { filterCurrentCategory(searchField.getText()); } + @Override public void removeUpdate(DocumentEvent e) { filterCurrentCategory(searchField.getText()); } + @Override public void changedUpdate(DocumentEvent e) { filterCurrentCategory(searchField.getText()); } + }); + searchField.addActionListener(e -> filterCurrentCategory(searchField.getText())); - JButton settings = new JButton(LoadIcon.loadIcon("settings.png", 32)); + JPanel center = new JPanel(new GridBagLayout()); + center.setOpaque(false); + center.add(searchField); + + JButton settings = new JButton(LoadIcon.loadIcon("settings.png", 24)); settings.putClientProperty(FlatClientProperties.BUTTON_TYPE, FlatClientProperties.BUTTON_TYPE_BORDERLESS); settings.setCursor(Cursor.getPredefinedCursor(Cursor.HAND_CURSOR)); settings.setContentAreaFilled(false); settings.addActionListener(e -> showSettings()); - JPanel titlePanel = new JPanel(new FlowLayout(FlowLayout.LEFT, 0, 0)); - titlePanel.setOpaque(false); - titlePanel.add(title); - - header.add(titlePanel, BorderLayout.WEST); + header.add(left, BorderLayout.WEST); + header.add(center, BorderLayout.CENTER); header.add(settings, BorderLayout.EAST); return header; } + // 自定义圆角的搜索文本框容器(含聚焦动画) + private static 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; - @Override - public void update(Graphics g) { - GlobalEventBus.EVENT_BUS.post(new MainWindowEvents.update(this, g)); - super.update(g); + RoundedSearchField(int baseWidth, int heightPx) { + this.baseWidth = baseWidth; + this.heightPx = heightPx; + this.targetWidth = baseWidth; + setOpaque(false); + setLayout(new BorderLayout()); + textField = new JTextField(); + textField.setBorder(BorderFactory.createEmptyBorder(6, 10, 6, 10)); + textField.setOpaque(false); + textField.setFont(UIManager.getFont("TextField.font")); + // initial size + setPreferredSize(new Dimension(baseWidth, heightPx)); + add(textField, BorderLayout.CENTER); + + // focus listener -> expand + highlight + textField.addFocusListener(new FocusAdapter() { + @Override + public void focusGained(FocusEvent e) { + animateTo(baseWidth + 80); + } + + @Override + public void focusLost(FocusEvent e) { + animateTo(baseWidth); + } + }); + + // mouse click on panel focuses textfield + addMouseListener(new MouseAdapter() { + @Override public void mouseClicked(MouseEvent e) { textField.requestFocusInWindow(); } + }); + + // animation timer + animTimer = new Timer(16, ae -> { + // smooth progress towards target width + int curW = getWidth(); + int diff = targetWidth - curW; + if (Math.abs(diff) <= 1) { + setPreferredSize(new Dimension(targetWidth, heightPx)); + revalidate(); + repaint(); + 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(); + } + }); + } + + 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); } } - private WindowsJDialog dialog; + // ---------- 侧边栏 ---------- + private JPanel createSideBar() { + JPanel sidebar = new JPanel(new BorderLayout()); + sidebar.setOpaque(true); + sidebar.setBackground(SIDEBAR_COLOR); + sidebar.setPreferredSize(new Dimension(220, getHeight())); + sidebar.setBorder(BorderFactory.createEmptyBorder(8, 8, 8, 8)); + + // top: logo + app name + JPanel top = new JPanel(new BorderLayout()); + top.setOpaque(false); + JLabel logo = new JLabel(LoadIcon.loadIcon("logo.png", 36)); + logo.setBorder(BorderFactory.createEmptyBorder(8, 6, 8, 6)); + JLabel appName = new JLabel(LanguageManager.getLoadedLanguages().getText("mainWindow.title.2")); + appName.setFont(new Font(selectFont("Segoe UI", "Microsoft YaHei", "SansSerif", 15).getName(), Font.BOLD, 15)); + appName.setForeground(Color.WHITE); + appName.setBorder(BorderFactory.createEmptyBorder(8, 6, 8, 6)); + top.add(logo, BorderLayout.WEST); + top.add(appName, BorderLayout.CENTER); + sidebar.add(top, BorderLayout.NORTH); + + // list of categories + JPanel listPanel = new JPanel(); + listPanel.setLayout(new BoxLayout(listPanel, BoxLayout.Y_AXIS)); + listPanel.setOpaque(false); + listPanel.setBorder(BorderFactory.createEmptyBorder(8, 6, 8, 6)); + + for (ToolCategory category : categories) { + JButton btn = createSideButton(category); + listPanel.add(btn); + listPanel.add(Box.createVerticalStrut(8)); + sideButtons.put(category.getId().toString(), btn); + } + + JScrollPane listScroll = new JScrollPane(listPanel); + listScroll.setBorder(null); + listScroll.setOpaque(false); + listScroll.getViewport().setOpaque(false); + listScroll.setHorizontalScrollBarPolicy(ScrollPaneConstants.HORIZONTAL_SCROLLBAR_NEVER); + listScroll.setVerticalScrollBarPolicy(ScrollPaneConstants.VERTICAL_SCROLLBAR_AS_NEEDED); + listScroll.getVerticalScrollBar().setUI(new CustomScrollBarUI()); + listScroll.setBackground(new Color(0,0,0,0)); + + sidebar.add(listScroll, BorderLayout.CENTER); + + // bottom = settings / about + JPanel bottom = new JPanel(); + bottom.setOpaque(false); + bottom.setLayout(new BoxLayout(bottom, BoxLayout.Y_AXIS)); + bottom.setBorder(BorderFactory.createEmptyBorder(6, 6, 6, 6)); + + //JButton settingsButton = new JButton("设置"); + //styleSideSmallButton(settingsButton); + //settingsButton.addActionListener(e -> showSettings()); + //settingsButton.setBackground(SIDEBAR_COLOR); + //bottom.add(settingsButton); + //bottom.add(Box.createVerticalStrut(8)); +// + //JButton aboutButton = new JButton("关于"); + //styleSideSmallButton(aboutButton); + //aboutButton.addActionListener(e -> JOptionPane.showMessageDialog(this, "作者: tzdwindows7")); + //bottom.add(aboutButton); + + sidebar.add(bottom, BorderLayout.SOUTH); + + return sidebar; + } + + private JButton createSideButton(ToolCategory category) { + JButton button = new JButton(category.getName()); + button.setMaximumSize(new Dimension(Integer.MAX_VALUE, 44)); + button.setHorizontalAlignment(SwingConstants.LEFT); + // icon 使用白色版本如果可用(你可以自己准备深色侧栏专用图标) + ImageIcon ic = category.getIconImage() != null ? category.getIconImage() : LoadIcon.loadIcon(category.getIcon(), 18); + button.setIcon(ic); + button.setIconTextGap(12); + button.setCursor(Cursor.getPredefinedCursor(Cursor.HAND_CURSOR)); + button.setForeground(Color.WHITE); + button.setBackground(new Color(0,0,0,0)); + button.setOpaque(false); + button.setBorder(BorderFactory.createEmptyBorder(8, 12, 8, 12)); + button.setFocusPainted(false); + button.setFont(new Font(selectFont("Segoe UI", "Microsoft YaHei", "SansSerif", 13).getName(), Font.PLAIN, 13)); + + button.addActionListener(e -> { + // 切换分类时清空搜索并触发动画切换 + searchField.setText(""); + switchCategory(category.getId().toString(), false); + }); + + // hover 效果(浅背景) + button.addMouseListener(new MouseAdapter() { + @Override public void mouseEntered(MouseEvent e) { + if (!Objects.equals(currentCategoryId, category.getId().toString())) { + button.setOpaque(true); + button.setBackground(new Color(0x4A4D50)); // 深灰微亮 + } + } + @Override public void mouseExited(MouseEvent e) { + if (!Objects.equals(currentCategoryId, category.getId().toString())) { + button.setOpaque(false); + button.setBackground(new Color(0,0,0,0)); + } + } + }); + + return button; + } + + private void styleSideSmallButton(JButton btn) { + btn.setMaximumSize(new Dimension(Integer.MAX_VALUE, 36)); + btn.setAlignmentX(Component.LEFT_ALIGNMENT); + btn.setCursor(Cursor.getPredefinedCursor(Cursor.HAND_CURSOR)); + btn.setForeground(Color.DARK_GRAY); + btn.setBackground(new Color(0xF4F4F4)); + btn.setFocusPainted(false); + btn.setBorder(BorderFactory.createLineBorder(new Color(220,220,220))); + btn.setOpaque(true); + btn.setFont(new Font(selectFont("Segoe UI", "Microsoft YaHei", "SansSerif", 12).getName(), Font.PLAIN, 12)); + } + + // ---------- 工具卡/面板 ---------- + private JPanel createToolsPanel(ToolCategory category) { + JPanel panel = new JPanel(new WrapLayout(FlowLayout.LEFT, 16, 16)); + panel.setOpaque(false); + panel.setBorder(null); + + for (ToolItem tool : category.getTools()) { + JPanel card = createToolCard(tool); + panel.add(card); + } + + JPanel wrapper = new JPanel(new BorderLayout()); + wrapper.setOpaque(false); + wrapper.add(panel, BorderLayout.NORTH); + return wrapper; + } + + private JPanel createToolCard(ToolItem tool) { + JPanel card = new JPanel() { + @Override + protected void paintComponent(Graphics g) { + Graphics2D g2 = (Graphics2D) g.create(); + int w = getWidth(), h = getHeight(); + g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); + + // 轻微阴影 + g2.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER, 0.06f)); + g2.setColor(Color.BLACK); + for (int i = 0; i < 3; i++) { + g2.fill(new RoundRectangle2D.Float(i, i, w - i*2, h - i*2, 12, 12)); + } + + // 卡片背景:使用浅灰而非突兀白色,跟 FlatLightLaf 保持协调 + g2.setComposite(AlphaComposite.SrcOver); + Color bg = new Color(250, 250, 250); + g2.setColor(bg); + g2.fill(new RoundRectangle2D.Float(0, 0, w, h, 12, 12)); + + // 修改卡片背景和边框颜色 + g2.setColor(CARD_BG); + g2.fill(new RoundRectangle2D.Float(0, 0, w, h, 12, 12)); + + // 边框 + g2.setColor(CARD_BORDER); + g2.setStroke(new BasicStroke(1f)); + g2.draw(new RoundRectangle2D.Float(0.5f, 0.5f, w - 1, h - 1, 12, 12)); + + // 边框 + g2.setColor(CARD_BORDER); + g2.setStroke(new BasicStroke(1f)); + g2.draw(new RoundRectangle2D.Float(0.5f, 0.5f, w - 1, h - 1, 12, 12)); + + g2.dispose(); + + super.paintComponent(g); + } + + @Override public boolean isOpaque() { return false; } + }; + card.setOpaque(false); + card.setLayout(new BorderLayout(10, 8)); + card.setBorder(BorderFactory.createEmptyBorder(10, 10, 10, 10)); + card.setPreferredSize(new Dimension(240, 140)); + 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(), 48)); + } + iconLabel.setBorder(BorderFactory.createEmptyBorder(4,4,4,4)); + iconLabel.setHorizontalAlignment(SwingConstants.LEFT); + + JPanel centerPanel = new JPanel(new BorderLayout()); + centerPanel.setOpaque(false); + JLabel titleLabel = new JLabel(tool.getTitle()); + titleLabel.setFont(new Font(selectFont("Segoe UI", "Microsoft YaHei", "SansSerif", 15).getName(), Font.BOLD, 15)); + titleLabel.setForeground(UIManager.getColor("Label.foreground")); + JTextArea descArea = new JTextArea(tool.getDescription()); + descArea.setFont(new Font(selectFont("Segoe UI", "Microsoft YaHei", "SansSerif", 13).getName(), Font.PLAIN, 13)); + titleLabel.setForeground(TEXT_COLOR); + descArea.setForeground(TEXT_COLOR.darker()); + descArea.setForeground(new Color(100,100,105)); + descArea.setLineWrap(true); + descArea.setWrapStyleWord(true); + descArea.setEditable(false); + descArea.setOpaque(false); + descArea.setBorder(null); + descArea.setRows(3); + + centerPanel.add(titleLabel, BorderLayout.NORTH); + centerPanel.add(descArea, BorderLayout.CENTER); + + card.add(iconLabel, BorderLayout.WEST); + card.add(centerPanel, BorderLayout.CENTER); + + card.setToolTipText(createToolTipHTML(tool)); + + // 初始化状态 + cardScales.put(card, 1.0f); + cardElevations.put(card, 2); + + // 点击动画与动作触发 + CardMouseAdapter adapter = new CardMouseAdapter(card, tool); + card.addMouseListener(adapter); + + // 保留 PanelUI 钩子(事件总线) + card.setUI(new PanelUI() { + @Override public void installUI(JComponent c) { + GlobalEventBus.EVENT_BUS.post(new TABUIEvents(card, c)); + super.installUI(c); + } + @Override public void update(Graphics g, JComponent c) { + GlobalEventBus.EVENT_BUS.post(new TABUIEvents.update(g, c)); + super.update(g, c); + } + }); + + // 悬停时增强阴影(动画) + card.addMouseListener(new CardMouseAdapter(card, tool) { + @Override public void mouseEntered(MouseEvent e) { + cardElevations.put(card, 8); + animateCardElevation(card, 8); + } + @Override public void mouseExited(MouseEvent e) { + animateCardElevation(card, 2); + } + }); + + return card; + } + + private String createToolTipHTML(ToolItem tool) { + return "" + + "

" + escapeHtml(tool.getName()) + "

" + + "
" + escapeHtml(tool.getDescription()) + "
" + + ""; + } + + private String escapeHtml(String s) { + if (s == null) return ""; + return s.replace("&","&").replace("<","<").replace(">",">").replace("\n","
"); + } + + // 卡片放大/阴影动画(合并) + private void animateCardElevation(JComponent card, int targetElevation) { + new Timer(12, new AbstractAction() { + private int currentElevation = cardElevations.getOrDefault(card, 2); + @Override + public void actionPerformed(ActionEvent e) { + if (currentElevation < targetElevation) currentElevation++; + else if (currentElevation > targetElevation) currentElevation--; + cardElevations.put(card, currentElevation); + + float targetScale = 1.0f + (currentElevation - 2) * 0.0065f; + float cur = cardScales.getOrDefault(card, 1.0f); + float diff = targetScale - cur; + float step = diff * 0.28f; + if (Math.abs(diff) < 0.001f) cardScales.put(card, targetScale); + else cardScales.put(card, cur + step); + + card.repaint(); + if (currentElevation == targetElevation && Math.abs(cardScales.get(card) - targetScale) < 0.002f) ((Timer)e.getSource()).stop(); + } + }).start(); + } + + // 切换分类(使用 layeredPane 添加 overlay 动画) + private void switchCategory(String categoryId, boolean immediate) { + if (Objects.equals(currentCategoryId, categoryId)) return; + JScrollPane newPane = categoryScrollPanes.get(categoryId); + if (newPane == null) return; + + Component currentComp = null; + if (currentCategoryId != null) currentComp = categoryScrollPanes.get(currentCategoryId); + + currentCategoryId = categoryId; + updateSideSelection(categoryId); + + if (immediate || currentComp == null) { + cardsLayout.show(cardsPanel, categoryId); + return; + } + + // 先隐藏新面板 + cardsPanel.setVisible(false); + + // 创建 overlay(自定义绘制旧/新快照) + final JComponent oldComp = (JComponent) currentComp; + final JComponent newComp = newPane; + + // overlay 覆盖在 cardsPanel 之上 + JComponent overlay = new JComponent() { + private float progress = 0f; + @Override protected void paintComponent(Graphics g) { + super.paintComponent(g); + Graphics2D g2 = (Graphics2D) g.create(); + int w = getWidth(), h = getHeight(); + + BufferedImage oldImg = createComponentImage(oldComp, w, h); + BufferedImage newImg = createComponentImage(newComp, w, h); + + if (oldImg != null) { + float alphaOld = Math.max(0f, 1f - progress); + int xOld = (int) (-progress * w * 0.2f); + g2.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER, alphaOld)); + g2.drawImage(oldImg, xOld, 0, w, h, null); + } + if (newImg != null) { + float alphaNew = Math.min(1f, progress); + int xNew = (int) ((1f - progress) * w * 0.08f); + g2.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER, alphaNew)); + g2.drawImage(newImg, xNew, 0, w, h, null); + } + g2.dispose(); + } + }; + + // set bounds and add to layeredPane at PALETTE_LAYER + Dimension size = cardsPanel.getSize(); + overlay.setBounds(0, 0, size.width, size.height); + layeredPane.add(overlay, JLayeredPane.PALETTE_LAYER); + layeredPane.revalidate(); + layeredPane.repaint(); + + // animate overlay.progress from 0 -> 1 + Timer t = new Timer(16, null); + final long start = System.currentTimeMillis(); + t.addActionListener(e -> { + float raw = (System.currentTimeMillis() - start) / 320f; + float eased = Math.min(1f, (float)(1 - Math.pow(1 - raw, 3))); + try { + java.lang.reflect.Field f = overlay.getClass().getDeclaredField("progress"); + f.setAccessible(true); + f.setFloat(overlay, eased); + } catch (Exception ex) { + // ignore + } + overlay.repaint(); + if (raw >= 1f) { + ((Timer)e.getSource()).stop(); + layeredPane.remove(overlay); + layeredPane.revalidate(); + layeredPane.repaint(); + // 动画完成后才显示新面板 + cardsPanel.setVisible(true); + cardsLayout.show(cardsPanel, categoryId); + } + }); + t.start(); + } + + // 使用 printAll 截图组件,避免渲染残留 + private BufferedImage createComponentImage(Component comp, int targetW, int targetH) { + if (comp == null) return null; + Dimension size = comp.getSize(); + if (size.width <= 0 || size.height <= 0) { + size = cardsPanel.getSize(); + if (size.width <= 0 || size.height <= 0) return null; + } + int w = Math.max(1, size.width); + int h = Math.max(1, size.height); + BufferedImage img = new BufferedImage(Math.max(w, targetW), Math.max(h, targetH), BufferedImage.TYPE_INT_ARGB); + Graphics2D g = img.createGraphics(); + // 使用 printAll 完整绘制子组件,从而避免轻/重组件混合导致的渲染残留 + comp.printAll(g); + g.dispose(); + return img; + } + + private JPanel createFooter() { + JPanel footer = new JPanel(new BorderLayout()); + footer.setOpaque(false); + footer.setBorder(BorderFactory.createEmptyBorder(8, 8, 8, 8)); + JLabel status = new JLabel("Ready"); + status.setFont(new Font(selectFont("Segoe UI", "Microsoft YaHei", "SansSerif", 12).getName(), Font.PLAIN, 12)); + status.setForeground(UIManager.getColor("Label.foreground")); + footer.add(status, BorderLayout.WEST); + return footer; + } + + private void updateSideSelection(String categoryId) { + for (Map.Entry e : sideButtons.entrySet()) { + String id = e.getKey(); + JButton btn = e.getValue(); + if (Objects.equals(id, categoryId)) { + btn.setOpaque(true); + btn.setBackground(new Color(0x4A90E2)); // 选中时蓝色条(也可以改为其他色) + btn.setBorder(BorderFactory.createMatteBorder(0, 4, 0, 0, new Color(0x0A66C2))); + // 动画提示 + animateButtonPulse(btn); + } else { + btn.setOpaque(false); + btn.setBackground(new Color(0,0,0,0)); + btn.setBorder(BorderFactory.createEmptyBorder(0,4,0,0)); + } + } + } + + private void animateButtonPulse(JButton btn) { + Timer t = new Timer(14, null); + final long start = System.currentTimeMillis(); + Font base = btn.getFont(); + t.addActionListener(e -> { + float p = Math.min(1f, (System.currentTimeMillis() - start) / 260f); + float eased = (float)(1 - Math.pow(1 - p, 3)); + float s = 1f + 0.03f * (float)Math.sin(eased * Math.PI); + btn.setFont(base.deriveFont(base.getSize2D() * s)); + if (p >= 1f) { + ((Timer)e.getSource()).stop(); + btn.setFont(base); + } + }); + t.start(); + } + + // 搜索过滤当前分类(按 title/name) + private void filterCurrentCategory(String query) { + if (currentCategoryId == null) return; + String q = query == null ? "" : query.trim().toLowerCase(); + ToolCategory category = categories.stream().filter(tc -> Objects.equals(tc.getId().toString(), currentCategoryId)).findFirst().orElse(null); + if (category == null) return; + + SwingUtilities.invokeLater(() -> { + JPanel newPanel = new JPanel(new WrapLayout(FlowLayout.LEFT, 16, 16)); + newPanel.setOpaque(false); + for (ToolItem tool : category.getTools()) { + if (q.isEmpty() || tool.getTitle().toLowerCase().contains(q) || tool.getName().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.revalidate(); + scrollPane.repaint(); + } + }); + } + + // ---------- 字体选择工具(返回 Font) ---------- + private Font selectFont(Object... namesAndSize) { + int size = 13; + return new Font("微软雅黑", Font.PLAIN, size); + } + + private boolean isFontAvailable(String fontName) { + if (fontName == null) return false; + try { + return Arrays.asList(GraphicsEnvironment.getLocalGraphicsEnvironment().getAvailableFontFamilyNames()).contains(fontName); + } catch (Exception e) { + return false; + } + } + + // ---------- showSettings 恢复实现 ---------- public void showSettings() { if (dialog == null || AxisInnovatorsBox.getMain().isWindowStartup(dialog)) { dialog = new WindowsJDialog(this, LanguageManager.getLoadedLanguages().getText("mainWindow.settings.title"), true); } dialog.setTitle(LanguageManager.getLoadedLanguages().getText("mainWindow.settings.title")); - dialog.setSize(600, 400); + dialog.setSize(680, 480); dialog.setLocationRelativeTo(this); JPanel content = new JPanel(new BorderLayout()); - content.setBorder(BorderFactory.createEmptyBorder(20, 20, 20, 20)); + content.setBorder(BorderFactory.createEmptyBorder(12, 12, 12, 12)); + content.setOpaque(false); JTabbedPane tabbedPane = new JTabbedPane(); - List registrationSettingsItemList = RegistrationSettingsItem.getRegistrationSettingsItemList(); for (RegistrationSettingsItem registrationSettingsItem : registrationSettingsItemList) { @@ -455,147 +851,7 @@ public class MainWindow extends JFrame { } } - private JPanel createToolCard(ToolItem tool) { - JPanel card = new JPanel() { - @Override - protected void paintComponent(Graphics g) { - Graphics2D g2d = (Graphics2D) g.create(); - - // 毛玻璃效果 - BufferedImage background = new BufferedImage( - getWidth(), getHeight(), BufferedImage.TYPE_INT_ARGB); - super.paintComponent(background.getGraphics()); - BufferedImage blurred = applyGaussianBlur(background, 5); - - g2d.drawImage(blurred, 0, 0, null); - - // 绘制阴影 - int elevation = cardElevations.getOrDefault(this, 2); - for (int i = 0; i < elevation; i++) { - g2d.setColor(new Color(0, 0, 0, 20 - i * 2)); - g2d.fillRoundRect(i, i, getWidth() - i * 2, getHeight() - i * 2, 15, 15); - } - - float scale = cardScales.getOrDefault(this, 1.0f); - int offset = (int) ((scale - 1) * getWidth() / 2); - g2d.translate(-offset, -offset); - g2d.scale(scale, scale); - - super.paintComponent(g2d); - g2d.dispose(); - } - }; - card.setPreferredSize(new Dimension(200, 150)); - card.setMinimumSize(new Dimension(100, 75)); - card.setLayout(new BorderLayout(15, 15)); - //card.setBackground(CARD_COLOR); - card.setBorder(BorderFactory.createCompoundBorder( - BorderFactory.createLineBorder(new Color(225, 229, 234), 1), - BorderFactory.createEmptyBorder(20, 20, 20, 20) - )); - 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(), 64)); - } - - iconLabel.setHorizontalAlignment(SwingConstants.CENTER); - - // 文字面板 - JPanel textPanel = new JPanel(); - textPanel.setLayout(new BoxLayout(textPanel, BoxLayout.Y_AXIS)); - textPanel.setOpaque(false); - - JLabel titleLabel = new JLabel(tool.getTitle()); - titleLabel.setFont(new Font("微软雅黑", Font.BOLD, 18)); - titleLabel.setForeground(new Color(255, 255, 255)); - titleLabel.setAlignmentX(Component.CENTER_ALIGNMENT); - - JTextArea descArea = new JTextArea(tool.getDescription()); - descArea.setFont(new Font("微软雅黑", Font.PLAIN, 14)); - descArea.setForeground(new Color(177, 177, 177)); - descArea.setLineWrap(true); - descArea.setWrapStyleWord(true); - descArea.setEditable(false); - descArea.setOpaque(false); - descArea.setAlignmentX(Component.CENTER_ALIGNMENT); - - card.setToolTipText(createToolTipHTML(tool)); - - textPanel.add(titleLabel); - textPanel.add(Box.createVerticalStrut(10)); - textPanel.add(descArea); - - card.add(iconLabel, BorderLayout.NORTH); - card.add(textPanel, BorderLayout.CENTER); - - card.addMouseListener(new CardMouseAdapter(card, tool)); - card.addMouseListener(new MouseAdapter() { - @Override - public void mouseClicked(MouseEvent e) { - SwingUtilities.invokeLater(() -> { - - }); - } - }); - card.setUI(new PanelUI() { - @Override - public void installUI(JComponent c) { - GlobalEventBus.EVENT_BUS.post(new TABUIEvents(card, c)); - super.installUI(c); - } - - @Override - public void update(Graphics g, JComponent c) { - GlobalEventBus.EVENT_BUS.post(new TABUIEvents.update(g, c)); - super.update(g, c); - } - }); - - card.addMouseListener(new CardMouseAdapter(card, tool) { - @Override - public void mouseEntered(MouseEvent e) { - cardElevations.put(card, 8); - animateCardElevation(card, 8); - } - - @Override - public void mouseExited(MouseEvent e) { - animateCardElevation(card, 2); - } - }); - return card; - } - - - private String createToolTipHTML(ToolItem tool) { - return "" + - "

" + tool.getName() + "

" + - "

" + tool.getDescription() + "

" + - ""; - } - - private void animateCardElevation(JComponent card, int targetElevation) { - new Timer(10, new AbstractAction() { - private int currentElevation = cardElevations.getOrDefault(card, 2); - - @Override - public void actionPerformed(ActionEvent e) { - if (currentElevation < targetElevation) currentElevation++; - else if (currentElevation > targetElevation) currentElevation--; - - cardElevations.put(card, currentElevation); - card.repaint(); - - if (currentElevation == targetElevation) { - ((Timer)e.getSource()).stop(); - } - } - }).start(); - } - + // ---------- 内部类:ToolCategory, ToolItem 等(保持原结构) ---------- public static class ToolCategory { private final String name; private final String icon; @@ -603,238 +859,134 @@ public class MainWindow extends JFrame { private final String description; private final UUID id = UUID.randomUUID(); private final List tools = new ArrayList<>(); - - /** - * 一个大的分类项类 - * @param name 分类项的显示名称 - * @param icon 分类项的图标(resources的路径) - * @param description 分类项的描述 - */ public ToolCategory(String name, String icon, String description) { - this.name = name; - this.icon = icon; - this.description = description; - this.iconImage = null; + this.name = name; this.icon = icon; this.description = description; this.iconImage = null; } - public ToolCategory(String name, ImageIcon icon, String description) { - this.name = name; - this.iconImage = icon; - this.description = description; - this.icon = null; - } - - /** - * 注册工具的方法 - * @param tool 工具项 - */ - public void addTool(ToolItem tool) { - tools.add(tool); - } - - public String getDescription() { - return description; - } - - public String getIcon() { - return icon; - } - - public String getName() { - return name; - } - - public List getTools() { - return tools; - } - - public ImageIcon getIconImage() { - return iconImage; - } - - public UUID getId() { - return id; + this.name = name; this.iconImage = icon; this.description = description; this.icon = null; } + public void addTool(ToolItem tool) { tools.add(tool); } + public String getDescription() { return description; } + public String getIcon() { return icon; } + public String getName() { return name; } + public List getTools() { return tools; } + public ImageIcon getIconImage() { return iconImage; } + public UUID getId() { return id; } } - // 工具项数据类 - - /** - * 工具注册类 - */ public static class ToolItem { private final ImageIcon imageIcon; private final String title; private final String icon; - private final String description; private final int id; private final Action action; - - public ToolItem(String title, String icon, String description, int id, Action action){ - this.title = title; - this.icon = icon; - this.description = description; - this.id = id; - this.action = action; - this.imageIcon = null; + public ToolItem(String title, String icon, String description, int id, Action action) { + this.title = title; this.icon = icon; this.description = description; this.id = id; this.action = action; this.imageIcon = null; } - public ToolItem(String title, ImageIcon icon, String description, int id, Action action) { - this.title = title; - this.imageIcon = icon; - this.description = description; - this.id = id; - this.action = action; - this.icon = null; - } - - public String getTitle() { - return title; - } - - public ImageIcon icon() { - return imageIcon; - } - - public String getIcon() { - return icon; - } - - public Action getAction() { - return action; - } - - public ImageIcon getImageIcon() { - return imageIcon; - } - - public int getId() { - return id; - } - - public String getDescription() { - return description; - } - - public String getName() { - return title; + this.title = title; this.imageIcon = icon; this.description = description; this.id = id; this.action = action; this.icon = null; } + public String getTitle() { return title; } + public ImageIcon icon() { return imageIcon; } + public String getIcon() { return icon; } + public Action getAction() { return action; } + public ImageIcon getImageIcon() { return imageIcon; } + public int getId() { return id; } + public String getDescription() { return description; } + public String getName() { return title; } } - // 分类栏面卡 + // ---------- 自定义 Tab UI(保留) ---------- public static class CustomTabbedPaneUI extends BasicTabbedPaneUI { private static final Color SELECTED_COLOR = new Color(183, 202, 221); private static final Color UNSELECTED_COLOR = new Color(125, 174, 237); - - - @Override - protected void paintTabBackground(Graphics g, int tabPlacement, - int tabIndex, int x, int y, int w, int h, - boolean isSelected) { + @Override protected void paintTabBackground(Graphics g, int tabPlacement, int tabIndex, int x, int y, int w, int h, boolean isSelected) { Graphics2D g2d = (Graphics2D) g.create(); g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); - - if (isSelected) { - g2d.setColor(SELECTED_COLOR); - } else { - g2d.setColor(UNSELECTED_COLOR); - } - + g2d.setColor(isSelected ? SELECTED_COLOR : UNSELECTED_COLOR); g2d.fillRoundRect(x + 2, y + 2, w - 4, h - 4, 10, 10); g2d.dispose(); } } + // ---------- 自定义滚动条 ---------- + public static class CustomScrollBarUI extends BasicScrollBarUI { + @Override protected void configureScrollBarColors() { + this.thumbColor = new Color(160, 160, 180); + this.trackColor = new Color(245, 245, 248); + } + @Override protected JButton createDecreaseButton(int orientation) { return createInvisibleButton(); } + @Override protected JButton createIncreaseButton(int orientation) { return createInvisibleButton(); } + private JButton createInvisibleButton() { + JButton btn = new JButton(); btn.setPreferredSize(new Dimension(0,0)); btn.setBorder(null); return btn; + } + @Override protected void paintThumb(Graphics g, JComponent c, Rectangle thumbBounds) { + Graphics2D g2 = (Graphics2D) g.create(); + g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); + g2.setColor(thumbColor); + g2.fillRoundRect(thumbBounds.x + 2, thumbBounds.y + 2, Math.max(6, thumbBounds.width - 4), Math.max(10, thumbBounds.height - 4), 8, 8); + g2.dispose(); + } + } + + // ---------- 卡片鼠标适配器(含按压动画与点击触发) ---------- private class CardMouseAdapter extends MouseAdapter { private final JPanel card; private final ToolItem tool; private Timer pressTimer; private Timer releaseTimer; private static int delay = 0; - public CardMouseAdapter(JPanel card, ToolItem tool) { - this.card = card; - this.tool = tool; - } - - @Override - public void mousePressed(MouseEvent e) { - startPressAnimation(); - } - - @Override - public void mouseReleased(MouseEvent e) { + CardMouseAdapter(JPanel card, ToolItem tool) { this.card = card; this.tool = tool; } + @Override public void mousePressed(MouseEvent e) { startPressAnimation(); } + @Override public void mouseReleased(MouseEvent e) { if (delay == 0) { delay++; - startReleaseAnimation(() -> tool.getAction().actionPerformed( - new ActionEvent(card, ActionEvent.ACTION_PERFORMED, "") - )); - } else { - delay--; - } - } - - @Override - public void mouseExited(MouseEvent e) { - if (pressTimer != null && pressTimer.isRunning()) { - startReleaseAnimation(null); - } + startReleaseAnimation(() -> { + // 先强制重绘,避免图标残留(如果 action 引发 UI 线程阻塞也尽量降低残留) + card.repaint(); + cardsPanel.repaint(); + SwingUtilities.invokeLater(() -> { + try { + tool.getAction().actionPerformed(new ActionEvent(card, ActionEvent.ACTION_PERFORMED, "")); + } catch (Exception ex) { + logger.warn("Tool action failed: " + ex.getMessage(), ex); + } + }); + }); + } else { delay--; } } + @Override public void mouseExited(MouseEvent e) { if (pressTimer != null && pressTimer.isRunning()) startReleaseAnimation(null); } private void startPressAnimation() { - if (pressTimer != null && pressTimer.isRunning()) { - return; - } - - pressTimer = new Timer(10, new AbstractAction() { + if (pressTimer != null && pressTimer.isRunning()) return; + pressTimer = new Timer(12, new AbstractAction() { private final long startTime = System.currentTimeMillis(); - - @Override - public void actionPerformed(ActionEvent e) { - float progress = Math.min(1.0f, - (System.currentTimeMillis() - startTime) / 150f); - - // 使用二次缓动函数 - float scale = 1.0f - 0.1f * (float) Math.pow(progress, 0.5); + @Override public void actionPerformed(ActionEvent e) { + float progress = Math.min(1.0f, (System.currentTimeMillis() - startTime) / 160f); + float scale = 1.0f - 0.06f * (float)Math.pow(progress, 0.5); cardScales.put(card, scale); card.repaint(); - - if (progress >= 1.0f) { - ((Timer) e.getSource()).stop(); - } + if (progress >= 1.0f) ((Timer)e.getSource()).stop(); } }); pressTimer.start(); } private void startReleaseAnimation(Runnable callback) { - if (pressTimer != null) { - pressTimer.stop(); - } - if (releaseTimer != null && releaseTimer.isRunning()) { - return; - } - + if (pressTimer != null) pressTimer.stop(); + if (releaseTimer != null && releaseTimer.isRunning()) return; final float startScale = cardScales.getOrDefault(card, 1.0f); - releaseTimer = new Timer(10, new AbstractAction() { + releaseTimer = new Timer(12, new AbstractAction() { private final long startTime = System.currentTimeMillis(); - - @Override - public void actionPerformed(ActionEvent e) { - float progress = Math.min(1.0f, - (System.currentTimeMillis() - startTime) / 200f); - - // 使用弹性缓动函数 - float scale = startScale + - (1.0f - startScale) * (float) (1 - Math.pow(1 - progress, 3)); + @Override public void actionPerformed(ActionEvent e) { + float progress = Math.min(1.0f, (System.currentTimeMillis() - startTime) / 220f); + float scale = startScale + (1.0f - startScale) * (float)(1 - Math.pow(1 - progress, 3)); cardScales.put(card, scale); card.repaint(); - if (progress >= 1.0f) { - ((Timer) e.getSource()).stop(); - if (callback != null) { - callback.run(); - } + ((Timer)e.getSource()).stop(); + if (callback != null) callback.run(); } } }); @@ -842,43 +994,50 @@ public class MainWindow extends JFrame { } } - public static class CustomScrollBarUI extends BasicScrollBarUI { - @Override - protected void configureScrollBarColors() { - this.thumbColor = new Color(90, 90, 120); - this.trackColor = new Color(50, 50, 70); + // ---------- WrapLayout(用于自动换行卡片) ---------- + public static class WrapLayout extends FlowLayout { + public WrapLayout() { super(); } + public WrapLayout(int align) { super(align); } + public WrapLayout(int align, int hgap, int vgap) { super(align, hgap, vgap); } + @Override public Dimension preferredLayoutSize(Container target) { return layoutSize(target, true); } + @Override public Dimension minimumLayoutSize(Container target) { + Dimension minimum = layoutSize(target, false); + minimum.width -= (getHgap() + 1); + return minimum; } - - @Override - protected JButton createDecreaseButton(int orientation) { - return createInvisibleButton(); + private Dimension layoutSize(Container target, boolean preferred) { + synchronized (target.getTreeLock()) { + int targetWidth = target.getWidth(); + if (targetWidth == 0) targetWidth = Integer.MAX_VALUE; + int hgap = getHgap(); int vgap = getVgap(); + Insets insets = target.getInsets(); + int horizontalInsetsAndGap = insets.left + insets.right + (hgap * 2); + int maxWidth = targetWidth - horizontalInsetsAndGap; + Dimension dim = new Dimension(0, 0); + int rowWidth = 0; int rowHeight = 0; + int nmembers = target.getComponentCount(); + for (int i = 0; i < nmembers; i++) { + Component m = target.getComponent(i); + if (!m.isVisible()) continue; + Dimension d = preferred ? m.getPreferredSize() : m.getMinimumSize(); + if (rowWidth + d.width > maxWidth) { + addRow(dim, rowWidth, rowHeight); + rowWidth = 0; rowHeight = 0; + } + if (rowWidth != 0) rowWidth += hgap; + rowWidth += d.width; + rowHeight = Math.max(rowHeight, d.height); + } + addRow(dim, rowWidth, rowHeight); + dim.width += horizontalInsetsAndGap; + dim.height += insets.top + insets.bottom + vgap * 2; + return dim; + } } - - @Override - protected JButton createIncreaseButton(int orientation) { - return createInvisibleButton(); - } - - private JButton createInvisibleButton() { - JButton btn = new JButton(); - btn.setPreferredSize(new Dimension(0, 0)); - btn.setMinimumSize(new Dimension(0, 0)); - btn.setMaximumSize(new Dimension(0, 0)); - return btn; - } - - @Override - protected void paintThumb(Graphics g, JComponent c, Rectangle thumbBounds) { - Graphics2D g2 = (Graphics2D)g; - g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); - g2.setColor(thumbColor); - g2.fillRoundRect( - thumbBounds.x + 2, - thumbBounds.y + 2, - thumbBounds.width - 4, - thumbBounds.height - 4, - 8, 8 - ); + private void addRow(Dimension dim, int rowWidth, int rowHeight) { + dim.width = Math.max(dim.width, rowWidth); + if (dim.height > 0) dim.height += getVgap(); + dim.height += rowHeight; } } -} \ No newline at end of file +} diff --git a/src/main/java/com/axis/innovators/box/util/UserLocalInformation.java b/src/main/java/com/axis/innovators/box/util/UserLocalInformation.java index 121edf4..91ea57e 100644 --- a/src/main/java/com/axis/innovators/box/util/UserLocalInformation.java +++ b/src/main/java/com/axis/innovators/box/util/UserLocalInformation.java @@ -39,6 +39,9 @@ public class UserLocalInformation { OnlineVerification onlineVerification = OnlineVerification.validateLogin( verification, password); + if (onlineVerification == null){ + return null; + } return VerificationService.determineUserType(onlineVerification); } }