mirror of
https://github.com/TermoraDev/termora.git
synced 2026-01-16 02:12:58 +08:00
feat: blink (#273)
This commit is contained in:
@@ -400,10 +400,10 @@ class Database private constructor(private val env: Environment) : Disposable {
|
|||||||
protected inner class CursorStylePropertyDelegate(defaultValue: CursorStyle) :
|
protected inner class CursorStylePropertyDelegate(defaultValue: CursorStyle) :
|
||||||
PropertyDelegate<CursorStyle>(defaultValue) {
|
PropertyDelegate<CursorStyle>(defaultValue) {
|
||||||
override fun convertValue(value: String): CursorStyle {
|
override fun convertValue(value: String): CursorStyle {
|
||||||
try {
|
return try {
|
||||||
return CursorStyle.valueOf(value)
|
CursorStyle.valueOf(value)
|
||||||
} catch (e: Exception) {
|
} catch (_: Exception) {
|
||||||
return initializer.invoke()
|
initializer.invoke()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -458,6 +458,11 @@ class Database private constructor(private val env: Environment) : Disposable {
|
|||||||
*/
|
*/
|
||||||
var beep by BooleanPropertyDelegate(true)
|
var beep by BooleanPropertyDelegate(true)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 光标闪烁
|
||||||
|
*/
|
||||||
|
var cursorBlink by BooleanPropertyDelegate(false)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 选中复制
|
* 选中复制
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
package app.termora
|
package app.termora
|
||||||
|
|
||||||
import app.termora.actions.AnActionEvent
|
import app.termora.actions.AnActionEvent
|
||||||
|
import app.termora.actions.DataProvider
|
||||||
import app.termora.actions.DataProviders
|
import app.termora.actions.DataProviders
|
||||||
import com.formdev.flatlaf.extras.components.FlatTabbedPane
|
import com.formdev.flatlaf.extras.components.FlatTabbedPane
|
||||||
import org.apache.commons.lang3.StringUtils
|
import org.apache.commons.lang3.StringUtils
|
||||||
@@ -13,11 +14,13 @@ import kotlin.math.abs
|
|||||||
|
|
||||||
class MyTabbedPane : FlatTabbedPane() {
|
class MyTabbedPane : FlatTabbedPane() {
|
||||||
|
|
||||||
private val owner: Window get() = SwingUtilities.getWindowAncestor(this)
|
|
||||||
private val dragMouseAdaptor = DragMouseAdaptor()
|
private val dragMouseAdaptor = DragMouseAdaptor()
|
||||||
private val terminalTabbedManager
|
private val terminalTabbedManager
|
||||||
get() = AnActionEvent(this, StringUtils.EMPTY, EventObject(this))
|
get() = AnActionEvent(this, StringUtils.EMPTY, EventObject(this))
|
||||||
.getData(DataProviders.TerminalTabbedManager)
|
.getData(DataProviders.TerminalTabbedManager)
|
||||||
|
private val owner
|
||||||
|
get() = AnActionEvent(this, StringUtils.EMPTY, EventObject(this))
|
||||||
|
.getData(DataProviders.TermoraFrame) as TermoraFrame
|
||||||
|
|
||||||
init {
|
init {
|
||||||
initEvents()
|
initEvents()
|
||||||
@@ -145,11 +148,11 @@ class MyTabbedPane : FlatTabbedPane() {
|
|||||||
// 如果等于 null 表示在空地方释放,那么单独一个窗口
|
// 如果等于 null 表示在空地方释放,那么单独一个窗口
|
||||||
if (c == null) {
|
if (c == null) {
|
||||||
val window = TermoraFrameManager.getInstance().createWindow()
|
val window = TermoraFrameManager.getInstance().createWindow()
|
||||||
dragToAnotherWindow(window)
|
dragToAnotherWindow(owner, window)
|
||||||
window.location = releasedPoint
|
window.location = releasedPoint
|
||||||
window.isVisible = true
|
window.isVisible = true
|
||||||
} else if (c != owner && c is TermoraFrame) { // 如果在某个窗口内释放,那么就移动到某个窗口内
|
} else if (c != owner && c is TermoraFrame) { // 如果在某个窗口内释放,那么就移动到某个窗口内
|
||||||
dragToAnotherWindow(c)
|
dragToAnotherWindow(owner, c)
|
||||||
} else {
|
} else {
|
||||||
val tab = this.terminalTab
|
val tab = this.terminalTab
|
||||||
val terminalTabbedManager = terminalTabbedManager
|
val terminalTabbedManager = terminalTabbedManager
|
||||||
@@ -224,20 +227,29 @@ class MyTabbedPane : FlatTabbedPane() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
private fun dragToAnotherWindow(frame: TermoraFrame) {
|
private fun dragToAnotherWindow(oldFrame: TermoraFrame, frame: TermoraFrame) {
|
||||||
val tab = this.terminalTab ?: return
|
val tab = this.terminalTab ?: return
|
||||||
|
val terminalPanel = (tab as DataProvider?)?.getData(DataProviders.TerminalPanel) ?: return
|
||||||
val tabbedManager = frame.getData(DataProviders.TerminalTabbed) ?: return
|
val tabbedManager = frame.getData(DataProviders.TerminalTabbed) ?: return
|
||||||
val tabbedPane = frame.getData(DataProviders.TabbedPane) ?: return
|
val tabbedPane = frame.getData(DataProviders.TabbedPane) ?: return
|
||||||
|
val windowScope = frame.getData(DataProviders.WindowScope) ?: return
|
||||||
|
val oldWindowScope = oldFrame.getData(DataProviders.WindowScope) ?: return
|
||||||
val location = Point(MouseInfo.getPointerInfo().location)
|
val location = Point(MouseInfo.getPointerInfo().location)
|
||||||
SwingUtilities.convertPointFromScreen(location, tabbedPane)
|
SwingUtilities.convertPointFromScreen(location, tabbedPane)
|
||||||
val index = tabbedPane.indexAtLocation(location.x, location.y)
|
val index = tabbedPane.indexAtLocation(location.x, location.y)
|
||||||
|
|
||||||
|
|
||||||
moveTab(
|
moveTab(
|
||||||
tabbedManager,
|
tabbedManager,
|
||||||
tab,
|
tab,
|
||||||
index
|
index
|
||||||
)
|
)
|
||||||
|
|
||||||
|
TerminalPanelFactory.getInstance(oldWindowScope).removeTerminalPanel(terminalPanel)
|
||||||
|
TerminalPanelFactory.getInstance(windowScope).addTerminalPanel(terminalPanel)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
if (frame.hasFocus()) {
|
if (frame.hasFocus()) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -305,6 +305,7 @@ class SettingsOptionsPane : OptionsPane() {
|
|||||||
private val cursorStyleComboBox = FlatComboBox<CursorStyle>()
|
private val cursorStyleComboBox = FlatComboBox<CursorStyle>()
|
||||||
private val debugComboBox = YesOrNoComboBox()
|
private val debugComboBox = YesOrNoComboBox()
|
||||||
private val beepComboBox = YesOrNoComboBox()
|
private val beepComboBox = YesOrNoComboBox()
|
||||||
|
private val cursorBlinkComboBox = YesOrNoComboBox()
|
||||||
private val fontComboBox = FlatComboBox<String>()
|
private val fontComboBox = FlatComboBox<String>()
|
||||||
private val shellComboBox = FlatComboBox<String>()
|
private val shellComboBox = FlatComboBox<String>()
|
||||||
private val maxRowsTextField = IntSpinner(0, 0)
|
private val maxRowsTextField = IntSpinner(0, 0)
|
||||||
@@ -390,6 +391,12 @@ class SettingsOptionsPane : OptionsPane() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
cursorBlinkComboBox.addItemListener { e ->
|
||||||
|
if (e.stateChange == ItemEvent.SELECTED) {
|
||||||
|
terminalSetting.cursorBlink = cursorBlinkComboBox.selectedItem as Boolean
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
shellComboBox.addItemListener {
|
shellComboBox.addItemListener {
|
||||||
if (it.stateChange == ItemEvent.SELECTED) {
|
if (it.stateChange == ItemEvent.SELECTED) {
|
||||||
@@ -478,6 +485,7 @@ class SettingsOptionsPane : OptionsPane() {
|
|||||||
fontComboBox.selectedItem = terminalSetting.font
|
fontComboBox.selectedItem = terminalSetting.font
|
||||||
debugComboBox.selectedItem = terminalSetting.debug
|
debugComboBox.selectedItem = terminalSetting.debug
|
||||||
beepComboBox.selectedItem = terminalSetting.beep
|
beepComboBox.selectedItem = terminalSetting.beep
|
||||||
|
cursorBlinkComboBox.selectedItem = terminalSetting.cursorBlink
|
||||||
cursorStyleComboBox.selectedItem = terminalSetting.cursor
|
cursorStyleComboBox.selectedItem = terminalSetting.cursor
|
||||||
selectCopyComboBox.selectedItem = terminalSetting.selectCopy
|
selectCopyComboBox.selectedItem = terminalSetting.selectCopy
|
||||||
autoCloseTabComboBox.selectedItem = terminalSetting.autoCloseTabWhenDisconnected
|
autoCloseTabComboBox.selectedItem = terminalSetting.autoCloseTabWhenDisconnected
|
||||||
@@ -499,7 +507,7 @@ class SettingsOptionsPane : OptionsPane() {
|
|||||||
private fun getCenterComponent(): JComponent {
|
private fun getCenterComponent(): JComponent {
|
||||||
val layout = FormLayout(
|
val layout = FormLayout(
|
||||||
"left:pref, $formMargin, default:grow, $formMargin, left:pref, $formMargin, pref, default:grow",
|
"left:pref, $formMargin, default:grow, $formMargin, left:pref, $formMargin, pref, default:grow",
|
||||||
"pref, $formMargin, pref, $formMargin, pref, $formMargin, pref, $formMargin, pref, $formMargin, pref, $formMargin, pref, $formMargin, pref, $formMargin, pref"
|
"pref, $formMargin, pref, $formMargin, pref, $formMargin, pref, $formMargin, pref, $formMargin, pref, $formMargin, pref, $formMargin, pref, $formMargin, pref, $formMargin, pref"
|
||||||
)
|
)
|
||||||
|
|
||||||
val beepBtn = JButton(Icons.run)
|
val beepBtn = JButton(Icons.run)
|
||||||
@@ -526,6 +534,8 @@ class SettingsOptionsPane : OptionsPane() {
|
|||||||
.add(selectCopyComboBox).xy(3, rows).apply { rows += step }
|
.add(selectCopyComboBox).xy(3, rows).apply { rows += step }
|
||||||
.add("${I18n.getString("termora.settings.terminal.cursor-style")}:").xy(1, rows)
|
.add("${I18n.getString("termora.settings.terminal.cursor-style")}:").xy(1, rows)
|
||||||
.add(cursorStyleComboBox).xy(3, rows).apply { rows += step }
|
.add(cursorStyleComboBox).xy(3, rows).apply { rows += step }
|
||||||
|
.add("${I18n.getString("termora.settings.terminal.cursor-blink")}:").xy(1, rows)
|
||||||
|
.add(cursorBlinkComboBox).xy(3, rows).apply { rows += step }
|
||||||
.add("${I18n.getString("termora.settings.terminal.floating-toolbar")}:").xy(1, rows)
|
.add("${I18n.getString("termora.settings.terminal.floating-toolbar")}:").xy(1, rows)
|
||||||
.add(floatingToolbarComboBox).xy(3, rows).apply { rows += step }
|
.add(floatingToolbarComboBox).xy(3, rows).apply { rows += step }
|
||||||
.add("${I18n.getString("termora.settings.terminal.auto-close-tab")}:").xy(1, rows)
|
.add("${I18n.getString("termora.settings.terminal.auto-close-tab")}:").xy(1, rows)
|
||||||
|
|||||||
@@ -1,50 +1,67 @@
|
|||||||
package app.termora
|
package app.termora
|
||||||
|
|
||||||
import app.termora.highlight.KeywordHighlightPaintListener
|
import app.termora.highlight.KeywordHighlightPaintListener
|
||||||
|
import app.termora.terminal.DataKey
|
||||||
import app.termora.terminal.PtyConnector
|
import app.termora.terminal.PtyConnector
|
||||||
import app.termora.terminal.Terminal
|
import app.termora.terminal.Terminal
|
||||||
import app.termora.terminal.panel.TerminalHyperlinkPaintListener
|
import app.termora.terminal.panel.TerminalHyperlinkPaintListener
|
||||||
import app.termora.terminal.panel.TerminalPanel
|
import app.termora.terminal.panel.TerminalPanel
|
||||||
|
import kotlinx.coroutines.*
|
||||||
import java.awt.event.ComponentEvent
|
import java.awt.event.ComponentEvent
|
||||||
import java.awt.event.ComponentListener
|
import java.awt.event.ComponentListener
|
||||||
import javax.swing.SwingUtilities
|
import javax.swing.SwingUtilities
|
||||||
|
import kotlin.time.Duration.Companion.milliseconds
|
||||||
|
|
||||||
class TerminalPanelFactory {
|
class TerminalPanelFactory : Disposable {
|
||||||
private val terminalPanels = mutableListOf<TerminalPanel>()
|
private val terminalPanels = mutableListOf<TerminalPanel>()
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
|
|
||||||
|
private val Factory = DataKey(TerminalPanelFactory::class)
|
||||||
|
|
||||||
fun getInstance(scope: Scope): TerminalPanelFactory {
|
fun getInstance(scope: Scope): TerminalPanelFactory {
|
||||||
return scope.getOrCreate(TerminalPanelFactory::class) { TerminalPanelFactory() }
|
return scope.getOrCreate(TerminalPanelFactory::class) { TerminalPanelFactory() }
|
||||||
}
|
}
|
||||||
|
|
||||||
fun getAllTerminalPanel(): List<TerminalPanel> {
|
fun getAllTerminalPanel(): Array<TerminalPanel> {
|
||||||
return ApplicationScope.forApplicationScope().windowScopes()
|
return ApplicationScope.forApplicationScope().windowScopes()
|
||||||
.map { getInstance(it) }
|
.map { getInstance(it) }
|
||||||
.flatMap { it.getTerminalPanels() }
|
.flatMap { it.terminalPanels }.toTypedArray()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
init {
|
||||||
|
// repaint
|
||||||
|
Painter.getInstance()
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
fun createTerminalPanel(terminal: Terminal, ptyConnector: PtyConnector): TerminalPanel {
|
fun createTerminalPanel(terminal: Terminal, ptyConnector: PtyConnector): TerminalPanel {
|
||||||
val terminalPanel = TerminalPanel(terminal, ptyConnector)
|
val terminalPanel = TerminalPanel(terminal, ptyConnector)
|
||||||
terminalPanel.addTerminalPaintListener(MultipleTerminalListener())
|
terminalPanel.addTerminalPaintListener(MultipleTerminalListener())
|
||||||
terminalPanel.addTerminalPaintListener(KeywordHighlightPaintListener.getInstance())
|
terminalPanel.addTerminalPaintListener(KeywordHighlightPaintListener.getInstance())
|
||||||
terminalPanel.addTerminalPaintListener(TerminalHyperlinkPaintListener.getInstance())
|
terminalPanel.addTerminalPaintListener(TerminalHyperlinkPaintListener.getInstance())
|
||||||
|
terminal.getTerminalModel().setData(Factory, this)
|
||||||
|
|
||||||
Disposer.register(terminalPanel, object : Disposable {
|
Disposer.register(terminalPanel, object : Disposable {
|
||||||
override fun dispose() {
|
override fun dispose() {
|
||||||
terminalPanels.remove(terminalPanel)
|
if (terminal.getTerminalModel().hasData(Factory)) {
|
||||||
|
terminal.getTerminalModel().getData(Factory).removeTerminalPanel(terminalPanel)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
terminalPanels.add(terminalPanel)
|
|
||||||
|
addTerminalPanel(terminalPanel)
|
||||||
return terminalPanel
|
return terminalPanel
|
||||||
}
|
}
|
||||||
|
|
||||||
fun getTerminalPanels(): List<TerminalPanel> {
|
fun getTerminalPanels(): Array<TerminalPanel> {
|
||||||
return terminalPanels
|
return terminalPanels.toTypedArray()
|
||||||
}
|
}
|
||||||
|
|
||||||
fun repaintAll() {
|
fun repaintAll() {
|
||||||
if (SwingUtilities.isEventDispatchThread()) {
|
if (SwingUtilities.isEventDispatchThread()) {
|
||||||
terminalPanels.forEach { it.repaintImmediate() }
|
getTerminalPanels().forEach { it.repaintImmediate() }
|
||||||
} else {
|
} else {
|
||||||
SwingUtilities.invokeLater { repaintAll() }
|
SwingUtilities.invokeLater { repaintAll() }
|
||||||
}
|
}
|
||||||
@@ -62,4 +79,35 @@ class TerminalPanelFactory {
|
|||||||
terminalPanels.remove(terminalPanel)
|
terminalPanels.remove(terminalPanel)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun addTerminalPanel(terminalPanel: TerminalPanel) {
|
||||||
|
terminalPanels.add(terminalPanel)
|
||||||
|
terminalPanel.terminal.getTerminalModel().setData(Factory, this)
|
||||||
|
}
|
||||||
|
|
||||||
|
private class Painter : Disposable {
|
||||||
|
companion object {
|
||||||
|
fun getInstance(): Painter {
|
||||||
|
return ApplicationScope.forApplicationScope().getOrCreate(Painter::class) { Painter() }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private val coroutineScope = CoroutineScope(Dispatchers.IO)
|
||||||
|
|
||||||
|
init {
|
||||||
|
coroutineScope.launch {
|
||||||
|
while (coroutineScope.isActive) {
|
||||||
|
delay(500.milliseconds)
|
||||||
|
SwingUtilities.invokeLater {
|
||||||
|
ApplicationScope.forApplicationScope().windowScopes()
|
||||||
|
.map { getInstance(it) }.forEach { it.repaintAll() }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun dispose() {
|
||||||
|
coroutineScope.cancel()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -45,7 +45,9 @@ class TermoraFrameManager {
|
|||||||
frames.remove(window)
|
frames.remove(window)
|
||||||
|
|
||||||
// dispose windowScope
|
// dispose windowScope
|
||||||
Disposer.dispose(ApplicationScope.forWindowScope(e.window))
|
val windowScope = ApplicationScope.forWindowScope(e.window)
|
||||||
|
Disposer.disposeChildren(windowScope, null)
|
||||||
|
Disposer.dispose(windowScope)
|
||||||
|
|
||||||
val windowScopes = ApplicationScope.windowScopes()
|
val windowScopes = ApplicationScope.windowScopes()
|
||||||
|
|
||||||
|
|||||||
119
src/main/kotlin/app/termora/terminal/panel/TerminalBlink.kt
Normal file
119
src/main/kotlin/app/termora/terminal/panel/TerminalBlink.kt
Normal file
@@ -0,0 +1,119 @@
|
|||||||
|
package app.termora.terminal.panel
|
||||||
|
|
||||||
|
import app.termora.ApplicationScope
|
||||||
|
import app.termora.Database
|
||||||
|
import app.termora.Disposable
|
||||||
|
import app.termora.terminal.*
|
||||||
|
import kotlinx.coroutines.*
|
||||||
|
import java.util.concurrent.atomic.AtomicBoolean
|
||||||
|
import kotlin.time.Duration.Companion.milliseconds
|
||||||
|
|
||||||
|
class TerminalBlink(terminal: Terminal) : Disposable {
|
||||||
|
|
||||||
|
|
||||||
|
private var cursorBlinkJob: Job? = null
|
||||||
|
private val terminalSettings get() = Database.getDatabase().terminal
|
||||||
|
private val isDisposed = AtomicBoolean(false)
|
||||||
|
private val globalBlink get() = GlobalBlink.getInstance()
|
||||||
|
private val coroutineScope get() = globalBlink.coroutineScope
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 返回 true 表示可以显示某些内容 [TextStyle.blink]
|
||||||
|
*/
|
||||||
|
val blink get() = globalBlink.blink
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 这个与 [blink] 不同的是它是控制光标的
|
||||||
|
*/
|
||||||
|
@Volatile
|
||||||
|
var cursorBlink = true
|
||||||
|
private set
|
||||||
|
|
||||||
|
init {
|
||||||
|
|
||||||
|
reset()
|
||||||
|
|
||||||
|
// 如果有写入,那么显示光标 N 秒
|
||||||
|
terminal.getTerminalModel().addDataListener(object : DataListener {
|
||||||
|
override fun onChanged(key: DataKey<*>, data: Any) {
|
||||||
|
// 写入后,重置光标
|
||||||
|
if (key == VisualTerminal.Written) {
|
||||||
|
reset()
|
||||||
|
} else if (key == TerminalPanel.Focused) {
|
||||||
|
// 获取焦点的一瞬间则立即重置
|
||||||
|
if (data == true) {
|
||||||
|
reset()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private fun reset() {
|
||||||
|
if (isDisposed.get()) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
cursorBlink = true
|
||||||
|
cursorBlinkJob?.cancel()
|
||||||
|
cursorBlinkJob = coroutineScope.launch {
|
||||||
|
while (coroutineScope.isActive) {
|
||||||
|
|
||||||
|
delay(500.milliseconds)
|
||||||
|
|
||||||
|
if (isDisposed.get()) {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
// 如果开启了光标闪烁才闪速
|
||||||
|
cursorBlink = if (terminalSettings.cursorBlink) {
|
||||||
|
!cursorBlink
|
||||||
|
} else {
|
||||||
|
true
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun dispose() {
|
||||||
|
if (isDisposed.compareAndSet(false, true)) {
|
||||||
|
cursorBlinkJob?.cancel()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private class GlobalBlink : Disposable {
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
fun getInstance(): GlobalBlink {
|
||||||
|
return ApplicationScope.forApplicationScope()
|
||||||
|
.getOrCreate(GlobalBlink::class) { GlobalBlink() }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
val coroutineScope by lazy { CoroutineScope(Dispatchers.IO) }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 返回 true 表示可以显示某些内容 [TextStyle.blink]
|
||||||
|
*/
|
||||||
|
@Volatile
|
||||||
|
var blink = true
|
||||||
|
private set
|
||||||
|
|
||||||
|
|
||||||
|
init {
|
||||||
|
coroutineScope.launch {
|
||||||
|
while (coroutineScope.isActive) {
|
||||||
|
delay(500)
|
||||||
|
blink = !blink
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun dispose() {
|
||||||
|
coroutineScope.cancel()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -17,6 +17,7 @@ import kotlin.time.Duration
|
|||||||
class TerminalDisplay(
|
class TerminalDisplay(
|
||||||
private val terminalPanel: TerminalPanel,
|
private val terminalPanel: TerminalPanel,
|
||||||
private val terminal: Terminal,
|
private val terminal: Terminal,
|
||||||
|
private val terminalBlink: TerminalBlink
|
||||||
) : JComponent() {
|
) : JComponent() {
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
@@ -136,12 +137,13 @@ class TerminalDisplay(
|
|||||||
val lineHeight = getLineHeight()
|
val lineHeight = getLineHeight()
|
||||||
val style = if (inputMethodData.isNoTyping)
|
val style = if (inputMethodData.isNoTyping)
|
||||||
terminal.getTerminalModel().getData(DataKey.CursorStyle) else CursorStyle.Bar
|
terminal.getTerminalModel().getData(DataKey.CursorStyle) else CursorStyle.Bar
|
||||||
|
val hasFocus = terminal.getTerminalModel().getData(TerminalPanel.Focused, false)
|
||||||
|
|
||||||
// background
|
// background
|
||||||
g.color = Color(colorPalette.getColor(TerminalColor.Cursor.BACKGROUND))
|
g.color = Color(colorPalette.getColor(TerminalColor.Cursor.BACKGROUND))
|
||||||
|
|
||||||
if (style == CursorStyle.Block) {
|
if (style == CursorStyle.Block) {
|
||||||
if (terminalPanel.hasFocus()) {
|
if (hasFocus) {
|
||||||
g.fillRect(xOffset, (y - 1) * lineHeight, width, lineHeight)
|
g.fillRect(xOffset, (y - 1) * lineHeight, width, lineHeight)
|
||||||
} else {
|
} else {
|
||||||
g.drawRect(xOffset, (y - 1) * lineHeight, width, lineHeight)
|
g.drawRect(xOffset, (y - 1) * lineHeight, width, lineHeight)
|
||||||
@@ -217,19 +219,23 @@ class TerminalDisplay(
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun drawCharacters(g: Graphics2D) {
|
private fun drawCharacters(g: Graphics2D) {
|
||||||
val reverseVideo = terminal.getTerminalModel().getData(DataKey.ReverseVideo, false)
|
val terminalModel = terminal.getTerminalModel()
|
||||||
val rows = terminal.getTerminalModel().getRows()
|
val reverseVideo = terminalModel.getData(DataKey.ReverseVideo, false)
|
||||||
val cols = terminal.getTerminalModel().getCols()
|
val rows = terminalModel.getRows()
|
||||||
|
val cols = terminalModel.getCols()
|
||||||
val triple = Triple(Char.Space.toString(), TextStyle.Default, 1)
|
val triple = Triple(Char.Space.toString(), TextStyle.Default, 1)
|
||||||
val cursorPosition = terminal.getCursorModel().getPosition()
|
val cursorPosition = terminal.getCursorModel().getPosition()
|
||||||
val averageCharWidth = getAverageCharWidth()
|
val averageCharWidth = getAverageCharWidth()
|
||||||
val maxVerticalScrollOffset = terminal.getScrollingModel().getMaxVerticalScrollOffset()
|
val maxVerticalScrollOffset = terminal.getScrollingModel().getMaxVerticalScrollOffset()
|
||||||
val verticalScrollOffset = terminal.getScrollingModel().getVerticalScrollOffset()
|
val verticalScrollOffset = terminal.getScrollingModel().getVerticalScrollOffset()
|
||||||
val selectionModel = terminal.getSelectionModel()
|
val selectionModel = terminal.getSelectionModel()
|
||||||
val cursorStyle = terminal.getTerminalModel().getData(DataKey.CursorStyle)
|
val cursorStyle = terminalModel.getData(DataKey.CursorStyle)
|
||||||
val showCursor = terminal.getTerminalModel().getData(DataKey.ShowCursor)
|
val showCursor = terminalModel.getData(DataKey.ShowCursor)
|
||||||
val markupModel = terminal.getMarkupModel()
|
val markupModel = terminal.getMarkupModel()
|
||||||
val lineHeight = getLineHeight()
|
val lineHeight = getLineHeight()
|
||||||
|
val blink = terminalBlink.blink
|
||||||
|
val cursorBlink = terminalBlink.cursorBlink
|
||||||
|
val hasFocus = terminalModel.getData(TerminalPanel.Focused, false)
|
||||||
|
|
||||||
|
|
||||||
for (i in 1..rows) {
|
for (i in 1..rows) {
|
||||||
@@ -269,6 +275,13 @@ class TerminalDisplay(
|
|||||||
background = colorPalette.getColor(TerminalColor.Basic.SELECTION_BACKGROUND)
|
background = colorPalette.getColor(TerminalColor.Basic.SELECTION_BACKGROUND)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 如果启用了闪烁
|
||||||
|
if (textStyle.blink) {
|
||||||
|
if (!blink) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// 设置字体
|
// 设置字体
|
||||||
g.font = getDisplayFont(text, textStyle)
|
g.font = getDisplayFont(text, textStyle)
|
||||||
val charWidth = min(
|
val charWidth = min(
|
||||||
@@ -310,14 +323,17 @@ class TerminalDisplay(
|
|||||||
|
|
||||||
// 渲染光标
|
// 渲染光标
|
||||||
if (caret) {
|
if (caret) {
|
||||||
|
// 这几种情况光标才会渲染:输入中、闪烁中、没有焦点
|
||||||
|
if (inputMethodData.isTyping || cursorBlink || !hasFocus) {
|
||||||
drawCursor(g, i, xOffset, charWidth)
|
drawCursor(g, i, xOffset, charWidth)
|
||||||
// 如果是获取焦点状态,那么颜色互换
|
// 如果是获取焦点状态,那么颜色互换
|
||||||
if (terminalPanel.hasFocus() && cursorStyle == CursorStyle.Block && inputMethodData.isNoTyping) {
|
if (hasFocus && cursorStyle == CursorStyle.Block && inputMethodData.isNoTyping) {
|
||||||
g.color = Color(colorPalette.getColor(TerminalColor.Basic.BACKGROUND))
|
g.color = Color(colorPalette.getColor(TerminalColor.Basic.BACKGROUND))
|
||||||
} else {
|
} else {
|
||||||
g.color = Color(foreground)
|
g.color = Color(foreground)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// 渲染文本
|
// 渲染文本
|
||||||
g.drawString(text, xOffset, i * lineHeight - g.fontMetrics.descent)
|
g.drawString(text, xOffset, i * lineHeight - g.fontMetrics.descent)
|
||||||
|
|||||||
@@ -37,12 +37,14 @@ class TerminalPanel(val terminal: Terminal, private val ptyConnector: PtyConnect
|
|||||||
companion object {
|
companion object {
|
||||||
val Debug = DataKey(Boolean::class)
|
val Debug = DataKey(Boolean::class)
|
||||||
val Finding = DataKey(Boolean::class)
|
val Finding = DataKey(Boolean::class)
|
||||||
|
val Focused = DataKey(Boolean::class)
|
||||||
val SelectCopy = DataKey(Boolean::class)
|
val SelectCopy = DataKey(Boolean::class)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private val terminalBlink = TerminalBlink(terminal)
|
||||||
private val terminalFindPanel = TerminalFindPanel(this, terminal)
|
private val terminalFindPanel = TerminalFindPanel(this, terminal)
|
||||||
private val floatingToolbar = FloatingToolbarPanel()
|
private val floatingToolbar = FloatingToolbarPanel()
|
||||||
private val terminalDisplay = TerminalDisplay(this, terminal)
|
private val terminalDisplay = TerminalDisplay(this, terminal, terminalBlink)
|
||||||
private val dataProviderSupport = DataProviderSupport()
|
private val dataProviderSupport = DataProviderSupport()
|
||||||
|
|
||||||
val scrollBar = TerminalScrollBar(this@TerminalPanel, terminalFindPanel, terminal)
|
val scrollBar = TerminalScrollBar(this@TerminalPanel, terminalFindPanel, terminal)
|
||||||
@@ -140,10 +142,12 @@ class TerminalPanel(val terminal: Terminal, private val ptyConnector: PtyConnect
|
|||||||
|
|
||||||
this.addFocusListener(object : FocusAdapter() {
|
this.addFocusListener(object : FocusAdapter() {
|
||||||
override fun focusLost(e: FocusEvent) {
|
override fun focusLost(e: FocusEvent) {
|
||||||
|
terminal.getTerminalModel().setData(Focused, false)
|
||||||
repaintImmediate()
|
repaintImmediate()
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun focusGained(e: FocusEvent) {
|
override fun focusGained(e: FocusEvent) {
|
||||||
|
terminal.getTerminalModel().setData(Focused, true)
|
||||||
repaintImmediate()
|
repaintImmediate()
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
@@ -386,6 +390,7 @@ class TerminalPanel(val terminal: Terminal, private val ptyConnector: PtyConnect
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun dispose() {
|
override fun dispose() {
|
||||||
|
Disposer.dispose(terminalBlink)
|
||||||
Disposer.dispose(floatingToolbar)
|
Disposer.dispose(floatingToolbar)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -69,6 +69,7 @@ termora.settings.terminal.debug=Debug mode
|
|||||||
termora.settings.terminal.beep=Beep
|
termora.settings.terminal.beep=Beep
|
||||||
termora.settings.terminal.select-copy=Select copy
|
termora.settings.terminal.select-copy=Select copy
|
||||||
termora.settings.terminal.cursor-style=Cursor type
|
termora.settings.terminal.cursor-style=Cursor type
|
||||||
|
termora.settings.terminal.cursor-blink=Cursor blink
|
||||||
termora.settings.terminal.local-shell=Local shell
|
termora.settings.terminal.local-shell=Local shell
|
||||||
termora.settings.terminal.floating-toolbar=Floating Toolbar
|
termora.settings.terminal.floating-toolbar=Floating Toolbar
|
||||||
termora.settings.terminal.auto-close-tab=Auto Close Tab
|
termora.settings.terminal.auto-close-tab=Auto Close Tab
|
||||||
@@ -123,8 +124,6 @@ termora.find-everywhere.groups.opened-hosts=Opened hosts
|
|||||||
termora.find-everywhere.groups.tools=Tools
|
termora.find-everywhere.groups.tools=Tools
|
||||||
termora.find-everywhere.groups.settings=${termora.setting}
|
termora.find-everywhere.groups.settings=${termora.setting}
|
||||||
termora.find-everywhere.quick-command.local-terminal=Local Terminal
|
termora.find-everywhere.quick-command.local-terminal=Local Terminal
|
||||||
termora.find-everywhere.double-shift-deprecated=The double-click Shift shortcut will be removed in a future version
|
|
||||||
termora.find-everywhere.double-shift-deprecated-instead=${termora.find-everywhere.double-shift-deprecated}, use {0} instead
|
|
||||||
|
|
||||||
# Welcome
|
# Welcome
|
||||||
termora.welcome.my-hosts=My hosts
|
termora.welcome.my-hosts=My hosts
|
||||||
|
|||||||
@@ -65,8 +65,6 @@ termora.find-everywhere.groups.opened-hosts=已打开的主机
|
|||||||
termora.find-everywhere.groups.tools=工具
|
termora.find-everywhere.groups.tools=工具
|
||||||
termora.find-everywhere.groups.settings=${termora.setting}
|
termora.find-everywhere.groups.settings=${termora.setting}
|
||||||
termora.find-everywhere.quick-command.local-terminal=本地终端
|
termora.find-everywhere.quick-command.local-terminal=本地终端
|
||||||
termora.find-everywhere.double-shift-deprecated=双击 Shift 快捷键将会在未来的版本中移除
|
|
||||||
termora.find-everywhere.double-shift-deprecated-instead=${termora.find-everywhere.double-shift-deprecated},请使用 {0} 代替
|
|
||||||
|
|
||||||
termora.settings.terminal=终端
|
termora.settings.terminal=终端
|
||||||
termora.settings.terminal.font=字体
|
termora.settings.terminal.font=字体
|
||||||
@@ -76,6 +74,7 @@ termora.settings.terminal.debug=调试模式
|
|||||||
termora.settings.terminal.beep=蜂鸣声
|
termora.settings.terminal.beep=蜂鸣声
|
||||||
termora.settings.terminal.select-copy=选中复制
|
termora.settings.terminal.select-copy=选中复制
|
||||||
termora.settings.terminal.cursor-style=光标样式
|
termora.settings.terminal.cursor-style=光标样式
|
||||||
|
termora.settings.terminal.cursor-blink=光标闪烁
|
||||||
termora.settings.terminal.local-shell=本地终端
|
termora.settings.terminal.local-shell=本地终端
|
||||||
termora.settings.terminal.floating-toolbar=悬浮工具栏
|
termora.settings.terminal.floating-toolbar=悬浮工具栏
|
||||||
termora.settings.terminal.auto-close-tab=自动关闭标签
|
termora.settings.terminal.auto-close-tab=自动关闭标签
|
||||||
|
|||||||
@@ -74,8 +74,6 @@ termora.find-everywhere.groups.opened-hosts=已開啟的主機
|
|||||||
termora.find-everywhere.groups.tools=工具
|
termora.find-everywhere.groups.tools=工具
|
||||||
termora.find-everywhere.groups.settings=${termora.setting}
|
termora.find-everywhere.groups.settings=${termora.setting}
|
||||||
termora.find-everywhere.quick-command.local-terminal=本地端
|
termora.find-everywhere.quick-command.local-terminal=本地端
|
||||||
termora.find-everywhere.double-shift-deprecated=雙擊 Shift 快捷鍵將會在未來的版本中移除
|
|
||||||
termora.find-everywhere.double-shift-deprecated-instead=${termora.find-everywhere.double-shift-deprecated},請使用 {0} 代替
|
|
||||||
|
|
||||||
termora.settings.terminal=終端
|
termora.settings.terminal=終端
|
||||||
termora.settings.terminal.font=字體
|
termora.settings.terminal.font=字體
|
||||||
@@ -85,6 +83,7 @@ termora.settings.terminal.debug=偵錯模式
|
|||||||
termora.settings.terminal.beep=蜂鳴聲
|
termora.settings.terminal.beep=蜂鳴聲
|
||||||
termora.settings.terminal.select-copy=選取複製
|
termora.settings.terminal.select-copy=選取複製
|
||||||
termora.settings.terminal.cursor-style=遊標風格
|
termora.settings.terminal.cursor-style=遊標風格
|
||||||
|
termora.settings.terminal.cursor-blink=遊標閃爍
|
||||||
termora.settings.terminal.local-shell=本地端
|
termora.settings.terminal.local-shell=本地端
|
||||||
termora.settings.terminal.floating-toolbar=懸浮工具列
|
termora.settings.terminal.floating-toolbar=懸浮工具列
|
||||||
termora.settings.terminal.auto-close-tab=自動關閉標籤
|
termora.settings.terminal.auto-close-tab=自動關閉標籤
|
||||||
|
|||||||
Reference in New Issue
Block a user