Skip to content

Commit

Permalink
Create IDE LaF bridge (#102)
Browse files Browse the repository at this point in the history
* [WIP] Start building the bridge

* Update dependencies, remove unused ones

* [WIP] Continue building the bridge

* [WIP] Continue building the bridge (got to Chip)

Includes tweaks on wrong values etc

* [WIP] Fill out infra for IntUiBridge.kt

Still not done!

* [WIP] Fill out infra for IntUiBridge.kt

Still not done!

* [WIP] More work

* [WIP] More progress (missing text fields & tabs)

* Finish filling in the bridge, then 🤞

* Make default fallbacks explicit, fix some issues

* Delete unused code

* Implement New UI icon mapping support in Bridge

Note: it doesn't work yet, since IconMapLoader.loadIconMapping()
complains that the mapping isn't done (when it is, since it's done in
the IDE pre-loading).

I suspect this is a classloading issue.

* More cleanup, stuff's still broken yey

* More fixin' stuff, hopefully the CI too

* Fix CI warning

* Refactor UI Component Styling in Jewel to Support defaults (#104)

* Refactor UI Component Styling in Jewel to Support defaults

Retrieving text styles requires coroutines. This commit aims to allow the theme to start with text defaults and as soon as text styles get retrieved, the theme gets updated.

* Fixes according to MR comments

* Fix this thing

---------

Co-authored-by: Sebastiano Poggi <[email protected]>

* More fixes to components/theme, cleanups

* Improve showcase and simplify text area (bye hints)

* Improve showcase and simplify text area (bye hints), fix more things

* Avoid crashing on old UI

---------

Co-authored-by: Lamberto Basti <[email protected]>
  • Loading branch information
rock3r and lamba92 authored Sep 1, 2023
1 parent 0bf6a27 commit 0564675
Show file tree
Hide file tree
Showing 144 changed files with 4,473 additions and 2,692 deletions.
4 changes: 3 additions & 1 deletion .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -39,13 +39,15 @@ jobs:
run: chmod +x gradlew

- name: Run :check task
run: ./gradlew check
run: ./gradlew check --continue

- name: Merge SARIF reports
# Necessary because upload-sarif only takes up to 15 SARIF files and we have more
run: ./gradlew :mergeSarifReports
if: ${{ always() }}

- uses: github/codeql-action/upload-sarif@v2
if: ${{ always() }}
with:
sarif_file: ${{ github.workspace }}/build/reports/static-analysis.sarif
checkout_path: ${{ github.workspace }}
Expand Down
3 changes: 3 additions & 0 deletions .idea/codeStyles/Project.xml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1,085 changes: 1,085 additions & 0 deletions .idea/inspectionProfiles/Lint_only.xml

Large diffs are not rendered by default.

298 changes: 1 addition & 297 deletions .idea/inspectionProfiles/Project_Default.xml

Large diffs are not rendered by default.

2 changes: 2 additions & 0 deletions .idea/runConfigurations/IDE_sample.xml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

49 changes: 35 additions & 14 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,14 +1,18 @@
[![JetBrains incubator](https://camo.githubusercontent.com/be6f8b50b2400e8b0dc74e58dd9a68803fe6698f5f30d843a7504888879f8392/68747470733a2f2f6a622e67672f6261646765732f696e63756261746f722d706c61737469632e737667)](https://github.com/JetBrains#jetbrains-on-github) [![CI checks](https://github.com/JetBrains/jewel/actions/workflows/build.yml/badge.svg?branch=main)](https://github.com/JetBrains/jewel/actions/workflows/build.yml)

# Jewel: a Compose for Desktop theme

<img alt="Jewel logo" src="art/jewel-logo.svg" width="20%"/>

Jewel aims at recreating the _Darcula_ and _New UI_ Swing Look and Feels used on the IntelliJ Platform into Compose for Desktop.
Jewel aims at recreating the _Darcula_ and _New UI_ Swing Look and Feels used on the IntelliJ Platform into Compose for
Desktop.

> **Warning**
>This project is in very early development and is probably not ready to be used in production projects. You _can_, but there
>are no published snapshots, and you should expect APIs to break fairly often, things to move around, and all that jazz.
>Use at your risk!
> This project is in very early development and is probably not ready to be used in production projects. You _can_, but
> there
> are no published snapshots, and you should expect APIs to break fairly often, things to move around, and all that
> jazz.
> Use at your risk!
## Project structure

Expand All @@ -19,7 +23,8 @@ The project is split in modules:
3. `themes` are the two themes implemented by Jewel:
1. `darcula` is the old school Intellij LaF, called Darcula, which has two implementations:
1. `darcula-standalone` is the base theme and can be used in any Compose for Desktop project
2. `darcula-ide` is a version of the theme that can be used in an IDEA plugin, and integrates with the IDE's Swing LaF and themes via a
2. `darcula-ide` is a version of the theme that can be used in an IDEA plugin, and integrates with the IDE's
Swing LaF and themes via a
bridge (more
on that later)
2. `new-ui` implements the new IntelliJ LaF, known as "new UI". This also has the same two implementations
Expand All @@ -31,29 +36,45 @@ The project is split in modules:

To run the stand-alone sample app, you can run the `:samples:standalone:run` Gradle task.

To run the IntelliJ IDEA plugin sample, you can run the `:samples:ide-plugin:runIde` Gradle task. This will download and run a copy of IJ Community
with the plugin installed; you can check the additional panels in the IDE once it starts up (at the bottom, by default, in old UI; in the overflow
To run the IntelliJ IDEA plugin sample, you can run the `:samples:ide-plugin:runIde` Gradle task. This will download and
run a copy of IJ Community
with the plugin installed; you can check the additional panels in the IDE once it starts up (at the bottom, by default,
in old UI; in the overflow
in the new UI).

If you're using IntelliJ IDEA, you can use the "Stand-alone sample" and "IDE sample" run configurations.

### The Swing Bridge

In the `*-ide` modules, there is a crucial element for proper integration with the IDE: a bridge between the Swing theme and LaF, and the Compose
In the `*-ide` modules, there is a crucial element for proper integration with the IDE: a bridge between the Swing theme
and LaF, and the Compose
world.
This bridge ensures that we pick up the colours, typography, metrics, and images as defined in the current IntelliJ theme, and apply them to the
This bridge ensures that we pick up the colours, typography, metrics, and images as defined in the current IntelliJ
theme, and apply them to the
Compose theme as well.

The work of building this bridge is fairly complex as there isn't a good mapping between the IDE LaF properties, the Darcula design specs, and the
The work of building this bridge is fairly complex as there isn't a good mapping between the IDE LaF properties, the
Darcula design specs, and the
Compose implementations. Sometimes, you will need to get a bit creative.

When adding a new composable to the IJ theme, you need to make sure you also update the bridge to properly support it at runtime. You can refer to the
[Darcula design specs](https://jetbrains.design/intellij) and corresponding [Figma specs](https://jetbrains.design/intellij/resources/UI_kit/), but
the ultimate goal is consistency with the Swing implementation, so the ground truth of what you see in the IDE is the reference for any implementation
When adding a new composable to the IJ theme, you need to make sure you also update the bridge to properly support it at
runtime. You can refer to the
[Darcula design specs](https://jetbrains.design/intellij) and
corresponding [Figma specs](https://jetbrains.design/intellij/resources/UI_kit/), but
the ultimate goal is consistency with the Swing implementation, so the ground truth of what you see in the IDE is the
reference for any implementation
and trumps the specs.

To find the required values in the IDE, we recommend enabling
the [IDE internal mode](https://plugins.jetbrains.com/docs/intellij/enabling-internal.html)
and using the [UI Inspector](https://plugins.jetbrains.com/docs/intellij/internal-ui-inspector.html) and
[LaF Defaults](https://plugins.jetbrains.com/docs/intellij/internal-ui-laf-defaults.html) tools to figure out the names of the parameters to use in
[LaF Defaults](https://plugins.jetbrains.com/docs/intellij/internal-ui-laf-defaults.html) tools to figure out the names
of the parameters to use in
the bridge.

To see debug logs in the IDE, add these to __Help | Diagnostic Tools | Debug Log Settings__:

```
#org.jetbrains.jewel.demo
#org.jetbrains.jewel
```
1 change: 0 additions & 1 deletion build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@ val sarif: Configuration by configurations.creating {
}

dependencies {
sarif(projects.foundation)
sarif(projects.core)
sarif(projects.composeUtils)
sarif(projects.samples.standalone)
Expand Down
1 change: 1 addition & 0 deletions buildSrc/src/main/kotlin/jewel.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ kotlin {
optIn("kotlin.experimental.ExperimentalTypeInference")
optIn("androidx.compose.ui.ExperimentalComposeUiApi")
optIn("androidx.compose.foundation.ExperimentalFoundationApi")
optIn("org.jetbrains.jewel.ExperimentalJewelApi")
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ package org.jetbrains.jewel.buildlogic.theme

import com.squareup.kotlinpoet.ClassName
import io.gitlab.arturbosch.detekt.Detekt
import java.net.URL
import kotlinx.serialization.Serializable
import kotlinx.serialization.json.Json
import kotlinx.serialization.json.JsonElement
Expand All @@ -23,7 +22,8 @@ import org.gradle.kotlin.dsl.register
import org.gradle.kotlin.dsl.withType
import org.gradle.util.internal.GUtil
import org.jetbrains.kotlin.gradle.dsl.KotlinJvmProjectExtension
import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
import org.jetbrains.kotlin.gradle.tasks.BaseKotlinCompile
import java.net.URL

abstract class IntelliJThemeGeneratorPlugin : Plugin<Project> {

Expand All @@ -42,7 +42,7 @@ abstract class IntelliJThemeGeneratorPlugin : Plugin<Project> {
ideaVersion.set(this@all.ideaVersion)
themeFile.set(this@all.themeFile)
}
tasks.withType<KotlinCompile> {
tasks.withType<BaseKotlinCompile> {
dependsOn(task)
}
tasks.withType<Detekt> {
Expand Down
1 change: 0 additions & 1 deletion compose-utils/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,5 @@ dependencies {
api(compose.desktop.currentOs) {
exclude(group = "org.jetbrains.compose.material")
}
implementation(libs.jna)
implementation(libs.kotlinx.serialization.json)
}
1 change: 0 additions & 1 deletion core/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,5 @@ plugins {

dependencies {
api(projects.composeUtils)
api(projects.foundation)
api(compose.desktop.common)
}
38 changes: 18 additions & 20 deletions core/src/main/kotlin/org/jetbrains/jewel/Button.kt
Original file line number Diff line number Diff line change
Expand Up @@ -29,11 +29,9 @@ import androidx.compose.ui.semantics.Role
import androidx.compose.ui.text.TextStyle
import org.jetbrains.jewel.CommonStateBitMask.Active
import org.jetbrains.jewel.CommonStateBitMask.Enabled
import org.jetbrains.jewel.CommonStateBitMask.Error
import org.jetbrains.jewel.CommonStateBitMask.Focused
import org.jetbrains.jewel.CommonStateBitMask.Hovered
import org.jetbrains.jewel.CommonStateBitMask.Pressed
import org.jetbrains.jewel.CommonStateBitMask.Warning
import org.jetbrains.jewel.foundation.Stroke
import org.jetbrains.jewel.foundation.border
import org.jetbrains.jewel.styling.ButtonStyle
Expand All @@ -55,7 +53,7 @@ fun DefaultButton(
interactionSource = interactionSource,
style = style,
content = content,
textStyle = textStyle
textStyle = textStyle,
)
}

Expand All @@ -76,7 +74,7 @@ fun OutlinedButton(
interactionSource = interactionSource,
style = style,
content = content,
textStyle = textStyle
textStyle = textStyle,
)
}

Expand Down Expand Up @@ -117,31 +115,35 @@ private fun ButtonImpl(
val shape = RoundedCornerShape(style.metrics.cornerSize)
val colors = style.colors
val borderColor by colors.borderFor(buttonState)
println("state: $buttonState ($enabled) -> $borderColor")

Box(
modifier.clickable(
onClick = onClick,
enabled = enabled,
role = Role.Button,
interactionSource = interactionSource,
indication = null
).background(colors.backgroundFor(buttonState).value, shape)
modifier = modifier
.clickable(
onClick = onClick,
enabled = enabled,
role = Role.Button,
interactionSource = interactionSource,
indication = null,
)
.background(colors.backgroundFor(buttonState).value, shape)
.border(Stroke.Alignment.Center, style.metrics.borderWidth, borderColor, shape)
.focusOutline(buttonState, shape),
propagateMinConstraints = true
propagateMinConstraints = true,
) {
val contentColor by colors.contentFor(buttonState)

CompositionLocalProvider(
LocalContentColor provides contentColor.takeOrElse { textStyle.color },
LocalTextStyle provides textStyle.copy(color = contentColor.takeOrElse { textStyle.color })
LocalTextStyle provides textStyle.copy(color = contentColor.takeOrElse { textStyle.color }),
) {
Row(
Modifier
.defaultMinSize(style.metrics.minSize.width, style.metrics.minSize.height)
.padding(style.metrics.padding),
horizontalArrangement = Arrangement.Center,
verticalAlignment = Alignment.CenterVertically,
content = content
content = content,
)
}
}
Expand Down Expand Up @@ -182,7 +184,7 @@ value class ButtonState(val state: ULong) : FocusableComponentState {
focused = focused,
pressed = pressed,
hovered = hovered,
active = active
active = active,
)

override fun toString() =
Expand All @@ -194,19 +196,15 @@ value class ButtonState(val state: ULong) : FocusableComponentState {
fun of(
enabled: Boolean = true,
focused: Boolean = false,
error: Boolean = false,
pressed: Boolean = false,
hovered: Boolean = false,
warning: Boolean = false,
active: Boolean = true,
) = ButtonState(
state = (if (enabled) Enabled else 0UL) or
(if (focused) Focused else 0UL) or
(if (hovered) Hovered else 0UL) or
(if (pressed) Pressed else 0UL) or
(if (warning) Warning else 0UL) or
(if (error) Error else 0UL) or
(if (active) Active else 0UL)
(if (active) Active else 0UL),
)
}
}
Loading

0 comments on commit 0564675

Please sign in to comment.