Skip to content

Commit

Permalink
Update docs, cleanup code after refactor (#199)
Browse files Browse the repository at this point in the history
* Clean up code, add simple IntUiTheme entry point

Moved foundation.tree into foundation.lazy, too.

* Update README for 0.8.0

Added code samples, updated project structure, added sections on custom
window decoration and icon runtime patching.

* Add getting started section
  • Loading branch information
rock3r authored Oct 22, 2023
1 parent 935db52 commit 2c94e87
Show file tree
Hide file tree
Showing 33 changed files with 551 additions and 364 deletions.
229 changes: 192 additions & 37 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
[![JetBrains incubator](https://img.shields.io/badge/JetBrains-incubator-yellow)](https://github.com/JetBrains#jetbrains-on-github) [![CI checks](https://img.shields.io/github/actions/workflow/status/JetBrains/jewel/build.yml?logo=github)](https://github.com/JetBrains/jewel/actions/workflows/build.yml) [![Licensed under Apache 2.0](https://img.shields.io/github/license/JetBrains/jewel)](https://github.com/JetBrains/jewel/blob/main/LICENSE) [![Latest release](https://img.shields.io/github/v/release/JetBrains/jewel?include_prereleases&label=Latest%20Release&logo=github)](https://github.com/JetBrains/jewel/releases/latest)


# Jewel: a Compose for Desktop theme

<img alt="Jewel logo" src="art/jewel-logo.svg" width="20%"/>
Expand All @@ -10,31 +9,80 @@ desktop-optimized theme and set of components.

> [!WARNING]
>
> This project is in active development, and caution is advised when considering it for production uses. You _can_,
> but you should expect APIs to change often, things to move around and/or break, and all that jazz. Binary
> compatibility is not currently guaranteed across releases, but it is an eventual aim for 1.0, if it is possible.
> This project is in active development, and caution is advised when considering it for production uses. You _can_ use
> it, but you should expect APIs to change often, things to move around and/or break, and all that jazz. Binary
> compatibility is not guaranteed across releases, and APIs are still in flux and subject to change.
>
> Writing 3rd party IntelliJ Plugins in Compose for Desktop is currently **not officially supported** by the IntelliJ
> Platform. It should work, but your mileage may vary, and if things break you're on your own.
>
> Use at your own risk! (but have fun if you do!)
> Use at your own risk!
Jewel provides an implementation of the IntelliJ Platform themes that can be used in any Compose for Desktop
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.

## 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.

For now, Jewel artifacts aren't available on Maven Central. You need to add a custom Maven repository to your build:

```kotlin
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:

```kotlin
dependencies {
implementation("org.jetbrains.jewel:jewel-int-ui-standalone:[jewel version]")

// Optional, for custom decorated windows:
implementation("org.jetbrains.jewel:jewel-int-ui-decorated-window:[jewel version]")
}
```

For an **IntelliJ Platform plugin**, then you should depend on the appropriate `ide-laf-bridge` artifact:

Jewel provides stand-alone implementations of the IntelliJ Platform themes that can be used in any Compose for Desktop
application, and 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.
```kotlin
dependencies {
// 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-specific:[jewel version]-ij-[platform version]")
}
```

## Project structure

The project is split in modules:

1. `buildSrc` contains the build logic, including:
* The `jewel` and `jewel-publish` configuration plugins
* The `jewel-check-public-api` and `jewel-linting` configuration plugins
* The Theme Palette generator plugin
2. `core` contains the foundational Jewel functionality, including the components and their styling primitives
3. `int-ui` implements the standalone version of the IntelliJ New UI, which implements the
["Int UI" design system](https://www.figma.com/community/file/1227732692272811382/int-ui-kit), and can be used
anywhere
4. `ide-laf-bridge` contains the Swing LaF bridge to use in IntelliJ Platform plugins (see more below)
5. `samples` contains the example apps, which showcase the available components:
1. `standalone` is a regular CfD app, using the predefined "base" theme definitions
2. `ide-plugin` is an IntelliJ plugin, adding some UI to the IDE, and showcasing the use of the Swing Bridge
* The Studio Releases generator plugin
2. `foundation` contains the foundational Jewel functionality:
* Basic components without strong styling (e.g., `SelectableLazyColumn`, `BasicLazyTree`)
* The `JewelTheme` interface with a few basic composition locals
* The state management primitives
* The Jewel annotations
* A few other primitives
3. `ui` contains all the styled components and custom painters logic
4. `decorated-window` contains basic, unstyled functionality to have custom window decoration on the JetBrains Runtime
5. `int-ui` contains two modules:
* `int-ui-standalone` has a standalone version of the Int UI styling values that can be used in any Compose for
Desktop app
* `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)
* The `ide-laf-bridge-*` sub-modules contain code that is specific to a certain IntelliJ Platform version
7. `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

### Int UI Standalone theme

Expand All @@ -46,31 +94,137 @@ to your heart's content. By default, it matches the official Int UI specs.
> as it has extra features and patches for UI functionalities that aren't available in other JDKs.
> We **do not support** running Jewel on any other JDK.
To use Jewel components in a non-IntelliJ Platform environment, you need to wrap your UI hierarchy in a `IntUiTheme`
composable:

```kotlin
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

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](art/docs/custom-chrome.png)

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.

To get an IntelliJ-like custom title bar, you need to pass the window decoration styling to your theme call, and add the
`DecoratedWindow` composable at the top level of the theme:

```kotlin
IntUiTheme(
themeDefinition,
componentStyling = {
themeDefinition.decoratedWindowComponentStyling(
titleBarStyle = TitleBarStyle.light()
)
},
) {
DecoratedWindow(
onCloseRequest = { exitApplication() },
) {
// ...
}
}
```

### 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.
Jewel includes a crucial element for proper integration with the IDE: a bridge between the Swing components 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 Compose components as well — at least for themes that use the
standard [IntelliJ theming](https://plugins.jetbrains.com/docs/intellij/themes-getting-started.html) mechanisms.
theme, and apply them to the Compose components as well. This means Jewel will automatically adapt to IntelliJ Platform
themes that use the [standard theming](https://plugins.jetbrains.com/docs/intellij/themes-getting-started.html)
mechanisms.

> [!NOTE]
> IntelliJ themes that use non-standard mechanisms (such as providing custom UI implementations for Swing components)
> are not, and will never, be supported.
> are not, and can never, be supported.
If you're writing an IntelliJ Platform plugin, you should use the `SwingBridgeTheme` instead of a standalone theme.
If you're writing an IntelliJ Platform plugin, you should use the `SwingBridgeTheme` instead of the standalone theme:

```kotlin
SwingBridgeTheme {
// ...
}
```

#### Accessing icons

When you want to draw an icon from the resources, you should use a `PainterProvider`. Reading an icon from the IDE is
as easy as using the `retrieveStatefulIcon()` and `retrieveStatelessIcon()`:
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:

```kotlin
// Load the "close" icon from the IDE's AllIcons class
Icon(
"actions/close.svg",
iconClass = AllIcons::class.java,
contentDescription = "Close",
)
```

To obtain a `Painter`, instead, you'd use:

```kotlin
val painterProvider = rememberResourcePainterProvider(
path = "actions/close.svg",
iconClass = AllIcons::class.java
)
val painter by painterProvider.getPainter()
```

#### 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.

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`.

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
Jewel load the appropriate icon automatically:

```kotlin
val svgLoader = service<SwingBridgeService>().svgLoader
val painterProvider = retrieveStatelessIcon("icons/bot-toolwindow.svg", svgLoader, iconData)
// myState implements SelectableComponentState and has a ToggleableState property
val myPainter by myPainterProvider.getPainter(
if (myState.toggleableState == ToggleableState.Indeterminate) {
IndeterminateHint
} else {
PainterHint.None
},
Selected(myState),
Stateful(myState),
)
```

Where the `IndeterminateHint` looks like this:

```kotlin
private object IndeterminateHint : PainterSuffixHint() {
override fun suffix(): String = "Indeterminate"
}
```

Assuming the PainterProvider has a base path of `components/myIcon.svg`, Jewel will automatically translate it to the
right path based on the state. If you want to learn more about this system, look at the `PainterHint` interface and its
implementations.

### Swing interoperability

As this is Compose for Desktop, you get a good degree of interoperability with Swing. To avoid glitches and z-order
Expand All @@ -79,7 +233,7 @@ issues, you should enable the
before you initialize Compose content.

The `ToolWindow.addComposeTab()` extension function provided by the `ide-laf-bridge` module will take care of that for
you, but if you want to also enable it in other scenarios and in standalone applications, you can call the
you. However, if you want to also enable it in other scenarios and in standalone applications, you can call the
`enableNewSwingCompositing()` function in your Compose entry points (that is, right before creating a `ComposePanel`).

> [!NOTE]
Expand All @@ -94,20 +248,21 @@ If you don't already have access to the Kotlin Slack, you can request it
[here](https://surveys.jetbrains.com/s3/kotlin-slack-sign-up).

## License

Jewel is licensed under the [Apache 2.0 license](https://github.com/JetBrains/jewel/blob/main/LICENSE).

```
Copyright 2022–3 JetBrains s.r.o.
Copyright 2022–3 JetBrains s.r.o.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
```
Binary file added art/docs/custom-chrome.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ import androidx.compose.ui.window.WindowState
import androidx.compose.ui.window.rememberWindowState
import com.jetbrains.JBR
import org.jetbrains.jewel.foundation.Stroke
import org.jetbrains.jewel.foundation.border
import org.jetbrains.jewel.foundation.modifier.border
import org.jetbrains.jewel.foundation.theme.JewelTheme
import org.jetbrains.jewel.window.styling.DecoratedWindowStyle
import org.jetbrains.jewel.window.utils.DesktopPlatform
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,8 @@ import java.awt.Frame
import java.awt.event.MouseEvent
import java.awt.event.WindowEvent

@Composable internal fun DecoratedWindowScope.TitleBarOnLinux(
@Composable
internal fun DecoratedWindowScope.TitleBarOnLinux(
modifier: Modifier = Modifier,
gradientStartColor: Color = Color.Unspecified,
style: TitleBarStyle = JewelTheme.defaultTitleBarStyle,
Expand Down Expand Up @@ -72,15 +73,17 @@ import java.awt.event.WindowEvent
}
}

@Composable private fun TitleBarScope.CloseButton(
@Composable
private fun TitleBarScope.CloseButton(
onClick: () -> Unit,
state: DecoratedWindowState,
style: TitleBarStyle = JewelTheme.defaultTitleBarStyle,
) {
ControlButton(onClick, state, style.icons.closeButton, "Close", style, style.paneCloseButtonStyle)
}

@Composable private fun TitleBarScope.ControlButton(
@Composable
private fun TitleBarScope.ControlButton(
onClick: () -> Unit,
state: DecoratedWindowState,
painterProvider: PainterProvider,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,8 @@ private class NewFullscreenControlsNode(
var newControls: Boolean,
) : Modifier.Node()

@Composable internal fun DecoratedWindowScope.TitleBarOnMacOs(
@Composable
internal fun DecoratedWindowScope.TitleBarOnMacOs(
modifier: Modifier = Modifier,
gradientStartColor: Color = Color.Unspecified,
style: TitleBarStyle = JewelTheme.defaultTitleBarStyle,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,8 @@ import org.jetbrains.jewel.foundation.theme.JewelTheme
import org.jetbrains.jewel.ui.util.isDark
import org.jetbrains.jewel.window.styling.TitleBarStyle

@Composable internal fun DecoratedWindowScope.TitleBarOnWindows(
@Composable
internal fun DecoratedWindowScope.TitleBarOnWindows(
modifier: Modifier = Modifier,
gradientStartColor: Color = Color.Unspecified,
style: TitleBarStyle = JewelTheme.defaultTitleBarStyle,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,8 @@ internal const val TITLE_BAR_BORDER_LAYOUT_ID = "__TITLE_BAR_BORDER__"
}
}

@Composable internal fun DecoratedWindowScope.TitleBarImpl(
@Composable
internal fun DecoratedWindowScope.TitleBarImpl(
modifier: Modifier = Modifier,
gradientStartColor: Color = Color.Unspecified,
style: TitleBarStyle = JewelTheme.defaultTitleBarStyle,
Expand Down Expand Up @@ -224,7 +225,8 @@ internal class TitleBarMeasurePolicy(
}
}

@Composable internal fun rememberTitleBarMeasurePolicy(
@Composable
internal fun rememberTitleBarMeasurePolicy(
window: Window,
state: DecoratedWindowState,
applyTitleBar: (Dp, DecoratedWindowState) -> PaddingValues,
Expand Down
Loading

0 comments on commit 2c94e87

Please sign in to comment.