feat: support to set transparency (#446)

This commit is contained in:
hstyi
2025-04-01 14:57:57 +08:00
committed by GitHub
parent 2c5442f1f3
commit 744e64b359
9 changed files with 116 additions and 6 deletions

View File

@@ -421,6 +421,13 @@ class Database private constructor(private val env: Environment) : Disposable {
} }
} }
protected inner class DoublePropertyDelegate(defaultValue: Double) :
PropertyDelegate<Double>(defaultValue) {
override fun convertValue(value: String): Double {
return value.toDoubleOrNull() ?: initializer.invoke()
}
}
protected inner class LongPropertyDelegate(defaultValue: Long) : protected inner class LongPropertyDelegate(defaultValue: Long) :
PropertyDelegate<Long>(defaultValue) { PropertyDelegate<Long>(defaultValue) {
@@ -632,6 +639,11 @@ class Database private constructor(private val env: Environment) : Disposable {
I18n.containsLanguage(Locale.getDefault()) ?: Locale.US.toString() I18n.containsLanguage(Locale.getDefault()) ?: Locale.US.toString()
} }
/**
* 透明度
*/
var opacity by DoublePropertyDelegate(1.0)
} }
/** /**

View File

@@ -0,0 +1,7 @@
package app.termora
import java.util.*
interface NotifyListener : EventListener {
fun addNotify()
}

View File

@@ -61,6 +61,7 @@ import java.nio.charset.StandardCharsets
import java.util.* import java.util.*
import java.util.function.Consumer import java.util.function.Consumer
import javax.swing.* import javax.swing.*
import javax.swing.JSpinner.NumberEditor
import javax.swing.event.DocumentEvent import javax.swing.event.DocumentEvent
import javax.swing.event.PopupMenuEvent import javax.swing.event.PopupMenuEvent
import javax.swing.event.PopupMenuListener import javax.swing.event.PopupMenuListener
@@ -131,6 +132,8 @@ class SettingsOptionsPane : OptionsPane() {
val backgroundComBoBox = YesOrNoComboBox() val backgroundComBoBox = YesOrNoComboBox()
val followSystemCheckBox = JCheckBox(I18n.getString("termora.settings.appearance.follow-system")) val followSystemCheckBox = JCheckBox(I18n.getString("termora.settings.appearance.follow-system"))
val preferredThemeBtn = JButton(Icons.settings) val preferredThemeBtn = JButton(Icons.settings)
val opacitySpinner = NumberSpinner(100, 0, 100)
private val appearance get() = database.appearance private val appearance get() = database.appearance
init { init {
@@ -140,6 +143,21 @@ class SettingsOptionsPane : OptionsPane() {
private fun initView() { private fun initView() {
backgroundComBoBox.isEnabled = SystemInfo.isWindows
opacitySpinner.isEnabled = SystemInfo.isMacOS || SystemInfo.isWindows
opacitySpinner.model = object : SpinnerNumberModel(appearance.opacity, 0.1, 1.0, 0.1) {
override fun getNextValue(): Any {
return super.getNextValue() ?: maximum
}
override fun getPreviousValue(): Any {
return super.getPreviousValue() ?: minimum
}
}
opacitySpinner.editor = NumberEditor(opacitySpinner, "#.##")
opacitySpinner.model.stepSize = 0.05
followSystemCheckBox.isSelected = appearance.followSystem followSystemCheckBox.isSelected = appearance.followSystem
preferredThemeBtn.isEnabled = followSystemCheckBox.isSelected preferredThemeBtn.isEnabled = followSystemCheckBox.isSelected
backgroundComBoBox.selectedItem = appearance.backgroundRunning backgroundComBoBox.selectedItem = appearance.backgroundRunning
@@ -179,6 +197,14 @@ class SettingsOptionsPane : OptionsPane() {
} }
} }
opacitySpinner.addChangeListener {
val opacity = opacitySpinner.value
if (opacity is Double) {
TermoraFrameManager.getInstance().setOpacity(opacity)
appearance.opacity = opacity
}
}
backgroundComBoBox.addItemListener { backgroundComBoBox.addItemListener {
if (it.stateChange == ItemEvent.SELECTED) { if (it.stateChange == ItemEvent.SELECTED) {
appearance.backgroundRunning = backgroundComBoBox.selectedItem as Boolean appearance.backgroundRunning = backgroundComBoBox.selectedItem as Boolean
@@ -283,7 +309,7 @@ class SettingsOptionsPane : OptionsPane() {
private fun getFormPanel(): JPanel { private fun getFormPanel(): JPanel {
val layout = FormLayout( val layout = FormLayout(
"left:pref, $formMargin, default:grow, $formMargin, default, default:grow", "left:pref, $formMargin, default:grow, $formMargin, default, default:grow",
"pref, $formMargin, pref, $formMargin, pref" "pref, $formMargin, pref, $formMargin, pref, $formMargin, pref"
) )
val box = FlatToolBar() val box = FlatToolBar()
box.add(followSystemCheckBox) box.add(followSystemCheckBox)
@@ -304,10 +330,12 @@ class SettingsOptionsPane : OptionsPane() {
} }
})).xy(5, rows).apply { rows += step } })).xy(5, rows).apply { rows += step }
if (SystemInfo.isWindows) {
builder.add("${I18n.getString("termora.settings.appearance.opacity")}:").xy(1, rows)
.add(opacitySpinner).xy(3, rows).apply { rows += step }
builder.add("${I18n.getString("termora.settings.appearance.background-running")}:").xy(1, rows) builder.add("${I18n.getString("termora.settings.appearance.background-running")}:").xy(1, rows)
.add(backgroundComBoBox).xy(3, rows) .add(backgroundComBoBox).xy(3, rows)
}
return builder.build() return builder.build()
} }

View File

@@ -9,6 +9,7 @@ import app.termora.terminal.DataKey
import com.formdev.flatlaf.FlatClientProperties import com.formdev.flatlaf.FlatClientProperties
import com.formdev.flatlaf.util.SystemInfo import com.formdev.flatlaf.util.SystemInfo
import com.jetbrains.JBR import com.jetbrains.JBR
import org.apache.commons.lang3.ArrayUtils
import java.awt.BorderLayout import java.awt.BorderLayout
import java.awt.Dimension import java.awt.Dimension
import java.awt.Insets import java.awt.Insets
@@ -24,6 +25,7 @@ import javax.swing.SwingUtilities
import javax.swing.SwingUtilities.isEventDispatchThread import javax.swing.SwingUtilities.isEventDispatchThread
import javax.swing.UIManager import javax.swing.UIManager
fun assertEventDispatchThread() { fun assertEventDispatchThread() {
if (!isEventDispatchThread()) throw WrongThreadException("AWT EventQueue") if (!isEventDispatchThread()) throw WrongThreadException("AWT EventQueue")
} }
@@ -41,6 +43,7 @@ class TermoraFrame : JFrame(), DataProvider {
private val welcomePanel = WelcomePanel(windowScope) private val welcomePanel = WelcomePanel(windowScope)
private val sftp get() = Database.getDatabase().sftp private val sftp get() = Database.getDatabase().sftp
private val myUI = MyFlatRootPaneUI() private val myUI = MyFlatRootPaneUI()
private var notifyListeners = emptyArray<NotifyListener>()
init { init {
@@ -239,4 +242,16 @@ class TermoraFrame : JFrame(), DataProvider {
return id.hashCode() return id.hashCode()
} }
fun addNotifyListener(listener: NotifyListener) {
notifyListeners += listener
}
fun removeNotifyListener(listener: NotifyListener) {
notifyListeners = ArrayUtils.removeElements(notifyListeners, listener)
}
override fun addNotify() {
super.addNotify()
notifyListeners.forEach { it.addNotify() }
}
} }

View File

@@ -1,8 +1,18 @@
package app.termora package app.termora
import app.termora.native.osx.NativeMacLibrary
import com.formdev.flatlaf.ui.FlatNativeWindowsLibrary
import com.formdev.flatlaf.util.SystemInfo import com.formdev.flatlaf.util.SystemInfo
import com.sun.jna.Pointer
import com.sun.jna.platform.win32.User32
import com.sun.jna.platform.win32.WinDef
import com.sun.jna.platform.win32.WinUser.*
import de.jangassen.jfa.ThreadUtils
import de.jangassen.jfa.foundation.Foundation
import de.jangassen.jfa.foundation.ID
import org.slf4j.LoggerFactory import org.slf4j.LoggerFactory
import java.awt.Frame import java.awt.Frame
import java.awt.Window
import java.awt.event.WindowAdapter import java.awt.event.WindowAdapter
import java.awt.event.WindowEvent import java.awt.event.WindowEvent
import javax.swing.JFrame import javax.swing.JFrame
@@ -13,6 +23,7 @@ import javax.swing.WindowConstants.DO_NOTHING_ON_CLOSE
import kotlin.math.max import kotlin.math.max
import kotlin.system.exitProcess import kotlin.system.exitProcess
class TermoraFrameManager { class TermoraFrameManager {
companion object { companion object {
@@ -51,6 +62,15 @@ class TermoraFrameManager {
} }
} }
frame.addNotifyListener(object : NotifyListener {
private val opacity get() = Database.getDatabase().appearance.opacity
override fun addNotify() {
val opacity = this.opacity
if (opacity >= 1.0) return
setOpacity(frame, opacity)
}
})
return frame.apply { frames.add(this) } return frame.apply { frames.add(this) }
} }
@@ -153,6 +173,31 @@ class TermoraFrameManager {
return FrameRectangle(x, y, w, h, s) return FrameRectangle(x, y, w, h, s)
} }
fun setOpacity(opacity: Double) {
if (opacity < 0 || opacity > 1 || SystemInfo.isLinux) return
for (window in getWindows()) {
setOpacity(window, opacity)
}
}
private fun setOpacity(window: Window, opacity: Double) {
if (SystemInfo.isMacOS) {
val nsWindow = ID(NativeMacLibrary.getNSWindow(window) ?: return)
ThreadUtils.dispatch_async {
Foundation.invoke(nsWindow, "setOpaque:", false)
Foundation.invoke(nsWindow, "setAlphaValue:", opacity)
}
} else if (SystemInfo.isWindows) {
val alpha = ((opacity * 255).toInt() and 0xFF).toByte()
val hwnd = WinDef.HWND(Pointer.createConstant(FlatNativeWindowsLibrary.getHWND(window)))
val exStyle = User32.INSTANCE.GetWindowLong(hwnd, User32.GWL_EXSTYLE)
if (exStyle and WS_EX_LAYERED == 0) {
User32.INSTANCE.SetWindowLong(hwnd, GWL_EXSTYLE, exStyle or WS_EX_LAYERED)
}
User32.INSTANCE.SetLayeredWindowAttributes(hwnd, 0, alpha, LWA_ALPHA)
}
}
private data class FrameRectangle( private data class FrameRectangle(
val x: Int, val y: Int, val w: Int, val h: Int, val s: Int val x: Int, val y: Int, val w: Int, val h: Int, val s: Int
) { ) {

View File

@@ -146,7 +146,7 @@ open class EmailFormattedTextField(var maxLength: Int = Int.MAX_VALUE) : Outline
} }
abstract class NumberSpinner( open class NumberSpinner(
value: Int, value: Int,
minimum: Int, minimum: Int,
maximum: Int, maximum: Int,

View File

@@ -54,6 +54,7 @@ termora.settings.appearance.theme=Theme
termora.settings.appearance.language=Language termora.settings.appearance.language=Language
termora.settings.appearance.i-want-to-translate=I want to translate termora.settings.appearance.i-want-to-translate=I want to translate
termora.settings.appearance.follow-system=Sync with OS termora.settings.appearance.follow-system=Sync with OS
termora.settings.appearance.opacity=Opacity
termora.settings.appearance.background-running=Backgrounding termora.settings.appearance.background-running=Backgrounding
termora.setting.security=Security termora.setting.security=Security

View File

@@ -51,6 +51,7 @@ termora.settings.appearance.theme=主题
termora.settings.appearance.language=语言 termora.settings.appearance.language=语言
termora.settings.appearance.i-want-to-translate=我想要翻译 termora.settings.appearance.i-want-to-translate=我想要翻译
termora.settings.appearance.follow-system=跟随系统 termora.settings.appearance.follow-system=跟随系统
termora.settings.appearance.opacity=透明度
termora.settings.appearance.background-running=后台运行 termora.settings.appearance.background-running=后台运行
termora.setting.security=安全 termora.setting.security=安全

View File

@@ -52,6 +52,7 @@ termora.settings.appearance.theme=主题
termora.settings.appearance.language=語言 termora.settings.appearance.language=語言
termora.settings.appearance.i-want-to-translate=我想要翻譯 termora.settings.appearance.i-want-to-translate=我想要翻譯
termora.settings.appearance.follow-system=跟隨系統 termora.settings.appearance.follow-system=跟隨系統
termora.settings.appearance.opacity=透明度
termora.settings.appearance.background-running=後台運行 termora.settings.appearance.background-running=後台運行
termora.setting.security=安全 termora.setting.security=安全