feat(theme): 动态主题适配及 UI 组件样式优化- 新增 updateTheme 方法以支持动态主题更新
- 优化 MainWindow 中的组件样式,包括按钮、滚动条等 - 调整侧边栏样式,增加选中状态和悬停效果 - 优化卡片背景和边框颜色,适应不同主题 - 修复部分组件在深色主题下的显示问题
@@ -889,10 +889,12 @@ public class AxisInnovatorsBox {
|
|||||||
for (WindowsJDialog windowsJDialog : windowsJDialogList) {
|
for (WindowsJDialog windowsJDialog : windowsJDialogList) {
|
||||||
windowsJDialog.getContentPane().removeAll();
|
windowsJDialog.getContentPane().removeAll();
|
||||||
windowsJDialog.initUI();
|
windowsJDialog.initUI();
|
||||||
|
windowsJDialog.updateTheme();
|
||||||
windowsJDialog.revalidate();
|
windowsJDialog.revalidate();
|
||||||
windowsJDialog.repaint();
|
windowsJDialog.repaint();
|
||||||
}
|
}
|
||||||
ex.initUI();
|
ex.initUI();
|
||||||
|
ex.updateTheme();
|
||||||
ex.revalidate();
|
ex.revalidate();
|
||||||
RegistrationSettingsItem.overloading();
|
RegistrationSettingsItem.overloading();
|
||||||
isWindow = true;
|
isWindow = true;
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ import org.apache.logging.log4j.Logger;
|
|||||||
|
|
||||||
import javax.swing.*;
|
import javax.swing.*;
|
||||||
import javax.swing.Timer;
|
import javax.swing.Timer;
|
||||||
|
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;
|
||||||
@@ -60,20 +61,20 @@ public class MainWindow extends JFrame {
|
|||||||
private WindowsJDialog dialog;
|
private WindowsJDialog dialog;
|
||||||
|
|
||||||
// 侧边栏颜色(比面板背景稍暗)
|
// 侧边栏颜色(比面板背景稍暗)
|
||||||
private static final Color SIDEBAR_COLOR = Optional.ofNullable(UIManager.getColor("Panel.background"))
|
private static Color SIDEBAR_COLOR = Optional.ofNullable(UIManager.getColor("Panel.background"))
|
||||||
.orElse(new Color(0x3C3F41));
|
.orElse(new Color(0x3C3F41));
|
||||||
|
|
||||||
// 卡片背景(深色模式适配,比侧边栏稍亮)
|
// 卡片背景(深色模式适配,比侧边栏稍亮)
|
||||||
private static final Color CARD_BG = Optional.ofNullable(UIManager.getColor("control"))
|
private static Color CARD_BG = Optional.ofNullable(UIManager.getColor("control"))
|
||||||
.map(bg -> ThemeColors.brighten(bg, 0.1f))
|
.map(bg -> ThemeColors.brighten(bg, 0.1f))
|
||||||
.orElse(new Color(0x4A4D50));
|
.orElse(new Color(0x4A4D50));
|
||||||
|
|
||||||
// 卡片边框(使用系统边框色或稍亮颜色)
|
// 卡片边框(使用系统边框色或稍亮颜色)
|
||||||
private static final Color CARD_BORDER = Optional.ofNullable(UIManager.getColor("controlHighlight"))
|
private static Color CARD_BORDER = Optional.ofNullable(UIManager.getColor("controlHighlight"))
|
||||||
.orElse(new Color(0x5C5F61));
|
.orElse(new Color(0x5C5F61));
|
||||||
|
|
||||||
// 文本颜色(直接使用系统主题的文本颜色)
|
// 文本颜色(直接使用系统主题的文本颜色)
|
||||||
private static final Color TEXT_COLOR = Optional.ofNullable(UIManager.getColor("textText"))
|
private static Color TEXT_COLOR = Optional.ofNullable(UIManager.getColor("textText"))
|
||||||
.orElse(new Color(0xE0E0E0));
|
.orElse(new Color(0xE0E0E0));
|
||||||
|
|
||||||
public MainWindow() {
|
public MainWindow() {
|
||||||
@@ -99,6 +100,8 @@ public class MainWindow extends JFrame {
|
|||||||
UIManager.put("TextArea.font", fontRes);
|
UIManager.put("TextArea.font", fontRes);
|
||||||
UIManager.put("TabbedPane.font", fontRes);
|
UIManager.put("TabbedPane.font", fontRes);
|
||||||
UIManager.put("TitledBorder.font", fontRes);
|
UIManager.put("TitledBorder.font", fontRes);
|
||||||
|
UIManager.put("ScrollPane.border", BorderFactory.createEmptyBorder());
|
||||||
|
UIManager.put("Panel.border", BorderFactory.createEmptyBorder());
|
||||||
|
|
||||||
// 图标
|
// 图标
|
||||||
setIconImage(LoadIcon.loadIcon("logo.png", 32).getImage());
|
setIconImage(LoadIcon.loadIcon("logo.png", 32).getImage());
|
||||||
@@ -122,6 +125,8 @@ public class MainWindow extends JFrame {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
setLocationRelativeTo(null);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -135,13 +140,26 @@ public class MainWindow extends JFrame {
|
|||||||
* 初始化并显示 UI
|
* 初始化并显示 UI
|
||||||
*/
|
*/
|
||||||
public void initUI() {
|
public void initUI() {
|
||||||
|
categoryScrollPanes.clear();
|
||||||
|
categoryToolPanels.clear();
|
||||||
|
sideButtons.clear();
|
||||||
|
cardScales.clear();
|
||||||
|
cardElevations.clear();
|
||||||
|
currentCategoryId = null;
|
||||||
|
|
||||||
|
// 清除现有UI组件
|
||||||
|
if (layeredPane != null) {
|
||||||
|
layeredPane.removeAll();
|
||||||
|
}
|
||||||
|
getContentPane().removeAll();
|
||||||
|
|
||||||
setTitle(LanguageManager.getLoadedLanguages().getText("mainWindow.title"));
|
setTitle(LanguageManager.getLoadedLanguages().getText("mainWindow.title"));
|
||||||
setDefaultCloseOperation(DISPOSE_ON_CLOSE);
|
setDefaultCloseOperation(DISPOSE_ON_CLOSE);
|
||||||
setSize(1200, 800);
|
setSize(1200, 800);
|
||||||
setLocationRelativeTo(null);
|
|
||||||
|
|
||||||
// 主容器
|
// 主容器
|
||||||
JPanel mainPanel = new JPanel(new BorderLayout());
|
JPanel mainPanel = new JPanel(new BorderLayout());
|
||||||
|
mainPanel.setBorder(BorderFactory.createEmptyBorder());
|
||||||
mainPanel.setOpaque(true);
|
mainPanel.setOpaque(true);
|
||||||
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);
|
||||||
@@ -221,6 +239,87 @@ public class MainWindow extends JFrame {
|
|||||||
|
|
||||||
// 更新 UI 字体样式(确保生效)
|
// 更新 UI 字体样式(确保生效)
|
||||||
SwingUtilities.updateComponentTreeUI(this);
|
SwingUtilities.updateComponentTreeUI(this);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 更新主题
|
||||||
|
*/
|
||||||
|
public void updateTheme() {
|
||||||
|
// 1. 更新UI管理器默认值
|
||||||
|
Font defaultFont = selectFont(new String[]{"Microsoft YaHei", "微软雅黑", "PingFang SC", "SimHei", "宋体", "新宋体", "SansSerif"}, 14);
|
||||||
|
FontUIResource fontRes = new FontUIResource(defaultFont);
|
||||||
|
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);
|
||||||
|
|
||||||
|
// 2. 强制更新所有UI组件
|
||||||
|
SwingUtilities.updateComponentTreeUI(this);
|
||||||
|
|
||||||
|
// 3. 手动更新关键组件
|
||||||
|
|
||||||
|
// 更新搜索框
|
||||||
|
if (searchField != null) {
|
||||||
|
searchField.defaultBorderColor = UIManager.getColor("TextField.borderColor");
|
||||||
|
if (searchField.defaultBorderColor == null) {
|
||||||
|
searchField.defaultBorderColor = new Color(180, 180, 180);
|
||||||
|
}
|
||||||
|
searchField.repaint();
|
||||||
|
}
|
||||||
|
|
||||||
|
// 4. 特殊处理卡片颜色
|
||||||
|
for (JComponent card : cardScales.keySet()) {
|
||||||
|
card.repaint();
|
||||||
|
}
|
||||||
|
|
||||||
|
// 5. 更新分类滚动面板
|
||||||
|
for (JScrollPane scrollPane : categoryScrollPanes.values()) {
|
||||||
|
scrollPane.getVerticalScrollBar().setUI(new CustomScrollBarUI());
|
||||||
|
scrollPane.repaint();
|
||||||
|
}
|
||||||
|
|
||||||
|
// 6. 更新主面板背景
|
||||||
|
Component content = getContentPane();
|
||||||
|
if (content instanceof JComponent) {
|
||||||
|
JComponent contentPane = (JComponent) content;
|
||||||
|
Color panelBg = UIManager.getColor("Panel.background");
|
||||||
|
if (panelBg == null) panelBg = new Color(245, 246, 248);
|
||||||
|
contentPane.setBackground(panelBg);
|
||||||
|
contentPane.repaint();
|
||||||
|
}
|
||||||
|
|
||||||
|
// 7. 更新所有按钮状态
|
||||||
|
for (JButton btn : sideButtons.values()) {
|
||||||
|
btn.setForeground(Optional.ofNullable(UIManager.getColor("textText"))
|
||||||
|
.orElse(new Color(0xE0E0E0)));
|
||||||
|
btn.repaint();
|
||||||
|
}
|
||||||
|
|
||||||
|
// 8. 更新当前分类显示
|
||||||
|
if (currentCategoryId != null) {
|
||||||
|
JScrollPane currentPane = categoryScrollPanes.get(currentCategoryId);
|
||||||
|
if (currentPane != null) {
|
||||||
|
cardsLayout.show(cardsPanel, currentCategoryId);
|
||||||
|
currentPane.repaint();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (sideBar != null) {
|
||||||
|
sideBar.setBackground(getSidebarColor());
|
||||||
|
sideBar.repaint();
|
||||||
|
}
|
||||||
|
|
||||||
|
// 9. 确保窗口正确重绘
|
||||||
|
revalidate();
|
||||||
|
repaint();
|
||||||
|
}
|
||||||
|
|
||||||
|
private Color getSidebarColor() {
|
||||||
|
return Optional.ofNullable(UIManager.getColor("Panel.background"))
|
||||||
|
.orElse(new Color(0x3C3F41));
|
||||||
}
|
}
|
||||||
|
|
||||||
private void syncLayeredBounds() {
|
private void syncLayeredBounds() {
|
||||||
@@ -363,6 +462,14 @@ public class MainWindow extends JFrame {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void updateThemeColors() {
|
||||||
|
defaultBorderColor = UIManager.getColor("TextField.borderColor");
|
||||||
|
if (defaultBorderColor == null) {
|
||||||
|
defaultBorderColor = new Color(180, 180, 180);
|
||||||
|
}
|
||||||
|
repaint();
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void paintComponent(Graphics g) {
|
protected void paintComponent(Graphics g) {
|
||||||
super.paintComponent(g);
|
super.paintComponent(g);
|
||||||
@@ -433,19 +540,21 @@ public class MainWindow extends JFrame {
|
|||||||
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(BorderFactory.createEmptyBorder(8, 8, 8, 8));
|
sidebar.setBorder(null);
|
||||||
|
|
||||||
// top: logo + app name
|
// top: logo + app name
|
||||||
JPanel top = new JPanel(new BorderLayout());
|
JPanel top = new JPanel(new BorderLayout());
|
||||||
top.setOpaque(false);
|
top.setOpaque(false);
|
||||||
JLabel logo = new JLabel(LoadIcon.loadIcon("logo.png", 36));
|
|
||||||
logo.setBorder(BorderFactory.createEmptyBorder(8, 6, 8, 6));
|
JPanel centerPanel = new JPanel();
|
||||||
JLabel appName = new JLabel(LanguageManager.getLoadedLanguages().getText("mainWindow.title.2"));
|
centerPanel.setLayout(new BoxLayout(centerPanel, BoxLayout.Y_AXIS));
|
||||||
appName.setFont(new Font(selectFont("Segoe UI", "Microsoft YaHei", "SansSerif", 15).getName(), Font.BOLD, 15));
|
centerPanel.setOpaque(false);
|
||||||
appName.setForeground(Color.WHITE);
|
|
||||||
appName.setBorder(BorderFactory.createEmptyBorder(8, 6, 8, 6));
|
JLabel logo = new JLabel(LoadIcon.loadIcon("logo.png", 60));
|
||||||
top.add(logo, BorderLayout.WEST);
|
logo.setBorder(BorderFactory.createEmptyBorder(10, 6, 10 + 10, 6));
|
||||||
top.add(appName, BorderLayout.CENTER);
|
logo.setAlignmentX(Component.CENTER_ALIGNMENT);
|
||||||
|
centerPanel.add(logo);
|
||||||
|
top.add(centerPanel, BorderLayout.CENTER);
|
||||||
sidebar.add(top, BorderLayout.NORTH);
|
sidebar.add(top, BorderLayout.NORTH);
|
||||||
|
|
||||||
// list of categories
|
// list of categories
|
||||||
@@ -468,7 +577,7 @@ public class MainWindow extends JFrame {
|
|||||||
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());
|
||||||
listScroll.setBackground(new Color(0,0,0,0));
|
listScroll.setBackground(new Color(0, 0, 0, 0));
|
||||||
|
|
||||||
sidebar.add(listScroll, BorderLayout.CENTER);
|
sidebar.add(listScroll, BorderLayout.CENTER);
|
||||||
|
|
||||||
@@ -478,59 +587,148 @@ public class MainWindow extends JFrame {
|
|||||||
bottom.setLayout(new BoxLayout(bottom, BoxLayout.Y_AXIS));
|
bottom.setLayout(new BoxLayout(bottom, BoxLayout.Y_AXIS));
|
||||||
bottom.setBorder(BorderFactory.createEmptyBorder(6, 6, 6, 6));
|
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);
|
sidebar.add(bottom, BorderLayout.SOUTH);
|
||||||
|
|
||||||
return sidebar;
|
return sidebar;
|
||||||
}
|
}
|
||||||
|
|
||||||
private JButton createSideButton(ToolCategory category) {
|
private JButton createSideButton(ToolCategory category) {
|
||||||
|
// 竖杠距离按钮左侧位置
|
||||||
|
final int BAR_X = 8;
|
||||||
|
// 竖杠宽度
|
||||||
|
final int BAR_WIDTH = 5;
|
||||||
|
// 竖杠到图标的空隙
|
||||||
|
final int GAP_BAR_TO_ICON = 12;
|
||||||
|
final Color SELECT_FILL = new Color(0, 120, 215, 20);
|
||||||
|
Color HOVER_FILL;// 悬停颜色
|
||||||
|
if (isDarkTheme()) {
|
||||||
|
HOVER_FILL = new Color(0x535360);
|
||||||
|
} else {
|
||||||
|
HOVER_FILL = new Color(0xD0E0F6);
|
||||||
|
}
|
||||||
|
|
||||||
JButton button = new JButton(category.getName());
|
JButton button = new JButton(category.getName());
|
||||||
|
|
||||||
button.setMaximumSize(new Dimension(Integer.MAX_VALUE, 44));
|
button.setMaximumSize(new Dimension(Integer.MAX_VALUE, 44));
|
||||||
button.setHorizontalAlignment(SwingConstants.LEFT);
|
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(TEXT_COLOR);
|
|
||||||
button.setBackground(TEXT_COLOR);
|
|
||||||
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 -> {
|
// 原始图标
|
||||||
// 切换分类时清空搜索并触发动画切换
|
ImageIcon rawIcon = category.getIconImage() != null
|
||||||
searchField.setText("");
|
? category.getIconImage()
|
||||||
switchCategory(category.getId().toString(), false);
|
: LoadIcon.loadIcon(category.getIcon(), 18);
|
||||||
});
|
int rawIconWidth = (rawIcon != null) ? rawIcon.getIconWidth() : 18;
|
||||||
|
final int ICON_PADDING_LEFT = BAR_X + BAR_WIDTH + GAP_BAR_TO_ICON; // 左侧为图标预留的偏移
|
||||||
|
|
||||||
// hover 效果(浅背景)
|
// 包装一个带左侧内边距的 Icon,使图标整体右移
|
||||||
button.addMouseListener(new MouseAdapter() {
|
Icon paddedIcon = new Icon() {
|
||||||
@Override public void mouseEntered(MouseEvent e) {
|
@Override
|
||||||
if (!Objects.equals(currentCategoryId, category.getId().toString())) {
|
public void paintIcon(Component c, Graphics g, int x, int y) {
|
||||||
button.setOpaque(true);
|
if (rawIcon != null) {
|
||||||
button.setBackground(new Color(0x4A4D50)); // 深灰微亮
|
int dy = (getIconHeight() - rawIcon.getIconHeight()) / 2;
|
||||||
|
rawIcon.paintIcon(c, g, x + ICON_PADDING_LEFT, y + dy);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@Override public void mouseExited(MouseEvent e) {
|
|
||||||
if (!Objects.equals(currentCategoryId, category.getId().toString())) {
|
@Override
|
||||||
button.setOpaque(false);
|
public int getIconWidth() {
|
||||||
button.setBackground(new Color(0,0,0,0));
|
return ICON_PADDING_LEFT + rawIconWidth;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getIconHeight() {
|
||||||
|
return (rawIcon != null) ? rawIcon.getIconHeight() : 18;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
button.setIcon(paddedIcon);
|
||||||
|
button.setHorizontalTextPosition(SwingConstants.RIGHT);
|
||||||
|
button.setIconTextGap(16);
|
||||||
|
button.setCursor(Cursor.getPredefinedCursor(Cursor.HAND_CURSOR));
|
||||||
|
|
||||||
|
// 自定义 Border:只在当前被选中时绘制左侧竖杠
|
||||||
|
Border indicatorBorder = new Border() {
|
||||||
|
@Override
|
||||||
|
public void paintBorder(Component c, Graphics g, int x, int y, int width, int height) {
|
||||||
|
if (Objects.equals(currentCategoryId, category.getId().toString())) {
|
||||||
|
Graphics2D g2 = (Graphics2D) g.create();
|
||||||
|
try {
|
||||||
|
g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
|
||||||
|
g2.setColor(new Color(0x0162F1));
|
||||||
|
g2.fillRect(BAR_X, y, BAR_WIDTH, height);
|
||||||
|
} finally {
|
||||||
|
g2.dispose();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Insets getBorderInsets(Component c) {
|
||||||
|
return new Insets(0, 0, 0, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isBorderOpaque() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// 将自定义竖杠 Border 和 内边距 EmptyBorder 组合
|
||||||
|
button.setBorder(BorderFactory.createCompoundBorder(
|
||||||
|
indicatorBorder,
|
||||||
|
BorderFactory.createEmptyBorder(8, 8, 8, 20)
|
||||||
|
));
|
||||||
|
|
||||||
|
// 样式
|
||||||
|
button.setForeground(TEXT_COLOR);
|
||||||
|
// 初始设为不填充,由 updateSelection 决定是否填充背景
|
||||||
|
button.setOpaque(false);
|
||||||
|
button.setContentAreaFilled(true); // 允许根据 opaque/background 填充(mouse/selected 状态会切换 opaque)
|
||||||
|
button.setFocusPainted(false);
|
||||||
|
button.setFont(new Font(
|
||||||
|
selectFont("Segoe UI", "Microsoft YaHei", "SansSerif", 13).getName(),
|
||||||
|
Font.PLAIN, 13
|
||||||
|
));
|
||||||
|
|
||||||
|
// helper: 更新选中/未选中时的外观(填充透明浅蓝或透明)
|
||||||
|
Runnable updateSelection = () -> {
|
||||||
|
boolean selected = Objects.equals(currentCategoryId, category.getId().toString());
|
||||||
|
if (selected) {
|
||||||
|
button.setOpaque(true);
|
||||||
|
button.setBackground(SELECT_FILL);
|
||||||
|
} else {
|
||||||
|
button.setOpaque(false);
|
||||||
|
// 为避免残留,仍设置透明背景(透明颜色)
|
||||||
|
button.setBackground(new Color(0, 0, 0, 0));
|
||||||
|
}
|
||||||
|
button.repaint();
|
||||||
|
};
|
||||||
|
|
||||||
|
// 初始状态
|
||||||
|
updateSelection.run();
|
||||||
|
|
||||||
|
// 点击
|
||||||
|
button.addActionListener(e -> {
|
||||||
|
searchField.setText("");
|
||||||
|
switchCategory(category.getId().toString(), false);
|
||||||
|
// 更新自己(外部如果也改变 currentCategoryId,外部应确保调用所有按钮的 repaint)
|
||||||
|
updateSelection.run();
|
||||||
|
});
|
||||||
|
|
||||||
|
// 悬停:仅在未选中时显示 hover 填充;离开时恢复选中/未选中外观
|
||||||
|
button.addMouseListener(new MouseAdapter() {
|
||||||
|
@Override
|
||||||
|
public void mouseEntered(MouseEvent e) {
|
||||||
|
if (!Objects.equals(currentCategoryId, category.getId().toString())) {
|
||||||
|
button.setOpaque(true);
|
||||||
|
button.setBackground(HOVER_FILL);
|
||||||
|
button.repaint();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@Override
|
||||||
|
public void mouseExited(MouseEvent e) {
|
||||||
|
// 恢复到选中/未选中状态
|
||||||
|
updateSelection.run();
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
return button;
|
return button;
|
||||||
@@ -570,39 +768,37 @@ public class MainWindow extends JFrame {
|
|||||||
@Override
|
@Override
|
||||||
protected void paintComponent(Graphics g) {
|
protected void paintComponent(Graphics g) {
|
||||||
Graphics2D g2 = (Graphics2D) g.create();
|
Graphics2D g2 = (Graphics2D) g.create();
|
||||||
int w = getWidth(), h = getHeight();
|
try {
|
||||||
g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
|
int w = getWidth(), h = getHeight();
|
||||||
|
g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
|
||||||
|
|
||||||
// 轻微阴影
|
// 动态获取当前主题颜色
|
||||||
g2.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER, 0.06f));
|
Color cardBg = getCardBg();
|
||||||
g2.setColor(Color.BLACK);
|
Color cardBorder = getCardBorder();
|
||||||
for (int i = 0; i < 3; i++) {
|
Color shadowColor = isDarkTheme() ? new Color(30, 30, 30) : Color.BLACK;
|
||||||
g2.fill(new RoundRectangle2D.Float(i, i, w - i*2, h - i*2, 12, 12));
|
|
||||||
|
// 1. 绘制阴影(根据主题调整透明度)
|
||||||
|
g2.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER, isDarkTheme() ? 0.12f : 0.06f));
|
||||||
|
g2.setColor(shadowColor);
|
||||||
|
for (int i = 0; i < 3; i++) {
|
||||||
|
g2.fill(new RoundRectangle2D.Float(i, i, w - i*2, h - i*2, 12, 12));
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. 绘制卡片背景(动态适应主题)
|
||||||
|
g2.setComposite(AlphaComposite.SrcOver);
|
||||||
|
g2.setColor(cardBg);
|
||||||
|
g2.fill(new RoundRectangle2D.Float(0, 0, w, h, 12, 12));
|
||||||
|
|
||||||
|
// 3. 绘制边框(优化为单次绘制)
|
||||||
|
g2.setColor(cardBorder);
|
||||||
|
g2.setStroke(new BasicStroke(1f));
|
||||||
|
g2.draw(new RoundRectangle2D.Float(0.5f, 0.5f, w - 1, h - 1, 12, 12));
|
||||||
|
|
||||||
|
// 4. 绘制内容(通过super.paintComponent)
|
||||||
|
super.paintComponent(g);
|
||||||
|
} finally {
|
||||||
|
g2.dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
// 卡片背景:使用浅灰而非突兀白色,跟 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; }
|
@Override public boolean isOpaque() { return false; }
|
||||||
@@ -681,6 +877,21 @@ public class MainWindow extends JFrame {
|
|||||||
return card;
|
return card;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private boolean isDarkTheme() {
|
||||||
|
return AxisInnovatorsBox.getMain().getRegistrationTopic().isDarkMode();
|
||||||
|
}
|
||||||
|
|
||||||
|
private Color getCardBg() {
|
||||||
|
return Optional.ofNullable(UIManager.getColor("control"))
|
||||||
|
.map(bg -> ThemeColors.brighten(bg, 0.1f))
|
||||||
|
.orElse(new Color(0x4A4D50));
|
||||||
|
}
|
||||||
|
|
||||||
|
private Color getCardBorder() {
|
||||||
|
return Optional.ofNullable(UIManager.getColor("controlHighlight"))
|
||||||
|
.orElse(new Color(0x5C5F61));
|
||||||
|
}
|
||||||
|
|
||||||
private String createToolTipHTML(ToolItem tool) {
|
private String createToolTipHTML(ToolItem tool) {
|
||||||
return "<html><body style='width:300px;padding:8px;'>" +
|
return "<html><body style='width:300px;padding:8px;'>" +
|
||||||
"<h3 style='margin:0;color:#2c3e50;'>" + escapeHtml(tool.getName()) + "</h3>" +
|
"<h3 style='margin:0;color:#2c3e50;'>" + escapeHtml(tool.getName()) + "</h3>" +
|
||||||
@@ -1018,8 +1229,11 @@ public class MainWindow extends JFrame {
|
|||||||
// ---------- 自定义滚动条 ----------
|
// ---------- 自定义滚动条 ----------
|
||||||
public static class CustomScrollBarUI extends BasicScrollBarUI {
|
public static class CustomScrollBarUI extends BasicScrollBarUI {
|
||||||
@Override protected void configureScrollBarColors() {
|
@Override protected void configureScrollBarColors() {
|
||||||
this.thumbColor = new Color(160, 160, 180);
|
this.thumbColor = UIManager.getColor("ScrollBar.thumb");
|
||||||
this.trackColor = new Color(245, 245, 248);
|
if (thumbColor == null) thumbColor = new Color(160, 160, 180);
|
||||||
|
|
||||||
|
this.trackColor = UIManager.getColor("ScrollBar.track");
|
||||||
|
if (trackColor == null) trackColor = new Color(245, 245, 248);
|
||||||
}
|
}
|
||||||
@Override protected JButton createDecreaseButton(int orientation) { return createInvisibleButton(); }
|
@Override protected JButton createDecreaseButton(int orientation) { return createInvisibleButton(); }
|
||||||
@Override protected JButton createIncreaseButton(int orientation) { return createInvisibleButton(); }
|
@Override protected JButton createIncreaseButton(int orientation) { return createInvisibleButton(); }
|
||||||
|
|||||||
@@ -60,4 +60,11 @@ public class WindowsJDialog extends JDialog {
|
|||||||
RegistrationTopic registrationTopic = axisInnovatorsBox.getRegistrationTopic();
|
RegistrationTopic registrationTopic = axisInnovatorsBox.getRegistrationTopic();
|
||||||
return registrationTopic.isDarkMode();
|
return registrationTopic.isDarkMode();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 在当前窗口中更新主题
|
||||||
|
*/
|
||||||
|
public void updateTheme() {
|
||||||
|
SwingUtilities.updateComponentTreeUI(this);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 12 KiB After Width: | Height: | Size: 4.3 KiB |
|
Before Width: | Height: | Size: 6.8 KiB After Width: | Height: | Size: 2.9 KiB |
|
Before Width: | Height: | Size: 11 KiB After Width: | Height: | Size: 146 KiB |
|
Before Width: | Height: | Size: 8.1 KiB After Width: | Height: | Size: 3.0 KiB |