Skip to content

Commit

Permalink
feat: blink (#273)
Browse files Browse the repository at this point in the history
  • Loading branch information
hstyi authored Feb 19, 2025
1 parent 503cfa9 commit 57547c9
Show file tree
Hide file tree
Showing 11 changed files with 251 additions and 37 deletions.
13 changes: 9 additions & 4 deletions src/main/kotlin/app/termora/Database.kt
Original file line number Diff line number Diff line change
Expand Up @@ -400,10 +400,10 @@ class Database private constructor(private val env: Environment) : Disposable {
protected inner class CursorStylePropertyDelegate(defaultValue: CursorStyle) :
PropertyDelegate<CursorStyle>(defaultValue) {
override fun convertValue(value: String): CursorStyle {
try {
return CursorStyle.valueOf(value)
} catch (e: Exception) {
return initializer.invoke()
return try {
CursorStyle.valueOf(value)
} catch (_: Exception) {
initializer.invoke()
}
}
}
Expand Down Expand Up @@ -458,6 +458,11 @@ class Database private constructor(private val env: Environment) : Disposable {
*/
var beep by BooleanPropertyDelegate(true)

/**
* 光标闪烁
*/
var cursorBlink by BooleanPropertyDelegate(false)

/**
* 选中复制
*/
Expand Down
20 changes: 16 additions & 4 deletions src/main/kotlin/app/termora/MyTabbedPane.kt
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package app.termora

import app.termora.actions.AnActionEvent
import app.termora.actions.DataProvider
import app.termora.actions.DataProviders
import com.formdev.flatlaf.extras.components.FlatTabbedPane
import org.apache.commons.lang3.StringUtils
Expand All @@ -13,11 +14,13 @@ import kotlin.math.abs

class MyTabbedPane : FlatTabbedPane() {

private val owner: Window get() = SwingUtilities.getWindowAncestor(this)
private val dragMouseAdaptor = DragMouseAdaptor()
private val terminalTabbedManager
get() = AnActionEvent(this, StringUtils.EMPTY, EventObject(this))
.getData(DataProviders.TerminalTabbedManager)
private val owner
get() = AnActionEvent(this, StringUtils.EMPTY, EventObject(this))
.getData(DataProviders.TermoraFrame) as TermoraFrame

init {
initEvents()
Expand Down Expand Up @@ -145,11 +148,11 @@ class MyTabbedPane : FlatTabbedPane() {
// 如果等于 null 表示在空地方释放,那么单独一个窗口
if (c == null) {
val window = TermoraFrameManager.getInstance().createWindow()
dragToAnotherWindow(window)
dragToAnotherWindow(owner, window)
window.location = releasedPoint
window.isVisible = true
} else if (c != owner && c is TermoraFrame) { // 如果在某个窗口内释放,那么就移动到某个窗口内
dragToAnotherWindow(c)
dragToAnotherWindow(owner, c)
} else {
val tab = this.terminalTab
val terminalTabbedManager = terminalTabbedManager
Expand Down Expand Up @@ -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 terminalPanel = (tab as DataProvider?)?.getData(DataProviders.TerminalPanel) ?: return
val tabbedManager = frame.getData(DataProviders.TerminalTabbed) ?: 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)
SwingUtilities.convertPointFromScreen(location, tabbedPane)
val index = tabbedPane.indexAtLocation(location.x, location.y)


moveTab(
tabbedManager,
tab,
index
)

TerminalPanelFactory.getInstance(oldWindowScope).removeTerminalPanel(terminalPanel)
TerminalPanelFactory.getInstance(windowScope).addTerminalPanel(terminalPanel)



if (frame.hasFocus()) {
return
}
Expand Down
12 changes: 11 additions & 1 deletion src/main/kotlin/app/termora/SettingsOptionsPane.kt
Original file line number Diff line number Diff line change
Expand Up @@ -305,6 +305,7 @@ class SettingsOptionsPane : OptionsPane() {
private val cursorStyleComboBox = FlatComboBox<CursorStyle>()
private val debugComboBox = YesOrNoComboBox()
private val beepComboBox = YesOrNoComboBox()
private val cursorBlinkComboBox = YesOrNoComboBox()
private val fontComboBox = FlatComboBox<String>()
private val shellComboBox = FlatComboBox<String>()
private val maxRowsTextField = IntSpinner(0, 0)
Expand Down Expand Up @@ -390,6 +391,12 @@ class SettingsOptionsPane : OptionsPane() {
}
}

cursorBlinkComboBox.addItemListener { e ->
if (e.stateChange == ItemEvent.SELECTED) {
terminalSetting.cursorBlink = cursorBlinkComboBox.selectedItem as Boolean
}
}


shellComboBox.addItemListener {
if (it.stateChange == ItemEvent.SELECTED) {
Expand Down Expand Up @@ -478,6 +485,7 @@ class SettingsOptionsPane : OptionsPane() {
fontComboBox.selectedItem = terminalSetting.font
debugComboBox.selectedItem = terminalSetting.debug
beepComboBox.selectedItem = terminalSetting.beep
cursorBlinkComboBox.selectedItem = terminalSetting.cursorBlink
cursorStyleComboBox.selectedItem = terminalSetting.cursor
selectCopyComboBox.selectedItem = terminalSetting.selectCopy
autoCloseTabComboBox.selectedItem = terminalSetting.autoCloseTabWhenDisconnected
Expand All @@ -499,7 +507,7 @@ class SettingsOptionsPane : OptionsPane() {
private fun getCenterComponent(): JComponent {
val layout = FormLayout(
"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)
Expand All @@ -526,6 +534,8 @@ class SettingsOptionsPane : OptionsPane() {
.add(selectCopyComboBox).xy(3, rows).apply { rows += step }
.add("${I18n.getString("termora.settings.terminal.cursor-style")}:").xy(1, rows)
.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(floatingToolbarComboBox).xy(3, rows).apply { rows += step }
.add("${I18n.getString("termora.settings.terminal.auto-close-tab")}:").xy(1, rows)
Expand Down
64 changes: 56 additions & 8 deletions src/main/kotlin/app/termora/TerminalPanelFactory.kt
Original file line number Diff line number Diff line change
@@ -1,50 +1,67 @@
package app.termora

import app.termora.highlight.KeywordHighlightPaintListener
import app.termora.terminal.DataKey
import app.termora.terminal.PtyConnector
import app.termora.terminal.Terminal
import app.termora.terminal.panel.TerminalHyperlinkPaintListener
import app.termora.terminal.panel.TerminalPanel
import kotlinx.coroutines.*
import java.awt.event.ComponentEvent
import java.awt.event.ComponentListener
import javax.swing.SwingUtilities
import kotlin.time.Duration.Companion.milliseconds

class TerminalPanelFactory {
class TerminalPanelFactory : Disposable {
private val terminalPanels = mutableListOf<TerminalPanel>()

companion object {

private val Factory = DataKey(TerminalPanelFactory::class)

fun getInstance(scope: Scope): TerminalPanelFactory {
return scope.getOrCreate(TerminalPanelFactory::class) { TerminalPanelFactory() }
}

fun getAllTerminalPanel(): List<TerminalPanel> {
fun getAllTerminalPanel(): Array<TerminalPanel> {
return ApplicationScope.forApplicationScope().windowScopes()
.map { getInstance(it) }
.flatMap { it.getTerminalPanels() }
.flatMap { it.terminalPanels }.toTypedArray()
}
}

init {
// repaint
Painter.getInstance()
}


fun createTerminalPanel(terminal: Terminal, ptyConnector: PtyConnector): TerminalPanel {
val terminalPanel = TerminalPanel(terminal, ptyConnector)
terminalPanel.addTerminalPaintListener(MultipleTerminalListener())
terminalPanel.addTerminalPaintListener(KeywordHighlightPaintListener.getInstance())
terminalPanel.addTerminalPaintListener(TerminalHyperlinkPaintListener.getInstance())
terminal.getTerminalModel().setData(Factory, this)

Disposer.register(terminalPanel, object : Disposable {
override fun dispose() {
terminalPanels.remove(terminalPanel)
if (terminal.getTerminalModel().hasData(Factory)) {
terminal.getTerminalModel().getData(Factory).removeTerminalPanel(terminalPanel)
}
}
})
terminalPanels.add(terminalPanel)

addTerminalPanel(terminalPanel)
return terminalPanel
}

fun getTerminalPanels(): List<TerminalPanel> {
return terminalPanels
fun getTerminalPanels(): Array<TerminalPanel> {
return terminalPanels.toTypedArray()
}

fun repaintAll() {
if (SwingUtilities.isEventDispatchThread()) {
terminalPanels.forEach { it.repaintImmediate() }
getTerminalPanels().forEach { it.repaintImmediate() }
} else {
SwingUtilities.invokeLater { repaintAll() }
}
Expand All @@ -62,4 +79,35 @@ class TerminalPanelFactory {
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()
}
}

}
4 changes: 3 additions & 1 deletion src/main/kotlin/app/termora/TermoraFrameManager.kt
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,9 @@ class TermoraFrameManager {
frames.remove(window)

// 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()

Expand Down
119 changes: 119 additions & 0 deletions src/main/kotlin/app/termora/terminal/panel/TerminalBlink.kt
Original file line number Diff line number Diff line change
@@ -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()
}
}
}
Loading

0 comments on commit 57547c9

Please sign in to comment.