diff --git a/src/jvmMain/kotlin/dev/romainguy/kotlin/explorer/DocumentChangeListener.kt b/src/jvmMain/kotlin/dev/romainguy/kotlin/explorer/DocumentChangeListener.kt new file mode 100644 index 00000000..e73405b0 --- /dev/null +++ b/src/jvmMain/kotlin/dev/romainguy/kotlin/explorer/DocumentChangeListener.kt @@ -0,0 +1,37 @@ +/* + * Copyright (C) 2023 Romain Guy + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package dev.romainguy.kotlin.explorer + +import javax.swing.event.DocumentEvent +import javax.swing.event.DocumentListener + +/** + * A [DocumentListener] that takes a single lambda that's invoked on any change + */ +class DocumentChangeListener(private val block: (DocumentEvent) -> Unit) : DocumentListener { + override fun insertUpdate(event: DocumentEvent) { + block(event) + } + + override fun removeUpdate(event: DocumentEvent) { + block(event) + } + + override fun changedUpdate(event: DocumentEvent) { + block(event) + } +} \ No newline at end of file diff --git a/src/jvmMain/kotlin/dev/romainguy/kotlin/explorer/KotlinExplorer.kt b/src/jvmMain/kotlin/dev/romainguy/kotlin/explorer/KotlinExplorer.kt index 21d3e02b..5f0595ce 100644 --- a/src/jvmMain/kotlin/dev/romainguy/kotlin/explorer/KotlinExplorer.kt +++ b/src/jvmMain/kotlin/dev/romainguy/kotlin/explorer/KotlinExplorer.kt @@ -14,7 +14,6 @@ * limitations under the License. */ -@file:OptIn(ExperimentalSplitPaneApi::class) @file:Suppress("FunctionName") package dev.romainguy.kotlin.explorer @@ -39,9 +38,6 @@ import org.fife.ui.rsyntaxtextarea.SyntaxConstants import org.fife.ui.rsyntaxtextarea.Theme import org.fife.ui.rtextarea.RTextScrollPane import org.fife.ui.rtextarea.SearchEngine -import org.jetbrains.compose.splitpane.ExperimentalSplitPaneApi -import org.jetbrains.compose.splitpane.HorizontalSplitPane -import org.jetbrains.compose.splitpane.rememberSplitPaneState import org.jetbrains.jewel.foundation.theme.JewelTheme import org.jetbrains.jewel.intui.standalone.theme.IntUiTheme import org.jetbrains.jewel.intui.standalone.theme.darkThemeDefinition @@ -62,8 +58,6 @@ import java.awt.event.FocusEvent import java.awt.event.FocusListener import java.io.IOException import javax.swing.SwingUtilities -import javax.swing.event.DocumentEvent -import javax.swing.event.DocumentListener private const val FontSizeEditingMode = 12.0f private const val FontSizePresentationMode = 20.0f @@ -73,9 +67,6 @@ private fun FrameWindowScope.KotlinExplorer( explorerState: ExplorerState ) { // TODO: Move all those remembers to an internal private state object - var sourceTextArea by remember { mutableStateOf(null) } - var dexTextArea by remember { mutableStateOf(null) } - var oatTextArea by remember { mutableStateOf(null) } var activeTextArea by remember { mutableStateOf(null) } var status by remember { mutableStateOf("Ready") } @@ -115,6 +106,11 @@ private fun FrameWindowScope.KotlinExplorer( override fun focusLost(e: FocusEvent?) { } }} + + val sourceTextArea = remember { sourceTextArea(focusTracker, explorerState) } + val dexTextArea = remember { dexTextArea(explorerState, focusTracker) } + val oatTextArea = remember { oatTextArea(focusTracker) } + val findDialog = remember { FindDialog(window, searchListener).apply { searchContext.searchWrap = true } } var showSettings by remember { mutableStateOf(!explorerState.toolPaths.isValid) } @@ -123,13 +119,13 @@ private fun FrameWindowScope.KotlinExplorer( sourceTextArea, { dex -> if (dex != null) { - updateTextArea(dexTextArea!!, dex) + updateTextArea(dexTextArea, dex) } else { - dexTextArea?.refreshText() + dexTextArea.refreshText() } }, - { oat -> updateTextArea(oatTextArea!!, oat) }, + { oat -> updateTextArea(oatTextArea, oat) }, { statusUpdate -> status = statusUpdate }, { findDialog.isVisible = true }, { SearchEngine.find(activeTextArea, findDialog.searchContext) }, @@ -145,98 +141,12 @@ private fun FrameWindowScope.KotlinExplorer( Column( modifier = Modifier.background(JewelTheme.globalColors.paneBackground) ) { - HorizontalSplitPane( + ThreeWaySplitter( modifier = Modifier.weight(1.0f), - splitPaneState = rememberSplitPaneState(initialPositionPercentage = 0.3f) - ) { - first { - SwingPanel( - modifier = Modifier.fillMaxSize(), - factory = { - sourceTextArea = RSyntaxTextArea().apply { - configureSyntaxTextArea(SyntaxConstants.SYNTAX_STYLE_KOTLIN) - addFocusListener(focusTracker) - SwingUtilities.invokeLater { requestFocusInWindow() } - document.addDocumentListener(object : DocumentListener { - override fun insertUpdate(e: DocumentEvent?) { - explorerState.sourceCode = text - } - - override fun removeUpdate(e: DocumentEvent?) { - explorerState.sourceCode = text - } - - override fun changedUpdate(e: DocumentEvent?) { - explorerState.sourceCode = text - } - }) - } - RTextScrollPane(sourceTextArea) - }, - update = { - sourceTextArea?.text = explorerState.sourceCode - sourceTextArea?.font = sourceTextArea?.font?.deriveFont( - if (explorerState.presentationMode) FontSizePresentationMode else FontSizeEditingMode - ) - } - ) - } - second { - HorizontalSplitPane( - modifier = Modifier.weight(1.0f), - splitPaneState = rememberSplitPaneState(initialPositionPercentage = 0.5f) - ) { - first { - SwingPanel( - modifier = Modifier.fillMaxSize(), - factory = { - dexTextArea = DexTextArea(explorerState).apply { - configureSyntaxTextArea(SyntaxConstants.SYNTAX_STYLE_NONE) - addFocusListener(focusTracker) - } - RTextScrollPane(dexTextArea) - }, - update = { - dexTextArea?.font = dexTextArea?.font?.deriveFont( - if (explorerState.presentationMode) { - FontSizePresentationMode - } else { - FontSizeEditingMode - } - ) - } - ) - } - second { - SwingPanel( - modifier = Modifier.fillMaxSize(), - factory = { - oatTextArea = RSyntaxTextArea().apply { - configureSyntaxTextArea(SyntaxConstants.SYNTAX_STYLE_NONE) - addFocusListener(focusTracker) - } - RTextScrollPane(oatTextArea) - }, - update = { - oatTextArea?.font = oatTextArea?.font?.deriveFont( - if (explorerState.presentationMode) { - FontSizePresentationMode - } else { - FontSizeEditingMode - } - ) - } - ) - } - splitter { - HorizontalSplitter() - } - } - } - splitter { - HorizontalSplitter() - } - } + { SourcePanel(sourceTextArea, explorerState) }, + { TextPanel(dexTextArea, explorerState) }, + { TextPanel(oatTextArea, explorerState) }, + ) Row { Text( modifier = Modifier @@ -250,6 +160,51 @@ private fun FrameWindowScope.KotlinExplorer( } } +@Composable +private fun SourcePanel(sourceTextArea: RSyntaxTextArea, explorerState: ExplorerState) { + SwingPanel( + modifier = Modifier.fillMaxSize(), + factory = { + RTextScrollPane(sourceTextArea) + }, + update = { + sourceTextArea.text = explorerState.sourceCode + sourceTextArea.setFont(explorerState) + } + ) +} + +@Composable +private fun TextPanel(textArea: RSyntaxTextArea, explorerState: ExplorerState) { + SwingPanel( + modifier = Modifier.fillMaxSize(), + factory = { RTextScrollPane(textArea) }, + update = { textArea.setFont(explorerState) }) +} + +private fun sourceTextArea(focusTracker: FocusListener, explorerState: ExplorerState): RSyntaxTextArea { + return RSyntaxTextArea().apply { + configureSyntaxTextArea(SyntaxConstants.SYNTAX_STYLE_KOTLIN) + addFocusListener(focusTracker) + SwingUtilities.invokeLater { requestFocusInWindow() } + document.addDocumentListener(DocumentChangeListener { explorerState.sourceCode = text }) + } +} + +private fun dexTextArea(explorerState: ExplorerState, focusTracker: FocusListener): DexTextArea { + return DexTextArea(explorerState).apply { + configureSyntaxTextArea(SyntaxConstants.SYNTAX_STYLE_NONE) + addFocusListener(focusTracker) + } +} + +private fun oatTextArea(focusTracker: FocusListener): RSyntaxTextArea { + return RSyntaxTextArea().apply { + configureSyntaxTextArea(SyntaxConstants.SYNTAX_STYLE_NONE) + addFocusListener(focusTracker) + } +} + @Composable private fun FrameWindowScope.MainMenu( explorerState: ExplorerState, @@ -459,6 +414,11 @@ private fun Settings( } } +private fun RSyntaxTextArea.setFont(explorerState: ExplorerState) { + val presentation = explorerState.presentationMode + font = font.deriveFont(if (presentation) FontSizePresentationMode else FontSizeEditingMode) +} + fun main() = application { val explorerState = remember { ExplorerState() } diff --git a/src/jvmMain/kotlin/dev/romainguy/kotlin/explorer/Splitter.kt b/src/jvmMain/kotlin/dev/romainguy/kotlin/explorer/Splitter.kt index 5d90a39d..677f7d44 100644 --- a/src/jvmMain/kotlin/dev/romainguy/kotlin/explorer/Splitter.kt +++ b/src/jvmMain/kotlin/dev/romainguy/kotlin/explorer/Splitter.kt @@ -14,17 +14,21 @@ * limitations under the License. */ +@file:Suppress("FunctionName", "OPT_IN_USAGE", "KotlinRedundantDiagnosticSuppress") @file:OptIn(ExperimentalSplitPaneApi::class) package dev.romainguy.kotlin.explorer import androidx.compose.foundation.layout.* +import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier import androidx.compose.ui.input.pointer.PointerIcon import androidx.compose.ui.input.pointer.pointerHoverIcon import androidx.compose.ui.unit.dp import org.jetbrains.compose.splitpane.ExperimentalSplitPaneApi +import org.jetbrains.compose.splitpane.HorizontalSplitPane import org.jetbrains.compose.splitpane.SplitterScope +import org.jetbrains.compose.splitpane.rememberSplitPaneState import java.awt.Cursor private fun Modifier.cursorForHorizontalResize(): Modifier = @@ -48,3 +52,29 @@ fun SplitterScope.HorizontalSplitter() { ) } } + +@Composable +fun ThreeWaySplitter( + modifier: Modifier = Modifier, + panel1: @Composable () -> Unit, + panel2: @Composable () -> Unit, + panel3: @Composable () -> Unit, +) { + HorizontalSplitPane( + modifier = modifier, + splitPaneState = rememberSplitPaneState(initialPositionPercentage = 1.0f / 3) + ) { + first { panel1() } + second { + HorizontalSplitPane( + modifier = modifier, + splitPaneState = rememberSplitPaneState(initialPositionPercentage = 1.0f / 2) + ) { + first { panel2() } + second { panel3() } + splitter { HorizontalSplitter() } + } + } + splitter { HorizontalSplitter() } + } +}