feat(theme): 动态主题适配及 UI 组件样式优化- 新增 updateTheme 方法以支持动态主题更新

- 优化 MainWindow 中的组件样式,包括按钮、滚动条等
- 调整侧边栏样式,增加选中状态和悬停效果
- 优化卡片背景和边框颜色,适应不同主题
- 修复部分组件在深色主题下的显示问题
This commit is contained in:
tzdwindows 7
2025-08-15 18:59:39 +08:00
parent 78bae01544
commit 1a1750d5a6
7 changed files with 309 additions and 86 deletions

View File

@@ -889,10 +889,12 @@ public class AxisInnovatorsBox {
for (WindowsJDialog windowsJDialog : windowsJDialogList) {
windowsJDialog.getContentPane().removeAll();
windowsJDialog.initUI();
windowsJDialog.updateTheme();
windowsJDialog.revalidate();
windowsJDialog.repaint();
}
ex.initUI();
ex.updateTheme();
ex.revalidate();
RegistrationSettingsItem.overloading();
isWindow = true;

View File

@@ -10,6 +10,7 @@ import org.apache.logging.log4j.Logger;
import javax.swing.*;
import javax.swing.Timer;
import javax.swing.border.Border;
import javax.swing.event.DocumentEvent;
import javax.swing.event.DocumentListener;
import javax.swing.plaf.FontUIResource;
@@ -60,20 +61,20 @@ public class MainWindow extends JFrame {
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));
// 卡片背景(深色模式适配,比侧边栏稍亮)
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))
.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));
// 文本颜色(直接使用系统主题的文本颜色)
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));
public MainWindow() {
@@ -99,6 +100,8 @@ public class MainWindow extends JFrame {
UIManager.put("TextArea.font", fontRes);
UIManager.put("TabbedPane.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());
@@ -122,6 +125,8 @@ public class MainWindow extends JFrame {
}
}
});
setLocationRelativeTo(null);
}
/**
@@ -135,13 +140,26 @@ public class MainWindow extends JFrame {
* 初始化并显示 UI
*/
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"));
setDefaultCloseOperation(DISPOSE_ON_CLOSE);
setSize(1200, 800);
setLocationRelativeTo(null);
// 主容器
JPanel mainPanel = new JPanel(new BorderLayout());
mainPanel.setBorder(BorderFactory.createEmptyBorder());
mainPanel.setOpaque(true);
Color panelBg = UIManager.getColor("Panel.background");
if (panelBg == null) panelBg = new Color(245, 246, 248);
@@ -221,6 +239,87 @@ public class MainWindow extends JFrame {
// 更新 UI 字体样式(确保生效)
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() {
@@ -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
protected void paintComponent(Graphics g) {
super.paintComponent(g);
@@ -433,19 +540,21 @@ public class MainWindow extends JFrame {
sidebar.setOpaque(true);
sidebar.setBackground(SIDEBAR_COLOR);
sidebar.setPreferredSize(new Dimension(220, getHeight()));
sidebar.setBorder(BorderFactory.createEmptyBorder(8, 8, 8, 8));
sidebar.setBorder(null);
// 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);
JPanel centerPanel = new JPanel();
centerPanel.setLayout(new BoxLayout(centerPanel, BoxLayout.Y_AXIS));
centerPanel.setOpaque(false);
JLabel logo = new JLabel(LoadIcon.loadIcon("logo.png", 60));
logo.setBorder(BorderFactory.createEmptyBorder(10, 6, 10 + 10, 6));
logo.setAlignmentX(Component.CENTER_ALIGNMENT);
centerPanel.add(logo);
top.add(centerPanel, BorderLayout.CENTER);
sidebar.add(top, BorderLayout.NORTH);
// list of categories
@@ -468,7 +577,7 @@ public class MainWindow extends JFrame {
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));
listScroll.setBackground(new Color(0, 0, 0, 0));
sidebar.add(listScroll, BorderLayout.CENTER);
@@ -478,59 +587,148 @@ public class MainWindow extends JFrame {
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) {
// 竖杠距离按钮左侧位置
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());
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(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 -> {
// 切换分类时清空搜索并触发动画切换
searchField.setText("");
switchCategory(category.getId().toString(), false);
});
// 原始图标
ImageIcon rawIcon = category.getIconImage() != null
? category.getIconImage()
: 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 效果(浅背景)
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)); // 深灰微亮
// 包装一个带左侧内边距的 Icon使图标整体右移
Icon paddedIcon = new Icon() {
@Override
public void paintIcon(Component c, Graphics g, int x, int y) {
if (rawIcon != null) {
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())) {
button.setOpaque(false);
button.setBackground(new Color(0,0,0,0));
@Override
public int getIconWidth() {
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;
@@ -570,39 +768,37 @@ public class MainWindow extends JFrame {
@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);
try {
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));
// 动态获取当前主题颜色
Color cardBg = getCardBg();
Color cardBorder = getCardBorder();
Color shadowColor = isDarkTheme() ? new Color(30, 30, 30) : Color.BLACK;
// 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; }
@@ -681,6 +877,21 @@ public class MainWindow extends JFrame {
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) {
return "<html><body style='width:300px;padding:8px;'>" +
"<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 {
@Override protected void configureScrollBarColors() {
this.thumbColor = new Color(160, 160, 180);
this.trackColor = new Color(245, 245, 248);
this.thumbColor = UIManager.getColor("ScrollBar.thumb");
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 createIncreaseButton(int orientation) { return createInvisibleButton(); }

View File

@@ -60,4 +60,11 @@ public class WindowsJDialog extends JDialog {
RegistrationTopic registrationTopic = axisInnovatorsBox.getRegistrationTopic();
return registrationTopic.isDarkMode();
}
/**
* 在当前窗口中更新主题
*/
public void updateTheme() {
SwingUtilities.updateComponentTreeUI(this);
}
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 12 KiB

After

Width:  |  Height:  |  Size: 4.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.8 KiB

After

Width:  |  Height:  |  Size: 2.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 11 KiB

After

Width:  |  Height:  |  Size: 146 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.1 KiB

After

Width:  |  Height:  |  Size: 3.0 KiB