feat: theme sync with OS (#82)

This commit is contained in:
hstyi
2025-01-15 22:24:19 +08:00
committed by GitHub
parent e30316eab3
commit 0884486e91
7 changed files with 577 additions and 57 deletions

View File

@@ -129,14 +129,14 @@ class ApplicationRunner {
}
val themeManager = ThemeManager.getInstance()
val settings = Database.getDatabase()
var theme = settings.appearance.theme
// 如果是跟随系统或者不存在样式,那么使用默认的
if (settings.appearance.followSystem || !themeManager.themes.containsKey(theme)) {
val appearance = Database.getDatabase().appearance
var theme = appearance.theme
// 如果是跟随系统
if (appearance.followSystem) {
theme = if (OsThemeDetector.getDetector().isDark) {
"Dark"
appearance.darkTheme
} else {
"Light"
appearance.lightTheme
}
}

View File

@@ -551,6 +551,8 @@ class Database private constructor(private val env: Environment) : Disposable {
* 跟随系统
*/
var followSystem by BooleanPropertyDelegate(true)
var darkTheme by StringPropertyDelegate("Dark")
var lightTheme by StringPropertyDelegate("Light")
/**
* 语言

View File

@@ -6,14 +6,13 @@ import com.formdev.flatlaf.FlatClientProperties
import com.formdev.flatlaf.FlatLaf
import com.formdev.flatlaf.util.SystemInfo
import com.jetbrains.JBR
import java.awt.BorderLayout
import java.awt.Dimension
import java.awt.Window
import java.awt.*
import java.awt.event.KeyEvent
import java.awt.event.WindowAdapter
import java.awt.event.WindowEvent
import javax.swing.*
abstract class DialogWrapper(owner: Window?) : JDialog(owner) {
private val rootPanel = JPanel(BorderLayout())
private val titleLabel = JLabel()
@@ -147,6 +146,31 @@ abstract class DialogWrapper(owner: Window?) : JDialog(owner) {
rootPane.actionMap.put("close", object : AnAction() {
override fun actionPerformed(evt: AnActionEvent) {
val c = KeyboardFocusManager.getCurrentKeyboardFocusManager().focusOwner
val popups: List<JPopupMenu> = SwingUtils.getDescendantsOfType(
JPopupMenu::class.java,
c as Container, true
)
var openPopup = false
for (p in popups) {
p.isVisible = false
openPopup = true
}
val window = SwingUtilities.windowForComponent(c)
val windows = window.ownedWindows
for (w in windows) {
if (w.isVisible && w.javaClass.getName().endsWith("HeavyWeightWindow")) {
openPopup = true
w.dispose()
}
}
if (openPopup) {
return
}
doCancelAction()
}
})

View File

@@ -8,6 +8,11 @@ import com.formdev.flatlaf.FlatPropertiesLaf
import com.formdev.flatlaf.util.SystemInfo
import java.util.*
interface LafTag
interface LightLafTag : LafTag
interface DarkLafTag : LafTag
class DraculaLaf : FlatPropertiesLaf("Dracula", Properties().apply {
putAll(
mapOf(
@@ -16,7 +21,7 @@ class DraculaLaf : FlatPropertiesLaf("Dracula", Properties().apply {
"@windowText" to "#eaeaea",
)
)
}), ColorTheme {
}), ColorTheme, DarkLafTag {
override fun getColor(color: TerminalColor): Int {
return when (color) {
TerminalColor.Basic.BACKGROUND -> 0x282935
@@ -54,7 +59,8 @@ class DraculaLaf : FlatPropertiesLaf("Dracula", Properties().apply {
}
class LightLaf : FlatLightLaf(), ColorTheme {
class LightLaf : FlatLightLaf(), ColorTheme, LightLafTag {
override fun getColor(color: TerminalColor): Int {
return when (color) {
TerminalColor.Normal.BLACK -> 0
@@ -81,7 +87,7 @@ class LightLaf : FlatLightLaf(), ColorTheme {
}
class DarkLaf : FlatDarkLaf(), ColorTheme {
class DarkLaf : FlatDarkLaf(), ColorTheme, DarkLafTag {
override fun getColor(color: TerminalColor): Int {
return when (color) {
TerminalColor.Normal.BLACK -> 0
@@ -110,7 +116,7 @@ class DarkLaf : FlatDarkLaf(), ColorTheme {
}
}
class iTerm2DarkLaf : FlatDarkLaf(), ColorTheme {
class iTerm2DarkLaf : FlatDarkLaf(), ColorTheme, DarkLafTag {
override fun getColor(color: TerminalColor): Int {
return when (color) {
@@ -158,7 +164,7 @@ class TermiusLightLaf : FlatPropertiesLaf("Termius Light", Properties().apply {
"@windowText" to "#32364a",
)
)
}), ColorTheme {
}), ColorTheme, LightLafTag {
override fun getColor(color: TerminalColor): Int {
@@ -201,7 +207,7 @@ class TermiusDarkLaf : FlatPropertiesLaf("Termius Dark", Properties().apply {
"@windowText" to "#21b568",
)
)
}), ColorTheme {
}), ColorTheme, DarkLafTag {
override fun getColor(color: TerminalColor): Int {
return when (color) {
@@ -243,7 +249,7 @@ class NovelLaf : FlatPropertiesLaf("Novel", Properties().apply {
"@windowText" to "#3b2322",
)
)
}), ColorTheme {
}), ColorTheme, LightLafTag {
override fun getColor(color: TerminalColor): Int {
return when (color) {
TerminalColor.Normal.BLACK -> 0x000000
@@ -282,7 +288,7 @@ class AtomOneDarkLaf : FlatPropertiesLaf("Atom One Dark", Properties().apply {
"@windowText" to "#abb2bf",
)
)
}), ColorTheme {
}), ColorTheme, DarkLafTag {
override fun getColor(color: TerminalColor): Int {
return when (color) {
TerminalColor.Normal.BLACK -> 0x000000
@@ -320,7 +326,7 @@ class AtomOneLightLaf : FlatPropertiesLaf("Atom One Light", Properties().apply {
"@windowText" to "#383a42",
)
)
}), ColorTheme {
}), ColorTheme, LightLafTag {
override fun getColor(color: TerminalColor): Int {
return when (color) {
TerminalColor.Normal.BLACK -> 0x000000
@@ -358,7 +364,7 @@ class EverforestDarkLaf : FlatPropertiesLaf("Everforest Dark", Properties().appl
"@windowText" to "#d3c6aa",
)
)
}), ColorTheme {
}), ColorTheme, DarkLafTag {
override fun getColor(color: TerminalColor): Int {
return when (color) {
TerminalColor.Normal.BLACK -> 0x42494e
@@ -395,7 +401,7 @@ class EverforestLightLaf : FlatPropertiesLaf("Everforest Light", Properties().ap
"@windowText" to "#5c6a72",
)
)
}), ColorTheme {
}), ColorTheme, LightLafTag {
override fun getColor(color: TerminalColor): Int {
return when (color) {
TerminalColor.Normal.BLACK -> 0x42494e
@@ -432,7 +438,7 @@ class NightOwlLaf : FlatPropertiesLaf("Night Owl", Properties().apply {
"@windowText" to "#d6deeb",
)
)
}), ColorTheme {
}), ColorTheme, DarkLafTag {
override fun getColor(color: TerminalColor): Int {
return when (color) {
TerminalColor.Normal.BLACK -> 0x072945
@@ -469,7 +475,7 @@ class LightOwlLaf : FlatPropertiesLaf("Light Owl", Properties().apply {
"@windowText" to "#403f53",
)
)
}), ColorTheme {
}), ColorTheme, LightLafTag {
override fun getColor(color: TerminalColor): Int {
return when (color) {
TerminalColor.Normal.BLACK -> 0x403f53
@@ -506,7 +512,7 @@ class AuraLaf : FlatPropertiesLaf("Aura", Properties().apply {
"@windowText" to "#edecee",
)
)
}), ColorTheme {
}), ColorTheme, DarkLafTag {
override fun getColor(color: TerminalColor): Int {
return when (color) {
TerminalColor.Normal.BLACK -> 0x1c1b22
@@ -543,7 +549,7 @@ class Cobalt2Laf : FlatPropertiesLaf("Cobalt2", Properties().apply {
"@windowText" to "#ffffff",
)
)
}), ColorTheme {
}), ColorTheme, DarkLafTag {
override fun getColor(color: TerminalColor): Int {
return when (color) {
TerminalColor.Normal.BLACK -> 0x000000
@@ -580,7 +586,7 @@ class OctocatDarkLaf : FlatPropertiesLaf("Octocat Dark", Properties().apply {
"@windowText" to "#8b949e",
)
)
}), ColorTheme {
}), ColorTheme, DarkLafTag {
override fun getColor(color: TerminalColor): Int {
return when (color) {
TerminalColor.Normal.BLACK -> 0x000000
@@ -617,7 +623,7 @@ class OctocatLightLaf : FlatPropertiesLaf("Octocat Light", Properties().apply {
"@windowText" to "#3e3e3e",
)
)
}), ColorTheme {
}), ColorTheme, LightLafTag {
override fun getColor(color: TerminalColor): Int {
return when (color) {
TerminalColor.Normal.BLACK -> 0x000000
@@ -654,7 +660,7 @@ class AyuDarkLaf : FlatPropertiesLaf("Ayu Dark", Properties().apply {
"@windowText" to "#e6e1cf",
)
)
}), ColorTheme {
}), ColorTheme, DarkLafTag {
override fun getColor(color: TerminalColor): Int {
return when (color) {
TerminalColor.Normal.BLACK -> 0x000000
@@ -691,7 +697,7 @@ class AyuLightLaf : FlatPropertiesLaf("Ayu Light", Properties().apply {
"@windowText" to "#5c6773",
)
)
}), ColorTheme {
}), ColorTheme, LightLafTag {
override fun getColor(color: TerminalColor): Int {
return when (color) {
TerminalColor.Normal.BLACK -> 0x000000
@@ -728,7 +734,7 @@ class HomebrewLaf : FlatPropertiesLaf("Homebrew", Properties().apply {
"@windowText" to "#00ff00",
)
)
}), ColorTheme {
}), ColorTheme, DarkLafTag {
override fun getColor(color: TerminalColor): Int {
return when (color) {
TerminalColor.Normal.BLACK -> 0x2e2e2e
@@ -767,7 +773,7 @@ class ProLaf : FlatPropertiesLaf("Pro", Properties().apply {
"@windowText" to "#f2f2f2",
)
)
}), ColorTheme {
}), ColorTheme, DarkLafTag {
override fun getColor(color: TerminalColor): Int {
return when (color) {
TerminalColor.Normal.BLACK -> 0x2e2e2e
@@ -806,7 +812,7 @@ class NordLightLaf : FlatPropertiesLaf("Nord Light", Properties().apply {
"@windowText" to "#414858",
)
)
}), ColorTheme {
}), ColorTheme, LightLafTag {
override fun getColor(color: TerminalColor): Int {
return when (color) {
TerminalColor.Normal.BLACK -> 0x2c3344
@@ -845,7 +851,7 @@ class NordDarkLaf : FlatPropertiesLaf("Nord Dark", Properties().apply {
"@windowText" to "#d8dee9",
)
)
}), ColorTheme {
}), ColorTheme, DarkLafTag {
override fun getColor(color: TerminalColor): Int {
return when (color) {
TerminalColor.Normal.BLACK -> 0x3b4252
@@ -885,7 +891,7 @@ class GitHubLightLaf : FlatPropertiesLaf("GitHub Light", Properties().apply {
"@windowText" to "#3e3e3e",
)
)
}), ColorTheme {
}), ColorTheme, LightLafTag {
override fun getColor(color: TerminalColor): Int {
return when (color) {
TerminalColor.Normal.BLACK -> 0x3e3e3e
@@ -924,7 +930,7 @@ class GitHubDarkLaf : FlatPropertiesLaf("GitHub Dark", Properties().apply {
"@windowText" to "#8b949e",
)
)
}), ColorTheme {
}), ColorTheme, DarkLafTag {
override fun getColor(color: TerminalColor): Int {
return when (color) {
TerminalColor.Normal.BLACK -> 0x000000
@@ -964,7 +970,7 @@ class ChalkLaf : FlatPropertiesLaf("Chalk", Properties().apply {
"@windowText" to "#d2d8d9",
)
)
}), ColorTheme {
}), ColorTheme, DarkLafTag {
override fun getColor(color: TerminalColor): Int {
return when (color) {
TerminalColor.Normal.BLACK -> 0x7d8b8f

View File

@@ -17,11 +17,8 @@ import app.termora.terminal.CursorStyle
import app.termora.terminal.DataKey
import app.termora.terminal.panel.TerminalPanel
import cash.z.ecc.android.bip39.Mnemonics
import com.formdev.flatlaf.FlatLaf
import com.formdev.flatlaf.extras.FlatSVGIcon
import com.formdev.flatlaf.extras.components.FlatButton
import com.formdev.flatlaf.extras.components.FlatComboBox
import com.formdev.flatlaf.extras.components.FlatLabel
import com.formdev.flatlaf.extras.components.*
import com.formdev.flatlaf.util.SystemInfo
import com.jgoodies.forms.builder.FormBuilder
import com.jgoodies.forms.layout.FormLayout
@@ -49,6 +46,8 @@ import java.nio.charset.StandardCharsets
import java.util.*
import javax.swing.*
import javax.swing.event.DocumentEvent
import javax.swing.event.PopupMenuEvent
import javax.swing.event.PopupMenuListener
import kotlin.time.Duration.Companion.milliseconds
@@ -109,6 +108,7 @@ class SettingsOptionsPane : OptionsPane() {
val themeComboBox = FlatComboBox<String>()
val languageComboBox = FlatComboBox<String>()
val followSystemCheckBox = JCheckBox(I18n.getString("termora.settings.appearance.follow-system"))
val preferredThemeBtn = JButton(Icons.settings)
private val appearance get() = database.appearance
init {
@@ -119,6 +119,7 @@ class SettingsOptionsPane : OptionsPane() {
private fun initView() {
followSystemCheckBox.isSelected = appearance.followSystem
preferredThemeBtn.isEnabled = followSystemCheckBox.isSelected
themeComboBox.isEnabled = !followSystemCheckBox.isSelected
themeManager.themes.keys.forEach { themeComboBox.addItem(it) }
@@ -158,19 +159,17 @@ class SettingsOptionsPane : OptionsPane() {
followSystemCheckBox.addActionListener {
appearance.followSystem = followSystemCheckBox.isSelected
themeComboBox.isEnabled = !followSystemCheckBox.isSelected
preferredThemeBtn.isEnabled = followSystemCheckBox.isSelected
appearance.theme = themeComboBox.selectedItem as String
if (followSystemCheckBox.isSelected) {
SwingUtilities.invokeLater {
if (OsThemeDetector.getDetector().isDark) {
if (!FlatLaf.isLafDark()) {
themeManager.change("Dark")
themeComboBox.selectedItem = "Dark"
}
themeManager.change(appearance.darkTheme)
themeComboBox.selectedItem = appearance.darkTheme
} else {
if (FlatLaf.isLafDark()) {
themeManager.change("Light")
themeComboBox.selectedItem = "Light"
}
themeManager.change(appearance.lightTheme)
themeComboBox.selectedItem = appearance.lightTheme
}
}
}
@@ -189,6 +188,8 @@ class SettingsOptionsPane : OptionsPane() {
}
}
}
preferredThemeBtn.addActionListener { showPreferredThemeContextmenu() }
}
override fun getIcon(isSelected: Boolean): Icon {
@@ -203,19 +204,74 @@ class SettingsOptionsPane : OptionsPane() {
return this
}
private fun showPreferredThemeContextmenu() {
val popupMenu = FlatPopupMenu()
val dark = JMenu("For Dark OS")
val light = JMenu("For Light OS")
val darkTheme = appearance.darkTheme
val lightTheme = appearance.lightTheme
for (e in themeManager.themes) {
val clazz = Class.forName(e.value)
val item = JCheckBoxMenuItem(e.key)
item.isSelected = e.key == lightTheme || e.key == darkTheme
if (clazz.interfaces.contains(DarkLafTag::class.java)) {
dark.add(item).addActionListener {
if (e.key != darkTheme) {
appearance.darkTheme = e.key
if (OsThemeDetector.getDetector().isDark) {
themeComboBox.selectedItem = e.key
}
}
}
} else if (clazz.interfaces.contains(LightLafTag::class.java)) {
light.add(item).addActionListener {
if (e.key != lightTheme) {
appearance.lightTheme = e.key
if (!OsThemeDetector.getDetector().isDark) {
themeComboBox.selectedItem = e.key
}
}
}
}
}
popupMenu.add(dark)
popupMenu.addSeparator()
popupMenu.add(light)
popupMenu.addPopupMenuListener(object : PopupMenuListener {
override fun popupMenuWillBecomeVisible(e: PopupMenuEvent) {
}
override fun popupMenuWillBecomeInvisible(e: PopupMenuEvent) {
}
override fun popupMenuCanceled(e: PopupMenuEvent) {
}
})
popupMenu.show(preferredThemeBtn, 0, preferredThemeBtn.height + 2)
}
private fun getFormPanel(): JPanel {
val layout = FormLayout(
"left:pref, $formMargin, default:grow, $formMargin, default, default:grow",
"pref, $formMargin, pref, $formMargin"
)
val box = FlatToolBar()
box.add(followSystemCheckBox)
box.add(Box.createHorizontalStrut(2))
box.add(preferredThemeBtn)
var rows = 1
val step = 2
return FormBuilder.create().layout(layout)
.add("${I18n.getString("termora.settings.appearance.theme")}:").xy(1, rows)
.add(themeComboBox).xy(3, rows)
.add(followSystemCheckBox).xy(5, rows).apply { rows += step }
.add(box).xy(5, rows).apply { rows += step }
.add("${I18n.getString("termora.settings.appearance.language")}:").xy(1, rows)
.add(languageComboBox).xy(3, rows)
.add(Hyperlink(object : AnAction(I18n.getString("termora.settings.appearance.i-want-to-translate")) {

View File

@@ -28,6 +28,7 @@ class ThemeManager private constructor() {
}
}
val appearance by lazy { Database.getDatabase().appearance }
val themes = mapOf(
"Light" to LightLaf::class.java.name,
"Dark" to DarkLaf::class.java.name,
@@ -79,18 +80,16 @@ class ThemeManager private constructor() {
GlobalScope.launch(Dispatchers.IO) {
OsThemeDetector.getDetector().registerListener(object : Consumer<Boolean> {
override fun accept(isDark: Boolean) {
if (!Database.getDatabase().appearance.followSystem) {
if (!appearance.followSystem) {
return
}
if (FlatLaf.isLafDark() && isDark) {
return
}
if (isDark) {
SwingUtilities.invokeLater { change("Dark") }
} else {
SwingUtilities.invokeLater { change("Light") }
SwingUtilities.invokeLater {
if (isDark) {
change(appearance.darkTheme)
} else {
change(appearance.lightTheme)
}
}
}
})