From df89c4d40f01b572fdc04838f1317a465cd3b992 Mon Sep 17 00:00:00 2001 From: Daniel Bertoldi Date: Tue, 14 Jul 2026 11:51:06 -0300 Subject: [PATCH] [JEWEL-1335] Gradle Scripts Migration to Bazel (Part 2) closes https://github.com/JetBrains/intellij-community/pull/3576 (cherry picked from commit a6cc31d181e82f56d39d28a2ebc81d34ef727187) (cherry picked from commit ad2197f4c189c5b798cff3fcfad29cce35f7b13a) IJ-MR-220560 GitOrigin-RevId: b0a0ea6d63514c38c8752b7c3f4cb5e82d6eec0e --- .../jewel/build-scripts/bazel/BUILD.bazel | 58 +++ .../scripts/bazel/JewelBazelScriptsUtils.kt | 342 ++++++++++++++++++ .../scripts/bazel/JewelVersionUpdater.kt | 83 +++++ .../jewel/scripts/bazel/KtfmtRunner.kt | 77 ++++ .../test/resources/TitleBarStyling-forTest.kt | 64 ++-- 5 files changed, 592 insertions(+), 32 deletions(-) create mode 100644 platform/jewel/build-scripts/bazel/BUILD.bazel create mode 100644 platform/jewel/build-scripts/bazel/src/main/kotlin/org/jetbrains/jewel/scripts/bazel/JewelBazelScriptsUtils.kt create mode 100644 platform/jewel/build-scripts/bazel/src/main/kotlin/org/jetbrains/jewel/scripts/bazel/JewelVersionUpdater.kt create mode 100644 platform/jewel/build-scripts/bazel/src/main/kotlin/org/jetbrains/jewel/scripts/bazel/KtfmtRunner.kt diff --git a/platform/jewel/build-scripts/bazel/BUILD.bazel b/platform/jewel/build-scripts/bazel/BUILD.bazel new file mode 100644 index 000000000000..8c953d132311 --- /dev/null +++ b/platform/jewel/build-scripts/bazel/BUILD.bazel @@ -0,0 +1,58 @@ +load("@rules_java//java:defs.bzl", "java_binary") +load("@rules_kotlin//kotlin:jvm.bzl", "kt_jvm_import", "kt_jvm_library") + +kt_jvm_import( + name = "kotlinpoet", + jar = "@jewel_deps_kotlinpoet//file", + visibility = ["//visibility:private"], +) + +kt_jvm_import( + name = "ktfmt", + jar = "@jewel_deps_ktfmt//file", + visibility = ["//visibility:private"], +) + +kt_jvm_library( + name = "jewel-bazel-scripts-utils", + srcs = ["src/main/kotlin/org/jetbrains/jewel/scripts/bazel/JewelBazelScriptsUtils.kt"], + deps = ["@lib//:kotlinx-coroutines-core"], +) + +kt_jvm_library( + name = "ktfmt-runner-lib", + srcs = ["src/main/kotlin/org/jetbrains/jewel/scripts/bazel/KtfmtRunner.kt"], + deps = [ + ":jewel-bazel-scripts-utils", + ":ktfmt", + ], +) + +kt_jvm_library( + name = "jewel-version-updater", + srcs = ["src/main/kotlin/org/jetbrains/jewel/scripts/bazel/JewelVersionUpdater.kt"], + deps = [ + ":jewel-bazel-scripts-utils", + "@lib//:kotlinx-coroutines-core", + ], +) + +java_binary( + name = "ktfmtFormat", + args = ["format"], + main_class = "org.jetbrains.jewel.scripts.bazel.KtfmtRunnerKt", + runtime_deps = [":ktfmt-runner-lib"], +) + +java_binary( + name = "ktfmtCheck", + args = ["check"], + main_class = "org.jetbrains.jewel.scripts.bazel.KtfmtRunnerKt", + runtime_deps = [":ktfmt-runner-lib"], +) + +java_binary( + name = "jewelVersionUpdater", + main_class = "org.jetbrains.jewel.scripts.bazel.JewelVersionUpdaterKt", + runtime_deps = [":jewel-version-updater"], +) diff --git a/platform/jewel/build-scripts/bazel/src/main/kotlin/org/jetbrains/jewel/scripts/bazel/JewelBazelScriptsUtils.kt b/platform/jewel/build-scripts/bazel/src/main/kotlin/org/jetbrains/jewel/scripts/bazel/JewelBazelScriptsUtils.kt new file mode 100644 index 000000000000..a69dc8314788 --- /dev/null +++ b/platform/jewel/build-scripts/bazel/src/main/kotlin/org/jetbrains/jewel/scripts/bazel/JewelBazelScriptsUtils.kt @@ -0,0 +1,342 @@ +package org.jetbrains.jewel.scripts.bazel + +import java.io.File +import java.util.concurrent.CompletableFuture +import java.util.concurrent.TimeUnit +import kotlin.time.Duration +import kotlin.time.Duration.Companion.seconds +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.runBlocking +import kotlinx.coroutines.withContext +import org.intellij.lang.annotations.Language + +private const val CONTROL_CODE = '\u001b' +private const val CODE_SUCCESS = "$CONTROL_CODE[0;32m" +private const val CODE_ERR = "$CONTROL_CODE[0;31m" +private const val CODE_WARN = "$CONTROL_CODE[0;33m" +private const val CODE_BOLD = "$CONTROL_CODE[1m" +private const val CODE_CLEAR = "$CONTROL_CODE[0m" + +/** + * Gets the argument at [index], or throws if it's missing. + * + * @param index The index of the argument to retrieve. + * @return The argument at [index]. + */ +fun Array.getOrThrow(index: Int) = this.getOrNull(index) ?: error("Index $index does not have any arguments") + +/** + * Gets the Bazel workspace root directory. + * + * @return The workspace directory, read from the `BUILD_WORKSPACE_DIRECTORY` environment variable. Throws if that + * variable isn't set or doesn't point at a valid directory — this only works when run via `bazel run`. + */ +fun getBuildWorkspaceDirectory(): File = + File(System.getenv("BUILD_WORKSPACE_DIRECTORY")).takeIf { it.isDirectory } + ?: error("BUILD_WORKSPACE_DIRECTORY is not set or is not a valid directory. Run via `bazel run`.".asError()) + +/** + * Gets the Jewel subproject's root directory. + * + * @return The `platform/jewel` directory under the Bazel workspace root, or `null` if it doesn't exist. + */ +fun getJewelRoot(): File? = File(getBuildWorkspaceDirectory(), "platform/jewel").takeIf { it.isDirectory } + +/** + * Gets the runfiles directory for the current `bazel run` invocation. + * + * @return The runfiles directory path, read from the `RUNFILES_DIR` or `JAVA_RUNFILES` environment variable. Throws if + * neither is set — this only works when run via `bazel run`. + */ +fun getRunfilesDir() = + System.getenv("RUNFILES_DIR") + ?: System.getenv("JAVA_RUNFILES") + ?: error("RUNFILES_DIR is not set. Run via `bazel run`.") + +val isWindows = "windows" in System.getProperty("os.name").lowercase() + +private val stylingSupported = doesTerminalSupportStyling() + +/** + * Detects if the current terminal supports styling (colors, bold, etc.). + * + * @return `true` if a compatible terminal is detected, `false` otherwise. + */ +private fun doesTerminalSupportStyling(): Boolean = + System.getenv("TERM")?.contains("color") == true || System.getenv("COLORTERM") == "truecolor" + +fun red(text: String) = "\u001B[31m$text\u001B[0m" + +/** + * Formats a string as a success (green) text with ANSI escape codes if the terminal supports it. + * + * @return The formatted text, or the text itself if the terminal does not support styling. + */ +fun String.asSuccess(): String = if (stylingSupported) "$CODE_SUCCESS$this$CODE_CLEAR" else this + +/** + * Formats a string as a warning message with ANSI escape codes. + * + * @return The formatted text, or the text itself if the terminal does not support styling. + */ +fun String.asWarning() = if (stylingSupported) "$CODE_WARN$this$CODE_CLEAR" else this + +/** + * Formats a string as an error message with ANSI escape codes if the terminal supports it. + * + * @return The formatted text, or the text itself if the terminal does not support styling. + */ +fun String.asError() = if (stylingSupported) "$CODE_ERR$this$CODE_CLEAR" else this + +/** + * Formats a string as a bold text with ANSI escape codes if the terminal supports it. + * + * @return The formatted text, or the text itself if the terminal does not support styling. + */ +fun String.asBold(): String = if (stylingSupported) "$CODE_BOLD$this$CODE_CLEAR" else this + +/** + * Prints a success message to the standard output stream with success (green) formatting. + * + * @param message The success message to print. + */ +fun printlnSuccess(message: String) { + println(message.asSuccess()) +} + +/** + * Prints a warning message to the standard error stream with warning formatting. + * + * @param message The warning message to print. + */ +fun printlnWarn(message: String) { + System.err.println(message.asWarning()) +} + +/** + * Prints an error message to the standard error stream with error formatting. + * + * @param message The error message to print. + */ +fun printlnErr(message: String) { + System.err.println(message.asError()) +} + +/** + * Checks whether the given directory is (part of) a git repository. + * + * @return true if it is, false otherwise. + */ +suspend fun isDirectoryGitRepo(directory: File, runner: CommandRunner = DefaultCommandRunner): Boolean = + runner("git rev-parse --is-inside-work-tree", directory, exitOnError = false).isSuccess + +/** + * Checks whether the given branch exists in the current git repository. + * + * @param branch The name of the branch to check. + * @return true if the branch exists, false otherwise. + */ +suspend fun branchExists(branch: String, directory: File, runner: CommandRunner = DefaultCommandRunner): Boolean = + runner("git rev-parse --verify $branch", directory).isSuccess + +/** + * Gets the date of the latest release from the "RELEASE NOTES.md" file. + * + * @return The latest release date in "yyyy-mm-dd" format if found, otherwise `null`. + */ +fun getLatestReleaseDate(baseDir: File = File(".").canonicalFile): String? { + val releaseNotesFile = File(baseDir, "RELEASE NOTES.md") + if (!releaseNotesFile.exists()) { + printlnWarn( + "⚠️ Release notes file not found at '${releaseNotesFile.absolutePath}', can't determine start date." + ) + return null + } + + val releaseHeaderRegex = """## v\d+\.\d+ \((....-..-..)\)""".toRegex() + releaseNotesFile.useLines { lines -> + for (line in lines) { + val match = releaseHeaderRegex.find(line) + if (match != null) { + return match.groupValues[1] + } + } + } + printlnWarn("⚠️ Could not find any release date in RELEASE NOTES.md.") + return null +} + +interface CommandRunner { + suspend operator fun invoke( + @Language("shell") command: String, + workingDir: File?, + timeoutAmount: Duration = 60.seconds, + exitOnError: Boolean = true, + streamOutput: Boolean = false, + ): CmdResult +} + +object DefaultCommandRunner : CommandRunner { + override suspend fun invoke( + command: String, + workingDir: File?, + timeoutAmount: Duration, + exitOnError: Boolean, + streamOutput: Boolean, + ): CmdResult = runCommand(command, workingDir, timeoutAmount, exitOnError, streamOutput) +} + +/** + * Runs a shell command and captures its output and exit code. + * + * @param command The command to run. + * @param workingDir The working directory for the command. Can be `null` to use the current directory. + * @param timeoutAmount The maximum duration to wait for the command to complete. Defaults to 60 seconds. + * @param exitOnError If `true`, the process will exit if the command returns a non-zero exit code. Defaults to `true`. + * @return A [CmdResult] object containing the output and exit code of the command. + */ +private suspend fun runCommand( + @Language("shell") command: String, + workingDir: File?, + timeoutAmount: Duration = 60.seconds, + exitOnError: Boolean = true, + streamOutput: Boolean = false, +): CmdResult = + withContext(Dispatchers.IO) { + val process = + ProcessBuilder(command.split(" ")) + .directory(workingDir) + .redirectErrorStream(true) + .redirectOutput(if (streamOutput) ProcessBuilder.Redirect.INHERIT else ProcessBuilder.Redirect.PIPE) + .redirectError(if (streamOutput) ProcessBuilder.Redirect.INHERIT else ProcessBuilder.Redirect.PIPE) + .start() + + val outputFuture = + if (!streamOutput) { + CompletableFuture.supplyAsync { process.inputStream.bufferedReader().readText() } + } else null + val exitCode = process.waitFor() + val output = outputFuture?.get(timeoutAmount.inWholeSeconds, TimeUnit.SECONDS) ?: "" + + if (exitCode == 0) { + CmdResult.Success(output) + } else { + if (exitOnError) { + error( + buildString { + appendLine() + append("Command '$command' failed with exit code $exitCode".asError()) + if (output.isNotBlank()) { + appendLine(":".asError()) + appendLine(output.asError()) + } else { + appendLine() + } + } + ) + } + CmdResult.Failure(output) + } + } + +// Similar to Kotlin's Result, but always carries output +sealed interface CmdResult { + val output: String + + val isSuccess: Boolean + get() = this is Success + + val isFailure: Boolean + get() = this is Failure + + /** Gets the output of the command if it was successful, otherwise throws an exception. */ + fun getOrThrow(): String = + if (isSuccess) { + output.trim() + } else { + throw UnsupportedOperationException("Command failed with output $output") + } + + /** + * Represents a successful command execution. + * + * @param output The standard output of the command. + */ + data class Success(override val output: String) : CmdResult + + /** + * Represents a failed command execution. + * + * @param output The standard error of the command. + */ + data class Failure(override val output: String) : CmdResult +} + +suspend fun getCurrentBranchName(directory: File, runner: CommandRunner = DefaultCommandRunner): String = + runner("git rev-parse --abbrev-ref HEAD", directory, exitOnError = true).output.trim() + +/** + * Gets the current terminal width by executing `tput cols`, `stty size`, or checking COLUMNS, or using a fallback + * value. + * + * @return The terminal width in columns. + */ +fun getTerminalWidth(): Int { + try { + val tputCols = runBlocking { runCommand(command = "tput cols", workingDir = null, exitOnError = false) } + if (tputCols.isSuccess) return tputCols.output.trim().toInt() + + val sttySize = runBlocking { runCommand(command = "stty size", workingDir = null, exitOnError = false) } + if (sttySize.isSuccess) return sttySize.output.trim().split(" ").last().toInt() + + return System.getenv("COLUMNS")?.toIntOrNull() ?: 80 + } catch (_: Exception) { + return 80 + } +} + +/** + * Detects if the current terminal likely supports OSC 8 hyperlinks by checking for known environment variables. + * + * @return `true` if a compatible terminal is detected, `false` otherwise. + */ +private fun doesTerminalSupportHyperlinks(): Boolean { + // Check for iTerm, VSCode, Hyper, WezTerm, etc. + val termProgram = System.getenv("TERM_PROGRAM") + if (termProgram != null) { + return when (termProgram) { + "iTerm.app", + "vscode", + "WezTerm", + "Hyper" -> true + else -> false + } + } + + // Check for VTE-based terminals (GNOME Terminal, Tilix) + // Support was added in version 0.50 -> 5000 + val vteVersion = System.getenv("VTE_VERSION")?.toIntOrNull() + if (vteVersion != null && vteVersion >= 5000) { + return true + } + + // Check for IntelliJ's built-in terminal + if (System.getenv("TERMINAL_EMULATOR") == "JetBrains-JediTerm") { + return true + } + + // Fallback if no known terminal is detected + return false +} + +/** + * Formats a string as a hyperlink using OSC 8 escape codes if the terminal supports it. + * + * @param url The URL for the hyperlink. + * @return The formatted string with the hyperlink, or the original string if hyperlinks are not supported. + */ +fun String.asLink(url: String): String { + if (!doesTerminalSupportHyperlinks()) return this + val bell = '\u0007' // Bell character (acts as separator) + return "$CONTROL_CODE]8;;$url$bell$this$CONTROL_CODE]8;;$bell" +} diff --git a/platform/jewel/build-scripts/bazel/src/main/kotlin/org/jetbrains/jewel/scripts/bazel/JewelVersionUpdater.kt b/platform/jewel/build-scripts/bazel/src/main/kotlin/org/jetbrains/jewel/scripts/bazel/JewelVersionUpdater.kt new file mode 100644 index 000000000000..b58dbe172754 --- /dev/null +++ b/platform/jewel/build-scripts/bazel/src/main/kotlin/org/jetbrains/jewel/scripts/bazel/JewelVersionUpdater.kt @@ -0,0 +1,83 @@ +package org.jetbrains.jewel.scripts.bazel + +import java.io.File +import java.util.Properties +import kotlin.system.exitProcess + +// Run it like: bazel run //platform/jewel/build-scripts/bazel:jewelVersionUpdater +fun main(args: Array) { + print("⏳ Locating Jewel folder...") + + val jewelDir = getJewelRoot() + + if (jewelDir == null || !jewelDir.isDirectory) { + printlnErr( + "Could not find the Jewel folder. Please make sure you're running the script from somewhere inside it." + ) + exitProcess(1) + } + + println(" DONE: ${jewelDir.absolutePath}") + + print("🔍 Looking up Jewel API version...") + + val apiVersion = loadJewelVersionFromProperties(jewelDir) + + println(" DONE: $apiVersion") + + print("✍️ Writing updated Jewel API version...") + + val outFile = updateJewelApiVersion(jewelDir, apiVersion) + + print(" DONE\n Generated ") + + val relativePath = outFile.relativeTo(getBuildWorkspaceDirectory()).path + println(relativePath.asLink("file://${outFile.absolutePath}")) +} + +// =============================== HELPERS =============================== // + +private fun loadJewelVersionFromProperties(jewelDir: File): String { + val propertiesFile = File(jewelDir, "gradle.properties") + + if (!propertiesFile.isFile) { + printlnErr("Could not find the gradle.properties file.") + exitProcess(1) + } + + val properties = Properties().apply { propertiesFile.inputStream().use { load(it) } } + val version = properties.getProperty("jewel.release.version")?.trim() + + if (version.isNullOrBlank()) { + printlnErr("Could not find the Jewel API version in gradle.properties (jewel.release.version)") + exitProcess(1) + } + return version +} + +private fun updateJewelApiVersion(jewelDir: File, apiVersion: String): File { + val outFile = + File(jewelDir, "foundation/src/main/generated-kotlin/org/jetbrains/jewel/foundation/JewelApiVersion.kt") + + outFile.parentFile.mkdirs() + + outFile.writeText(jewelApiVersionTemplate.replace(JEWEL_API_VERSION_PLACEHOLDER, apiVersion)) + + return outFile +} + +private const val JEWEL_API_VERSION_PLACEHOLDER = "%%JEWEL_VERSION%%" + +private val jewelApiVersionTemplate = + """ + |// ATTENTION: this file is auto-generated. DO NOT EDIT MANUALLY! + |// Use the jewel-version-updater script instead. + | + |package org.jetbrains.jewel.foundation + | + |/** The Jewel API version for this build, expressed as a string. E.g.: "0.30.0" */ + |public val JewelBuild.apiVersionString: String + | get() = "%%JEWEL_VERSION%%" + | + """ + .trimMargin() diff --git a/platform/jewel/build-scripts/bazel/src/main/kotlin/org/jetbrains/jewel/scripts/bazel/KtfmtRunner.kt b/platform/jewel/build-scripts/bazel/src/main/kotlin/org/jetbrains/jewel/scripts/bazel/KtfmtRunner.kt new file mode 100644 index 000000000000..b26e54bd7625 --- /dev/null +++ b/platform/jewel/build-scripts/bazel/src/main/kotlin/org/jetbrains/jewel/scripts/bazel/KtfmtRunner.kt @@ -0,0 +1,77 @@ +package org.jetbrains.jewel.scripts.bazel + +import com.facebook.ktfmt.format.Formatter +import com.facebook.ktfmt.format.FormattingOptions +import java.io.File +import kotlin.system.exitProcess + +// Run it like: bazel run //platform/jewel/build-scripts/bazel:ktfmtCheck or ktfmtFormat +fun main(args: Array) { + val parameter: String = args.getOrThrow(0) + + val jewelRoot = getJewelRoot() + + val filesWithWrongFormat: MutableList = mutableListOf() + + val formattingOptions = + FormattingOptions( + maxWidth = 120, + blockIndent = 4, + continuationIndent = 4, + manageTrailingCommas = true, + removeUnusedImports = true, + ) + + when (parameter) { + "format" -> { + jewelRoot + ?.walkTopDownValidDirectories() + ?.filter { it.isKotlinFile() } + ?.forEach { file -> + val formattedFile = Formatter.format(formattingOptions, file.readText()) + + file.writeText(formattedFile) + } + + println("\n${"SUCCESS:".asSuccess()} Files were formatted.") + } + "check" -> { + jewelRoot + ?.walkTopDownValidDirectories() + ?.filter { it.isKotlinFile() } + ?.forEach { file -> + val fileText = file.readText() + val formattedFile = Formatter.format(formattingOptions, fileText) + + if (formattedFile != fileText) { + filesWithWrongFormat.add(file.path) + } + } + + if (filesWithWrongFormat.isNotEmpty()) { + println("\n${red("ERROR: ")} Some files are not properly formatted. Here's the list:") + filesWithWrongFormat.forEach { filePath -> println(" - $filePath") } + println( + "\nPlease run `bazel run //platform/jewel/build-scripts/bazel:ktfmtFormat` in the terminal or run " + + "the `ktfmtFormat` target in platform/jewel/build-scripts/BUILD.bazel file." + ) + + exitProcess(1) + } else { + println("\n${"SUCCESS:".asSuccess()} All files are correctly formatted.") + } + } + else -> { + error("Unknown parameter '$parameter'. Valid values: format, check") + } + } +} + +private fun File.walkTopDownValidDirectories() = + this.walkTopDown().onEnter { dir -> + dir.name != "build" && + dir.name != "generated" && + dir.name != "buildSrc" // TODO: remove this check once we get rid of Gradle. + } + +private fun File.isKotlinFile() = this.isFile && this.name.substringAfterLast(".") == "kt" diff --git a/platform/jewel/detekt-plugin/src/test/resources/TitleBarStyling-forTest.kt b/platform/jewel/detekt-plugin/src/test/resources/TitleBarStyling-forTest.kt index 0838e9fcca59..2e5ad3233a6b 100644 --- a/platform/jewel/detekt-plugin/src/test/resources/TitleBarStyling-forTest.kt +++ b/platform/jewel/detekt-plugin/src/test/resources/TitleBarStyling-forTest.kt @@ -58,14 +58,14 @@ public class TitleBarStyle( override fun toString(): String { return "TitleBarStyle(" + - "colors=$colors, " + - "metrics=$metrics, " + - "icons=$icons, " + - "dropdownStyle=$dropdownStyle, " + - "iconButtonStyle=$iconButtonStyle, " + - "paneButtonStyle=$paneButtonStyle, " + - "paneCloseButtonStyle=$paneCloseButtonStyle" + - ")" + "colors=$colors, " + + "metrics=$metrics, " + + "icons=$icons, " + + "dropdownStyle=$dropdownStyle, " + + "iconButtonStyle=$iconButtonStyle, " + + "paneButtonStyle=$paneButtonStyle, " + + "paneCloseButtonStyle=$paneCloseButtonStyle" + + ")" } public companion object @@ -145,20 +145,20 @@ public class TitleBarColors( override fun toString(): String { return "TitleBarColors(" + - "background=$background, " + - "inactiveBackground=$inactiveBackground, " + - "content=$content, " + - "border=$border, " + - "fullscreenControlButtonsBackground=$fullscreenControlButtonsBackground, " + - "titlePaneButtonHoveredBackground=$titlePaneButtonHoveredBackground, " + - "titlePaneButtonPressedBackground=$titlePaneButtonPressedBackground, " + - "titlePaneCloseButtonHoveredBackground=$titlePaneCloseButtonHoveredBackground, " + - "titlePaneCloseButtonPressedBackground=$titlePaneCloseButtonPressedBackground, " + - "iconButtonHoveredBackground=$iconButtonHoveredBackground, " + - "iconButtonPressedBackground=$iconButtonPressedBackground, " + - "dropdownPressedBackground=$dropdownPressedBackground, " + - "dropdownHoveredBackground=$dropdownHoveredBackground" + - ")" + "background=$background, " + + "inactiveBackground=$inactiveBackground, " + + "content=$content, " + + "border=$border, " + + "fullscreenControlButtonsBackground=$fullscreenControlButtonsBackground, " + + "titlePaneButtonHoveredBackground=$titlePaneButtonHoveredBackground, " + + "titlePaneButtonPressedBackground=$titlePaneButtonPressedBackground, " + + "titlePaneCloseButtonHoveredBackground=$titlePaneCloseButtonHoveredBackground, " + + "titlePaneCloseButtonPressedBackground=$titlePaneCloseButtonPressedBackground, " + + "iconButtonHoveredBackground=$iconButtonHoveredBackground, " + + "iconButtonPressedBackground=$iconButtonPressedBackground, " + + "dropdownPressedBackground=$dropdownPressedBackground, " + + "dropdownHoveredBackground=$dropdownHoveredBackground" + + ")" } public companion object @@ -196,11 +196,11 @@ public class TitleBarMetrics( override fun toString(): String { return "TitleBarMetrics(" + - "height=$height, " + - "gradientStartX=$gradientStartX, " + - "gradientEndX=$gradientEndX, " + - "titlePaneButtonSize=$titlePaneButtonSize" + - ")" + "height=$height, " + + "gradientStartX=$gradientStartX, " + + "gradientEndX=$gradientEndX, " + + "titlePaneButtonSize=$titlePaneButtonSize" + + ")" } public companion object @@ -238,11 +238,11 @@ public class TitleBarIcons( override fun toString(): String { return "TitleBarIcons(" + - "minimizeButton=$minimizeButton, " + - "maximizeButton=$maximizeButton, " + - "restoreButton=$restoreButton, " + - "closeButton=$closeButton" + - ")" + "minimizeButton=$minimizeButton, " + + "maximizeButton=$maximizeButton, " + + "restoreButton=$restoreButton, " + + "closeButton=$closeButton" + + ")" } public companion object