mirror of
https://github.com/TermoraDev/termora.git
synced 2026-01-16 02:12:58 +08:00
feat: support to set transparency (#446)
This commit is contained in:
@@ -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)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
7
src/main/kotlin/app/termora/NotifyListener.kt
Normal file
7
src/main/kotlin/app/termora/NotifyListener.kt
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
package app.termora
|
||||||
|
|
||||||
|
import java.util.*
|
||||||
|
|
||||||
|
interface NotifyListener : EventListener {
|
||||||
|
fun addNotify()
|
||||||
|
}
|
||||||
@@ -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.background-running")}:").xy(1, rows)
|
builder.add("${I18n.getString("termora.settings.appearance.opacity")}:").xy(1, rows)
|
||||||
.add(backgroundComBoBox).xy(3, rows)
|
.add(opacitySpinner).xy(3, rows).apply { rows += step }
|
||||||
}
|
|
||||||
|
builder.add("${I18n.getString("termora.settings.appearance.background-running")}:").xy(1, rows)
|
||||||
|
.add(backgroundComBoBox).xy(3, rows)
|
||||||
|
|
||||||
return builder.build()
|
return builder.build()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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() }
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -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
|
||||||
) {
|
) {
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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=安全
|
||||||
|
|||||||
@@ -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=安全
|
||||||
|
|||||||
Reference in New Issue
Block a user