diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml
index 375ac9c7b..485135277 100644
--- a/.github/workflows/build.yml
+++ b/.github/workflows/build.yml
@@ -27,8 +27,8 @@ jobs:
- name: Run :check task
run: ./gradlew check --continue --no-daemon
- - uses: github/codeql-action/upload-sarif@v3
- if: ${{ always() }}
- with:
- sarif_file: ${{ github.workspace }}/build/reports/static-analysis.sarif
- checkout_path: ${{ github.workspace }}
+# - uses: github/codeql-action/upload-sarif@v3
+# if: ${{ always() }}
+# with:
+# sarif_file: ${{ github.workspace }}/build/reports/static-analysis.sarif
+# checkout_path: ${{ github.workspace }}
diff --git a/.idea/inspectionProfiles/Project_Default.xml b/.idea/inspectionProfiles/Project_Default.xml
index 410449b81..65ea4fd04 100644
--- a/.idea/inspectionProfiles/Project_Default.xml
+++ b/.idea/inspectionProfiles/Project_Default.xml
@@ -3,6 +3,7 @@
+
@@ -11,9 +12,9 @@
-
+
-
+
\ No newline at end of file
diff --git a/.idea/ktlint.xml b/.idea/ktlint.xml
deleted file mode 100644
index 43c0ba1fc..000000000
--- a/.idea/ktlint.xml
+++ /dev/null
@@ -1,10 +0,0 @@
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/build.gradle.kts b/build.gradle.kts
index afebca7ef..cf2494d60 100644
--- a/build.gradle.kts
+++ b/build.gradle.kts
@@ -20,11 +20,11 @@ dependencies {
}
tasks {
- val mergeSarifReports by
- registering(MergeSarifTask::class) {
- source(configurations.outgoingSarif)
- include { it.file.extension == "sarif" }
- }
-
- register("check") { dependsOn(mergeSarifReports) }
-}
\ No newline at end of file
+// val mergeSarifReports by
+ // registering(MergeSarifTask::class) {
+ // source(configurations.outgoingSarif)
+ // include { it.file.extension == "sarif" }
+ // }
+//
+// register("check") { dependsOn(mergeSarifReports) }
+}
diff --git a/buildSrc/settings.gradle.kts b/buildSrc/settings.gradle.kts
index 763fd1d5a..28a4003cf 100644
--- a/buildSrc/settings.gradle.kts
+++ b/buildSrc/settings.gradle.kts
@@ -16,4 +16,4 @@ dependencyResolutionManagement {
}
versionCatalogs { create("libs") { from(files("../gradle/libs.versions.toml")) } }
-}
\ No newline at end of file
+}
diff --git a/buildSrc/src/main/kotlin/MergeSarifTask.kt b/buildSrc/src/main/kotlin/MergeSarifTask.kt
index 996796fa5..618c7ef1b 100644
--- a/buildSrc/src/main/kotlin/MergeSarifTask.kt
+++ b/buildSrc/src/main/kotlin/MergeSarifTask.kt
@@ -1,4 +1,7 @@
+import io.github.detekt.sarif4k.Run
import io.github.detekt.sarif4k.SarifSchema210
+import io.github.detekt.sarif4k.Tool
+import io.github.detekt.sarif4k.ToolComponent
import io.github.detekt.sarif4k.Version
import kotlinx.serialization.json.Json
import kotlinx.serialization.json.decodeFromStream
@@ -21,38 +24,36 @@ open class MergeSarifTask : SourceTask() {
@get:OutputFile
val mergedSarifPath: RegularFileProperty =
- project.objects
- .fileProperty()
- .convention(project.layout.buildDirectory.file("reports/static-analysis.sarif"))
+ project.objects.fileProperty().convention(project.layout.buildDirectory.file("reports/static-analysis.sarif"))
@TaskAction
fun merge() {
val json = Json { prettyPrint = true }
logger.lifecycle("Merging ${source.files.size} SARIF file(s)...")
- logger.lifecycle(
- source.files.joinToString("\n") { " * ~${it.path.removePrefix(project.rootDir.path)}" }
- )
+ logger.lifecycle(source.files.joinToString("\n") { " * ~${it.path.removePrefix(project.rootDir.path)}" })
val merged =
SarifSchema210(
schema = SARIF_SCHEMA,
version = Version.The210,
- runs = source.files
- .asSequence()
- .filter { it.extension == "sarif" }
- .map { file ->
- file.inputStream().use { json.decodeFromStream(it) }
- }
- .flatMap { report -> report.runs }
- .groupBy { run -> run.tool.driver.guid ?: run.tool.driver.name }
- .values
- .asSequence()
- .filter { it.isNotEmpty() }
- .map { run ->
- run.first().copy(results = run.flatMap { it.results ?: emptyList() })
- }
- .toList()
+ runs =
+ source.files
+ .asSequence()
+ .filter { it.extension == "sarif" }
+ .map { file -> file.inputStream().use { json.decodeFromStream(it) } }
+ .flatMap { report -> report.runs }
+ .groupBy { run -> run.tool.driver.guid ?: run.tool.driver.name }
+ .values
+ .asSequence()
+ .filter { it.isNotEmpty() }
+ .map { runs ->
+ Run(
+ results = runs.flatMap { it.results.orEmpty() },
+ tool = Tool(driver = ToolComponent(name = "Jewel static analysis")),
+ )
+ }
+ .toList(),
)
logger.lifecycle("Merged SARIF file contains ${merged.runs.size} run(s)")
diff --git a/buildSrc/src/main/kotlin/PublishConfiguration.kt b/buildSrc/src/main/kotlin/PublishConfiguration.kt
index 040a4df28..0d0e4b778 100644
--- a/buildSrc/src/main/kotlin/PublishConfiguration.kt
+++ b/buildSrc/src/main/kotlin/PublishConfiguration.kt
@@ -16,9 +16,7 @@ internal fun PublishingExtension.configureJewelRepositories(project: Project) {
}
}
- maven(project.rootProject.layout.buildDirectory.dir("maven-test")) {
- name = "LocalTest"
- }
+ maven(project.rootProject.layout.buildDirectory.dir("maven-test")) { name = "LocalTest" }
}
}
diff --git a/buildSrc/src/main/kotlin/ValidatePublicApiTask.kt b/buildSrc/src/main/kotlin/ValidatePublicApiTask.kt
index 1a6ecdb03..2e77a57cd 100644
--- a/buildSrc/src/main/kotlin/ValidatePublicApiTask.kt
+++ b/buildSrc/src/main/kotlin/ValidatePublicApiTask.kt
@@ -1,17 +1,16 @@
+import java.io.File
+import java.util.Stack
+import java.util.regex.PatternSyntaxException
import org.gradle.api.GradleException
import org.gradle.api.tasks.CacheableTask
import org.gradle.api.tasks.Input
import org.gradle.api.tasks.SourceTask
import org.gradle.api.tasks.TaskAction
-import java.io.File
-import java.util.Stack
-import java.util.regex.PatternSyntaxException
@CacheableTask
open class ValidatePublicApiTask : SourceTask() {
- @Input
- var excludedClassRegexes: Set = emptySet()
+ @Input var excludedClassRegexes: Set = emptySet()
init {
group = "verification"
@@ -23,28 +22,29 @@ open class ValidatePublicApiTask : SourceTask() {
private val classFqnRegex = "public (?:\\w+ )*class (\\S+)\\b".toRegex()
@Suppress("ConvertToStringTemplate") // The odd concatenation is needed because of $; escapes get confused
- private val copyMethodRegex =
- ("public static synthetic fun copy(-\\w+)?" + "\\$" + "default\\b").toRegex()
+ private val copyMethodRegex = ("public static synthetic fun copy(-\\w+)?" + "\\$" + "default\\b").toRegex()
@TaskAction
fun validatePublicApi() {
logger.info("Validating ${source.files.size} API file(s)...")
val violations = mutableMapOf>()
- val excludedRegexes = excludedClassRegexes.map {
- try {
- it.toRegex()
- } catch (ignored: PatternSyntaxException) {
- throw GradleException("Invalid data exclusion regex: '$it'")
- }
- }.toSet()
+ val excludedRegexes =
+ excludedClassRegexes
+ .map {
+ try {
+ it.toRegex()
+ } catch (ignored: PatternSyntaxException) {
+ throw GradleException("Invalid data exclusion regex: '$it'")
+ }
+ }
+ .toSet()
inputs.files.forEach { apiFile ->
logger.lifecycle("Validating public API from file ${apiFile.path}")
apiFile.useLines { lines ->
- val actualDataClasses = findDataClasses(lines)
- .filterExclusions(excludedRegexes)
+ val actualDataClasses = findDataClasses(lines).filterExclusions(excludedRegexes)
if (actualDataClasses.isNotEmpty()) {
violations[apiFile] = actualDataClasses
@@ -101,9 +101,7 @@ open class ValidatePublicApiTask : SourceTask() {
}
}
- val actualDataClasses =
- dataClasses.filterValues { it.hasCopyMethod && !it.isLikelyValueClass }
- .keys
+ val actualDataClasses = dataClasses.filterValues { it.hasCopyMethod && !it.isLikelyValueClass }.keys
return actualDataClasses
}
@@ -111,13 +109,14 @@ open class ValidatePublicApiTask : SourceTask() {
if (excludedRegexes.isEmpty()) return this
return filterNot { dataClassFqn ->
- val isExcluded = excludedRegexes.any { it.matchEntire(dataClassFqn) != null }
+ val isExcluded = excludedRegexes.any { it.matchEntire(dataClassFqn) != null }
- if (isExcluded) {
- logger.info(" Ignoring excluded data class $dataClassFqn")
+ if (isExcluded) {
+ logger.info(" Ignoring excluded data class $dataClassFqn")
+ }
+ isExcluded
}
- isExcluded
- }.toSet()
+ .toSet()
}
}
diff --git a/buildSrc/src/main/kotlin/icon-keys-generator.gradle.kts b/buildSrc/src/main/kotlin/icon-keys-generator.gradle.kts
index 1a5489984..3a0300588 100644
--- a/buildSrc/src/main/kotlin/icon-keys-generator.gradle.kts
+++ b/buildSrc/src/main/kotlin/icon-keys-generator.gradle.kts
@@ -284,4 +284,4 @@ private data class IconKeyHolder(
val keys: MutableList = mutableListOf(),
)
-private data class IconKey(val name: String, val oldPath: String, val newPath: String?)
\ No newline at end of file
+private data class IconKey(val name: String, val oldPath: String, val newPath: String?)
diff --git a/buildSrc/src/main/kotlin/jewel.gradle.kts b/buildSrc/src/main/kotlin/jewel.gradle.kts
index 8a0baa98a..38f4a7f32 100644
--- a/buildSrc/src/main/kotlin/jewel.gradle.kts
+++ b/buildSrc/src/main/kotlin/jewel.gradle.kts
@@ -1,3 +1,6 @@
+import com.ncorti.ktfmt.gradle.tasks.KtfmtBaseTask
+import org.jetbrains.kotlin.gradle.dsl.JvmTarget
+
plugins {
id("jewel-linting")
kotlin("jvm")
@@ -37,6 +40,8 @@ val jdkLevel = project.property("jdk.level") as String
kotlin {
jvmToolchain { languageVersion = JavaLanguageVersion.of(jdkLevel) }
+ compilerOptions.jvmTarget.set(JvmTarget.fromTarget(jdkLevel))
+
target {
compilations.all { kotlinOptions { freeCompilerArgs += "-Xcontext-receivers" } }
sourceSets.all {
@@ -70,6 +75,7 @@ tasks {
}
formatKotlinMain { exclude { it.file.absolutePath.replace('\\', '/').contains("build/generated") } }
+ withType { exclude { it.file.absolutePath.contains("build/generated") } }
lintKotlinMain {
exclude { it.file.absolutePath.replace('\\', '/').contains("build/generated") }
@@ -89,4 +95,4 @@ configurations.named("sarif") {
artifact(tasks.detektMain.flatMap { it.sarifReportFile }) { builtBy(tasks.detektMain) }
artifact(sarifReport) { builtBy(tasks.lintKotlinMain) }
}
-}
\ No newline at end of file
+}
diff --git a/buildSrc/src/main/kotlin/org/jetbrains/jewel/buildlogic/demodata/AndroidStudioReleases.kt b/buildSrc/src/main/kotlin/org/jetbrains/jewel/buildlogic/demodata/AndroidStudioReleases.kt
index e1d7524d7..4529111f0 100644
--- a/buildSrc/src/main/kotlin/org/jetbrains/jewel/buildlogic/demodata/AndroidStudioReleases.kt
+++ b/buildSrc/src/main/kotlin/org/jetbrains/jewel/buildlogic/demodata/AndroidStudioReleases.kt
@@ -3,6 +3,8 @@ package org.jetbrains.jewel.buildlogic.demodata
import com.squareup.kotlinpoet.ClassName
import gradle.kotlin.dsl.accessors._34fcf23848cfa0f534eebf6913e08a53.kotlin
import gradle.kotlin.dsl.accessors._34fcf23848cfa0f534eebf6913e08a53.sourceSets
+import java.io.File
+import java.net.URI
import kotlinx.serialization.json.Json
import kotlinx.serialization.json.decodeFromStream
import org.gradle.api.DefaultTask
@@ -19,8 +21,6 @@ import org.gradle.kotlin.dsl.get
import org.gradle.kotlin.dsl.getByType
import org.gradle.kotlin.dsl.property
import org.gradle.kotlin.dsl.setProperty
-import java.io.File
-import java.net.URI
open class StudioVersionsGenerationExtension(project: Project) {
@@ -28,28 +28,25 @@ open class StudioVersionsGenerationExtension(project: Project) {
project.objects
.directoryProperty()
.convention(
- project.layout.dir(
- project.provider { project.sourceSets.named("main").get().kotlin.srcDirs.first() }
- )
+ project.layout.dir(project.provider { project.sourceSets.named("main").get().kotlin.srcDirs.first() })
)
val resourcesDirs: SetProperty =
project.objects
.setProperty()
- .convention(project.provider {
- when {
- project.plugins.hasPlugin("org.gradle.jvm-ecosystem") ->
- project.extensions.getByType()["main"]
- .resources.srcDirs
+ .convention(
+ project.provider {
+ when {
+ project.plugins.hasPlugin("org.gradle.jvm-ecosystem") ->
+ project.extensions.getByType()["main"].resources.srcDirs
- else -> emptySet()
+ else -> emptySet()
+ }
}
- })
+ )
val dataUrl: Property =
- project.objects
- .property()
- .convention("https://jb.gg/android-studio-releases-list.json")
+ project.objects.property().convention("https://jb.gg/android-studio-releases-list.json")
}
internal const val STUDIO_RELEASES_OUTPUT_CLASS_NAME =
@@ -57,14 +54,11 @@ internal const val STUDIO_RELEASES_OUTPUT_CLASS_NAME =
open class AndroidStudioReleasesGeneratorTask : DefaultTask() {
- @get:OutputFile
- val outputFile: RegularFileProperty = project.objects.fileProperty()
+ @get:OutputFile val outputFile: RegularFileProperty = project.objects.fileProperty()
- @get:Input
- val dataUrl = project.objects.property()
+ @get:Input val dataUrl = project.objects.property()
- @get:Input
- val resourcesDirs = project.objects.setProperty()
+ @get:Input val resourcesDirs = project.objects.setProperty()
init {
group = "jewel"
@@ -80,12 +74,8 @@ open class AndroidStudioReleasesGeneratorTask : DefaultTask() {
val lookupDirs = resourcesDirs.get()
logger.lifecycle("Fetching Android Studio releases list from $url...")
- logger.debug(
- "Registered resources directories:\n" +
- lookupDirs.joinToString("\n") { " * ${it.absolutePath}" }
- )
- val releases = URI.create(url).toURL().openStream()
- .use { json.decodeFromStream(it) }
+ logger.debug("Registered resources directories:\n" + lookupDirs.joinToString("\n") { " * ${it.absolutePath}" })
+ val releases = URI.create(url).toURL().openStream().use { json.decodeFromStream(it) }
val className = ClassName.bestGuess(STUDIO_RELEASES_OUTPUT_CLASS_NAME)
val file = AndroidStudioReleasesReader.readFrom(releases, className, url, lookupDirs)
diff --git a/buildSrc/src/main/kotlin/org/jetbrains/jewel/buildlogic/demodata/AndroidStudioReleasesReader.kt b/buildSrc/src/main/kotlin/org/jetbrains/jewel/buildlogic/demodata/AndroidStudioReleasesReader.kt
index 71ccef332..187558698 100644
--- a/buildSrc/src/main/kotlin/org/jetbrains/jewel/buildlogic/demodata/AndroidStudioReleasesReader.kt
+++ b/buildSrc/src/main/kotlin/org/jetbrains/jewel/buildlogic/demodata/AndroidStudioReleasesReader.kt
@@ -17,12 +17,7 @@ private val ContentItemClassName =
internal object AndroidStudioReleasesReader {
- fun readFrom(
- releases: ApiAndroidStudioReleases,
- className: ClassName,
- url: String,
- resourceDirs: Set,
- ) =
+ fun readFrom(releases: ApiAndroidStudioReleases, className: ClassName, url: String, resourceDirs: Set) =
FileSpec.builder(className)
.apply {
indent(" ")
@@ -36,48 +31,37 @@ internal object AndroidStudioReleasesReader {
}
.build()
- private fun createBaseTypeSpec(
- className: ClassName,
- releases: ApiAndroidStudioReleases,
- resourceDirs: Set,
- ) = TypeSpec.objectBuilder(className)
- .superclass(
- ClassName.bestGuess("org.jetbrains.jewel.samples.ideplugin.releasessample.ContentSource")
- .parameterizedBy(ContentItemClassName)
- )
- .apply {
- addProperty(
- PropertySpec.builder(
- name = "items",
- type =
- List::class.asClassName().parameterizedBy(ContentItemClassName),
- KModifier.OVERRIDE
- )
- .initializer(readReleases(releases, resourceDirs))
- .build()
+ private fun createBaseTypeSpec(className: ClassName, releases: ApiAndroidStudioReleases, resourceDirs: Set) =
+ TypeSpec.objectBuilder(className)
+ .superclass(
+ ClassName.bestGuess("org.jetbrains.jewel.samples.ideplugin.releasessample.ContentSource")
+ .parameterizedBy(ContentItemClassName)
)
+ .apply {
+ addProperty(
+ PropertySpec.builder(
+ name = "items",
+ type = List::class.asClassName().parameterizedBy(ContentItemClassName),
+ KModifier.OVERRIDE,
+ )
+ .initializer(readReleases(releases, resourceDirs))
+ .build()
+ )
- addProperty(
- PropertySpec.builder(
- "displayName",
- type = String::class.asClassName(),
- KModifier.OVERRIDE
+ addProperty(
+ PropertySpec.builder("displayName", type = String::class.asClassName(), KModifier.OVERRIDE)
+ .initializer("\"%L\"", "Android Studio releases")
+ .build()
)
- .initializer("\"%L\"", "Android Studio releases")
- .build()
- )
- }
- .build()
+ }
+ .build()
private fun readReleases(releases: ApiAndroidStudioReleases, resourceDirs: Set) =
releases.content.item
.map { readRelease(it, resourceDirs) }
.joinToCode(prefix = "listOf(\n", separator = ",\n", suffix = ")")
- private fun readRelease(
- release: ApiAndroidStudioReleases.Content.Item,
- resourceDirs: Set,
- ) =
+ private fun readRelease(release: ApiAndroidStudioReleases.Content.Item, resourceDirs: Set) =
CodeBlock.builder()
.apply {
add("AndroidStudio(\n")
@@ -94,25 +78,24 @@ internal object AndroidStudioReleasesReader {
}
.build()
- private fun imagePathForOrNull(
- release: ApiAndroidStudioReleases.Content.Item,
- resourceDirs: Set,
- ): String? {
+ private fun imagePathForOrNull(release: ApiAndroidStudioReleases.Content.Item, resourceDirs: Set): String? {
// Take the release animal from the name, remove spaces and voila'
- val releaseAnimal = release.name
- .substringBefore(" | ")
- .substringAfter("Android Studio")
- .trim()
- .replace(" ", "")
+ val releaseAnimal = release.name.substringBefore(" | ").substringAfter("Android Studio").trim().replace(" ", "")
if (releaseAnimal.isEmpty() || releaseAnimal.any { it.isDigit() }) return null
// We only have stable and canary splash screens. Betas use the stable ones.
- val channel = release.channel.lowercase()
- .let {
+ val channel =
+ release.channel.lowercase().let {
when (it) {
- "release", "rc", "stable", "beta", "patch" -> "stable"
- "canary", "preview", "alpha" -> "canary"
+ "release",
+ "rc",
+ "stable",
+ "beta",
+ "patch" -> "stable"
+ "canary",
+ "preview",
+ "alpha" -> "canary"
else -> {
println(" Note: channel '${it}' isn't supported for splash screens")
null
@@ -134,8 +117,7 @@ internal object AndroidStudioReleasesReader {
private fun translateDate(rawDate: String): String {
val month = rawDate.substringBefore(" ").trimStart('0')
val year = rawDate.substringAfterLast(" ".trimStart('0'))
- val day = rawDate.substring(month.length + 1, rawDate.length - year.length - 1)
- .trimStart('0')
+ val day = rawDate.substring(month.length + 1, rawDate.length - year.length - 1).trimStart('0')
if (day.isEmpty()) {
println("$rawDate\nMonth: '$month'\nYear: '$year'")
@@ -163,7 +145,9 @@ internal object AndroidStudioReleasesReader {
private fun readChannel(rawChannel: String) =
when (rawChannel.lowercase().trim()) {
- "stable", "patch", "release" -> "ReleaseChannel.Stable"
+ "stable",
+ "patch",
+ "release" -> "ReleaseChannel.Stable"
"beta" -> "ReleaseChannel.Beta"
"canary" -> "ReleaseChannel.Canary"
else -> "ReleaseChannel.Other"
diff --git a/buildSrc/src/main/kotlin/org/jetbrains/jewel/buildlogic/demodata/ApiAndroidStudioReleases.kt b/buildSrc/src/main/kotlin/org/jetbrains/jewel/buildlogic/demodata/ApiAndroidStudioReleases.kt
index b028de096..23e15f5cf 100644
--- a/buildSrc/src/main/kotlin/org/jetbrains/jewel/buildlogic/demodata/ApiAndroidStudioReleases.kt
+++ b/buildSrc/src/main/kotlin/org/jetbrains/jewel/buildlogic/demodata/ApiAndroidStudioReleases.kt
@@ -4,9 +4,7 @@ import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable
@Serializable
-internal data class ApiAndroidStudioReleases(
- @SerialName("content") val content: Content = Content(),
-) {
+internal data class ApiAndroidStudioReleases(@SerialName("content") val content: Content = Content()) {
@Serializable
internal data class Content(
diff --git a/buildSrc/src/main/kotlin/org/jetbrains/jewel/buildlogic/ideversion/CheckIdeaVersionTask.kt b/buildSrc/src/main/kotlin/org/jetbrains/jewel/buildlogic/ideversion/CheckIdeaVersionTask.kt
index 88fc1142b..37f8fbdf0 100644
--- a/buildSrc/src/main/kotlin/org/jetbrains/jewel/buildlogic/ideversion/CheckIdeaVersionTask.kt
+++ b/buildSrc/src/main/kotlin/org/jetbrains/jewel/buildlogic/ideversion/CheckIdeaVersionTask.kt
@@ -1,9 +1,9 @@
package org.jetbrains.jewel.buildlogic.ideversion
+import java.io.File
import org.gradle.api.DefaultTask
import org.gradle.api.GradleException
import org.gradle.api.tasks.TaskAction
-import java.io.File
open class CheckIdeaVersionTask : DefaultTask() {
@@ -12,11 +12,9 @@ open class CheckIdeaVersionTask : DefaultTask() {
"fields=code,releases,releases.version,releases.build,releases.type,releases.majorVersion&" +
"code=IC"
- private val ideaVersionRegex =
- "202\\d\\.(\\d\\.)?\\d".toRegex(RegexOption.IGNORE_CASE)
+ private val ideaVersionRegex = "202\\d\\.(\\d\\.)?\\d".toRegex(RegexOption.IGNORE_CASE)
- private val intelliJPlatformBuildRegex =
- "2\\d{2}\\.\\d+\\.\\d+(?:-EAP-SNAPSHOT)?".toRegex(RegexOption.IGNORE_CASE)
+ private val intelliJPlatformBuildRegex = "2\\d{2}\\.\\d+\\.\\d+(?:-EAP-SNAPSHOT)?".toRegex(RegexOption.IGNORE_CASE)
private val currentIjpVersion = project.currentIjpVersion
@@ -30,11 +28,8 @@ open class CheckIdeaVersionTask : DefaultTask() {
val rawIdeaVersion = readCurrentVersionInfo()
val ideaVersion = validateIdeaVersion(rawIdeaVersion)
- val platformBuildsForThisMajorVersion = IJPVersionsFetcher.fetchBuildsForCurrentMajorVersion(
- releasesUrl,
- ideaVersion.majorVersion,
- logger
- )
+ val platformBuildsForThisMajorVersion =
+ IJPVersionsFetcher.fetchBuildsForCurrentMajorVersion(releasesUrl, ideaVersion.majorVersion, logger)
if (platformBuildsForThisMajorVersion == null) {
logger.error("Cannot check platform version, no builds found for current version $ideaVersion")
@@ -71,10 +66,10 @@ open class CheckIdeaVersionTask : DefaultTask() {
}
appendLine(
- "Please update the 'idea' and 'intelliJPlatformBuild' " +
- "versions in the catalog accordingly."
+ "Please update the 'idea' and 'intelliJPlatformBuild' " + "versions in the catalog accordingly."
)
- })
+ }
+ )
}
logger.lifecycle(
@@ -111,11 +106,7 @@ open class CheckIdeaVersionTask : DefaultTask() {
)
val declaredPlatformBuild =
- catalogDependencyLine
- .substringAfter(versionName)
- .trimStart(' ', '=')
- .trimEnd()
- .trim('"')
+ catalogDependencyLine.substringAfter(versionName).trimStart(' ', '=').trimEnd().trim('"')
if (!declaredPlatformBuild.matches(intelliJPlatformBuildRegex)) {
throw GradleException("Invalid IJP build found in version catalog: '$declaredPlatformBuild'")
diff --git a/buildSrc/src/main/kotlin/org/jetbrains/jewel/buildlogic/ideversion/CurrentIjpTarget.kt b/buildSrc/src/main/kotlin/org/jetbrains/jewel/buildlogic/ideversion/CurrentIjpTarget.kt
index 27e93b816..04adba0fa 100644
--- a/buildSrc/src/main/kotlin/org/jetbrains/jewel/buildlogic/ideversion/CurrentIjpTarget.kt
+++ b/buildSrc/src/main/kotlin/org/jetbrains/jewel/buildlogic/ideversion/CurrentIjpTarget.kt
@@ -4,8 +4,8 @@ import org.gradle.api.Project
val Project.currentIjpVersion: String
get() {
- val rawValue = property("ijp.target") as? String
- ?: error("Property ijp.target not defined. Check your gradle.properties!")
+ val rawValue =
+ property("ijp.target") as? String ?: error("Property ijp.target not defined. Check your gradle.properties!")
if (rawValue.length != 3 || rawValue.toIntOrNull()?.let { it < 0 } == true) {
error("Invalid ijp.target property value: '$rawValue'")
diff --git a/buildSrc/src/main/kotlin/org/jetbrains/jewel/buildlogic/ideversion/IJPVersionsFetcher.kt b/buildSrc/src/main/kotlin/org/jetbrains/jewel/buildlogic/ideversion/IJPVersionsFetcher.kt
index 1413336dd..749d70caa 100644
--- a/buildSrc/src/main/kotlin/org/jetbrains/jewel/buildlogic/ideversion/IJPVersionsFetcher.kt
+++ b/buildSrc/src/main/kotlin/org/jetbrains/jewel/buildlogic/ideversion/IJPVersionsFetcher.kt
@@ -1,10 +1,10 @@
package org.jetbrains.jewel.buildlogic.ideversion
+import java.io.IOException
+import java.net.URI
import kotlinx.serialization.json.Json
import kotlinx.serialization.json.decodeFromStream
import org.gradle.api.logging.Logger
-import java.io.IOException
-import java.net.URI
internal object IJPVersionsFetcher {
@@ -16,7 +16,8 @@ internal object IJPVersionsFetcher {
val icReleases =
try {
- URI.create(releasesUrl).toURL()
+ URI.create(releasesUrl)
+ .toURL()
.openStream()
.use { json.decodeFromStream>(it) }
.first()
@@ -42,22 +43,17 @@ internal object IJPVersionsFetcher {
majorPlatformVersion: String,
logger: Logger,
): List? {
- val releases = fetchIJPVersions(releasesUrl, logger)
- ?: return null
+ val releases = fetchIJPVersions(releasesUrl, logger) ?: return null
- return releases.asSequence()
+ return releases
+ .asSequence()
.filter { it.majorVersion == majorPlatformVersion }
.sortedWith(ReleaseComparator)
.toList()
}
- fun fetchLatestBuildForCurrentMajorVersion(
- releasesUrl: String,
- majorPlatformVersion: String,
- logger: Logger,
- ) =
- fetchBuildsForCurrentMajorVersion(releasesUrl, majorPlatformVersion, logger)
- ?.last()
+ fun fetchLatestBuildForCurrentMajorVersion(releasesUrl: String, majorPlatformVersion: String, logger: Logger) =
+ fetchBuildsForCurrentMajorVersion(releasesUrl, majorPlatformVersion, logger)?.last()
fun compare(first: ApiIdeaReleasesItem.Release, second: ApiIdeaReleasesItem.Release): Int =
VersionComparator.compare(first.build, second.build)
diff --git a/buildSrc/src/main/kotlin/org/jetbrains/jewel/buildlogic/theme/IntUiThemeDescriptorReader.kt b/buildSrc/src/main/kotlin/org/jetbrains/jewel/buildlogic/theme/IntUiThemeDescriptorReader.kt
index 904fd8b49..96d090f32 100644
--- a/buildSrc/src/main/kotlin/org/jetbrains/jewel/buildlogic/theme/IntUiThemeDescriptorReader.kt
+++ b/buildSrc/src/main/kotlin/org/jetbrains/jewel/buildlogic/theme/IntUiThemeDescriptorReader.kt
@@ -58,17 +58,16 @@ internal object IntUiThemeDescriptorReader {
}
.build()
- private val colorPaletteClassName =
- ClassName.bestGuess("org.jetbrains.jewel.foundation.theme.ThemeColorPalette")
- private val iconDataClassName =
- ClassName.bestGuess("org.jetbrains.jewel.foundation.theme.ThemeIconData")
+ private val colorPaletteClassName = ClassName.bestGuess("org.jetbrains.jewel.foundation.theme.ThemeColorPalette")
+ private val iconDataClassName = ClassName.bestGuess("org.jetbrains.jewel.foundation.theme.ThemeIconData")
private fun TypeSpec.Builder.readColors(colors: Map) {
- val colorGroups = colors.entries
- .groupBy { it.key.replace("""\d+""".toRegex(), "") }
- .filterKeys { colorGroups.contains(it) }
- .map { (groupName, colors) ->
- val parameterName = if (groupName.lowercase() == "grey") "gray" else groupName.lowercase()
+ val colorGroups =
+ colors.entries
+ .groupBy { it.key.replace("""\d+""".toRegex(), "") }
+ .filterKeys { colorGroups.contains(it) }
+ .map { (groupName, colors) ->
+ val parameterName = if (groupName.lowercase() == "grey") "gray" else groupName.lowercase()
// We assume color lists are in the same order as in colorGroups
colors
@@ -78,17 +77,18 @@ internal object IntUiThemeDescriptorReader {
}
.joinToCode(
prefix = "\n$parameterName = listOf(\n",
- separator = ",\n",
- suffix = "\n)"
- )
- }
-
- val rawMap = colors
- .map { (key, value) ->
- val colorHexString = value.replace("#", "0xFF")
- CodeBlock.of("%S to Color(%L)", key, colorHexString)
- }
- .joinToCode(prefix = "\nrawMap = mapOf(\n", separator = ",\n", suffix = "\n)")
+ separator = ",\n",
+ suffix = "\n)",
+ )
+ }
+
+ val rawMap =
+ colors
+ .map { (key, value) ->
+ val colorHexString = value.replace("#", "0xFF")
+ CodeBlock.of("%S to Color(%L)", key, colorHexString)
+ }
+ .joinToCode(prefix = "\nrawMap = mapOf(\n", separator = ",\n", suffix = "\n)")
addProperty(
PropertySpec.builder("colors", colorPaletteClassName, KModifier.OVERRIDE)
@@ -134,6 +134,7 @@ internal object IntUiThemeDescriptorReader {
}
private inline fun Map.toMapCodeBlock() =
- entries.map { (key, value) -> CodeBlock.of("\"%L\" to \"%L\"", key, value) }
+ entries
+ .map { (key, value) -> CodeBlock.of("\"%L\" to \"%L\"", key, value) }
.joinToCode(prefix = "mapOf(", separator = ",\n", suffix = ")")
}
diff --git a/buildSrc/src/main/kotlin/org/jetbrains/jewel/buildlogic/theme/IntelliJThemeGeneratorPlugin.kt b/buildSrc/src/main/kotlin/org/jetbrains/jewel/buildlogic/theme/IntelliJThemeGeneratorPlugin.kt
index 4fd300461..7266d7e9e 100644
--- a/buildSrc/src/main/kotlin/org/jetbrains/jewel/buildlogic/theme/IntelliJThemeGeneratorPlugin.kt
+++ b/buildSrc/src/main/kotlin/org/jetbrains/jewel/buildlogic/theme/IntelliJThemeGeneratorPlugin.kt
@@ -1,6 +1,7 @@
package org.jetbrains.jewel.buildlogic.theme
import com.squareup.kotlinpoet.ClassName
+import java.net.URI
import kotlinx.serialization.Serializable
import kotlinx.serialization.json.Json
import kotlinx.serialization.json.JsonElement
@@ -14,7 +15,6 @@ import org.gradle.api.tasks.Input
import org.gradle.api.tasks.OutputFile
import org.gradle.api.tasks.TaskAction
import org.gradle.kotlin.dsl.property
-import java.net.URI
class ThemeGeneratorContainer(container: NamedDomainObjectContainer) :
NamedDomainObjectContainer by container
@@ -22,9 +22,7 @@ class ThemeGeneratorContainer(container: NamedDomainObjectContainer()
val themeClassName = project.objects.property()
val themeFile = project.objects.property()
@@ -32,17 +30,13 @@ class ThemeGeneration(val name: String, project: Project) {
open class IntelliJThemeGeneratorTask : DefaultTask() {
- @get:OutputFile
- val outputFile: RegularFileProperty = project.objects.fileProperty()
+ @get:OutputFile val outputFile: RegularFileProperty = project.objects.fileProperty()
- @get:Input
- val ideaVersion = project.objects.property()
+ @get:Input val ideaVersion = project.objects.property()
- @get:Input
- val themeFile = project.objects.property()
+ @get:Input val themeFile = project.objects.property()
- @get:Input
- val themeClassName = project.objects.property()
+ @get:Input val themeClassName = project.objects.property()
init {
group = "jewel"
@@ -59,16 +53,15 @@ open class IntelliJThemeGeneratorTask : DefaultTask() {
}
logger.lifecycle("Fetching theme descriptor from $url...")
- val themeDescriptor = URI.create(url).toURL().openStream()
- .use { json.decodeFromStream(it) }
+ val themeDescriptor =
+ URI.create(url).toURL().openStream().use { json.decodeFromStream(it) }
val className = ClassName.bestGuess(themeClassName.get())
val file = IntUiThemeDescriptorReader.readThemeFrom(themeDescriptor, className, ideaVersion.get(), url)
val outputFile = outputFile.get().asFile
logger.lifecycle(
- "Theme descriptor for ${themeDescriptor.name} parsed and " +
- "code generated into ${outputFile.path}"
+ "Theme descriptor for ${themeDescriptor.name} parsed and " + "code generated into ${outputFile.path}"
)
outputFile.bufferedWriter().use { file.writeTo(it) }
}
diff --git a/decorated-window/api/decorated-window.api b/decorated-window/api/decorated-window.api
index aa02fbb48..d84dece45 100644
--- a/decorated-window/api/decorated-window.api
+++ b/decorated-window/api/decorated-window.api
@@ -184,12 +184,12 @@ public final class org/jetbrains/jewel/window/styling/TitleBarColors$Companion {
public final class org/jetbrains/jewel/window/styling/TitleBarIcons {
public static final field $stable I
public static final field Companion Lorg/jetbrains/jewel/window/styling/TitleBarIcons$Companion;
- public fun (Lorg/jetbrains/jewel/ui/painter/PainterProvider;Lorg/jetbrains/jewel/ui/painter/PainterProvider;Lorg/jetbrains/jewel/ui/painter/PainterProvider;Lorg/jetbrains/jewel/ui/painter/PainterProvider;)V
+ public fun (Lorg/jetbrains/jewel/ui/icon/IconKey;Lorg/jetbrains/jewel/ui/icon/IconKey;Lorg/jetbrains/jewel/ui/icon/IconKey;Lorg/jetbrains/jewel/ui/icon/IconKey;)V
public fun equals (Ljava/lang/Object;)Z
- public final fun getCloseButton ()Lorg/jetbrains/jewel/ui/painter/PainterProvider;
- public final fun getMaximizeButton ()Lorg/jetbrains/jewel/ui/painter/PainterProvider;
- public final fun getMinimizeButton ()Lorg/jetbrains/jewel/ui/painter/PainterProvider;
- public final fun getRestoreButton ()Lorg/jetbrains/jewel/ui/painter/PainterProvider;
+ public final fun getCloseButton ()Lorg/jetbrains/jewel/ui/icon/IconKey;
+ public final fun getMaximizeButton ()Lorg/jetbrains/jewel/ui/icon/IconKey;
+ public final fun getMinimizeButton ()Lorg/jetbrains/jewel/ui/icon/IconKey;
+ public final fun getRestoreButton ()Lorg/jetbrains/jewel/ui/icon/IconKey;
public fun hashCode ()I
public fun toString ()Ljava/lang/String;
}
diff --git a/decorated-window/src/main/kotlin/org/jetbrains/jewel/window/TitleBar.Linux.kt b/decorated-window/src/main/kotlin/org/jetbrains/jewel/window/TitleBar.Linux.kt
index 3c86cde9d..c40fe2e70 100644
--- a/decorated-window/src/main/kotlin/org/jetbrains/jewel/window/TitleBar.Linux.kt
+++ b/decorated-window/src/main/kotlin/org/jetbrains/jewel/window/TitleBar.Linux.kt
@@ -21,8 +21,8 @@ import org.jetbrains.jewel.foundation.theme.JewelTheme
import org.jetbrains.jewel.ui.component.Icon
import org.jetbrains.jewel.ui.component.IconButton
import org.jetbrains.jewel.ui.component.styling.IconButtonStyle
+import org.jetbrains.jewel.ui.icon.IconKey
import org.jetbrains.jewel.ui.painter.PainterHint
-import org.jetbrains.jewel.ui.painter.PainterProvider
import org.jetbrains.jewel.ui.painter.PainterProviderScope
import org.jetbrains.jewel.ui.painter.PainterSuffixHint
import org.jetbrains.jewel.window.styling.TitleBarStyle
@@ -90,7 +90,7 @@ private fun TitleBarScope.CloseButton(
private fun TitleBarScope.ControlButton(
onClick: () -> Unit,
state: DecoratedWindowState,
- painterProvider: PainterProvider,
+ iconKey: IconKey,
description: String,
style: TitleBarStyle = JewelTheme.defaultTitleBarStyle,
iconButtonStyle: IconButtonStyle = style.paneButtonStyle,
@@ -100,12 +100,10 @@ private fun TitleBarScope.ControlButton(
Modifier.align(Alignment.End).focusable(false).size(style.metrics.titlePaneButtonSize),
style = iconButtonStyle,
) {
- Icon(painterProvider.getPainter(if (state.isActive) PainterHint else Inactive).value, description)
+ Icon(iconKey, description, hint = if (state.isActive) PainterHint else Inactive)
}
}
-private object Inactive : PainterSuffixHint() {
+private data object Inactive : PainterSuffixHint() {
override fun PainterProviderScope.suffix(): String = "Inactive"
-
- override fun toString(): String = "Inactive"
}
diff --git a/decorated-window/src/main/kotlin/org/jetbrains/jewel/window/styling/TitleBarStyling.kt b/decorated-window/src/main/kotlin/org/jetbrains/jewel/window/styling/TitleBarStyling.kt
index a7fd2ae4e..b7631fd6c 100644
--- a/decorated-window/src/main/kotlin/org/jetbrains/jewel/window/styling/TitleBarStyling.kt
+++ b/decorated-window/src/main/kotlin/org/jetbrains/jewel/window/styling/TitleBarStyling.kt
@@ -13,7 +13,7 @@ import androidx.compose.ui.unit.DpSize
import org.jetbrains.jewel.foundation.GenerateDataFunctions
import org.jetbrains.jewel.ui.component.styling.DropdownStyle
import org.jetbrains.jewel.ui.component.styling.IconButtonStyle
-import org.jetbrains.jewel.ui.painter.PainterProvider
+import org.jetbrains.jewel.ui.icon.IconKey
import org.jetbrains.jewel.window.DecoratedWindowState
@Stable
@@ -79,10 +79,10 @@ public class TitleBarMetrics(
@Immutable
@GenerateDataFunctions
public class TitleBarIcons(
- public val minimizeButton: PainterProvider,
- public val maximizeButton: PainterProvider,
- public val restoreButton: PainterProvider,
- public val closeButton: PainterProvider,
+ public val minimizeButton: IconKey,
+ public val maximizeButton: IconKey,
+ public val restoreButton: IconKey,
+ public val closeButton: IconKey,
) {
public companion object
}
diff --git a/foundation/build.gradle.kts b/foundation/build.gradle.kts
index 5f8420d15..eafe76698 100644
--- a/foundation/build.gradle.kts
+++ b/foundation/build.gradle.kts
@@ -15,4 +15,4 @@ dependencies {
testImplementation(compose.desktop.uiTestJUnit4)
testImplementation(compose.desktop.currentOs) { exclude(group = "org.jetbrains.compose.material") }
-}
\ No newline at end of file
+}
diff --git a/gradle.properties b/gradle.properties
index 85ce5034b..282841c2e 100644
--- a/gradle.properties
+++ b/gradle.properties
@@ -10,4 +10,4 @@ org.jetbrains.intellij.platform.buildFeature.useBinaryReleases=false
jdk.level=17
ijp.target=233
-jewel.release.version=0.23.1
+jewel.release.version=0.24.0
diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml
index 98cd0a0d5..49a18435c 100644
--- a/gradle/libs.versions.toml
+++ b/gradle/libs.versions.toml
@@ -1,6 +1,6 @@
[versions]
commonmark = "0.22.0"
-composeDesktop = "1.7.0-alpha03"
+composeDesktop = "1.7.0-beta01"
detekt = "1.23.6"
dokka = "1.9.20"
idea = "233.15619.7"
@@ -9,7 +9,7 @@ jna = "5.14.0"
kotlin = "1.9.21"
kotlinSarif = "0.5.0"
kotlinpoet = "1.17.0"
-kotlinterGradlePlugin = "4.3.0"
+kotlinterGradlePlugin = "4.4.1"
kotlinxSerialization = "1.6.3"
kotlinxBinaryCompat = "0.14.0"
ktfmtGradlePlugin = "0.20.1"
diff --git a/ide-laf-bridge/build.gradle.kts b/ide-laf-bridge/build.gradle.kts
index 21d013d43..b2c06de36 100644
--- a/ide-laf-bridge/build.gradle.kts
+++ b/ide-laf-bridge/build.gradle.kts
@@ -13,4 +13,4 @@ dependencies {
testImplementation(compose.desktop.uiTestJUnit4)
testImplementation(compose.desktop.currentOs) { exclude(group = "org.jetbrains.compose.material") }
-}
\ No newline at end of file
+}
diff --git a/int-ui/int-ui-decorated-window/api/int-ui-decorated-window.api b/int-ui/int-ui-decorated-window/api/int-ui-decorated-window.api
index ba13fd97f..56da8c68d 100644
--- a/int-ui/int-ui-decorated-window/api/int-ui-decorated-window.api
+++ b/int-ui/int-ui-decorated-window/api/int-ui-decorated-window.api
@@ -1,3 +1,12 @@
+public final class org/jetbrains/jewel/intui/window/DecoratedWindowIconKeys {
+ public static final field $stable I
+ public static final field INSTANCE Lorg/jetbrains/jewel/intui/window/DecoratedWindowIconKeys;
+ public final fun getClose ()Lorg/jetbrains/jewel/ui/icon/IconKey;
+ public final fun getMaximize ()Lorg/jetbrains/jewel/ui/icon/IconKey;
+ public final fun getMinimize ()Lorg/jetbrains/jewel/ui/icon/IconKey;
+ public final fun getRestore ()Lorg/jetbrains/jewel/ui/icon/IconKey;
+}
+
public final class org/jetbrains/jewel/intui/window/IntUiDecoratedWindowResourceResolverKt {
public static final fun decoratedWindowPainterProvider (Ljava/lang/String;)Lorg/jetbrains/jewel/ui/painter/ResourcePainterProvider;
}
@@ -23,8 +32,8 @@ public final class org/jetbrains/jewel/intui/window/styling/IntUiDecoratedWindow
public final class org/jetbrains/jewel/intui/window/styling/IntUiTitleBarStylingKt {
public static final fun dark (Lorg/jetbrains/jewel/window/styling/TitleBarStyle$Companion;Lorg/jetbrains/jewel/window/styling/TitleBarColors;Lorg/jetbrains/jewel/window/styling/TitleBarMetrics;Lorg/jetbrains/jewel/window/styling/TitleBarIcons;Landroidx/compose/runtime/Composer;II)Lorg/jetbrains/jewel/window/styling/TitleBarStyle;
public static final fun dark-a6iJyiw (Lorg/jetbrains/jewel/window/styling/TitleBarColors$Companion;JJJJJJJJJJJJJLandroidx/compose/runtime/Composer;III)Lorg/jetbrains/jewel/window/styling/TitleBarColors;
- public static final fun defaults (Lorg/jetbrains/jewel/window/styling/TitleBarIcons$Companion;Lorg/jetbrains/jewel/ui/painter/PainterProvider;Lorg/jetbrains/jewel/ui/painter/PainterProvider;Lorg/jetbrains/jewel/ui/painter/PainterProvider;Lorg/jetbrains/jewel/ui/painter/PainterProvider;)Lorg/jetbrains/jewel/window/styling/TitleBarIcons;
- public static synthetic fun defaults$default (Lorg/jetbrains/jewel/window/styling/TitleBarIcons$Companion;Lorg/jetbrains/jewel/ui/painter/PainterProvider;Lorg/jetbrains/jewel/ui/painter/PainterProvider;Lorg/jetbrains/jewel/ui/painter/PainterProvider;Lorg/jetbrains/jewel/ui/painter/PainterProvider;ILjava/lang/Object;)Lorg/jetbrains/jewel/window/styling/TitleBarIcons;
+ public static final fun defaults (Lorg/jetbrains/jewel/window/styling/TitleBarIcons$Companion;Lorg/jetbrains/jewel/ui/icon/IconKey;Lorg/jetbrains/jewel/ui/icon/IconKey;Lorg/jetbrains/jewel/ui/icon/IconKey;Lorg/jetbrains/jewel/ui/icon/IconKey;)Lorg/jetbrains/jewel/window/styling/TitleBarIcons;
+ public static synthetic fun defaults$default (Lorg/jetbrains/jewel/window/styling/TitleBarIcons$Companion;Lorg/jetbrains/jewel/ui/icon/IconKey;Lorg/jetbrains/jewel/ui/icon/IconKey;Lorg/jetbrains/jewel/ui/icon/IconKey;Lorg/jetbrains/jewel/ui/icon/IconKey;ILjava/lang/Object;)Lorg/jetbrains/jewel/window/styling/TitleBarIcons;
public static final fun defaults-LgNEgRQ (Lorg/jetbrains/jewel/window/styling/TitleBarMetrics$Companion;FFFJ)Lorg/jetbrains/jewel/window/styling/TitleBarMetrics;
public static synthetic fun defaults-LgNEgRQ$default (Lorg/jetbrains/jewel/window/styling/TitleBarMetrics$Companion;FFFJILjava/lang/Object;)Lorg/jetbrains/jewel/window/styling/TitleBarMetrics;
public static final fun light (Lorg/jetbrains/jewel/window/styling/TitleBarStyle$Companion;Lorg/jetbrains/jewel/window/styling/TitleBarColors;Lorg/jetbrains/jewel/window/styling/TitleBarMetrics;Lorg/jetbrains/jewel/window/styling/TitleBarIcons;Landroidx/compose/runtime/Composer;II)Lorg/jetbrains/jewel/window/styling/TitleBarStyle;
diff --git a/int-ui/int-ui-decorated-window/src/main/kotlin/org/jetbrains/jewel/intui/window/DecoratedWindowIconKeys.kt b/int-ui/int-ui-decorated-window/src/main/kotlin/org/jetbrains/jewel/intui/window/DecoratedWindowIconKeys.kt
new file mode 100644
index 000000000..3365e5f15
--- /dev/null
+++ b/int-ui/int-ui-decorated-window/src/main/kotlin/org/jetbrains/jewel/intui/window/DecoratedWindowIconKeys.kt
@@ -0,0 +1,11 @@
+package org.jetbrains.jewel.intui.window
+
+import org.jetbrains.jewel.ui.icon.IconKey
+import org.jetbrains.jewel.ui.icon.PathIconKey
+
+public object DecoratedWindowIconKeys {
+ public val minimize: IconKey = PathIconKey("icons/intui/window/minimize.svg", this::class.java)
+ public val maximize: IconKey = PathIconKey("icons/intui/window/maximize.svg", this::class.java)
+ public val restore: IconKey = PathIconKey("icons/intui/window/restore.svg", this::class.java)
+ public val close: IconKey = PathIconKey("icons/intui/window/close.svg", this::class.java)
+}
diff --git a/int-ui/int-ui-decorated-window/src/main/kotlin/org/jetbrains/jewel/intui/window/styling/IntUiTitleBarStyling.kt b/int-ui/int-ui-decorated-window/src/main/kotlin/org/jetbrains/jewel/intui/window/styling/IntUiTitleBarStyling.kt
index bb1cac611..399aacb84 100644
--- a/int-ui/int-ui-decorated-window/src/main/kotlin/org/jetbrains/jewel/intui/window/styling/IntUiTitleBarStyling.kt
+++ b/int-ui/int-ui-decorated-window/src/main/kotlin/org/jetbrains/jewel/intui/window/styling/IntUiTitleBarStyling.kt
@@ -13,7 +13,7 @@ import org.jetbrains.jewel.intui.standalone.styling.Undecorated
import org.jetbrains.jewel.intui.standalone.styling.defaults
import org.jetbrains.jewel.intui.standalone.styling.light
import org.jetbrains.jewel.intui.standalone.styling.undecorated
-import org.jetbrains.jewel.intui.window.decoratedWindowPainterProvider
+import org.jetbrains.jewel.intui.window.DecoratedWindowIconKeys
import org.jetbrains.jewel.ui.component.styling.DropdownColors
import org.jetbrains.jewel.ui.component.styling.DropdownMetrics
import org.jetbrains.jewel.ui.component.styling.DropdownStyle
@@ -21,7 +21,7 @@ import org.jetbrains.jewel.ui.component.styling.IconButtonColors
import org.jetbrains.jewel.ui.component.styling.IconButtonMetrics
import org.jetbrains.jewel.ui.component.styling.IconButtonStyle
import org.jetbrains.jewel.ui.component.styling.MenuStyle
-import org.jetbrains.jewel.ui.painter.PainterProvider
+import org.jetbrains.jewel.ui.icon.IconKey
import org.jetbrains.jewel.window.styling.TitleBarColors
import org.jetbrains.jewel.window.styling.TitleBarIcons
import org.jetbrains.jewel.window.styling.TitleBarMetrics
@@ -319,8 +319,8 @@ public fun TitleBarMetrics.Companion.defaults(
): TitleBarMetrics = TitleBarMetrics(height, gradientStartX, gradientEndX, titlePaneButtonSize)
public fun TitleBarIcons.Companion.defaults(
- minimizeButton: PainterProvider = decoratedWindowPainterProvider("icons/intui/window/minimize.svg"),
- maximizeButton: PainterProvider = decoratedWindowPainterProvider("icons/intui/window/maximize.svg"),
- restoreButton: PainterProvider = decoratedWindowPainterProvider("icons/intui/window/restore.svg"),
- closeButton: PainterProvider = decoratedWindowPainterProvider("icons/intui/window/close.svg"),
+ minimizeButton: IconKey = DecoratedWindowIconKeys.minimize,
+ maximizeButton: IconKey = DecoratedWindowIconKeys.maximize,
+ restoreButton: IconKey = DecoratedWindowIconKeys.restore,
+ closeButton: IconKey = DecoratedWindowIconKeys.close,
): TitleBarIcons = TitleBarIcons(minimizeButton, maximizeButton, restoreButton, closeButton)
diff --git a/int-ui/int-ui-standalone/src/main/kotlin/org/jetbrains/jewel/intui/standalone/theme/IntUiGlobalColors.kt b/int-ui/int-ui-standalone/src/main/kotlin/org/jetbrains/jewel/intui/standalone/theme/IntUiGlobalColors.kt
index 27c25cab7..a6461a75a 100644
--- a/int-ui/int-ui-standalone/src/main/kotlin/org/jetbrains/jewel/intui/standalone/theme/IntUiGlobalColors.kt
+++ b/int-ui/int-ui-standalone/src/main/kotlin/org/jetbrains/jewel/intui/standalone/theme/IntUiGlobalColors.kt
@@ -27,14 +27,14 @@ public fun GlobalColors.Companion.dark(
@Composable
public fun BorderColors.Companion.light(
- normal: Color = IntUiLightTheme.colors.gray(9),
+ normal: Color = IntUiLightTheme.colors.gray(12),
focused: Color = IntUiLightTheme.colors.gray(14),
disabled: Color = IntUiLightTheme.colors.gray(11),
): BorderColors = BorderColors(normal, focused, disabled)
@Composable
public fun BorderColors.Companion.dark(
- normal: Color = IntUiDarkTheme.colors.gray(5),
+ normal: Color = IntUiDarkTheme.colors.gray(1),
focused: Color = IntUiDarkTheme.colors.gray(2),
disabled: Color = IntUiDarkTheme.colors.gray(4),
): BorderColors = BorderColors(normal, focused, disabled)
diff --git a/markdown/core/src/main/kotlin/org/jetbrains/jewel/markdown/processing/ProcessingUtil.kt b/markdown/core/src/main/kotlin/org/jetbrains/jewel/markdown/processing/ProcessingUtil.kt
index 88df689b5..b0f65dcfb 100644
--- a/markdown/core/src/main/kotlin/org/jetbrains/jewel/markdown/processing/ProcessingUtil.kt
+++ b/markdown/core/src/main/kotlin/org/jetbrains/jewel/markdown/processing/ProcessingUtil.kt
@@ -21,32 +21,15 @@ import org.jetbrains.jewel.markdown.extensions.MarkdownProcessorExtension
internal fun Node.readInlineContent(
markdownProcessor: MarkdownProcessor,
extensions: List,
-): List =
- object : Iterable {
- override fun iterator(): Iterator =
- object : Iterator {
- var current = this@readInlineContent.firstChild
+): List = buildList {
+ var current = this@readInlineContent.firstChild
+ while (current != null) {
+ val inline = current.toInlineMarkdownOrNull(markdownProcessor, extensions)
+ if (inline != null) add(inline)
- override fun hasNext(): Boolean = current != null
-
- override fun next(): InlineMarkdown {
- while (hasNext()) {
- val inline = current.toInlineMarkdownOrNull(markdownProcessor, extensions)
-
- current = current.next
-
- if (inline == null) {
- continue
- } else {
- return inline
- }
- }
-
- throw NoSuchElementException()
- }
- }
- }
- .toList()
+ current = current.next
+ }
+}
@VisibleForTesting
internal fun Node.toInlineMarkdownOrNull(
diff --git a/markdown/extension/gfm-alerts/src/main/kotlin/org/jetbrains/jewel/markdown/extensions/github/alerts/GitHubAlertBlockRenderer.kt b/markdown/extension/gfm-alerts/src/main/kotlin/org/jetbrains/jewel/markdown/extensions/github/alerts/GitHubAlertBlockRenderer.kt
index 234bf5084..51638ae6c 100755
--- a/markdown/extension/gfm-alerts/src/main/kotlin/org/jetbrains/jewel/markdown/extensions/github/alerts/GitHubAlertBlockRenderer.kt
+++ b/markdown/extension/gfm-alerts/src/main/kotlin/org/jetbrains/jewel/markdown/extensions/github/alerts/GitHubAlertBlockRenderer.kt
@@ -6,13 +6,10 @@ import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.padding
import androidx.compose.runtime.Composable
import androidx.compose.runtime.CompositionLocalProvider
-import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.drawBehind
import androidx.compose.ui.geometry.Offset
-import androidx.compose.ui.graphics.ColorFilter
-import androidx.compose.ui.graphics.isSpecified
import androidx.compose.ui.graphics.takeOrElse
import androidx.compose.ui.input.pointer.PointerIcon
import androidx.compose.ui.input.pointer.pointerHoverIcon
@@ -47,7 +44,8 @@ public class GitHubAlertBlockRenderer(private val styling: AlertStyling, private
) {
// Smart cast doesn't work in this case, and then the detection for redundant suppression is
// also borked
- @Suppress("MoveVariableDeclarationIntoWhen", "RedundantSuppression") val alert = block as? Alert
+ @Suppress("MoveVariableDeclarationIntoWhen", "RedundantSuppression") // ktfmt: break line
+ val alert = block as? Alert
when (alert) {
is Caution -> Alert(alert, styling.caution, enabled, blockRenderer, onUrlClick, onTextClick)
@@ -75,34 +73,25 @@ public class GitHubAlertBlockRenderer(private val styling: AlertStyling, private
val x = if (isLtr) lineWidthPx / 2 else size.width - lineWidthPx / 2
drawLine(
- styling.lineColor,
- Offset(x, 0f),
- Offset(x, size.height),
- lineWidthPx,
- styling.strokeCap,
- styling.pathEffect,
+ color = styling.lineColor,
+ start = Offset(x, 0f),
+ end = Offset(x, size.height),
+ strokeWidth = lineWidthPx,
+ cap = styling.strokeCap,
+ pathEffect = styling.pathEffect,
)
}
.padding(styling.padding),
verticalArrangement = Arrangement.spacedBy(rootStyling.blockVerticalSpacing),
) {
Row(horizontalArrangement = Arrangement.spacedBy(8.dp), verticalAlignment = Alignment.CenterVertically) {
- val titleIconProvider = styling.titleIconKey
- if (titleIconProvider != null) {
- val colorFilter =
- remember(styling.titleIconTint) {
- if (styling.titleIconTint.isSpecified) {
- ColorFilter.tint(styling.titleIconTint)
- } else {
- null
- }
- }
-
+ val titleIconKey = styling.titleIconKey
+ if (titleIconKey != null) {
Icon(
- titleIconProvider,
+ key = titleIconKey,
contentDescription = null,
iconClass = AlertStyling::class.java,
- colorFilter = colorFilter,
+ tint = styling.titleIconTint,
)
}
diff --git a/samples/ide-plugin/build.gradle.kts b/samples/ide-plugin/build.gradle.kts
index 9a6380e43..40c682715 100644
--- a/samples/ide-plugin/build.gradle.kts
+++ b/samples/ide-plugin/build.gradle.kts
@@ -38,6 +38,4 @@ tasks {
runIde {
systemProperties["org.jetbrains.jewel.debug"] = "true"
- jvmArgs = listOf("-Xmx3g")
- }
-}
\ No newline at end of file
+ jvmArgs = listOf("-Xmx3g")} }
diff --git a/samples/ide-plugin/src/main/kotlin/org/jetbrains/jewel/samples/ideplugin/ComponentShowcaseTab.kt b/samples/ide-plugin/src/main/kotlin/org/jetbrains/jewel/samples/ideplugin/ComponentShowcaseTab.kt
index 1873eaa9d..172dab707 100644
--- a/samples/ide-plugin/src/main/kotlin/org/jetbrains/jewel/samples/ideplugin/ComponentShowcaseTab.kt
+++ b/samples/ide-plugin/src/main/kotlin/org/jetbrains/jewel/samples/ideplugin/ComponentShowcaseTab.kt
@@ -55,7 +55,6 @@ import org.jetbrains.jewel.ui.component.IconActionButton
import org.jetbrains.jewel.ui.component.IconButton
import org.jetbrains.jewel.ui.component.LazyTree
import org.jetbrains.jewel.ui.component.OutlinedButton
-import org.jetbrains.jewel.ui.component.PlatformIcon
import org.jetbrains.jewel.ui.component.RadioButtonRow
import org.jetbrains.jewel.ui.component.Slider
import org.jetbrains.jewel.ui.component.Text
@@ -175,7 +174,7 @@ private fun RowScope.ColumnOne() {
Tooltip(
tooltip = {
Row(horizontalArrangement = Arrangement.spacedBy(8.dp)) {
- Icon(AllIconsKeys.General.ShowInfos, contentDescription = null)
+ Icon(key = AllIconsKeys.General.ShowInfos, contentDescription = null)
Text("This is a tooltip")
}
}
@@ -194,63 +193,72 @@ private fun RowScope.ColumnOne() {
@Composable
private fun IconsShowcase() {
+ val iconBackgroundColor =
+ JewelTheme.colorPalette.blueOrNull(4) ?: JBUI.CurrentTheme.Banner.INFO_BACKGROUND.toComposeColor()
+
Row(
modifier = Modifier.fillMaxWidth().padding(horizontal = 16.dp),
horizontalArrangement = Arrangement.spacedBy(16.dp),
) {
Box(Modifier.size(24.dp), contentAlignment = Alignment.Center) {
- PlatformIcon(AllIconsKeys.Nodes.ConfigFolder, "taskGroup")
+ Icon(AllIconsKeys.Nodes.ConfigFolder, "taskGroup")
}
Box(Modifier.size(24.dp), contentAlignment = Alignment.Center) {
- PlatformIcon(AllIconsKeys.Nodes.ConfigFolder, "taskGroup", hint = Badge(Color.Red, DotBadgeShape.Default))
+ Icon(
+ key = AllIconsKeys.Nodes.ConfigFolder,
+ contentDescription = "taskGroup",
+ hint = Badge(Color.Red, DotBadgeShape.Default),
+ )
}
Box(
- Modifier.size(24.dp).background(JewelTheme.colorPalette.blue(4), shape = RoundedCornerShape(4.dp)),
+ Modifier.size(24.dp).background(iconBackgroundColor, shape = RoundedCornerShape(4.dp)),
contentAlignment = Alignment.Center,
) {
- PlatformIcon(AllIconsKeys.Nodes.ConfigFolder, "taskGroup", hint = Stroke(Color.White))
+ Icon(key = AllIconsKeys.Nodes.ConfigFolder, contentDescription = "taskGroup", hint = Stroke(Color.White))
}
Box(
- Modifier.size(24.dp).background(JewelTheme.colorPalette.blue(4), shape = RoundedCornerShape(4.dp)),
+ Modifier.size(24.dp).background(iconBackgroundColor, shape = RoundedCornerShape(4.dp)),
contentAlignment = Alignment.Center,
) {
- PlatformIcon(
- AllIconsKeys.Nodes.ConfigFolder,
- "taskGroup",
+ Icon(
+ key = AllIconsKeys.Nodes.ConfigFolder,
+ contentDescription = "taskGroup",
hints = arrayOf(Stroke(Color.White), Badge(Color.Red, DotBadgeShape.Default)),
)
}
Box(Modifier.size(24.dp), contentAlignment = Alignment.Center) {
- PlatformIcon(AllIconsKeys.Nodes.ConfigFolder, "taskGroup", hint = Size(20))
+ Icon(key = AllIconsKeys.Nodes.ConfigFolder, contentDescription = "taskGroup", hint = Size(20))
}
Box(Modifier.size(24.dp), contentAlignment = Alignment.Center) {
- PlatformIcon(
- AllIconsKeys.Actions.Close,
- "An icon",
+ Icon(
+ key = AllIconsKeys.Actions.Close,
+ contentDescription = "An icon",
modifier = Modifier.border(1.dp, Color.Magenta),
hint = Size(20),
)
}
Box(Modifier.size(24.dp), contentAlignment = Alignment.Center) {
- PlatformIcon(AllIconsKeys.Nodes.ConfigFolder, "taskGroup", hint = Size(20))
+ Icon(key = AllIconsKeys.Nodes.ConfigFolder, contentDescription = "taskGroup", hint = Size(20))
}
Box(Modifier.size(24.dp), contentAlignment = Alignment.Center) {
Icon(
- IdeSampleIconKeys.gitHub,
+ key = IdeSampleIconKeys.gitHub,
iconClass = IdeSampleIconKeys::class.java,
modifier = Modifier.border(1.dp, Color.Magenta),
contentDescription = "An owned icon",
)
}
- IconButton(onClick = {}, Modifier.size(24.dp)) { PlatformIcon(AllIconsKeys.Actions.Close, "Close") }
+ IconButton(onClick = {}, Modifier.size(24.dp)) {
+ Icon(key = AllIconsKeys.Actions.Close, contentDescription = "Close")
+ }
IconActionButton(
AllIconsKeys.Actions.AddList,
diff --git a/samples/ide-plugin/src/main/kotlin/org/jetbrains/jewel/samples/ideplugin/releasessample/ReleasesSampleCompose.kt b/samples/ide-plugin/src/main/kotlin/org/jetbrains/jewel/samples/ideplugin/releasessample/ReleasesSampleCompose.kt
index d557197c9..eb63152ee 100644
--- a/samples/ide-plugin/src/main/kotlin/org/jetbrains/jewel/samples/ideplugin/releasessample/ReleasesSampleCompose.kt
+++ b/samples/ide-plugin/src/main/kotlin/org/jetbrains/jewel/samples/ideplugin/releasessample/ReleasesSampleCompose.kt
@@ -94,18 +94,17 @@ import org.jetbrains.jewel.ui.util.thenIf
fun ReleasesSampleCompose(project: Project) {
var selectedItem: ContentItem? by remember { mutableStateOf(null) }
HorizontalSplitLayout(
- first = { modifier ->
+ first = {
LeftColumn(
project = project,
- modifier = modifier.fillMaxSize(),
+ modifier = Modifier.fillMaxSize(),
onSelectedItemChange = { selectedItem = it },
)
},
- second = { modifier -> RightColumn(selectedItem = selectedItem, modifier = modifier.fillMaxSize()) },
- Modifier.fillMaxSize(),
- initialDividerPosition = 400.dp,
- minRatio = .15f,
- maxRatio = .7f,
+ second = { RightColumn(selectedItem = selectedItem, modifier = Modifier.fillMaxSize()) },
+ modifier = Modifier.fillMaxSize(),
+ firstPaneMinWidth = 300.dp,
+ secondPaneMinWidth = 300.dp,
)
}
diff --git a/samples/standalone/src/main/kotlin/org/jetbrains/jewel/samples/standalone/StandaloneSampleIcons.kt b/samples/standalone/src/main/kotlin/org/jetbrains/jewel/samples/standalone/StandaloneSampleIcons.kt
index 442d8d7d7..4381ec699 100644
--- a/samples/standalone/src/main/kotlin/org/jetbrains/jewel/samples/standalone/StandaloneSampleIcons.kt
+++ b/samples/standalone/src/main/kotlin/org/jetbrains/jewel/samples/standalone/StandaloneSampleIcons.kt
@@ -25,6 +25,7 @@ object StandaloneSampleIcons {
val scrollbar = PathIconKey("icons/components/scrollbar.svg", StandaloneSampleIcons::class.java)
val segmentedControls = PathIconKey("icons/components/segmentedControl.svg", StandaloneSampleIcons::class.java)
val slider = PathIconKey("icons/components/slider.svg", StandaloneSampleIcons::class.java)
+ val splitlayout = PathIconKey("icons/components/splitLayout.svg", StandaloneSampleIcons::class.java)
val tabs = PathIconKey("icons/components/tabs.svg", StandaloneSampleIcons::class.java)
val textArea = PathIconKey("icons/components/textArea.svg", StandaloneSampleIcons::class.java)
val textField = PathIconKey("icons/components/textField.svg", StandaloneSampleIcons::class.java)
diff --git a/samples/standalone/src/main/kotlin/org/jetbrains/jewel/samples/standalone/view/component/Buttons.kt b/samples/standalone/src/main/kotlin/org/jetbrains/jewel/samples/standalone/view/component/Buttons.kt
index a63439e07..1c9dbc751 100644
--- a/samples/standalone/src/main/kotlin/org/jetbrains/jewel/samples/standalone/view/component/Buttons.kt
+++ b/samples/standalone/src/main/kotlin/org/jetbrains/jewel/samples/standalone/view/component/Buttons.kt
@@ -15,10 +15,10 @@ import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp
import org.jetbrains.jewel.ui.component.ActionButton
import org.jetbrains.jewel.ui.component.DefaultButton
+import org.jetbrains.jewel.ui.component.Icon
import org.jetbrains.jewel.ui.component.IconActionButton
import org.jetbrains.jewel.ui.component.IconButton
import org.jetbrains.jewel.ui.component.OutlinedButton
-import org.jetbrains.jewel.ui.component.PlatformIcon
import org.jetbrains.jewel.ui.component.SelectableIconActionButton
import org.jetbrains.jewel.ui.component.SelectableIconButton
import org.jetbrains.jewel.ui.component.Text
@@ -71,19 +71,19 @@ private fun IconButtons(selected: Boolean, onSelectableClick: () -> Unit) {
Text("Focusable:")
- IconButton(onClick = {}) { PlatformIcon(AllIconsKeys.Actions.Close, contentDescription = "IconButton") }
+ IconButton(onClick = {}) { Icon(key = AllIconsKeys.Actions.Close, contentDescription = "IconButton") }
Text("Not focusable:")
IconButton(onClick = {}, focusable = false) {
- PlatformIcon(AllIconsKeys.Actions.Close, contentDescription = "IconButton")
+ Icon(key = AllIconsKeys.Actions.Close, contentDescription = "IconButton")
}
Text("Selectable:")
SelectableIconButton(onClick = onSelectableClick, selected = selected) { state ->
val tint by LocalIconButtonStyle.current.colors.selectableForegroundFor(state)
- PlatformIcon(
+ Icon(
key = AllIconsKeys.Actions.MatchCase,
contentDescription = "SelectableIconButton",
hints = arrayOf(Selected(selected), Stroke(tint)),
@@ -95,7 +95,7 @@ private fun IconButtons(selected: Boolean, onSelectableClick: () -> Unit) {
var checked by remember { mutableStateOf(false) }
ToggleableIconButton(onValueChange = { checked = !checked }, value = checked) { state ->
val tint by LocalIconButtonStyle.current.colors.toggleableForegroundFor(state)
- PlatformIcon(
+ Icon(
key = AllIconsKeys.Actions.MatchCase,
contentDescription = "ToggleableIconButton",
hints = arrayOf(Selected(checked), Stroke(tint)),
diff --git a/samples/standalone/src/main/kotlin/org/jetbrains/jewel/samples/standalone/view/component/Dropdowns.kt b/samples/standalone/src/main/kotlin/org/jetbrains/jewel/samples/standalone/view/component/Dropdowns.kt
index 81c73be1e..1f562c580 100644
--- a/samples/standalone/src/main/kotlin/org/jetbrains/jewel/samples/standalone/view/component/Dropdowns.kt
+++ b/samples/standalone/src/main/kotlin/org/jetbrains/jewel/samples/standalone/view/component/Dropdowns.kt
@@ -14,7 +14,6 @@ import org.jetbrains.jewel.ui.Outline
import org.jetbrains.jewel.ui.component.Dropdown
import org.jetbrains.jewel.ui.component.Text
import org.jetbrains.jewel.ui.component.separator
-import org.jetbrains.jewel.ui.component.styling.DropdownStyle
import org.jetbrains.jewel.ui.icons.AllIconsKeys
@Composable
@@ -89,7 +88,6 @@ fun Dropdowns() {
} else {
selectableItem(
iconKey = dropdownIconsSample.random(),
- iconClass = DropdownStyle::class.java,
keybinding =
if (Random.nextBoolean()) {
null
@@ -111,7 +109,6 @@ fun Dropdowns() {
} else {
selectableItem(
iconKey = dropdownIconsSample.random(),
- iconClass = DropdownStyle::class.java,
keybinding =
if (Random.nextBoolean()) {
null
@@ -134,7 +131,6 @@ fun Dropdowns() {
} else {
selectableItem(
iconKey = dropdownIconsSample.random(),
- iconClass = DropdownStyle::class.java,
selected = false,
onClick = {},
) {
diff --git a/samples/standalone/src/main/kotlin/org/jetbrains/jewel/samples/standalone/view/component/Icons.kt b/samples/standalone/src/main/kotlin/org/jetbrains/jewel/samples/standalone/view/component/Icons.kt
index 0d5546635..53eb14240 100644
--- a/samples/standalone/src/main/kotlin/org/jetbrains/jewel/samples/standalone/view/component/Icons.kt
+++ b/samples/standalone/src/main/kotlin/org/jetbrains/jewel/samples/standalone/view/component/Icons.kt
@@ -18,7 +18,6 @@ import androidx.compose.ui.unit.dp
import org.jetbrains.jewel.foundation.theme.JewelTheme
import org.jetbrains.jewel.samples.standalone.StandaloneSampleIcons
import org.jetbrains.jewel.ui.component.Icon
-import org.jetbrains.jewel.ui.component.PlatformIcon
import org.jetbrains.jewel.ui.component.Text
import org.jetbrains.jewel.ui.icons.AllIconsKeys
import org.jetbrains.jewel.ui.painter.badge.DotBadgeShape
@@ -52,29 +51,33 @@ internal fun Icons() {
horizontalArrangement = Arrangement.spacedBy(16.dp),
) {
Box(Modifier.size(24.dp), contentAlignment = Alignment.Center) {
- PlatformIcon(AllIconsKeys.Nodes.ConfigFolder, "taskGroup")
+ Icon(key = AllIconsKeys.Nodes.ConfigFolder, contentDescription = "taskGroup")
}
Box(Modifier.size(24.dp), contentAlignment = Alignment.Center) {
- PlatformIcon(AllIconsKeys.Nodes.ConfigFolder, "taskGroup", hint = Badge(Color.Red, DotBadgeShape.Default))
+ Icon(
+ key = AllIconsKeys.Nodes.ConfigFolder,
+ contentDescription = "taskGroup",
+ hint = Badge(Color.Red, DotBadgeShape.Default),
+ )
}
Box(
Modifier.size(24.dp).background(JewelTheme.colorPalette.blue(4), shape = RoundedCornerShape(4.dp)),
contentAlignment = Alignment.Center,
) {
- PlatformIcon(AllIconsKeys.Nodes.ConfigFolder, "taskGroup", hint = Stroke(Color.White))
+ Icon(key = AllIconsKeys.Nodes.ConfigFolder, contentDescription = "taskGroup", hint = Stroke(Color.White))
}
Box(
Modifier.size(24.dp).background(JewelTheme.colorPalette.blue(4), shape = RoundedCornerShape(4.dp)),
contentAlignment = Alignment.Center,
) {
- PlatformIcon(
- AllIconsKeys.Nodes.ConfigFolder,
- "taskGroup",
+ Icon(
+ key = AllIconsKeys.Nodes.ConfigFolder,
+ contentDescription = "taskGroup",
hints = arrayOf(Stroke(Color.White), Badge(Color.Red, DotBadgeShape.Default)),
)
}
Box(Modifier.size(24.dp), contentAlignment = Alignment.Center) {
- PlatformIcon(AllIconsKeys.Nodes.ConfigFolder, "taskGroup", hint = Size(20))
+ Icon(key = AllIconsKeys.Nodes.ConfigFolder, contentDescription = "taskGroup", hint = Size(20))
}
}
}
diff --git a/samples/standalone/src/main/kotlin/org/jetbrains/jewel/samples/standalone/view/component/SplitLayouts.kt b/samples/standalone/src/main/kotlin/org/jetbrains/jewel/samples/standalone/view/component/SplitLayouts.kt
new file mode 100644
index 000000000..b70f9d26d
--- /dev/null
+++ b/samples/standalone/src/main/kotlin/org/jetbrains/jewel/samples/standalone/view/component/SplitLayouts.kt
@@ -0,0 +1,94 @@
+import androidx.compose.foundation.border
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.Row
+import androidx.compose.foundation.layout.Spacer
+import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.height
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.layout.width
+import androidx.compose.foundation.text.input.TextFieldState
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.remember
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.unit.dp
+import org.jetbrains.jewel.foundation.theme.JewelTheme
+import org.jetbrains.jewel.ui.component.HorizontalSplitLayout
+import org.jetbrains.jewel.ui.component.OutlinedButton
+import org.jetbrains.jewel.ui.component.SplitLayoutState
+import org.jetbrains.jewel.ui.component.Text
+import org.jetbrains.jewel.ui.component.TextField
+import org.jetbrains.jewel.ui.component.VerticalSplitLayout
+
+@Composable
+fun SplitLayouts(
+ outerSplitState: SplitLayoutState,
+ verticalSplitState: SplitLayoutState,
+ innerSplitState: SplitLayoutState,
+ onResetState: () -> Unit,
+) {
+ Column(Modifier.fillMaxSize()) {
+ Row(verticalAlignment = Alignment.CenterVertically) {
+ Text("Reset split state:")
+ Spacer(Modifier.width(8.dp))
+ OutlinedButton(onClick = onResetState) { Text("Reset") }
+ }
+
+ Spacer(Modifier.height(16.dp))
+
+ HorizontalSplitLayout(
+ state = outerSplitState,
+ first = { FirstPane() },
+ second = { SecondPane(innerSplitState = innerSplitState, verticalSplitState = verticalSplitState) },
+ modifier = Modifier.fillMaxWidth().weight(1f).border(1.dp, color = JewelTheme.globalColors.borders.normal),
+ firstPaneMinWidth = 300.dp,
+ secondPaneMinWidth = 200.dp,
+ )
+ }
+}
+
+@Composable
+private fun FirstPane() {
+ Box(modifier = Modifier.fillMaxSize().padding(16.dp), contentAlignment = Alignment.Center) {
+ val state by remember { mutableStateOf(TextFieldState()) }
+ TextField(state, placeholder = { Text("Placeholder") })
+ }
+}
+
+@Composable
+private fun SecondPane(innerSplitState: SplitLayoutState, verticalSplitState: SplitLayoutState) {
+ VerticalSplitLayout(
+ state = verticalSplitState,
+ modifier = Modifier.fillMaxSize(),
+ first = {
+ val state by remember { mutableStateOf(TextFieldState()) }
+ Box(Modifier.fillMaxSize().padding(16.dp), contentAlignment = Alignment.Center) {
+ TextField(state, placeholder = { Text("Right Panel Content") })
+ }
+ },
+ second = {
+ HorizontalSplitLayout(
+ first = {
+ Box(Modifier.fillMaxSize().padding(16.dp), contentAlignment = Alignment.Center) {
+ Text("Second Pane left")
+ }
+ },
+ second = {
+ Box(Modifier.fillMaxSize().padding(16.dp), contentAlignment = Alignment.Center) {
+ Text("Second Pane right")
+ }
+ },
+ modifier = Modifier.fillMaxSize(),
+ state = innerSplitState,
+ firstPaneMinWidth = 100.dp,
+ secondPaneMinWidth = 100.dp,
+ )
+ },
+ firstPaneMinWidth = 300.dp,
+ secondPaneMinWidth = 100.dp,
+ )
+}
diff --git a/samples/standalone/src/main/kotlin/org/jetbrains/jewel/samples/standalone/view/markdown/JewelReadme.kt b/samples/standalone/src/main/kotlin/org/jetbrains/jewel/samples/standalone/view/markdown/JewelReadme.kt
index b49818137..4ac0e1f2c 100755
--- a/samples/standalone/src/main/kotlin/org/jetbrains/jewel/samples/standalone/view/markdown/JewelReadme.kt
+++ b/samples/standalone/src/main/kotlin/org/jetbrains/jewel/samples/standalone/view/markdown/JewelReadme.kt
@@ -7,8 +7,6 @@ internal val JewelReadme =
"""
# Jewel: a Compose for Desktop theme
-
-
Jewel aims at recreating the IntelliJ Platform's _New UI_ Swing Look and Feel in Compose for Desktop, providing a
desktop-optimized theme and set of components.
@@ -27,56 +25,119 @@ Jewel provides an implementation of the IntelliJ Platform themes that can be use
application. Additionally, it has a Swing LaF Bridge that only works in the IntelliJ Platform (i.e., used to create IDE
plugins), but automatically mirrors the current Swing LaF into Compose for a native-looking, consistent UI.
+> [!TIP]
+> If you want to learn more about Jewel and Compose for Desktop and why they're a great, modern solution for your
+> desktop
+> UI needs, check out [this talk](https://www.droidcon.com/2023/11/15/meet-jewelcreate-ide-plugins-in-compose/) by Jewel
+> contributors Sebastiano and Chris.
+>
+> It covers why Compose is a viable choice, and an overview of the Jewel project, plus
+> some real-life use cases.
+
## Getting started
-To use Jewel in your app, you only need to add the relevant dependency. There are two scenarios: standalone Compose for
-Desktop app, and IntelliJ Platform plugin.
+The first thing to add is the necessary Gradle plugins, including the Compose Multiplatform plugin. You need to add a
+custom repository for it in `settings.gradle.kts`:
-For now, Jewel artifacts aren't available on Maven Central. You need to add a custom Maven repository to your build:
+```kotlin
+pluginManagement {
+ repositories {
+ google()
+ gradlePluginPortal()
+ maven("https://maven.pkg.jetbrains.space/public/p/compose/dev")
+ mavenCentral()
+ }
+}
+```
+
+Then, in your app's `build.gradle.kts`:
```kotlin
+plugins {
+ // MUST align with the Kotlin and Compose dependencies in Jewel
+ kotlin("jvm") version "..."
+ id("org.jetbrains.compose") version "..."
+}
+
repositories {
maven("https://packages.jetbrains.team/maven/p/kpm/public/")
// Any other repositories you need (e.g., mavenCentral())
}
```
-If you're writing a **standalone app**, then you should depend on the `int-ui-standalone` artifact:
+> [!WARNING]
+> If you use convention plugins to configure your project you might run into issues such as
+> [this](https://github.com/JetBrains/compose-multiplatform/issues/3748). To solve it, make sure the
+> plugins are only initialized once — for example, by declaring them in the root `build.gradle.kts`
+> with `apply false`, and then applying them in all the submodules that need them.
+
+To use Jewel in your app, you only need to add the relevant dependency. There are two scenarios: standalone Compose for
+Desktop app, and IntelliJ Platform plugin.
+
+If you're writing a **standalone app**, then you should depend on the latest `int-ui-standalone-*` artifact:
```kotlin
dependencies {
- implementation("org.jetbrains.jewel:jewel-int-ui-standalone:[jewel version]")
+ // See https://github.com/JetBrains/Jewel/releases for the release notes
+ implementation("org.jetbrains.jewel:jewel-int-ui-standalone-[latest platform version]:[jewel version]")
// Optional, for custom decorated windows:
- implementation("org.jetbrains.jewel:jewel-int-ui-decorated-window:[jewel version]")
+ implementation("org.jetbrains.jewel:jewel-int-ui-decorated-window-[latest platform version]:[jewel version]")
+
+ // Do not bring in Material (we use Jewel)
+ implementation(compose.desktop.currentOs) {
+ exclude(group = "org.jetbrains.compose.material")
+ }
}
```
-For an **IntelliJ Platform plugin**, then you should depend on the appropriate `ide-laf-bridge` artifact:
+For an **IntelliJ Platform plugin**, then you should depend on the appropriate `ide-laf-bridge-*` artifact:
```kotlin
dependencies {
+ // See https://github.com/JetBrains/Jewel/releases for the release notes
// The platform version is a supported major IJP version (e.g., 232 or 233 for 2023.2 and 2023.3 respectively)
implementation("org.jetbrains.jewel:jewel-ide-laf-bridge-[platform version]:[jewel version]")
+
+ // Do not bring in Material (we use Jewel) and Coroutines (the IDE has its own)
+ api(compose.desktop.currentOs) {
+ exclude(group = "org.jetbrains.compose.material")
+ exclude(group = "org.jetbrains.kotlinx")
+ }
}
```
> [!TIP]
->
->
->
->
-> If you want to learn more about Jewel and Compose for Desktop and why they're a great, modern solution for your
-> desktop
-> UI needs, check out [this talk](https://www.droidcon.com/2023/11/15/meet-jewelcreate-ide-plugins-in-compose/) by Jewel
-> contributors Sebastiano and Chris.
->
-> It covers why Compose is a viable choice, and an overview of the Jewel project, plus
-> some real-life use cases.
+> It's easier to use version catalogs — you can use the Jewel
+> [version catalog](https://github.com/JetBrains/jewel/blob/main/gradle/libs.versions.toml) as reference.
-
+## Using ProGuard/obfuscation/minification
+
+Jewel doesn't officially support using ProGuard to minimize and/or obfuscate your code, and there is currently no plan
+to.
+That said, people are reporting successes in using it. Please note that there is no guarantee that it will keep working,
+and you most definitely need to have some rules in place. We don't provide any official rule set, but these have been
+known
+to work for some: https://github.com/romainguy/kotlin-explorer/blob/main/compose-desktop.pro
+
+> [!IMPORTANT]
+> We won't accept bug reports for issues caused by the use of ProGuard or similar tools.
+
+## Dependencies matrix
+
+Jewel is in continuous development and we focus on supporting only the Compose version we use internally.
+You can see the latest supported version
+in [libs.versions.toml](https://github.com/JetBrains/jewel/blob/main/gradle/libs.versions.toml).
+
+Different versions of Compose are not guaranteed to work with different versions of Jewel.
+
+The Compose Compiler version used is the latest compatible with the given Kotlin version. See
+[here](https://developer.android.com/jetpack/androidx/releases/compose-compiler) for the Compose
+Compiler release notes, which indicate the compatibility.
+
+The minimum supported Kotlin version is dictated by the minimum supported IntelliJ IDEA platform.
## Project structure
@@ -101,7 +162,12 @@ The project is split in modules:
* `int-ui-decorated-window` has a standalone version of the Int UI styling values for the custom window decoration
that can be used in any Compose for Desktop app
6. `ide-laf-bridge` contains the Swing LaF bridge to use in IntelliJ Platform plugins (see more below)
-7. `samples` contains the example apps, which showcase the available components:
+7. `markdown` contains a few modules:
+ * `core` the core logic for parsing and rendering Markdown documents with Jewel, using GitHub-like styling
+ * `extension` contains several extensions to the base CommonMark specs that can be used to add more features
+ * `ide-laf-bridge-styling` contains the IntelliJ Platform bridge theming for the Markdown renderer
+ * `int-ui-standalone-styling` contains the standalone Int UI theming for the Markdown renderer
+8. `samples` contains the example apps, which showcase the available components:
* `standalone` is a regular CfD app, using the standalone theme definitions and custom window decoration
* `ide-plugin` is an IntelliJ plugin that showcases the use of the Swing Bridge
@@ -124,7 +190,13 @@ as `[mainTag]-xxx`, and used to publish the artifacts for that major IJP version
> We only support the latest build of IJP for each major IJP version. If the latest 233 version is 2023.3.3, for
> example, we will only guarantee that Jewel works on that. Versions 2023.3.0–2023.3.2 might or might not work.
-### Int UI Standalone theme
+> [!CAUTION]
+> When you target Android Studio, you might encounter issues due to Studio shipping its own (older) version of Jewel
+> and Compose for Desktop. If you want to target Android Studio, you'll need to shadow the CfD and Jewel dependencies
+> until that dependency isn't leaked on the classpath by Studio anymore. You can look at how the
+> [Package Search](https://github.com/JetBrains/package-search-intellij-plugin) plugin implements shadowing.
+
+## Int UI Standalone theme
The standalone theme can be used in any Compose for Desktop app. You use it as a normal theme, and you can customise it
to your heart's content. By default, it matches the official Int UI specs.
@@ -148,12 +220,10 @@ IntUiTheme(isDark = false) {
If you want more control over the theming, you can use other `IntUiTheme` overloads, like the standalone sample does.
-#### Custom window decoration
+### Custom window decoration
The JetBrains Runtime allows windows to have a custom decoration instead of the regular title bar.
-![A screenshot of the custom window decoration in the standalone sample](https://github.com/JetBrains/jewel/blob/main/art/docs/custom-chrome.png?raw=true)
-
The standalone sample app shows how to easily get something that looks like a JetBrains IDE; if you want to go _very_
custom, you only need to depend on the `decorated-window` module, which contains all the required primitives, but not
the Int UI styling.
@@ -163,12 +233,10 @@ To get an IntelliJ-like custom title bar, you need to pass the window decoration
```kotlin
IntUiTheme(
- themeDefinition,
- componentStyling = {
- themeDefinition.decoratedWindowComponentStyling(
- titleBarStyle = TitleBarStyle.light()
- )
- },
+ theme = themeDefinition,
+ styling = ComponentStyling.default().decoratedWindow(
+ titleBarStyle = TitleBarStyle.light()
+ ),
) {
DecoratedWindow(
onCloseRequest = { exitApplication() },
@@ -178,7 +246,7 @@ IntUiTheme(
}
```
-### Running on the IntelliJ Platform: the Swing bridge
+## Running on the IntelliJ Platform: the Swing bridge
Jewel includes a crucial element for proper integration with the IDE: a bridge between the Swing components — theme
and LaF — and the Compose world.
@@ -200,73 +268,107 @@ SwingBridgeTheme {
}
```
-#### Supported IntelliJ Platform versions
+### Supported IntelliJ Platform versions
To use Jewel in the IntelliJ Platform, you should depend on the appropriate `jewel-ide-laf-bridge-*` artifact, which
will bring in the necessary transitive dependencies. These are the currently supported versions of the IntelliJ Platform
and the branch on which the corresponding bridge code lives:
-| IntelliJ Platform version(s) | Branch to use |
- |------------------------------|-------------------|
-| 2024.1 (EAP 3+) | `main` |
-| 2023.3 | `releases/233` |
-| 2023.2 | `releases/232` |
-| 2023.1 or older | **Not supported** |
+| IntelliJ Platform version(s) | Branch to use |
+|------------------------------|-------------------------|
+| 2024.2 (beta 1+) | `main` |
+| 2024.1 (EAP 3+) | `releases/241` |
+| 2023.3 | `releases/233` |
+| 2023.2 (**deprecated**) | `archived-releases/232` |
+| 2023.1 or older | **Not supported** |
For an example on how to set up an IntelliJ Plugin, you can refer to
the [`ide-plugin` sample](https://github.com/JetBrains/jewel/blob/main/samples/ide-plugin/build.gradle.kts).
-#### Accessing icons
+## Icons
-When you want to draw an icon from the resources, you can either use the `Icon` composable and pass it the resource path
-and the corresponding class to look up the classpath from, or go one lever deeper and use the lower level,
-`Painter`-based API.
-
-The `Icon` approach looks like this:
+Loading icons is best done with the `Icon` composable, which offers a key-based API that is portable across bridge and
+standalone modules. Icon keys implement the `IconKey` interface, which is then internally used to obtain a resource path
+to load the icon from.
```kotlin
-// Load the "close" icon from the IDE's AllIcons class
-Icon(
- "actions/close.svg",
- iconClass = AllIcons::class.java,
- contentDescription = "Close",
-)
+Icon(key = MyIconKeys.myIcon, contentDescription = "My icon")
```
-To obtain a `Painter`, instead, you'd use:
+### Loading icons from the IntelliJ Platform
+
+If you want to load an IJ platform icon, you can use `AllIconsKeys`, which is generated from the `AllIcons` platform
+file. When using this in an IJ plugin, make sure you are using a version of the Jewel library matching the platform
+version, because icons are known to shift between major versions — and sometimes, minor versions, too.
+
+To use icons from `AllIconsKeys` in an IJ plugin, you don't need to do anything, as the icons are in the classpath by
+default. If you want to use icons in a standalone app, you'll need to make sure the icons you want are on the classpath.
+You can either copy the necessary icons in your resources, matching exactly the path they have in the IDE, or you can
+add a dependency to the `com.jetbrains.intellij.platform:icons` artifact, which contains all the icons that end up in
+`AllIconsKeys`. The latter is the recommended approach, since it's easy and the icons don't take up much disk space.
+
+Add this to your **standalone app** build script:
```kotlin
-val painterProvider = rememberResourcePainterProvider(
- path = "actions/close.svg",
- iconClass = AllIcons::class.java
-)
-val painter by painterProvider.getPainter()
+dependencies {
+ implementation("com.jetbrains.intellij.platform:icons:[ijpVersion]")
+ // ...
+}
+
+repositories {
+ // Choose either of these two, depending on whether you're using a stable IJP or not
+ maven("https://www.jetbrains.com/intellij-repository/releases")
+ maven("https://www.jetbrains.com/intellij-repository/snapshots")
+}
```
-#### Icon runtime patching
+> [!NOTE]
+> If you are targeting an IntelliJ plugin, you don't need this additional setup since the icons are provided by the
+> platform itself.
-Jewel emulates the under-the-hood machinations that happen in the IntelliJ Platform when loading icons. Specifically,
-the resource will be subject to some transformations before being loaded.
+### Loading your own icons
-For example, in the IDE, if New UI is active, the icon path may be replaced with a different one. Some key colors in SVG
-icons will also be replaced based on the current theme. See
-[the docs](https://plugins.jetbrains.com/docs/intellij/work-with-icons-and-images.html#new-ui-icons).
+To access your own icons, you'll need to create and maintain the `IconKey`s for them. We found that the easiest way when
+you have up to a few dozen icons is to manually create an icon keys holder, like the ones we have in our samples. If you
+have many more, you should consider generating these holders, instead.
-Beyond that, even in standalone, Jewel will pick up icons with the appropriate dark/light variant for the current theme,
-and for bitmap icons it will try to pick the 2x variants based on the `LocalDensity`.
+In your holders, you can choose which implementation of `IconKey` to use:
+* If your icons do not need to change between old UI and new UI, you can use the simpler `PathIconKey`
+* If your icons are different in old and new UI, you should use `IntelliJIconKey`, which accepts two paths, one per
+ variant
+* If you have different needs, you can also implement your own version of `IconKey`
+
+### Painter hints
+
+Jewel has an API to influence the loading and drawing of icons, called `PainterHint`. `Icon` composables have overloads
+that take zero, one or more `PainterHint`s that will be used to compute the end result that shows up on screen.
+
+`PainterHint`s can change the icon path (by adding a prefix/suffix, or changing it completely), tweak the contents of an
+image (SVG patching, XML patching, bitmap patching), add decorations (e.g., badges), or do nothing at all (`None`). We
+have several types of built-in `PainterHint`s which should cover all needs; if you find some use case that is not yet
+handled, please file a feature request and we'll evaluate it.
+
+Both standalone and bridge themes provide a default set of implicit `PainterHint`s, for example to implement runtime
+patching, like the IDE does. You can also use `PainterHint`s to affect how an icon will be drawn, or to select a
+specific icon file, based on some criteria (e.g., `Size`).
If you have a _stateful_ icon, that is if you need to display different icons based on some state, you can use the
-`PainterProvider.getPainter(PainterHint...)` overload. You can then use one of the state-mapping `PainterHint` to let
+`Icon(..., hint)` and `Icon(..., hints)` overloads. You can then use one of the state-mapping `PainterHint` to let
Jewel load the appropriate icon automatically:
```kotlin
// myState implements SelectableComponentState and has a ToggleableState property
-val myPainter by myPainterProvider.getPainter(
+val indeterminateHint =
if (myState.toggleableState == ToggleableState.Indeterminate) {
IndeterminateHint
} else {
PainterHint.None
- },
+ }
+
+Icon(
+ key = myKey,
+ contentDescription = "My icon",
+ indeterminateHint,
Selected(myState),
Stateful(myState),
)
@@ -284,7 +386,22 @@ Assuming the PainterProvider has a base path of `components/myIcon.svg`, Jewel w
right path based on the state. If you want to learn more about this system, look at the `PainterHint` interface and its
implementations.
-### Fonts
+Please look at the `PainterHint` implementations and our samples for further information.
+
+### Default icon runtime patching
+
+Jewel emulates the under-the-hood machinations that happen in the IntelliJ Platform when loading icons. Specifically,
+the resource will be subject to some transformations before being loaded. This is built on the `PainterHint` API we
+described above.
+
+For example, in the IDE, if New UI is active, the icon path may be replaced with a different one. Some key colors in SVG
+icons will also be replaced based on the current theme. See
+[the docs](https://plugins.jetbrains.com/docs/intellij/work-with-icons-and-images.html#new-ui-icons).
+
+Beyond that, even in standalone, Jewel will pick up icons with the appropriate dark/light variant for the current theme,
+and for bitmap icons it will try to pick the 2x variants based on the `LocalDensity`.
+
+## Fonts
To load a system font, you can obtain it by its family name:
@@ -310,15 +427,15 @@ API:
```kotlin
val myAwtFamily = myFont.asComposeFontFamily()
-// This will attempt to resolve the logical AWT font
+// This will attempt to resolve the logical AWT font
val myLogicalFamily = Font("Dialog").asComposeFontFamily()
-// This only works in the IntelliJ Platform,
+// This only works in the IntelliJ Platform,
// since JBFont is only available there
val myLabelFamily = JBFont.label().asComposeFontFamily()
```
-### Swing interoperability
+## Swing interoperability
As this is Compose for Desktop, you get a good degree of interoperability with Swing. To avoid glitches and z-order
issues, you should enable the
@@ -340,6 +457,7 @@ Here is a small selection of projects that use Compose for Desktop and Jewel:
* [Package Search](https://github.com/JetBrains/package-search-intellij-plugin) (IntelliJ Platform plugin)
* [Kotlin Explorer](https://github.com/romainguy/kotlin-explorer) (standalone app)
+* New task-based Profiler UI in Android Studio Koala
* ...and more to come!
## Need help?
@@ -367,5 +485,5 @@ 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.
```
- """
+"""
.trimIndent()
diff --git a/samples/standalone/src/main/kotlin/org/jetbrains/jewel/samples/standalone/viewmodel/ComponentsViewModel.kt b/samples/standalone/src/main/kotlin/org/jetbrains/jewel/samples/standalone/viewmodel/ComponentsViewModel.kt
index 7af0a80e9..4891b35c5 100644
--- a/samples/standalone/src/main/kotlin/org/jetbrains/jewel/samples/standalone/viewmodel/ComponentsViewModel.kt
+++ b/samples/standalone/src/main/kotlin/org/jetbrains/jewel/samples/standalone/viewmodel/ComponentsViewModel.kt
@@ -1,9 +1,11 @@
package org.jetbrains.jewel.samples.standalone.viewmodel
+import SplitLayouts
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateListOf
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.setValue
+import androidx.compose.runtime.snapshots.SnapshotStateList
import org.jetbrains.jewel.samples.standalone.StandaloneSampleIcons
import org.jetbrains.jewel.samples.standalone.view.component.Borders
import org.jetbrains.jewel.samples.standalone.view.component.Buttons
@@ -21,49 +23,78 @@ import org.jetbrains.jewel.samples.standalone.view.component.Tabs
import org.jetbrains.jewel.samples.standalone.view.component.TextAreas
import org.jetbrains.jewel.samples.standalone.view.component.TextFields
import org.jetbrains.jewel.samples.standalone.view.component.Tooltips
+import org.jetbrains.jewel.ui.component.SplitLayoutState
object ComponentsViewModel {
- val views = componentsMenuItems
+ private var outerSplitState by mutableStateOf(SplitLayoutState(0.5f))
+ private var verticalSplitState by mutableStateOf(SplitLayoutState(0.5f))
+ private var innerSplitState by mutableStateOf(SplitLayoutState(0.5f))
+ val views: SnapshotStateList =
+ mutableStateListOf(
+ ViewInfo(title = "Buttons", iconKey = StandaloneSampleIcons.Components.button, content = { Buttons() }),
+ ViewInfo(
+ title = "Radio Buttons",
+ iconKey = StandaloneSampleIcons.Components.radioButton,
+ content = { RadioButtons() },
+ ),
+ ViewInfo(
+ title = "Checkboxes",
+ iconKey = StandaloneSampleIcons.Components.checkbox,
+ content = { Checkboxes() },
+ ),
+ ViewInfo(
+ title = "Dropdowns",
+ iconKey = StandaloneSampleIcons.Components.comboBox,
+ content = { Dropdowns() },
+ ),
+ ViewInfo(
+ title = "Chips and trees",
+ iconKey = StandaloneSampleIcons.Components.tree,
+ content = { ChipsAndTrees() },
+ ),
+ ViewInfo(
+ title = "Progressbar",
+ iconKey = StandaloneSampleIcons.Components.progressBar,
+ content = { ProgressBar() },
+ ),
+ ViewInfo(title = "Icons", iconKey = StandaloneSampleIcons.Components.toolbar, content = { Icons() }),
+ ViewInfo(title = "Links", iconKey = StandaloneSampleIcons.Components.links, content = { Links() }),
+ ViewInfo(title = "Borders", iconKey = StandaloneSampleIcons.Components.borders, content = { Borders() }),
+ ViewInfo(
+ title = "Segmented Controls",
+ iconKey = StandaloneSampleIcons.Components.segmentedControls,
+ content = { SegmentedControls() },
+ ),
+ ViewInfo(title = "Sliders", iconKey = StandaloneSampleIcons.Components.slider, content = { Sliders() }),
+ ViewInfo(title = "Tabs", iconKey = StandaloneSampleIcons.Components.tabs, content = { Tabs() }),
+ ViewInfo(title = "Tooltips", iconKey = StandaloneSampleIcons.Components.tooltip, content = { Tooltips() }),
+ ViewInfo(
+ title = "TextAreas",
+ iconKey = StandaloneSampleIcons.Components.textArea,
+ content = { TextAreas() },
+ ),
+ ViewInfo(
+ title = "TextFields",
+ iconKey = StandaloneSampleIcons.Components.textField,
+ content = { TextFields() },
+ ),
+ ViewInfo(
+ title = "Scrollbars",
+ iconKey = StandaloneSampleIcons.Components.scrollbar,
+ content = { Scrollbars() },
+ ),
+ ViewInfo(
+ title = "SplitLayout",
+ iconKey = StandaloneSampleIcons.Components.splitlayout,
+ content = {
+ SplitLayouts(outerSplitState, verticalSplitState, innerSplitState) {
+ outerSplitState = SplitLayoutState(0.5f)
+ verticalSplitState = SplitLayoutState(0.5f)
+ innerSplitState = SplitLayoutState(0.5f)
+ }
+ },
+ ),
+ )
var currentView by mutableStateOf(views.first())
}
-
-private val componentsMenuItems =
- mutableStateListOf(
- ViewInfo(title = "Buttons", iconKey = StandaloneSampleIcons.Components.button, content = { Buttons() }),
- ViewInfo(
- title = "Radio Buttons",
- iconKey = StandaloneSampleIcons.Components.radioButton,
- content = { RadioButtons() },
- ),
- ViewInfo(title = "Checkboxes", iconKey = StandaloneSampleIcons.Components.checkbox, content = { Checkboxes() }),
- ViewInfo(title = "Dropdowns", iconKey = StandaloneSampleIcons.Components.comboBox, content = { Dropdowns() }),
- ViewInfo(
- title = "Chips and trees",
- iconKey = StandaloneSampleIcons.Components.tree,
- content = { ChipsAndTrees() },
- ),
- ViewInfo(
- title = "Progressbar",
- iconKey = StandaloneSampleIcons.Components.progressBar,
- content = { ProgressBar() },
- ),
- ViewInfo(title = "Icons", iconKey = StandaloneSampleIcons.Components.toolbar, content = { Icons() }),
- ViewInfo(title = "Links", iconKey = StandaloneSampleIcons.Components.links, content = { Links() }),
- ViewInfo(title = "Borders", iconKey = StandaloneSampleIcons.Components.borders, content = { Borders() }),
- ViewInfo(
- title = "Segmented Controls",
- iconKey = StandaloneSampleIcons.Components.segmentedControls,
- content = { SegmentedControls() },
- ),
- ViewInfo(title = "Sliders", iconKey = StandaloneSampleIcons.Components.slider, content = { Sliders() }),
- ViewInfo(title = "Tabs", iconKey = StandaloneSampleIcons.Components.tabs, content = { Tabs() }),
- ViewInfo(title = "Tooltips", iconKey = StandaloneSampleIcons.Components.tooltip, content = { Tooltips() }),
- ViewInfo(title = "TextAreas", iconKey = StandaloneSampleIcons.Components.textArea, content = { TextAreas() }),
- ViewInfo(
- title = "TextFields",
- iconKey = StandaloneSampleIcons.Components.textField,
- content = { TextFields() },
- ),
- ViewInfo(title = "Scrollbars", iconKey = StandaloneSampleIcons.Components.scrollbar, content = { Scrollbars() }),
- )
diff --git a/samples/standalone/src/main/resources/icons/components/splitLayout.svg b/samples/standalone/src/main/resources/icons/components/splitLayout.svg
new file mode 100644
index 000000000..c661e72d4
--- /dev/null
+++ b/samples/standalone/src/main/resources/icons/components/splitLayout.svg
@@ -0,0 +1,7 @@
+
+
diff --git a/samples/standalone/src/main/resources/icons/components/splitLayout_dark.svg b/samples/standalone/src/main/resources/icons/components/splitLayout_dark.svg
new file mode 100644
index 000000000..ae2d9d335
--- /dev/null
+++ b/samples/standalone/src/main/resources/icons/components/splitLayout_dark.svg
@@ -0,0 +1,7 @@
+
+
diff --git a/settings.gradle.kts b/settings.gradle.kts
index 533c46b8c..aba5686ec 100644
--- a/settings.gradle.kts
+++ b/settings.gradle.kts
@@ -28,6 +28,7 @@ dependencyResolutionManagement {
plugins {
id("com.gradle.enterprise") version "3.15.1"
id("org.gradle.toolchains.foojay-resolver-convention") version "0.7.0"
+ id("org.danilopianini.gradle-pre-commit-git-hooks") version "2.0.9"
}
include(
@@ -53,3 +54,47 @@ gradleEnterprise {
termsOfServiceAgree = "yes"
}
}
+
+val isWindows
+ get() = System.getProperty("os.name").contains("win", true)
+
+val gradleCommand: String by
+ lazy(LazyThreadSafetyMode.NONE) {
+ val gradlewFilename =
+ if (isWindows) {
+ "gradlew.bat"
+ } else {
+ "gradlew"
+ }
+
+ val gradlew = File(rootProject.projectDir, gradlewFilename)
+ if (gradlew.exists() && gradlew.isFile && gradlew.canExecute()) {
+ logger.info("Using gradlew wrapper at ${gradlew.invariantSeparatorsPath}")
+ gradlew.invariantSeparatorsPath
+ } else {
+ "gradle"
+ }
+ }
+
+val shebang = if (isWindows) "" else "#!/bin/sh"
+
+gitHooks {
+ hook("pre-push") {
+ from(shebang) {
+ // language=Shell Script
+ """
+ |#### Note: this hook was autogenerated. You can edit it in settings.gradle.kts
+ |GRADLEW=$gradleCommand
+ |if ! ${'$'}GRADLEW ktfmtCheck ; then
+ | ${'$'}GRADLEW ktfmtFormat
+ | echo 1>&2 "\nktfmt found problems; commit the result and re-push"
+ | exit 1
+ |fi
+ |
+ """
+ .trimMargin()
+ }
+ }
+
+ createHooks(overwriteExisting = true)
+}
diff --git a/ui/api/ui.api b/ui/api/ui.api
index a4db0e563..5d93e3acf 100644
--- a/ui/api/ui.api
+++ b/ui/api/ui.api
@@ -497,6 +497,7 @@ public final class org/jetbrains/jewel/ui/component/MenuItemState$Companion {
public final class org/jetbrains/jewel/ui/component/MenuKt {
public static final fun MenuSeparator (Landroidx/compose/ui/Modifier;Lorg/jetbrains/jewel/ui/component/styling/MenuItemMetrics;Lorg/jetbrains/jewel/ui/component/styling/MenuItemColors;Landroidx/compose/runtime/Composer;II)V
public static final fun MenuSubmenuItem (Landroidx/compose/ui/Modifier;ZZLjava/lang/String;Ljava/lang/Class;Landroidx/compose/foundation/interaction/MutableInteractionSource;Lorg/jetbrains/jewel/ui/component/styling/MenuStyle;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function2;Landroidx/compose/runtime/Composer;II)V
+ public static final fun MenuSubmenuItem (Landroidx/compose/ui/Modifier;ZZLorg/jetbrains/jewel/ui/icon/IconKey;Landroidx/compose/foundation/interaction/MutableInteractionSource;Lorg/jetbrains/jewel/ui/component/styling/MenuStyle;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function2;Landroidx/compose/runtime/Composer;II)V
public static final fun PopupMenu (Lkotlin/jvm/functions/Function1;Landroidx/compose/ui/Alignment$Horizontal;Landroidx/compose/ui/Modifier;Lorg/jetbrains/jewel/ui/component/styling/MenuStyle;Landroidx/compose/ui/window/PopupProperties;Lkotlin/jvm/functions/Function1;Landroidx/compose/runtime/Composer;II)V
public static final fun items (Lorg/jetbrains/jewel/ui/component/MenuScope;ILkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function3;)V
public static final fun items (Lorg/jetbrains/jewel/ui/component/MenuScope;Ljava/util/List;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function3;)V
@@ -521,13 +522,13 @@ public final class org/jetbrains/jewel/ui/component/MenuManagerKt {
public abstract interface class org/jetbrains/jewel/ui/component/MenuScope {
public abstract fun passiveItem (Lkotlin/jvm/functions/Function2;)V
- public abstract fun selectableItem (ZLorg/jetbrains/jewel/ui/icon/IconKey;Ljava/lang/Class;Ljava/util/Set;Lkotlin/jvm/functions/Function0;ZLkotlin/jvm/functions/Function2;)V
- public abstract fun submenu (ZLjava/lang/String;Ljava/lang/Class;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function2;)V
+ public abstract fun selectableItem (ZLorg/jetbrains/jewel/ui/icon/IconKey;Ljava/util/Set;Lkotlin/jvm/functions/Function0;ZLkotlin/jvm/functions/Function2;)V
+ public abstract fun submenu (ZLorg/jetbrains/jewel/ui/icon/IconKey;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function2;)V
}
public final class org/jetbrains/jewel/ui/component/MenuScope$DefaultImpls {
- public static synthetic fun selectableItem$default (Lorg/jetbrains/jewel/ui/component/MenuScope;ZLorg/jetbrains/jewel/ui/icon/IconKey;Ljava/lang/Class;Ljava/util/Set;Lkotlin/jvm/functions/Function0;ZLkotlin/jvm/functions/Function2;ILjava/lang/Object;)V
- public static synthetic fun submenu$default (Lorg/jetbrains/jewel/ui/component/MenuScope;ZLjava/lang/String;Ljava/lang/Class;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function2;ILjava/lang/Object;)V
+ public static synthetic fun selectableItem$default (Lorg/jetbrains/jewel/ui/component/MenuScope;ZLorg/jetbrains/jewel/ui/icon/IconKey;Ljava/util/Set;Lkotlin/jvm/functions/Function0;ZLkotlin/jvm/functions/Function2;ILjava/lang/Object;)V
+ public static synthetic fun submenu$default (Lorg/jetbrains/jewel/ui/component/MenuScope;ZLorg/jetbrains/jewel/ui/icon/IconKey;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function2;ILjava/lang/Object;)V
}
public final class org/jetbrains/jewel/ui/component/PlatformIconKt {
@@ -754,8 +755,18 @@ public final class org/jetbrains/jewel/ui/component/SliderState$Companion {
}
public final class org/jetbrains/jewel/ui/component/SplitLayoutKt {
- public static final fun HorizontalSplitLayout-BssWTFQ (Lkotlin/jvm/functions/Function3;Lkotlin/jvm/functions/Function3;Landroidx/compose/ui/Modifier;JFFFFFFLandroidx/compose/runtime/Composer;II)V
- public static final fun VerticalSplitLayout-BssWTFQ (Lkotlin/jvm/functions/Function3;Lkotlin/jvm/functions/Function3;Landroidx/compose/ui/Modifier;JFFFFFFLandroidx/compose/runtime/Composer;II)V
+ public static final fun HorizontalSplitLayout-Zv8xjqY (Lkotlin/jvm/functions/Function2;Lkotlin/jvm/functions/Function2;Landroidx/compose/ui/Modifier;Lorg/jetbrains/jewel/ui/component/styling/DividerStyle;FFFLorg/jetbrains/jewel/ui/component/SplitLayoutState;Landroidx/compose/runtime/Composer;II)V
+ public static final fun VerticalSplitLayout-Zv8xjqY (Lkotlin/jvm/functions/Function2;Lkotlin/jvm/functions/Function2;Landroidx/compose/ui/Modifier;Lorg/jetbrains/jewel/ui/component/styling/DividerStyle;FFFLorg/jetbrains/jewel/ui/component/SplitLayoutState;Landroidx/compose/runtime/Composer;II)V
+ public static final fun rememberSplitLayoutState (FLandroidx/compose/runtime/Composer;II)Lorg/jetbrains/jewel/ui/component/SplitLayoutState;
+}
+
+public final class org/jetbrains/jewel/ui/component/SplitLayoutState {
+ public static final field $stable I
+ public fun (F)V
+ public final fun getDividerPosition ()F
+ public final fun getLayoutCoordinates ()Landroidx/compose/ui/layout/LayoutCoordinates;
+ public final fun setDividerPosition (F)V
+ public final fun setLayoutCoordinates (Landroidx/compose/ui/layout/LayoutCoordinates;)V
}
public abstract interface class org/jetbrains/jewel/ui/component/TabContentScope {
diff --git a/ui/src/main/kotlin/org/jetbrains/jewel/ui/component/Icon.kt b/ui/src/main/kotlin/org/jetbrains/jewel/ui/component/Icon.kt
index 24e2db8cc..a5afb3973 100644
--- a/ui/src/main/kotlin/org/jetbrains/jewel/ui/component/Icon.kt
+++ b/ui/src/main/kotlin/org/jetbrains/jewel/ui/component/Icon.kt
@@ -32,6 +32,7 @@ import androidx.compose.ui.semantics.role
import androidx.compose.ui.semantics.semantics
import androidx.compose.ui.unit.dp
import java.io.InputStream
+import org.jetbrains.annotations.ApiStatus.ScheduledForRemoval
import org.jetbrains.jewel.foundation.theme.JewelTheme
import org.jetbrains.jewel.ui.icon.IconKey
import org.jetbrains.jewel.ui.icon.newUiChecker
@@ -48,6 +49,7 @@ import org.xml.sax.InputSource
"org.jetbrains.jewel.ui.icon.PathIconKey",
),
)
+@ScheduledForRemoval(inVersion = "Before 1.0")
@Composable
public fun Icon(
resource: String,
@@ -71,6 +73,7 @@ public fun Icon(
"org.jetbrains.jewel.ui.icon.PathIconKey",
),
)
+@ScheduledForRemoval(inVersion = "Before 1.0")
@Composable
public fun Icon(
resource: String,
@@ -94,6 +97,7 @@ public fun Icon(
"org.jetbrains.jewel.ui.icon.PathIconKey",
),
)
+@ScheduledForRemoval(inVersion = "Before 1.0")
@Composable
public fun Icon(
resource: String,
@@ -117,6 +121,7 @@ public fun Icon(
"org.jetbrains.jewel.ui.icon.PathIconKey",
),
)
+@ScheduledForRemoval(inVersion = "Before 1.0")
@Composable
public fun Icon(
resource: String,
diff --git a/ui/src/main/kotlin/org/jetbrains/jewel/ui/component/Menu.kt b/ui/src/main/kotlin/org/jetbrains/jewel/ui/component/Menu.kt
index 4317e9040..c76b78a70 100644
--- a/ui/src/main/kotlin/org/jetbrains/jewel/ui/component/Menu.kt
+++ b/ui/src/main/kotlin/org/jetbrains/jewel/ui/component/Menu.kt
@@ -82,6 +82,7 @@ import org.jetbrains.jewel.ui.component.styling.MenuItemColors
import org.jetbrains.jewel.ui.component.styling.MenuItemMetrics
import org.jetbrains.jewel.ui.component.styling.MenuStyle
import org.jetbrains.jewel.ui.icon.IconKey
+import org.jetbrains.jewel.ui.icon.PathIconKey
import org.jetbrains.jewel.ui.painter.hints.Stateful
import org.jetbrains.jewel.ui.theme.menuStyle
import org.jetbrains.skiko.hostOs
@@ -187,7 +188,6 @@ private fun ShowMenuItem(item: MenuItem, canShowIcon: Boolean = false, canShowKe
canShowIcon = canShowIcon,
canShowKeybinding = canShowKeybinding,
iconKey = item.iconKey,
- iconClass = item.iconClass,
keybinding = item.keybinding,
content = item.content,
)
@@ -197,8 +197,7 @@ private fun ShowMenuItem(item: MenuItem, canShowIcon: Boolean = false, canShowKe
enabled = item.isEnabled,
submenu = item.submenu,
canShowIcon = canShowIcon,
- iconResource = item.iconResource,
- iconClass = item.iconClass,
+ iconKey = item.iconKey,
content = item.content,
)
@@ -210,7 +209,6 @@ public interface MenuScope {
public fun selectableItem(
selected: Boolean,
iconKey: IconKey? = null,
- iconClass: Class<*>? = iconKey?.let { it::class.java },
keybinding: Set? = null,
onClick: () -> Unit,
enabled: Boolean = true,
@@ -219,8 +217,7 @@ public interface MenuScope {
public fun submenu(
enabled: Boolean = true,
- iconResource: String? = null,
- iconClass: Class<*> = this::class.java,
+ iconKey: IconKey? = null,
submenu: MenuScope.() -> Unit,
content: @Composable () -> Unit,
)
@@ -258,7 +255,6 @@ private fun (MenuScope.() -> Unit).asList() = buildList {
override fun selectableItem(
selected: Boolean,
iconKey: IconKey?,
- iconClass: Class<*>?,
keybinding: Set?,
onClick: () -> Unit,
enabled: Boolean,
@@ -269,7 +265,6 @@ private fun (MenuScope.() -> Unit).asList() = buildList {
isSelected = selected,
isEnabled = enabled,
iconKey = iconKey,
- iconClass = iconClass,
keybinding = keybinding,
onClick = onClick,
content = content,
@@ -283,12 +278,11 @@ private fun (MenuScope.() -> Unit).asList() = buildList {
override fun submenu(
enabled: Boolean,
- iconResource: String?,
- iconClass: Class<*>,
+ iconKey: IconKey?,
submenu: MenuScope.() -> Unit,
content: @Composable () -> Unit,
) {
- add(SubmenuItem(enabled, iconResource, iconClass, submenu, content))
+ add(SubmenuItem(enabled, iconKey, submenu, content))
}
}
)
@@ -302,7 +296,6 @@ private data class MenuSelectableItem(
val isSelected: Boolean,
val isEnabled: Boolean,
val iconKey: IconKey?,
- val iconClass: Class<*>?,
val keybinding: Set?,
val onClick: () -> Unit = {},
override val content: @Composable () -> Unit,
@@ -312,8 +305,7 @@ private data class MenuPassiveItem(override val content: @Composable () -> Unit)
private data class SubmenuItem(
val isEnabled: Boolean = true,
- val iconResource: String?,
- val iconClass: Class<*>,
+ val iconKey: IconKey?,
val submenu: MenuScope.() -> Unit,
override val content: @Composable () -> Unit,
) : MenuItem
@@ -341,7 +333,6 @@ internal fun MenuItem(
modifier: Modifier = Modifier,
enabled: Boolean = true,
iconKey: IconKey?,
- iconClass: Class<*>?,
keybinding: Set?,
canShowIcon: Boolean,
canShowKeybinding: Boolean,
@@ -421,12 +412,7 @@ internal fun MenuItem(
if (canShowIcon) {
val iconModifier = Modifier.size(style.metrics.itemMetrics.iconSize)
if (iconKey != null) {
- Icon(
- key = iconKey,
- contentDescription = null,
- iconClass = iconClass ?: iconKey.javaClass,
- modifier = iconModifier,
- )
+ Icon(key = iconKey, contentDescription = null, modifier = iconModifier)
} else {
Box(modifier = iconModifier)
}
@@ -454,6 +440,14 @@ internal fun MenuItem(
}
}
+@Deprecated(
+ "Use the IconKey variant",
+ ReplaceWith(
+ "MenuSubmenuItem(modifier, enabled, canShowIcon, iconResource?.let { PathIconKey(it, iconClass) }, " +
+ "interactionSource, style, submenu, content)",
+ "org/jetbrains/jewel/ui/component/Menu.kt:472",
+ ),
+)
@Composable
public fun MenuSubmenuItem(
modifier: Modifier = Modifier,
@@ -465,6 +459,21 @@ public fun MenuSubmenuItem(
style: MenuStyle = JewelTheme.menuStyle,
submenu: MenuScope.() -> Unit,
content: @Composable () -> Unit,
+) {
+ val iconKey = remember(iconResource, iconClass) { iconResource?.let { PathIconKey(it, iconClass) } }
+ MenuSubmenuItem(modifier, enabled, canShowIcon, iconKey, interactionSource, style, submenu, content)
+}
+
+@Composable
+public fun MenuSubmenuItem(
+ modifier: Modifier = Modifier,
+ enabled: Boolean = true,
+ canShowIcon: Boolean,
+ iconKey: IconKey?,
+ interactionSource: MutableInteractionSource = remember { MutableInteractionSource() },
+ style: MenuStyle = JewelTheme.menuStyle,
+ submenu: MenuScope.() -> Unit,
+ content: @Composable () -> Unit,
) {
var itemState by
remember(interactionSource) { mutableStateOf(MenuItemState.of(selected = false, enabled = enabled)) }
@@ -525,8 +534,8 @@ public fun MenuSubmenuItem(
horizontalArrangement = Arrangement.spacedBy(4.dp),
) {
if (canShowIcon) {
- if (iconResource != null) {
- Icon(resource = iconResource, iconClass = iconClass, contentDescription = "")
+ if (iconKey != null) {
+ Icon(key = iconKey, contentDescription = null)
} else {
Box(Modifier.size(style.metrics.itemMetrics.iconSize))
}
diff --git a/ui/src/main/kotlin/org/jetbrains/jewel/ui/component/PlatformIcon.kt b/ui/src/main/kotlin/org/jetbrains/jewel/ui/component/PlatformIcon.kt
index 7309a54ff..8a097d3b2 100644
--- a/ui/src/main/kotlin/org/jetbrains/jewel/ui/component/PlatformIcon.kt
+++ b/ui/src/main/kotlin/org/jetbrains/jewel/ui/component/PlatformIcon.kt
@@ -3,9 +3,15 @@ package org.jetbrains.jewel.ui.component
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
+import org.jetbrains.annotations.ApiStatus.ScheduledForRemoval
import org.jetbrains.jewel.ui.icon.IntelliJIconKey
import org.jetbrains.jewel.ui.painter.PainterHint
+@Deprecated(
+ "Use Icon directly, this doesn't have any advantage over it anymore.",
+ ReplaceWith("Icon(key, contentDescription, modifier, tint, hint)", "com.jewel.ui.component.Icon"),
+)
+@ScheduledForRemoval(inVersion = "Before 1.0")
@Composable
public fun PlatformIcon(
key: IntelliJIconKey,
@@ -14,9 +20,15 @@ public fun PlatformIcon(
tint: Color = Color.Unspecified,
hint: PainterHint,
) {
+ @Suppress("DEPRECATION") // Everything is deprecated here anyway
PlatformIcon(key, contentDescription, modifier, tint, *arrayOf(hint))
}
+@Deprecated(
+ "Use Icon directly, this doesn't have any advantage over it anymore.",
+ ReplaceWith("Icon(key, contentDescription, modifier, tint, hints)", "com.jewel.ui.component.Icon"),
+)
+@ScheduledForRemoval(inVersion = "Before 1.0")
@Composable
public fun PlatformIcon(
key: IntelliJIconKey,
diff --git a/ui/src/main/kotlin/org/jetbrains/jewel/ui/component/SplitLayout.kt b/ui/src/main/kotlin/org/jetbrains/jewel/ui/component/SplitLayout.kt
index 6d3d43416..1ba463a39 100644
--- a/ui/src/main/kotlin/org/jetbrains/jewel/ui/component/SplitLayout.kt
+++ b/ui/src/main/kotlin/org/jetbrains/jewel/ui/component/SplitLayout.kt
@@ -1,6 +1,9 @@
+@file:Suppress("DuplicatedCode") // It's similar, but not exactly the same
+
package org.jetbrains.jewel.ui.component
-import androidx.compose.foundation.gestures.Orientation as ComposeOrientation
+import androidx.compose.foundation.focusable
+import androidx.compose.foundation.gestures.Orientation
import androidx.compose.foundation.gestures.draggable
import androidx.compose.foundation.gestures.rememberDraggableState
import androidx.compose.foundation.interaction.MutableInteractionSource
@@ -15,184 +18,453 @@ import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
-import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.geometry.Rect
import androidx.compose.ui.input.pointer.PointerIcon
import androidx.compose.ui.input.pointer.pointerHoverIcon
import androidx.compose.ui.layout.Layout
+import androidx.compose.ui.layout.LayoutCoordinates
+import androidx.compose.ui.layout.Measurable
+import androidx.compose.ui.layout.MeasureResult
+import androidx.compose.ui.layout.MeasureScope
import androidx.compose.ui.layout.layoutId
+import androidx.compose.ui.layout.onGloballyPositioned
import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.unit.Constraints
+import androidx.compose.ui.unit.Density
import androidx.compose.ui.unit.Dp
+import androidx.compose.ui.unit.LayoutDirection
import androidx.compose.ui.unit.dp
-import java.awt.Cursor
import kotlin.math.roundToInt
import org.jetbrains.jewel.foundation.theme.JewelTheme
-import org.jetbrains.jewel.ui.Orientation
+import org.jetbrains.jewel.ui.Orientation.Horizontal
+import org.jetbrains.jewel.ui.Orientation.Vertical
+import org.jetbrains.jewel.ui.component.styling.DividerStyle
+import org.jetbrains.jewel.ui.theme.dividerStyle
+import org.jetbrains.skiko.Cursor
+/**
+ * A customizable horizontal split layout Composable function that allows you to divide the available space between two
+ * components using a draggable divider. The divider can be dragged to resize the panes, but cannot be focused.
+ *
+ * @param first The Composable function representing the first component, that will be placed on one side of the
+ * divider, typically on the left or above.
+ * @param second The Composable function representing the second component, that will be placed on the other side of the
+ * divider, typically on the right or below.
+ * @param modifier The modifier to be applied to the layout.
+ * @param draggableWidth The width of the draggable area around the divider. This is a invisible, wider area around the
+ * divider that can be dragged by the user to resize the panes.
+ * @param firstPaneMinWidth The minimum size of the first component.
+ * @param secondPaneMinWidth The minimum size of the second component.
+ * @param dividerStyle The divider style to be applied to the layout.
+ * @param state The [SplitLayoutState] object that will be used to store the split state.
+ */
@Composable
public fun HorizontalSplitLayout(
- first: @Composable (Modifier) -> Unit,
- second: @Composable (Modifier) -> Unit,
+ first: @Composable () -> Unit,
+ second: @Composable () -> Unit,
modifier: Modifier = Modifier,
- dividerColor: Color = JewelTheme.globalColors.borders.normal,
- dividerThickness: Dp = 1.dp,
- dividerIndent: Dp = 0.dp,
+ dividerStyle: DividerStyle = JewelTheme.dividerStyle,
draggableWidth: Dp = 8.dp,
- minRatio: Float = 0f,
- maxRatio: Float = 1f,
- initialDividerPosition: Dp = 300.dp,
+ firstPaneMinWidth: Dp = Dp.Unspecified,
+ secondPaneMinWidth: Dp = Dp.Unspecified,
+ state: SplitLayoutState = rememberSplitLayoutState(),
+) {
+ SplitLayoutImpl(
+ first = first,
+ second = second,
+ modifier = modifier,
+ dividerStyle = dividerStyle,
+ draggableWidth = draggableWidth,
+ firstPaneMinWidth = firstPaneMinWidth,
+ secondPaneMinWidth = secondPaneMinWidth,
+ strategy = horizontalTwoPaneStrategy(),
+ state = state,
+ )
+}
+
+/**
+ * A customizable vertical split layout Composable function that allows you to divide the available space between two
+ * components using a draggable divider. The divider can be dragged to resize the panes, but cannot be focused.
+ *
+ * @param first The Composable function representing the first component, that will be placed on one side of the
+ * divider, typically on the left or above.
+ * @param second The Composable function representing the second component, that will be placed on the other side of the
+ * divider, typically on the right or below.
+ * @param modifier The modifier to be applied to the layout.
+ * @param draggableWidth The width of the draggable area around the divider. This is a invisible, wider area around the
+ * divider that can be dragged by the user to resize the panes.
+ * @param firstPaneMinWidth The minimum size of the first component.
+ * @param secondPaneMinWidth The minimum size of the second component.
+ * @param dividerStyle The divider style to be applied to the layout.
+ * @param state The [SplitLayoutState] object that will be used to store the split state.
+ */
+@Composable
+public fun VerticalSplitLayout(
+ first: @Composable () -> Unit,
+ second: @Composable () -> Unit,
+ modifier: Modifier = Modifier,
+ dividerStyle: DividerStyle = JewelTheme.dividerStyle,
+ draggableWidth: Dp = 8.dp,
+ firstPaneMinWidth: Dp = Dp.Unspecified,
+ secondPaneMinWidth: Dp = Dp.Unspecified,
+ state: SplitLayoutState = rememberSplitLayoutState(),
+) {
+ SplitLayoutImpl(
+ first = first,
+ second = second,
+ modifier = modifier,
+ dividerStyle = dividerStyle,
+ draggableWidth = draggableWidth,
+ firstPaneMinWidth = firstPaneMinWidth,
+ secondPaneMinWidth = secondPaneMinWidth,
+ strategy = verticalTwoPaneStrategy(),
+ state = state,
+ )
+}
+
+/**
+ * Represents the state for a split layout, which is used to control the position of the divider and layout coordinates.
+ *
+ * @param initialSplitFraction The initial fraction value that determines the position of the divider.
+ * @constructor Creates a [SplitLayoutState] with the given initial split fraction.
+ */
+public class SplitLayoutState(initialSplitFraction: Float) {
+ /**
+ * A mutable floating-point value representing the position of the divider within the split layout. The position is
+ * expressed as a fraction of the total layout size, ranging from 0.0 (divider at the start) to 1.0 (divider at the
+ * end). This allows dynamic adjustment of the layout's two panes, reflecting the current state of the divider's
+ * placement.
+ */
+ public var dividerPosition: Float by mutableStateOf(initialSplitFraction.coerceIn(0f, 1f))
+
+ /**
+ * Holds the layout coordinates for the split layout. These coordinates are used to track the position and size of
+ * the layout parts, facilitating the adjustment of the divider and the layout's panes during interactions like
+ * dragging.
+ */
+ public var layoutCoordinates: LayoutCoordinates? by mutableStateOf(null)
+}
+
+/**
+ * Remembers a [SplitLayoutState] instance with the provided initial split fraction.
+ *
+ * @param initialSplitFraction The initial fraction value that determines the position of the divider.
+ * @return A remembered [SplitLayoutState] instance.
+ */
+@Composable
+public fun rememberSplitLayoutState(initialSplitFraction: Float = 0.5f): SplitLayoutState = remember {
+ SplitLayoutState(initialSplitFraction)
+}
+
+private val HorizontalResizePointerIcon = PointerIcon(Cursor(Cursor.E_RESIZE_CURSOR))
+private val VerticalResizePointerIcon = PointerIcon(Cursor(Cursor.N_RESIZE_CURSOR))
+
+@Composable
+private fun SplitLayoutImpl(
+ first: @Composable () -> Unit,
+ second: @Composable () -> Unit,
+ strategy: SplitLayoutStrategy,
+ modifier: Modifier,
+ draggableWidth: Dp,
+ firstPaneMinWidth: Dp,
+ secondPaneMinWidth: Dp,
+ dividerStyle: DividerStyle,
+ state: SplitLayoutState,
) {
val density = LocalDensity.current
- var dividerX by remember { mutableStateOf(with(density) { initialDividerPosition.roundToPx() }) }
+ var isDragging by remember { mutableStateOf(false) }
+ val resizePointerIcon = if (strategy.isHorizontal()) HorizontalResizePointerIcon else VerticalResizePointerIcon
+
+ var dragOffset by remember { mutableStateOf(0f) }
+
+ val draggableState = rememberDraggableState { delta ->
+ state.layoutCoordinates?.let { coordinates ->
+ val size = if (strategy.isHorizontal()) coordinates.size.width else coordinates.size.height
+ val minFirstPositionPx = with(density) { firstPaneMinWidth.toPx() }
+ val minSecondPositionPx = with(density) { secondPaneMinWidth.toPx() }
+
+ dragOffset += delta
+ val position = size * state.dividerPosition + dragOffset
+ val newPosition = position.coerceIn(minFirstPositionPx, size - minSecondPositionPx)
+ state.dividerPosition = newPosition / size
+ }
+ }
Layout(
- modifier = modifier,
+ modifier =
+ modifier
+ .onGloballyPositioned { coordinates ->
+ state.layoutCoordinates = coordinates
+ // Reset drag offset when layout changes
+ dragOffset = 0f
+ }
+ .pointerHoverIcon(if (isDragging) resizePointerIcon else PointerIcon.Default),
content = {
+ Box(Modifier.layoutId("first")) { first() }
+ Box(Modifier.layoutId("second")) { second() }
+
val dividerInteractionSource = remember { MutableInteractionSource() }
- first(Modifier.layoutId("first"))
+ val dividerOrientation = if (strategy.isHorizontal()) Vertical else Horizontal
+ val fillModifier = if (strategy.isHorizontal()) Modifier.fillMaxHeight() else Modifier.fillMaxWidth()
+ val orientation = if (strategy.isHorizontal()) Orientation.Horizontal else Orientation.Vertical
Divider(
- orientation = Orientation.Vertical,
- modifier = Modifier.fillMaxHeight().layoutId("divider"),
- color = dividerColor,
- thickness = dividerThickness,
- startIndent = dividerIndent,
+ orientation = dividerOrientation,
+ modifier = fillModifier.layoutId("divider").focusable(false),
+ color = dividerStyle.color,
+ thickness = dividerStyle.metrics.thickness,
)
- second(Modifier.layoutId("second"))
-
Box(
- Modifier.fillMaxHeight()
- .width(draggableWidth)
+ Modifier.let { modifier ->
+ if (strategy.isHorizontal()) {
+ modifier.fillMaxHeight().width(draggableWidth)
+ } else {
+ modifier.fillMaxWidth().height(draggableWidth)
+ }
+ }
.draggable(
+ orientation = orientation,
+ state = draggableState,
+ onDragStarted = {
+ isDragging = true
+ dragOffset = 0f
+ },
+ onDragStopped = {
+ isDragging = false
+ dragOffset = 0f
+ },
interactionSource = dividerInteractionSource,
- orientation = ComposeOrientation.Horizontal,
- state = rememberDraggableState { delta -> dividerX += delta.toInt() },
)
- .pointerHoverIcon(PointerIcon(Cursor(Cursor.E_RESIZE_CURSOR)))
+ .pointerHoverIcon(resizePointerIcon)
.layoutId("divider-handle")
+ .focusable(false)
)
},
- ) { measurables, incomingConstraints ->
- val availableWidth = incomingConstraints.maxWidth
- val actualDividerX =
- dividerX
- .coerceIn(0, availableWidth)
- .coerceIn((availableWidth * minRatio).roundToInt(), (availableWidth * maxRatio).roundToInt())
-
- val dividerMeasurable = measurables.single { it.layoutId == "divider" }
- val dividerPlaceable =
- dividerMeasurable.measure(Constraints.fixed(dividerThickness.roundToPx(), incomingConstraints.maxHeight))
-
- val firstComponentConstraints =
- Constraints.fixed((actualDividerX).coerceAtLeast(0), incomingConstraints.maxHeight)
- val firstPlaceable =
- measurables.find { it.layoutId == "first" }?.measure(firstComponentConstraints)
- ?: error("No first component found. Have you applied the provided Modifier to it?")
-
- val secondComponentConstraints =
- Constraints.fixed(
- width = availableWidth - actualDividerX + dividerPlaceable.width,
- height = incomingConstraints.maxHeight,
+ ) { measurables, constraints ->
+ if (state.layoutCoordinates == null) {
+ notReadyLayout(constraints)
+ } else {
+ doLayout(
+ strategy,
+ density,
+ state,
+ dividerStyle,
+ draggableWidth,
+ firstPaneMinWidth,
+ secondPaneMinWidth,
+ constraints,
+ measurables,
)
- val secondPlaceable =
- measurables.find { it.layoutId == "second" }?.measure(secondComponentConstraints)
- ?: error("No second component found. Have you applied the provided Modifier to it?")
-
- val dividerHandlePlaceable =
- measurables
- .single { it.layoutId == "divider-handle" }
- .measure(Constraints.fixedHeight(incomingConstraints.maxHeight))
-
- layout(availableWidth, incomingConstraints.maxHeight) {
- firstPlaceable.placeRelative(0, 0)
- dividerPlaceable.placeRelative(actualDividerX - dividerPlaceable.width / 2, 0)
- secondPlaceable.placeRelative(actualDividerX + dividerPlaceable.width, 0)
- dividerHandlePlaceable.placeRelative(actualDividerX - dividerHandlePlaceable.measuredWidth / 2, 0)
}
}
}
-@Composable
-public fun VerticalSplitLayout(
- first: @Composable (Modifier) -> Unit,
- second: @Composable (Modifier) -> Unit,
- modifier: Modifier = Modifier,
- dividerColor: Color = JewelTheme.globalColors.borders.normal,
- dividerThickness: Dp = 1.dp,
- dividerIndent: Dp = 0.dp,
- draggableWidth: Dp = 8.dp,
- minRatio: Float = 0f,
- maxRatio: Float = 1f,
- initialDividerPosition: Dp = 300.dp,
-) {
- val density = LocalDensity.current
- var dividerY by remember { mutableStateOf(with(density) { initialDividerPosition.roundToPx() }) }
+private fun MeasureScope.notReadyLayout(constraints: Constraints) =
+ layout(constraints.minWidth, constraints.minHeight) {}
- Layout(
- modifier = modifier,
- content = {
- val dividerInteractionSource = remember { MutableInteractionSource() }
- first(Modifier.layoutId("first"))
+private fun MeasureScope.doLayout(
+ strategy: SplitLayoutStrategy,
+ density: Density,
+ state: SplitLayoutState,
+ dividerStyle: DividerStyle,
+ draggableWidth: Dp,
+ firstPaneMinWidth: Dp,
+ secondPaneMinWidth: Dp,
+ constraints: Constraints,
+ measurables: List,
+): MeasureResult {
+ val firstMeasurable = measurables.find { it.layoutId == "first" } ?: error("No first component found.")
+ val secondMeasurable = measurables.find { it.layoutId == "second" } ?: error("No second component found.")
+ val dividerMeasurable = measurables.find { it.layoutId == "divider" } ?: error("No divider component found.")
+ val dividerHandleMeasurable =
+ measurables.find { it.layoutId == "divider-handle" } ?: error("No divider-handle component found.")
- Divider(
- orientation = Orientation.Horizontal,
- modifier = Modifier.fillMaxHeight().layoutId("divider"),
- color = dividerColor,
- thickness = dividerThickness,
- startIndent = dividerIndent,
- )
+ val splitResult = strategy.calculateSplitResult(density = density, layoutDirection = layoutDirection, state = state)
- second(Modifier.layoutId("second"))
+ val gapOrientation = splitResult.gapOrientation
+ val gapBounds = splitResult.gapBounds
- Box(
- Modifier.fillMaxWidth()
- .height(draggableWidth)
- .draggable(
- interactionSource = dividerInteractionSource,
- orientation = ComposeOrientation.Vertical,
- state = rememberDraggableState { delta -> dividerY += delta.toInt() },
+ val dividerWidth = with(density) { dividerStyle.metrics.thickness.roundToPx() }
+ val handleWidth = with(density) { draggableWidth.roundToPx() }
+ val minFirstPaneSizePx = with(density) { firstPaneMinWidth.roundToPx() }
+ val minSecondPaneSizePx = with(density) { secondPaneMinWidth.roundToPx() }
+
+ // The visual divider itself. It's a thin line that separates the two panes
+ val dividerPlaceable =
+ dividerMeasurable.measure(
+ when (gapOrientation) {
+ Orientation.Vertical -> {
+ constraints.copy(
+ minWidth = dividerWidth,
+ maxWidth = dividerWidth,
+ minHeight = constraints.minHeight,
+ maxHeight = constraints.maxHeight,
)
- .pointerHoverIcon(PointerIcon(Cursor(Cursor.N_RESIZE_CURSOR)))
- .layoutId("divider-handle")
+ }
+
+ Orientation.Horizontal -> {
+ constraints.copy(
+ minWidth = constraints.minWidth,
+ maxWidth = constraints.maxWidth,
+ minHeight = dividerWidth,
+ maxHeight = dividerWidth,
+ )
+ }
+ }
+ )
+
+ // This is an invisible, wider area around the divider that can be dragged by the user
+ // to resize the panes
+ val dividerHandlePlaceable =
+ dividerHandleMeasurable.measure(
+ when (gapOrientation) {
+ Orientation.Vertical -> {
+ constraints.copy(
+ minWidth = handleWidth,
+ maxWidth = handleWidth,
+ minHeight = constraints.minHeight,
+ maxHeight = constraints.maxHeight,
+ )
+ }
+
+ Orientation.Horizontal -> {
+ constraints.copy(
+ minWidth = constraints.minWidth,
+ maxWidth = constraints.maxWidth,
+ minHeight = handleWidth,
+ maxHeight = handleWidth,
+ )
+ }
+ }
+ )
+
+ val availableSpace =
+ if (gapOrientation == Orientation.Vertical) {
+ (constraints.maxWidth - dividerWidth).coerceAtLeast(1)
+ } else {
+ (constraints.maxHeight - dividerWidth).coerceAtLeast(1)
+ }
+
+ val (adjustedFirstSize, adjustedSecondSize) =
+ calculateAdjustedSizes(availableSpace, minFirstPaneSizePx, minSecondPaneSizePx)
+
+ val firstGap =
+ when (gapOrientation) {
+ Orientation.Vertical -> gapBounds.left
+ Orientation.Horizontal -> gapBounds.top
+ }
+
+ val firstSize: Int = firstGap.roundToInt().coerceIn(adjustedFirstSize, availableSpace - adjustedSecondSize)
+
+ val secondSize = availableSpace - firstSize
+
+ val firstConstraints =
+ when (gapOrientation) {
+ Orientation.Vertical -> constraints.copy(minWidth = adjustedFirstSize, maxWidth = firstSize)
+ Orientation.Horizontal -> constraints.copy(minHeight = adjustedFirstSize, maxHeight = firstSize)
+ }
+
+ val secondConstraints =
+ when (gapOrientation) {
+ Orientation.Vertical -> constraints.copy(minWidth = adjustedSecondSize, maxWidth = secondSize)
+ Orientation.Horizontal -> constraints.copy(minHeight = adjustedSecondSize, maxHeight = secondSize)
+ }
+
+ val firstPlaceable = firstMeasurable.measure(firstConstraints)
+ val secondPlaceable = secondMeasurable.measure(secondConstraints)
+
+ return layout(constraints.maxWidth, constraints.maxHeight) {
+ firstPlaceable.placeRelative(0, 0)
+ when (gapOrientation) {
+ Orientation.Vertical -> {
+ dividerPlaceable.placeRelative(firstSize, 0)
+ dividerHandlePlaceable.placeRelative(firstSize - handleWidth / 2, 0)
+ secondPlaceable.placeRelative(firstSize + dividerWidth, 0)
+ }
+
+ Orientation.Horizontal -> {
+ dividerPlaceable.placeRelative(0, firstSize)
+ dividerHandlePlaceable.placeRelative(0, firstSize - handleWidth / 2)
+ secondPlaceable.placeRelative(0, firstSize + dividerWidth)
+ }
+ }
+ }
+}
+
+private class SplitResult(val gapOrientation: Orientation, val gapBounds: Rect)
+
+private interface SplitLayoutStrategy {
+ fun calculateSplitResult(density: Density, layoutDirection: LayoutDirection, state: SplitLayoutState): SplitResult
+
+ fun isHorizontal(): Boolean
+}
+
+private fun horizontalTwoPaneStrategy(gapWidth: Dp = 0.dp): SplitLayoutStrategy =
+ object : SplitLayoutStrategy {
+ override fun calculateSplitResult(
+ density: Density,
+ layoutDirection: LayoutDirection,
+ state: SplitLayoutState,
+ ): SplitResult {
+ val layoutCoordinates = state.layoutCoordinates ?: return SplitResult(Orientation.Vertical, Rect.Zero)
+ val availableWidth = layoutCoordinates.size.width.toFloat().coerceAtLeast(1f)
+ val splitWidthPixel = with(density) { gapWidth.toPx() }
+
+ val dividerPosition = state.dividerPosition.coerceIn(0f, 1f)
+ val splitX = (availableWidth * dividerPosition).coerceIn(0f, availableWidth)
+
+ return SplitResult(
+ gapOrientation = Orientation.Vertical,
+ gapBounds =
+ Rect(
+ left = splitX - splitWidthPixel / 2f,
+ top = 0f,
+ right = (splitX + splitWidthPixel / 2f).coerceAtMost(availableWidth),
+ bottom = layoutCoordinates.size.height.toFloat().coerceAtLeast(1f),
+ ),
)
- },
- ) { measurables, incomingConstraints ->
- val availableHeight = incomingConstraints.maxHeight
- val actualDividerY =
- dividerY
- .coerceIn(0, availableHeight)
- .coerceIn((availableHeight * minRatio).roundToInt(), (availableHeight * maxRatio).roundToInt())
-
- val dividerMeasurable = measurables.single { it.layoutId == "divider" }
- val dividerPlaceable =
- dividerMeasurable.measure(Constraints.fixed(incomingConstraints.maxWidth, dividerThickness.roundToPx()))
-
- val firstComponentConstraints =
- Constraints.fixed(incomingConstraints.maxWidth, (actualDividerY - 1).coerceAtLeast(0))
- val firstPlaceable =
- measurables.find { it.layoutId == "first" }?.measure(firstComponentConstraints)
- ?: error("No first component found. Have you applied the provided Modifier to it?")
-
- val secondComponentConstraints =
- Constraints.fixed(
- width = incomingConstraints.maxWidth,
- height = availableHeight - actualDividerY + dividerPlaceable.height,
+ }
+
+ override fun isHorizontal(): Boolean = true
+ }
+
+private fun verticalTwoPaneStrategy(gapHeight: Dp = 0.dp): SplitLayoutStrategy =
+ object : SplitLayoutStrategy {
+ override fun calculateSplitResult(
+ density: Density,
+ layoutDirection: LayoutDirection,
+ state: SplitLayoutState,
+ ): SplitResult {
+ val layoutCoordinates = state.layoutCoordinates ?: return SplitResult(Orientation.Horizontal, Rect.Zero)
+ val availableHeight = layoutCoordinates.size.height.toFloat().coerceAtLeast(1f)
+ val splitHeightPixel = with(density) { gapHeight.toPx() }
+
+ val dividerPosition = state.dividerPosition.coerceIn(0f, 1f)
+ val splitY = (availableHeight * dividerPosition).coerceIn(0f, availableHeight)
+
+ return SplitResult(
+ gapOrientation = Orientation.Horizontal,
+ gapBounds =
+ Rect(
+ left = 0f,
+ top = splitY - splitHeightPixel / 2f,
+ right = layoutCoordinates.size.width.toFloat().coerceAtLeast(1f),
+ bottom = (splitY + splitHeightPixel / 2f).coerceAtMost(availableHeight),
+ ),
)
- val secondPlaceable =
- measurables.find { it.layoutId == "second" }?.measure(secondComponentConstraints)
- ?: error("No second component found. Have you applied the provided Modifier to it?")
-
- val dividerHandlePlaceable =
- measurables
- .single { it.layoutId == "divider-handle" }
- .measure(Constraints.fixedWidth(incomingConstraints.maxWidth))
-
- layout(incomingConstraints.maxWidth, availableHeight) {
- firstPlaceable.placeRelative(0, 0)
- dividerPlaceable.placeRelative(0, actualDividerY - dividerPlaceable.height / 2)
- secondPlaceable.placeRelative(0, actualDividerY + dividerPlaceable.height)
- dividerHandlePlaceable.placeRelative(0, actualDividerY - dividerHandlePlaceable.measuredHeight / 2)
}
+
+ override fun isHorizontal(): Boolean = false
}
+
+private fun calculateAdjustedSizes(availableSpace: Int, minFirstPaneSize: Int, minSecondPaneSize: Int): Pair {
+ val totalMinSize = minFirstPaneSize + minSecondPaneSize
+ if (availableSpace >= totalMinSize) {
+ return minFirstPaneSize to minSecondPaneSize
+ }
+
+ val ratio = minFirstPaneSize.toFloat() / totalMinSize
+ val adjustedFirstSize = (availableSpace * ratio).roundToInt()
+ return adjustedFirstSize to availableSpace - adjustedFirstSize
}