Giter Site home page Giter Site logo

jetbrains / jewel Goto Github PK

View Code? Open in Web Editor NEW
580.0 26.0 25.0 14.9 MB

An implementation of the IntelliJ look and feels in Compose for Desktop

License: Apache License 2.0

Kotlin 98.33% Java 1.67%
compose desktop intellij kotlin laf look-and-feel multiplatform swing theme ui

jewel's Introduction

JetBrains incubator CI checks Licensed under Apache 2.0 Latest release Compose for Desktop version

Jewel: a Compose for Desktop theme

Jewel logo

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.

Warning

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!

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.

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

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:

pluginManagement {
    repositories {
        google()
        gradlePluginPortal()
        maven("https://maven.pkg.jetbrains.space/public/p/compose/dev")
        mavenCentral()
    }
}

Then, in your app's build.gradle.kts:

plugins {
    // Should align with the Kotlin and Compose dependencies in Jewel
    kotlin("jvm") version "1.9.21"
    id("org.jetbrains.compose") version "1.6.0-dev1440"
}

repositories {
    maven("https://packages.jetbrains.team/maven/p/kpm/public/")
    // Any other repositories you need (e.g., mavenCentral())
}

Warning

If you use convention plugins to configure your project you might run into issues such as this. 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:

dependencies {
    // 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-[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:

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

It's easier to use version catalogs — you can use the Jewel version catalog 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

For each version of Jewel, these are the minimum supported Kotlin and Compose Multiplatform versions:

Jewel version Kotlin version Compose version
0.15.2 -> * 1.8.21 1.6.10-dev1490
0.15.1 1.8.21 1.6.10-dev1457
0.15.0 1.8.21 1.6.0-dev1440
0.13.1 -> 0.14.1 1.8.21 1.6.0-dev1369

For older versions please refer to the Jewel tags and release notes.

The Compose Compiler version used is the latest compatible with the given Kotlin version. See here 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

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
    • 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)
  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

Branching strategy and IJ Platforms

Code on the main branch is developed and tested against the current latest IntelliJ Platform version.

When the EAP for a new major version starts, we cut a releases/xxx release branch, where xxx is the tracked major IJP version. At that point, the main branch starts tracking the latest available major IJP version, and changes are cherry-picked into each release branch as needed. All active release branches have the same functionality (where supported by the corresponding IJP version), but might differ in platform version-specific fixes and internals.

The standalone Int UI theme will always work the same way as the latest major IJP version; release branches will not include the int-ui module, which is always released from the main branch.

Releases of Jewel are always cut from a tag on the main branch; the HEAD of each releases/xxx branch is then tagged as [mainTag]-xxx, and used to publish the artifacts for that major IJP version.

Important

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.

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

For an example on how to set up a standalone app, you can refer to the standalone sample.

Warning

Note that Jewel requires the JetBrains Runtime to work correctly. Some features like font loading depend on it, 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:

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

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:

IntUiTheme(
   theme = themeDefinition,
   styling = ComponentStyling.default().decoratedWindow(
      titleBarStyle = TitleBarStyle.light()
   ),
) {
    DecoratedWindow(
        onCloseRequest = { exitApplication() },
    ) {
        // ...
    }
}

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.

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. This means Jewel will automatically adapt to IntelliJ Platform themes that use the standard theming mechanisms.

Note

IntelliJ themes that use non-standard mechanisms (such as providing custom UI implementations for Swing components) are not, and can never, be supported.

If you're writing an IntelliJ Platform plugin, you should use the SwingBridgeTheme instead of the standalone theme:

SwingBridgeTheme {
    // ...
}

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

Accessing 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:

// 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:

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.

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:

// 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:

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.

Fonts

To load a system font, you can obtain it by its family name:

val myFamily = FontFamily("My Family")

If you want to use a font embedded in the JetBrains Runtime, you can use the EmbeddedFontFamily API instead:

import javax.swing.text.StyledEditorKit.FontFamilyAction

// Will return null if no matching font family exists in the JBR
val myEmbeddedFamily = EmbeddedFontFamily("Embedded family")

// It's recommended to load a fallback family when dealing with embedded familes
val myFamily = myEmbeddedFamily ?: FontFamily("Fallback family")

You can obtain a FontFamily from any java.awt.Font — including from JBFonts — by using the asComposeFontFamily() API:

val myAwtFamily = myFont.asComposeFontFamily()

// This will attempt to resolve the logical AWT font
val myLogicalFamily = Font("Dialog").asComposeFontFamily()

// This only works in the IntelliJ Platform,
// since JBFont is only available there
val myLabelFamily = JBFont.label().asComposeFontFamily()

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 experimental Swing rendering pipeline before you initialize Compose content.

The ToolWindow.addComposeTab() extension function provided by the ide-laf-bridge module will take care of that for 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

The new Swing rendering pipeline is experimental and may have performance repercussions when using infinitely repeating animations. This is a known issue by the Compose Multiplatform team, that requires changes in the Java runtime to fix. Once the required changes are made in the JetBrains Runtime, we'll remove this notice.

Written with Jewel

Here is a small selection of projects that use Compose for Desktop and Jewel:

Need help?

You can find help on the #jewel channel on the Kotlin Slack. If you don't already have access to the Kotlin Slack, you can request it here.

License

Jewel is licensed under the Apache 2.0 license.

Copyright 2022–4 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

    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.

jewel's People

Contributors

amaurymedeiros avatar c5inco avatar chozzle avatar devkanro avatar diegopl avatar edivad1999 avatar fedorkudasov avatar fscarponi avatar jrlogsdon avatar lamba92 avatar obask avatar olonho avatar rivanparmar avatar rock3r avatar walingar avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

jewel's Issues

Set up screenshot testing

We should have an automated way to test components against Swing implementations to compare for consistency, given we can't trust the Figma specs to match the implementations, nor that the Swing implementations won't change over time.

The tests should cover:

  • Darcula/IntelliJ Theme (with High Contrast to come later)
  • Int UI Light and Dark
  • All supported variations of components
  • All supported states of components (e.g., normal, focused, pressed, hover, error)
  • For animated components:
    • If the animation is state-driven, at least the start/end state for each pair
    • If the animation is time-driven and/or cyclical, at least the start/end or start/mid-point
  • For stateful components such as text fields/areas, a set of different states depending on the component (e.g., empty, short text, long text)
  • For scrollable components, for each scrollable axis: non-scrolling state (e.g., not enough content on that axis to scroll), beginning of scroll, end of scroll
  • For components with an empty state, the empty state should be captured, too

This should ideally be done on all platforms (macOS, Windows, Linux), but as a start, macOS only is a decent compromise.
The goal is to run this job every time there is a new IJ platform release, so we get pings when there are divergencies.

We likely don't want to be too pixel-precise because text rendering alone will likely cause issues, otherwise, but sensitivity shall be adjusted when the tests exist.

Complex component: table

We need a proper, lazy, 2D-scrollable Table component. Resizable/sortable columns are not mandatory for the first iteration.

Popups should use native windows

Our popup windows (e.g., menus, dropdowns, tooltips, etc) don't use native Swing popup windows. This causes a number of issues, such as:

  1. Popups are limited to showing inside the canvas that generated them
  2. They may not match the other Swing native windows in behavior
  3. Shadow needs to be implemented per-OS and it's hard to match OS behaviour, especially on Linux where there are a million variables at play

Fix up repositories setup in buildscripts

For whatever reason, the repositories block we set in settings.gradle.kts seems not to be applied to all projects.

This should be fixed, and all the various repositories block anywhere else removed.

Combobox popup menu size is incorrect

The combobox popup menu should inherit the same width as the combobox, but it's either wrapping contents or matching the parent as it is right now.

Design new demo app

The current demo app is very barebones. We would like to get something a bit nicer that a collection of widgets, to showcase how to use Jewel.

The mockup can contain components we don't have yet, and we can backfill the gaps as we go.

Menus don't handle item selection properly

In Swing, when you use the keyboard to select items, the item under the mouse doesn't keep the selected state if you don't touch the mouse:

swing-menu

But in Jewel, they do:

jewel-menu

This is likely because we're abusing the "focused" and "hovered" states instead of keeping track of the Selected state which is the semantically correct one to associate this behaviour with.

Add support for high contrast theme

We have no clue what the specs are, for this.

In the IDE, we expect this to happen automagically via the bridge, so it's not super urgent, but it will eventually be necessary for the standalone theme to also support it.

Determine if we still need the Compose compiler version override

In the root buildscript, we have:

configurations.all {
    resolutionStrategy.dependencySubstitution {
        substitute(module("org.jetbrains.compose.compiler:compiler")).apply {
            using(module("androidx.compose.compiler:compiler:1.2.1-dev-k1.7.10-27cf0868d10"))
        }
    }
}

Is this still required? Shouldn't be!

Implement IDE LaF bridge for existing components

We need to ensure the components integrate with the IDE theme as much as possible; this task is to create an "IDE theme", that consists of a bridge between the Swing LaF and the Compose theme.

The IDE theme must match the Swing LaF as closely as possible, and respond to theme and LaF change (Darcula support is not required for the first iteration, but if all goes well, it should at least partially come for free)

FocusRequester is not initialized

A pop-up window appears while scrolling through the TreeSample example tree view:

Exception in thread "AWT-EventQueue-0" java.lang.IllegalStateException: 
   FocusRequester is not initialized. Here are some possible fixes:

   1. Remember the FocusRequester: val focusRequester = remember { FocusRequester() }
   2. Did you forget to add a Modifier.focusRequester() ?
   3. Are you attempting to request focus during composition? Focus requests should be made in
   response to some event. Eg Modifier.clickable { focusRequester.requestFocus() }

	at androidx.compose.ui.focus.FocusRequester.requestFocus(FocusRequester.kt:54)
	at org.jetbrains.jewel.theme.intellij.components.TreeViewKt$BaseTreeLayout$4$2$7.invokeSuspend(TreeView.kt:240)
	at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:33)
	at kotlinx.coroutines.DispatchedTask.run(DispatchedTask.kt:106)
	at androidx.compose.ui.platform.FlushCoroutineDispatcher$dispatch$2$1.invoke(CoroutineDispatchers.skiko.kt:51)
	at androidx.compose.ui.platform.FlushCoroutineDispatcher$dispatch$2$1.invoke(CoroutineDispatchers.skiko.kt:46)
	at androidx.compose.ui.platform.FlushCoroutineDispatcher.performRun(CoroutineDispatchers.skiko.kt:78)
	at androidx.compose.ui.platform.FlushCoroutineDispatcher.access$performRun(CoroutineDispatchers.skiko.kt:29)
	at androidx.compose.ui.platform.FlushCoroutineDispatcher$dispatch$2.invokeSuspend(CoroutineDispatchers.skiko.kt:46)
	at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:33)
	at kotlinx.coroutines.DispatchedTask.run(DispatchedTask.kt:106)
	at java.desktop/java.awt.event.InvocationEvent.dispatch(InvocationEvent.java:313)
	at java.desktop/java.awt.EventQueue.dispatchEventImpl(EventQueue.java:776)
	at java.desktop/java.awt.EventQueue$4.run(EventQueue.java:727)
	at java.desktop/java.awt.EventQueue$4.run(EventQueue.java:721)
	at java.base/java.security.AccessController.doPrivileged(Native Method)
	at java.base/java.security.ProtectionDomain$JavaSecurityAccessImpl.doIntersectionPrivilege(ProtectionDomain.java:85)
	at java.desktop/java.awt.EventQueue.dispatchEvent(EventQueue.java:746)
	at java.desktop/java.awt.EventDispatchThread.pumpOneEventForFilters(EventDispatchThread.java:203)
	at java.desktop/java.awt.EventDispatchThread.pumpEventsForFilter(EventDispatchThread.java:124)
	at java.desktop/java.awt.EventDispatchThread.pumpEventsForHierarchy(EventDispatchThread.java:113)
	at java.desktop/java.awt.EventDispatchThread.pumpEvents(EventDispatchThread.java:109)
	at java.desktop/java.awt.EventDispatchThread.pumpEvents(EventDispatchThread.java:101)
	at java.desktop/java.awt.EventDispatchThread.run(EventDispatchThread.java:90)

Running on Linux machine.

Create wrapper for IDE platform icons

Standalone is optional, but at least in the IDE theme we should have an easy way to address platform icons (AllIcons, StudioIcons, etc). This should be auto-generated from IDE sources.

Rounded rectangles not consistent with IJ

After some pixel-level comparisons, we found that the way rounded rectangles are drawn in skia does not match the way IJ draws rounded rectangles.

According to the developer of skia, not long ago, skia changed the drawing method of the rounded rectangle from Quad to Conic. But since SVG doesn't support Conic, I can't visualize in SVG the path where skia currently draws a rounded rectangle, but I believe Conic should be able to draw a perfect 1/4 arc as the rounded corners of a rectangle.

In addition, by analyzing and exporting IJ's code, we found that IJ uses a perplexing combination of Quad and Cubic to draw rounded rectangles.

Here is an SVG image used to visualize the various borders of IJ, we just need to focus on its upper left corner.
test

<svg width="200" height="48" viewBox="0 0 200 48" fill="none" xmlns="http://www.w3.org/2000/svg">
    <path fill="blue" fill-rule="evenodd"
          d="M134.0 0.0 Q140.0 0.0, 140.0 6.0 L140.0 22.0 Q140.0 28.0, 134.0 28.0 L6.0 28.0 Q0.0 28.0, 0.0 22.0 L0.0 6.0 Q0.0 0.0, 6.0 0.0 Z M134.0 3.0 Q137.0 3.0, 137.0 6.0 L137.0 22.0 Q137.0 25.0, 134.0 25.0 L6.0 25.0 Q3.0 25.0, 3.0 22.0 L3.0 6.0 Q3.0 3.0, 6.0 3.0 Z M0.0 0.0 M0.0 0.0 "/>
    <path fill="green" fill-rule="evenodd"
          d="M2.0 5.0 L2.0 23.0 C2.0 24.656855, 3.3431458 26.0, 5.0 26.0 L87.0 26.0 C88.65685 26.0, 90.0 24.656855, 90.0 23.0 L90.0 5.0 C90.0 3.3431458, 88.65685 2.0, 87.0 2.0 L5.0 2.0 C3.3431458 2.0, 2.0 3.3431458, 2.0 5.0 Z M3.0 5.5 L3.0 22.5 C3.0 23.880713, 4.119288 25.0, 5.5 25.0 L86.5 25.0 C87.880714 25.0, 89.0 23.880713, 89.0 22.5 L89.0 5.5 C89.0 4.119288, 87.880714 3.0, 86.5 3.0 L5.5 3.0 C4.119288 3.0, 3.0 4.119288, 3.0 5.5 Z "/>
    <path fill="red" fill-rule="evenodd" opacity=".5" d="M0 6 A 6 6 0 0 1 6 0 L6 3 A 3 3 0 0 0 3 6"/>
    <path fill="yellow" fill-rule="evenodd" opacity=".5" d="M2 5 A 3 3 0 0 1 5 2 L5 3 A 2 2 0 0 0 3 5 Z"/>
</svg>

Blue path is exported from IJ's holo border path which using evenodd path of two RoundRectangle2D with cubic.
Green path is exported from IJ's normal border path which using evenodd path of two Quad path.
Red is a prefect 1/4 arc for holo border size.
Yellow is a prefect 1/4 arc for normal border size.

截屏2023-01-25 21 15 27

As shown in the picture, the blue and green borders use different functions, and their circle centers are also different.

We need to have more discussions with IJ's designers and developers to determine the necessity of these details.

Implement the new UI

  1. Find the specs doc for the new UI (in progress...)
  2. Test the bridge, make sure it picks up the new theme
  3. Implement the defaults for the new UI specs

Complex component: code viewer (in-IDE only)

It is a non-editable Text Area, that takes the highlighting style from the Editor colour scheme, and uses the IDE's highlighting engine to colour text.

For now (or ever?), it doesn't need to support folding regions and any advanced editor feature.

SelectableLazyColumn focus handling is incorrect

Quoting:

I just tried SelectableLazyColumn and noticed that it is implemented in a way that every item can be focused, but this is not really correct behaviour. Only Column itself should be focusable and element is focused only when Column is focused and element is selected. Otherwise focus traversal will work wrong, so you will navigate through the elements by tab, but you should select another component instead.

Update and clean up docs (draft)

The docs need a big update after the recent changes. This issue is to put in the major changes. It is NOT about polishing the docs — that's a separate task.

Rework modules organization

The core module used to depend on the foundation module, but that was odd and backwards. For now everything has been folded in core (as of #82), but we need to split things out, into core, foundation and components.

Core should contain all the basic APIs (state, annotations, graphics, AWT interop, etc). In Foundation we'd put all complex foundational API, such as Basic* components, only depending on core. Then we should have a Components module that contains all theme-bound components and depends on foundation.

Fonts loaded from classpath are missing

IntelliJ loads JetBrains Mono from a file in the classpath resources. The current Skiko font loading machinery does not see it. See function retrieveFont for details.

Could not initialize class kotlinx.coroutines.CoroutineExceptionHandlerImplKt

The problem is yet again IDEA-285839. It is happening in branch treeview by running gradlew :themes:intellij:idea:runIde

java.lang.NoClassDefFoundError: Could not initialize class kotlinx.coroutines.CoroutineExceptionHandlerImplKt
	at kotlinx.coroutines.CoroutineExceptionHandlerKt.handleCoroutineException(CoroutineExceptionHandler.kt:29)
	at kotlinx.coroutines.DispatchedTask.handleFatalException(DispatchedTask.kt:146)
	at kotlinx.coroutines.DispatchedTask.run(DispatchedTask.kt:115)
	at java.desktop/java.awt.event.InvocationEvent.dispatch(InvocationEvent.java:313)
	at java.desktop/java.awt.EventQueue.dispatchEventImpl(EventQueue.java:776)
	at java.desktop/java.awt.EventQueue$4.run(EventQueue.java:727)
	at java.desktop/java.awt.EventQueue$4.run(EventQueue.java:721)
	at java.base/java.security.AccessController.doPrivileged(Native Method)
	at java.base/java.security.ProtectionDomain$JavaSecurityAccessImpl.doIntersectionPrivilege(ProtectionDomain.java:85)
	at java.desktop/java.awt.EventQueue.dispatchEvent(EventQueue.java:746)
	at com.intellij.ide.IdeEventQueue.defaultDispatchEvent(IdeEventQueue.java:891)
	at com.intellij.ide.IdeEventQueue._dispatchEvent(IdeEventQueue.java:760)
	at com.intellij.ide.IdeEventQueue.lambda$dispatchEvent$6(IdeEventQueue.java:447)
	at com.intellij.openapi.progress.impl.CoreProgressManager.computePrioritized(CoreProgressManager.java:818)
	at com.intellij.ide.IdeEventQueue.lambda$dispatchEvent$7(IdeEventQueue.java:446)
	at com.intellij.openapi.application.impl.ApplicationImpl.runIntendedWriteActionOnCurrentThread(ApplicationImpl.java:805)
	at com.intellij.ide.IdeEventQueue.dispatchEvent(IdeEventQueue.java:498)
	at java.desktop/java.awt.EventDispatchThread.pumpOneEventForFilters(EventDispatchThread.java:203)
	at java.desktop/java.awt.EventDispatchThread.pumpEventsForFilter(EventDispatchThread.java:124)
	at java.desktop/java.awt.EventDispatchThread.pumpEventsForHierarchy(EventDispatchThread.java:113)
	at java.desktop/java.awt.EventDispatchThread.pumpEvents(EventDispatchThread.java:109)
	at java.desktop/java.awt.EventDispatchThread.pumpEvents(EventDispatchThread.java:101)
	at java.desktop/java.awt.EventDispatchThread.run(EventDispatchThread.java:90)

Gradle mergeSarifReports task breaks build

Running static analysis and the mergeSarifReports task break Gradle (and the CI). I have done some fixes on the improve-ci, but these remain and I am not sure how to fix them:

Some problems were found with the configuration of task ':themes:int-ui:int-ui-core:detektMain' (type 'Detekt').
  - Gradle detected a problem with the following location: '/Users/rock3r/src/jewel/themes/int-ui/int-ui-core/build/generated/theme'.
    
    Reason: Task ':themes:int-ui:int-ui-core:detektMain' uses this output of task ':themes:int-ui:int-ui-core:generateIntUiDarkTheme' without declaring an explicit or implicit dependency. This can lead to incorrect results being produced, depending on what order the tasks are executed.
    
    Possible solutions:
      1. Declare task ':themes:int-ui:int-ui-core:generateIntUiDarkTheme' as an input of ':themes:int-ui:int-ui-core:detektMain'.
      2. Declare an explicit dependency on ':themes:int-ui:int-ui-core:generateIntUiDarkTheme' from ':themes:int-ui:int-ui-core:detektMain' using Task#dependsOn.
      3. Declare an explicit dependency on ':themes:int-ui:int-ui-core:generateIntUiDarkTheme' from ':themes:int-ui:int-ui-core:detektMain' using Task#mustRunAfter.
    
    For more information, please refer to https://docs.gradle.org/8.2/userguide/validation_problems.html#implicit_dependency in the Gradle documentation.
  - Gradle detected a problem with the following location: '/Users/rock3r/src/jewel/themes/int-ui/int-ui-core/build/generated/theme'.
    
    Reason: Task ':themes:int-ui:int-ui-core:detektMain' uses this output of task ':themes:int-ui:int-ui-core:generateIntUiLightTheme' without declaring an explicit or implicit dependency. This can lead to incorrect results being produced, depending on what order the tasks are executed.
    
    Possible solutions:
      1. Declare task ':themes:int-ui:int-ui-core:generateIntUiLightTheme' as an input of ':themes:int-ui:int-ui-core:detektMain'.
      2. Declare an explicit dependency on ':themes:int-ui:int-ui-core:generateIntUiLightTheme' from ':themes:int-ui:int-ui-core:detektMain' using Task#dependsOn.
      3. Declare an explicit dependency on ':themes:int-ui:int-ui-core:generateIntUiLightTheme' from ':themes:int-ui:int-ui-core:detektMain' using Task#mustRunAfter.
    
    For more information, please refer to https://docs.gradle.org/8.2/userguide/validation_problems.html#implicit_dependency in the Gradle documentation.
* Try:
> Run with --stacktrace option to get the stack trace.
> Run with --info or --debug option to get more log output.
> Run with --scan to get full insights.
> Get more help at https://help.gradle.org.
==============================================================================
2: Task failed with an exception.
-----------
* What went wrong:
Some problems were found with the configuration of task ':mergeSarifReports' (type 'MergeSarifTask').
  - Gradle detected a problem with the following location: '/Users/rock3r/src/jewel/build/reports'.
    
    Reason: Task ':mergeSarifReports' uses this output of task ':compose-utils:detektMain' without declaring an explicit or implicit dependency. This can lead to incorrect results being produced, depending on what order the tasks are executed.
    
    Possible solutions:
      1. Declare task ':compose-utils:detektMain' as an input of ':mergeSarifReports'.
      2. Declare an explicit dependency on ':compose-utils:detektMain' from ':mergeSarifReports' using Task#dependsOn.
      3. Declare an explicit dependency on ':compose-utils:detektMain' from ':mergeSarifReports' using Task#mustRunAfter.
    
    For more information, please refer to https://docs.gradle.org/8.2/userguide/validation_problems.html#implicit_dependency in the Gradle documentation.
  - Gradle detected a problem with the following location: '/Users/rock3r/src/jewel/build/reports'.
    
    Reason: Task ':mergeSarifReports' uses this output of task ':compose-utils:detekt' without declaring an explicit or implicit dependency. This can lead to incorrect results being produced, depending on what order the tasks are executed.
    
    Possible solutions:
      1. Declare task ':compose-utils:detekt' as an input of ':mergeSarifReports'.
      2. Declare an explicit dependency on ':compose-utils:detekt' from ':mergeSarifReports' using Task#dependsOn.
      3. Declare an explicit dependency on ':compose-utils:detekt' from ':mergeSarifReports' using Task#mustRunAfter.
    
    For more information, please refer to https://docs.gradle.org/8.2/userguide/validation_problems.html#implicit_dependency in the Gradle documentation.
  - Gradle detected a problem with the following location: '/Users/rock3r/src/jewel/build/reports'.
    
    Reason: Task ':mergeSarifReports' uses this output of task ':core:detektMain' without declaring an explicit or implicit dependency. This can lead to incorrect results being produced, depending on what order the tasks are executed.
    
    Possible solutions:
      1. Declare task ':core:detektMain' as an input of ':mergeSarifReports'.
      2. Declare an explicit dependency on ':core:detektMain' from ':mergeSarifReports' using Task#dependsOn.
      3. Declare an explicit dependency on ':core:detektMain' from ':mergeSarifReports' using Task#mustRunAfter.
    
    For more information, please refer to https://docs.gradle.org/8.2/userguide/validation_problems.html#implicit_dependency in the Gradle documentation.
  - Gradle detected a problem with the following location: '/Users/rock3r/src/jewel/build/reports'.
    
    Reason: Task ':mergeSarifReports' uses this output of task ':core:detekt' without declaring an explicit or implicit dependency. This can lead to incorrect results being produced, depending on what order the tasks are executed.
    
    Possible solutions:
      1. Declare task ':core:detekt' as an input of ':mergeSarifReports'.
      2. Declare an explicit dependency on ':core:detekt' from ':mergeSarifReports' using Task#dependsOn.
      3. Declare an explicit dependency on ':core:detekt' from ':mergeSarifReports' using Task#mustRunAfter.
    
    For more information, please refer to https://docs.gradle.org/8.2/userguide/validation_problems.html#implicit_dependency in the Gradle documentation.
  - Gradle detected a problem with the following location: '/Users/rock3r/src/jewel/build/reports'.
    
    Reason: Task ':mergeSarifReports' uses this output of task ':foundation:detektMain' without declaring an explicit or implicit dependency. This can lead to incorrect results being produced, depending on what order the tasks are executed.
    
    Possible solutions:
      1. Declare task ':foundation:detektMain' as an input of ':mergeSarifReports'.
      2. Declare an explicit dependency on ':foundation:detektMain' from ':mergeSarifReports' using Task#dependsOn.
      3. Declare an explicit dependency on ':foundation:detektMain' from ':mergeSarifReports' using Task#mustRunAfter.
    
    For more information, please refer to https://docs.gradle.org/8.2/userguide/validation_problems.html#implicit_dependency in the Gradle documentation.

Table sample 'Key -1 is missing in the map.'

Trying to run the table sample provided in the project but I get the following error:

Exception in thread "main" java.util.NoSuchElementException: Key -1 is missing in the map.
	at kotlin.collections.MapsKt__MapWithDefaultKt.getOrImplicitDefaultNullable(MapWithDefault.kt:24)
	at kotlin.collections.MapsKt__MapsKt.getValue(Maps.kt:348)
	at org.jetbrains.jewel.components.TableState.getDividerOffset(Table.kt:198)
	at org.jetbrains.jewel.components.TableKt$Row$2.measure-3p2s80s(Table.kt:315)
	at androidx.compose.ui.node.InnerPlaceable.measure-BRTryo0(InnerPlaceable.kt:44)
	at androidx.compose.ui.node.LayoutNode$performMeasure$1.invoke(LayoutNode.kt:1428)
	at androidx.compose.ui.node.LayoutNode$performMeasure$1.invoke(LayoutNode.kt:1427)
	at androidx.compose.runtime.snapshots.Snapshot$Companion.observe(Snapshot.kt:2117)
	at androidx.compose.runtime.snapshots.SnapshotStateObserver.observeReads(SnapshotStateObserver.kt:113)
	at androidx.compose.ui.node.OwnerSnapshotObserver.observeReads$ui(OwnerSnapshotObserver.kt:78)
	at androidx.compose.ui.node.OwnerSnapshotObserver.observeMeasureSnapshotReads$ui(OwnerSnapshotObserver.kt:66)
	at androidx.compose.ui.node.LayoutNode.performMeasure-BRTryo0$ui(LayoutNode.kt:1427)
	at androidx.compose.ui.node.OuterMeasurablePlaceable.remeasure-BRTryo0(OuterMeasurablePlaceable.kt:94)
	at androidx.compose.ui.node.OuterMeasurablePlaceable.measure-BRTryo0(OuterMeasurablePlaceable.kt:75)
	at androidx.compose.ui.node.LayoutNode.measure-BRTryo0(LayoutNode.kt:1366)
	at androidx.compose.foundation.lazy.layout.LazyLayoutMeasureScopeImpl.measure-0kLqBqw(LazyLayoutMeasureScope.kt:120)
	at androidx.compose.foundation.lazy.LazyMeasuredItemProvider.getAndMeasure-ZjPyQlc(LazyMeasuredItemProvider.kt:47)
	at androidx.compose.foundation.lazy.LazyListMeasureKt.measureLazyList-7Xnphek(LazyListMeasure.kt:151)
	at androidx.compose.foundation.lazy.LazyListKt$rememberLazyListMeasurePolicy$1$1.invoke-0kLqBqw(LazyList.kt:304)
	at androidx.compose.foundation.lazy.LazyListKt$rememberLazyListMeasurePolicy$1$1.invoke(LazyList.kt:197)
	at androidx.compose.foundation.lazy.layout.LazyLayoutKt$LazyLayout$2$1.invoke-0kLqBqw(LazyLayout.kt:74)
	at androidx.compose.foundation.lazy.layout.LazyLayoutKt$LazyLayout$2$1.invoke(LazyLayout.kt:70)
	at androidx.compose.ui.layout.LayoutNodeSubcompositionsState$createMeasurePolicy$1.measure-3p2s80s(SubcomposeLayout.kt:590)
	at androidx.compose.ui.node.InnerPlaceable.measure-BRTryo0(InnerPlaceable.kt:44)
	at androidx.compose.ui.graphics.SimpleGraphicsLayerModifier.measure-3p2s80s(GraphicsLayerModifier.kt:405)

Scrollbars should have different appearances on macOS vs Windows & Linux

Scrollbars are rounded and flat, resembling the macOS' native ones on that OS, but they are rectangular and with a border on Windows and Linux. They also behave differently in terms of animations and show/hide behaviour. They also have "thin" variants.

  macOS Win/Linux
Normal com.intellij.ui.components.MacScrollBarUI com.intellij.ui.components.DefaultScrollBarUI
Thin com.intellij.ui.components.ThinMacScrollBarUI com.intellij.ui.components.ThinScrollBarUI

Create configuration plugin

We should get rid of the allprojects block in the root build.gradle.kts and replace it with a plugin so that we can simplify and improve our common module setup, while at the same time improving configuration cacheability.

Refactoring New UI implementation based on core components

Look and feel

  • Palettes Basic colors have been added, it will be changed in future
  • Painters SVGs have been redesigned for Checkbox and Radio button, it does not copy from IJ because there are many temp icons in 22.3
  • Metrics Just copy from Darcula theme, need check and adjust
  • Typography Change the default font to Inter

Core component support

  • Focus style support

Consistency check

  • Check box
  • Radio button
  • Buttons Need focus style support
  • Slider Current implemented by swing component
  • TextField Missing round border style, focus style, heading icon and trailer icon
  • ProgressBar Missing implementation
  • SegmentedButtonColors Missing implementation
  • TextArea Missing implementation
  • Link and DropdownLink Missing implementation
  • ToolTip Missing implementation
  • DropdownMenu Missing implementation
  • ContextMenu Missing implementation
  • ComboBox Missing implementation
  • And more...

Consistency Issue

Project struct

  • Remove old impl
  • Grallery app

JBR-only improvements

Port over older code that has JBR-only improvements, such as custom window decoration.

Rewrite Jewel theme system

The current implementation of the Jewel themes and components is not compatible with an IDE bridge implementation, because the components are not implemented the same way as the Swing ones and take different inputs.

This task is to rewrite the theme and components implementation to align with how the Swing components are drawn, as it is necessary to be able to inherit the styling from them. Another goal is to improve and streamline the developer experience with APIs that are more consistent and easier to use.

Enable Library mode and ensure binary compat

We should enable the Kotlin compiler library mode, so we have to be explicit about what our public API is and isn't. We also need to enable the compiler plugin that generates the API signature, so we know we won't break binary compat

Fix font loading issues

Font loading can fail, because the AwtFontManager in Skiko reads the font name from ttf files that it searches on the filesystem, instead of using system (native) APIs to enumerate fonts.

This causes issues, for example on macOS where the system font is called San Francisco, but it's somewhat hidden. AWT loads it as .AppleSystemUIFont, but the font file (/System/Library/Fonts/SFNS.ttf) lists its font family name as System Font. It's then impossible to match those by name.

The root cause for this is that AWT's Font doesn't carry information on the font file it was created from. But to create a Typeface in Compose/Skiko, we do need to point it to a file. In Skiko's AwtFontManager there is a cache of fonts that are available on the system, but that's populated by manually traversing well-known locations on the filesystem, loading the font files as AWT Fonts, and then storing them in a memory cache based on their family name. This process is blocking the UI and fairly expensive, especially when run in the IDE, and doesn't store this cache anywhere.

What we want to figure out is if Skia already does some font loading by itself and if so, if we can use that instead to enumerate available fonts. We then need to find out if this fixes the aforementioned issue with matching, and if not, figure out an alternative solution.

Tab & TabStrip

  • Tab component
  • Basic scrollable TabStrip component

Note: TabStrip doesn't need to handle content changes. It's limited to the tab strip only.

screenshot of IntUI tab specs

Composables — batch 1

  • Text
  • Button
  • Checkbox
  • ContextMenu
  • Dropdown
  • ProgressBar
  • Link
  • Radio Button
  • Text field
  • Text area

Speed dial in lists like in IJ

In IJ, when you are focused on a list and start typing, the list items get highlighted/filtered based on the text you type. We need the same behavior.

[API review] Improve Jewel public API

As follow-up to #83, we still have several improvements to make to the API, before proceeding to getting a proper API review session. After an initial discussion of things we can improve with members of the Jetpack Compose API council, these are the first few things we want to change:

1. Switch "compound" component overrides to slot APIs

We have some compound components, e.g., CheckboxRow and similar, that have overloads that take a string to provide a "complete" component. We should rather prefer to use slot-style APIs, since that is the usual pattern for Compose and it provides more and easier customization options. The effort for users to, for example, use a Text to fill in the slot is minimal, too.

We should also make sure that we avoid as much as possible using private APIs to implement our components, as this hinders reusability of building blocks from users, forcing them to copy-paste things even when not necessary.

2. Ensure component naming consistency

We should make sure that all our barebones, no-decoration "base" components follow the naming conventions set in the rest of Component, by being prefixed by the Basic word. For example, see TextField vs BasicTextField in Material and Foundation.

We should also rename OutlinedButton to just Button, since those are the buttons people think of by default. Users will not be familiar with names from the specs in Figma.

3. Get rid of styles

We should remove style parameters (which I introduced in 0.2.0, replacing the existing Defaults naming and conventions). Instead, we should either inline the values as default values for component parameters, if there are few of a type, or provide objects containing related defaults if there are several (or they depend for example on light vs dark). We cannot copy the Material approach entirely since we are effectively implementing a single theme that needs to support a minimum of 5 "feels"*.

The base/standalone feels are:

. Int UI Darcula
Light Int UI Light IntelliJ Light
Dark Int UI Dark Darcula

And to those we should add the Swing LaF Bridge "feel"; and eventually the High Visibility "feel", too (in the IDE, it'll come via the LaF Bridge theme, but we will need a separate one for standalone).


*Given that in Compose theme has a very specific meaning, that is different from what we use it for in Jewel, we should remodulate names accordingly. We need some new terminology since we face new requirements that Material does not. I propose:

  • Theme -> Jewel
  • Feel -> any supported combination from 3. — e.g., "Darcula", "Int UI Light", "Swing Bridge"
  • Style -> we should not use this term since it is confusing

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.