feat(gui): add dynamic code editor theme from UI theme (PR #2471)

fix(gui): created dynamic theme for code editor (#1763)
This commit is contained in:
Yaroslav
2025-05-02 19:10:27 +03:00
committed by GitHub
parent 79c91634ad
commit fbebcb9845
14 changed files with 195 additions and 6 deletions
@@ -102,6 +102,7 @@ public class JadxSettings extends JadxCLIArgs {
private boolean showHeapUsageBar = false;
private boolean alwaysSelectOpened = false;
private boolean useAlternativeFileDialog = false;
private boolean useDynamicEditorTheme = false;
private Map<String, WindowLocation> windowPos = new HashMap<>();
private int mainWindowExtendedState = JFrame.NORMAL;
@@ -324,6 +325,14 @@ public class JadxSettings extends JadxCLIArgs {
this.useAlternativeFileDialog = useAlternativeFileDialog;
}
public boolean isUseDynamicEditorTheme() {
return useDynamicEditorTheme;
}
public void setUseDynamicEditorTheme(boolean useDynamicEditorTheme) {
this.useDynamicEditorTheme = useDynamicEditorTheme;
}
public String getExcludedPackages() {
return excludedPackages;
}
@@ -353,6 +353,7 @@ public class JadxSettingsWindow extends JDialog {
EditorTheme[] editorThemes = EditorTheme.getAllThemes();
JComboBox<EditorTheme> themesCbx = new JComboBox<>(editorThemes);
themesCbx.setEnabled(!settings.isUseDynamicEditorTheme());
for (EditorTheme theme : editorThemes) {
if (theme.getPath().equals(settings.getEditorThemePath())) {
themesCbx.setSelectedItem(theme);
@@ -366,6 +367,14 @@ public class JadxSettingsWindow extends JDialog {
mainWindow.loadSettings();
});
JCheckBox useDynamicEditorTheme = new JCheckBox();
useDynamicEditorTheme.setSelected(settings.isUseDynamicEditorTheme());
useDynamicEditorTheme.addItemListener(e -> {
settings.setUseDynamicEditorTheme(e.getStateChange() == ItemEvent.SELECTED);
mainWindow.loadSettings();
themesCbx.setEnabled(!settings.isUseDynamicEditorTheme());
});
JComboBox<String> lafCbx = new JComboBox<>(LafManager.getThemes());
lafCbx.setSelectedItem(settings.getLafTheme());
lafCbx.addActionListener(e -> {
@@ -376,6 +385,7 @@ public class JadxSettingsWindow extends JDialog {
SettingsGroup group = new SettingsGroup(NLS.str("preferences.appearance"));
group.addRow(NLS.str("preferences.language"), languageCbx);
group.addRow(NLS.str("preferences.laf_theme"), lafCbx);
group.addRow(NLS.str("preferences.dynamic_editor_theme"), useDynamicEditorTheme);
group.addRow(NLS.str("preferences.theme"), themesCbx);
JLabel fontLabel = group.addRow(getFontLabelStr(), fontBtn);
JLabel smaliFontLabel = group.addRow(getSmaliFontLabelStr(), smaliFontBtn);
@@ -31,9 +31,11 @@ import javax.swing.text.DefaultCaret;
import org.fife.ui.rsyntaxtextarea.AbstractTokenMakerFactory;
import org.fife.ui.rsyntaxtextarea.RSyntaxTextArea;
import org.fife.ui.rsyntaxtextarea.RSyntaxUtilities;
import org.fife.ui.rsyntaxtextarea.Token;
import org.fife.ui.rsyntaxtextarea.TokenMakerFactory;
import org.fife.ui.rsyntaxtextarea.TokenTypes;
import org.fife.ui.rtextarea.Gutter;
import org.fife.ui.rtextarea.SearchContext;
import org.fife.ui.rtextarea.SearchEngine;
import org.jetbrains.annotations.Nullable;
@@ -365,16 +367,25 @@ public abstract class AbstractCodeArea extends RSyntaxTextArea {
RSyntaxTextArea area = new RSyntaxTextArea();
area.setEditable(false);
area.setCodeFoldingEnabled(false);
area.setAntiAliasingEnabled(true);
loadCommonSettings(mainWindow, area);
return area;
}
public static void loadCommonSettings(MainWindow mainWindow, RSyntaxTextArea area) {
area.setAntiAliasingEnabled(true);
mainWindow.getEditorTheme().apply(area);
JadxSettings settings = mainWindow.getSettings();
if (settings.isUseDynamicEditorTheme()) {
DynamicCoreAreaTheme.applyCustomTheme(area);
} else {
mainWindow.getEditorTheme().apply(area);
}
area.setFont(settings.getFont());
Gutter gutter = RSyntaxUtilities.getGutter(area);
if (gutter != null) {
gutter.setLineNumberFont(settings.getFont());
}
}
public void loadSettings() {
@@ -509,6 +520,14 @@ public abstract class AbstractCodeArea extends RSyntaxTextArea {
}
}
@Override
public void updateUI() {
super.updateUI();
if (contentPanel != null && contentPanel.getMainWindow() != null) {
loadSettings();
}
}
@Override
public Dimension getPreferredSize() {
try {
@@ -0,0 +1,137 @@
package jadx.gui.ui.codearea;
import java.awt.Color;
import javax.swing.UIManager;
import org.fife.ui.rsyntaxtextarea.RSyntaxTextArea;
import org.fife.ui.rsyntaxtextarea.RSyntaxUtilities;
import org.fife.ui.rsyntaxtextarea.SyntaxScheme;
import org.fife.ui.rsyntaxtextarea.Token;
import org.fife.ui.rtextarea.Gutter;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class DynamicCoreAreaTheme {
private static final Logger LOG = LoggerFactory.getLogger(DynamicCoreAreaTheme.class);
public static void applyCustomTheme(RSyntaxTextArea textArea) {
// Get the current theme's background color from UIManager
Color themeBackground = UIManager.getColor("Panel.background");
boolean isDarkTheme = isDarkTheme(themeBackground);
SyntaxScheme scheme = textArea.getSyntaxScheme();
// Background colors based on the theme
Color editorBackground = isDarkTheme ? themeBackground : Color.WHITE; // Use white for light theme
Color lineHighlight = isDarkTheme
? adjustBrightness(themeBackground, 1.2f)
: Color.decode("#EBECF0"); // Light gray for light theme
Color lineNumberForeground = UIManager.getColor("Label.foreground");
// Add these lines after setting the background colors
Color selectionColor = isDarkTheme
? new Color(51, 153, 255, 90) // Semi-transparent blue for dark theme
: new Color(51, 153, 255, 50); // Lighter blue for light theme
Color markAllHighlightColor = isDarkTheme ? Color.decode("#32593D") : Color.decode("#ffc800");
Color markOccurrencesColor = isDarkTheme ? Color.decode("#52503A") : Color.decode("#FCE8F4");
Color matchedBracketBackground = isDarkTheme ? adjustBrightness(Color.decode("#3B514D"), 1.2f) : Color.decode("#93D9D9");
Color gutterBorderColor = UIManager.getColor("Separator.foreground");
Color hyperlinkColor = UIManager.getColor("EditorPane.selectionBackground");
// Set the syntax colors for the theme
if (isDarkTheme) {
Color dataTypeColor = Color.decode("#4EC9B0");
Color booleanColor = Color.decode("#569CD6");
scheme.getStyle(Token.COMMENT_EOL).foreground = Color.decode("#57A64A");
scheme.getStyle(Token.COMMENT_MULTILINE).foreground = Color.decode("#57A64A");
scheme.getStyle(Token.COMMENT_DOCUMENTATION).foreground = Color.decode("#57A64A");
scheme.getStyle(Token.COMMENT_KEYWORD).foreground = Color.decode("#57A64A");
scheme.getStyle(Token.COMMENT_MARKUP).foreground = Color.decode("#57A64A");
scheme.getStyle(Token.RESERVED_WORD).foreground = Color.decode("#569CD6");
scheme.getStyle(Token.RESERVED_WORD_2).foreground = dataTypeColor;
scheme.getStyle(Token.FUNCTION).foreground = Color.decode("#DCDCAA");
scheme.getStyle(Token.LITERAL_NUMBER_DECIMAL_INT).foreground = Color.decode("#D7BA7D");
scheme.getStyle(Token.LITERAL_NUMBER_FLOAT).foreground = Color.decode("#D7BA7D");
scheme.getStyle(Token.LITERAL_NUMBER_HEXADECIMAL).foreground = Color.decode("#D7BA7D");
scheme.getStyle(Token.LITERAL_BOOLEAN).foreground = booleanColor;
scheme.getStyle(Token.LITERAL_CHAR).foreground = Color.decode("#CE9178");
scheme.getStyle(Token.LITERAL_STRING_DOUBLE_QUOTE).foreground = Color.decode("#CE9178");
scheme.getStyle(Token.DATA_TYPE).foreground = dataTypeColor;
scheme.getStyle(Token.OPERATOR).foreground = Color.WHITE;
scheme.getStyle(Token.SEPARATOR).foreground = Color.WHITE;
scheme.getStyle(Token.IDENTIFIER).foreground = Color.decode("#9CDCFE");
// XML-specific colors for dark theme
scheme.getStyle(Token.MARKUP_TAG_DELIMITER).foreground = Color.decode("#808080"); // Gray for < > /
scheme.getStyle(Token.MARKUP_TAG_NAME).foreground = Color.decode("#569CD6"); // Blue for tag names
scheme.getStyle(Token.MARKUP_TAG_ATTRIBUTE).foreground = Color.decode("#9CDCFE"); // Light blue for attributes
scheme.getStyle(Token.MARKUP_TAG_ATTRIBUTE_VALUE).foreground = Color.decode("#CE9178"); // Orange for values
} else {
Color booleanColor = Color.decode("#0451A5");
Color keyColor = Color.decode("#4A7A4F"); // Soft green for keys
Color dataTypeColor = Color.decode("#267F99");
scheme.getStyle(Token.COMMENT_EOL).foreground = Color.decode("#008000");
scheme.getStyle(Token.COMMENT_MULTILINE).foreground = Color.decode("#008000");
scheme.getStyle(Token.COMMENT_DOCUMENTATION).foreground = Color.decode("#008000");
scheme.getStyle(Token.COMMENT_KEYWORD).foreground = Color.decode("#008000");
scheme.getStyle(Token.COMMENT_MARKUP).foreground = Color.decode("#008000");
scheme.getStyle(Token.RESERVED_WORD).foreground = Color.decode("#0000FF");
scheme.getStyle(Token.RESERVED_WORD_2).foreground = dataTypeColor;
scheme.getStyle(Token.FUNCTION).foreground = Color.decode("#795E26");
scheme.getStyle(Token.ANNOTATION).foreground = Color.decode("#9E8809");
scheme.getStyle(Token.LITERAL_NUMBER_DECIMAL_INT).foreground = Color.decode("#098658");
scheme.getStyle(Token.LITERAL_NUMBER_FLOAT).foreground = Color.decode("#098658");
scheme.getStyle(Token.LITERAL_NUMBER_HEXADECIMAL).foreground = Color.decode("#098658");
scheme.getStyle(Token.LITERAL_BOOLEAN).foreground = booleanColor;
scheme.getStyle(Token.LITERAL_CHAR).foreground = Color.decode("#067d17");
scheme.getStyle(Token.LITERAL_STRING_DOUBLE_QUOTE).foreground = Color.decode("#067d17"); // Soft blue for values
scheme.getStyle(Token.DATA_TYPE).foreground = dataTypeColor;
scheme.getStyle(Token.OPERATOR).foreground = Color.decode("#333333");
scheme.getStyle(Token.SEPARATOR).foreground = Color.decode("#333333");
scheme.getStyle(Token.IDENTIFIER).foreground = Color.decode("#001080");
// XML-specific colors for light theme
scheme.getStyle(Token.MARKUP_TAG_DELIMITER).foreground = Color.decode("#800000"); // Dark red for < > /
scheme.getStyle(Token.MARKUP_TAG_NAME).foreground = keyColor; // Soft green for tag names (keys)
scheme.getStyle(Token.MARKUP_TAG_ATTRIBUTE).foreground = Color.decode("#FF0000"); // Red for attributes
scheme.getStyle(Token.MARKUP_TAG_ATTRIBUTE_VALUE).foreground = Color.decode("#0000FF"); // Blue for values
}
// Apply background colors
textArea.setBackground(editorBackground);
textArea.setCaretColor(UIManager.getColor("EditorPane.caretForeground"));
textArea.setSelectionColor(selectionColor);
textArea.setCurrentLineHighlightColor(lineHighlight);
textArea.setMarkAllHighlightColor(markAllHighlightColor);
textArea.setMarkOccurrencesColor(markOccurrencesColor);
textArea.setHyperlinkForeground(hyperlinkColor);
textArea.setMatchedBracketBGColor(matchedBracketBackground);
textArea.setMatchedBracketBorderColor(lineNumberForeground);
textArea.setPaintMatchedBracketPair(true);
textArea.setAnimateBracketMatching(false);
textArea.setFadeCurrentLineHighlight(true);
// Reset gutter colors directly to ensure the change applies
Gutter gutter = RSyntaxUtilities.getGutter(textArea);
if (gutter != null) {
gutter.setBackground(editorBackground);
gutter.setBorderColor(gutterBorderColor);
gutter.setLineNumberColor(lineNumberForeground);
}
}
private static boolean isDarkTheme(Color background) {
double brightness = (background.getRed() * 0.299
+ background.getGreen() * 0.587
+ background.getBlue() * 0.114) / 255;
return brightness < 0.5;
}
private static Color adjustBrightness(Color color, float factor) {
float[] hsb = Color.RGBtoHSB(color.getRed(), color.getGreen(), color.getBlue(), null);
hsb[2] = Math.min(1.0f, hsb[2] * factor); // Adjust brightness
return Color.getHSBColor(hsb[0], hsb[1], hsb[2]);
}
}
@@ -176,8 +176,10 @@ public final class SmaliArea extends AbstractCodeArea {
private class NormalModel extends SmaliModel {
public NormalModel() {
Theme theme = getContentPanel().getMainWindow().getEditorTheme();
setSyntaxScheme(theme.scheme);
if (!getContentPanel().getMainWindow().getSettings().isUseDynamicEditorTheme()) {
Theme theme = getContentPanel().getMainWindow().getEditorTheme();
setSyntaxScheme(theme.scheme);
}
setSyntaxEditingStyle(SYNTAX_STYLE_SMALI);
}
@@ -327,6 +329,9 @@ public final class SmaliArea extends AbstractCodeArea {
}
public boolean refreshTheme() {
if (getContentPanel().getMainWindow().getSettings().isUseDynamicEditorTheme()) {
return false;
}
Theme theme = getContentPanel().getMainWindow().getEditorTheme();
boolean refresh = theme != curTheme;
if (refresh) {
@@ -337,7 +342,7 @@ public final class SmaliArea extends AbstractCodeArea {
}
private void updateTheme() {
Style[] mainStyles = curTheme.scheme.getStyles();
Style[] mainStyles = getSyntaxScheme().getStyles();
Style[] styles = new Style[mainStyles.length];
for (int i = 0; i < mainStyles.length; i++) {
Style mainStyle = mainStyles[i];
@@ -242,6 +242,7 @@ preferences.integerFormat=Ganzzahlformat
preferences.font=Schriftart ändern
preferences.smali_font=Monospaced-Schriftart (Smali/Hex)
preferences.laf_theme=Thema
#preferences.dynamic_editor_theme=Use dynamic editor theme
preferences.theme=Thema ändern
preferences.start_jobs=Autom. Hintergrunddekompilierung starten
preferences.select_font=Ändern
@@ -242,6 +242,7 @@ preferences.integerFormat=Integer format
preferences.font=Editor font
preferences.smali_font=Monospaced font (Smali/Hex)
preferences.laf_theme=Theme
preferences.dynamic_editor_theme=Use dynamic editor theme
preferences.theme=Editor theme
preferences.start_jobs=Auto start background decompilation
preferences.select_font=Change
@@ -242,6 +242,7 @@ preferences.raw_cfg=Generate RAW CFG graphs
preferences.font=Fuente del editor
#preferences.smali_font=Monospaced font (Smali/Hex)
#preferences.laf_theme=Theme
#preferences.dynamic_editor_theme=Use dynamic editor theme
preferences.theme=Tema del editor
preferences.start_jobs=Inicio autom. descompilación de fondo
preferences.select_font=Seleccionar
@@ -242,6 +242,7 @@ preferences.integerFormat=Format bilangan bulat
preferences.font=Font editor
preferences.smali_font=Font monospasi (Smali/Hex)
preferences.laf_theme=Tema
#preferences.dynamic_editor_theme=Use dynamic editor theme
preferences.theme=Tema editor
preferences.start_jobs=Deskompilasi latar belakang otomatis
preferences.select_font=Ubah
@@ -242,6 +242,7 @@ preferences.raw_cfg=RAW CFG 그래프 생성
preferences.font=에디터 글씨체
#preferences.smali_font=Monospaced font (Smali/Hex)
preferences.laf_theme=테마
#preferences.dynamic_editor_theme=Use dynamic editor theme
preferences.theme=에디터 테마
preferences.start_jobs=백그라운드에서 디컴파일 자동 시작
preferences.select_font=변경
@@ -242,6 +242,7 @@ preferences.raw_cfg=Gera gráficos CFG no formato RAW
preferences.font=Fonte do editor
#preferences.smali_font=Monospaced font (Smali/Hex)
preferences.laf_theme=Tema
#preferences.dynamic_editor_theme=Use dynamic editor theme
preferences.theme=Tema do editor
preferences.start_jobs=Inicializar descompilação automaticamente em segundo-plano
preferences.select_font=Alterar
@@ -242,6 +242,7 @@ preferences.integerFormat=Формат чисел
preferences.font=Шрифт редактора Java
preferences.smali_font=Шрифт smali/HEX редактора
preferences.laf_theme=Тема приложения
#preferences.dynamic_editor_theme=Use dynamic editor theme
preferences.theme=Тема редактора
preferences.start_jobs=Автоматическая декомпиляция
preferences.select_font=Изменить
@@ -242,6 +242,7 @@ preferences.integerFormat=数值格式化
preferences.font=编辑器字体
preferences.smali_font=等宽字体 (Smali/Hex)
preferences.laf_theme=主题
#preferences.dynamic_editor_theme=Use dynamic editor theme
preferences.theme=编辑器主题
preferences.start_jobs=自动进行后台反编译
preferences.select_font=修改
@@ -242,6 +242,7 @@ preferences.integerFormat=整數模式
preferences.font=編輯器字型
preferences.smali_font=等寬字型 (Smali/Hex)
preferences.laf_theme=主題
#preferences.dynamic_editor_theme=Use dynamic editor theme
preferences.theme=編輯器主題
preferences.start_jobs=自動開始背景反編譯
preferences.select_font=變更