diff --git a/src/jvmMain/kotlin/dev/romainguy/kotlin/explorer/KotlinExplorer.kt b/src/jvmMain/kotlin/dev/romainguy/kotlin/explorer/KotlinExplorer.kt index 7963682a..7062c9cf 100644 --- a/src/jvmMain/kotlin/dev/romainguy/kotlin/explorer/KotlinExplorer.kt +++ b/src/jvmMain/kotlin/dev/romainguy/kotlin/explorer/KotlinExplorer.kt @@ -24,7 +24,6 @@ import androidx.compose.runtime.* import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.awt.SwingPanel -import androidx.compose.ui.graphics.Color import androidx.compose.ui.input.key.Key.Companion.D import androidx.compose.ui.input.key.Key.Companion.F import androidx.compose.ui.input.key.Key.Companion.G @@ -57,10 +56,7 @@ import org.jetbrains.jewel.intui.window.decoratedWindow import org.jetbrains.jewel.intui.window.styling.dark import org.jetbrains.jewel.intui.window.styling.light import org.jetbrains.jewel.ui.ComponentStyling -import org.jetbrains.jewel.ui.component.DefaultButton -import org.jetbrains.jewel.ui.component.Icon import org.jetbrains.jewel.ui.component.Text -import org.jetbrains.jewel.ui.component.TextField import org.jetbrains.jewel.window.DecoratedWindow import org.jetbrains.jewel.window.TitleBar import org.jetbrains.jewel.window.newFullscreenControls @@ -323,89 +319,6 @@ private fun applyTheme(textArea: RSyntaxTextArea) { } } -@Composable -private fun ErrorIcon() { - Icon( - "icons/error.svg", - iconClass = Settings::class.java, - contentDescription = "Error", - tint = Color(0xffee4056) - ) -} - -@Composable -private fun ValidIcon() { - Icon( - "icons/done.svg", - iconClass = Settings::class.java, - contentDescription = "Valid", - tint = Color(0xff3369d6) - ) -} - - -@Composable -private fun Settings( - explorerState: ExplorerState, - onSaveClick: () -> Unit -) { - var androidHome by remember { mutableStateOf(explorerState.toolPaths.androidHome.toString()) } - var kotlinHome by remember { mutableStateOf(explorerState.toolPaths.kotlinHome.toString()) } - - Box(modifier = Modifier.fillMaxSize()) { - Column(modifier = Modifier.align(Alignment.Center)) { - Row { - Text( - "Android home directory: ", - modifier = Modifier.align(Alignment.CenterVertically) - ) - TextField( - androidHome, - { text -> androidHome = text }, - modifier = Modifier.defaultMinSize(minWidth = 360.dp), - trailingIcon = { - if (!explorerState.toolPaths.isAndroidHomeValid) { - ErrorIcon() - } else { - ValidIcon() - } - } - ) - } - Spacer(Modifier.height(8.dp)) - Row { - Text( - "Kotlin home directory: ", - modifier = Modifier.align(Alignment.CenterVertically) - ) - TextField( - kotlinHome, - { text -> kotlinHome = text }, - modifier = Modifier.defaultMinSize(minWidth = 360.dp), - trailingIcon = { - if (!explorerState.toolPaths.isKotlinHomeValid) { - ErrorIcon() - } else { - ValidIcon() - } - } - ) - } - Spacer(Modifier.height(8.dp)) - DefaultButton( - { - explorerState.settings.entries["ANDROID_HOME"] = androidHome - explorerState.settings.entries["KOTLIN_HOME"] = kotlinHome - explorerState.reloadToolPathsFromSettings() - onSaveClick() - } - ) { - Text("Save") - } - } - } -} - private fun RSyntaxTextArea.updateStyle(explorerState: ExplorerState) { val presentation = explorerState.presentationMode font = font.deriveFont(if (presentation) FontSizePresentationMode else FontSizeEditingMode) @@ -420,7 +333,7 @@ private fun updateTextArea(textArea: RSyntaxTextArea, text: String) { fun main() = application { val explorerState = remember { ExplorerState() } - Runtime.getRuntime().addShutdownHook(Thread { writeState(explorerState) }) + Runtime.getRuntime().addShutdownHook(Thread { explorerState.writeState() }) val themeDefinition = if (KotlinExplorerTheme.System.isDark()) { JewelTheme.darkThemeDefinition() @@ -473,5 +386,4 @@ private fun ExplorerState.setWindowState(windowState: WindowState) { windowPosX = windowState.position.x.value.toInt() windowPosY = windowState.position.y.value.toInt() windowPlacement = windowState.placement - -} \ No newline at end of file +} diff --git a/src/jvmMain/kotlin/dev/romainguy/kotlin/explorer/Paths.kt b/src/jvmMain/kotlin/dev/romainguy/kotlin/explorer/Paths.kt index 948e8e88..51c8284a 100644 --- a/src/jvmMain/kotlin/dev/romainguy/kotlin/explorer/Paths.kt +++ b/src/jvmMain/kotlin/dev/romainguy/kotlin/explorer/Paths.kt @@ -18,32 +18,16 @@ package dev.romainguy.kotlin.explorer import java.nio.file.Files import java.nio.file.Path -import java.nio.file.Paths import java.util.stream.Stream import kotlin.io.path.exists import kotlin.io.path.extension import kotlin.jvm.optionals.getOrElse -fun createToolPaths(settings: Settings): ToolPaths { - val androidHome = Paths.get(settings.entries.getOrElse("ANDROID_HOME") { - System.getenv("ANDROID_HOME") ?: System.getProperty("user.home") - }) - val kotlinHome = Paths.get(settings.entries.getOrElse("KOTLIN_HOME") { - System.getenv("KOTLIN_HOME") ?: System.getProperty("user.home") - }) - - return ToolPaths(settings, androidHome, kotlinHome) -} - -class ToolPaths internal constructor( - settings: Settings, - val androidHome: Path, - val kotlinHome: Path -) { +class ToolPaths(settingsDirectory: Path, androidHome: Path, kotlinHome: Path) { val tempDirectory = Files.createTempDirectory("kotlin-explorer")!! val platform: Path val d8: Path - val adb: Path + val adb: Path = androidHome.resolve(if (isWindows) "platform-tools/adb.exe" else "platform-tools/adb") val dexdump: Path val kotlinc: Path val kotlinLibs: List @@ -57,7 +41,6 @@ class ToolPaths internal constructor( private set init { - adb = androidHome.resolve(if (isWindows) "platform-tools/adb.exe" else "platform-tools/adb") val buildToolsDirectory = listIfExists(androidHome.resolve("build-tools")) .sorted { p1, p2 -> p2.toString().compareTo(p1.toString()) @@ -92,7 +75,7 @@ class ToolPaths internal constructor( .getOrElse { lib.resolve("annotations.jar") } ) - sourceFile = settings.directory.resolve("source-code.kt") + sourceFile = settingsDirectory.resolve("source-code.kt") isAndroidHomeValid = adb.exists() && d8.exists() && dexdump.exists() isKotlinHomeValid = kotlinc.exists() @@ -100,8 +83,4 @@ class ToolPaths internal constructor( } } -private fun listIfExists(path: Path) = if (path.exists()) { - Files.list(path) -} else { - Stream.empty() -} +private fun listIfExists(path: Path) = if (path.exists()) Files.list(path) else Stream.empty() diff --git a/src/jvmMain/kotlin/dev/romainguy/kotlin/explorer/Settings.kt b/src/jvmMain/kotlin/dev/romainguy/kotlin/explorer/Settings.kt new file mode 100644 index 00000000..65f5bc02 --- /dev/null +++ b/src/jvmMain/kotlin/dev/romainguy/kotlin/explorer/Settings.kt @@ -0,0 +1,105 @@ +/* + * 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. + */ + +@file:Suppress("FunctionName") + +package dev.romainguy.kotlin.explorer + +import androidx.compose.foundation.layout.* +import androidx.compose.runtime.Composable +import androidx.compose.runtime.MutableState +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.unit.dp +import org.jetbrains.jewel.ui.component.DefaultButton +import org.jetbrains.jewel.ui.component.Icon +import org.jetbrains.jewel.ui.component.Text +import org.jetbrains.jewel.ui.component.TextField + +@Composable +fun Settings( + explorerState: ExplorerState, + onSaveClick: () -> Unit +) { + val androidHome = remember { mutableStateOf(explorerState.androidHome) } + val kotlinHome = remember { mutableStateOf(explorerState.kotlinHome) } + + Box(modifier = Modifier.fillMaxSize()) { + Column(modifier = Modifier.align(Alignment.Center)) { + StringSetting("Android home directory: ", androidHome) { explorerState.toolPaths.isAndroidHomeValid } + Spacer(Modifier.height(8.dp)) + StringSetting("Kotlin home directory: ", kotlinHome) { explorerState.toolPaths.isKotlinHomeValid } + + DefaultButton({ explorerState.saveState(androidHome, kotlinHome, onSaveClick) }) { + Text("Save") + } + } + } +} + +private fun ExplorerState.saveState( + androidHome: MutableState, + kotlinHome: MutableState, + onSaveClick: () -> Unit +) { + this.androidHome = androidHome.value + this.kotlinHome = kotlinHome.value + this.reloadToolPathsFromSettings() + onSaveClick() +} + +@Composable +private fun StringSetting(title: String, state: MutableState, isValid: () -> Boolean) { + Row { + Text( + title, + modifier = Modifier + .alignByBaseline() + .defaultMinSize(minWidth = 160.dp), + ) + TextField( + value = state.value, + onValueChange = { state.value = it }, + modifier = Modifier + .alignByBaseline() + .defaultMinSize(minWidth = 360.dp), + trailingIcon = { if (isValid()) ValidIcon() else ErrorIcon() } + ) + } +} + +@Composable +private fun ErrorIcon() { + Icon( + "icons/error.svg", + iconClass = ExplorerState::class.java, + contentDescription = "Error", + tint = Color(0xffee4056) + ) +} + +@Composable +private fun ValidIcon() { + Icon( + "icons/done.svg", + iconClass = ExplorerState::class.java, + contentDescription = "Valid", + tint = Color(0xff3369d6) + ) +} diff --git a/src/jvmMain/kotlin/dev/romainguy/kotlin/explorer/State.kt b/src/jvmMain/kotlin/dev/romainguy/kotlin/explorer/State.kt index 9fcc7232..e9124ddf 100644 --- a/src/jvmMain/kotlin/dev/romainguy/kotlin/explorer/State.kt +++ b/src/jvmMain/kotlin/dev/romainguy/kotlin/explorer/State.kt @@ -25,6 +25,8 @@ import java.nio.file.Paths import kotlin.io.path.exists import kotlin.io.path.readLines +private const val AndroidHome = "ANDROID_HOME" +private const val KotlinHome = "KOTLIN_HOME" private const val Optimize = "OPTIMIZE" private const val Presentation = "PRESENTATION" private const val ShowLineNumbers = "SHOW_LINE_NUMBERS" @@ -37,10 +39,14 @@ private const val WindowHeight = "WINDOW_HEIGHT" private const val Placement = "WINDOW_PLACEMENT" @Stable -class ExplorerState( - val settings: Settings = Settings() -) { - var toolPaths by mutableStateOf(createToolPaths(settings)) +class ExplorerState { + private val directory = settingsPath() + private val file: Path = directory.resolve("settings") + private val entries: MutableMap = readSettings(file) + + var androidHome by StringState(AndroidHome, System.getenv("ANDROID_HOME") ?: System.getProperty("user.home")) + var kotlinHome by StringState(KotlinHome, System.getenv("KOTLIN_HOME") ?: System.getProperty("user.home")) + var toolPaths by mutableStateOf(createToolPaths()) var optimize by BooleanState(Optimize, true) var presentationMode by BooleanState(Presentation, false) var showLineNumbers by BooleanState(ShowLineNumbers, true) @@ -54,22 +60,27 @@ class ExplorerState( var windowPlacement by SettingsState(Placement, Floating) { WindowPlacement.valueOf(this) } fun reloadToolPathsFromSettings() { - toolPaths = createToolPaths(settings) + toolPaths = createToolPaths() } + private fun createToolPaths() = ToolPaths(directory, Path.of(androidHome), Path.of(kotlinHome)) + private inner class BooleanState(key: String, initialValue: Boolean) : SettingsState(key, initialValue, { toBoolean() }) private inner class IntState(key: String, initialValue: Int) : SettingsState(key, initialValue, { toInt() }) + private inner class StringState(key: String, initialValue: String) : + SettingsState(key, initialValue, { this }) + private open inner class SettingsState(private val key: String, initialValue: T, parse: String.() -> T) : MutableState { - private val state = mutableStateOf(settings.entries[key]?.parse() ?: initialValue) + private val state = mutableStateOf(entries[key]?.parse() ?: initialValue) override var value: T get() = state.value set(value) { - settings.entries[key] = value.toString() + entries[key] = value.toString() state.value = value } @@ -77,14 +88,11 @@ class ExplorerState( override fun component2() = state.component2() } -} -data class Settings( - val directory: Path = settingsPath(), - val file: Path = directory.resolve("settings"), - val entries: MutableMap = readSettings(file) -) { - fun getValue(name: String, defaultValue: String) = entries[name] ?: defaultValue + fun writeState() { + Files.writeString(toolPaths.sourceFile, sourceCode) + Files.writeString(file, entries.map { (key, value) -> "$key=$value" }.joinToString("\n")) + } } private fun settingsPath() = Paths.get(System.getProperty("user.home"), ".kotlin-explorer").apply { @@ -110,17 +118,3 @@ private fun readSourceCode(toolPaths: ToolPaths) = if (toolPaths.sourceFile.exis } else { "fun square(a: Int): Int {\n return a * a\n}\n" } - -fun writeState(state: ExplorerState) { - Files.writeString( - state.toolPaths.sourceFile, - state.sourceCode - ) - Files.writeString( - state.settings.file, - state.settings.entries - .map { "${it.key}=${it.value}" } - .joinToString("\n") - ) -} -